/* RIOT viewer — «l'acta» ballot-paper theme.
   The chrome is paper + print-ink + stamp-pad violet; saturated color is reserved
   for DATA (party brand colors) and never decorates chrome. The GHOST is the one
   non-party data mark and it deliberately wears YOUR ink (--ghost-ink = --stamp),
   never a colour of its own.
   Booth rule: no valence color (green/red) before a vote is cast — For/Against
   differ by position and weight only. Valence (moss/brick) exists only in
   post-vote contexts: room splits, the reveal, the minutes. */
:root{
  --paper:#F2F0E8;          /* ballot stock */
  --card:#FBFAF6;           /* the ballot itself */
  --card2:#ECE9DF;          /* inset surfaces (raw text, pressed) */
  --rule:#D9D5C8;           /* form rules, borders, tracks */
  --ink:#1C1B17;            /* print black */
  --ink80:#3F3C33;          /* body text */
  --ink60:#6C685D;          /* secondary print */
  --stamp:#4B3FA0;          /* stamp-pad violet — the one chrome accent */
  --stamp-soft:rgba(75,63,160,.08);
  --ghost-ink:var(--stamp); /* the GHOST's ink = the YOU ring's violet: spectral, chromatically yours */
  --moss:#3E6B4F;           /* post-vote only: for / agree / approved */
  --brick:#9C3D3D;          /* post-vote only: against / differ / rejected */
  --amber:#8A6A1F;          /* editorial provenance: AI reword, lectura, preview */
  --f-ui:"Archivo",-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
  --f-mono:"Courier Prime","Courier New",ui-monospace,monospace;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%}
body{background:var(--paper);color:var(--ink);
  font:16px/1.5 var(--f-ui);overflow:hidden}
:focus-visible{outline:2px solid var(--stamp);outline-offset:2px;border-radius:4px}
.app{display:flex;flex-direction:column;height:100dvh;max-width:680px;margin:0 auto}

/* ---- header ---- */
header{display:flex;align-items:center;justify-content:space-between;
  padding:12px 14px 4px;flex:0 0 auto}
.brand{position:relative;display:flex;align-items:center;gap:7px;min-width:0}
.citysel{display:flex;align-items:center;gap:8px;background:none;border:0;cursor:pointer;
  color:var(--ink);font:inherit;font-weight:800;font-size:16px;letter-spacing:.4px;
  padding:6px 8px;border-radius:8px;min-height:44px;min-width:0}
/* the masthead carries the instance's own institution name — one line,
   ellipsised rather than wrapped if it runs past the brand's share of the row */
.citysel #cityName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* two forms: the SHORT name in single/async mode (§/⚙ + switcher share the
   header), the FULL institution name in live mode (chrome stripped by the
   body.live-* rules below — the brand owns the whole row) */
#cityName .cn-f{display:none}
body.in-room    #cityName .cn-s,
body.live-voter #cityName .cn-s,
body.live-lobby #cityName .cn-s,
body.live-mod   #cityName .cn-s{display:none}
body.in-room    #cityName .cn-f,
body.live-voter #cityName .cn-f,
body.live-lobby #cityName .cn-f,
body.live-mod   #cityName .cn-f{display:inline}
/* multiplayer owns the whole row but a long full name can still pass 390px —
   wrap to a second line rather than ellipsise, the header has the vertical
   room here (no §/⚙ to hold it to one line) */
body.in-room    .citysel #cityName,
body.live-voter .citysel #cityName,
body.live-lobby .citysel #cityName,
body.live-mod   .citysel #cityName{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.18}
.citysel:hover{background:var(--card)}
.citysel .rose{height:23px;width:23px;flex:0 0 auto;display:block;object-fit:contain}
.citysel .citychev{color:var(--ink60);font-size:11px;font-weight:600;margin-left:1px}
/* the tag optically shares the wordmark's baseline: vertical centring alone
   leaves the smaller mono text sitting ~2px high next to 17px/800 Archivo */
.brand .ver{font:700 10.5px var(--f-mono);color:var(--ink60);letter-spacing:.4px;
  align-self:center;position:relative;top:2px}
.previewtag{color:var(--amber);background:rgba(138,106,31,.12);font:700 10px var(--f-mono);
  padding:2px 7px;border-radius:3px;letter-spacing:.6px;text-transform:uppercase;align-self:center}
.citymenu{position:absolute;top:100%;left:0;margin-top:6px;background:var(--card);border:1px solid var(--rule);
  border-radius:10px;padding:6px;min-width:174px;box-shadow:0 14px 40px rgba(28,27,23,.18);z-index:40;display:none}
.citymenu.open{display:block}
.citymenu button{display:flex;align-items:center;gap:10px;width:100%;text-align:left;background:none;border:0;
  color:var(--ink);font:inherit;font-size:14px;font-weight:600;padding:10px;border-radius:7px;cursor:pointer;min-height:44px}
.citymenu button:hover{background:var(--card2)}
.citymenu button.active{color:var(--stamp)}
.citymenu img{width:24px;height:24px;object-fit:contain;flex:0 0 auto;background:#fff;border-radius:50%;padding:3px;border:1px solid var(--rule)}
.citymenu .tick{margin-left:auto;color:var(--stamp);font-size:13px}
.hdr-right{display:flex;align-items:center;gap:6px}
.hbtn{background:none;border:1.5px solid var(--rule);color:var(--ink);cursor:pointer;padding:0;
  border-radius:50%;font-size:17px;line-height:1;width:44px;height:44px;
  display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;font-family:var(--f-mono)}
.hbtn:hover{border-color:var(--ink60);background:var(--card)}
.hbtn:active{transform:scale(.94)}
/* ---- the room strip: activity, never direction ---- */
#roomstrip{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:2px 14px 8px;flex:0 0 auto;min-height:36px}
#roomstrip[hidden]{display:none}
.rs-faces{display:flex;align-items:center}
.face{width:28px;height:28px;border-radius:50%;background:var(--card);border:1.5px solid var(--rule);
  display:inline-flex;align-items:center;justify-content:center;font-size:14px;
  margin-left:-7px;position:relative;flex:0 0 auto}
.face:first-child{margin-left:0}
.face.init{font:700 11px var(--f-mono);color:var(--ink80);text-transform:uppercase}
.face.more{font:700 10px var(--f-mono);color:var(--ink60)}
.face.me{border-color:var(--stamp)}
/* a ballot landed somewhere in the room: one quiet ring, compositor-only */
.face.tick::after{content:"";position:absolute;inset:-5px;border-radius:50%;
  border:2px solid var(--stamp);opacity:0;animation:facetick .7s ease-out}
@keyframes facetick{0%{transform:scale(.6);opacity:.7}100%{transform:scale(1.15);opacity:0}}
/* the wave: a person says hello — their avatar hops, on every phone in the room.
   Compositor-only (transform). No z-index lift: the faces overlap by a few px and
   a discrete z-change mid-hop reads as the hopping disc snapping over its neighbour
   and back. Keeping the resting stack order means it just rises and falls in place. */
.face.wave{animation:facewave .76s cubic-bezier(.3,.7,.3,1)}
@keyframes facewave{0%{transform:translateY(0)}28%{transform:translateY(-10px)}
  52%{transform:translateY(0)}70%{transform:translateY(-4px)}100%{transform:translateY(0)}}
/* the three-tap super wave: a bigger hop and one full turn of the avatar */
.face.wavebig{animation:facewavebig .94s cubic-bezier(.3,.7,.3,1)}
@keyframes facewavebig{0%{transform:translateY(0) rotate(0)}
  28%{transform:translateY(-18px) rotate(200deg)}
  56%{transform:translateY(0) rotate(360deg)}
  74%{transform:translateY(-6px) rotate(360deg)}
  100%{transform:translateY(0) rotate(360deg)}}
.rs-right{display:flex;align-items:center;gap:7px;min-width:0;flex-shrink:1}
.rs-label{font:400 11px var(--f-mono);color:var(--ink60);letter-spacing:.3px;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.rs-pulse{width:7px;height:7px;border-radius:50%;background:var(--rule);flex:0 0 auto;position:relative}
.rs-pulse.on{background:var(--stamp)}
.rs-pulse.on::after{content:"";position:absolute;inset:-4px;border-radius:50%;
  border:1.5px solid var(--stamp);opacity:0;animation:facetick .7s ease-out}

/* ---- progress: one thin rule, mono count, a violet tick where the room is ---- */
#survey{flex:1 1 auto;position:relative;padding:4px 14px 0;min-height:0;display:flex;flex-direction:column}
@media (max-width:680px){
  #survey{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;padding-bottom:14px}
}
.progressrow{display:flex;align-items:center;gap:10px;margin:2px 0 12px;flex:0 0 auto}
.ptrack{flex:1 1 auto;height:3px;background:var(--rule);border-radius:2px;position:relative}
.pfill{height:100%;width:0;background:var(--ink);border-radius:2px;transition:width .35s cubic-bezier(.2,.7,.3,1)}
.plabel{font:700 12px var(--f-mono);color:var(--ink60);flex:0 0 auto;letter-spacing:.4px}

/* ---- the ballot (card stack) ---- */
.stack{position:relative;margin:0 auto;width:100%}
.card{position:relative;background:var(--card);border:1px solid var(--rule);
  border-radius:10px;padding:22px 22px 18px;display:flex;flex-direction:column;
  box-shadow:0 1px 0 rgba(28,27,23,.04),0 12px 32px rgba(28,27,23,.10);
  transition:transform .38s cubic-bezier(.2,.7,.3,1),opacity .38s}
.card.behind1,.card.behind2{position:absolute;inset:0}
.card.behind1{transform:scale(.96) translateY(12px);opacity:.55}
.card.behind2{transform:scale(.92) translateY(24px);opacity:.25}
.card.gone-r{transform:translateX(120%) rotate(6deg);opacity:0}
.card.gone-l{transform:translateX(-120%) rotate(-6deg);opacity:0}
.card.gone-d{transform:translateY(120%);opacity:0}
.card .topic{align-self:flex-start;font:700 10.5px var(--f-mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink60);border-bottom:1px solid var(--rule);padding:0 1px 4px}
.card .topic.orig{color:var(--amber);border-color:rgba(138,106,31,.4)}
.card h2{font-size:23px;line-height:1.32;margin:14px 0 8px;font-weight:650;letter-spacing:-.012em;color:var(--ink)}
.card h2.long{font-size:19.5px;line-height:1.38}
/* original wording = a verbatim quote from the source document → the mono
   "minutes" face, capped so very long originals never push the vote buttons off */
.card h2.orig{font:400 14px/1.6 var(--f-mono);letter-spacing:0;color:var(--ink80);
  max-height:34vh;overflow-y:auto;-webkit-overflow-scrolling:touch}
.card .body{color:var(--ink80);font-size:15px}
.card .body.rawtxt{font:12.5px/1.55 var(--f-mono);color:var(--ink80);white-space:pre-wrap}
.card .brief{color:var(--ink80);font-size:15px;line-height:1.55}
.card .brief p{margin:0 0 11px}
.card .brief ul{margin:0 0 11px;padding-left:18px}
.card .brief li{margin:4px 0}
.card .brief strong{color:var(--ink);font-weight:650}
.card .stake{margin-top:10px;color:var(--ink60);font-size:13px;border-left:2px solid var(--rule);padding-left:10px}
.card .deepmore{align-self:flex-start;margin:10px 0 0;background:none;border:0;color:var(--stamp);
  font:600 12.5px var(--f-mono);cursor:pointer;padding:10px 0;min-height:40px}
.card .deep{margin-top:6px;padding-top:11px;border-top:1px solid var(--rule);font-size:13.5px;color:var(--ink80);line-height:1.5}
.card .deep p{margin:0 0 9px}
.card .deep ul{margin:0 0 9px;padding-left:18px}
.card .deep li{margin:3px 0}
.card .deep strong{color:var(--ink);font-weight:650}
/* the kicker now leads the detail view (moved off the booth face) — eyebrow */
.card .reveal>.topic{display:inline-block;margin:0 0 12px;align-self:auto}
/* verbatim source disclosure: provenance is amber (the editorial idiom), the
   wording itself wears the mono "minutes" face like any source quote */
.card .srcmore{align-self:flex-start;margin:12px 0 0;background:none;border:0;color:var(--amber);
  font:700 11px var(--f-mono);letter-spacing:.05em;text-transform:uppercase;cursor:pointer;padding:10px 0;min-height:40px}
.card .srcmore:active{transform:scale(.98)}
.card .srcorig{margin-top:8px;padding:11px 12px;border-radius:6px;background:var(--card2);
  border:1px solid rgba(138,106,31,.25);font:400 13.5px/1.6 var(--f-mono);color:var(--ink80)}
/* analyst read — editorial (amber) layer, visually apart from the neutral facts */
.card .lectura{margin-top:12px;padding:11px 12px;border-radius:6px;
  background:rgba(138,106,31,.07);border:1px solid rgba(138,106,31,.25)}
.card .lectura strong{color:var(--amber)}
.card .suggest{display:flex;align-items:center;gap:8px;margin:0 0 12px;padding:8px 10px;
  border-radius:8px;background:var(--stamp-soft);border:1px solid rgba(75,63,160,.3)}
.card .suggest .stxt{flex:1;font-size:12.5px;color:var(--ink60);font-weight:600}
.card .suggest button{border:0;border-radius:6px;padding:9px 12px;font:inherit;font-size:12px;font-weight:700;cursor:pointer;min-height:36px}
.card .suggest .sugOk{background:var(--ink);color:var(--card)}
.card .suggest .sugNo{background:var(--card2);color:var(--ink);border:1px solid var(--rule)}
.card.expanded .suggest{display:none}
.more{align-self:flex-start;margin:0;background:none;border:0;color:var(--stamp);
  font:600 13px var(--f-mono);cursor:pointer;padding:10px 0;min-height:44px}
.card .reveal{margin-top:6px;max-height:42vh;overflow:auto}
.card .reveal[hidden]{display:none}
.card .closex{position:absolute;top:12px;right:12px;width:38px;height:38px;border-radius:50%;
  border:1.5px solid var(--rule);background:var(--card);color:var(--ink);font-size:17px;line-height:1;
  cursor:pointer;display:none;align-items:center;justify-content:center;z-index:3}
.card .closex:active{transform:scale(.94)}
/* original-wording toggle: a labeled provenance pill (amber = editorial), not a
   bare icon — the state is visible text, the ⇄ is the tap affordance. The button
   itself is a transparent ≥44px hit area; the pill inside is the visual.
   Shared idiom: per-card on the booth (absolute, top-right) and page-wide on
   the Minutes — pill rules stay unscoped, only the placement is card-scoped. */
.aiflag{min-height:44px;display:flex;align-items:center;background:none;border:0;padding:0;cursor:pointer}
.card .aiflag{position:absolute;top:6px;right:8px;padding:0 6px 0 12px;z-index:3}
.aifpill{display:inline-flex;align-items:center;gap:5px;white-space:nowrap;
  font:700 10px var(--f-mono);letter-spacing:.07em;text-transform:uppercase;color:var(--amber);
  border:1px solid rgba(138,106,31,.45);background:rgba(138,106,31,.08);
  padding:5px 10px;border-radius:999px;transition:background .15s,color .15s}
.aifswap{font-size:12px;line-height:1;opacity:.75}
.aiflag:hover .aifpill{background:rgba(138,106,31,.16)}
.aiflag:active .aifpill{transform:scale(.95)}
.aiflag.on .aifpill{background:var(--amber);border-color:var(--amber);color:var(--card)}
/* long topics wrap clear of the pill instead of running under it */
.card .aiflag~.topic{max-width:calc(100% - 148px)}
.card.expanded .aiflag{display:none}
.card.expanded h2,
.card.expanded .more{display:none}
/* reading the brief is the highest-intent voting moment — the buttons stay */
.card.expanded .acts{padding-top:12px;border-top:1px solid var(--rule)}
.card.expanded{overflow:hidden}
.card.expanded .closex{display:flex}
.card.expanded .reveal{margin-top:14px;max-height:none;overflow-y:auto;flex:1 1 auto;min-height:0;-webkit-overflow-scrolling:touch}

/* ---- vote buttons: ink on paper, zero valence. Position carries the meaning;
       Abstain is deliberately quieter; For/Against are exactly equal in weight. ---- */
.acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;padding-top:16px;flex:0 0 auto}
/* the lobby's moved privacy line — LIVE first ballot only, micro-type under
   the vote buttons, where the hesitation happens */
.acts-priv{flex:1 1 100%;margin:0;text-align:center;font:400 10.5px var(--f-mono);color:var(--ink60)}
.btn{flex:1;border-radius:8px;padding:14px 6px;min-height:52px;font:700 15.5px var(--f-ui);
  cursor:pointer;display:flex;align-items:center;justify-content:center;
  background:transparent;transition:transform .08s,background .08s,color .08s}
.btn.f,.btn.a{border:2px solid var(--ink);color:var(--ink)}
.btn.ab{border:1.5px solid var(--rule);color:var(--ink60);font-weight:500}
.btn:active{transform:scale(.97);background:var(--ink);color:var(--card)}
.btn.ab:active{background:var(--ink60);color:var(--card)}
/* during the stamp/split beat the ballot is cast — buttons recede */
.card.voted .acts{opacity:.3;pointer-events:none;transition:opacity .2s}

/* ---- the stamps: ink hits paper — bottom-right, just above the action slot.
   .stamprow is an in-flow zero-height line (no absolute positioning → can't
   shrink-fit or stretch); stamps grow UPWARD from it and may overlap the card
   content above — that's the effect: a stamp lands on the page, the page
   doesn't move. The card never changes size when a stamp lands. */
.stamprow{display:flex;justify-content:flex-end;align-items:flex-end;flex-wrap:wrap;
  align-content:flex-end;margin-top:auto;padding:0 2px;pointer-events:none;
  height:0;overflow:visible}
.stamprow:empty{display:none}
.stamp{display:inline-flex;align-items:center;z-index:5;pointer-events:none;position:relative;
  transform:rotate(-8deg);margin:2px 4px -4px 10px;white-space:nowrap;
  font:800 22px var(--f-mono);letter-spacing:.18em;text-transform:uppercase;color:var(--stamp);
  border:3px solid var(--stamp);border-radius:8px;padding:7px 16px 5px;
  box-shadow:inset 0 0 0 1.5px var(--stamp);background:rgba(251,250,246,.5);
  mix-blend-mode:multiply;animation:stampin .2s cubic-bezier(.2,1.6,.4,1) both}
@keyframes stampin{0%{transform:rotate(-8deg) scale(1.8);opacity:0}
  100%{transform:rotate(-8deg) scale(1);opacity:.92}}

/* ---- after-vote split: the room's tally on THIS card (your ballot is already in) ---- */
.split{margin-top:auto;padding-top:14px;flex:0 0 auto;animation:fadein .25s both}
@keyframes fadein{from{opacity:0}to{opacity:1}}
.split .sp-k{margin:0 0 9px;font:700 10.5px var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink60)}
.sp-row{display:flex;align-items:center;gap:10px;margin:6px 0}
.sp-l{flex:0 0 64px;font:700 12px var(--f-mono);text-transform:uppercase;letter-spacing:.06em;color:var(--ink80)}
.sp-row.mine .sp-l{color:var(--stamp)}
.sp-row.mine .sp-l::after{content:" ←";font-weight:400}
.sp-track{flex:1 1 auto;height:9px;background:var(--card2);border-radius:2px;overflow:hidden}
.sp-fill{display:block;height:100%;width:0;background:var(--ink);border-radius:2px;
  transition:width .45s cubic-bezier(.2,.7,.3,1)}
.sp-row.mine .sp-fill{background:var(--stamp)}
.sp-n{flex:0 0 26px;text-align:right;font:700 14px var(--f-mono);color:var(--ink)}
.split .sp-hint{margin:9px 0 0;text-align:center;font:400 11px var(--f-mono);color:var(--ink60)}

/* ---- the reveal (done screen) ---- */
#done{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;
  justify-content:flex-start;text-align:center;padding:10px 0 28px;
  overflow-y:auto;-webkit-overflow-scrolling:touch;background:var(--paper)}
/* On the reveal the page IS the scroller (html.reveal, set by doneVis):
   release the viewer's overflow:hidden page lock and let #done leave its
   absolute overlay and flow, so wheel/touch anywhere — including outside the
   680px column on desktop — scrolls the results, and header + strip scroll
   away with the page. The booth's fixed no-scroll column is untouched. */
html.reveal,html.reveal body{overflow:auto;height:auto}
html.reveal .app{height:auto;min-height:100dvh}
html.reveal #survey{overflow:visible}
html.reveal #done{position:static;overflow:visible}
.donek{margin:4px 0 2px;font:700 11px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ink60)}
#done h2{font-size:24px;line-height:1.25;margin:4px 0 6px;font-weight:650;letter-spacing:-.01em;max-width:560px}
#done .doneSub{color:var(--ink60);margin:0 0 14px;font-size:14px;max-width:520px}
#done .donenote{color:var(--ink60);margin:14px 0 0;font:400 11.5px var(--f-mono)}
#resultMap{flex:0 0 auto;position:relative;width:100%;max-width:600px;aspect-ratio:4/3;max-height:45vh;
  border:1px solid var(--rule);border-radius:12px;overflow:hidden;margin:2px 0 12px;background:var(--card)}
#resultMap .axis{position:absolute;background:var(--rule)}
#resultMap .axis.x{left:8%;right:8%;top:50%;height:1px}
#resultMap .axis.y{top:8%;bottom:8%;left:50%;width:1px}
#resultMap .maptag{position:absolute;top:8px;left:11px;font:700 10px var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink60);z-index:5;pointer-events:none}
.mdot{position:absolute;transform:translate(-50%,-50%);z-index:3;
  transition:left .55s cubic-bezier(.2,.7,.3,1),top .55s cubic-bezier(.2,.7,.3,1);
  animation:dotin .5s cubic-bezier(.2,.9,.3,1.3) both;animation-delay:var(--d,0ms)}
@keyframes dotin{0%{opacity:0;scale:.2}100%{opacity:1;scale:1}}
.mdot .mc{width:30px;height:30px;border-radius:50%;background:#fff;overflow:hidden;
  display:flex;align-items:center;justify-content:center;border:1.5px solid var(--rule);
  box-shadow:0 2px 6px rgba(28,27,23,.18)}
.mdot .mc img{width:100%;height:100%;object-fit:contain;padding:3px}
.mdot .mc.fb{color:#fff;font:800 9px var(--f-ui);border:0}
.mdot.me{z-index:4}
.mdot.me .mc{width:34px;height:34px;background:var(--stamp);color:var(--card);
  border-color:var(--card);font:700 10px var(--f-mono);
  box-shadow:0 0 0 4px rgba(75,63,160,.18),0 2px 8px rgba(28,27,23,.25)}
/* your join emoji as the YOU mark: paper disc, violet ring, "you" tag below */
.mdot.me .mc.you{background:var(--card);border:2px solid var(--stamp);
  font:400 19px/1 var(--f-ui)}
.mdot.me .mtag{position:absolute;top:calc(100% + 3px);left:50%;transform:translateX(-50%);
  font:700 9px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--stamp);
  background:var(--card);border:1px solid var(--stamp);border-radius:4px;padding:1px 5px 0 6px}
/* other participants — deliberately subordinate: small faded emojis (the crowd
   is texture, the parties are the anchors), anonymous ink ring when faceless */
.peer{position:absolute;width:11px;height:11px;border-radius:50%;transform:translate(-50%,-50%);
  z-index:1;opacity:.35;background:none;border:2px solid var(--ink);
  pointer-events:none;transition:left .6s cubic-bezier(.2,.7,.3,1),top .6s cubic-bezier(.2,.7,.3,1);
  animation:dotin .5s both;animation-delay:var(--d,0ms)}
.peer.emo{width:auto;height:auto;border:0;border-radius:0;opacity:.45;
  font:400 13px/1 var(--f-ui);filter:grayscale(.35)}
/* the honesty layer (layoutMap): positions are data. Hairlines + anchor rings
   sit at TRUE coordinates under fanned-out tied dots (violet anchor = YOU
   shares that spot); flat edge bars mark a dot clamped in from beyond the
   border. Above the peer texture, below the dots. */
.mlay{position:absolute;inset:0;width:100%;height:100%;z-index:2;pointer-events:none;
  animation:mfade .5s both;animation-delay:var(--d,0ms)}
@keyframes mfade{0%{opacity:0}100%{opacity:1}}
.mlay line{stroke:var(--ink60);stroke-width:1;opacity:.65}
.mlay .anchor{fill:none;stroke:var(--ink60);stroke-width:1.5}
.mlay .anchor.you{stroke:var(--stamp);stroke-width:2}
.mlay .ebar{fill:var(--ink60);opacity:.8}
/* the GHOST on the map: the bare mark, no disc — and its honesty contract:
   in a fan-out the dashed shell is what gets displaced; the solid core never
   leaves the true coordinate (.split hides the in-dot core, .mlay .gcore
   re-inks it at the true point). */
.mdot.ghost{z-index:3}
.mdot.ghost .ghostmark{filter:drop-shadow(0 1px 2px rgba(28,27,23,.18))}
.mdot.ghost.split .g-core{display:none}
.mlay .gcore{fill:var(--ghost-ink)}
/* YOU pinned to the bottom edge: the "you" tag flips above the disc */
.mdot.me.pinb .mtag{top:auto;bottom:calc(100% + 3px)}
/* projection bench — one scrollable row of mono chips under the map: the same
   vote distances flattened eight ways; active = ink. Dots morph on switch. */
#mapProj{width:100%;max-width:600px;margin:-6px 0 14px}
#mapProj .mp-row{display:flex;gap:6px;overflow-x:auto;padding:2px;margin:0 -2px;
  scrollbar-width:none;-webkit-overflow-scrolling:touch}
#mapProj .mp-row::-webkit-scrollbar{display:none}
.mp-chip{flex:0 0 auto;min-height:44px;padding:0 13px;display:flex;align-items:center;
  font:700 10px var(--f-mono);letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink60);background:var(--card);border:1px solid var(--rule);border-radius:8px;
  cursor:pointer}
.mp-chip:active{transform:scale(.96)}
.mp-chip.on{background:var(--ink);color:var(--card);border-color:var(--ink)}
.mp-note{margin:6px 2px 0;font:400 10.5px/1.5 var(--f-mono);color:var(--ink60)}
/* brand-colour backfill so square logos blend into their disc (no white corners) */
.lg.bg-PSC,.mc.bg-PSC{background:#e2001a}
.lg.bg-CUP,.mc.bg-CUP{background:#f2cb05}   /* sampled from the 2026 logo PNG — the backfill must be invisible behind it */
/* most / least affinity */
.extremes{display:flex;gap:10px;width:100%;max-width:600px;margin:0 0 14px}
.exc{flex:1 1 0;min-width:0;background:var(--card);border:1px solid var(--rule);font:inherit;color:var(--ink);
  border-radius:10px;padding:15px 12px;display:flex;flex-direction:column;align-items:center;gap:8px;
  cursor:pointer;transition:transform .08s,border-color .12s}
.exc:hover{border-color:var(--ink60)} .exc:active{transform:scale(.99)}
.exc.most{border-color:rgba(62,107,79,.55)}
.exc.least{border-color:rgba(156,61,61,.55)}
.exc .exlab{font:700 10px var(--f-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--ink60)}
.exc .exname{font-size:15px;font-weight:650;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.exc .expct{font:700 23px var(--f-mono);line-height:1;color:var(--ink)}
/* ranked party list */
.dparties{width:100%;max-width:600px;margin:2px 0 14px;display:flex;flex-direction:column;gap:7px}
.dparties .dplabel{font:700 10px var(--f-mono);letter-spacing:.14em;text-transform:uppercase;color:var(--ink60);
  text-align:left;padding:2px 2px 1px}
.dprow{display:flex;align-items:center;gap:12px;width:100%;font:inherit;color:var(--ink);cursor:pointer;
  background:var(--card);border:1px solid var(--rule);border-radius:9px;padding:9px 13px;
  transition:background .12s,border-color .12s,transform .08s;min-height:54px}
.dprow:hover{background:var(--card2);border-color:var(--ink60)} .dprow:active{transform:scale(.995)}
.dprow .dptx{flex:1 1 auto;min-width:0;text-align:left}
.dprow .dpname{display:block;font-size:14px;font-weight:650}
.dprow .dptrack{display:block;height:4px;border-radius:2px;background:var(--card2);margin-top:5px;overflow:hidden}
.dprow .dpfill{display:block;height:100%;border-radius:2px}
.dprow .dppct{font:700 15px var(--f-mono);flex:0 0 auto;color:var(--ink)}
.dprow .dpgo{color:var(--ink60);font-size:19px;flex:0 0 auto;line-height:1}
/* party discs (shared) */
.lg{width:34px;height:34px;flex:0 0 auto;border-radius:50%;background:#fff;display:flex;
  align-items:center;justify-content:center;overflow:hidden;border:1.5px solid var(--rule)}
.lg img{width:100%;height:100%;object-fit:contain;padding:4px}
.lg.fb{color:#fff;font:800 12px var(--f-ui);border:0}
.exc .lg{width:46px;height:46px}
.exc .lg img{padding:5px}
.exc .lg.fb{font-size:14px}
/* the GHOST mark — "soul anchor": dashed shell + solid core, drawn by ghostMark()
   in app.js (the single source of truth). Inked in --ghost-ink only; NO fill
   inside the ring (the paper shows through), no disc chrome, no opacity tricks —
   it must read spectral next to the parties' saturated solid discs. */
.lg.ghost{background:none;border:0;overflow:visible}
.lg.ghost .ghostmark,.ghostmark{display:block}
/* the GHOST stamp — same construction as FOR/APPROVED (rounded-rect border,
   mono, letterspaced, inset ring) at header scale; decoration for large
   surfaces only, never at list or map size. Static: no thunk — nothing landed. */
.gstamp{display:inline-flex;align-items:center;transform:rotate(-4deg);white-space:nowrap;margin-right:8px;
  font:800 14px var(--f-mono);letter-spacing:.18em;text-transform:uppercase;color:var(--ghost-ink);
  border:2.5px solid var(--ghost-ink);border-radius:6px;padding:4px 10px 2px;
  box-shadow:inset 0 0 0 1px var(--ghost-ink);background:rgba(251,250,246,.5);
  mix-blend-mode:multiply;opacity:.92}
/* action buttons */
.doneActions{display:flex;gap:10px;width:100%;max-width:600px}
.doneActions button{flex:1 1 0;border-radius:9px;padding:14px 12px;font:700 15px var(--f-ui);
  cursor:pointer;transition:transform .08s;min-height:52px}
.doneActions button:active{transform:scale(.97)}
#copyVotes{background:var(--ink);color:var(--card);border:0}
.restart{background:transparent;border:2px solid var(--ink);color:var(--ink)}

/* ---- full-screen overlays (minutes / party compare / marks / import) ---- */
#log,#partyView,#marksView,#importView{position:fixed;inset:0;background:var(--paper);z-index:20;display:none;overflow:auto;padding:0 0 40px}
.lh{position:sticky;top:0;background:rgba(242,240,232,.94);backdrop-filter:blur(8px);
  border-bottom:1px solid var(--rule);padding:12px 16px;display:flex;justify-content:space-between;align-items:center;z-index:2}
#log .lwrap,#marksView .lwrap{max-width:760px;margin:0 auto;padding:16px 18px}
#partyView .lwrap,#importView .lwrap{max-width:680px;margin:0 auto;padding:16px}
.lcard{background:var(--card);border:1px solid var(--rule);border-radius:9px;padding:14px 16px;margin-bottom:12px}
.lcard .lt{font-weight:650;font-size:14px}
.lcard .lm{color:var(--ink60);font:400 11.5px var(--f-mono);margin-top:5px;display:flex;gap:10px;flex-wrap:wrap;align-items:center}
.badge{font:700 10px var(--f-mono);letter-spacing:.08em;padding:3px 8px;border-radius:3px}
.b-app,.badge.b-agree{background:rgba(62,107,79,.13);color:var(--moss)}
.b-rej,.badge.b-disagree{background:rgba(156,61,61,.12);color:var(--brick)}
.b-pend,.badge.b-na{background:var(--card2);color:var(--ink60)}
.lvotes{display:flex;flex-wrap:wrap;gap:5px;margin-top:9px}
.pchip{font:400 11px var(--f-mono);padding:4px 8px;border-radius:4px;border:1px solid var(--rule);
  display:flex;gap:6px;align-items:center;background:var(--card)}
.pchip.you{border-color:var(--stamp);color:var(--stamp)}
.dot{width:7px;height:7px;border-radius:50%;flex:0 0 auto}
.d-for{background:var(--moss)} .d-against{background:var(--brick)} .d-abstain{background:#979182}
.d-split,.d-absent{background:#BBB4A4}
.lraw{background:var(--card2);border:1px solid var(--rule);border-radius:6px;padding:10px 12px;margin-top:9px;
  font:11.5px/1.55 var(--f-mono);color:var(--ink80);white-space:pre-wrap}
.lcard a.src{color:var(--stamp);font:600 11.5px var(--f-mono);text-decoration:none}
/* ---- minutes controls: provenance pill + the moderator's curator strip ---- */
.logctl{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 14px}
.logcur{display:flex;align-items:center;gap:9px;cursor:pointer;min-height:44px;margin-left:auto;
  font:700 10px var(--f-mono);letter-spacing:.07em;text-transform:uppercase;color:var(--ink60)}
.logcur[hidden]{display:none}
/* curator's review check, one per minutes row — hidden outside curator mode */
.lg-mark{display:none}
#log.curator .lg-mark{display:inline-flex;align-items:center;gap:7px;float:right;
  margin:-6px -8px 2px 12px;padding:6px 8px;min-height:44px;cursor:pointer;
  font:700 10px var(--f-mono);letter-spacing:.07em;text-transform:uppercase;color:var(--ink60)}
.lg-mark input{width:17px;height:17px;accent-color:var(--brick);margin:0;cursor:pointer}
.lg-mark.on{color:var(--brick)}
/* original wording = verbatim source → the minutes' own typewritten face */
#log.orig .lcard .lt{font:600 12.5px/1.55 var(--f-mono)}
.pvback{flex:0 0 auto;width:44px;height:44px;border-radius:50%;border:1.5px solid var(--rule);
  background:var(--card);color:var(--ink);font:400 20px/1 var(--f-ui);cursor:pointer;padding:0;
  display:inline-flex;align-items:center;justify-content:center;text-align:center}
.pvback:hover{border-color:var(--ink60)}
.pvback:active{transform:scale(.94)}
#partyView .lhrow{max-width:680px;margin:0 auto;display:flex;align-items:center;gap:10px;width:100%}
#pvHead{display:flex;align-items:center;gap:12px;min-width:0;flex:1 1 auto}
#pvHead .lg{width:30px;height:30px;margin:0;flex:0 0 auto}
/* the header is a reveal party row: name over a thin affinity bar, % to the right
   (reuses .dptrack/.dpfill/.dppct from .dprow) — a long name ellipsizes */
#pvHead .pvtx{flex:1 1 auto;min-width:0;text-align:left}
#pvHead .pvname{display:block;font-size:16px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#pvHead .dptrack{display:block;height:4px;border-radius:2px;background:var(--card2);margin-top:5px;overflow:hidden}
#pvHead .dpfill{display:block;height:100%;border-radius:2px}
#pvHead .dppct{font:700 16px var(--f-mono);flex:0 0 auto;color:var(--ink)}
.pvrow{display:flex;flex-wrap:wrap;gap:6px;margin-top:9px;align-items:center}
#partyView #pvIntro{font-size:14.5px}
#partyView .lcard{padding:16px 18px}
#partyView .lcard .lt{font-size:17px;line-height:1.4}
#partyView .pvrow{gap:8px;margin-top:13px}
#partyView .pvrow .pchip{font-size:13px;padding:6px 11px;gap:7px}
#partyView .pvrow .dot{width:9px;height:9px}
#partyView .badge{font-size:11.5px;padding:4px 10px}
.muted{color:var(--ink60)}
details summary{cursor:pointer;color:var(--ink60);font:400 11.5px var(--f-mono);margin-top:8px;outline:none}

/* ---- curator / dev bar ---- */
#devbar{position:fixed;left:0;right:0;bottom:0;z-index:18;display:none;gap:10px;
  align-items:center;justify-content:center;
  padding:10px 16px calc(10px + env(safe-area-inset-bottom));
  background:rgba(242,240,232,.94);backdrop-filter:blur(8px);border-top:1px solid var(--rule)}
body.dev #devbar{display:flex}
body.dev .app{padding-bottom:66px}
.markbtn{flex:1 1 auto;max-width:360px;background:var(--card);border:1.5px solid var(--rule);
  color:var(--ink);font-size:14px;padding:11px 14px;border-radius:9px;cursor:pointer;min-height:46px}
.markbtn:hover{border-color:var(--ink60)}
.markbtn:disabled{opacity:.45;cursor:default}
.markbtn.on{background:rgba(156,61,61,.1);border-color:var(--brick);color:var(--brick)}
.markreview{background:none;border:1.5px solid var(--rule);color:var(--ink60);
  font:600 12px var(--f-mono);padding:9px 11px;border-radius:8px;cursor:pointer;flex:0 0 auto;min-height:44px}
.markreview:hover{color:var(--ink);border-color:var(--ink60)}
#importBox{width:100%;min-height:180px;background:var(--card);border:1px solid var(--rule);color:var(--ink);
  border-radius:9px;padding:12px 14px;font:12.5px/1.5 var(--f-mono);resize:vertical}
#importStatus{font:400 13px var(--f-mono);margin:10px 0 0;min-height:18px}
#importStatus.ok{color:var(--moss)} #importStatus.err{color:var(--brick)}

/* ---- bottom sheet ---- */
#sheet{position:fixed;inset:0;z-index:30;display:none}
#sheet.open{display:block}
.sheet-back{position:absolute;inset:0;background:rgba(28,27,23,.35);opacity:0;transition:opacity .28s}
#sheet.open .sheet-back{opacity:1}
.sheet-panel{position:absolute;left:0;right:0;bottom:0;max-width:680px;margin:0 auto;
  background:var(--paper);border-top-left-radius:16px;border-top-right-radius:16px;
  border-top:1px solid var(--rule);padding:8px 16px calc(20px + env(safe-area-inset-bottom));
  transform:translateY(110%);transition:transform .3s cubic-bezier(.2,.7,.3,1);
  box-shadow:0 -18px 50px rgba(28,27,23,.25)}
#sheet.open .sheet-panel{transform:translateY(0)}
.sheet-grab{width:40px;height:4px;border-radius:3px;background:var(--rule);margin:8px auto 14px}
.sheet-panel h3{margin:0 0 4px;font:700 11px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--ink60);padding:0 4px}
.sheet-item{display:flex;align-items:center;gap:14px;width:100%;text-align:left;
  background:var(--card);border:1px solid var(--rule);border-radius:10px;
  padding:14px 16px;margin-top:10px;color:var(--ink);cursor:pointer;font:inherit;min-height:56px}
.sheet-item:active{transform:scale(.99)}
.sheet-item.danger{border-color:rgba(156,61,61,.5)}
.sheet-item.danger b{color:var(--brick)}
.sheet-item.danger.armed{background:rgba(156,61,61,.08);border-color:var(--brick)}
/* version tag — moved off the masthead into the options sheet (Rob, 2026-06-13) */
.sheet-ver{margin:16px 4px 2px;text-align:center;font:700 10.5px var(--f-mono);
  color:var(--ink60);letter-spacing:.5px}
#sheet .devonly{display:none}
body.dev #sheet .devonly:not([hidden]){display:flex}
.si-ic{font-size:19px;line-height:1;flex:0 0 auto;font-family:var(--f-mono)}
.si-tx{flex:1 1 auto;min-width:0}
.si-tx b{display:block;font-size:15px;font-weight:650}
.si-tx small{display:block;color:var(--ink60);font-size:12px;margin-top:2px;line-height:1.35}
.si-go{color:var(--ink60);font-size:15px;flex:0 0 auto}
.switch{position:relative;width:46px;height:27px;flex:0 0 auto}
.switch input{opacity:0;width:0;height:0;position:absolute}
.slider{position:absolute;inset:0;background:var(--rule);border-radius:999px;transition:background .2s}
.slider::before{content:"";position:absolute;width:21px;height:21px;left:3px;top:3px;
  background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px rgba(28,27,23,.25)}
.switch input:checked + .slider{background:var(--stamp)}
.switch input:checked + .slider::before{transform:translateX(19px)}

/* ---- join (room onboarding) ---- */
#join{position:fixed;inset:0;z-index:60;background:var(--paper);display:flex;
  align-items:center;justify-content:center;overflow-y:auto;padding:20px}
#join[hidden]{display:none}
.join-card{width:100%;max-width:420px;text-align:center}
.join-logo{width:44px;height:44px;object-fit:contain}
.join-kicker{margin:12px 0 4px;font:700 11px var(--f-mono);letter-spacing:.2em;color:var(--stamp)}
.join-h{margin:0 0 8px;font-size:29px;line-height:1.18;font-weight:800;letter-spacing:-.015em}
.join-sub{margin:0 auto 20px;font-size:14.5px;color:var(--ink60);max-width:340px;line-height:1.5}
.join-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;max-width:288px;margin:0 auto 18px}
.join-grid button{aspect-ratio:1;min-height:44px;border:1.5px solid var(--rule);border-radius:10px;
  background:var(--card);font-size:19px;cursor:pointer;padding:0;line-height:1;
  display:flex;align-items:center;justify-content:center}
.join-grid button:active{transform:scale(.92)}
.join-grid button.sel{border:2px solid var(--stamp);background:var(--stamp-soft)}
.join-grid button{font-size:24px}
.join-go{display:block;width:100%;max-width:300px;margin:0 auto;background:var(--ink);color:var(--card);
  border:0;border-radius:10px;padding:16px;font:700 16.5px var(--f-ui);cursor:pointer;min-height:54px}
.join-go:active{transform:scale(.98)}

/* ============================  LIVE SESSION  ============================
   Lockstep room voting. The voter phone stays radically simple; the stage
   (the moderator's screen on the projector) carries the room moment. */

/* voter chrome: no settings, no minutes, no city switch — until the final
   reveal, where ⚙ returns */
/* multiplayer header = the parliament name only. §/⚙ (and the switcher) are
   the single-player chrome; settings is the solo experience (Rob, 2026-06-13:
   strictly solo-only — no options sheet for voters, not even at the reveal). */
body.in-room #quickLog,body.in-room #menuBtn,
body.live-voter #quickLog,body.live-voter #menuBtn{display:none}
body.in-room .citysel,body.live-voter .citysel{pointer-events:none}
body.in-room .citychev,body.live-voter .citychev{display:none}
body.live-lobby #survey,body.live-lobby #roomstrip{display:none}
/* the lobby keeps the app's own header (rose · city · version, left-aligned
   as everywhere) — Rob, 2026-06-12: no bespoke letterhead on this screen */
body.live-mod #survey,body.live-mod #roomstrip,body.live-mod #join{display:none}
body.live-mod .app{max-width:none}

/* the wave: just the bare hand at the foot of the screen, no disc/border/shadow —
   transparent background so the footer crowd flows behind it (the roomfloor
   repulsion clears a breathing ring so nothing actually piles under the hand).
   Offered only to a seated voter mid-sitting
   (body.can-wave; lobby → per-card reveal). One tap hops your avatar on every
   phone. The icon earns the missing label by being quietly alive: a slow,
   occasional idle wiggle, plus a gentle bob whenever someone ELSE waves — a tiny
   presence indicator before you ever touch it. */
#waveBtn{display:none}
body.can-wave #waveBtn{position:fixed;left:50%;bottom:calc(11px + env(safe-area-inset-bottom));
  transform:translateX(-50%);z-index:40;display:inline-flex;align-items:center;justify-content:center;
  width:54px;height:54px;border:0;background:transparent;color:var(--ink60);
  cursor:pointer;transition:transform .12s cubic-bezier(.2,.7,.3,1),color .12s}
body.can-wave #waveBtn:hover{color:var(--ink80)}
body.can-wave #waveBtn:active{transform:translateX(-50%) scale(.9)}
/* someone else waved (multiplayer.js bobWaveBtn) — keep the translateX centring */
body.can-wave #waveBtn.bob{animation:wvbob .62s cubic-bezier(.3,.7,.3,1)}
@keyframes wvbob{0%,100%{transform:translateX(-50%) translateY(0)}
  35%{transform:translateX(-50%) translateY(-6px)}65%{transform:translateX(-50%) translateY(0)}}
#waveBtn .wv-hand{width:25px;height:25px;flex:0 0 auto;transform-origin:72% 88%;
  animation:wvidle 7s ease-in-out infinite}
/* a brief wave near the end of each long cycle — present, not fidgety */
@keyframes wvidle{0%,80%,100%{transform:rotate(0)}
  84%{transform:rotate(-9deg)}88%{transform:rotate(8deg)}92%{transform:rotate(-5deg)}96%{transform:rotate(2deg)}}
#waveBtn:active .wv-hand{animation:wvtip .5s ease}
@keyframes wvtip{0%,100%{transform:rotate(0)}25%{transform:rotate(-16deg)}75%{transform:rotate(12deg)}}

/* the countdown: a ceiling, not a clock — the bar closes from both edges
   toward the centre; the last five seconds turn stamp-violet */
.livefill{position:absolute;inset:0;background:var(--ink);border-radius:2px;
  transform-origin:center;transform:scaleX(1)}
.livefill.low{background:var(--stamp)}
.livefill[hidden]{display:none}
.ptrack.counting .pfill{display:none}

/* paused: the moderator holds the room */
#pausedNote{display:none;margin:-4px 0 10px;text-align:center;
  font:700 11px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--stamp)}
body.live-paused #pausedNote{display:block}
body.live-paused #stack{opacity:.5;pointer-events:none}

/* ballot cast — how many are in, never the split (that belongs to the reveal) */
.castp{margin-top:auto;padding-top:14px;flex:0 0 auto;text-align:center;animation:fadein .25s both}
.castp .sp-k{margin:0 0 7px;font:700 10.5px var(--f-mono);letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink60)}
.cp-n{font:700 34px var(--f-mono);line-height:1;color:var(--ink)}
.cp-n span{font-size:17px;color:var(--ink60);font-weight:400;margin-left:4px}
.cp-w{margin:7px 0 0;font:400 11px var(--f-mono);color:var(--ink60)}

/* the official imprint: the chamber's stamp lands NEXT TO yours in the margin —
   two imprints, one glance. Post-vote context — valence allowed. Opposite angle. */
.stamp.official{flex-direction:column;gap:1px;transform:rotate(4deg);
  font-size:19px;padding:6px 14px 4px;animation-name:stampoff}
.stamp.official small{font:700 10px var(--f-mono);letter-spacing:.2em;text-transform:uppercase;opacity:.85}
/* the margin subtitle — the real head-count under the verdict word, in the
   minutes' typewritten lowercase (overrides the stamp's uppercase). Sized for
   reading, not decoration (Rob, 2026-06-12: the count was illegible at 9px —
   older eyes are the audience too); full ink, no opacity fade on the numbers. */
.stamp.official .st-m{font:700 12.5px var(--f-mono);letter-spacing:.04em;text-transform:none;margin-top:2px}
.stamp.official.st-app{color:var(--moss);border-color:var(--moss);box-shadow:inset 0 0 0 1.5px var(--moss)}
.stamp.official.st-rej{color:var(--brick);border-color:var(--brick);box-shadow:inset 0 0 0 1.5px var(--brick)}
@keyframes stampoff{0%{transform:rotate(4deg) scale(1.8);opacity:0}
  100%{transform:rotate(4deg) scale(1);opacity:.92}}

/* the room's reveal: emoji piles in button order — every cast face lands on
   its pile (the fun beat; the chamber's stamp above is the verdict) */
/* three columns share row tracks via subgrid: parties (row 1) pile up to the
   rule, the label (row 2) carries the rule, the room sums up below (row 3) —
   so the divider lines up across columns whatever the pile heights. */
.piles{display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:auto auto auto;
  gap:0 8px;margin:4px 0 0}
.pl-col{display:grid;grid-row:span 3;grid-template-rows:subgrid;justify-items:center;min-width:0}
.pl-stack{display:flex;flex-wrap:wrap-reverse;justify-content:center;align-items:center;
  gap:2px;min-height:30px;max-width:100%}
.pl-stack .face{width:26px;height:26px;font-size:14px;margin:0}
.pl-stack .face.init{font-size:10px}
.pl-none{font:400 12px var(--f-mono);color:var(--rule)}
/* the chamber stays on the line: party logo discs pile up just above the rule */
.pl-chamber{grid-row:1;align-self:end;min-height:0;margin-top:1px}
.pl-stack .lg{width:22px;height:22px}
.pl-stack .lg img{padding:2.5px}
.pl-stack .lg.fb{font-size:7px;letter-spacing:0}
.pl-lab{grid-row:2;align-self:start;font:700 11px var(--f-mono);text-transform:uppercase;
  letter-spacing:.06em;color:var(--ink80);border-top:2px solid var(--ink);padding-top:5px;
  width:100%;text-align:center}
/* the room, summarised below the line: up to 3 OVERLAPPING faces (you first,
   on top) + the count beside them — the number shows the lean, the cluster
   stays a fixed glance whatever the size (Rob, 2026-06-13). */
.pl-foot{grid-row:3;align-self:start;display:flex;flex-wrap:nowrap;justify-content:center;
  align-items:center;gap:6px;padding-top:7px}
.pl-faces{display:flex;align-items:center}
.pl-faces .face{width:26px;height:26px;font-size:14px;margin:0}
.pl-faces .face.init{font-size:10px}
.pl-faces .pl-drop{position:relative}
.pl-faces .pl-drop + .pl-drop{margin-left:-9px}     /* overlap, as the strip does up top */
.pl-faces .pl-drop:nth-child(1){z-index:3}          /* you (first) stays on top — violet ring visible */
.pl-faces .pl-drop:nth-child(2){z-index:2}
.pl-faces .pl-drop:nth-child(3){z-index:1}
.pl-faces .face:not(.me){border-color:var(--paper)} /* paper ring separates the overlap cleanly */
.pl-n{font:700 13px var(--f-mono);color:var(--ink)}
.pl-col.quiet .pl-lab{color:var(--ink60);border-top-color:var(--rule)}
.pl-col.quiet .pl-n{color:var(--ink60)}
.pl-col.mine .pl-lab{color:var(--stamp);border-top-color:var(--stamp)}
.pl-col.mine .pl-n{color:var(--stamp)}
.pl-drop{display:inline-flex;animation:pldrop .3s cubic-bezier(.2,1.4,.4,1) both}
@keyframes pldrop{0%{transform:translateY(-14px) scale(1.5);opacity:0}
  100%{transform:translateY(0) scale(1);opacity:1}}
.pl-to{margin:9px 0 0;text-align:center;font:400 11px var(--f-mono);color:var(--ink60)}
.lv-verdict{margin:11px 0 0;text-align:center;font:700 12px var(--f-mono);
  letter-spacing:.08em;text-transform:uppercase;animation:fadein .3s both}
.lv-verdict.agree{color:var(--moss)}
.lv-verdict.differ{color:var(--brick)}
.lv-verdict.tie{color:var(--ink60)}
.lv-verdict[hidden]{display:none}

/* ---- the footer room (live sessions only) ----
   The people, as one organic crowd at the bottom of the booth. roomfloor.js
   owns each body's transform (translate + a wave-pop scale); CSS owns only the
   disc, the violet ME ring, and an opacity fade-in on spawn (NEVER a transform
   animation — that would fight the per-frame JS placement). Presence is the
   footer now, so the top strip retires in live. The crowd fills down to the foot
   and shares the floating wave hand's level; a soft repulsion bubble keeps a
   breath of clear space around the hand (roomfloor.js place()). */
#roomfloor{display:none}
body.live-floor #roomfloor{display:block;position:relative;flex:0 0 auto;overflow:hidden;
  height:150px;margin:0 14px}  /* runs to the screen bottom; height set live by roomfloor.js; overflow clips the undecided peeping over the bottom edge. The crowd now fills down to the foot, level with the floating wave hand (a soft bubble parts the emoji around the hand — roomfloor.js place()), no longer a reserved empty band */
body.live-voter #roomstrip{display:none}
/* the footer is the SAME paper as the booth — don't let #survey clip the card's
   drop shadow at its edge (that hard line broke the card→crowd transition), and
   soften the shadow so the card floats over one continuous surface (Rob) */
body.live-floor #survey{overflow:visible}
body.live-floor #stack .card{box-shadow:0 1px 0 rgba(28,27,23,.04),0 8px 20px rgba(28,27,23,.05)}
.rf-body{position:absolute;left:0;top:0;border-radius:50%;background:var(--card);
  display:flex;align-items:center;justify-content:center;line-height:1;
  border:0.5px solid var(--rule);box-shadow:0 1px 2px rgba(28,27,23,.08);
  will-change:transform;animation:rfin .34s ease both}
.rf-body.me{border:1.5px solid var(--stamp);z-index:5}
@keyframes rfin{from{opacity:0}to{opacity:1}}

/* ---- voter lobby: nobody reads ----
   Three zones: letterhead line (CITY · vN + glyph) → hero (live chip, huge
   serif title, one-liner) → presence (overlapping faces, big count, CTA).
   The screen's only jobs: something is about to happen / people are arriving /
   wait for it. Session metadata lives in the tiny "about" disclosure; the
   methodology pitch and the privacy line moved to the first vote card. All
   copy from CFG.lobby. On open (.counting) the room dims and a 3·2·1 counts the
   sitting in over the gathered faces before the first card lands. */
#lobby{flex:1 1 auto;display:flex;min-height:0;position:relative;
  padding:14px 20px calc(16px + env(safe-area-inset-bottom))}
#lobby[hidden]{display:none}
/* html.lvhold: set by the inline head script BEFORE first paint when this tab
   is seated in a live lobby (sessionStorage flag, kept current by live.js) —
   a refresh re-paints the lobby on frame one instead of flashing the booth
   shell while Firebase reconnects. Mirrors the body.live-lobby chrome strip
   above; live.js retires the class as soon as real session state lands. */
html.lvhold #survey,html.lvhold #roomstrip{display:none}
html.lvhold #lobby[hidden]{display:flex}
html.lvhold #quickLog,html.lvhold #menuBtn{display:none}
/* brand and lobby copy are JS-set — until the boot prefill runs the held frame
   is clean paper, never the wrong city's header (the static markup defaults to
   Reus). visibility keeps layout, so nothing jumps. */
html.lvhold .brand,html.lvhold .lb-doc{visibility:hidden}
/* avatars are the hero: a small chip+title head, the gathered faces filling the
   centre, the count + the opt-in who-sits-here trigger at the foot. The whole
   stack centres as one unit (the roster opens as a bottom sheet, NOT inline, so
   nothing here ever needs to scroll or squish the faces). On open (.counting)
   the room dims and the 3·2·1 lands over it. */
.lb-doc{flex:1 1 auto;display:flex;flex-direction:column;align-items:center;justify-content:center;
  width:100%;max-width:520px;margin:0 auto;min-height:0;text-align:center;
  padding-bottom:64px;   /* clear the fixed wave hand floating at the foot */
  transition:opacity .45s cubic-bezier(.2,.7,.3,1)}
#lobby.counting .lb-doc{opacity:.2;pointer-events:none}   /* the faces stay faintly behind the count */
.lb-head{flex:0 0 auto;display:flex;flex-direction:column;align-items:center}
/* chip alignment is OPTICAL, not box math: in a line-height:1 12px Courier
   Prime span the caps' visual centre sits ~1px ABOVE the box centre (measured:
   ascent 9 / descent 4 — the box hangs low for descenders the caps never use),
   so the dot lifts 1px to sit on the caps' midline */
.lb-chip{display:flex;align-items:center;gap:9px;margin:0 0 12px;line-height:1;
  font:700 12px/1 var(--f-mono);letter-spacing:.24em;text-transform:uppercase;color:var(--stamp)}
.lb-pulse{width:8px;height:8px;border-radius:50%;background:var(--stamp);flex:0 0 auto;
  transform:translateY(-1px);
  animation:lbpulse 1.6s ease-in-out infinite}   /* ambient, not alarming */
@keyframes lbpulse{0%,100%{opacity:1}50%{opacity:.3}}
.lb-title{margin:0;font:800 clamp(22px,6.4vw,30px)/1.12 var(--f-ui);
  letter-spacing:-.02em;color:var(--ink)}
/* the avatar cluster — the hero; grows when few, tightens as the room fills
   (sizes set on #lobbyFaces by live.js as --av/--ov/--fs) */
.lb-stage{flex:0 1 auto;display:flex;align-items:center;justify-content:center;
  width:100%;min-height:0;max-height:46vh;padding:22px 0 14px;overflow:hidden}
.lb-faces{display:flex;align-items:center;flex-wrap:wrap;justify-content:center;align-content:center;margin:0}
#lobbyFaces .face{width:var(--av,52px);height:var(--av,52px);font-size:var(--fs,26px);
  margin:0 calc(var(--ov,14px) * -1) 0 0;border-width:2px}
#lobbyFaces .face:not(.me){border-color:var(--paper)}  /* overlap ring; me keeps the violet */
#lobbyFaces .face.init{font-size:calc(var(--fs,26px) * 0.7)}
.face.lb-pop{animation:lbpop .4s cubic-bezier(.2,1.4,.4,1) both}  /* a newcomer scales in */
@keyframes lbpop{0%{transform:scale(.2);opacity:0}100%{transform:scale(1);opacity:1}}
.lb-count{display:flex;flex-direction:column;align-items:center;gap:1px;margin:0;
  font:400 12px/1.5 var(--f-mono);color:var(--ink60)}
.lb-count b{font:700 30px/1.1 var(--f-mono);color:var(--ink)}
/* who sits here — the opt-in trigger; one quiet mono link under the count that
   opens the roster bottom sheet (the only reading on the glance screen is
   opt-in). 44px touch target via padding. */
.lb-who{margin:12px 0 0;background:none;border:0;cursor:pointer;padding:8px 6px;
  font:400 11.5px/1.4 var(--f-mono);color:var(--ink60);
  text-decoration:underline;text-underline-offset:2px}
.lb-who[hidden]{display:none}
.lb-who:active{transform:scale(.98)}
/* roster rows (in the sheet) — identity, mirroring the solo cover: disc + name
   + a line of identity. Identity ONLY (the firewall holds pre-vote). */
.lb-prow{display:flex;align-items:flex-start;gap:11px;margin:0 0 14px}
.lb-prow:last-child{margin-bottom:0}
.lb-prow .lg{width:26px;height:26px;margin-top:1px;flex:0 0 auto}
.lb-prow .lg img{padding:3px}
.lb-prow .lg.fb{font-size:7.5px;letter-spacing:0}  /* 5-char tokens must fit the 26px disc */
.lb-ptx{font:400 13px/1.45 var(--f-ui);color:var(--ink60);min-width:0}
.lb-ptx b{display:block;font:650 13.5px/1.3 var(--f-ui);color:var(--ink)}
/* the roster bottom sheet — same bones as the options sheet (#sheet), but rides
   ABOVE the wave hand (z-50 vs 40) and its list scrolls inside a capped panel;
   live.js adds pull-down-to-dismiss. */
/* .shown = visible in the DOM (panel still off-screen at translateY(110%));
   .open = slid up. Splitting them lets the slide transition run: display flips
   on .shown, then a reflow, then .open animates transform + scrim — a single
   display:none→block+open flip would snap, with no frame to transition from. */
#rosterSheet{position:fixed;inset:0;z-index:50;display:none}
#rosterSheet.shown{display:block}
#rosterSheet.open .sheet-back{opacity:1}
#rosterSheet.open .sheet-panel{transform:translateY(0)}
.rs-panel{max-height:76vh;display:flex;flex-direction:column}
.rs-list{overflow-y:auto;-webkit-overflow-scrolling:touch;margin-top:8px;
  padding-bottom:4px;text-align:left}
/* the countdown: the chair's line small in stamp violet, then a big 3·2·1 over
   the dimmed room (the gathered faces still faintly behind it) */
.lb-k{margin:0 0 10px;font:700 11px var(--f-mono);letter-spacing:.2em;text-transform:uppercase;color:var(--stamp)}
.lb-cd{font:800 clamp(78px,28vw,128px)/1 var(--f-ui);letter-spacing:-.03em;color:var(--ink)}
.lb-cd.cd-beat{animation:cdbeat .62s cubic-bezier(.2,1.3,.35,1) both}
@keyframes cdbeat{0%{transform:scale(.55);opacity:0}45%{opacity:1}100%{transform:scale(1);opacity:1}}
.lb-open{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;text-align:center;padding:20px;opacity:0;pointer-events:none}
.lb-open[hidden]{display:none}
#lobby.counting .lb-open{opacity:1;transition:opacity .4s cubic-bezier(.2,.7,.3,1) .15s}

/* ============================  SOLO COVER  ============================
   ?solo=1 opens on the record's first page, not in the booth: what this
   record IS (CFG.solo_lobby — kicker/title/lore/meta/cta), one ink CTA into
   the deck. Same skeleton as the live lobby (eyebrow → big title → foot)
   so it reads as the same room — but nothing here is live: a static violet
   eyebrow, never a pulsing chip, no faces, no count. Solo is the one
   entrance where the visitor came to READ, so the lore paragraphs wear
   body ink (--ink80), not the lobby's muted one-liner grey. */
body.solo-cover #survey,body.solo-cover #roomstrip{display:none}
/* no §/⚙ until the deck is dealt — the sheet/minutes belong to the booth */
body.solo-cover #quickLog,body.solo-cover #menuBtn{display:none}
#solo{flex:1 1 auto;display:flex;min-height:0;padding:14px 20px 0}
#solo[hidden]{display:none}
/* The cover scrolls as THE PAGE (html.cover — set by soloEnter, cleared by
   soloStart): the same release as html.reveal, because anything less is a
   middle-scroll. A .sl-doc-level scroller was tried first (Rob, 2026-06-12)
   and failed the same way the hero-level one did — the wheel only bit inside
   the 520px column; on desktop most of the screen is OUTSIDE it. With the
   document as the one scroller, wheel/touch anywhere works natively. The app
   header pins (Rob likes it sticky; the reveal lets it scroll away — the
   cover does not) and the foot pins via its own sticky bottom. */
/* body must be overflow:VISIBLE here, not auto (the reveal's release uses
   auto and lets its header scroll away, so it never noticed): an overflow:auto
   body is itself a scroll container, so the sticky header pins to body's box
   and rides off-screen with it instead of pinning to the viewport. */
html.cover{overflow:auto;height:auto}
html.cover body{overflow:visible;height:auto}
html.cover .app{height:auto;min-height:100dvh}
html.cover header{position:sticky;top:0;z-index:5;background:var(--paper)}
.sl-doc{flex:1 1 auto;display:flex;flex-direction:column;width:100%;max-width:520px;
  margin:0 auto;min-height:0;text-align:left;
  transition:opacity .36s cubic-bezier(.2,.7,.3,1),transform .36s cubic-bezier(.2,.7,.3,1)}
#solo.away .sl-doc{opacity:0;transform:translateY(-10px);pointer-events:none}
/* centering via auto-margin pseudos, NOT justify-content:center — a centered
   flex column that overflows clips its top above the scroll reach (the party
   rows made long covers real); auto margins collapse to 0 under overflow so
   short covers centre and long ones scroll from the kicker down */
.sl-hero{flex:1 1 auto;display:flex;flex-direction:column;padding:18px 0}
.sl-hero::before,.sl-hero::after{content:"";flex:0 0 auto;margin:auto}
.sl-k{margin:0 0 16px;font:700 12px/1.4 var(--f-mono);letter-spacing:.24em;
  text-transform:uppercase;color:var(--stamp)}
.sl-title{margin:0 0 16px;font:800 clamp(30px,8.6vw,46px)/1.1 var(--f-ui);
  letter-spacing:-.02em;color:var(--ink)}
.sl-lore p{margin:0 0 12px;font:400 15.5px/1.55 var(--f-ui);color:var(--ink80);max-width:54ch}
.sl-lore p:last-child{margin-bottom:0}
.sl-lore[hidden]{display:none}
/* who sits here: compact party rows under the lore — disc, name, a few words.
   Identity only (the booth doctrine holds pre-vote: no directions, no counts). */
.sl-parties{margin-top:20px;padding-top:16px;border-top:1px solid var(--rule)}
.sl-parties[hidden]{display:none}
.sl-plabel{margin:0 0 12px;font:700 10.5px var(--f-mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink60)}
.sl-prow{display:flex;align-items:flex-start;gap:11px;margin:0 0 10px}
.sl-prow:last-child{margin-bottom:0}
.sl-prow .lg{width:26px;height:26px;margin-top:1px}
.sl-prow .lg img{padding:3px}
.sl-prow .lg.fb{font-size:7.5px;letter-spacing:0}  /* 5-char tokens (NSDAP) must fit the 26px disc */
.sl-ptx{font:400 13px/1.45 var(--f-ui);color:var(--ink60);min-width:0}
.sl-ptx b{display:block;font:650 13.5px/1.3 var(--f-ui);color:var(--ink)}
/* pinned foot: opaque paper so the lore slides under it, bottom padding
   (incl. the safe area) lives here now that #solo's own bottom padding is
   gone — sticky to the doc's bottom edge, not the padded box above it */
.sl-foot{flex:0 0 auto;position:sticky;bottom:0;z-index:2;background:var(--paper);
  padding:14px 0 calc(16px + env(safe-area-inset-bottom))}
.sl-meta{margin:0 0 14px;font:700 11px/1.6 var(--f-mono);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink60)}
.sl-meta[hidden]{display:none}
.sl-go{display:block;width:100%;max-width:300px;margin:0;background:var(--ink);
  color:var(--card);border:0;border-radius:10px;padding:16px;
  font:700 16.5px var(--f-ui);cursor:pointer;min-height:54px}
.sl-go:active{transform:scale(.98)}
/* phones: the CTA spans the column edge to edge (the 300px cap is a desktop measure) */
@media (max-width:680px){.sl-go{max-width:none}}
.sl-note{margin:10px 0 0;font:400 11px/1.5 var(--f-mono);color:var(--ink60)}
.sl-note[hidden]{display:none}

/* ---- room verdict on the final reveal (closes the page, after the personal finding) ---- */
#roomVerdict{width:100%;max-width:600px;margin:18px 0 6px;background:var(--card);
  border:1px solid var(--rule);border-radius:12px;padding:16px 16px 10px;text-align:left}
#roomVerdict[hidden]{display:none}
.rv-k{margin:0 0 6px;font:700 10.5px var(--f-mono);letter-spacing:.16em;text-transform:uppercase;color:var(--stamp)}
.rv-h{margin:0 0 12px;font-size:18px;line-height:1.3;font-weight:650;letter-spacing:-.01em}
.rv-row{display:flex;flex-direction:column;gap:6px;padding:10px 0;border-top:1px solid var(--rule)}
.rv-t{font-size:13.5px;font-weight:600;line-height:1.35}
.rv-chips{display:flex;gap:6px;flex-wrap:wrap}

/* ---- the stage: legible from five metres ---- */
#stage{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;
  padding:4px clamp(16px,4vw,52px) 0}
#stage[hidden]{display:none}
.sg-main{flex:1 1 auto;display:flex;flex-direction:column;min-height:0;overflow-y:auto}
.sg-k{margin:0;font:700 clamp(11px,1.4vw,16px) var(--f-mono);letter-spacing:.18em;
  text-transform:uppercase;color:var(--ink60)}
.sg-card{position:relative;display:flex;flex-direction:column;flex:1 1 auto;min-height:0;padding-top:8px}
.sg-topic{margin:14px 0 0;font:700 clamp(11px,1.5vw,17px) var(--f-mono);letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink60);align-self:flex-start;
  border-bottom:1px solid var(--rule);padding:0 1px 5px}
.sg-h{margin:14px 0 18px;font-weight:800;font-size:clamp(25px,4.6vw,58px);
  line-height:1.16;letter-spacing:-.015em;max-width:24ch}
.sg-track{position:relative;height:8px;background:var(--rule);border-radius:4px;margin:auto 0 16px;flex:0 0 auto}
.sg-fill{position:absolute;inset:0;background:var(--ink);border-radius:4px;
  transform-origin:center;transform:scaleX(1)}
.sg-fill.low{background:var(--stamp)}
.sg-meta{display:flex;justify-content:space-between;align-items:baseline;margin:0 0 14px;flex:0 0 auto}
.sg-in{font:700 clamp(20px,3.2vw,40px) var(--f-mono);color:var(--ink)}
.sg-in b{font-weight:700}
.sg-secs{font:700 clamp(15px,2.2vw,26px) var(--f-mono);color:var(--ink60)}
.sg-revwrap{margin:0 0 18px}
.sg-revwrap:empty{display:none}
#stage .split{margin-top:0;padding-top:6px}
#stage .sp-k{font-size:clamp(11px,1.4vw,16px)}
#stage .piles{gap:clamp(10px,2vw,28px)}
#stage .pl-stack{gap:clamp(2px,.4vw,6px)}
#stage .pl-faces .face{width:clamp(28px,3.4vw,46px);height:clamp(28px,3.4vw,46px);
  font-size:clamp(15px,1.9vw,26px);margin:0}   /* beat the id-strength #stage .face overlap margin */
#stage .pl-faces .face.init{font-size:clamp(10px,1.2vw,16px)}
#stage .pl-faces .pl-drop + .pl-drop{margin-left:clamp(-14px,-1.5vw,-9px)}  /* overlap scales with face */
#stage .pl-lab{font-size:clamp(12px,1.6vw,20px)}
#stage .pl-n{font-size:clamp(14px,2.2vw,28px)}
#stage .pl-stack .lg{width:clamp(24px,3vw,40px);height:clamp(24px,3vw,40px)}
#stage .pl-stack .lg.fb{font-size:clamp(8px,1vw,13px)}
#stage .pl-to{font-size:clamp(11px,1.4vw,16px)}
#stage .lv-verdict{font-size:clamp(13px,2vw,22px);margin-top:14px}
#stage .stamprow{margin-top:0;padding:0 0 8px;height:auto}  /* stage: in-flow (its own rhythm; no card to keep still) */
#stage .stamp{font-size:clamp(22px,3vw,38px);border-width:4px;border-radius:10px;
  padding:1.1vh 1.6vw .7vh;margin:0 6px -2px 12px}
#stage .stamp.official small{font-size:clamp(10px,1.2vw,15px)}
#stage .stamp.official .st-m{font-size:clamp(14px,1.7vw,21px)}
/* ---- holding page: the bare URL, before any link is shared ---- */
/* Zero info: just the wordmark. The glitch is a misregistered print — a
   stamp-violet copy and an ink copy slip out of register in sliced bursts
   (steps(1), multiply), then the page sits still. Pseudos are opacity:0 at
   both keyframe endpoints, so reduced-motion collapses to a static "riot". */
#holding{position:fixed;inset:0;z-index:60;background:var(--paper);
  display:flex;align-items:center;justify-content:center;padding:32px 28px}
#holding[hidden]{display:none}
/* html.hold is set by the tiny inline head script BEFORE first paint: the boot
   block at the end of body only runs after the bundles/Firebase load — far
   too late to hide the app chrome. These two rules swap shell↔holding on frame
   one; keep the [hidden] override AFTER #holding[hidden] so it out-ranks it. */
html.hold .app{display:none}
html.hold #holding[hidden]{display:flex}
.hd-riot{position:relative;margin:0;font-weight:800;line-height:1;
  font-size:clamp(13px,3.85vw,27px);letter-spacing:-.045em;color:var(--ink);
  user-select:none;-webkit-user-select:none}
/* chromatic split: the word tears into a rose echo (left) and a blue echo
   (right) — anaglyph misalignment, both layers fire on the SAME clock so the
   tricolour reads as one event, not two slips. Multiply keeps it print-like. */
.hd-riot::before,.hd-riot::after{content:attr(data-text);position:absolute;
  inset:0;opacity:0;pointer-events:none;mix-blend-mode:multiply}
.hd-riot::before{color:#E8336E;animation:hdSplitR 7s steps(1) infinite}
.hd-riot::after{color:#2A52E8;animation:hdSplitB 7s steps(1) infinite}
@keyframes hdSplitR{
  0%,100%{opacity:0;transform:none;clip-path:inset(0)}
  43%{opacity:.9;transform:translate(-2px,1px);clip-path:inset(40% 0 30% 0)}
  43.9%{opacity:0}
  88%{opacity:.9;transform:translate(-3px,1px);clip-path:inset(0)}
  89.2%{opacity:.9;transform:translate(-2px,0);clip-path:inset(45% 0 12% 0)}
  90.4%{opacity:0}}
@keyframes hdSplitB{
  0%,100%{opacity:0;transform:none;clip-path:inset(0)}
  43%{opacity:.9;transform:translate(2px,-1px);clip-path:inset(12% 0 52% 0)}
  43.9%{opacity:0}
  88%{opacity:.9;transform:translate(3px,-1px);clip-path:inset(0)}
  89.2%{opacity:.9;transform:translate(2px,0);clip-path:inset(8% 0 58% 0)}
  90.4%{opacity:0}}

/* stage lobby: the join moment */
.sg-lobby{margin:auto;display:flex;flex-direction:column;align-items:center;text-align:center;padding:20px 0}
/* the join QR: print ink on a true-white panel (scanners want white, not paper) */
.sg-qr{width:clamp(150px,30vmin,300px);height:auto;margin:18px 0 4px;color:var(--ink);
  background:#fff;border:1px solid var(--rule);border-radius:12px;padding:clamp(8px,1.4vmin,14px)}
.sg-join{margin:14px 0 6px;font:700 clamp(24px,5vw,64px) var(--f-mono);letter-spacing:.01em;word-break:break-all}
.sg-sub{margin:6px 0 0;font:400 clamp(12px,1.8vw,20px) var(--f-mono);color:var(--ink60)}
.sg-faces{display:flex;flex-wrap:wrap;justify-content:center;max-width:620px;margin:22px 0 12px}
#stage .face{width:clamp(30px,4vw,46px);height:clamp(30px,4vw,46px);font-size:clamp(15px,2vw,23px);margin-left:-9px}
.sg-inlab{margin:0;font:400 clamp(14px,2.2vw,24px) var(--f-mono);color:var(--ink60)}
.sg-inlab b{color:var(--ink);font-size:clamp(20px,3vw,36px)}
/* stage final: the room v. the parliament, big */
.sg-final{margin:auto 0;padding:16px 0}
.sg-final .sg-h{max-width:30ch}
.sg-rvlist{max-width:880px;margin:6px 0 14px}
.sg-rvlist .rv-row{flex-direction:row;align-items:baseline;gap:16px;padding:clamp(8px,1.2vh,14px) 0}
.sg-rvlist .rv-t{flex:1 1 auto;font-size:clamp(14px,2vw,22px)}
.sg-rvlist .badge{font-size:clamp(10px,1.3vw,15px)}
/* stage controls (moderator only) */
#sgCtl{flex:0 0 auto;display:flex;gap:10px;justify-content:center;flex-wrap:wrap;
  padding:12px 0 calc(12px + env(safe-area-inset-bottom));border-top:1px solid var(--rule)}
#sgCtl[hidden]{display:none}
.sgc{min-height:48px;padding:12px 18px;border-radius:9px;font:700 14px var(--f-ui);cursor:pointer;
  background:transparent;border:2px solid var(--ink);color:var(--ink);transition:transform .08s}
.sgc:active{transform:scale(.97)}
.sgc.pri{background:var(--ink);color:var(--card);border-color:var(--ink)}
.sgc.danger{border-color:rgba(156,61,61,.55);color:var(--brick)}
.sgc.danger.armed{background:rgba(156,61,61,.08);border-color:var(--brick)}

/* ---- moderator setup ---- */
#modSetup{flex:1 1 auto;overflow-y:auto;min-height:0;padding:8px 16px 40px}
#modSetup[hidden]{display:none}
.ms-wrap{max-width:720px;margin:0 auto}
.ms-k{margin:8px 0 4px;font:700 11px var(--f-mono);letter-spacing:.2em;text-transform:uppercase;color:var(--stamp)}
.ms-h{margin:0 0 6px;font-size:26px;font-weight:800;letter-spacing:-.015em;line-height:1.2}
.ms-sub{margin:0 0 16px;color:var(--ink60);font-size:14px}
.ms-list{display:flex;flex-direction:column;gap:8px;margin:0 0 18px}
.ms-sess{display:flex;align-items:center;gap:12px;background:var(--card);border:1px solid var(--rule);
  border-radius:10px;padding:14px 16px;font:inherit;color:var(--ink);cursor:pointer;min-height:56px;text-align:left}
.ms-sess:hover{border-color:var(--ink60)}
.ms-sess:active{transform:scale(.995)}
.ms-sess.all{border-color:rgba(75,63,160,.45)}
.ms-sess.all .ms-date{color:var(--stamp)}
.ms-date{font:700 13px var(--f-mono);flex:0 0 auto}
.ms-code{font:400 12px var(--f-mono);color:var(--ink60);flex:1 1 auto;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ms-n{font:700 12px var(--f-mono);flex:0 0 auto}
.ms-item{display:flex;gap:12px;align-items:flex-start;background:var(--card);border:1px solid var(--rule);
  border-radius:9px;padding:13px 14px;cursor:pointer;min-height:48px}
.ms-item.off{opacity:.45}
.ms-item input{width:20px;height:20px;accent-color:var(--stamp);margin-top:1px;flex:0 0 auto}
.ms-it{font-size:14px;line-height:1.4}
.ms-cfg{display:flex;flex-direction:column;gap:10px;margin:0 0 18px}
.ms-cfg label{display:flex;align-items:center;gap:8px;font:400 13px var(--f-mono);color:var(--ink80);flex-wrap:wrap}
.ms-cfg input{width:68px;background:var(--card);border:1px solid var(--rule);border-radius:7px;
  padding:9px 6px;font:700 15px var(--f-mono);color:var(--ink);text-align:center;min-height:44px}
.ms-cfg .switch input{width:0;min-height:0;padding:0;border:0}   /* the GHOST switch reuses the sheet idiom */
.ms-cfg small{color:var(--ink60)}
.ms-acts{display:flex;gap:10px}
.ms-back{flex:0 0 auto;background:transparent;border:2px solid var(--ink);color:var(--ink);
  border-radius:9px;padding:12px 16px;font:700 14px var(--f-ui);cursor:pointer;min-height:50px}
.ms-go{flex:1 1 auto;background:var(--ink);color:var(--card);border:0;border-radius:9px;
  padding:12px 16px;font:700 15px var(--f-ui);cursor:pointer;min-height:50px}
.ms-go:active,.ms-back:active{transform:scale(.98)}

/* ---- quality floor ---- */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-delay:0ms!important;transition-duration:.01ms!important}
}
