@charset "UTF-8";

/* ============================================================
   Card Battle Lab · Rules: Gundam Card Game Comprehensive Rules explorer.
   Single self-contained file. Fetches ../data/rules/rules.json.
   Aesthetic: "tuned code editor for the rulebook" — mono for
   structure, serif for substance, mint as the single glow.
   ============================================================ */
:root{
  --bg:#0c0f12; --surface:#13181c; --surface2:#1a2127; --reader:#0c0f12;  /* reader surface == app bg so every view shares one background */
  --border:#222b31; --border-strong:#2c373e;
  --text:#dbe2e6; --prose:#e3e8ea; --muted:#9aa6af; --muted2:#7e8a94;
  --accent:#5af0c0; --accent-text:#5af0c0; --accent-dim:#2f8a72; --accent-wash:rgba(90,240,192,.16);
  --accent2:#79a8ff; --xref:#8fb6ff; --amber:#f0b24a; --amber-text:#f0b24a; --amber-wash:rgba(240,178,74,.16);
  --example-tint:rgba(121,168,255,.07); --focus:#5af0c0; --selection:rgba(90,240,192,.22);
  --trait:#cda6e0;
  /* official card-label lozenge colors (fallbacks; the theme bridge remaps these
     to the game's --c-lz-* tokens). keyword ability + trigger + activate + burst
     + pair + link + once, mirroring the printed on-card categories. */
  --lbl-keyword:#f6fafa;   /* combat keywords <...> : near-white */
  --lbl-trigger:#64d3d9;   /* Deploy / Attack / Destroyed : cyan */
  --lbl-activate:#64b3ec;  /* Activate Main / Action : blue */
  --lbl-burst:#d3860a;     /* Burst : orange (light ink) */
  --lbl-pair:#c76c98;      /* When/During Pair : pink */
  --lbl-link:#f5ee30;      /* When/During Link : yellow */
  --lbl-once:#7d2b3a;      /* Once per Turn : maroon (light ink) */
  --lbl-ink:#171b1f;       /* dark text inside light/colored chips */
  --lbl-ink-light:#f2f2f2; /* light text inside dark chips (burst / once) */
  --lbl-border:rgba(0,0,0,.22);
  --kw-generic:#aab6b3;
  --font-display:'Bricolage Grotesque',system-ui,sans-serif;
  --font-body:'Newsreader',Georgia,serif;
  --font-mono:'JetBrains Mono',ui-monospace,monospace;
  --gutter:90px; --tree-w:312px; --app-max:1340px;
  --line:1.62; --rowpad:9px;
  --topbar-h:50px; --status-h:27px;
}
html[data-theme="light"]{
  --bg:#efe9da; --surface:#f5f0e4; --surface2:#eae3d2; --reader:#efe9da;
  --border:#d9cfb8; --border-strong:#c5b896;
  --text:#33291b; --prose:#2a241b; --muted:#5f5641; --muted2:#6c6049;
  --accent:#1d8f6f; --accent-text:#0b6147; --accent-dim:#5fb89d; --accent-wash:rgba(29,143,111,.16);
  --accent2:#2f63c7; --xref:#23519f; --amber:#a9741a; --amber-text:#7c540f; --amber-wash:rgba(169,116,26,.18);
  --example-tint:rgba(47,99,199,.08); --focus:#1d8f6f; --selection:rgba(29,143,111,.2);
  --trait:#7e3fa6;
}
html[data-contrast="more"]{ --prose:#ffffff; --text:#ffffff; --border:#3a4750; --border-strong:#55656f; --muted:#c2ccd2; --muted2:#aab4bb; }

*{box-sizing:border-box}
html,body{height:100%}
img{-webkit-touch-callout:none;-webkit-user-drag:none}
body{
  margin:0; background:var(--bg); color:var(--text);
  font-family:var(--font-body); font-size:16px;
  -webkit-font-smoothing:antialiased;
}
::selection{background:var(--selection)}
:focus-visible{outline:2px solid var(--focus); outline-offset:2px; border-radius:3px}
button,select,input{font-family:inherit; color:inherit}
.mono{font-family:var(--font-mono); font-variant-numeric:tabular-nums}
a{color:var(--xref)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}

/* ---------- App shell ---------- */
#app{display:flex; flex-direction:column; height:100dvh}
.topbar{
  flex:0 0 auto; background:linear-gradient(180deg,var(--surface),var(--surface2));
  border-bottom:1px solid var(--border); position:relative; z-index:40;
}
/* Top bar is a single row (brand + menu bar + utilities), sharing the centered
   site column. Search + the active view's filter tags live at the top of the body
   column instead (aligned to the content, not stretched over the index). */
.topinner{max-width:var(--app-max); width:100%; margin-inline:auto; padding:8px 14px}
.tb-row{display:flex; align-items:center; gap:12px; min-width:0}
.tb-top{flex-wrap:wrap; row-gap:8px}
.tb-top .brand{margin-right:auto}
/* Body column: search + filter tags pinned at its top, view content scrolls below */
.body{flex:1 1 auto; min-width:0; min-height:0; display:flex; flex-direction:column}
.bodyhead{flex:0 0 auto; display:flex; flex-direction:column; gap:8px; padding:10px 22px;
  border-bottom:1px solid var(--border); background:var(--bg)}
.bodyhead .searchwrap{max-width:none; margin:0}
.bodyhead .filterbar{border-bottom:0; background:none; padding:0}
body[data-view="home"] .bodyhead{display:none}
/* only the active view's filter bar shows, on the rules + FAQ views */
#filterbar,#faqFilterbar{display:none}
body[data-view="rules"] #filterbar{display:flex}
body[data-view="faq"] #faqFilterbar{display:flex}
.brand{display:flex; align-items:center; gap:10px; white-space:nowrap}
.brand .mark{display:inline-flex; align-items:center; gap:8px; font-family:var(--font-display); font-weight:800; font-size:1.18rem; letter-spacing:-.01em; color:var(--text); cursor:pointer}
/* Wide title lockup (1600x266) replaces the "Card Battle Lab" wordmark; height-bound so it scales with the bar */
.brand .brand-img{display:block; width:auto; height:34px; max-width:min(52vw,300px); object-fit:contain; object-position:left center}
.brand .mark b{color:var(--accent-text); font-weight:800}
.brand .ver{font-family:var(--font-mono); font-size:.72rem; color:var(--muted); border:1px solid var(--border-strong); border-radius:999px; padding:2px 8px}
.searchwrap{flex:1 1 auto; min-width:0; max-width:700px; margin:0 auto; display:flex; align-items:center; gap:8px}
.searchfield{position:relative; flex:1 1 auto; min-width:0}
.searchwrap input{
  width:100%; background:var(--bg); border:1px solid var(--border-strong); border-radius:8px;
  padding:8px 30px 8px 32px; font-family:var(--font-mono); font-size:.86rem; color:var(--text);
}
.searchwrap input::placeholder{color:var(--muted2)}
.searchwrap .sicon{position:absolute; left:10px; top:50%; transform:translateY(-50%); color:var(--muted); pointer-events:none; font-size:.8rem}
.searchwrap .skbd{position:absolute; right:8px; top:50%; transform:translateY(-50%); font-family:var(--font-mono); font-size:.64rem; color:var(--muted2); border:1px solid var(--border); border-radius:4px; padding:1px 5px}
.searchwrap input[type=search]::-webkit-search-cancel-button{-webkit-appearance:none; appearance:none}
/* unified Clear — search text + filters + selection. Sits just right of the field; JS toggles .show when any of those is dirty. */
.searchwrap .clearall{flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; height:33px; padding:0 12px; border:1px solid var(--border-strong); background:var(--bg); color:var(--muted); border-radius:8px; cursor:default; font-family:var(--font-mono); font-size:.72rem; font-weight:600; white-space:nowrap; line-height:1; transition:color .12s,border-color .12s,background .12s}
.searchwrap .clearall.show{border-color:var(--accent-dim); background:var(--accent-wash); color:var(--accent-text); cursor:pointer}
.searchwrap .clearall.show:hover{border-color:var(--accent); color:var(--text)}
.searchfield #search:not(:placeholder-shown) ~ .skbd{display:none}
.toprt{display:flex; align-items:center; gap:8px; white-space:nowrap}
.viewswitch{display:flex; background:var(--bg); border:1px solid var(--border-strong); border-radius:8px; overflow:hidden}
.viewswitch button{
  background:none; border:0; padding:7px 12px; font-family:var(--font-display); font-weight:600;
  font-size:.74rem; letter-spacing:.03em; text-transform:uppercase; color:var(--muted); cursor:pointer;
  display:inline-flex; align-items:center; gap:6px;
}
.viewswitch button[aria-selected="true"]{background:var(--accent-wash); color:var(--accent-text)}
.viewswitch button:hover{color:var(--text); box-shadow:inset 0 0 0 1px var(--accent)}
.viewswitch button[aria-selected="true"]:hover{color:var(--accent-text)}
.viewswitch .navi{display:inline-flex; opacity:.85}
.viewswitch button[aria-selected="true"] .navi{opacity:1}
.viewswitch .navi svg{width:15px; height:15px; display:block}
.botnav .bni[data-icon]{display:inline-flex; align-items:center; justify-content:center}
.botnav .bni svg{width:21px; height:21px; display:block}
.sbtn .navi{display:inline-flex; vertical-align:-3px}
.sbtn .navi svg{width:16px; height:16px; display:block}
.ctl{background:var(--bg); border:1px solid var(--border-strong); border-radius:8px; padding:6px 8px; font-family:var(--font-mono); font-size:.74rem; color:var(--text); cursor:pointer}
.ctl:disabled{opacity:.5; cursor:not-allowed}
.iconbtn{background:var(--bg); border:1px solid var(--border-strong); border-radius:8px; width:34px; height:33px; cursor:pointer; color:var(--muted); font-size:.95rem; display:inline-flex; align-items:center; justify-content:center}
.iconbtn:hover{border-color:var(--accent); color:var(--text)}
#collapseAllBtn svg{width:18px; height:18px; display:block}
body:not([data-view="rules"]):not([data-view="faq"]) #collapseAllBtn{display:none}

/* ---------- Search palette ---------- */
.palette{
  position:absolute; top:calc(100% + 6px); left:0; right:0; background:var(--surface);
  border:1px solid var(--border-strong); border-radius:10px; box-shadow:0 16px 44px rgba(0,0,0,.5);
  max-height:60vh; overflow:auto; display:none; z-index:60; padding:5px;
}
.palette.open{display:block; animation:rise .09s ease}
@keyframes rise{from{opacity:0; transform:translateY(8px)}to{opacity:1; transform:none}}
.palette .pitem{display:grid; grid-template-columns:88px 1fr; gap:10px; padding:7px 9px; border-radius:7px; cursor:pointer; align-items:baseline}
.palette .pitem[aria-selected="true"]{background:var(--accent-wash)}
.palette .pitem .pnum{font-family:var(--font-mono); font-size:.78rem; font-weight:600; color:var(--accent-text)}
.palette .pitem .pbody{min-width:0}
.palette .pitem .pcrumb{font-family:var(--font-mono); font-size:.66rem; color:var(--muted); margin-bottom:1px}
.palette .pitem .psnip{font-size:.82rem; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.palette .pjump{display:flex; gap:10px; align-items:center; padding:8px 9px; border-radius:7px; cursor:pointer; background:var(--bg)}
.palette .pjump .tag{font-family:var(--font-display); font-weight:700; font-size:.66rem; letter-spacing:.06em; text-transform:uppercase; color:var(--accent-text); border:1px solid var(--accent-dim); border-radius:4px; padding:2px 6px}
.palette .phint{padding:6px 10px; font-family:var(--font-mono); font-size:.68rem; color:var(--muted)}

/* ---------- Main ---------- */
.main{flex:1 1 auto; display:flex; min-height:0; width:100%; max-width:var(--app-max); margin-inline:auto}
.scrim{display:none; position:fixed; inset:var(--topbar-h) 0 var(--status-h) 0; background:rgba(0,0,0,.45); z-index:34}
.scrim.show{display:block}
.tree{
  width:var(--tree-w); flex:0 0 auto; background:var(--surface); border-right:1px solid var(--border);
  overflow:auto; padding:8px 4px 18px; transition:width .18s cubic-bezier(.4,0,.2,1)}
.tree.collapsed{width:0; padding:0; overflow:hidden; border-right:0}
.tree .tlabel{font-family:var(--font-display); font-weight:600; font-size:.64rem; text-transform:uppercase; letter-spacing:.08em; color:var(--muted); padding:4px 12px 8px}
.tree .tpagelabel{margin-top:12px; border-top:1px solid var(--border); padding-top:12px}
.tree .tpagehead:first-child{margin-top:0; border-top:0; padding-top:8px}
.tree .tpagehead{display:flex; align-items:center; gap:4px; cursor:pointer; color:var(--muted)}
.tree .tpagehead:hover{color:var(--text)}
.tree .tpagehead .caret{flex:0 0 14px; text-align:center; font-size:.6rem; color:var(--muted2); transition:transform .12s ease; user-select:none}
.tree .tpagehead.expanded .caret{transform:rotate(90deg)}
.tree .tpagehead .tpgttl{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.tfaqnode .tnum{color:var(--amber-text)}
.tfaqnode .tcount{background:var(--amber-wash); color:var(--amber-text)}
.tnode{display:flex; align-items:center; gap:4px; padding:3px 8px 3px 0; border-radius:6px; cursor:pointer; line-height:1.4; color:var(--muted)}
.tnode:hover{background:var(--surface2); color:var(--text)}
.tnode.active{background:var(--accent-wash); color:var(--accent-text)}
.tnode .caret{flex:0 0 16px; text-align:center; font-size:.6rem; color:var(--muted2); transition:transform .12s ease; user-select:none}
.tnode.expanded .caret{transform:rotate(90deg)}
.tnode .caret.leaf{visibility:hidden}
.tnode .tnum{font-family:var(--font-mono); font-size:.78rem; font-weight:500; color:inherit; flex:0 0 auto}
.tnode .ttitle{font-size:.8rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--muted)}
.tnode.active .ttitle{color:var(--text)}
.tnode .tcount{margin-left:auto; font-family:var(--font-mono); font-size:.62rem; color:var(--muted); background:var(--bg); border-radius:999px; padding:0 6px; flex:0 0 auto}
.tnode.thidden{display:none}
.tchildren{display:none}
.tchildren.open{display:block}
.tsection{margin-bottom:1px}
.tsection>.tnode .tnum{color:var(--accent2)}

/* ---------- Reader ---------- */
.readwrap{flex:1 1 auto; min-width:0; min-height:0; display:flex; flex-direction:column; background:var(--reader)}
.crumbs{flex:0 0 auto; position:sticky; top:0; z-index:20; display:flex; align-items:center; gap:6px;
  padding:8px 22px; background:var(--reader);
  font-family:var(--font-mono); font-size:.74rem; color:var(--muted); min-height:34px; flex-wrap:wrap; border-bottom:1px solid var(--border)}
.crumbs a{color:var(--muted); text-decoration:none}
.crumbs a:hover{color:var(--accent-text)}
.crumbs .sep{color:var(--muted2)}
.crumbs .cur{color:var(--text)}
.reader{flex:1 1 auto; overflow:auto; padding:6px 0 40vh}
/* FAQ matches surfaced inside the rules reader while searching */
.rfaq{margin:10px 0 18px; border:1px solid var(--amber); background:var(--amber-wash); border-radius:10px; padding:10px 12px}
.rfaq-h{display:flex; align-items:center; justify-content:space-between; gap:10px; font-family:var(--font-mono); font-size:.74rem; color:var(--amber-text); margin-bottom:6px}
.rfaq-n{background:var(--amber); color:var(--c-ink-dark,#171b1f); border-radius:999px; padding:0 6px; font-weight:700; margin-left:2px}
.rfaq-all{border:0; background:none; color:var(--amber-text); font-family:var(--font-mono); font-size:.72rem; cursor:pointer; text-decoration:underline}
.rfaq-item{display:flex; align-items:center; gap:8px; width:100%; text-align:left; border:0; background:none; cursor:pointer; padding:5px 4px; border-radius:6px; color:var(--text)}
.rfaq-item:hover{background:color-mix(in srgb, var(--amber) 14%, transparent)}
.rfaq-item .rfaq-q{flex:1 1 auto; font-size:.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.rfaq-item .rfaq-code{font-family:var(--font-mono); font-size:.68rem; color:var(--muted)}
/* blue variant: the "Matching rules" peek points at the Rules page (index/section blue), not FAQ amber */
.rfaq.rfaq-blue{border-color:var(--accent2); background:color-mix(in srgb, var(--accent2) 13%, transparent)}
.rfaq.rfaq-blue .rfaq-h{color:var(--accent2)}
.rfaq.rfaq-blue .rfaq-n{background:var(--accent2); color:var(--c-ink-light,#f2f2f2)}
.rfaq.rfaq-blue .rfaq-all{color:var(--accent2)}
.rfaq.rfaq-blue .rfaq-item:hover{background:color-mix(in srgb, var(--accent2) 14%, transparent)}
.rfaq.rfaq-blue .faqbadge{color:var(--accent2); border-color:var(--accent2)}
.rcol{max-width:980px; margin:0 auto; padding:0 22px}

.group .ghead{display:flex; align-items:baseline; gap:12px; margin:26px 0 6px}
.group.collapsible .ghead{cursor:pointer}
.ghead .gcaret{font-family:var(--font-mono); font-size:.7rem; color:var(--muted2); transition:transform .12s; display:none}
.group.collapsible .ghead .gcaret{display:inline}
.group.collapsed .ghead .gcaret{transform:rotate(-90deg)}
.ghead .gn{font-family:var(--font-mono); font-size:.8rem; font-weight:700; color:var(--accent-text)}
.ghead .gt{font-family:var(--font-display); font-weight:700; font-size:1.5rem; letter-spacing:-.01em; color:var(--text)}
.ghead .gc{font-family:var(--font-mono); font-size:.7rem; color:var(--muted)}
.ghead .grule{flex:1 1 auto; height:1px; background:var(--border); align-self:center; margin-left:6px}
.group.collapsible .ghead:hover .gt{color:var(--accent-text)}
.group.collapsed .gbody{display:none}

.rule{
  padding:var(--rowpad) 0; position:relative; padding-left:14px;
  content-visibility:auto; contain-intrinsic-size:auto 64px;
  scroll-margin-top:60px;
}
.rule:hover{background:rgba(255,255,255,.018)}
html[data-theme="light"] .rule:hover{background:rgba(0,0,0,.025)}
.rule.rhidden{display:none}
/* one indented line: [caret] [number] [text] — caret left of number, all indented by depth */
.rline{display:flex; align-items:flex-start; gap:8px; padding-left:var(--indent,0); padding-right:30px}
.rule .rcaret{flex:0 0 12px; width:12px; margin-top:.34em; padding:0; border:0; background:none; color:var(--muted2); font-size:.56rem; line-height:1; text-align:center; cursor:pointer; transition:transform .12s ease, color .12s; user-select:none}
.rule.rexpanded .rcaret{transform:rotate(90deg)}
.rule .rcaret.leaf{visibility:hidden; cursor:default}
.rule .rcaret:hover{color:var(--accent-text)}
.rule .num{flex:0 0 auto; font-family:var(--font-mono); font-size:.78rem; font-weight:600; color:var(--muted); padding-top:.18em; white-space:nowrap}
.rule .num a{color:var(--muted); text-decoration:none}
.rule .num a:hover{color:var(--accent-text)}
.rule .prose{flex:1 1 auto; font-size:1.03rem; line-height:var(--line); color:var(--prose); max-width:70ch; font-weight:400}
.rule.depth2 .prose{font-family:var(--font-display); font-weight:600; font-size:1.06rem; color:var(--text)}
.rule.pulse .rline{animation:pulse .6s ease}
@keyframes pulse{0%{box-shadow:0 0 0 0 transparent}30%{box-shadow:-3px 0 0 0 var(--accent)}100%{box-shadow:-3px 0 0 0 transparent}}
.rule.selected{background:var(--accent-wash); box-shadow:inset 2px 0 0 var(--accent); border-radius:4px}
html[data-theme="light"] .rule.selected{background:var(--accent-wash)}
.rule .copy{position:absolute; right:4px; top:.3em; opacity:0; border:0; background:none; color:var(--muted2); cursor:pointer; padding:2px; line-height:0; display:inline-flex; border-radius:5px; transition:opacity .12s, color .12s, background .12s}
.rule .copy svg{width:14px; height:14px; display:block; pointer-events:none}
.rule:hover .copy,.rule:focus-within .copy,.rule.selected .copy{opacity:1}
.rule .copy:hover{color:var(--accent-text); background:var(--accent-wash)}
.rule .copy.copied{color:var(--accent-text); opacity:1}

.prose .ex{display:block; margin-top:6px; padding:6px 12px; border-left:2px solid var(--accent2);
  background:var(--example-tint); font-style:italic; font-size:.96rem; color:var(--text); border-radius:0 4px 4px 0}
.prose mark{background:var(--accent-wash); color:inherit; border-bottom:1px solid var(--accent); border-radius:2px; padding:0 1px}

/* inline pills + xrefs */
.pill{font-family:var(--font-mono); font-size:.78rem; border:1px solid currentColor; border-radius:5px; padding:0 5px; cursor:pointer; white-space:nowrap; background:none}
.pill.trait{color:var(--trait)}
.pill.trait:hover{background:var(--accent-wash)}
/* effect/keyword labels = the game's official on-card lozenges: sharp-cornered
   rectangle, per-category fill, per-category ink. keyword abilities stay
   near-white; trigger cyan / activate blue / burst orange / pair pink / link
   yellow / once maroon match the printed card furniture. */
.pill.kw,.efchip{--lz:var(--lbl-keyword); position:relative; color:var(--lbl-ink); background:var(--lz); border:0; font-weight:700; border-radius:0; padding:0 6px; white-space:nowrap; font-family:var(--font-mono)}
.efchip{display:inline-block; font-size:.86em; line-height:1.4}
.pill.kw.trigger,.efchip.trigger{--lz:var(--lbl-trigger)}
.pill.kw.activate,.efchip.activate{--lz:var(--lbl-activate)}
.pill.kw.pair,.efchip.pair{--lz:var(--lbl-pair)}
.pill.kw.link,.efchip.link{--lz:var(--lbl-link)}
.pill.kw.burst,.efchip.burst{--lz:var(--lbl-burst); color:var(--lbl-ink-light)}
.pill.kw.once,.efchip.once{--lz:var(--lbl-once); color:var(--lbl-ink-light)}
.pill.kw:hover{filter:brightness(1.08)}
.kwrow .efchip.kwnav{cursor:pointer; padding:5px 11px; font-size:.82rem}
.kwrow .efchip.kwnav:hover{filter:brightness(1.08); transform:translateY(-1px)}
a.xref{color:var(--xref); text-decoration:underline; text-underline-offset:2px; text-decoration-thickness:1px; cursor:pointer; font-family:var(--font-mono); font-size:.92em}
a.xref.sec{font-family:inherit; font-size:1em}
.annot{display:flex; flex-wrap:wrap; gap:6px; margin-top:5px; padding-left:calc(var(--indent,0) + 20px)}
.annot .a{font-family:var(--font-mono); font-size:.68rem; color:var(--muted); background:none; border:1px solid var(--border); border-radius:5px; padding:1px 6px; cursor:pointer}
.annot .a.chg{color:var(--amber-text); border-color:var(--amber); background:var(--amber-wash)}
.annot .a:hover{border-color:var(--accent-dim); color:var(--text)}

/* ---------- Filter bar ---------- */
.filterbar{flex:0 0 auto; display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:8px 22px; border-bottom:1px solid var(--border); background:var(--surface2)}
.fgroup{display:flex; align-items:center; gap:5px; flex-wrap:wrap}
.flabel{font-family:var(--font-display); font-weight:600; font-size:.62rem; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin-right:2px}
.chip{font-family:var(--font-mono); font-size:.72rem; color:var(--muted); background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:3px 8px; cursor:pointer; white-space:nowrap}
.chip:hover{border-color:var(--accent); color:var(--text)}
.chip.on{background:var(--accent-wash); border-color:var(--accent-dim); color:var(--accent-text)}
/* keyword filter lozenges: official lozenge colour as OUTLINE + TEXT when off,
   the full official fill + ink when on (same palette as the on-card chips). */
.chip.kwf{--lz:var(--lbl-keyword); color:var(--lz); background:none; border-radius:0; font-weight:700; border-color:color-mix(in srgb,var(--lz) 55%,transparent)}
.chip.kwf.trigger{--lz:var(--lbl-trigger)} .chip.kwf.activate{--lz:var(--lbl-activate)}
.chip.kwf.pair{--lz:var(--lbl-pair)} .chip.kwf.link{--lz:var(--lbl-link)}
.chip.kwf.burst{--lz:var(--lbl-burst)} .chip.kwf.once{--lz:var(--lbl-once)}
.chip.kwf:hover{border-color:var(--lz); color:var(--lz); filter:brightness(1.1)}
.chip.kwf.on{background:var(--lz); color:var(--lbl-ink); border-color:var(--lz)}
.chip.kwf.burst.on,.chip.kwf.once.on{color:var(--lbl-ink-light)}
.fdiv{width:1px; height:18px; background:var(--border); margin:0 2px}
.filterbar input[type=range]{accent-color:var(--accent); width:96px}
.depthval{font-family:var(--font-mono); font-size:.72rem; color:var(--accent-text); min-width:34px}
.fclear{margin-left:auto; font-family:var(--font-mono); font-size:.72rem; color:var(--muted); background:none; border:1px solid var(--border); border-radius:6px; padding:3px 9px; cursor:pointer}
.fclear:hover{color:var(--accent-text); border-color:var(--accent-dim)}

/* ---------- Glossary / Changelog ---------- */
.viewpanel{flex:1 1 auto; min-height:0; overflow:auto; padding:24px 0 30vh}
.gcol{max-width:920px; margin:0 auto; padding:0 24px}   /* one shared width for every secondary view so the content box never shifts between tabs */
.gcol h2{font-family:var(--font-display); font-weight:700; font-size:1.7rem; color:var(--text); margin:34px 0 4px; scroll-margin-top:70px}
.gcol h2:first-child{margin-top:6px}
.gcol .gsub{color:var(--muted); font-size:.95rem; margin:0 0 16px}
.cards{display:grid; grid-template-columns:repeat(auto-fill,minmax(280px,1fr)); gap:14px}
.gcard{background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px; display:flex; flex-direction:column; gap:8px; cursor:pointer; transition:border-color .12s, transform .08s; scroll-margin-top:70px}
.gcard:hover{border-color:var(--accent-dim); transform:translateY(-1px)}
.gcard:focus-visible{outline:2px solid var(--focus); outline-offset:2px}
.gcard .ct{display:flex; align-items:center; gap:8px; justify-content:space-between}
.gcard .ctt{font-family:var(--font-display); font-weight:700; font-size:1.02rem; color:var(--text)}
.gcard .pill{font-size:.82rem}
.gcard .cg{font-size:.92rem; line-height:1.5; color:var(--muted); flex:1}
.gcard .open{align-self:flex-start; font-family:var(--font-mono); font-size:.72rem; color:var(--accent-text); background:var(--accent-wash); border:1px solid var(--accent-dim); border-radius:6px; padding:4px 9px; pointer-events:none}
.gcard .open .oico{margin-right:5px; font-weight:700}
.gcard.searchable:hover .open{background:var(--accent); color:var(--bg)}
.ghint{font-style:italic; color:var(--muted2)}
.gcard .cnum{font-family:var(--font-mono); font-size:.7rem; color:var(--muted)}

.timeline{position:relative; margin-left:10px; padding-left:24px; border-left:2px solid var(--border)}
.tlnode{position:relative; margin-bottom:26px; scroll-margin-top:70px}
.tlnode::before{content:''; position:absolute; left:-31px; top:5px; width:10px; height:10px; border-radius:50%; background:var(--amber); box-shadow:0 0 0 4px var(--bg)}
.tlnode.cur::before{background:var(--accent)}
.tlhead{display:flex; align-items:baseline; gap:12px; margin-bottom:8px}
.tlhead .v{font-family:var(--font-display); font-weight:700; font-size:1.3rem; color:var(--text)}
.tlhead .d{font-family:var(--font-mono); font-size:.76rem; color:var(--muted)}
.tlhead .badge{font-family:var(--font-mono); font-size:.62rem; color:var(--accent-text); border:1px solid var(--accent-dim); border-radius:999px; padding:1px 8px}
.chgrow{display:flex; align-items:center; gap:9px; padding:4px 0; flex-wrap:wrap}
.chgrow .verb{font-family:var(--font-mono); font-size:.64rem; text-transform:uppercase; letter-spacing:.04em; color:var(--amber-text); border:1px solid var(--amber); background:var(--amber-wash); border-radius:4px; padding:1px 6px}
.chgrow .cnum{font-family:var(--font-mono); font-size:.82rem; color:var(--xref); text-decoration:underline; cursor:pointer}
.chgrow .cnum.dead{color:var(--muted2); text-decoration:line-through; cursor:default}
.chgrow .deadnote{font-family:var(--font-mono); font-size:.64rem; color:var(--muted2); font-style:italic}
.chgrow .ctext{font-size:.86rem; color:var(--muted)}
.chgrow .vfilter{margin-left:6px; font-family:var(--font-mono); font-size:.66rem; color:var(--muted); border:1px solid var(--border); border-radius:5px; padding:1px 7px; cursor:pointer}
.chgrow .vfilter:hover{color:var(--accent-text); border-color:var(--accent-dim)}

/* ---------- Status bar ---------- */
.status{height:var(--status-h); flex:0 0 auto; display:flex; align-items:center; gap:14px; padding:0 14px;
  background:var(--surface); border-top:1px solid var(--border); font-family:var(--font-mono); font-size:.72rem; color:var(--muted)}
.status b{color:var(--accent-text); font-weight:700}
.status .seg{display:flex; align-items:center; gap:5px}
.status .hitstep button{background:none; border:1px solid var(--border); border-radius:4px; color:var(--muted); cursor:pointer; width:18px; height:16px; line-height:1; font-size:.7rem}
.status .back{margin-left:auto; cursor:pointer; color:var(--muted)}
.status .back:hover{color:var(--accent-text)}
.status .back[hidden]{display:none}
.status .km{color:var(--muted2)}

/* ---------- Overlays ---------- */
.toast{position:fixed; bottom:46px; left:50%; transform:translateX(-50%) translateY(10px); background:var(--accent); color:var(--bg);
  font-family:var(--font-mono); font-size:.78rem; font-weight:600; padding:8px 16px; border-radius:8px; opacity:0; pointer-events:none; transition:.2s; z-index:90}
.toast.show{opacity:1; transform:translateX(-50%)}
.help{position:fixed; inset:0; background:rgba(0,0,0,.6); display:none; align-items:center; justify-content:center; z-index:100}
.help.open{display:flex}
.help .box{background:var(--surface); border:1px solid var(--border-strong); border-radius:14px; padding:24px; max-width:520px; width:90%}
.help h3{font-family:var(--font-display); margin:0 0 14px; color:var(--text)}
.help .kg{display:grid; grid-template-columns:auto 1fr; gap:8px 16px; font-size:.86rem}
.help kbd{font-family:var(--font-mono); font-size:.74rem; background:var(--bg); border:1px solid var(--border-strong); border-radius:5px; padding:2px 7px; color:var(--accent-text)}
.help .desc{color:var(--muted)}
.help .hclose{margin-top:18px; background:var(--accent-wash); border:1px solid var(--accent-dim); color:var(--accent-text); border-radius:8px; padding:8px 14px; font-family:var(--font-mono); font-size:.78rem; cursor:pointer}
.loading{position:fixed; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:14px; color:var(--muted); font-family:var(--font-mono); font-size:.85rem; background:var(--bg); z-index:200; text-align:center; padding:20px}
.loading .spin{width:30px; height:30px; border:2px solid var(--border); border-top-color:var(--accent); border-radius:50%; animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}
.hamb{display:none}

/* ---------- Responsive ---------- */
@media(max-width:960px){
  .tree{position:fixed; left:0; top:var(--topbar-h); bottom:var(--status-h); z-index:36; box-shadow:10px 0 30px rgba(0,0,0,.4)}
  .tree.collapsed{box-shadow:none}
  .hamb{display:inline-flex}
}
@media(max-width:720px){
  .searchwrap{max-width:none}
  .viewswitch button{padding:7px 8px; font-size:.66rem}
  .toprt .ctl.hideSm{display:none}
  .rcol,.crumbs{padding-left:14px; padding-right:14px}
  :root{--gutter:72px}
}
@media (prefers-reduced-motion: reduce){ *{animation-duration:.001ms !important; transition-duration:.001ms !important} }

/* ============================================================
   PUBLIC REDESIGN — tokens, landing, glossary, mobile nav
   Layered on top of the original "code-editor" core so the
   reader stays clean while Home/Glossary feel welcoming.
   ============================================================ */
:root{
  --botnav-h:0px;
  --elev:0 1px 0 rgba(255,255,255,.03), 0 10px 30px rgba(0,0,0,.36);
  --card-radius:14px;
  /* one accent for every section group (standalone fallback; the theme-bridge
     maps all --sec-* to --accent when loaded) */
  --sec-1:var(--accent); --sec-2:var(--accent); --sec-3:var(--accent); --sec-4:var(--accent); --sec-5:var(--accent);
  --sec-6:var(--accent); --sec-7:var(--accent); --sec-8:var(--accent); --sec-9:var(--accent); --sec-10:var(--accent);
  --sec-11:var(--accent); --sec-12:var(--accent); --sec-13:var(--accent);
}
html[data-theme="light"]{ --elev:0 1px 0 rgba(255,255,255,.6), 0 12px 30px rgba(70,52,22,.13); }
/* the [hidden] attribute must win over panels that set an explicit display (.readwrap) */
[hidden]{display:none !important}

/* ---------- Home / landing ---------- */
.home-panel{background:var(--reader); padding:0}
.home{max-width:920px; margin:0 auto; padding:0 24px calc(var(--botnav-h) + 80px)}  /* same column as every other view */
.hero{padding:clamp(40px,7vw,84px) 0 26px; max-width:780px}
.hero .eyebrow{font-family:var(--font-mono); font-size:.74rem; letter-spacing:.16em; text-transform:uppercase; color:var(--accent-text); margin-bottom:14px; opacity:0; animation:hin .5s .02s ease forwards}
.hero h1{font-family:var(--font-display); font-weight:800; font-size:clamp(1.9rem,5vw,3rem); line-height:1.06; letter-spacing:-.02em; color:var(--text); margin:0 0 10px; opacity:0; animation:hin .5s .06s ease forwards}
.hero h1 b{color:var(--accent-text); font-weight:800}
.hero h1 .hver{font-size:.5em; font-weight:600; color:var(--muted); letter-spacing:0; white-space:nowrap}
.herocopy{font-family:var(--font-mono); font-size:.74rem; color:var(--muted2); letter-spacing:.01em; margin:0 0 20px; opacity:0; animation:hin .5s .1s ease forwards}
.hero .lede{font-family:var(--font-body); font-size:clamp(1.04rem,2.3vw,1.28rem); line-height:1.5; color:var(--muted); max-width:60ch; margin:0 0 24px; opacity:0; animation:hin .5s .12s ease forwards}
.hero .lede b{color:var(--prose); font-weight:600}
.herosearch{position:relative; max-width:540px; margin-bottom:20px; opacity:0; animation:hin .5s .18s ease forwards}
.herosearch input{width:100%; background:var(--surface); border:1px solid var(--border-strong); border-radius:12px; padding:15px 16px 15px 46px; font-family:var(--font-mono); font-size:1rem; color:var(--text); box-shadow:var(--elev)}
.herosearch input::placeholder{color:var(--muted2)}
.herosearch .sicon{position:absolute; left:16px; top:50%; transform:translateY(-50%); color:var(--accent-text); font-size:1.1rem; pointer-events:none}
.cta{display:flex; flex-wrap:wrap; gap:10px; opacity:0; animation:hin .5s .24s ease forwards}
.cta button{font-family:var(--font-display); font-weight:600; font-size:.9rem; border-radius:10px; padding:11px 16px; cursor:pointer; border:1px solid var(--border-strong); background:var(--surface); color:var(--text); display:inline-flex; align-items:center; gap:8px; transition:transform .12s, border-color .12s, background .12s}
.cta button:hover{transform:translateY(-1px); border-color:var(--accent-dim)}
.cta button.primary{background:var(--accent); border-color:var(--accent); color:var(--c-ink-dark,#171b1f); font-weight:700}
.cta button.primary:hover{filter:brightness(1.06)}
.cta button .ci{font-size:1.05rem; opacity:.9}
@keyframes hin{from{opacity:0; transform:translateY(10px)}to{opacity:1; transform:none}}

.hsec{margin-top:44px}
.hsec .hh{display:flex; align-items:baseline; gap:12px; margin-bottom:14px}
.hsec .hh h2{font-family:var(--font-display); font-weight:700; font-size:1.3rem; color:var(--text); margin:0}
.hsec .hh .sub{font-family:var(--font-mono); font-size:.74rem; color:var(--muted)}
.hsec .hh .more{margin-left:auto; font-family:var(--font-mono); font-size:.74rem; color:var(--accent-text); background:none; border:0; cursor:pointer}
.hsec .hh .more:hover{text-decoration:underline}

.secgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(232px,1fr)); gap:12px}
.seccard{position:relative; text-align:left; background:var(--surface); border:1px solid var(--border); border-radius:var(--card-radius); padding:15px 16px 15px 18px; cursor:pointer; overflow:hidden; transition:transform .12s, border-color .12s, box-shadow .12s; display:flex; flex-direction:column; gap:6px}
.seccard::before{content:''; position:absolute; left:0; top:0; bottom:0; width:4px; background:var(--sc,var(--accent))}
.seccard:hover{transform:translateY(-2px); border-color:var(--border-strong); box-shadow:var(--elev)}
.seccard .sn{font-family:var(--font-mono); font-size:.72rem; font-weight:600; color:var(--sc,var(--accent-text))}
.seccard .st{font-family:var(--font-display); font-weight:700; font-size:1.04rem; color:var(--text); line-height:1.15}
.seccard .sd{font-family:var(--font-body); font-size:.92rem; color:var(--muted); line-height:1.4}
.seccard .scount{margin-top:auto; font-family:var(--font-mono); font-size:.66rem; color:var(--muted2)}

.kwrow{display:flex; flex-wrap:wrap; gap:8px}
.kwrow .kwpill{font-family:var(--font-mono); font-size:.82rem; border:1px solid currentColor; border-radius:8px; padding:6px 11px; cursor:pointer; background:color-mix(in srgb, currentColor 9%, transparent); transition:transform .1s}
.kwrow .kwpill:hover{transform:translateY(-1px)}

.whatsnew{background:var(--surface); border:1px solid var(--border); border-radius:var(--card-radius); padding:18px 20px; display:flex; flex-direction:column; gap:8px; box-shadow:var(--elev)}
.whatsnew .wnh{display:flex; align-items:center; gap:10px}
.whatsnew .wnh .v{font-family:var(--font-display); font-weight:700; font-size:1.1rem; color:var(--text)}
.whatsnew .wnh .badge{font-family:var(--font-mono); font-size:.62rem; color:var(--accent-text); border:1px solid var(--accent-dim); border-radius:999px; padding:1px 8px}
.whatsnew .wnl{font-family:var(--font-body); color:var(--muted); font-size:.95rem; line-height:1.5}
.whatsnew button.wnopen{align-self:flex-start; font-family:var(--font-mono); font-size:.74rem; color:var(--accent-text); background:var(--accent-wash); border:1px solid var(--accent-dim); border-radius:8px; padding:6px 11px; cursor:pointer}

.hfoot{margin-top:54px; padding-top:20px; border-top:1px solid var(--border); font-family:var(--font-mono); font-size:.72rem; color:var(--muted2); line-height:1.6}
.hfoot a{color:var(--xref)}

/* ---------- Core mechanics quick-reference cards (in Glossary) ---------- */
.coremech{display:grid; grid-template-columns:repeat(2,1fr); gap:14px}
@media(max-width:700px){.coremech{grid-template-columns:1fr}}
.cmcard{background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px 18px; box-shadow:var(--elev); scroll-margin-top:70px}
.cmcard h3{font-family:var(--font-display); font-weight:700; font-size:1.04rem; color:var(--text); margin:0 0 10px; display:flex; align-items:center; gap:8px}
.cmcard h3 .ico{color:var(--accent-text)}
.cmcard ul{margin:0; padding-left:18px; display:flex; flex-direction:column; gap:7px}
.cmcard li{font-family:var(--font-body); font-size:.95rem; line-height:1.45; color:var(--prose)}
.cmcard .seq{list-style:none; padding:0; counter-reset:s}
.cmcard .seq li{position:relative; padding-left:30px; counter-increment:s}
.cmcard .seq li::before{content:counter(s); position:absolute; left:0; top:0; width:21px; height:21px; border-radius:6px; background:var(--accent-wash); color:var(--accent-text); font-family:var(--font-mono); font-size:.72rem; font-weight:700; display:flex; align-items:center; justify-content:center}

/* click-to-search affordance: a subtle magnifier marking every click-searchable
   word/phrase, consistent across pages + themes. On the filled lozenges it sits
   OUTSIDE the fill (in a reserved right margin) and is tinted by the lozenge's
   own colour (--lz); on the plain text affordances it stays inline. */
.termchip::after, .chip.qterm::after{
  content:"⌕"; margin-left:3px; font-size:.72em; font-weight:400; opacity:.5; vertical-align:baseline;
}
.pill.kw::after, .efchip.kwnav::after{
  content:"⌕"; position:absolute; left:calc(100% + 2px); top:50%; transform:translateY(-50%);
  color:var(--lz); opacity:.85; font-size:.95em; font-weight:700; pointer-events:none;
}
/* reserve room to the right of the fill so the outside glyph never collides
   with following text / the next chip */
.pill.kw, .efchip.kwnav{margin-right:.95em}
/* inert chips (chipsOnly) inside clickable rows: styled look, no ⌕, no pointer */
.pill.kw.nolink{cursor:inherit; pointer-events:none; margin-right:0}
.pill.kw.nolink::after{content:none}
/* split terminology parts — each phrase searches the rules independently */
.termchip{cursor:pointer; color:var(--accent-text); border-bottom:1px dashed var(--accent-dim); font-family:var(--font-mono); font-size:.92em; white-space:nowrap}
.termchip:hover{background:var(--accent-wash)}
/* anchor-arrival flash for tree page/heading navigation */
@keyframes apulse{0%{background:var(--accent-wash)}100%{background:transparent}}
.anchor-pulse{animation:apulse 1s ease; border-radius:6px}

/* ---------- Mobile bottom nav + More sheet ---------- */
.botnav{display:none}
.sheet{position:fixed; left:0; right:0; bottom:0; z-index:70; background:var(--surface); border-top:1px solid var(--border-strong);
  border-radius:18px 18px 0 0; box-shadow:0 -14px 40px rgba(0,0,0,.45); padding:8px 16px calc(16px + env(safe-area-inset-bottom));
  transform:translateY(110%); transition:transform .24s cubic-bezier(.4,0,.2,1); max-height:78vh; overflow:auto}
.sheet.open{transform:none}
.sheet-grip{width:38px; height:4px; border-radius:999px; background:var(--border-strong); margin:6px auto 12px}
.sheet h3{font-family:var(--font-display); font-weight:600; font-size:.64rem; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); margin:16px 0 8px}
.sheet h3:first-of-type{margin-top:0}
.sheet .srow{display:flex; flex-wrap:wrap; gap:8px}
.sheet .sbtn{flex:1 1 calc(50% - 8px); min-height:46px; display:flex; align-items:center; justify-content:center; gap:8px; font-family:var(--font-display); font-weight:600; font-size:.86rem; color:var(--text); background:var(--bg); border:1px solid var(--border-strong); border-radius:10px; padding:10px; cursor:pointer}
.sheet .sbtn[aria-selected="true"]{background:var(--accent-wash); border-color:var(--accent-dim); color:var(--accent-text)}
.sheet .sbtn.full{flex-basis:100%}
.sheet select{width:100%; min-height:46px; background:var(--bg); border:1px solid var(--border-strong); border-radius:10px; padding:0 12px; font-family:var(--font-mono); font-size:.86rem; color:var(--text)}
.sheet .sfield{flex:1 1 calc(50% - 8px)}
.sheet .sfield.full{flex-basis:100%}
.sheet .sfield label{display:block; font-family:var(--font-mono); font-size:.66rem; color:var(--muted); margin-bottom:4px}
.sheet .sabout{font-family:var(--font-mono); font-size:.74rem; color:var(--muted2); line-height:1.6}
.sheet .sabout a{color:var(--xref)}

/* mobile filter accordion — button lives in the top bar, shown only on the rules view at mobile widths */
.mfilter{display:none; align-items:center; gap:8px; font-family:var(--font-display); font-weight:600; font-size:.8rem; color:var(--text); background:var(--surface); border:1px solid var(--border-strong); border-radius:9px; padding:9px 13px; cursor:pointer}
.mfilter .fcount{font-family:var(--font-mono); font-size:.66rem; color:var(--c-ink-dark,#171b1f); background:var(--accent); border-radius:999px; padding:0 6px; min-width:16px; text-align:center}
.mfilter .fcount:empty{display:none}

/* ============================================================
   MOBILE (≤780px): bottom nav drives views; filter accordion;
   nothing hidden becomes unreachable (relocated to More sheet)
   ============================================================ */
@media(max-width:780px){
  :root{--botnav-h:calc(58px + env(safe-area-inset-bottom)); --msearch-h:52px}
  .topinner{padding:6px 10px; gap:0}
  .brand .ver{display:none}
  .brand .brand-img{height:28px}
  .viewswitch{display:none}
  .status{display:none}
  .toprt > .ctl, #themeBtn, #helpBtn, #collapseAllBtn{display:none}
  /* Filters button lives in the top bar, on the rules + FAQ views */
  body[data-view="rules"] #mfilterBtn, body[data-view="faq"] #mfilterBtn{display:inline-flex; margin-left:auto}
  /* search bar docked at the bottom, above the page-icon nav (thumb-reachable) */
  .searchwrap{position:fixed; left:0; right:0; bottom:var(--botnav-h); z-index:51; max-width:none; margin:0;
    padding:8px 12px calc(8px + env(safe-area-inset-bottom) * 0);
    background:linear-gradient(180deg,var(--surface),var(--surface2)); border-top:1px solid var(--border)}
  .searchwrap .skbd{display:none}
  /* results palette opens UPWARD from the bottom-docked input */
  .palette{top:auto; bottom:calc(100% + 4px); max-height:min(54vh,360px)}
  /* drawers + content sit above BOTH the search bar and the bottom nav */
  .tree{bottom:calc(var(--botnav-h) + var(--msearch-h)) !important; top:var(--topbar-h)}
  .scrim{bottom:calc(var(--botnav-h) + var(--msearch-h))}
  .reader{padding-bottom:calc(var(--botnav-h) + var(--msearch-h) + 30vh)}
  .viewpanel, .home-panel{padding-bottom:0}
  .viewpanel{padding-bottom:calc(var(--botnav-h) + var(--msearch-h) + 40px)}
  #faqScroll{padding-bottom:calc(var(--botnav-h) + var(--msearch-h) + 40px)}  /* faq body scrolls, not the panel */
  .botnav{display:flex; position:fixed; left:0; right:0; bottom:0; z-index:50; height:var(--botnav-h);
    padding-bottom:env(safe-area-inset-bottom); background:linear-gradient(180deg,var(--surface),var(--surface2));
    border-top:1px solid var(--border)}
  .botnav .bn{flex:1 1 0; min-width:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
    background:none; border:0; cursor:pointer; color:var(--muted); font-family:var(--font-display); padding:6px 0}
  .botnav .bn .bni{font-size:1.05rem; line-height:1}
  .botnav .bn .bnl{font-size:.5rem; font-weight:600; letter-spacing:0; line-height:1; max-width:100%; overflow:hidden; text-overflow:clip; white-space:nowrap}
  .botnav .bn[aria-selected="true"]{color:var(--accent-text)}
  /* narrowest phones: drop labels, icon-only (titles/aria-labels preserved) */
  @media(max-width:360px){
    .botnav .bn .bnl{display:none}
    .botnav .bn .bni{font-size:1.25rem}
    .botnav .bn{padding:8px 0}
  }
  /* search is bottom-docked (out of flow), so the body-head collapses to nothing
     until the Filters button opens the tag tray */
  .bodyhead{padding:0; border-bottom:0; gap:0}
  body[data-view="rules"] #filterbar,
  body[data-view="faq"] #faqFilterbar{display:none}
  body.filters-open[data-view="rules"] #filterbar,
  body.filters-open[data-view="faq"] #faqFilterbar{display:flex;
    padding:8px 12px; background:linear-gradient(180deg,var(--surface),var(--surface2));
    border-bottom:1px solid var(--border)}
  .crumbs{position:static}
  /* roomier touch density on the landing */
  .secgrid{grid-template-columns:1fr 1fr; gap:10px}
  .seccard .sd{display:none}
}
@media(max-width:440px){ .secgrid{grid-template-columns:1fr} .seccard .sd{display:block} }

/* ---------- Touch ergonomics (coarse pointers) ---------- */
@media(pointer:coarse){
  .chip{padding:8px 12px}
  .tnode{padding:9px 8px 9px 0}
  .iconbtn{width:42px; height:40px}
  .mfilter{padding:12px 15px}
  .hamb{width:42px; height:40px}
  .rule .copy{opacity:.5}
  .palette .pitem{padding:11px 9px}
  .viewswitch button{padding:10px 12px}
}

/* ============================================================
   FAQ VIEW
   ============================================================ */
/* FAQ mirrors the Rules layout: filterbar + crumbs pinned at the top, only the body scrolls
   (search lives in the header). Overrides .viewpanel's own scroll+padding. */
.faq-panel{display:flex; flex-direction:column; overflow:hidden; padding:0}
.faq-panel .crumbs{background:var(--surface2)}
#faqScroll{flex:1 1 auto; overflow:auto; padding:24px 0 30vh}
.faqcats.collapsed .catchip.extra{display:none}
.faqmore{font-family:var(--font-mono); font-size:.7rem; color:var(--accent-text); background:none; border:1px solid var(--border); border-radius:6px; padding:3px 8px; cursor:pointer}
.faqmore:hover{border-color:var(--accent-dim)}
.faqcount{font-family:var(--font-mono); font-size:.76rem; color:var(--muted); margin:0 0 14px}
.faqset{display:flex; align-items:center; gap:10px; margin:30px 0 12px; padding:6px 0 8px; border-bottom:2px solid var(--amber); cursor:pointer; user-select:none}
.faqsetwrap:first-child .faqset{margin-top:6px}
.faqset:hover .faqsetname{color:var(--amber-text)}
.faqsetcaret{flex:0 0 auto; font-size:.7rem; color:var(--amber-text); transition:transform .14s ease}
.faqsetwrap.collapsed .faqsetcaret{transform:rotate(-90deg)}
.faqsetwrap.collapsed .faqsetbody{display:none}
.faqset .faqsetname{font-family:var(--font-display); font-weight:700; font-size:1.4rem; color:var(--text)}
.faqset .faqsetc{font-family:var(--font-mono); font-size:.72rem; color:var(--amber-text); background:var(--amber-wash); border-radius:999px; padding:1px 8px}
.faqgroup{margin-bottom:22px; scroll-margin-top:70px}
.faqgroup .faqgh{padding-left:6px}
.faqgroup.hasart{display:flex; gap:16px; align-items:flex-start}
.faqgroup.hasart .faqgbody{flex:1 1 auto; min-width:0}
.faqart{flex:0 0 auto; width:clamp(84px,20vw,116px); display:block; line-height:0; position:sticky; top:8px}
.faqart img{width:100%; aspect-ratio:5/7; object-fit:cover; border-radius:8px; border:1px solid var(--border); box-shadow:0 2px 10px rgba(0,0,0,.28); background:var(--surface2)}
.faqart:hover img{border-color:var(--accent-dim)}
.faqgh{display:flex; align-items:baseline; gap:10px; margin:20px 0 8px; padding-bottom:6px; border-bottom:1px solid var(--border)}
.faqgh .gn{font-family:var(--font-mono); font-size:.78rem; font-weight:700; color:var(--accent-text)}
.faqgh .gt{font-family:var(--font-display); font-weight:700; font-size:1.16rem; color:var(--text)}
.faqgh .gcard-link{margin-left:auto; font-family:var(--font-mono); font-size:.7rem; color:var(--xref); text-decoration:none}
.faqgh .gcard-link:hover{text-decoration:underline}
.faqgh .gc{font-family:var(--font-mono); font-size:.7rem; color:var(--muted)}
.faqitem{background:var(--surface); border:1px solid var(--border); border-radius:10px; margin-bottom:8px; overflow:hidden; scroll-margin-top:70px}
.faqitem:hover{border-color:var(--border-strong)}
.faqitem.selected{border-color:var(--accent-dim); box-shadow:inset 2px 0 0 var(--accent)}
.faqitem.pulse{animation:faqpulse .7s ease}
@keyframes faqpulse{0%{box-shadow:0 0 0 0 var(--accent-wash)}40%{box-shadow:0 0 0 3px var(--accent-wash)}100%{box-shadow:0 0 0 0 transparent}}
.faqq{display:flex; align-items:flex-start; gap:10px; padding:12px 14px; cursor:pointer; list-style:none}
.faqq::-webkit-details-marker{display:none}
.faqbadge{flex:0 0 auto; font-family:var(--font-mono); font-size:.7rem; font-weight:600; color:var(--accent-text); border:1px solid var(--accent-dim); border-radius:5px; padding:1px 6px; margin-top:1px}
.faqqt{flex:1 1 auto; font-family:var(--font-body); font-size:1rem; line-height:1.4; color:var(--prose)}
.faqcaret{flex:0 0 14px; margin-top:3px; color:var(--muted2); font-size:.66rem; transition:transform .14s ease; user-select:none}
.faqitem[open] .faqcaret{transform:rotate(90deg)}
.faqcopy{flex:0 0 auto; opacity:0; border:0; background:none; color:var(--muted2); cursor:pointer; padding:2px; line-height:0; border-radius:5px; margin-top:1px}
.faqcopy svg{width:13px; height:13px; display:block; pointer-events:none}
.faqitem:hover .faqcopy,.faqitem[open] .faqcopy{opacity:1}
.faqcopy:hover{color:var(--accent-text); background:var(--accent-wash)}
.faqcopy.copied{color:var(--accent-text); opacity:1}
.faqa{padding:0 14px 13px 40px; font-family:var(--font-body); font-size:.96rem; line-height:1.55; color:var(--text)}
.faqa p{margin:0 0 8px}
.faqa p:last-child{margin-bottom:0}
.faqameta{margin-top:10px; display:flex; align-items:center; gap:12px; flex-wrap:wrap; font-family:var(--font-mono); font-size:.68rem; color:var(--muted2)}
.faqameta a.cardlink{color:var(--xref); text-decoration:none}
.faqameta a.cardlink:hover{text-decoration:underline}
.faqempty{padding:50px 0; color:var(--muted); font-family:var(--font-mono); text-align:center}
.faqgh .faqcopy{align-self:center; margin-top:0}
.faqgroup:hover .faqgh .faqcopy{opacity:1}
@media(pointer:coarse){ .faqq{padding:15px 14px} .faqcopy{opacity:.5} }

/* ============================================================
   GAME MODES VIEW
   ============================================================ */
.modepick{display:flex; flex-wrap:wrap; gap:10px; margin:4px 0 22px}
.modecard{position:relative; text-align:left; background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:12px 15px; cursor:pointer; flex:1 1 160px; min-width:150px; transition:transform .12s, border-color .12s, box-shadow .12s; display:flex; flex-direction:column; gap:3px}
.modecard:hover{transform:translateY(-2px); border-color:var(--border-strong); box-shadow:var(--elev)}
.modecard[aria-selected="true"]{border-color:var(--accent-dim); background:var(--accent-wash)}
.modecard .mci{font-size:1.2rem}
.modecard .mct{font-family:var(--font-display); font-weight:700; font-size:.98rem; color:var(--text); line-height:1.15}
.modecard .mcd{font-family:var(--font-mono); font-size:.66rem; color:var(--muted2)}
.modehead{display:flex; align-items:baseline; gap:14px; flex-wrap:wrap; margin-bottom:4px}
.modehead h2{font-family:var(--font-display); font-weight:800; font-size:1.6rem; color:var(--text); margin:0}
.modehead .msrc{font-family:var(--font-mono); font-size:.72rem; color:var(--muted)}
.modehead .msrc a{color:var(--xref)}
.modesec{margin-top:22px; scroll-margin-top:70px}
.modesec > h3{font-family:var(--font-display); font-weight:700; font-size:1.2rem; color:var(--accent-text); margin:0 0 8px; padding-bottom:5px; border-bottom:1px solid var(--border)}
.modebody{font-family:var(--font-body); font-size:1rem; line-height:1.6; color:var(--prose)}
.modebody p{margin:0 0 12px}
.modebody ul,.modebody ol{margin:0 0 12px; padding-left:22px; display:flex; flex-direction:column; gap:6px}
.modebody li{line-height:1.5}
.modebody a{color:var(--xref)}
.modebody img{max-width:100%; height:auto; border-radius:8px; margin:8px 0}
.modebody table{border-collapse:collapse; width:100%; margin:8px 0; font-size:.9rem; display:block; overflow-x:auto}
.modebody th,.modebody td{border:1px solid var(--border); padding:8px 11px; text-align:left; vertical-align:top}
.modebody th{background:var(--surface2); font-family:var(--font-display); font-weight:600; color:var(--text)}
.modebody b,.modebody strong{color:var(--text)}
/* nested subsection headings inside the Tournament manual */
.modebody h4{font-family:var(--font-display); font-weight:700; font-size:1.02rem; color:var(--text); margin:18px 0 6px}
.modebody h5{font-family:var(--font-display); font-weight:600; font-size:.9rem; color:var(--muted); margin:14px 0 5px; text-transform:none; letter-spacing:.01em}
.opdl{display:inline-flex; align-items:center; gap:6px; font-family:var(--font-display); font-weight:600; font-size:.9rem; text-decoration:none; color:var(--accent-text); background:var(--accent-wash); border:1px solid var(--accent-dim); border-radius:9px; padding:9px 15px}
.opdl:hover{border-color:var(--accent); box-shadow:inset 0 0 0 1px var(--accent)}

/* ============================================================
   TIMELINE VIEW  (release + ban/restriction chronology)
   namespaced .tl-* so it doesn't collide with the changelog's
   .timeline / .tlnode classes.
   ============================================================ */
.tl-meta{font-family:var(--font-mono); font-size:.72rem; color:var(--muted); margin:0 0 14px}
.tl-filters{display:flex; flex-wrap:wrap; gap:7px; margin:0 0 8px}
.tl-filters .chip{font-size:.74rem}
/* "Show upcoming" — state toggle, amber treatment when on (mirrors the .tl-upcoming tag) */
.tl-upto{font-family:var(--font-mono); font-size:.74rem; color:var(--muted); background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:3px 10px; cursor:pointer; white-space:nowrap}
.tl-upto:hover{border-color:var(--border-strong); color:var(--text)}
.tl-upto[aria-pressed="true"]{color:var(--amber-text); border-color:var(--amber); background:var(--amber-wash)}
.tl-upto[aria-pressed="true"]:hover{color:var(--amber-text); border-color:var(--amber)}
.tl-wrap{position:relative; margin:20px 0 0 8px; padding-left:30px; border-left:2px solid var(--border)}
.tl-node{position:relative; margin-bottom:24px; scroll-margin-top:70px}
.tl-node:last-child{margin-bottom:6px}
.tl-dot{position:absolute; left:-37px; top:5px; width:12px; height:12px; border-radius:50%; box-shadow:0 0 0 4px var(--bg)}
.tl-dot-rel{background:var(--accent)}
.tl-dot-ban{background:var(--amber)}
.tl-date{display:block; font-family:var(--font-mono); font-size:.72rem; letter-spacing:.02em; color:var(--muted); margin-bottom:7px}
.tl-node.tl-future{opacity:.64}
.tl-card{background:var(--surface); border:1px solid var(--border); border-radius:12px; padding:13px 16px; display:flex; flex-direction:column; gap:6px; max-width:660px}
.tl-card.tl-release{flex-direction:row; gap:14px; align-items:flex-start}
.tl-relbody{display:flex; flex-direction:column; gap:6px; min-width:0; flex:1 1 auto}
.tl-relthumb{flex:0 0 auto; width:80px; height:auto; border-radius:8px; border:1px solid var(--border); align-self:flex-start; background:var(--bg)}
/* pack renders are ~square with a light baked-in background — frame them on a soft light panel
   so they read as a product thumbnail in both themes rather than a glaring white sticker. */
.tl-packthumb{width:104px; height:104px; object-fit:contain; border-radius:10px; background:#eef0f2; padding:5px}
html[data-theme="light"] .tl-packthumb{background:#faf7ef; border-color:var(--border-strong)}
/* narrow screens: shrink the release thumb so the text body keeps a usable width */
@media(max-width:560px){ .tl-relthumb{width:72px} .tl-packthumb{width:80px; height:80px} }
.tl-card.tl-ban{border-left:3px solid var(--amber)}
.tl-cardhead{display:flex; align-items:center; gap:9px; flex-wrap:wrap}
.tl-code{font-family:var(--font-mono); font-size:.74rem; font-weight:600; color:var(--accent-text); background:var(--accent-wash); border:1px solid var(--accent-dim); border-radius:6px; padding:1px 8px}
.tl-kind{font-family:var(--font-mono); font-size:.62rem; text-transform:uppercase; letter-spacing:.05em; color:var(--muted2)}
.tl-upcoming{font-family:var(--font-mono); font-size:.58rem; text-transform:uppercase; letter-spacing:.06em; color:var(--amber-text); border:1px solid var(--amber); border-radius:999px; padding:1px 7px}
.tl-name{font-family:var(--font-display); font-weight:700; font-size:1.12rem; color:var(--text); line-height:1.2}
.tl-series{font-family:var(--font-body); font-size:.92rem; color:var(--muted)}
.tl-notes{font-family:var(--font-body); font-size:.9rem; color:var(--muted); line-height:1.45}
.tl-link{align-self:flex-start; font-family:var(--font-mono); font-size:.72rem; color:var(--accent-text); text-decoration:none; border:1px solid var(--accent-dim); background:var(--accent-wash); border-radius:6px; padding:3px 10px; margin-top:2px}
.tl-link:hover{background:var(--accent); color:var(--bg)}
.tl-dates{font-family:var(--font-mono); font-size:.7rem; color:var(--muted)}
.tl-changes{display:flex; flex-direction:column; gap:11px; margin-top:4px}
.tl-change{display:flex; flex-direction:column; gap:5px; padding-left:10px; border-left:2px solid var(--border)}
.tl-changetop{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.tl-badge{font-family:var(--font-mono); font-size:.62rem; font-weight:600; text-transform:uppercase; letter-spacing:.04em; border-radius:5px; padding:2px 7px; white-space:nowrap}
.tl-badge-ban{color:var(--amber-text); border:1px solid var(--amber); background:var(--amber-wash)}
.tl-badge-restrict{color:var(--amber-text); border:1px solid var(--amber); background:transparent}
.tl-cc{display:inline-flex; align-items:center; gap:6px}
.tl-cthumb{flex:0 0 auto; width:48px; height:auto; border-radius:4px; border:1px solid var(--border); background:var(--bg)}
.tl-ccode{font-size:.74rem; color:var(--xref)}
.tl-cname{font-family:var(--font-body); font-size:.9rem; color:var(--prose)}
.tl-plus{color:var(--muted2); font-family:var(--font-mono); margin:0 3px}
.tl-criteria{font-family:var(--font-body); font-size:.9rem; color:var(--prose); margin-top:1px}
.tl-tag{font-family:var(--font-mono); font-size:.58rem; text-transform:uppercase; letter-spacing:.04em; color:var(--muted2); border:1px solid var(--border); border-radius:4px; padding:0 5px; white-space:nowrap}
.tl-details summary{font-family:var(--font-mono); font-size:.72rem; color:var(--accent-text); cursor:pointer; padding:3px 0; width:fit-content}
.tl-details summary:hover{text-decoration:underline}
.tl-members{display:flex; flex-wrap:wrap; gap:8px; margin-top:8px}
.tl-members .tl-cc{background:var(--bg); border:1px solid var(--border); border-radius:6px; padding:3px 9px}
.tl-reason{font-family:var(--font-body); font-size:.84rem; color:var(--muted); line-height:1.45}
.tl-empty{padding:40px 0; color:var(--muted); font-family:var(--font-mono); text-align:center}


/* Card Battle Lab palette — generated from viewer/index.html :root. */
:root {
      /* Shared usable viewport. Keep the body artwork edge-to-edge while all
         screens and viewport overlays reserve the device's unsafe edges. */
      --gcg-safe-top: env(safe-area-inset-top, 0px);
      --gcg-safe-right: env(safe-area-inset-right, 0px);
      --gcg-safe-bottom: env(safe-area-inset-bottom, 0px);
      --gcg-safe-left: env(safe-area-inset-left, 0px);
      --gcg-viewport-top: var(--gcg-safe-top);
      --gcg-viewport-left: var(--gcg-safe-left);
      --gcg-viewport-width: calc(100vw - var(--gcg-safe-left) - var(--gcg-safe-right));
      --gcg-viewport-height: calc(100dvh - var(--gcg-safe-top) - var(--gcg-safe-bottom));
      /* The page is dark: tell the UA so its OWN surfaces (select popups,
         scrollbars, form-control chrome) render dark too. Author styling
         cannot reach a native <select> popup — this is the only knob. */
      
      /* tint knobs: hue 0 + sat 0%% = neutral grey (current look) */
      --c-hue: 0;
      --c-sat: 0%;
      /* TOMBSTONE (P7): the numbered neutral ramp --c-surface-0..9 is RETIRED.
         Its 10 luminances now live directly on the semantic SURFACE roles below
         (--c-app-bg .. --c-ctrl-hi), each an independent hsl() primary and its
         own tweaker knob. No component may reference var(--c-surface-N). */
      /* ── Border roles (P4, SURFACE_TOKEN_SEMANTIC_PLAN.md) ── three weights,
         each ONE job. The overnight consolidation drew edges from 10 arbitrary
         ramp luminances (s3..s9 + border + strong); P4 collapses the 8 dim
         ones (all resting container edges — chips, tiles, inputs, dividers;
         none carried selection/emphasis meaning, which lives on -strong) into
         a single subtle edge. Tune the three in the UI Tweaker. */
      --c-border-subtle: hsl(var(--c-hue) var(--c-sat) 18.4314%);  /* faint internal edges (was s3..s9) */
      --c-border: hsl(var(--c-hue) var(--c-sat) 17.4%);         /* normal component edge */
      --c-border-strong: hsl(var(--c-hue) var(--c-sat) 25.3%);  /* emphasis / selected / hover */

      /* ── Fade durations (Dave 2026-09-28): the ONE set every opacity fade
         reads. In is slower than out so leaving never feels sticky. */
      --t-fade-snap:   .18s;  /* in-game popups (inspector, pickers, selection) */
      --t-fade-fast:   .4s;   /* any screen / sheet fading OUT */
      --t-fade-page:   .8s;   /* a screen / sheet fading IN, boot panel */
      --t-fade-reveal: 1.5s;  /* Home after boot, board on game start */

      /* ── Semantic SURFACE roles (P2 rename → P7 promotion,
         SURFACE_TOKEN_SEMANTIC_PLAN.md) ── "A surface is a surface." Each role
         names ONE job and IS its own primary now: an independent
         hsl(var(--c-hue) var(--c-sat) L%) at the luminance it used to alias, so
         the numbered ramp (--c-surface-0..9) is RETIRED. Values are unchanged
         (zero-visual), but each surface is now its OWN tweaker knob (the UI
         Tweaker discovers ramp knobs by value via HSL_RE), named by purpose
         instead of a shared number. Components reference these roles, never a
         raw index; gate_token_roles.py forbids any live var(--c-surface-N). */
      --c-app-bg:      hsl(var(--c-hue) var(--c-sat) 3.9216%);   /* page / board backdrop */
      --c-field:       hsl(var(--c-hue) var(--c-sat) 5.098%);    /* text inputs, deep insets */
      --c-panel: hsl(var(--c-hue) var(--c-sat) 12.6%);   /* primary panels, sheets, sidebar */
      --c-panel-2: hsl(var(--c-hue) var(--c-sat) 3.9%);  /* nested / inset panel */
      --c-panel-3:     hsl(var(--c-hue) var(--c-sat) 11.7647%);  /* raised panel, row-hover step */
      --c-ctrl: hsl(var(--c-hue) var(--c-sat) 13.4%);   /* control resting face */
      --c-ctrl-2:      hsl(var(--c-hue) var(--c-sat) 13.3333%);  /* raised control, header band */
      --c-ctrl-3: hsl(var(--c-hue) var(--c-sat) 19.7%);  /* active / hover control */
      --c-ctrl-raised: hsl(var(--c-hue) var(--c-sat) 20.0%);     /* highest raised fill (rare) */
      --c-ctrl-hi:     hsl(var(--c-hue) var(--c-sat) 22.7451%);  /* top raised fill (rare) */
      /* Faction dot ink: the White-color indicator is a pale TEXT colour at the
         old surface-6 luminance (its own value, decoupled from --c-ctrl-2). */
      --c-faction-white: hsl(var(--c-hue) var(--c-sat) 13.3333%);

      /* Panel transparency (P7 Stage C) — ONE global knob. --c-panel stays the
         opaque luminance (a tweaker ramp knob); panel FILLS reference --panel-bg,
         which fades the panel toward transparent by --panel-alpha. Default 1 =
         fully opaque = zero-visual; dial it down (UI Tweaker › Alpha) to make
         every panel dark-transparent at once (deck-builder side panels, sheets,
         tiles, match-setup). Borders/gradient-stops/select-options keep raw
         --c-panel on purpose — they must not go see-through. */
      --panel-alpha: 0.19;
      --panel-bg: color-mix(in srgb, var(--c-panel), transparent calc((1 - var(--panel-alpha)) * 100%));
      /* P8-b: the same fade for the two nested/raised panel surfaces, so a
         STATIC fill can pick the nearest luminance and still ride the ONE knob.
         EXCLUDED from --panel-bg* on purpose (they must stay opaque): popover /
         dropdown option lists (.speed-menu-opt, .picker-filter-menu, .gcg-select
         option, .gcg-menu), hover/selected STATE fills (those go to --c-ctrl*),
         meter off-segments, and any gradient stop or border colour. */
      --panel-bg-2: color-mix(in srgb, var(--c-panel-2), transparent calc((1 - var(--panel-alpha)) * 100%));
      --panel-bg-3: color-mix(in srgb, var(--c-panel-3), transparent calc((1 - var(--panel-alpha)) * 100%));

      /* ── Control lighting (P3, SURFACE_TOKEN_SEMANTIC_PLAN.md) ──
         "A gradient is LIGHTING of a surface." One shared bevel overlay
         (--ctrl-lift) sits over a SINGLE surface token, replacing the old
         ramp-stop button gradients (s7→s5→s3 etc.). The light source is
         constant: a control brightens on hover (a higher surface underneath)
         and is pushed IN on press (--ctrl-press inverts the bevel). Tune the
         two --lift-* stops once and every neutral face follows. FILL/QUIET
         button families keep their own color-mix lighting off --c. */
      --lift-hi: rgba(255,255,255,.06);   /* top highlight */
      --lift-lo: rgba(0,0,0,.24);         /* bottom shadow */
      --ctrl-lift:  linear-gradient(180deg, var(--lift-hi), transparent 42%, var(--lift-lo));
      --ctrl-press: linear-gradient(180deg, var(--lift-lo), transparent 55%);
      --ctrl-face:        var(--ctrl-lift),  linear-gradient(var(--c-ctrl),   var(--c-ctrl));
      --ctrl-face-hover:  var(--ctrl-lift),  linear-gradient(var(--c-ctrl-3), var(--c-ctrl-3));
      --ctrl-face-active: var(--ctrl-press), linear-gradient(var(--c-ctrl-2), var(--c-ctrl-2));

      --c-text-faint: hsl(var(--c-hue) var(--c-sat) 53.3333%);   /* was 40% — raised for WCAG AA on small secondary text */
      --c-text-subtle: hsl(var(--c-hue) var(--c-sat) 55.0%);     /* was 46.6667% — raised for WCAG AA */
      --c-text-muted: hsl(var(--c-hue) var(--c-sat) 58.0%);      /* was 53.3333% — raised for WCAG AA (e.g. deck-row subtitle on surface-7) */
      --c-text-dim: hsl(var(--c-hue) var(--c-sat) 64.7%);
      --c-text: hsl(var(--c-hue) var(--c-sat) 80.0%);
      --c-text-bright: hsl(var(--c-hue) var(--c-sat) 86.6667%);
      --c-text-strong: hsl(var(--c-hue) var(--c-sat) 87.8431%);
      --c-text-hi: hsl(var(--c-hue) var(--c-sat) 93.3333%);
      --c-white: hsl(var(--c-hue) var(--c-sat) 100.0%);
      --c-accent: #d1d1d1;
      --c-danger: #ff6666;
      --c-danger-2: #f87171;
      --c-warn: #e0a94a;   /* kit caution amber (UI_CONSISTENCY_AUDIT.md DECISION 9) */
      --c-success: #9dcc9d;
      --c-cloud: #82e6ff;    /* bright cyan-blue: cloud-saved marks, challenge score bar.
                                Renamed from --c-continue (Dave 2026-09-19) when the
                                Continue diamonds moved to --c-ctx green. The old
                                name is retired: gate_retired_tokens.py fails any
                                branch that lands a reference to it. */
      --c-season: #82e6ff;   /* Season progression (Season XP bar, Season level), beside
                                Career's amber --c-warn. Same value as --c-cloud by
                                Dave's pick (2026-09-19), its own role and token. */
      --c-chroma: #00FF00;   /* Showcase chroma-key backdrop (Dave 2026-10-09). Pure
                                green to match OBS's Green key preset; a player
                                override lives in Showcase setup. Backdrop only. */
      --c-success-surface: #2a3a2a;
      --c-success-surface-dim: #1a2a1a;
      --c-success-border: #4a6a4a;
      --c-info-surface: #1a2a3a;

      /* ── On-fill inks (P1, SURFACE_TOKEN_SEMANTIC_PLAN.md) ── dark text that
         sits ON a bright fill. ONE role per fill FAMILY, not a literal per site
         and not a surface index. A near-black tinted toward its background's
         hue reads as the same material in shadow. Replaces the 16 scattered
         #0a1628/#2c0808/#1a0f00 literals AND the surface-0/-2 lozenge text. */
      --c-on-accent:  #0a1628;  /* on accent-blue selection fills */
      --c-on-danger:  #2c0808;  /* on danger-red fills */
      --c-on-burst:   #1a0f00;  /* on the burst amber fill */
      --c-on-warn:    #2a1c04;  /* dark ink on the caution-amber fill (.gcg-btn--warn) */
      /* Keyword-lozenge ink is CONTRAST against a card-derived fill, so it is a
         two-member pair, not one ink: --c-ink-dark on LIGHT fills (teal/blue/
         pink/yellow/orange/near-white keywords), --c-ink-light on DARK fills
         (once #8b2222, the attack loz). Standalone values — decoupled from the
         surface ramp so surface-0 is a surface again, not borrowed as ink. */
      --c-ink-dark:  #0a0a0a;
      --c-ink-light: var(--c-white);

      /* ── Z SCALE (v0.79.0, SHELL_ADAPT_PLAN.md B) ── the app's top stacking
         tiers, declared once. Menus (Home / Options) sit ABOVE every game
         surface INCLUDING the in-app dialogs: a menu opened over a live
         dialog/choice COVERS it (never closes it, the decision stays pending),
         while a dialog spawned FROM an open menu (e.g. Options "Reset")
         gets .over-menu and rides --z-menu-dialog so it stays answerable.
         Census of fixed layers below this scale, for orientation:
         choice modal 9000 · card inspector float 9500 · setup modal 11900.
         Boot overlay (100000) and the server-stale bar (99999) stay above all. */
      /* The PvP chat head's actions popover. Above the chat panel in BOTH its
         homes (480 in play, 11950 over the lobby's #setupModal scrim) and still
         BELOW --z-dialog, so the Report dialog it opens stacks over it. */
      --z-pvp-chat-menu: 11960;
      --z-dialog: 12000;       /* uiConfirm/uiAlert/uiPrompt + #gsModal */
      --z-menu: 13000;         /* Main Menu, Options (backdrop + panel) */
      --z-menu-dialog: 13500;  /* a dialog spawned while a menu is open */
      --z-notice: 14000;       /* BOTH Notices hosts (toast + drop). Global
                                  transient messages render on top of whatever
                                  is happening, menus and dialogs included
                                  (Dave, 2026-08-20): they take no pointer
                                  events except each entry's own dismiss x, so
                                  they can overlay a pending decision without
                                  ever swallowing its clicks. Only the boot
                                  overlay and the server-stale bar sit above. */

      /* ── UI kit tokens (E0, UI_CONSISTENCY_AUDIT.md Part 3 / design/mockups/
         ui-kit-r3-final.html). The radius SCALE for all chrome: controls --r-s,
         cards/panels --r-m, floating panels --r-l, modal shells --r-xl,
         lozenge/toggle pills --r-loz. Element passes E1+ migrate the 13
         scattered literal radii onto these — new chrome must consume the
         tokens, never a literal px radius.
         Modal glass is the KIT recipe (small floating surfaces only: dialogs,
         popovers, sheets). It is deliberately separate from --glass-bg below,
         which is the board HUD's lighter wash. Large full-screen surfaces tint
         with --scrim and NO blur (audit item 17: the space scene must read
         through, and viewport-sized backdrop-filter is a GPU cliff). NB
         backdrop-filter makes the element a containing block for position:
         fixed descendants — never put it on an ancestor of the PASS float. */
      --r-s: 4px;
      --r-m: 6px;
      --r-l: 8px;
      --r-xl: 12px;
      --r-loz: 9px;
      /* Panel Glass (Options › Display): every glass fill is a *-tint (the
         Normal look, what the UI Tweaker edits) plus a consumer token that
         ui_components.css re-derives from the tint with --glass-t, the
         transparency scale: 1 Normal, .5 Dark (half as see-through), 0 Solid
         (opaque, blur stripped). Consumers read the plain names below; the
         plain = tint lines here are the fallback where relative colour
         syntax is unsupported. Every blurred surface fills from these
         tokens; never give one its own rgba (gate_panel_glass sweeps it). */
      --glass-t: 1;
      --glass-modal-tint: rgba(15, 15, 15, 0.55);
      --glass-modal-bg: var(--glass-modal-tint);
      --glass-modal-blur: blur(14px) saturate(115%);
      --scrim: rgba(0, 0, 0, 0.52);
      /* Interior group/panel surface INSIDE a modal: a translucent white lift so
         the host's frost shows through, instead of an opaque slab that reads as
         a different material (Match pcards vs Options groups mismatch). */
      --surface-haze-tint: rgba(5, 5, 5, 0.56);   /* 0.44 -> 0.56 for legibility over bright art (Dave 2026-09-24) */
      --surface-haze: var(--surface-haze-tint);
      /* Empty list slot ("something can go here"): Lobby player slots and
         empty playlist wells share it via .gcg-row--empty (Dave 2026-10-06). */
      --slot-empty-border: 1px dotted color-mix(in srgb, var(--c-border) 55%, transparent);

      /* Canonical gameplay palette. Trigger/activate/burst are sampled from
         the printed card furniture; the remaining semantic colours are the
         one shared vocabulary for controls, reticules, logs, card detail,
         prompts, timeline marks and stat assistance. Never fork local hexes. */
      --c-game-trigger: #64d3d9;       /* Deploy / Destroyed / printed Attack */
      --c-game-activate: #64b3ec;      /* Activate / Main / Action */
      --c-game-burst: #ca8834;
      --c-game-attack: #ff5a5a;
      --c-game-keyword: #f2f2f2;       /* printed white keyword lozenges, incl. First Strike */
      --c-game-block: var(--c-game-keyword);
      --c-game-pair: #ff8cba;
      --c-game-link: #fbe651;
      --c-game-damage: #d76cff;
      --c-game-buff: #f2a654;
      --c-game-debuff: #4250ff;
      --c-game-heal: #3adb6a;
      --c-game-unavailable: #9aa0a6;  /* Return / discard / exile */
      --c-game-prevent: #38c6d6;      /* damage prevention / shield-immunity cyan (Prevent stat) */
      --c-game-destroy: #d84040;      /* hard opponent removal crimson (Destroy stat) */
      --c-game-rest: #367cfe;
      --c-game-interact: #f2f2f2;
      --c-game-once: #8b2222;

      /* Card-text aliases kept for the shared log/detail formatter. */
      --c-lz-trigger: var(--c-game-trigger);
      --c-lz-activate: var(--c-game-activate);
      --c-lz-pair: var(--c-game-pair);
      --c-lz-link: var(--c-game-link);
      --c-lz-burst: var(--c-game-burst);
      --c-lz-ability: var(--c-game-keyword);
      --c-lz-once: var(--c-game-once);
      --c-lz-timing: var(--c-game-trigger);
      --c-effect-target: var(--c-game-damage);
      /* card colors (used by _colorValue inline styles) */
      --c-card-blue: #3a82c0;
      --c-card-green: #5fa83c;
      --c-card-red: #c41f54;
      --c-card-white: #f1f1e8;
      --c-card-purple: #9b5cc4;
      --c-card-token: #d6c98a;

      /* ── Chart / stat contract (--s-*) — CURVE_ANALYSIS_PLAN.md §20.2 ──
         The deck-analysis surfaces (Deck Metadata composition + stat meters
         today, the curve charts in P1/P2) paint from THESE names and from no
         literal anywhere. Every one is a var() CHAIN onto a canon token above,
         never a hex: retune --c-game-link once and the spend line, the cost
         histogram, the pilot type bar and the Haste meter all follow. That
         indirection is the whole point — the chart is allowed its own
         vocabulary ("spend", "boss", "wall") without owning a second palette.
         Renaming a mapping here is a design decision; minting a hex here is a
         bug (gate_metadata_tokens.py).
         NOT declared: shields + dmg (both CUT from the UI, plan §22).
         "neutral-1" in the approved spec is #6a6a6a. The --c-text ramp bottoms
         out at --c-text-faint = 53.3% L = #888888, a visibly lighter grey, so
         the nearest EXISTING token is --dim (#6b6b6b, 1/255 per channel away)
         rather than a ramp step — and "faded rail / quiet marks" is exactly
         what other-locked and Bounce are. No new grey minted. */
      --s-spend:        var(--c-game-link);
      --s-threat:       var(--c-card-red);
      --s-boss:         var(--c-danger);
      --s-boss2:        var(--c-danger);   /* P>=2 — differentiated by dash/weight, not hue */
      /* Per-boss lightness ladder (P1 multi-boss): up to three boss bells share
         ONE hue and separate by lightness, because they are three instances of
         the same statistic — three unrelated hues would read as three unrelated
         series, and the card-art icon above each line is what actually names it.
         Derived off --s-boss in CSS, never in JS: a JS ladder would bake
         whatever --s-boss resolved to at boot and stop tracking a retune.
         Mixed against --c-white / --c-app-bg (not #fff / #000) so the ladder
         follows a theme change too. */
      --s-boss-1:       var(--s-boss);
      --s-boss-2:       color-mix(in srgb, var(--s-boss) 62%, var(--c-white));
      --s-boss-3:       color-mix(in srgb, var(--s-boss) 74%, var(--c-app-bg));
      --s-gas:          var(--c-game-trigger);
      --s-wall:         var(--c-game-keyword);
      --s-dead:         var(--c-game-debuff);
      --s-lvlock:       var(--c-card-blue);
      --s-costlock:     var(--c-warn);
      --s-otherlock:    var(--dim);
      --s-resleft:      var(--c-card-token);
      --s-exalive:      var(--c-game-trigger);
      --s-kill:         var(--c-card-red);
      /* Histogram bars. The Lv/Cost pair in the metadata panel and the same two
         series in the combined chart are literally the same two statistics, so
         they share one token each — letting them drift would be the palette
         telling one lie in two places. */
      --s-bar-lv:       var(--c-game-trigger);
      --s-bar-cost:     var(--c-game-link);
      /* Card-TYPE identity (composition stack). */
      --s-type-unit:    var(--c-game-activate);
      --s-type-pilot:   var(--c-game-link);
      --s-type-command: var(--c-game-trigger);
      --s-type-base:    var(--c-game-burst);
      /* Deck legality. Invalid = construction (50 cards, 2 colours, 4 copies) is
         a CAUTION; Illegal = format restrictions is a DANGER. */
      --s-legal:        var(--c-success);
      --s-illegal:      var(--c-danger);
      --s-invalid:      var(--c-warn);
      /* The 14 deck-property stat meters. The approved mapping deliberately
         splits four hue DOUBLINGS the panel used to ship (Base shared Power's
         red, HP shared Heal's green, Rush shared Haste's yellow, Tokens shared
         Draw's teal), so no two bars inside the stat panel read as one stat. */
      --s-stat-power:   var(--c-game-attack);
      --s-stat-ping:    var(--c-game-damage);
      --s-stat-buff:    var(--c-game-buff);
      --s-stat-base:    var(--c-game-once);
      --s-stat-hp:      var(--c-card-green);
      --s-stat-block:   var(--c-game-keyword);
      --s-stat-heal:    var(--c-game-heal);
      --s-stat-rush:    var(--c-game-burst);
      --s-stat-haste:   var(--c-game-link);
      --s-stat-draw:    var(--c-game-trigger);
      --s-stat-tokens:  var(--c-card-token);
      --s-stat-rest:    var(--c-game-rest);
      --s-stat-bounce:  var(--dim);
      --s-stat-debuff:  var(--c-game-debuff);
      --s-stat-prevent: var(--c-game-prevent);
      --s-stat-destroy: var(--c-game-destroy);

      /* ── Seismograph timeline tokens (zoomc port) — read by the canvas
         component off document.documentElement. Literal values (not var()
         indirection) so getComputedStyle returns resolved colors. ── */
      --p1: #6699ff;    /* P1 = --c-accent */
      --p2: #ff6666;    /* P2 = --c-danger */
      /* Board-state greens (BOARD_TABLE_PLAN §1): EDIT-mode affordances vs the
         context (PASS) action family. Two distinct greens, formalized as tokens
         so the HUD frame, move-targets and the context button all pull from one
         place instead of scattered literals. */
      --c-edit: #3aa050; /* Lab EDIT affordances: frames, move targets, spawn zones */
      --c-ctx: #4fd07a;  /* context-action green: PASS active, control-point marks */
      /* Commit vs selection (P7 Stage B): two DIFFERENT jobs that both used to
         borrow --c-accent, so a Confirm/Save button read the same as a selected
         pill-toggle. Now each is its own tunable param. Seeded to today's values
         (commit = the ctx green, toggle = accent blue) → zero-visual, but you can
         dial them apart in the UI Tweaker (Semantic colors). */
      --c-commit: #c8c8c8;  /* commit/confirm/save/OK button face (.gcg-btn--ok) */
      --c-on-commit: #06230f;  /* dark ink ON the commit-green fill */
      --c-toggle-on: #aeaeae;  /* selected pill-toggle face (.gcg-segmented .on) */
      /* Supporter tier colours (PATREON_AUTH_PLAN.md §Client). Rookie uses the
         existing neutral text token. Veteran borrows activate blue, Ace keeps
         the existing burst gold, and Legend uses royal purple to match
         rank_3.webp. Shared badges and locks consume only these aliases. */
      --c-tier-0: var(--c-text-dim);       /* Rookie / Log in · Free */
      --c-tier-1: var(--c-game-activate);  /* Veteran */
      --c-tier-2: var(--c-game-burst);     /* Ace */
      --c-tier-3: #b46cff;                 /* Legend */
      /* Quest GRADES (QUEST_UI_PLAN.md §1) — the metallic garnish on a quest
         card, DERIVED from the quest's xp by questGradeFor(), never stored.
         "Grade" is deliberate: "tier" is reserved for the supporter tiers two
         lines up. Real metals rather than aliases of the semantic palette —
         bronze/silver/gold have to read as a ladder against each other, which
         is a job no existing token does. Values are the Quest Designer's
         (devtools/quest_designer.html), the living mockup for this card. */
      --c-grade-bronze: #bf7b3c;
      --c-grade-silver: #a9bed6;
      --c-grade-gold:   #f0c53f;
      --ink: #eeeeee;   /* = --c-text-hi   */
      --dim: #6b6b6b;   /* faded rail / quiet marks */
      --mute: #949494;  /* = --c-text-muted */
      --mono: 'Chakra Petch', ui-monospace, 'Cascadia Code', Consolas, monospace;
      /* ── Type tiers (BOARD_TABLE_PLAN P3 font pass) ──
         Three faces, three jobs: --font-display (Orbitron, loaded at 900 only —
         unreadable below ~9px, so captions/body never wear it) for headers,
         buttons, watermarks and reticule labels; --font-body for prose and
         controls; --mono (above) for card codes / seeds / win-loss numerals.
         Consume the tokens — a literal 'Orbitron' outside the @font-face is a
         gate failure (gate_text_floor.py). */
      --font-display: 'Orbitron', sans-serif;
      --font-body: 'Chakra Petch', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
      /* ── UI Tweaker axes (unitless multipliers, default 1 = today's pixels) ──
         Kit/chrome CSS multiplies its own font-sizes by these, one per FACE:
         display (Orbitron headers/buttons), body (Rajdhani prose/controls),
         mono (codes/seeds/numerals). Board text is deliberately NOT scaled —
         the table module is a fixed design canvas that fitBoard() scales. */
      --fs-display: 1.08;
      --fs-body: 1.08;
      --fs-mono: 1;
      /* Button-face size axis (px). ui_components.css reads it as
         `var(--btn-fs, 12px)` and derives --sm/--touch from it; button faces
         ride ONLY this axis, never --fs-body as well (the two would compound).
         Declared here — and not merely relied on as that fallback — so the UI
         Tweaker's Save can patch it like every other token (/api/save-css-vars
         rewrites a literal `--name: value;` declaration, so a token that only
         ever exists as a var() fallback is unsaveable). */
      --btn-fs: 12.5px;
      /* Global hover lift; the kit's `button:hover { filter: brightness(...) }`
         and its board-lozenge twin read this so one knob moves both. */
      --hover-brightness: 1.42;
      /* fill-button interaction recipe (kit .gcg-btn--fill + its game twins);
         sliders in the UI Tweaker */
      --mix-fill-border: 72%;
      --mix-fill-top: 78%;
      --mix-fill-bottom: 82%;
      --mix-fill-hover-border: 55%;
      --mix-fill-hover-top: 52%;
      --mix-fill-hover-mid: 80%;
      --mix-fill-hover-bottom: 96%;
      --mix-fill-active-top: 90%;
      --mix-fill-active-mid: 68%;
      --mix-fill-active-bottom: 58%;

      /* ── Shared panel chrome (Match sheet redesign, match-panel-r2) ──
         --msheet-pad is the Match sheet's BODY inset (state-G geometry; the head
         has its own). NB the --msheet-* namespace, NOT --panel-*: the shell /
         Layout Tuner already owns --panel-pad / --panel-radius (forced to 0 a
         few lines below, and written onto documentElement at runtime), which
         silently zeroed this sheet's padding + corners when it reused them.
         E8b TOMBSTONE: --msheet-bg rgba(22,22,22,0.66) / --msheet-blur blur(16px)
         saturate(118%) / --msheet-radius 10px are RETIRED. They existed to give
         the Match sheet the same frosted-dark surface as .ui-dialog-box; both
         now consume .gcg-modal's --glass-modal-* recipe directly, so a second
         namespace for "the modal surface" is exactly the drift E8 removes.
         Nothing else referenced them (repo-wide grep). */
      /* ── Shared modal insets (Dave 2026-08-10: "How do we make this
         consistent?") ── Every top-level modal used its own literals: Home
         .menu-sections 16px / Options #uiSettingsPanel 10px / Match --msheet-pad
         20px horizontal, and the group boxes inside them each had a bespoke
         inner padding too. Two knobs now, tweaker rows in "Modal geometry":
         --modal-pad = modal edge → its groups, --group-pad = group border →
         its content. --msheet-pad keeps a 14px TOP (state-G geometry); its
         sides and bottom derive from the shared token so the last control
         (Cancel / Leave) sits the same distance from the edge as the sides
         (Dave, 2026-09-25: the old 18px bottom read as a bigger gap). */
      --modal-pad: 12px;
      --group-pad: 8px;
      --msheet-pad: 14px var(--modal-pad, 16px) var(--modal-pad, 16px);

      /* ── Phase 1 locked Table Module (UI_REDESIGN_PLAN §1.1/§1.2) ──
         Fixed design-space geometry, frozen from the comfortable 1920×1080 wide
         render. NEVER use calc(var(--table-scale)*…) inside the module — the whole
         module is uniformly scaled by transform, so every px constant stays valid. */
      --table-w: 1560px;          /* wide-layout design canvas width  */
      --board-min-wide: 760px;    /* minimum usable wide board before shell chrome yields */
      --table-h: 490px;           /* wide-layout design canvas height (480 + 10 for the taller message strip, v0.79.2) */
      --pa-w: 1270px;             /* battle-area (play-area) locked width */
      /* Reserved right gutter (design px) inside the wide play-area column,
         permanently empty so the unscaled pinned context button (~172px screen)
         is pinned there UNCONDITIONALLY and can never be reached by the
         resource row. Sized to fit the button at the smallest wide-shell scale
         (~0.49): (172+2*EDGE)/0.49 ~= 384 -> 400. Below that scale the shell
         flips to narrow-shell (bottom bar), a separate path. */
      --pa-gap: 24px;             /* constant gap between center-packed units */
      --flank-w: 133px;           /* shield-col / deck-trash-col width */
      --msg-strip-h: 48px;        /* per-player message strip band (wide canvas; compact overrides it — see .play-area-wrap.compact) */
      --table-scale: 1;           /* set by fitBoard() via ResizeObserver */
      --wm-opacity: 1;            /* ABSOLUTE watermark opacity (Off/Low/Med/High) — see _UI_WM_OPACITY */
      --wm-result-opacity: 0.45;  /* settled WIN/LOSE watermark, independent of the setting (= High; JS re-stamps from _UI_WM_OPACITY.high) */
      /* Glass HUD (v0.67.0): the side panels are always translucent so the
         PHASE/STEP watermark — and the table once zoomed — reads through them. */
      --glass-tint: rgba(13, 13, 13, 0.62);
      --glass-bg: var(--glass-tint);
      --glass-raised-tint: rgba(34, 34, 34, 0.55);
      --glass-bg-raised: var(--glass-raised-tint);
      --glass-blur: 7px;
      --divider-h: 30px;          /* reserved height of the between-tables divider */

      /* ── Phase 4 locked Compact Table Module (UI_REDESIGN_PLAN §1.2/§2 Phase 4) ──
         Second frozen design canvas, selected instead of --table-w/h by fitBoard()'s
         layout picker. Measured once (see Phase 4 report) against the live compact
         renderer at a comfortable size, then hardcoded — same "freeze, don't compute"
         rule as Phase 1. Battle area is 3×2 instead of 6×1; flanks/message-strip/
         resources/watermark are otherwise identical geometry rules to wide. */
      --table-cw: 840px;           /* compact-layout design canvas width — measured (see Phase 4 report) */
      --table-ch: 700px;           /* compact-layout design canvas height (incl. hidden-hand strip) */
      /* 542 = the width that CENTRES the battle column in the canvas, and that
         is a hard requirement, not a taste call: p2's module is the same content
         rotated 180°, so anything off-centre by +d in the canvas lands at -d on
         the other seat and the two halves of the table sit 2d apart (they were
         14px off → 28px apart → 15px on screen). Both flanks therefore reserve
         the SAME --flank-w and the middle gets what's left:
         6 + 133 + 10 + 542 + 10 + 133 + 6 = 840. Keeping 570 instead and paying
         for the symmetry with a wider canvas was the alternative; it costs every
         compact card ~3% of --table-scale, whereas 542 costs the hand fan 5% of
         its width and nothing else (3 units at 1.3 scale + gaps = 415 < 542). */
      --pa-cw: 542px;              /* compact battle-area locked width (3 cols × up to 170 + gaps) */
      --pa-c-row-gap: 8px;         /* gap between the two 3-wide battle rows */
      --flank-cw: 104px;           /* compact deck/trash/RFG zone width — the FLANK reserves --flank-w like the shield side (see --pa-cw), this is the zone content */

      /* (RETIRED v0.83.0 — the Phase 2 board zoom/pan multiplier lived here:
         --board-zoom-mult / --board-pan-x / --board-pan-y. fitBoard's
         --table-scale is the ONLY board scale now; see .board-stack.) */

      /* ── Shell chrome widths (SHELL_TIERS_PLAN.md). Declared here because
         _shellPlan() reads them back out of the cascade to build its yield
         ladder: the JS must never carry a second copy of these numbers or the
         two drift and the ladder starts budgeting for a column that is no
         longer that wide. Change the width HERE and the tier boundaries move
         with it. */
      --sidebar-w: 293px;          /* .log-column expanded */
      --sidebar-rail-w: 40px;      /* .log-column.rail-collapsed */
      --dock-w: 330px;             /* .dock-right (fixed card-detail column) */
      /* Sidebar chrome responds to its own box, never the device name or table
         scale. Width drives desktop drag density; height covers short landscape
         screens. The content reaches this minimum before the panel may snap shut. */
      --sidebar-density-min: .72;
      --sidebar-density-ref-w: 293;
      --sidebar-density-ref-h: 720;
      /* Screen-sized UI chrome uses the usable visual viewport. These derived
         axes never feed --table-scale or the card-detail --cd-scale. */
      --gcg-ui-density: 1;
      --gcg-ui-density-min: .78;
      --gcg-ui-density-ref-w: 440;
      --gcg-ui-density-ref-h: 720;
      --gcg-ui-fs-body: 1.08;
      --gcg-ui-fs-display: 1.08;
      --gcg-ui-fs-mono: 1;
      --gcg-ui-btn-fs: 12.5px;
      --gcg-ui-modal-pad: 16px;
      --gcg-mobile-bar-h: 52px;
      --gcg-disabled-opacity: 0.45;
      /* Shared blocking messages use the usable visual viewport, not a device
         label or pixel density. JS publishes --gcg-message-density plus the
         derived type/button axes whenever that viewport changes. */
      --gcg-message-density: 1;
      --gcg-message-density-min: .78;
      --gcg-message-density-ref-w: 440;
      --gcg-message-density-ref-h: 720;
      --gcg-message-x: 50vw;
      --gcg-message-y: 50vh;
      --gcg-message-fs-body: 1.08;
      --gcg-message-fs-display: 1.08;
      --gcg-message-btn-fs: 12.5px;

      /* ── Layout Tuner (live wide-shell framing knobs — see the "Layout Tuner"
         gear entry + the LayoutTuner IIFE at end of <script>). Defaults are the
         owner-tuned values; the tuner writes overrides onto documentElement. */
      --shell-pad: 0px;            /* #playScreen outer padding */
      --shell-gap: 0px;            /* #playScreen column gap + .play-body row gap */
      --panel-radius: 0px;         /* top-level shell panel corner radius */
      --panel-pad: 0px;            /* main internal body padding of shell panels (log body) */
      --float-frame-pad: 0px;      /* pinned PASS/.pb-float container padding */
      --panel-border-alpha: 0;     /* fades ALL shell-panel + float-frame edges at once */
      /* Derived edge colors: alpha=1 → color-mix 100% is the original color exactly. */
      --panel-border-color: color-mix(in srgb, var(--c-border-subtle) calc(var(--panel-border-alpha) * 100%), transparent);
      --float-border-color: color-mix(in srgb, var(--c-border) calc(var(--panel-border-alpha) * 100%), transparent);

      /* ── E10b (UI audit item 17) — the space scene is ONE app-wide layer ──
         R8 scoped the backdrop to #playScreen, so Deck Manager / Batch Tests /
         Optimizer sat on flat app grey and the app looked like two products.
         The image + scrim pair now paints on <body> (see the body rule below)
         and EVERY screen sits on it; screens that still need a readable slab
         wear --screen-tint instead of an opaque --c-surface-N fill.
         --app-bg-dim is R8's --play-bg-dim at the same value, renamed because
         it is no longer Play-only. Declared on :root so deck_manager.js's
         injected CSS can read it too (same document, so var() resolves). */
      --app-bg-dim: 0.78;                  /* scrim strength over the backdrop image */
      --screen-tint: rgba(15, 15, 15, 0.55);  /* translucent screen slab — scene reads through, bare text stays legible. NO backdrop-filter (blur is reserved for small floating glass, and it would make the screen a containing block for fixed children). */
      /* ── COUNT GLOW — the single source of truth for a card-quantity number ──
         Orbitron 900 white over a layered black outer glow, so a count reads on
         any art with NO scrim box behind it. One token consumed everywhere a
         "×N" copy count appears: the Deck Manager tile badge, the cardlist rows
         (editable + read-only), and the generated deck / wishlist share images
         (the canvas mirrors this stack in _countGlow(), deck_manager.js). The
         board's DECK/TRASH/RFG zone-overlay keeps its own heavier board-legibility
         variant — that is a zone-pile count, a different context. Declared on
         :root so deck_manager.js's injected CSS resolves the var() (same doc). */
      --gcg-count-glow: 2px 2px 2px #000, 2px 2px 5px #000, 2px 2px 2px #000;
    }
/* ============================================================
   theme-bridge.css — Card Battle Lab palette for the rules site.

   The rules page authors its own SEMANTIC tokens (--bg, --surface,
   --accent, --lbl-*, --sec-*, …). This layer REDEFINES those tokens
   in terms of Card Battle Lab's canonical --c-* palette, so the rules
   site renders in the exact colours the game ships without forking a
   second palette. The CBL --c-* tokens themselves are inlined ahead of
   this block by build_rules_site.py (extracted verbatim from
   viewer/index.html's :root), so every var() below resolves.

   Scope / precedence:
   - This is a :root block injected AFTER the page's own :root, so it
     overrides the page's DARK defaults by source order (equal specificity).
   - The page's light theme (html[data-theme="light"]) and high-contrast
     (html[data-contrast="more"]) blocks have HIGHER specificity, so they
     still win when active — CBL is dark-only, and light mode keeps the
     rules page's own parchment palette untouched.

   Mapping rationale: CBL is neutral near-black chrome. Interactive chrome
   (links / focus / accents) uses the game's Activate/Action BLUE, and the one
   prominent action (the Play CTA) reuses the app's primary "Start Match"
   button. The trigger CYAN (--c-game-trigger) is NOT used as chrome here: on
   this page it renders as the trigger lozenge (--lbl-trigger), so it stays
   reserved for that meaning. The card-label chips map 1:1 onto the game's
   official lozenge tokens (--c-lz-*) because they are the SAME printed
   concepts (keyword / trigger / activate / burst / pair / link / once).
   Section hues rotate across the CBL game+card colour vocabulary.
   ============================================================ */
:root {
  /* ── Chrome: surfaces, borders, text ramp ── */
  --bg:            var(--c-app-bg);
  --reader:        var(--c-app-bg);
  --surface:       var(--c-panel);
  --surface2:      var(--c-panel-3);
  --border:        var(--c-border);
  --border-strong: var(--c-border-strong);

  --text:   var(--c-text);
  --prose:  var(--c-text-bright);
  --muted:  var(--c-text-dim);
  --muted2: var(--c-text-faint);

  /* ── Interactive chrome accent: NEUTRAL, matching the app's own --c-accent
     (#d1d1d1). In CBL every HUE is reserved for a canonical game meaning, so
     generic site chrome (links, focus, active tab, brand highlight, section
     accents) must NOT borrow a game colour. It previously used the Activate
     blue, which clashed with that rule: the Activate blue renders on THIS page
     as the Activate lozenge (--lbl-activate below) and means "Activate". Colour
     now appears ONLY where it is canonical: the card-label lozenges (--lbl-*,
     the official on-card categories) and the green Play CTA (the app's reused
     commit button). Interactivity is carried by weight/underline/brightness,
     not by hue. Highlights brighten toward --c-white (a brighter shade of the
     element's own neutral), never a flat grey. ── */
  --accent:      var(--c-accent);
  --accent-text: var(--c-accent);
  --accent-dim:  color-mix(in srgb, var(--c-accent) 55%, var(--c-app-bg));
  --accent-wash: color-mix(in srgb, var(--c-accent) 14%, transparent);
  --focus:       var(--c-accent);
  --selection:   color-mix(in srgb, var(--c-accent) 22%, transparent);

  /* ── Secondary accent / cross-references: a brighter neutral (one step up the
     text ramp), distinguished from body prose by weight + underline, not hue. ── */
  --accent2: var(--c-text-bright);
  --xref:    var(--c-text-bright);

  /* ── Caution / examples: amber = the app's canonical warn colour, kept because
     it carries the same "caution" meaning here. Example tint is a faint neutral
     wash (no hue). ── */
  --amber:        var(--c-warn);
  --amber-text:   var(--c-warn);
  --amber-wash:   color-mix(in srgb, var(--c-warn) 16%, transparent);
  --example-tint: color-mix(in srgb, var(--c-white) 6%, transparent);

  /* ── Traits ── */
  --trait: var(--c-card-purple);

  /* ── Card-label chips → the game's official card-lozenge tokens (--c-lz-*),
     1:1 with the printed on-card categories: keyword ability = near-white,
     trigger = cyan, activate = blue, burst = orange, pair = pink, link =
     yellow, once = maroon. Two inks: dark on the light fills, light on the
     dark fills (burst / once). ── */
  --lbl-keyword:  var(--c-lz-ability);
  --lbl-trigger:  var(--c-lz-trigger);
  --lbl-activate: var(--c-lz-activate);
  --lbl-burst:    var(--c-lz-burst);
  --lbl-pair:     var(--c-lz-pair);
  --lbl-link:     var(--c-lz-link);
  --lbl-once:     var(--c-lz-once);
  --lbl-ink:      var(--c-ink-dark);
  --lbl-ink-light:var(--c-ink-light);
  --kw-generic:   var(--c-game-unavailable);

  /* ── Section accent: ONE colour for every section group. The rules are one
     body; a rotating rainbow implied a taxonomy that isn't real and read as
     bespoke decoration. Every section card wears the interactive Activate
     blue, same as the rest of the site chrome. ── */
  --sec-1:  var(--accent);
  --sec-2:  var(--accent);
  --sec-3:  var(--accent);
  --sec-4:  var(--accent);
  --sec-5:  var(--accent);
  --sec-6:  var(--accent);
  --sec-7:  var(--accent);
  --sec-8:  var(--accent);
  --sec-9:  var(--accent);
  --sec-10: var(--accent);
  --sec-11: var(--accent);
  --sec-12: var(--accent);
  --sec-13: var(--accent);

  /* ── Frost recipe, sourced from the game's own tokens ──
     Big surfaces get a blurred dark-glass fill (--glass-modal-*); the reader
     column gets a heavier opaque scrim so long rule text stays crisp; content
     cards get a translucent (no-blur) panel so the space scene peeks through
     without a blur layer per card. */
  --rs-glass:  var(--glass-modal-bg, rgba(15,15,15,.55));
  --rs-blur:   var(--glass-modal-blur, blur(14px) saturate(115%));
  --rs-reader: color-mix(in srgb, var(--c-app-bg) 84%, transparent);
  --rs-panel:  color-mix(in srgb, var(--c-app-bg) 80%, transparent);
  /* The main content region's own frosted backdrop: lighter than the panels so
     the scene reads through the blur, dark enough that the darker panels on top
     still separate. One layer, shared by every view. */
  --rs-page:   color-mix(in srgb, var(--c-app-bg) 52%, transparent);
}

/* ── Space scene behind everything (same background asset as the app) ── */
body{
  background: var(--c-app-bg) url('./BG_1.webp') center center / cover no-repeat fixed;
}

/* ── ONE frosted box for the whole content column ──────────────────────────
   Using SEPARATE backdrop-filter boxes for the header, tree and body produced a
   visible seam: a backdrop-filter only blurs the scene inside its OWN rect, so
   at a shared edge the two blurs cannot blend and a line appears that looks like
   the blur strength changed (it did not — every box was blur(14px)). Fix: a
   SINGLE fixed frosted layer, --app-max wide and centred, sitting behind the
   whole column. One continuous blur, no internal seam. It is still content-
   width, so the scene stays SHARP in the left/right margins; on mobile
   (min(100%, ...) resolves to 100%) it fills the screen, so the header is never
   the lone un-frosted surface. Placed on a pseudo of <body> (not an ancestor of
   #app), so #app's position:fixed descendants still resolve to the viewport. ── */
body::before{
  content:'';
  position:fixed;
  top:0; bottom:0;
  left:50%;
  transform:translateX(-50%);
  width:min(100%, var(--app-max, 1340px));
  z-index:0;
  pointer-events:none;
  background: var(--rs-page);
  -webkit-backdrop-filter: var(--rs-blur);
  backdrop-filter: var(--rs-blur);
}
#app{ position:relative; z-index:1; }

/* ── True overlays (mobile bottom nav, sheets, help box, mobile filter popover)
   float OVER scrolling content, so they keep their own blur + darker glass. The
   filter bar is NOT here: it lives inside .bodyhead (below) and is left on its
   authored transparent fill, so it does not stack a second glass layer. The
   status footer is NOT here either: it is an in-flow bottom band (see below). ── */
.botnav,
.sheet,
.help .box,
.mfilter{
  background: var(--rs-glass) !important;
  -webkit-backdrop-filter: var(--rs-blur);
  backdrop-filter: var(--rs-blur);
}

/* ── Status footer: an in-flow bottom band, not a floating overlay. It was the
   only shell row left full-bleed (.topinner and .main are both --app-max and
   centred), which read as a full-width bar under the narrower content column.
   Constrain it to the same centred column, and make it transparent like the
   header so the single frosted column shows through — no own backdrop-filter,
   so no seam forms at its top edge. Its authored subtle border-top stays as a
   light divider. ── */
.status{
  width:100%;
  max-width:var(--app-max, 1340px);
  margin-inline:auto;
  background: transparent !important;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── Darker translucent bands on the single frosted column — tint ONLY, no
   second backdrop-filter (a second box is exactly what caused the seam). None of
   these overlap scrolling content (each view's scroller is a separate child
   below them), so no blur is needed for legibility.
   - .tree  : the nav rail.
   - .bodyhead : the search + filter-bar header. Was solid var(--bg) (opaque
     near-black); now the same darker glass as the tree.  ── */
.tree,
.bodyhead{
  background: var(--rs-glass) !important;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── Breadcrumb / result-count strip: was an opaque grey slab (var(--reader) on
   rules, var(--surface2) grey on FAQ). Transparent, so the frosted column shows
   through and it reads as floating text, not a bar. ── */
.crumbs,
.faq-panel .crumbs{
  background: transparent !important;
}

/* ── Header: no full-width bar, no grey line, no OWN blur — transparent, so the
   single frosted column shows through it. Content (.topinner) keeps its
   page-width centred column. ── */
.topbar{
  background: transparent !important;
  border-bottom: 0 !important;
  box-shadow: none !important;
}
.topinner{
  background: transparent !important;
}

/* ── Body + every view: transparent on the single frosted column, so home,
   rules, faq, glossary, modes, timeline, tournament and changelog all share one
   continuous softened surface with no seam. ── */
.body,
.reader,
.readwrap,
.home-panel{
  background: transparent !important;
}

/* ── Rule groups → panels, one per text group, mirroring the game's Options
   screen (each titled group is its own darker surface card). No per-group blur:
   they sit on the already-frosted .body, so a darker translucent fill + the
   app's card border / radius / elevation is all that's needed (this also keeps
   them consistent with the .gcard content cards below). The section head sits
   INSIDE the panel, so its old 26px top margin is folded into the padding. ── */
.group{
  background: var(--rs-panel);
  border:1px solid var(--border);
  border-radius:var(--card-radius);
  box-shadow: var(--elev);
  padding:16px 20px 18px;
  margin:0 0 16px;
}
.group .ghead{ margin:0 0 10px; }

/* ── Content cards: translucent panel, no per-card blur (perf) ── */
.gcard,
.seccard,
.whatsnew,
.cmcard,
.modecard,
.tl-card,
.faqitem{
  background: var(--rs-panel) !important;
}

/* ── Play CTA: the ONLY prominent action on the page (funnel into the game).
   It IS the app's standard primary/confirm button (viewer/index.html
   .rp-btn.primary): --c-commit fill, --c-on-commit ink, display-face 900
   uppercase with .08em tracking, the app's --r-m control radius, and the same
   gradient / border / glow. No bespoke pill shape or body-font weight — the
   funnel reads identically to the game's own "do this thing" control. The
   #fff/#000 below are that recipe's own shade endpoints, copied 1:1. ── */
.playcta{
  --c: var(--c-commit);
  display:inline-flex; align-items:center; justify-content:center; gap:.4em;
  padding:8px 18px; border-radius:var(--r-m, 6px);
  font-family:var(--font-display, inherit); font-weight:900; font-size:.72rem;
  letter-spacing:.08em; text-transform:uppercase; white-space:nowrap; text-decoration:none;
  color: var(--c-on-commit);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--c) 78%, #fff) 0%,
    var(--c) 48%,
    color-mix(in srgb, var(--c) 82%, #000) 100%);
  border: 1px solid color-mix(in srgb, var(--c) 72%, #000);
  box-shadow: 0 2px 14px color-mix(in srgb, var(--c) 28%, transparent), inset 0 1px 0 rgba(255,255,255,.30);
  cursor:pointer; transition: background .12s ease, border-color .12s ease, transform .1s ease;
}
.playcta:hover{
  border-color: color-mix(in srgb, var(--c) 55%, #fff);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--c) 52%, #fff) 0%,
    color-mix(in srgb, var(--c) 80%, #fff) 48%,
    color-mix(in srgb, var(--c) 96%, #fff) 100%);
}
.playcta:active{ transform: translateY(1px); }
.playcta:focus-visible{ outline:2px solid var(--c-white); outline-offset:2px; }
.playcta.full{ width:100%; padding:12px 18px; font-size:.9rem; margin-bottom:12px; }
/* Tabler "play" glyph inside the CTA, sized to the label. */
.playcta svg{ width:1.05em; height:1.05em; flex:0 0 auto; }

