@font-face {
  font-family: 'Oswald';
  src: url('/fonts/oswald-600.ttf') format('truetype');
  font-weight: 600;
  font-display: swap;
}
:root {
  /* surfaces */
  --bg: #14100d; --surface: #1c1611; --surface-2: #251d17;
  --line: #3a2f26; --line-strong: #4a3b30;
  /* ink */
  --bone: #e8ddd0; --bone-dim: #b3a596; --dim: #8a7f70;
  /* accent + cold neutral */
  /* --steel: cold blue-grey used for 11-12px mono labels on --surface-2 and --bg.
     #939fa6 computes 6.12:1 on #251d17 and 6.98:1 on #14100d, clearing WCAG AA
     (>=4.5:1) for normal text on both. Do not darken past ~4.5:1. */
  --rust: #cd5c2a; --rust-hot: #e8703a; --rust-deep: #8f3d1c; --steel: #939fa6;
  /* type: only Oswald 600 is self-hosted; the mono stack lists OS-resident faces
     only (no unhosted webfonts), so users get a real installed monospace. */
  --mono: ui-monospace, 'Cascadia Mono', Consolas, monospace;
  --emboss: inset 0 1px 0 rgba(232,221,208,0.06), inset 0 -1px 0 rgba(0,0,0,0.35);
  --stamp-shadow: 0 2px 0 var(--rust-deep);
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --r-sm: 3px; --r-md: 4px;
}
* { box-sizing: border-box; margin: 0; }
/* overflow-x: clip (not hidden) still masks any stray overflow but does NOT make
   the page a scroll container. The layout is verified to have zero actual
   horizontal overflow from 320px up, so this is a belt-and-braces guard only. */
html, body { overflow-x: clip; }
body {
  background: var(--bg); color: var(--bone);
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  line-height: 1.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}
/* v2 anchors the composition instead of centring it: short content under
   auto-margins floats mid-void ("empty Google clone" — council round 3).
   A fixed upper anchor + a footer floor reads composed at every height.
   (v1's flex-on-main +54px margin-collapse finding lives in git history;
   anchoring makes the whole technique moot.) */
main { max-width: 940px; margin: 0 auto; padding: clamp(96px, 18vh, 220px) 24px 80px; }
h1, h2, .name, button, .sub-verdict { font-family: 'Oswald', 'Arial Narrow', sans-serif; letter-spacing: 0.04em; }

/* hero: v2 centres the lockup like a splash screen (council round 3) —
   the composition is a search engine now, and the search row below is the
   axis everything hangs from. 64px cap, down from 76: splash-screen scale,
   not billboard. */
.hero { text-align: center; display: flex; flex-direction: column; align-items: center; margin-bottom: 28px; }
.brand-lockup { display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 4px; }
.brand-mark { height: clamp(48px, 11.5vw, 70px); width: auto; flex: none; }
.hero h1 { font-size: clamp(44px, 7vw, 64px); color: var(--bone); line-height: 0.95; }
.hero h1 span { color: var(--rust); }
.tagline { font-size: clamp(18px, 2.4vw, 20px); color: var(--rust-hot); margin-top: 6px; }
/* Quip under the search ("two sizes, not twins" — the tagline stays
   clamp(18-20px) rust; this is the smaller bone-dim aside). */
.sub { margin: 18px auto 0; text-align: center; font-size: 15px; max-width: 42ch; color: var(--bone-dim); }

/* form — fused search row, centred on the hero axis */
#check-form { display: flex; gap: 5px; max-width: 660px; margin: 0 auto; }
/* The query field speaks the record system's chrome voice: mono, on the
   dossier surface. 16px is a floor — below it iOS zooms the focused input.
   No fake "$ >" prompt glyph: ::before does not render on replaced elements
   and a shell costume on a paste field is affordance noise.
   One control, two documents — combined so it cannot drift: faces.html's
   search input is the same field with a different form target (?q= vs the
   in-page submit), and it must look and behave identically. */
#steam-input, .faces-search input {
  flex: 1 1 auto; padding: 14px 16px;
  font-family: var(--mono); font-size: 16px; letter-spacing: 0.02em;
  background: var(--surface-2); color: var(--bone);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  box-shadow: var(--emboss);
}
/* --dim on --surface-2 measures 4.22:1. Placeholder text is AA-exempt, but
   this is the lowest ratio in the file — do not darken it further. */
#steam-input::placeholder, .faces-search input::placeholder { color: var(--dim); }
#steam-input:focus, .faces-search input:focus { outline: 2px solid var(--rust); outline-offset: 1px; border-color: var(--rust); }
button {
  padding: 14px 22px; font-size: 16px; cursor: pointer; white-space: nowrap;
  background: var(--rust); color: #fff; border: 0; border-radius: var(--r-sm);
  text-transform: uppercase; transition: background 160ms var(--ease-out), transform 120ms var(--ease-out);
}
button:hover { background: var(--rust-hot); }
button:active { transform: scale(0.97); }
.error { color: #c0563f; margin-top: 14px; }
/* the search error sits on the v2 centred axis; #fb-err (same class) stays
   left-aligned inside the left-aligned feedback card */
#error { text-align: center; }

/* Under-search utility cluster (council rounds 2+3): helper first —
   it resolves conversion friction; faces is exploratory. Steel on --bg is
   the documented 6.98:1. The // is CSS punctuation (alt-text form), rust
   like every readout mark. */
.util-links { display: flex; justify-content: center; align-items: center; gap: 24px; margin-top: 16px; flex-wrap: wrap; }
.util-links a {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--steel); text-decoration: none;
  padding: 13px 0; /* 44px effective target: 13+13 + 18px line box (12px x 1.5) */
}
.util-links a::before { content: '// ' / ''; color: var(--rust-deep); }
.util-links a:hover, .util-links a:focus-visible { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* One-click sample nudge under the form. The href is a SYNTHETIC base-sweep
   seed (bald Lucia — the community "Brinda" jab); the plate it resolves gets a
   // SAMPLE stamp so no screenshot ships as a real person. Quiet like the
   util-links, centred under the search. */
.sample-line { text-align: center; margin-top: 12px; }
.sample-line a {
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--steel); text-decoration: none;
}
.sample-line a::before { content: '// ' / ''; color: var(--rust-deep); }
.sample-line a:hover, .sample-line a:focus-visible { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }

/* The sample nudge and the inline SteamID helper share one row under the form,
   centred like the form above them. On a phone the row wraps and the two stack;
   the sample line drops its own top margin so the pair sit level in the row. */
.sample-row {
  display: flex; flex-wrap: wrap; justify-content: center;
  align-items: baseline; gap: 6px 20px; margin-top: 12px;
}
.sample-row .sample-line { margin-top: 0; }

/* Inline SteamID disclosure — a native <details>, zero JS. Replaces the old
   /steamid cluster link that read as a dead end; the deep-link page still
   exists for SEO. Summary is quiet mono chrome in the util-links register;
   the body is readable prose like .sub. */
.steamid-help { font-family: var(--mono); }
/* --dim on --bg measures 4.82:1 (see share.css) — AA, and the closest to the
   floor of anything on the page; do not darken. Not uppercased: the summary is
   a plain question, sentence case, matching the kimi mock. */
.steamid-help summary {
  cursor: pointer; list-style: none;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.12em;
  color: var(--dim);
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 0;
}
/* Safari still paints its own marker without this. */
.steamid-help summary::-webkit-details-marker { display: none; }
/* CSS-drawn chevron rather than the platform triangle (per-browser), reusing
   share.css's .langpick pattern; it rotates when the disclosure is open. */
.steamid-help summary::after {
  content: ''; width: 5px; height: 5px; margin-top: -3px;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg); transition: transform 140ms ease;
}
.steamid-help[open] summary::after { transform: rotate(-135deg); margin-top: 2px; }
.steamid-help summary:hover, .steamid-help summary:focus-visible {
  color: var(--bone); text-decoration: underline; text-underline-offset: 3px;
}
.steamid-help summary:focus-visible { outline: 2px solid var(--rust); outline-offset: 2px; }
/* Body prose: system-ui like .sub, --bone-dim on --bg measures 7.87:1 (clear
   AA). max-width holds a comfortable measure; overflow-wrap keeps the literal
   steamcommunity.com/id/you from pushing past a narrow viewport. */
.steamid-help p {
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 13.5px; line-height: 1.5; color: var(--bone-dim);
  max-width: 52ch; margin-top: 8px; text-align: left;
  overflow-wrap: anywhere;
}

/* Grok's status line: the exhibit's "proof of a living system" job done
   with text — no art, no JS, no legal surface. Dim: it is ambience, not
   action; 4.82:1 on --bg per the share.css measurement of the same pair. */
.statusline {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--dim); text-align: center;
  margin-top: 26px;
}
.statusline::before { content: '// ' / ''; color: var(--rust-deep); }

/* result / verdict dossier plate */
#result { margin-top: 44px; }
.plate {
  position: relative; /* anchors the tier stamp */
  display: flex; gap: 30px; padding: 32px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--emboss);
}

/* Tier stamp: epic/legendary verdicts get a rubber-stamped mark on the file,
   like an evidence stamp. Colours from the rarity ladder brief (AA-checked:
   epic #b48ae0 = 6.04:1, legendary = --rust-hot 5.38:1 on --surface-2). The
   turbulence mask eats patches of ink so it reads stamped, not printed. */
.tier-stamp {
  position: absolute; top: 20px; right: 22px; z-index: 1;
  transform: rotate(-11deg);
  font-family: var(--mono); font-weight: 700;
  font-size: clamp(15px, 2.6vw, 21px); letter-spacing: 0.2em;
  text-transform: uppercase; white-space: nowrap;
  padding: 7px 16px 6px; border: 3px double currentColor; border-radius: 5px;
  pointer-events: none;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='70'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.32' numOctaves='3'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='0.45 0.9 1 0.8 1 1 0.65 1 1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='70'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.32' numOctaves='3'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='discrete' tableValues='0.45 0.9 1 0.8 1 1 0.65 1 1 1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
}
.tier-stamp[hidden] { display: none; }
.tier-stamp.epic { color: #b48ae0; }
.tier-stamp.legendary { color: var(--rust-hot); }
/* katakana stamps run long; tighten so レジェンダリー fits the corner */
html[lang="ja"] .tier-stamp { letter-spacing: 0.08em; font-size: clamp(14px, 2.4vw, 18px); }
/* The slam: stamps land, they do not fade in. The transition lives on the
   .revealed state ONLY: a new lookup drops .revealed for two frames to reset
   the plate, and a base-state transition (with its 340ms delay) would swallow
   that reset and skip the slam on every lookup after the first. */
#result.revealed .tier-stamp { transition: opacity 200ms var(--ease-out) 340ms, transform 200ms var(--ease-out) 340ms; }
#result:not(.revealed) .tier-stamp { opacity: 0; transform: rotate(-11deg) scale(1.9); }

/* Jackpot: when an epic/legendary stamp lands, the plate takes the hit
   (pokies-payout shudder) and the confetti canvas bursts from the stamp.
   The shake is keyed to the stamp's 340ms slam delay so impact and recoil
   read as one event. Reduced-motion drops all of it (see below). */
@keyframes plate-hit {
  0%, 54% { transform: none; }
  56% { transform: translate(2px, 3px) rotate(0.35deg); }
  62% { transform: translate(-3px, -2px) rotate(-0.3deg); }
  68% { transform: translate(2px, -2px) rotate(0.2deg); }
  74% { transform: translate(-1px, 2px); }
  80%, 100% { transform: none; }
}
#result.revealed .plate.jackpot { animation: plate-hit 980ms var(--ease-out) 1; }
.confetti-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 2;
}
@media (prefers-reduced-motion: reduce) {
  #result.revealed .plate.jackpot { animation: none; }
  .confetti-canvas { display: none; }
}
/* // SAMPLE stamp: the sample lookup (76561197960266064) is a SYNTHETIC
   base-sweep seed — NOT a real account — that decodes to bald Lucia, the Rust
   community's "Brinda" jab. This mark brands the plate so no screenshot of it
   circulates as a real person. Anchored to the text column (plate-main), it
   sits as a stamp line ABOVE the eyebrow: never the top-right corner the tier
   stamp owns, and clear of the portrait — which reflows to the plate top on
   mobile (order:-1). Deliberately dim (--rust-deep); alt-text '' via the
   content pair so screen readers skip the punctuation. */
.plate.sample .plate-main::before {
  content: '// SAMPLE' / '';
  display: block;
  font-family: var(--mono); font-weight: 700; font-size: 11px;
  letter-spacing: 0.24em; text-transform: uppercase; color: var(--rust-deep);
  margin-bottom: 8px;
}
.plate-main { flex: 1 1 58%; min-width: 0; }
.eyebrow { font-family: var(--mono); font-size: 12px; letter-spacing: 0.26em; color: var(--steel); text-transform: uppercase; }
.name {
  font-size: clamp(56px, 13vw, 96px); line-height: 0.92; color: var(--rust);
  text-transform: uppercase; text-shadow: var(--stamp-shadow); margin: 8px 0 2px;
}
.sub-verdict { font-size: clamp(22px, 4vw, 30px); color: var(--bone); text-transform: uppercase; margin-bottom: 18px; }
/* Dossier grid, ported from share.css .dossier (drift-guarded by
   test/frontpage-flow.test.js). Two equal columns give the block a spine;
   space-between rows stretched label and value to opposite edges of the
   column and read as a generic table. LEFT-aligned, unlike the share page's
   centred cells: the plate is the card's own composition (record left,
   photo right) and the card is rigorously left-aligned. */
.traits {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 20px; margin-top: 12px; padding-top: 16px;
  border-top: 1px solid var(--line-strong);
}
/* An odd cell count strands the last cell in column 1; span it. Front-page
   rows are gender/race/hair/[beard]/shade, so MALES are the odd count (5) —
   half the roster. (The share page's odd case is female; its dossier shows
   different rows. The selector is the same, the biology is opposite.) */
.trait:last-child:nth-child(odd) { grid-column: 1 / -1; }
.trait { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.trait dt { font-family: var(--mono); font-size: 11px; letter-spacing: 0.16em; color: var(--steel); text-transform: uppercase; }
.trait dd { font-family: 'Oswald', sans-serif; font-size: 18px; letter-spacing: 0.04em; color: var(--bone); text-transform: uppercase; margin: 0; }
.verified { font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; color: var(--dim); text-transform: uppercase; margin-top: 16px; }
/* Terminal readout mark, echoing the share page's terminal-readout voice. CSS
   content, not a string change: 11-language cost of zero. The '/ ""' is
   the alt-text form: it keeps the mark out of the accessibility tree.
   Where the syntax is unsupported (older Firefox) the WHOLE declaration is
   invalid and dropped, so those browsers render no mark at all — graceful,
   it is pure chrome. */
.verified::before { content: '// ' / ''; color: var(--rust-deep); }
.subject-id + .verified { margin-top: 4px; }
/* The whisper under Spoon's honest bald-Lucia result → the Brinda he lost.
   Reads like a readout line but is a button; overrides the global button
   chrome. --rust-hot on --bg is 5.38:1 (AA). Only ever shown for his seed. */
.brinda-nod {
  display: inline-block; margin-top: 14px; padding: 0;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em;
  text-transform: uppercase; text-align: left; white-space: nowrap;
  color: var(--rust-hot); background: none; border: 0; cursor: pointer;
}
.brinda-nod:hover, .brinda-nod:focus-visible { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
.brinda-nod:active { transform: none; }
.brinda-nod[hidden] { display: none; }
.evidence { flex: 0 0 38%; margin: 0; display: flex; flex-direction: column; }
/* Two-layer evidence stack. #mugshot is the instant static base;
   #mugshot-card is the /card/ render cropped to its portrait box and faded
   in on load — armed only where the card-art probe passes (see rustmug.js).
   Crop geometry is share.css's measured numbers (337x400 box at x798 y42 on
   the 1200x630 card) — drift-guarded by test/frontpage-flow.
   Aspect moves 3/4 -> 337/395 so both layers fill the same frame; the base
   mug is 3:4 and object-fit: cover trims a sliver, not face.
   (v2 retired the front-page exhibit; its #specimen-card twin left these
   selectors with it — the overlay pattern lives on here alone.) */
.evidence-photo { position: relative; border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; aspect-ratio: 337 / 400; background: #000; }
.evidence-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
#mugshot-card {
  position: absolute; width: 356.08%; height: auto; max-width: none;
  left: -236.80%; top: -10.50%;
  opacity: 0; transition: opacity 220ms var(--ease-out);
}
#mugshot-card[hidden] { display: none; }
#mugshot-card.card-in { opacity: 1; }
@media (prefers-reduced-motion: reduce) { #mugshot-card { transition: none; } }
.evidence-photo.egg { background: #fff; }
.evidence-photo.egg img { object-fit: contain; }
.share[hidden] { display: none; }
.evidence-tag { font-family: var(--mono); font-size: 11px; letter-spacing: 0.13em; color: var(--steel); text-transform: uppercase; margin-top: 8px; }

/* share */
.share { margin-top: 24px; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
#share-done { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--rust-hot); }

/* 3D viewer trigger (lazy, models-gated; the "View in 3D" button lives here) */
.viewer3d { margin-top: 24px; border-top: 1px solid var(--line); padding-top: 20px; display: flex; flex-direction: column; align-items: center; gap: 16px; }
.viewer3d[hidden] { display: none; }

/* "Did you know?" trivia: the LAST thing inside #result (r4) — a quiet
   pre-footer sign-off, not another mid-page divider. Wider margin-top than
   .share/.viewer3d's 24px functional breaks: this one closes the result
   view, so it gets more air before its own rule. footer's own clamp(96vh…)
   margin-top adds the rest of the breathing room down to the site chrome.
   Label in brand orange so the fact reads as content, not chrome.
   --rust-hot, not --rust: 5.38:1 vs 4.07:1 on --surface-2 (AA needs 4.5). */
.didyouknow { margin-top: 40px; border-top: 1px solid var(--line); padding-top: 20px; }
.didyouknow[hidden] { display: none; }
.dyk-label { font-family: var(--mono); font-size: 13px; letter-spacing: 0.16em; color: var(--rust-hot); text-transform: uppercase; }
.dyk-fact { color: var(--bone); font-size: 16px; line-height: 1.55; margin: 8px 0 0; max-width: 62ch; }

/* 3D viewer modal (Wave 4): native <dialog>, ResizeObserver-driven canvas.
   ~90vw/90vh on desktop, fullscreen on mobile, dossier border + emboss like .plate. */
.viewer-dialog {
  width: 90vw; height: 90vh; max-width: 90vw; max-height: 90vh;
  /* the global `* { margin: 0 }` reset kills the UA dialog's centring margin:auto,
     so restore it explicitly (position:fixed + inset:0 + margin:auto = centred) */
  margin: auto;
  padding: 0; overflow: hidden; color: var(--bone);
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-radius: var(--r-md); box-shadow: var(--emboss);
}
.viewer-dialog::backdrop { background: rgba(8, 5, 3, 0.74); }
.viewer-stage { position: relative; width: 100%; height: 100%; }
/* the canvas container fills the stage; touch-action:none so one-finger orbit and
   pinch-zoom never scroll or zoom the page underneath */
.viewer-stage .viewer3d-mount { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; touch-action: none; }
.viewer-stage canvas { display: block; width: 100%; height: 100%; background: transparent; cursor: grab; }
.viewer-stage canvas:active { cursor: grabbing; } /* grabbing while the pointer is down */
/* The canvas is a tab stop (arrows spin, +/- zoom, 0 resets), so it needs a
   visible focus ring like every other control. Inset, because the canvas fills
   the stage to the edge and an outer ring would be clipped by the dialog. */
.viewer-stage canvas:focus-visible { outline: 2px solid var(--rust); outline-offset: -2px; }

/* exit — top-right dossier tab */
.viewer-close {
  position: absolute; top: 12px; right: 12px; z-index: 3;
  width: auto; padding: 7px 15px; font-size: 12px; letter-spacing: 0.12em;
  background: rgba(20, 16, 13, 0.82); color: var(--bone);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
}
.viewer-close:hover { background: var(--rust); color: #fff; border-color: var(--rust); }

/* control bar — bottom centre */
.viewer-controls {
  position: absolute; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 3;
  display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; max-width: calc(100% - 24px);
}
.viewer-controls button {
  width: auto; padding: 8px 14px; font-size: 12px; letter-spacing: 0.1em;
  background: rgba(20, 16, 13, 0.82); color: var(--bone-dim);
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
}
.viewer-controls button:hover { background: var(--rust); color: #fff; border-color: var(--rust); }
.viewer-controls button[aria-pressed="true"] { background: var(--rust-deep); color: #fff; border-color: var(--rust); }

/* loading badge (during three.js import + mount) */
.viewer-loading {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 2;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--steel);
}
.viewer-loading[hidden] { display: none; }

/* interaction hint — model-viewer-style sideways wiggle, auto-dismissed on first drag */
.viewer-hint {
  position: absolute; left: 50%; top: 64%; transform: translateX(-50%); z-index: 2;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 0 16px; text-align: center; pointer-events: none;
}
.viewer-hint[hidden] { display: none; }
.viewer-hint-text {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--bone); background: rgba(8, 5, 3, 0.62); padding: 6px 12px; border-radius: var(--r-sm);
}
.viewer-hint-glyph {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line-strong); background: rgba(20, 16, 13, 0.72) center / 18px 18px no-repeat;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23e8ddd0' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12a9 9 0 1 0 3-6.7'/%3E%3Cpath d='M3 4v5h5'/%3E%3C/svg%3E");
  animation: rm-viewer-wiggle 1.7s var(--ease-out) infinite;
}
@keyframes rm-viewer-wiggle {
  0%, 100% { transform: translateX(-7px); }
  50% { transform: translateX(7px); }
}

/* reveal motion */
#result .name, #result .sub-verdict, #result .trait, #result .evidence { transition: opacity 220ms var(--ease-out), transform 220ms var(--ease-out), filter 220ms var(--ease-out); }
#result:not(.revealed) .name { opacity: 0; transform: scale(1.05); filter: blur(3px); }
#result:not(.revealed) .sub-verdict, #result:not(.revealed) .evidence { opacity: 0; transform: translateY(8px); }
#result:not(.revealed) .trait { opacity: 0; transform: translateY(8px); }
#result.revealed .trait:nth-child(2) { transition-delay: 40ms; }
#result.revealed .trait:nth-child(3) { transition-delay: 80ms; }
#result.revealed .trait:nth-child(4) { transition-delay: 120ms; }
#result.revealed .trait:nth-child(5) { transition-delay: 160ms; }

/* faces (the /faces registry document — index carries none of this in v2) */
.faces-grid figure { margin: 0; }
.faces-grid img {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover; display: block;
  border-radius: var(--r-sm); border: 1px solid var(--line);
}
/* The static screenshots were shot under a bright blue noon sky and fight
   the rust-and-bone page. Pull them toward the palette in CSS: cheap,
   reversible, zero legal surface. Tuned by eye against the card backdrops;
   captions sit OUTSIDE the images so no text contrast is touched. /card/
   imagery (the result overlay) is NEVER graded — it grades itself
   server-side once the layer library ships.
   The img's own 1px --line border sits INSIDE the filter and tints with it.
   Considered and accepted: --line is an already-dark desaturated brown that
   moves a few RGB steps (decorative, no text-contrast requirement), and the
   markup surgery to exclude it (border on figure wraps the caption; wrapper
   divs x34) costs far more than the shift is worth. */
.faces-grid img { filter: saturate(0.62) sepia(0.22) brightness(0.94) contrast(1.04); }
.faces { margin-top: 64px; }
.faces > p { color: var(--bone-dim); margin: 8px 0 18px; max-width: 640px; }
/* 0,2,0 so the promoted h2 groups keep their rust regardless of any generic
   .faces heading styling (faces.html promotes these to h2 for its h1 page). */
.faces .faces-group { color: var(--rust); font-family: 'Oswald', sans-serif; font-size: 15px; letter-spacing: 0.12em; text-transform: uppercase; margin: 22px 0 10px; }
/* Registry, not gallery: 26 named identities read as index cards. auto-fill
   because the group counts run 5/5/3/5/5/3 — fixed column counts strand
   orphans, and ragged last rows are how real files look. Names step up to
   Oswald so each cell reads name-first, photo-second. */
.faces-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.faces-grid figcaption {
  font-family: 'Oswald', 'Arial Narrow', sans-serif; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--bone); font-size: 14px;
  margin-top: 5px; text-align: center;
}

/* faces.html's own search form container — the input itself shares
   #steam-input's rules above (it posts a zero-JS GET to / instead of an
   in-page submit, but must look and behave identically). */
.faces-search { display: flex; gap: 10px; margin: 18px 0 26px; }

/* 404: the punchline and the instruction are two beats, not one block.
   Both paragraphs inherit zero margin, so without this they butt together and
   read as a single run-on. --bone-dim is the .sub register (already used on
   --bg elsewhere): the joke keeps full weight, the utility line steps back. */
.notfound-cta { margin-top: 14px; color: var(--bone-dim); max-width: 52ch; }

/* /steamid: the two example addresses are literals shown as terminal
   output — mono, boxed on the dossier surface, deliberately untranslated
   (URL shapes are language-neutral; keeping them literal is what makes
   the helper cost one sentence instead of a page of strings). */
.id-examples {
  margin: 22px 0; padding: 16px 20px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--emboss);
}
.id-examples p {
  font-family: var(--mono); font-size: 14px; letter-spacing: 0.04em;
  color: var(--bone); margin: 4px 0;
  /* the profile example is a 47-char unbroken literal — without a break
     opportunity it forces the layout wider than a phone, and overflow-x:
     clip HIDES the loss instead of scrolling it */
  overflow-wrap: anywhere;
}

/* /steamid's way back is the page's one action — the form.button string
   ("Show me the face") worn as a real CTA, mirroring share.css's .cta.
   Bare, it rendered as a UA-default blue anchor: rgb(0,0,238) on --bg
   measures ~1.8:1 — an AA failure (visited purple is worse). Measured,
   not assumed: #14100d on --rust-hot is 6.14:1; hover/focus swap to
   --rust is 4.65:1 — both clear AA (share.css documents the same pair).
   min-height keeps the 48px touch target. */
.back-cta {
  font-family: 'Oswald', 'Arial Narrow', sans-serif; font-weight: 600;
  font-size: 17px; letter-spacing: 0.14em; text-transform: uppercase;
  text-decoration: none;
  color: #14100d; background: var(--rust-hot);
  padding: 14px 30px; border-radius: var(--r-sm); box-shadow: 0 2px 0 var(--rust-deep);
  min-height: 48px; display: inline-flex; align-items: center;
}
.back-cta:hover, .back-cta:focus-visible { background: var(--rust); }
.back-cta:focus-visible { outline: 2px solid var(--bone); outline-offset: 3px; }

/* faces.html's back-home wordmark link (index's own h1 wordmark isn't a
   link; this small variant is, since the page's h1 is the registry heading). */
.brand-home {
  font-family: 'Oswald', 'Arial Narrow', sans-serif; font-size: 28px;
  letter-spacing: 0.04em; color: var(--bone); text-decoration: none;
}
.brand-home span { color: var(--rust); }

/* feedback: file a report */
.feedback { margin-top: 64px; padding: 28px 32px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r-md); box-shadow: var(--emboss); }
.feedback h2 { color: var(--bone); }
.fb-sub { color: var(--bone-dim); margin: 6px 0 18px; }
.fb-type { border: 0; padding: 0; margin: 0 0 14px; display: flex; gap: 10px; }
.fb-type label { cursor: pointer; }
.fb-type input { position: absolute; opacity: 0; pointer-events: none; }
.fb-type span {
  display: inline-block; padding: 8px 18px;
  font-family: 'Oswald', 'Arial Narrow', sans-serif; font-size: 14px; letter-spacing: 0.1em; text-transform: uppercase;
  background: var(--surface); color: var(--bone-dim);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: background 160ms var(--ease-out), color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.fb-type input:checked + span { background: var(--rust); color: #fff; border-color: var(--rust); }
.fb-type input:focus-visible + span { outline: 2px solid var(--rust); outline-offset: 1px; }
#fb-msg, #fb-email {
  width: 100%; padding: 12px 14px; font-size: 15px;
  background: var(--surface); color: var(--bone);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  font-family: inherit; resize: vertical;
}
#fb-msg:focus, #fb-email:focus { outline: 2px solid var(--rust); outline-offset: 1px; border-color: var(--rust); }
.fb-row { display: flex; gap: 10px; margin-top: 12px; }
.fb-row input { flex: 1 1 auto; }
.fb-trap { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.fb-done { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--rust-hot); margin-top: 14px; }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* Footer floor: the anchor's counterpart — a viewport-proportional gap keeps
   the two-line footer reading as the composition's base, not a caption glued
   to the status line. */
footer { margin-top: clamp(96px, 16vh, 200px); border-top: 1px solid var(--line); padding-top: 18px; color: var(--dim); }
footer p { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; margin-top: 6px; }
footer a { color: var(--bone-dim); }
footer a:hover { color: var(--rust-hot); }
.linklike {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--bone-dim); text-decoration: underline; text-underline-offset: 2px;
}
.linklike:hover { background: none; color: var(--rust-hot); }
.linklike:active { transform: none; }

/* Narrow phones: the horizontal brand-mark + RUSTMUG lockup runs out of room
   and the wordmark clips. Stack it vertically — CENTRED now, matching the v2
   splash axis (v1 kept it left for the dossier design) — shrink the mark and
   drop the h1 floor so RUSTMUG stays fully visible from 320px up. */
@media (max-width: 440px) {
  .brand-lockup { flex-direction: column; align-items: center; gap: 10px; }
  .brand-mark { height: 56px; }
  .hero h1 { font-size: clamp(40px, 13vw, 52px); }
}

@media (max-width: 560px) {
  /* The anchor scales down with the viewport: 12vh keeps the lockup high
     enough that search + cluster + status line all sit in the first screen
     on a phone. Hero gap trim kept from v1 — small screens still can't
     afford 28px between tagline and search. */
  main { padding-top: clamp(56px, 12vh, 120px); }
  .hero { margin-bottom: 24px; }
  #check-form, .faces-search { flex-direction: column; }
  button { width: 100%; }
  .fb-row { flex-direction: column; }
  .linklike { width: auto; }
  .plate { flex-direction: column; gap: 22px; padding: 22px 18px; }
  /* Mirrors share.css's mobile .dossier trim: at this width the plate's
     desktop padding leaves .traits columns too narrow for the longest
     translated values (e.g. Polish "kilkudniowy zarost"), so they wrap
     raggedly. Trimming padding/gap here widens the columns to clear them. */
  .traits { gap: 12px 14px; padding-top: 14px; }
  .evidence { order: -1; }
  /* fullscreen viewer on phones: 100vh first, then 100dvh where supported */
  .viewer-dialog {
    width: 100vw; height: 100vh; height: 100dvh;
    max-width: 100vw; max-height: 100vh; max-height: 100dvh;
    border: 0; border-radius: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  #result .name, #result .sub-verdict, #result .trait, #result .evidence,
  #result .tier-stamp { transition: none; }
  #result:not(.revealed) .name, #result:not(.revealed) .sub-verdict,
  #result:not(.revealed) .trait, #result:not(.revealed) .evidence { opacity: 1; transform: none; filter: none; }
  /* keep the rotation, drop only the entrance */
  #result:not(.revealed) .tier-stamp { opacity: 1; transform: rotate(-11deg); }
  button { transition: none; }
  /* static text only: drop the wiggle glyph, the hint copy still shows */
  .viewer-hint-glyph { display: none; }
}

/* language picker (top-right): globe toggle + collapsible dropdown card of
   endonyms. Options are generated by i18n.js from the LANGS registry. */
.lang-switch { position: relative; display: flex; justify-content: flex-end; margin-bottom: 20px; }
#lang-toggle {
  display: inline-flex; align-items: center; gap: 7px;
  width: auto; padding: 6px 11px; font-size: 12px; letter-spacing: 0.04em;
  text-transform: none; white-space: nowrap;
  background: transparent; color: var(--dim);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: color 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
#lang-toggle:hover { background: transparent; color: var(--bone); border-color: var(--line-strong); }
#lang-toggle:active { transform: none; }
#lang-toggle[aria-expanded="true"] { color: var(--bone); border-color: var(--rust); }
.lang-globe { flex: none; opacity: 0.85; }
.lang-caret { font-size: 9px; line-height: 1; opacity: 0.7; }
#lang-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  margin: 0; padding: 5px; list-style: none; min-width: 176px;
  /* scrolls once the list outgrows the viewport (~12 languages planned) */
  max-height: min(60vh, 420px); overflow-y: auto;
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--emboss), 0 14px 30px rgba(0, 0, 0, 0.5);
}
#lang-menu button {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 7px 10px; font-size: 13px; letter-spacing: 0.03em;
  text-align: left; text-transform: none; white-space: nowrap;
  background: transparent; color: var(--bone-dim);
  border: 0; border-radius: var(--r-sm);
  transition: background 120ms var(--ease-out), color 120ms var(--ease-out);
}
#lang-menu button:hover, #lang-menu button:focus-visible { background: var(--surface); color: var(--bone); }
#lang-menu button:active { transform: none; }
#lang-menu button[aria-selected="true"] { color: var(--bone); }
#lang-menu button[aria-selected="true"]::after { content: '✓'; color: var(--rust); font-size: 12px; }
@media (prefers-reduced-motion: no-preference) {
  #lang-menu { transform-origin: top right; animation: lang-pop 120ms var(--ease-out); }
  @keyframes lang-pop { from { opacity: 0; transform: translateY(-3px); } }
}
/* CJK endonyms need their native faces even when the page language is Latin;
   Oswald/Arial Narrow carry no CJK glyphs. Extend when zh-Hant ships:
   [lang^="zh"] → "PingFang TC", "Microsoft JhengHei". */
.lang-switch [lang="ja"] { font-family: "Hiragino Sans", "Yu Gothic", Meiryo, "Noto Sans JP", sans-serif; }
.lang-switch [lang="ko"] { font-family: "Malgun Gothic", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif; }

/* Japanese: Oswald and Arial Narrow have no CJK glyphs, so add OS-resident JP
   faces to the stacks. Latin (e.g. the face name) still resolves to Oswald. */
html[lang="ja"] body {
  font-family: system-ui, -apple-system, "Segoe UI", "Hiragino Sans", "Yu Gothic", Meiryo, "Noto Sans JP", sans-serif;
}
html[lang="ja"] h1, html[lang="ja"] h2, html[lang="ja"] .name, html[lang="ja"] button,
html[lang="ja"] .sub-verdict, html[lang="ja"] .trait dd, html[lang="ja"] .faces-group,
html[lang="ja"] .fb-type span {
  font-family: 'Oswald', 'Arial Narrow', "Hiragino Sans", "Yu Gothic", Meiryo, "Noto Sans JP", sans-serif;
}

/* Korean: same treatment — Oswald has no Hangul glyphs, add OS-resident KR faces */
html[lang="ko"] body {
  font-family: system-ui, -apple-system, "Segoe UI", "Malgun Gothic", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
}
html[lang="ko"] h1, html[lang="ko"] h2, html[lang="ko"] .name, html[lang="ko"] button,
html[lang="ko"] .sub-verdict, html[lang="ko"] .trait dd, html[lang="ko"] .faces-group,
html[lang="ko"] .fb-type span {
  font-family: 'Oswald', 'Arial Narrow', "Malgun Gothic", "Apple SD Gothic Neo", "Noto Sans KR", sans-serif;
}
