/*
	Roguegrind - design system v2
	Premium dark presentation around the game's pixel-art identity: the deep
	dungeon-blue + gold palette of the launcher, the 8BIT WONDER display face
	for brand moments, crisp pixel sprites - inside a modern surface language
	(glass nav, 1px hairline borders, soft layered shadows, fluid type scale).

	Deliberately single-theme dark: the brand world is a dungeon crawler and
	the site commits to it, same as the game launcher.

	Body text is the system sans stack on purpose - zero font download, no
	third-party font host (GDPR), and it's the same approach Linear/Vercel
	take. 8BIT WONDER is reserved for display moments; it has no punctuation
	glyphs, so the stack falls back per-glyph - intentional.
*/

@font-face {
	font-family: '8BIT WONDER';
	src: url('../fonts/8bitwonder.ttf') format('truetype');
	font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
	/* color */
	--bg: #0b0d13;
	--bg-raise: #10141d;
	--surface: #141926;
	--surface-2: #1b2132;
	--border: rgba(255, 255, 255, 0.07);
	--border-strong: rgba(255, 255, 255, 0.13);
	--text: #edeff5;
	--text-2: #b4bac8;
	--muted: #8b92a5;
	--gold: #e8b64c;
	--gold-soft: #f2cf82;
	--gold-deep: #b8892b;
	--gold-glow: rgba(232, 182, 76, 0.14);
	--green: #63d888;
	--red: #ff7a70;
	--silver: #c8d0e0;
	--bronze: #d29a6b;

	/* Bestiary biome accents - muted/desaturated on purpose, distinct from
	   --green/--red/--gold above so a biome border is never mistaken for a
	   status color. */
	--biome-grass: #8bab5c;
	--biome-jungle: #3f8f5e;
	--biome-desert: #c2955c;
	--biome-volcano: #c1573f;
	--biome-sand: #d8c48a;
	--biome-snow: #9ecbd8;
	/* Not a natural biome - the dungeon-exclusive encounters (Bat, Wraith,
	   the Deamon Dragon boss). A cool slate-violet reads as "underground"
	   without borrowing from any of the five terrain colors above. */
	--biome-dungeon: #8f7fae;

	/* type */
	--px-font: '8BIT WONDER', 'Courier New', monospace;
	--body-font: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
		'Helvetica Neue', Arial, sans-serif;

	/* Legibility floor for the pixel display face. 8BIT WONDER is a bitmap
	   design with no lowercase and no punctuation glyphs: below roughly 14px
	   its letterforms stop being read and start being pattern-matched, which
	   showed up worst on the things that mattered most - weapon and creature
	   names were rendering at 9.9px, and the site's single most important
	   button, DOWNLOAD NOW, at 11.5px.
	   The rule this token encodes: --px-font is for brand moments and headings
	   at --px-min or larger. Anything that has to be smaller than that (tags,
	   badges, version pills, table meta) switches to --body-font instead of
	   shrinking the pixel face further. .patch-version already worked this way;
	   this generalises it. */
	--px-min: 0.875rem;   /* 14px - hard floor for --px-font */
	--px-sm: 0.9375rem;   /* 15px - card and sub-heading rank */

	/* shape + elevation */
	--r-sm: 8px;
	--r-md: 12px;
	--r-lg: 16px;
	--r-xl: 22px;
	/* Every elevation token leads with a 1px inset top highlight - raised
	   dark surfaces read as "lit from above", the shared tell of Linear/
	   Vercel-class dark UIs. Baked into the tokens so all surfaces get it
	   consistently instead of per-component. */
	--edge-light: inset 0 1px 0 rgba(255, 255, 255, 0.05);
	--shadow-sm: var(--edge-light), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.25);
	--shadow-md: var(--edge-light), 0 2px 4px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.35);
	--shadow-lg: var(--edge-light), 0 4px 8px rgba(0, 0, 0, 0.4), 0 24px 64px rgba(0, 0, 0, 0.45);

	--nav-h: 64px;

	/* Native UI (scrollbars, form controls) should match the dark theme
	   instead of flashing light-grey Windows chrome. */
	color-scheme: dark;
	accent-color: var(--gold);
}

/* ---------- Base ---------- */

* { margin: 0; padding: 0; box-sizing: border-box; }

html {
	scroll-behavior: smooth;
	scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
	background: var(--bg);
	color: var(--text);
	font-family: var(--body-font);
	font-size: 16px;
	line-height: 1.65;
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

img.pixel {
	image-rendering: pixelated;
	image-rendering: crisp-edges;
}

img { max-width: 100%; height: auto; }

a { color: var(--gold-soft); }
a:hover { color: var(--gold); }

/* Prose links only - nav/buttons opt out via text-decoration: none. A hair
   of offset + a translucent underline reads calmer than the browser default. */
a:not([class]) {
	text-decoration-color: rgba(232, 182, 76, 0.35);
	text-underline-offset: 3px;
	transition: color 0.15s, text-decoration-color 0.15s;
}
a:not([class]):hover { text-decoration-color: var(--gold); }

/* Thin themed scrollbars - the light default bar is the single loudest
   "this isn't a native dark app" tell on Windows. */
* {
	scrollbar-width: thin;
	scrollbar-color: rgba(255, 255, 255, 0.14) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
	background: rgba(255, 255, 255, 0.14);
	border-radius: 999px;
	border: 2px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.24); }

::selection { background: rgba(232, 182, 76, 0.28); }

:focus-visible {
	outline: 2px solid var(--gold);
	outline-offset: 3px;
	border-radius: 4px;
}

h1, h2, h3, h4 { text-wrap: balance; }

/* Visually hidden, still announced. Used by the live regions that narrate the
   armory/bestiary grids (see site.js) - those rewrite the grid with no page
   load, which is silent to a screen reader unless something says so.
   Clipped rather than display:none/visibility:hidden, both of which would
   take the element out of the accessibility tree and defeat the purpose. */
.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

.skip-link {
	position: absolute;
	top: -48px;
	left: 12px;
	z-index: 200;
	padding: 0.6rem 1rem;
	background: var(--gold);
	color: #201806;
	border-radius: var(--r-sm);
	font-weight: 600;
	text-decoration: none;
	transition: top 0.15s;
}
.skip-link:focus-visible { top: 12px; color: #201806; }

/* ---------- Nav ---------- */

.nav {
	position: fixed;
	top: 0; left: 0; right: 0;
	z-index: 100;
	display: flex;
	align-items: center;
	gap: 2rem;
	height: var(--nav-h);
	padding: 0 clamp(1rem, 4vw, 2.5rem);
	background: rgba(11, 13, 19, 0.72);
	border-bottom: 1px solid transparent;
	transition: background 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
}

/* At the very top the nav floats borderless over the hero; once the page
   scrolls it earns its hairline + a soft drop shadow (Linear/Vercel pattern -
   the bar visually "detaches" only when there's content moving under it).
   .scrolled is set by site.js; the border stays on without JS. */
html:not(.js) .nav,
.nav.scrolled {
	border-bottom-color: var(--border);
}

.nav.scrolled {
	background: rgba(11, 13, 19, 0.85);
	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

@supports (backdrop-filter: blur(1px)) {
	.nav {
		backdrop-filter: blur(14px) saturate(160%);
		-webkit-backdrop-filter: blur(14px) saturate(160%);
	}
}

.nav .brand {
	display: flex;
	align-items: center;
	gap: 0.65rem;
	font-family: var(--px-font);
	font-size: 0.9rem;
	color: var(--gold);
	text-decoration: none;
	letter-spacing: 1px;
	flex: none;
}

.nav .brand img { width: 26px; height: 26px; }

.nav .links {
	margin-left: auto;
	display: flex;
	align-items: center;
	gap: 1.9rem;
}

.nav .links a {
	font-size: 0.8rem;
	font-weight: 550;
	letter-spacing: 0.07em;
	color: var(--muted);
	text-decoration: none;
	transition: color 0.15s;
}

.nav .links a:hover { color: var(--text); }

.nav .links a[aria-current="page"] { color: var(--gold-soft); }

/* Language switch styled as a compact utility pill so it reads as a control,
   not another destination in the page's information architecture. */
.nav .links a.lang-toggle {
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.1em;
	padding: 0.32rem 0.7rem;
	border: 1px solid var(--border-strong);
	border-radius: 999px;
	color: var(--muted);
	transition: color 0.15s, border-color 0.15s, background 0.15s;
}

.nav .links a.lang-toggle:hover {
	color: var(--text);
	border-color: rgba(255, 255, 255, 0.25);
	background: rgba(255, 255, 255, 0.04);
}

/* Login styled as the nav's single call to action */
.nav .links a.nav-cta {
	padding: 0.45rem 1.05rem;
	border: 1px solid rgba(232, 182, 76, 0.5);
	border-radius: 999px;
	color: var(--gold-soft);
	transition: color 0.15s, background 0.15s, border-color 0.15s;
}

.nav .links a.nav-cta:hover {
	background: var(--gold);
	border-color: var(--gold);
	color: #201806;
}

.nav-burger {
	display: none;
	margin-left: auto;
	width: 42px;
	height: 42px;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 5px;
	background: transparent;
	border: 1px solid var(--border-strong);
	border-radius: var(--r-sm);
	cursor: pointer;
}

.nav-burger span {
	display: block;
	width: 18px;
	height: 2px;
	background: var(--text);
	border-radius: 2px;
}

/* ---------- Scroll progress (homepage only) ---------- */

.scroll-progress {
	position: fixed;
	top: var(--nav-h);
	left: 0;
	right: 0;
	height: 3px;
	background: var(--border);
	z-index: 99;
}

.scroll-progress-bar {
	height: 100%;
	width: 0%;
	background: linear-gradient(90deg, var(--gold-deep), var(--gold));
}

/* ---------- Hero ---------- */

.hero {
	position: relative;
	min-height: 100svh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	text-align: center;
	padding: calc(var(--nav-h) + 4rem) 1.5rem 5rem;
	overflow: hidden;
	isolation: isolate;
}

/* Key art backdrop. The art is bright (sky, snow, water), so it carries its
   own scrim via .hero-art::after below rather than relying on the text's
   own shadow - white-on-cloud was the one combination that failed. */
.hero-art {
	position: absolute;
	inset: 0;
	z-index: -3;
	display: block;
	pointer-events: none;
}

.hero-art img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Nearest-neighbour so the pixel art keeps its hard edges when the
	   browser scales it, matching .pixel elsewhere on the site. */
	image-rendering: pixelated;
}

/* Scrim: dark at the edges and along the bottom where the copy sits, more
   transparent through the upper middle so the volcano and castle still read.
   A flat overlay dimmed the whole illustration into mud.
   Deliberately a pseudo-element rather than its own <div>: every extra child
   of .hero shifts the :nth-child animation delays below, and one silent
   off-by-one there is worse than the nesting. */
.hero-art::after {
	content: '';
	position: absolute;
	inset: 0;
	/* The mid-stop was 0.42 and the radial 0.35 originally, which measured a
	   worst-case contrast of 3.06 for the tagline and 4.35 for the eyebrow
	   against the brightest parts of the art (clouds, snow) - both under the
	   4.5 AA needs for text that size. Raised until the measured worst case
	   clears 4.5 everywhere rather than until it looked fine. */
	background:
		linear-gradient(180deg, rgba(11, 13, 19, 0.86) 0%, rgba(11, 13, 19, 0.62) 32%, rgba(11, 13, 19, 0.78) 72%, var(--bg) 100%),
		radial-gradient(ellipse 70% 55% at 50% 45%, rgba(11, 13, 19, 0.46), transparent 75%);
}

/* Faint 32px grid - the game's tile raster as a background texture - fading
   out radially, plus a warm gold glow behind the headline. */
.hero::before {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -1;
	background-image:
		linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
		linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
	background-size: 32px 32px;
	mask-image: radial-gradient(ellipse 90% 70% at 50% 38%, #000 0%, transparent 68%);
	-webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 38%, #000 0%, transparent 68%);
}

.hero::after {
	content: '';
	position: absolute;
	inset: 0;
	z-index: -2;
	background:
		radial-gradient(ellipse 52% 38% at 50% 30%, var(--gold-glow), transparent 70%),
		radial-gradient(ellipse 70% 50% at 50% 100%, rgba(56, 78, 150, 0.12), transparent 70%);
}

/* Staggered entrance - the one place a page-load animation is worth it.
   `both` keeps elements at their end state forever, so this can never strand
   content invisible; reduced-motion zeroes the duration further down. */
@keyframes hero-rise {
	from { opacity: 0; transform: translateY(16px); }
	to   { opacity: 1; transform: none; }
}

/* The backdrop must not fade in with the copy - it is the page's LCP element,
   and animating it in delays the largest paint for no visual gain.
   The indices below start at 3 rather than 2 because .hero-art is always the
   first child: :nth-child counts it even though it is excluded from the
   animation. Add another element before the eyebrow and these all shift. */
.hero > *:not(.hero-art) { animation: hero-rise 0.65s cubic-bezier(0.22, 1, 0.36, 1) both; }
.hero > *:nth-child(3) { animation-delay: 0.08s; }
.hero > *:nth-child(4) { animation-delay: 0.16s; }
.hero > *:nth-child(5) { animation-delay: 0.24s; }
.hero > *:nth-child(6) { animation-delay: 0.32s; }
.hero > *:nth-child(7) { animation-delay: 0.4s; }

.hero .eyebrow {
	font-size: 0.78rem;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--gold-soft);
	border: 1px solid rgba(232, 182, 76, 0.35);
	background: rgba(232, 182, 76, 0.07);
	border-radius: 999px;
	padding: 0.35rem 0.95rem;
	margin-bottom: 1.6rem;
}

.hero h1 {
	font-family: var(--px-font);
	font-size: clamp(2.2rem, 8vw, 5rem);
	line-height: 1.1;
	color: var(--text);
	letter-spacing: 2px;
	text-shadow: 0 0 60px rgba(232, 182, 76, 0.35);
}

/* Gradient-filled brand accent (Vercel/Linear headline treatment). The solid
   color stays as painted fallback where background-clip: text is missing. */
.hero h1 .accent { color: var(--gold); }

@supports (-webkit-background-clip: text) or (background-clip: text) {
	.hero h1 .accent {
		background: linear-gradient(180deg, var(--gold-soft) 20%, var(--gold-deep));
		-webkit-background-clip: text;
		background-clip: text;
		-webkit-text-fill-color: transparent;
		color: var(--gold); /* used by ::selection and forced-colors modes */
	}
}

.hero .tagline {
	margin-top: 1.2rem;
	max-width: 620px;
	font-size: clamp(1rem, 2.4vw, 1.2rem);
	color: var(--text-2);
	text-wrap: balance;
}

.sprite-row {
	display: flex;
	gap: clamp(1.2rem, 5vw, 3.2rem);
	margin: 3rem 0 3.2rem;
	align-items: center;
}

.sprite-row img {
	width: clamp(52px, 8vw, 84px);
	height: auto;
	filter: drop-shadow(0 12px 20px rgba(0, 0, 0, 0.55));
	animation: float 4s ease-in-out infinite;
}

.sprite-row img:nth-child(2) { animation-delay: -0.8s; }
.sprite-row img:nth-child(3) { animation-delay: -1.6s; }
.sprite-row img:nth-child(4) { animation-delay: -2.4s; }
.sprite-row img:nth-child(5) { animation-delay: -3.2s; }

@keyframes float {
	0%, 100% { transform: translateY(0); }
	50%      { transform: translateY(-10px); }
}

.hero .actions {
	display: flex;
	gap: 0.9rem;
	flex-wrap: wrap;
	justify-content: center;
}

.hero .note {
	margin-top: 2.4rem;
	color: var(--muted);
	font-size: 0.88rem;
	max-width: 540px;
}

.hero .note a { color: var(--text-2); }
.hero .note a:hover { color: var(--gold-soft); }

/* Sits right under the primary actions row, not lumped in with the muted
   .note paragraphs below - those blended in enough that the jar link inside
   one of them went unnoticed next to the contact email. Bolder/brighter
   than .note, but still text-only (not a full .btn) so it stays visibly
   secondary to the main Download/Play/Rankings buttons above it. */
.hero-alt-download {
	margin-top: 2rem;
	color: var(--text-2);
	font-size: 0.85rem;
}

.hero-alt-download a {
	color: var(--gold-soft);
	font-weight: 600;
	text-decoration: underline;
	text-underline-offset: 3px;
}

.hero-alt-download a:hover { color: var(--gold); }

/* ---------- Download section ---------- */

/* Holds what used to be three muted .note paragraphs stacked in the hero.
   Two cards instead, because the choice is genuinely binary (do you have
   Java or not) and a card each lets both carry their own button. */
.download-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
	gap: 1.4rem;
	max-width: 900px;
	margin: 0 auto;
}

.download-card {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.8rem 1.6rem;
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: 1rem;
}

.download-card h3 {
	font-family: var(--px-font);
	font-size: var(--px-sm);
	letter-spacing: 1.5px;
	color: var(--gold-soft);
}

.download-card p {
	color: var(--text-2);
	font-size: 0.92rem;
	flex: 1;
}

.download-card a[href^="https://www.java.com"] { color: var(--gold-soft); }

.download-card .btn { margin-top: 0.2rem; }

/* ---------- Buttons ---------- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.6rem;
	font-family: var(--px-font);
	/* Was a flat 0.72rem (11.5px). A call to action is the one thing on a page
	   that must never be squinted at, and this class carries every one of them
	   including the hero download button. */
	font-size: clamp(var(--px-min), 0.82rem + 0.2vw, 1rem);
	letter-spacing: 0.06em;
	padding: 0.95rem 1.7rem;
	text-decoration: none;
	cursor: pointer;
	border: 1px solid transparent;
	border-radius: var(--r-md);
	background: linear-gradient(180deg, var(--gold-soft), #dfa63c);
	color: #201806;
	/* The inset top edge is the "machined" highlight premium UIs share - it
	   sells the button as a physical surface catching light from above. */
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
		0 8px 24px rgba(232, 182, 76, 0.22), var(--shadow-sm);
	transition: transform 0.16s ease, box-shadow 0.16s ease, filter 0.16s ease,
		background-color 0.16s ease, border-color 0.16s ease;
}

.btn:hover {
	transform: translateY(-2px);
	filter: brightness(1.06);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35),
		0 14px 36px rgba(232, 182, 76, 0.3), var(--shadow-md);
	color: #201806;
}

.btn:active {
	transform: translateY(0);
	transition-duration: 0.06s;
	filter: brightness(0.97);
}

.btn.ghost {
	background: rgba(255, 255, 255, 0.02);
	border-color: var(--border-strong);
	color: var(--text-2);
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.btn.ghost[aria-pressed="true"] {
	background: var(--gold-glow);
	border-color: var(--gold);
	color: var(--gold-soft);
}

.btn.ghost:hover {
	background: rgba(255, 255, 255, 0.05);
	border-color: rgba(232, 182, 76, 0.5);
	color: var(--gold-soft);
	box-shadow: var(--shadow-sm);
}

.btn svg { width: 1.15em; height: 1.15em; flex: none; fill: currentColor; }

.btn-discord {
	background: #5865f2;
	color: #fff;
	box-shadow: 0 8px 24px rgba(88, 101, 242, 0.3), var(--shadow-sm);
	min-width: 300px;
	font-size: var(--px-sm);
	padding: 1.05rem 1.6rem;
}

.btn-discord:hover {
	color: #fff;
	box-shadow: 0 14px 36px rgba(88, 101, 242, 0.4), var(--shadow-md);
}

.btn-steam {
	background: #1b2838;
	border-color: rgba(102, 192, 244, 0.35);
	color: #cfe6f5;
	box-shadow: var(--shadow-sm);
	min-width: 300px;
	font-size: var(--px-sm);
	padding: 1.05rem 1.6rem;
}

.btn-steam:hover {
	color: #fff;
	border-color: rgba(102, 192, 244, 0.6);
	box-shadow: 0 14px 36px rgba(23, 26, 33, 0.6), var(--shadow-md);
}

/* ---------- Sections ---------- */

.section {
	max-width: 1160px;
	margin: 0 auto;
	padding: 6rem clamp(1.25rem, 4vw, 2.5rem);
}

.section-eyebrow {
	font-size: 0.75rem;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--gold);
	margin-bottom: 0.9rem;
}

.section-title {
	font-family: var(--px-font);
	font-size: clamp(1.25rem, 3.4vw, 1.9rem);
	color: var(--text);
	letter-spacing: 2px;
	margin-bottom: 1rem;
}

.section-sub {
	color: var(--muted);
	margin-bottom: 3rem;
	max-width: 640px;
	font-size: 1.02rem;
}

/* ---------- Feature grid ---------- */

.features {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
	gap: 1.1rem;
}

.feature-card {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.6rem;
	transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.feature-card:hover {
	transform: translateY(-3px);
	border-color: rgba(232, 182, 76, 0.35);
	box-shadow: var(--shadow-md);
}

.feature-card .icon-well {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 56px;
	height: 56px;
	margin-bottom: 1.1rem;
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: var(--r-md);
	transition: border-color 0.18s ease, background 0.18s ease;
}

/* The icon well answers the card's gold hover border - one coordinated
   accent change per hover, not a pile of independent effects. */
.feature-card:hover .icon-well {
	border-color: rgba(232, 182, 76, 0.3);
	background: linear-gradient(180deg, var(--surface-2), rgba(232, 182, 76, 0.06));
}

.feature-card .icon-well img {
	width: 34px;
	height: 34px;
	filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
}

.feature-card h3 {
	font-family: var(--px-font);
	font-size: var(--px-sm);
	color: var(--gold-soft);
	letter-spacing: 1px;
	margin-bottom: 0.7rem;
	line-height: 1.4;
}

.feature-card p {
	color: var(--muted);
	font-size: 0.92rem;
}

/* ---------- Armory ---------- */

.armory-controls {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 1rem;
	margin-bottom: 1.6rem;
}

/* Same look as .biome-filter (bestiary) - different class so the two
   filter groups can't accidentally cross-wire in the JS click handlers. */
.weapon-filter {
	font-family: inherit;
	font-size: 0.82rem;
	font-weight: 550;
	letter-spacing: 0.04em;
	color: var(--muted);
	background: transparent;
	border: none;
	/* min-height rather than more vertical padding: these pills sit in a
	   wrapping row, and padding alone measured 33px tall - under the 44px
	   WCAG 2.5.8 target size that matters most on touch, where these filters
	   are the primary way to navigate a long grid. */
	padding: 0.5rem 1.1rem;
	min-height: 44px;
	border-radius: 999px;
	cursor: pointer;
	transition: color 0.15s, background 0.15s;
}

.weapon-filter:hover { color: var(--text); }

.weapon-filter.active {
	background: var(--gold);
	color: #201806;
	font-weight: 600;
}

.armory {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(185px, 1fr));
	gap: 1.1rem;
}

.item-card {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border-strong);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.6rem 1rem 1.4rem;
	text-align: center;
	transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.item-card:hover {
	transform: translateY(-3px);
	box-shadow: var(--shadow-md);
}

.item-card.rarity-ut {
	border-color: rgba(232, 182, 76, 0.4);
	background:
		radial-gradient(120% 70% at 50% 0%, rgba(232, 182, 76, 0.1), transparent 60%),
		linear-gradient(180deg, var(--surface), var(--bg-raise));
}

.item-card.rarity-ut:hover {
	border-color: rgba(232, 182, 76, 0.7);
	box-shadow: 0 10px 30px rgba(232, 182, 76, 0.12), var(--shadow-md);
}

.item-card img {
	width: 72px;
	height: 72px;
	filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.5));
	transition: transform 0.18s ease;
}

.item-card:hover img { transform: scale(1.07); }

/* Was 0.62rem - 9.9px of an all-caps bitmap face, the smallest type on the
   site and the name of the thing each card is about. */
.item-card h4 {
	font-family: var(--px-font);
	font-size: var(--px-min);
	letter-spacing: 1px;
	line-height: 1.45;
	color: var(--text);
	margin: 1rem 0 0.6rem;
}

.item-card .badge {
	display: inline-block;
	font-size: 0.66rem;
	font-weight: 600;
	letter-spacing: 0.1em;
	padding: 0.22rem 0.65rem;
	margin-bottom: 0.8rem;
	border: 1px solid var(--border-strong);
	border-radius: 999px;
	color: var(--silver);
	background: rgba(255, 255, 255, 0.03);
}

.item-card.rarity-ut .badge {
	border-color: rgba(232, 182, 76, 0.45);
	color: var(--gold-soft);
	background: rgba(232, 182, 76, 0.08);
}

.item-card .stats {
	color: var(--muted);
	font-size: 0.82rem;
	line-height: 1.55;
	font-variant-numeric: tabular-nums;
}

.item-card .stats b { color: var(--green); font-size: 0.95rem; }

/* ---------- Bestiary ---------- */

.bestiary-controls {
	display: flex;
	align-items: center;
	justify-content: space-between;
	flex-wrap: wrap;
	gap: 1rem;
	margin-bottom: 1.6rem;
}

.biome-filter {
	font-family: inherit;
	font-size: 0.82rem;
	font-weight: 550;
	letter-spacing: 0.04em;
	color: var(--muted);
	background: transparent;
	border: none;
	/* min-height rather than more vertical padding: these pills sit in a
	   wrapping row, and padding alone measured 33px tall - under the 44px
	   WCAG 2.5.8 target size that matters most on touch, where these filters
	   are the primary way to navigate a long grid. */
	padding: 0.5rem 1.1rem;
	min-height: 44px;
	border-radius: 999px;
	cursor: pointer;
	transition: color 0.15s, background 0.15s;
}

.biome-filter:hover { color: var(--text); }

.biome-filter.active {
	background: var(--gold);
	color: #201806;
	font-weight: 600;
}

.bestiary {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
	gap: 1.1rem;
}

/* Biome accents - a top-border keyed off data-biome, added only to the mobs
   actually locked to a home biome (see ProceduralLevel.spawnRandomlyInBiome).
   Every other mob-card is untouched by these rules. */
.mob-card[data-biome="grass"]   { border-top: 3px solid var(--biome-grass); }
.mob-card[data-biome="jungle"]  { border-top: 3px solid var(--biome-jungle); }
.mob-card[data-biome="desert"]  { border-top: 3px solid var(--biome-desert); }
.mob-card[data-biome="volcano"] { border-top: 3px solid var(--biome-volcano); }
.mob-card[data-biome="sand"]    { border-top: 3px solid var(--biome-sand); }
.mob-card[data-biome="snow"]    { border-top: 3px solid var(--biome-snow); }
.mob-card[data-biome="dungeon"] { border-top: 3px solid var(--biome-dungeon); }

.mob-card {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border-strong);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.6rem 1.2rem 1.4rem;
	text-align: center;
	transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}

.mob-card:hover {
	transform: translateY(-3px);
	box-shadow: var(--shadow-md);
}

.mob-card img {
	width: 64px;
	height: 64px;
	filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.5));
	transition: transform 0.18s ease;
}

.mob-card:hover img { transform: scale(1.07); }

.mob-card h4 {
	font-family: var(--px-font);
	font-size: var(--px-min);
	letter-spacing: 1px;
	line-height: 1.45;
	color: var(--text);
	margin: 1rem 0 0.6rem;
}

.mob-card .badge {
	display: inline-block;
	font-size: 0.66rem;
	font-weight: 600;
	letter-spacing: 0.1em;
	padding: 0.22rem 0.65rem;
	margin-bottom: 0.8rem;
	border: 1px solid var(--border-strong);
	border-radius: 999px;
	color: var(--silver);
	background: rgba(255, 255, 255, 0.03);
}

/* HP reads as the "life" stat - tint it like the health potion red. */
.mob-card .badge.hp {
	border-color: rgba(214, 84, 84, 0.4);
	color: #e08585;
	background: rgba(214, 84, 84, 0.07);
}

.mob-card p {
	color: var(--muted);
	font-size: 0.85rem;
	line-height: 1.55;
	text-align: left;
}

/* The boss gets the same gold treatment as untiered legendaries. */
.mob-card.boss {
	border-color: rgba(232, 182, 76, 0.4);
	background:
		radial-gradient(120% 70% at 50% 0%, rgba(232, 182, 76, 0.1), transparent 60%),
		linear-gradient(180deg, var(--surface), var(--bg-raise));
}

.mob-card.boss:hover {
	border-color: rgba(232, 182, 76, 0.7);
	box-shadow: 0 10px 30px rgba(232, 182, 76, 0.12), var(--shadow-md);
}

.mob-card.boss .badge {
	border-color: rgba(232, 182, 76, 0.45);
	color: var(--gold-soft);
	background: rgba(232, 182, 76, 0.08);
}

.mob-card.boss img {
	width: 96px;
	height: 96px;
}

/* ---------- Leaderboards (tabs + table) ---------- */

/* Full-bleed band so this section reads as visually distinct from the
   identical eyebrow/title/sub/grid rhythm every other section shares - a
   tonal shift only, using existing tokens, deliberately no gold accent so it
   doesn't compete with the Armory/boss/legendary gold treatment elsewhere. */
.board-band {
	background: linear-gradient(180deg, var(--bg-raise), var(--surface) 45%, var(--bg-raise));
	border-top: 1px solid var(--border);
	border-bottom: 1px solid var(--border);
}

.board-subheading {
	font-family: var(--px-font);
	font-size: 0.9rem;
	color: var(--text);
	letter-spacing: 2px;
	margin: 3.2rem 0 1.4rem;
}

.tabs input[type="radio"] {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}

.tab-bar {
	display: inline-flex;
	gap: 4px;
	padding: 4px;
	margin-bottom: 1.4rem;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 999px;
}

.tab-bar label {
	font-size: 0.82rem;
	font-weight: 550;
	letter-spacing: 0.04em;
	color: var(--muted);
	padding: 0.5rem 1.3rem;
	border-radius: 999px;
	cursor: pointer;
	transition: color 0.15s, background 0.15s;
	user-select: none;
}

.tab-bar label:hover { color: var(--text); }

.tab-bar input:checked + label {
	background: var(--gold);
	color: #201806;
	font-weight: 600;
}

.tab-bar input:focus-visible + label {
	outline: 2px solid var(--gold);
	outline-offset: 2px;
}

.tab-panel { display: none; }

.tabs:has(#tab-alltime:checked) #panel-alltime,
.tabs:has(#tab-monthly:checked) #panel-monthly,
.tabs:has(#tab-weekly:checked) #panel-weekly,
.tabs:has(#tab-daily:checked) #panel-daily { display: block; }

/* Graceful degradation: no :has() support -> show all three stacked. */
@supports not selector(:has(*)) {
	.tab-panel { display: block; margin-bottom: 2rem; }
}

/* Every wide table on the site lives in one of these. At 375px the glory
   table measures 676px inside a 375px wrapper, so the last two columns are
   only reachable by scrolling - and a scroll container that isn't focusable
   cannot be scrolled by keyboard at all (WCAG 2.1.1). Each wrapper is now a
   labelled, focusable region in the markup (tabindex="0" role="region"), and
   this rule gives that focus a visible ring.

   The two edge gradients are the affordance that it scrolls in the first
   place: background-attachment: local pins the outer pair to the content, so
   the left shadow only appears once there is content scrolled off to the
   left, and the right one disappears when you reach the end. Nothing here
   depends on JS. */
.table-wrapper {
	overflow-x: auto;
	/* Layer order, front to back:
	   1-2. covers, attachment: local  - these scroll WITH the table, so at
	        rest each one sits on top of its shadow and hides it. Scroll right
	        and the left cover travels out of view, revealing the left shadow;
	        reach the end and the right cover lands on the right shadow again.
	   3-4. shadows, attachment: scroll - pinned to the wrapper's own edges.
	   5.   the card surface itself.
	   Net effect: the fades appear only in the direction there is still
	   content to reach, and a table that fits shows none at all - no JS, no
	   resize listener, and nothing to keep in sync. */
	background:
		linear-gradient(90deg, var(--surface), rgba(20, 25, 38, 0)) 0 0 / 26px 100% no-repeat local,
		linear-gradient(270deg, var(--surface), rgba(20, 25, 38, 0)) 100% 0 / 26px 100% no-repeat local,
		linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent) 0 0 / 20px 100% no-repeat scroll,
		linear-gradient(270deg, rgba(0, 0, 0, 0.5), transparent) 100% 0 / 20px 100% no-repeat scroll,
		linear-gradient(180deg, var(--surface), var(--bg-raise)) 0 0 / 100% 100% no-repeat scroll;
	border: 1px solid var(--border);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
}

.table-wrapper:focus-visible {
	outline: 2px solid var(--gold);
	outline-offset: 2px;
}

.board table {
	width: 100%;
	border-collapse: collapse;
	font-variant-numeric: tabular-nums;
}

.board thead th {
	font-size: 0.72rem;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	text-align: left;
	color: var(--muted);
	padding: 0.9rem 1.25rem;
	background: rgba(255, 255, 255, 0.02);
	border-bottom: 1px solid var(--border-strong);
	white-space: nowrap;
}

.board tbody td {
	padding: 0.78rem 1.25rem;
	border-top: 1px solid var(--border);
	font-size: 0.94rem;
	color: var(--text-2);
	white-space: nowrap;
}

.board tbody tr:first-child td { border-top: 0; }
.board tbody tr:hover { background: rgba(255, 255, 255, 0.025); }

.board td.rank {
	font-family: var(--px-font);
	font-size: var(--px-min);
	color: var(--muted);
	width: 4.5rem;
}

.board tr.top-1 td.rank { color: var(--gold); }
.board tr.top-2 td.rank { color: var(--silver); }
.board tr.top-3 td.rank { color: var(--bronze); }

.board td.player { color: var(--text); font-weight: 550; }
.board td.glory { color: var(--gold-soft); font-weight: 600; }
.board td.killed-by { color: var(--muted); font-size: 0.86rem; }
.board td.died { color: var(--muted); font-size: 0.86rem; }

/* "Quests Completed" needs to stay on one line (wrapping made the header row
   uneven against the other single-line headers) - tightened via reduced
   padding/letter-spacing instead of width/wrap, so the column is narrower
   than the default but the label doesn't break. */
.board th.quests {
	padding-left: 0.6rem;
	padding-right: 0.6rem;
	letter-spacing: 0.04em;
}
.board td.quests {
	color: var(--muted);
	font-size: 0.86rem;
	padding-left: 0.6rem;
	padding-right: 0.6rem;
}

.board .empty td {
	text-align: center;
	color: var(--muted);
	padding: 2rem;
}

/* ---------- Hall of Infamy pagination ---------- */

.pagination {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 1rem;
	margin-top: 1.2rem;
}

.pagination .btn {
	padding: 0.6rem 1.3rem;
	font-size: var(--px-min);
	/* Same 44px touch-target floor as the filter pills above - pagination is
	   reached by thumb on exactly the devices where it matters. */
	min-height: 44px;
}

/* Disabled prev/next render as an inert span (see render_hall_of_infamy_pagination()
   in index.php), not a link - dim it so it doesn't look clickable. */
.pagination span.btn[aria-disabled="true"],
.pagination button.btn:disabled {
	opacity: 0.35;
	pointer-events: none;
}

.pagination-status {
	color: var(--muted);
	font-size: 0.82rem;
	font-variant-numeric: tabular-nums;
}

/* ---------- Patch notes ---------- */

.patch-entry {
	position: relative;
	padding-left: 1.75rem;
	margin-bottom: 3.2rem;
	border-left: 1px solid var(--border-strong);
}

.patch-entry::before {
	content: '';
	position: absolute;
	left: -5px;
	top: 0.35rem;
	width: 9px;
	height: 9px;
	border-radius: 50%;
	background: var(--gold);
	box-shadow: 0 0 0 4px rgba(232, 182, 76, 0.15);
}

/* h2 since patchnotes.php stopped skipping a heading level (the entries sit
   directly under the page's h1). The teaser on index.php uses .patch-entry
   without a heading at all, so nothing else is affected. */
.patch-entry h2 {
	font-family: var(--px-font);
	font-size: var(--px-sm);
	color: var(--text);
	letter-spacing: 1px;
	margin-bottom: 1.2rem;
}

/* Version badge next to a patch-notes date (see render_patch_version_badge()
   in patchnotes_data.php) - body font rather than the pixel display face so
   it stays legible at this size, matching .patch-tag/.badge conventions
   elsewhere rather than the h3's own pixel-font styling. */
.patch-version {
	display: inline-block;
	vertical-align: middle;
	margin-left: 0.7rem;
	font-family: var(--body-font);
	font-size: 0.66rem;
	font-weight: 600;
	letter-spacing: 0.06em;
	padding: 0.22rem 0.65rem;
	border: 1px solid var(--border-strong);
	border-radius: 999px;
	color: var(--silver);
	background: rgba(255, 255, 255, 0.03);
	text-transform: none;
}

/* Not yet uploaded to the download page - the gold treatment matches
   .patch-tag.change (a change worth noticing), distinct from the neutral
   pill above so it can't be mistaken for a shipped version number. */
.patch-version.unreleased {
	border-color: rgba(232, 182, 76, 0.4);
	color: var(--gold-soft);
	background: rgba(232, 182, 76, 0.08);
}

/* Nested in the hero's gold download button (see index.php) instead of
   sitting next to body text on a dark background - the default silver-on-
   near-black look above has poor contrast against that gradient, so this
   reuses the button's own dark text color instead. .btn's flex gap already
   spaces it from the label, so the standalone badge's own margin is dropped. */
.btn .patch-version {
	margin-left: 0;
	border-color: rgba(32, 24, 6, 0.35);
	color: #201806;
	background: rgba(32, 24, 6, 0.12);
}

.patch-entry ul {
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: 0.7rem;
}

.patch-entry li {
	display: flex;
	align-items: baseline;
	gap: 0.8rem;
	color: var(--text-2);
	font-size: 0.95rem;
}

/* Body font, not the pixel face: at this size the pixel face is below the
   --px-min floor, and a tag is chrome rather than a brand moment. Same call
   .patch-version already made. */
.patch-tag {
	flex: none;
	font-family: var(--body-font);
	font-size: 0.7rem;
	font-weight: 700;
	letter-spacing: 0.1em;
	padding: 0.18rem 0.6rem;
	border-radius: 999px;
	border: 1px solid var(--border-strong);
	color: var(--silver);
	background: rgba(255, 255, 255, 0.03);
}

.patch-tag.new    { border-color: rgba(99, 216, 136, 0.4);  color: var(--green); background: rgba(99, 216, 136, 0.08); }
.patch-tag.fix    { border-color: rgba(255, 122, 112, 0.4); color: var(--red);   background: rgba(255, 122, 112, 0.08); }
.patch-tag.change { border-color: rgba(232, 182, 76, 0.4);  color: var(--gold-soft); background: rgba(232, 182, 76, 0.08); }

/* ---------- Login box ---------- */

.register-box {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border);
	border-radius: var(--r-xl);
	box-shadow: var(--shadow-md);
	padding: 2.6rem 2.2rem;
	max-width: 560px;
}

.login-box {
	text-align: center;
	margin: 0 auto;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 1rem;
}

.msg {
	width: 100%;
	padding: 0.85rem 1.1rem;
	font-size: 0.92rem;
	border: 1px solid;
	border-radius: var(--r-md);
}

.msg.ok  { border-color: rgba(99, 216, 136, 0.4);  color: var(--green); background: rgba(99, 216, 136, 0.07); }
.msg.err { border-color: rgba(255, 122, 112, 0.4); color: var(--red);   background: rgba(255, 122, 112, 0.07); }

.hint { color: var(--muted); font-size: 0.85rem; max-width: 420px; }

/* Player names in the rankings link to their public profile. Underlining a
   whole column of them reads as noise in a dense table, so the affordance is
   carried by colour plus an underline on hover/focus instead - the global
   prose-link rule would otherwise underline every row permanently. */
.board td.player a {
	color: var(--gold-soft);
	text-decoration: none;
}

.board td.player a:hover,
.board td.player a:focus-visible {
	color: var(--gold);
	text-decoration: underline;
	text-underline-offset: 3px;
}

/* ---------- Profile ---------- */

.profile { padding-top: calc(var(--nav-h) + 4rem); }

.profile-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
	gap: 1.1rem;
	margin-bottom: 3.2rem;
	max-width: 900px;
}

.stat {
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.4rem 1.2rem;
	text-align: center;
}

.stat-value {
	display: block;
	font-family: var(--px-font);
	font-size: 1.55rem;
	color: var(--gold-soft);
	letter-spacing: 1px;
	font-variant-numeric: tabular-nums;
}

.stat-value-small { font-size: 0.9rem; padding: 0.5rem 0; }

.stat-label {
	display: block;
	font-size: 0.68rem;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--muted);
	margin-top: 0.7rem;
}

/* margin-top matters as much as margin-bottom here. This only ever had the
   bottom half, which happened to look right on profile.php because everything
   above each heading (.profile-stats, .profile-items, the settings form)
   carries its own margin-bottom. Used on a page where a heading follows a
   .features grid or a .board table - neither of which has a bottom margin -
   the heading sat flush against the element above it, measured at exactly 0px.
   Adjacent-sibling margins collapse, so adding this does not double the
   spacing anywhere it already looked correct. */
.block-heading {
	font-family: var(--px-font);
	font-size: 0.9rem;
	color: var(--text);
	letter-spacing: 2px;
	margin-top: 3.2rem;
	margin-bottom: 1.4rem;
}

/* ...except as the first thing in its container, where a top margin would
   just push the whole block down for no reason. */
.block-heading:first-child { margin-top: 0; }

.profile-items { margin-bottom: 2rem; }

/* Settings form (profile.php -> profile_update.php). Deliberately narrow:
   two short fields read better as a column than stretched across the same
   full width the stat grid above them uses. */
.profile-settings {
	max-width: 460px;
	margin-bottom: 3.2rem;
	background: linear-gradient(180deg, var(--surface), var(--bg-raise));
	border: 1px solid var(--border);
	border-radius: var(--r-lg);
	box-shadow: var(--shadow-sm);
	padding: 1.6rem 1.4rem;
}

.profile-settings .field { margin-bottom: 1.5rem; }

.profile-settings label,
.profile-settings .field-label {
	display: block;
	font-size: 0.68rem;
	font-weight: 600;
	letter-spacing: 0.12em;
	text-transform: uppercase;
	color: var(--muted);
	margin-bottom: 0.6rem;
}

/* Body font, deliberately. This field renders what the user is typing, and
   --px-font has no punctuation and no umlaut glyphs - characters they type
   would come back as blanks or fall back per-glyph mid-word. The label above
   it keeps the pixel face; the value they are editing has to be shown in a
   font that can render everything a keyboard can produce. */
.profile-settings input[type="text"] {
	width: 100%;
	background: var(--bg);
	border: 1px solid var(--border-strong);
	border-radius: var(--r-sm);
	color: var(--text);
	font-family: var(--body-font);
	font-size: 1rem;
	letter-spacing: 0.04em;
	padding: 0.75rem 0.8rem;
}

.profile-settings input[type="text"]:focus-visible {
	outline: none;
	border-color: var(--gold-deep);
	box-shadow: 0 0 0 3px var(--gold-glow);
}

.profile-settings .hint { margin-top: 0.55rem; display: block; }

/* Radio pills - the native control is hidden but still focusable, so the
   group stays keyboard- and screen-reader-operable; :focus-visible on it
   styles the adjacent label, same trick .tab-bar already uses above. */
.pill-group { display: flex; gap: 0.6rem; flex-wrap: wrap; }

/* Hidden exactly the way .tabs input[type="radio"] above does it - taken out
   of flow and made transparent, but NOT collapsed to 0x0, which can drop the
   control out of the accessibility tree entirely. The label's `for` still
   drives it, so clicking and keyboard operation both keep working. */
.pill-group input[type="radio"] {
	position: absolute;
	opacity: 0;
	pointer-events: none;
}

.pill-group label {
	margin: 0;
	cursor: pointer;
	padding: 0.6rem 1.1rem;
	border: 1px solid var(--border-strong);
	border-radius: var(--r-sm);
	background: var(--surface-2);
	color: var(--text-2);
	font-family: var(--px-font);
	font-size: var(--px-min);
	letter-spacing: 1px;
	transition: background 0.15s, color 0.15s, border-color 0.15s;
}

.pill-group label:hover { border-color: var(--gold-deep); color: var(--text); }

.pill-group input:checked + label {
	background: var(--gold-glow);
	border-color: var(--gold);
	color: var(--gold-soft);
}

.pill-group input:focus-visible + label {
	outline: 2px solid var(--gold);
	outline-offset: 2px;
}

/* Save outcome, rendered after profile_update.php's redirect. */
.profile-notice {
	max-width: 460px;
	margin-bottom: 1.2rem;
	padding: 0.85rem 1rem;
	border-radius: var(--r-sm);
	border: 1px solid var(--border-strong);
	font-size: 0.9rem;
}

.profile-notice.is-ok {
	border-color: var(--green);
	color: var(--green);
	background: rgba(99, 216, 136, 0.08);
}

.profile-notice.is-err {
	border-color: var(--red);
	color: var(--red);
	background: rgba(255, 122, 112, 0.08);
}

/* ---------- Sub-pages & teasers ---------- */

/* armory.php / bestiary.php only need to clear the fixed nav here - unlike
   .profile and .howto, their <main> is NOT a .section: it wraps the original
   <section class="section"> lifted straight out of index.php, which brings its
   own max-width and 6rem padding. Carrying .section on both nested one inside
   the other, which stacked 128px + 96px of top padding and inset the content
   from a 1160px container inside another 1160px container. */
.subpage { padding-top: var(--nav-h); }

/* What is left on the front page where the full grids used to be. Centred,
   unlike the grids themselves, because a teaser is a signpost rather than
   content to scan. */
.teaser { text-align: center; }
.teaser .section-sub { margin-left: auto; margin-right: auto; }
.teaser .cta-row { justify-content: center; }

.teaser-sprites {
	display: flex;
	justify-content: center;
	align-items: center;
	gap: clamp(1rem, 4vw, 2.6rem);
	margin: 2.4rem 0;
	flex-wrap: wrap;
}

.teaser-sprites img {
	width: clamp(48px, 7vw, 86px);
	height: auto;
	filter: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.5));
}

/* ---------- How to Play ---------- */

.howto { padding-top: calc(var(--nav-h) + 4rem); }

/* Sits under a .block-heading as a second level - the page has enough
   sections that the specials table needed a rank of its own. */
.howto-subheading {
	font-family: var(--px-font);
	font-size: var(--px-sm);
	color: var(--gold-soft);
	letter-spacing: 1.5px;
	margin: 2.6rem 0 1rem;
}

.howto-note { max-width: 70ch; margin-bottom: 1.4rem; }

/* Reuses .board's table chrome; only the key column needs its own treatment,
   since a key cap should read as something you press. */
.controls-table td.key {
	font-family: var(--px-font);
	font-size: var(--px-min);
	color: var(--gold-soft);
	letter-spacing: 1px;
	white-space: nowrap;
}

.howto-tips {
	max-width: 74ch;
	margin: 0 0 2.6rem 1.2rem;
	color: var(--text-2);
}

.howto-tips li {
	margin-bottom: 0.9rem;
	padding-left: 0.3rem;
}

.howto-tips li::marker { color: var(--gold); }

/* Renamed from .howto-cta once armory.php, bestiary.php and index.php's two
   teasers all started using it - a class named after one page is misleading
   the moment a second page adopts it.
   margin-top for the same reason .block-heading needed one: on bestiary.php
   this follows the mob grid, which has no bottom margin, so the buttons sat
   flush against the last row of cards. */
.cta-row {
	display: flex;
	gap: 0.9rem;
	flex-wrap: wrap;
	margin-top: 3rem;
}

/* ---------- Legal / long-form text ---------- */

.prose { max-width: 68ch; }
.prose p { margin-bottom: 1.5rem; color: var(--text-2); }
.prose b { color: var(--text); }
.prose ul { margin: -0.6rem 0 1.5rem 1.3rem; color: var(--text-2); }
.prose li { margin-bottom: 0.35rem; }
.prose code {
	background: var(--surface-2);
	border: 1px solid var(--border);
	border-radius: 5px;
	padding: 0.1rem 0.4rem;
	font-size: 0.9em;
}

/* ---------- Footer ---------- */

footer {
	border-top: 1px solid var(--border);
	background: var(--bg-raise);
	padding: 3rem 1.5rem 3.5rem;
	color: var(--muted);
	font-size: 0.88rem;
}

.foot-inner {
	max-width: 1160px;
	margin: 0 auto;
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0.9rem;
	text-align: center;
}

.foot-brand {
	display: flex;
	align-items: center;
	gap: 0.6rem;
	font-family: var(--px-font);
	font-size: var(--px-min);
	color: var(--gold);
	letter-spacing: 1px;
}

.foot-links { display: flex; gap: 1.6rem; }

.foot-links a {
	color: var(--muted);
	text-decoration: none;
	transition: color 0.15s;
}

.foot-links a:hover { color: var(--gold-soft); }

/* ---------- Scroll reveal (JS-gated so content never hides without it) ---------- */

html.js [data-reveal] {
	opacity: 0;
	transform: translateY(14px);
	transition: opacity 0.55s ease, transform 0.55s ease;
}

/* Stagger cards that reveal in the same viewport pass. Keyed on grid column
   position (repeating every 4) so a whole row never pops in as one block -
   the cascade is what separates "animated" from "choreographed". */
html.js [data-reveal]:nth-child(4n+2) { transition-delay: 0.07s; }
html.js [data-reveal]:nth-child(4n+3) { transition-delay: 0.14s; }
html.js [data-reveal]:nth-child(4n+4) { transition-delay: 0.21s; }

html.js [data-reveal].revealed {
	opacity: 1;
	transform: none;
}

/* ---------- Motion preferences ---------- */

@media (prefers-reduced-motion: reduce) {
	html { scroll-behavior: auto; }
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
	}
	html.js [data-reveal] { opacity: 1; transform: none; }
}

/* ---------- Responsive ---------- */

@media (max-width: 780px) {
	.nav { gap: 1rem; }

	.nav-burger { display: inline-flex; }

	.nav .links {
		position: fixed;
		top: var(--nav-h);
		left: 0; right: 0;
		flex-direction: column;
		align-items: stretch;
		gap: 0;
		padding: 0.6rem 1rem 1.2rem;
		background: rgba(11, 13, 19, 0.97);
		border-bottom: 1px solid var(--border);
		display: none;
	}

	@supports (backdrop-filter: blur(1px)) {
		.nav .links {
			background: rgba(11, 13, 19, 0.85);
			backdrop-filter: blur(18px) saturate(160%);
			-webkit-backdrop-filter: blur(18px) saturate(160%);
		}
	}

	.nav .links.open {
		display: flex;
		animation: menu-in 0.22s cubic-bezier(0.22, 1, 0.36, 1);
	}

	@keyframes menu-in {
		from { opacity: 0; transform: translateY(-8px); }
		to   { opacity: 1; transform: none; }
	}

	.nav .links a {
		padding: 0.95rem 0.4rem;
		font-size: 0.95rem;
		border-bottom: 1px solid var(--border);
	}

	.nav .links a:last-child { border-bottom: 0; }

	.nav .links a.nav-cta {
		margin-top: 0.9rem;
		border-radius: var(--r-md);
		text-align: center;
		border-color: var(--gold);
		background: var(--gold);
		color: #201806;
	}

	/* In the stacked menu the language pill would otherwise render at its
	   tiny desktop size against full-width rows - center it as a chip. */
	.nav .links a.lang-toggle {
		align-self: center;
		margin-top: 0.9rem;
		padding: 0.5rem 1.5rem;
		border-bottom: 1px solid var(--border-strong);
	}

	.section { padding-top: 4rem; padding-bottom: 4rem; }

	.btn-discord, .btn-steam { min-width: 0; width: 100%; }

	.tab-bar { width: 100%; }
	.tab-bar label { flex: 1; text-align: center; padding: 0.5rem 0.6rem; }
}
