/* ===================================================================
   THE STILLNESS PROTOCOL — brutalist / neon-red / monospace dread
   =================================================================== */

@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700;800&display=swap');

:root{
  --black:#000000;
  --near-black:#050505;
  --red:#ff1a1a;
  --red-dim:#7a0d0d;
  --white:#f4f4f4;
  --grey:#8a8a8a;
  --grey-dim:#3a3a3a;
  color-scheme: dark;
}

*{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
[hidden]{ display:none !important; }

html,body{
  margin:0; padding:0; width:100%; height:100%;
  background:var(--black); color:var(--white);
  font-family:'JetBrains Mono', 'Courier New', monospace;
  overflow:hidden; overscroll-behavior:none; touch-action:none;
  user-select:none; -webkit-user-select:none; -webkit-touch-callout:none;
}

/* ---------- ambient dread texture ---------- */
#noise{
  position:fixed; inset:0; pointer-events:none; z-index:2; opacity:.035; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
#scanline{
  position:fixed; inset:0; pointer-events:none; z-index:3; opacity:.06;
  background:repeating-linear-gradient(to bottom, rgba(255,255,255,.6) 0px, transparent 1px, transparent 3px);
}

/* ---------- screens ---------- */
.screen{
  position:fixed; inset:0; display:none; flex-direction:column; align-items:center;
  padding:24px; text-align:center; z-index:1;
  animation:screenIn .5s ease both;
  /* body sets touch-action:none to kill pull-to-refresh and rubber-banding
     mid-round — but that also blocks touch scrolling inside every
     descendant, so any screen taller than the viewport had its bottom
     stranded with no way to reach it. Screens pan vertically; nothing else
     does, and a screen whose content fits still cannot scroll at all.
     Vertical centring is done with auto margins on the child rather than
     justify-content, because a centred flex item that overflows gets its
     top clipped and becomes unreachable even when the container scrolls. */
  overflow-y:auto; -webkit-overflow-scrolling:touch; touch-action:pan-y;
  padding-top:calc(24px + env(safe-area-inset-top));
  padding-bottom:calc(24px + env(safe-area-inset-bottom));
}
.screen[data-active="true"]{ display:flex; }
/* A scan sweep on entry — a screen that simply appears reads as a page
   load; one that is scanned in reads as a system showing you something. */
.screen[data-active="true"]::before{
  /* fixed, not absolute: the screen is a scroll container, and an
     absolutely-positioned child translated a full height downwards adds
     that height to its scrollable area — which strands the bottom of the
     aftermath exactly the way the layout tests exist to catch. */
  content:''; position:fixed; inset:0; pointer-events:none; z-index:4;
  background:linear-gradient(to bottom, transparent 0%, rgba(255,26,26,.16) 45%, rgba(255,255,255,.10) 50%, rgba(255,26,26,.16) 55%, transparent 100%);
  animation:scanSweep .62s ease-out 1 both;
}
@keyframes scanSweep{ from{ transform:translateY(-100%); } to{ transform:translateY(100%); } }

/* Slice displacement under a sabotage hit. Brightness alone is smoothed
   away by video compression; a displaced slice survives it. */
body.glitching #noise{ opacity:.14; }
/* screenIn is restated first so the list only grows by one entry:
   animations are matched by position, so the entry screen keeps its
   finished state instead of restarting and re-fading the whole screen
   every time a card lands. */
body.glitching .screen[data-active="true"]{
  animation:screenIn .5s ease both, sliceGlitch .16s steps(2) 2;
}
/* ...except where a thumb is committed. There the displacement is
   applied to each sibling ABOVE the pad instead of to their container,
   so the picture still tears without the pad ever moving under the
   finger holding it. */
body.glitching .screen[data-active="true"]:has(.touch-pad){ animation:screenIn .5s ease both; }
body.glitching .victim-wrap > *:not(.touch-pad){ animation:sliceGlitch .16s steps(2) 2; }
body.shake .victim-wrap > *:not(.touch-pad){ animation:shakeAnim .4s ease; }
/* the pad is always in the DOM, so this has to ask whether the ACTIVE
   screen is one that has a thumb on it — not merely whether a pad
   exists somewhere in the document */
body.shake:has(.screen[data-active="true"] .touch-pad){ animation:none; }
@keyframes sliceGlitch{
  0%{ transform:translate(0,0); clip-path:none; }
  25%{ transform:translate(-5px,2px); clip-path:inset(12% 0 62% 0); }
  50%{ transform:translate(6px,-2px); clip-path:inset(58% 0 18% 0); }
  75%{ transform:translate(-3px,0); clip-path:inset(34% 0 44% 0); }
  100%{ transform:translate(0,0); clip-path:none; }
}
@keyframes screenIn{ from{ opacity:0; } to{ opacity:1; } }

.kicker{
  color:var(--red); letter-spacing:.35em; font-size:11px; font-weight:700;
  text-transform:uppercase; margin:0 0 18px;
}
.blink{ animation:blink 1.4s step-start infinite; }
@keyframes blink{ 50%{ opacity:.25; } }

h1{
  font-size:clamp(28px,7vw,52px); line-height:1.05; margin:0 0 20px; font-weight:800;
  letter-spacing:-.01em; text-transform:uppercase;
}
h2{ font-size:clamp(20px,5vw,32px); margin:0 0 12px; font-weight:800; text-transform:uppercase; }

.lede{ color:var(--grey); font-size:14px; line-height:1.6; max-width:420px; margin:0 auto 34px; }
/* --grey-dim (#3a3a3a) on black is near-invisible on a phone and gone
   entirely once a video compressor has been near it. This is a game
   designed to be filmed, so secondary text uses --grey and --grey-dim is
   reserved for rules, borders and furniture. */
.fine-print{ color:var(--grey); font-size:11px; letter-spacing:.05em; margin-top:16px; min-height:14px; }
.notice-red{ color:var(--red); }
.label{ color:var(--grey); font-size:10px; letter-spacing:.25em; text-transform:uppercase; margin:0 0 8px; }

/* glitch title */
.glitch{ position:relative; }
/* Both halves stay inside the palette — the cyan that used to live on
   ::after was a third colour on the most-seen element in the app.
   They are also invisible except during the spike now: held permanently
   at opacity .7 they were a standing double-image, which with two reds
   reads as a blurry render rather than a glitch. A title that is clean
   96% of the time and tears for two frames reads as deliberate. */
.glitch::before,.glitch::after{
  content:attr(data-text); position:absolute; left:0; top:0; width:100%; overflow:hidden;
  opacity:0; pointer-events:none;
}
.glitch::before{ color:var(--red); clip-path:inset(0 0 58% 0); animation:glitchTop 4.2s infinite steps(1); }
.glitch::after{ color:var(--white); clip-path:inset(58% 0 0 0); animation:glitchBot 5.1s infinite steps(1); }
@keyframes glitchTop{
  0%,93%{ transform:translate(0,0); opacity:0; }
  94%{ transform:translate(-4px,-1px); opacity:.9; }
  96%{ transform:translate(3px,1px); opacity:.9; }
  97%,100%{ transform:translate(0,0); opacity:0; }
}
@keyframes glitchBot{
  0%,90%{ transform:translate(0,0); opacity:0; }
  91%{ transform:translate(3px,1px); opacity:.8; }
  93%{ transform:translate(-3px,-1px); opacity:.8; }
  94%,100%{ transform:translate(0,0); opacity:0; }
}

/* margin:auto centres these while there is room and collapses to 0 when
   there is not, which is what lets .screen scroll to the very bottom
   instead of clipping the top. The old max-height:92vh + overflow-y:auto
   made each of these its own scroll container — nested inside a screen
   that could not be panned at all, which is how content ended up
   unreachable rather than merely tall. */
.crt-wrap{ max-width:480px; width:100%; margin:auto 0; }
.panel{
  max-width:460px; width:100%; margin:auto 0;
  border:1px solid var(--grey-dim); padding:28px 22px; background:var(--near-black);
}
.panel.wide{ max-width:560px; }
.divider{ height:1px; background:var(--grey-dim); margin:22px 0; width:100%; }

/* ---------- buttons ---------- */
button{ font-family:inherit; cursor:pointer; border:none; }
.btn-primary,.btn-ghost,.btn-danger{
  display:block; width:100%; padding:16px 20px; font-size:13px; font-weight:700;
  letter-spacing:.15em; text-transform:uppercase; margin-top:14px; transition:transform .12s ease, opacity .12s ease;
}
.btn-primary{ background:var(--red); color:var(--black); }
.btn-primary:active{ transform:scale(.97); }
.btn-primary:disabled{ background:var(--grey-dim); color:var(--grey); cursor:not-allowed; }
.btn-ghost{ background:transparent; color:var(--white); border:1px solid var(--grey-dim); }
.btn-ghost:active{ transform:scale(.97); border-color:var(--white); }
.btn-danger{ background:transparent; color:var(--red); border:1px solid var(--red); }
.btn-danger:active{ transform:scale(.97); background:rgba(255,26,26,.12); }
.btn-danger:disabled{ color:var(--grey-dim); border-color:var(--grey-dim); cursor:not-allowed; }
.btn-danger.big{ padding:20px; font-size:15px; margin-top:26px; animation:pulseRed 1.6s ease-in-out infinite; }
@keyframes pulseRed{ 0%,100%{ box-shadow:0 0 0 0 rgba(255,26,26,.4);} 50%{ box-shadow:0 0 0 10px rgba(255,26,26,0);} }
.btn-primary.big{ padding:22px; font-size:16px; margin-top:26px; }

/* ---------- lobby ---------- */
.role-readout{ font-size:20px; font-weight:800; color:var(--white); letter-spacing:.05em; margin:0 0 12px; }
#input-stake{
  width:100%; background:var(--black); border:1px solid var(--grey-dim); color:var(--white);
  padding:14px; font-family:inherit; font-size:13px; letter-spacing:.03em; margin-bottom:4px;
}
#input-stake:focus{ outline:none; border-color:var(--red); }
.roster-list{ list-style:none; margin:0; padding:0; text-align:left; font-size:12px; }
.roster-list li{ display:flex; justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--grey-dim); color:var(--grey); }
.roster-list li span.tag{ color:var(--red); font-weight:700; }

/* ---------- victim screen ---------- */
#screen-victim{ background:var(--black); position:relative; }
.victim-wrap{ width:100%; max-width:420px; margin:auto 0; display:flex; flex-direction:column; align-items:center; }
.stake-block{ margin-bottom:26px; }
.stake-label{ color:var(--red); letter-spacing:.35em; font-size:11px; font-weight:700; text-transform:uppercase; margin:0 0 8px; }
.stake-value{ color:var(--white); font-size:clamp(20px,6vw,28px); font-weight:800; letter-spacing:.02em; text-transform:uppercase; margin:0; line-height:1.3; }
/* ---------- THE INSTRUMENT ----------
   Concentric tolerance rings, cardinal registration ticks, a crosshair at
   true centre, a depleting arc around the rim for the countdown, a hollow
   target the reticle has to travel to, and the reticle itself trailing a
   short history so tremor is visible rather than inferred.

   Markup is generated by Effects.mountInstrument(); these are its
   clothes. Everything is stroked heavily on purpose — this gets filmed
   off a second phone in a dim room, and hairlines do not survive that. */
.instrument{
  position:relative; width:min(66vw,272px); height:min(66vw,272px);
  margin:2px 0 10px;
}
.inst-svg{ width:100%; height:100%; display:block; overflow:visible; }

.inst-furniture{ fill:none; }
.inst-ring-outer{ stroke:var(--grey-dim); stroke-width:2; fill:none; }
.inst-ring{ stroke:var(--grey-dim); stroke-width:1.5; stroke-dasharray:3 7; fill:none; opacity:.85; }
.inst-cardinals{ stroke:var(--grey); stroke-width:3; }
.inst-crosshair{ stroke:var(--grey-dim); stroke-width:2; }

/* countdown as a shape: peripheral vision reads a depleting arc long
   before it reads digits, and the eyes are on the reticle */
.inst-arc{
  fill:none; stroke:var(--red); stroke-width:5; stroke-linecap:butt;
  transition:stroke-dashoffset .12s linear;
}

/* how much slack is left, drawn around the *demanded* angle. This
   shrinking through the round is the tolerance ramp made visible. */
.inst-safe{
  fill:rgba(255,255,255,.05); stroke:var(--white); stroke-width:1.5;
  stroke-dasharray:4 5; opacity:.7;
  transition:r .12s linear;
}
.instrument.is-danger .inst-safe{ stroke:var(--red); opacity:1; }

/* where the Grid wants the phone — hollow, so the filled reticle reads
   as "you" and this reads as "there" with no legend needed */
.inst-target{ stroke:var(--white); stroke-width:3; fill:none; }
.instrument.is-pending .inst-target{ stroke:var(--red); animation:targetPulse .6s ease-in-out infinite; }
@keyframes targetPulse{ 0%,100%{ opacity:1; } 50%{ opacity:.25; } }

.inst-trail{
  fill:none; stroke:var(--white); stroke-width:2; opacity:.35;
  stroke-linejoin:round; stroke-linecap:round;
}
.instrument.is-danger .inst-trail{ stroke:var(--red); opacity:.5; }

.inst-reticle-core{ fill:var(--white); stroke:none; transition:fill .12s; }
.inst-reticle-halo{ fill:none; stroke:var(--white); stroke-width:2; opacity:.55; transition:stroke .12s; }
.instrument.is-danger .inst-reticle-core{ fill:var(--red); }
.instrument.is-danger .inst-reticle-halo{ stroke:var(--red); }
/* STEADY collapses the margin instead of moving the target — the whole
   instrument tightens rather than something new appearing */
.instrument.is-steady .inst-ring-outer{ stroke:var(--red); }
.instrument.is-steady .inst-safe{ stroke:var(--red); stroke-dasharray:none; }

/* ---------- the directive: what to do RIGHT NOW ----------
   Above the instrument, where the eyes already are. This used to render
   inside the touch pad, underneath the player's own thumb. */
.directive{
  margin:0 0 6px; min-height:22px;
  font-size:clamp(15px,4.4vw,19px); font-weight:800; letter-spacing:.1em;
  text-transform:uppercase; color:var(--white);
}
.directive.pending{ color:var(--red); animation:directiveIn .28s ease both; }
@keyframes directiveIn{ from{ opacity:0; transform:translateY(-6px);} to{ opacity:1; transform:none;} }

.countdown{
  margin:0 0 14px; font-size:clamp(40px,13vw,62px); font-weight:800; line-height:1;
  color:var(--white); font-variant-numeric:tabular-nums; letter-spacing:.02em;
}
.countdown.urgent{ color:var(--red); }

/* ---------- the touch pad ----------
   A dashed outline with grey text read as an inert box, not a control.
   It is the single thing a first-time player must touch, so it gets a
   real fill, a real border and a pressed state. */
.touch-pad{
  width:100%; padding:22px 18px; min-height:88px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,26,26,.07); border:2px solid var(--red); color:var(--white);
  font-family:inherit; letter-spacing:.18em; text-transform:uppercase;
  transition:background .12s, border-color .12s, transform .1s;
  touch-action:none; -webkit-touch-callout:none;
}
.pad-hint{ font-size:13px; font-weight:700; color:var(--white); }
.touch-pad.active{ background:rgba(255,26,26,.2); border-color:var(--white); transform:scale(.99); }
.touch-pad.active .pad-hint{ color:var(--white); }
#flashbang-overlay{
  position:fixed; inset:0; background:#fff; opacity:0; pointer-events:none; z-index:50; transition:opacity 60ms linear;
}
#flashbang-overlay.flash{ opacity:1; }
#emoji-layer{ position:fixed; inset:0; pointer-events:none; z-index:40; overflow:hidden; }
.floating-emoji{ position:absolute; font-size:34px; animation:floatUp 2.6s ease-in forwards; }
@keyframes floatUp{ 0%{ transform:translateY(10vh) scale(.8); opacity:0;} 15%{ opacity:1;} 100%{ transform:translateY(-100vh) scale(1.2); opacity:0;} }
/* The shake and the glitch used to be applied to <body> and to the
   active screen — both of which are ANCESTORS of the touch pad. Moving
   an ancestor of an element that a finger is actively holding is how you
   get a touchcancel on a thumb that never left the glass, which the
   round then read as letting go. Everything above the pad still moves;
   the pad itself never does. It is better design anyway: the one thing
   the player must hold should be the one thing that stays still. */
.shake{ animation:shakeAnim .4s ease; }
.victim-wrap .touch-pad{ animation:none !important; }
@keyframes shakeAnim{ 10%,90%{ transform:translate3d(-1px,0,0);} 20%,80%{ transform:translate3d(2px,0,0);} 30%,50%,70%{ transform:translate3d(-4px,0,0);} 40%,60%{ transform:translate3d(4px,0,0);} }

/* ---------- solo mode: the Handler ---------- */
#screen-solo-victim{ background:var(--black); position:relative; }

.mute-toggle{
  position:absolute; top:18px; right:18px; z-index:45; width:40px; height:40px;
  background:var(--near-black); border:1px solid var(--grey-dim); color:var(--white);
  font-size:16px; line-height:1; padding:0; border-radius:50%;
}
.mute-toggle:active{ transform:scale(.92); }

.notification-banner{
  position:fixed; top:0; left:0; right:0; z-index:46; pointer-events:none;
  background:var(--black); border-bottom:1px solid var(--red); padding:14px 20px;
  text-align:center; transform:translateY(-100%); transition:transform .25s ease;
}
.notification-banner.show{ transform:translateY(0); }

.handler-transmission{
  position:fixed; top:38%; left:0; right:0; z-index:44; pointer-events:none;
  text-align:center; padding:0 24px; color:var(--white); font-size:15px; font-weight:700;
  letter-spacing:.08em; text-transform:uppercase; text-shadow:0 0 12px rgba(0,0,0,.9);
  opacity:0; transition:opacity .2s ease;
}
.handler-transmission.show{ opacity:1; }

.false-failure-overlay{
  position:fixed; inset:0; z-index:60; background:var(--black); display:flex;
  align-items:center; justify-content:center; padding:24px; text-align:center;
  opacity:0; pointer-events:none; transition:opacity 80ms linear;
}
.false-failure-overlay.show{ opacity:1; }
.false-failure-overlay p{
  margin:0; color:var(--red); font-size:clamp(22px,6vw,36px); font-weight:800;
  letter-spacing:.03em; text-transform:uppercase; line-height:1.3;
}

/* ---------- telemetry (GM / spectator) ---------- */
#gm-timer,#spec-timer{ font-variant-numeric:tabular-nums; color:var(--red); }
.telemetry-grid{ display:grid; grid-template-columns:repeat(3,1fr); gap:14px; margin:18px 0 6px; }
.telemetry-cell{ text-align:left; }
.telemetry-value{ font-size:16px; font-weight:700; margin:0; }
.bar{ height:8px; background:var(--grey-dim); position:relative; overflow:hidden; }
.bar-fill{ position:absolute; top:0; bottom:0; left:50%; width:0%; background:var(--red); transition:width .08s linear, left .08s linear; }

/* The victim's state, on the Gamemaster's and Spectators' screens. Loud
   on purpose while realigning: that window is the one worth timing a
   sabotage into, and a subtle label change would not get looked at in a
   room this noisy. */
.subject-state{
  margin:0 0 14px; padding:10px; text-align:center;
  font-size:13px; font-weight:800; letter-spacing:.2em; text-transform:uppercase;
  border:1px solid var(--grey-dim); color:var(--grey);
  transition:color .12s, border-color .12s;
}
.subject-state.realigning{
  border-color:var(--red); color:var(--red);
  animation:pulseRed 1s ease-in-out infinite;
}

.sabotage-row,.distraction-row{ display:flex; gap:10px; flex-wrap:wrap; }
.sabotage-row button{ flex:1; min-width:130px; }
.distraction-row button{
  flex:0 0 auto; font-size:22px; padding:10px 14px; background:var(--near-black); border:1px solid var(--grey-dim);
}
.distraction-row button:active{ border-color:var(--red); transform:scale(.94); }

.feed-list{ list-style:none; margin:0; padding:0; text-align:left; font-size:11px; color:var(--grey); max-height:110px; overflow-y:auto; }
.feed-list li{ padding:4px 0; border-bottom:1px solid var(--grey-dim); }

/* ---------- aftermath ---------- */
/* Both receipts. #solo-aftermath-canvas previously had no rule of its own
   at all, so it rendered at its intrinsic 1080x1350 — nearly three times
   the width of the phone it was on, and 1636px of content in a 599px box,
   which put RUN IT AGAIN and OPEN A GRID a thousand pixels below the fold.
   Capping height as well as width matters on short viewports: the receipt
   is 4:5, so a width-only cap still eats ~410px of a ~550px usable area
   and strands the actions underneath it. width/height:auto keeps the
   aspect ratio while both caps apply. */
#aftermath-canvas,
#solo-aftermath-canvas{
  display:block; width:auto; height:auto;
  max-width:min(340px, 100%);
  max-height:46vh;
  margin:0 auto 18px; border:1px solid var(--grey-dim); background:#000;
}
.aftermath-actions{ display:flex; gap:10px; width:100%; }
/* the buttons are width:100% block elements; without flex:1/min-width:0
   two visible at once overflow the row rather than sharing it */
.aftermath-actions button{ margin-top:0; flex:1 1 0; min-width:0; }

/* ---------- the outcome hold ----------
   Full-bleed, heavy, high contrast: this is the frame someone's camera is
   pointed at when the round goes wrong, so it has to survive compression
   and be readable from across a room. */
.outcome-hold{
  /* Informational in both of its uses — the 3-2-1 arming beat and the
     outcome hold — so it must never eat input. Without this the arming
     overlay sits on top of the touch pad for two seconds and the victim
     cannot get their thumb down. */
  pointer-events:none;
  position:fixed; inset:0; z-index:70; background:var(--black);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; padding:24px; gap:10px;
  opacity:0; transition:opacity 90ms linear;
}
.outcome-hold.show{ opacity:1; }
.outcome-hold-kicker{
  margin:0; color:var(--grey); font-size:12px; font-weight:700;
  letter-spacing:.3em; text-transform:uppercase;
}
.outcome-hold-main{
  margin:0; font-size:clamp(34px,11vw,64px); font-weight:800; line-height:1.05;
  letter-spacing:.02em; text-transform:uppercase; color:var(--white);
}
.outcome-hold.is-fail .outcome-hold-main{ color:var(--red); }
.outcome-hold-cause{
  margin:0; font-size:clamp(15px,4.6vw,22px); font-weight:700;
  letter-spacing:.14em; text-transform:uppercase; color:var(--white);
}
.outcome-hold.show .outcome-hold-main{ animation:stampIn .22s cubic-bezier(.2,1.4,.4,1) both; }
@keyframes stampIn{ from{ transform:scale(1.35); opacity:0; } to{ transform:scale(1); opacity:1; } }

/* ---------- room code ----------
   Word codes are longer than base36 was, so this wraps and shrinks rather
   than overflowing on a narrow phone. */
.room-code{
  margin:0 0 8px; font-size:clamp(19px,6.2vw,30px); font-weight:800;
  letter-spacing:.04em; line-height:1.15; color:var(--white);
  text-transform:uppercase; word-break:break-word;
}
.room-barcode{ margin:0 auto 14px; color:var(--grey); max-width:100%; }
.room-barcode svg{ display:block; margin:0 auto; max-width:100%; }
/* A QR needs a light ground and hard edges — it is the one place in this
   app that cannot be black-on-black. */
.room-qr{ margin:0 auto 14px; width:168px; max-width:70%; }
.room-qr svg{ display:block; width:100%; height:auto; border:6px solid var(--white); background:var(--white); }

/* ---------- frame furniture ----------
   L-brackets at the corners of a panel, so a screen reads as a page from
   a file rather than a div. Four separate marks rather than one stretched
   border: a stretched SVG smears its stroke on whichever axis is longer,
   and that is most visible at 320px. */
.panel, .crt-wrap{ position:relative; }
.frame-corner{
  position:absolute; width:20px; height:20px; color:var(--grey-dim);
  pointer-events:none;
}
.frame-corner-tl{ top:-1px; left:-1px; }
.frame-corner-tr{ top:-1px; right:-1px; transform:scaleX(-1); }
.frame-corner-br{ bottom:-1px; right:-1px; transform:scale(-1); }
.frame-corner-bl{ bottom:-1px; left:-1px; transform:scaleY(-1); }

/* ---------- the deck ---------- */
.deck{ display:grid; grid-template-columns:repeat(auto-fit, minmax(112px, 1fr)); gap:8px; margin-bottom:12px; }
.card{
  display:flex; flex-direction:column; align-items:center; gap:6px;
  padding:12px 8px; background:var(--near-black); border:1px solid var(--red);
  color:var(--red); font-family:inherit; transition:transform .1s, opacity .12s;
}
.card:active{ transform:scale(.96); background:rgba(255,26,26,.12); }
.card:disabled{ border-color:var(--grey-dim); color:var(--grey-dim); opacity:.55; }
.card-glyph{ width:26px; height:26px; }
.card-glyph svg{ width:100%; height:100%; display:block; }
/* never icon-only: this is played by people who have never seen it */
.card-name{ font-size:9px; font-weight:700; letter-spacing:.08em; text-align:center; line-height:1.25; }
.card-pips{ display:flex; gap:3px; align-items:center; font-size:11px; }
.pip{ width:7px; height:7px; background:currentColor; display:block; }
.pip.spent{ background:transparent; box-shadow:inset 0 0 0 1px currentColor; }

/* the telegraph: something is coming, but not what or when */
@keyframes telegraphPulse{
  0%,100%{ box-shadow:inset 0 0 0 0 rgba(255,26,26,0); }
  50%{ box-shadow:inset 0 0 60px 0 rgba(255,26,26,.45); }
}
body.telegraph::after{
  content:''; position:fixed; inset:0; pointer-events:none; z-index:48;
  animation:telegraphPulse .45s ease-in-out 3;
}

/* ---------- betting ---------- */
.bet-row{ display:flex; gap:8px; }
.bet-row button{ margin-top:0; flex:1 1 0; min-width:0; }
.bet-tally{
  margin:10px 0 0; font-size:12px; font-weight:800; letter-spacing:.1em;
  text-transform:uppercase; color:var(--white);
}

/* ---------- session ---------- */
.at-risk{
  margin:10px 0 0; font-size:11px; font-weight:700; letter-spacing:.16em;
  text-transform:uppercase; color:var(--red);
}
.session-line{
  margin:0 0 10px; font-size:15px; font-weight:800; letter-spacing:.1em;
  text-transform:uppercase; color:var(--white);
}
.rotation{ list-style:none; margin:0; padding:0; text-align:left; font-size:12px; }
.rotation li{
  display:flex; justify-content:space-between; gap:10px; align-items:baseline;
  padding:7px 0 7px 12px; border-bottom:1px solid var(--grey-dim);
  border-left:3px solid transparent; color:var(--grey);
}
.rotation li .tag{ color:var(--grey); font-size:10px; letter-spacing:.08em; white-space:nowrap; }
.rotation-next{ border-left-color:var(--red) !important; color:var(--white) !important; }
.rotation-next .tag{ color:var(--red) !important; font-weight:700; }
.rotation-done{ opacity:.55; }

.scoreboard{ list-style:none; margin:0 0 14px; padding:0; text-align:left; font-size:12px; }
.scoreboard li{
  display:flex; justify-content:space-between; gap:10px; align-items:baseline;
  padding:8px 0; border-bottom:1px solid var(--grey-dim); color:var(--white);
}
.scoreboard li .tag{ color:var(--grey); font-size:10px; letter-spacing:.06em; text-align:right; }

.verdict{
  margin:0 0 14px; font-size:13px; font-weight:800; letter-spacing:.08em;
  text-transform:uppercase; color:var(--red); line-height:1.4;
}
.verdict-final{ font-size:clamp(16px,5vw,22px); margin-top:6px; }

.codename-row{ display:flex; gap:8px; margin:0 0 12px; }
.codename-row input{
  flex:1 1 auto; min-width:0; background:var(--black); border:1px solid var(--grey-dim);
  color:var(--white); padding:11px; font-family:inherit; font-size:12px; letter-spacing:.1em;
  text-transform:uppercase;
}
.codename-row input:focus{ outline:none; border-color:var(--red); }
.codename-row button{ margin-top:0; width:auto; flex:0 0 auto; padding:11px 16px; }

/* ---------- the Grid mark ---------- */
.grid-mark{ width:64px; height:64px; margin:0 auto 18px; color:var(--red); }
.grid-mark svg{ width:100%; height:100%; display:block; }

/* ---------- lobby: three beats, not a paragraph ---------- */
.beats{ list-style:none; counter-reset:beat; margin:0; padding:0; text-align:left; }
.beats li{
  counter-increment:beat; position:relative; padding:0 0 0 30px; margin:0 0 12px;
  font-size:12px; line-height:1.55; color:var(--grey);
}
.beats li::before{
  content:counter(beat); position:absolute; left:0; top:0;
  width:20px; height:20px; line-height:19px; text-align:center;
  border:1px solid var(--red); color:var(--red); font-size:11px; font-weight:700;
}
.beats li b{ color:var(--white); font-weight:800; letter-spacing:.04em; display:block; }

/* ---------- roster role marks ---------- */
.roster-list li .role-mark{ width:14px; height:14px; flex:0 0 auto; color:var(--red); }
.roster-list li .role-mark svg{ width:100%; height:100%; display:block; }
.roster-list li .who{ display:flex; align-items:center; gap:8px; min-width:0; }
.roster-list li .who span{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ---------- audio toggle (was a speaker emoji) ---------- */
.mute-toggle{ display:flex; align-items:center; justify-content:center; color:var(--white); }
.mute-toggle svg{ width:18px; height:18px; display:block; }

/* ---------- misc ---------- */
::-webkit-scrollbar{ width:6px; }
::-webkit-scrollbar-thumb{ background:var(--grey-dim); }

@media (min-width:640px){
  .screen{ padding:40px; }
}

/* Every motion in here is decorative — the round is legible without any
   of it. Someone who has asked their OS to stop moving things gets a
   still version rather than a broken one. */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  .blink, .glitch::before, .glitch::after{ animation:none !important; }
  .screen[data-active="true"]::before{ display:none; }
  body.glitching .screen[data-active="true"]{ animation:screenIn .5s ease both; }
  .glitch::before, .glitch::after{ display:none; }
  .inst-trail{ display:none; }
}


/* ---------- stake packs ---------- */
.pack-list{ list-style:none; margin:18px 0; padding:0; text-align:left; }
.pack{
  border:1px solid var(--grey-dim); padding:14px; margin-bottom:12px;
  background:rgba(255,255,255,.02);
}
.pack.is-owned{ border-color:var(--red); }
.pack-head{ display:flex; justify-content:space-between; align-items:baseline; gap:12px; }
.pack-name{ font-weight:800; letter-spacing:.06em; color:var(--white); }
/* the age flag is a label, never a colour on its own — this gets read in
   a dim room off someone else's screen */
.pack-flag{
  font-size:10px; font-weight:800; letter-spacing:.1em; color:var(--red);
  border:1px solid var(--red); padding:1px 4px; margin-left:6px; vertical-align:middle;
}
.pack-count{ font-size:11px; letter-spacing:.1em; color:var(--grey); white-space:nowrap; }
.pack-blurb{ margin:8px 0 12px; font-size:12px; line-height:1.5; color:var(--grey); }
.pack-action{ width:100%; margin:0; }
.pack-action:disabled{ opacity:.45; }
.pack.is-owned .pack-action{ border-color:var(--red); color:var(--red); }

/* The ask sits under the share buttons, quiet, and only after a round
   has actually landed. */
.support-line{ margin-top:18px; display:flex; gap:18px; justify-content:center; }
.support-line a{
  color:var(--grey); text-decoration:none; border-bottom:1px solid var(--grey-dim);
  letter-spacing:.08em; padding-bottom:2px;
}
.support-line a:hover, .support-line a:focus-visible{ color:var(--red); border-color:var(--red); }

/* ---------- the front door, rebuilt ---------- */
/* One button, and it is the biggest thing on the screen after the name.
   The old gateway had two buttons of equal weight and a third line of
   fine print, so nothing said "start here". */
.btn-primary.big{
  font-size:20px; padding:20px 28px; letter-spacing:.16em; margin-top:8px;
}
/* For actions that are genuinely secondary — how to play, skip. A ghost
   button reads as a real choice; these should not. */
.link-button{
  background:none; border:none; color:var(--grey); font-family:inherit;
  font-size:12px; letter-spacing:.12em; text-transform:uppercase;
  text-decoration:underline; text-underline-offset:4px; padding:10px;
  margin-top:10px; cursor:pointer;
}
.link-button:hover, .link-button:focus-visible{ color:var(--red); }

/* Each language writes its own name. A switcher that says "German" to a
   German speaker is the same mistake as the rest of the old copy. */
.lang-row{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; margin-top:18px; }
.lang-chip{
  background:none; border:1px solid var(--grey-dim); color:var(--grey);
  font-family:inherit; font-size:11px; letter-spacing:.06em;
  padding:5px 9px; cursor:pointer;
}
.lang-chip.is-on{ border-color:var(--red); color:var(--red); }

/* ---------- how to play ---------- */
.sheet{
  position:fixed; inset:0; z-index:70; background:rgba(0,0,0,.92);
  display:flex; align-items:center; justify-content:center; padding:24px;
}
.sheet-body{ max-width:420px; width:100%; border:1px solid var(--red); padding:22px; background:var(--black); }
.sheet-body .beats{ text-align:left; margin:14px 0 20px; padding-left:20px; line-height:1.7; }

/* ---------- practice ---------- */
/* Deliberately the same instrument, the same pad and the same layout as
   a real round. What you learn here has to be what you see later, or it
   is not practice, it is a different game. */
#screen-practice .stake-block{ margin-bottom:6px; }
#screen-practice .directive{ min-height:2.6em; }
#practice-done{ margin-top:14px; }
#practice-done .verdict{ color:var(--white); margin-bottom:6px; }

/* ---------- EYES OFF ---------- */
/* Never fully black. A phone that goes completely dark mid-round reads
   as a crash, and a player who thinks the app died stops playing and
   starts debugging. What survives is a slow pulse and the directive, so
   it is unmistakably still the game — and the heartbeat carries the
   only sense of time a blind player has. */
body.eyes-off .instrument,
body.eyes-off .countdown,
body.eyes-off .stake-block,
body.eyes-off .bet-tally{ opacity:0; transition:opacity .45s ease; }
body.eyes-off .touch-pad{ opacity:.22; border-color:var(--grey-dim); }
body.eyes-off .directive{
  color:var(--red); opacity:.85; animation:eyesOffPulse 1.1s ease-in-out infinite;
}
body.eyes-off::after{
  content:''; position:fixed; inset:0; z-index:45; pointer-events:none;
  background:#000; opacity:.93;
}
body.eyes-off .directive, body.eyes-off .touch-pad{ position:relative; z-index:46; }
@keyframes eyesOffPulse{ 0%,100%{ opacity:.35; } 50%{ opacity:1; } }

/* ---------- the last five ---------- */
/* Nothing new arrives in here, so nothing new should be on screen. The
   round narrows to the count and the ring and the fact of holding on. */
body.last-stand .stake-block,
body.last-stand .bet-tally{ opacity:.25; transition:opacity .6s ease; }
body.last-stand .countdown{ color:var(--red); transform:scale(1.14); transition:transform .4s ease; }
body.last-stand .directive{ color:var(--red); letter-spacing:.22em; }
body.last-stand .inst-safe{ stroke:var(--red); }

@media (prefers-reduced-motion: reduce){
  body.eyes-off .directive{ animation:none; opacity:.9; }
  body.last-stand .countdown{ transform:none; }
}

/* ---------- the legal pages ---------- */
/* Same palette and type as the game, because a policy page that looks
   like it belongs to a different company is the moment somebody decides
   not to hand over their card details. Readable width, real line height:
   these are the two pages here that are actually meant to be read. */
body.doc-page{ overflow:auto; touch-action:auto; }
.doc{
  max-width: 680px; margin: 0 auto; padding: 32px 22px 80px;
  text-align: left; line-height: 1.65; color: var(--grey);
}
.doc h1{ font-size: clamp(30px, 8vw, 46px); color: var(--white); margin: 6px 0 4px; letter-spacing: .04em; }
.doc h2{ font-size: 17px; color: var(--red); letter-spacing: .1em; text-transform: uppercase; margin: 34px 0 10px; }
.doc h2.danger-head{ border-left: 3px solid var(--red); padding-left: 10px; }
.doc .lede{ color: var(--white); font-size: 16px; margin-bottom: 8px; }
.doc p, .doc li{ font-size: 14px; }
.doc ul{ padding-left: 20px; }
.doc li{ margin-bottom: 8px; }
.doc a{ color: var(--white); text-decoration: underline; text-underline-offset: 3px; }
.doc a:hover, .doc a:focus-visible{ color: var(--red); }
.doc strong{ color: var(--white); }
.doc hr{ border: none; border-top: 1px solid var(--grey-dim); margin: 34px 0 16px; }
.doc-updated{ font-size: 12px; letter-spacing: .1em; text-transform: uppercase; color: var(--grey-dim); margin-top: 0; }
.legal-links{ display: flex; gap: 16px; justify-content: center; margin-top: 12px; }
.legal-links a{ color: var(--grey); text-decoration: underline; text-underline-offset: 3px; }
.legal-links a:hover, .legal-links a:focus-visible{ color: var(--red); }
