/**
 * arcade.css
 * ============================================================================
 * 8-bit arcade design system for Books on Logan.
 *
 * Source of aesthetic truth: docs/instruction_card_v3.html. The card is the
 * printed counterpart of this site; the two should feel like the same brand.
 *
 * Conventions
 *   - Tokens live in :root. NEVER hardcode a color outside the token block.
 *   - Components are declared in their own labeled section. New widgets get a
 *     new section, not a sprinkle of selectors at the bottom.
 *   - Admin overrides live at the very bottom (`.is-admin` body class).
 *   - Animations are restrained: hover/focus/load only. No looped scanline
 *     shimmer or perpetual XP-bar pulse.
 *   - Mobile-first. Larger viewports get progressive enhancement via min-width
 *     media queries.
 *
 * Privacy note: imports Google Fonts. The site already permits one external
 * dependency (Tailwind CDN, now removed). Self-hosting Press Start 2P /
 * Pixelify Sans / VT323 from public/fonts/ is a clean follow-up if the
 * operator decides to drop the third-party hit.
 * ============================================================================
 */

@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Pixelify+Sans:wght@400..700&family=VT323&display=swap');


/* ============================================================================
 * 1. TOKENS
 * Colors lifted verbatim from instruction_card_v3.html so print + web match.
 * ============================================================================
 */
:root {
	/* Surfaces */
	--bg:             #0d0d2b;
	--bg-card:        #16163a;
	--bg-elevated:    #1d1d4a;
	--border:         #2a2a5a;
	--border-bright:  #3a3a7a;

	/* Text */
	--ink:            #fefef5;
	--ink-mute:       #8888bb;
	--ink-dim:        #7c7caa;

	/* Accents */
	--pink:           #ff3366;
	--cyan:           #00d4ff;
	--gold:           #ffce00;
	--lime:           #7cff5a;
	--magenta:        #b537f2;

	/* Difficulty palette (matches DB-driven weight_class.color_hex values) */
	--light-color:    #5cb85c;
	--medium-color:   #4a90e2;
	--heavy-color:    #d9534f;

	/* State */
	--success:        #7cff5a;
	--warn:           #ffce00;
	--error:          #ff3366;

	/* Type
	 *
	 * --font-marquee = Press Start 2P, the iconic chunky pixel font. Hard to
	 *                   read past a few words, so it's reserved for the brand
	 *                   marquee title and the giant stat numerals only.
	 * --font-hero    = Pixelify Sans 700 (uppercase + tracked at the call
	 *                   site). Used to be Press Start 2P, but the chunky font
	 *                   was hurting legibility on every label and button. This
	 *                   still reads as "arcade UI" thanks to the rounded
	 *                   pixel-style strokes, while staying scannable at 9–14px.
	 * --font-body    = Pixelify Sans (mixed-case body text).
	 * --font-mono    = VT323, the CRT terminal font. For tabular numbers and
	 *                   inline code-ish text inside body copy.
	 */
	--font-marquee:   'Press Start 2P', 'Courier New', monospace;
	--font-hero:      'Pixelify Sans', 'Trebuchet MS', sans-serif;
	--font-body:      'Pixelify Sans', 'Trebuchet MS', sans-serif;
	--font-mono:      'VT323', 'Courier New', monospace;

	/* Geometry */
	--pixel:          2px;
	--pixel-shadow:   3px 3px 0 0 #000;
	--pixel-shadow-lg:5px 5px 0 0 #000;
	--radius-0:       0;

	/* Motion */
	--ease-pixel:     steps(4, end);
	--ease-snap:      cubic-bezier(0.2, 0.8, 0.2, 1);
}


/* ============================================================================
 * 2. RESET / BASE
 * ============================================================================
 */
*, *::before, *::after {
	box-sizing: border-box;
}

html, body {
	margin: 0;
	padding: 0;
}

html {
	background: var(--bg);
}

body {
	font-family: var(--font-body);
	font-size: 17px;
	line-height: 1.4;
	color: var(--ink);
	background:
		radial-gradient(ellipse at 50% -10%, #1a1a4a 0%, var(--bg) 60%),
		var(--bg);
	min-height: 100vh;
	display: flex;
	flex-direction: column;

	/* Subtle CRT scanline texture, baked in (no animation = no battery drain) */
	background-image:
		repeating-linear-gradient(
			0deg,
			transparent 0,
			transparent 3px,
			rgba(255, 255, 255, 0.018) 3px,
			rgba(255, 255, 255, 0.018) 4px
		),
		radial-gradient(ellipse at 50% -10%, #1a1a4a 0%, var(--bg) 60%);
	background-attachment: fixed;
	-webkit-font-smoothing: antialiased;
}

a {
	color: var(--cyan);
	text-decoration: none;
	border-bottom: 1px dashed currentColor;
	transition: color 0.12s var(--ease-snap), border-color 0.12s var(--ease-snap);
}

a:hover, a:focus-visible {
	color: var(--gold);
	border-bottom-style: solid;
}

a:focus-visible {
	outline: 2px solid var(--cyan);
	outline-offset: 2px;
}

::selection {
	background: var(--pink);
	color: var(--bg);
}


/* ============================================================================
 * 3. LAYOUT SHELL
 * arcade-shell wraps the whole page; arcade-main is the content slot.
 * ============================================================================
 */
.arcade-shell {
	flex: 1 0 auto;
	display: flex;
	flex-direction: column;
	width: 100%;
	max-width: 920px;
	margin: 0 auto;
	padding: 16px;
	gap: 14px;
}

.arcade-header {
	border-bottom: var(--pixel) dashed var(--border-bright);
	padding-bottom: 10px;
	text-align: center;
}

.arcade-main {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 14px;
}

.arcade-footer {
	margin-top: 10px;
	padding-top: 10px;
	border-top: var(--pixel) dashed var(--border-bright);
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.1em;
	color: var(--ink-mute);
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 12px;
	flex-wrap: wrap;
}

.arcade-footer a {
	color: var(--ink-mute);
	border-bottom-color: transparent;
}

.arcade-footer a:hover {
	color: var(--gold);
}


/* ============================================================================
 * 4. MARQUEE / BRAND HEADER
 * ============================================================================
 */
.marquee {
	padding: 4px 0 6px;
}

.marquee__pre {
	font-family: var(--font-hero);
	font-size: 11px;
	color: var(--cyan);
	letter-spacing: 0.2em;
	margin: 0 0 6px;
}

.marquee__pre .deco {
	color: var(--ink-mute);
	margin: 0 6px;
}

.marquee__title {
	font-family: var(--font-marquee);
	font-size: clamp(18px, 6vw, 32px);
	margin: 0;
	line-height: 1.05;
	letter-spacing: 0.02em;
	color: var(--gold);
	text-shadow:
		3px 3px 0 var(--pink),
		6px 6px 0 var(--magenta);
	text-decoration: none;
	border: 0;
	display: inline-block;
}

a.marquee__title:hover, a.marquee__title:focus-visible {
	color: var(--gold);
	transform: translate(-1px, -1px);
}

.marquee__sub {
	font-family: var(--font-hero);
	font-size: 10px;
	color: var(--lime);
	letter-spacing: 0.15em;
	margin-top: 8px;
}


/* ============================================================================
 * 5. CARDS / PIXEL BOX
 * ============================================================================
 */
.pixel-box {
	border: var(--pixel) solid var(--border-bright);
	background: var(--bg-card);
	box-shadow:
		var(--pixel-shadow),
		inset 1px 1px 0 0 rgba(255, 255, 255, 0.04);
	padding: 14px;
}

.pixel-box--lg {
	padding: 20px;
}

.pixel-box--cyan   { border-color: var(--cyan); }
.pixel-box--pink   { border-color: var(--pink); }
.pixel-box--gold   { border-color: var(--gold); }
.pixel-box--lime   { border-color: var(--lime); }
.pixel-box--light  { border-color: var(--light-color); }
.pixel-box--medium { border-color: var(--medium-color); }
.pixel-box--heavy  { border-color: var(--heavy-color); }


/* ============================================================================
 * 6. SECTION HEADERS
 * Headline with dashed pixel wings on either side.
 * ============================================================================
 */
.section-header {
	font-family: var(--font-hero);
	font-size: 11px;
	color: var(--cyan);
	letter-spacing: 0.12em;
	margin: 0 0 10px;
	display: flex;
	align-items: center;
	gap: 10px;
	text-transform: uppercase;
}

.section-header::before,
.section-header::after {
	content: '';
	display: inline-block;
	flex: 1;
	height: var(--pixel);
	background:
		repeating-linear-gradient(
			90deg,
			var(--border-bright) 0,
			var(--border-bright) 4px,
			transparent 4px,
			transparent 8px
		);
}

.section-header--solo::before,
.section-header--solo::after {
	display: none;
}


/* ============================================================================
 * 7. TYPOGRAPHY HELPERS
 * ============================================================================
 */
.t-hero    { font-family: var(--font-hero); }
.t-body    { font-family: var(--font-body); }
.t-mono    { font-family: var(--font-mono); }

.t-cyan    { color: var(--cyan); }
.t-pink    { color: var(--pink); }
.t-gold    { color: var(--gold); }
.t-lime    { color: var(--lime); }
.t-magenta { color: var(--magenta); }
.t-mute    { color: var(--ink-mute); }
.t-dim     { color: var(--ink-dim); }
.t-error   { color: var(--error); }

.t-center  { text-align: center; }
.t-right   { text-align: right; }

.t-caps    { text-transform: uppercase; letter-spacing: 0.1em; }

.t-stat {
	font-family: var(--font-marquee);
	font-size: clamp(28px, 8vw, 48px);
	color: var(--gold);
	line-height: 1;
	text-shadow: 2px 2px 0 var(--pink);
	font-variant-numeric: tabular-nums;
}

.t-stat--pink { color: var(--pink); text-shadow: 2px 2px 0 var(--magenta); }
.t-stat--cyan { color: var(--cyan); text-shadow: 2px 2px 0 var(--magenta); }

.tab-nums { font-variant-numeric: tabular-nums; }


/* ============================================================================
 * 7b. EYEBROW + HERO-TITLE PATTERNS
 *
 * Two patterns that used to be inlined dozens of times across the EJS
 * templates:
 *
 *   .eyebrow      The small uppercase tracker label that sits above hero
 *                 H1s ("▶ HIGH SCORES ◀") and as section meta labels
 *                 ("TOTAL XP", "NEXT TIER"). Color comes from a sibling
 *                 utility (.t-cyan / .t-gold / .t-pink / .t-lime /
 *                 .t-magenta / .t-mute).
 *   .hero-title   The brand triple-cast pink/magenta drop-shadow used on
 *                 every page H1 and the giant stat numerals. Font-size
 *                 stays inline because clamp() ranges vary widely
 *                 across pages — you don't want a single class trying
 *                 to be every size.
 *
 * Variants:
 *   .eyebrow--lg            12px / 0.2em / margin 0 0 12px (used on the
 *                           bigger error-page preheaders)
 *   .hero-title--marquee    Swap font-family to Press Start 2P (the
 *                           chunky arcade-cabinet pixel font)
 *   .hero-title--shadow-lg  Bigger shadow scale for the leaderboard
 *                           mega-number (4px / 8px instead of 3px / 6px)
 * ============================================================================
 */

.eyebrow {
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 0.15em;
	text-transform: uppercase;
	margin: 0 0 6px;
}

.eyebrow--lg {
	font-size: 12px;
	letter-spacing: 0.2em;
	margin: 0 0 12px;
}

.hero-title {
	font-family: var(--font-hero);
	font-weight: 700;
	margin: 0;
	line-height: 1.1;
	text-shadow:
		3px 3px 0 var(--pink),
		6px 6px 0 var(--magenta);
}

.hero-title--marquee {
	font-family: var(--font-marquee);
}

.hero-title--shadow-lg {
	text-shadow:
		4px 4px 0 var(--pink),
		8px 8px 0 var(--magenta);
}


/* ============================================================================
 * 8. BUTTONS
 * ============================================================================
 */
.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink);
	background: var(--bg-elevated);
	border: var(--pixel) solid var(--ink);
	padding: 12px 18px;
	cursor: pointer;
	box-shadow: var(--pixel-shadow);
	text-decoration: none;
	transition:
		transform 0.08s var(--ease-snap),
		box-shadow 0.08s var(--ease-snap),
		background 0.12s var(--ease-snap);
	user-select: none;
	min-height: 44px;
}

.btn:hover, .btn:focus-visible {
	transform: translate(-1px, -1px);
	box-shadow: 4px 4px 0 0 #000;
	outline: none;
}

.btn:active {
	transform: translate(2px, 2px);
	box-shadow: 1px 1px 0 0 #000;
}

.btn:focus-visible {
	outline: var(--pixel) solid var(--cyan);
	outline-offset: 2px;
}

.btn--primary { background: var(--gold);    color: var(--bg); border-color: var(--bg); }
.btn--cyan    { background: var(--cyan);    color: var(--bg); border-color: var(--bg); }
.btn--pink    { background: var(--pink);    color: var(--ink); border-color: var(--bg); }
.btn--magenta { background: var(--magenta); color: var(--ink); border-color: var(--bg); }
.btn--lime    { background: var(--lime);    color: var(--bg); border-color: var(--bg); }
.btn--danger  { background: #b22228;        color: var(--ink); border-color: #000; }

.btn--block   { width: 100%; }

.btn--sm {
	font-size: 11px;
	padding: 6px 10px;
	min-height: 32px;
	letter-spacing: 0.05em;
}

.btn--ghost {
	background: transparent;
	border-color: var(--border-bright);
	color: var(--ink-mute);
}

.btn--ghost:hover {
	color: var(--ink);
	border-color: var(--cyan);
}


/* ============================================================================
 * 9. FORMS
 * Input, label, help, error. Built to feel like a CRT terminal field.
 * ============================================================================
 */
.field {
	display: flex;
	flex-direction: column;
	gap: 6px;
	margin-bottom: 14px;
}

.field__label {
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.1em;
	color: var(--cyan);
	text-transform: uppercase;
}

.field__label .req {
	color: var(--pink);
	margin-left: 2px;
}

.input,
.textarea,
.select {
	font-family: var(--font-mono);
	font-size: 18px;
	color: var(--ink);
	background: var(--bg);
	border: var(--pixel) solid var(--border-bright);
	padding: 10px 12px;
	width: 100%;
	box-shadow: inset 2px 2px 0 0 rgba(0, 0, 0, 0.35);
	transition: border-color 0.12s var(--ease-snap), box-shadow 0.12s var(--ease-snap);
	-webkit-appearance: none;
	appearance: none;
}

.input:focus,
.textarea:focus,
.select:focus {
	outline: none;
	border-color: var(--cyan);
	box-shadow:
		inset 2px 2px 0 0 rgba(0, 0, 0, 0.35),
		0 0 0 2px rgba(0, 212, 255, 0.25);
}

.input.is-invalid,
.textarea.is-invalid {
	border-color: var(--pink);
}

.textarea {
	resize: vertical;
	min-height: 88px;
	font-size: 16px;
	line-height: 1.4;
}

.field__help {
	font-family: var(--font-body);
	font-size: 13px;
	color: var(--ink-mute);
	line-height: 1.3;
}

.field__error {
	font-family: var(--font-mono);
	font-size: 14px;
	color: var(--pink);
}

.checkbox-row {
	display: flex;
	align-items: flex-start;
	gap: 10px;
	cursor: pointer;
	font-family: var(--font-body);
	font-size: 14px;
	color: var(--ink);
	line-height: 1.4;
}

.checkbox-row input[type="checkbox"] {
	width: 18px;
	height: 18px;
	margin: 2px 0 0;
	flex-shrink: 0;
	accent-color: var(--lime);
	cursor: pointer;
}

.honeypot {
	position: absolute;
	left: -9999px;
	opacity: 0;
	pointer-events: none;
}

/* Form-level error banner */
.form-alert {
	border: var(--pixel) solid var(--pink);
	background: rgba(255, 51, 102, 0.08);
	padding: 10px 12px;
	color: var(--ink);
	font-family: var(--font-mono);
	font-size: 16px;
	margin-bottom: 14px;
}

.form-alert--ok {
	border-color: var(--lime);
	background: rgba(124, 255, 90, 0.08);
}

.form-alert--warn {
	border-color: var(--gold);
	background: rgba(255, 206, 0, 0.08);
}


/* ============================================================================
 * 10. XP BAR
 * Segmented pixel fill. Shimmer animation triggers on `.xp-bar--shimmer`,
 * not perpetually.
 * ============================================================================
 */
.xp-bar {
	width: 100%;
	height: 14px;
	background: #000;
	border: var(--pixel) solid var(--border-bright);
	overflow: hidden;
	position: relative;
}

.xp-bar__fill {
	height: 100%;
	background:
		repeating-linear-gradient(
			90deg,
			var(--lime) 0,
			var(--lime) 8px,
			#5acf3f 8px,
			#5acf3f 10px
		);
	transition: width 0.5s var(--ease-snap);
}

.xp-bar--shimmer .xp-bar__fill::after {
	content: '';
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: linear-gradient(
		90deg,
		transparent 0,
		rgba(255, 255, 255, 0.4) 50%,
		transparent 100%
	);
	transform: translateX(-100%);
	animation: xp-shimmer 1.4s var(--ease-snap) 1;
}

@keyframes xp-shimmer {
	to { transform: translateX(100%); }
}


/* ============================================================================
 * 11. TIER / WEIGHT BADGES
 * ============================================================================
 */
.badge {
	display: inline-flex;
	align-items: center;
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	padding: 4px 8px;
	background: var(--bg-elevated);
	border: var(--pixel) solid currentColor;
	color: var(--cyan);
	box-shadow: 2px 2px 0 0 #000;
}

.badge--tier  { color: var(--gold); }
.badge--lime  { color: var(--lime); }
.badge--pink  { color: var(--pink); }
.badge--mute  { color: var(--ink-mute); border-color: var(--ink-dim); }

.badge--solid {
	background: currentColor;
}

.badge--solid > span {
	color: var(--bg);
}


/* ============================================================================
 * 12. STAT BLOCK
 * Hero number with a caption underneath.
 * ============================================================================
 */
.stat-block {
	text-align: center;
	padding: 14px 8px;
}

.stat-block__caption {
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.15em;
	color: var(--ink-mute);
	text-transform: uppercase;
	margin-bottom: 6px;
}

.stat-block__value {
	font-family: var(--font-marquee);
	font-size: clamp(36px, 10vw, 64px);
	color: var(--gold);
	line-height: 1;
	text-shadow: 3px 3px 0 var(--pink), 6px 6px 0 var(--magenta);
	font-variant-numeric: tabular-nums;
	display: inline-block;
}

.stat-block__sub {
	font-family: var(--font-body);
	font-size: 14px;
	color: var(--ink-mute);
	margin-top: 8px;
}


/* ============================================================================
 * 13. LISTS / GRID PRIMITIVES
 * ============================================================================
 */
.grid-2 {
	display: grid;
	grid-template-columns: 1fr;
	gap: 12px;
}

.grid-3 {
	display: grid;
	grid-template-columns: 1fr;
	gap: 10px;
}

@media (min-width: 640px) {
	.grid-2 { grid-template-columns: 1fr 1fr; }
	.grid-3 { grid-template-columns: 1fr 1fr 1fr; }
}

.stack-sm > * + * { margin-top: 8px; }
.stack    > * + * { margin-top: 14px; }
.stack-lg > * + * { margin-top: 20px; }


/* ============================================================================
 * 14. ANIMATIONS / EFFECTS
 * Restrained: blink only on hero CTAs, glitch only on hover for hero titles.
 * ============================================================================
 */
.blink {
	animation: blink 1.1s steps(2, end) infinite;
}

@keyframes blink {
	50% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
	.blink { animation: none; }
	.xp-bar--shimmer .xp-bar__fill::after { display: none; }
}

.glitch-hover {
	position: relative;
	transition: transform 0.06s var(--ease-pixel);
}

.glitch-hover:hover {
	transform: translate(-1px, 1px);
}


/* ============================================================================
 * 15. MUTE TOGGLE
 * Fixed-position pixel button. Top-right on desktop, bottom-right on mobile
 * (so it doesn't fight the marquee on small screens).
 * ============================================================================
 */
.arcade-mute {
	position: fixed;
	right: 12px;
	top: 12px;
	z-index: 50;
	font-family: var(--font-hero);
	font-size: 11px;
	letter-spacing: 0.1em;
	color: var(--ink);
	background: var(--bg-elevated);
	border: var(--pixel) solid var(--cyan);
	box-shadow: 2px 2px 0 0 #000;
	padding: 6px 10px;
	cursor: pointer;
	user-select: none;
	min-height: 32px;
}

.arcade-mute:hover {
	color: var(--gold);
}

.arcade-mute[data-muted="1"] {
	border-color: var(--ink-dim);
	color: var(--ink-mute);
}

@media (max-width: 640px) {
	.arcade-mute {
		top: auto;
		bottom: 12px;
	}
}


/* ============================================================================
 * 16. UTILITY
 * Sparingly: just spacing and visibility shortcuts.
 * ============================================================================
 */
.sr-only {
	position: absolute;
	width: 1px; height: 1px;
	padding: 0; margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.divider {
	border: 0;
	height: var(--pixel);
	background:
		repeating-linear-gradient(
			90deg,
			var(--border-bright) 0,
			var(--border-bright) 6px,
			transparent 6px,
			transparent 12px
		);
	margin: 12px 0;
}

.spacer { flex: 1; }


/* ============================================================================
 * 17. ADMIN OVERRIDE PALETTE
 * Same arcade base, but cyan accents → magenta so the operator always knows
 * they're inside the gated surface. Body class set by admin layout.
 * ============================================================================
 */
body.is-admin {
	--cyan: #b537f2;
	background:
		radial-gradient(ellipse at 50% -10%, #2a0a3a 0%, #0d0d2b 60%),
		var(--bg);
}

body.is-admin .marquee__title {
	color: var(--pink);
	text-shadow: 3px 3px 0 var(--magenta), 6px 6px 0 #000;
}

body.is-admin .arcade-mute {
	border-color: var(--pink);
}

body.is-admin .section-header {
	color: var(--magenta);
}


/* ============================================================================
 * 18. RESPONSIVE BREAKPOINTS
 * ============================================================================
 */
@media (min-width: 720px) {
	.arcade-shell {
		padding: 22px;
		gap: 18px;
	}

	.pixel-box {
		padding: 18px;
	}
}

@media (min-width: 1024px) {
	.arcade-shell {
		max-width: 1024px;
	}
}


/* ============================================================================
 * 19. READABILITY OVERRIDES
 * --font-hero is now Pixelify Sans (variable 400..700). We default the
 * font-family at the call sites but didn't set font-weight there — bulk
 * apply bold so every label/header keeps the chunky arcade UI feel rather
 * than rendering at the lighter regular weight.
 *
 * Marquee titles and giant stat numerals use --font-marquee (Press Start 2P)
 * which is a single-weight font, so the rule below has no effect on them.
 * ============================================================================
 */
.arcade-footer,
.marquee__pre,
.marquee__sub,
.section-header,
.t-hero,
.btn,
.field__label,
.stat-block__caption,
.badge,
.arcade-mute {
	font-weight: 700;
}


/* ============================================================================
 * 20. HAMBURGER MENU + DRAWER
 * Top-LEFT slide-in nav drawer. Mobile-first: full-height panel covering
 * up to 320px from the left edge, semi-transparent backdrop over the rest.
 * Mute button stays top-RIGHT, so the two controls don't fight.
 *
 * Markup contract (rendered by layouts/main.ejs):
 *   <button class="arcade-menu-toggle" aria-controls="arcade-drawer" aria-expanded="false">
 *   <div    class="arcade-drawer-backdrop" hidden>
 *   <aside  class="arcade-drawer" id="arcade-drawer" hidden>
 *     <div class="arcade-drawer__head">
 *       <span class="arcade-drawer__title">…</span>
 *       <button class="arcade-drawer__close">…</button>
 *     <nav class="arcade-drawer__nav"><a>…</a> …
 *     <div class="arcade-drawer__foot">…
 *
 * Behavior is driven by arcade.js (the Drawer module). When open,
 * `body.drawer-open` is set to lock background scroll.
 * ============================================================================
 */
.arcade-menu-toggle {
	position: fixed;
	left: 12px;
	top: 12px;
	z-index: 60;
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 0.12em;
	color: var(--ink);
	background: var(--bg-elevated);
	border: var(--pixel) solid var(--cyan);
	box-shadow: 2px 2px 0 0 #000;
	padding: 8px 10px;
	cursor: pointer;
	user-select: none;
	min-height: 36px;
	min-width: 36px;
	text-transform: uppercase;
}

.arcade-menu-toggle:hover {
	color: var(--gold);
}

.arcade-menu-toggle:focus-visible {
	outline: var(--pixel) solid var(--gold);
	outline-offset: 2px;
}

/* Three-bar pixel hamburger icon. Animates to an X when the drawer is open,
 * via the aria-expanded="true" attribute selector. */
.arcade-menu-toggle__bars {
	display: inline-block;
	width: 14px;
	height: 12px;
	position: relative;
}

.arcade-menu-toggle__bars span {
	position: absolute;
	left: 0;
	width: 14px;
	height: 2px;
	background: currentColor;
	transition: transform 0.18s var(--ease-snap), opacity 0.12s var(--ease-snap), top 0.18s var(--ease-snap);
}

.arcade-menu-toggle__bars span:nth-child(1) { top: 0; }
.arcade-menu-toggle__bars span:nth-child(2) { top: 5px; }
.arcade-menu-toggle__bars span:nth-child(3) { top: 10px; }

.arcade-menu-toggle[aria-expanded="true"] .arcade-menu-toggle__bars span:nth-child(1) {
	top: 5px;
	transform: rotate(45deg);
}
.arcade-menu-toggle[aria-expanded="true"] .arcade-menu-toggle__bars span:nth-child(2) {
	opacity: 0;
}
.arcade-menu-toggle[aria-expanded="true"] .arcade-menu-toggle__bars span:nth-child(3) {
	top: 5px;
	transform: rotate(-45deg);
}

.arcade-menu-toggle__label {
	display: inline-block;
}

@media (max-width: 380px) {
	/* On the smallest phones, hide the text label so the icon button stays
	   compact. The aria-label attribute carries the accessible name. */
	.arcade-menu-toggle__label {
		display: none;
	}
}

/* Backdrop: covers everything behind the drawer. Click to close. */
.arcade-drawer-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.65);
	z-index: 70;
	opacity: 0;
	transition: opacity 0.18s var(--ease-snap);
}

.arcade-drawer-backdrop.is-open {
	opacity: 1;
}

/* Drawer: slides in from the left. */
.arcade-drawer {
	position: fixed;
	top: 0;
	left: 0;
	bottom: 0;
	width: min(82vw, 320px);
	z-index: 80;
	background: var(--bg-card);
	border-right: var(--pixel) solid var(--cyan);
	box-shadow: 4px 0 0 0 #000, 6px 0 24px rgba(0, 0, 0, 0.5);
	transform: translateX(-100%);
	transition: transform 0.22s var(--ease-snap);
	display: flex;
	flex-direction: column;

	/* CRT scanlines mirror the body's bg pattern, scaled to the drawer. */
	background-image:
		repeating-linear-gradient(
			0deg,
			transparent 0,
			transparent 3px,
			rgba(255, 255, 255, 0.025) 3px,
			rgba(255, 255, 255, 0.025) 4px
		);
}

.arcade-drawer.is-open {
	transform: translateX(0);
}

.arcade-drawer__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	padding: 14px 14px 10px;
	border-bottom: var(--pixel) dashed var(--border-bright);
}

.arcade-drawer__title {
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 0.12em;
	color: var(--cyan);
	text-transform: uppercase;
}

.arcade-drawer__close {
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 12px;
	color: var(--ink);
	background: transparent;
	border: var(--pixel) solid var(--border-bright);
	padding: 4px 8px;
	cursor: pointer;
	min-height: 32px;
	min-width: 32px;
	box-shadow: 2px 2px 0 0 #000;
}

.arcade-drawer__close:hover {
	color: var(--pink);
	border-color: var(--pink);
}

.arcade-drawer__close:focus-visible {
	outline: var(--pixel) solid var(--cyan);
	outline-offset: 2px;
}

.arcade-drawer__nav {
	display: flex;
	flex-direction: column;
	padding: 8px 0;
	flex: 1;
	overflow-y: auto;
}

.arcade-drawer__nav a {
	display: block;
	padding: 14px 16px;
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 12px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--ink);
	text-decoration: none;
	border: 0;
	border-left: 4px solid transparent;
	transition: background 0.12s var(--ease-snap), color 0.12s var(--ease-snap), border-left-color 0.12s var(--ease-snap);
	min-height: 44px;
}

.arcade-drawer__nav a:hover,
.arcade-drawer__nav a:focus-visible {
	background: var(--bg-elevated);
	color: var(--gold);
	border-left-color: var(--cyan);
	outline: none;
}

.arcade-drawer__nav a[aria-current="page"] {
	color: var(--gold);
	border-left-color: var(--gold);
	background: var(--bg-elevated);
}

.arcade-drawer__foot {
	border-top: var(--pixel) dashed var(--border-bright);
	padding: 12px 14px;
	font-family: var(--font-mono);
	font-size: 14px;
	color: var(--ink-mute);
	line-height: 1.4;
}

.arcade-drawer__foot a {
	color: var(--cyan);
	border-bottom: 1px dashed currentColor;
}

/* Lock background scroll while drawer is open. */
body.drawer-open {
	overflow: hidden;
}

/* Hidden state via the [hidden] attribute is fine for the initial DOM,
 * but the arcade.js module manages display + transitions explicitly. */
.arcade-drawer[hidden],
.arcade-drawer-backdrop[hidden] {
	display: none;
}

@media (prefers-reduced-motion: reduce) {
	.arcade-drawer,
	.arcade-drawer-backdrop {
		transition: none;
	}
	.arcade-menu-toggle__bars span {
		transition: none;
	}
}


/* ============================================================================
 * 21. GAME-WINDOW MODAL
 * Once-per-session announcement explaining when the seasonal program is
 * open. Three states (data-state attr): 'before' (cyan/gold), 'during'
 * (lime), 'after' (pink).
 *
 * Markup contract (rendered conditionally in layouts/main.ejs):
 *   <div class="game-modal-backdrop" hidden></div>
 *   <div class="game-modal" data-state="..." hidden>
 *     <div class="game-modal__inner pixel-box pixel-box--lg">
 *       <p class="game-modal__pre">…</p>
 *       <h2 class="game-modal__title">…</h2>
 *       <p class="game-modal__date">…</p>
 *       <p class="game-modal__copy">…</p>
 *       <button class="game-modal__close">…</button>
 *
 * Behavior is driven by arcade.js (GameModal module). When open, the modal
 * gets `is-open` class (transition trigger). Backdrop click / Escape /
 * close button all dismiss + persist `arcade.gameModal.shown.v1` so the
 * modal stays out of the way for the rest of the session.
 * ============================================================================
 */
.game-modal-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(0, 0, 0, 0.78);
	z-index: 90;
	opacity: 0;
	transition: opacity 0.18s var(--ease-snap);
	backdrop-filter: blur(2px);
	-webkit-backdrop-filter: blur(2px);
}

.game-modal-backdrop.is-open {
	opacity: 1;
}

.game-modal {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	z-index: 100;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 16px;
	opacity: 0;
	transform: scale(0.96);
	transition: opacity 0.22s var(--ease-snap), transform 0.22s var(--ease-snap);
	pointer-events: none;
}

.game-modal.is-open {
	opacity: 1;
	transform: scale(1);
	pointer-events: auto;
}

.game-modal__inner {
	max-width: 28em;
	width: 100%;
	text-align: center;
	margin: 0;
}

/* State-specific accent color on the inner pixel-box border. */
.game-modal[data-state="before"] .game-modal__inner { border-color: var(--cyan); }
.game-modal[data-state="during"] .game-modal__inner { border-color: var(--lime); }
.game-modal[data-state="after"]  .game-modal__inner { border-color: var(--pink); }

.game-modal__pre {
	font-family: var(--font-hero);
	font-weight: 700;
	font-size: 11px;
	letter-spacing: 0.2em;
	text-transform: uppercase;
	margin: 0 0 8px;
}

.game-modal[data-state="before"] .game-modal__pre { color: var(--cyan); }
.game-modal[data-state="during"] .game-modal__pre { color: var(--lime); }
.game-modal[data-state="after"]  .game-modal__pre { color: var(--pink); }

.game-modal__title {
	font-family: var(--font-marquee);
	font-size: clamp(22px, 6vw, 36px);
	line-height: 1.05;
	margin: 0 0 6px;
	color: var(--gold);
	text-shadow: 3px 3px 0 var(--pink), 6px 6px 0 var(--magenta);
}

.game-modal[data-state="after"] .game-modal__title {
	color: var(--ink);
	text-shadow: 3px 3px 0 var(--pink), 6px 6px 0 #000;
}

.game-modal__date {
	font-family: var(--font-mono);
	font-size: 18px;
	color: var(--ink);
	margin: 0 0 12px;
}

.game-modal__copy {
	font-family: var(--font-body);
	font-size: 15px;
	line-height: 1.4;
	color: var(--ink-mute);
	margin: 0 0 16px;
}

.game-modal__close {
	margin-top: 4px;
}

/* Hidden state — when arcade.js hasn't shown it yet, or after dismiss. */
.game-modal[hidden],
.game-modal-backdrop[hidden] {
	display: none;
}

@media (prefers-reduced-motion: reduce) {
	.game-modal,
	.game-modal-backdrop {
		transition: none;
	}
	.game-modal {
		transform: none;
	}
}


/* ============================================================================
 * 22. UNCOUNTED BOOK ROW MARKER
 * Books logged outside the seasonal game window are still rendered in the
 * reader's quest log; we strikethrough the XP value and append an "OFF-SEASON"
 * badge so it's visually obvious the report didn't score.
 *
 * Used by views/public/reader.ejs and views/admin/reader-detail.ejs via the
 * `is-uncounted` class on the row element.
 * ============================================================================
 */
.is-uncounted {
	opacity: 0.72;
}

.is-uncounted .book-xp {
	text-decoration: line-through;
	text-decoration-thickness: 2px;
	color: var(--ink-mute) !important;
}

.badge--off-season {
	color: var(--ink-mute);
	border-color: var(--ink-mute);
	background: var(--bg);
	letter-spacing: 0.08em;
}


/* ============================================================================
 * 23. LEADERBOARD ENTRANCE + RELATIVE-XP BAR + TOP-SPOT THROB
 *
 * Per-row entrance: each .lb-row flies in from the right, staggered by
 * its index (--i set inline by the template). The big total-XP bar
 * (.lb-bar) animates its width from 0 to var(--bar-pct, 0%) just after
 * the row settles. The top spot (.lb-row.is-top) gets a slow gold halo
 * throb so the eye is drawn to it without screaming.
 *
 * All animations honor prefers-reduced-motion: rows + bars snap to their
 * final state, the throb stops. The arcade.js CountUp module similarly
 * skips number tweening when reduced motion is requested, leaving the
 * server-rendered final values in place.
 * ============================================================================
 */

@keyframes row-fly-in {
	from {
		transform: translateX(28px);
		opacity: 0;
	}
	to {
		transform: translateX(0);
		opacity: 1;
	}
}

.lb-row {
	animation: row-fly-in 0.45s var(--ease-snap) backwards;
	animation-delay: calc(var(--i, 0) * 70ms);
}

/* Total-XP bar — sized relative to the leader. Bigger and more
 * prominent than the within-tier progress bar (.xp-bar--mini) below it. */
.lb-bar {
	width: 100%;
	height: 18px;
	background: #000;
	border: var(--pixel) solid var(--border-bright);
	overflow: hidden;
	position: relative;
	margin: 4px 0 6px;
}

.lb-bar__fill {
	height: 100%;
	width: var(--bar-pct, 0%);
	background:
		repeating-linear-gradient(
			90deg,
			var(--lime) 0,
			var(--lime) 8px,
			#5acf3f 8px,
			#5acf3f 10px
		);
	animation: lb-fill 0.85s var(--ease-snap) backwards;
	animation-delay: calc(var(--i, 0) * 70ms + 200ms);
}

@keyframes lb-fill {
	from { width: 0%; }
	to   { width: var(--bar-pct, 0%); }
}

/* Top-spot styling. Border + halo first applied as static state, then
 * a gentle box-shadow throb cycles forever once the entrance settles. */
.lb-row.is-top {
	border-color: var(--gold);
}

.lb-row.is-top .lb-bar {
	border-color: var(--gold);
	animation: lb-throb 2.6s ease-in-out infinite;
	/* Wait until row + bar entrance complete before starting the throb,
	 * so the eye doesn't get yanked mid-animation. */
	animation-delay: 1.6s;
}

@keyframes lb-throb {
	0%, 100% {
		box-shadow:
			0 0 0 2px rgba(255, 206, 0, 0.35),
			0 0 14px rgba(255, 206, 0, 0.35);
	}
	50% {
		box-shadow:
			0 0 0 3px rgba(255, 206, 0, 0.65),
			0 0 28px rgba(255, 206, 0, 0.7);
	}
}

/* Slimmer companion to .xp-bar — used as the within-tier progress bar
 * underneath the big total-XP bar on the leaderboard. */
.xp-bar--mini {
	height: 8px;
}

@media (prefers-reduced-motion: reduce) {
	.lb-row {
		animation: none;
	}
	.lb-bar__fill {
		animation: none;
		/* Without the keyframe, the static rule (width: var(--bar-pct))
		 * applies — bar shows at its final width with no transition. */
	}
	.lb-row.is-top .lb-bar {
		animation: none;
		box-shadow:
			0 0 0 2px rgba(255, 206, 0, 0.5),
			0 0 16px rgba(255, 206, 0, 0.5);
	}
}
