ui/optionspanel.razor.scss
// Shared options block (jump toggle + volume + gamepad). Designed in 1080-reference space (matches
// TitleScreen).
@import "Theme.scss";
OptionsPanel {
flex-direction: column;
align-items: center;
font-family: $font;
font-smooth: never;
.rows {
position: relative;
flex-direction: column;
gap: 16px;
// Left lane the smiling-cube selector slides in. Keep in sync with ROW_PITCH and the
// .selector width.
padding-left: 72px;
// The smiling-cube selector. Absolutely positioned so it slides smoothly between rows;
// `top` and the frame (background-position) are set inline from the controller.
// Row pitch = .row height (56) + gap (16) = 72px (ROW_PITCH in the razor). The sheet is
// 7 frames wide, so background-size:700% makes one frame exactly fill the box.
.selector {
position: absolute;
left: 0;
top: 0;
width: 56px;
height: 56px;
background-image: url( "sprites/menu_selector.png" );
background-repeat: no-repeat;
background-size: 700% 100%;
image-rendering: pixelated;
}
// In-list section divider (GAMEPAD), styled like the top VOLUME heading. Explicit height
// keeps the selector math deterministic — see GAMEPAD_GAP in the razor (margin-top 24 +
// height 56 + one extra .rows gap 16 = 96). The negative margin-left cancels the selector
// lane so the word centres over the whole block like VOLUME rather than over the row lane.
.section {
height: 56px;
margin-top: 24px;
margin-left: -72px;
align-items: center;
justify-content: center;
font-size: 48px;
// Pixel font reserves a tall line box (see .label); hug the glyphs so the word centres
// in the 56px band rather than riding high.
line-height: 0.8;
color: $white;
text-shadow: 4px 4px 0 $black;
}
.row {
flex-direction: row;
align-items: center;
height: 56px;
width: 620px;
pointer-events: all;
cursor: blank-hand;
.label {
font-size: 32px;
// The pixel font reserves a tall line box with empty space below the glyphs; unhugged
// it pushes the text above the row centre (leaving the centred selector cube low) and
// inflates the row past its 56px height, drifting ROW_PITCH off further each row down.
line-height: 0.8;
color: $black;
width: 200px;
flex-shrink: 0;
}
.value {
font-size: 32px;
line-height: 0.8;
color: $black;
width: 64px;
flex-shrink: 0;
text-align: right;
}
// The slider itself is a self-styled PixelSlider component (its own stylesheet owns the
// pixel/cube look — s&box stylesheets don't cascade into child components).
.slider-wrap {
flex-grow: 1;
align-items: stretch;
margin: 0 16px;
}
// Highlight the focused row (cube parity with the title menu).
&.selected {
.label,
.value,
.text {
color: $white;
}
}
}
// The toggle row: label on the left (free to grow past the 200px slider-label lane, since
// "DISABLE ↑ JUMP" is wider), checkbox pushed to the right where the slider value sits. The
// PixelCheckbox is a self-styled child component (owns its own pixel look).
.row.toggle {
.label {
width: auto;
flex-grow: 1;
}
.checkbox-wrap {
flex-shrink: 0;
align-items: center;
justify-content: center;
}
}
.row.restart-delay .label {
font-size: 24px;
line-height: 1;
}
.row.back {
justify-content: flex-start;
// Extra gap above BACK (mirrored by BACK_GAP in the razor so the selector stays aligned).
margin-top: 24px;
.text {
align-self: flex-start;
width: auto;
text-align: center;
font-size: 40px;
line-height: 0.8; // hug the pixel-font glyphs (see .label) so the box padding is even
color: $black;
background-color: #c98a8a; // desaturated red box, matching the score/replay BACK buttons
padding: 6px 36px;
}
// .row.back .text ties specificity with the generic &.selected rule above and wins by
// source order, so re-apply the focused colour explicitly for the BACK button.
&.selected .text {
color: $black;
background-color: #d99a9a;
}
}
}
// The in-game overlay now uses the same bright teal checkerboard field as the main menu, so the
// hosted panel styles identically whether or not it's `.dimmed` (the flag now only switches the
// BACK/CLOSE wording in the razor). The near-black default labels read fine over the teal field.
}