/* ============================================================
   THE PANEL

   A daily lineup, opened from the homepage exactly like the Heist. Ported from
   redesign/tools/panel-prototype.html, which stays as the playable reference.

   EVERY CLASS IS pn- PREFIXED. juq.css owns a pile of short global names
   (.card, .chip, .dot, .up, .lab, .line, .panel) and a feature class that
   collides with one silently inherits its layout, which has cost real time on
   this rebuild more than once. The only unprefixed things used here are the
   shared overlay shell (.overlay, .mid, .panel) that the Heist also uses, on
   purpose, so the two games open the same way.

   THE OPENER IS DELIBERATELY NOT THE VAULT. The Heist scales in, spins a dial
   and slides two doors apart: symmetrical, rotational, heavy steel. A second
   container-opens animation would read as the same idea in a different colour,
   so this one falls, gets stamped, and folds along one edge. Same world, no
   shared gesture.
   ============================================================ */

/* OBJECT SIZES, not type steps. The folder is a physical thing at a fixed
   size and the two display numbers are bigger than the type ramp goes, which
   is what makes them display. Named here rather than typed inline so they move
   together and so the drift check has something honest to read. */
.pn-wrap { --pn-file-w: 430px; --pn-file-h: 296px; --pn-display: 34px; --pn-gap: 3vh; }

/* ============================================================
   THE POPUP DOES NOT MOVE

   The shared overlay centres whatever it holds, which is right for the Heist,
   whose vault is one fixed size. It is wrong here. Every round of The Panel is
   a different height: a long message, a reply, a thread tab, ten lines of room
   or twenty. Centred, each of those re-centred the whole popup, so the card
   being read slid up or down under the cursor between rounds.

   Anchored to a fixed top instead. It grows DOWNWARD as content arrives, and
   once it reaches the cap it scrolls inside itself rather than growing at all,
   so the top edge is in the same place from the first specimen to the last.
   Scoped to .pn-wrap so the Heist keeps the centring it wants.
   ============================================================ */
.pn-wrap { align-items:start; padding-top:var(--pn-gap); padding-bottom:var(--pn-gap); }
.pn-wrap .mid { align-items:start; }
.pn-wrap .panel { max-height:calc(100vh - var(--pn-gap) * 2);
  max-height:calc(100dvh - var(--pn-gap) * 2); }

/* ============================================================
   ANIMATION

   ONE ORCHESTRATED MOMENT rather than motion everywhere. The page is still
   while you think and everything moves at once when you commit, which is what
   makes a reveal feel like a verdict instead of a page update.
   ============================================================ */
@keyframes pnDrop  { 0% { opacity:0; transform:translateY(-90px) scale(.94); }
                     70% { opacity:1; transform:translateY(6px) scale(1.01); }
                     100% { opacity:1; transform:none; } }
@keyframes pnGone  { to { opacity:0; transform:scale(1.1); } }
@keyframes pnCover { 0% { transform:rotateX(0); } 100% { transform:rotateX(-112deg); } }
@keyframes pnStamp { 0% { opacity:0; transform:rotate(-16deg) scale(2.7); }
                     60% { opacity:1; transform:rotate(-8deg) scale(.94); }
                     100% { opacity:1; transform:rotate(-9deg) scale(1); } }
@keyframes pnPhoto { 0% { transform:translate(0,26px) rotate(0deg) scale(.86); opacity:0; }
                     100% { opacity:1; } }
@keyframes pnSweep { 0% { opacity:0; } 20% { opacity:1; } 100% { opacity:0; } }
@keyframes pnSweepGo { to { left:120%; } }
@keyframes pnBars  { to { transform:translateY(-115%); opacity:0; } }
@keyframes pnRise  { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
@keyframes pnHit   { 0% { opacity:0; transform:scale(1.35) rotate(-3deg); }
                     60% { opacity:1; transform:scale(.97) rotate(.4deg); }
                     100% { opacity:1; transform:scale(1) rotate(0); } }
@keyframes pnFlash { 0% { box-shadow:0 0 0 0 var(--pn-flash); } 100% { box-shadow:0 0 0 14px transparent; } }
@keyframes pnStrike{ from { transform:rotate(-6deg) scaleX(0); } to { transform:rotate(-6deg) scaleX(1); } }
@keyframes pnShake { 10%,90% { transform:translateX(-2px); } 30%,70% { transform:translateX(3px); } 50% { transform:translateX(-3px); } }
@keyframes pnPip   { 0% { transform:scale(1); } 40% { transform:scale(1.5); } 100% { transform:scale(1); } }

/* ============================================================
   THE CASE FILE
   ============================================================ */

/* PINNED TO THE VIEWPORT, not to the panel behind it. The folder used to be
   centred inside .mid, which is exactly as tall as the panel; anchoring the
   panel to the top would have dragged the intro up with it, and an empty panel
   before the first round has loaded would have put the folder somewhere
   different again. Its own fixed centre keeps the opening identical no matter
   what is behind it. The margins are half its size, in the same variables, so
   the narrow-screen override needs no second sum. */
.pn-file { position:fixed; z-index:2; top:50%; left:50%;
  width:var(--pn-file-w); height:var(--pn-file-h);
  margin:calc(var(--pn-file-h) / -2) 0 0 calc(var(--pn-file-w) / -2);
  perspective:1100px; opacity:0; }
.overlay.show .pn-file { opacity:1; animation:pnDrop .42s cubic-bezier(.25,1.35,.4,1) .05s backwards; }
.overlay.open .pn-file { animation:pnGone .34s ease-in forwards; pointer-events:none; }

/* Offset left, never centred: a centred tab is a shape, an offset one is
   stationery. */
.pn-tab { position:absolute; top:-15px; left:26px; width:104px; height:16px; z-index:4;
  background:var(--accent); border:var(--bw) solid var(--accent); border-radius:4px 4px 0 0; }

.pn-stack { position:absolute; inset:0; z-index:1; }
/* Above the paperwork, below the cover, so these only appear as it falls. */
.pn-loose { position:absolute; inset:0; z-index:2; }
/* Size, angle and position all come from the inline style: a stack of identical
   squares is a grid, and a grid is not a stack. */
.pn-photo { position:absolute; border-radius:3px; display:grid; place-items:center;
  font-family:var(--f-mono); font-weight:700; color:#0c0c0e; overflow:hidden;
  box-shadow:0 2px 8px rgba(0,0,0,.5); border:2px solid #0c0c0e; }
.pn-photo img { width:100%; height:100%; object-fit:cover; }
.overlay.show .pn-photo { animation:pnPhoto .5s cubic-bezier(.25,1.2,.4,1) .1s backwards; }

.pn-inner { position:absolute; inset:0; background:var(--surface-2);
  border:var(--bw) solid var(--line-strong); border-radius:var(--radius);
  padding:var(--s-7); display:flex; flex-direction:column; gap:var(--s-4); }
.pn-inner-k { font-family:var(--f-mono); font-size:var(--t-label); letter-spacing:.12em;
  text-transform:uppercase; color:var(--muted); }
.pn-bar { height:9px; border-radius:2px; background:#050506; border:var(--bw) solid #26262c; }
.pn-bar.short { width:62%; }

/* THE TOP FALLS TOWARDS YOU, hinged on the BOTTOM edge. CSS has Y down and Z at
   the viewer, so a POSITIVE rotateX pushes the top AWAY: hinged at the bottom
   that folds the cover backwards over the desk. Negative brings it towards you,
   which is how a person opens a file in front of them. backface-visibility does
   the other half for free: past 90 the cover is showing its back, so it stops
   being drawn rather than falling across the case underneath. */
.pn-cover { position:absolute; inset:0; z-index:3; background:var(--surface);
  border:var(--bw) solid var(--ink); border-radius:var(--radius); padding:var(--s-7);
  transform-origin:bottom center; backface-visibility:hidden;
  box-shadow:6px 6px 0 rgba(0,0,0,.45); }
.overlay.open .pn-cover { animation:pnCover .5s cubic-bezier(.5,0,.25,1) forwards; }
.pn-k { font-family:var(--f-mono); font-size:var(--t-label); letter-spacing:.14em;
  text-transform:uppercase; color:var(--accent); }
.pn-t { font-weight:900; font-style:italic; text-transform:uppercase;
  letter-spacing:-.02em; font-size:var(--pn-display); line-height:1; margin:var(--s-3) 0; }
.pn-sub { font-family:var(--f-mono); font-size:var(--t-mono); color:var(--muted); }

/* THE SIGNATURE BEAT, the way the dial is the vault's. Comes down from far too
   big and overshoots, because the difference between an impact and a fade is
   entirely in the overshoot. Rotated, because a stamp never lands square. */
.pn-stamp { position:absolute; right:16px; bottom:16px; border:2px solid var(--accent);
  border-radius:4px; padding:var(--s-3) var(--s-5); text-align:center; color:var(--accent);
  transform:rotate(-9deg); opacity:0; }
.overlay.show .pn-stamp { animation:pnStamp .3s cubic-bezier(.3,1.5,.5,1) .46s forwards; }
.pn-stamp-1 { display:block; font-family:var(--f-mono); font-weight:700; font-size:var(--t-md);
  letter-spacing:.1em; text-transform:uppercase; }
.pn-stamp-2 { display:block; font-family:var(--f-mono); font-size:var(--t-micro);
  letter-spacing:.14em; text-transform:uppercase; opacity:.75; }

.pn-sweep { position:absolute; inset:0; border-radius:var(--radius); overflow:hidden;
  pointer-events:none; opacity:0; }
.overlay.show .pn-sweep { animation:pnSweep .55s ease-out .78s; }
.pn-sweep::before { content:""; position:absolute; top:-40%; bottom:-40%; width:70px; left:-90px;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.13), transparent);
  transform:rotate(14deg); }
.overlay.show .pn-sweep::before { animation:pnSweepGo .55s ease-out .78s; }

/* ============================================================
   THE GAME
   ============================================================ */

/* ---------- THE FORTNIGHT STRIP ----------

   Fourteen days across the top of the game, so a case you missed is one click
   rather than a thing you have to know exists. A late case pays nothing, and
   the strip has to carry that WITHOUT looking like a scolding: a played day is
   solid, a missed day is an outline, and neither is red.

   ONE ROW THAT SCROLLS RATHER THAN WRAPPING. A fortnight wrapping to two rows
   on a phone reads as a calendar with a missing week, which is worse than a
   row you push sideways. */
.pn-days { display:flex; gap:var(--s-2); padding:var(--s-4) var(--s-7) 0;
  overflow-x:auto; scrollbar-width:none; }
.pn-days::-webkit-scrollbar { display:none; }
.pn-days:empty { display:none; }

.pn-day { flex:0 0 auto; display:flex; flex-direction:column; align-items:center; gap:1px;
  min-width:34px; padding:var(--s-2) var(--s-3);
  border:var(--bw) solid var(--line); border-radius:var(--radius);
  background:transparent; color:var(--muted); cursor:pointer;
  font-family:var(--f-mono); line-height:1; }
/* The weekday is the useful half - "Tuesday" is what somebody remembers
   missing - so it gets read first and the number confirms it. */
.pn-day i { font-style:normal; font-size:var(--t-micro); letter-spacing:.08em; text-transform:uppercase; }
.pn-day b { font-size:var(--t-body); font-weight:700; font-variant-numeric:tabular-nums; }
.pn-day:hover { border-color:var(--line-strong); color:var(--ink); }

/* PLAYED IS FILLED, and it is the only filled state: whether it paid is in the
   hover text rather than in a second colour, because a wall of colours across
   the top of a game is a legend nobody asked to learn. */
.pn-day-done { border-color:var(--ok); color:var(--ok); }
.pn-day-done b { color:var(--ok); }
/* Started and abandoned, drawn as half-done rather than as either. */
.pn-day-part { border-style:dashed; border-color:var(--line-strong); color:var(--ink); }
.pn-day-today { border-color:var(--line-strong); color:var(--ink); }
.pn-day.on { background:var(--accent); border-color:var(--accent); color:var(--accent-ink); }
.pn-day.on b, .pn-day.on i { color:var(--accent-ink); }
.pn-day:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

.pn-body { padding:var(--s-7); }

.pn-docket { display:flex; align-items:center; justify-content:space-between; gap:var(--s-5);
  flex-wrap:wrap; border:var(--bw) solid var(--line-strong); border-radius:var(--radius);
  padding:var(--s-4) var(--s-6); background:var(--surface-2); margin-bottom:var(--s-6); }
.pn-docket-l { display:flex; align-items:center; gap:var(--s-5); flex-wrap:wrap; }
.pn-lbl { font-family:var(--f-mono); font-size:var(--t-label); letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted); }
.pn-v { font-family:var(--f-mono); font-size:var(--t-mono); color:var(--ink); }
/* The pips count GUESSES. There are no lives: every case is ten specimens and
   you play all ten, because ten is all there is. */
.pn-pips { display:flex; gap:var(--s-2); }
.pn-pip { width:10px; height:18px; border:var(--bw) solid var(--line-strong); border-radius:2px;
  transition:background .18s, border-color .18s, opacity .18s; }
.pn-pip.on { background:var(--accent); border-color:var(--accent); }
.pn-pip.spent { opacity:.35; }
.pn-pip.going { animation:pnPip .34s ease-out; }
.pn-bank { font-family:var(--f-mono); font-size:var(--t-mono); color:var(--ok); }
/* Not green, because nothing was won, and not red, because nothing went wrong:
   a late case is a replay and the chip should read as a fact. */
.pn-bank-nil { color:var(--muted); cursor:help; }

.pn-spec { border:var(--bw) solid var(--line-strong); border-radius:var(--radius);
  background:var(--surface); overflow:hidden; }
.pn-spec.pn-reveal { --pn-flash:rgba(255,90,30,.16); animation:pnFlash .6s ease-out; }
.pn-spec.pn-reveal.pn-won { --pn-flash:rgba(95,208,138,.18); }
.pn-top { display:flex; align-items:center; gap:var(--s-6); padding:var(--s-6);
  border-bottom:var(--bw) solid var(--line); }

/* There is a person in the frame rather than a question mark, so the bars have
   something to be in front of, and the reveal lifts them off the same element
   instead of swapping the picture. */
.pn-mug { position:relative; width:64px; height:64px; flex:0 0 auto;
  border-radius:var(--radius); overflow:hidden; background:#101013; }
.pn-mug-img { position:absolute; inset:0; background:var(--surface-2);
  transition:background .34s ease-out; }
.pn-mug-img img { width:100%; height:100%; object-fit:cover; }
.pn-mug-img::before { content:""; position:absolute; left:50%; top:20%; width:40%; aspect-ratio:1;
  transform:translateX(-50%); border-radius:50%; background:var(--line-strong); }
.pn-mug-img::after { content:""; position:absolute; left:50%; bottom:-16%; width:78%; height:46%;
  transform:translateX(-50%); border-radius:50% 50% 0 0; background:var(--line-strong); }
.pn-mug-img.pn-real::before, .pn-mug-img.pn-real::after { display:none; }
.pn-bars { position:absolute; inset:0;
  background:repeating-linear-gradient(90deg,#4a4a55 0 1px,#08080a 1px 8px,transparent 8px 16px);
  box-shadow:inset 0 0 0 3px #08080a, inset 0 0 16px rgba(0,0,0,.9); }
/* The bars LIFT, they do not fade. A cell door goes up. */
.pn-mug.pn-open .pn-bars { animation:pnBars .42s cubic-bezier(.4,.05,.2,1) forwards; }

/* A redaction is a DARK bar and it says what it is. The point is not to hide
   that something is missing, it is to say out loud that it was taken out. */
.pn-redact { display:inline-flex; align-items:center; background:#050506; color:#6a6a72;
  border:var(--bw) solid #303038; border-radius:3px; font-family:var(--f-mono);
  font-size:var(--t-label); letter-spacing:.14em; text-transform:uppercase; padding:3px var(--s-3);
  user-select:none; vertical-align:middle; }
.pn-redact-lg { font-size:var(--t-mono); padding:var(--s-2) var(--s-4); }
.pn-redact-bare { display:inline-block; background:#050506; border:var(--bw) solid #26262c;
  border-radius:3px; color:transparent; user-select:none; padding:0 var(--s-2); }

.pn-who { display:flex; flex-direction:column; gap:var(--s-3); min-width:0; }
.pn-name { font-size:var(--t-h3); font-weight:700; letter-spacing:-.01em; }
.pn-meta { font-family:var(--f-mono); font-size:var(--t-mono); color:var(--muted); }
.pn-reveal .pn-name { animation:pnRise .3s ease-out .16s backwards; }
.pn-reveal .pn-meta { animation:pnRise .3s ease-out .24s backwards; }

/* Right or wrong sits beside the face it is about, because the name, the
   mugshot and whether you got them are one fact. */
.pn-verdict { margin-left:auto; display:flex; align-items:center; gap:var(--s-5); flex:0 0 auto;
  animation:pnHit .34s cubic-bezier(.2,1.4,.4,1) .18s backwards; }
.pn-verdict-w { display:flex; flex-direction:column; align-items:flex-end; gap:2px; }
.pn-verdict-t { font-weight:900; font-style:italic; text-transform:uppercase;
  font-size:var(--t-lg); letter-spacing:-.01em; color:var(--ok); }
.pn-verdict.pn-bad .pn-verdict-t { color:var(--accent); }
.pn-verdict-n { font-family:var(--f-mono); font-size:var(--t-h3); color:var(--ok); }
.pn-verdict.pn-bad .pn-verdict-n { color:var(--muted); }
.pn-verdict-s { font-family:var(--f-mono); font-size:var(--t-micro); letter-spacing:.08em;
  text-transform:uppercase; color:var(--muted); }

.pn-msg-wrap { padding:var(--s-7) var(--s-6); }
.pn-msg { font-size:var(--t-h3); line-height:1.6; margin:0; word-break:break-word; }
.pn-msg .pn-at { color:#7aa2ff; font-weight:600; }
/* Written by the dealer, which escapes everything around it. */
.pn-emote { height:26px; vertical-align:-7px; border-radius:3px; }
.pn-reply { display:block; font-size:var(--t-mono); color:var(--muted);
  border-left:2px solid var(--line-strong); padding-left:var(--s-4); margin-bottom:var(--s-3); }
.pn-reveal .pn-reply { animation:pnRise .3s ease-out .3s backwards; }

.pn-tags { display:flex; gap:var(--s-3); flex-wrap:wrap; margin-top:var(--s-6); }
.pn-tag { font-family:var(--f-mono); font-size:var(--t-micro); letter-spacing:.08em; text-transform:uppercase;
  padding:2px var(--s-3); border:var(--bw) solid var(--line-strong); border-radius:3px; color:var(--muted); }
.pn-tag.pn-hot { color:var(--accent); border-color:var(--accent); }
.pn-tag.pn-odd { color:#7aa2ff; border-color:#7aa2ff; }

/* ---------- the lineup ---------- */
.pn-ask { display:flex; align-items:baseline; justify-content:space-between; gap:var(--s-5);
  flex-wrap:wrap; margin:var(--s-8) 0 var(--s-5); }
.pn-ask h3 { margin:0; font-size:var(--t-lg); font-weight:700; }
.pn-lineup { display:grid; grid-template-columns:repeat(4,1fr); gap:var(--s-4); }
.pn-sus { display:flex; flex-direction:column; align-items:center; gap:var(--s-3);
  padding:var(--s-6) var(--s-4); text-align:center; cursor:pointer; background:var(--surface);
  border:var(--bw) solid var(--line); border-radius:var(--radius); color:var(--ink); font:inherit;
  transition:border-color .12s, transform .12s, opacity .2s; }
.pn-sus:hover:not(:disabled) { border-color:var(--line-strong); transform:translateY(-2px); }
.pn-sus:active:not(:disabled) { transform:translateY(0); }
.pn-sus:disabled { cursor:default; }
.pn-face { width:44px; height:44px; border-radius:var(--radius); display:grid; place-items:center;
  font-family:var(--f-mono); font-weight:700; font-size:var(--t-md); color:#0c0c0e; overflow:hidden; }
.pn-face img { width:100%; height:100%; object-fit:cover; }
.pn-sus-name { font-size:var(--t-sm); font-weight:600; word-break:break-word; }
/* Struck out, not removed: the grid stays still and you can see what you ruled
   out. The line draws itself rather than appearing. */
.pn-sus.pn-out { opacity:.4; position:relative; border-style:dashed; animation:pnShake .3s ease-out; }
.pn-sus.pn-out::after { content:""; position:absolute; left:8%; right:8%; top:50%;
  border-top:2px solid var(--accent); transform-origin:left center;
  animation:pnStrike .22s ease-out forwards; }
.pn-sus.pn-right { border-color:var(--ok); box-shadow:0 0 0 2px rgba(95,208,138,.22); }

/* ---------- committing to a guess ----------

   THE ANSWER LIVES ON THE SERVER, so there is a round trip between the click
   and the verdict and there is no version of this game without one. These
   rules are what the player looks at during it: the card they chose presses in
   and holds a slow pulse, and the three they did not fade back. Half a second
   of nothing is a broken button; half a second of something is a decision being
   checked. */
.pn-sus.pn-picked { border-color:var(--accent); transform:translateY(0) scale(.98);
  box-shadow:0 0 0 2px rgba(255,90,30,.28); animation:pnPicked 1.1s ease-in-out .12s infinite; }
.pn-lineup.pn-waiting .pn-sus:not(.pn-picked) { opacity:.45; }
@keyframes pnPicked {
  50% { box-shadow:0 0 0 5px rgba(255,90,30,.10); }
}

/* The lineup steps aside rather than vanishing. Height is animated from a
   measured pixel value set in script, because `auto` is not an animatable
   length and a grid collapsing to nothing is the jump this replaces. */
.pn-lineup.pn-fold { overflow:hidden; transition:height .24s cubic-bezier(.4,0,.2,1), opacity .18s ease-in;
  opacity:0; }
.pn-ask.pn-fold { transition:opacity .16s ease-in, transform .2s ease-in; opacity:0;
  transform:translateY(-4px); }
/* What replaces it arrives the same way everything else in the reveal does. */
.pn-log.pn-in, .pn-actions.pn-in { animation:pnRise .32s ease-out backwards; }
.pn-log.pn-in { animation-delay:.04s; }
.pn-actions.pn-in { animation-delay:.12s; }

/* ---------- the room and the thread ---------- */
.pn-log { border:var(--bw) solid var(--line); border-radius:var(--radius);
  overflow:hidden; margin-top:var(--s-6); }
.pn-log-h { padding:var(--s-4) var(--s-6); background:var(--surface-2); border-bottom:var(--bw) solid var(--line);
  display:flex; justify-content:space-between; gap:var(--s-5); flex-wrap:wrap; align-items:center; }
.pn-tabs { display:flex; gap:var(--s-2); }
.pn-tab-btn { font-family:var(--f-mono); font-size:var(--t-label); letter-spacing:.08em;
  text-transform:uppercase; padding:2px var(--s-3); border-radius:3px; cursor:pointer;
  background:transparent; border:var(--bw) solid transparent; color:var(--muted); }
.pn-tab-btn:hover { color:var(--ink); }
.pn-tab-btn.on { color:var(--accent); border-color:var(--accent); }
/* Fixed, because the room always holds 25 messages and the panel would jump
   between rounds. A thread is usually two, so it sizes to its content. */
.pn-scroll { position:relative; height:340px; overflow-y:auto; overscroll-behavior:contain;
  scrollbar-width:thin; scrollbar-color:var(--line-strong) var(--surface); }
.pn-scroll.pn-thread { height:auto; max-height:340px; }
/* ONE RULE FOR BOTH PANELS. The witness stand used to carry its own near-copy
   of this and had already drifted from it: no Firefox properties, and no inset
   on the thumb. Sharing the selector is what stops that happening again. */
.pn-scroll::-webkit-scrollbar,
.pn-stand-feed::-webkit-scrollbar { width:10px; }
.pn-scroll::-webkit-scrollbar-track,
.pn-stand-feed::-webkit-scrollbar-track { background:var(--surface); }
.pn-scroll::-webkit-scrollbar-thumb,
.pn-stand-feed::-webkit-scrollbar-thumb { background:var(--line-strong); border-radius:5px;
  border:3px solid var(--surface); }
/* Centred, matching the witness stand beside it. The 2px nudge on .pn-time
   went with it: it existed to line a top-aligned timestamp up with the first
   line of text, and centring is what that nudge was approximating. */
.pn-line { display:flex; align-items:center; gap:var(--s-4); padding:var(--s-3) var(--s-6);
  font-size:var(--t-sm); line-height:1.5; }
.pn-line:nth-child(even) { background:rgba(255,255,255,.014); }
.pn-time { font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted); flex:0 0 auto; }
.pn-line-b { min-width:0; word-break:break-word; }
.pn-line.pn-it { background:var(--accent-soft); box-shadow:inset 3px 0 0 var(--accent); }

/* One recipe for "a person", used by the log, the thread and the recap: the
   same name beside the same little face everywhere is a person you start to
   recognise, which is the one skill the game asks for. */
.pn-chip { flex:0 0 auto; display:inline-grid; place-items:center; border-radius:3px;
  font-family:var(--f-mono); font-weight:700; color:#0c0c0e; line-height:1;
  vertical-align:-3px; overflow:hidden; }
.pn-chip img { width:100%; height:100%; object-fit:cover; }
/* MIDDLE, NOT BASELINE. This is an inline-flex box sitting inline with the
   message text, and an inline-flex box takes its baseline from its first item:
   the little face. So the BOTTOM OF THE AVATAR was landing on the text
   baseline and the name rode a few pixels above the words next to it.
   Note the vertical-align on .pn-chip does nothing here, because vertical-align
   does not apply to flex items. It is doing its job where the chip is used
   inline on its own. */
.pn-named { display:inline-flex; align-items:center; gap:var(--s-2); min-width:0;
  vertical-align:middle; }
.pn-named-n { font-weight:700; }

.pn-thread-in { padding:var(--s-6); display:flex; flex-direction:column; gap:var(--s-4); }
.pn-tmsg { display:flex; gap:var(--s-4); }
.pn-tmsg.pn-in1 { margin-left:var(--s-7); }
.pn-tmsg.pn-in2 { margin-left:40px; }
.pn-trail { flex:0 0 auto; width:2px; border-radius:1px; background:var(--line-strong); }
.pn-tmsg.pn-it .pn-trail { background:var(--accent); }
.pn-tbody { min-width:0; }
.pn-tmeta { display:flex; align-items:baseline; gap:var(--s-3); flex-wrap:wrap; margin-bottom:2px; }
.pn-tto, .pn-ttime { font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted); }
.pn-ttext { font-size:var(--t-sm); line-height:1.5; word-break:break-word; }
.pn-tmsg.pn-it .pn-tbody { background:var(--accent-soft); border-radius:var(--radius);
  padding:var(--s-3) var(--s-4); margin:-8px -10px; }

.pn-actions { display:flex; gap:var(--s-4); justify-content:flex-end; margin-top:var(--s-7); flex-wrap:wrap; }

/* ============================================================
   THE RECAP
   ============================================================ */
.pn-end { animation:pnRise .3s ease-out; }
.pn-end-top { display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--s-6); flex-wrap:wrap; margin-bottom:var(--s-6); }
.pn-end h2 { font-size:var(--t-h2); margin:0 0 var(--s-2); font-weight:900; font-style:italic;
  text-transform:uppercase; letter-spacing:-.02em; }
.pn-big { font-family:var(--f-mono); font-size:var(--pn-display); color:var(--ok); line-height:1;
  display:flex; align-items:baseline; gap:var(--s-3); }
.pn-big-u { font-size:var(--t-label); letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
/* A late run's headline is a score rather than a payout, so it loses the green
   that means "this went into your balance". */
.pn-big-nil { color:var(--ink); }
.pn-late-note { font-family:var(--f-mono); font-size:var(--t-mono); color:var(--muted);
  border:var(--bw) dashed var(--line-strong); border-radius:var(--radius);
  padding:var(--s-3) var(--s-5); margin-bottom:var(--s-6); }
.pn-end-lbl { display:block; margin:var(--s-7) 0 var(--s-4); }

/* ---------- the leaderboard ----------
   The tab row reuses .pn-log-h and .pn-tab-btn, which the room and the thread
   already use, so a tab is one object on this screen rather than two that
   resemble each other. */
.pn-end-h { margin:var(--s-7) 0 0; border:var(--bw) solid var(--line);
  border-bottom:0; border-radius:var(--radius) var(--radius) 0 0; }
#pnRecap, #pnBoard { border:var(--bw) solid var(--line); border-top:0;
  border-radius:0 0 var(--radius) var(--radius); overflow:hidden; }
/* A class that sets `display` beats the browser's [hidden], so it is said
   again. Hit repeatedly on this site. */
#pnBoard[hidden], #pnRecap[hidden] { display:none; }

/* THE SAME OBJECT AS THE RECAP NEXT DOOR: a header band, then rows, full
   bleed, divided by a rule. It was a padded grid of two floating lists, which
   made the two tabs look like two different components that happened to share
   a pane. */
.pn-boards { display:grid; grid-template-columns:1fr 1fr; }
.pn-bcol { min-width:0; }
.pn-bcol + .pn-bcol { border-left:var(--bw) solid var(--line-strong); }
@media (max-width:560px) {
  .pn-boards { grid-template-columns:1fr; }
  .pn-bcol + .pn-bcol { border-left:0; border-top:var(--bw) solid var(--line-strong); }
}
.pn-bcol .pn-lbl { display:block; margin:0; padding:var(--s-4) var(--s-6);
  background:var(--surface-2); border-bottom:var(--bw) solid var(--line); }
.pn-brow { display:flex; align-items:center; gap:var(--s-4);
  padding:var(--s-4) var(--s-6); border-top:var(--bw) solid var(--line); }
.pn-brow:first-of-type { border-top:0; }
/* YOUR OWN ROW, which is the row anybody opening a leaderboard is looking for.
   Marked with the accent rather than moved to the top: a board that reorders
   itself for the reader is not a board. Full bleed and square, because a
   rounded pill inside a rounded pane is the nesting this screen just lost. */
.pn-brow.pn-me { background:var(--accent-soft);
  box-shadow:inset 3px 0 0 var(--accent); }
.pn-bn { flex:0 0 auto; width:20px; font-family:var(--f-mono); font-size:var(--t-micro);
  color:var(--muted); }
.pn-bname { flex:1 1 auto; min-width:0; font-size:var(--t-sm); font-weight:600;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pn-bfound { flex:0 0 auto; font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted); }
.pn-bscore { flex:0 0 auto; font-family:var(--f-mono); font-size:var(--t-sm); color:var(--ok);
  font-variant-numeric:tabular-nums; }
.pn-board-wait { padding:var(--s-8) var(--s-6); text-align:center; color:var(--muted);
  font-family:var(--f-mono); font-size:var(--t-mono); line-height:1.6; }

.pn-share { display:flex; align-items:flex-start; gap:var(--s-5); flex-wrap:wrap; }
.pn-share-act { display:flex; flex-direction:column; gap:var(--s-3); }
.pn-grid { margin:0; flex:0 0 auto; font-family:var(--f-mono); font-size:var(--t-sm); line-height:1.7;
  color:var(--ink); background:var(--surface-2); border:var(--bw) solid var(--line);
  border-radius:var(--radius); padding:var(--s-4) var(--s-5); white-space:pre; text-align:left; overflow-x:auto; }
.pn-share-box { width:100%; margin-top:var(--s-4); height:110px; resize:none; font-family:var(--f-mono);
  font-size:var(--t-sm); background:var(--surface-2); color:var(--ink);
  border:var(--bw) solid var(--accent); border-radius:var(--radius); padding:var(--s-4); }

/* One group per outcome, with the count and the pay on the header, so the tally
   is attached to the rounds it describes rather than floating above them. */
/* THE PANE IS THE BOX, so these are SECTIONS IN IT, not boxes in it.
   Each group used to carry its own border, its own radius and its own margin,
   and the count inside carried a third: with the tab pane wrapping the lot that
   read as four nested rectangles, which is what it looked like. Rounded corners
   made it worse, because a 20px pane around a 20px group around a 3px pill has
   three different curves arguing in one corner.

   What is left is one rule between sections and one band per header. The
   grouping is still completely legible, because a heavier line and a filled
   header say "new section" perfectly well without a box around it. */
.pn-grp { border:0; border-radius:0; margin:0; }
.pn-grp + .pn-grp { border-top:var(--bw) solid var(--line-strong); }
.pn-grp-h { display:flex; align-items:center; gap:var(--s-4); padding:var(--s-4) var(--s-6);
  background:var(--surface-2); border-bottom:var(--bw) solid var(--line); }
.pn-grp-sq { font-size:var(--t-sm); line-height:1; }
.pn-grp-t { font-weight:700; font-size:var(--t-sm); }
/* The count is a number, not an object. It had a border and a radius of its
   own, which is a box inside a header inside a section inside the pane. */
.pn-grp-n { font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted);
  border:0; padding:0; }
.pn-grp-p { margin-left:auto; font-family:var(--f-mono); font-size:var(--t-mono); color:var(--ok); }
.pn-grp-u { font-size:var(--t-micro); letter-spacing:.08em; text-transform:uppercase; color:var(--muted); }

/* Every round is a button, because every round opens back up. */
/* Same horizontal padding as the header above it, so the number, the message
   and the face all line up down the pane instead of stepping in and out at
   every section. */
.pn-rev { display:flex; align-items:center; gap:var(--s-4); width:100%; text-align:left;
  padding:var(--s-4) var(--s-6); background:transparent; border:0; border-top:var(--bw) solid var(--line);
  color:var(--ink); font:inherit; cursor:pointer; }
.pn-rev:first-of-type { border-top:0; }
.pn-rev:hover { background:var(--surface-2); }
.pn-rev-n { flex:0 0 auto; width:18px; font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted); }
.pn-rev-t { flex:1 1 auto; font-size:var(--t-sm); min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
.pn-rev-w { flex:0 0 auto; font-size:var(--t-micro); font-family:var(--f-mono); }
/* WHO IT WAS, held to the right edge of the row. The message truncates and this
   does not: the name is the answer, and an answer that shortens to "sper…"
   under a long message is the wrong thing to have given away the width to. */
.pn-rev-who { flex:0 0 auto; display:flex; align-items:center; gap:var(--s-3);
  margin-left:auto; padding-left:var(--s-4); max-width:40%; }
.pn-rev-wn { font-size:var(--t-sm); font-weight:600; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
/* The face never squashes when the name beside it is long. */
.pn-rev-who .pn-chip { flex:0 0 auto; }

@media (max-width:560px) {
  /* The name goes before the message does. */
  .pn-rev-wn { display:none; }
  .pn-rev-who { max-width:none; }
}

/* ---------- standing states ---------- */
.pn-state { text-align:center; padding:var(--s-10) var(--s-6); }
.pn-state-ico { font-size:var(--pn-display); line-height:1; margin-bottom:var(--s-5); }
.pn-state h2 { font-size:var(--t-h2); margin:0 0 var(--s-4); font-weight:900; font-style:italic;
  text-transform:uppercase; letter-spacing:-.02em; }
.pn-state p { color:var(--muted); font-size:var(--t-sm); margin:0 auto; max-width:52ch; line-height:1.6; }

/* A class that sets `display` beats the browser's own [hidden] rule, so each
   one has to be said again. Hit five times on this site. */
.pn-scroll[hidden], .pn-tab-btn[hidden], .pn-share-box[hidden], .pn-grp[hidden],
.pn-sus[hidden], .pn-verdict[hidden], .pn-state[hidden], .pn-end[hidden],
.pn-named[hidden], .pn-tags[hidden] { display:none; }

@media (max-width:700px) {
  .pn-lineup { grid-template-columns:repeat(2,1fr); }
  .pn-msg { font-size:var(--t-md); }
  .pn-file { --pn-file-w:min(430px, 88vw); }
  .pn-top { flex-wrap:wrap; }
  .pn-verdict { margin-left:0; width:100%; justify-content:space-between;
    padding-top:var(--s-5); border-top:var(--bw) solid var(--line); }
}

@media (prefers-reduced-motion:reduce) {
  .pn-file, .pn-photo, .pn-stamp, .pn-sweep { animation:none !important; }
  .pn-file { display:none; }
  .pn-spec, .pn-verdict, .pn-name, .pn-meta, .pn-reply, .pn-sus, .pn-end,
  .pn-log, .pn-actions, .pn-lineup, .pn-ask,
  .pn-mug .pn-bars { animation:none !important; transition:none !important; }
  .pn-mug.pn-open .pn-bars { display:none; }
  /* The picked card still has to READ as picked: the pulse goes, the press
     stays. Script takes the fold path out entirely, so those never apply. */
  .pn-sus.pn-picked { animation:none !important; }
}

/* ============================================================
   THE WITNESS STAND

   The room they were in a month ago on the left, what they are saying right now
   on the right. Below 900px it stacks underneath instead of squeezing: two
   narrow columns of chat are worse than one of each.
   ============================================================ */
/* STRETCH, NOT START. The two columns must end level, and the stand cannot be
   the one that decides the height: it carries an extra header row, so sizing it
   from its own content made it stand roughly eighty pixels taller than the
   room. The room sets the height and the stand fills it. */
.pn-court { display:grid; grid-template-columns:1fr 300px; gap:var(--s-6);
  align-items:stretch; margin-top:var(--s-6); }
.pn-court > .pn-log { margin-top:0; min-width:0; }

.pn-stand { border:var(--bw) solid var(--line); border-radius:var(--radius);
  overflow:hidden; display:flex; flex-direction:column; min-width:0; }
.pn-stand-h { flex:none; padding:var(--s-4) var(--s-5); background:var(--surface-2);
  border-bottom:var(--bw) solid var(--line);
  display:flex; justify-content:space-between; align-items:center; gap:var(--s-4); }

/* THE LIGHT. Lit means we have heard them since this panel opened, and nothing
   stronger: it is not "online" and cannot be without Tenzin connecting an
   account. Sized off the label so it reads as a status and not a bullet. */
.pn-stand-dot { width:9px; height:9px; border-radius:2px; flex:none;
  background:var(--line-strong); transition:background .3s ease, box-shadow .3s ease; }
.pn-stand-dot[data-on="1"] { background:var(--good, #5fd08a);
  box-shadow:0 0 0 3px rgba(95,208,138,.18); }

/* Name and badges share the first line, the state gets its own. flex-basis on
   the state is what wraps it, so no extra wrapper element is needed. */
.pn-stand-who { flex:none; padding:var(--s-4) var(--s-5); border-bottom:var(--bw) solid var(--line);
  display:flex; align-items:center; gap:var(--s-3); }
.pn-stand-id { display:flex; flex-wrap:wrap; align-items:center; gap:2px var(--s-3);
  min-width:0; flex:1 1 auto; }
.pn-stand-badges { display:inline-flex; gap:var(--s-2); }
/* A FIXED SQUARE, and it has to be. The first go at "as tall as both lines"
   used align-self:stretch with aspect-ratio:1 and width:auto, which is circular:
   the width resolves from the height, the height from the row, and the row from
   the width. The browser resolved it enormous and the face filled the panel.
   40px is the two lines beside it, the name and the count, measured off the
   type scale rather than guessed. */
.pn-stand-face { width:40px; height:40px; flex:none; align-self:center;
  border-radius:var(--radius); overflow:hidden;
  background:var(--surface-2); border:var(--bw) solid var(--line);
  display:flex; align-items:center; justify-content:center;
  font-family:var(--f-mono); font-size:var(--t-sm); color:var(--muted); }
.pn-stand-face img { width:100%; height:100%; object-fit:cover; display:block; }
.pn-stand-state { flex-basis:100%; }

/* THE BADGE CHIPS. The markup comes from assets/twitch-chat.js, which is shared,
   so the classes are ch- prefixed rather than pn-. They live here because the
   Witness Stand is the only thing rendering them so far; they move to a chat
   stylesheet the day the Chat feature needs them too.

   WITHOUT THESE RULES THE BADGE IS BARE TEXT, and it was: an unstyled SUB in
   front of a message came out as 'SUBhi', reading as part of what was said. */
.ch-bdg { font-family:var(--f-mono); font-size:var(--t-micro); letter-spacing:.06em;
  padding:1px var(--s-2); border-radius:3px; border:var(--bw) solid var(--line-strong);
  color:var(--muted); line-height:1.4; }
.ch-b-brd, .ch-b-mod { color:var(--accent); border-color:var(--accent); }
.ch-b-vip, .ch-b-sub { color:var(--ink); }
.pn-stand-name { font-weight:700; font-size:var(--t-sm); overflow-wrap:anywhere; }
.pn-stand-state { font-family:var(--f-mono); font-size:var(--t-micro);
  letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
.pn-stand-state.ws-on { color:var(--good, #5fd08a); }
.pn-stand-state.ws-err { color:var(--accent); }

/* flex-basis ZERO, deliberately. With `auto` the feed would contribute its own
   messages to the natural height, so a busy chatter would push the stand past
   the room again the moment they said enough. At 0 the stand's natural height
   is just its two headers, the room is therefore always the taller of the two,
   and the feed takes exactly whatever is left. */
/* NO PADDING AND NO GAP ON THE FEED. The stripe below has to run the full
   width of the panel, so each row pads itself, exactly as .pn-line does in the
   room next to it. */
.pn-stand-feed { flex:1 1 0; min-height:0; overflow-y:auto; overscroll-behavior:contain;
  scrollbar-width:thin; scrollbar-color:var(--line-strong) var(--surface);
  display:flex; flex-direction:column; }
.pn-stand-empty { margin:0; padding:var(--s-4) var(--s-5); color:var(--muted);
  font-size:var(--t-sm); line-height:1.6; }

.pn-stand-msg { display:flex; align-items:center; gap:var(--s-3);
  font-size:var(--t-sm); line-height:1.5; padding:var(--s-3) var(--s-5); }
/* The same banding the room uses, and the same value, so the two columns read
   as one surface rather than two different tables. */
.pn-stand-msg .ch-emote { height:26px; vertical-align:-7px; border-radius:3px; }
.pn-stand-msg:nth-child(even) { background:rgba(255,255,255,.014); }
/* A /me is an action, not a sentence. */
.pn-stand-me .pn-stand-body { font-style:italic; color:var(--accent); }
.pn-stand-time { font-family:var(--f-mono); font-size:var(--t-micro); color:var(--muted);
  flex:none; }
.pn-stand-body { min-width:0; overflow-wrap:anywhere; }

@media (max-width:900px) {
  .pn-court { grid-template-columns:1fr; }
  .pn-stand-feed { flex:none; height:200px; }
}
