/* webfonts (v8.38, self-hosted subsets) — the font stack law:
   Chakra Petch = DISPLAY ONLY (verdict headline, score digits, tier word)
   · Inter = body/UI · system mono = data/logs/HUD micro-labels.
   font-display:swap — text never waits on the webfont. */
@font-face{font-family:'Chakra Petch'; font-style:normal; font-weight:600; font-display:swap;
  src:url('/assets/fonts/chakrapetch-600-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Chakra Petch'; font-style:normal; font-weight:600; font-display:swap;
  src:url('/assets/fonts/chakrapetch-600-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Chakra Petch'; font-style:normal; font-weight:700; font-display:swap;
  src:url('/assets/fonts/chakrapetch-700-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Chakra Petch'; font-style:normal; font-weight:700; font-display:swap;
  src:url('/assets/fonts/chakrapetch-700-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Inter'; font-style:normal; font-weight:400; font-display:swap;
  src:url('/assets/fonts/inter-400-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Inter'; font-style:normal; font-weight:400; font-display:swap;
  src:url('/assets/fonts/inter-400-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Inter'; font-style:normal; font-weight:600; font-display:swap;
  src:url('/assets/fonts/inter-600-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Inter'; font-style:normal; font-weight:600; font-display:swap;
  src:url('/assets/fonts/inter-600-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'Inter'; font-style:normal; font-weight:700; font-display:swap;
  src:url('/assets/fonts/inter-700-latin-ext.woff2') format('woff2');
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'Inter'; font-style:normal; font-weight:700; font-display:swap;
  src:url('/assets/fonts/inter-700-latin.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
:root{
  /* PHOSPHOR world (v8.73 owner ruling, closing the #42/#45 clean-room study):
     neutral near-black ground, achromatic "signal" chrome — the only chroma on
     screen is the tier metals + the severity ramp ("all chroma is meaning").
     Old navy/cyan world lives in git history only (no runtime legacy path).
     Neutral ramp: #0C0E10 ground · #14171A panel · #1B1F23 elevated · #272C31
     hairline · #3A4147 (rgba 58,65,71) the lightest chrome gray — hairlines,
     tracks and quiet borders take THAT triple at whatever alpha they need.
     Slate (rgba 100,116,139 / #475569) is retired everywhere it carried no
     meaning: it is blue, and under Phosphor blue is a tier metal's job. */
  --bg:#0C0E10; --panel:#14171A; --elev:#1B1F23; --panel-border:#272C31; --chrome:#3A4147;
  /* theme tokens (v8.38, re-ruled v8.73): --accent drives BRAND CHROME (links,
     buttons, pills, logo, active nav); the pre-paint head script overrides the
     trio from lg_accent. --cyan stays as the legacy alias — every var(--cyan)
     follows. --accent-ink is the text ON accent-filled CTAs (dark ink works on
     signal white and every picker accent alike).
     SACRED, never themed: tier metals (TIERS in JS) + the severity palette. */
  --accent:#F2F5F7; --accent-hi:#ffffff; --accent-rgb:242,245,247;
  --accent-ink:#0C0E10;
  --cyan:var(--accent);
  --green:#34d399; --amber:#fbbf24; --orange:#fb923c; --red:#f87171;
  --text:#F2F5F7; --dim:#9AA5AE; --dimmer:#7E8890;   /* v8.73: dimmer 5.3:1 on --bg (v8.38 floor was 4.26:1) */
  --mono:ui-monospace,'Cascadia Code','JetBrains Mono',SFMono-Regular,Consolas,monospace;
  --sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;
  --display:'Chakra Petch','Inter',ui-monospace,monospace;
}
*{margin:0;padding:0;box-sizing:border-box}
html{color-scheme:dark}
::selection{background:rgba(var(--accent-rgb),.35); color:#F2F5F7}
/* thin themed scrollbar — cyan thumb on hover */
*{scrollbar-width:thin; scrollbar-color:rgba(58,65,71,.5) transparent}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:rgba(58,65,71,.45); border-radius:4px}
::-webkit-scrollbar-thumb:hover{background:var(--cyan)}
/* accessibility: visible focus ring in theme color */
button:focus-visible, a:focus-visible, select:focus-visible, input:focus-visible, summary:focus-visible{
  outline:2px solid var(--cyan); outline-offset:2px}
body{
  background:var(--bg); color:var(--text); font-family:var(--sans);
  min-height:100vh; overflow-x:hidden;
}
/* font stack law (v8.38): Inter is the body/UI default; mono is RESERVED for
   data, logs and HUD micro-labels; Chakra Petch is the display layer. */
.hdr-meta,.card .value,.card .unit,.card .label,.panel h2,.state-token,
table,.empty,.ev-card,.conf-label,.chart-tip,#proofUrl,.stress-nums,
#stressStatus,.game-chips,.logo .sub,.sheet-head span,
.soon-chip,.auto-hint,.set label,.set select,.set input[type=text]{font-family:var(--mono)}
/* the slashed zero ("9Ø" at phone sizes) stays OFF on the big digits */
.card .value,.grade-badge,.verdict{font-feature-settings:"zero" 0}
/* static grid background (v8.20: no drift — nothing on this page blinks) */
.bg-grid{position:fixed; inset:0; z-index:-2;
  background-image:linear-gradient(rgba(var(--accent-rgb),.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(var(--accent-rgb),.05) 1px,transparent 1px);
  background-size:44px 44px;
  mask-image:radial-gradient(ellipse 90% 70% at 50% 0%,#000 30%,transparent 100%);
}
.bg-glow{position:fixed; z-index:-1; width:900px; height:500px; left:50%; top:-180px;
  transform:translateX(-50%); pointer-events:none;
  background:radial-gradient(ellipse at center,rgba(var(--accent-rgb),.13),transparent 65%);
}
.wrap{max-width:1180px; margin:0 auto; padding:26px 20px 60px}

/* header */
header{display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:12px; margin-bottom:16px}
.logo{display:flex; align-items:center; gap:14px}
.logo .mark{width:28px; height:28px; display:block}
.logo h1{font-size:26px; letter-spacing:.35em; font-weight:800;
  /* v8.73: wordmark gradient ends on the accent itself (the old fixed #34d399
     tail was the navy-world signature; green now belongs to severity only) */
  background:linear-gradient(90deg,var(--accent-hi),var(--accent) 40%,var(--accent));
  -webkit-background-clip:text; background-clip:text; color:transparent;
  text-shadow:0 0 34px rgba(var(--accent-rgb),.35);}
.logo .sub{display:block; font-size:10px; letter-spacing:.28em; color:var(--dim); -webkit-text-fill-color:var(--dim);
  text-transform:uppercase; font-weight:400; text-shadow:none}
.live-dot{width:10px;height:10px;border-radius:50%;background:var(--green);
  box-shadow:0 0 12px var(--green)}
.live-dot.bad{background:var(--red); box-shadow:0 0 12px var(--red)}
.hdr-meta{text-align:right; font-size:11px; color:var(--dim); line-height:1.7; min-width:0;
  font-variant-numeric:tabular-nums}
.hdr-meta b{color:var(--cyan); font-weight:600}
.hdr-meta .who{color:var(--dimmer); font-size:10px; letter-spacing:.04em;
  max-width:420px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.hdr-nav{display:flex; gap:6px; justify-content:flex-end; align-items:center; margin-bottom:7px}
.nav-link{padding:4px 12px; text-decoration:none; margin-left:4px}
.nav-link:hover{color:var(--cyan)}
/* v8.55: header LEADERBOARD is desktop-only — too prominent on phones
   (the footer leaderboard link stays on all screens) */
@media(max-width:639px){.hdr-nav .nav-link{display:none}}
@media(max-width:639px){.hdr-nav .lang-sw{display:none} /* v8.64.4: specificity fix — base .lang-sw{display:inline-flex} came later at equal specificity and won */}
/* locale switcher (v8.37): compact EN | PT text toggle, active locale cyan */
.lang-sw{display:inline-flex; align-items:center; margin-left:8px;
  font-size:9.5px; letter-spacing:.12em}
.lang-sw a{color:var(--dim); text-decoration:none; padding:4px 7px}
.lang-sw a:hover{color:var(--text)}
.lang-sw a.on{color:var(--cyan)}
.lang-sw .sep{color:var(--dimmer)}
/* header path visualizer (v8.14): You · ISP ──•── node (auto) — the probe
   packet sits statically centered on the wire (v8.20: no travel animation);
   measured time + samples live in the line's tooltip */
/* the measured path, as a path: one endpoint each side of the wire. min-width:0
   on the flex line (and on the endpoints) is what keeps a long ISP name from
   pushing the header past a 320 px viewport — max-width alone never shrinks a
   flex item below its content (#84.5 / v8.80 hero-gate overflow). */
.path{display:flex; align-items:center; gap:9px; justify-content:flex-end;
  max-width:min(480px,100%); min-width:0}
.path .endpoint{color:var(--dim); min-width:0; max-width:180px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.path .wire{position:relative; flex:0 0 auto; width:64px; height:1px;
  background:linear-gradient(90deg,rgba(58,65,71,.5),rgba(var(--accent-rgb),.8),rgba(58,65,71,.5))}
.path .pkt{position:absolute; top:-2px; left:30px; width:5px; height:5px; border-radius:50%;
  background:var(--cyan); box-shadow:0 0 6px var(--cyan); opacity:.7}
/* v8.78.1: never drop the wire entirely — without it the two endpoints mash
   into one line ("Helsinki, Finland France, EU"). A short wire keeps the
   you→node path readable as a separator at phone widths. */
@media(max-width:680px){.path .wire{width:18px}.path .pkt{display:none}}

/* unified toast pill — the ONLY transient notification channel, reserved for
   moments that matter: share prompts (severe-spike evidence, flex). Infra
   events (RTC fallback/reconnect, throttle, tab-hidden pause) are NEVER
   toasts — quiet inline states only (v8.15.1).
   Fixed top-center, one at a time, ~8 s. */
#shareToast{position:fixed; left:50%; top:14px; transform:translate(-50%,-160%); z-index:60;
  display:flex; align-items:center; gap:12px; max-width:min(520px,94vw);
  background:rgba(27,31,35,.96); border:1px solid rgba(var(--accent-rgb),.35); border-radius:12px;
  padding:12px 14px; box-shadow:0 8px 40px rgba(0,0,0,.6);
  transition:transform .3s ease-out}
#shareToast.show{transform:translate(-50%,0)}
#shareToast .t-text{flex:1; font-size:11.5px; line-height:1.5; color:var(--text)}
#shareToast .t-text b{display:block; color:var(--cyan); letter-spacing:.04em}
#shareToast .t-sub{color:var(--dim)}
#toastAction{white-space:nowrap}
#toastClose{background:none; border:none; color:var(--dimmer); cursor:pointer; font-size:13px; padding:4px}
#toastClose:hover{color:var(--text)}
/* #43: at ≤480 px the centered/max-width pill left the text column too
   narrow for the Share+Close buttons beside it (one-word-per-line), and
   top:14px sat squarely on the header logo. Full-width pill, text on its
   own row, actions row below it right-aligned; repositioned to clear the
   header (measured .logo bottom ≤68 px at 320-480 px — see
   docs/orchestration/knowledge). translateY(calc(-100% - <top>)) hides the
   pill fully above the viewport regardless of how tall its content is. */
@media(max-width:480px){
  #shareToast{left:10px; right:10px; top:134px; max-width:none; width:auto;
    flex-wrap:wrap; row-gap:8px; transform:translateY(calc(-100% - 134px))}
  #shareToast.show{transform:translateY(0)}
  #shareToast .t-text{flex:1 1 100%; font-size:12px}
  #toastAction{margin-left:auto}
}

/* challenge banner (v8.18) — slim fixed overlay slot, top-center BELOW the
   toast slot (toast z-index 60 takes precedence). Never layout-pushing;
   the amber accent separates the dare from the cyan toast. */
#challengeBar{position:fixed; left:50%; top:64px; transform:translate(-50%,calc(-100% - 80px)); z-index:50;
  display:flex; align-items:center; gap:10px; max-width:min(520px,94vw);
  background:rgba(27,31,35,.96); border:1px solid rgba(251,191,36,.45); border-radius:12px;
  padding:9px 14px; box-shadow:0 8px 40px rgba(0,0,0,.6);
  transition:transform .3s ease-out}
#challengeBar.show{transform:translate(-50%,0)}
#challengeBar .cb-text{flex:1; font-size:11.5px; line-height:1.5; color:var(--text);
  letter-spacing:.04em; transition:opacity .2s}
#challengeBar .cb-text b{color:var(--amber); font-weight:700}
#chBarAction{white-space:nowrap}
#chBarClose{background:none; border:none; color:var(--dimmer); cursor:pointer; font-size:13px; padding:4px}
#chBarClose:hover{color:var(--text)}

/* ============================================================================
   THE HERO (v8.78, issue #70) — instrument + rank object
   ----------------------------------------------------------------------------
   The v8.70 "tier owns the frame" banner is retired: the frame it colored no
   longer exists. Its rule survives in a stronger form — tier metal appears ONLY
   on the rank object (border, ring, tier word, verb), the instrument is fully
   achromatic, and severity lives in exactly one state pill plus the chart's own
   indicators. Nothing here animates on a loop and no state change moves layout:
   every text slot reserves its tallest occupant at every width.
   ========================================================================== */

/* --tier is written by verdict.js from tierOf(score); neutral chrome until a
   verdict exists, so the object is never wearing metal it has not earned. */
:root{--tier:#7E8890}

.hero-split{display:grid; grid-template-columns:1fr 330px; gap:14px; align-items:start;
  margin-bottom:16px}
.hero-main{min-width:0}
.rail{position:sticky; top:14px}

/* ---------- the instrument: the NOW ---------- */
.instrument{display:flex; flex-direction:column; margin-bottom:0}
.phead{display:flex; justify-content:space-between; align-items:center; gap:10px; margin-bottom:10px}
.phead h2{margin:0}
.phead .legend{display:flex; gap:14px; font-size:9.5px; letter-spacing:.08em; text-transform:none;
  color:var(--dimmer); flex-wrap:wrap}

/* the live status strip: the state word sits WITH its sentence and its
   evidence, on the chart panel — one reading, not a banner and a graph. */
.strip{display:flex; align-items:flex-start; gap:11px; padding:0 0 11px; margin-bottom:11px;
  border-bottom:1px solid rgba(58,65,71,.35)}
.strip .pill{flex:0 0 auto; font-family:var(--mono); font-size:9px; font-weight:700; letter-spacing:.2em;
  padding:4px 11px; border-radius:999px; border:1px solid currentColor; background:rgba(12,14,16,.5);
  white-space:nowrap; margin-top:2px; min-width:132px; text-align:center; line-height:15px;  /* 132: JA キャリブレーション中 must fit the reserved slot (review F6) */
  transition:opacity .2s, color .3s, border-color .3s}
/* the text column takes the row, always: as a plain flex item it sized to its
   own content, so the sentence and subline changed WIDTH between states. */
.strip .txt{flex:1; min-width:0; font-variant-numeric:tabular-nums}
/* TWO-line reserves at EVERY width — measured against the longest string any
   state ships at the narrowest column (PT/JA run longer than EN).
   v8.80: act 1 leaves .say EMPTY (the strip is silent until the first verdict),
   and the slot still reserves its two lines, so 0:10 fills a hole instead of
   pushing the chart down. */
.strip .say{font-family:var(--display); font-weight:700; font-size:17px; letter-spacing:.01em;
  line-height:1.3; color:var(--text); min-height:45px; transition:opacity .2s;
  font-feature-settings:"zero" 0}
.strip .ev{font-size:12.5px; color:var(--dim); line-height:1.35; margin-top:4px; min-height:34px;
  transition:opacity .2s}
/* the chart's footnotes: measured-so-far (left) and the rule the amber line
   actually enforces (right). Both are reserved single-line slots. */
.chartfoot{display:flex; justify-content:space-between; gap:12px; margin-top:9px; flex-wrap:wrap;
  font-family:var(--mono); font-size:10px; color:var(--dimmer); letter-spacing:.03em;
  font-variant-numeric:tabular-nums; min-height:14px; line-height:1.4}
/* Both footnotes are FIXED BOXES: half the row each, two text lines reserved
   (v8.83, #93). They are not fixed because the row is pretty — they are fixed
   because their own copy changes length mid-session and a slot whose geometry
   depends on the length of its occupant is not a slot (§Layout stability). The
   spike rule alone jumps ~76 px the moment the first baseline exists ("spikes
   count only when latency stays over the line" → "…over 84 ms, held 3 s"), and
   with content-sized boxes that dragged the right-hand footnote's left edge
   across the panel and, between ~900 and ~1100 px, wrapped the whole row onto a
   second line. The reservation is what makes the wrap free: at those widths the
   longest rule string (PT, 328 px) does not fit half a row, so it takes two
   lines and the box never changes height. */
.chartfoot>span{flex:1 1 0; min-width:0; min-height:28px}
/* the confidence half of the footnote carries meaning, so it takes --dim.
   Act 1 empties it, so it keeps a line box either way — at 360 the footnotes
   stack, and a collapsed empty span moved the row by a pixel between acts
   (hero-gate fixed-slot law is exact, and it is right to be). */
.chartfoot .ramp{color:var(--dim)}
.chartfoot .ramp:empty::before{content:"\00a0"}
.chartfoot .rule{text-align:right}
/* act 1's one editorial line — the anti-speed-test manifesto. Dies with act 1. */
.antispeed{margin-top:12px; padding-top:11px; border-top:1px solid rgba(58,65,71,.35);
  font-family:var(--mono); font-size:12px; color:var(--dim); letter-spacing:.02em; line-height:1.6}
.antispeed[hidden]{display:none}
.antispeed b{color:var(--text); font-weight:700; margin-right:6px}

/* ---------- THE CARD, assembling (v8.80, #85) ----------
   ONE object, present from second zero, in ONE home (rail on desktop, thumb
   dock below 900 px). Act 1 fills it with the ghost ladder + the slots that
   stamp in; act 3 swaps those for the ring and the tier word; the 60 s bar
   completes it in place. Occupants swap, the object stays. */
.rank{position:relative; background:#0C0E10; border:1px solid var(--tier); border-radius:14px;
  padding:14px 16px; transition:border-color .9s ease}
.rank .dgrid{position:absolute; inset:0; opacity:.5; pointer-events:none; border-radius:14px;
  background-image:linear-gradient(rgba(242,245,247,.05) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(242,245,247,.05) 1px,transparent 1px);
  background-size:44px 44px}
.rhead{position:relative; display:flex; justify-content:space-between; align-items:baseline; gap:8px;
  font-family:var(--mono); font-size:9px; letter-spacing:.28em; color:var(--dimmer); min-height:12px}
/* the stamp is the card's publication state in one word, then its duration
   credential: PROVISIONAL while the number is a draft, TESTED · N MIN once the
   session has cleared the bar (v8.83, #93). It updates at MINUTE granularity,
   which is what keeps its crossfade calm. */
.stamp{flex:0 0 auto; font-size:8.5px; letter-spacing:.2em; color:var(--dimmer); white-space:nowrap;
  border:1px solid rgba(58,65,71,.85); border-radius:999px; padding:3px 9px; line-height:1.2;
  transition:opacity .2s, color var(--swapdur,.2s), border-color var(--swapdur,.2s)}
.stamp:empty{visibility:hidden}
.stamp.done{color:var(--text); border-color:rgba(242,245,247,.5)}
/* the word dissolves, the PILL DOES NOT (v8.83, #93). The 1:00 change rides the
   ring's own fuse — a generic opacity crossfade blanked the whole badge for a
   frame and finished while the arc was still dashed, so one still in the beat
   claimed a settled reading over a visibly draft ring. Fading the TEXT COLOR
   instead keeps the outline on screen the whole way (the slot is a frame, never
   a blink), and swapState() stretches the beat to the fuse. Last of the .stamp
   rules on purpose: it has to beat .stamp.done's color at equal specificity. */
.stamp.dissolve{color:transparent}

/* the ghost tier ladder — static, no motion, tier metals at ghost weight. It
   pre-teaches the scale the shared card is graded on while there is nothing to
   show yet. Ghost weight has a FLOOR (.85): below it WOOD drops under AA-large
   on this ground. */
.ladder{position:relative; margin-top:10px}
.ladder[hidden]{display:none}
.ltier{display:flex; align-items:center; gap:9px; font-family:var(--display); font-weight:700;
  font-size:13.5px; letter-spacing:.2em; padding:5px 0; opacity:.85}
.ltier svg{flex:0 0 auto}
.ltier em{margin-left:auto; font-family:var(--mono); font-size:9.5px; font-style:normal;
  letter-spacing:.1em; color:var(--dim)}

/* act 3: the ring + the tier word, in the same object the ladder was in */
.cardbody{position:relative; display:flex; align-items:center; gap:16px; margin-top:10px}
.cardbody[hidden]{display:none}
.dring{position:relative; flex:0 0 auto; width:120px; height:120px}
.dring canvas{display:block; width:100%; height:100%}
.tcol{min-width:0; flex:1}
.rank .tierword{font-family:var(--display); font-weight:700; font-size:19px; letter-spacing:.2em;
  color:var(--tier); display:flex; align-items:center; gap:7px; min-height:24px; transition:color .3s}
.rank .tierword svg{width:17px; height:17px; flex:0 0 auto}
/* what the card is carrying, in the card's own terms — never truncated:
   truncating the payload line is the wrong economy, and PT/JA run longer. */
/* v8.83: the slot reserves THREE lines, its tallest occupant at rail width —
   the incident door ("leave it running while you play…") and the settled line
   ("Game traffic under this number. Longer run, firmer rank.") both run to
   three, and a two-line reservation let the board line and the verb under them
   step down a row on a text swap. Free on the desktop rail: the 120 px ring is
   still the taller half of .cardbody, so the card does not grow. */
.carries{font-size:11.5px; color:var(--dim); line-height:1.5; margin-top:6px; min-height:52px;
  transition:opacity .2s, color var(--swapdur,.2s)}
/* the confidence state is as loud as the score (glance panel, Rex): "holds
   until 1:00" is suspense, not a hedge, so it reads at text weight. */
.carries.hold{color:var(--text)}
/* the card's two state texts share ONE motion grammar (verdict.js swapState):
   a 200 ms crossfade normally, and at the bar the ring's own fuse — the line
   dissolves and the settled sentence forms as the arc lands solid, instead of
   being finished and legible while the ring was still visibly a draft. Last of
   the .carries rules: it has to beat .hold's color at equal specificity. */
.carries.dissolve{color:transparent}

/* act 1: the slots that stamp in as they are earned */
.slots{position:relative; display:grid; grid-template-columns:1fr 1fr; gap:8px 12px; margin-top:12px}
.slots[hidden]{display:none}
.slot .k{display:block; font-family:var(--mono); font-size:8.5px; letter-spacing:.18em;
  color:var(--dimmer); text-transform:uppercase}
.slot .v{display:block; font-family:var(--mono); font-size:13px; font-weight:600; color:var(--text);
  margin-top:2px; font-variant-numeric:tabular-nums;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.slot .v.empty{color:var(--dimmer); font-weight:400}

/* the ONE countdown: one track (the 60 s test), one label naming the next
   milestone. The notch marks where the first verdict lands. */
.track{position:relative; height:3px; border-radius:2px; background:rgba(58,65,71,.45); margin:12px 0 6px}
.track[hidden]{display:none}
.track i{display:block; height:100%; width:0; background:var(--text); border-radius:2px;
  transition:width .3s linear}
.track .notch{position:absolute; top:-3px; width:1px; height:9px; background:rgba(242,245,247,.5)}
.track .notch[hidden]{display:none}
.trlabel{position:relative; font-family:var(--mono); font-size:10px; letter-spacing:.16em;
  color:var(--dim); text-align:right; font-variant-numeric:tabular-nums; min-height:14px}
.trlabel:empty{visibility:hidden}

/* the board line: news, printed once, with the promise it rests on */
.boardline{position:relative; font-family:var(--mono); font-size:10.5px; color:var(--dim);
  margin-top:11px; line-height:1.6; border-top:1px solid rgba(58,65,71,.4); padding-top:9px}
.boardline[hidden]{display:none}
.boardline b{color:var(--text); font-weight:600}
.boardline .priv{display:block; color:var(--dimmer); font-size:9.5px; letter-spacing:.02em}

/* the one loud verb — and it only exists once the card is real */
.rankverb{position:relative; display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; margin-top:12px; min-height:48px; border-radius:11px; background:var(--accent);
  color:var(--accent-ink); font-family:var(--mono); font-weight:700; font-size:13px;
  letter-spacing:.14em; border:1px solid transparent; padding:0 12px; cursor:pointer;
  transition:background .18s, box-shadow .18s, border-color .18s}
.rankverb[hidden]{display:none}
/* the verb arrives at the END of the ring's fuse, not inside it (v8.83, #93).
   The page's brightest object appearing at ~t450 of a 900 ms beat upstaged the
   climax it is the reward for. Its BOX is reserved from the first complete
   frame — this is opacity only, so the card's layout is final the moment the
   card completes, and nothing moves when the verb becomes visible. One shot,
   it ends, and LOWPOWER/reduced motion never gets it (no fuse, no delay). */
.rankverb.arriving{animation:verbIn .32s ease-out var(--verbdelay,.9s) both}
@keyframes verbIn{from{opacity:0}to{opacity:1}}
.rankverb:hover{background:var(--accent-hi); box-shadow:0 0 18px rgba(var(--accent-rgb),.4)}
.rankverb:active{background:#C6CFD6; box-shadow:none}
.rankverb:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
/* while the news is bad the READING is the brightest object on screen: the
   incident acts strip the object of its metal border and its filled verb. */
body.incident .rank{border-color:rgba(58,65,71,.8); box-shadow:none}
body.incident .rank .dgrid{opacity:.28}
body.incident .rankverb{background:transparent; color:var(--text); border-color:rgba(58,65,71,1);
  box-shadow:none}
body.incident .rankverb:hover{background:rgba(242,245,247,.07); border-color:var(--text);
  box-shadow:none}
body.incident .rankverb:active{background:rgba(242,245,247,.14)}

/* ---------- the fixed dock (<=900 px) ---------- */
.dockfade{display:none}
@media(max-width:900px){
  /* no grid below 900: a zero-height rail still occupied a grid row and ate a
     phantom gap that moved the content under it when the object arrived. */
  .hero-split{display:block}
  .rail{position:static}
  .rank{position:fixed; z-index:40; left:10px; right:10px; bottom:10px;
    display:flex; flex-wrap:wrap; align-items:center; gap:0 11px; padding:9px 13px 11px;
    box-shadow:0 18px 50px rgba(0,0,0,.6)}
  /* the ladder and the act-1 slots are DEFERRED on the phone: five ghost rows
     would make the dock half the viewport, and unlabelled glyphs read as
     decoration (glance panel). The dock's act-1 job is the countdown. */
  .ladder,.slots{display:none}
  .rhead{flex:1 0 100%; font-size:8.5px; letter-spacing:.22em}
  .cardbody{flex:1 1 140px; min-width:0; gap:11px; margin-top:7px}
  .dring{width:52px; height:52px}
  .rank .tierword{font-size:14px; letter-spacing:.18em; min-height:18px}
  .rank .tierword svg{width:13px; height:13px}
  .carries{font-size:10.5px; margin-top:2px; min-height:0}
  /* the verb takes its own full row in the dock: at every width <=900 it wraps
     anyway, and a content-width button left-aligned on an empty row reads as an
     accident rather than the surface's one loud verb. */
  .rankverb{flex:1 0 100%; width:auto; margin-top:9px; min-height:46px; font-size:12.5px}
  .track{flex:1 0 100%; margin:9px 0 5px}
  .trlabel{flex:1 0 100%; text-align:left}
  /* act 1's countdown is the one thing keeping a first-time player on the page,
     so in act 1 it is the dock's loud element rather than its quietest
     (formative glance: "the line keeping me here is the smallest thing"). */
  body:not(.act3) .trlabel{font-size:12px; letter-spacing:.14em; color:var(--text)}
  .boardline{flex:1 0 100%; margin-top:7px; padding-top:7px; font-size:10px}
  /* the page scrolls UNDER the dock, which is what a bottom bar is for. The
     fade says "there is more below" instead of "a control got cut in half";
     nothing is ever permanently occluded — .wrap reserves the dock's measured
     height plus headroom (heroDockChanged in main.js). */
  .dockfade{display:block; position:fixed; left:0; right:0; height:26px; z-index:39;
    pointer-events:none; bottom:var(--dockh,0px);
    background:linear-gradient(to bottom,rgba(12,14,16,0),rgba(12,14,16,.88))}
}
@media(max-width:600px){
  .rank{left:0; right:0; bottom:0; border-radius:14px 14px 0 0; border-bottom:0}
  /* (v8.83 deleted the .carries.flavor hide here with the tier roast it hid:
     nothing assigns that class any more, and the settled line is NEWS — it
     keeps its place in the dock at every width.) */
  .boardline{font-size:9.5px; line-height:1.5}
  .boardline .priv{font-size:9px}
}

/* metric cards */
.cards{display:grid; grid-template-columns:repeat(auto-fit,minmax(170px,1fr)); gap:16px; margin-bottom:8px}
.card{background:var(--panel); border:1px solid var(--panel-border); border-radius:12px;
  padding:14px 16px; backdrop-filter:blur(8px); position:relative; overflow:hidden;
  transition:border-color .3s; font-variant-numeric:tabular-nums}
.card:hover{border-color:rgba(var(--accent-rgb),.4)}
.card .label{font-size:9.5px; letter-spacing:.22em; color:var(--dim); text-transform:uppercase; margin-bottom:8px}
.card .value{font-size:26px; font-weight:700; letter-spacing:.02em; line-height:1;
  text-shadow:0 0 22px currentColor; transition:color .3s, text-shadow .4s}
#mRtt.glow-down{text-shadow:0 0 16px rgba(52,211,153,.95),0 0 46px rgba(52,211,153,.65)}
#mRtt.glow-up{text-shadow:0 0 16px rgba(248,113,113,.95),0 0 46px rgba(248,113,113,.65)}
.card .unit{font-size:12px; color:var(--dim); font-weight:400; margin-left:3px; text-shadow:none}
/* the hint WRAPS, it never ellipsises (glance panel): at 360 the two-column
   grid clipped "your normal: 45 ms · at n…" mid-sentence, which reads as a bug
   rather than as economy. Two reserved lines, so nothing moves when it wraps. */
.card .hint{font-size:10px; color:var(--dimmer); margin-top:8px; min-height:13px; line-height:1.35}
@media(max-width:820px){.card .hint{min-height:27px}}
/* panels */
.panel{background:var(--panel); border:1px solid var(--panel-border); border-radius:14px;
  padding:16px 18px; backdrop-filter:blur(8px); margin-bottom:16px}
.panel h2{font-size:10.5px; letter-spacing:.26em; color:var(--dim); text-transform:uppercase; margin-bottom:12px;
  display:flex; justify-content:space-between; align-items:center}
.panel h2 .legend{display:flex; gap:14px; font-size:9.5px; letter-spacing:.08em; text-transform:none}
.legend span{display:flex; align-items:center; gap:5px}
.sw{width:14px;height:3px;border-radius:2px;display:inline-block}
.swgap{height:8px;border-radius:1px;background:repeating-linear-gradient(45deg,rgba(148,163,184,.55) 0 2px,transparent 2px 5px);border:1px solid rgba(148,163,184,.35)}
canvas.chart{width:100%; display:block}
/* explicit CSS heights: without them a width:100% canvas scales by its
   intrinsic ratio until prepCanvas runs — a big first-paint layout shift */
#rttChart{height:260px}
#spkChart,#lossChart,#hoursChart{height:150px}
.chart-tip{position:fixed; z-index:60; display:none; pointer-events:none;
  background:rgba(12,14,16,.94); border:1px solid rgba(var(--accent-rgb),.3); border-radius:8px;
  padding:6px 10px; font-size:11px; line-height:1.6; color:var(--text); white-space:nowrap;
  box-shadow:0 4px 18px rgba(0,0,0,.5)}
.split{display:grid; grid-template-columns:1fr 1fr 1fr; gap:16px}
@media(max-width:1000px){.split{grid-template-columns:1fr 1fr}}
@media(max-width:680px){.split{grid-template-columns:1fr}}
/* narrow screens: panel headers stack — title line, then the legend as a
   single horizontal scroll row (never a wrap); log tables scroll
   horizontally like the game chips */
@media(max-width:680px){
  .panel h2{flex-direction:column; align-items:stretch; row-gap:8px}
  .panel h2 .legend{flex-wrap:nowrap; overflow-x:auto; gap:14px; padding-bottom:2px}
  .panel h2 .legend>span{flex:0 0 auto; white-space:nowrap}
}
.log-scroll{overflow-x:auto}

/* event log */
table{width:100%; border-collapse:collapse; font-size:12px}
th{font-size:9.5px; letter-spacing:.2em; color:var(--dim); text-transform:uppercase;
  text-align:left; padding:6px 10px; border-bottom:1px solid rgba(var(--accent-rgb),.12)}
td{padding:7px 10px; border-bottom:1px solid rgba(58,65,71,.08); color:#C6CFD6}
tr:hover td{background:rgba(var(--accent-rgb),.04)}
.sev{display:inline-block; padding:2px 9px; border-radius:20px; font-size:10px; font-weight:700; letter-spacing:.08em}
.sev.minor{background:rgba(251,191,36,.14); color:var(--amber); border:1px solid rgba(251,191,36,.35)}
.sev.major{background:rgba(251,146,60,.14); color:var(--orange); border:1px solid rgba(251,146,60,.35)}
.sev.severe{background:rgba(248,113,113,.14); color:var(--red); border:1px solid rgba(248,113,113,.4)}
.empty{color:var(--dimmer); font-size:12px; padding:14px 10px}
tr.outage td:first-child{border-left:3px solid var(--red)}
.fbtn{background:transparent; border:1px solid rgba(58,65,71,.35); color:var(--dim);
  border-radius:6px; padding:3px 10px; font-size:9.5px;
  letter-spacing:.12em; cursor:pointer; text-transform:uppercase; transition:all .2s}
.fbtn:hover{color:var(--text)}
.fbtn.on{border-color:var(--cyan); color:var(--cyan); background:rgba(var(--accent-rgb),.08)}
/* v8.66 (320 px audit): the log filter row must wrap — unw wrapped it was the
   page's widest min-content and forced the .duo grid track past the viewport
   (327 px EN / 372 px PT at a 320 px screen). Buttons keep their 44 px tap
   height; they just take a second row on narrow phones. */
.log-tools{display:flex; gap:6px; align-items:center; flex-wrap:wrap}

/* spike & outage log — event cards (v8.14): severity chip, "+N ms above
   normal", duration, timestamp. Cards are the shareable evidence atom. */
.ev-card{display:flex; align-items:center; gap:12px; padding:10px 12px; margin-bottom:8px;
  border:1px solid rgba(58,65,71,.15); border-radius:10px; background:rgba(12,14,16,.4)}
.ev-card.outage{border-left:3px solid var(--red)}
.ev-what{flex:1; font-size:12px; color:#C6CFD6; min-width:0}
.ev-sub{font-size:10px; color:var(--dimmer); margin-top:3px}
.ev-when{font-size:11px; color:var(--dim); flex:0 0 auto; font-variant-numeric:tabular-nums}

/* "Do something with it" — the verbs column (v8.14, sits beside the log) */
.duo{display:grid; grid-template-columns:1.5fr 1fr; gap:16px}
@media(max-width:900px){.duo{grid-template-columns:1fr}}
.verb{display:flex; align-items:center; gap:12px; width:100%; text-align:left; cursor:pointer;
  background:rgba(12,14,16,.4); border:1px solid rgba(58,65,71,.15); border-radius:10px;
  padding:11px 13px; margin-bottom:8px; color:var(--text);
  transition:border-color .2s}
.verb:hover{border-color:rgba(var(--accent-rgb),.45)}
a.verb{text-decoration:none}
.verb .v-ico{color:var(--cyan); flex:0 0 16px; text-align:center}
.verb .v-ico svg{width:15px; height:15px; fill:currentColor; vertical-align:-2px}
.verb .v-txt{flex:1; min-width:0}
.verb .v-t{display:block; font-size:12px; font-weight:700; letter-spacing:.03em}
.verb .v-s{display:block; font-size:10px; color:var(--dimmer); margin-top:3px; line-height:1.45}
.verb .v-go{color:var(--dimmer); flex:0 0 auto}
.verb.soon{opacity:.55; cursor:default}
.verb.soon:hover{border-color:rgba(58,65,71,.15)}
.soon-chip{font-size:8.5px; letter-spacing:.14em; color:var(--dim); text-transform:uppercase;
  border:1px solid rgba(58,65,71,.35); border-radius:4px; padding:1px 6px; margin-left:8px}

/* chart window "auto" hint (permanent — AUTO is the only window mode, v8.54) */
   /* v8.35: never split the chip mid-wrap */

.panel .purpose{font-size:10.5px; color:var(--dimmer); margin-top:8px}
.panel .purpose .info{cursor:help; color:var(--dim)}

/* READY FOR — per-game chips */
.game-chips{display:flex; flex-wrap:wrap; gap:8px; min-height:29px}
.chip{display:inline-flex; align-items:center; gap:7px; padding:5px 11px; border-radius:999px;
  border:1px solid var(--panel-border); background:rgba(12,14,16,.5);
  font-size:11px; letter-spacing:.03em; white-space:nowrap; cursor:default;
  transition:border-color .3s, color .3s}
.chip .glyph{font-weight:700}
.chip.good{border-color:rgba(52,211,153,.55); color:var(--green); box-shadow:0 0 10px rgba(52,211,153,.22)}
.chip.ok{border-color:rgba(52,211,153,.3); color:var(--green)}
.chip.borderline{border-color:rgba(251,191,36,.45); color:var(--amber)}
.chip.bad{border-color:rgba(248,113,113,.5); color:var(--red)}
.chip.na{color:var(--dim)}
@media(max-width:680px){.game-chips{flex-wrap:nowrap; overflow-x:auto; padding-bottom:4px}}

/* settings */
details.panel summary{cursor:pointer; font-size:10.5px; letter-spacing:.26em; color:var(--dim);
  text-transform:uppercase; list-style:none; user-select:none}
details.panel summary::before{content:'▸ '; color:var(--cyan)}
details.panel[open] summary::before{content:'▾ '}
details.panel summary:hover{color:var(--cyan)}
.set-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:16px; margin-top:14px}
.set label{display:block; font-size:10px; letter-spacing:.14em; color:var(--dim); text-transform:uppercase; margin-bottom:6px}
.set select,.set input[type=text]{width:100%; background:rgba(12,14,16,.8); border:1px solid rgba(var(--accent-rgb),.2);
  color:var(--text); border-radius:8px; padding:8px 10px; font-family:var(--mono); font-size:12px; outline:none}
.set select:focus,.set input:focus{border-color:var(--cyan)}
.set .note{font-size:10px; color:var(--dimmer); margin-top:5px}
.btn{background:rgba(var(--accent-rgb),.1); border:1px solid rgba(var(--accent-rgb),.4); color:var(--cyan);
  border-radius:8px; padding:8px 18px; font-size:11px; letter-spacing:.14em;
  cursor:pointer; text-transform:uppercase; transition:all .2s}
.btn:hover{background:rgba(var(--accent-rgb),.22); box-shadow:0 0 18px rgba(var(--accent-rgb),.25)}
.btn.danger{border-color:rgba(248,113,113,.4); color:var(--red); background:rgba(248,113,113,.08)}
.btn.danger:hover{background:rgba(248,113,113,.18); box-shadow:0 0 18px rgba(248,113,113,.2)}
.toggle{display:flex; align-items:center; gap:10px; font-size:12px; color:var(--text); cursor:pointer}
.toggle input{accent-color:var(--cyan); width:16px; height:16px; cursor:pointer}

/* one-time coral glow when new shareable evidence lands (severe spike
   captured / 10 min of visible data) — pulse once, never bounce. v8.78: the
   score module is gone; the rank object wears the pulse now. */
.rank.evpulse{animation:evPulse 1.4s ease-out 1}
@keyframes evPulse{0%{box-shadow:0 0 0 0 rgba(248,113,113,0)}
  30%{box-shadow:0 0 36px 6px rgba(248,113,113,.5)}100%{box-shadow:0 0 0 0 rgba(248,113,113,0)}}
@media (prefers-reduced-motion:reduce){.rank.evpulse{animation:none}}

/* buttons row + small panels */
.btn-row{display:flex; gap:12px; flex-wrap:wrap; align-items:center}
.panel p.desc{font-size:12px; color:#9AA5AE; line-height:1.8; margin-bottom:12px; max-width:760px}
.panel p.desc b{color:var(--text)}

/* stress test */
#stressStatus{font-size:11px; color:var(--dim); margin-top:10px; min-height:15px}
.stress-result{margin-top:14px; display:flex; gap:20px; flex-wrap:wrap; align-items:center}
.grade-badge{font-family:var(--display); font-size:32px; font-weight:700; width:66px; height:66px; border-radius:14px; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center; border:1px solid currentColor;
  text-shadow:0 0 20px currentColor; background:rgba(12,14,16,.6)}
.stress-nums{font-size:12px; color:#C6CFD6; line-height:2}
.stress-nums b{font-size:15px}
.stress-note{font-size:10.5px; color:var(--dimmer); margin-top:10px; max-width:640px; line-height:1.7}

/* content sections */
.content h3{font-size:12.5px; color:var(--cyan); margin:16px 0 6px; letter-spacing:.04em}
.content h3:first-of-type{margin-top:4px}
.content p{font-size:12.5px; line-height:1.9; color:#9AA5AE; margin-bottom:10px; max-width:860px}
.content b{color:var(--text)}
.content ul{font-size:12.5px; line-height:1.9; color:#9AA5AE; margin:0 0 10px 18px; max-width:860px}

/* explainer */
.expl{font-size:12px; line-height:2; color:#9AA5AE; columns:2; column-gap:36px}
@media(max-width:760px){.expl{columns:1}}
.expl b{color:var(--cyan)}
/* theme picker (v8.38): accent swatches + AMOLED flip — footer row only
   (the share-sheet twin died in the v8.53 sheet declutter). 44 px tap
   targets; the dot wears the swatch color. */
.theme-row{display:flex; align-items:center; justify-content:center; gap:2px; margin-top:30px}
.theme-row .tlabel{font-size:9px; letter-spacing:.3em; color:var(--dimmer); margin-right:10px}
.theme-row .tsep{color:var(--dimmer); margin:0 6px; font-size:9px}
.tsw{width:44px; height:44px; display:inline-flex; align-items:center; justify-content:center;
  background:none; border:none; cursor:pointer; padding:0}
.tsw::before{content:''; width:16px; height:16px; border-radius:50%; background:var(--sw);
  transition:transform .15s, box-shadow .15s}
.tsw:hover::before{transform:scale(1.15)}
.tsw.on::before{box-shadow:0 0 0 2px var(--bg), 0 0 0 3.5px var(--sw), 0 0 14px var(--sw)}
.tsw.amoled::before{background:#000; border:1.5px solid rgba(58,65,71,.7)}
footer{text-align:center; font-size:10px; color:var(--dimmer); letter-spacing:.2em; margin-top:14px}
footer a{color:var(--dim); text-decoration:none}
footer a:hover{color:var(--cyan)}
footer.legal-nav{margin-top:8px; font-size:9px; letter-spacing:.16em; color:var(--dimmer)}
footer.legal-nav a{color:var(--dimmer)}
footer.legal-nav a:hover{color:var(--cyan)}
.content a{color:var(--cyan)}

/* share center — bottom sheet (mobile-first) */
#shareModal{display:none; position:fixed; inset:0; z-index:50; background:rgba(6,7,8,.85);
  backdrop-filter:blur(6px); align-items:flex-end; justify-content:center}
#shareModal.show{display:flex}
.share-sheet{background:var(--panel); border:1px solid var(--panel-border); border-bottom:none;
  border-radius:16px 16px 0 0; padding:14px 16px calc(16px + env(safe-area-inset-bottom));
  width:100%; max-width:640px; max-height:92vh; overflow-y:auto; text-align:center;
  animation:sheetUp .22s ease-out}
@keyframes sheetUp{from{transform:translateY(28px); opacity:0}to{transform:none; opacity:1}}
@media(min-width:700px){
  #shareModal{align-items:center; padding:20px}
  .share-sheet{border-radius:14px; border-bottom:1px solid var(--panel-border)}
}
.sheet-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:10px}
.sheet-head span{font-size:10.5px; letter-spacing:.26em; color:var(--dim); text-transform:uppercase}
.sheet-head button{background:none; border:none; color:var(--dim); font-size:15px; cursor:pointer; padding:4px 8px}
.sheet-head button:hover{color:var(--text)}
/* card preview + its copy chip (v8.83, #97 — supersedes the v8.53 corner
   glyph): copy-image-to-clipboard is SHEET chrome on its own strip above the
   card, never drawn over the artifact (a button on the card is in every
   screenshot of that card). Subordinate to COPY LINK by weight — ghost chip,
   mono label — and a 44 px tap target on phones (see the mobile block). */
.card-chrome{display:flex; justify-content:flex-end; margin-bottom:6px}
.card-wrap{position:relative; margin-bottom:12px}
.share-sheet canvas{width:100%; height:auto; display:block; border-radius:10px;
  border:1px solid rgba(var(--accent-rgb),.2)}
#btnShareGlyph{display:inline-flex; align-items:center; gap:8px; min-height:38px;
  padding:0 13px; background:transparent; border:1px solid var(--panel-border);
  border-radius:9px; color:var(--dim); cursor:pointer; transition:all .15s;
  font-family:var(--mono); font-size:11px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase}
#btnShareGlyph svg{width:16px; height:16px; fill:currentColor}
#btnShareGlyph:hover{color:var(--text); border-color:var(--chrome); background:rgba(242,245,247,.06)}
#btnShareGlyph:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
/* COPY LINK — the sheet's single primary CTA (v8.53): full-width hero on the
   existing accent gradient, visually dominant over the channel row below */
#btnShareLink{width:100%; box-sizing:border-box; padding:13px 18px;
  font-size:13px; font-weight:700; letter-spacing:.18em; text-transform:uppercase;
  background:linear-gradient(180deg, var(--accent-hi), var(--cyan)); border:1px solid var(--cyan);
  color:var(--accent-ink); border-radius:10px; box-shadow:0 0 16px rgba(var(--accent-rgb),.3)}
#btnShareLink:hover{background:var(--accent-hi); box-shadow:0 0 18px rgba(var(--accent-rgb),.4)}
/* channel row (v8.19): one brand-logo icon per channel, one click each.
   Inline SVG (CC0 simple-icons-style paths) in currentColor — quiet dim at
   rest, brand color on hover; tiny labels stay under the icons, tooltips on
   title. No external assets/fonts. Holds at 320 px (5 × 56 px + gaps). */
.share-channels{display:flex; flex-wrap:wrap; justify-content:center; gap:4px; margin-top:10px}
.chan{display:flex; flex-direction:column; align-items:center; gap:4px; width:52px; padding:8px 0 6px;
  background:none; border:1px solid transparent; border-radius:10px; color:var(--dim);
  font-size:8.5px; letter-spacing:.04em; cursor:pointer; transition:all .15s}
.chan svg{width:22px; height:22px; fill:currentColor}
.chan:hover{border-color:var(--panel-border); background:rgba(148,163,184,.06)}
#btnShareX:hover{color:#e7e9ea}
#btnShareDiscord:hover{color:#5865f2}
#btnShareWhatsApp:hover{color:#25d366}
#btnShareRedditPost:hover{color:#ff4500}
#btnShareTelegram:hover{color:#26a5e4}
#shareCallout{margin-top:10px}
#shareCallout .btn{width:100%; border-color:rgba(248,113,113,.45); color:var(--red)}
#shareCallout .btn:hover{background:rgba(248,113,113,.12)}
/* muted footer line (v8.53): the Discord server invite — small, no button */
.share-foot{margin-top:14px; font-size:10px; letter-spacing:.06em; color:var(--dimmer)}
.share-foot a{color:var(--dim); text-decoration:none}
.share-foot a:hover{color:var(--cyan)}
.share-sheet .note{font-size:10px; color:var(--dimmer); margin-top:10px; min-height:13px}
/* #80 — a copy is invisible unless we say so. The confirmation lives in the
   same reserved slot as the quiet note (no layout shift) but wears a green bar
   so it cannot be missed on the way to the Discord window. */
.share-sheet .note.ok{font-family:var(--mono); font-size:12px; font-weight:700; letter-spacing:.1em;
  color:var(--green); border:1px solid rgba(52,211,153,.45); background:rgba(52,211,153,.10);
  border-radius:10px; padding:11px 12px}
.chan.done{color:var(--green); border-color:rgba(52,211,153,.45)}
#btnShareDiscord.done:hover{color:var(--green)}

/* v8.78: the SHARE button is no longer a button — the whole rank object is one
   control and #btnShare is the verb LABEL inside it (.rankverb owns the paint). */
#btnShare{white-space:nowrap; overflow:hidden; text-overflow:ellipsis; transition:opacity .2s}

/* public proof link output */
#proofUrl{flex:1; min-width:240px; background:rgba(12,14,16,.8); border:1px solid rgba(var(--accent-rgb),.2);
  color:var(--cyan); border-radius:8px; padding:8px 10px; font-family:var(--mono); font-size:12px; outline:none}

/* ============ mobile pass (v8.35) — phones are the X-ads landing surface ============
   Owner screenshot-driven: the verdict sentence NEVER truncates (it wraps in
   full; the font clamps down so 2 lines still cover most states — the slot
   min-heights stay, so short states don't shrink the hero). Hero fits above
   the fold: tighter padding, bigger ring (drawScore scales it to 176 px ≤430
   px). Every tap target ≥44 px. dvh + safe-area so iOS chrome / home bar
   never eat the SHARE button or the sheet. */
@media(max-width:680px){
  .wrap{padding:16px 12px 40px}
  header{margin-bottom:10px}
  .logo h1{font-size:20px}
  .hero-split{margin-bottom:12px}

  /* tap targets ≥44 px */
  .fbtn{display:inline-flex; align-items:center; justify-content:center;
    min-height:44px; padding:6px 12px}
  .lang-sw a{display:inline-flex; align-items:center; min-height:44px; padding:6px 10px}
  .btn{display:inline-flex; align-items:center; justify-content:center; min-height:44px}
  #btnShare{display:inline-flex; align-items:center; justify-content:center;
    min-height:48px; padding:12px 18px; font-size:11px}
  .sheet-head button, #toastClose, #chBarClose{min-width:44px; min-height:44px; padding:10px}
  #btnShareGlyph{min-height:44px; padding:0 14px}
  .chan{width:56px; min-height:56px; justify-content:center}

  /* share sheet: internal scroll that can't chain to the page, real dynamic
     viewport height */
  footer{line-height:2.1}
}
/* sheet viewport cap: dvh on phones (iOS chrome-aware), keep 92vh on desktop */
.share-sheet{max-height:100dvh; overscroll-behavior:contain}
@media(min-width:700px){.share-sheet{max-height:92vh}}

/* the vitals grid: four tiles, one home per number (v8.80). The uptime tile and
   the session strip are gone — every fact they carried has another home. */
.vitals{display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:16px}
@media(max-width:820px){.vitals{grid-template-columns:1fr 1fr}}


/* ============ the instrument at phone widths (v8.78, #70) ============
   The legend belongs to the chart, not in front of it: above the plot it showed
   a red LAGSPIKE swatch before any spike existed. Reordering also puts the
   reading first, where a thumb lands. */
@media(max-width:600px){
  .phead{display:contents}
  .instrument h2{order:1; margin-bottom:10px}
  /* align-items:flex-start in a COLUMN flex shrink-wraps the cross axis, which
     let the text column's WIDTH change between states (the fixed-slot law is
     about left+width as much as top+height). Stretch it; only the pill opts out. */
  .instrument .strip{order:2; flex-direction:column; gap:9px; align-items:stretch}
  .instrument .strip .pill{margin-top:0; align-self:flex-start}
  .instrument .strip .say{font-size:16px; min-height:42px}
  .instrument .strip .ev{min-height:34px}
  .instrument canvas.chart{order:3}
  .instrument .legend{order:4; margin-top:11px; justify-content:center; gap:10px 14px}
  /* the footnotes stack at 360 (they never fit side by side once the spike rule
     carries real numbers), so the slot reserves both lines. Each still reserves
     its own two lines (v8.83): at 320-360 the spike rule is a two-line string in
     EN and PT and a one-line string once it carries the number, and the row may
     not shrink when it does. */
  .chartfoot{order:5; flex-direction:column; gap:3px; min-height:59px}
  .chartfoot .rule{text-align:left}
  .antispeed{order:6; font-size:11.5px}
}

/* ============ the sheet's birth state (v8.80, #85) ============
   There is no modal any more. The share sheet's FIRST open after a session
   completes IS the ceremony: the real 1200x630 card strikes once in its tier
   metal and settles, the born line says what happened, and KEEP WATCHING is the
   honest ghost exit for a monitor. One-shot glow that ends (DESIGN.md §Motion). */
.share-sheet .born{font-family:var(--mono); font-size:10.5px; color:var(--dim);
  letter-spacing:.06em; line-height:1.5; min-height:16px; margin:-4px 0 10px}
.share-sheet .born:empty{display:none}
.card-wrap.strike{animation:cardStrike 1.25s ease-out 1}
@keyframes cardStrike{
  0%{box-shadow:0 0 0 1px rgba(var(--tier-rgb,242,245,247),.9),0 0 54px 6px rgba(var(--tier-rgb,242,245,247),.42)}
  100%{box-shadow:0 0 0 1px rgba(var(--tier-rgb,242,245,247),0),0 0 0 0 rgba(var(--tier-rgb,242,245,247),0)}}
@media (prefers-reduced-motion:reduce){.card-wrap.strike{animation:none}
  /* belt and braces: the verb's arrival is already gated on a fuse that never
     starts under reduced motion, and it may never be left invisible if it is */
  .rankverb.arriving{animation:none}}
.sheet-exit{display:flex; justify-content:center; margin-top:12px}
.keepwatching{display:inline-flex; align-items:center; justify-content:center; min-height:48px;
  padding:0 20px; border-radius:11px; background:transparent; color:var(--dim);
  border:1px solid rgba(58,65,71,1); font-family:var(--mono); font-weight:700; font-size:12px;
  letter-spacing:.14em; cursor:pointer; transition:color .18s, border-color .18s, background .18s}
.keepwatching:hover{background:rgba(242,245,247,.07); border-color:var(--text); color:var(--text)}
.keepwatching:focus-visible{outline:2px solid var(--accent); outline-offset:2px}

/* ============ low-power mode (v8.66) — .lowpower on <html>, set by chart.js ============
   Cheap phones (≤4 cores / ≤4 GB / prefers-reduced-motion) get the same truth
   with cheaper pixels: no backdrop blur (the most expensive CSS on weak
   GPUs), no text/box glow, and every transition/animation flattened — the
   reduced-motion experience becomes the default. Layout, colors, copy and
   data are identical; nothing here touches measurement. */
.lowpower .card,.lowpower .panel,.lowpower #shareModal{backdrop-filter:none}
.lowpower .card-wrap.strike{animation:none}
.lowpower .rankverb.arriving{animation:none}
.lowpower .card .value,.lowpower #mRtt.glow-down,.lowpower #mRtt.glow-up,
.lowpower .grade-badge,.lowpower .logo h1{text-shadow:none}
.lowpower .live-dot,.lowpower .live-dot.bad,.lowpower .path .pkt,
.lowpower .chip.good,.lowpower .btn:hover,.lowpower .btn.danger:hover,
.lowpower .rankverb:hover,
.lowpower #btnShareLink,.lowpower #btnShareLink:hover,
.lowpower .tsw.on::before{box-shadow:none}
/* the dock's lift shadow stays: it is depth, not decoration — without it a
   fixed bar reads as content welded to the bottom of the page. */
.lowpower *,.lowpower *::before,.lowpower *::after{
  animation-duration:.01s!important; animation-iteration-count:1!important;
  transition-duration:.01s!important}
