ui/titlescreen.razor.scss
// Designed in 1080-reference space (ScreenPanel AutoScreenScale = ConsistentHeight).
@import "Theme.scss";

// Selected-QUIT accent colour. Muted salmon-red (OKLCH ~L 0.82 / C 0.09 / h 27) — enough hue to
// read as "this one exits" but low-chroma so it sits in the palette rather than alarming.
// Lightness is deliberately lifted well above the rolled backgrounds' mid range (an earlier
// L≈0.68 pick matched the teal roll's intensity and blended in): selection pop comes from the
// LIGHTNESS gap, like the selection white, with hue only carrying the exit meaning.
$quit-red: #ffaaa0;

// The daily calendar's "today" mark (see .i-daily .img .today). Same hue as $quit-red
// (OKLCH h 27) but deeper — L 0.60 / C 0.15 — because this red sits ON the icon's white
// date dot: the pale quit salmon vanished against it. Deliberately the most saturated
// red in the menu; at an 8px square that's an accent, not an alarm.
$daily-mark-red: #c9564d;

TitleScreen {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	flex-direction: column;
	align-items: center;
	justify-content: center;

	font-family: $font;
	font-smooth: never;

	.title {
		flex-direction: column;
		align-items: center;
		margin-bottom: 80px;

		// One black drop-shadow over the whole line (the glyphs are white-on-transparent),
		// reproducing the original MenuTitle swatch (WHITE body, BLACK shadow).
		.line {
			flex-direction: row;
			filter-drop-shadow: 8px 8px 0 $black;

			.glyph {
				width: 150px;
				height: 150px;
				background-image: url( "fonts/font_big.png" );
				background-repeat: no-repeat;
				background-tint: $white;
				image-rendering: pixelated;
			}
		}
	}

	// Bottom-aligned "mountain" of square icon buttons — blocks resting on a floor, sizes peaking
	// at the centred PLAY (order comes from TitleStage.DisplayOrder, sides width-mirrored around
	// the hero: DAILY and LEADERBOARDS share the primary tier, OPTIONS and QUIT the tertiary one).
	// Same recipe as the shared .square-btn (Theme.scss): dark body, WHITE border, hard offset
	// shadow, press-in where the shadow's outer edge stays put — but the title row is its own
	// family because of the size tiers and the keyboard-driven selected state.
	.options {
		flex-direction: row;
		align-items: flex-end;
		gap: 16px;

		.option {
			justify-content: center;
			align-items: center;
			pointer-events: all;
			cursor: blank-hand;

			> * {
				pointer-events: none;
			}

			background-color: rgba( $black, 0.6 );
			border: 4px solid $white;
			border-radius: 0;
			// Unselected buttons recede (same trick as the old text rows' 0.55 dim): opacity, not a
			// fixed blend colour, so they fade toward whatever the day's rolled background is.
			opacity: 0.6;
			transition: background-color 0.1s ease, opacity 0.1s ease;

			.img {
				background-size: 100% 100%;
				background-repeat: no-repeat;
				image-rendering: pixelated;
				background-tint: $white;
			}

			// Group gaps between the wings and the central play cluster (adds to the 16px row gap).
			// The extra margin is dead space to pointer events — deliberate padding around QUIT so
			// overshoot clicks miss it. One break per side, both 24px, so the two wing buttons sit
			// the same 40px from their neighbour and PLAY stays centred: the left cluster
			// (options64+16 +24+ workshop96+16+daily128+16 = 360) equals the right
			// (16+lb128+16+replays96+16 +24+ quit64 = 360). Re-derive when any tier size or the
			// order changes.
			&.section-start {
				margin-left: 24px;
			}

			// Selected (keyboard or hover, driven from the stage): the one fully-opaque, fully-dark
			// button in the row — the same "only solid thing on screen" rule as the old selection white.
			&.selected {
				opacity: 1;
				background-color: rgba( $black, 0.9 );
			}

			// Size tiers. Icon display size == icon image resolution on EVERY tier (40/60/80/120),
			// so 1 image px = 1 ref px and the art's pixels read the same size on all buttons.
			// Faces shrank one notch when WORKSHOP joined the row (216/144/108/72 → 192/128/96/64),
			// keeping the icon art at its native resolutions, so the icon inset is now a little
			// tighter than the letterbox buttons' 40-in-72 ratio. Shadow depth scales with the tier
			// (like the old per-tier text shadows). Keep tier sizes in sync with TierClass in
			// TitleScreen.razor.
			&.hero {
				width: 192px;
				height: 192px;
				box-shadow: 10px 10px 0 $square-btn-shadow;

				.img { width: 120px; height: 120px; }
			}

			&.primary {
				width: 128px;
				height: 128px;
				box-shadow: 9px 9px 0 $square-btn-shadow;

				.img { width: 80px; height: 80px; }
			}

			&.secondary {
				width: 96px;
				height: 96px;
				box-shadow: 7px 7px 0 $square-btn-shadow;

				.img { width: 60px; height: 60px; }
			}

			&.tertiary {
				width: 64px;
				height: 64px;
				box-shadow: 5px 5px 0 $square-btn-shadow;

				.img { width: 40px; height: 40px; }
			}

			// Pressed: the face sinks INTO the shadow — the translate + the remaining shadow offset
			// always sum to the tier's resting depth, so the shadow's outer edge stays put and only
			// the face moves (same trick as .square-btn:active). Press depth scales with the tier.
			// These sit AFTER the tier rules: equal specificity, so order breaks the tie.
			&.hero:active {
				transform: translate( 3px, 3px );
				box-shadow: 7px 7px 0 $square-btn-shadow;
			}

			&.primary:active {
				transform: translate( 3px, 3px );
				box-shadow: 6px 6px 0 $square-btn-shadow;
			}

			&.secondary:active {
				transform: translate( 2px, 2px );
				box-shadow: 5px 5px 0 $square-btn-shadow;
			}

			&.tertiary:active {
				transform: translate( 2px, 2px );
				box-shadow: 3px 3px 0 $square-btn-shadow;
			}

			// QUIT selected: the one button that gets a hue — border and icon shift to the muted
			// exit red. Deliberately the ONLY coloured selection in the row.
			&.i-quit.selected {
				border-color: $quit-red;

				.img { background-tint: $quit-red; }
			}

			// "Today" marker while the daily is unattempted (div gated in the razor): one cell of
			// the calendar icon's date grid blinks red, like the calendar marking today's date.
			// An 8px square centred on a 4px date dot — the extra 2px ring makes the mark read
			// at menu distance and hides the subpixel fringe where the div's edges and the scaled
			// icon pixels don't land on the same screen pixel. WHICH dot is per-day (walks the
			// grid, see TitleStage.DailyMarkCell) so left/top are inline in the razor; icon art is
			// 1 image px = 1 ref px, so those are PNG pixel coords (dots are 4px chunks at
			// x 20/32/44/56, y 32/42/52/62). The .img MUST be position:relative: s&box's Yoga
			// anchors absolute panels to the nearest POSITIONED ancestor (web rule, root
			// fallback), not the parent — without it the mark lands in the screen corner.
			// The mark renders at FULL strength even on the resting button — see .marked below,
			// which lifts the panel-opacity dim off this button so the child can escape it.
			&.i-daily .img {
				position: relative;

				.today {
					position: absolute;
					width: 8px;
					height: 8px;
					background-color: $daily-mark-red;
					animation: daily-today-blink 0.6s infinite;
				}
			}

			// While the daily is unattempted (razor-gated .marked), the resting 0.6 dim moves off
			// panel opacity onto each drawn part: bg 0.6→0.36 alpha, border/icon-tint whites at
			// 0.6, shadow 0.45→0.27 (primary-tier 10px depth — DAILY is always primary). The
			// engine multiplies panel opacity into every drawn element rather than compositing a
			// flattened layer, so this renders IDENTICALLY to the neighbours' opacity dim — but
			// panel opacity caps all descendants, and this frees the .today mark to blink at full
			// strength on the resting button. Reverts to the plain opacity dim once attempted.
			&.i-daily.marked {
				opacity: 1;
				background-color: rgba( $black, 0.36 );
				border-color: rgba( $white, 0.6 );
				box-shadow: 10px 10px 0 rgba( 0, 0, 0, 0.27 );

				.img { background-tint: rgba( $white, 0.6 ); }
			}

			// Selected while marked: back to the normal solid treatment (these outrank the plain
			// .selected rules on class count, so every dimmed property is restated).
			&.i-daily.marked.selected {
				background-color: rgba( $black, 0.9 );
				border-color: $white;
				box-shadow: 10px 10px 0 $square-btn-shadow;

				.img { background-tint: $white; }
			}

			// ...and pressed while marked: keep the primary tier's press-sink shadow (the
			// .marked.selected 10px shadow outranks .primary:active's 7px, which would pin the
			// shadow's outer edge wrong while the face translates).
			&.i-daily.marked.selected:active {
				box-shadow: 7px 7px 0 $square-btn-shadow;
			}

			// Icon images: white-on-transparent pixel-art PNGs in Assets/ui/icons, drawn on a
			// 2px-chunk grid like the letterbox icons. EDITOR/OPTIONS reuse the letterbox art.
			&.i-play .img { background-image: url( "ui/icons/icon_title_play.png" ); }
			&.i-daily .img { background-image: url( "ui/icons/icon_title_daily.png" ); }
			&.i-trophy .img { background-image: url( "ui/icons/icon_title_trophy.png" ); }
			&.i-replay .img { background-image: url( "ui/icons/icon_title_replay.png" ); }
			// Generated placeholder (crate + up-arrow) at the secondary tier's native 60px — hand-edit
			// the PNG freely, the pixel grid is 3px cells.
			&.i-workshop .img { background-image: url( "ui/icons/icon_title_workshop.png" ); }
			&.i-edit .img { background-image: url( "ui/icons/icon_edit.png" ); }
			&.i-gear .img { background-image: url( "ui/icons/icon_gear.png" ); }
			&.i-quit .img { background-image: url( "ui/icons/icon_quit.png" ); }
		}
	}

	// Names the selected (icon-only) button: the same WHITE-on-BLACK selection swatch as the old
	// selected text row, centred under the whole row so it doesn't jump around with the selection.
	.option-label {
		margin-top: 40px;
		font-size: 64px;
		// Hug the pixel font's glyphs (it reserves a tall line box with empty space below).
		line-height: 0.8;
		color: $white;
		text-shadow: 4px 4px 0 $black;

		&.quit {
			color: $quit-red;
		}

		// Daily-unattempted shimmer while DAILY is selected: WHITE↔gold, full opacity throughout,
		// so the selection stays solid and the pulse reads as "there's a prize here".
		&.flash {
			animation: daily-label-pulse 0.4s infinite;
		}
	}

	// Editor-only dev shortcut into the level editor. Bottom-left letterbox, square — keep the
	// geometry in sync with the level select screen's .editor-button (72px box, 40px inset).
	// Look comes from the shared .square-btn (Theme.scss).
	.editor-button {
		position: absolute;
		bottom: 40px;
		left: 40px;
	}
}

// "Today" cursor blink on the daily calendar's marked date cell (see .i-daily .img .today).
// Hard on/off like a terminal cursor — a stationary blink reads far calmer than the old
// whole-button pulse it replaces. 60% duty cycle; the 1% ramp is only there to avoid relying
// on steps() support. While OFF the icon's white date dot shows through, so the cell ticks
// white↔red rather than appearing/vanishing.
@keyframes daily-today-blink {
	0%, 60% {
		opacity: 1;
	}
	61%, 100% {
		opacity: 0;
	}
}

// DAILY CHALLENGE unattempted pulse on the label while DAILY is selected. Colour-only shimmer
// between the selection WHITE and the score-victory gold ($score-victory) — full opacity
// throughout, so the selection stays solid and the pulse reads as "there's a prize here".
@keyframes daily-label-pulse {
	0%, 100% {
		color: #f8f5e6;
	}
	50% {
		color: #f0df80;
	}
}