/* sw-rpg-icons webfont (MIT) — https://github.com/aflegel/FantasyFlightIcons — result/force-pip glyphs only */
@font-face{
  font-family:'sw-rpg-icons';
  src:url(data:font/woff;charset=utf-8;base64,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) format('woff');
  font-weight:normal; font-style:normal;
}
/* FT Aurebesh by Rodrigo Fuenzalida — SIL Open Font License 1.1; full license: assets/fonts/FTAurebesh-OFL.rtf */
@font-face{font-family:'ft-aurebesh';src:url('../assets/fonts/FTAurebesh-Regular.woff2') format('woff2');font-style:normal;font-weight:400;font-display:swap}

/* ---- webfaces, ALL SELF-HOSTED (SIL Open Font License 1.1) ----
   Latin subsets pulled from Google Fonts; each family's full license sits
   beside its file (assets/fonts/*-OFL.txt). These exist because #printDoc used
   to read the ambient var(--disp)/var(--body), which themes.css redefines per
   book theme — so printed output changed font with the screen theme AND needed
   a live webfont fetch, in an app that is otherwise offline-first. (Don't name
   the CDN host here: test-phase3 greps the standalone build for it, and a
   comment is enough to trip that guard.)
   #printDoc now pins these three (see the print palette block below).
   Oxanium ships as ONE variable file covering the 200-800 weight axis. */
@font-face{font-family:'Oxanium';src:url('../assets/fonts/Oxanium-var.woff2') format('woff2');font-style:normal;font-weight:200 800;font-display:swap}
@font-face{font-family:'Chakra Petch';src:url('../assets/fonts/ChakraPetch-400.woff2') format('woff2');font-style:normal;font-weight:400;font-display:swap}
@font-face{font-family:'Chakra Petch';src:url('../assets/fonts/ChakraPetch-600.woff2') format('woff2');font-style:normal;font-weight:600;font-display:swap}
@font-face{font-family:'Share Tech Mono';src:url('../assets/fonts/ShareTechMono-400.woff2') format('woff2');font-style:normal;font-weight:400;font-display:swap}
/* The book themes' three faces, self-hosted too (2026-08-16) — the app now has
   no external origin at all, which is what lets the CSP say `default-src 'self'`
   without a font exception. Exo 2 ships as one variable file (100-900 axis);
   the other two have no variable release, so their three printed weights ship
   as statics. Latin subset, no unicode-range — same as the faces above. */
@font-face{font-family:'Exo 2';src:url('../assets/fonts/Exo2-var.woff2') format('woff2');font-style:normal;font-weight:100 900;font-display:swap}
@font-face{font-family:'Barlow Semi Condensed';src:url('../assets/fonts/BarlowSemiCondensed-400.woff2') format('woff2');font-style:normal;font-weight:400;font-display:swap}
@font-face{font-family:'Barlow Semi Condensed';src:url('../assets/fonts/BarlowSemiCondensed-500.woff2') format('woff2');font-style:normal;font-weight:500;font-display:swap}
@font-face{font-family:'Barlow Semi Condensed';src:url('../assets/fonts/BarlowSemiCondensed-600.woff2') format('woff2');font-style:normal;font-weight:600;font-display:swap}
@font-face{font-family:'Alegreya Sans';src:url('../assets/fonts/AlegreyaSans-400.woff2') format('woff2');font-style:normal;font-weight:400;font-display:swap}
@font-face{font-family:'Alegreya Sans';src:url('../assets/fonts/AlegreyaSans-500.woff2') format('woff2');font-style:normal;font-weight:500;font-display:swap}
@font-face{font-family:'Alegreya Sans';src:url('../assets/fonts/AlegreyaSans-700.woff2') format('woff2');font-style:normal;font-weight:700;font-display:swap}


/* ============================================================
   THEME TOKENS — every appearance property in the app reads
   from these custom properties. This :root block is the full
   default set (Holo / Dark). css/themes.css overrides tokens
   per [data-theme] + [data-mode] on <html>; components below
   NEVER hardcode a theme color.
   ============================================================ */
:root{
  /* page + surfaces */
  --void:#131a22;
  --bg-layers:
    radial-gradient(1px 1px at 12% 22%, rgba(160,220,255,.35) 50%, transparent 51%),
    radial-gradient(1px 1px at 78% 14%, rgba(160,220,255,.25) 50%, transparent 51%),
    radial-gradient(1px 1px at 55% 68%, rgba(160,220,255,.2) 50%, transparent 51%),
    radial-gradient(1px 1px at 30% 84%, rgba(160,220,255,.28) 50%, transparent 51%),
    radial-gradient(1px 1px at 90% 55%, rgba(160,220,255,.22) 50%, transparent 51%),
    radial-gradient(ellipse at 50% -20%, #0a1826 0%, var(--void) 60%);
  --bg-size:auto;
  --panel-a:rgba(13,26,36,.92); --panel-b:rgba(9,17,24,.92);
  --panel:#1b242e; --panel2:#212d39; --line:#33424f;
  --input-bg:#0a141d;
  --hover:#11212e; --hover-strong:#123045; --tile-hover:#12283a;
  --pick-on:#0f2c3d;
  --modal-scrim:rgba(2,6,10,.78); --modal-glow:rgba(98,230,255,.12);
  /* Light backdrop for the tour's centered intro/outro cards only (the tour never
     dims the screen otherwise). One base value; all themes inherit it. */
  --tour-scrim:rgba(2,6,10,.4);
  --shadow:rgba(0,0,0,.6);
  /* sidebar (scoped: may stay dark in light themes) */
  --sb-a:#08111a; --sb-b:#060b11; --sb-line:var(--line);
  --sb-ink:var(--ink); --sb-dim:var(--dim); --sb-faint:var(--faint);
  --sb-accent:var(--holo); --sb-accent-dim:var(--holo-dim);
  --sb-hover:#0e1d29; --sb-active:#0f2432;
  /* topbar */
  --topbar-bg:rgba(8,17,26,.8);
  /* primary + secondary accents */
  --holo:#8ec9dd; --holo-dim:#48788c; --holo-bright:#a9f1ff;
  --glow:rgba(142,201,221,.25);
  --focus-ring:rgba(98,230,255,.25);
  --accent-wash:rgba(142,201,221,.06); --accent-wash-mid:rgba(142,201,221,.12); --accent-wash-hover:rgba(142,201,221,.16);
  /* theming pass — shell + third-role tokens (see themes.tokens handoff).
     --ink-hi brightest ink (near-black in light): inks numeric readouts so
     figures outrank labels. --info third role: references/source attribution,
     distinct from --amber. --on-amber ink ON a filled amber/gold plate,
     inverts per mode — NEVER a literal. --hatch/--hatch-angle surface hatch.
     --rim/--rim-glow shell brackets + rivets, alias --holo-bright/--glow so
     light mode's --glow:transparent kills all bloom at once. */
  --ink-hi:#f4f6f7; --info:#e3b06d; --on-amber:#fff3de;
  --hatch:rgba(0,0,0,.22); --hatch-angle:180deg;
  --rim:var(--holo-bright); --rim-glow:var(--glow);
  /* hexagon "screwhead" rivet — metallic, theme-independent (a screw is metal);
     drawn as an SVG background at the four panel corners (see .panel). */
  --rivet:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='11'%20height='12'%20viewBox='0%200%2011%2012'%3E%3Cpath%20d='M5.5%200.5L10%203L10%209L5.5%2011.5L1%209L1%203Z'%20fill='%233c4750'%20stroke='%236b7c86'%20stroke-width='0.8'/%3E%3Cline%20x1='2.9'%20y1='6'%20x2='8.1'%20y2='6'%20stroke='%2310151a'%20stroke-width='1'/%3E%3C/svg%3E");
  /* text-size zoom factor — mirrors the zoom applied to #app so vh-based
     heights can divide it back out. Keep in lockstep with the [data-textsize]
     zoom rules below. RULE: inside #app/#modal/#dlg a viewport fit is
     calc(100vh/var(--ts-zoom,1) - <chrome px>) — a vh length is NOT scaled
     back by zoom, so a bare calc(100vh - N) renders ~12% past the screen at
     Large (the Atlas ran 231px below the fold). A plain fraction cap
     (60vh, min(72vh,…)) only grows proportionally and needs no correction. */
  --ts-zoom:1;
  /* native SVG galactic chart */
  --map-space:#050b12; --map-star:var(--holo-bright); --map-core-glow:var(--amber);
  --map-grid:rgba(142,201,221,.2); --map-region-line:rgba(142,201,221,.28);
  --map-region-deep:#ff4d57; --map-region-core:#ff8b4a;
  --map-region-colonies:#ffc94f; --map-region-inner:#ffe58c;
  --map-region-expansion:#86d3ff; --map-region-mid:#5da8ff;
  --map-region-outer:#ff78ae; --map-region-hutt:#ae6cff;
  --map-region-wild:#43ddb2; --map-region-unknown:#9699a8;
  --map-route:rgba(141,183,199,.62); --map-route-casing:#01040a; --map-route-active:var(--amber);
  --map-marker:var(--holo-bright); --map-marker-halo:var(--holo); --map-campaign:var(--green);
  --map-field:#7fb8cf; --map-field-cell:#8fd0e6;
  /* status accents */
  --amber:#e3b06d; --amber-dim:#8a6428; --amber-bg:#241a08; --amber-bg-hover:#3a2a0d; --amber-wash:rgba(255,181,77,.06); --amber-wash-hover:rgba(227,176,109,.16);
  --red:#e2786e; --red-dim:#7a2f2b; --red-bg:#220c0b; --red-bg-hover:#3a1512; --red-wash:rgba(255,95,86,.06);
  --green:#7fcaa3; --green-dim:#2b6e51; --green-bg:#0d2a1e; --green-wash:rgba(89,224,160,.05); --green-glow:rgba(127,202,163,.25);
  /* Force-power identity — a fixed FFG green (deliberately NOT themed, like the
     card-stock/dice colors), distinct from --green which marks talent ownership. */
  --force-green:#2f7d4f;
  /* Repair-state severity (equipment tab) — fixed green/yellow/orange/red like
     the OggDude reference, deliberately NOT themed (same exception class as
     --force-green/dice/card-stock: a severity read, not a brand color). */
  --repair-none:#4a9f5a; --repair-minor:#d9c34a; --repair-moderate:#d98a3f; --repair-major:#c94f4f;
  /* text */
  --ink:#dde5ea; --dim:#9fb2bf; --faint:#8a9ca6;
  /* buttons */
  --btn-bg:#0e2331; --btn-hover:#164059;
  /* press physics (consumed by .btn/.tile/.navbtn press states): a 2px bottom
     bevel that a press drops onto its seat, swapping to a recessed inner shadow.
     Declared here; the press rules live after the THEMING PASS layer. */
  --bevel:0 2px 0 rgba(0,0,0,.4); --bevel-recess:inset 0 2px 3px rgba(0,0,0,.45); --press-shift:1px;
  /* dice result symbols (user-overridable in Settings) */
  --sym-main:#f4f6f7;
  --sym-su:var(--sym-main); --sym-ad:var(--sym-main); --sym-fa:var(--sym-main); --sym-th:var(--sym-main);
  --sym-tr:#e3b06d; --sym-de:#e2786e;
  /* Light/Dark Side pips: mode-driven (see [data-mode] rules in
     css/themes.css) — these are just pre-JS fallbacks. */
  --sym-li:#fff; --sym-li-stroke-w:0; --sym-li-stroke-c:transparent;
  --sym-da:#000; --sym-da-stroke-w:1px; --sym-da-stroke-c:var(--faint);
  /* reSpecialized [CF]/[RP] markers: inked like body text so they stay
     legible on any theme background (re-inked on card stock / print).
     --cf-mask is the FFG Conflict glyph (user-supplied reference): a
     four-point jagged compass star with barbed tips, a hollow-core ring,
     and four arc segments between the points. Kept as an SVG mask so one
     shape serves every surface and background-color carries the ink. */
  --sym-cf:var(--ink); --sym-rp:var(--ink);
  /* --rp-mask is the FFG Repeatable glyph (user-supplied reference): a hollow
     diamond drawn as two interlocking chevron strokes with offset gaps and
     stepped arrow ends — the cycle motif. Same mask technique as --cf-mask. */
  --rp-mask:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%3E%3Cpath%20d='M22,34L50,6L94,50L83,61L75,53L78,50L50,22L30,42Z'/%3E%3Cpath%20d='M78,66L50,94L6,50L17,39L25,47L22,50L50,78L70,58Z'/%3E%3C/svg%3E");
  --cf-mask:url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20100%20100'%3E%3Cpath%20fill-rule='evenodd'%20d='M50,20a30,30,0,1,0,0,60a30,30,0,1,0,0-60Zm0,12a18,18,0,1,1,0,36a18,18,0,1,1,0-36Z'/%3E%3Cpath%20d='M50,1L60,18L69,13L61,25L57,23L50,30L43,23L39,25L31,13L40,18Z'/%3E%3Cpath%20d='M50,99L60,82L69,87L61,75L57,77L50,70L43,77L39,75L31,87L40,82Z'/%3E%3Cpath%20d='M99,50L82,60L87,69L75,61L77,57L70,50L77,43L75,39L87,31L82,40Z'/%3E%3Cpath%20d='M1,50L18,60L13,69L25,61L23,57L30,50L23,43L25,39L13,31L18,40Z'/%3E%3Cpath%20d='M90.8,31A45,45,0,0,0,69,9.2L65.7,16.5A37,37,0,0,1,83.5,34.4Z'/%3E%3Cpath%20d='M31,9.2A45,45,0,0,0,9.2,31L16.5,34.4A37,37,0,0,1,34.3,16.5Z'/%3E%3Cpath%20d='M9.2,69A45,45,0,0,0,31,90.8L34.3,83.5A37,37,0,0,1,16.5,65.6Z'/%3E%3Cpath%20d='M69,90.8A45,45,0,0,0,90.8,69L83.5,65.6A37,37,0,0,1,65.7,83.5Z'/%3E%3C/svg%3E");
  /* dice pool shapes (FFG canon — themes rarely override) */
  --die-p:#e8c93e; --die-a:#41ad49; --die-b:#72cddc; --die-d:#6a3a9e; --die-c:#a3312d; --die-s:#26282b; --die-s-edge:#828a94; --die-f:#f2efe6; --die-f-outline:#141414;
  /* decorative fx */
  --fx-scanlines:.225;
  /* CRT vignette — the tube is darker at its edges. Rendered on html::after
     under the scanline layer; zeroed in every light mode (see themes.css). */
  --fx-vignette:.55;
  /* scrollbar */
  --scroll-track:#060b11; --scroll-thumb:#173242;
  /* typography */
  --disp:'Oxanium',sans-serif; --body:'Chakra Petch',sans-serif; --label:'Chakra Petch',sans-serif;
  /* fixed-width technical strings. Declared here (not only in the print layer,
     where it lived before) because .cbrk/.sy-ID/DD/RD glyphs consume it on
     screen and were silently falling back to the browser monospace. */
  --mono:'Share Tech Mono',ui-monospace,monospace;
  /* interface density: spacing multiplier (Settings > Display & Accessibility) */
  --dsp:1;
}
/* ---- Global text size (overall UI scale) ---- */
:root[data-textsize="small"] #app,
:root[data-textsize="small"] #modal,
:root[data-textsize="small"] #dlg,
:root[data-textsize="small"] #swUpdate{ zoom:0.9 }
:root[data-textsize="large"] #app,
:root[data-textsize="large"] #modal,
:root[data-textsize="large"] #dlg,
:root[data-textsize="large"] #swUpdate{ zoom:1.12 }
:root[data-textsize="small"]{ --ts-zoom:0.9 }
:root[data-textsize="large"]{ --ts-zoom:1.12 }
/* ---- Interface density (spacing scale) ---- */
:root[data-density="compact"]{ --dsp:0.75 } /* was 0.6 — too aggressive; see the .panel clearance note */
:root[data-density="spacious"]{ --dsp:1.3 }
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
/* icon glyphs (⚠ ⚔ ⚙ ☠ ♟ …) in their monochrome TEXT form, never colour
   emoji; boot.js adds U+FE0E where a browser lacks this property (iOS Safari) */
html{font-variant-emoji:text}
body{
  background:var(--void); color:var(--ink); font-family:var(--body); font-size:14px;
  background-image:var(--bg-layers);
  background-size:var(--bg-size);
  /* no-repeat + centered so a book page texture renders once at a fixed width
     (see --bg-size in the book light themes) rather than tiling. Harmless for
     the gradient-based themes, whose --bg-layers fill the area regardless. */
  background-repeat:no-repeat;
  background-position:center top;
  background-attachment:fixed;
}
body::after{ /* scanlines */
  content:""; pointer-events:none; position:fixed; inset:0; z-index:99;
  background:repeating-linear-gradient(0deg, rgba(0,0,0,.05) 0 1px, transparent 1px 3px);
  mix-blend-mode:overlay; opacity:var(--fx-scanlines);
}
html::after{ /* CRT vignette — the tube is darker at its edges; sits just under
  the scan layer so it dims content as well as background. Zeroed in light modes
  (--fx-vignette:0) and hidden in print. */
  content:""; pointer-events:none; position:fixed; inset:0; z-index:98;
  background:radial-gradient(ellipse 100% 100% at 50% 50%, transparent 55%, #000 128%);
  opacity:var(--fx-vignette);
}
@media (prefers-reduced-motion: reduce){ *{animation:none!important;transition:none!important} }
/* min-height:100% (body is html,body{height:100%}) rather than 100vh/100dvh:
   the viewport units leave a full-length, non-functional scrollbar on short
   views (the vh box can exceed the actual layout viewport). % is measured
   against the real box and still fills the screen. */
#app{display:flex; min-height:100%}
/* ---------- header: single chrome bar (theming pass) ---------- */
/* The former #topbar (back / crumb / search / clock) is folded into #topnav —
   one bar, ~40px reclaimed app-wide. NEVER give #hdr/#topnav overflow:hidden:
   it would clip the #gsResults dropdown (the mobile nav panel clips via
   #navPanelWrap). The bar rides the --sb-* family, so back/crumb/search use
   sb tokens to stay legible when light themes keep the bar dark. */
/* The bar gradient is painted on #hdr as well as #topnav: #hdr carries the
   safe-area top inset (see the MOBILE SWEEP block), and with the paint only on
   #topnav that inset strip was TRANSPARENT — on a notched iPhone the page
   scrolled through it and the bar read as sitting under the status bar. */
#hdr{position:sticky; top:0; z-index:40; background:linear-gradient(90deg,var(--sb-a),var(--sb-b))}
#topnav{
  display:flex; align-items:center; gap:14px; padding:5px 12px;
  background:linear-gradient(90deg,var(--sb-a),var(--sb-b));
  border-bottom:1px solid var(--sb-line); position:relative;
  flex-wrap:wrap;
}
.logo{display:inline-flex; align-items:center; gap:8px; font-family:var(--disp); font-size:11px;
  letter-spacing:.08em; color:var(--sb-accent); text-transform:uppercase; white-space:nowrap; padding:0}
/* app emblem in front of the wordmark — sized under the nav row height so it
   never grows the banner (the .navgrp buttons remain the tallest element) */
.logomark{height:20px; width:20px; flex:none; border-radius:4px;
  background:url(../assets/icons/icon-192.png) center/cover no-repeat;
  box-shadow:0 0 0 1px var(--sb-line)}
#navPanelWrap{display:contents}
/* desktop: a slim menu bar — only category names in the row; each category's
   module list reveals as a dropdown on hover, and pins open on click
   (App.navPin toggles .pinned; navigating or clicking outside unpins) */
/* flex:0 0 auto — the nav keeps its content width and NEVER shrinks below it
   (its nowrap buttons would otherwise overflow the box and overlap the control
   group, badly at large-text zoom). The #topctrl group wraps to a second line
   instead, via #topnav's flex-wrap. */
/* margin-right:auto (rather than margin-left:auto on #topctrl) absorbs the free
   space on the nav's own line: when everything fits on one row the control group
   still sits at the far right, but when #topctrl wraps to its own row it aligns
   to the row start (left) instead of being pushed right by an auto left margin. */
#navGroups{display:flex; gap:2px; align-items:center; flex:0 0 auto; min-width:0; margin-right:auto}
/* right-side chrome controls travel together: right-aligned on the same row
   when they fit, wrapping to their own row as a unit (left-aligned) when they
   don't. On mobile the group dissolves (display:contents) so the existing
   single-bar rules keep applying to #backBtn/#gsWrap directly. */
#topctrl{display:flex; align-items:center; gap:14px; flex:0 0 auto; min-width:0}
.navcluster{position:relative}
.navgrp{display:inline-flex; align-items:center; gap:5px; font-family:var(--label); font-size:10px;
  letter-spacing:.2em; color:var(--sb-dim); text-transform:uppercase; background:transparent;
  border:1px solid transparent; padding:6px 11px; cursor:pointer; white-space:nowrap;
  transition:background .12s,color .12s}
/* category glyph: dim by default, brightens with the category's own accent cue */
.navgrp .ic{color:var(--sb-accent-dim); font-size:11px; line-height:1}
.navcluster:hover>.navgrp,.navcluster.pinned>.navgrp{background:var(--sb-hover); color:var(--sb-ink)}
.navcluster:hover>.navgrp .ic,.navcluster.pinned>.navgrp .ic{color:var(--sb-accent)}
.navcluster.pinned>.navgrp{border-color:var(--sb-accent-dim); color:var(--sb-accent)}
/* the category holding the active view keeps an accent cue even when closed */
.navcluster:has(.navbtn.active)>.navgrp{color:var(--sb-accent); box-shadow:inset 0 -2px 0 var(--sb-accent)}
.navcluster:has(.navbtn.active)>.navgrp .ic{color:var(--sb-accent)}
/* solo entries: a category-bar button that navigates directly (no dropdown) */
.navcluster.solo>.navgrp .ic{color:var(--sb-accent-dim)}
.navcluster.solo>.navgrp.active{color:var(--sb-accent); box-shadow:inset 0 -2px 0 var(--sb-accent)}
.navcluster.solo>.navgrp.active .ic{color:var(--sb-accent)}
.navmenu{display:none; position:absolute; top:100%; left:0; z-index:45; min-width:216px;
  background:linear-gradient(180deg,var(--sb-a),var(--sb-b)); border:1px solid var(--sb-line);
  box-shadow:0 12px 30px rgba(0,0,0,.5); padding:5px}
.navcluster:hover>.navmenu,.navcluster.pinned>.navmenu{display:block}
.navbtn{
  display:flex; width:100%; align-items:center; gap:6px; text-align:left; cursor:pointer;
  background:transparent; border:1px solid transparent; color:var(--sb-dim);
  font-family:var(--body); font-size:12px; letter-spacing:.03em; padding:calc(5px*var(--dsp)) 9px;
  clip-path:polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
  transition:background .12s,color .12s; white-space:nowrap;
}
.navbtn:hover{background:var(--sb-hover); color:var(--sb-ink)}
.navbtn.active{background:var(--sb-active); color:var(--sb-accent); border-color:var(--sb-accent-dim);
  box-shadow:inset 2px 0 0 var(--sb-accent)}
.navbtn .ic{width:16px; text-align:center; color:var(--sb-accent-dim)}
.navbtn.active .ic{color:var(--sb-accent)}
/* ---------- chrome-bar controls (former topbar contents) ---------- */
#main{flex:1; min-width:0; display:flex; flex-direction:column}
#menuToggle{display:none}
#backBtn{
  font-family:var(--disp); font-size:10px; letter-spacing:.12em; text-transform:uppercase; white-space:nowrap;
  color:var(--sb-accent); background:var(--sb-hover); border:1px solid var(--sb-line);
  padding:4px 12px; border-radius:0; cursor:pointer; transition:background .12s,border-color .12s,opacity .12s;
}
#backBtn:hover:not(:disabled){background:var(--sb-active); border-color:var(--sb-accent-dim)}
#backBtn:disabled{opacity:.32; cursor:default}
#crumb{font-family:var(--disp); font-size:11px; letter-spacing:.18em; color:var(--sb-accent); text-transform:uppercase; white-space:nowrap}
#gsWrap{position:relative; width:min(300px,32vw)}
/* de-pilled search: a square machined slot with an inset ⌕ and a 1px accent
   underline that brightens on focus (the ⌕ was moved out of the placeholder
   so it persists while typing) */
#gsWrap::before{content:"⌕"; position:absolute; left:9px; top:50%; transform:translateY(-50%);
  color:var(--sb-accent-dim); font-size:12px; line-height:1; pointer-events:none; z-index:1}
#gsearch{width:100%; border-radius:0; padding:5px 12px 5px 26px; font-size:12px;
  background:var(--input-bg); border:1px solid var(--sb-line); border-bottom-color:var(--sb-accent-dim)}
#gsearch:focus{border-color:var(--sb-line); border-bottom-color:var(--sb-accent); box-shadow:none}
#gsWrap:focus-within::before{color:var(--sb-accent)}
#gsResults{
  position:absolute; top:110%; left:0; right:0; background:var(--panel2); border:1px solid var(--holo-dim);
  max-height:320px; overflow-y:auto; z-index:60; display:none;
}
#gsResults .gsr{padding:8px 10px; cursor:pointer; border-bottom:1px solid var(--line); font-size:12.5px}
#gsResults .gsr:hover{background:var(--hover-strong)}
#gsResults .gsr small{color:var(--faint); display:block; font-size:10px; letter-spacing:.15em; text-transform:uppercase}
#stardate{font-size:10px; color:var(--sb-faint); letter-spacing:.2em; white-space:nowrap}
#content{padding:calc(18px*var(--dsp)); max-width:1280px; width:100%; margin:0 auto}
/* ---------- panels (signature bracket frames) ---------- */
.panel{position:relative; background:linear-gradient(180deg,var(--panel-a),var(--panel-b));
  border:1px solid var(--line); padding:calc(14px*var(--dsp)) max(16px, calc(16px*var(--dsp))); margin-bottom:calc(16px*var(--dsp))}
/* Chrome clearance: the corner rivets reach ~13px in and the brackets are 14px.
   Density may scale a panel's vertical rhythm, but the side padding never drops
   below 16px (mobile, where it is 10px, floors the vertical padding at 14px
   instead) — Compact used to slide titles and notes under the rivets. */
.panel::before,.panel::after,
.panel>.bk::before,.panel>.bk::after{
  content:""; position:absolute; width:14px; height:14px; border:2px solid var(--holo-dim); z-index:1}
.panel::before{top:-2px; left:-2px; border-right:none; border-bottom:none}
.panel::after{top:-2px; right:-2px; border-left:none; border-bottom:none}
.panel>.bk::before{bottom:-2px; left:-2px; border-right:none; border-top:none}
.panel>.bk::after{bottom:-2px; right:-2px; border-left:none; border-top:none}
.panel.amber::before,.panel.amber::after,.panel.amber>.bk::before,.panel.amber>.bk::after{border-color:var(--amber-dim)}
.ptitle{font-family:var(--disp); font-size:11px; letter-spacing:.22em; text-transform:uppercase;
  color:var(--holo); margin-bottom:10px; display:flex; align-items:center; gap:8px}
.ptitle::after{content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--holo-dim),transparent)}
.ptitle.amb{color:var(--amber)} .ptitle.amb::after{background:linear-gradient(90deg,var(--amber-dim),transparent)}
/* ---------- controls ---------- */
input,select,textarea,button{font-family:var(--body); color:var(--ink)}
input:not([type]),input[type=text],input[type=number],input[type=date],input[type=search],input[list],select,textarea{
  background:var(--input-bg); color:var(--ink); border:1px solid var(--line); padding:7px 9px; font-size:13px; width:100%; min-width:0;
  outline:none; border-radius:0;
}
input:focus,select:focus,textarea:focus{border-color:var(--holo); box-shadow:0 0 0 1px var(--focus-ring)}
::placeholder{color:var(--faint);opacity:1}
select option{background:var(--input-bg);color:var(--ink)}
textarea{min-height:84px; resize:vertical; line-height:1.5}
/* machined select cap: a themed ▾ caret sitting in an 18px well behind a 1px
   --line divider, so a closed select reads as a bracketed slot, not a web
   form field. The caret keeps the two-gradient technique because it themes
   through var(--holo-dim); the divider well is what makes it hardware. */
select{appearance:none;
  background-image:
    linear-gradient(45deg,transparent 50%,var(--holo-dim) 50%),
    linear-gradient(135deg,var(--holo-dim) 50%,transparent 50%),
    linear-gradient(var(--line),var(--line));
  background-position:calc(100% - 13px) 55%,calc(100% - 8px) 55%,calc(100% - 22px) 0;
  background-size:5px 5px,5px 5px,1px 100%;
  background-repeat:no-repeat; padding-right:28px}
label.fl,.fl.gen-legend{display:block; font-family:var(--label); font-size:9.5px; letter-spacing:.22em; text-transform:uppercase; color:var(--faint); margin:10px 0 4px}
.btn{
  cursor:pointer; background:var(--btn-bg); border:1px solid var(--holo-dim); color:var(--holo);
  font-family:var(--body); font-size:11px; letter-spacing:.16em; text-transform:uppercase; padding:8px 14px;
  clip-path:polygon(0 0, calc(100% - 9px) 0, 100% 9px, 100% 100%, 9px 100%, 0 calc(100% - 9px));
  transition:background .12s;
}
.btn:hover{background:var(--btn-hover)}
.btn.amb{color:var(--amber); border-color:var(--amber-dim); background:var(--amber-bg)}
.btn.amb:hover{background:var(--amber-bg-hover)}
.btn.red{color:var(--red); border-color:var(--red-dim); background:var(--red-bg)}
.btn.red:hover{background:var(--red-bg-hover)}
.btn.ghost{background:transparent; border-color:var(--line); color:var(--dim)}
.btn.sm{padding:4px 9px; font-size:10px}
.toolbar{display:flex; flex-wrap:wrap; gap:calc(8px*var(--dsp)); margin-bottom:calc(14px*var(--dsp)); align-items:center}
.toolbar input[type=text]{flex:1; min-width:160px}
.toolbar select{width:auto; min-width:130px; flex:0 1 auto}
/* ---------- cards ---------- */
.cards{display:grid; grid-template-columns:repeat(auto-fill,minmax(270px,1fr)); gap:calc(10px*var(--dsp))}
.card{background:var(--panel2); border:1px solid var(--line); border-left:3px solid var(--holo-dim);
  padding:calc(10px*var(--dsp)) calc(12px*var(--dsp)); cursor:pointer; transition:background .12s, border-color .12s; position:relative}
.card:hover{background:var(--hover); border-color:var(--holo-dim)}
.card h3{font-size:14px; font-weight:600; letter-spacing:.04em; color:var(--ink); margin-bottom:2px}
.card .sub{font-size:11px; color:var(--dim); letter-spacing:.05em}
.card .meta{font-size:10.5px; color:var(--faint); margin-top:6px; line-height:1.6}
.card.f-dead{border-left-color:var(--red); opacity:.75}
.card.f-dead h3{color:var(--red); text-decoration:line-through}
.card.f-major{border-left-color:var(--amber)}
.card.f-missing{border-left-color:var(--red)}
.card.f-owned{border-left-color:var(--green)}
.card.f-done{border-left-color:var(--green); opacity:.7}
.npc-library-tabs{display:flex;flex-wrap:wrap;gap:3px;margin:8px 0 12px}
.npc-library-tabs .cgtab{flex:0 0 auto}
.npc-filter-toolbar{margin-bottom:7px}
/* Relationship web tab (folded-in Relationship Chart) */
.npc-web-add{gap:6px;flex-wrap:wrap;margin-bottom:8px}
.npc-web-add select{min-width:150px;flex:0 1 auto}
.npc-web-legend{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin:2px 0 8px;font-size:11px;color:var(--dim)}
.npc-web-legend span{display:inline-flex;align-items:center;gap:5px}
.npc-web-legend i{width:14px;height:3px;border-radius:2px;display:inline-block}
.npc-web-chart{border:1px solid var(--line);background:var(--panel2);padding:10px;margin-bottom:8px}
.npc-web-row{padding:5px 0;border-bottom:1px solid var(--line);line-height:1.9}
.npc-web-row:last-child{border-bottom:none}
.npc-web-row b.reflink{margin-right:6px}
.npc-library{display:grid;grid-template-columns:minmax(270px,.72fr) minmax(0,1.5fr);gap:calc(14px*var(--dsp));align-items:start}
/* Framed list + sticky preview, matching the character roster (.chlist/.chprev) */
.npc-cards{display:grid;gap:4px;max-height:calc(100vh/var(--ts-zoom,1) - 250px);overflow:auto;border:1px solid var(--line);background:var(--panel2);padding:5px}
.npc-card{display:flex;flex-direction:column;gap:3px;text-align:left;background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--holo-dim);padding:9px 10px;color:var(--ink);cursor:pointer;transition:background .12s,border-color .12s}
.npc-card:hover{background:var(--pick-on)}
.npc-card.active{background:var(--pick-on);box-shadow:inset 3px 0 0 var(--holo)}
.npc-card-title{font-size:12.5px;font-weight:600;color:var(--holo)}
.npc-card small{color:var(--dim);font-size:10px}
.npc-card-source{color:var(--faint)!important;font-size:9px!important;text-transform:uppercase;letter-spacing:.05em}
.npc-preview{min-width:0;border:1px solid var(--line);background:var(--panel2);padding:calc(14px*var(--dsp));position:sticky;top:calc(var(--hdr-h,52px) + 10px);max-height:calc(100vh/var(--ts-zoom,1) - 130px);overflow:auto}
@media(max-width:720px){
  .npc-library{grid-template-columns:1fr}
  .npc-cards{max-height:340px}
  .npc-preview{position:static;max-height:none}
}
.npc-list{margin:7px 0;padding-left:20px;line-height:1.65}
.npc-stat .chargrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:9px 0}
.npc-stat .chargrid .charbox{padding:6px 4px}
.npc-stat .chargrid .charbox .cv{font-size:20px}
.npc-stat .chargrid .charbox .cn{font-size:8px}
.npc-sheet-skills{width:100%}
.npc-sheet-skills td{padding:3px 6px;font-size:12px}
.npc-sheet-skills .npc-sk-rank{text-align:center;width:36px;color:var(--holo)}
.npc-statstrip{display:flex;flex-wrap:wrap;gap:6px;margin:9px 0}
.npc-stat-cell{display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:52px;padding:5px 8px;border:1px solid var(--line);background:var(--panel2)}
.npc-stat-cell b{font-family:var(--disp);font-size:17px;color:var(--holo);line-height:1.1}
.npc-stat-cell small{font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-top:2px}
.npc-source-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:8px 0 10px}
.npc-source-actions .cite{margin:0}
.npc-editor-band{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--line);background:var(--panel2);margin-bottom:12px}
.npc-editor-body{min-height:330px}
.npc-editor-actions{margin-top:16px;padding-top:12px;border-top:1px solid var(--line)}
.npc-skill-table{max-height:min(58vh,620px)}
.npc-charrow{margin-bottom:10px}
.npc-charrow .charbox{min-width:110px}
/* Attributes tab: reuses the .charrow (6-up) + .charbox/.cbrk treatment. */
.npc-attrrow{margin-bottom:10px}
/* Larger, tighter per-source breakdown for the attribute tiles — capped width so
   the tile doesn't grow (scoped; never touches the shared .cbrk in chargen). */
.npc-attrrow .cbrk{font-size:10px;gap:1px 6px;max-width:104px}
.npc-attr-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:9px}
.npc-attr-card{border:1px solid var(--line);background:var(--panel2);padding:8px 9px}
.npc-attr-card .fl{margin-bottom:6px}
.npc-attr-card .stepper{width:100%}
.npc-attr-card .stepper input{flex:1;min-width:0;text-align:center}
  .npc-skill-summary{display:flex;gap:12px;flex-wrap:wrap;padding:7px 10px;border:1px solid var(--line);background:var(--panel2);font-size:11px;margin-bottom:9px}
  .npc-description-input{min-height:130px}
.npc-description-preview .rref{min-height:70px;line-height:1.55}
.npc-description-meta{display:flex;gap:6px 14px;flex-wrap:wrap;margin:7px 0;color:var(--dim);font-size:11px}
.npc-description-meta b{color:var(--ink);font-family:var(--label);font-size:9px;letter-spacing:.1em;text-transform:uppercase}
.npc-stat-meta{margin:8px 0 10px;padding:7px 9px;border:1px solid var(--line);background:var(--panel2)}
  .npc-stat-desc{margin:10px 0;line-height:1.55}
  .fullcheck{display:flex;align-items:center;gap:7px;padding-top:21px;font-size:12px;color:var(--ink)}
  .npc-picker-grid{display:grid;grid-template-columns:minmax(260px,.72fr) minmax(0,1.35fr);gap:12px;margin-top:14px}
  .npc-talent-picker{margin-top:18px}
  .npc-picker-filter{width:100%;margin:6px 0 7px}
  .npc-check-list{max-height:330px;overflow:auto;border:1px solid var(--line);background:var(--panel2)}
  .npc-check-row{display:flex;align-items:center;gap:7px;min-height:31px;padding:5px 8px;border-bottom:1px solid var(--line);font-size:12px;cursor:pointer}
  /* The author display:flex rule otherwise overrides the browser's default
     [hidden] rule, making the skill/talent/Force filters look inert. */
  .npc-check-row[hidden]{display:none}
  .npc-check-row:last-child{border-bottom:0}
  .npc-check-row:hover{background:var(--hover)}
  .npc-check-row input{width:auto;margin:0}
  .npc-check-row>span:first-of-type{flex:1;min-width:0}
  .npc-check-row small{color:var(--dim);font-size:10px}
  .npc-force-locked{margin-top:18px;padding:10px;border:1px dashed var(--line);color:var(--dim);font-size:11px}
.npc-force-controls{display:flex;gap:7px;align-items:center;margin:7px 0}
.npc-force-controls select{min-width:160px}
.npc-force-controls .npc-picker-filter{margin:0}
.npc-attribute-table{margin-top:10px;border:1px solid var(--line);background:var(--panel2);overflow:auto}
.npc-attribute-row{display:grid;grid-template-columns:minmax(125px,1.4fr) repeat(4,minmax(52px,.65fr)) minmax(94px,1fr) minmax(55px,.6fr);align-items:center;gap:5px;min-width:600px;padding:7px 9px;border-bottom:1px solid var(--line);font-size:12px;text-align:center}
.npc-attribute-row:last-child{border-bottom:0}
.npc-attribute-label{text-align:left;font-weight:600}
.npc-attribute-head{background:var(--panel);color:var(--holo);font-family:var(--label);font-size:9px;letter-spacing:.1em;text-transform:uppercase}
.npc-attribute-head span:first-child{text-align:left}
.npc-special-grid,.npc-custom-armor{display:grid;grid-template-columns:minmax(220px,.7fr) minmax(0,1.3fr);gap:14px;margin-top:12px}
.npc-ability-list{display:grid;gap:4px;max-height:260px;overflow:auto;padding:6px;border:1px solid var(--line);background:var(--panel2)}
.npc-ability-choice{display:block;width:100%;text-align:left;border:1px solid var(--line);border-left:3px solid var(--holo-dim);background:var(--panel);color:var(--ink);padding:7px 8px;cursor:pointer}
.npc-ability-choice:hover,.npc-ability-choice.active{background:var(--hover);border-color:var(--holo)}
.npc-ability-preview{min-height:88px;line-height:1.55}
.npc-talent-add{display:flex;gap:7px;align-items:center;flex-wrap:wrap;margin:12px 0 7px}
.npc-talent-add select{min-width:220px}
.npc-talent-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px}
.npc-talent-chips .lkchip{display:inline-flex;align-items:center;gap:4px}
.npc-talent-row{padding:7px 0;border-bottom:1px solid var(--line);line-height:1.55}
.npc-talent-row:last-child{border-bottom:0}
.npc-owned-equipment{margin-top:10px}
.npc-owned-equipment .initrow{margin-bottom:5px}
  @media(max-width:700px){.npc-library{grid-template-columns:1fr}.npc-cards{max-height:260px}.npc-stat .chargrid{grid-template-columns:repeat(3,1fr)}.npc-picker-grid,.npc-special-grid,.npc-custom-armor{grid-template-columns:minmax(0,1fr)}.npc-picker-grid>section,.npc-special-grid>section,.npc-custom-armor>section{min-width:0}.npc-force-controls{align-items:stretch;flex-direction:column}}
@media(max-width:860px){
  /* npc-workspace is a plain .cgwrap, so it inherits the shared off-canvas
     step-drawer (cgrail/cgscrim/cgsteps-close) like every other rail workspace —
     no bespoke horizontal-scroll strip. */
  .npc-attr-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
}
.badge{display:inline-block; font-family:var(--label); font-size:9px; letter-spacing:.18em; text-transform:uppercase;
  border:1px solid var(--line); color:var(--dim); padding:2px 6px; margin:2px 4px 0 0}
.badge.red{color:var(--red); border-color:var(--red-dim)}
/* Restricted marker — red outline, red text, no fill, used everywhere an item
   is legally restricted: equipment/attachment/weapon lists + detail, and the
   crafting template header/preview. Shared so all "restricted" reads identically. */
.badge.rst{color:var(--red); border-color:var(--red)}
/* Droid statblock characteristic chips (crafting droid chassis/preview) */
.dchars{display:flex; gap:6px; flex-wrap:wrap}
.dchar{display:flex; flex-direction:column; align-items:center; min-width:38px;
  border:1px solid var(--line); border-radius:4px; padding:3px 6px; background:rgba(127,127,127,.08)}
.dchar-v{font-family:var(--label); font-size:15px; font-weight:600; color:var(--acc)}
.dchar-l{font-family:var(--label); font-size:8px; letter-spacing:.12em; text-transform:uppercase; color:var(--dim)}
.badge.amb{color:var(--amber); border-color:var(--amber-dim)}
.badge.grn{color:var(--green); border-color:var(--green-dim)}
.badge.mount{color:var(--holo); border-color:var(--holo-dim)}
.badge.cyn{color:var(--holo); border-color:var(--holo-dim)}
.reflink{color:var(--holo); cursor:pointer; text-decoration:underline; text-decoration-color:var(--holo-dim); text-underline-offset:3px}
.reflink:hover{color:var(--holo-bright)}
/* Universal codex link — any game concept (talent/skill/quality/species
   ability/etc.) rendered inline, plus rule references from any surface
   outside the Rules Reference. Dotted underline distinguishes it from
   .reflink (in-app navigation) — clicking opens a quick-view popover in
   place via App.cxQuick (entities) or App.ruleQuick (rules, built by
   ruleLink in core.js), not a page navigation. */
.cxlink{cursor:pointer; text-decoration:underline dotted; text-decoration-color:var(--holo-dim); text-underline-offset:2px}
.cxlink:hover{color:var(--holo); text-decoration-color:var(--holo)}
/* inside the light talent/power tree cards, keep the dark card-stock ink */
.tbody .cxlink,.thead .cxlink{color:inherit; text-decoration-color:rgba(16,20,24,.4)}
.tbody .cxlink:hover,.thead .cxlink:hover{color:#101418; text-decoration-color:#101418}
/* ---------- editor modal ---------- */
#modal{position:fixed; inset:0; background:var(--modal-scrim); z-index:80; display:none; align-items:flex-start; justify-content:center; overflow-y:auto; padding:4vh 12px}
#modal.open{display:flex}
#modalBox{width:min(680px,100%); background:var(--panel); border:1px solid var(--holo-dim); padding:calc(18px*var(--dsp)) calc(20px*var(--dsp)) calc(22px*var(--dsp)); position:relative; margin-bottom:6vh;
  box-shadow:0 0 40px var(--modal-glow)}
.formgrid{display:grid; grid-template-columns:1fr 1fr; gap:0 calc(16px*var(--dsp))}
.formgrid>div{min-width:0}
.formgrid input,.formgrid select,.formgrid textarea{max-width:100%}
.card input,.card select,.card textarea{max-width:100%}
.formgrid .full{grid-column:1/-1}
.modalActions{display:flex; gap:8px; margin-top:18px; flex-wrap:wrap}
.modalActions .spacer{flex:1}
/* ---------- data editor: the M5d grid editors ----------
   A four-column authoring lattice mirroring the talent-tree layout a player
   sees, so a spanned node occupies the same width here as it does there —
   cells set grid-column:span N themselves. Stays four columns at every
   breakpoint: the 4×N grid is the game construct being authored, not a
   responsive layout choice. */
.degrid{display:grid; grid-template-columns:repeat(4,1fr); gap:calc(6px*var(--dsp)); align-items:stretch; min-width:560px}
.degrid-rowlabel{grid-column:1/-1; font-size:9px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--dim); border-bottom:1px solid var(--line); padding:calc(6px*var(--dsp)) 0 2px; margin-top:calc(4px*var(--dsp))}
.degrid-cell{border:1px solid var(--line); background:var(--panel2); padding:calc(5px*var(--dsp)); min-width:0}
.degrid-cell input,.degrid-cell select{max-width:100%}
/* ---------- dashboard ---------- */
.tilegrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:calc(10px*var(--dsp))}
.tile{background:var(--panel2); border:1px solid var(--line); padding:calc(12px*var(--dsp)) calc(12px*var(--dsp)) calc(10px*var(--dsp)); cursor:pointer; text-align:left;
  clip-path:polygon(0 0, calc(100% - 12px) 0, 100% 12px, 100% 100%, 0 100%); transition:background .12s}
.tile:hover{background:var(--tile-hover); border-color:var(--holo-dim)}
.tile .tname{font-family:var(--disp); font-size:10px; letter-spacing:.14em; color:var(--holo); text-transform:uppercase}
.tile .tcount{font-size:22px; font-weight:600; color:var(--ink); margin-top:6px}
.tile .tsub{font-size:9.5px; color:var(--faint); letter-spacing:.12em; text-transform:uppercase}
.statgrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:calc(10px*var(--dsp))}
.ptitle.sub{font-size:9px}
.kv{font-size:12.5px; line-height:1.7}
.kv.sm{font-size:12px}
.badge.xs{font-size:8px}
.kv b{color:var(--holo); font-weight:500; letter-spacing:.08em}
.alertline{font-size:12px; padding:6px 10px; border-left:3px solid var(--amber); background:var(--amber-wash); margin:4px 0; color:var(--amber)}
.alertline.red{border-color:var(--red); background:var(--red-wash); color:var(--red)}
.alertline.grn{border-color:var(--green); background:var(--green-wash); color:var(--green)}
.alertline[onclick]{cursor:pointer}
.panel.combat{border-color:var(--red); background:var(--red-wash)}
.ptitle.red{color:var(--red)}
.dashqa{margin:0 0 calc(14px*var(--dsp))}
.dashfight{display:flex; align-items:center; gap:calc(10px*var(--dsp)); flex-wrap:wrap; padding:calc(4px*var(--dsp)) 0}
/* ---------- dice ---------- */
.dicegrid{display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:10px}
.diebox{border:1px solid var(--line); padding:10px; text-align:center; background:var(--panel2)}
.diebox .dname{font-size:9.5px; letter-spacing:.2em; text-transform:uppercase; margin-bottom:6px}
/* A bordered, boxed group (not just a bare row of controls with a gap) so
   the −/value/+ relationship reads at a glance even when several steppers
   sit close together (e.g. Qty and +HP side by side on an equipment row). */
.stepper{display:flex; align-items:stretch; justify-content:center; gap:0; border:1px solid var(--line); border-radius:4px; background:var(--input-bg); overflow:hidden}
.stepper button{width:28px; height:28px; font-size:16px; cursor:pointer; background:var(--btn-bg); border:none; color:var(--ink)}
.stepper .cnt{font-size:20px; font-weight:600; width:34px; text-align:center; display:flex; align-items:center; justify-content:center; border-left:1px solid var(--line); border-right:1px solid var(--line)}
.bigpct{font-family:var(--disp); font-size:42px; color:var(--holo); text-shadow:0 0 12px var(--glow)}
.hbar{height:14px; background:var(--input-bg); border:1px solid var(--line); position:relative; margin:3px 0}
.hbar>span{position:absolute; inset:0; background:linear-gradient(90deg,var(--holo-dim),var(--holo)); opacity:.7}
.hrow{display:grid; grid-template-columns:52px 1fr 52px; gap:8px; align-items:center; font-size:11px; color:var(--dim)}
/* ---------- initiative ---------- */
.initrow{display:flex; align-items:center; gap:10px; padding:8px 10px; border:1px solid var(--line); border-left:3px solid var(--holo-dim); margin-bottom:6px; background:var(--panel2)}
.initrow.npc{border-left-color:var(--red)}
.initrow.acted{opacity:.45}
.initrow .iname{flex:1; font-weight:600}
/* Party Inventory rows carry a full item stat line, so the name block must be
   allowed to shrink and the holder select must not eat the row. */
/* Encounter run rows: expand caret, always-visible weapon line, soak chip,
   clearer defeat toggle. */
.encexp{min-width:26px;padding:2px 4px;font-size:13px}
.encexp-sp{display:inline-block;visibility:hidden}
.encsoak{border:1px solid var(--line);padding:1px 5px;white-space:nowrap}
.encweap{font-size:12px;margin-top:4px;color:var(--dim)}
.encweap .fchip{margin-right:6px}
.encvehextra{margin:0 0 6px 8px}
.encattchip{white-space:nowrap}
.encdefeat{white-space:nowrap}
/* run-row condition toggles: dimmed when off, the amber plate (.btn.amb's
   tokens) when on — never a hardcoded ink */
.lkchip.enccond{cursor:pointer;opacity:.55}
.lkchip.enccond.on{opacity:1;background:linear-gradient(180deg,var(--amber-dim),var(--amber-bg));border-color:var(--amber);color:var(--on-amber)}
/* fixed-width group-mode / side selects so builder rows line up whether or not
   a row has one (non-minion / non-vehicle rows get an equal-width spacer) */
.encmode{width:130px;flex:0 0 130px}
.encmode-sp{display:inline-block;visibility:hidden}
/* stepper value box must fit "value/max" like "20/50" without clipping —
   auto width + smaller font, and let the group overflow so nothing is cut */
.initrow .stepper{overflow:visible}
.initrow .stepper .cnt{width:auto;min-width:52px;padding:0 8px;font-size:15px;box-sizing:border-box}
/* Encounter environment reminders — chips in the builder, a bar in the run. */
.envchip{font-size:11px;padding:3px 6px;border:1px solid var(--line);border-left:2px solid var(--amber);background:var(--panel2);margin-bottom:4px}
.envchip b{color:var(--amber);letter-spacing:.08em;font-size:9.5px;margin-right:4px}
.envbar{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}
.envbar .envchip{margin-bottom:0}
/* Party Roster aggregates: group vitals, and the Obligation/Duty roll-up. */
.vitstrip{border:1px solid var(--line);background:var(--panel2);margin-top:6px}
.vitrow{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:6px 10px;border-bottom:1px solid var(--line);font-size:12px}
.vitrow:last-child{border-bottom:none}
.vitrow .vitname{flex:1;min-width:120px;font-weight:600}
.vitrow .vitmeta{margin:0}
.vittrack{display:inline-flex;align-items:center;gap:5px}
.vittrack .vitlbl{color:var(--dim);font-size:10px;letter-spacing:.1em;width:12px}
.vittrack .vitmax{color:var(--faint)}
/* a triggered Obligation/Duty shifts the threshold this session (advisory) */
.vittrack .vitmod{font-size:10px;font-weight:600;margin-left:-2px}
.vittrack .vitmod.neg{color:var(--red)}
.vittrack .vitmod.pos{color:var(--green)}
.obwarn{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:4px 0}
/* Group Vitals header: the recovery actions sit right, wrapping under on a phone */
.pty-vitals-h{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.pty-vitals-acts{margin-left:auto;display:inline-flex;gap:4px;flex-wrap:wrap}
.pty-rec-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:5px 0;border-bottom:1px solid var(--line)}
.pty-rec-row:last-child{border-bottom:none}
.pty-rec-name{flex:1 1 160px;font-size:13px}
.pty-rec-res{min-width:0}
.pty-crew-stats{font-size:11px;color:var(--dim);margin-top:3px;line-height:1.5}
.vittrack b.over{color:var(--red)}
.vitline{display:flex;align-items:center;gap:6px;font-size:12px;margin:3px 0}
.vitline .vitlbl{width:54px;color:var(--dim);font-size:10px;letter-spacing:.1em;text-transform:uppercase}
.vitline .vitmax{color:var(--faint)}
.vbar{display:inline-block;width:70px;height:5px;background:var(--bg);border:1px solid var(--line)}
.vbar i{display:block;height:100%}
.vbar i.w{background:var(--red)}
.vbar i.s{background:var(--amber)}
.obgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:14px;margin-top:8px}
.obcol{border:1px solid var(--line);border-left:3px solid var(--holo-dim);padding:8px 10px;background:var(--panel2)}
.obrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;padding:3px 0;border-bottom:1px solid var(--line)}
.obrow:last-child{border-bottom:none}
.obrow .obwho{flex:1;min-width:100px}
.obrow .obval{font-weight:600;min-width:28px;text-align:right}
.obrow.obtot{border-top:1px solid var(--holo-dim);color:var(--holo);font-weight:600}
/* Reputation tier beside the ladder stepper — sign carries the colour. */
.rep-tier{font-size:12px;font-weight:600}
.rep-tier.pos{color:var(--green)}
.rep-tier.neg{color:var(--red)}
/* Session scenes are an ORDERED sequence (hence the ↑↓ controls), so they
   stack full-width instead of sitting in the usual card grid. A run scene
   dims like a used initiative slot. */
.scenelist{display:block}
.scenecard{margin-bottom:8px}
.scenecard.done{opacity:.55}
.scenecard textarea{width:100%}
.scene-head{gap:6px}
.scene-head .scene-title{flex:1;min-width:120px}
.scene-head .scene-kind{max-width:130px}
.scene-ctl{display:inline-flex;gap:4px;align-items:center}
.scene-foot{gap:6px}
.scene-foot .scene-enc{max-width:200px}
.scene-foot .scene-enc.quiet{font-size:11px; color:var(--dim)}
.sess-enc{display:inline-flex; gap:4px; align-items:center}
.sess-result{margin:2px 0}
.scene-cite{display:inline-flex; gap:4px; align-items:center}
.sess-vocab-row{display:flex; gap:6px; align-items:center; margin:4px 0}
.sess-vocab-row input{flex:1 1 auto; min-width:0}
.sess-vocab-row .smallnote{white-space:nowrap; margin:0}
.sess-vocab-sw{display:inline-block; width:14px; height:3px; flex:0 0 auto}
.sess-note h3{font-size:13px}
.sess-edhead{margin-bottom:calc(6px*var(--dsp))}
/* These Session Planner toolbars grow the spacer (flex:1) rather than rely on the
   global .spacer auto-margin fallback appended at the end of this file */
.sess-edhead .spacer,.sess-edtools .spacer,.sess-tlbar .spacer,.scene-foot .spacer{flex:1 1 auto}
.sess-stamp{font-size:11px; cursor:pointer; display:inline-flex; gap:6px; align-items:center; margin:0}
.sess-tlbar{border:1px solid var(--line); border-radius:6px; padding:6px 10px}
.sess-tloff{border-left:3px solid var(--amber); background:var(--amber-wash)}
.sess-tlnow{min-width:0; font-size:12px; color:var(--ink)}
.sess-startrolls{display:flex; flex-direction:column; gap:6px; margin:6px 0 12px}
.sess-startroll{display:flex; flex-wrap:wrap; gap:6px; align-items:center}
/* Branch paths: the leads-to row, the current-scene accent, and the flow map. */
.scene-branch{gap:6px;align-items:center;border-top:1px dashed var(--line);padding-top:6px}
.scene-branch-chip small{color:var(--faint)}
.branch-lbl{max-width:130px;font-size:11px;padding:2px 6px}
.scenecard.scene-current{box-shadow:inset 3px 0 0 var(--amber)}
.scene-flash{animation:sceneFlash 1.1s ease-out}
@keyframes sceneFlash{0%{box-shadow:0 0 0 2px var(--amber)}100%{box-shadow:0 0 0 2px transparent}}
.sessflow{overflow-x:auto;border:1px solid var(--line);background:var(--panel2);border-radius:6px;padding:8px;margin-top:8px}
.sflow-node{cursor:pointer}
.sflow-node.done{opacity:.5}
.sflow-node:focus-visible{outline:2px solid var(--amber);outline-offset:2px}
.sessflow-legend{display:flex;flex-wrap:wrap;gap:12px;align-items:center;margin-top:6px}
.sessflow-legend i{display:inline-block;width:14px;height:3px;vertical-align:middle;margin-right:4px}
/* Initiative slots: order number, name, the three result fields, skill,
   roll, claim. Wraps rather than squeezing the number fields to nothing. */
.initslot{flex-wrap:wrap}
.initslot .initord{min-width:20px;text-align:right;color:var(--faint);font-size:11px;letter-spacing:.08em}
.initslot .initres{display:inline-flex;align-items:center;gap:3px;font-size:12px;color:var(--dim)}
/* box-sizing + no spinner: the native number arrows overflowed the 44px box */
.initslot .initres input{width:48px;text-align:center;padding:2px 3px;box-sizing:border-box;-moz-appearance:textfield;appearance:textfield}
.initslot .initres input::-webkit-outer-spin-button,.initslot .initres input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
/* a slot whose assigned actor is down: struck through, skipped this round */
.initslot.down{opacity:.5}
.initslot.down .iname{text-decoration:line-through}
.initslot .initclaim select{max-width:170px}
.initslot select{max-width:140px}
/* Turn timer bar (initPanel, both hosts). The clock flashes when expired —
   the visual alarm that backs up the holo-call chime. */
.inittimer{align-items:center; gap:8px; flex-wrap:wrap}
.inittimer .inittimer-clock{font-size:20px; letter-spacing:.08em; color:var(--holo); min-width:64px; text-align:center; font-variant-numeric:tabular-nums}
.inittimer.expired .inittimer-clock{color:var(--red); animation:timerflash .6s steps(2,end) infinite}
@keyframes timerflash{50%{opacity:.25}}
.invrow{flex-wrap:wrap}
.invrow .iname{flex:1 1 240px; min-width:0}
.invrow select{max-width:200px; flex:0 1 auto}
/* The per-row note takes the whole second line: it is free text, and wedging
   it between the stepper and Deliver would squeeze both on a narrow screen. */
.invrow .invnote{flex:1 0 100%; font-size:11px; padding:3px 6px; background:transparent; border:0; border-top:1px dashed var(--line); border-radius:0}
.invrow .invnote:placeholder-shown{opacity:.5}
.invrow .invnote:focus{opacity:1; background:var(--panel)}
.initrow input[type=checkbox]{width:18px; height:18px; accent-color:var(--holo)}
/* Specializations owned (chargen tabSpecs): two cards per row instead of one
   full-width row each, now that skills render inline with the name/badges
   rather than on their own wrapped line. */
.specgrid{display:grid; grid-template-columns:repeat(2,1fr); gap:8px; margin-bottom:6px}
.specgrid .initrow{margin-bottom:0}
.specskills{display:block; font-weight:400; font-size:13.5px; color:var(--ink); margin-top:3px}
@media (max-width:760px){.specgrid{grid-template-columns:1fr}}
/* ---------- attachment mod slots (EQ24) ---------- */
.modslot{display:inline-flex; align-items:center; gap:6px; border:1px solid var(--line); border-radius:4px; padding:4px 8px; font-size:10.5px; background:var(--panel2)}
/* ---------- map ---------- */
#galaxySvg text{font-family:var(--body)}
.planetdot{cursor:pointer}
.planetdot circle{transition:r .12s}
.planetdot:hover circle{r:7}
/* ---------- gm screen ---------- */
/* Masonry flow (multi-column), NOT a grid: a grid couples every panel in a row
   to the tallest one's height, leaving dead space under short panels. Columns
   pack panels vertically instead, so they reflow to fill space as they grow or
   shrink. `.gm-wide` spans every column (the wide-panel case). */
.gmgrid{columns:420px; column-gap:calc(16px*var(--dsp))}
.gmgrid>.panel{break-inside:avoid; -webkit-column-break-inside:avoid; margin:0 0 calc(16px*var(--dsp))}
.gm-wide{column-span:all}
.gmacts{margin-left:auto; display:flex; gap:4px; text-transform:none; letter-spacing:normal}
.gmbody{min-width:0}
.gmbody.gm-h-s{max-height:240px; overflow-y:auto}
.gmbody.gm-h-m{max-height:420px; overflow-y:auto}
.gmbody.gm-h-l{max-height:640px; overflow-y:auto}
.gmgrid.gmedit .panel>.ptitle{cursor:grab; user-select:none; touch-action:none}
.gmpad{width:100%; min-height:150px; resize:vertical}
.gmgrid .panel.gm-dragging{opacity:.5}
.gmgrid .panel.gm-drop{outline:2px dashed var(--amber); outline-offset:3px}
/* ---------- dataset manager ---------- */
/* Same masonry rationale as .gmgrid above: the view was a single column of
   full-width rows, so every short section left a page-width band of dead
   space. Columns pack the panels vertically instead. .ds-wide spans them. */
.dsgrid{columns:440px; column-gap:calc(16px*var(--dsp))}
.dsgrid>.panel{break-inside:avoid; -webkit-column-break-inside:avoid; margin:0 0 calc(16px*var(--dsp))}
.ds-wide{column-span:all}
.ds-head{margin-bottom:calc(10px*var(--dsp))}
.ds-head .kv{margin-bottom:8px}
/* The one scroll-box class for this view — also listed in KEEP_SCROLL_SEL
   (core.js) so a source toggle's full re-render no longer snaps it to the top. */
.ds-scroll{max-height:340px; overflow:auto; border:1px solid var(--line); border-radius:4px; padding:6px}
.ds-scroll table.qt{border:0}
/* the merge-mode select must not win the .initrow flex negotiation and squeeze
   the pack name into a vertical sliver (the .encmode precedent) */
.dsmode{width:132px; flex:0 0 132px; font-size:10px; color:var(--dim); background:var(--input-bg); border:1px solid var(--holo-dim); border-radius:3px; padding:1px 2px}
.dspack .iname{min-width:0}
.dstog{font-size:10px; color:var(--dim); display:flex; gap:4px; align-items:center; flex:none; white-space:nowrap}
.dstog input[type=checkbox]{accent-color:var(--green)}
.dsfile{flex:1; min-width:180px; padding:6px; border:1px dashed var(--holo-dim); background:var(--input-bg); color:var(--ink)}
.dsstack{padding:2px 8px; border:1px solid var(--holo-dim); border-top:0; font-size:10.5px}
.dsstack:first-of-type{border-top:1px solid var(--holo-dim)}
.dsstack.hi{color:var(--holo)}
/* source selector: grouped by game line, two-up so ~60 books fit the box */
.dssrc-grp{margin-bottom:10px}
.dssrc-grp:last-child{margin-bottom:0}
.dssrc-hd{display:flex; align-items:center; gap:6px; flex-wrap:wrap; font-family:var(--label); font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--holo); border-bottom:1px solid var(--line); padding-bottom:3px; margin-bottom:4px}
.dssrc-cnt{margin-right:auto; color:var(--faint); letter-spacing:normal; text-transform:none; font-family:var(--body); font-size:10.5px}
.dssrc-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); gap:2px 10px}
.dssrc{display:flex; align-items:center; gap:7px; padding:3px 4px; font-size:12px; cursor:pointer}
.dssrc:hover{background:var(--tile-hover)}
.dssrc.off{opacity:.5}
.dssrc input[type=checkbox]{accent-color:var(--green); flex:none}
.dssrc-n{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.dssrc-c{color:var(--faint); font-size:10.5px; flex:none}
.rref{border:1px solid var(--line); background:var(--panel2); padding:calc(10px*var(--dsp)) calc(12px*var(--dsp)); margin-bottom:calc(10px*var(--dsp)); font-size:12.5px; line-height:1.6}
.rref h4{font-family:var(--label); font-size:11px; letter-spacing:.18em; text-transform:uppercase; color:var(--amber); margin-bottom:5px}
table.qt{width:100%; border-collapse:collapse; font-size:12px}
table.qt td,table.qt th{border:1px solid var(--line); padding:calc(4px*var(--dsp)) calc(7px*var(--dsp)); text-align:left}
table.qt th{font-family:var(--label); color:var(--holo); font-weight:500; letter-spacing:.1em; font-size:10px; text-transform:uppercase}
/* Phones: a quick-table wider than the viewport scrolls inside its own
   `.tblscroll` wrapper (see the appended MOBILE SWEEP tail) rather than pushing
   the page sideways. The old `table.qt{display:block;overflow-x:auto}` fallback
   was retired — display:block dropped the shared column grid, so columns lost
   alignment; every wide quick-table now carries a `.tblscroll` parent, and the
   character-sheet tables keep their deliberate `.sheetbody table.qt` data-label
   card reflow further down. */
/* ---------- rules reference (bespoke) ---------- */
.rule-activefilter{font-size:11px; color:var(--dim); letter-spacing:.05em; margin-bottom:10px; display:flex; gap:10px; align-items:center; flex-wrap:wrap}
.rule-strip{display:flex; gap:6px; align-items:center; flex-wrap:wrap; margin-bottom:8px; padding-bottom:8px; border-bottom:1px solid var(--line)}
.rule-strip small{font-family:var(--label); font-size:9px; letter-spacing:.16em; text-transform:uppercase; color:var(--faint); margin-right:4px}
.rule-bulkbar,.hb-bulkbar{display:flex; gap:6px; align-items:center; flex-wrap:wrap; margin-bottom:12px; padding:8px 10px; border:1px solid var(--amber-dim); background:var(--amber-bg)}
.rule-bulkbar span,.hb-bulkbar>span{font-family:var(--label); font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--amber); margin-right:4px}
.rules-acc{display:flex; flex-direction:column; gap:8px}
.acc{border:1px solid var(--line); background:var(--panel2)}
.acc-head{display:flex; align-items:center; gap:10px; padding:0 13px 0 11px; transition:background .12s}
.acc-head:hover{background:var(--hover)}
.acc-sel{width:15px; height:15px; accent-color:var(--holo); cursor:pointer; flex:0 0 auto}
.acc-sel.partial{opacity:.6; outline:1px solid var(--holo-dim); outline-offset:1px}
.acc-toggle{flex:1; display:flex; align-items:center; gap:10px; cursor:pointer; text-align:left;
  background:transparent; border:none; padding:11px 0; color:var(--holo);
  font-family:var(--label); font-size:12px; letter-spacing:.14em; text-transform:uppercase}
.acc-toggle .acc-name{flex:1}
.acc-selcount{font-size:9px; letter-spacing:.14em; color:var(--amber)}
.acc-chev{display:inline-block; transition:transform .15s; color:var(--faint); font-size:11px}
.acc-head.open .acc-chev{transform:rotate(90deg); color:var(--holo)}
.acc-body{padding:calc(4px*var(--dsp)) calc(12px*var(--dsp)) calc(12px*var(--dsp)); display:flex; flex-direction:column; gap:calc(10px*var(--dsp))}
.rule-item{border:1px solid var(--line); border-left:3px solid var(--holo-dim); background:var(--panel); padding:calc(10px*var(--dsp)) calc(12px*var(--dsp)); transition:border-color .12s}
.rule-item.pinned{border-left-color:var(--amber)}
.rule-item.is-hidden{opacity:.55}
.rule-item.rule-flash{border-color:var(--holo); box-shadow:0 0 14px var(--glow)}
/* App.cgGoto() lands here after a "Configure…" talent-card action jumps
   the player to the control that actually makes the choice. */
.cg-focus{outline:2px solid var(--holo)!important; box-shadow:0 0 14px var(--glow)!important; border-radius:2px}
.rule-top{display:flex; align-items:center; gap:8px; margin-bottom:6px}
.rule-sel{width:15px; height:15px; accent-color:var(--holo); flex:0 0 auto; cursor:pointer}
.rule-title{flex:1; font-size:14px; font-weight:600; letter-spacing:.04em; color:var(--ink); cursor:pointer; display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.rule-title:hover{color:var(--holo-bright)}
.rule-star{color:var(--amber); font-size:12px}
.rule-actions{display:flex; gap:5px; flex-wrap:wrap; flex:0 0 auto}
.rule-item .rref{margin-bottom:6px}
.rule-item .meta{font-size:10.5px; color:var(--faint); margin-top:4px; line-height:1.6}
.rule-tags{display:flex; gap:5px; flex-wrap:wrap; margin-top:7px}
.rule-tags .fchip{cursor:pointer}
.rule-tags .fchip:hover{border-color:var(--holo-dim); color:var(--holo)}
.rule-links{display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-top:7px}
.rule-links small{font-family:var(--label); font-size:8.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--faint)}
/* rules editor (wide, document-style) */
#modalBox.wide{width:min(960px,100%)}
/* compact dialogs (Award XP) — just wide enough for the title line */
#modalBox.narrow{width:min(380px,100%)}
#modalBox.wide textarea{min-height:300px}
.redit{display:grid; grid-template-columns:1fr 340px; gap:18px; align-items:start}
.redit-main{display:flex; flex-direction:column; gap:4px; min-width:0}
.redit-main input,.redit-main select,.redit-main textarea{width:100%; max-width:100%}
.redit-main .fl{margin-top:8px}
.redit-row{display:grid; grid-template-columns:1fr 1fr 80px; gap:10px}
.redit-flags{display:flex; gap:16px; flex-wrap:wrap; margin:10px 0 2px}
.redit-flags label{display:flex; gap:6px; align-items:center; font-size:12px; color:var(--dim); cursor:pointer}
.redit-flags input{width:16px; height:16px; accent-color:var(--holo)}
.redit-side{position:sticky; top:0}
.redit-side .rref{max-height:60vh; overflow:auto}
.rtok{display:none; font-size:11px; line-height:1.9; color:var(--dim); border:1px solid var(--line); background:var(--panel2); padding:8px 10px; margin-top:6px}
.rtok.open{display:block}
.rtok b{color:var(--holo); font-weight:600}
.rtok-toggle{margin-top:6px; align-self:flex-start}
/* ---------- misc ---------- */
.empty{color:var(--faint); font-size:12.5px; padding:20px; text-align:center; border:1px dashed var(--line); letter-spacing:.08em}
.smallnote{font-size:10.5px; color:var(--faint); line-height:1.6; margin-top:8px}
/* Collapsible "How to use" disclosure for the reSpecialized optional rules
   (rmHelp(), js/tools.js). Themed only through tokens. */
.rmhelp{margin:6px 0 2px}
.rmhelp>summary{cursor:pointer; list-style:none; display:inline-flex; align-items:center; gap:5px;
  font-family:var(--label); font-size:10px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--holo); user-select:none}
.rmhelp>summary::-webkit-details-marker{display:none}
.rmhelp>summary::before{content:"▸"; font-size:9px; transition:transform .12s}
.rmhelp[open]>summary::before{transform:rotate(90deg)}
.rmhelp>summary:hover{filter:brightness(1.15)}
.rmhelp-body{font-size:11px; color:var(--dim); line-height:1.65; margin:6px 0 2px; padding:8px 10px;
  border-left:2px solid var(--line); background:var(--input-bg)}
.rmhelp-body ol,.rmhelp-body ul{margin:4px 0 4px; padding-left:18px}
.rmhelp-body li{margin:2px 0}
.rmhelp-body b{color:var(--faint)}
.genout{background:var(--input-bg); border:1px solid var(--line); padding:12px; min-height:52px; font-size:14px; line-height:1.7; margin-top:8px; white-space:pre-wrap}
.genintro{margin:-4px 0 calc(14px*var(--dsp))}
.gen-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:calc(10px*var(--dsp));margin-bottom:calc(14px*var(--dsp))}
.gen-group{background:var(--input-bg);border:1px solid var(--line);padding:calc(10px*var(--dsp))}
.gen-group h3{margin:0 0 calc(8px*var(--dsp));font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--dim)}
.gen-group .toolbar{gap:6px;margin:0}
.translator-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:calc(10px*var(--dsp));margin-bottom:calc(14px*var(--dsp))}
.translator-card{background:var(--input-bg);border:1px solid var(--line);padding:calc(12px*var(--dsp))}
.translator-card h3{margin:0 0 4px;font-size:13px;color:var(--holo)}
.translator-card textarea{display:block;width:100%;min-height:92px;resize:vertical;margin:10px 0 8px}
.transout{min-height:72px;padding:10px;border:1px solid var(--line);background:var(--panel);white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.65}
.aurebesh-out{font-family:'ft-aurebesh',sans-serif;font-size:25px;line-height:1.45;letter-spacing:.05em}
.translator-card .toolbar{margin:8px 0 0}

/* ---------- characters management (roster) ---------- */
.chmgr{display:flex;gap:calc(14px*var(--dsp));align-items:flex-start}
.chmgr-main{flex:1;min-width:0}
.chmgr-btns{flex:0 0 132px;display:flex;flex-direction:column;gap:calc(6px*var(--dsp));
  position:sticky;top:calc(var(--hdr-h,52px) + 10px)}
.chmgr-btns .btn{width:100%}
.chmgr-sep{height:1px;background:var(--line);margin:calc(6px*var(--dsp)) 0}
.chlist{border:1px solid var(--line);background:var(--panel2);margin-bottom:calc(12px*var(--dsp));
  max-height:340px;overflow-y:auto}
/* Every track is fixed or fr (NO content-sized auto/max-content): each .chrow is
   its own grid, so a content-sized column would resolve to a different width per
   row and shift every other column. Fixed 200px status track keeps the header and
   all rows column-aligned regardless of name length or badge content — and the
   badges WRAP inside it (Party Sync added a third badge, and a nowrap cell
   overflowing the track gave the whole list a horizontal scrollbar). */
.chrow{display:grid;grid-template-columns:minmax(140px,1.2fr) 1.3fr .8fr .7fr 200px;gap:10px;align-items:center;
  padding:calc(6px*var(--dsp)) 12px;cursor:pointer;border-bottom:1px solid var(--line);font-size:12.5px}
.chrow:last-child{border-bottom:none}
.chrow:hover{background:var(--pick-on)}
.chrow.sel{background:var(--pick-on);box-shadow:inset 3px 0 0 var(--holo)}
.chrow .chnm{color:var(--holo);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chrow .chsub,.chrow .chcamp{color:var(--dim);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.chrow .chstat{display:flex;flex-wrap:wrap;gap:3px 4px;align-items:center;min-width:0;white-space:normal}
.chrow .chstat .badge{white-space:nowrap}
.chxp.over,.chfact .over{color:var(--red)}
.chrow.chlist-head,.chrow.chlist-head:hover{cursor:default;background:var(--panel);color:var(--faint);
  font-size:10px;letter-spacing:.14em;text-transform:uppercase;position:sticky;top:0}
.chrow.chlist-head span{color:var(--faint)}
/* Crafting's project list rides the same row shell with four cells, not five */
.chrow.crrow{grid-template-columns:minmax(140px,1.3fr) 1.3fr .8fr 200px}
.chprev{display:flex;gap:calc(14px*var(--dsp));border:1px solid var(--line);background:var(--panel2);
  padding:calc(12px*var(--dsp))}
.chprev-img{width:120px;height:150px;object-fit:cover;object-position:top;
  border:1px solid var(--holo-dim);background:var(--panel);flex:0 0 auto}
.chprev-facts{flex:1;min-width:0;display:flex;flex-direction:column;gap:calc(4px*var(--dsp));font-size:12.5px}
.chfact{display:flex;gap:10px}
.chfact-l{flex:0 0 118px;color:var(--faint);font-size:10px;letter-spacing:.12em;text-transform:uppercase;padding-top:2px}
.cghdr-actions{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap}
@media(max-width:720px){
  .chmgr{flex-direction:column}
  .chmgr-btns{position:static;flex:1 1 auto;width:100%;flex-direction:row;flex-wrap:wrap}
  .chmgr-btns .btn{width:auto;flex:1 1 auto}
  .chmgr-sep{display:none}
  /* fr + fixed only (never auto) so the columns stay aligned across rows here too */
  .chrow{grid-template-columns:minmax(0,1fr) minmax(56px,.5fr) 116px}
  .chrow .chsub,.chrow .chcamp{display:none}
  .chrow.crrow{grid-template-columns:minmax(0,1fr) 150px}
  .chrow.crrow .chstat .badge{white-space:normal}
  /* the crafting landing has no wide preview pane to force full width (the
     roster's .chprev does), so stretch the column and stop the grow that
     stretched the Open/Duplicate/Delete row into tall boxes */
  .chmgr.crmgr{align-items:stretch}
  .chmgr.crmgr .chmgr-main,.chmgr.crmgr .chmgr-btns{flex:0 0 auto}
  .chprev{flex-direction:column}
  .chprev-img{width:100%;height:auto;max-height:220px;object-fit:contain}
  .chfact{flex-wrap:wrap}
  .chfact-l{flex-basis:100%}
}

/* ---------- vehicles ---------- */
/* The Vehicles section reuses the character-manager (.chmgr/.chrow/.chprev)
   and workspace (.cgwrap/.cgsteps) shells wholesale. Vehicle art is landscape
   (ship renders / silhouettes), unlike 3:4 character portraits — these
   overrides relax the portrait aspect the shared classes impose. */
.ovpimg.vpimg{aspect-ratio:4/3;object-fit:contain} /* .ovpimg's 3/4 is later in this file — win by specificity */
.chprev-img.vprev-img{width:150px;height:112px;object-fit:contain}
.vstats td:first-child{color:var(--faint);font-size:10px;letter-spacing:.12em;text-transform:uppercase;width:210px}
/* facts table (Sensor Range downward) as two side-by-side tables instead of
   one narrow-content, full-width table — the long single column left a lot
   of dead space beside short values. */
.vfacts2{display:grid;grid-template-columns:1fr 1fr;gap:0 14px;align-items:start}
.vfacts2 .vstats td:first-child{width:140px}
@media (max-width:700px){.vfacts2{grid-template-columns:1fr}}
.vmods{margin:4px 0 0 18px;font-size:12.5px;line-height:1.6}
.vmods li{margin-bottom:3px}
/* V6 Gameplay "Current Condition" — live changes vs the profile, with cause */
.vcondlist{margin:0;padding-left:18px;font-size:12.5px;line-height:1.6}
.vcondlist li{margin-bottom:2px}
/* V6: the vehicle Weapons table, weapon picker and crit table ride the
   chargen .eqtbl phone reflow (stacked cards, actions always reachable).
   They are also table.qt, whose cell border/padding outranks .eqtbl's — undo
   it inside the reflow only. */
@media(max-width:640px){
  table.qt.eqtbl td{border:0;padding:2px 0}
  table.qt.eqtbl td[data-label="No."]{display:none}
}

/* ---------- chargen ---------- */
/* chargen workspace: vertical step nav beside the content pane. On mobile the
   steps collapse to a thin always-visible rail whose centered tab flies the
   full list out over the content (App.cgNavToggle / .cgwrap.navopen). */
.cgwrap{display:flex;gap:calc(14px*var(--dsp));align-items:flex-start}
.cgrail{display:none}
.cgscrim{display:none}
.cgsteps{flex:0 0 200px;display:flex;flex-direction:column;gap:2px;
  position:sticky;top:calc(var(--hdr-h,52px) + 10px)}
.cgsteps-close{display:none}
.cgmain{flex:1;min-width:0}
.cgtab{cursor:pointer;background:transparent;border:1px solid var(--line);color:var(--dim);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;padding:7px 12px;display:flex;align-items:center;gap:5px;text-align:left}
.cgtab.on{background:var(--pick-on);color:var(--holo);border-color:var(--holo-dim);box-shadow:inset 2px 0 0 var(--holo)}
.cgtab .stepno{opacity:.55}
.cgtab .tabstat{margin:0}
.cgtab .tabstat.done{color:var(--green)}
.cgtab .tabstat.partial,.cgtab .tabstat.todo{color:var(--amber)}
.cgtab .tabstat.warn{color:var(--red)}
.cgsummary{display:flex;gap:14px;flex-wrap:wrap;align-items:center;font-size:11.5px;padding:7px 12px;border:1px solid var(--line);background:var(--panel2);margin-bottom:10px}
.cgsum-chip{padding:2px 8px;border:1px solid var(--line);letter-spacing:.08em;text-transform:uppercase;font-size:10px}
.cgsum-chip.warn{cursor:pointer;color:var(--amber);border-color:var(--amber-dim)}
.cgsum-chip.ok{color:var(--green);border-color:var(--green-dim)}
.cgfiltbar{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px;align-items:center}
.cgfilt{cursor:pointer;background:transparent;border:1px solid var(--line);color:var(--dim);font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:5px 10px}
.cgfilt.on{background:var(--pick-on);color:var(--holo);border-color:var(--holo-dim)}
.cgfiltsep{width:1px;align-self:stretch;background:var(--line);margin:0 6px}
/* a specialization tree shown before it is purchased: view-only preview */
.tree.preview-locked .tnode{opacity:.5;cursor:default;pointer-events:none}
.xpbar{display:flex;gap:16px;flex-wrap:wrap;font-size:12px;align-items:center;padding:8px 12px;border:1px solid var(--line);background:var(--panel2);margin-bottom:12px}
.xpbar b{color:var(--amber);font-weight:600}
.xpbar-link{cursor:pointer}
.xpbar-link:hover{color:var(--holo)}
.xpbar .ok{color:var(--green)} .xpbar .over{color:var(--red)}
.pickgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:8px;max-height:380px;overflow-y:auto;padding:2px}
.pick{border:1px solid var(--line);background:var(--panel2);padding:8px 10px;cursor:pointer;font-size:12.5px}
.pick:hover{border-color:var(--holo-dim)}
.pick.on{border-color:var(--holo);background:var(--pick-on);box-shadow:inset 3px 0 0 var(--holo)}
.pick small{display:block;color:var(--faint);font-size:10px;margin-top:2px}
/* .pick as a real <button> (Data Editor rows) — reset only what the UA sets:
   NO display declaration here. button.pick (0,1,1) would beat .de-row (0,1,0),
   so declaring display:block would kill the row's flex layout; grid items
   blockify on their own inside .pickgrid. */
button.pick{font-family:inherit;text-align:left;width:100%;color:inherit}
/* Combined-mode ruleset attribution — rides the browse tile's metadata line
   (inside the block `small` above), so a long label can never make one tile
   taller than its neighbours. */
.rschip{display:inline-block;margin:0 0 0 2px;font-size:9px;letter-spacing:.05em;text-transform:uppercase;
  color:var(--dim);border:1px solid var(--line);padding:0 3px;vertical-align:baseline;white-space:nowrap}
.charbox{border:1px solid var(--line);background:var(--panel2);text-align:center;padding:10px 6px}
.charbox .cv{font-family:var(--disp);font-size:30px;color:var(--holo)}
.charbox .cn{font-size:10.5px;letter-spacing:.2em;color:var(--dim);text-transform:uppercase}
/* Critical-Injury-reduced characteristic: red alert state (OggDude's red
   injury triangle equivalent) */
.charbox.hurt{border-color:var(--red-dim);box-shadow:inset 0 0 0 1px var(--red-dim)}
.charbox.hurt .cv{color:var(--red)}
/* (The main characteristics-tab tiles now use .chartile, defined in the
   appended layer at the end of this file; .charbox/.charrow here still style
   the species preview tiles and, scoped under .cghud, the bottom HUD hexes.)
   The compact .cbrk grid below remains the NPC stat-block characteristic
   breakdown (npc-view.js, via cbrkRow). */
.cbrk{display:grid;grid-template-columns:1fr auto;gap:1px 10px;margin:6px auto;max-width:120px;
  font-size:8.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);text-align:left}
.cbrk b{font-family:var(--mono,monospace);font-weight:600;color:var(--amber);text-align:right}
.cbrk b.z{color:var(--faint);opacity:.55;font-weight:400}
.cbrk b.neg{color:var(--red)}
/* chars-tab XP counter line */
.cgxpline{font-size:11px;color:var(--dim);margin-bottom:10px}
.cgxpline b{font-family:var(--disp);font-size:14px;color:var(--amber)}
.cgxpline b.neg{color:var(--red)}
.cgxpline.locked{color:var(--amber);font-size:10px;letter-spacing:.14em;text-transform:uppercase}
/* injury-row characteristic assignment strip */
.cichar{flex-basis:100%;display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.cibadge-char{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--red);
  border:1px solid var(--red-dim);padding:2px 7px;white-space:nowrap}
.cibadge-char.off{color:var(--faint);border-color:var(--line);text-decoration:line-through}
.charrow{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px}
/* Step the tile grid down before the six cards crowd. The left build rail eats
   width, so the six-across layout only holds on a wide pane; drop to 3-across
   on tablets/small laptops and 2-across on phones (the old ~97px three-across
   phone tiles were unusable). */
@media(max-width:1024px){ .charrow{grid-template-columns:repeat(3,minmax(0,1fr))} }
@media(max-width:480px){
  .charrow{grid-template-columns:repeat(2,minmax(0,1fr))}
  .charbox .cv{font-size:24px}
}
.cisec{margin-top:16px; border-top:1px solid var(--line); padding-top:10px}
.cimodbar{display:flex; gap:18px; flex-wrap:wrap; align-items:center; font-size:11px; color:var(--dim); margin:8px 0}
.cimodbar b{font-family:var(--disp); font-size:15px; color:var(--amber)}
.cisev{display:inline-flex; gap:4px; align-items:center; vertical-align:middle}
.cisevtier{font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--dim)}
.cisevpips{display:inline-flex; gap:1px}
.mincharf{display:flex; align-items:center; gap:14px; flex-wrap:wrap; margin:-4px 0 14px}
.mincharf .mcLbl{font-size:9.5px; letter-spacing:.18em; text-transform:uppercase; color:var(--faint); white-space:nowrap}
.mincharf .mcFields{display:flex; gap:8px; flex-wrap:wrap}
.mincharf .mcField{display:flex; flex-direction:column; gap:4px; width:58px}
.mincharf .mcField label{font-size:8.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--faint); text-align:center}
.mincharf .mcField input{text-align:center; padding:6px 4px}
.mincharf .reflink{margin-left:auto; font-size:11px; letter-spacing:.14em; text-transform:uppercase}
/* Trees are canonically 4 columns wide (FFG card-sheet layout) — never reflow
   them; below the min width the .treewrap scrolls horizontally within bounds.
   The padding-left/margin-left pair gives the leftmost column's Force-talent
   starburst (which pokes 14px past the card's left edge) room INSIDE the
   scroll clip box without moving the tree: overflow clips at the padding box,
   and the negative margin pulls the box back so the tree's left edge lands
   exactly where it did before. (Top overhang is already handled by .tree's
   own padding-top.) */
.treewrap{overflow-x:auto;max-width:100%;padding-left:14px;margin-left:-14px}
.tree{display:grid;grid-template-columns:minmax(0,1fr) 14px minmax(0,1fr) 14px minmax(0,1fr) 14px minmax(0,1fr);gap:0;margin-top:8px;--tnode-link:4px;min-width:540px;position:relative}
/* Connector architecture: the .hlink/.vcell divs are invisible GEOMETRY PROBES
   (they hold the gutter columns and the 16px inter-row gap in the grid, and
   carry data-a/data-b + the owned "on" state), but the visible piping is drawn
   by drawTreeLines() (chargen-build.js) into one absolutely-positioned SVG
   overlay per tree (.treelines) — plain <line> elements measured from the real
   card rects AFTER layout, scale-corrected so CSS zoom (Text Size) can't skew
   the math, and re-drawn from a ResizeObserver whenever any card's size
   changes (font load, text rewrap, window resize). The SVG sits BEHIND the
   cards (z-index:0 vs .tnode's 1) and every line end tucks a couple px under
   the opaque card face, so subpixel seams are impossible. Horizontal lines
   ride the banner baseline (FFG card-sheet look), verticals span card-bottom
   to card-top of the joined pair. pointer-events:none keeps card clicks alive. */
.hlink,.vlink{position:relative;z-index:0}
.hlink{align-self:stretch}
.vcell{height:16px;position:relative;z-index:0}
.treelines{position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none;overflow:visible}
.treelines line{stroke:#8b979f;stroke-width:var(--tnode-link)}
.treelines line.on{stroke:var(--green)}
.die{display:inline-block;width:15px;height:15px;margin:0 1px;vertical-align:-2px;border-radius:2px;border:1px solid rgba(0,0,0,.5)}
.die.dp{background:var(--die-p)}.die.da{background:var(--die-a);border-radius:8px 2px 8px 2px}.die.db{background:var(--die-b)}
.die.dd{background:var(--die-d);border-radius:8px 2px 8px 2px}.die.dc{background:var(--die-c)}.die.ds{background:var(--die-s);border-color:var(--die-s-edge)}.die.df{background:var(--die-f)}
.sym{font-weight:700}
.skilltbl td{padding:5px 7px;border-bottom:1px solid var(--line);font-size:12.5px}
.skilltbl th{font-family:var(--label);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--holo);text-align:left;padding:5px 7px;border-bottom:1px solid var(--holo-dim)}
.skillsplit .skilltbl tr.career td:nth-child(2){color:var(--holo)}
/* Skills tab: stacked by DEFAULT (safe first paint / phone / large text); the
   right-hand detail column is enabled by the JS-measured `.sideby` class only
   when the container is wide enough for both (cgSkillLayout, ÷ text-size zoom).
   table-layout:fixed is scoped to .skillsplit so it doesn't affect tabSheet()'s
   own reuse of .skilltbl (no header row, no explicit widths there). */
.skillsplit{display:grid;grid-template-columns:1fr;gap:10px;align-items:start;max-width:1360px}
.skillsplit.sideby{grid-template-columns:1fr 360px}
.skillsplit>div{min-width:0}
/* the fixed % column layout needs a floor to resolve against — below it the
   .tblscroll wrapper scrolls instead of letting header text overlap cells.
   Compact (default) columns need far less than the full 8-column set. */
.tblscroll{overflow-x:auto;max-width:100%}
.skillsplit .skilltbl{table-layout:fixed;min-width:440px}
.skillsplit.cols-full .skilltbl{min-width:560px}
/* tighter cells in full mode so the 9-column table fits without a scrollbar */
.skillsplit.cols-full .skilltbl td,.skillsplit.cols-full .skilltbl th{padding:5px 4px}
.thabbr{display:none}
@media(max-width:640px){
  .skilltbl .thfull{display:none}
  .skilltbl .thabbr{display:inline}
  .skilltbl th{letter-spacing:.08em}
  .poolnote{white-space:normal}
  .mincharf .mcLbl{white-space:normal}
}
.skillsplit .skilltbl tr{cursor:pointer}
/* the detail plate's top aligns with the first category accordion's head (no
   offset — the old 38px lined it up with a since-removed bare table header row) */
.skilldetail{margin-top:0}
.skilldetail .scrolltext{max-height:calc(100vh/var(--ts-zoom,1) - 200px)}
/* EQ14: the equipment/attachment detail panels sat inside an already-
   scrolling modal/page — a tight 230px cap on top of that read as a nested
   scrollbar. Scoped (not a change to the shared .scrolltext default used by
   spec/power/talent descriptions elsewhere) so only these panels grow. */
.eqdetail .scrolltext{max-height:420px}
/* Catalog detail strip (tabEquip): opens directly UNDER the clicked row,
   inside the list, so a short screen always shows it where you clicked. */
.eqtbl tr.eqdetailrow>td{padding:0;background:var(--panel2);border-bottom:1px solid var(--holo-dim)}
.eqtbl tr.eqdetailrow:hover td{background:var(--panel2)}
.eqtbl .eqdetail-inrow{margin:0;border:0;border-left:3px solid var(--holo);padding:10px 12px}
.eqdetail-inrow .scrolltext{max-height:160px}
.eqdetail-inrow .eqimg{max-height:110px}
.eqdetail-acts{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}
.eqcaret{display:inline-block;width:1em;color:var(--faint);font-size:.85em}
.eqtbl tr.eqsel .eqcaret{color:var(--holo)}
.eqtbl td.eqname:focus-visible{outline:2px solid var(--holo);outline-offset:-2px}
.stepper.mini button{width:22px;height:22px;font-size:13px}
.stepper.mini .cnt{font-size:14px;width:22px}
.sheetgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px}
/* GM Grants list rows. Base padding/border shared; the layout comes from a
   variant class so the trailing controls sit in fixed columns regardless of
   entity-name length (grow-sk = skills, grow-tal = talents). The name cell
   truncates instead of pushing the controls around. applyGrantFilter toggles
   inline display:none and clears back to "" — the class rule restores layout. */
.grow{padding:3px 5px;border-bottom:1px solid var(--line)}
.grow-flex{display:flex;align-items:center;gap:6px}
.grow-sk{display:grid;grid-template-columns:1fr 78px 64px;align-items:center;gap:8px}
.grow-tal{display:grid;grid-template-columns:1fr 52px 78px;align-items:center;gap:8px}
.grow-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.grow-tag{display:flex;justify-content:flex-start;align-items:center}
.grow-ctl{display:flex;justify-content:flex-start;align-items:center}
.grow-career{display:flex;gap:4px;align-items:center;font-size:10px;cursor:pointer;white-space:nowrap}
/* Inline hint after an .fl section label — kept no larger than the 9.5px label
   so the heading never reads as smaller than its own hint (GM Grants headings). */
.fl-hint{font-size:9px}
/* v3 additions */

/* v4 codex */

/* ---- chargen rework (overview, force, equip, sections) ---- */
.ovgrid{display:grid;grid-template-columns:1fr 300px;gap:18px;align-items:start}
@media(max-width:720px){.ovgrid{grid-template-columns:1fr}}
.ovportrait{display:flex;flex-direction:column}
.ovpimg{width:100%;aspect-ratio:3/4;object-fit:cover;object-position:top;border:1px solid var(--holo-dim);background:var(--input-bg);border-radius:2px}
.ovpempty{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--faint);font-size:52px}
.ovpempty small{font-size:11px}
.portbtn{cursor:pointer;display:block;text-align:center;margin-top:8px}
.cardsection{margin-top:12px;border-top:1px solid var(--line);padding-top:8px}
.scrolltext{font-size:12.5px;line-height:1.6;max-height:230px;overflow-y:auto;padding-right:6px}
.ftile{border-top:3px solid var(--holo)!important;position:relative}
.mentorbox{display:flex;gap:8px;align-items:flex-start;font-size:11.5px;color:var(--dim);margin:2px 0 10px;cursor:pointer}
.mentorbox input{width:16px;height:16px;flex:none;accent-color:var(--green)}
.tnode-empty{border:none!important;background:transparent!important;box-shadow:none!important;pointer-events:none;min-height:0!important}
.eqtbl .eqname{cursor:pointer;color:var(--holo)}
.eqtbl .eqname:hover{text-decoration:underline}
.eqtbl tr.eqsel{background:var(--accent-wash-mid)}
.eqdetail .eqstats{display:flex;flex-wrap:wrap;gap:6px 14px;margin:4px 0}
.eqstat{font-size:12px;color:var(--dim)}.eqstat b{color:var(--amber);font-weight:600}
/* Key combat figures (Damage/Critical/Range/Encumbrance/Soak/Defense) on an
   owned-equipment row — stand out from the surrounding faint itemLine text. */
.eqkeystat{color:var(--ink);font-weight:700;font-size:1.08em}
/* A stat changed by an installed attachment mod (EQ2) — accent color + a
   dotted underline; the base-value tooltip carries the "before" number. */
.modded{color:var(--holo);border-bottom:1px dotted var(--holo-dim);cursor:help}
.modded.neg{color:var(--red);border-bottom-color:var(--red-dim)}
/* Chargen Equipment owned rows (.eqown, tabEquip renderRow): the stat line,
   the action-button group and the Equip/Carry/Show/Qty/+HP/repair control
   row. Desktop reads as before; the ≤640px block below restacks them. */
.eqown-line{color:var(--faint);font-size:10.5px;font-weight:400}
.eqown-acts{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.eqown-ctl{flex-basis:100%;display:flex;gap:12px;align-items:center;flex-wrap:wrap;font-size:10px;color:var(--dim)}
.eqown-ctl label,.eqown-step{display:flex;gap:4px;align-items:center}
.eqsubtabs{margin:8px 0 4px}
.eqst{white-space:nowrap}
@media(max-width:640px){
  /* status strip: Encumbrance | Credits, Armor | Weapons, then the threshold
     note, cybernetics and pocket money full width — an even grid, not a ragged wrap */
  .eqstrip{display:grid;grid-template-columns:1fr 1fr;gap:6px 12px}
  .eqstrip>.eqstrip-note{grid-column:1/-1;order:9;margin:0}
  .eqstrip>.eqstrip-wide{grid-column:1/-1;order:10}
  .eqsubtabs .cgtab{flex:1 1 0}
}
/* Repair state as a colored tab row (OggDude-style), not a dropdown. */
.repairtabs{display:flex;border:1px solid var(--line);border-radius:3px;overflow:hidden}
.reptab{font-family:var(--label);font-size:8.5px;letter-spacing:.08em;text-transform:uppercase;
  padding:3px 8px;border:none;border-right:1px solid var(--line);cursor:pointer;
  background:var(--btn-bg);color:var(--dim)}
.reptab:last-child{border-right:none}
.reptab.repnone.on{background:var(--repair-none);color:#0a2010}
.reptab.repminor.on{background:var(--repair-minor);color:#241a02}
.reptab.repmoderate.on{background:var(--repair-moderate);color:#200f02}
.reptab.repmajor.on{background:var(--repair-major);color:#fff}
.eqimg{float:right;max-width:140px;max-height:110px;object-fit:contain;margin:4px 0 8px 12px;border:1px solid var(--line);border-radius:3px;background:var(--input-bg)}


/* p2 pdf viewer */
.cite.live{border-style:solid;cursor:pointer}
.cite.live:hover{background:var(--amber-wash-hover)}
#pdfOverlay{position:fixed;inset:0;z-index:200;background:rgba(8,12,16,.94);display:none;flex-direction:column}
/* One row, always: the title shrinks/ellipsizes and the control groups hold
   their size (flex:0 0 auto) so nothing wraps to a second line; overflow-x is
   the safety valve on a truly tiny viewport rather than wrapping. */
.pdfbar{display:flex;gap:8px;align-items:center;padding:8px 12px;background:var(--panel);border-bottom:1px solid var(--line);flex-wrap:nowrap;overflow-x:auto}
.pdfbar .spacer{flex:1 1 auto;min-width:0}
.pdfbar .pdftitle{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pdfbar .btn{flex:0 0 auto;white-space:nowrap}
.pdfcal{flex:0 0 auto;white-space:nowrap}
.pdfgrp{display:flex;gap:8px;align-items:center;flex-wrap:nowrap;flex:0 0 auto}
.pdftitle{font-family:var(--disp);font-size:12px;color:var(--holo);letter-spacing:.08em}
.pdfpg{font-size:12px;color:var(--dim)}
.pdfcal{font-size:11px;color:var(--amber)}
/* the wash is translucent — layer it over the opaque panel so the page under
   the overlay never shows through the notice */
.pdfnote{display:flex;gap:8px;align-items:center;padding:6px 12px;font-size:12px;
  background:linear-gradient(var(--amber-wash),var(--amber-wash)),var(--panel);color:var(--amber);border-bottom:1px solid var(--line)}
.pdfnote button{margin-left:auto}
/* PDF library row: compact two-row card — header (name · status · actions),
   then one aligned controls row (Book / Page offset+Calibrate / Aliases) that
   wraps to more rows only when the viewport is too narrow to hold it. */
.librow{border:1px solid var(--line);border-left:3px solid var(--holo-dim);background:var(--panel2);
  padding:8px 10px 10px;margin-bottom:6px}
.librow-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.librow-head .iname{flex:1 1 auto;min-width:0;font-weight:600;overflow-wrap:anywhere}
.librow-badges,.librow-acts{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap}
.libren{background:none;border:none;color:var(--faint);cursor:pointer;font-size:12px;padding:2px 4px;border-radius:4px}
.libren:hover,.libren:focus-visible{color:var(--holo)}
.librow-file{flex:1 1 100%;margin:0}
/* add zone: a <label> around the (visually hidden, still focusable) file input,
   so a click anywhere opens the picker and a drop lands on App.libDrop */
.libdrop{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;cursor:pointer;
  padding:14px 12px;border:1px dashed var(--holo-dim);background:var(--accent-wash);border-radius:6px;position:relative}
.libdrop:hover,.libdrop.over,.libdrop:focus-within{background:var(--accent-wash-hover);border-color:var(--holo)}
.libdrop.off{opacity:.5;cursor:not-allowed}
.libdrop input{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden}
.libdrop-big{font-family:var(--disp);letter-spacing:.12em;color:var(--holo);font-size:13px}
.libdrop-sub{font-size:11.5px;color:var(--dim)}
.libstore{margin:8px 0 0}
.librelink{margin:8px 0 0;padding:6px 8px;border:1px dashed var(--holo-dim);background:var(--accent-wash)}
.libbookpdf{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:4px 0 10px}
.libwarn{color:var(--amber)}
.libcov{margin:8px 0 4px}
.libcov-list{max-height:320px;overflow:auto;border:1px solid var(--line);margin-top:6px;background:var(--panel2)}
.libcov-row{display:flex;align-items:center;gap:8px;padding:4px 8px;border-bottom:1px solid var(--line);font-size:12px}
.libcov-row:last-child{border-bottom:none}
.libcov-row.on .libcov-b{color:var(--dim)}
.libcov-b{flex:1 1 auto;min-width:0}
.libcov-n{color:var(--faint);font-size:11px;white-space:nowrap}
.librow-head .libmeta{color:var(--faint);font-size:11px;font-weight:400}
.librow-ctl{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-top:8px}
.librow-field{display:flex;flex-direction:column;gap:3px;margin:0}
.librow-field>span:first-child{font-family:var(--label);font-size:9.5px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--faint)}
.librow-field select{min-width:220px}
.librow-inline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-height:28px}
.librow-aliases{flex:1 1 220px;min-width:180px}
.librow-aliases input{width:120px;font-size:11px}
/* status badges + alias chips */
.libbadge{font-size:10px;letter-spacing:.04em;padding:1.5px 7px;border-radius:10px;
  border:1px solid var(--line);color:var(--dim);background:var(--panel2)}
.libbadge.ok{color:var(--holo);border-color:var(--holo-dim)}
.libbadge.warn{color:var(--amber);border-color:var(--amber-dim);background:var(--amber-bg)}
.libbadge.bad{color:#fff;background:var(--repair-major);border-color:var(--repair-major)}
.aliaschip{display:inline-flex;gap:4px;align-items:center;font-size:11px;padding:1.5px 6px;
  border-radius:10px;border:1px solid var(--holo-dim);color:var(--holo);background:var(--accent-wash)}
.aliaschip button{background:none;border:none;color:inherit;cursor:pointer;padding:0;font-size:11px;line-height:1}
.pdfcanvaswrap{flex:1;overflow:auto;display:flex;padding:16px;background:#0a0f14;cursor:grab;user-select:none;touch-action:pan-x pan-y}
.pdfcanvaswrap.dragging{cursor:grabbing}
/* page = canvas + the search-highlight layer stacked over it */
.pdfmain{flex:1;display:flex;min-height:0;position:relative}
.pdfmain>.pdfcanvaswrap{flex:1;min-width:0}
.pdfpage{position:relative;margin:0 auto;flex-shrink:0}
#pdfCanvas{box-shadow:0 4px 24px rgba(0,0,0,.6);background:#fff;display:block;image-rendering:auto}
.pdfhl{position:absolute;inset:0;pointer-events:none}
.pdfhl i{position:absolute;background:rgba(255,196,0,.38);outline:1px solid rgba(214,150,0,.9);border-radius:2px}
.pdfhl i.cur{background:rgba(255,120,0,.45)}
#pdfOverlay .pdfpgin,#pdfOverlay .pdfbkin{width:60px;min-width:0;flex:0 0 auto}
.pdfpg{white-space:nowrap;display:inline-flex;align-items:center;gap:4px}
.pdftools{display:inline-flex;align-items:center;gap:8px;flex:0 0 auto;white-space:nowrap}
.pdfmore{display:flex;align-items:center;gap:8px}
.pdfbk{color:var(--amber)}
/* find bar + results */
.pdffind{background:var(--panel);border-bottom:1px solid var(--line);padding:6px 12px}
.pdffind-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.pdffind-row input{flex:1 1 200px;min-width:0}
.pdffind-stat{font-size:11.5px;color:var(--dim);margin-top:4px}
.pdffind-list{max-height:30vh;overflow:auto;margin-top:6px;border:1px solid var(--line);background:var(--panel2)}
.pdffind-hit{display:flex;gap:8px;width:100%;text-align:left;background:none;border:none;border-bottom:1px solid var(--line);
  color:var(--ink);padding:5px 8px;font-size:12px;cursor:pointer}
.pdffind-hit b{color:var(--amber);white-space:nowrap;font-weight:600}
.pdffind-hit span{color:var(--dim);overflow:hidden;text-overflow:ellipsis}
.pdffind-hit:hover,.pdffind-hit:focus-visible,.pdffind-hit.on{background:var(--accent-wash-hover)}
/* Contents drawer: a side panel on wide screens, a bottom sheet on phones */
.pdfoutline{flex:0 0 280px;max-width:40%;display:flex;flex-direction:column;background:var(--panel);border-right:1px solid var(--line);min-height:0}
.pdfoutline-head{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;border-bottom:1px solid var(--line);
  font-family:var(--disp);font-size:12px;letter-spacing:.08em;color:var(--holo)}
.pdfoutline-body{overflow:auto;padding:4px 0 10px;flex:1}
.pdfoutline-body .smallnote{padding:8px 12px}
.pdfoutline ul{list-style:none;margin:0;padding:0 0 0 12px}
.pdfoutline>.pdfoutline-body>ul{padding-left:4px}
.pdfoutline summary{cursor:pointer;display:flex;align-items:center;gap:2px}
.pdfoutline summary::marker{color:var(--faint)}
.pdfol-go{display:flex;gap:8px;justify-content:space-between;align-items:baseline;width:100%;text-align:left;background:none;border:none;
  color:var(--ink);padding:4px 8px;font-size:12px;cursor:pointer;border-radius:3px}
.pdfol-go:disabled{color:var(--faint);cursor:default}
.pdfol-go:hover:not(:disabled),.pdfol-go:focus-visible,.pdfol-go.on{background:var(--accent-wash-hover)}
.pdfol-pg{color:var(--faint);font-size:11px;white-space:nowrap}
@media (max-width:1100px){.pdfbar-top .pdfbl,.pdfbar-top .pdfnav-top .pdfbl{display:none}}
/* phone-only chrome, hidden on wider screens */
.pdfbar-bot,.pdfmore-btn{display:none}

.lkchip{display:inline-flex;gap:5px;align-items:center;border:1px solid var(--holo-dim);color:var(--holo);font-size:11px;padding:2.5px 9px;border-radius:11px;cursor:pointer;margin:2px 2px 2px 0;background:var(--accent-wash)}
.lkchip:hover{background:var(--accent-wash-hover)}
.lkchip small{color:var(--faint);font-size:8px;letter-spacing:.1em;text-transform:uppercase}
.cite{display:inline-block;border:1px dashed var(--amber);color:var(--amber);font-size:10.5px;padding:2px 8px;margin:2px 3px 2px 0;cursor:pointer}
/* descBody's OggDude citation-pointer fallback ("Please see <cite> …"). General
   case rides theme tokens; the .tcite variant sits on the FFG light card stock
   (fixed palette — the theming exception) so it reads in the card's own ink. */
.citesee{font-size:11px;color:var(--dim);line-height:1.6;margin:2px 0}
.tbody .citesee.tcite{color:#3a444c;font-size:10px;line-height:1.4;margin:0}

/* ============ V3: softened palette components ============ */
.panel{box-shadow:none}
.tree{gap:0;align-items:start;--tnode-chamfer:10px;padding-top:16px}
/* Talent/power tree cards deliberately mimic FFG's printed light card
   stock in every theme — their surface colors are canonical, only the
   ownership/hover accents follow the theme. .tnode is just the grid-sized
   positioning box (unclipped, so .fstar below can spill past its edge);
   .tface is the actual chamfered card face — background/border/ownership
   glow and the clip-path all live there so the glow and the hex outline
   stay in sync. Corners are cut on all four sides (a soft octagon, not one
   single sharp point) to read like the FFG page's beveled cards. NOTE:
   .tree uses align-items:start so cards size to their own content instead
   of being force-stretched to match the tallest card in the row. The
   connectors reach their neighbours (not the cards): they're SVG lines in
   the .treelines overlay (see the connector-architecture comment at the
   base .tree rule), drawn from measured card rects by drawTreeLines() and
   kept fresh by a ResizeObserver — so no gap can show even when a short
   card sits in a tall row. padding-top gives the top row's Force-talent
   starbursts (which poke ~14px above the card) clearance from the Legend
   just above the tree. */
.tnode{display:flex;flex-direction:column;min-height:120px;position:relative;z-index:1}
.tnode .tface{background:#e6e9ec;border:1px solid #9aa6ad;color:#1d252c;display:flex;flex-direction:column;height:100%;position:relative;z-index:2;
  clip-path:polygon(var(--tnode-chamfer) 0,calc(100% - var(--tnode-chamfer)) 0,100% var(--tnode-chamfer),100% calc(100% - var(--tnode-chamfer)),
    calc(100% - var(--tnode-chamfer)) 100%,var(--tnode-chamfer) 100%,0 calc(100% - var(--tnode-chamfer)),0 var(--tnode-chamfer))}
/* Force-talent cards skip the top-left cut specifically — the starburst
   (see .fstar below) relies on a plain square corner there so its spill
   zone is governed purely by z-order against the whole card face, not by
   a diagonal edge slicing across the glyph partway through it. The face's
   own 1px border stroke is a second, separate seam — a border is a visible
   line even where an opaque fill would just invisibly cover the star, so
   the top/left sides (the only sides the star's spill box reaches) drop
   their border here rather than draw a hairline across the glyph. */
.tnode.tforce .tface{clip-path:polygon(0 0,calc(100% - var(--tnode-chamfer)) 0,100% var(--tnode-chamfer),100% calc(100% - var(--tnode-chamfer)),
    calc(100% - var(--tnode-chamfer)) 100%,var(--tnode-chamfer) 100%,0 calc(100% - var(--tnode-chamfer)));
  border-top:none;border-left:none}
.tnode .thead{display:flex;gap:7px;align-items:center;font-family:var(--disp);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:#f2f4f5;padding:6px 8px}
.thead.hd-a{background:#a8323a}.thead.hd-p{background:#2a6f96}.thead.hd-f{background:#33506b}
.tnode .tchk{width:13px;height:13px;border:1.6px solid #f2f4f5;border-radius:2px;display:inline-flex;align-items:center;justify-content:center;font-size:10px;flex:none;line-height:1}
.tnode .tbody{padding:7px 9px 26px;font-size:10.5px;line-height:1.45;flex:1}
.tnode .tbody b{color:#101418}
.tnode.avail:hover .tface{background:#eef1f3}
.tnode.owned:hover,.tnode:hover{filter:none}
.tnode.avail:hover .thead{filter:brightness(1.05)}
/* Ranked marker: a solid white diamond. Lives at the right edge of the
   banner (a trailing flex child, name span grows via flex:1 to push it
   there) rather than inline in the body text; the Legend reuses the exact
   same class so both places stay in sync automatically. */
.rankicon{display:inline-block;width:8px;height:8px;background:#fff;transform:rotate(45deg);flex:none}
/* reSpecialized banner markers — .rankicon's pattern: trailing flex children
   on the card banner, fixed white like every banner glyph (the card-stock
   theming exception). Conflict = eight-point burst; Repeatable = hollow
   diamond holding a pip. The Legend reuses these exact classes (re-inked
   there for panel legibility — see .treelegend below). */
.cficon{display:inline-block;width:12px;height:12px;flex:none;background:#fff;
  -webkit-mask:var(--cf-mask) center/contain no-repeat;mask:var(--cf-mask) center/contain no-repeat}
.rpicon{display:inline-block;width:12px;height:12px;flex:none;background:#fff;
  -webkit-mask:var(--rp-mask) center/contain no-repeat;mask:var(--rp-mask) center/contain no-repeat}
/* "Configure…/Choose…" action for a talent that unlocked an optional player
   choice — the talent grants the option, the button routes to where the
   player actually makes it (App.cgGoto). Styled to sit on the light FFG
   card stock rather than the app's holo-chrome .btn look. */
.tnode .tcard-acts{margin-top:6px;display:flex;flex-wrap:wrap;gap:4px}
.tnode .tcard-act{cursor:pointer;background:#2f9c68;border:none;color:#f4f6f7;font-size:8.5px;letter-spacing:.04em;text-transform:none;padding:3px 7px;border-radius:2px;clip-path:none;font-family:inherit}
.tnode .tcard-act:hover{background:#27835a}
.tnode .tcard-act.ghost{background:transparent;border:1px solid #9aa6ad;color:#5d6b76}
.tnode .tcard-act.ghost:hover{background:#eef1f3}
.tnode .tcost{position:absolute;bottom:7px;right:7px;font-family:var(--disp);font-size:8.5px;letter-spacing:.08em;padding:3px 9px;border-radius:3px}
/* Cost badge always matches ITS OWN card's banner color — a sibling
   selector off .thead's hd-* class, not a flat default, so a Passive
   (blue) card never shows a red cost badge. */
.thead.hd-a ~ .tcost{background:#a8323a;color:#f4e9e9}
.thead.hd-p ~ .tcost{background:#2a6f96;color:#eaf3fa}
.thead.hd-f ~ .tcost{background:var(--force-green);color:#eaf5ee}
/* Ownership keeps the light card stock readable: green-tinted stock +
   border/glow + checked box — never a dark theme fill under the dark text.
   Glow/border live on .tface (not .tnode) so they follow the chamfered
   outline instead of drawing a rectangle around a hex card. */
.tnode.owned .tface{background:#e7f0e9;border-color:var(--green);box-shadow:0 0 0 1.5px var(--green),0 0 14px var(--green-glow)}
.tnode.owned{cursor:pointer}
.tnode.owned .tchk{background:#2f9c68;border-color:#e9f5ee}
.tnode.avail .tface{border-color:#c8d4da}
.tnode.avail{cursor:pointer}
.tnode.avail:hover .tface{box-shadow:0 0 0 1.5px var(--holo)}
.tnode.locked{opacity:.42;cursor:default;filter:grayscale(.5)}
/* Force Talent card template (a distinct card variant chosen by treeHtml for
   Force-requiring talents). An eight-point sparkle-star nests into the banner's
   top-left corner, matching the OggDude/Hyperdrive reference: the star is the
   SAME color as the banner and painted IN FRONT of the whole card face
   (z-index:3, above .tface's z-index:2). Because the star matches the banner
   colour and never reaches the light card body (it ends ~20px down; the banner
   is ~25px tall), its in-card portion is seamless over the banner while its
   top-left arms poke out onto the dark page. Painting it in FRONT means it
   also covers whatever .tface draws at that square corner — the 1px border AND
   the owned/hover box-shadow glow ring — so no stray line ever crosses the
   glyph (the earlier BEHIND ordering let the glow ring paint on top of the
   star). The one thing the star must NOT cover is the ownership checkbox, so
   .tnode.tforce .fstar below clips the star's bottom-right chunk (where the
   checkbox sits) out — invisible over the same-coloured banner, but it frees
   the checkbox. Star colour tracks the card's hd-* header (red hd-a / slate
   hd-p). .fstar keeps pointer-events:none so being in front doesn't eat card
   clicks. */
.tnode .fstar{position:absolute;top:-14px;left:-14px;width:34px;height:34px;z-index:3;pointer-events:none}
/* Carve the checkbox's bottom-right chunk (x>59%, y>53% of the 34px star box)
   out of the star so the in-front star never covers the checkbox; the whole
   visible poke-out (x<41% OR y<41%) is untouched. */
.tnode.tforce .fstar{clip-path:polygon(0 0,100% 0,100% 53%,59% 53%,59% 100%,0 100%)}
.fstar.hd-a{color:#a8323a}.fstar.hd-p{color:#2a6f96}
/* Force power/ability trees: distinct green identity (green header, and via
   the .thead.hd-f ~ .tcost rule above, a green XP cost badge too), kept
   separate from the talent starburst. */
.tree.force .thead.hd-f{background:var(--force-green)}
/* Talent Tree Legend — a compact key shown above every tree (career/spec,
   Force powers, signature abilities). Swatches/star/rankicon reuse the exact
   card-stock colors and icons the tree itself uses (never invent new ones). */
.treelegend{display:flex;flex-wrap:wrap;gap:14px;align-items:center;font-size:10px;letter-spacing:.04em;
  color:var(--dim);margin:8px 0 6px;padding:6px 10px;border:1px solid var(--line);background:var(--panel2)}
.tlitem{display:inline-flex;align-items:center;gap:5px}
.tlswatch{width:11px;height:11px;border-radius:2px;display:inline-block}
.tlswatch.hd-a{background:#a8323a}.tlswatch.hd-p{background:#2a6f96}
/* Force Talent legend icon: half red / half blue (the two banner colors a
   Force talent's own card could carry) rather than one fixed color, since
   the per-card star always matches whichever banner it's actually on — the
   legend's own path fills are set directly in treeLegend(), not via color. */
.tlstar{width:14px;height:14px;vertical-align:-2px}
/* On the legend's theme panel (not a colored banner) the white banner icons
   would vanish in light themes — re-ink them with the theme's text color.
   Same treatment for the ranked diamond, which had the same problem. */
.treelegend .rankicon,.treelegend .cficon,.treelegend .rpicon{background:var(--ink)}
/* Force Presence ten-point Balance track (reSpecialized module). Pip states
   mirror the primer's own glyphs — Neutral is the half-filled ◑ "mixed"
   point, Light a hollow ○, Dark a filled ● — using the fixed white/black
   Light/Dark faces (the sy-LI/sy-DA convention) so they read identically on
   every theme; the theme only inks the outlines. Track ends reuse the
   Destiny token art (.dtoken), sized down to pip scale. */
.fptrack{display:flex;gap:5px;align-items:center;margin:6px 0}
.fptrack .dtoken{width:21px;height:21px;flex:none}
.fppip{width:17px;height:17px;border-radius:50%;border:1.6px solid var(--dim);background:transparent;cursor:pointer;padding:0;flex:none}
.fppip.L{background:transparent;border-color:var(--ink)}
/* Neutral is a deliberate vertical ◑ split (white-left / black-right — the
   Unicode "mixed" glyph), NOT the primer tracker's horizontal top-black /
   bottom-white half. This is an intentional design choice; a later
   primer-fidelity pass should leave it as ◑, not "correct" the orientation. */
.fppip.N{background:linear-gradient(90deg,#fff 0 50%,#000 50% 100%);border-color:var(--faint)}
.fppip.D{background:#000;border-color:var(--faint)}
span.fppip{cursor:default;display:inline-block}
/* dice shapes */
.die{width:14px;height:14px;border:none;border-radius:0;box-shadow:0 0 0 1px rgba(0,0,0,.35) inset}
.die.dp{background:var(--die-p);clip-path:polygon(50% 0%,85% 13%,100% 50%,85% 87%,50% 100%,15% 87%,0% 50%,15% 13%)}
.die.da{background:var(--die-a);clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%)}
.die.db{background:var(--die-b);border-radius:3px}
.die.dd{background:var(--die-d);clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%)}
.die.dc{background:var(--die-c);clip-path:polygon(50% 0%,85% 13%,100% 50%,85% 87%,50% 100%,15% 87%,0% 50%,15% 13%)}
.die.ds{background:var(--die-s);border-radius:3px;box-shadow:0 0 0 1px var(--die-s-edge) inset}
/* setback die REMOVED by a talent/species ability — struck through */
.die.rem{position:relative;opacity:.75}
.die.rem::after{content:"";position:absolute;left:50%;top:50%;width:10px;height:2.5px;background:var(--red);border-radius:1px;transform:translate(-50%,-50%)}
.poolnote{font-size:10px;color:var(--dim);margin-left:5px;white-space:nowrap}
/* One shared baseline for every glyph in a dice pool (dice shapes, icon-font
   result symbols, and the inline-flex diamond difficulty symbols). */
.poolwrap .die,.poolwrap .sym,.poolwrap .poolnote>.sym{vertical-align:middle}
/* Force die: box-shadow-based outlines get cut away by clip-path (only the
   sliver touching the box edge survives), which is why the outline used to
   disappear along the diagonal edges — especially against light-mode
   backgrounds close to --die-f's own pale color. Fixed by making the
   outline color the die's own background and inset-clipping the pale fill
   on top, so the outline always traces the full octagon. */
.die.df{position:relative;background:var(--die-f-outline);clip-path:polygon(50% 0%,85% 13%,100% 50%,85% 87%,50% 100%,15% 87%,0% 50%,15% 13%)}
.die.df::after{content:"";position:absolute;inset:1.3px;background:var(--die-f);clip-path:polygon(50% 0%,85% 13%,100% 50%,85% 87%,50% 100%,15% 87%,0% 50%,15% 13%)}
.sym{font-family:'sw-rpg-icons';font-style:normal;speak:none;font-size:14px;line-height:1;vertical-align:-2px}
.sy-SU{color:var(--sym-su)}.sy-AD{color:var(--sym-ad)}.sy-FA{color:var(--sym-fa)}.sy-TH{color:var(--sym-th)}
.sy-DE{color:var(--sym-de)}.sy-TR{color:var(--sym-tr)}
/* Cancel Threat keeps the actual Threat glyph visible and strikes it with a
   red cancellation bar, so it cannot be mistaken for a generic cancel icon. */
.sy-CT{position:relative;display:inline-block;width:14px;height:14px;vertical-align:-2px}
.sy-CT-threat{font-family:'sw-rpg-icons';font-size:14px;line-height:14px;color:var(--sym-th)}
.sy-CT::after{content:"";position:absolute;z-index:1;left:-1px;top:6px;width:16px;height:2px;border-radius:2px;background:var(--red);transform:rotate(-42deg);box-shadow:0 0 0 1px var(--bg)}
/* Difficulty-pool adjustments are purple diamond tokens, matching the FFG
   pool-symbol guide ([DD] "add difficulty" shares [ID]'s die-with-plus look).
   They are display-only until the dice system resolves a supplied difficulty. */
.sy-ID,.sy-DD,.sy-RD,.sy-UD,.sy-DW{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;margin:0 1px;font-family:var(--mono);font-size:11px;font-weight:700;line-height:1;color:#fff;background:#68418b;clip-path:polygon(50% 0,100% 50%,50% 100%,0 50%);vertical-align:-2px}
/* [RESTRICTED]/[RE] item marker — OggDude's red prohibition roundel, drawn
   in CSS so it takes the theme's --red rather than a fixed glyph color. */
.sy-RE{position:relative;display:inline-block;width:14px;height:14px;margin:0 1px;border:2px solid var(--red);border-radius:50%;vertical-align:-2px}
.sy-RE::after{content:"";position:absolute;left:0;top:4px;width:10px;height:2px;border-radius:1px;background:var(--red);transform:rotate(45deg)}
/* reSpecialized primer markers, CSS-drawn like [RE]. [CF] Conflict: the
   primer's eight-point burst (a node that awards Conflict when USED).
   [RP] Repeatable: the primer's diamond badge — hollow diamond holding a
   pip (a Force-power effect usable multiple times per Force power check). */
.sy-CF{display:inline-block;width:14px;height:14px;margin:0 1px;background:var(--sym-cf);vertical-align:-2px;
  -webkit-mask:var(--cf-mask) center/contain no-repeat;mask:var(--cf-mask) center/contain no-repeat}
.sy-RP{display:inline-block;width:14px;height:14px;margin:0 1px;background:var(--sym-rp);vertical-align:-2px;
  -webkit-mask:var(--rp-mask) center/contain no-repeat;mask:var(--rp-mask) center/contain no-repeat}
/* Light/Dark Side use the same filled-disc glyph, opposite ink colors —
   like the two faces of a physical Force die. Each gets an outline only in
   the mode where its fill color would otherwise blend into the background
   (white pip in light mode, black pip in dark mode); the stroke tokens are
   mode-driven in css/themes.css, not hardcoded here. */
.sy-LI{color:var(--sym-li);-webkit-text-stroke:var(--sym-li-stroke-w,0) var(--sym-li-stroke-c,transparent)}
.sy-DA{color:var(--sym-da);-webkit-text-stroke:var(--sym-da-stroke-w,0) var(--sym-da-stroke-c,transparent)}
/* symbols inside the light talent-tree cards keep fixed dark print colors.
   Light/Dark Side keep the same hollow-white/solid-black look as [data-mode=
   "light"] (css/themes.css) — card stock is always light, so it's the same
   fixed pair, not a color pulled from the ambient purple/dark theme palette. */
.tbody .sy-SU,.tbody .sy-AD,.tbody .sy-FA,.tbody .sy-TH{color:#101418}.tbody .sy-DE{color:#9c2f27}
.tbody .sy-TR{color:#8a6412}.tbody .sy-LI{color:#fff;-webkit-text-stroke:1px #000}.tbody .sy-DA{color:#000;-webkit-text-stroke:0 transparent}
.tbody .sy-CT-threat{color:#101418}.tbody .sy-CT::after{background:#a82020;box-shadow:0 0 0 1px #f6f1df}.tbody .sy-ID,.tbody .sy-DD,.tbody .sy-RD,.tbody .sy-UD,.tbody .sy-DW{color:#fff}.tbody .sy-RE{border-color:#a82020}.tbody .sy-RE::after{background:#a82020}
.tbody .sy-CF,.tbody .sy-RP{background:#101418}
/* Force Point [FP]: the pip you spend — the FFG half-filled ◑ symbol, distinct
   from the Force die [FO]. Rendered as the ◑ glyph, theme-inked so its filled
   half stays legible in both light and dark mode (and on light card stock). */
.fpoint{display:inline-block;font-size:15px;line-height:1;vertical-align:-2px;margin:0 1px;color:var(--ink)}
.tbody .fpoint{color:#101418}
/* destiny pool tokens — the user's own token art (assets/destiny-*.png), shown
   as circular background images. The dark token is zoomed so its outer red
   band + rim crop out, leaving just the black disc + red burst emblem. */
.dtoken{width:52px;height:52px;display:inline-block;border-radius:50%;background-repeat:no-repeat;background-position:center;background-size:cover;position:relative}
.dtoken-light{background-image:url("../assets/destiny-light.png")}
.dtoken-dark{background-image:url("../assets/destiny-dark.png");background-color:#0a0a0a}
.dtoken-dark::after{content:"";position:absolute;inset:0;border-radius:50%;box-shadow:inset 0 0 0 2px rgba(210,210,210,.28);pointer-events:none}
.dtok-pool{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:6px 0}
.dtok-pool.compact .dtoken{width:34px;height:34px}
.dtokbtn{background:none;border:none;padding:0;cursor:pointer;line-height:0;border-radius:50%;transition:transform .08s}
.dtokbtn:hover{transform:translateY(-2px);filter:brightness(1.08)}
.dtokbtn:focus-visible{outline:2px solid var(--holo);outline-offset:2px}
.dsides{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:6px 0 4px}
@media(max-width:640px){.dsides{grid-template-columns:1fr}}
.dside{border:1px solid var(--line);border-radius:4px;padding:10px 12px;background:var(--panel2)}
.dsidehdr{display:flex;justify-content:space-between;align-items:baseline;font-family:var(--disp);letter-spacing:.06em;font-size:13px;margin-bottom:4px}
.dsidehdr b{font-size:24px;color:var(--holo)}
.dside-dark .dsidehdr b{color:var(--red)}
.dtface{display:inline-flex;gap:1px;padding:2px 6px;border:1px solid var(--line);border-radius:3px;margin:2px 3px 2px 0;background:var(--panel)}
.dchk{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dim);white-space:nowrap}
.dchk input{width:16px;height:16px;accent-color:var(--holo)}
/* species picker */
.spick{display:flex;gap:9px;align-items:center}
.spthumb{width:36px;height:36px;object-fit:cover;border-radius:3px;border:1px solid var(--line);flex:none;background:var(--input-bg)}
.spchars{font-family:var(--disp);font-size:9px;color:var(--holo);letter-spacing:.06em}
.spportrait{float:right;width:auto;height:200px;max-width:260px;object-fit:contain;margin:0 0 10px 14px;border:1px solid var(--line);border-radius:3px;background:var(--input-bg)}
.trait{border-left:3px solid var(--amber);background:var(--panel2);padding:7px 10px;font-size:12px;line-height:1.55;margin-top:4px}
/* species selection cards (portrait ~40% + name + full stat-box grid) */
.spgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;max-height:520px;overflow-y:auto;padding:2px}
.spcard{display:flex;align-items:stretch;gap:0;padding:0;overflow:hidden}
/* Portrait column height is driven entirely by the stat-box body (the tiles
   stay a locked, decent size); the image is taken out of flow (absolute) so a
   tall-narrow portrait can't stretch the row taller. object-fit:contain then
   fills as much of that fixed box as possible without ever stretching,
   compressing, or exceeding the box in either dimension. */
.spcard-portrait{flex:0 0 40%;max-width:40%;background:var(--input-bg);border-right:1px solid var(--line);position:relative;overflow:hidden}
.spcard-portrait img{position:absolute;inset:3px;width:calc(100% - 6px);height:calc(100% - 6px);object-fit:contain;display:block}
.spcard-body{flex:1;min-width:0;padding:9px 10px;display:flex;flex-direction:column;gap:7px}
.spcard-head{display:flex;flex-direction:column;align-items:flex-start;gap:2px}
.spcard-name{font-size:13.5px;font-weight:600;line-height:1.15;color:var(--holo)}
.spcard-src{font-size:9px;color:var(--faint);text-transform:uppercase;letter-spacing:.08em}
.spcard-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:4px}
.spbox{border:1px solid var(--line);background:var(--panel);text-align:center;padding:5px 2px}
.spbv{font-family:var(--disp);font-size:19px;line-height:1;color:var(--holo)}
.spbl{font-size:7.5px;letter-spacing:.1em;color:var(--faint);text-transform:uppercase;margin-top:3px}
.spbox-xp .spbv{color:var(--amber)}
/* vehicle stat-block tiles (shared renderer: workspace, codex, hull picker).
   Headline stats as .charbox tiles; defense zones as a compass cluster.
   Equipment-modified values flag with an amber border + tooltip. */
.vehtiles{margin:6px 0 8px}
/* Profile band: the stat tiles (three rows of two), the defense diamond, and
   — on the Codex page / stock-hull picker — the full art all sit on one level
   so the block reads across instead of stacking into a tall column of dead
   space. Wraps on narrow viewports. */
.vehprofrow{display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap;margin-bottom:10px}
.vehtiles-col{flex:0 1 300px;min-width:230px}
.vdefcol,.vehpiccol{flex:0 0 auto}
.vehpiccol{align-self:center}
/* tiles as two columns → three rows (echoes the printed card's medallions) */
.vehtiles-row{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin:0}
.vehtiles .charbox{width:auto;min-width:0;box-sizing:border-box;padding:10px 6px}
.vtile.vt-amber .cv{color:var(--amber)}
.modded-tile{border-color:var(--amber);box-shadow:inset 0 0 0 1px var(--amber)}
.vtile-base{font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--amber);margin-top:1px} /* V6: modded tile's base value, visible without hover */
/* OggDude-style defense diamond: the ship silhouette centred in a colour-coded
   zone diamond (two-piece fore/aft for small craft, four cardinal wedges for
   larger hulls). On the Codex page the full art sits to its right. */
.vdeftitle{margin:0 0 4px}
.vdefwrap{display:flex;justify-content:center}
.vdefdiamond{width:180px;height:180px;max-width:58vw}
.vdefimg{max-width:210px;max-height:170px;border:1px solid var(--line);border-radius:4px;background:var(--panel2)}
.vdd-num{font-family:var(--disp);font-size:26px;font-weight:700;fill:#fff}
.vdd-lbl{font-size:9px;letter-spacing:.09em;fill:rgba(255,255,255,.82);text-transform:uppercase}
.vdd-hub{fill:var(--panel);opacity:.7}
/* Codex category picker — uniform-width chips in rows of five, instead of a
   ragged flex-wrap where each chip is sized to its label. */
/* ---------- Galactic Codex browse + Data Editor (F10) ----------
   Both reuse chargen's vertical step nav wholesale (.cgwrap / .cgsteps / .cgtab
   / .cgmain) rather than minting another vertical-nav idiom — these rules only
   adjust for a rail of ~30 entries rather than 8. */
.de-wrap .cgsteps,.cx-wrap .cgsteps{flex:0 0 190px;max-height:min(72vh,780px);overflow:auto;position:sticky;top:8px}
.de-rail .cgtab,.cx-rail .cgtab{padding:calc(5px*var(--dsp)) calc(9px*var(--dsp));letter-spacing:.08em;font-size:10px}
/* Section headers that sort the ~31 codex categories into groups. flex-basis:100%
   forces a line break in the mobile wrapped-chip row so a header always starts a
   new line above its group. */
.cx-rail .cx-railhdr,.de-rail .cx-railhdr{flex-basis:100%;font-family:var(--label);font-size:8.5px;letter-spacing:.14em;
  text-transform:uppercase;color:var(--faint);padding:9px 6px 3px;margin-top:2px;border-top:1px solid var(--line)}
.de-count{color:var(--faint);font-size:9.5px;font-family:var(--label);flex:none}
.de-rail .cgtab.on .de-count,.cx-rail .cgtab.on .de-count{color:var(--holo)}
/* Codex card enrichments (scoped to the codex grid so shared .pick is
   untouched): a two-line prose snippet and a continuity tag. */
.pickgrid.cxlist{max-height:min(68vh,760px)}
.cxlist .pick .cx-snip{color:var(--dim);font-size:10.5px;line-height:1.35;margin-top:3px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.cx-cont{font-size:8.5px;letter-spacing:.05em;padding:0 4px;border:1px solid var(--line);border-radius:2px;color:var(--faint);vertical-align:middle}
.cx-cont.legends{color:var(--amber);border-color:var(--amber-dim)}
.cx-cont.both{color:var(--holo);border-color:var(--holo-dim)}
/* Codex search clear button + inline segmented filters (continuity, ruleset). */
.cx-search{flex:1;position:relative;display:flex}
.cx-search input{flex:1;padding-right:28px}
.cx-clear{position:absolute;right:4px;top:50%;transform:translateY(-50%);background:transparent;border:0;color:var(--faint);font-size:13px;line-height:1;cursor:pointer;padding:4px 6px}
.cx-clear:hover{color:var(--holo)}
.cx-filters{display:flex;flex-wrap:wrap;align-items:center;gap:8px 18px;margin-bottom:12px}
.cx-filter{display:flex;align-items:center;gap:7px}
.cx-seg{display:inline-flex;border:1px solid var(--line);border-radius:4px;overflow:hidden}
.cx-seg button{background:transparent;border:0;border-left:1px solid var(--line);color:var(--dim);font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:5px 11px;cursor:pointer}
.cx-seg button:first-child{border-left:0}
.cx-seg button.on{background:var(--pick-on);color:var(--holo);box-shadow:inset 0 2px 0 var(--holo)}
.cx-seg b{color:inherit;opacity:.65;font-weight:600}
.de-count b{color:var(--amber);font-weight:600}
.de-hint{margin:2px 0 8px;color:var(--faint)}
.de-list{max-height:min(46vh,520px)}
/* A row carries name, layer chips and key/provider — the Cust?/Key/DS/Name
   columns of OggDude's list, as a flow row so it still reflows on a phone. */
.de-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}
.de-row .de-name{flex:1 1 auto;min-width:0}
/* flex:0 1 auto + min-width:0 lets the chips area shrink so a long provenance
   name (⬡ Septaris Community Data) wraps inside the card instead of running off
   the right edge; the badge itself is allowed to break onto a second line. */
.de-row .de-chips{flex:0 1 auto;min-width:0;display:flex;flex-wrap:wrap;gap:3px;align-items:center}
.de-row .de-chips .badge{max-width:100%;white-space:normal;overflow-wrap:anywhere}
.de-row small{flex:1 1 100%}
/* Row layer markers (deRowMark) — the Codex LIST's single-glyph idiom, which
   replaced a full badge on every row. A stock record carries none at all, so
   the marks column only ever appears on a row that is genuinely not stock.
   .de-chips keeps its rules above: deLayerChip still renders the full badge in
   the row chooser and the editor header, where one record is on screen. */
.de-marks{flex:0 0 auto;display:flex;gap:4px;align-items:center;font-size:12px;line-height:1}
.de-mark{color:var(--amber)}
.de-row .de-key{color:var(--faint);letter-spacing:.06em}
.de-count .de-zero{opacity:.4}
/* ---- Data Editor master/detail (list left, editor right) ----
   .delayout is an override hook on the career page's .crlayout/.crbrowse/
   .crdetail idiom, exactly as .speclayout is — never a second master/detail.
   Two differences from the career page: the split sits INSIDE .cgmain, which
   already gives up ~190px to the category rail, so the browse column is
   narrower; and the browse column owns the scroll, because .de-list and
   .pickgrid each carry a max-height of their own and three nested scrollers
   in one column is unusable. */
.delayout .crbrowse{flex:0 0 330px;max-height:min(70vh,760px)}
.delayout .crdetail{flex:1 1 auto}
.delayout .de-list{max-height:none;overflow:visible;grid-template-columns:1fr}
.delayout .crdetail>.panel{margin-top:0}
/* The editor is the tall pane here, so sticky would pin a 2000px panel's TOP
   and scroll its own Save bar out of reach — the career page's detail plate is
   short enough for sticky, this one is not. */
.delayout .crdetail{position:static}
@media(max-width:1100px){
  .delayout{flex-direction:column}
  .delayout .crbrowse{flex:none;width:100%;max-height:300px}
  .delayout .de-list{grid-template-columns:repeat(auto-fill,minmax(190px,1fr))}
  .delayout .crdetail{width:100%}
}
/* A full-width editor band below the split (DE_BAND — the three talent/Force
   grids). The .spec-treeband precedent: a 560px-min-width grid cannot live in
   a ~54% column without becoming a horizontal scroller. */
.de-band{margin-top:12px}
/* ---- collapsible form sections (deSec) ----
   The .acc accordion, reused inside a record form. .de-sec is the override
   hook: an .acc inside a .formgrid needs the full row and its body has to be a
   grid again, not the accordion's default column flex. */
.de-sec{min-width:0}
.de-sec>.acc{background:transparent}
.de-sec .acc-body{display:block;padding:calc(6px*var(--dsp)) calc(10px*var(--dsp)) calc(12px*var(--dsp))}
.de-sec .acc-toggle{font-size:11px;letter-spacing:.12em;padding:9px 0}
/* .always sections have no button — the head is a plain div, so it must not
   look or behave like one. */
.de-sec .acc-head>div.acc-toggle{cursor:default;color:var(--faint)}
.de-sec .acc-selcount{white-space:normal;text-align:right;max-width:45%;text-transform:none;letter-spacing:.04em;color:var(--faint)}
.de-sec .acc-head.open .acc-selcount{display:none}
/* Live citation chips for the record being edited (deCites) — under the
   Sources button in every form, in the ✎ Description editor, and beside the
   row chooser's View action. citeChip styles itself; this only spaces them. */
.de-cites{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px}
.de-choices .de-cites{margin:-2px 0 4px 2px}
/* the editor title's Codex link — a peer of the title, not part of it */
.de-titlelink{font-family:var(--body);font-size:11px;letter-spacing:normal;text-transform:none}
/* delete-impact readouts (deUsage) — in the editor while you work, and in the
   themed dialog that gates a destructive delete */
.de-uses-note{border-left:3px solid var(--holo-dim);padding:6px 9px;margin-top:8px}
.de-uses{margin:6px 0 8px;padding-left:18px;font-size:12px;line-height:1.55;max-height:190px;overflow:auto}
.de-uses li{margin-bottom:2px}
/* ---- Data Editor set bar ----
   Desktop: the admin group is just part of the flex row and the toggle that
   reveals it on a phone does not exist. Below 640px the group folds away and
   the set picker + ACTIVE stay, so the first record is not ~500px down the
   page behind a red Delete Set at thumb height. */
.de-setadm{display:contents}
.de-admtoggle{display:none}
@media(max-width:640px){
  .de-setbar{gap:calc(6px*var(--dsp))}
  .de-admtoggle{display:inline-flex}
  .de-setadm{display:none}
  .de-setadm.on{display:flex;flex-basis:100%;flex-wrap:wrap;gap:calc(6px*var(--dsp));align-items:center}
  /* the picker takes the row it needs; ACTIVE and the toggle share the next */
  .de-setbar>select{flex:1 1 100%;min-width:0}
  /* The two labels are pure decoration on a phone — both controls carry an
     aria-label — and the scope/source selects fit one row between them. */
  .de-wrap .toolbar>.fchip,.de-setbar>.fchip{display:none}
  .de-wrap .cgmain>.toolbar>select{flex:1 1 calc(50% - 4px);min-width:0}
  .de-hint{margin-top:0}
}
.de-choices{display:flex;flex-direction:column;gap:7px;margin:10px 0}
.de-choice{display:block;text-align:left;padding:calc(9px*var(--dsp)) calc(11px*var(--dsp));height:auto;line-height:1.45}
/* DIRECT child only: the explanatory <small> below also uses <b> for emphasis,
   and a descendant selector turned every one of those into its own line. */
.de-choice>b{display:block;color:var(--holo);margin-bottom:3px}
/* .btn sets uppercase + letter-spacing, which is right for a button label and
   unreadable for a sentence — the explanation is prose, so it opts out. */
.de-choice small{display:block;color:var(--dim);font-size:11.5px;white-space:normal;
  text-transform:none;letter-spacing:normal;font-family:var(--body)}
.de-choice small b{color:var(--ink);font-weight:600}
.de-choice[disabled]{opacity:.5}
/* Data Editor sticky action bar — chargen's .cghud recipe under its own
   class: js/chargen-build.js:1169 queries .cghud by name, so a second .cghud
   would break cgFitSkillDetail. The opaque --panel2→--panel gradient is
   REQUIRED and declared HERE, once — never re-declare background in a second
   rule (the `color, gradient` var() shorthand trap, see app.css:3050-3056). */
.dehud{position:sticky; bottom:0; z-index:6; border-top:1px solid var(--holo);
  background:linear-gradient(180deg, var(--panel2), var(--panel));
  padding:10px 12px; margin:12px calc(-16px*var(--dsp)) calc(-14px*var(--dsp))}
/* weapon-mount config checkboxes (firing arcs, turret, retractable) — bigger
   box + readable text, no stray margin from a borrowed .smallnote span
   throwing the checkbox out of vertical alignment with its label. */
.wchk{display:flex;gap:7px;align-items:center;font-size:13px;color:var(--dim)}
.wchk input[type=checkbox]{width:17px;height:17px;accent-color:var(--holo);flex:none}
/* vehicle stock-hull picker — reuses the species-card system with vehicle
   tweaks: two-up cards (bigger, more rectangular ship art), full stat labels,
   a price line under the name, and a larger detail portrait */
/* Attachment picker — HORIZONTAL cards stacked in a scroll column. Every
   column but the name is a FIXED px width (not fr/auto) so the columns line
   up across every row regardless of how wide that row's content is — an
   `auto` actions column (e.g. the "needs N HP" fallback text) used to be
   wider on some rows than others, which stole space from the fr-sized
   columns before it and threw source/stats out of alignment row to row.
   Shared by the chargen attachment manager and the vehicles Attachments tab. */
.attpickgrid{display:flex;flex-direction:column;gap:6px;max-height:420px;overflow-y:auto;padding:2px}
.attpickgrid .fl{margin:8px 0 2px}
.attpick{display:grid;grid-template-columns:minmax(0,1fr) 130px 150px 150px;gap:12px;align-items:center;padding:7px 11px;border:1px solid var(--line);border-left:3px solid var(--holo-dim);background:var(--panel2)}
.attpick-name{font-weight:600;min-width:0;overflow-wrap:anywhere}
.attpick-stats{font-size:11px;color:var(--dim)}
.attpick-src{font-size:10px;min-width:0;overflow-wrap:anywhere}
.attpick-act{display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end;align-items:center;min-width:0}
.attpick-act .smallnote{white-space:normal;text-align:right}
@media (max-width:640px){
  .attpick{grid-template-columns:1fr auto;gap:4px 10px}
  .attpick-src{grid-column:1;grid-row:2}
  .attpick-stats{grid-column:1;grid-row:2;justify-self:end}
  .attpick-act{grid-column:2;grid-row:1/3;justify-content:flex-end}
}
/* Positions & Ship Actions reference — a CSS multi-column flow, not a
   row-locked grid. Positions carry wildly different action counts (Pilot has
   6, Gunner has 1), so pairing them into fixed grid ROWS left tall gaps
   under the short card in a row; a column flow instead lets each card sit
   directly under the next-shortest one in its own column, like a newspaper
   layout, with no row-height coupling between the two columns. */
.vgposgrid{column-count:2;column-gap:8px}
@media (max-width:760px){.vgposgrid{column-count:1}}
.vgposcard{margin:0 0 8px;break-inside:avoid}
.vgacttbl td{padding:2px 6px;vertical-align:top}
.vgacttbl td:first-child{width:34px;white-space:nowrap}
.vpgrid{grid-template-columns:repeat(2,1fr)}
@media (max-width:760px){.vpgrid{grid-template-columns:1fr}}
.vpcard .spcard-portrait{flex:0 0 40%;max-width:40%}
.vpcard .spcard-name{white-space:normal;overflow-wrap:anywhere}
.vpcard-price{font-size:11px;color:var(--amber);line-height:1.2}
.vpcard-price b{font-family:var(--disp);letter-spacing:.03em}
.vpportrait{height:150px;max-width:300px}
/* collapsible species skill-training picks — chosen skills shown in green */
.sptrain-chosen{display:flex;flex-wrap:wrap;gap:4px;justify-content:flex-end;align-items:center}
.sptrain-chip{font-size:10px;color:var(--green);border:1px solid var(--green-dim);background:var(--green-wash);padding:1px 7px;border-radius:2px;white-space:nowrap;text-transform:none;letter-spacing:0}
.sptrain-none{font-size:10px;color:var(--faint);text-transform:none;letter-spacing:.04em}
.sptrain .acc-body .pickgrid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
/* force tiles */
.ftile{position:relative;padding-right:10px}
.ftile .fchips{display:flex;gap:5px;margin-top:4px;flex-wrap:wrap}
.fchip{font-size:8.5px;letter-spacing:.1em;border:1px solid var(--line);padding:2px 6px;color:var(--dim);text-transform:uppercase}
.fchip.frq{border-color:var(--amber);color:var(--amber)}
.ftile.own{border-color:var(--green);box-shadow:inset 3px 0 0 var(--green)}
.ftile.own .fown{color:var(--green)}
/* Force-power browse tiles keep their own distinct green (force-green), same
   as the Force-power tree identity — everything else's "owned" state above
   uses the general --green token to match the STARTING spec badge. */
.ftile.pforce{border-top-color:var(--force-green)!important}
.ftile.pforce.own{border-color:var(--force-green);box-shadow:inset 3px 0 0 var(--force-green)}
.ftile.pforce.own .fown{color:var(--force-green)}
.ftile.pforce .fchip.frq{border-color:var(--force-green);color:var(--force-green)}
/* equipment table */
.eqtbl{width:100%;border-collapse:collapse}
.eqtbl th{font-family:var(--label);font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--holo);text-align:left;padding:6px;border-bottom:1px solid var(--holo-dim);position:sticky;top:0;z-index:2;background:var(--panel)} /* z-index: the in-row detail strip has positioned content that would paint over the sticky header */
.eqtbl th.eqsort{cursor:pointer;user-select:none;white-space:nowrap}
.eqtbl th.eqsort:hover{color:var(--holo-bright)}
.eqtbl th.eqsort.on{color:var(--holo-bright)}
/* Equipment picker filter bar: category + search on top, labelled filter
   dropdowns in an even auto-fitting grid below (was a ragged flex wrap). */
.eqfilters{margin-top:12px;display:flex;flex-direction:column;gap:8px}
.eqfilters-top{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.eqfilters-top .eqcat{flex:1 1 200px;max-width:200px;min-width:150px}
.eqfilters-top .eqsearch{flex:1 1 auto;width:auto;min-width:140px}
.eqfilters-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:8px 10px;align-items:end}
.eqf{display:flex;flex-direction:column;gap:3px;min-width:0}
.eqf>select{width:100%}
.eqf-l{font-family:var(--label);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.eqafford{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:var(--dim);white-space:nowrap;cursor:pointer;padding:5px 2px}
.eqtbl td{padding:5px 6px;border-bottom:1px solid var(--line);font-size:12px}
.eqtbl tr:hover td{background:var(--accent-wash)}
.eqwrap{max-height:min(70vh,640px);overflow-y:auto;overflow-x:auto;border:1px solid var(--line)}
.nextxp{font-size:9.5px;color:var(--amber);white-space:nowrap}
/* skill action cell: stepper + next-rank XP sit in one right-aligned row;
   flex-wrap only ever kicks in on the narrowest widths, and because the
   table is fixed-layout every action cell is the same width, so they wrap
   as a set rather than one row at a time */
.skact{display:flex;align-items:center;justify-content:flex-start;gap:6px;flex-wrap:nowrap}
/* stepper fixed at the column's left edge so the +/- controls don't shift as
   the trailing "N XP" cost changes width (5 vs 15 XP); the XP note trails it */
.skact .stepper{flex:none}.skact .nextxp{flex:none}
/* vertical (column) stepper for the skills action cell: + on top, − below,
   with the XP cost stacked (number over "XP") beside it so + aligns with the
   number and − with the label — matches the requested [+] 10 / [−] XP shape */
.stepper.minicol{flex-direction:column}
.stepper.minicol button{width:26px;height:18px;font-size:13px;line-height:1;padding:0}
.stepper.minicol button+button{border-top:1px solid var(--line)}
.nextxp.xpcol{display:flex;flex-direction:column;align-items:flex-start;justify-content:center}
.nextxp.xpcol .xpnum,.nextxp.xpcol .xplbl{height:18px;line-height:18px;display:block}
.bgblock{border:1px solid var(--line);background:var(--panel2);padding:10px 12px;margin-bottom:12px}
.bgblock .fl{margin-top:8px}
/* Holocron backlink block on codex pages — a warm accent so GM notes read as
   distinct from the (neutral) structural links above them. */
.holoblock{border-left:3px solid var(--amber)}
.holoblock .rref{margin-bottom:8px}
.holoblock .rref:last-child{margin-bottom:0}
/* Band header — flex-WRAP so title/subtitle/citation/buttons never overlap on
   narrow cards (unlike .ptitle). Title left, buttons pushed right, cite tucked
   before them; all drop to a new line rather than colliding. */
.bandhead{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--line)}
/* 13px, not 12: at 12px under fractional display scaling (e.g. Windows 110%)
   Michroma's thin N diagonal resamples away — "FORCE PRESENCE" read as
   "PRESEnCE". One px keeps every band head crisp. */
.bandhead .bh-title{font-family:var(--disp);font-size:13px;letter-spacing:.18em;text-transform:uppercase;color:var(--holo)}
.bandhead .bh-sub{font-size:10.5px;color:var(--holo-dim);letter-spacing:.06em}
.bandhead .bh-stat{font-size:11px;color:var(--amber);letter-spacing:.06em}
.bandhead .bh-cite{margin-left:auto}
.bandhead .bh-btns{display:flex;flex-wrap:wrap;gap:6px}
.bandhead .bh-cite + .bh-btns{margin-left:0}
/* Horizontal field row: fixed-width label + control that fills the rest, with
   the description clamp on its own line below. */
.bgfield{display:flex;flex-wrap:wrap;align-items:center;gap:6px 12px;margin:8px 0}
.bgfield .bgfield-lbl{flex:0 0 230px;white-space:nowrap;font-family:var(--disp);font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--holo-dim)}
.bgfield select{flex:1;min-width:180px}
.bgfield .bgfield-desc{flex-basis:100%}
/* Wrapping row of option checkboxes (Obligation/Duty creation options). */
.optrow{display:flex;flex-wrap:wrap;gap:6px 18px;margin:6px 0}
/* Auto-growing detail/story textarea — wraps and expands down; no scrollbar,
   no manual resize handle (JS sizes it to content via cgGrow). */
textarea.cggrow{width:100%;resize:none;overflow:hidden;min-height:38px;line-height:1.5}
/* See-more description clamp — fixed max-height when collapsed, NO scrollbar. */
.descmore{font-size:12.5px;line-height:1.6;margin-top:6px}
.descmore.clamp{max-height:88px;overflow:hidden;-webkit-mask-image:linear-gradient(180deg,#000 60%,transparent);mask-image:linear-gradient(180deg,#000 60%,transparent)}
.descmore-btn{margin-top:4px}

/* ---------- settings ---------- */
.setgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:calc(10px*var(--dsp))}
.themepick{border:1px solid var(--line);background:var(--panel2);padding:12px;cursor:pointer;text-align:left;display:flex;flex-direction:column}
.themepick:hover{border-color:var(--holo-dim)}
.themepick.on{border-color:var(--holo);box-shadow:inset 3px 0 0 var(--holo);background:var(--pick-on)}
.themepick .tpname{font-family:var(--disp);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink)}
.themepick .tpdesc{font-size:10.5px;color:var(--faint);margin-top:3px;line-height:1.5}
.themepick .tpsw{display:flex;gap:5px;margin-top:auto;padding-top:10px}
.themepick .tpsw i{width:18px;height:18px;border-radius:50%;border:1px solid rgba(0,0,0,.35);display:inline-block}
.moderow{display:flex;flex-wrap:wrap;gap:8px;margin-top:4px}
.symrow{display:flex;align-items:center;gap:12px;padding:7px 4px;border-bottom:1px solid var(--line)}
.symrow:last-child{border-bottom:none}
.symrow .sym{font-size:22px;width:30px;text-align:center}
.symrow .syname{flex:1;font-size:12.5px}
.symrow input[type=color]{width:44px;height:28px;padding:1px;background:var(--input-bg);border:1px solid var(--line);cursor:pointer}

/* ---------- Dice Calculator ---------- */
/* Mode switcher: current dice type on the left, a divider, then a single
   "Switch to … Dice" button — one row (never stacked like the old two tabs). */
.dmode{display:flex;align-items:center;gap:12px;margin-bottom:14px;flex-wrap:wrap}
.dmode-cur{font-family:var(--disp);font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--holo);white-space:nowrap}
.dmode-sep{width:1px;align-self:stretch;min-height:22px;background:var(--line)}
.dmode .btn{white-space:nowrap}
.die.lg{width:22px;height:22px;vertical-align:-5px}
.die.xl{width:34px;height:34px;vertical-align:middle}
.die.lg.df::after{inset:1.8px}
.die.xl.df::after{inset:2.5px}
.dice-cols{display:grid;grid-template-columns:5fr 6fr;gap:0 16px;align-items:start}
.dsec{font-family:var(--label);font-size:9px;letter-spacing:.24em;text-transform:uppercase;color:var(--faint);margin:14px 0 7px;padding-top:10px;border-top:1px solid var(--line)}
/* palette */
/* Three rows by role — positive (P/A/B), negative (C/D/S), Force centred on
   its own — instead of an auto-fill that stranded one die on a second row. */
.dpal{display:grid;grid-template-columns:repeat(3,84px);justify-content:center;gap:8px}
.dpal-btn{position:relative;display:flex;flex-direction:column;align-items:center;gap:5px;padding:8px 4px 7px;background:var(--panel2);border:1px solid var(--line);transition:background .12s,border-color .12s}
.dpal-btn:hover{background:var(--accent-wash-hover);border-color:var(--holo-dim)}
.dpal-add{background:transparent;border:none;cursor:pointer;padding:4px 12px;line-height:0}
.dpal-add:hover .die{filter:brightness(1.15)}
.dpal-add .sym{font-size:24px;line-height:1}
.dpal-n{position:absolute;top:13px;right:6px;font-family:var(--disp);font-size:11px;color:var(--holo);text-shadow:0 0 8px var(--glow)}
/* Labels read in --ink/--dim; the die colour rides an underline, because the
   darker die tokens (challenge, difficulty) failed contrast as text. */
.dpal-lbl{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);text-align:center;line-height:1.3;padding-bottom:2px;border-bottom:2px solid transparent}
.dlbl-p{border-bottom-color:var(--die-p)}.dlbl-a{border-bottom-color:var(--die-a)}.dlbl-b{border-bottom-color:var(--die-b)}
.dlbl-c{border-bottom-color:var(--die-c)}.dlbl-d{border-bottom-color:var(--die-d)}.dlbl-s{border-bottom-color:var(--die-s-edge)}.dlbl-f{border-bottom-color:var(--line)}
.dpal-mini{display:flex;gap:4px}
.dpal-mini button{width:24px;height:20px;font-size:11px;line-height:1;cursor:pointer;background:var(--btn-bg);border:1px solid var(--line);color:var(--dim)}
.dpal-mini button:hover{color:var(--holo);border-color:var(--holo-dim)}
.droll1-ic{display:inline-block}
.droll1:hover .droll1-ic{animation:dspin1 .5s ease-in-out}
@keyframes dspin1{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
.dpal-syms{grid-template-columns:repeat(4,70px)}
.dpal-syms .dpal-btn{padding:6px 3px}
/* current pool strip */
.dpool{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:center;min-height:48px;padding:13px 14px;border:1px dashed var(--line);background:var(--input-bg)}
.dpool .die{cursor:pointer;margin:0}
.dpool .die:hover{outline:2px solid var(--red);outline-offset:1px}
.dpool-empty{color:var(--faint);font-size:11.5px;letter-spacing:.06em}
.dlabel-in{width:100%;font-size:12px}
.dkeys{line-height:1.7}
/* fixed, flex-centered box — icon-font glyphs are vertically positioned by
   their own metrics and get visually cropped by line-height math (the
   webfont's private-use glyphs sit taller than a normal em box); centering
   with flex sidesteps that entirely instead of tuning line-height/padding. */
.dpool-sym{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;background:transparent;border:1px solid var(--line);cursor:pointer}
.dpool-sym .sym{font-size:15px;line-height:1}
.dpool-sym:hover{border-color:var(--red)}
.dsaved{display:flex;flex-wrap:wrap;gap:6px}
.dsaved-chip{display:inline-flex;border:1px solid var(--line);background:var(--panel2)}
.dsaved-chip button{background:transparent;border:none;cursor:pointer;font-size:11px;padding:5px 8px}
.dsaved-load{color:var(--holo)}
.dsaved-load:hover{background:var(--accent-wash-hover)}
.dsaved-del{color:var(--faint);border-left:1px solid var(--line)!important}
.dsaved-del:hover{color:var(--red)}
/* roll results */
.dfaces{display:flex;flex-wrap:wrap;justify-content:center;gap:7px;margin:4px 0 10px}
.dface{display:inline-flex;flex-direction:column;align-items:center;justify-content:space-between;gap:6px;border:1px solid var(--line);background:var(--panel2);padding:8px 8px 6px;min-width:48px;animation:droll .32s ease-out both}
.dface .sym{font-size:15px}
.dface-syms{line-height:1;min-height:16px;display:flex;align-items:center;gap:1px}
.dface-blank{color:var(--faint);font-size:13px;line-height:1}
.dface-fixed{border-style:dashed}
.dface-tag{font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--faint)}
.dnet{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:10px 12px;border:1px solid var(--line);background:var(--panel2)}
.dnet .sym{font-size:24px;margin:0 1px}
.dverdict{font-family:var(--disp);font-size:13px;letter-spacing:.18em;text-transform:uppercase;text-shadow:0 0 10px var(--glow)}
.dnet-text{font-size:11.5px;color:var(--dim);letter-spacing:.05em}
.nface{display:inline-flex;align-items:baseline;gap:3px;border:1px solid var(--holo-dim);color:var(--holo);font-family:var(--disp);padding:7px 9px 6px;font-size:15px;background:var(--panel2);animation:droll .32s ease-out both}
.nface .nd{color:var(--faint);font-size:8px;letter-spacing:.05em}
.nface.nmod{border-color:var(--line);color:var(--dim)}
.ntotal{font-family:var(--disp);font-size:30px;color:var(--holo);text-shadow:0 0 12px var(--glow)}
/* keyboard focus for the onclick-carrying pool dice + history rows (they are
   role="button" + KEYACT now); same ring recipe as the Codex-pass P2 block. */
.dpool .die:focus-visible,.dhrow:focus-visible{outline:2px solid var(--holo);outline-offset:1px}
/* dice settle: opacity in + a 2px drop, no rotate/scale overshoot (the system
   rule is "nothing bounces, nothing scales") */
@keyframes droll{0%{transform:translateY(-2px);opacity:0}100%{transform:none;opacity:1}}
/* history */
.dhist{max-height:360px;overflow-y:auto;margin-bottom:8px}
.dhrow{display:grid;grid-template-columns:104px 1fr auto auto;gap:8px;align-items:center;font-size:11px;border-bottom:1px solid var(--line);padding:6px 2px;color:var(--dim);cursor:pointer}
.dhacts{display:inline-flex;gap:2px;align-items:center}
.dhrow:hover{background:var(--accent-wash)}
.dhrow .dht{color:var(--faint);font-size:9.5px;letter-spacing:.04em;display:flex;flex-direction:column;gap:2px;align-items:flex-start}
.dhrow .die{width:11px;height:11px;margin:0 1px}
.dhrow .sym{font-size:12px}
.dhrow .dhx{background:transparent;border:none;color:var(--faint);cursor:pointer;font-size:12px;padding:2px}
.dhrow .dhx:hover{color:var(--red)}
.dhbadge{font-size:7.5px;letter-spacing:.14em;border:1px solid var(--line);color:var(--faint);padding:1px 4px;text-transform:uppercase}
/* where a roll came from (a crafting check, an acquisition, a crit table) —
   one truncated line; the full text is the row's title */
.dhbadge.dhsrc{display:block;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dhplus{color:var(--faint);font-size:10px}
/* statistics */
.dstat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));justify-content:center;gap:8px;margin-bottom:12px}
.dstat{border:1px solid var(--line);background:var(--panel2);text-align:center;padding:9px 4px}
.dstat .dv{font-family:var(--disp);font-size:18px;color:var(--holo)}
.dstat .dn{font-size:9.5px;letter-spacing:.14em;color:var(--faint);text-transform:uppercase;margin-top:4px;line-height:1.4}
.dstats-toggle{display:block;margin:4px auto 0}
/* side-by-side (equal-width columns) on desktop; rows are narrowed so both
   columns stay legible inside the (fairly narrow) left dice-cols panel.
   Falls back to stacked below 700px, where even a narrowed pair is too tight. */
.dhists{display:grid;grid-template-columns:1fr 1fr;align-items:start;gap:10px 16px;margin-top:12px;min-width:0}
.dhists h5{font-family:var(--label);font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:var(--faint);margin-bottom:5px;text-align:center}
.dhists .hrow{grid-template-columns:32px 1fr 34px;gap:4px;font-size:9.5px}
.dhists .hrow.zero span:first-child{color:var(--holo)}
@media(max-width:420px){.dhists{grid-template-columns:1fr}}
.dpresets{display:flex;flex-wrap:wrap;justify-content:center;gap:8px}
/* "Send to Dice Calculator" button on skill/weapon/NPC roll lines — compact,
   sits inline beside the pool preview. */
.dsend{padding:0 8px;line-height:1.7;vertical-align:middle}
/* numeric tab */
.dnum-one{margin-top:7px;background:transparent;border:1px solid var(--line);color:var(--dim);font-size:9px;letter-spacing:.12em;text-transform:uppercase;padding:3px 8px;cursor:pointer;display:inline-flex;align-items:center;gap:4px}
.dnum-one:hover{color:var(--holo);border-color:var(--holo-dim)}
.btn:disabled{opacity:.4;cursor:default}
.btn:disabled:hover{background:var(--btn-bg)}
@media (max-width:900px){.dice-cols{grid-template-columns:1fr}}

@media print{ #hdr,.cgrail,.cgsteps,body::after,html::after{display:none!important} .panel{break-inside:avoid} body{background:#fff;color:#000} }

::-webkit-scrollbar{width:9px;height:9px}
/* ticked rail: the track carries faint accent tick marks so it reads as
   instrumentation rather than a browser affordance */
::-webkit-scrollbar-track{background:var(--scroll-track);
  background-image:repeating-linear-gradient(to bottom, transparent 0 9px, var(--holo-dim) 9px 10px)}
::-webkit-scrollbar-thumb{background:var(--scroll-thumb); border:2px solid var(--scroll-track)}
::-webkit-scrollbar-thumb:hover{background:var(--holo-dim)}
@media (max-width:860px){
  /* topnav collapses to a slim bar; the nav panel drops down from under it */
  #topnav{padding:5px 10px; gap:8px; row-gap:6px}
  /* a SQUARE tap target: as a plain .btn the coarse-pointer min-height:40px made
     the ☰ a tall narrow sliver next to the ~20px logo and ~23px Back */
  /* order:9 parks it at the far right of the bar — the search grows into the
     middle, so margin-left:auto alone left it stranded beside the logomark */
  #menuToggle{display:inline-flex; align-items:center; justify-content:center; order:9;
    margin-left:auto; flex:0 0 auto; width:38px; height:30px; min-height:0; padding:0; font-size:15px; line-height:1}
  #navPanelWrap{display:block; position:absolute; top:100%; left:0; right:0;
    overflow:hidden; pointer-events:none; z-index:5}
  /* The panel must never be taller than what is actually ON SCREEN, or its last
     cluster (Settings → Guide → About) sits under the browser chrome with no way
     to scroll to it. 100vh is the LARGE viewport on iOS Safari — it does not
     shrink for the toolbars — so the dvh unit is the fix, with vh as the
     fallback; the header height comes from --hdr-h rather than a magic 90px. */
  #navGroups{display:block; transform:translateY(-102%); transition:transform .2s;
    pointer-events:auto; background:linear-gradient(180deg,var(--sb-a),var(--sb-b));
    border-bottom:1px solid var(--sb-line); box-shadow:0 12px 30px rgba(0,0,0,.5);
    padding:8px 10px; max-height:calc(100vh/var(--ts-zoom,1) - var(--hdr-h,56px) - 8px); overflow-y:auto;
    -webkit-overflow-scrolling:touch; overscroll-behavior:contain}
  @supports (height:100dvh){
    #navGroups{max-height:calc(100dvh/var(--ts-zoom,1) - var(--hdr-h,56px) - 8px)}
  }
  /* A tail of scrollable empty space under the last cluster. This is the
     belt-and-braces for the About-is-unreachable bug: even if every height
     signal misreports (Android reports env(safe-area-inset-bottom) as 0 while
     a standalone PWA sits edge-to-edge behind the system nav bar), scrolling
     to the end now lifts the final row well clear of any bottom obstruction.
     Costs nothing when the panel IS sized right — it is just empty space at
     the end of a scroll. */
  /* 88px covers a real Android system nav bar (~48px three-button, more on
     some Samsung skins) with room to spare; a 56px tail left the last row
     ~13px short against a tall bar. */
  #navGroups{padding-bottom:88px}
  @supports (padding:env(safe-area-inset-bottom)){
    #navGroups{padding-bottom:calc(88px + env(safe-area-inset-bottom))}
  }
  #topnav.open #navGroups{transform:none}
  /* dropdown panel: categories stack, each an inline header over a button grid
     (the desktop hover/pin dropdown mechanics don't apply here) */
  .navcluster{display:block; padding:2px 0}
  .navcluster .navgrp{display:block; width:100%; text-align:left; padding:6px 2px 2px;
    color:var(--sb-faint); font-size:8.5px; letter-spacing:.28em; cursor:default}
  .navcluster:hover>.navgrp,.navcluster.pinned>.navgrp{background:none; color:var(--sb-faint); border-color:transparent}
  .navmenu,.navcluster:hover>.navmenu,.navcluster.pinned>.navmenu{display:grid; position:static;
    grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:4px; min-width:0;
    background:none; border:none; box-shadow:none; padding:0}
  .navbtn{white-space:normal}
  /* solo entries have no dropdown — in the panel they stay tappable, styled
     like a navbtn (the generic rules above turn .navgrp into an inert header) */
  .navcluster.solo .navgrp,.navcluster.solo:hover>.navgrp{display:flex; align-items:center; gap:6px;
    cursor:pointer; color:var(--sb-dim); font-family:var(--body); font-size:12px;
    letter-spacing:.03em; text-transform:none; padding:calc(5px*var(--dsp)) 9px;
    border:1px solid transparent;
    clip-path:polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px))}
  .navcluster.solo .navgrp.active,.navcluster.solo:hover>.navgrp.active{background:var(--sb-active);
    color:var(--sb-accent); border-color:var(--sb-accent-dim); box-shadow:inset 2px 0 0 var(--sb-accent)}
  /* mobile: hug the screen edge — ~5px between content and the border */
  #content{padding:12px 5px}
  .panel{padding-left:calc(10px*var(--dsp)); padding-right:calc(10px*var(--dsp));
    padding-top:max(14px, calc(14px*var(--dsp))); padding-bottom:max(14px, calc(14px*var(--dsp)))}
  /* chargen: short sticky rail that floats in place while the page scrolls;
     tapping it flies the step list out */
  .cgwrap{gap:8px}
  /* whole rail is the tap target (onclick on the div); the ▸ is just a glyph */
  .cgrail{display:flex; position:sticky; top:calc(var(--hdr-h,52px) + 8px); z-index:5;
    flex:0 0 14px; align-self:flex-start; height:120px; align-items:center; justify-content:center;
    border:1px solid var(--line); background:var(--panel2); cursor:pointer}
  .cgrail-btn{color:var(--holo); font-size:12px; line-height:1; pointer-events:none}
  /* Grow the touch hit area to the app's 44px coarse-pointer floor WITHOUT
     widening the 14px visual spine: a transparent overlay extends the clickable
     zone left (into empty panel padding / the screen edge) and a little into the
     8px gap. It belongs to .cgrail, so a tap on it still fires the div's onclick;
     -6px stays inside the gap, so it never steals a click from the content. */
  .cgrail::after{content:""; position:absolute; top:0; bottom:0; left:-24px; right:-6px}
  .cgwrap.navopen .cgscrim{display:block; position:fixed; inset:0; z-index:69; background:rgba(0,0,0,.45)}
  .cgsteps{position:fixed; left:0; top:0; bottom:0; z-index:70; width:min(250px,78vw);
    transform:translateX(-102%); transition:transform .18s;
    background:linear-gradient(180deg,var(--sb-a),var(--sb-b));
    border-right:1px solid var(--sb-line); box-shadow:8px 0 30px rgba(0,0,0,.6);
    overflow-y:auto; padding:12px 10px}
  .cgwrap.navopen .cgsteps{transform:none}
  .cgsteps-close{display:block; align-self:flex-end; background:transparent;
    border:1px solid var(--line); color:var(--dim); cursor:pointer; padding:2px 8px; margin-bottom:6px}
  .formgrid{grid-template-columns:1fr}
  #stardate{display:none}
  /* single chrome bar on mobile: the control group dissolves so back/search
     lay out directly in the bar (crumb yields to the search box) */
  #topctrl{display:contents}
  #crumb{display:none}
  /* The wordmark is the space hog (~166px) and the logomark already brands the
     bar; dropping the text is what gives the search room to be usable. */
  .logo>span:not(.logomark){display:none}
  .logo{gap:0}
  /* flex-basis makes the wrap decision for us: the field stays inline and GROWS
     into whatever the bar has spare (~143px at 320, ~198px at 375, ~213px at
     390), and only wraps to its own full-width row on something narrower than
     ~300px. It was being crushed to 47px on a 375px phone. Basis is 120 rather
     than 160 so a 320px screen still gets ONE 55px bar instead of a 89px
     two-row bar with the ☰ stranded alone on the second line. */
  #gsWrap{width:auto; flex:1 1 120px; min-width:0}
  #backBtn{flex:0 0 auto}
  .redit{grid-template-columns:1fr}
  .redit-side{position:static}
  .redit-row{grid-template-columns:1fr 1fr}
  /* Rule rows stay ONE line on a phone: the title wraps, the three icon
     buttons (★ / ◇ / ⋯) never do — the Pin label drops to its glyph. */
  .rule-top{align-items:flex-start}
  .rule-btxt{display:none}
}

/* ── PWA update banner (Phase 2) ─────────────────────────────── */
#swUpdate{
  position:fixed; left:50%; bottom:calc(16px + env(safe-area-inset-bottom));
  transform:translateX(-50%); z-index:9999;
  display:flex; align-items:center; gap:12px;
  padding:10px 34px 10px 16px; /* right: room for the corner ✕ */
  background:linear-gradient(180deg,var(--panel-a),var(--panel-b));
  border:1px solid var(--holo-dim); border-radius:8px;
  box-shadow:0 0 18px var(--glow), 0 6px 20px var(--shadow);
  color:var(--ink); font-family:var(--body); font-size:13px;
  /* max-content: shrink-to-fit would stop at 50vw (left:50%) and wrap early */
  width:max-content; max-width:calc(100vw - 24px); box-sizing:border-box;
}
#swUpdate[hidden]{display:none}
/* The message may wrap; the two buttons never shrink — a nowrap message used
   to push the dismiss control off the right edge of a phone screen. */
#swUpdate span{color:var(--holo); flex:1 1 auto; min-width:0}
#swUpdate button{flex:0 0 auto; white-space:nowrap}
#swUpdate button{
  font-family:var(--body); font-size:12px; cursor:pointer;
  border:1px solid var(--holo-dim); border-radius:6px;
  background:var(--btn-bg); color:var(--ink); padding:6px 12px;
}
#swUpdate #swReload{border-color:var(--holo); color:var(--holo)}
#swUpdate #swReload:hover{background:var(--hover-strong)}
#swUpdate #swDismiss{position:absolute; top:3px; right:3px; padding:2px 6px; border:0; background:none;
  color:var(--faint); font-size:12px; line-height:1}
#swUpdate #swDismiss:hover{color:var(--ink)}

/* Compact-sheet options dialog (screen modal; uses theme tokens) */
.prcoptgrid{display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:10px}
.prcoptcol{min-width:0}
.prcoptlist{max-height:128px; overflow:auto; border:1px solid var(--line); border-radius:5px; padding:5px 7px; margin-top:3px}
.prcopt{display:flex; gap:7px; align-items:center; font-size:11.5px; padding:2px 0; cursor:pointer}
.prcopt input{accent-color:var(--holo); flex:none}
.princl{display:flex;gap:8px;align-items:center;padding:4px 0;cursor:pointer}
.princl span{font-size:12px}.princl small{color:var(--faint);font-size:10px;margin-left:4px}
.princl.disabled{opacity:.45;cursor:default}

/* =====================================================================
   PHASE 7 PRINT SHEET (js/print.js)
   A fixed, theme-INDEPENDENT Star Wars print stylesheet. The print doc is
   built into #printDoc (a body sibling), hidden on screen, and revealed only
   under @media print via body.printing. Two ink variants ride a class on
   #printDoc: .ink-color / .ink-clean (default). (The retired Simplified /
   grayscale mode is coerced to Clean by prNormStyle; see js/print.js.)
   ===================================================================== */
#printDoc{display:none}
#printDoc.ink-color{background-color:#fff!important}
#printDoc.ink-color .prpage{background-color:#fff!important}

/* ========================= PRINT CENTER =========================
   A workbench, separate from the owned-sourcebook PDF Library.  Selection is
   intentionally session-only; only presentation preferences persist. */
.pc-shell{max-width:1280px;margin:0 auto}.pc-head{display:flex;justify-content:space-between;align-items:flex-end;gap:16px;margin-bottom:14px}.pc-head .smallnote{max-width:620px}.pc-tabs{display:flex;gap:5px;flex-wrap:wrap;margin:10px 0 14px}.pc-tabs button{min-width:150px}.pc-tabs button.on{border-color:var(--holo);background:var(--accent-wash);color:var(--holo)}
.pc-grid{display:grid;grid-template-columns:minmax(230px,.55fr) minmax(320px,1.15fr) minmax(230px,.65fr);gap:12px;align-items:start}.pc-panel{border:1px solid var(--line);background:var(--panel);padding:12px}.pc-panel h3{margin:0 0 8px;font:700 11px var(--disp);letter-spacing:.12em;color:var(--holo);text-transform:uppercase}.pc-group{display:flex;width:100%;justify-content:space-between;gap:8px;text-align:left;border:1px solid var(--line);background:transparent;color:var(--ink);padding:8px 9px;margin:4px 0}.pc-group.on{border-color:var(--holo);background:var(--accent-wash)}.pc-group b{font-size:12px}.pc-group small,.pc-meta{color:var(--faint);font-size:10px}.pc-group small{display:block;margin-top:2px}.pc-tools{display:grid;gap:7px;margin-bottom:10px}.pc-tools input,.pc-tools select{width:100%}.pc-actions{display:flex;gap:5px;flex-wrap:wrap}.pc-list{max-height:570px;overflow:auto;border-top:1px solid var(--line)}.pc-row{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;padding:8px 3px;border-bottom:1px solid var(--line)}.pc-row input{width:15px;height:15px;accent-color:var(--holo)}.pc-row b{font-size:12px}.pc-row small{display:block;color:var(--faint);font-size:10px;margin-top:2px}.pc-summary{display:grid;gap:9px}.pc-count{font:700 28px var(--disp);color:var(--amber)}.pc-playcards{display:grid;gap:8px}.pc-playcard{border:1px solid var(--line);padding:10px;background:var(--panel2)}.pc-playcard b{display:block;font-size:13px}.pc-playcard small{display:block;color:var(--faint);margin:3px 0 8px;font-size:10px}@media(max-width:920px){.pc-grid{grid-template-columns:1fr}.pc-list{max-height:360px}}

/* Takeover rules (media-scoped). Everything else below targets #printDoc, which
   is display:none on screen unless previewing, so it never affects the app. */
@media print{
  @page{size:letter portrait;margin:8mm}
  /* printing directly OR from the on-screen preview both hand the page to #printDoc */
  body.printing>*:not(#printDoc), body.prpreview>*:not(#printDoc){display:none!important}
  body.printing #printDoc, body.prpreview #printDoc{display:block!important}
  body.printing, body.prpreview{background:#fff!important; overflow:visible!important}
  #prPrevBar{display:none!important}
  /* neutralize the screen-preview page framing so print uses @page geometry */
  body.prpreview #printDoc{margin:0!important; width:auto!important}
  body.prpreview .prpage{width:auto!important; min-height:0!important; margin:0!important; padding:0!important; box-shadow:none!important}
  #printDoc .prpage{height:263.4mm}
  /* Dossier catalog pages carry a decorative perimeter frame, so their content
     must stay INSIDE it. Keep frame-clearing padding on paper — this MUST be
     !important to beat the `padding:0!important` neutraliser above (an !important
     declaration wins over a non-important one regardless of specificity). The
     preview uses the identical box (see @media screen), so the frame + measured
     connectors land the same on screen and on paper. Without this, print stripped
     the padding and the frame sliced across the content edges. */
  #printDoc .prpage.prdossier{padding:24px 24px 20px!important}
}
@media screen{
  /* On-screen preview (App.prPreview): stacked letter-sized pages + close bar. */
  body.prpreview>*:not(#printDoc):not(#prPrevBar):not(#prComposing){display:none!important}
  body.prpreview{overflow:auto; background:#464b52!important}
  /* Transient "Composing…" pill during a scratch-composition override build —
     survives the body.prpreview hide-all so a refresh (ink/theme switch) under
     an override still shows it. */
  #prComposing{position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
    z-index:10000; background:#161a1e; color:#f4f8fb; padding:12px 22px; border-radius:8px;
    font-size:13px; letter-spacing:.06em; box-shadow:0 4px 18px rgba(0,0,0,.5)}
  body.prpreview #printDoc{display:block!important; margin:62px auto 40px; width:816px}
  body.prpreview .prpage{width:816px; height:1040px; min-height:1040px; overflow:hidden; background:#fff!important; box-shadow:0 3px 16px rgba(0,0,0,.45); padding:26px 30px 20px}
  #prPrevBar{position:fixed; top:0; left:0; right:0; height:44px; z-index:9999; background:#161a1e; color:#f4f8fb;
    align-items:center; gap:14px; padding:0 16px; font-size:13px; display:none; box-shadow:0 2px 8px rgba(0,0,0,.4)}
  body.prpreview #prPrevBar{display:flex}
  /* Off-screen layout pass for direct Print / Save-PDF (prRender): #printDoc is
     display:none on screen, so pagination + connector measurement need it laid
     out. Park it off-canvas with the SAME letter-page geometry as preview (its
     ~728px content width matches the print sheet, so the measured SVG lines
     survive the switch to print media) — nothing flashes on screen. */
  body.proffscreen #printDoc{display:block; position:fixed; left:-10000px; top:0; margin:0; width:816px; z-index:-1}
  body.proffscreen .prpage{width:816px; height:1040px; min-height:1040px; overflow:hidden; background:#fff; padding:26px 30px 20px}
  /* Dossier catalog pages preview at the SAME box the printer uses — the @page
     content area (~756px at 8mm margins) × 263.4mm, with padding that clears the
     perimeter frame — so what you see is what prints (content stays inside the
     frame, connectors measured here line up on paper). Centered in the wider
     #printDoc column. */
  body.prpreview .prpage.prdossier, body.proffscreen .prpage.prdossier{
    width:756px; height:996px; min-height:996px; padding:24px 24px 20px; margin-left:auto; margin-right:auto}
  /* Character sheets preview at the EXACT printed box, the same fix the dossier
     uses one rule up: the @page content area (~756px at 8mm margins) × 263.4mm.
     Before this the sheet used the generic 816×1040 preview box while print used
     756×996 — so print was 60px narrower and 44px shorter, content that fit the
     preview overflowed the page (frame + borders sliced off), and tree
     connectors measured in the 816 offscreen box landed detached when printed at
     756. Gutters are margin-based (see .prsheet block), so padding stays 0 and
     matches print; only the box dimensions move here. */
  body.prpreview .prpage.prsheet, body.proffscreen .prpage.prsheet{
    width:756px; height:996px; min-height:996px; padding:0; margin-left:auto; margin-right:auto}
}

  /* ---- print palette (does NOT read the screen theme) ----
     TOKEN CONTRACT — the rule that keeps Clean ink working, and the rule a
     book theme has to respect:
       --pr-accent  structural PRIMARY. Bands, hairlines, tile outlines, hex
                    badges, tree connector stroke. Must stay dark enough for
                    white text on top of it.
       --pr-accent2 DECORATIVE ONLY. Page rail gradient end, raised-characteristic
                    marker, corner ticks. NEVER text, never a fill behind text —
                    two of the five book pairs have a red accent2, which would
                    read as "injured" beside --pr-danger.
       --pr-danger  stays theme-INVARIANT, so an injured characteristic never
                    reads as "this character is themed Edge of the Empire".
     Ink modes: `.ink-clean` sweeps `background-color:transparent!important`
     (bottom of this block) but does NOT touch background-image. So anything
     Clean should DROP is authored as a background-color (tile caption washes,
     table-header tints, callout fills); anything Clean must KEEP is authored as
     a gradient/background-image (the accent rail, hex chevrons, write-lines,
     corner ticks). Where a gradient must also drop, kill it explicitly with
     `background-image:none!important` — the idiom .prdossier already uses. */
  #printDoc{
    --pr-ink:#161a1e; --pr-faint:#5a636b; --pr-line:#c3ccd4;
    --pr-accent:#23527a;            /* Star Wars steel-blue (Holo default below) */
    --pr-accent2:#3fb0c8;           /* decorative only — see the contract above */
    --pr-accent-grey:#8fa1ab;       /* chamfered-tile outer edge */
    --pr-danger:#9c2f27;            /* injury / theme-invariant */
    --pr-accent-ink:#f4f8fb; --pr-yellow:#e8b923;
    --pr-band:#23527a; --pr-tint:#eef3f8; --pr-page:#ffffff;
    --pr-zebra:rgba(20,26,30,.045);   /* alternating-row wash; theme-neutral, drops in Clean */
    /* Typography is PINNED here, not inherited. #printDoc used to read the
       ambient var(--disp)/var(--body), which css/themes.css redefines per book
       theme — so printed output silently changed font with the screen theme and
       depended on a live Google Fonts fetch despite this being an offline-first
       PWA. These three faces are self-hosted (@font-face at the top of this
       file) and apply to every print family. */
    --disp:'Oxanium',sans-serif; --body:'Chakra Petch',sans-serif;
    --label:'Chakra Petch',sans-serif; --mono:'Share Tech Mono',ui-monospace,monospace;
    /* result symbols → dark, readable on white (mirrors the .tbody treatment) */
    --sym-su:#101418; --sym-ad:#101418; --sym-fa:#101418; --sym-th:#101418;
    --sym-de:#9c2f27; --sym-tr:#8a6412; --sym-cf:#101418; --sym-rp:#101418;
    --sym-li:#fff; --sym-li-stroke-w:1px; --sym-li-stroke-c:#000;
    --sym-da:#000; --sym-da-stroke-w:0; --sym-da-stroke-c:transparent;
    /* pin tokens the tree grid / cite chips read that are otherwise themed on
       screen (owned-node border, citation-chip color) so print never inherits
       the live theme's value. --force-green is already fixed in :root. */
    --green:#2f7d4f; --amber:#8a6412;
    color:var(--pr-ink); background:var(--pr-page);
    font-family:var(--body,system-ui,sans-serif); font-size:10px; line-height:1.35;
    -webkit-print-color-adjust:exact; print-color-adjust:exact;
  }
  #printDoc *{-webkit-print-color-adjust:exact; print-color-adjust:exact; box-sizing:border-box}

  /* ---- book-theme accent pairs (print dialog; default Holo) ----
     Only the two accents, the band and the tint move. Everything else in the
     palette — ink, faint, line, danger, yellow, page, and every pinned
     dice/symbol/card-stock color — is deliberately theme-invariant.
     .prdossier (the Print Center catalog skin) redefines --pr-* at the PAGE
     level, which beats these container-level rules, so catalog packets keep
     their fixed cyan-steel look whatever book theme is selected. */
  #printDoc.prtheme-holo{--pr-accent:#0f5570;--pr-accent2:#3fb0c8;--pr-band:#0f5570;--pr-tint:#eef3f6}
  #printDoc.prtheme-eote{--pr-accent:#4e6034;--pr-accent2:#8e1f1c;--pr-band:#4e6034;--pr-tint:#f1f3ec}
  #printDoc.prtheme-aor {--pr-accent:#095668;--pr-accent2:#8f4014;--pr-band:#095668;--pr-tint:#ecf2f4}
  #printDoc.prtheme-fad {--pr-accent:#8f2b28;--pr-accent2:#665025;--pr-band:#8f2b28;--pr-tint:#f6eeed}
  #printDoc.prtheme-core{--pr-accent:#63501a;--pr-accent2:#075363;--pr-band:#63501a;--pr-tint:#f4f1e8}

  /* ---- encounter tracker sheet (E4) ---- */
  #printDoc .prtrkgrid{display:inline-flex; flex-wrap:wrap; gap:2px; margin:2px 6px 2px 0; vertical-align:middle}
  #printDoc .prtrkb{width:11px; height:11px; border:1px solid var(--pr-ink); display:inline-block; background:#fff}
  #printDoc .prtrkb.unitend{margin-right:5px}
  #printDoc .prtrkrem{width:11px; height:11px; border:1px solid var(--pr-ink); border-radius:50%; display:inline-block; background:#fff}
  #printDoc .prtrkd{width:9px; height:9px; border:1.5px solid var(--pr-ink); display:inline-block; margin-right:1px; vertical-align:middle}
  #printDoc .prtrkd.bo{border-color:#2d7dd2; background:#d8e9f8}
  #printDoc .prtrkd.se{border-color:#101418; background:#dcdcdc}
  #printDoc .prtrkd.di{border-color:#5c2d91; background:#e8ddf3}
  #printDoc .prtrkdl{font-size:8px; letter-spacing:.08em; text-transform:uppercase; color:var(--pr-faint); margin:0 4px 0 2px; vertical-align:middle}
  #printDoc .prtrkt{font-size:8.5px; color:var(--pr-faint); font-weight:normal}
  #printDoc .prtrkrow{display:flex; align-items:center; gap:6px; flex-wrap:wrap; border-bottom:1px solid var(--pr-line); padding:4px 0; break-inside:avoid}
  #printDoc .prtrkname{font-weight:bold; min-width:150px; font-size:10.5px}
  #printDoc .prtrkline{display:inline-block; min-width:130px; border-bottom:1px solid var(--pr-ink)}
  #printDoc .prtrkcrit{font-family:monospace; letter-spacing:1px; margin-right:8px; font-size:10px}
  #printDoc .prtrkkey{margin-top:8px; padding:4px 6px; background:var(--pr-tint); font-size:8.5px}
  #printDoc .prtrkinit{width:100%; border-collapse:collapse; font-size:9.5px; margin:3px 0 6px}
  #printDoc .prtrkinit th{background:var(--pr-band); color:var(--pr-accent-ink); padding:2px 5px; text-align:left; font-size:8px; letter-spacing:.08em; text-transform:uppercase}
  #printDoc .prtrkinit td{border-bottom:1px solid var(--pr-line); padding:2px 5px}
  #printDoc .prtrkinit .prtrkcell{width:34px; border:1px solid var(--pr-line)}
  #printDoc .prnotes{font-size:10px; line-height:1.5}
  #printDoc .prnotes ul, #printDoc .prnotes ol{margin:2px 0 2px 16px}
  #printDoc a{color:inherit; text-decoration:none}
  /* interactive residue from reused renderers — never show in print */
  #printDoc .tcard-acts,#printDoc .tcard-act,#printDoc button{display:none!important}

  .prpage{break-after:page; page-break-after:always; position:relative; padding-bottom:14px; min-height:0; display:flex; flex-direction:column; overflow:hidden}
  .prpage:last-child{break-after:auto; page-break-after:auto}
  #printDoc tr,#printDoc .prcard,#printDoc .prmed{break-inside:avoid; page-break-inside:avoid}
  .prbody{flex:1;min-height:0;overflow:hidden}

  /* header band */
  .prhead{border:1.5px solid var(--pr-accent); border-radius:0; padding:6px 9px; margin-bottom:8px;clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px))}
  .prhrow{display:flex; gap:8px; align-items:baseline; padding:2px 0; border-bottom:1px dotted var(--pr-line)}
  .prhrow:last-child{border-bottom:none}
  .prhk{font-family:var(--disp,inherit); font-size:8px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint); min-width:80px}
  .prhv{font-size:11px; font-weight:600}
  .prhv.prhname{font-size:16px; letter-spacing:.02em}
  .prfoot{position:absolute; left:0; right:0; bottom:-2px; display:flex; justify-content:space-between;
    font-size:7.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--pr-faint); border-top:1px solid var(--pr-line); padding-top:3px}

  /* section band */
  .prband{background:var(--pr-band); color:var(--pr-accent-ink); font-family:var(--disp,inherit);
    font-size:9.5px; letter-spacing:.18em; text-transform:uppercase; padding:3px 8px; border-radius:0;clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,6px 100%,0 calc(100% - 6px));
    margin:9px 0 5px; border-bottom:2px solid var(--pr-yellow)}
  .prsection{break-inside:auto;position:relative}
  .prsection>.prband:first-child{break-after:avoid;page-break-after:avoid}
  .prsectioncontinued>.prband{opacity:.86}
  .prdense .prbody{font-size:9px;line-height:1.28}.prdense .prtree{transform:scale(.92);width:108.7%}
  .prsource{font-size:7px;color:var(--pr-faint);font-weight:400;text-transform:none;letter-spacing:0}
  .prsourceblank{white-space:nowrap}
  .prwritelines{height:calc(var(--pr-lines,6) * 17px);background:repeating-linear-gradient(to bottom,transparent 0,transparent 16px,var(--pr-line) 16px,var(--pr-line) 17px)}
  .prstatus{display:inline-block;border:1px solid var(--pr-accent);color:var(--pr-accent);font-size:6px;letter-spacing:.08em;padding:0 3px;margin-left:3px}
  .prwrite td{height:18px}
  .prcritmeta{font-size:8px;color:var(--pr-faint);text-align:right;margin-bottom:2px}
  .prcrittbl,.prvtbl{width:100%;border-collapse:collapse;margin-bottom:5px}
  .prcrittbl th,.prvtbl th{background:var(--pr-tint);color:var(--pr-faint);font-size:7px;letter-spacing:.09em;text-transform:uppercase;text-align:left;padding:2px 5px;border-bottom:1px solid var(--pr-accent)}
  .prcrittbl td,.prvtbl td{font-size:8.5px;padding:2px 5px;border-bottom:1px solid var(--pr-line);vertical-align:top}

  /* characteristic medallions */
  .prmeds{display:grid; grid-template-columns:repeat(6,1fr); gap:6px; text-align:center}
  .prmed{border:2px solid var(--pr-accent); border-radius:50%; aspect-ratio:1/1; display:flex; flex-direction:column;
    align-items:center; justify-content:center; padding:2px}
  .prmed.hurt{border-color:var(--pr-danger)}
  .prmedv{font-size:20px; font-weight:800; line-height:1}
  .prmedl{font-size:6.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--pr-faint); margin-top:1px}

  /* attribute + stat boxes */
  .prattrs,.prequiphead{display:grid; grid-template-columns:repeat(6,1fr); gap:6px}
  .prequiphead{grid-template-columns:repeat(2,1fr); max-width:320px}
  .prstat{border:1px solid var(--pr-line); border-radius:3px; padding:4px 6px; text-align:center; background:var(--pr-tint)}
  .prstatl{font-size:6.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--pr-faint)}
  .prstatv{font-size:15px; font-weight:800}
  .prthr{font-weight:800} .prcur{display:inline-block; min-width:20px; border-bottom:1px solid var(--pr-line)}

  /* skills */
  .prskgrid{display:grid; grid-template-columns:1fr 1fr; gap:10px; align-items:start}
  .prsktbl,.prwtbl,.prtaltbl,.preqtbl{width:100%; border-collapse:collapse; margin-bottom:6px}
  .prsktbl th,.prwtbl th,.prtaltbl th,.preqtbl th{background:var(--pr-tint); color:var(--pr-faint);
    font-family:var(--disp,inherit); font-size:7px; letter-spacing:.1em; text-transform:uppercase; text-align:left; padding:2px 5px; border-bottom:1px solid var(--pr-accent)}
  .prsktbl td,.prwtbl td,.prtaltbl td,.preqtbl td{padding:2px 5px; border-bottom:1px solid var(--pr-line); vertical-align:top; font-size:9px}
  .prsk{white-space:nowrap} .prskc{color:var(--pr-faint); font-size:7.5px} .prskr{text-align:center; width:30px; font-weight:700}
  /* career-skill column: a tick in its own column, like the printed sheet's CR */
  .prskcr{width:16px; text-align:center; color:var(--pr-accent); font-weight:700}
  /* Column positions are LOCKED across every skill table on the page: with
     table-layout:fixed the first row's widths win, so General / Combat /
     Knowledge / Custom all place CR, name, Rank and Dice Pool at the same x
     instead of each sizing to its own longest skill name. The pool column takes
     the remainder and is allowed to spill past its track (a long pool is wider
     than any sane fixed width, and clipping dice would lose information). */
  .prsheet .prsktbl{table-layout:fixed}
  .prsheet .prsktbl th.prsk,.prsheet .prsktbl td.prsk{width:112px}
  .prsheet .prsktbl th.prskp,.prsheet .prsktbl td.prskp{width:auto; overflow:visible; white-space:nowrap}

  /* ---- visual depth: levels, shading, framed groups ----
     The sheet is dense and every block read the same; this gives it the layered
     look of the printed reference. Two tiers, chosen so the mode split makes
     sense: STRUCTURE (borders, drop-shadows, the career-row accent bar) is
     authored to survive the Clean-ink sweep; SHADING (zebra rows, the career
     wash) is a background-color, so it enriches Full Color and drops in Clean,
     which is exactly the ink-saver's job. */
  /* framed skill groups — each reads as its own panel/level */
  .prsheet .prsktbl{border:1px solid var(--pr-line); border-top:2px solid var(--pr-accent)}
  /* zebra striping on the data tables (header is row 1 / odd, so bodies alternate) */
  .prsheet .prsktbl tr:nth-child(even)>td,
  .prsheet .preqtbl tr:nth-child(even)>td,
  .prsheet .prtaltbl tr:nth-child(even)>td{background-color:var(--pr-zebra)}
  /* career skills stand out — a theme wash PLUS a Clean-surviving accent bar on
     the CR cell, so the row is distinguishable even with the wash stripped */
  .prsheet .prsktbl tr.career>td{background-color:var(--pr-tint)}
  .prsheet .prsktbl tr.career>td.prskcr{box-shadow:inset 2px 0 0 var(--pr-accent)}
  /* chamfered tiles + characteristic hexes + section badges lift off the page.
     drop-shadow (not box-shadow) follows the clip-path silhouette, prints, and
     survives Clean — a light, ink-cheap sense of level. */
  .prsheet .prtile,.prsheet .prhexc{filter:drop-shadow(0 1px 1px rgba(0,0,0,.17))}
  .prsheet .prband.prhex .prhexb{filter:drop-shadow(0 1px .8px rgba(0,0,0,.22))}
  .prc{text-align:center} .prq{font-size:8px; color:var(--pr-faint)}
  .prskp .die,.prskp .sym,.prwtbl .die{vertical-align:-2px}

  /* description / social */
  .prdesc{display:grid; grid-template-columns:130px 96px 1fr; gap:10px; align-items:start}
  .prbiocol .prbio{display:flex; gap:6px; padding:2px 0; border-bottom:1px dotted var(--pr-line); font-size:9.5px}
  .prport{width:96px; height:120px; object-fit:cover; object-position:top; border:2px solid var(--pr-accent); border-radius:4px}
  .prportempty{display:flex; align-items:center; justify-content:center; font-size:34px; color:var(--pr-line); background:var(--pr-tint)}
  .prsocial{font-size:9.5px; line-height:1.45} .prsocial p{margin:2px 0}
  .prcard{border:1px solid var(--pr-line); border-radius:4px; padding:6px 8px; margin:5px 0; font-size:9.5px; line-height:1.4}
  .prcardh{font-family:var(--disp,inherit); font-size:8.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--pr-accent); margin-bottom:3px; font-weight:700}
  .prstory{font-size:9.5px; line-height:1.5; text-align:justify}
  .prnote{font-size:9px; color:var(--pr-faint); font-style:italic; padding:2px 0}

  /* XP boxes */
  .prxp{display:grid; grid-template-columns:repeat(5,1fr); gap:6px; margin-bottom:4px}
  .prxpbox{border:1px solid var(--pr-line); border-radius:3px; text-align:center; padding:4px}
  .prxpl{font-size:6.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--pr-faint)}
  .prxpv{font-size:16px; font-weight:800} .prxpwrite .prxpv{border-bottom:1px solid var(--pr-line)}

  /* talents / powers */
  .prtn{font-weight:700; white-space:nowrap} .prtd{font-size:8.5px; line-height:1.35}
  .prforce{color:var(--pr-yellow)} .prsignodes{margin-top:3px; font-size:9px}
  .pratt{color:var(--pr-accent); font-weight:600} .prinote{color:var(--pr-faint); font-style:italic}
  .preqcap{font-family:var(--disp,inherit); font-size:8px; letter-spacing:.12em; text-transform:uppercase; color:var(--pr-accent); margin:6px 0 2px; font-weight:700}

  /* trees (reused treeHtml grid; connectors from the probe divs) */
  .prtree{transform-origin:top left; margin-top:1px}
  .prtree .tree{--tnode-link:4px; margin-top:2px}
  /* treeHtml is rendered with disabled=true, which stamps .preview-locked (and,
     on char sheets, .locked on unowned nodes) — both dim the cards to ~.5/.42
     opacity for the interactive picker. In PRINT the card IS the reference, so
     restore full strength; the ✓/empty checkbox still signals ownership. */
  #printDoc .prtree .tree.preview-locked .tnode{opacity:1}
  #printDoc .prtree .tnode.locked{opacity:1; filter:none}
  /* connectors — chargen's measured-SVG engine (drawTreeLines, wired via
     prConnect after prPaginate) draws them into a .treelines overlay from the
     real card rects, exactly like the on-screen trees, so they land on the card
     edges instead of floating in fixed grid tracks. The .hlink/.vcell probe divs
     stay (they carry the data-a/data-b gids the engine reads) but are invisible.
     Stroke follows --pr-accent — steel (color/clean) or near-black (simple) — so
     the ink sweep can't blank it (it only strips background-color, not stroke). */
  #printDoc .treelines line{stroke:var(--pr-accent); stroke-width:4px}
  .prtree .tnode-empty{visibility:hidden}
  /* The Force starburst prints (it's rules-relevant at the table); its fixed
     red/blue banner colors are already print-safe on white. */

  /* legend above each printed tree: styled to match the top banner — solid steel
     band, white text, and white Conflict/Repeatable/Ranked glyphs exactly like
     the ones on the cards. Active/Passive swatches keep their red/blue card
     colors, with a hairline outline so blue Passive reads on the blue band. */
  .prpage .treelegend{color:var(--pr-accent-ink); border:none; background:var(--pr-band);
    font-size:9.5px; gap:14px; padding:3px 10px; margin:2px 0 3px; font-weight:600}
  .prpage .treelegend .tlswatch{width:11px; height:11px; box-shadow:inset 0 0 0 1px rgba(255,255,255,.55)}
  .prpage .treelegend .rankicon,.prpage .treelegend .cficon,.prpage .treelegend .rpicon{background:#fff}
  .ink-clean .prpage .treelegend{background:var(--pr-band)!important; color:var(--pr-accent-ink)!important}
  /* Clean strips every background-color, which erased the white banner glyphs
     (ranked diamond + Conflict/Repeatable marks) from printed tree cards. Clean
     keeps those .thead banners colored (re-asserted below), so re-ink the glyphs
     WHITE — the fixed card-stock exception (Full Color keeps them already). */
  .ink-clean .prtree .thead .rankicon,.ink-clean .prtree .thead .cficon,.ink-clean .prtree .thead .rpicon{background:#fff!important}
  /* Legend now rides the steel band, so its Conflict/Repeatable/Ranked glyphs are
     WHITE like the ones on the cards; re-assert with enough specificity to beat
     the sweep that would otherwise blank them in Clean mode. */
  .ink-clean .prpage .treelegend .rankicon,.ink-clean .prpage .treelegend .cficon,.ink-clean .prpage .treelegend .rpicon{background:#fff!important}
  /* Active/Passive swatches key the card-banner colors — the sweep blanks them
     too, leaving the legend saying "Active"/"Passive" with no color. Clean keeps
     the colored banners, so mirror them (red/blue). */
  .ink-clean .prpage .treelegend .tlswatch.hd-a{background:#a8323a!important}
  .ink-clean .prpage .treelegend .tlswatch.hd-p{background:#2a6f96!important}
  /* the mask-drawn Conflict/Repeatable glyphs in the Dice & Symbol Key are
     also background-colored, so the ink strip would blank them too */
  .ink-clean .sy-CF,.ink-clean .sy-RP{background:var(--pr-ink)!important}
  /* The .ink-clean `background-color:transparent!important` sweep
     (bottom of this @media block) also blanks three things it shouldn't: the
     tree node card-stock banners + XP-cost badges, the tree connectors, and the
     dice shapes — all of which are deliberate fixed-color exceptions (FFG card
     stock / canon dice). Re-assert them. Full Color is untouched by the sweep. */
  .ink-clean .prtree .thead.hd-a{background:#a8323a!important}
  .ink-clean .prtree .thead.hd-p{background:#2a6f96!important}
  .ink-clean .prtree .thead.hd-f{background:#33506b!important}
  .ink-clean .prtree .tree.force .thead.hd-f{background:var(--force-green)!important}
  .ink-clean .prtree .thead.hd-a ~ .tcost{background:#a8323a!important;color:#f4e9e9!important}
  .ink-clean .prtree .thead.hd-p ~ .tcost{background:#2a6f96!important;color:#eaf3fa!important}
  .ink-clean .prtree .thead.hd-f ~ .tcost{background:#33506b!important;color:#eaf5ee!important}
  .ink-clean .prtree .tree.force .thead.hd-f ~ .tcost{background:var(--force-green)!important}
  /* connectors are the measured .treelines SVG now (stroke, not background), so
     the sweep — which only strips background-color — leaves them alone. */
  /* dice shapes (difficulty dice in rules text render as blank clip-paths once
     their fill is stripped) — re-assert their canon fills. */
  .ink-clean .die.dp{background:var(--die-p)!important}
  .ink-clean .die.da{background:var(--die-a)!important}
  .ink-clean .die.db{background:var(--die-b)!important}
  .ink-clean .die.dd{background:var(--die-d)!important}
  .ink-clean .die.dc{background:var(--die-c)!important}
  .ink-clean .die.ds{background:var(--die-s)!important}
  .ink-clean .die.df{background:var(--die-f-outline)!important}
  /* Clean is the reduced-ink mode. It keeps the tree card-stock banners SOLID
     (so the white ranked/Conflict glyphs stay legible) but DESATURATES them to
     grey — visibly less toner than Full Color, which keeps the true FFG
     red/blue/green card stock. Dice keep their canon colours: the filter is
     scoped to the tree banners/cost bars/legend swatches and never reaches
     .die/.sym, so the re-asserted dice fills above are untouched. */
  .ink-clean .prtree .thead,.ink-clean .prtree .tcost,
  .ink-clean .prpage .treelegend .tlswatch{filter:grayscale(1)}
  /* Clean also drops the dossier species portrait — a large ink block a
     reduced-ink print does not need (the vitals + lore carry the page). */
  #printDoc.ink-clean .prdossier .prspportrait{display:none!important}
  /* reference-catalog page furniture */
  .prtreemeta{font-size:9px;color:var(--pr-ink);letter-spacing:.02em;line-height:1.55;margin:2px 0 3px}
  .prtreemeta b{color:var(--pr-accent);font-weight:700}
  .propt{margin:3px 0;font-size:9.5px;line-height:1.4}.propt:first-child{margin-top:0}
  /* Citation chips (pointer descs) — on screen these are dashed amber pills that
     each wrap to their own line inside a narrow tree card, stacking into a tall
     dashed block. In print, flatten them to plain inline text so a "Please see
     … p. N … for details." pointer is one compact line. */
  #printDoc .cite{display:inline!important; border:none!important; padding:0!important; margin:0!important;
    color:var(--pr-faint)!important; font-size:inherit!important; cursor:default}
  #printDoc .cite.live::after{content:""}
  #printDoc .citesee,#printDoc .citesee.tcite{color:var(--pr-faint); font-size:9px; line-height:1.35; margin:0}

  /* ================= CHARACTER SHEET v3 (.prsheet) =================
     The printed character sheet's own visual language: an accent rail down the
     left edge, a rotated page label, corner ticks, chevron section dividers,
     chamfered stat tiles and characteristic hexagons. Emitted by the prHex /
     prTile / prHexChar / prCallout / prWriteBox / prFill / prCheckStrip / prXref
     atoms in js/print.js.

     EVERY rule here is scoped under .prsheet. That scoping is the whole
     containment story and it runs both ways: the Print Center's .prdossier
     catalog pages never inherit a rail or a chevron, and these components never
     leak into the vehicle / encounter / party families that share .prband,
     .prsection and the pagination engine. tools/test-print.js pins both
     directions — don't unscope a rule "just for one page".

     Geometry note: preview, the off-screen measuring pass, and print now all use
     the SAME box (756×996, the @page content area — see the two rules above), so
     the furniture and pagination are identical across media. --pr-rail is just a
     small inset from the page edge for the rail/ticks; the content gutter is the
     24px margin on .prhead/.prbody below. Keep .prsheet's padding 0 in every
     medium — the box rules depend on it. */
  #printDoc .prpage.prsheet{--pr-rail:6px}
  .prsheet::before{content:""; position:absolute; left:var(--pr-rail); top:0; bottom:0; width:5px;
    /* gradient, not a background-color — the Clean-ink sweep strips colors */
    background-image:linear-gradient(to bottom,var(--pr-accent),var(--pr-accent2))}
  /* Corner ticks sit a symmetric 6px in from every edge (top and bottom alike),
     so the bottom-right tick reads as the lowest corner of the card. The footer
     is lifted just above them (bottom:8px) and its 18px side padding keeps the
     page number clear of the tick, which is why the old 26px bottom inset — added
     back when the corner drew through the page number — is no longer needed. */
  .prsheet::after{content:""; position:absolute; left:var(--pr-rail); right:var(--pr-rail); top:6px; bottom:6px;
    pointer-events:none; background-repeat:no-repeat;
    background-image:linear-gradient(var(--pr-accent2),var(--pr-accent2)),linear-gradient(var(--pr-accent2),var(--pr-accent2)),
      linear-gradient(var(--pr-accent2),var(--pr-accent2)),linear-gradient(var(--pr-accent2),var(--pr-accent2));
    background-size:18px 1.2px,1.2px 18px,18px 1.2px,1.2px 18px;
    background-position:right top,right top,right bottom,right bottom}
  .prsheet>.prvlabel{position:absolute; left:calc(var(--pr-rail) + 9px); bottom:22px; font-style:normal;
    writing-mode:vertical-rl; transform:rotate(180deg); font-family:var(--mono);
    font-size:7px; letter-spacing:.3em; text-transform:uppercase; color:var(--pr-faint)}
  /* The strip a continuation page inherits instead of repeating the masthead.
     A direct child of .prpage, outside .prbody, so it can never inflate the
     scrollHeight that prPageOverflow measures. */
  .prsheet>.prheadalt{display:none}
  /* Symmetric side gutters so content can't kiss (and overflow) the printable
     edge — the right side had no gutter before, which clipped wide dice pools. */
  .prsheet>.prhead,.prsheet>.prbody{margin:0 24px}
  .prsheet>.prfoot{left:var(--pr-rail); right:var(--pr-rail); padding:0 18px; bottom:8px}

  /* masthead (page 1) + identity strip (pages 2+) */
  /* Top gutter so the masthead never kisses the printable edge: ~14px of interior
     margin on top of @page's 8mm gives a comfortable, safe top margin without
     pushing the masthead visibly low. */
  .prsheet>.prhead{border:none; border-bottom:2px solid var(--pr-accent); border-radius:0; clip-path:none;
    padding:0 0 6px; margin-top:14px; margin-bottom:9px}
  .prmast{display:flex; align-items:flex-end; justify-content:space-between; gap:14px}
  .prmastfile{font-family:var(--mono); font-size:7.5px; letter-spacing:.22em; text-transform:uppercase; color:var(--pr-accent)}
  .prmastname{font-family:var(--disp); font-size:28px; font-weight:700; line-height:1.05}
  .prmastgrid{display:grid; grid-template-columns:auto auto; gap:2px 20px; font-size:9.5px}
  .prmastgrid span{display:block; font-size:6.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint)}
  .prmastrail{text-align:right; font-family:var(--mono); font-size:7px; letter-spacing:.1em;
    text-transform:uppercase; color:var(--pr-faint); line-height:1.7}
  .prstrip{display:flex; align-items:baseline; justify-content:space-between; gap:10px}
  .prstripn{font-family:var(--disp); font-size:14px; font-weight:700}
  .prstripm{font-size:8.5px; color:var(--pr-faint)}
  .prstript{font-family:var(--mono); font-size:8px; letter-spacing:.18em; text-transform:uppercase; color:var(--pr-accent)}
  .prsheetfoot{font-family:var(--mono); font-size:7.5px}

  /* chevron section divider — prHex() keeps the .prband class so the split /
     continuation path (prSplitSection clones prBand()) still works */
  /* !important because the ink re-assert rules further down (`.ink-clean
     .prband{background:…!important}`) exist to keep the SOLID band's fill alive
     through the Clean transparency sweep — and they would repaint the chevron's
     hairline row as a solid bar. The chevron carries its own fill on .prhexb. */
  #printDoc .prsheet .prband.prhex{background:none!important; color:var(--pr-accent)!important;
    border-bottom:none; clip-path:none; padding:0; border-radius:0;
    display:flex; align-items:center; gap:10px; margin:11px 0 6px; letter-spacing:normal}
  .prsheet .prband.prhex::before,.prsheet .prband.prhex::after{content:""; flex:1; border-top:1px solid var(--pr-accent)}
  .prhexb{padding:1px; background-image:linear-gradient(var(--pr-accent),var(--pr-accent));
    clip-path:polygon(9px 0,calc(100% - 9px) 0,100% 50%,calc(100% - 9px) 100%,9px 100%,0 50%)}
  .prhexb>b{display:block; clip-path:inherit; padding:3px 20px; color:var(--pr-accent);
    background-image:linear-gradient(var(--pr-page),var(--pr-page));
    font-family:var(--disp); font-size:8.5px; font-weight:700; letter-spacing:.26em; text-transform:uppercase}

  /* chamfered stat tile */
  .prtiles{display:grid; grid-template-columns:repeat(4,1fr); gap:10px}
  .prtile{padding:1px; background-image:linear-gradient(var(--pr-accent-grey),var(--pr-accent-grey));
    clip-path:polygon(10px 0,calc(100% - 10px) 0,100% 30%,100% 70%,calc(100% - 10px) 100%,10px 100%,0 70%,0 30%)}
  .prtile.key{background-image:linear-gradient(var(--pr-accent),var(--pr-accent))}
  .prtilein{clip-path:inherit; height:56px; display:flex; flex-direction:column;
    background-image:linear-gradient(var(--pr-page),var(--pr-page))}
  .prtilecap{text-align:center; padding:2.5px 0; color:var(--pr-accent); background-color:var(--pr-tint);
    font-family:var(--disp); font-size:6.5px; font-weight:700; letter-spacing:.2em; text-transform:uppercase}
  .prtileb{flex:1; display:flex; align-items:stretch}
  .prtilec{flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center}
  .prtilev{font-family:var(--disp); font-size:19px; font-weight:800; line-height:1}
  .prtilec small{font-size:5.5px; letter-spacing:.12em; text-transform:uppercase; color:var(--pr-faint)}
  .prtiled{width:1px; background-image:linear-gradient(var(--pr-line),var(--pr-line))}
  .prwritec{display:inline-block; min-width:26px; border-bottom:1px solid var(--pr-line)}

  /* characteristic hexagon */
  .prhexrow{display:flex; justify-content:center; gap:22px}
  .prhexc{width:76px; height:84px; padding:2px; position:relative;
    background-image:linear-gradient(var(--pr-accent),var(--pr-accent));
    clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}
  .prhexc.raised{padding:2.5px; background-image:linear-gradient(var(--pr-accent2),var(--pr-accent2))}
  .prhexc.hurt{background-image:linear-gradient(var(--pr-danger),var(--pr-danger))}
  .prhexcin{clip-path:inherit; height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center;
    background-image:linear-gradient(var(--pr-page),var(--pr-page))}
  .prhexcv{font-family:var(--disp); font-size:26px; font-weight:800; line-height:1}
  .prhexcl{font-size:6.5px; letter-spacing:.16em; text-transform:uppercase; color:var(--pr-accent); font-weight:700}
  /* raised/injured carry a glyph as well as a color — two book themes have a
     red decorative accent, and a mono printout has no color at all */
  .prhexf{position:absolute; left:0; right:0; bottom:9px; text-align:center; font-size:7px; font-style:normal; line-height:1}
  .prhexf.up{color:var(--pr-accent2)} .prhexf.down{color:var(--pr-danger)}

  /* callouts, write-in boxes, ruled fills */
  .prcallout{border:1px solid var(--pr-accent); padding:5px 8px; margin:5px 0; font-size:8.5px;
    line-height:1.45; color:var(--pr-faint)}
  .prcallout b{color:var(--pr-ink)}
  .prcallout.pr-warn{border-color:var(--pr-yellow); background-color:rgba(232,185,35,.08)}
  .prwritebox{border:1px solid var(--pr-line); border-top:2px solid var(--pr-accent); padding:5px 8px; margin:6px 0}
  .prwbl{font-family:var(--disp); font-size:7px; font-weight:700; letter-spacing:.16em;
    text-transform:uppercase; color:var(--pr-accent); margin-bottom:3px}
  .prwbl small{font-weight:400; letter-spacing:.04em; text-transform:none; color:var(--pr-faint)}
  /* fixed height by design — see prFill()'s comment: a growing fill would
     absorb the slack prPageOverflow measures and silence pagination */
  .prfill{flex:0 0 auto; margin-top:6px}
  .prcards{display:flex; flex-direction:column; gap:6px}
  .prsubtotal{text-align:right; font-size:7.5px; letter-spacing:.1em; text-transform:uppercase;
    color:var(--pr-faint); padding:2px 4px 0}
  .prsubtotal b{color:var(--pr-ink); font-size:9px}

  /* check strips (item repair state, status effects, vehicle actions) */
  .prchk{display:inline-flex; align-items:center; gap:6px; vertical-align:middle}
  .prchkb{display:inline-flex; align-items:center; gap:3px; font-size:7px; color:var(--pr-faint)}
  .prchkb>i{width:9px; height:9px; border:1px solid var(--pr-faint); display:inline-block}
  .prchkb.on>i{border-color:var(--pr-accent); border-width:1.5px;
    background-image:linear-gradient(var(--pr-accent),var(--pr-accent))}
  .prchkb.on{color:var(--pr-accent); font-weight:700}
  .prchk.stack{display:flex; flex-direction:column; align-items:flex-start; gap:5px}
  .prchk.stack .prchkb{align-items:flex-start; gap:5px}
  .prchk.stack .prchkb>i{width:10px; height:10px; flex:none; margin-top:1px}
  .prchk.stack .prchkb small{font-size:8.5px; color:var(--pr-ink); line-height:1.35}
  .prchk.stack .prchkb small b{font-weight:700}

  /* cross-page pointer (resolved after pagination by prResolveXrefs) */
  .prxref{color:var(--pr-accent); font-size:8px; letter-spacing:.06em; text-transform:uppercase}
  .prxref b{font-weight:700}

  /* "Removes a setback die" marker. On screen it is a red bar on a die dimmed
     to .75; printed at 10px on white that read as a smudge. Full strength, and
     a WHITE bar — maximum contrast on the dark setback die, and legible on a
     mono printout where the red would not be. Authored as a gradient so the
     Clean-ink transparency sweep leaves it alone. */
  #printDoc .die.rem{opacity:1}
  #printDoc .die.rem::after{width:11px; height:3px; border-radius:0; background-color:transparent;
    background-image:linear-gradient(#fff,#fff)}

  /* panels + the dice/marker legend under a section divider */
  .prpanel{border:1px solid var(--pr-line); border-top:2px solid var(--pr-accent); padding:5px 8px; margin:6px 0}
  .prpanel .prnote{padding:1px 0}
  .prdicelegend{display:flex; justify-content:center; flex-wrap:wrap; gap:14px; margin:0 0 6px;
    font-size:7.5px; letter-spacing:.06em; color:var(--pr-faint)}
  .prdicelegend b{color:var(--pr-accent)}
  .prtilenote{display:flex; justify-content:flex-end; gap:16px; padding:3px 4px 0;
    font-size:8px; letter-spacing:.06em; color:var(--pr-faint)}
  .prtilenote b{color:var(--pr-ink); font-size:9px}
  .prtiles.prtiles3{grid-template-columns:repeat(3,1fr)}

  /* PAGE 2 — weapon / armor cards */
  .prwcard{display:flex; align-items:stretch; border:1px solid var(--pr-line);
    border-left:4px solid var(--pr-accent); break-inside:avoid; page-break-inside:avoid}
  .prwmain{flex:1; padding:5px 9px; min-width:0}
  .prwh{display:flex; align-items:baseline; gap:10px}
  .prwn{font-family:var(--disp); font-size:11.5px; font-weight:700}
  .prwstate{font-size:7px; letter-spacing:.12em; text-transform:uppercase; color:var(--pr-accent); font-weight:600}
  .prwstats{display:flex; flex-wrap:wrap; gap:14px; align-items:baseline; margin-top:2px; font-size:9px; color:var(--pr-faint)}
  .prwstats b{color:var(--pr-ink); font-size:10.5px}
  .prwq{font-size:8px; color:var(--pr-faint); margin-top:2px; line-height:1.45}
  .prwq b{color:var(--pr-ink)}
  .prwrail{width:158px; flex:none; border-left:1px dashed var(--pr-line); padding:5px 9px;
    display:flex; flex-direction:column; justify-content:center; gap:4px}
  .prwrl{font-size:6.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint)}
  .prwmeta{font-size:7px; color:var(--pr-faint)}
  .prcondgrid{display:grid; grid-template-columns:290px 1fr; gap:12px; align-items:start}
  .prcondx{color:var(--pr-faint); font-size:7.5px}
  /* status effects: two columns of three (the six standard conditions) */
  .prchk.cond{display:grid; grid-template-columns:1fr 1fr; gap:5px 12px}
  .prchk.cond .prchkb{align-items:center; gap:5px}
  .prchk.cond .prchkb>i{width:10px; height:10px; flex:none}
  .prchk.cond .prchkb small{font-size:8.5px; color:var(--pr-ink)}
  .prsoakparts{font-size:9px; line-height:1.6; color:var(--pr-faint)}
  .prsoakparts b{color:var(--pr-ink)}

  /* PAGE 3 — encumbrance / money header */
  .preqhead{display:grid; grid-template-columns:170px 1fr 170px 1fr; gap:10px; align-items:stretch}
  .preqhead .prwritebox,.preqhead .prpanel{margin:0}
  .prencbox .prencv{font-size:9px; line-height:1.5}
  .prchk.tight{gap:2px}
  .prchk.tight .prchkb>i{width:7px; height:7px}

  /* PAGE 1 — description block (portrait + physical fields + notable features).
     align-items:stretch + a flex-filling panel means Notable Features grows to
     the portrait's height instead of leaving dead space beside it; the portrait
     itself is larger than the first cut. */
  .pridgrid{display:grid; grid-template-columns:138px 158px 1fr; gap:12px; align-items:stretch}
  .prbiocol .prbio{display:flex; flex-direction:column; gap:1px; padding:2px 0; border-bottom:1px solid var(--pr-line)}
  .prbiocol .prbio span{font-size:6.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint)}
  .prbiocol .prbio b{font-size:10px; min-height:12px}
  .prsheet .prport{width:138px; height:174px}
  .pridgrid .prpanel{display:flex; flex-direction:column}
  .pridgrid .prpanel>.prstory,.pridgrid .prpanel>.prwritelines{flex:1}
  .pridgrid .prwlfill{height:auto; min-height:80px}
  .prchip{display:inline-block; border:1px solid var(--pr-accent); color:var(--pr-accent);
    font-size:8.5px; letter-spacing:.04em; padding:1px 8px; margin-right:5px}
  .prchip small{font-size:6px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint); margin-right:2px}
  .prmotiv{border-bottom:1px solid var(--pr-line); padding:4px 0}
  .prmotivd{font-size:9px; line-height:1.45; margin-top:2px}
  .prmoralcard{border:1px solid var(--pr-line); padding:5px 8px; font-size:9px; line-height:1.4}
  /* Light-Side / Dark-Side Force User banner — a filled steel bar, matching the
     reference sheet's placement under the morality tiles. Background-color so it
     drops in Clean ink like every other wash. */
  .prsideband{text-align:center; margin:6px 0; padding:3px 0; font-family:var(--disp);
    font-size:9px; font-weight:700; letter-spacing:.22em; text-transform:uppercase;
    color:var(--pr-accent-ink); background-color:var(--pr-band)}
  .ink-clean .prsideband{color:var(--pr-accent)!important; border:1px solid var(--pr-accent)}

  /* PAGE 5 — background field list */
  .prfields{display:flex; flex-direction:column}
  .prfieldline{display:grid; grid-template-columns:150px 1fr; gap:8px; padding:3px 0;
    border-bottom:1px solid var(--pr-line); font-size:9px}
  .prfieldline span{font-size:6.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint); padding-top:2px}

  /* PAGE 7 — force / power cards */
  .prsheet .prcard{border-radius:0}
  .prcardx{float:right; font-family:var(--mono); font-size:7px; letter-spacing:.1em;
    text-transform:uppercase; color:var(--pr-faint); font-weight:400}

  /* companion + vehicle pages wearing the sheet skin */
  .prsheet .prcompmeta{display:flex; align-items:baseline; gap:10px; margin-bottom:5px}
  .prsheet .prcompmeta b{font-family:var(--disp); font-size:13px}
  .prtiles.prtiles6{grid-template-columns:repeat(6,1fr)}
  .prvehhead .prvname{display:inline-block; min-width:110px; border-bottom:1px solid var(--pr-line)}
  .prvhero{display:grid; grid-template-columns:190px 1fr; gap:12px; align-items:start; margin-top:8px}
  .prsheet .prvimg{width:100%; height:130px; border:1px solid var(--pr-line); object-fit:contain; background:var(--pr-tint)}
  .prvfactgrid{display:grid; grid-template-columns:auto 1fr auto 1fr; gap:2px 10px; font-size:9px; align-items:baseline}
  .prvfactgrid span{font-size:6.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--pr-faint)}
  .prsheet .prspeed{display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:8px; font-size:9px}
  .prsheet .prspeed b{font-family:var(--disp); font-size:7px; letter-spacing:.16em;
    text-transform:uppercase; color:var(--pr-accent)}

  /* ============ Reference-Catalog DOSSIER skin (catalog pages only) ============
     Ports the approved tools/print-prototype language — steel/cyan/amber clipped
     panels + perimeter linework — onto catalog pages via the .prdossier marker.
     Character sheets never carry .prdossier, so their --pr-* tokens are untouched.
     We redefine --pr-* to the dossier palette here (so the existing
     .prhead/.prband/.prcard rules inherit it) and add the structural pieces. All
     backgrounds carry !important + id-level specificity so the .ink-clean/simple
     `background-color:transparent` sweep at the bottom of this block can't blank
     them (Full Color keeps the cyan-steel paper; clean/simple go white). */
  #printDoc .prpage.prdossier{
    --pr-ink:#172732; --pr-faint:#52707e; --pr-line:#7b9ca8; --pr-line-soft:#b8ccd3;
    --pr-accent:#203d51; --pr-accent-ink:#f4fbfd; --pr-yellow:#dda83b;
    --pr-band:#203d51; --pr-tint:#d8e9ed; --pr-page:#e6f1f3; --pr-panel:#f7fbfc;
    --pr-cyan:#22a9c5; --pr-steel2:#2e6178; --pr-shadow:rgba(26,54,68,.22);
    background-color:var(--pr-page)!important;
    background-image:
      linear-gradient(90deg,transparent 0 49.8%,rgba(35,111,133,.04) 50%,transparent 50.2%),
      repeating-linear-gradient(0deg,rgba(28,85,105,.035) 0 1px,transparent 1px 5px),
      radial-gradient(circle at 84% 5%,rgba(39,181,207,.14),transparent 26%),
      linear-gradient(155deg,#f8fbfc,#e6f1f3);
  }
  #printDoc.ink-clean .prpage.prdossier{--pr-page:#fff; --pr-panel:#fff; --pr-tint:#edf4f6; --pr-shadow:transparent; background-image:none!important}
  /* perimeter: inset steel frame + corner cyan/amber/steel accent ticks */
  #printDoc .prpage.prdossier::before{content:"";position:absolute;z-index:0;pointer-events:none;inset:.16in;
    border:1.5px solid var(--pr-steel2);
    clip-path:polygon(0 .2in,.2in 0,calc(100% - .5in) 0,calc(100% - .42in) .09in,100% .09in,100% calc(100% - .22in),calc(100% - .22in) 100%,.1in 100%,0 calc(100% - .1in))}
  #printDoc .prpage.prdossier::after{content:"";position:absolute;z-index:0;pointer-events:none;inset:.23in;opacity:.5;
    background:
      linear-gradient(var(--pr-cyan),var(--pr-cyan)) 0 0 / .55in 3px no-repeat,
      linear-gradient(var(--pr-yellow),var(--pr-yellow)) .58in 0 / .2in 3px no-repeat,
      linear-gradient(var(--pr-cyan),var(--pr-cyan)) 100% 100% / .7in 3px no-repeat,
      linear-gradient(var(--pr-steel2),var(--pr-steel2)) 0 100% / .3in 3px no-repeat}
  #printDoc .prpage.prdossier>*{position:relative;z-index:1}
  /* Keep the footer's edge text off the bottom-corner frame accents: nudge the
     left label right and the page number left (the corner tick is widest at the
     bottom-right, so a larger right inset). Center label is untouched. */
  #printDoc .prpage.prdossier .prfoot{padding-left:44px;padding-right:84px}
  /* header → dossier header: steel gradient, amber under-shadow, cyan kicker */
  #printDoc .prdossier .prhead{border:none!important;border-radius:0;
    background:linear-gradient(105deg,var(--pr-band) 0 70%,var(--pr-steel2) 70% 100%)!important;
    box-shadow:0 4px 0 var(--pr-yellow),0 8px 18px var(--pr-shadow);
    clip-path:polygon(0 11px,11px 0,calc(100% - 18px) 0,100% 18px,100% 100%,10px 100%,0 calc(100% - 10px))}
  #printDoc .prdossier .prhrow{border-bottom-color:rgba(163,231,241,.22)}
  #printDoc .prdossier .prhead .prhk{color:#8fe3f0}
  #printDoc .prdossier .prhead .prhv{color:#eaf4f8}
  #printDoc .prdossier .prhead .prhv.prhname{color:#fff}
  /* section band → light amber-ticked section title (not a filled band) */
  #printDoc .prdossier .prband{background:transparent!important;color:var(--pr-band)!important;
    border-bottom:none;clip-path:none;display:flex;align-items:center;gap:8px;padding:2px 0;margin:9px 0 5px}
  #printDoc .prdossier .prband::before{content:"";width:.16in;height:5px;background:var(--pr-yellow);clip-path:polygon(0 0,100% 0,70% 100%,0 100%)}
  #printDoc .prdossier .prband::after{content:"";flex:1;border-top:1px solid var(--pr-line)}
  /* cards → tech-panel: clipped steel panel with a cyan left spine + drop shadow */
  #printDoc .prdossier .prcard{position:relative;border:1px solid var(--pr-line);
    background:var(--pr-panel)!important;box-shadow:4px 5px 0 var(--pr-shadow);padding-left:12px;
    clip-path:polygon(0 10px,10px 0,calc(100% - 12px) 0,100% 12px,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%)}
  #printDoc .prdossier .prcard::before{content:"";position:absolute;inset:0 auto 0 0;width:4px;background:var(--pr-cyan)}
  /* option rows → amber-keyed rows (matches the prototype .option-row) */
  #printDoc .prdossier .propt{padding:5px 6px;background:var(--pr-tint);margin:4px 0}
  #printDoc .prdossier .propt b{color:var(--pr-yellow)}
  /* species dossier — mirrors the chargen sheet: portrait floated top-right, a
     name+source header rule, amber vitals text lines, a full-width characteristic
     row (all inside .prsptop, which reserves the portrait's right gutter), then
     innate abilities + description flowing full width below (.prspbelow clears). */
  #printDoc .prdossier .prsplayout{position:relative}
  /* top section: content column + portrait column (portrait top-right), then the
     description flows as a normal block below — no float, so prPaginate measures
     section height correctly and splits the long writeup to fill the page. */
  #printDoc .prdossier .prsptop{display:grid;grid-template-columns:1fr 1.55in;gap:.16in;align-items:start;margin-bottom:6px}
  #printDoc .prdossier .prspportrait{width:100%;height:2.05in;object-fit:contain;
    border:1px solid var(--pr-steel2);background:var(--pr-tint);
    clip-path:polygon(0 8px,8px 0,100% 0,100% calc(100% - 11px),calc(100% - 11px) 100%,0 100%)}
  #printDoc .prdossier .prsphead{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;
    border-bottom:1px solid var(--pr-line);padding-bottom:4px;margin-bottom:6px}
  #printDoc .prdossier .prspname{font-family:var(--disp,inherit);font-size:19px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--pr-accent)}
  #printDoc .prdossier .prspsrc{font-size:8px;letter-spacing:.1em;text-transform:uppercase;color:var(--pr-faint);border:1px dashed var(--pr-line);padding:2px 7px}
  #printDoc .prdossier .prspvit{font-size:10px;line-height:1.65;margin-bottom:8px}
  #printDoc .prdossier .prspvit b{color:var(--pr-yellow);font-weight:700}
  #printDoc .prdossier .prspvit .prspno b{color:var(--pr-accent)}
  #printDoc .prdossier .prspchars{display:grid;grid-template-columns:repeat(6,1fr);gap:6px;margin:0}
  #printDoc .prdossier .prspchar{display:grid;place-items:center;min-height:.58in;border:1px solid var(--pr-line);background:var(--pr-tint)}
  #printDoc .prdossier .prspchar b{font-size:22px;font-weight:800;color:var(--pr-accent);line-height:1}
  #printDoc .prdossier .prspchar span{font-size:6.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--pr-faint);margin-top:3px}
  #printDoc .prdossier .prsplabel{font-size:7.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--pr-faint);margin:7px 0 2px}
  #printDoc .prdossier .prspability b{color:var(--pr-accent)}
  #printDoc .prdossier .prspdesc{font-size:10px;line-height:1.55;margin-top:8px}
  #printDoc .prdossier .prspdesc p{margin:5px 0}
  #printDoc .prdossier .prspdesc p:first-child{margin-top:0}
  /* Force Presence track on paper — same primer glyphs in print ink:
     Light hollow, Neutral half-filled, Dark solid. */
  .prpage .fptrack{display:inline-flex; vertical-align:middle; margin:0 4px}
  .prpage .fptrack .dtoken{width:13px; height:13px; box-shadow:none}
  .prpage .fppip{width:11px; height:11px; border-color:var(--pr-ink); background:#fff}
  .prpage .fppip.N{background:linear-gradient(90deg,#fff 0 50%,var(--pr-ink) 50% 100%)!important}
  .prpage .fppip.D{background:var(--pr-ink)!important}
  .prpage .fppip.L{background:#fff}

  /* dedicated vehicle and companion play sheets */
  .prcompmeta{display:flex;align-items:baseline;gap:12px;margin-bottom:5px}.prcompmeta b{font-size:14px}.prcompmeta .prsource{margin-left:auto}
  .prcompage .npc-stat{border-color:var(--pr-line);background:transparent}.prcompage .npc-stat h3{color:var(--pr-accent)}
  .prcompage .npc-statstrip{gap:4px}.prcompage .npc-stat-cell{border-color:var(--pr-line);background:var(--pr-tint)}
  /* Any NPC/adversary stat block in print (encounter stat-block pages, companion
     sheets) reuses the on-screen npcStatBlock, whose .rref/.npc-stat/.charbox carry
     the screen theme's dark --panel2 fill and cyan --holo accents. Left unpinned,
     an encounter's stat-block page prints as a full-page dark ink dump in Full
     Color. Neutralize to the print palette (extends the .prcompage rule above to
     the encounter .prnpc page). Backgrounds use --pr-tint WITHOUT !important so the
     Clean ink sweep still blanks them to white; the dark fill is beaten by the
     #printDoc id-specificity, not !important. */
  #printDoc .npc-stat{border-color:var(--pr-line);background:transparent}
  #printDoc .npc-stat h3,#printDoc .npc-stat h4{color:var(--pr-accent)}
  #printDoc .npc-stat-cell,#printDoc .npc-stat-meta,#printDoc .npc-stat .charbox{border-color:var(--pr-line);background:var(--pr-tint)}
  #printDoc .npc-stat-cell b,#printDoc .npc-stat .charbox .cv,#printDoc .npc-sk-rank{color:var(--pr-accent)}
  /* NPC/adversary PLAY SHEET (prNpcPage): the stat block authored into prSectionH
     sections. Reuses the .npc-stat descendant rules (chargrid/statstrip/skills)
     neutralized just above; these only tune the per-section layout. No new fills
     — the section band + --pr-tint cells carry the ink, so Clean is unaffected. */
  .prsheet.prnpc .npc-stat{margin:0;border:0;padding:0}
  .prsheet.prnpc .npc-stat .chargrid{margin:0 0 8px}
  .prsheet.prnpc .npc-statstrip{margin:0}
  .prsheet.prnpc .npc-sheet-skills{width:100%}
  .prsheet.prnpc .npc-sheet-skills td{font-size:11px;padding:2px 6px}
  .prsheet.prnpc .npc-eqlist{margin:0;padding-left:16px}.prsheet.prnpc .npc-eqlist li{margin:2px 0}
  .prsheet.prnpc .npc-weapon-sets{display:flex;flex-direction:column;gap:8px}
  .prsheet.prnpc .npc-portrait-float{max-width:120px;max-height:150px}
  .prsheet.prnpc .npc-tag-row{margin-top:6px}
  .prspeed{display:flex;align-items:center;gap:8px;margin-top:7px;padding:4px 6px;border:1px solid var(--pr-line)}.prspeed span{display:inline-flex;gap:2px;align-items:center}.prspeed i{font-style:normal;font-size:14px}.prcheck{width:20px;text-align:center;font-size:14px!important}
  .prprose{font-size:8.5px;line-height:1.35;margin-top:5px}.prprose p{margin:2px 0}
  /* Keep-with-next: never let the print engine strand a continuation band above
     an empty page bottom, nor break inside a single paragraph. The JS paginator
     (prSplitProseMeasured) is primary; these belt-and-suspenders the browser's
     own pass. .prsection>.prband:first-child already carries break-after:avoid. */
  .prprose p{break-inside:avoid;page-break-inside:avoid}
  .prprose p:first-child{break-before:avoid;page-break-before:avoid}
  /* A subheading segment ([H1] rendered block) must never be the last thing on a
     page — keep it with the paragraph that follows (mirrors the JS keep-with-next
     in prSplitProseMeasured). */
  .prprose .prsphd{break-after:avoid;page-break-after:avoid;break-inside:avoid}

  /* dice / symbol key */
  .prkey{display:flex; flex-wrap:wrap; gap:6px 14px; padding:6px}
  .prkeyc{display:inline-flex; align-items:center; gap:5px; font-size:10px; border:1px solid var(--pr-line); border-radius:3px; padding:3px 7px}

  /* ---- compact one-sheet ---- */
  .prcompact{font-size:9px}
  .prcompact .prband.prhex{margin:5px 0 2px}
  .prctop{display:flex;justify-content:space-between;gap:8px}.prctop>div{flex:1}.prcport{width:70px;height:82px;object-fit:cover;border:1.5px solid var(--pr-accent);clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px))}
  .prcident{font-size:11px; font-weight:600; margin-bottom:4px}
  .prcstat{display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin-bottom:4px}
  .prcompact .prhexrow{gap:6px; justify-content:flex-start; margin:0}
  .prcompact .prhexc{width:42px; height:46px}
  .prcompact .prhexcv{font-size:16px} .prcompact .prhexcl{font-size:5px; letter-spacing:.05em}
  .prcompact .prhexf{bottom:5px; font-size:6px}
  .prcchips{display:flex; gap:5px; flex-wrap:wrap}
  .prcchip{border:1px solid var(--pr-line); border-radius:10px; padding:1px 7px; font-size:9px}
  .prcgtag{color:var(--pr-faint); font-size:7.5px}
  .prc2col{display:grid; grid-template-columns:1fr 1fr; gap:10px; align-items:start}
  .prcline{font-size:8.5px; padding:1px 0; border-bottom:1px dotted var(--pr-line); line-height:1.25}.prcline span{display:block;color:var(--pr-faint);font-size:7.2px}
  .prcgear{display:flex; flex-wrap:wrap; gap:5px}
  .prcompact .prsktbl td,.prcompact .prwtbl td{padding:1px 4px; font-size:8.5px}
  .promitted{font-size:7px;color:var(--pr-accent);font-weight:700;text-align:right;margin:1px 0}
  .prcstory{font-size:7.5px;line-height:1.3;margin-top:4px;padding:3px 5px;border-left:2px solid var(--pr-accent);background:var(--pr-tint)}
  .prcompactkey .prkey{gap:3px 8px;padding:2px}.prcompactkey .prkeyc{font-size:7px;padding:1px 4px}

  /* ---- ink variants ---- */
  .ink-clean, .ink-clean *{background-color:transparent!important}
  .ink-clean .prband{background:var(--pr-band)!important; color:var(--pr-accent-ink)!important}
  .ink-clean .prmed,.ink-clean .prstat,.ink-clean .prxpbox,.ink-clean .prcard,.ink-clean .prkeyc,.ink-clean .prvstat,.ink-clean .prsktbl th,.ink-clean .prwtbl th,.ink-clean .prtaltbl th,.ink-clean .preqtbl th{background:transparent!important}

/* ============================================================
   INTERACTIVE GALACTIC NAVIGATION CHART
   ============================================================ */
.gal-panel{
  /* Fixed-dark chrome family. The Galactic Atlas is an in-universe SCREEN —
     dark in every theme, the same exception class as the PDF viewer chrome —
     so these are named constants, NOT theme aliases that would flip in light
     mode. SVG data-viz paints (region dots, hyperlanes, labels) stay literal
     like the dice colours; --map-* already owns the region spectrum. */
  --gal-space:#02050b; --gal-void:#04101b; --gal-panel:#07131e; --gal-panel2:#0a1c29;
  --gal-line:#173347; --gal-line-bright:#28516a; --gal-line-mid:#23485f;
  --gal-ink:#d9f4ff; --gal-dim:#8ba6b6; --gal-faint:#7897aa;
  --gal-accent:#8de6ff; --gal-accent-dim:#55d6ff; --gal-accent-deep:#3fc9ef;
  padding:0;overflow:hidden;min-height:calc(100vh/var(--ts-zoom,1) - 98px);
  border-color:var(--gal-line);background:var(--gal-space)}
.gal-title{display:grid;grid-template-columns:minmax(230px,1fr) minmax(260px,440px) auto;gap:calc(12px*var(--dsp));align-items:center;padding:calc(12px*var(--dsp)) calc(16px*var(--dsp));border-bottom:1px solid #1a4961;background:linear-gradient(90deg,#07121e 0%,#0a1c29 52%,#07131e 100%);box-shadow:0 8px 30px #0008;position:relative;z-index:4}
.gal-title:after{content:"";position:absolute;left:16px;right:16px;bottom:-1px;height:1px;background:linear-gradient(90deg,#53d7ffcc,transparent 45%,#53d7ff66)}
.gal-title .ptitle{letter-spacing:.16em;color:#8de6ff;text-shadow:0 0 18px #2fc7ff66}.gal-title .smallnote{letter-spacing:.07em;color:#7194aa}
.gal-search{position:relative;z-index:8}.gal-search input{width:100%;margin:0;background:#06111dcc;border-color:#28516a;color:#d9f4ff;box-shadow:inset 0 0 18px #02060bcc}
.gal-search-results{position:absolute;top:calc(100% + 4px);left:0;right:0;background:#07131ef5;border:1px solid #28516a;box-shadow:0 16px 38px #000d;max-height:310px;overflow:auto}
.gal-search-results:empty{display:none}.gal-search-results button{display:flex;width:100%;justify-content:space-between;gap:12px;border:0;border-bottom:1px solid #173347;border-radius:0;background:transparent;color:#d8edf5;padding:9px 11px;text-align:left}
.gal-search-results button:hover,.gal-search-results button:focus{background:#123149}.gal-search-results span{color:#7897aa;font-size:11px}
.gal-zoom{display:flex;gap:4px}.gal-zoom button{height:34px;min-width:34px;border:1px solid #28516a;background:#0a2030;color:#bfeeff;border-radius:3px}.gal-zoom button:hover{background:#123b53}
.gal-layout{--gal-left-width:220px;--gal-right-width:0px;position:relative;display:grid;grid-template-columns:var(--gal-left-width) minmax(420px,1fr) var(--gal-right-width);min-height:720px;height:calc(100vh/var(--ts-zoom,1) - 184px);background:#02050b;transition:grid-template-columns .22s ease}.gal-layout.gal-left-closed{--gal-left-width:0px}.gal-layout.gal-right-open{--gal-right-width:310px}
.gal-sidebar,.gal-dossier{min-width:0;background:linear-gradient(180deg,#07131ef7,#050d16f5);overflow:auto;scrollbar-color:#245069 #06111b;transition:opacity .16s ease,padding .22s ease}.gal-left-closed .gal-sidebar,.gal-right-closed .gal-dossier{opacity:0;pointer-events:none;padding-left:0;padding-right:0;border:0;overflow:hidden}
.gal-sidebar{border-right:1px solid #17435b;padding:calc(13px*var(--dsp));box-shadow:12px 0 36px #0008;z-index:2}.gal-sidebar section{padding:0 0 14px;margin:0 0 14px;border-bottom:1px solid #173347}
.gal-sidebar h3,.gal-dossier h3{font:700 10px/1.2 var(--disp);letter-spacing:.18em;text-transform:uppercase;color:#55d6ff;margin:0 0 9px}
.gal-segments{display:grid;grid-auto-flow:column;grid-auto-columns:1fr}.gal-segments button,.gal-toggle,.gal-listbtn,.gal-route-actions button{border:1px solid #23485f;background:#081725;color:#8ba6b6;padding:7px 8px}.gal-segments button:first-child{border-radius:3px 0 0 3px}.gal-segments button:last-child{border-radius:0 3px 3px 0}.gal-segments .on,.gal-toggle.on,.gal-listbtn.on{background:linear-gradient(180deg,#16465d,#103347);border-color:#3fc9ef;color:#bff3ff;box-shadow:inset 0 0 16px #23b8e522}
.gal-atlas-note{display:grid;gap:3px;margin-top:8px;padding:8px;border-left:2px solid #38c8ee;background:#0a1b28}.gal-atlas-note b{font:700 9px var(--disp);letter-spacing:.12em;color:#8be8ff}.gal-atlas-note span{font-size:9px;line-height:1.4;color:#708fa1}
.gal-legend{display:grid;gap:4px;margin-top:8px;font-size:9px;color:#708fa1}.gal-legend span{display:flex;align-items:center;gap:6px}.gal-legend i{width:8px;height:8px;border-radius:50%;flex:none}.gal-lg-traced{background:#e8fbff;box-shadow:0 0 4px #8feaffcc}.gal-lg-field{width:5px;height:5px;background:var(--map-field);opacity:.7}
.gal-tone-deep{--gal-region-color:var(--map-region-deep)}.gal-tone-core{--gal-region-color:var(--map-region-core)}.gal-tone-colonies{--gal-region-color:var(--map-region-colonies)}.gal-tone-inner{--gal-region-color:var(--map-region-inner)}.gal-tone-expansion{--gal-region-color:var(--map-region-expansion)}.gal-tone-mid{--gal-region-color:var(--map-region-mid)}.gal-tone-outer{--gal-region-color:var(--map-region-outer)}.gal-tone-hutt{--gal-region-color:var(--map-region-hutt)}.gal-tone-wild{--gal-region-color:var(--map-region-wild)}.gal-tone-unknown{--gal-region-color:var(--map-region-unknown)}
.gal-region-key{display:grid;grid-template-columns:1fr 1fr;gap:5px 8px;margin:2px 0 8px}.gal-region-key button{display:flex;align-items:center;gap:6px;min-width:0;padding:2px 0;border:0;background:transparent;color:#607988;font-size:8px;line-height:1.2;text-align:left;cursor:pointer;opacity:.55}.gal-region-key button.on{color:#b9d4df;opacity:1}.gal-region-key button:hover,.gal-region-key button:focus-visible{color:#e6f8ff;outline:0;text-shadow:0 0 8px var(--gal-region-color)}.gal-region-key i{width:8px;height:8px;flex:none;border-radius:50%;background:var(--gal-region-color);box-shadow:0 0 7px color-mix(in srgb,var(--gal-region-color) 65%,transparent)}.gal-region-key button:not(.on) i{filter:grayscale(1);box-shadow:none}.gal-region-note{margin-bottom:9px;line-height:1.35}
.gal-field-system,.gal-field-cell{cursor:pointer;outline:none}.gal-field-visual,.gal-system-visual,.gal-region-label-visual,.gal-routeplan-end-visual{transform:scale(var(--gal-screen-scale,1));transform-origin:0 0}.gal-field-dot,.gal-field-segment{fill:var(--gal-region-color,var(--map-region-unknown));stroke:#020711;stroke-width:1.1;vector-effect:non-scaling-stroke;pointer-events:all;filter:drop-shadow(0 0 3px var(--gal-region-color,var(--map-field)))}.gal-field-dot{opacity:.78}.gal-field-segment{opacity:.74}.gal-field-system:hover .gal-field-dot,.gal-field-system:focus .gal-field-dot,.gal-field-system.selected .gal-field-dot,.gal-field-cell:hover .gal-field-segment,.gal-field-cell:focus .gal-field-segment{opacity:1;stroke:#f1fbff;stroke-width:1.8}
.gal-field-label{fill:#edfaff;font:11px var(--label);paint-order:stroke;stroke:#01040a;stroke-width:3px;stroke-linejoin:round;pointer-events:none}.gal-field-label.culled{display:none}.gal-field-system:hover .gal-field-label.culled,.gal-field-system:focus .gal-field-label.culled,.gal-field-system.selected .gal-field-label.culled{display:block}
.gal-toggle,.gal-listbtn{display:flex;width:100%;align-items:center;gap:7px;text-align:left;border-radius:2px;margin:3px 0}.gal-listbtn{justify-content:space-between}.gal-listbtn span{color:#69889a}
.gal-backdrop-control{margin:4px 0 7px;padding:7px 8px;border:1px solid #23485f;background:#06131f}.gal-backdrop-control>div{display:flex;align-items:center;gap:7px}.gal-backdrop-control>div:first-child{justify-content:space-between;margin-bottom:5px}.gal-backdrop-control label,.gal-backdrop-control output{font:700 9px var(--disp);letter-spacing:.08em;text-transform:uppercase;color:#7fa6b8}.gal-backdrop-control input{min-width:0;flex:1;accent-color:#45ccef}.gal-backdrop-control button{padding:3px 6px;border:1px solid #28516a;background:#081725;color:#8ba6b6;font-size:9px;text-transform:uppercase}.gal-backdrop-control button.on{border-color:#45ccef;color:#c8f4ff;background:#12384a}
.gal-route-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;margin-top:5px}.gal-route-actions button{padding:5px 2px;font-size:10px}.gal-sidebar .btn{margin:6px 3px 0 0}
.gal-stage{position:relative;min-width:0;overflow:hidden;background-color:#02050b;touch-action:none;box-shadow:inset 0 0 70px #000,inset 0 0 0 1px #173347}.gal-backdrop-img{pointer-events:none}.gal-stage:before{content:"";position:absolute;z-index:0;inset:0;pointer-events:none;opacity:0;background-image:radial-gradient(circle at 17% 22%,#d5f5ff 0 1px,transparent 1.6px),radial-gradient(circle at 72% 38%,#86caff 0 .8px,transparent 1.4px),radial-gradient(circle at 40% 75%,#fff 0 .65px,transparent 1.15px),radial-gradient(circle at 85% 82%,#78bfe7 0 .9px,transparent 1.45px);background-size:137px 113px,191px 167px,251px 211px,307px 263px;background-position:0 0,37px 71px,83px 19px,143px 109px;transition:opacity .18s ease}.gal-stage.gal-zoom-high:before{opacity:.2}.gal-stage.gal-zoom-local:before{opacity:.5}.gal-stage:after{content:"";position:absolute;inset:0;pointer-events:none;background:linear-gradient(90deg,#02050b99 0,transparent 12% 88%,#02050b99),radial-gradient(circle at 50% 48%,transparent 42%,#02050b88 100%);mix-blend-mode:multiply}.gal-stage.placing-pin{cursor:crosshair}.gal-stage svg{position:relative;z-index:1;display:block;width:100%;height:100%;min-height:520px;outline:none;user-select:none}.gal-stage svg:focus-visible{box-shadow:inset 0 0 0 3px var(--focus-ring)}
.gal-space{fill:#02050b}.gal-starfield{opacity:.1}.gal-dust-orbit{fill:none;stroke:#5a8da8;stroke-width:1;stroke-dasharray:2 16;opacity:.24}.gal-dust-orbit-2{stroke:#b98976;stroke-dasharray:1 21;opacity:.16}.gal-grid line{stroke:#3e7c98;stroke-width:.7;stroke-opacity:.18;vector-effect:non-scaling-stroke}.gal-grid text{fill:#78a5b8;font:9px var(--label);text-anchor:middle;opacity:.72}
.gal-chart-depth{pointer-events:none}.gal-depth-core{fill:url(#galChartDepth);opacity:.72}.gal-depth-ring{fill:none;stroke:#7bcde6;stroke-width:1;stroke-opacity:.12;stroke-dasharray:2 15;vector-effect:non-scaling-stroke}.gal-depth-ring-a{stroke-opacity:.16}.gal-depth-ring-b{stroke:#c99e82;stroke-opacity:.11;stroke-dasharray:1 22}.gal-zoom-local .gal-chart-depth{opacity:.65}
.gal-region{fill:var(--gal-region-color,var(--map-region-unknown));fill-opacity:var(--gal-region-wash,.04);stroke:var(--gal-region-color,var(--map-region-unknown));stroke-opacity:.6;stroke-width:1.15;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 4px color-mix(in srgb,var(--gal-region-color,var(--map-region-unknown)) 30%,transparent))}.gal-region-contour{fill:none;stroke:var(--gal-region-color,var(--map-region-unknown));stroke-opacity:.22;stroke-width:.65;stroke-dasharray:1 9;vector-effect:non-scaling-stroke;pointer-events:none}.gal-region-contour.selected{stroke:var(--amber);stroke-opacity:.8;stroke-width:1.15}
.gal-region-label{fill:var(--gal-region-color,var(--map-region-unknown));font:700 10px var(--disp);letter-spacing:.12em;text-anchor:middle;dominant-baseline:middle;paint-order:stroke;stroke:#01040a;stroke-width:3.5px;pointer-events:none;opacity:.95}
.gal-region.selected{stroke:var(--amber);stroke-opacity:1;stroke-width:2;fill-opacity:.12}.gal-region-label.selected{fill:var(--amber);opacity:1}
.gal-region-conflict-ring{fill:none;stroke:var(--amber);stroke-width:1.25;stroke-opacity:.82;stroke-dasharray:2 2;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 3px #ffd16688);pointer-events:none}
/* Selected printed grid cell — the only position an untraced gazetteer system has. */
.gal-gridfocus rect{fill:var(--amber);fill-opacity:.1;stroke:var(--amber);stroke-width:1.6;stroke-opacity:.9;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 6px var(--amber))}/* Sized to annotate the cell, not compete with the system labels sitting inside
   it — this text scales with the viewport like every other SVG label does. */
.gal-gridfocus text{fill:var(--amber);font:700 5.5px var(--label);letter-spacing:.08em;text-anchor:middle;paint-order:stroke;stroke:#02050b;stroke-width:2px;opacity:.9}
.gal-hyperlane-casing,.gal-hyperlane{fill:none;vector-effect:non-scaling-stroke}.gal-hyperlane-casing{stroke:var(--map-route-casing);stroke-opacity:.74;stroke-width:2.8;pointer-events:none}.gal-hyperlane{stroke:var(--map-route);stroke-opacity:.76;stroke-width:1.2;cursor:pointer;outline:none}.gal-hyperlane-group:hover .gal-hyperlane,.gal-hyperlane:focus,.gal-hyperlane.active{stroke:#bfeeff;stroke-opacity:1;stroke-width:2.4;filter:drop-shadow(0 0 6px #73ddff)}.gal-hyperlane-group:hover .gal-hyperlane-casing,.gal-hyperlane-casing.active{stroke-width:4.4}
.gal-system{cursor:pointer;outline:none}.gal-system-hit{fill:transparent;pointer-events:all}.gal-system-halo{fill:var(--gal-region-color,var(--map-region-unknown));opacity:.24}.gal-system-dot{fill:var(--gal-region-color,var(--map-region-unknown));stroke:#01040a;stroke-width:1.5;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 4px var(--gal-region-color,var(--map-region-unknown)))}.gal-system:hover .gal-system-halo,.gal-system:focus .gal-system-halo,.gal-system.selected .gal-system-halo{opacity:.8;filter:drop-shadow(0 0 7px var(--gal-region-color,var(--map-region-unknown)))}.gal-system.selected .gal-system-dot{fill:#fff4c9;stroke:#ffd166}
.gal-party-ring{fill:none;stroke:#ffd56c;stroke-width:2;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 5px #ffd56c)}
.gal-label{fill:#e5f6fb;font:11px var(--label);paint-order:stroke;stroke:#01040a;stroke-width:3.5px;stroke-linejoin:round;pointer-events:auto;cursor:pointer;text-shadow:0 1px 2px #000}.gal-label.iconic,.gal-label.major{font-weight:700;fill:#fff5cc}.gal-label.culled{display:none}.gal-system:hover .gal-label.culled,.gal-system:focus .gal-label.culled,.gal-system.selected .gal-label.culled{display:block}.gal-zoom-local .gal-region-label{display:none}
.gal-campaign-route{fill:none;stroke:var(--map-campaign);stroke-width:2;stroke-dasharray:7 4;vector-effect:non-scaling-stroke}.gal-campaign-route.active{stroke-width:3;filter:drop-shadow(0 0 3px var(--map-campaign))}
.gal-routeplan-line{fill:none;stroke:var(--map-route-active);stroke-width:2.5;stroke-dasharray:3 5;stroke-linecap:round;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 4px var(--map-route-active))}.gal-routeplan-end{fill:var(--map-route-active);stroke:#02070a;stroke-width:1;vector-effect:non-scaling-stroke}
.gal-routeinfo{display:flex;gap:8px;align-items:center;justify-content:space-between;margin:8px 0;padding:8px;border-left:2px solid var(--map-route-active);background:#0a1b28}.gal-routeinfo b{display:block;font:700 10px var(--disp);color:#ffd889}.gal-routeinfo>div span{font-size:10px;color:#8fb0c1}.gal-routeinfo .btn{flex:none}
.gal-pin{cursor:pointer;outline:none;--gal-pin-color:var(--amber)}.gal-pin-color-holo{--gal-pin-color:var(--holo-bright)}.gal-pin-color-green{--gal-pin-color:var(--green)}.gal-pin-color-red{--gal-pin-color:var(--red)}.gal-pin path{fill:var(--gal-pin-color);stroke:var(--map-space);stroke-width:1.5;vector-effect:non-scaling-stroke}.gal-pin text{fill:var(--gal-pin-color);font:700 8px var(--label);paint-order:stroke;stroke:var(--map-space);stroke-width:2px}.gal-pin:hover path,.gal-pin:focus path,.gal-pin.selected path{fill:var(--holo-bright);filter:drop-shadow(0 0 4px var(--holo))}
.gal-map-help{position:absolute;z-index:3;left:10px;bottom:9px;padding:5px 8px;background:#04101bd9;border:1px solid #28516a;color:#799caf;font-size:10px;letter-spacing:.04em;pointer-events:none}
.gal-panel-tab{position:absolute;top:14px;z-index:6;width:24px;height:42px;padding:0;border:1px solid #2d7896;background:#071b29ed;color:#8de6ff;font:700 22px/1 var(--disp);box-shadow:0 5px 18px #000b;transition:left .22s ease,right .22s ease,background .15s ease}.gal-panel-tab:hover,.gal-panel-tab:focus-visible{background:#123d52;color:#e2faff}.gal-panel-tab-left{left:var(--gal-left-width);transform:translateX(-1px);border-radius:0 5px 5px 0}.gal-panel-tab-right{right:var(--gal-right-width);transform:translateX(1px);border-radius:5px 0 0 5px}
.gal-dossier{border-left:1px solid #17435b;padding:calc(16px*var(--dsp));box-shadow:-12px 0 36px #0008;z-index:2}.gal-dossier-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;border-bottom:1px solid #23485f;padding-bottom:12px;margin-bottom:13px}.gal-dossier-head h2{font:700 19px/1.25 var(--disp);letter-spacing:.05em;color:#d9f7ff;text-shadow:0 0 18px #46cdf866;margin:0 0 7px}.gal-dossier-head>button{border:0;background:transparent;color:#7899aa;font-size:24px;line-height:1}.gal-dossier-empty{text-align:center;color:#6d8c9f;padding:20vh 12px 0}.gal-dossier-empty>span{display:block;font-size:36px;color:#45c7ec;text-shadow:0 0 20px #45c7ec88}
.gal-kv{display:grid;grid-template-columns:82px 1fr;gap:7px 10px;margin:12px 0}.gal-kv span{color:var(--faint);font-size:10px;text-transform:uppercase;letter-spacing:.1em}.gal-kv b{color:var(--ink);font-size:12px}.gal-dossier-actions{display:flex;flex-wrap:wrap;gap:5px;margin:13px 0}.gal-dossier textarea{min-height:92px}.gal-wiki{display:inline-block;margin:9px 0}.gal-provenance{border-top:1px solid var(--line);margin-top:12px;padding-top:10px}
/* Gazetteer rosters: a sector's systems, a region's sectors, a grid cell's
   neighbours. Hundreds of dotted-underline .cxlink spans read as one unbroken
   block, so the same links are restyled as wrapped chips here — same element,
   same quick-view click, just scannable. */
/* The 16 worlds whose two reference sources contradict each other. Tinted
   because it is a caveat about the data itself, not ordinary provenance text. */
.gal-dispute{border-left:2px solid var(--amber);padding:6px 0 6px 9px;margin:9px 0;max-width:820px}
.gal-chiprow{margin:9px 0}.gal-chiprow>.fchip{margin-bottom:5px}
.gal-chiplist{display:flex;flex-wrap:wrap;gap:4px}
.gal-chiplist .cxlink,.gal-chiplist .lkchip{border:1px solid var(--line);border-radius:3px;padding:2px 7px;background:var(--input-bg);color:var(--dim);font-size:11px;text-decoration:none;margin:0}
.gal-chiplist .cxlink:hover,.gal-chiplist .lkchip:hover{background:var(--hover);border-color:var(--holo-dim);color:var(--holo)}
.gal-planet-link,.gal-stop{display:flex;width:100%;justify-content:space-between;gap:10px;align-items:center;background:var(--input-bg);color:var(--dim);border:1px solid var(--line);border-radius:3px;padding:9px;margin:6px 0;text-align:left}.gal-planet-link:hover,.gal-stop:hover{background:var(--hover);border-color:var(--holo-dim)}.gal-planet-link b{color:var(--holo)}.gal-stop span{color:var(--faint)}
.gal-license{padding:7px 12px;border-top:1px solid #173347;background:#050d16;color:#658497;text-align:right}
@media(max-width:1180px){.gal-layout{--gal-left-width:190px}.gal-layout.gal-left-closed{--gal-left-width:0px}.gal-layout.gal-right-open{--gal-right-width:285px}.gal-title{grid-template-columns:1fr minmax(240px,360px) auto}}
@media(max-width:900px){.gal-title{grid-template-columns:1fr auto}.gal-search{grid-column:1/-1;grid-row:2}.gal-layout,.gal-layout.gal-left-closed{--gal-left-width:180px;--gal-right-width:0px;height:auto;min-height:0;grid-template-columns:180px minmax(0,1fr)}.gal-left-closed .gal-sidebar{opacity:1;pointer-events:auto;padding:calc(13px*var(--dsp));border-right:1px solid #17435b;overflow:auto}.gal-stage{height:68vh;min-height:470px}.gal-dossier{grid-column:1/-1;border-left:0;border-top:1px solid var(--line);max-height:none}.gal-right-closed .gal-dossier{display:none}.gal-dossier-empty{padding:40px 12px}.gal-panel-tab{display:none}.gal-license{text-align:left}}
@media(max-width:620px){.gal-panel{min-height:0}.gal-title{padding:10px}.gal-layout{display:flex;flex-direction:column}.gal-sidebar{order:2;border-right:0;border-top:1px solid var(--line);display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:10px}.gal-sidebar section{margin:0}.gal-stage{order:1;height:58vh;min-height:390px}.gal-dossier{order:3}.gal-zoom button:last-child{display:none}.gal-title .ptitle{font-size:13px}.gal-label{font-size:9px}.gal-license{font-size:9px}}

/* Route planner completion: the SVG stage is square at every desktop width, so
   the square coordinate system never letterboxes inside a wide black canvas. */
.gal-layout{height:auto;min-height:0;align-items:stretch}
.gal-stage{aspect-ratio:1;height:auto;min-height:0}
.gal-stage svg{height:100%;min-height:0;aspect-ratio:1}
.gal-mobile-bar,.gal-sheet-head{display:none}
.gal-route-search{width:100%;margin:0 0 6px;background:#06111d;border-color:#28516a;color:#d9f4ff}
.gal-listbtn small{font:700 7px var(--disp);letter-spacing:.08em;color:var(--amber)}
.gal-plan-summary{display:grid;gap:3px;margin:8px 0;padding:8px;border-left:2px solid var(--map-route-active);background:#0a1b28}
.gal-plan-summary b{font:700 10px var(--disp);color:#ffd889}.gal-plan-summary span{font-size:9px;color:#8fb0c1}
.gal-stop-anchor{cursor:default;opacity:.78}.gal-party-cell circle{fill:none;stroke:#ffd56c;stroke-width:2;vector-effect:non-scaling-stroke;filter:drop-shadow(0 0 5px #ffd56c)}
.gal-party-cell text{fill:#ffd56c;font:700 8px var(--label);paint-order:stroke;stroke:var(--map-space);stroke-width:2px}
.gal-load-error{max-width:560px;margin:12vh auto;padding:28px;border:1px solid var(--red);background:#170b0d;text-align:center}.gal-load-error h2{color:#ff9b9b}

@media(max-width:900px){
  .gal-mobile-bar{display:flex;position:sticky;top:0;z-index:12;background:#07131e;border-bottom:1px solid #1a4961}
  .gal-mobile-bar button{flex:1;border:0;border-right:1px solid #1a4961;border-radius:0;background:#0a2030;color:#bfeeff;padding:10px;font:700 10px var(--disp);letter-spacing:.08em}
  .gal-layout,.gal-layout.gal-left-closed{display:block;height:auto;min-height:0}
  .gal-stage{width:100%;height:auto;min-height:0;aspect-ratio:1}
  .gal-stage svg{width:100%;height:100%;min-height:0}
  .gal-sidebar,.gal-dossier,.gal-left-closed .gal-sidebar,.gal-right-closed .gal-dossier{display:block;position:fixed;z-index:60;left:0;right:0;bottom:0;width:100%;height:min(78vh,700px);max-height:min(78vh,700px);padding:16px 18px 28px;border:1px solid #2d7896;border-bottom:0;border-radius:14px 14px 0 0;overflow:auto;opacity:1;pointer-events:none;transform:translateY(105%);transition:transform .22s ease;box-shadow:0 -20px 60px #000d;background:linear-gradient(180deg,#0b2030,#050d16)}
  .gal-sidebar.mobile-open,.gal-dossier.mobile-open{pointer-events:auto;transform:translateY(0)}
  .gal-sheet-head{display:flex;position:sticky;top:-16px;z-index:2;align-items:center;justify-content:space-between;margin:-16px -18px 14px;padding:13px 18px;background:#0a1c29;border-bottom:1px solid #28516a}
  .gal-sheet-head h2{margin:0;font:700 13px var(--disp);letter-spacing:.1em;color:#8de6ff}.gal-sheet-head button{border:0;background:transparent;color:#bfeeff;font-size:24px}
  .gal-panel-tab{display:none}.gal-dossier-empty{padding:40px 12px}.gal-license{text-align:left}
}
@media(max-width:620px){
  .gal-layout{display:block}.gal-sidebar{display:block;order:initial;padding:16px 14px 28px}.gal-sidebar section{margin:0 0 14px}.gal-stage{height:auto;min-height:0}.gal-dossier{order:initial}
}

/* ============================================================
   GALACTIC ATLAS GOLD PASS — immersive map-first workspace
   ============================================================ */
html.atlas-mode body{overflow:hidden;background:#02050b}
html.atlas-mode #content{width:100%;max-width:none;margin:0;padding:0}
html.atlas-mode #topbar{display:none}
html.atlas-mode #topnav{min-height:44px;padding:5px 12px;border-bottom-color:#1a4961;background:linear-gradient(90deg,#06111b,#091925 55%,#06111b)}
html.atlas-mode .logo{font-size:10px}
.gal-panel{position:relative;height:calc(100dvh/var(--ts-zoom,1) - var(--hdr-h,44px));min-height:520px;border:0;border-radius:0;margin:0;overflow:hidden;--gal-violet:#ae7cff;--gal-violet-bright:#e5d4ff;--gal-offlane:#ffd166}
/* atlas-mode floating chrome, rebuilt as panel-language plates: square (no
   border-radius), opaque (backdrop-filter dropped — a hologram is
   transmissive, not frosted), and a hard 1px edge instead of the big soft
   shadow + inset top-highlight that read as Mapbox glass. */
.gal-title{position:absolute;z-index:20;top:12px;left:50%;width:min(920px,calc(100% - 96px));transform:translateX(-50%);grid-template-columns:minmax(190px,1fr) minmax(260px,460px) auto;padding:9px 11px;border:1px solid var(--gal-line-bright);background:linear-gradient(105deg,#06111bf7,#0a1d2bf5);box-shadow:0 2px 0 var(--gal-space)}
.gal-title:after{left:12px;right:12px;background:linear-gradient(90deg,transparent,#53d7ffcc,transparent)}
.gal-title .ptitle{font-size:11px}.gal-title .smallnote{font-size:8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.gal-layout,.gal-layout.gal-left-closed,.gal-layout.gal-right-open{display:block;position:absolute;inset:0;height:auto;min-height:0;background:#02050b}
.gal-stage,.gal-stage svg{position:absolute;inset:0;width:100%;height:100%;min-height:0;aspect-ratio:auto}
.gal-stage{z-index:1}.gal-stage svg{z-index:1}
.gal-stage:after{z-index:2;background:linear-gradient(90deg,#02050b99 0,transparent 12% 88%,#02050b99),radial-gradient(circle at 50% 48%,transparent 42%,#02050b88 100%);mix-blend-mode:multiply}
.gal-sidebar,.gal-dossier{position:absolute;z-index:12;top:82px;bottom:18px;height:auto;border:1px solid var(--gal-line-bright);background:linear-gradient(160deg,#071521fa,#040b13f8);box-shadow:0 2px 0 var(--gal-space);transition:transform .22s ease,opacity .18s ease}
.gal-sidebar{left:14px;width:340px;padding:16px;border-right:1px solid #28516acc}.gal-dossier{right:14px;width:380px;padding:18px;border-left:1px solid #28516acc}
.gal-left-closed .gal-sidebar{display:block;transform:translateX(calc(-100% - 24px));opacity:0}.gal-right-closed .gal-dossier{display:block;transform:translateX(calc(100% + 24px));opacity:0}
.gal-panel-tab{top:92px}.gal-panel-tab-left{position:absolute;left:14px;transform:none;border-radius:5px}.gal-left-open .gal-panel-tab-left{left:354px}.gal-panel-tab-right{position:absolute;right:14px;transform:none;border-radius:5px}.gal-right-open .gal-panel-tab-right{right:394px}
.gal-license{position:absolute;z-index:5;right:10px;bottom:4px;padding:3px 7px;border:0;background:#02070ac9;font-size:8px;border-radius:3px}
.gal-map-help{left:auto;right:12px;bottom:30px;border-radius:4px}
.gal-scale{position:absolute;z-index:4;left:18px;bottom:28px;display:grid;gap:4px;color:#85adc0;font:700 8px var(--disp);letter-spacing:.1em;text-shadow:0 1px 3px #000;pointer-events:none}.gal-scale i{display:block;width:90px;height:7px;border:1px solid #78dfff;border-top:0;box-shadow:0 0 10px #55d7ff55}
.gal-route-key{position:absolute;z-index:4;left:18px;bottom:64px;display:flex;gap:12px;padding:5px 7px;border:1px solid #28516a88;border-radius:4px;background:#04101bc9;color:#8da9b8;font-size:8px;text-transform:uppercase;letter-spacing:.1em;pointer-events:none}.gal-route-key span{display:flex;align-items:center;gap:5px}.gal-route-key i{display:block;width:22px;height:0;border-top:2px solid var(--gal-violet)}.gal-route-key i.off{border-color:var(--gal-offlane);border-top-style:dashed}
.gal-hyperlane-casing{stroke:#01040a;stroke-opacity:.74;stroke-width:2.8}.gal-hyperlane{stroke:#8db7c7;stroke-opacity:.74;stroke-width:1.2}.gal-hyperlane-group:hover .gal-hyperlane,.gal-hyperlane:focus,.gal-hyperlane.active{stroke:#bfeeff;stroke-width:2.4;filter:drop-shadow(0 0 6px #73ddff)}
.gal-routeplan-casing,.gal-routeplan-line,.gal-routeplan-offlane{fill:none;vector-effect:non-scaling-stroke;stroke-linecap:round;stroke-linejoin:round;pointer-events:none}.gal-routeplan-casing{stroke:#32195b;stroke-width:7;opacity:.9}.gal-routeplan-line{stroke:var(--gal-violet-bright);stroke-width:3;stroke-dasharray:none;filter:drop-shadow(0 0 5px var(--gal-violet))}.gal-routeplan-offlane{stroke:var(--gal-offlane);stroke-width:3;stroke-dasharray:9 6;filter:drop-shadow(0 0 5px #ffbf3f)}.gal-routeplan-end circle{fill:#07101a;stroke:var(--gal-violet-bright);stroke-width:2;vector-effect:non-scaling-stroke}.gal-routeplan-end path{fill:var(--gal-violet-bright);stroke:#31185b;stroke-width:1;vector-effect:non-scaling-stroke}.gal-routeplan-end.dest path{fill:var(--gal-offlane)}
.gal-route-endpoint{display:flex;align-items:center;border:1px solid #28516a;background:#06111d;border-radius:4px;box-shadow:inset 0 0 18px #0008}.gal-route-endpoint>span{padding-left:9px;color:#48cfff}.gal-route-endpoint input{flex:1;min-width:0;margin:0;border:0;background:transparent;box-shadow:none}.gal-route-endpoint button{border:0;background:transparent;color:#8aa8b8;padding:7px 9px;font-size:17px}
.gal-route-picks{position:relative;z-index:5;max-height:190px;overflow:auto;background:#06111d;border:1px solid #28516a;border-top:0}.gal-route-picks:empty{display:none}.gal-route-picks button{display:flex;width:100%;justify-content:space-between;gap:8px;padding:7px 9px;border:0;border-bottom:1px solid #173347;background:transparent;text-align:left}.gal-route-picks button:hover,.gal-route-picks button:focus{background:#123149}.gal-route-picks b{font-size:11px}.gal-route-picks span{font-size:8px;color:#7897aa}
.gal-endpoint-actions{display:grid;grid-template-columns:auto auto 1fr;gap:4px;margin:8px 0 10px}.gal-endpoint-actions button,.gal-hyper-presets button{padding:6px;border:1px solid #23485f;background:#081725;color:#8ba6b6;font-size:9px}.gal-endpoint-actions .primary{border-color:#54d9ff;background:#14465b;color:#d9f7ff}.gal-endpoint-actions button:disabled{opacity:.35}
.gal-hyper-presets{display:grid;grid-template-columns:repeat(5,1fr) 58px;gap:3px}.gal-hyper-presets button.on{border-color:#54d9ff;background:#17495e;color:#e1faff;box-shadow:inset 0 0 14px #30caff33}.gal-hyper-presets input{min-width:0;margin:0;padding:5px;text-align:center}
.gal-route-rules{margin-top:9px;border:1px solid #23485f;background:#06111d}.gal-route-rules summary{padding:8px;color:#6fcff0;font:700 8px var(--disp);letter-spacing:.09em;text-transform:uppercase;cursor:pointer}.gal-route-rules>label{display:flex;gap:7px;align-items:center;padding:4px 8px;color:#91a9b7;font-size:10px}.gal-route-rules .fl{display:block}.gal-route-rules select{margin-top:4px}.gal-offlane-config{display:grid;grid-template-columns:1fr 1fr 1fr;gap:4px;padding:7px}.gal-offlane-config label{color:#7897aa;font-size:8px;text-transform:uppercase}.gal-offlane-config input{margin-top:3px;padding:5px;text-align:center}
.gal-mission-brief{margin:0 0 14px;padding:12px;border:1px solid var(--gal-line);background:var(--gal-panel);box-shadow:inset 3px 0 var(--gal-violet)}.gal-brief-kicker{font:700 8px var(--disp);letter-spacing:.18em;color:#a988ff}.gal-brief-time{margin:4px 0 1px;font:700 22px var(--disp);color:#f2ecff;text-shadow:0 0 14px #a978ff77}.gal-brief-meta{font-size:9px;color:#91a8b7}.gal-route-decision{display:grid;gap:3px;margin-top:8px;padding:7px 8px;border-left:2px solid #60d9ff;background:#071827}.gal-route-decision b{font:700 8px var(--disp);letter-spacing:.12em;color:#78e4ff}.gal-route-decision span{font-size:9px;line-height:1.35;color:#b4c6cf}.gal-route-decision small{color:#8872c9;font-size:8px}.gal-route-chain{display:flex;gap:5px;align-items:center;overflow:auto;margin:9px 0;padding-bottom:4px;color:#778f9d;font-size:9px}.gal-route-chain b{white-space:nowrap;color:#dce9ef}.gal-brief-legs{display:grid;gap:5px;max-height:210px;overflow:auto}.gal-brief-leg{display:flex;gap:8px;align-items:center;padding:6px;background:#07131f}.gal-brief-leg i{width:24px;border-top:2px solid var(--gal-violet);flex:none}.gal-brief-leg.offlane i{border-color:var(--gal-offlane);border-top-style:dashed}.gal-brief-leg b,.gal-brief-leg span{display:block}.gal-brief-leg b{font-size:9px;color:#d9eaf0}.gal-brief-leg span{font-size:8px;color:#7694a5}.gal-astro-result{display:grid;gap:4px;margin-top:8px;padding:8px;border-left:2px solid #48d5ff;background:#081827}.gal-astro-result>span{font:700 8px var(--disp);letter-spacing:.12em;color:#65dfff}.gal-astro-result small{font-size:9px;color:#8fa6b4}.gal-brief-actions{display:flex;flex-wrap:wrap;gap:3px;margin-top:7px}
.gal-intel{position:absolute;z-index:80;inset:0;overflow:auto;padding:34px 28px 70px;background:linear-gradient(180deg,#020913f7,#02060bfb),radial-gradient(circle at 50% 15%,#12314a88,transparent 48%);color:#c7d6de}.gal-intel-top{display:flex;align-items:flex-start;justify-content:space-between;border-top:2px solid #2f7899;padding-top:24px}.gal-intel-top span{font:700 9px var(--disp);letter-spacing:.2em;color:#56cff8}.gal-intel-top h2{margin:7px 0 18px;font:700 clamp(28px,4vw,48px) var(--disp);color:#f2f8fb;text-shadow:0 0 22px #48cfff55}.gal-intel-top button{width:48px;height:48px;border:1px solid #3b6378;border-radius:50%;background:#08131d;color:#b5c7d0;font-size:28px}.gal-intel nav{position:sticky;top:-34px;z-index:2;display:flex;gap:6px;padding:10px 0;background:#030a12eF}.gal-intel nav a{padding:8px 13px;border:1px solid #28516a;border-radius:18px;color:#6fd8fb;text-decoration:none;font:700 9px var(--disp);letter-spacing:.1em;text-transform:uppercase}.gal-intel section{margin:12px 0}.gal-intel h3{border-left:2px solid #4ed4ff;padding:8px 12px;background:linear-gradient(90deg,#0b2130,transparent);font:700 13px var(--disp);letter-spacing:.12em;color:#68d9ff;text-transform:uppercase}.gal-intel-overview>p{max-width:1100px;font-size:14px;line-height:1.65}.gal-intel-meta{display:flex;gap:6px;flex-wrap:wrap}.gal-intel-meta span,.gal-intel-cards footer span{padding:3px 8px;border:1px solid #2a5066;border-radius:12px;color:#75bddd;font-size:8px;letter-spacing:.08em;text-transform:uppercase}.gal-intel-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:8px}.gal-intel-cards article{padding:13px 15px;border-left:2px solid #347ea0;background:#07121ddd}.gal-intel-cards article>div{font:700 13px var(--disp);color:#eef7fb}.gal-intel-cards small{display:block;margin-top:3px;color:#4fbfe9;letter-spacing:.1em;text-transform:uppercase}.gal-intel-cards p{color:#9fb0ba;line-height:1.5}.gal-intel-cards footer{display:flex;gap:4px;flex-wrap:wrap}.gal-intel-actions{display:flex;gap:6px;margin-top:24px}.gal-intel-empty.show{padding:20px;border:1px dashed #28516a;color:#7592a2}.gal-intel-lines{min-height:84px}
.gal-intel-sources{display:grid;gap:6px}.gal-intel-sources article{padding:10px 13px;border-left:2px solid #806b3b;background:#11130fbb}.gal-intel-sources a,.gal-intel-sources b{color:#e2c371;font:700 11px var(--disp);letter-spacing:.06em}.gal-intel-sources p{margin:5px 0 0;color:#93a4ad}

@media(max-width:1180px) and (min-width:901px){.gal-sidebar{width:320px}.gal-left-open .gal-panel-tab-left{left:334px}.gal-dossier{width:350px}.gal-right-open .gal-panel-tab-right{right:364px}}
@media(max-width:900px){
  html.atlas-mode #topnav{min-height:42px}html.atlas-mode .logo span:last-child{display:inline}
  .gal-panel{height:calc(100dvh/var(--ts-zoom,1) - var(--hdr-h,42px));min-height:0}.gal-title{top:8px;left:8px;right:8px;width:auto;transform:none;grid-template-columns:1fr auto;padding:8px}.gal-title>div:first-child .smallnote{display:none}.gal-search{grid-column:1/-1;grid-row:2}.gal-zoom{grid-row:1;grid-column:2}.gal-mobile-bar{position:absolute;z-index:25;top:103px;left:8px;right:8px;display:flex;border:1px solid #28516a;border-radius:5px;overflow:hidden;box-shadow:0 10px 30px #0009}.gal-layout,.gal-layout.gal-left-closed{position:absolute;inset:0}.gal-stage,.gal-stage svg{width:100%;height:100%;aspect-ratio:auto}.gal-sidebar,.gal-dossier,.gal-left-closed .gal-sidebar,.gal-right-closed .gal-dossier{position:fixed;z-index:60;top:auto;left:0;right:0;bottom:0;width:100%;height:min(82dvh,720px);max-height:min(82dvh,720px);padding:16px 18px 28px;border:1px solid #2d7896;border-bottom:0;border-radius:14px 14px 0 0;overflow:auto;opacity:1;pointer-events:none;transform:translateY(105%);transition:transform .22s ease;box-shadow:0 -20px 60px #000d;background:linear-gradient(180deg,#0b2030f8,#050d16fa)}.gal-sidebar.mobile-open,.gal-dossier.mobile-open{pointer-events:auto;transform:translateY(0)}.gal-panel-tab{display:none}.gal-license{display:none}.gal-map-help{display:none}.gal-scale{bottom:18px}.gal-route-key{bottom:54px}.gal-intel{position:fixed;padding:24px 14px 60px}.gal-intel nav{top:-24px;overflow:auto}.gal-intel-cards{grid-template-columns:1fr}
}
@media(max-width:520px){.gal-title .ptitle{font-size:9px}.gal-zoom button{height:30px;min-width:30px}.gal-mobile-bar{top:99px}.gal-region-label{font-size:8px;letter-spacing:.06em}.gal-brief-time{font-size:18px}.gal-hyper-presets{grid-template-columns:repeat(5,1fr)}.gal-hyper-presets input{grid-column:1/-1}.gal-intel-top h2{font-size:25px}.gal-intel nav a{padding:7px 9px}}

/* ============================================================
   THEMING PASS — STRUCTURAL LAYER (design handoff, 2026-08)
   Appended so equal-specificity rules override the base defs
   above. Reads the token layer added to :root / themes.css.
   Deviations from the handoff's app.shell.css, all because a
   selector is more overloaded in the real app than the handoff
   assumed:
     · block 7 (hex readouts) scoped to .cghud .charbox — a bare
       .charbox also wraps breakdown grids, inputs, selects and
       steppers elsewhere and must stay rectangular.
     · block 6 (rail chamfer) scoped to .cgsteps .cgtab so the
       horizontal .cgtab tab-bars (NPC library, workspace) keep
       their shape; the codex + data-editor vertical rails (also
       .cgsteps) intentionally pick it up.
     · block 8 (hex steppers) deferred — the boxed .stepper is
       used app-wide and the hex buttons fight its container;
       README sanctions dropping it.
     · block 9 (dice shapes) skipped — app.css:1086+ already
       shapes .die.da/.dp and a global box-shadow:none would
       strip the setback die edge.
   ============================================================ */

/* 1. SURFACE: radial bloom + 1px hatch — full-bleed, fixed behind all
   content (a #content-scoped layer stopped at the 1280px column). Negative
   z-index keeps it above the body background but under every element. */
body::before{
  content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
  background-image:
    radial-gradient(ellipse at 50% -20%, var(--accent-wash-mid) 0%, transparent 60%),
    repeating-linear-gradient(var(--hatch-angle), var(--hatch) 0 1px, transparent 1px 4px);
}

/* 2. SHELL: glowing brackets + rivets */
.panel::before, .panel::after,
.panel > .bk::before, .panel > .bk::after{
  border-color:var(--rim);
  box-shadow:0 0 8px var(--rim-glow);
}
.panel{
  background-image:
    var(--rivet), var(--rivet), var(--rivet), var(--rivet),
    radial-gradient(ellipse at 50% -20%, var(--accent-wash-mid) 0%, transparent 60%),
    repeating-linear-gradient(var(--hatch-angle), var(--hatch) 0 1px, transparent 1px 4px),
    linear-gradient(180deg, var(--panel-a), var(--panel-b));
  background-repeat:no-repeat,no-repeat,no-repeat,no-repeat,no-repeat,repeat,no-repeat;
  /* rivets at the four CORNERS (owner preference), at native 11x12 so the
     screwhead stays crisp (the old 8x9 squash blurred it). */
  background-position:
    left 2px top 2px, right 2px top 2px, left 2px bottom 2px, right 2px bottom 2px,
    center top, 0 0, 0 0;
  background-size:
    11px 12px, 11px 12px, 11px 12px, 11px 12px, 100% 100%, auto, auto;
}
.panel.amber::before, .panel.amber::after,
.panel.amber > .bk::before, .panel.amber > .bk::after{border-color:var(--amber-dim)}

/* 3. SWEEP: one pass on load, never a loop (markup: div.holo-sweep) */
@keyframes holoSweep{
  0%{transform:translateY(-120%); opacity:0}
  12%{opacity:.9}
  100%{transform:translateY(420%); opacity:0}
}
.panel > .holo-sweep{
  position:absolute; left:0; right:0; top:0; height:38%;
  pointer-events:none; z-index:4;
  background:linear-gradient(180deg, transparent, var(--glow), transparent);
  animation:holoSweep 3.5s ease-in-out 1 both;
}

/* 4. FLICKER — scoped to the one chrome bar (everything on it flickers
   together) and, in chargen, the character title row. Other panel titles
   keep the glow but stay steady. */
@keyframes holoFlicker{
  0%,100%{opacity:1} 92%{opacity:1} 94%{opacity:.4} 96%{opacity:1} 98%{opacity:.6}
}
.ptitle{
  text-shadow:0 0 6px var(--glow), 0 0 16px var(--glow);
}
/* holoFlicker retired (owner: the flickering chrome bar / chargen title
   interfered with reading). The steady bloom (text-shadow above) stays. */

/* 5. SHAPE RULE: pills for controls, chamfer for structure */
.btn{
  clip-path:none;
  border-radius:16px;
  background:linear-gradient(180deg, var(--btn-hover), var(--btn-bg));
  color:var(--ink-hi);
  text-shadow:0 0 6px var(--glow);
}
.btn.amb{
  background:linear-gradient(180deg, var(--amber-dim), var(--amber-bg));
  border-color:var(--amber);
  color:var(--on-amber);
  text-shadow:none;
}
.btn.ghost{background:transparent; color:var(--dim); text-shadow:none}
.cgfilt, .cgsum-chip, .lkchip, .cite{border-radius:14px}
.cgfilt.on{
  color:var(--holo-bright);
  border-color:var(--holo);
  text-shadow:0 0 6px var(--glow);
}

/* 6. STEP RAIL: chamfered, outlined plates (scoped to vertical rails) */
.cgsteps{gap:3px}
.cgsteps .cgtab{
  margin:0 12px; padding:7px 12px;
  border:1px solid var(--line);
  clip-path:polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 0 100%);
  color:var(--ink);
  font-size:12px; letter-spacing:normal; text-transform:none;
}
.cgsteps .cgtab .stepno{width:16px; color:var(--faint); font-size:10px; opacity:1}
.cgsteps .cgtab.on{
  color:var(--holo-bright);
  border-color:var(--holo);
  text-shadow:0 0 6px var(--glow);
  box-shadow:inset 2px 0 0 var(--holo);
}
.cgsteps .cgsteps-group{
  font-family:var(--label); font-size:8.5px; letter-spacing:.24em;
  text-transform:uppercase; color:var(--holo);
  padding:18px 18px 7px;
}
.cgsteps .cgsteps-group:first-child{padding-top:0}
/* status glyph rides the right edge of the plate (reference) */
.cgsteps .cgtab .tabstat{margin-left:auto}
/* Crafting's rail gates steps whose inputs don't exist yet */
.cgsteps .cgtab:disabled{opacity:.42;cursor:not-allowed}
@media(max-width:720px){
  .crhdr input[type=text]{flex:1 1 100%;max-width:none!important}
}

/* 7. HEX READOUTS: HUD .charbox becomes a hexagon (scoped to .cghud) */
.cghud .charbox{
  position:relative;
  width:48px; height:54px; padding:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  border:none; background:var(--holo);
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.cghud .charbox::before{
  content:""; position:absolute; inset:1.5px; z-index:0;
  background:var(--input-bg);
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.cghud .charbox .cv,
.cghud .charbox .cn{position:relative; z-index:1}
.cghud .charbox .cv{font-family:var(--disp); font-size:16px; color:var(--holo-bright); line-height:1}
.cghud .charbox .cn{font-size:7px; letter-spacing:.12em; color:var(--holo-bright); order:-1; text-transform:uppercase}
.cghud .charbox.hurt{background:var(--red-dim)}
.cghud .charbox.hurt .cv, .cghud .charbox.hurt .cn{color:var(--red)}
.cghud .charbox.attr{background:var(--holo-dim)}
.cghud .charbox.attr .cv, .cghud .charbox.attr .cn{color:var(--ink-hi)}

/* 7b. BOTTOM HUD bar (markup: .cghud in cgView) — reference layout:
   labelled XP/credit figures, group labels over the hex rows, an
   encumbrance status chip (wash + ⚠ + setback note when over), and the
   Next action as an 18px holo pill. */
.cghud{
  display:flex; align-items:flex-start; gap:22px; flex-wrap:wrap;
  border-top:1px solid var(--holo);
  background:linear-gradient(180deg, var(--panel2), var(--panel));
  padding:12px 20px; margin:12px calc(-16px*var(--dsp)) calc(-14px*var(--dsp));
}
.cghud .gl{font-family:var(--label); font-size:8px; letter-spacing:.2em; text-transform:uppercase; color:var(--dim); margin-bottom:5px}
.cghud-fig{display:flex; flex-direction:column; line-height:1}
.cghud-fig .gl{margin-bottom:4px}
.cghud-fig .fv{font-family:var(--disp); font-size:20px; color:var(--ink-hi)}
.cghud-fig .fv.amb{color:var(--amber)}
.cghud-fig .fs{font-size:9.5px; color:var(--dim); margin-top:2px}
.cghud-div{width:1px; align-self:stretch; background:var(--amber-dim); margin:2px 0}
.cghud-hexes{display:flex; gap:7px; flex-wrap:wrap}
.cghud .charbox .cv.sm{font-size:13px}
.cghud-enc{
  display:inline-flex; align-items:center; gap:7px;
  padding:4px 11px; border:1px solid var(--line); border-radius:13px;
  background:var(--input-bg);
}
.cghud-enc .w{color:var(--red); font-size:11px}
.cghud-enc .fv{font-family:var(--disp); font-size:15px; color:var(--ink-hi); line-height:1}
.cghud-enc.over{background:var(--red-bg-hover); border-color:var(--red-dim)}
.cghud .encnote{font-size:9.5px; color:var(--red); margin-top:4px}
.cghud-right{margin-left:auto; display:flex; align-items:center; gap:14px; align-self:center}
.cghud .freenote{font-size:11px; color:var(--dim)}
.cghud .btn.cghud-next{border-radius:18px; border-color:var(--holo); padding:10px 20px}

/* 8. HEX STEPPERS (skills table) — outer hex is the rim (--line for −,
   --holo-dim for +), the glyph rides ::before on a solid inner face.
   Scoped to .stepper.hex so the boxed steppers elsewhere keep their
   container; buttons without data-glyph keep their visible text. */
.stepper.hex{background:transparent; border:none; border-radius:0; gap:3px; overflow:visible; align-items:center}
.stepper.hex button[data-glyph]{
  position:relative; width:20px; height:23px; padding:0; border:none;
  background:var(--line); color:transparent; font-size:0;
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.stepper.hex button[data-glyph]::before{
  content:attr(data-glyph); position:absolute; inset:1px;
  display:flex; align-items:center; justify-content:center;
  background:var(--input-bg); color:var(--dim); font-size:11px;
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.stepper.hex button.plus[data-glyph]{background:var(--holo-dim)}
.stepper.hex button.plus[data-glyph]::before{background:var(--btn-bg); color:var(--ink-hi)}
.stepper.hex button[data-glyph]:disabled{opacity:.35; cursor:default}

/* Skills-table polish (reference): action cell right-aligned − + hexes with
   the XP cost beside; em-dashes for empty count cells; the career chip as a
   green pill; rank figures in the display face. */
.skact{display:flex; align-items:center; justify-content:flex-end; gap:6px}
/* buttons hug the XP value on the right; the trimmed min-width pulls the −/+
   hexes rightward toward the figure while the column's left edge stays put */
.skact .nextxp{font-size:10.5px; color:var(--amber); min-width:14px; text-align:right}
.skact .nextxp.max{color:var(--faint)}
.tblz{color:var(--faint)}
.cchip{font-size:8.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--green);
  border:1px solid var(--green-dim); padding:1px 7px; border-radius:8px; vertical-align:1px}
.skilltbl td b{font-family:var(--disp); font-size:13px; color:var(--ink-hi); font-weight:400}
.xpbar b{font-family:var(--disp); font-size:12px}

/* 10. STATUS RULE: wash + glyph, never hue alone */
.cgsum-chip.warn, .status-chip.over{
  background:var(--red-bg-hover);
  border-color:var(--red-dim);
  color:var(--red);
}
.cgsum-chip.warn b, .status-chip.over b{color:var(--ink-hi)}
.cgsum-chip.ok{background:var(--green-bg); border-color:var(--green); color:var(--green)}

/* 11. LINES: accents carry structure, neutral carries data */
.xpbar{border-color:var(--holo-dim)}
.cgwrap .cgsteps{border-right:1px solid var(--holo-dim)}
.skillsplit > div:last-child,
.eqdetail, .npc-preview{border-color:var(--holo-dim)}
.cisec, .cardsection{border-top-color:var(--amber-dim)}
.skilltbl th{color:var(--holo); border-bottom-color:var(--holo-dim)}
.skilltbl td{border-bottom-color:var(--line)}

/* 12. REFERENCES: the third role */
.cite{border-color:var(--info); color:var(--info)}
.lkchip.src{border-color:var(--info); color:var(--info); background:transparent}

/* 13. LINKS */
.reflink{text-decoration:underline dotted; text-underline-offset:3px}
/* A .reflink can be a real <button> (keyboard-reachable, gets a focus ring)
   without looking like one — the colour/underline still come from .reflink. */
button.reflink{background:none; border:0; padding:0; margin:0; font:inherit; line-height:inherit; cursor:pointer}

/* 14. TITLE-ROW IDENTITY (round 3) — species · career · spec ride the
   character title as a subtitle; the summary strip is gone. */
.ptitle .cgsub{font-family:var(--body); font-size:12px; letter-spacing:normal;
  text-transform:none; color:var(--dim); font-weight:400}
.ptitle .cgsum-chip{font-family:var(--body); letter-spacing:.08em; flex:none}

/* 15. STICKY HUD — the readout strip pins to the viewport bottom while the
   chargen panel scrolls. Its 7b gradient (--panel2 → --panel) is already
   opaque; do NOT re-declare background here — a `color, gradient` shorthand
   with var() computes to initial (transparent), the exact README trap. */
.cghud{position:sticky; bottom:0; z-index:6}

/* 16. SECTION HEADER (skills tab) — chamfered title plate + count note +
   filter pills (reuses .cgfilt pill styling). */
.cgsec-head{display:flex; align-items:center; gap:14px; margin-bottom:14px; flex-wrap:wrap}
.cgsec-title{font-family:var(--disp); font-size:12px; letter-spacing:.2em; text-transform:uppercase;
  color:var(--holo-bright); text-shadow:0 0 6px var(--glow);
  border:1px solid var(--holo); background:var(--pick-on); padding:9px 18px;
  clip-path:polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px))}
.cgsec-note{font-size:11.5px; color:var(--dim); margin-left:auto; order:2}
.cgsec-filts{order:1; display:flex; gap:6px; align-items:center; flex-wrap:wrap}

/* 17. SKILL DETAIL PLATE — chamfered 13px plate: title + ✕ box, subtitle,
   three hexes (governing characteristic / rank / next-cost in amber),
   description, then source chips and citation in the --info role. */
.skd-plate{border:1px solid var(--holo-dim); background:var(--panel2); padding:15px 16px;
  clip-path:polygon(0 0, calc(100% - 13px) 0, 100% 13px, 100% 100%, 13px 100%, 0 calc(100% - 13px))}
.skd-title{display:flex; align-items:flex-start; gap:8px; margin-bottom:3px;
  font-family:var(--disp); font-size:13px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--holo-bright); text-shadow:0 0 6px var(--glow); line-height:1.35}
.skd-close{margin-left:auto; flex:none; width:21px; height:21px; border:1px solid var(--line);
  background:transparent; color:var(--dim); font-size:11px; cursor:pointer;
  display:flex; align-items:center; justify-content:center; padding:0}
.skd-sub{font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--dim); margin-bottom:14px}
.skd-hexes{display:flex; gap:9px; margin-bottom:15px}
.skd-hex{position:relative; width:52px; height:58px; flex:none;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  background:var(--holo);
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%)}
.skd-hex::before{content:""; position:absolute; inset:1.5px; z-index:0;
  background:var(--input-bg);
  clip-path:polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%)}
.skd-hex>*{position:relative; z-index:1}
.skd-hex .hl{font-size:7.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--holo-bright)}
.skd-hex .hv{font-family:var(--disp); font-size:17px; color:var(--holo-bright); line-height:1}
.skd-hex.rank{background:var(--holo-dim)}
.skd-hex.rank .hl,.skd-hex.rank .hv{color:var(--ink-hi)}
.skd-hex.next{background:var(--amber-dim)}
.skd-hex.next .hl,.skd-hex.next .hv{color:var(--amber)}
.skd-desc{font-size:11.5px; color:var(--dim); line-height:1.65; margin-bottom:14px; max-height:340px; overflow-y:auto}
.skd-chips{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:9px}
.skd-chip{font-size:9.5px; letter-spacing:.12em; text-transform:uppercase;
  border:1px solid var(--holo-dim); color:var(--holo); padding:3px 11px; border-radius:11px}
.skd-chip.src{border-color:var(--info); color:var(--info)}
/* Sticky skill detail: pure-CSS sticky + viewport-bounded max-height so the
   DESCRIPTION scrolls internally (source chips pin below it) and the reader
   never has to scroll the whole PAGE to read it. No JS height measurement —
   the old fixed-pixel fit went stale when a category was collapsed and broke
   page scrolling. max-height divides out the text-size zoom on #app. */
.skd-plate.skilldetail{position:sticky; top:12px; max-height:calc((100vh - 24px)/var(--ts-zoom,1) - 96px);
  display:flex; flex-direction:column; overflow:hidden}
.skd-plate.skilldetail .skd-desc{flex:1 1 auto; min-height:0; max-height:none}

/* 18. FULL-BLEED CHARGEN SHELL — the reference runs its bars edge to edge:
   title row, XP bar, body row (rail flush left) and HUD all touch the panel
   border, separated by rules instead of floating in panel padding. */
#content{max-width:1360px}
.panel.cgpanel{padding:0}
.cgpanel .ptitle{padding:13px 20px; margin-bottom:0; border-bottom:1px solid var(--holo-dim)}
.cgpanel .ptitle::after{display:none}
.cgpanel .xpbar{border:none; border-bottom:1px solid var(--holo-dim); padding:9px 20px; margin-bottom:0}
.cgpanel .cgwrap{gap:0; align-items:stretch}   /* rail stretches full height so its divider runs all the way down */
.cgpanel .cgsteps{padding:16px 0 24px}
.cgpanel .cgmain{padding:16px 20px 0}
.cgpanel .cghud{margin:0}

/* 19. SHAPE + DIVIDER DETAILS — accordion bars take the structural chamfer
   (11px notch, accent frame); the filter separator is an ornamental rule,
   so it takes the secondary per the line hierarchy. */
.acc{border-color:var(--holo-dim);
  clip-path:polygon(0 0, calc(100% - 11px) 0, 100% 11px, 100% 100%, 11px 100%, 0 calc(100% - 11px))}
.cgfiltsep{background:var(--amber-dim)}

/* 20. POP — the readout figures carry the bloom their reference screens
   have; light mode kills all of it via --glow:transparent. */
.cghud .charbox .cv, .skd-hex .hv{text-shadow:0 0 8px var(--glow)}
.cghud-fig .fv{text-shadow:0 0 10px var(--glow)}
.cgsec-note b{color:var(--amber)}

/* ============================================================
   CASCADE REPAIR + RESPONSIVE TAIL (UI workstream)
   The THEMING PASS layer above carries no media queries, so its
   .cgpanel-scoped rules (specificity 0,2,0) out-specify the
   ≤860px mobile block's .cgsteps/.cgwrap rules (0,1,0) regardless
   of source order — silently dropping the off-canvas chargen
   drawer's own padding + gap. This block re-asserts those mobile
   values at matching specificity.

   CONVENTION: every responsive rule added by a later UI phase
   appends HERE, after the THEMING PASS layer, so source order ==
   cascade order for everything this workstream adds. Do not
   interleave new rules back into the file body.
   ============================================================ */
@media (max-width:860px){
  /* section 18's full-bleed shell sets .cgpanel .cgwrap{gap:0} and
     .cgpanel .cgsteps{padding:16px 0 24px} for the desktop rail;
     the mobile drawer needs its own gap + horizontal padding back */
  .cgpanel .cgwrap{gap:8px}
  .cgpanel .cgsteps{padding:12px 10px}
  /* the sliding drawer is a dark console (sb-* family), not the
     holo-dim vertical divider section 11 paints on desktop */
  .cgwrap .cgsteps{border-right-color:var(--sb-line)}
  /* the chargen title row is a non-wrapping flex line, so at phone width the
     Save / Save As / Revert / Characters actions overflow off the right edge.
     Let the row wrap; the actions (margin-left:auto) drop to their own line. */
  .cgpanel .ptitle{flex-wrap:wrap}
  .cghdr-actions{margin-left:0}
}

/* ============================================================
   DESIGN ALIGNMENT — drawn controls + press physics (UI Phase 3)
   Replaces the last OS-native surfaces (accent-color checkbox/radio)
   with machined hardware, and gives .btn a physical press. New rules
   only; the accent-color declarations elsewhere go inert under
   appearance:none. Deviation from the design-system readme: the
   check/dot ink is --input-bg (not --ink-hi) because --ink-hi tracks
   the accent's own lightness in both modes and would sit low-contrast
   ON the accent fill; --input-bg inverts opposite the fill, so the
   mark stays legible in every theme. */
input[type=checkbox], input[type=radio]{
  appearance:none; -webkit-appearance:none;
  width:14px; height:14px; flex:none; cursor:pointer; vertical-align:-2px;
  border:1px solid var(--line); background:var(--input-bg);
  display:inline-grid; place-content:center; position:relative;
  transition:background var(--t-state,.12s), border-color var(--t-state,.12s);
  /* one chamfered corner — a machined plate, not a web checkbox */
  clip-path:polygon(0 0, calc(100% - 4px) 0, 100% 4px, 100% 100%, 0 100%);
}
input[type=radio]{clip-path:none; border-radius:50%}
input[type=checkbox]:checked, input[type=radio]:checked{
  background:var(--chk-accent,var(--holo)); border-color:var(--chk-accent,var(--holo));
}
input[type=checkbox]:checked::after{
  content:""; width:3px; height:7px; margin-top:-1px;
  border:solid var(--input-bg); border-width:0 2px 2px 0; transform:rotate(45deg);
}
input[type=radio]:checked::after{
  content:""; width:6px; height:6px; border-radius:50%; background:var(--input-bg);
}
input[type=checkbox]:focus-visible, input[type=radio]:focus-visible{
  outline:2px solid var(--holo); outline-offset:1px;
}
input[type=checkbox]:disabled, input[type=radio]:disabled{opacity:.42; cursor:default}
/* the dataset/mentor checkboxes are green (ownership/enabled), not holo —
   set the accent on the container so the drawn fill picks it up */
.dstog, .dssrc, .mentorbox{--chk-accent:var(--green)}

/* PRESS PHYSICS — .btn sits on a 2px bevel and drops onto its seat when
   pressed (the theming pass already made it a clip-path-free pill, so an
   outer bevel shadow renders). .navbtn keeps its clip-path, which would
   clip an outer shadow, so it presses with the inset recess only. */
.btn{box-shadow:var(--bevel)}
.btn:active:not(:disabled){transform:translateY(var(--press-shift)); box-shadow:var(--bevel-recess)}
.navbtn:active{transform:translateY(var(--press-shift))}

/* ============================================================
   MOBILE SWEEP (UI Phase 5)
   Desktop-neutral additions only: safe-area insets read 0 off a
   notch-less display, and the tap-target bumps are gated on a
   coarse pointer. Paint-sensitive layout work (table scroll
   wrappers, the chargen talent tree / stat rows, PDF touch pan)
   is tracked as remaining in docs/ui-review.md — it needs a real
   narrow layout viewport to verify, which this environment can't
   provide. Appends after the THEMING PASS layer per convention. */

/* M1 — safe-area insets. In standalone PWA + viewport-fit=cover the
   sticky header, the fixed chargen drawer and the galaxy bottom sheets
   underlap the notch / gesture bar; env() is 0 on a normal display. */
@supports (padding:env(safe-area-inset-top)){
  #hdr{padding-top:env(safe-area-inset-top)}
  #content{padding-bottom:calc(18px*var(--dsp) + env(safe-area-inset-bottom))}
}
@media (max-width:860px){
  @supports (padding:env(safe-area-inset-top)){
    .cgsteps{padding-top:calc(12px + env(safe-area-inset-top));
      padding-bottom:calc(12px + env(safe-area-inset-bottom))}
  }
}
@media (max-width:900px){
  @supports (padding:env(safe-area-inset-bottom)){
    .gal-sidebar,.gal-dossier{padding-bottom:calc(28px + env(safe-area-inset-bottom))}
  }
}

/* M2 — coarse-pointer tap targets. Grows the smallest controls toward a
   ~40px touch area without touching the fine-pointer (desktop) layout. */
@media (pointer:coarse){
  .navbtn{min-height:40px}
  .gal-zoom button, .stepper button{min-height:34px}
  /* buttons: 34px (owner, 2026-10-02) — the 40/44px floor turned every
     .btn into a tall pill on phones; 34px is still a fair touch target */
  .btn, .btn.sm{min-height:34px}
  .skd-close{width:32px; height:32px}
  input[type=checkbox], input[type=radio]{min-width:18px; min-height:18px}
}

/* Roster button column — grouped, labelled, with descriptor lines (chargen-sheet
   rosterView). Widened from 132px so the two-line buttons read cleanly. */
.chmgr-btns{flex-basis:176px}
.chmgr-grp{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--faint);
  margin-top:calc(10px*var(--dsp));padding-bottom:calc(3px*var(--dsp));
  border-bottom:1px solid var(--line)}
.chmgr-grp:first-child{margin-top:0}
.chmgr-btns .btn.cgbtn2{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px;line-height:1.25;text-align:center}
.cgbtn2-t{display:block}
.cgbtn2-d{display:block;font-size:10px;letter-spacing:.03em;text-transform:none;opacity:.72}
@media(max-width:720px){
  .chmgr-grp{flex:1 1 100%;margin-top:calc(6px*var(--dsp))}
}

/* Roster search field (chargen-sheet rosterView) — sits in the .toolbar flex
   row beside the campaign / sort selects. */
.rostersearch{flex:1 1 200px;min-width:160px;max-width:340px}

/* Bottom HUD collapse (cgHud). Desktop keeps every group inline: the
   collapsible wrapper reproduces the strip's 22px inter-group gap so the layout
   is unchanged, and the Vitals toggle is hidden. On phones the characteristics
   / attributes / encumbrance groups fold behind the toggle — leaving XP /
   Credits / Next — and expand full-width on demand. */
.cghud-collapsible{display:flex;align-items:flex-start;gap:22px;flex-wrap:wrap}
.cghud-toggle{display:none}
@media(max-width:720px){
  .cghud-toggle{display:inline-flex;align-self:center}
  .cghud .cghud-collapsible{display:none;flex-basis:100%;order:5;margin-top:8px}
  .cghud.exp .cghud-collapsible{display:flex}
  /* keep XP · Credits · Vitals · Next on one row: tighter gaps and a compact
     Next pill (34px tall — still a fair tap target; the 44px coarse-pointer
     floor made the whole bar two rows tall) */
  .cghud{gap:10px 14px;padding:8px 14px}
  .cghud .btn.cghud-next{padding:6px 12px;min-height:34px;font-size:10px;letter-spacing:.08em}
  .cghud-right{gap:8px}
  .cghud .btn.cghud-toggle{min-height:34px}
  /* label centred: an inline-flex button stretches its text to the top */
  .cghud-toggle{align-items:center;justify-content:center}
}
/* Workspace XP strip (cgView): same fold on phones — the breakdown hides
   behind an "XP n/N ▾" toggle that sits on one row with Creation / GM Options,
   and opens full width below them. Desktop: the wrapper is transparent. */
.cgxp-fold{display:contents}
.cgxp-toggle{display:none}
@media(max-width:720px){
  .cgxpbar{gap:8px}
  .cgxp-toggle{display:inline-flex;align-items:center;gap:4px}
  /* the figure takes the button's own type, not .xpbar b's display face */
  .cgxp-toggle b{font:inherit;letter-spacing:inherit}
  .cgxpbar .cgxp-actions{gap:8px!important}
  .cgxpbar .cgxp-fold{display:none}
  .cgxpbar.exp .cgxp-fold{display:flex;flex-wrap:wrap;gap:6px 16px;flex-basis:100%;order:5;padding-top:6px;border-top:1px solid var(--line)}
}

/* GM Grants catalogues on phones. The fixed desktop columns (78/64/52px) leave
   the +/- steppers and the "career" checkbox cramped; below 720px the skills row
   drops "career" onto its own line under the name and lets the stepper size
   itself, and the helper fonts come up off 9-10px. (The mini-stepper +/- buttons
   already widen to a 40px touch target under the coarse-pointer block above.) */
@media(max-width:720px){
  .grow-sk{grid-template-columns:1fr auto;gap:4px 8px}
  .grow-sk .grow-career{grid-column:1 / -1;justify-content:flex-start;font-size:11px}
  .grow-tal{grid-template-columns:1fr auto auto}
  .fl-hint{font-size:10px}
}

/* ---- Image lightbox (click-to-enlarge) ---- */
/* A dedicated overlay, above #modal (z-index 80), so it can sit on top of a
   codex quick-view. Clicking the scrim/image/✕ closes it (see boot.js). */
#lightbox{position:fixed; inset:0; display:none; z-index:120;
  align-items:center; justify-content:center; background:var(--modal-scrim);
  padding:4vh 4vw; cursor:zoom-out}
#lightbox.open{display:flex}
#lightboxImg{max-width:100%; max-height:100%; object-fit:contain;
  box-shadow:0 0 40px var(--modal-glow); border:1px solid var(--holo-dim)}
#lightboxClose{position:absolute; top:14px; right:16px; width:34px; height:34px;
  background:var(--panel); color:var(--fg); border:1px solid var(--holo-dim);
  border-radius:4px; font-size:16px; line-height:1; cursor:pointer}
#lightboxClose:hover{border-color:var(--holo)}
/* Detail/hero images that open the lightbox on click. */
.zoomable{cursor:zoom-in}
/* Corner magnifier on selection-grid thumbnails — the card body still selects
   on click; only this button zooms (it stops propagation). */
.imgzoombtn{position:absolute; top:3px; right:3px; z-index:2;
  width:22px; height:22px; padding:0; line-height:1;
  display:flex; align-items:center; justify-content:center;
  background:var(--modal-scrim); color:var(--fg);
  border:1px solid var(--holo-dim); border-radius:3px;
  font-size:12px; cursor:zoom-in; opacity:0; transition:opacity .12s}
.spcard-portrait:hover .imgzoombtn, .imgzoombtn:focus-visible{opacity:.92}
.imgzoombtn:hover{border-color:var(--holo); opacity:1}
@media(pointer:coarse){ .imgzoombtn{opacity:.85} }

/* ---- Larger three-across Career & Specialization cards ---- */
/* Scoped to the career/spec browsers only (chargen-build.js adds .pickgrid-lg);
   the base .pickgrid used by other pickers is untouched. */
.pickgrid.pickgrid-lg{grid-template-columns:repeat(3,1fr); gap:12px}
.pickgrid-lg .ftile{padding:14px 16px; font-size:14px; min-height:92px}
.pickgrid-lg .ftile small{font-size:11px}
@media(max-width:1100px){ .pickgrid.pickgrid-lg{grid-template-columns:repeat(2,1fr)} }
@media(max-width:680px){ .pickgrid.pickgrid-lg{grid-template-columns:1fr} }
/* Career SELECTION cards: same 3-across grid but tighter — the tile holds only
   a name + one spec-list line, so the tall min-height left dead space. Row
   auto-stretch still keeps a row's cards aligned. */
.pickgrid-career .ftile{padding:9px 13px; min-height:0}
.pickgrid-career .ftile small{margin-top:3px}

/* Career SKILLS picker: brighter tiles so they read as distinct controls
   against the panel (the base .pick tone blended in). Column count is set
   inline per skill-count (8 → 4×2, 6 → 6×1). */
.cskillgrid{gap:8px}
.cskillgrid .cskilltile{background:var(--input-bg); border-color:var(--holo-dim); padding:9px 11px}
.cskillgrid .cskilltile:hover{border-color:var(--holo)}
.cskillgrid .cskilltile.on{background:var(--pick-on); border-color:var(--holo)}
.cskillgrid .cskilltile input{width:15px; height:15px; flex:none}

/* ============================================================
   SETTINGS UI/UX PASS (2026-08)
   Appended after the THEMING PASS layer per css/CLAUDE.md so source
   order == cascade order. Tokens only; no hardcoded colors.
   ============================================================ */

/* A label-left / action-right row used across the Settings Data tab
   (storage, backup, reset). Replaces the old inline flex rows that
   crushed their copy on narrow phones — below 520px the action drops
   under its description at full width. */
.datarow{display:flex; justify-content:space-between; align-items:center; gap:14px; padding:10px 0}
.datarow + .datarow{border-top:1px solid var(--line)}
.datarow .datarow-act{flex:0 0 auto}
@media(max-width:520px){
  .datarow{flex-direction:column; align-items:stretch}
  .datarow .datarow-act{width:100%}
}

/* Rules tab: one-line impact summary + "takes effect" tag above each
   collapsible "Learn more" (.rmhelp) block. */
.setimpact{font-size:12px; color:var(--dim); line-height:1.55; margin:4px 0 2px}
.setimpact .seteffect{display:inline-block; font-size:9px; letter-spacing:.12em; text-transform:uppercase;
  color:var(--faint); border:1px solid var(--line); border-radius:3px; padding:1px 6px; margin-left:4px; white-space:nowrap}

/* Selected affordance for segmented controls (#8) — a check precedes the
   pressed option's label, reinforcing the aria-pressed state visually. */
.moderow .btn[aria-pressed="true"]::before{content:"\2713\00a0"; font-size:.9em} /* nbsp: a plain trailing space collapsed against the label ("✓PARTY") */
/* Selected theme card gets a corner check on top of its .on tint. */
.themepick{position:relative}
.themepick.on::after{content:"\2713 Selected"; position:absolute; top:8px; right:8px;
  font-size:8.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--holo)}

/* Coarse-pointer tap targets (#3) — grows the controls the earlier
   pointer:coarse block missed (settings tabs, color swatches, theme
   cards) and re-asserts a 44px floor for the shared button set. Later
   source order wins over the 40px rule above at equal specificity. */
@media(pointer:coarse){
  .navbtn{min-height:44px}
  /* steppers + map zoom: 34px like every button (owner, 2026-10-02) */
  .gal-zoom button, .stepper button{min-height:34px}
  .gal-zoom button{min-width:34px}  /* square: the ≤520px rule narrows it to 30 */
  .btn, .btn.sm{min-height:34px}
  /* mini steppers (crit modifier, etc.) only get a height floor above — their
     22px width stays a hard tap target, so widen them on touch too. */
  .stepper.mini button{min-width:34px}
  /* destiny token buttons wrap 34/52px art; pad the hit area to 44px. */
  .dtokbtn{min-width:44px; min-height:44px; display:inline-flex; align-items:center; justify-content:center}
  .cgtab{min-height:44px}
  .setgrid .themepick{min-height:44px}
  .pick{min-height:44px}
  input[type=color]{min-height:44px}
  .symrow input[type=color]{height:44px}
}

/* Sources tab (Dataset Manager) — the book list is ~60 rows over six groups,
   so its scroller gets a viewport-relative cap instead of the 340px porthole
   every other .ds-scroll panel uses. */
.ds-scroll-src{max-height:60vh}
/* Group collapse chevron: a bare glyph button, sized to the header's own
   uppercase label type. Colors are tokens — never a literal. */
.dssrc-chev{background:none; border:0; padding:0 2px; margin:0; cursor:pointer;
  font-size:11px; line-height:1; color:var(--faint); flex:none}
.dssrc-chev:hover{color:var(--holo)}
.dssrc-filt{margin:0 0 8px}

/* Dataset Manager — post-recompose surfacing (P3). A config change that COST
   resolution says so once, in place, rather than opening a modal over what is
   usually a clean switch. Same amber plate as .rule-bulkbar; tokens only. */
.ds-alertstrip{display:flex; gap:6px; align-items:center; flex-wrap:wrap;
  margin-bottom:calc(12px*var(--dsp)); padding:calc(8px*var(--dsp)) 10px;
  border:1px solid var(--amber-dim); background:var(--amber-bg);
  color:var(--amber); font-size:12px; line-height:1.5}

/* Diagnostics drill-down (P5). A counts row whose active total trails its base
   total opens the per-book breakdown through a button.reflink on its name (a
   real, keyboard-reachable control — the row itself is no longer clickable).
   The modal's book list is deliberately NOT .ds-scroll: that class is
   positionally matched by restoreScroll and counted by test-phase1 on the
   Sources tab. */
.ds-catdetail{max-height:50vh; overflow:auto}

/* Import feedback (P6). The Activity Log renders below the fold on the Import
   tab, so a finished import scrolls it into view and pulses its outline —
   movement at the edge of vision, not a modal over a routine success. The
   global prefers-reduced-motion rule (top of this file) already kills it. */
.ds-flash{animation:dsFlash 1.2s ease-out 2}
@keyframes dsFlash{
  0%,60%{outline:2px solid var(--amber); outline-offset:2px}
  100%{outline-color:transparent}
}

/* ---- Dataset Manager — mobile pass (P7) ----
   The view was built desktop-first: a 440px masonry, a 132px fixed select and
   two nested scrollers (340px panels, 60vh Sources) inside a page that already
   scrolls. On a phone that stacks three scroll contexts and squeezes the record
   NAME — the one thing a row is read for — down to a few characters.

   Scoped to .ds-tabs / .dsgrid so the shared idioms (.npc-library-tabs, the
   .initrow used across chargen/NPC/encounters, .cgtab) keep their own layout
   everywhere else. Appended last per the css/CLAUDE.md cascade convention. */
@media(max-width:640px){
  /* Four tabs in one flex row shrink to unreadable; 2×2 gives each a full label. */
  .ds-tabs{display:grid; grid-template-columns:1fr 1fr; gap:4px}
  .ds-tabs .cgtab{width:100%; justify-content:flex-start}
  .dsgrid{columns:1}
  /* No nested scroll on phones — every panel flows and the PAGE scrolls. */
  .dsgrid .ds-scroll, .dsgrid .ds-scroll-src{max-height:none}
  /* Rows wrap instead of starving the name: label takes its own line, the
     controls (mode select + Enabled toggle) share the one below it. */
  .dsgrid .initrow{flex-wrap:wrap; row-gap:6px}
  .dsgrid .initrow .iname{flex:1 1 100%}
  .dssrc-grid{grid-template-columns:1fr}
  .dsmode{flex:1 1 auto; width:auto}
}
/* Touch targets — the two dataset-specific controls the earlier coarse-pointer
   blocks (:3252, :3359) never named. .dssrc/.dstog are label rows, so the floor
   goes on the label and the box grows with it. */
@media(pointer:coarse){
  .dssrc, .dstog{min-height:44px}
  .dssrc input[type=checkbox], .dstog input[type=checkbox]{width:20px; height:20px}
}

/* ---- Codex + Data Editor rails: the off-canvas drawer (Codex pass, P1) ----
   Both rails reuse chargen's .cgwrap/.cgsteps shell, so the generic ≤860px
   drawer rule (app.css §"chargen: short sticky rail", specificity 0,1,0)
   already fixes them off-canvas with transform:translateX(-102%). The
   desktop rule `.de-wrap .cgsteps,.cx-wrap .cgsteps` (0,2,0) out-specified it
   on `position`/`flex`/`max-height` — but never declared `transform` — so the
   rail stayed in flow AND invisible: ~1,140px of blank space above the first
   result below 760px, and a dead 190px column at 761–860px. Re-assert the
   drawer's own geometry at matching specificity; left/bottom/width/transform/
   background/z-index all come from the generic rule uncontested. `top:0` is
   required — the desktop rule's `top:8px` would offset the fixed drawer.
   The .cxlist max-height relaxation moves 760px→860px, deliberately: the rail
   stops eating vertical space at the same breakpoint the drawer starts. */
@media (max-width:860px){
  .de-wrap .cgsteps,.cx-wrap .cgsteps{flex:none;max-height:none;position:fixed;top:0}
  .pickgrid.cxlist{max-height:none}
}

/* ---- Party Roster section rail (Campaign pass) ----
   Eight stacked sections was a very long scroll; the rail shows one at a time.
   Five entries, so it needs none of the ~30-entry treatment .de-wrap/.cx-wrap
   get — only a sticky column, because a single tab can still be long (Economy
   carries the acquisition surface AND the ledger). Same ≤860px re-assert those
   two need, and for the same reason: `top:8px` at (0,2,0) would otherwise
   offset the generic fixed drawer, which declares its geometry at (0,1,0).
   The section builders were written to STACK, so each opens with an inline
   margin-top that reads as dead space once it is the first thing in a tab —
   inline styles are why this one needs !important. */
.pty-wrap .cgsteps{position:sticky;top:8px;max-height:min(72vh,780px);overflow:auto}
.pty-wrap .cgmain>.ptitle:first-child{margin-top:0!important}
@media (max-width:860px){
  .pty-wrap .cgsteps{flex:none;max-height:none;position:fixed;top:0}
}

/* ---- Keyboard focus + screen-reader utility (Codex pass, P2) ----
   The app's clickable surfaces are a mix of real <button>s and onclick-carrying
   spans/divs; both now sit in the tab order, so both need a visible ring. Same
   recipe as the drawn checkboxes above (outline + var(--holo)) rather than a
   new token — :focus-visible keeps it off mouse clicks. */
.pick:focus-visible,.cgtab:focus-visible,.lkchip:focus-visible,.cxlink:focus-visible,
.gsr:focus-visible,.reflink:focus-visible,.cite:focus-visible,.cx-seg button:focus-visible,
.cx-clear:focus-visible,.cgrail:focus-visible,.cgsteps-close:focus-visible,
.card[role="button"]:focus-visible,.alertline[role="button"]:focus-visible{
  outline:2px solid var(--holo); outline-offset:1px;
}
/* #modalBox takes focus programmatically when the dialog opens (it is a
   container, not a control) — the ring would be noise. */
#modalBox:focus,#modalBox:focus-visible{outline:none}
/* Visually hidden, still announced — the codex result-count live region. */
.sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0}

/* ---- Codex entry-page GM action row (Codex pass, P4) ----
   A second .toolbar under the entry's own toolbar. It reads as a secondary
   strip, not a peer of Back / Favorite / Edit: hairline above, tighter below. */
.cx-gmbar{margin-top:calc(-6px*var(--dsp)); padding-top:calc(8px*var(--dsp));
  border-top:1px solid var(--line)}

/* ---- Print Center: Play grid + handoff flash (Print Center pass, P4) ----
   The Play workspace carried an inline `grid-template-columns:1fr 1fr 1fr`,
   which out-specified the ≤920px `.pc-grid{grid-template-columns:1fr}` rule and
   left phones with three crushed columns. auto-fit collapses on its own at any
   width, and it absorbs the fourth (NPC) panel without a magic number. */
.pc-grid-play{grid-template-columns:repeat(auto-fit,minmax(250px,1fr))}
/* Handoff target: Chargen's "Print…" and the Vehicles roster's "Print" land
   here with the card centered and briefly outlined, so the user can see WHERE
   they arrived. Outline, not border/box-shadow — it costs no layout. The global
   prefers-reduced-motion rule (top of this file) already kills the animation. */
.pc-playcard.pc-focus{animation:pcFlash 1.2s ease-out 2}
@keyframes pcFlash{
  0%,100%{outline:2px solid transparent; outline-offset:2px}
  35%{outline:2px solid var(--amber); outline-offset:2px}
}

/* ---- Print Center: selected-items tray + front matter (Print Center pass, P5) ----
   Screen half — theme tokens only. The tray is the one place the whole packet
   is visible at once, so it scrolls rather than pushing the Preview button off
   the panel. */
.pc-tray{display:grid; gap:calc(8px*var(--dsp)); max-height:180px; overflow:auto;
  padding:calc(7px*var(--dsp)); border:1px solid var(--line); background:var(--panel2)}
.pc-traygroup{display:flex; flex-wrap:wrap; gap:4px; align-items:center}
.pc-traylabel{flex:1 1 100%; font:700 9px var(--disp); letter-spacing:.12em;
  text-transform:uppercase; color:var(--faint)}
.pc-chip{display:inline-flex; align-items:center; gap:5px; padding:3px 7px; font-size:11px;
  border:1px solid var(--line); background:var(--panel); color:var(--ink); cursor:pointer}
.pc-chip:hover{border-color:var(--holo); color:var(--holo)}
.pc-tog{display:flex; align-items:center; gap:8px; font-size:11.5px; color:var(--dim)}
.pc-tog input{width:15px; height:15px; accent-color:var(--holo)}
.pc-compose{display:flex; flex-direction:column; gap:6px; margin-top:8px; padding-top:8px; border-top:1px solid var(--line)}
.pc-compose .pc-complabel{font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--dim)}
.pc-compose select{width:100%; padding:5px 8px; border-radius:6px; border:1px solid var(--line); background:var(--panel); color:var(--ink); font-size:12px}
.pc-compose .pc-tog{margin-left:2px}

/* Print half — inside #printDoc, so the fixed --pr-* print palette applies and
   the screen theme is (deliberately) invisible here. */
#printDoc .prcovertitle{font:700 26px var(--disp); letter-spacing:.06em;
  color:var(--pr-accent); margin:38px 0 6px; text-align:center}
#printDoc .prcoversub{font-size:13px; text-align:center; color:var(--pr-ink)}
#printDoc .prcoverruleset{font-size:10px; text-align:center; color:var(--pr-accent);
  letter-spacing:.14em; text-transform:uppercase; margin:6px 0 0}
#printDoc .prcoverdate{font-size:9px; text-align:center; color:var(--pr-faint);
  letter-spacing:.1em; text-transform:uppercase; margin:4px 0 30px}
#printDoc .prdkrow{display:grid; grid-template-columns:34px 128px 1fr; align-items:center;
  gap:8px; padding:3px 0; font-size:10.5px; break-inside:avoid}
#printDoc .prdkglyph{text-align:center; font-size:15px; line-height:1}
#printDoc .prdkname{font-weight:700; color:var(--pr-accent)}
#printDoc .prdkmean{color:var(--pr-ink)}
#printDoc .prtocrow{display:flex; align-items:baseline; gap:6px; padding:2.5px 0;
  font-size:10.5px; break-inside:avoid}
#printDoc .prtocname{flex:0 1 auto}
#printDoc .prtocdots{flex:1 1 auto; border-bottom:1px dotted var(--pr-line);
  transform:translateY(-2px)}
#printDoc .prtocrow .prxref{flex:0 0 auto; color:var(--pr-faint); white-space:nowrap}

/* ---- Print Center: mobile reachability + a11y (Print Center pass, P6) ----
   Below 920px .pc-grid already collapses to one column, which stacked the
   panels rail → list → output: the count, the tray and the Preview button sat
   under ~570px of scrolling records, so on a phone the packet controls were
   effectively unreachable. Same fix the Galactic Atlas uses for its sidebar
   (.gal-sidebar{order:2}) — reorder the grid items, don't reflow the markup. */
@media(max-width:920px){
  .pc-output{order:2}
  .pc-listpanel{order:3}
}
/* Keyboard rings for the three Print Center controls the app-wide
   :focus-visible list (§"Keyboard focus + screen-reader utility") never named. */
.pc-group:focus-visible,.pc-row input:focus-visible,.pc-chip:focus-visible{
  outline:2px solid var(--holo); outline-offset:1px;
}
/* Touch targets — .pc-row is a label row, so the floor goes on the label and
   the checkbox grows with it (the .dssrc/.dstog precedent above). */
@media(pointer:coarse){
  .pc-row,.pc-group{min-height:44px}
  .pc-row input[type=checkbox]{width:20px; height:20px}
}

/* ---- Dice Calculator: mobile reflow + touch targets (dice pass) ----
   The narrative view stacks Build Pool + Statistics (left column) then Current
   Pool + Results + History (right column). On desktop the 5fr/6fr grid keeps
   that grouping; on phones the single column would put the Roll button (inside
   Current Pool) below the whole palette AND the statistics panel. Flatten the
   two bare wrapper divs with display:contents and reorder the panels by their
   dp-* class so Roll sits directly under Build Pool. */
@media(max-width:900px){
  .dice-cols>div{display:contents}
  .dice-cols>div>.dp-build{order:1}
  .dice-cols>div>.dp-pool{order:2}
  .dice-cols>div>.dp-result{order:3}
  .dice-cols>div>.dp-stats{order:4}
  .dice-cols>div>.dp-history{order:5}
}
/* Narrowest phones: let the history row's fixed timestamp track give way so the
   dice/result columns keep room (the M7 fixed-track finding). */
@media(max-width:420px){
  .dhrow{grid-template-columns:84px 1fr auto auto; gap:6px}
}
/* Touch targets — the dice calculator's own controls that the shared
   coarse-pointer blocks (:3257, :3364) never named. None are .btn-classed, so
   they inherit no tap floor. Grow the interactive ones to a comfortable size. */
@media(pointer:coarse){
  .dpal-mini button{min-width:40px; height:34px}
  .dpool-sym{width:38px; height:38px}
  .dpool .die.lg{width:30px; height:30px}
  .dnum-one{min-height:36px}
  .dhrow{min-height:44px}
  .dhrow .dhx{min-width:34px; min-height:34px}
  .dsaved-chip button{min-height:40px}
  .dstats-toggle{min-height:40px}
}

/* ---------- Random Generators — the generator station ----------
   Result surface sits directly under the buttons (above the collapsible
   language tools); the log renders newest-first with per-entry actions. */
.gen-filterbar{display:flex;gap:12px;align-items:center;flex-wrap:wrap;margin-bottom:calc(12px*var(--dsp))}
.gen-lock{display:flex;gap:8px;align-items:center;font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--dim)}
.gen-lock select{font-size:12px;text-transform:none;letter-spacing:normal}
.genactions{margin:0 0 8px}
.genflash{color:var(--holo);font-size:12px;letter-spacing:.04em;margin:0 0 8px}
.genlog{padding:0;white-space:normal;max-height:360px;overflow-y:auto}
.genentry{display:flex;gap:8px;align-items:flex-start;padding:10px 12px;border-bottom:1px solid var(--line)}
.genentry:last-child{border-bottom:none}
.genentry.latest{background:var(--pick-on);box-shadow:inset 3px 0 0 var(--holo)}
.gentext{flex:1;min-width:0;white-space:pre-wrap;font-size:14px;line-height:1.6}
.genrow-actions{display:flex;gap:4px;flex:0 0 auto}
.iconbtn{background:transparent;border:1px solid var(--line);color:var(--dim);cursor:pointer;
  padding:2px 7px;font-size:12px;line-height:1.3;border-radius:2px}
.iconbtn:hover{color:var(--holo);border-color:var(--holo-dim)}
.gen-langtools{margin-top:calc(14px*var(--dsp));border-top:1px solid var(--line);
  padding-top:calc(12px*var(--dsp))}
.gen-langtools>summary{cursor:pointer;font-size:12px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--holo);user-select:none;list-style:none}
.gen-langtools>summary::-webkit-details-marker{display:none}
.gen-langtools>summary::before{content:"▸";font-size:10px;display:inline-block;margin-right:6px;
  transition:transform .12s}
.gen-langtools[open]>summary::before{transform:rotate(90deg)}
.gen-langtools>summary:hover{filter:brightness(1.15)}
.gen-langtools .translator-grid{margin-top:10px}
.btn[disabled],.iconbtn[disabled]{opacity:.45;cursor:not-allowed;pointer-events:none}
@media(pointer:coarse){
  .iconbtn{min-width:36px;min-height:34px}
}

/* ---------- rules reference — improvement pass (appended) ---------- */
/* Per-rule collapse: the title toggles its own body; chevron rotates when shut. */
.rule-title{cursor:pointer}
.rule-chev{display:inline-block;color:var(--holo-dim);font-size:10px;margin-right:5px;transition:transform .12s;transform:none}
.rule-item.collapsed .rule-chev{transform:rotate(-90deg)}
.rule-item.collapsed{padding-top:calc(7px*var(--dsp));padding-bottom:calc(7px*var(--dsp))}

/* Toolbar "Manage ▾" overflow menu (the app's only overflow menu — kept minimal). */
.rule-manage{position:relative;display:inline-flex}
.rule-manage-menu{position:absolute;top:calc(100% + 4px);right:0;z-index:20;display:flex;flex-direction:column;gap:4px;
  min-width:170px;padding:6px;background:var(--panel);border:1px solid var(--line);border-radius:6px;
  box-shadow:0 8px 22px rgba(0,0,0,.45)}
.rule-manage-menu .btn{justify-content:flex-start;text-align:left;width:100%}

/* Quick-filter segmented row (reuses .cx-seg). */
.rule-quickfilters{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 10px}
/* Compact layout (2026-09 pass): search owns a full-width row; rows are
   titles-only until opened; row actions = ★, ◇ Pin, and a ⋯ menu. */
.rule-search{margin-bottom:8px}
.rule-search input{width:100%}
.rule-toolbar .rule-manage{margin-left:auto}
.rule-item.collapsed .rule-top{margin-bottom:0}
.rules-acc .acc-body{gap:calc(6px*var(--dsp))}
.rule-actions{flex-wrap:nowrap}
.rule-actions .rule-manage-menu{min-width:190px}
.rule-manage-menu kbd{margin-left:auto;padding:0 4px;border:1px solid var(--line);border-radius:3px;
  background:var(--input-bg);color:var(--faint);font:600 9px var(--label)}
.rule-bulkbar .spacer{flex:1}
.redit-builtin{margin:0 0 10px;display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.redit-warn{color:var(--red);margin-top:2px}

/* "New defaults available" notice — amber plate, mirrors .rule-bulkbar. */
.rule-newdefaults{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 10px;
  padding:calc(8px*var(--dsp)) calc(12px*var(--dsp));border:1px solid var(--amber-dim);
  border-radius:6px;background:var(--amber-bg);color:var(--amber)}

/* Editor: mobile-only live-preview toggle (hidden on desktop, which shows it always). */
.redit-pvtoggle{display:none}

@media(max-width:640px){
  /* Keep Save/Cancel reachable without scrolling the long editor to the bottom. */
  #modalBox .modalActions{position:sticky;bottom:0;z-index:5;margin-top:12px;
    padding:10px 0 4px;background:var(--panel);border-top:1px solid var(--line)}
  /* Live preview collapsed by default on phones; the toggle reveals it. */
  .redit-pvtoggle{display:inline-block;margin-top:8px}
  .redit .redit-side{display:none}
  .redit.pv-open .redit-side{display:block}
  /* Quick filters scroll sideways rather than wrapping into a second band. */
  .rule-quickfilters .cx-seg{max-width:100%;overflow-x:auto}
  .rule-quickfilters .cx-seg button{white-space:nowrap}
}

/* ============================================================
   ABOUT / CREDITS PASS (2026-08)
   Scoped to .aboutpage so the shared .rref/.ptitle idioms used
   elsewhere (Rules Reference, holoblocks) stay untouched. Tokens
   only; no hardcoded colors. Appended last per the css/CLAUDE.md
   cascade convention.
   ============================================================ */
.aboutpage h1.about-title{margin:0 0 6px}
.aboutpage h2{font-family:var(--disp);font-size:11px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--holo);margin:0}
.aboutpage h3{font-family:var(--label);font-size:11px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--amber);margin:12px 0 5px}
.aboutpage .about-sec-body>h3:first-child{margin-top:2px}
.aboutpage p{margin:0 0 8px;line-height:1.6}
.aboutpage .about-sec-body p:last-child{margin-bottom:0}
/* The global reset zeroes list padding, which hangs the bullets outside the panel. */
.aboutpage .about-team{margin:0 0 10px;padding-left:calc(20px*var(--dsp))}
/* Links: the default anchor tone is near-invisible on the panel; use the app's
   own link colour (matches .reflink) so every credit link is legible. The
   license-badge link is re-asserted faint below so it stays a quiet badge. */
.aboutpage a{color:var(--holo);text-decoration:underline;text-decoration-color:var(--holo-dim);text-underline-offset:2px}
.aboutpage a:hover{color:var(--holo-bright)}

/* Live-context strip under the title. */
.about-ctx{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:center;margin:0 0 14px;font-size:11px;color:var(--dim)}
.about-ctx .about-chip{display:inline-flex;align-items:center;gap:5px;
  border:1px solid var(--line);border-radius:4px;padding:3px 8px;background:var(--panel2)}
.about-ctx .about-chip b{color:var(--ink);font-weight:600}

/* Section index — jump chips, wraps on mobile. */
.about-index{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 16px}
.about-index button{font-family:var(--label);font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--dim);background:var(--panel2);border:1px solid var(--line);border-radius:4px;padding:6px 10px;cursor:pointer}
.about-index button:hover{color:var(--holo);border-color:var(--holo-dim)}

/* Collapsible section — reuses the .rref plate tone. */
details.about-sec{border:1px solid var(--line);background:var(--panel2);border-radius:6px;
  margin-bottom:10px;font-size:12.5px}
details.about-sec>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:8px;
  padding:calc(11px*var(--dsp)) calc(13px*var(--dsp))}
details.about-sec>summary::-webkit-details-marker{display:none}
details.about-sec>summary::after{content:"\25be";margin-left:auto;color:var(--faint);font-size:11px;transition:transform .15s}
details.about-sec[open]>summary::after{transform:rotate(180deg)}
details.about-sec>summary:hover h2{color:var(--acc)}
.about-sec-body{padding:0 calc(13px*var(--dsp)) calc(13px*var(--dsp));line-height:1.6}

/* Source cards — map sources and third-party software. */
.src-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:8px;margin:8px 0 0}
.src-card{border:1px solid var(--line);border-radius:5px;padding:9px 11px;background:var(--panel)}
.src-card-h{font-family:var(--label);font-size:12px;color:var(--ink);margin-bottom:2px}
.src-card-h a{color:var(--holo)}
.src-card-by{font-size:11px;color:var(--dim);margin-bottom:4px}
.src-card-lic{display:inline-block;font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);
  border:1px solid var(--line);border-radius:3px;padding:1px 6px;margin-bottom:5px}
.src-card-lic a{color:var(--faint)}
.src-card-scope{font-size:11px;color:var(--dim);line-height:1.5}

@media(pointer:coarse){
  .about-index button{min-height:44px}
  details.about-sec>summary{min-height:44px}
}

/* About pass 2 (2026-09-23): title row + Guide button, clickable version chip,
   report actions, per-pack In use / Off tags, attribution line, inline links. */
.about-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 6px}
.aboutpage .about-head h1.about-title{margin:0}
.about-head .btn{margin-left:auto}
.about-index button{font-size:10.5px}
.about-ctx .about-chip.cxlink{cursor:pointer}
.about-ctx .about-chip.cxlink:hover,.about-ctx .about-chip.cxlink:focus-visible{border-color:var(--holo-dim);color:var(--holo)}
.about-ctx-acts{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:auto}
.about-use{display:inline-block;vertical-align:middle;margin-left:6px;font-family:var(--label);font-size:9px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--faint);border:1px solid var(--line);border-radius:3px;padding:1px 6px}
.about-use.on{color:var(--green);border-color:var(--green)}
.src-card-ver{font-size:10px;color:var(--faint);letter-spacing:.06em}
.src-card-attr{font-size:11px;color:var(--ink);line-height:1.5;margin-bottom:4px}
.aboutpage .about-go{background:none;border:0;padding:0;font:inherit;color:var(--holo);cursor:pointer;
  text-decoration:underline;text-decoration-color:var(--holo-dim);text-underline-offset:2px}
.aboutpage .about-go:hover{color:var(--holo-bright)}
@media(max-width:640px){
  .about-ctx-acts{margin-left:0;width:100%}
  .about-ctx-acts .btn{flex:1 1 auto}
}

/* Card-level quick actions (optional per-schema cardActions — Holocron's
   one-tap pin). Absolute in the card's top-right; stops the card-open click. */
.card .cardacts{position:absolute; top:calc(8px*var(--dsp)); right:calc(8px*var(--dsp)); display:flex; gap:4px; z-index:2}
.card .cardacts .btn{font-size:10px; padding:2px 7px}
.card:has(.cardacts) h3{padding-right:64px}
/* Removable-chip ✕ — a focusable control (a11y); visible focus ring.
   Unscoped since the Data Editor pass: the same chip ✕ now appears inside
   .lkchip (record links) and inside a .badge (deStrList's free-string lists),
   and both were click-decorated <span>/<b> that no keyboard could reach. */
.chipx{border-radius:8px; padding:0 2px; line-height:1}
.chipx:focus-visible{outline:2px solid var(--holo); outline-offset:1px}
@media(pointer:coarse){ .card .cardacts .btn{min-height:32px} }

/* ---- Guide (in-app manual) — Guide review pass ----
   Actionable "Open …" links under each topic body: a compact secondary strip,
   not peers of the prose. Wrap on narrow screens. Theme tokens only. */
.guide-actions{display:flex; flex-wrap:wrap; gap:6px; margin-top:calc(8px*var(--dsp));
  padding-top:calc(7px*var(--dsp)); border-top:1px solid var(--line)}
/* Rule quick-view actions: long Rules Reference titles must wrap on a phone
   rather than push the row wider than the panel. */
.guide-actions .guide-rulebtn{white-space:normal; text-align:left; max-width:100%}
/* guideGo lands focus on a topic card; the flash is the visual cue, so the
   programmatic focus ring is suppressed (tabindex=-1, never tab-reachable). */
.rules-acc .rule-item[tabindex="-1"]:focus{outline:none}
/* …and the scroll clears the sticky header, so the topic title isn't hidden. */
.rules-acc .rule-item[id^="guide-"]{scroll-margin-top:calc(var(--hdr-h,60px) + 10px)}
/* Label on the tour hub's task-walkthrough row (js/guide.js guideTourHub). */
.guide-tourbar-lbl{color:var(--holo); font-weight:600; margin-right:4px}
/* Dashboard first-run pointer to the Guide. */
.dash-guidehint{display:flex; flex-wrap:wrap; align-items:center; gap:calc(10px*var(--dsp));
  justify-content:space-between; line-height:1.5}
.dash-guidehint-btns{display:flex; flex-wrap:wrap; gap:6px; flex:0 0 auto}
/* Mobile toolbar: search takes the full row, the bulk buttons sit compactly
   beneath it (the shared .toolbar wraps awkwardly at phone width). */
@media(max-width:560px){
  .guide-toolbar input[type=text]{flex:1 0 100%; min-width:0}
  .guide-toolbar .btn.sm{flex:1 1 auto}
}

/* ---- Conflict Roller / Force Presence — Conflict Roller overhaul pass ----
   The Balance pips stay 17px visually (the printed track's proportions), but a
   17px tap target fails WCAG 2.5.8 on a phone. A transparent ::after overlay
   grows the HIT AREA to ~41px on coarse pointers without touching the layout,
   and the pips sit in a 5px-gap row so the overlays never collide. */
@media(pointer:coarse){
  /* Wider gap on touch so the enlarged targets can meet edge-to-edge without
     overlapping — a symmetric ±12px overlay at the desktop 5px gap would let a
     tap on one pip's right half land on its neighbour (pitch 22 vs target 41). */
  .fptrack{gap:12px}
  .fptrack button.fppip{position:relative}
  .fptrack button.fppip::after{content:""; position:absolute; inset:-12px -6px; border-radius:8px}
}
/* Keyboard focus on a pip — theme token, never a hardcoded colour. */
.fppip:focus-visible{outline:2px solid var(--holo); outline-offset:2px}
/* Phone: the tally toolbars carry a label, a stepper and quick-add buttons —
   at 640px they wrap into a ragged stack, so give each row its own full-width
   line and let the controls breathe. */
@media(max-width:640px){
  #cg-conflict .toolbar{gap:6px}
  #cg-conflict .toolbar>.smallnote{flex:1 0 100%; margin:0}
  #cg-conflict .toolbar>.btn{flex:1 1 auto}
}
/* Direct numeric entry IS the stepper's count cell — one control, no duplicate
   total box. (0,2,1) so it beats the base input[type=number]{width:100%}. */
.stepper input.cnt{width:40px; padding:0 2px; text-align:center; border:none;
  border-left:1px solid var(--line); border-right:1px solid var(--line);
  border-radius:0; background:transparent; font-size:14px; font-weight:600;
  -moz-appearance:textfield; appearance:textfield}
.stepper input.cnt::-webkit-outer-spin-button,
.stepper input.cnt::-webkit-inner-spin-button{-webkit-appearance:none; margin:0}
/* "Tranquility" needs ~66px at 10px caps + .1em tracking — the original 54px
   column let the neighbouring stepper overlap the label on the party card. */
.vitline .vitlbl{width:68px}

/* ============ MARKETS & SHOPS ============
   The stock is a master/detail split: .crlayout/.crbrowse/.crdetail verbatim,
   with .shoplayout as the override hook (the .delayout/.speclayout precedent —
   never a second master/detail). The list owns the scroll; the detail panel is
   STATIC, not sticky, because a connections buy carries the dice widget and a
   sticky tall panel would pin its top and push Buy out of reach (the .delayout
   lesson). Below 1100px the two stack and App.shopBuyOpen scrolls the panel
   into view. The panel is never a cell inside .tblscroll — that put the whole
   buy flow behind a sideways scroll on a phone. */
.shoplayout{margin-top:8px}
.shoplayout .crbrowse{flex:1 1 56%;max-height:min(76vh,860px)}
.shoplayout .crdetail{flex:1 1 44%;position:static}
.shoplayout .tblscroll{margin-top:0}
@media(max-width:1100px){
  .shoplayout{flex-direction:column}
  .shoplayout .crbrowse{flex:none;width:100%;max-height:55vh}
  .shoplayout .crdetail{width:100%;scroll-margin-top:70px}
}
.shop-detail{border-left:3px solid var(--holo-dim)}
.shop-hint{border-left-color:var(--line)}
.shop-dname{font-size:14px}
.shop-stats{margin-top:2px}
.shop-sub{margin:-2px 0 4px}
/* The row's own edits (price, stock, reprice, remove, note) — set apart from
   the purchase above them because they change the SHOP, not one transaction. */
.shop-edit{margin-top:12px;padding-top:10px;border-top:1px dashed var(--line)}
/* Collapsible Market settings head: a real <button> (aria-expanded), styled as
   a section head rather than a pill, with the collapsed summary beside it. */
.shop-cfghead{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:baseline;width:100%;
  margin-top:10px;padding:7px 10px;background:transparent;color:var(--holo);
  border:1px solid var(--line);border-radius:4px;cursor:pointer;text-align:left;
  font:inherit;font-size:12px;letter-spacing:.06em;text-transform:uppercase}
.shop-cfghead .smallnote{text-transform:none;letter-spacing:0}
.shop-cfghead:hover{border-color:var(--holo-dim)}
.shop-cfghead:focus-visible{outline:2px solid var(--holo);outline-offset:1px}
/* Rows are read-only: a click anywhere opens the panel (the action button is
   the keyboard path). */
.shoptbl tr.shoprow{cursor:pointer}
.shoptbl td.shopnum{white-space:nowrap}
.shoptbl td.shopact{white-space:nowrap;text-align:right}
/* Under 640px every .eqtbl becomes stacked cards (the global table→card
   convention). A shop row is a TWO-line card inside it: the name across the top,
   then price · qty · action on one line. Rarity and Check are informative, not
   actionable, and both are spelled out in the panel, so they drop (the .chrow
   precedent). Scoped to .shoptbl, since .eqtbl is shared with the chargen
   equipment table. */
@media(max-width:640px){
  .shoptbl td.shopsec{display:none}
  .shoptbl tr.shoprow{display:grid;grid-template-columns:1fr auto auto;gap:2px 12px;align-items:center}
  .shoptbl tr.shoprow td:first-child{grid-column:1/-1;word-break:break-word}
  .shoptbl tr.shoprow td.shopnum{display:flex;gap:6px;align-items:baseline}
  .shoptbl tr.shoprow td.shopnum .smallnote{font-size:9px}
  .shoptbl tr.shoprow td.shopact{text-align:right}
}

/* Sortable headers are role="button" + KEYACT, so they need a focus ring like
   every other clickable surface. */
.eqtbl th.eqsort:focus-visible, .card:focus-visible, .rref:focus-visible{
  outline:2px solid var(--holo); outline-offset:1px;
}
/* Touch: the panel's controls sit shoulder to shoulder — give them room so a
   thumb cannot hit ✕ Remove while aiming at ＋. */
@media(pointer:coarse){
  .shop-detail .toolbar{gap:12px}
}

/* ---- Dashboard "Now / Next" (dashboard pass) ----
   The active scene, promoted out of the ordered scene list so the one thing the
   GM is about to run reads before the plan around it. Amber rail to match the
   campaign panel it sits inside; the all-run state goes green and drops its
   actions. Appended per the css/CLAUDE.md convention. */
.dashnow{display:flex; align-items:center; gap:calc(9px*var(--dsp)); flex-wrap:wrap;
  border:1px solid var(--line); border-left:3px solid var(--amber); background:var(--amber-wash);
  padding:calc(8px*var(--dsp)) calc(11px*var(--dsp)); margin-bottom:calc(12px*var(--dsp))}
.dashnow.done{border-left-color:var(--green); background:var(--green-wash)}
.dashnow-lbl{font-family:var(--disp); font-size:9.5px; letter-spacing:.16em; color:var(--amber); white-space:nowrap}
.dashnow.done .dashnow-lbl{color:var(--green)}
.dashnow-title{font-weight:600; font-size:13.5px; color:var(--ink); min-width:0; overflow-wrap:anywhere}
/* on a phone the actions wrap onto their own line rather than squeezing the
   title away — .spacer becomes a full-width break instead of a flex filler */
@media(max-width:560px){
  .dashnow .spacer{flex-basis:100%; height:0}
  .dashnow .btn{flex:1 1 auto}
}

/* ---- Dashboard quick actions (dashboard pass) ----
   A GRID of equal cells rather than a flex toolbar: five destinations of equal
   standing should read as one block of same-size targets, and auto-fit keeps
   the rows flush instead of leaving a ragged last line. */
.dashqa{display:grid; grid-template-columns:repeat(auto-fit,minmax(142px,200px));
  justify-content:start; gap:calc(8px*var(--dsp))}
/* phones: fill the width rather than leaving a ragged gutter on the right */
@media(max-width:560px){ .dashqa{grid-template-columns:repeat(auto-fit,minmax(132px,1fr))} }
.dashqa .btn{display:inline-flex; align-items:center; justify-content:center; gap:7px;
  width:100%; height:100%; text-align:center}

/* ---- Session Planner scene cards on phones (session-planner pass) ----
   The desktop header packs seven controls into one wrapping row; on a phone
   that collapses into a tall, unreadable cluster. Give it a deliberate layout:
   the title takes its own full-width line, and the second line carries the
   checkbox, kind badge/select and a right-aligned reorder/delete cluster with
   44px touch targets. Appended per the css/CLAUDE.md convention. */
@media(max-width:560px){
  .scene-head{gap:6px 8px; align-items:center}
  .scene-head .scene-title{order:-1; flex:1 1 100%; min-width:0}
  .scene-head .scene-done{order:1}
  .scene-head .badge{order:2}
  .scene-head .scene-kind{order:3; flex:1 1 auto; max-width:none}
  .scene-head .scene-ctl{order:4; margin-left:auto; gap:6px}
  .scene-foot{gap:6px 8px}
  .scene-foot select,.scene-foot .btn.sm{flex:1 1 auto}
  .scene-branch{gap:6px 8px}
  .scene-branch .scene-branch-add{flex:1 1 auto}
  .branch-lbl{max-width:none;flex:1 1 100%}
}
@media(pointer:coarse){
  .scene-ctl .btn.sm{min-width:40px; min-height:34px}
}
/* Session editor header on phones: the badge drops, the spacer collapses, and
   each row's buttons share the width evenly; the stamp toggle takes its own line. */
@media(max-width:560px){
  .sess-edhead .sess-edbadge,.sess-edhead .spacer,.sess-edtools .spacer{display:none}
  .sess-edhead .btn,.sess-edtools .btn{flex:1 1 auto}
  .sess-edtools .sess-stamp{flex:1 1 100%}
  .sess-tlbar .spacer{display:none}
  .sess-tlbar .btn{flex:1 1 auto}
}

/* ---- Initiative Tracker — next-slot cue, manual chip, timer options,
   and a mobile two-level card (Initiative improvement pass) ----
   Scoped to .initslot so the shared .initrow idiom (chargen/NPC/dataset) is
   untouched. Appended per the css/CLAUDE.md cascade convention. */
/* the current turn: same idiom as .npc-card.active (left accent + pick bg) */
.initslot.next{background:var(--pick-on); box-shadow:inset 3px 0 0 var(--holo)}
.initslot.next .initord{color:var(--holo)}
/* a hand-typed slot with nothing to auto-roll — labels the "manual" state */
.initslot .initmanual{font-size:10px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--faint); border:1px solid var(--line); border-radius:3px; padding:1px 5px}
/* Timer options: on desktop the ⚙ Options disclosure is always-open (summary
   hidden, body inline) so nothing regresses; a phone collapses it (see below). */
.inittimer-opts{display:contents}
.inittimer-opts>summary{display:none}
.inittimer-opts .inittimer-optbody{display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap}

@media(max-width:640px){
  /* Two-level card: header (order · name · delete) then results/skill/roll/claim
     flow full-width below. Delete moves out of reach of the primary controls. */
  .initslot{row-gap:8px; align-items:center}
  .initslot .initord{order:0}
  .initslot>input[type=checkbox]{order:1}
  .initslot .iname{order:2; flex:1 1 55%}
  .initslot .initdel{order:3; margin-left:auto}
  .initslot .initres{order:4; flex:1 1 100%; justify-content:flex-start}
  .initslot>select{order:5}
  .initslot .btn.sm:not(.initdel){order:6}
  .initslot .initmanual{order:6}
  .initslot .initclaim{order:7; flex:1 1 100%}
  .initslot .initclaim select{max-width:none; width:100%}
  /* collapse the timer prefs behind the ⚙ Options disclosure */
  .inittimer-opts{display:block; width:100%}
  .inittimer-opts>summary{display:inline-flex; list-style:none; cursor:pointer}
  .inittimer-opts>summary::-webkit-details-marker{display:none}
  .inittimer-opts:not([open]) .inittimer-optbody{display:none}
  .inittimer-opts[open] .inittimer-optbody{display:flex; margin-top:8px}
}
@media(pointer:coarse){
  /* 44px touch floors for the tracker's own controls */
  .initslot .btn.sm{min-height:34px}
  .initslot>input[type=checkbox]{width:22px; height:22px}
  .initslot .initres input{min-height:36px}
}

/* ===== ENCOUNTER BUILDER — density, column alignment, environment grid
   (appended per cascade convention, deliberately AHEAD of the mobile block
   below: these carry the same specificity, so the 640px overrides have to come
   last to win).
   `.encmeta` is the module's secondary label (type/kind/roster captions) —
   --faint at 11px was the module's readability complaint, so those labels read
   --dim at 12px and --faint is reserved for true tertiary text ("next falls
   at", the unlinked tag, the W/S stepper micro-labels). */
.encmeta{font-size:12px;color:var(--dim)}
/* Builder rows are a fixed six-column grid — identity | alt name | mode select
   | count stepper | threat/Roster/spacer | remove — so an adversary row, an
   ally row and a vehicle row line their controls up with each other instead of
   each flexing to its own content. Every row type occupies all six columns
   (an absent control ships an empty cell or a .encmode-sp spacer). */
.encpanel .initrow.encbuild{display:grid;
  grid-template-columns:minmax(140px,1fr) 160px 130px max-content minmax(70px,max-content) max-content;
  gap:6px 10px;align-items:center}
/* Environment builder: one grid for every control — label | control | short
   hint | rule link — so steppers, checkboxes and the gravity select align.
   A checkbox keeps its <label> click target, spanning the first two columns. */
/* editor meta row: Name | Adventure | Location | Session on one line; the
   phone breakpoint stacks it (an inline template used to beat that rule) */
.encpanel .formgrid.encmetagrid{grid-template-columns:2fr 1fr 1fr 1fr 1fr}
@media (max-width:640px){.encpanel .formgrid.encmetagrid{grid-template-columns:1fr}}
.encpanel .encbuildstat{margin:0 0 8px 22px}
.encpanel .encrunlive{color:var(--amber);margin:4px 0 6px}
.enchead .enchthr{margin-left:8px;vertical-align:middle}
.encpanel .encenvgrid{display:grid;grid-template-columns:110px max-content 1fr max-content;
  gap:4px 10px;align-items:center}
.encpanel .encenvgrid .smallnote{margin-top:0}
.encpanel .encenvlbl{font-size:12px}
.encpanel .encenvchk{grid-column:1/3;display:flex;gap:6px;align-items:center;font-size:12px;cursor:pointer}
.encpanel .encenvhead{grid-column:1/-1;justify-self:start;margin:8px 0 2px}
/* Threat readout: a caption over a calmer verdict — the 30px display type read
   as the panel's headline when it is an estimate, not a measurement. */
.encpanel .encthrcap{display:inline-block;margin-bottom:4px}
/* Attack roller picker. It renders into the shared #modal, NOT inside
   .encpanel, so this one is unscoped: a label | control grid so the weapon,
   target and range rows line their controls up. */
.encatkgrid{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px 12px;align-items:start}
.encatkgrid>.encmeta{padding-top:5px}

/* ===== ENCOUNTER BUILDER — mobile & accessibility (appended per cascade convention)
   The Encounter builder/run/library rows are desktop-density flex rows. On a
   phone they squeeze names or overflow, so `.encpanel .initrow` (excluding the
   initiative slots, which have their own treatment) becomes a two-level row:
   name/status full-width, controls wrap below. Scoped to `.encpanel` so the
   shared `.initrow` used elsewhere is untouched. Mirrors the .initslot precedent. */
.encacts{float:right}
/* the tracker tab's one-line now-acting strip (encRunNowStrip) */
.encnow{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:10px 0 4px;padding:6px 10px;
  border:1px solid var(--line);border-left:3px solid var(--amber);background:var(--accent-wash)}
@media(max-width:640px){
  /* the builder's six-column grid drops back to the shipped two-level flex row
     on a phone — six columns in 640px would squeeze every one of them */
  .encpanel .initrow.encbuild{display:flex; flex-wrap:wrap}
  /* the attack picker stacks label over control — two columns squeeze the
     weapon/target selects below usable width on a phone */
  .encatkgrid{grid-template-columns:minmax(0,1fr); gap:2px 0}
  .encatkgrid>.encmeta{padding-top:6px}
  /* header action bar wraps instead of floating four buttons off the edge */
  .enchead{display:flex; flex-wrap:wrap; align-items:center; gap:6px}
  .enchead .encacts{float:none; width:100%; display:flex; flex-wrap:wrap; gap:6px; margin-top:4px}
  .enchead .encacts .btn{flex:1 1 auto}
  /* two-level rows: name owns the top line, everything else flows beneath it */
  .encpanel .initrow:not(.initslot){flex-wrap:wrap; row-gap:6px}
  .encpanel .initrow:not(.initslot) .iname{flex:1 1 100%}
  /* the ▸ stat caret shares the name's line instead of taking one of its own */
  .encpanel .initrow:not(.initslot)>.encexp+.iname{flex:1 1 calc(100% - 44px)}
  /* full-width selects and the alt-name/label text inputs so names aren't squeezed */
  .encpanel .initrow:not(.initslot)>select,
  .encpanel .initrow:not(.initslot)>input[type=text]{flex:1 1 100%; width:auto; max-width:none}
  .encpanel .encmode{flex:1 1 auto}
}
@media(pointer:coarse){
  /* 44px touch floors for the encounter rows' own controls + steppers */
  .encpanel .initrow:not(.initslot) .btn.sm{min-height:34px}
  .encpanel .stepper button{min-width:32px; min-height:32px}
  .encpanel .initrow:not(.initslot)>input[type=checkbox]{width:22px; height:22px}
}
/* keyboard focus is visible on the clickable rows and condition chips that
   carry role="button" (they were plain divs/spans before) */
.encpanel .initrow[role=button]:focus-visible,
.encpanel .lkchip[role=button]:focus-visible,
.encpanel .iname[role=button]:focus-visible{outline:2px solid var(--holo); outline-offset:2px}

/* Shared PC wound readout (pcWoundReadout, js/tools.js). The retrace text is
   "+12/14"-shaped, and the default .cnt box is a fixed width:34px (l.569) —
   narrower still at .stepper.mini's 22px — so it clips. Appended at the END of
   the file DELIBERATELY: `.stepper .cnt.pcw` and `.stepper.mini .cnt` are both
   specificity (0,3,0), so only source order can decide, and the chargen sheet's
   stepper is a .mini. (Same reason as the .initrow .stepper .cnt override
   above, which serves the encounter run tracker's own steppers.) */
.stepper .cnt.pcw{width:auto; min-width:56px; padding:0 8px; font-size:15px; box-sizing:border-box}

/* GM Screen sticky section rail — switching tabs on a long Tools/Health screen
   must not mean scrolling back to the top. Scoped to .gm-tabrail so the shared
   .npc-library-tabs idiom (NPC Library, Codex) is untouched. Appended at the END
   per the cascade convention (css/CLAUDE.md); opaque --panel background so panels
   scroll under it cleanly. */
.gm-railwrap{position:sticky; top:0; z-index:5; background:var(--panel); padding:6px 0; display:flex; align-items:center; gap:6px}

/* Crafting landing "Start a project" tiles + the droid Table 3-11 quirk columns
   — responsive auto-fill grids (the house .cards pattern) replacing hard-coded
   fixed-column inline grids that crushed on phones. Appended at the END per the
   cascade convention (css/CLAUDE.md). */
.crdiscgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:calc(8px*var(--dsp)); margin-top:6px}
.crquirkcols{display:grid; grid-template-columns:repeat(auto-fill,minmax(320px,1fr)); gap:12px; margin-top:4px; align-items:start}

/* Narrative-NPC (profile) editor — portrait control + tag chips. Reuses the
   chargen/vehicles portrait idiom. Appended at the END per the cascade
   convention (css/CLAUDE.md). */
.npc-portrait-row{display:flex; gap:10px; align-items:flex-start; flex-wrap:wrap}
.npc-portrait-box{display:flex; flex-direction:column; gap:4px; align-items:center}
.npc-portrait-img{max-width:120px; max-height:150px; border:1px solid var(--line); border-radius:4px; background:var(--panel2)}
.npc-portrait-img.npc-portrait-float{float:right; margin:0 0 8px 10px; max-width:96px; max-height:120px}
.npc-portrait-empty{width:96px; height:120px; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; border:1px dashed var(--line); border-radius:4px; color:var(--faint); background:var(--panel2)}
.npc-tag-row{display:flex; flex-wrap:wrap; gap:4px; margin:4px 0 6px}

/* Relationship-web edge rows — type chip + inline note + remove. Appended at
   the END per the cascade convention (css/CLAUDE.md). */
.npc-rel-edge{display:inline-flex; align-items:center; gap:6px; margin:2px 8px 2px 0}
.npc-rel-note{font-size:11px; padding:1px 5px; width:130px; max-width:40vw; background:var(--panel2); border:1px solid var(--line); border-radius:3px; color:var(--text)}
.npc-rel-x{color:var(--red);background:none;border:0;padding:2px 6px;cursor:pointer;font:inherit;line-height:1}
.npc-rel-x:hover,.npc-rel-x:focus-visible{background:var(--hover)}
/* NPC Library pass 2 — filter panel, preview actions, Connections, phone list→detail */
.npc-filterpanel{display:flex;flex-direction:column;gap:6px;margin-bottom:7px}
.npc-filterpanel>.toolbar{flex-wrap:wrap;margin:0}
.npc-filtbtn{display:none}
.npc-backbar{display:none}
.npc-preview-actions{flex-wrap:wrap;gap:6px;margin-top:10px}
.npc-connections{margin-top:12px;border-top:1px solid var(--line);padding-top:8px}
.npc-conn-list{list-style:none;margin:4px 0 6px;padding:0}
.npc-conn{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:12px}
.npc-conn>i{width:14px;height:3px;border-radius:2px;flex:none}
.npc-conn>span{flex:1;min-width:0}
.npc-conn-add{flex-wrap:wrap;gap:6px}
.npc-library .linkbtn,.npc-web-row .linkbtn{background:none;border:0;padding:0;color:var(--holo);cursor:pointer;font:inherit;text-align:left}
.npc-library .linkbtn:hover,.npc-web-row .linkbtn:hover{text-decoration:underline}
.npc-web-row .linkbtn{font-weight:700;margin-right:6px}
.npc-web-f{display:flex;flex-direction:column;gap:2px}
.npc-web-f .fl{margin:0}
.npc-web-node:focus{outline:none}
.npc-web-node:focus-visible circle{stroke-width:3}
@media(max-width:700px){
  .npc-filtbtn{display:inline-flex}
  .npc-filterpanel{display:none}
  .npc-filterpanel.open{display:flex}
  .npc-library-tabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:thin;padding-bottom:2px}
  .npc-library .npc-cards{max-height:none}
  .npc-library.npc-detail-open .npc-cards{display:none}
  .npc-library:not(.npc-detail-open) .npc-preview{display:none}
  .npc-library.npc-detail-open .npc-backbar{display:block;margin-bottom:8px}
  .npc-web-add{align-items:stretch}
  .npc-web-add .npc-web-f{flex:1 1 100%}
  .npc-web-add select{width:100%;min-width:0}
  .npc-conn-add select{flex:1 1 40%;min-width:0}
}

/* NPC Library header overflow menu — dependency-free <details> disclosure so the
   header stays a short primary row on a phone. Appended at the END per the
   cascade convention (css/CLAUDE.md). */
.npc-stat-cell.npc-stat-na b{color:var(--faint)}
.charbox.npc-attr-na{opacity:.55}
.npc-trackstrain{display:block;margin-top:10px}
.npc-eq-worn{display:inline-flex;align-items:center;gap:4px;font-size:10px;color:var(--dim);white-space:nowrap;cursor:pointer}
.npc-eq-worn input{width:auto;margin:0}
.npc-more{position:relative; display:inline-block}
.npc-more>summary{list-style:none; cursor:pointer}
.npc-more>summary::-webkit-details-marker{display:none}
.npc-more-menu{position:absolute; right:0; top:calc(100% + 4px); z-index:40; display:flex; flex-direction:column; gap:4px; padding:6px; min-width:180px; background:var(--panel); border:1px solid var(--line); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.4)}
.npc-more-menu>.btn,.npc-more-menu>label{width:100%; text-align:left; justify-content:flex-start}

/* Galactic Atlas control panel — Explore / Route / Campaign tabs (galSidePanel).
   The panel was one long scroll; the tab strip lets a GM land on one job. */
.gal-panel-tabs{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;margin:0 0 13px}
.gal-panel-tabs button{border:1px solid #23485f;background:#081725;color:#8ba6b6;padding:9px 6px;font:700 9px var(--disp);letter-spacing:.11em;text-transform:uppercase;border-radius:3px;cursor:pointer}
.gal-panel-tabs button:hover{color:#cfeaf5;border-color:#2f6f8c}
.gal-panel-tabs button.on{background:linear-gradient(180deg,#16465d,#103347);border-color:#3fc9ef;color:#bff3ff;box-shadow:inset 0 0 16px #23b8e522}
.gal-panel-body>section:first-child{padding-top:0}
.gal-mobile-home{flex:0 0 auto !important;min-width:62px}
/* Marker-precision legend: bright labelled marker = verified, faint dot = grid-only. */
.gal-tier-key{position:absolute;z-index:4;left:18px;bottom:98px;display:flex;gap:12px;padding:5px 7px;border:1px solid #28516a88;border-radius:4px;background:#04101bc9;color:#8da9b8;font-size:8px;text-transform:uppercase;letter-spacing:.1em;pointer-events:none}
.gal-tier-key span{display:flex;align-items:center;gap:5px}
.gal-tier-key i{display:block;width:9px;height:9px;border-radius:50%}
.gal-tier-key i.verified{background:#bfeeff;box-shadow:0 0 6px #55d7ffaa;border:1px solid #bfeeff}
.gal-tier-key i.grid{width:6px;height:6px;background:#5f7a8c99;border:1px solid #5f7a8c}
@media(max-width:900px){.gal-tier-key{display:none}}
/* The open left control panel sits over the bottom-left legends (scale bar, route
   key, tier key) — shift them clear of it on desktop; on ≤900px the panel is a
   bottom sheet, so leave them alone. 340px panel + 14px inset + gap. */
@media(min-width:901px){.gal-left-open .gal-scale,.gal-left-open .gal-route-key,.gal-left-open .gal-tier-key{left:366px}}

/* ---- Data Editor dense sub-editor CARDS (data-editor.js deRowList sp.card) ----
   Per-record cards replace the wide horizontal tables the dense sub-editors
   (Weapon Systems, Innate Weapons, Mods, Special Dice) used to render. Each card
   is a main field grid + an optional secondary fieldset + full-width prose; the
   grid auto-fits and collapses to one column on a phone, so nothing scrolls
   sideways. Paired with the .wide (960px) modal these frames now request. */
.decards{display:flex;flex-direction:column;gap:10px}
.decard{border:1px solid var(--line);background:var(--panel2);padding:calc(10px*var(--dsp))}
.decard-hd{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.decard-n{font-family:var(--label);font-size:10px;letter-spacing:.12em;color:var(--faint)}
.decard-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px 10px;align-items:end}
.decard-f{display:flex;flex-direction:column;gap:3px;font-size:11px;min-width:0}
.decard-fl{font-family:var(--label);font-size:9px;letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}
.decard-f input,.decard-f select,.decard-f button{width:100%}
.decard-f input[type=checkbox]{width:16px;height:16px;align-self:flex-start;margin-top:2px}
.decard-sec{border:1px solid var(--line);margin-top:10px;padding:6px 10px 10px;min-width:0}
.decard-sec>legend{font-family:var(--label);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);padding:0 4px}
.decard-prose{margin-top:8px}

/* Species page: persistent side-by-side detail pane (Species improvement pass,
   2026-08-20). Appended per the cascade convention — the mobile block below
   re-asserts the stacked layout and un-floats the portrait on phones. */
.splayout{display:flex;gap:14px;align-items:flex-start}
/* grid caps at three species cards (194px each + 10px gap) plus 5px so the third
   card's bottom edge is fully caught, then scrolls */
.splayout .spgrid{flex:1 1 52%;min-width:0;max-height:607px}
/* the detail pane grows with its content and pushes the page — never its own
   inner scrollbar (sticky keeps it in view while the list scrolls beside it).
   Its panel's top must sit flush with the grid's top (no stray margin). */
.spdetail{flex:1 1 48%;min-width:0;position:sticky;top:8px}
.spdetail>.panel{margin-top:0;overflow-wrap:break-word}
/* six characteristic tiles don't fit the narrow pane — two rows of three */
.spdetail .charrow{grid-template-columns:repeat(3,minmax(0,1fr))}
/* the selected-species name reads as a heading, aligned flush-left with the
   content below it (`.cgpanel .ptitle` adds a 20px inset that indented only the
   name), with a single under-name divider (the inline ::after is dropped) and
   the source chip on its own line clear of that rule */
.spdetail .sptitle{font-size:20px;line-height:1.15;padding:0 0 8px;margin:0}
.spdetail .sptitle::after{display:none}
.spdetail-src{margin:9px 0 12px;font-size:12.5px;color:var(--faint)}
.spdetail-src .cite{font-size:12.5px}
@media(max-width:900px){
  .splayout{flex-direction:column}
  /* cap the list to about one species card so the detail sits right under it,
     instead of the reader scrolling the whole catalog to reach it */
  .splayout .spgrid{flex:none;width:100%;max-height:210px}
  .spdetail{position:static;width:100%}
  /* stack the detail portrait above the text instead of floating it narrow */
  .spportrait{float:none;display:block;width:100%;height:auto;max-width:100%;max-height:230px;margin:0 0 10px 0}
}

/* ============================================================
   CAREER PAGE IMPROVEMENT PASS (2026-08-20)
   Appended after the Species .splayout block per the cascade
   convention in css/CLAUDE.md. Tokens only; no hardcoded colors.
   ============================================================ */

/* Career step: persistent side-by-side detail pane, mirroring .splayout.
   The BROWSE column is the only scroller — the detail pane grows with its
   content and stays put via sticky while the list scrolls beside it. */
.crlayout{display:flex;gap:14px;align-items:flex-start}
.crbrowse{flex:1 1 46%;min-width:0;max-height:640px;overflow-y:auto;padding-right:2px}
.crdetail{flex:1 1 54%;min-width:0;position:sticky;top:8px}
.crdetail>.panel{margin-top:0;overflow-wrap:break-word}
/* one career card per row — the 3-across grid has nowhere to go in a 46% column */
.crbrowse .pickgrid.pickgrid-lg{grid-template-columns:1fr}
@media(max-width:900px){
  .crlayout{flex-direction:column}
  /* cap the list so the detail sits just below it rather than a screen away */
  .crbrowse{flex:none;width:100%;max-height:300px}
  .crdetail{position:static;width:100%}
}

/* Career SKILLS picker: the column count now rides a custom property set by
   chargen-build.js instead of an inline grid-template-columns, so these rules
   can override it. minmax(0,1fr) is the app's shrinkable-track idiom, and
   overflow:visible cancels .pickgrid's own overflow-y:auto — inherited, that
   turned the grid into a nested horizontal scroller on phones. */
.cskillgrid{grid-template-columns:repeat(var(--cskcols,4),minmax(0,1fr));max-height:none;overflow:visible}
@media(max-width:680px){ .cskillgrid{grid-template-columns:repeat(2,minmax(0,1fr))} }
/* an unpicked tile once the free ranks are all spent: inert, not just ignored */
.pick.dim{opacity:.45}
.pick.dim input{cursor:not-allowed}
/* rank-2 advisory marker inside a skill tile — never a block, just a heads-up */
.cskillwarn{margin-left:auto;color:var(--amber);font-size:11px}

/* Career detail header: big flush-left name, chips + citation on their own
   line — the .spdetail sptitle/spdetail-src idiom (owner follow-up: the one
   crowded ptitle line wrapped the ⇄ chip and citation at the pane's width). */
.crdetail .crtitle{font-size:20px;line-height:1.15;padding:0 0 8px;margin:0}
.crdetail .crtitle::after{display:none}
.crdetail-src{margin:9px 0 12px;font-size:12.5px;color:var(--faint);display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.crdetail-src .cite{font-size:12.5px}

/* ── Themed dialog overlay ─────────────────────────────────────────────
   Replaces native alert()/confirm()/prompt(). A dedicated overlay (sibling of
   #modal/#lightbox) that NEVER touches the modal back-stack, so a dialog can
   ride on top of an open record editor / PDF viewer and survive render(). Above
   everything interactive (#modal 80, #lightbox 120, #pdfOverlay 200); the
   #prPrevBar/toast 9999 layer stays above. */
#dlg{position:fixed;inset:0;background:var(--modal-scrim);z-index:300;display:none;
  align-items:center;justify-content:center;padding:4vh 12px;overflow-y:auto}
#dlg.open{display:flex}
#dlgBox{width:min(440px,100%);background:var(--panel);border:1px solid var(--holo-dim);
  padding:calc(18px*var(--dsp)) calc(20px*var(--dsp)) calc(22px*var(--dsp))}
#dlgBox:focus,#dlgBox:focus-visible{outline:none}
#dlgBox .dlgMsg{font-size:12.5px;line-height:1.6}
#dlgBox .dlgMsg p{margin:0 0 10px}
#dlgBox .dlgMsg p:last-child{margin-bottom:0}
#dlgInput{width:100%;margin-top:10px}

/* ============================================================
   SPECIALIZATIONS & TALENTS PASS (2026-08-21)
   Appended after the career block per the cascade convention in
   css/CLAUDE.md. Theme tokens only; no hardcoded colors.
   ============================================================ */

/* The specs step reuses the career page's master/detail idiom VERBATIM
   (.crlayout/.crbrowse/.crdetail) — .speclayout exists only as an override
   hook. The talent tree is NOT in the detail pane: .tree carries a 540px
   min-width, which a ~54% column can only give it as a horizontal scroller,
   so it renders in a full-width band below the split. */
.spec-treeband{margin-top:12px}

/* Tree fit-to-width + zoom (P2). Scoped to .fitzoom, which only the specs
   band tree carries — every other tree renders at natural scale. The origin
   is load-bearing: cgTreeFit pairs transform:scale(s) with width:100/s, and
   both assume the tree grows from its top-left corner. */
.fitzoom{touch-action:pan-x pan-y}
.fitzoom .tree{transform-origin:top left}
/* Shown only once cgTreeFit marks the band zoomable (the tree does not fit,
   or the reader has zoomed) — a desktop with room stays untouched. */
.treezoom{display:none;justify-content:flex-end;gap:4px;margin-bottom:4px}
.spec-treeband.zoomable .treezoom{display:flex}

/* Ineligible-click nudge (P5). An ineligible talent-tree BUY used to be a
   silent no-op, so the card read as a dead control. A blocked refund already
   raises a dialog and never shakes. */
@keyframes tshake{0%,100%{transform:translateX(0)}20%{transform:translateX(-3px)}
  40%{transform:translateX(3px)}60%{transform:translateX(-3px)}80%{transform:translateX(3px)}}
.tnode.tshake{animation:tshake .3s ease-in-out}
@media(prefers-reduced-motion:reduce){.tnode.tshake{animation:none;outline:1px solid var(--amber)}}

/* Section headings on the Specializations tab ("Specializations owned",
   "Browse & purchase") - larger, ink-colored peers of the tiny label.fl,
   which read as fine print next to the body-sized rows they title. */
.cgsechead{font-family:var(--label);font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink);margin:12px 0 6px}

/* The tree band's heading at the detail-title scale (the .crdetail .crtitle
   treatment) - the band sits below the whole master/detail split, and an
   11px ptitle read as fine print against the full-width tree it introduces. */
.spec-treeband .treeband-title{font-size:20px;line-height:1.15;padding:0 0 8px}

/* ---- Skills tab redesign (P0–P3): row highlight, compact/side-by layout,
   category summary, search toolbar, detail extras. Accent-wash tokens only. */
.skillsplit .skilltbl tr{cursor:pointer}
.skillsplit .skilltbl tr:hover td{background:var(--accent-wash)}
.skillsplit .skilltbl tr.sksel td{background:var(--accent-wash-mid)}
.skillsplit .skilltbl tr:focus-visible{outline:2px solid var(--holo);outline-offset:-2px}
.skillsplit .skilltbl button[disabled]{opacity:.4;cursor:not-allowed}
/* toolbar: search + Trained + column-mode toggle */
.cgskill-toolbar{align-items:center;gap:6px}
.cgskill-search{flex:0 1 260px;min-width:130px;font-size:12px;padding:5px 9px;
  background:var(--input-bg);border:1px solid var(--line);color:var(--ink)}
.cgfilt-lbl{font-family:var(--label);font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);margin-left:2px}
/* free-ranks readout + real "Assign" button in the section note */
.cgsec-warn{color:var(--amber)}
.cgsec-assign{margin-left:8px;vertical-align:baseline}
/* advisory rank-2 markers */
.skcap{color:var(--amber);cursor:help}
.skcap-note{color:var(--amber)}
/* detail-pane additions */
.skd-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:6px 0}
.skd-actions .dsend{font-size:12px}
.skd-ranks{margin:2px 0 6px}
.skd-careertag{color:var(--green)}
/* the shared detail body reused inside #modal: drop the sticky-pane height clamp */
.skd-plate.skd-modal{position:static;height:auto;max-height:none;overflow:visible}

/* ---- Characteristics: Critical Injuries section (legibility pass) ---- */
/* section heading — replaces a cramped inline 9.5px */
.cihead{font-size:13px}
/* GM-adjudication note under the roller */
.cinote{margin:2px 0 8px}
/* rolled/added result preview — Apply/Discard before it saves to the character */
.cipreview{border:1px solid var(--amber);background:var(--panel2);
  padding:8px 10px;margin:0 0 10px;border-radius:2px}
.cipreview-head{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:4px}
.cipreview-head .ciname{font-size:12.5px}
/* d100 reference table: give it a real minimum width so the Effect/Add columns
   scroll predictably inside .tblscroll on a phone instead of crushing to nothing */
.citbl{min-width:460px}

/* ---- Characteristics tab: hex-value tiles (2 rows × 3 columns) ---- */
.chartiles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
@media(max-width:860px){ .chartiles{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media(max-width:540px){ .chartiles{grid-template-columns:1fr} }
.chartile{position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;
  padding:18px 14px 16px;background:var(--panel2);border:1px solid var(--line);
  clip-path:polygon(0 0,calc(100% - 12px) 0,100% 12px,100% 100%,12px 100%,0 calc(100% - 12px))}
.chartile.hurt{border-color:var(--red-dim);box-shadow:inset 0 0 0 1px var(--red-dim)}
/* the big value hexagon — the HUD hex recipe, sized up */
.ct-hex{position:relative;z-index:1;width:88px;height:100px;display:flex;align-items:center;justify-content:center;
  background:var(--holo);clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}
.ct-hex::before{content:"";position:absolute;inset:2px;background:var(--input-bg);
  clip-path:polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%)}
.ct-val{position:relative;z-index:1;font-family:var(--disp);font-size:44px;line-height:1;
  color:var(--holo-bright);text-shadow:0 0 12px var(--glow)}
.ct-hex.up{background:var(--amber)}
.ct-hex.up .ct-val{color:var(--amber);text-shadow:0 0 12px var(--amber-dim)}
.ct-hex.neg,.chartile.hurt .ct-hex{background:var(--red)}
.ct-hex.neg .ct-val,.chartile.hurt .ct-val{color:var(--red);text-shadow:0 0 10px var(--red-dim)}
.ct-name{position:relative;z-index:1;margin-top:9px;font-family:var(--disp);font-size:16px;
  letter-spacing:.16em;text-transform:uppercase;color:var(--holo-bright)}
.ct-name a{color:inherit;text-decoration:none}
.ct-delta{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);margin-top:3px}
.ct-delta.neg{color:var(--red)}
/* per-source breakdown as centered chips — non-zero pops, zero recedes */
.ct-brk{display:flex;flex-wrap:wrap;gap:6px;justify-content:center;margin:12px 0 6px;max-width:280px}
.ct-chip{display:inline-flex;align-items:baseline;gap:5px;padding:2px 9px;
  border:1px solid var(--line);border-radius:11px;background:var(--panel)}
.ct-chip .l{font-size:9px;letter-spacing:.09em;text-transform:uppercase;color:var(--dim)}
.ct-chip .v{font-family:var(--mono,monospace);font-weight:600;font-size:12.5px;color:var(--amber)}
.ct-chip.pos{border-color:var(--amber-dim)}
.ct-chip.neg{border-color:var(--red-dim)}
.ct-chip.neg .v{color:var(--red)}
.ct-chip.z{opacity:.4}
.ct-chip.z .v{color:var(--faint);font-weight:400}
.ct-step{margin:4px 0 3px}
.ct-next{font-size:10.5px;letter-spacing:.02em;color:var(--dim)}
/* governed skills — bigger and legible, separators recede */
.ct-skills{margin-top:10px;font-size:12px;line-height:1.7;color:var(--ink)}
.ct-skills .ct-sep{color:var(--faint)}

/* ===================== Force page (chargen) ===================== */
/* Force-green hex variant of the Characteristics tile recipe, plus a muted
   "no rating" variant that must not read as an injury (red). */
.ct-hex.pforce{background:var(--force-green)}
.ct-hex.pforce .ct-val{color:var(--force-green);text-shadow:0 0 12px var(--force-green)}
.ct-hex.dim{background:var(--line)}
.ct-hex.dim .ct-val{color:var(--dim);text-shadow:none}
/* Prominent Force Rating status block: the hex tile beside a source/effect body. */
.force-status{display:flex;gap:16px;align-items:center;border:1px solid var(--line);
  background:var(--panel2);padding:10px 14px;margin-bottom:12px}
.force-status .chartile{flex:none;width:auto;min-width:78px;padding:10px 12px!important}
.force-status .ct-hex{width:34px;height:40px}
.force-status .ct-val{font-size:17px}
.force-status .ct-name{margin-top:5px;font-size:11px;letter-spacing:.1em}
.force-status .fs-body{flex:1 1 auto;min-width:0}
.force-status .fs-src{font-size:12.5px;color:var(--amber);margin-bottom:4px}
.force-status .fs-eff{font-size:12.5px;line-height:1.6;color:var(--ink)}
/* Power tile status chip + unreachable-FR fade (accordion heading states FR;
   the fade is the "can't reach this yet" signal — no "Locked" chip). */
.fchip.fstatus.st-own{border-color:var(--force-green);color:var(--force-green)}
.ftile.pforce.locked{opacity:.6}
/* Mentor discount toggle button + auto-applied Career-Power note. */
.mentortog[aria-pressed="true"]{border-color:var(--green);color:var(--green)}
/* No Mentor resource yet: dimmed, but it still works — a click opens the Extras tab */
.mentortog[aria-disabled="true"]{opacity:.45;margin-bottom:8px}
.mentornote{font-size:11.5px;color:var(--dim);margin:2px 0 10px}
.mentornote b{color:var(--green)}
@media(max-width:540px){
  .force-status{flex-direction:column;align-items:flex-start;gap:10px}
}

/* ===================== Signature Abilities page (chargen) ===================== */
/* Attach rows carry long "missing bottom-row talents: …" notes — let them wrap
   instead of crushing the Attach button on narrow screens. */
.sig-attach .initrow{flex-wrap:wrap;row-gap:4px}
.sig-attach .initrow .iname{min-width:200px}
/* Per-host requirement chips: each flagged bottom-row talent, owned or not. */
.sig-attach .iname .fchips{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0 0}
.fchip.sigreq{white-space:nowrap}
.fchip.sigreq.st-own{border-color:var(--force-green);color:var(--force-green)}
.fchip.sigreq.st-miss,.fchip.fstatus.st-miss{border-color:var(--amber);color:var(--amber)}
/* the Rules line sits below the header panel, not tucked under its edge */
.ct-rules.sig-rules{margin:6px 0 12px}

/* ===================== Force step browse pane ===================== */
/* The Force "Browse & purchase" list is a compact ~3-row scroll window rather
   than the full 640px master pane, so it doesn't dwarf the detail pane beside
   it. Scoped to the Force tab (Career/Specs/Skills keep the 640px .crbrowse).
   The mobile override is deliberate: this unconditional rule comes later in
   source than the base .crbrowse mobile media rule, so without it 340px would
   also apply on phones. On a phone (Force pass 2) the list stops being a box
   that scrolls inside the page — a nested scroller under a fold of filters was
   the worst of both — and flows with the page; the Filters disclosure starts
   closed there, so the first tiles sit right under the search box. */
.crbrowse-force{max-height:340px}
@media(max-width:900px){ .crbrowse-force{max-height:none;overflow:visible} }

/* ===================== Background & Motivation citations ===================== */
/* Per-field/per-row RAW citation chip sits after the select (or the motivation
   row's magnitude/controls) and never shrinks; on a wide row it rides the same
   line, on a narrow one it wraps below with the other controls. */
.bgfield .bgfield-cite{flex:0 0 auto}
.initrow .bgfield-cite{flex:0 0 auto;margin-left:auto}
@media(max-width:540px){ .initrow .bgfield-cite{margin-left:0}
  /* …and the row's ✕ that follows it pins to the right edge of that wrapped
     line, instead of sitting mid-row beside the cite chip. */
  .initrow .bgfield-cite + .btn.red{margin-left:auto} }
/* Random Motivation line picker (EotE · AoR · F&D) — a tight segmented group
   ahead of 🎲 in the band header. */
.bg-linechips{display:inline-flex;gap:2px;margin-right:6px}

/* ===================== Change History (chargen) ===================== */
/* The audit log reads as a 3-column table (date / type / change) on desktop.
   The per-entry "Open in <tab>" jump is a faint inline link under the deltas;
   session-group headers span all three columns. Scoped to .chhist so the shared
   .qt table styling is never touched. */
.chhist .chhist-jump{margin-top:3px}
.chhist .chhist-jump .cxlink{font-size:11px;color:var(--faint)}
/* Below 640px each entry becomes a stacked card — date top-left, type badge
   top-right, deltas beneath — so the log never crushes to a horizontal scroll
   on a phone (the card idiom the roster/NPC drawers already use). */
@media(max-width:640px){
  .chhist,.chhist tbody,.chhist tr,.chhist td{display:block}
  .chhist{border:0}
  .chhist tr.chhist-shead td{border:0;padding:8px 0 2px}
  .chhist tr:not(.chhist-shead){position:relative;border:1px solid var(--line);border-radius:6px;margin:6px 0;padding:7px 9px}
  .chhist tr:not(.chhist-shead) td{border:0;padding:0}
  .chhist td.chhist-when{color:var(--faint);font-size:11px;line-height:1.25}
  .chhist td.chhist-kind{position:absolute;top:7px;right:9px;margin:0}
  .chhist td.chhist-body{margin-top:6px}
}

/* ---- Change History print doc (js/print.js buildCharHistoryDoc) ---- */
/* A lean audit table under the character-sheet chrome; reuses the print token
   contract so it prints correctly in both Full Color and Clean ink. */
.prsheet .prhistdoc{width:100%;border-collapse:collapse;font-size:9.5px;line-height:1.3}
.prsheet .prhistdoc th,.prsheet .prhistdoc td{border:1px solid var(--pr-line);padding:2px 5px;text-align:left;vertical-align:top}
.prsheet .prhistdoc th{font-family:var(--disp,inherit);text-transform:uppercase;letter-spacing:.06em;font-size:8px;background:var(--pr-band);color:var(--pr-accent-ink)}
.prsheet .prhistdoc td.prhist-when{white-space:nowrap;width:66px;color:var(--pr-faint)}
.prsheet .prhistdoc td.prhist-kind{white-space:nowrap;width:74px;font-family:var(--disp,inherit);font-size:8px;letter-spacing:.04em}
.prsheet .prhistdoc tr.prhist-shead td{background:var(--pr-band);color:var(--pr-accent-ink);font-family:var(--disp,inherit);text-transform:uppercase;letter-spacing:.08em;font-size:8px}
.prsheet .prhistdoc .prhist-note{color:var(--pr-faint);font-style:italic}

/* ===================== Review finalize hub + Creation Lock ===================== */
/* Review is the "am I done?" surface: a finalize/lock CTA, a derived build
   summary, and the checklist/violations lists. Lock is the creation→play
   boundary; its sealed creation-only controls read as done (the Characteristics
   pattern), not disabled-and-nagging. Tokens only — no hardcoded colors. */
.cglockbtn{white-space:nowrap;letter-spacing:.06em}
/* Finalize / lock CTA block on the Review tab */
.cgfinal{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:center;justify-content:space-between;
  border:1px solid var(--line);border-left:3px solid var(--holo-dim);background:var(--panel2);
  padding:10px 12px;margin-bottom:10px;border-radius:4px}
.cgfinal.ready{border-left-color:var(--green);background:var(--green-wash)}
.cgfinal.locked{border-left-color:var(--amber);background:var(--amber-wash)}
.cgfinal-h{font-size:12.5px;line-height:1.5;flex:1 1 260px;min-width:0}
.cgfinal-actions{display:flex;gap:8px;flex-wrap:wrap;flex:0 0 auto}
/* Shared "creation complete" banner reused by every creation-only control */
.cglocknote{display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  border:1px solid var(--amber-dim);border-left:3px solid var(--amber);background:var(--amber-wash);
  padding:8px 10px;margin:0 0 10px;border-radius:4px}
.cglocknote-txt{flex:1 1 200px;min-width:0;font-size:12px;line-height:1.5;color:var(--amber)}
.cglocknote-btn{flex:0 0 auto}
/* Build summary readout: a compact identity line + hexes + one chip row */
.cgreview-sum .cgrv-id{display:flex;flex-wrap:wrap;gap:4px 18px;margin:6px 0 10px;font-size:13px;line-height:1.6}
.cgrv-id-l{color:var(--faint);text-transform:uppercase;letter-spacing:.08em;font-size:9.5px;margin-right:2px}
.cgrv-hexes{display:flex;gap:8px;flex-wrap:wrap;margin:2px 0 8px}
.cgrv-hex{flex:0 0 auto;text-align:center;border:1px solid var(--holo-dim);border-radius:4px;padding:4px 10px;background:var(--panel2)}
.cgrv-hv{font-family:var(--disp);font-size:18px;color:var(--amber);line-height:1}
.cgrv-hn{font-size:9px;letter-spacing:.1em;color:var(--dim);margin-top:2px}
.cgrv-atts{display:flex;gap:8px;flex-wrap:wrap}
.cgrv-att{display:flex;gap:6px;align-items:baseline;border:1px solid var(--line);border-radius:4px;padding:3px 8px;background:var(--panel2)}
.cgrv-al{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint)}
.cgrv-av{font-family:var(--disp);font-size:14px;color:var(--ink)}
.cgrv-sub{font-family:var(--disp);color:var(--faint);font-size:11px}
/* Rule-violation rows read as advisories, distinct from neutral checklist rows */
.initrow.warn{border-left-color:var(--amber)}
/* When creation is locked, the species/career picker columns collapse to the
   read-only detail pane (only that child renders) */
.crlayout.locked .crdetail,.splayout.locked .spdetail{flex:1 1 100%;width:100%;max-width:none}
/* Coarse-pointer: the Review row action buttons meet the 44px tap target */
@media(pointer:coarse){
  .cgfinal-actions .btn,.cglocknote-btn{min-height:34px}
}

/* ---------- Character Sheet tab (chargen Output) ---------- */
/* Portrait + identity header on the sheet's summary card. */
.sheethead{display:flex;gap:12px;align-items:flex-start}
/* object-fit:contain so the WHOLE portrait shows (full-body species art is
   taller than a crop box) — nothing is cut off; the panel fills any letterbox. */
.sheethead-img{width:135px;height:185px;flex:none;object-fit:contain;object-position:top;border:1px solid var(--line);border-radius:3px;background:var(--panel);cursor:zoom-in}
.sheethead-id{flex:1;min-width:0}
.sheethead-id h4{margin-bottom:3px}
.sheethead-sub{font-size:12px;line-height:1.5;margin-bottom:1px}
.sheethead-badges{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0 2px}
.sheethead-meta{color:var(--faint);font-size:11px;margin-top:3px}
/* Derived-stats readout: a wrap-friendly tile grid (replaces the old 6-col
   single-row table that overflowed at phone widths). */
.sheetstats{display:grid;grid-template-columns:repeat(auto-fit,minmax(70px,1fr));gap:6px;margin-top:8px}
.sheetstat{border:1px solid var(--line);background:var(--panel2);padding:5px 4px;text-align:center;min-width:0;container-type:inline-size}
.sheetstat.over{border-color:var(--red)}
.sheetstat .ss-v{font-family:var(--label);font-size:14px;color:var(--ink-hi)}
.sheetstat.over .ss-v{color:var(--red)}
.sheetstat .ss-l{font-family:var(--label);font-size:8px;letter-spacing:.05em;text-transform:uppercase;color:var(--holo);margin-top:2px;line-height:1.2}
/* Full label at full size, abbreviation once the tile gets narrow (container
   query on the tile itself — Def M/R and Enc carry both). */
.sheetstat .ss-l .labbr{display:none}
@container (max-width:118px){
  .sheetstat .ss-l .lfull{display:none}
  .sheetstat .ss-l .labbr{display:inline}
}
/* Per-weapon attack-pool sub-row sits attached under its weapon row. */
.sheetbody .wpoolrow>td{border-top:0;padding-top:0}
@media(max-width:640px){
  /* The sheet's multi-row weapon/armor/gear tables reflow to stacked cards
     rather than scrolling sideways. Scoped under .sheetbody so the app-wide
     table.qt scroll default (and every other qt table) is untouched. Skills are
     a grid of cells now, not a table, so they collapse on their own. */
  .sheetbody table.qt{display:block;overflow:visible;font-size:12.5px}
  .sheetbody table.qt tbody{display:block}
  .sheetbody table.qt tr:first-child{display:none} /* the <th> header row */
  .sheetbody table.qt tr{display:block;border:1px solid var(--line);margin-bottom:6px;padding:5px 8px}
  .sheetbody table.qt td{display:block;border:0;padding:1px 0;white-space:normal;text-align:left}
  .sheetbody table.qt td:first-child{font-weight:600;font-size:13px;color:var(--ink-hi);margin-bottom:2px}
  .sheetbody td[data-label]::before{content:attr(data-label);display:inline-block;min-width:62px;font-family:var(--label);font-size:9px;letter-spacing:.05em;text-transform:uppercase;color:var(--holo);margin-right:6px}
  .sheetbody td[data-label]:empty{display:none} /* drop empty labelled cells (e.g. an unarmed row's blank Qualities) */
  .sheetbody .wpoolrow{margin-top:-6px}
  .sheetbody .wpoolrow td:first-child{font-weight:400;font-size:12px}
}
/* Full-width header band (portrait/identity + characteristics + derived stats +
   vitals steppers). One bordered plate so the header reads as a single cohesive
   unit above the three balanced content columns; .sheethead loses its own
   border here (it is a sub-block of the plate, not its own card). Wraps to a
   stacked layout on narrow widths — no extra media query needed. */
.sheetbanner{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;border:1px solid var(--line);background:var(--panel2);padding:calc(10px*var(--dsp));margin-bottom:12px}
.sheetbanner .sheethead{flex:1 1 240px}
.sheetvitals{flex:2 1 320px;min-width:240px;display:flex;flex-direction:column;gap:8px}
.sheetvitals .sheetstats{margin-top:0}
/* Vitals steppers, styled like the encounter run tracker (label beside a
   full-size .stepper; .cnt widened via .pcw so "+12/14" / "0/20" fit). They sit
   in the identity block, under the Player/Campaign/XP meta line. */
.sheetsteppers{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin-top:8px}
.sheetsteppers .smallnote{margin-right:3px}
/* Sheet body: full-width stacked sections, each flowing its own items across as
   many columns as fit (skills as compact cells, talents/powers as cards) so the
   list reads horizontally instead of piling into one tall column. */
.sheetbody{display:flex;flex-direction:column;gap:12px}
.sheetsec>h4.ptitle{margin-bottom:6px}
/* Skills — a grid of compact cells (name+rank+⚄ on the top row, dice pool below,
   so a wide pool never overflows the cell). */
.skgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:6px}
.skcell{display:grid;grid-template-columns:1fr auto auto;grid-template-areas:"name rank send" "pool pool pool";gap:2px 8px;align-items:center;border:1px solid var(--line);background:var(--panel2);padding:5px 8px;min-width:0}
.skcell .skname{grid-area:name;min-width:0;font-size:12.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.skcell .skrank{grid-area:rank;font-family:var(--label);font-size:13px;color:var(--ink-hi)}
.skcell .dsend{grid-area:send}
.skcell .skpool{grid-area:pool;display:flex;flex-wrap:wrap;gap:2px;align-items:center;min-height:20px}
/* Card sections (talents, force powers, signature abilities, companions,
   background/notes). The cards vary a lot in length, so a rigid row-based grid
   left ragged bottoms and dead space. A masonry-style COLUMN flow packs them
   tightly top-to-bottom instead — each card stays whole (break-inside:avoid)
   and columns fill to roughly equal height, so there are no ragged rows. */
.cardgrid{column-width:260px;column-gap:10px}
.cardgrid>*{break-inside:avoid;-webkit-column-break-inside:avoid;margin:0 0 10px}
/* Section header row: title on the left, an optional control (e.g. the Talents
   "Show N hidden" toggle) on the right. */
.sheetsec-head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}
.sheetsec-head>h4.ptitle{margin-bottom:0}
/* Talent hide/unhide: a small ghost toggle in the card header (⦸ hide / ◉ show),
   and a dimmed treatment for a hidden card while the reveal toggle is on. */
.taltcard{position:relative}
.taltcard>h4{padding-right:20px}
.taltoggle{position:absolute;top:6px;right:6px;background:none;border:0;color:var(--faint);cursor:pointer;font-size:13px;line-height:1;padding:2px}
.taltoggle:hover{color:var(--holo)}
.taltcard.talhidden{opacity:.55}
.taltcard.talhidden .taltoggle{color:var(--holo)}
/* Story / Background: fields laid out horizontally (wrapping) so the section
   stretches across the width instead of stacking into a tall narrow card. */
.bgfields{display:flex;flex-wrap:wrap;gap:4px 22px}
/* Each Sheet field is one line of inline text. It shares the .bgfield class
   with the Background tab's label/select/cite row, which is a flex box — so
   without this every Codex link in a value became its own flex item and the
   line broke apart on a phone. */
.bgfields>.bgfield{display:block;margin:0}
.rref .sheetstory{margin-top:8px}
.bgfield{font-size:12.5px;line-height:1.5}
/* Chargen Equipment catalog picker: on phones the dense .eqtbl reflows to
   stacked cards instead of scrolling sideways, so the ＋/₵ action cell is
   always reachable (it used to scroll off the right edge — you couldn't buy on
   mobile). Scoped to .eqtbl only; the desktop table and every other table are
   untouched. Mirrors the .sheetbody data-label reflow. */
@media(max-width:640px){
  .eqwrap{overflow-x:hidden}
  .eqtbl,.eqtbl tbody{display:block}
  .eqtbl tr:first-child{display:none} /* the <th> header row */
  .eqtbl tr{display:grid;grid-template-columns:1fr 1fr;column-gap:12px;border:1px solid var(--line);border-left:3px solid var(--holo-dim);margin:0 0 6px;padding:6px 10px;background:var(--panel2)}
  .eqtbl tr.eqsel{background:var(--accent-wash-mid)}
  /* two stat columns per card; the name and the action row span both */
  .eqtbl td{display:block;border:0;padding:2px 0;white-space:normal;text-align:left;font-size:12.5px;min-width:0}
  /* weapons: Skill | Range on the first stat row (DOM order is Skill, Dam,
     Crit, Range…, which the desktop table needs), then Dam | Crit, Enc | HP,
     Price | Rar; gear/attachment Type spans the card */
  .eqtbl td.eqname{order:-2}
  .eqtbl td[data-label="Skill"],.eqtbl td[data-label="Range"]{order:-1}
  .eqtbl td.eqactcell{order:1}
  .eqtbl td[data-label="Applies to"],.eqtbl td[data-label="Type"]{grid-column:1/-1}
  .eqtbl td.eqname{grid-column:1/-1;font-weight:600;font-size:13.5px;color:var(--holo);margin-bottom:3px}
  .eqtbl td[data-label]::before{content:attr(data-label);display:inline-block;min-width:44px;font-family:var(--label);font-size:9px;letter-spacing:.05em;text-transform:uppercase;color:var(--holo);margin-right:6px}
  .eqtbl td.eqname[data-label]::before{display:none} /* name is the card header, no label */
  /* compact, right-aligned Add / Buy — 34px tall like every button */
  .eqtbl td.eqactcell{grid-column:1/-1;margin-top:6px;padding-top:6px;border-top:1px solid var(--line);display:flex;gap:8px;align-items:center}
  .eqtbl td.eqactcell::before{margin-right:auto!important}
  .eqtbl td.eqactcell .btn{flex:0 0 auto;min-width:52px;min-height:34px;padding:0 12px}
  /* the detail strip is not a stat card: one block, joined under its card */
  .eqtbl tr.eqsel{margin-bottom:0}
  .eqtbl tr.eqdetailrow{display:block;border:0;padding:0;margin:0 0 6px;background:none}
  .eqtbl tr.eqdetailrow>td{display:block;padding:0;border:1px solid var(--line);border-top:0}
  /* Owned rows: name + stat line full width (it was squeezed into a narrow
     column beside the buttons), then the action buttons, then ONE compact
     control row — Qty and +HP side by side, repair tabs full width. Touch
     targets keep their coarse-pointer height; only the value box narrows. */
  .eqown{gap:6px 8px}
  .eqown .iname{flex:1 1 100%;min-width:0}
  .eqown .eqown-line{display:block;margin-top:2px;font-size:11.5px}
  .eqown-acts{flex:1 1 100%}
  .eqown-ctl{gap:6px 14px;font-size:12px}
  .eqown .stepper .cnt{min-width:26px;padding:0 2px}
  .eqown .stepper.mini button{min-width:34px}
  .eqown-step{gap:6px}
  .eqown-ctl .repairtabs{flex:1 1 100%}
  .eqown-ctl .reptab{flex:1 1 0;padding:6px 4px}
}

/* ---- Compare specializations (side-by-side, in a full-size modal) ----
   The tab carries only the chosen-set launcher; the actual side-by-side view
   opens in a near-fullscreen modal (#modalBox.xwide) where each column renders
   its tree at natural size inside its own horizontal scroller. Tokens only —
   no hardcoded colors. Columns stack to one at ≤900px. */
.cmpbtn{font-family:var(--label);font-size:10px;letter-spacing:.04em;line-height:1;
  padding:2px 6px;min-height:22px;border:1px solid var(--line);border-radius:4px;
  background:var(--panel2);color:var(--ink-dim);cursor:pointer}
.cmpbtn:hover{border-color:var(--holo);color:var(--holo)}
.cmpbtn.on{background:var(--accent-wash-mid);border-color:var(--holo);color:var(--holo)}
.cmpbtn[disabled]{opacity:.4;cursor:not-allowed}
.cmpbtn:focus-visible{outline:2px solid var(--holo);outline-offset:1px}
.cmpbtn-corner{float:right;margin:0 0 2px 6px}
.cmpbtn-detail{vertical-align:middle;margin-left:8px}

.sc-head{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:8px}
.sc-head .treezoom{display:flex;margin-bottom:0}
.sc-chips{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 10px}
.sc-chip{display:inline-flex;align-items:center;gap:4px;font-size:12px;
  padding:2px 4px 2px 8px;border:1px solid var(--line);border-radius:12px;background:var(--panel2)}
.sc-chipx{border:0;background:none;color:var(--ink-dim);cursor:pointer;font-size:12px;line-height:1;padding:2px 4px}
.sc-chipx:hover{color:var(--red)}
.sc-colhead{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 8px}
.sc-slots{display:flex;flex-wrap:wrap;gap:4px}
.sc-title{font-family:var(--head,var(--label));font-size:16px;line-height:1.2;margin:0 0 2px}

/* The extra-wide comparison modal — wider than .wide (960px) so two trees fit
   at natural size. .cmpcol keeps its tree in its own overflow-x scroller (the
   .treewrap default), so a tree wider than its half scrolls rather than shrinks. */
#modalBox.xwide{width:min(1500px,96vw)}
.cmpcols{display:grid;grid-template-columns:1fr 1fr;gap:14px;align-items:start}
.cmpcol{min-width:0;border:1px solid var(--line);border-radius:6px;padding:10px;background:var(--panel2)}
@media(max-width:900px){.cmpcols{grid-template-columns:1fr}}
/* A read-only (preview) compare column stays INERT (pointer-events:none via
   .preview-locked) but must read at FULL brightness — dimming makes side-by-side
   comparison harder. Same un-dim the print layer applies (see #printDoc .prtree),
   scoped to .cmpcol so it never touches the tab's own preview trees. */
.cmpcol .tree.preview-locked .tnode{opacity:1}
.cmpcol .tree.preview-locked .tnode.locked{opacity:1;filter:none}
/* The compare launcher sits directly under the browse/detail split (.crlayout,
   which has no bottom margin) and above the tree band; a bare .panel has no TOP
   margin, so its corner brackets (which overhang ~2px + an 8px glow) collided
   with the picker above. Give it symmetric vertical breathing room so the
   brackets clear both neighbours. */
.panel.cmplauncher{margin-top:calc(18px*var(--dsp));margin-bottom:calc(18px*var(--dsp))}

/* ============================================================
   GUIDED TOUR (coach-marks) — 2026-08-25, review pass 2026-10-03
   Appended per the cascade convention. The #tour overlay (js/tour.js) is a
   sibling of #modal/#dlg. Non-modal: #tour never dims the page or eats its
   clicks; only the draggable card (#tourPop) is interactive.
     • anchored — #tourSpot is a bright ring over the target.
     • centered (.tour-center) — no target: the ring hides and a light scrim
       gives the card some separation.
     • paused (.tour-paused) — chargen's Unsaved Changes guard is up; the tour
       drops below #modal (80) so the guard sits on top.
   Sits below #dlg (300) so the fresh-install welcome dialog wins, above the
   header (40) / #modal (80) / #lightbox (120) so it covers the nav bar it
   points at. Theme tokens only.
   ============================================================ */
/* Non-modal coach-mark: the overlay never dims the screen or eats page clicks —
   a bright ring marks the target and only the (draggable) card is interactive. */
#tour{position:fixed; inset:0; z-index:260; display:none; pointer-events:none}
#tour.open{display:block; pointer-events:none}
/* Centered intro/outro/finale cards have no target, so a light backdrop gives the
   card a little separation from busy content. Anchored steps get no scrim. */
#tour.tour-center{background:var(--tour-scrim); pointer-events:auto}
#tourSpot{position:fixed; display:none; border-radius:8px; pointer-events:none;
  box-shadow:0 0 0 2px var(--panel), 0 0 0 4px var(--holo-bright), 0 0 22px 5px var(--modal-glow);
  transition:left .18s ease, top .18s ease, width .18s ease, height .18s ease}
#tourPop{position:fixed; left:50%; top:50%; width:min(360px,calc(100vw - 24px));
  transform:none; background:var(--panel); border:1px solid var(--holo-dim);
  box-shadow:0 10px 34px rgba(0,0,0,.5), 0 0 22px var(--modal-glow);
  padding:calc(14px*var(--dsp)) calc(16px*var(--dsp)) calc(13px*var(--dsp)); pointer-events:auto}
#tour.tour-center #tourPop{left:50%; top:50%; transform:translate(-50%,-50%)}
/* Off-screen dock: target scrolled away → the card parks at the bottom-center. */
#tourPop.tour-docked{left:50%!important; top:auto!important;
  bottom:calc(14px + var(--tour-dock-off,0px) + env(safe-area-inset-bottom,0px))!important; transform:translateX(-50%)}
/* Chargen's Unsaved Changes guard (#modal, z 80) must sit above the card. */
#tour.tour-paused{z-index:70}
/* Drag handle (the title bar). */
#tourPop .ptitle.tour-drag{cursor:move; user-select:none; display:flex; align-items:center; gap:7px; touch-action:none}
#tourPop .tour-grip{color:var(--faint); font-size:13px; line-height:1; letter-spacing:-1px; flex:0 0 auto}
#tourPop .tour-ttl{flex:1 1 auto; min-width:0}
/* Phone-only fold toggle (shown in the max-width block below). */
#tourPop .tour-minbtn{display:none; flex:0 0 auto; min-width:24px; min-height:24px; padding:0 4px;
  background:none; border:1px solid var(--line); border-radius:4px; color:var(--dim); font:inherit; line-height:1; cursor:pointer}
#tourPop.tour-min .tour-body,#tourPop.tour-min .tour-guardnote,#tourPop.tour-min .tour-progress,
#tourPop.tour-min .tour-exit,#tourPop.tour-min .tour-skipsec{display:none}
#tourPop.tour-min .ptitle{margin-bottom:0}
#tourPop.tour-min .tour-foot{margin-top:8px}
#tourPop.tour-dragging{transition:none; box-shadow:0 14px 40px rgba(0,0,0,.6), 0 0 26px var(--modal-glow)}
#tourPop .ptitle{margin:0 0 8px}
#tourPop .tour-body{font-size:12.5px; line-height:1.6; color:var(--ink)}
#tourPop .tour-body p{margin:0 0 8px}
#tourPop .tour-body p:last-child{margin-bottom:0}
#tourPop .tour-body ul{margin:6px 0; padding-left:18px}
.tour-guardnote{margin-top:8px; padding:7px 9px; border:1px solid var(--line);
  background:var(--panel2); font-size:11.5px; line-height:1.5; color:var(--dim)}
.tour-foot{display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap;
  gap:8px; margin-top:calc(11px*var(--dsp)); padding-top:calc(9px*var(--dsp));
  border-top:1px solid var(--line)}
.tour-progress{font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--faint)}
/* Two-tier progress for sectioned tours: the section position over a small
   "Section N of M" line. Flat tours keep the single-line variant above. */
.tour-progress .tour-prog-main{display:block; color:var(--dim); font-weight:600}
.tour-progress .tour-prog-sub{display:block; margin-top:1px; font-size:9.5px; color:var(--faint)}
.tour-foot-btns{display:flex; flex-wrap:wrap; gap:6px}
.tour-foot.tour-finale{justify-content:flex-end; border-top:none; padding-top:0}
@media(max-width:560px){
  /* On a phone an anchored/docked card sits at the bottom so it never collides
     with the ring or runs off-screen; a card the user has dragged keeps its
     place (.tour-moved is excluded). JS positioning is overridden by !important. */
  #tour:not(.tour-center) #tourPop:not(.tour-moved){left:8px !important; right:8px !important;
    top:auto !important; bottom:calc(8px + var(--tour-dock-off,0px) + env(safe-area-inset-bottom,0px)) !important;
    width:auto; transform:none}
  .tour-foot{justify-content:flex-end}
  /* A long interactive step can cover half a phone: ▾ folds it to its buttons. */
  #tourPop .tour-minbtn{display:inline-flex; align-items:center; justify-content:center}
  #tourPop{max-height:calc(100% - 16px - var(--tour-dock-off,0px)); overflow-y:auto}  /* % of the viewport: a fixed box's containing block */
}

/* ============================================================
   TOUR HUB (Guide) + Settings launcher — the Quick/Full chooser.
   Appended per the cascade convention. Theme tokens only.
   ============================================================ */
.guide-tourhub{margin:2px 0 12px}
.tourhub-cards{display:grid; grid-template-columns:1fr 1.4fr; gap:calc(10px*var(--dsp))}
.tourhub-card{border:1px solid var(--holo-dim); background:var(--panel2);
  padding:calc(11px*var(--dsp)) calc(13px*var(--dsp))}
.tourhub-h{color:var(--holo); font-weight:700; letter-spacing:.04em; margin-bottom:3px}
.tourhub-d{color:var(--faint); font-size:12px; line-height:1.5; margin-bottom:9px; max-width:60ch}
.tourhub-actions{display:flex; flex-wrap:wrap; gap:6px}
.tourhub-secs{margin-top:10px; padding-top:9px; border-top:1px solid var(--line)}
.tourhub-secs-lbl{display:block; color:var(--faint); font-size:10.5px; letter-spacing:.1em;
  text-transform:uppercase; margin-bottom:6px}
.tourhub-secchips{display:flex; flex-wrap:wrap; gap:5px}
.tourhub-tasks{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-top:10px}
/* Compact tour launcher on the Settings page. */
.set-tourrow{display:flex; flex-wrap:wrap; align-items:center; gap:6px}
.set-tourrow .set-tourrow-lbl{color:var(--faint); font-size:12px; margin-right:4px}
@media(max-width:640px){ .tourhub-cards{grid-template-columns:1fr} }

/* ============ Cybernetic Body Layout — technical schematic ============ */
/* Header band */
.cyb-titlebar{display:flex; align-items:center; gap:10px; padding:calc(8px*var(--dsp)) calc(11px*var(--dsp));
  border:1px solid var(--holo-dim); border-radius:8px;
  background:linear-gradient(180deg,var(--panel-a),var(--panel-b)); box-shadow:inset 0 0 22px var(--accent-wash)}
.cyb-emblem{display:inline-flex; color:var(--holo)} .cyb-emblem svg{width:20px; height:20px}
.cyb-title{font-size:15px; letter-spacing:.16em; font-weight:700; color:var(--holo-bright); text-shadow:0 0 9px var(--glow)}
.cyb-hstat{margin-left:auto; font-size:11px; letter-spacing:.09em; color:var(--dim)}
.cyb-hstat b{font-size:14px; color:var(--ink-hi); margin:0 2px} .cyb-hstat b.over{color:var(--red)} .cyb-hstat b.ok{color:var(--holo-bright)}
/* Toolbar (instruction + actions) */
.cyb-toolbar{display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:8px}
.cyb-instr{flex:1 1 260px; font-size:12px; color:var(--dim); line-height:1.4}
.cyb-actions{display:flex; gap:6px; flex-wrap:wrap; margin-left:auto}
.cyb-note{margin-top:8px} .cyb-foot{display:flex; justify-content:flex-end; margin-top:10px}
/* Stage: [left panels] · [silhouette] · [right panels] + callout overlay */
.cyb-stage{position:relative; display:grid; grid-template-columns:1fr minmax(200px,260px) 1fr;
  gap:calc(18px*var(--dsp)); align-items:start; margin-top:10px}
.cyb-col{display:flex; flex-direction:column; gap:calc(10px*var(--dsp)); position:relative; z-index:2}
.cyb-figstage{display:flex; justify-content:center; align-items:flex-start; position:relative; z-index:2}
/* Scanner frame around the body image: grid backdrop, glow, corner brackets, sweep */
.cyb-figframe{position:relative; width:100%; max-width:238px; padding:12px; border-radius:12px;
  border:1px solid var(--holo-dim); background-color:var(--panel);
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px),
    radial-gradient(120% 70% at 50% 34%, var(--accent-wash), transparent 72%);
  background-size:19px 19px, 19px 19px, 100% 100%; background-position:center;
  box-shadow:inset 0 0 34px var(--accent-wash), 0 0 14px var(--glow); overflow:hidden}
.cyb-figframe::before,.cyb-figframe::after{content:""; position:absolute; width:14px; height:14px; border:2px solid var(--holo); opacity:.7; pointer-events:none; z-index:3}
.cyb-figframe::before{top:6px; left:6px; border-right:0; border-bottom:0}
.cyb-figframe::after{bottom:6px; right:6px; border-left:0; border-top:0}
.cyb-fscan{position:absolute; left:0; right:0; top:0; height:2px; pointer-events:none; z-index:3;
  background:linear-gradient(90deg, transparent, var(--holo-bright), transparent); opacity:.5; box-shadow:0 0 8px var(--glow)}
/* On-screen body: fixed rim-lit image (in-universe screen) + interactive node overlay */
.cyb-bodybox{position:relative; width:100%; margin:0 auto; line-height:0}
.cyb-bodyimg{display:block; width:100%; height:auto; filter:drop-shadow(0 0 5px var(--glow))}
.cyb-nodes{position:absolute; inset:0; width:100%; height:100%; overflow:visible; z-index:2}
/* Print-only vector silhouette (cybSilhouetteSvg → ink) */
.cyb-fig{position:relative; z-index:1; width:100%; max-width:216px; height:auto;
  filter:drop-shadow(0 0 6px var(--glow)) drop-shadow(0 0 2px var(--holo-dim))}
.cyb-body path,.cyb-body ellipse{fill:var(--holo); fill-opacity:.12; stroke:var(--holo); stroke-opacity:.85; stroke-width:1.3}
.cyb-spine{stroke:var(--holo); stroke-opacity:.35; stroke-width:1; stroke-dasharray:2 4; fill:none}
/* Region nodes — the screen overlay's wider 554×900 viewBox needs thicker strokes */
.cyb-node-o{fill:none; stroke-width:2}
.cyb-nodes .cyb-node-o{stroke-width:4}
.cyb-node.cs-empty .cyb-node-o{stroke:var(--faint)} .cyb-node.cs-empty .cyb-node-i{fill:var(--faint)}
.cyb-node.cs-occupied .cyb-node-o{stroke:var(--holo); filter:drop-shadow(0 0 3px var(--glow))} .cyb-node.cs-occupied .cyb-node-i{fill:var(--holo)}
.cyb-node.cs-offline .cyb-node-o{stroke:var(--red)} .cyb-node.cs-offline .cyb-node-i{fill:var(--red)}
.cyb-node.sel .cyb-node-o{stroke:var(--holo-bright); filter:drop-shadow(0 0 6px var(--glow))}
.cyb-node.cs-offline.sel .cyb-node-o{stroke:var(--red)}
.cyb-node.arm .cyb-node-o{stroke-dasharray:5 4}
.cyb-node:hover .cyb-node-o{filter:drop-shadow(0 0 6px var(--glow))}
/* Callout lines (measured overlay, drawn behind the panels) */
.cyb-lines{position:absolute; inset:0; width:100%; height:100%; pointer-events:none; z-index:1; overflow:visible}
.cyb-line{stroke-width:1.4; opacity:.7}
.cyb-line.cs-empty{stroke:var(--faint); opacity:.4}
.cyb-line.cs-occupied{stroke:var(--holo-dim)}
.cyb-line.cs-offline{stroke:var(--red); opacity:.85}
.cyb-line.sel{stroke:var(--holo-bright); stroke-width:1.8; opacity:1; filter:drop-shadow(0 0 3px var(--glow))}
.cyb-line.cs-offline.sel{stroke:var(--red)}
/* Socket where a wire meets its panel — makes the callout connect, not float */
.cyb-sock{stroke:none}
.cyb-sock.cs-empty{fill:var(--faint)} .cyb-sock.cs-occupied{fill:var(--holo)} .cyb-sock.cs-offline{fill:var(--red)}
.cyb-sock.sel{fill:var(--holo-bright)} .cyb-sock.cs-offline.sel{fill:var(--red)}
/* Region panels — framed tech readouts */
.cyb-panel{--cyb-accent:var(--line); position:relative; cursor:pointer; overflow:hidden;
  background:linear-gradient(180deg,var(--panel-a),var(--panel-b)); border:1px solid var(--line); border-radius:8px;
  padding:calc(6px*var(--dsp)) calc(9px*var(--dsp)); box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);
  transition:border-color .12s,box-shadow .12s,background .12s}
.cyb-panel.side-L{border-right:3px solid var(--cyb-accent)}
.cyb-panel.side-R{border-left:3px solid var(--cyb-accent)}
.cyb-panel.cs-occupied{--cyb-accent:var(--holo)} .cyb-panel.cs-offline{--cyb-accent:var(--red)}
.cyb-panel.sel,.cyb-panel.in{--cyb-accent:var(--holo-bright)}
/* inner-edge corner brackets (toward the silhouette) */
.cyb-panel::before,.cyb-panel::after{content:""; position:absolute; width:9px; height:9px; border:1.5px solid var(--cyb-accent); opacity:.45; pointer-events:none; transition:opacity .12s}
.cyb-panel.side-L::before{top:5px; right:5px; border-left:0; border-bottom:0}
.cyb-panel.side-L::after{bottom:5px; right:5px; border-left:0; border-top:0}
.cyb-panel.side-R::before{top:5px; left:5px; border-right:0; border-bottom:0}
.cyb-panel.side-R::after{bottom:5px; left:5px; border-right:0; border-top:0}
.cyb-panel:hover{border-color:var(--holo-dim); background:var(--hover)}
.cyb-panel.sel,.cyb-panel.in{border-color:var(--holo); box-shadow:0 0 0 1px var(--holo) inset,0 0 14px var(--glow)}
.cyb-panel.sel::before,.cyb-panel.sel::after,.cyb-panel.in::before,.cyb-panel.in::after,.cyb-panel.cs-occupied::before,.cyb-panel.cs-occupied::after,.cyb-panel.cs-offline::before,.cyb-panel.cs-offline::after{opacity:.85}
.cyb-panel.arm{box-shadow:0 0 10px var(--glow)}
.cyb-panel.arm:hover{border-color:var(--holo)}
.cyb-panel:focus-visible{outline:2px solid var(--focus-ring); outline-offset:2px}
.cyb-phd{display:flex; align-items:center; gap:8px}
.cyb-ico{width:22px; height:22px; flex:none; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--line); border-radius:5px; background:var(--panel2); color:var(--holo)}
.cyb-icosvg{width:14px; height:14px}
.cyb-panel.cs-occupied .cyb-ico{border-color:var(--holo-dim)}
.cyb-panel.cs-offline .cyb-ico{color:var(--red); border-color:var(--red)}
.cyb-panel.cs-empty .cyb-ico{color:var(--faint)}
.cyb-pname{flex:1 1 auto; min-width:0; font-size:11px; letter-spacing:.08em; font-weight:700; color:var(--ink); text-transform:uppercase}
.cyb-count{font-size:15px; font-weight:700; color:var(--ink-hi); font-variant-numeric:tabular-nums; margin-right:1px}
.cyb-dot{width:9px; height:9px; border-radius:50%; background:var(--faint); flex:none}
.cyb-dot.cs-occupied{background:var(--holo); box-shadow:0 0 6px var(--glow)}
.cyb-dot.cs-offline{background:var(--red); box-shadow:0 0 6px var(--red)}
.cyb-pbd{margin-top:5px; display:flex; flex-direction:column; gap:3px}
.cyb-pbd-empty{flex-direction:row; align-items:center; justify-content:space-between; margin-top:4px}
.cyb-row{display:flex; align-items:center; gap:7px; font-size:12px;
  background:var(--panel2); border:1px solid var(--line); border-radius:5px; padding:2px 6px}
.cyb-rmark{width:5px; height:5px; border-radius:50%; background:var(--holo); flex:none; box-shadow:0 0 5px var(--glow)}
.cyb-row.broken .cyb-rmark{background:var(--red); box-shadow:0 0 5px var(--red)}
.cyb-row-nm{color:var(--ink); flex:1 1 auto; min-width:0} .cyb-row.broken .cyb-row-nm{color:var(--red)}
.cyb-empty{font-size:11px; color:var(--faint); font-style:italic; padding:1px 2px}
.cyb-x{border:none; background:none; color:var(--dim); cursor:pointer; font-size:14px; line-height:1; padding:0 3px; border-radius:50%; flex:none}
.cyb-x:hover{color:var(--red); background:var(--red-wash)}
/* Legend */
.cyb-legend{display:flex; flex-wrap:wrap; gap:18px; justify-content:center; margin-top:12px; font-size:10.5px; letter-spacing:.06em; color:var(--dim)}
.cyb-legend span{display:inline-flex; align-items:center; gap:6px}
.cyb-legend i.cyb-dot{display:inline-block}
.cyb-add{align-self:flex-start; margin-top:2px; border:1px dashed var(--line); background:transparent;
  color:var(--dim); font-size:10.5px; letter-spacing:.03em; border-radius:6px; padding:2px 8px; cursor:pointer}
.cyb-add:hover,.cyb-add.on{border-color:var(--holo-dim); color:var(--holo-bright); background:var(--accent-wash)}
.cyb-menu{margin-top:4px; display:flex; flex-direction:column; gap:3px; background:var(--panel2);
  border:1px solid var(--holo-dim); border-radius:6px; padding:4px}
.cyb-menu-it{text-align:left; border:none; background:transparent; color:var(--ink); font-size:11.5px; padding:3px 6px; border-radius:4px; cursor:pointer}
.cyb-menu-it:hover{background:var(--hover); color:var(--holo-bright)} .cyb-menu-it.broken{color:var(--red)}
/* Unassigned tray */
.cyb-tray{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-top:calc(10px*var(--dsp));
  padding:calc(7px*var(--dsp)) calc(9px*var(--dsp)); background:var(--panel); border:1px solid var(--line); border-radius:8px}
.cyb-tray-l{font-size:10px; letter-spacing:.12em; color:var(--faint); margin-right:2px}
.cyb-chip{display:inline-flex; align-items:center; gap:5px; cursor:pointer; color:var(--ink); font-size:12px;
  background:var(--panel2); border:1px solid var(--line); border-radius:20px; padding:3px 11px; transition:border-color .12s,background .12s}
.cyb-chip:hover{border-color:var(--holo-dim); background:var(--hover)}
.cyb-chip.sel{border-color:var(--holo); background:var(--accent-wash-mid); box-shadow:0 0 9px var(--glow)}
.cyb-chip.broken .cyb-chip-nm{color:var(--red)}
.cyb-chip:focus-visible{outline:2px solid var(--focus-ring); outline-offset:1px}
/* Motion (full holo-tech), disabled under reduced-motion */
@keyframes cybpulse{0%,100%{opacity:1} 50%{opacity:.35}}
@keyframes cybdash{to{stroke-dashoffset:-18}}
@keyframes cybbreathe{0%,100%{opacity:1} 50%{opacity:.8}}
@keyframes cybscan{0%{top:4%; opacity:0} 12%{opacity:.55} 88%{opacity:.55} 100%{top:96%; opacity:0}}
.cyb-fscan{opacity:0}
@media (prefers-reduced-motion: no-preference){
  .cyb-node.sel .cyb-node-o{animation:cybpulse 1.4s ease-in-out infinite}
  .cyb-line.sel{stroke-dasharray:5 4; animation:cybdash 1s linear infinite}
  .cyb-fig .cyb-body{animation:cybbreathe 4.5s ease-in-out infinite}
  .cyb-fscan{animation:cybscan 3.6s ease-in-out infinite}
}
/* Stack on narrow screens: silhouette on top, panels below, no callout lines */
@media(max-width:860px){
  .cyb-stage{grid-template-columns:1fr}
  .cyb-lines{display:none}
  .cyb-figstage{order:-1} .cyb-fig{max-width:200px}
  .cyb-panel.side-L,.cyb-panel.side-R{border-left-width:1px; border-right-width:1px; border-top:3px solid var(--cyb-accent)}
}
/* Printed-sheet echo (ink mode): silhouette beside a region→implant list */
.prcyb{display:flex; gap:14px; align-items:flex-start; flex-wrap:wrap}
.prcyb .prcybfig{flex:0 0 150px}
.prcyb .cyb-fig{width:150px; max-width:150px; filter:none}
.prcyb .prcybtbl{flex:1 1 220px}
.prsheet .cyb-body path,.prsheet .cyb-body ellipse{fill:#e9edf0; fill-opacity:1; stroke:#5a6472; stroke-opacity:1}
.prsheet .cyb-spine{stroke:#8a94a0; stroke-opacity:1}
.prsheet .cyb-node-o{fill:none} .prsheet .cyb-node-i{fill:#3a4a58}
.prsheet .cyb-node.cs-empty .cyb-node-o{stroke:#9aa0a6} .prsheet .cyb-node.cs-empty .cyb-node-i{fill:#c8cdd2}
.prsheet .cyb-node.cs-occupied .cyb-node-o{stroke:#3a4a58} .prsheet .cyb-node.cs-occupied .cyb-node-i{fill:#2b3946}
.prsheet .cyb-node.cs-offline .cyb-node-o{stroke:#a5342a} .prsheet .cyb-node.cs-offline .cyb-node-i{fill:#a5342a}

/* ============================================================
   MOBILE: global-search dropdown (roadmap "Mobile layout pass")
   #gsResults is an absolutely-positioned panel anchored under the header
   input inside the sticky, non-clipping #hdr. On desktop that's fine, but on
   a phone the fixed 320px height with no viewport bound and no visual float
   covered most of the screen and read as overlapping the scrolled content
   beneath it. Bound it to the viewport (dvh so browser chrome counts) and
   give it a pronounced float shadow over the already-opaque panel bg so it
   reads as a distinct layer. A full-viewport dimming scrim was considered but
   the input lives in the header as a SIBLING of the results, so any scrim at
   the panel's stacking level would also dim the input the user is typing in;
   the bounded, floated panel gives the layering cue without that side effect.
   ============================================================ */
@media (max-width:640px){
  #gsResults{
    max-height:min(62dvh,360px);
    box-shadow:0 16px 34px rgba(0,0,0,.6), 0 2px 8px rgba(0,0,0,.4);
  }
}

/* ============================================================
   MOBILE: PDF viewer chrome (roadmap "Mobile layout pass", M5)
   The touch gestures (pinch-zoom, one-finger pan, double-tap) are wired in
   js/pdflib.js; these are the ergonomics for the toolbar on a phone. The bar
   was one nowrap row that overflow-scrolled sideways on a narrow screen — let
   it wrap so every control is reachable, and grow the small controls to the
   coarse-pointer tap floor. Fixed-dark in-universe surface (like the galaxy
   chrome): no theme tokens introduced here. ============================================================ */
@media (max-width:640px){
  .librow-head{align-items:flex-start}
  .librow-head .iname{flex:1 1 100%}
  .librow-acts{margin-left:auto}
  .librow-ctl{gap:10px}
  .librow-field,.librow-book{flex:1 1 100%}
  .librow-field select{min-width:0;width:100%}
  .libcov-row{flex-wrap:wrap}
  .libcov-b{flex:1 1 100%}
  /* phone: a slim top bar (title · tools · ⋯ · ✕) and the page controls in a
     bottom bar within thumb reach; zoom/Calibrate/Foundry live behind ⋯ */
  .pdfbar{gap:6px;padding:6px 8px}
  .pdfbar-top .pdftitle{flex:1 1 0}
  .pdfbar-top .spacer,.pdfbar-top .pdfnav-top,.pdfbar-top .pdftools{display:none}
  .pdfmore-btn{display:inline-flex}
  .pdfmore{flex-wrap:wrap}
  .pdflbl,.pdfbl{display:none}
  .pdfoutline{position:absolute;left:0;right:0;bottom:0;max-width:none;max-height:65%;z-index:2;
    border-right:none;border-top:1px solid var(--line);box-shadow:0 -8px 24px rgba(0,0,0,.5)}
  .pdffind{padding:6px 8px}
  .pdfbar-bot{display:flex;justify-content:space-between;border-top:1px solid var(--line);border-bottom:none;
    padding-bottom:calc(6px + env(safe-area-inset-bottom,0px))}
  .pdfbar-bot .pdfpg{white-space:nowrap}
  #pdfOverlay .pdfbar-bot input{width:52px}
  .pdfcanvaswrap{padding:8px}
}
@media (pointer:coarse){
  .pdfbar .btn,.pdfbar .btn.sm{min-height:34px}
  .pdfpgin,.pdfbkin{min-height:40px}
}

/* ============================================================
   MOBILE: themed dialog overlay (roadmap "Mobile layout residuals")
   #dlg is `position:fixed;inset:0`, so its width follows the fixed-position
   initial containing block (window.innerWidth). When the layout viewport is
   narrower than that ICB — pinch-zoom, an on-screen keyboard, or a device
   emulator whose innerWidth stays wide while the layout viewport is emulated
   narrow — the centered #dlgBox then centers in the wider ICB and spills past
   the visible right edge (the confirm/prompt/tour dialogs run off-screen).
   Anchor the overlay to the VISUAL viewport with 100dvw instead: on a normal
   phone innerWidth == the layout viewport, so this is identical to inset:0;
   it only diverges in exactly the case that was breaking. Scoped to phones —
   on desktop 100dvw would include the vertical-scrollbar gutter and could add
   a horizontal scrollbar, so the ICB-based inset:0 stays there. Mobile
   scrollbars are overlay (no layout width), so no gutter issue here. ============================================================ */
@media (max-width:640px){
  /* inside the Text Size zoom a vw length scales too — divide it back out
     (css/CLAUDE.md "Viewport fits under Text Size"), or at Large the overlay
     is 112% of the phone and the box spills off the right edge. */
  #dlg{right:auto; width:calc(100dvw / var(--ts-zoom,1))}
}

/* ============================================================
   MOBILE: dashboard guide-hint button row (roadmap "Mobile layout residuals")
   `.dash-guidehint-btns` was `flex:0 0 auto` — it refused to shrink, so its own
   inner flex-wrap never fired and the four buttons (Quick Tour / Full Tour /
   Open the Guide / Dismiss) stayed one ~414px line that overflowed a 375px
   phone. Give it the full row at phone width so the buttons wrap within it. ============================================================ */
@media (max-width:640px){
  .dash-guidehint-btns{flex:1 1 100%; min-width:0}
}

/* ============================================================
   WHAT'S NEW panel (js/whats-new.js) — release notes rendered read-only into
   the shared #modal. Theme tokens only. ============================================================ */
.wn-entry{ padding:10px 0; border-bottom:1px solid var(--line) }
.wn-entry:last-child{ border-bottom:0 }
/* Each release is a <details>; the summary row is the fold toggle. */
.wn-head{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; cursor:pointer; list-style:none; min-height:24px }
.wn-head::-webkit-details-marker{ display:none }
.wn-head:focus-visible{ outline:1px solid var(--holo); outline-offset:2px }
.wn-chev{ display:inline-block; width:10px; font-size:10px; color:var(--faint); transition:transform .15s ease }
.wn-entry[open] .wn-chev{ transform:rotate(90deg) }
.wn-ver{ font-family:var(--mono); font-size:13px; font-weight:600; color:var(--holo) }
.wn-date{ font-size:11px; color:var(--faint); letter-spacing:.04em }
/* The title takes its own line under version · date, indented past the chevron. */
.wn-title{ flex:1 0 100%; padding-left:18px; font-size:14px; font-weight:600; color:var(--ink) }
.wn-entry:not([open]) .wn-title{ font-weight:500; color:var(--dim) }
.wn-entry .wn-items{ padding-left:38px }
.wn-items{ margin:6px 0 0; padding-left:20px }
.wn-items li{ margin:4px 0; font-size:13px; color:var(--ink); line-height:1.45 }

/* ============================================================
   CAMPAIGN CLOCK (js/campaign.js partyClockSection + GM Screen panel) — an
   optional in-universe date strip pinned above the roster rail beside Group
   Vitals. Theme tokens only. ============================================================ */
.pty-clock{ display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin:8px 0 2px; padding:8px 12px; border:1px solid var(--line);
  border-radius:8px; background:var(--panel-b) }
.pty-clock-off{ color:var(--dim) }
.pty-clock-lbl{ font-size:12px; letter-spacing:.06em; color:var(--holo); white-space:nowrap }
.pty-clock .stepper{ display:inline-flex; align-items:center; gap:6px }
.pty-clock-day{ width:64px; text-align:center; font-family:var(--mono); font-size:14px }
.pty-clock-era{ display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--dim) }
.pty-clock-era input{ width:110px }
.pty-clock-stop{ margin-left:auto; color:var(--faint) }
.gm-clock-date{ font-family:var(--mono); font-size:22px; color:var(--holo); letter-spacing:.04em }

/* ============================================================
   CAMPAIGN CHRONICLE — clock strip extras + Session Planner timeline rows
   (js/campaign.js sessTimeline/partyClockSection, GM panel). Theme tokens only. ============================================================ */
.pty-clock-acts{ display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; margin-left:auto }
.pty-clock-due{ font-size:12px; color:var(--amber); white-space:nowrap }
.chron-row{ display:flex; align-items:flex-start; gap:10px; padding:7px 10px; border-bottom:1px solid var(--line) }
.chron-row:last-child{ border-bottom:0 }
.chron-session{ cursor:pointer; border-left:2px solid var(--holo-dim) }
.chron-session:hover{ background:var(--hover) }
.chron-upcoming{ border-left:2px solid var(--amber); background:var(--amber-wash) }
.chron-day{ flex:0 0 auto; min-width:64px; font-family:var(--mono); font-size:11px; color:var(--faint); letter-spacing:.03em; padding-top:2px }
.chron-glyph{ flex:0 0 auto; color:var(--holo); width:16px; text-align:center }
.chron-body{ flex:1 1 auto; min-width:0; font-size:13px; color:var(--ink) }
.chron-note{ font-size:12px; color:var(--dim); margin-top:2px }
.chron-x{ flex:0 0 auto; color:var(--faint) }
.gm-clock-due{ margin-top:4px; font-size:12px; color:var(--amber) }
.gm-clock-recent{ margin-top:8px; font-size:12px; color:var(--dim); display:flex; flex-direction:column; gap:3px }
.gm-clock-recent .chron-day{ min-width:48px; display:inline-block }

/* ============================================================
   GALAXY SLIDERS (D1, UI review 2026-09-15) — the last un-drawn control.
   #galBackdropOpacity / #galRegionWash were native <input type=range> (accent-
   tinted OS thumb), off-language in the machined atlas chrome. Draw a squared
   track + thumb from the fixed-dark --gal-* family (like the rest of the Atlas,
   which stays dark in every theme). No new hex — reuse the galaxy tokens.
   Both -webkit- and -moz- pseudo peers, because they cannot be combined in one
   selector list (an unknown pseudo drops the whole rule). ============================================================ */
.gal-backdrop-control input[type=range],
.gal-region-control input[type=range]{
  -webkit-appearance:none; appearance:none;
  width:100%; height:20px; margin:0; background:transparent; cursor:pointer;
}
.gal-backdrop-control input[type=range]:focus-visible,
.gal-region-control input[type=range]:focus-visible{ outline:2px solid var(--gal-accent); outline-offset:2px }
/* WebKit */
.gal-backdrop-control input[type=range]::-webkit-slider-runnable-track,
.gal-region-control input[type=range]::-webkit-slider-runnable-track{
  height:4px; background:var(--gal-line-bright); border:1px solid var(--gal-line);
}
.gal-backdrop-control input[type=range]::-webkit-slider-thumb,
.gal-region-control input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none;
  width:12px; height:14px; margin-top:-6px; border-radius:0;
  background:var(--gal-accent); border:1px solid var(--gal-accent-deep);
  box-shadow:0 0 4px rgba(63,201,239,.5);
}
.gal-backdrop-control input[type=range]:hover::-webkit-slider-thumb,
.gal-region-control input[type=range]:hover::-webkit-slider-thumb{ background:var(--gal-accent-dim) }
/* Firefox */
.gal-backdrop-control input[type=range]::-moz-range-track,
.gal-region-control input[type=range]::-moz-range-track{
  height:4px; background:var(--gal-line-bright); border:1px solid var(--gal-line);
}
.gal-backdrop-control input[type=range]::-moz-range-progress,
.gal-region-control input[type=range]::-moz-range-progress{ height:4px; background:var(--gal-accent-deep) }
.gal-backdrop-control input[type=range]::-moz-range-thumb,
.gal-region-control input[type=range]::-moz-range-thumb{
  width:12px; height:14px; border-radius:0;
  background:var(--gal-accent); border:1px solid var(--gal-accent-deep);
}

/* ==================== PARTY SYNC (sync-view.js) ==================== */
/* The QR plate is the one deliberately theme-proof surface here: a scanner
   needs black-on-white whatever the theme, so these two tokens are defined
   once and never overridden in themes.css (css/CLAUDE.md fixed-color list). */
:root{--qr-bg:#ffffff; --qr-ink:#000000}
.sync-qr{display:block; width:min(240px,100%); height:auto; background:var(--qr-bg); padding:0; border:1px solid var(--line)}
.sync-qr-wait{padding:24px; text-align:center; border:1px dashed var(--line)}
.smallnote.warn{color:var(--amber)}
.sync-device{margin:10px 0}
.sync-device input{width:min(320px,100%)}
.sync-invite{display:flex; gap:calc(16px*var(--dsp)); align-items:flex-start; flex-wrap:wrap; margin-top:10px}
.sync-invite-qr{flex:0 0 auto}
.sync-invite-acts{flex:1 1 240px; min-width:0}
.sync-send{display:flex; gap:calc(16px*var(--dsp)); align-items:flex-start; flex-wrap:wrap; margin-top:12px}
.sync-send-qr{flex:0 0 auto; max-width:100%}
.sync-send-acts{flex:1 1 220px; min-width:0; display:flex; flex-direction:column; gap:6px; align-items:flex-start}
.sync-row{display:flex; justify-content:space-between; gap:10px; align-items:center; flex-wrap:wrap;
  padding:8px 10px; border:1px solid var(--line); border-left:3px solid var(--holo-dim); background:var(--panel2); margin-top:6px}
.sync-row-acts{display:flex; gap:6px; flex-wrap:wrap; align-items:center; margin-left:auto}
.sync-inbox{margin-top:12px}
.sync-facts{display:flex; flex-direction:column; gap:4px; margin:10px 0; padding:10px; border:1px solid var(--line); border-radius:8px}
.sync-fact{display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap}
.sync-notes{margin:6px 0 0 18px; padding:0}
.sync-groups{display:flex; flex-direction:column; gap:6px; margin:10px 0; max-height:55vh; overflow:auto}
.sync-group{display:flex; gap:10px; align-items:flex-start; padding:8px 10px; border:1px solid var(--line); border-left:3px solid var(--line); background:var(--panel2); cursor:pointer}
.sync-group.on{border-left-color:var(--holo)}
.sync-group input[type=checkbox]{margin-top:3px; accent-color:var(--holo); flex:0 0 auto}
.sync-group-body{flex:1; min-width:0; font-size:12px}
.sync-group-title{display:block; font-weight:600; letter-spacing:.04em; margin-bottom:2px}
.sync-wait{border-left-color:var(--amber)}
.sync-own{margin:10px 0}
.sync-recv{margin-top:12px}
.sync-paste{display:flex; gap:6px; margin-top:8px; max-width:520px}
.sync-paste input{flex:1 1 auto; min-width:0}
details.sync-fold{margin-top:12px; border-top:1px solid var(--line); padding-top:8px}
details.sync-fold>summary{cursor:pointer; font-size:12px; letter-spacing:.04em; color:var(--dim)}
details.sync-fold>summary:hover{color:var(--acc)}
details.sync-fold[open]>summary{margin-bottom:6px}
.sync-h{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.sync-gm .sync-row .reflink b{color:inherit}
@media (max-width:640px){
  .sync-invite,.sync-send{flex-direction:column}
  .sync-qr{width:min(200px,100%); margin:0 auto}
  .sync-row-acts{margin-left:0; width:100%}
}

/* ---- Battle Maps (docs/battle-maps.md) — appended per css/CLAUDE.md ---- */
.bm-toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:10px 0}
.bm-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:calc(12px*var(--dsp))}
.bm-card{position:relative;display:flex;flex-direction:column;border:1px solid var(--line);background:var(--panel2)}
.bm-card:hover,.bm-card:focus-within{border-color:var(--holo)}
/* the thumbnail IS the open button (a role=button card nested real buttons) */
.bm-card-open{display:block;width:100%;padding:0;margin:0;border:0;background:none;cursor:pointer;outline:none}
.bm-card-open:focus-visible{box-shadow:0 0 0 2px var(--focus-ring);position:relative;z-index:1}
.bm-card-rename{width:100%;margin:0;box-sizing:border-box}
.bm-lib-find{display:flex;gap:6px;align-items:center;margin-left:auto;flex-wrap:wrap}.bm-lib-find select{margin:0}.bm-lib-find input{margin:0;min-width:160px}
.bm-thumb{display:block;width:100%;aspect-ratio:16/10;object-fit:cover;background:var(--input-bg)}
.bm-thumb-wait,.bm-thumb-missing{display:flex;align-items:center;justify-content:center;color:var(--faint);font:700 10px var(--disp);letter-spacing:.12em}
.bm-thumb-missing{background:repeating-linear-gradient(45deg,var(--input-bg) 0 8px,var(--panel) 8px 16px)}
.bm-card-body{display:grid;gap:3px;padding:8px 10px;min-width:0}.bm-card-body b{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bm-card-acts{display:flex;gap:4px;justify-content:flex-end;padding:0 8px 8px;margin-top:auto}
.bm-head{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:10px}
.bm-name{flex:1;min-width:160px;margin:0;font:700 14px var(--disp);letter-spacing:.06em}
.bm-head-acts{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-left:auto}
.bm-zoom{display:inline-flex;gap:3px}.bm-zoom button{height:30px;min-width:30px;border:1px solid var(--line);background:var(--btn-bg);color:var(--ink);border-radius:3px;cursor:pointer}.bm-zoom button:hover{background:var(--btn-hover)}
.bm-work{display:grid;grid-template-columns:300px minmax(0,1fr);gap:calc(12px*var(--dsp));align-items:start}
.bm-rail{display:grid;gap:10px;max-height:calc(100vh/var(--ts-zoom,1) - 210px);overflow:auto;min-width:0}
.bm-rail section{border:1px solid var(--line);background:var(--panel2);padding:10px;min-width:0}
.bm-rail h3{margin:0 0 8px;font:700 10px var(--disp);letter-spacing:.16em;text-transform:uppercase;color:var(--holo)}
.bm-rail textarea{width:100%;margin:8px 0 0;box-sizing:border-box}
.bm-row{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:5px 0;font-size:11px;color:var(--dim)}
.bm-row input[type=number]{width:78px;margin:0}.bm-row select{margin:0;max-width:60%}
.bm-nudge{display:flex;gap:3px;align-items:center;flex-wrap:wrap;justify-content:flex-end}
.bm-nudge button{min-width:28px;height:26px;padding:0 4px;border:1px solid var(--line);background:var(--btn-bg);color:var(--ink);border-radius:3px;font-size:10px;cursor:pointer}.bm-nudge button:hover{background:var(--btn-hover)}
.bm-nudge input[type=number]{width:64px;margin:0}.bm-nudge input[type=color]{width:34px;height:26px;padding:0;margin:0;border:1px solid var(--line);background:transparent}
.bm-nudge input[type=range]{width:90px;margin:0}
.bm-check{display:flex;gap:8px;align-items:center;margin:6px 0;font-size:11px;color:var(--dim)}
.bm-rail>section>input[type=range]{width:100%;margin:2px 0 6px}
.bm-stage{position:relative;min-width:0;height:calc(100vh/var(--ts-zoom,1) - 210px);height:calc(100dvh/var(--ts-zoom,1) - 210px);min-height:360px;overflow:hidden;background:var(--input-bg);border:1px solid var(--line);touch-action:none;user-select:none;-webkit-user-select:none}
.bm-svg{display:block;width:100%;height:100%;outline:none}.bm-svg:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.bm-img,.bm-gridrect,.bm-edge,.bm-bands,.bm-measure-g{pointer-events:none}
.bm-edge{stroke:var(--holo);stroke-opacity:.5;stroke-width:1;vector-effect:non-scaling-stroke}
.bm-img-missing{fill:var(--panel);stroke:var(--line);vector-effect:non-scaling-stroke}.bm-img-missing-text{fill:var(--faint);font-family:var(--disp);font-weight:700;letter-spacing:.2em}
.bm-screen{transform:scale(var(--bm-screen-scale,1));transform-origin:0 0}
.bm-label{fill:var(--ink);font:700 11px var(--label);paint-order:stroke;stroke:var(--void);stroke-width:3px;stroke-linejoin:round;pointer-events:none}
/* Band rings: theme-token strokes over a FIXED white casing so they read on any art (CLAUDE.md exceptions). */
.bm-band{fill:none;stroke-width:1.75;stroke-opacity:.9;vector-effect:non-scaling-stroke}
.bm-band-casing{fill:none;stroke:#fff;stroke-opacity:.4;stroke-width:4;vector-effect:non-scaling-stroke}
.bm-band-engaged,.bm-band-close{stroke:var(--red)}.bm-band-short{stroke:var(--amber)}.bm-band-medium{stroke:var(--green)}.bm-band-long{stroke:var(--holo)}
/* band ZONES: each band an annulus tinted with its token (owner: tinted zones + chips) */
.bm-zone{stroke:none;pointer-events:none}
.bm-zone-engaged,.bm-zone-close{fill:var(--red);fill-opacity:.2}.bm-zone-short{fill:var(--amber);fill-opacity:.14}.bm-zone-medium{fill:var(--green);fill-opacity:.1}.bm-zone-long{fill:var(--holo);fill-opacity:.07}
.bm-bands-lite .bm-zone{fill-opacity:.05}.bm-bands-lite .bm-band{stroke-dasharray:5 4}
.bm-label.bm-band-label-engaged,.bm-label.bm-band-label-close{fill:var(--red)}.bm-label.bm-band-label-short{fill:var(--amber)}.bm-label.bm-band-label-medium{fill:var(--green)}.bm-label.bm-band-label-long{fill:var(--holo)}
.bm-label[class*="bm-band-label"]{font-size:10px;letter-spacing:.12em;text-transform:uppercase}
/* the band chip over every other token, from the selected one */
.bm-bchip{pointer-events:none}.bm-bchip rect{fill:var(--void);fill-opacity:.85;stroke-width:1.25;vector-effect:non-scaling-stroke}
.bm-bchip text{font:700 8.5px var(--label);letter-spacing:.1em}
.bm-bchip-engaged rect,.bm-bchip-close rect{stroke:var(--red)}.bm-bchip-engaged text,.bm-bchip-close text{fill:var(--red)}
.bm-bchip-short rect{stroke:var(--amber)}.bm-bchip-short text{fill:var(--amber)}
.bm-bchip-medium rect{stroke:var(--green)}.bm-bchip-medium text{fill:var(--green)}
.bm-bchip-long rect{stroke:var(--holo)}.bm-bchip-long text{fill:var(--holo)}
.bm-bchip-extreme rect{stroke:var(--dim)}.bm-bchip-extreme text{fill:var(--dim)}
.bm-measure{stroke:var(--amber);stroke-width:2;vector-effect:non-scaling-stroke}
.bm-measure-pt{fill:var(--amber);stroke:#fff;stroke-width:1.5;vector-effect:non-scaling-stroke}
.bm-measure-label{fill:var(--amber)}
.bm-align-pt{fill:none;stroke:var(--holo);stroke-width:2;vector-effect:non-scaling-stroke}
.bm-tool-measure .bm-svg,.bm-tool-align .bm-svg{cursor:crosshair}
.bm-hint{position:absolute;left:10px;bottom:10px;z-index:2;max-width:calc(100% - 20px);padding:4px 8px;border:1px solid var(--line);background:var(--panel);color:var(--dim);font-size:10px;pointer-events:none}
/* Label density: at fit/far zoom only PCs, the selection and a hovered or
   focused token show a name; health waits for mid zoom unless selected. */
.bm-zoom-fit .bm-tok-name,.bm-zoom-fit .bm-tok-health,.bm-zoom-far .bm-tok-name,.bm-zoom-far .bm-tok-health,.bm-zoom-far .bm-tok-badge,.bm-zoom-far .bm-bar,.bm-zoom-far .bm-bar-bg{display:none}
.bm-zoom-fit .bm-tok-pc .bm-tok-name,.bm-svg .bm-sel .bm-tok-name,.bm-svg .bm-token:hover .bm-tok-name,.bm-svg .bm-token:focus-visible .bm-tok-name,
.bm-svg .bm-sel .bm-tok-health,.bm-svg .bm-token:hover .bm-tok-health,.bm-svg .bm-sel .bm-bar,.bm-svg .bm-sel .bm-bar-bg,.bm-svg .bm-token:hover .bm-bar,.bm-svg .bm-token:hover .bm-bar-bg{display:inline}
.bm-table .bm-zoom-fit .bm-tok-name{display:inline}
.bm-quality{display:grid;grid-template-columns:auto 1fr;gap:8px 12px;align-items:center;margin:10px 0}
.bm-quality .btn{white-space:nowrap}
.bm-busy{color:var(--amber);margin:6px 0}
/* Breakpoints (pass 3): 900px stacks the library editor and the Map tab's
   stage full width; 1100px docks the encounter inspector under the stage.
   Two thresholds on purpose — the inspector needs ~300px beside the map, the
   editor's rail less. */
@media(max-width:900px){.bm-work{grid-template-columns:1fr}.bm-rail{max-height:none;order:2}.bm-stage{height:60vh;height:60dvh;min-height:300px;order:1}.bm-quality{grid-template-columns:1fr}}

/* ---- Battle Maps P2: the encounter Map tab + tokens ---- */
.enc-runtabs{display:flex;flex-wrap:wrap;gap:4px;margin:10px 0 6px}
.enc-runtabs button{border:1px solid var(--line);background:var(--btn-bg);color:var(--dim);padding:6px 14px;border-radius:3px;cursor:pointer;font:700 10px var(--disp);letter-spacing:.12em;text-transform:uppercase}
.enc-runtabs button[aria-selected="true"]{border-color:var(--holo);color:var(--holo);background:var(--accent-wash)}
.bm-enc{min-width:0}
.bm-enc-bar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:0 0 8px}
.bm-encpick{margin:0;max-width:240px}
.bm-enc .bm-stage{height:calc(100vh/var(--ts-zoom,1) - 330px);height:calc(100dvh/var(--ts-zoom,1) - 330px);min-height:340px}
.bm-tray{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:0 0 8px;padding:6px 8px;border:1px dashed var(--line);background:var(--panel2)}
.bm-chip::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:6px;background:var(--red)}
.bm-chip-pc::before{background:var(--holo)}.bm-chip-ally::before{background:var(--green)}.bm-chip-veh::before{background:var(--amber)}
.bm-tool-place .bm-svg{cursor:copy}
.bm-token{cursor:grab;outline:none}.bm-token:active{cursor:grabbing}
.bm-token:focus-visible .bm-tok-ring,.bm-token:focus-visible .bm-mark{stroke:var(--focus-ring)}
.bm-tok-fill{fill:var(--panel)}
.bm-tok-ring{fill:none;stroke:var(--red)}
.bm-tok-pc .bm-tok-ring{stroke:var(--holo)}.bm-tok-ally .bm-tok-ring{stroke:var(--green)}.bm-tok-veh .bm-tok-ring{stroke:var(--amber)}
/* the selection glow and ring stay white over any art, like the band casing (CLAUDE.md exceptions) */
.bm-sel .bm-tok-ring{filter:drop-shadow(0 0 6px #fff)}
.bm-sel .bm-tok-ring,.bm-sel .bm-mark{stroke-dasharray:none;stroke:#fff}
.bm-tok-initial{fill:var(--ink);font-family:var(--disp);font-weight:700}
.bm-tok-x{stroke:var(--red);stroke-linecap:round}
.bm-down .bm-tok-img{filter:grayscale(1);opacity:.6}
.bm-hidden{opacity:.5}.bm-hidden .bm-tok-ring{stroke-dasharray:6 5}
.bm-mark{stroke:#fff;stroke-width:2;vector-effect:non-scaling-stroke;fill:var(--amber)}
.bm-mark-red{fill:var(--red)}.bm-mark-green{fill:var(--green)}.bm-mark-holo{fill:var(--holo)}
.bm-tok-name{fill:var(--ink)}.bm-tok-health.bm-tok-over{fill:var(--red)}.bm-tok-health{fill:var(--ink);font-size:10px}.bm-tok-badge{fill:var(--amber);font-size:10px}
.bm-bar-bg{fill:var(--void);opacity:.75}.bm-bar{fill:var(--green)}.bm-bar.mid{fill:var(--amber)}.bm-bar.low{fill:var(--red)}
@media(max-width:900px){.bm-enc .bm-stage{height:60vh;height:60dvh;min-height:300px}}

/* ---- Battle Maps P3: fog of war ---- */
.bm-fogbar{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:0 0 8px;padding:6px 8px;border:1px solid var(--line);background:var(--panel2)}
.bm-fog{pointer-events:none}
.bm-tool-fog .bm-svg{cursor:crosshair}
.bm-brush{fill:var(--holo);fill-opacity:.18;stroke:var(--holo);stroke-width:1.5;stroke-dasharray:5 4;vector-effect:non-scaling-stroke;pointer-events:none}
.bm-brush-hide{fill:var(--red);stroke:var(--red)}

/* ---- Battle Maps P4: the table screen ---- */
html.table-mode body{overflow:hidden}
html.table-mode #topbar,html.table-mode #topnav{display:none}
html.table-mode #content{width:100%;max-width:none;margin:0;padding:0}
.bm-table{position:fixed;inset:0;background:var(--void);z-index:5}
.bm-table .bm-stage{position:absolute;inset:0 0 42px 0;height:auto;min-height:0;border:0;background:var(--void)}
.bm-tablebar{position:absolute;left:0;right:0;bottom:0;height:42px;display:flex;align-items:center;gap:10px;padding:0 12px;border-top:1px solid var(--line);background:var(--panel);color:var(--ink);font:700 11px var(--disp);letter-spacing:.1em}
.bm-tablebar-acts{display:flex;gap:8px;align-items:center;margin-left:auto}
.bm-table-tip{font:400 11px var(--label);letter-spacing:0;color:var(--dim)}
@media(max-width:900px){.bm-table-tip{display:none}}
.bm-table-wait{position:absolute;inset:0 0 42px 0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;text-align:center;padding:24px;color:var(--dim)}
.bm-table .bm-token{cursor:default}.bm-table .bm-tok-pc{cursor:grab}

.bm-vehbands{margin-top:8px}.bm-vehbands summary{cursor:pointer;font-size:11px;color:var(--dim)}
/* ---- Battle Maps v2: the docked inspector (right panel ≥1100px, bottom sheet below) ---- */
.bm-enc-work{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:calc(10px*var(--dsp));align-items:stretch;position:relative}
.bm-insp{min-width:0;height:calc(100vh/var(--ts-zoom,1) - 330px);height:calc(100dvh/var(--ts-zoom,1) - 330px);min-height:340px;overflow:auto;border:1px solid var(--line);background:var(--panel2);padding:10px 12px;box-sizing:border-box}
.bm-insp-head{display:flex;align-items:center;gap:6px;margin-bottom:4px}
.bm-insp-head b{flex:1;min-width:0;font:700 12px var(--disp);letter-spacing:.08em;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;outline:none}
.bm-insp-x,.bm-insp-min{width:26px;height:26px;border:1px solid var(--line);background:var(--btn-bg);color:var(--dim);border-radius:3px;cursor:pointer;flex:none}
.bm-insp-x:hover,.bm-insp-min:hover{background:var(--btn-hover);color:var(--ink)}
.bm-insp-min{display:none}
.bm-insp-badges{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
.bm-insp-img{display:block;width:88px;height:88px;object-fit:cover;border-radius:50%;border:2px solid var(--line);margin:4px 0 8px}
.bm-insp-sec{border-top:1px solid var(--line);padding:8px 0;margin:0}
.bm-insp-sec>summary{cursor:pointer;font:700 10px var(--disp);letter-spacing:.14em;text-transform:uppercase;color:var(--holo)}
.bm-insp-h{font:700 10px var(--disp);letter-spacing:.14em;text-transform:uppercase;color:var(--holo);margin-bottom:6px}
.bm-insp-acts{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}
.bm-insp .bm-row{margin:6px 0}
.bm-insp .bm-vitals{display:grid;gap:6px;margin:0}
.bm-vital{display:flex;align-items:center;justify-content:space-between;gap:8px}
.bm-vital-l{font-size:11px;color:var(--dim)}
.bm-vital .stepper{display:inline-flex}
.bm-conds{display:flex;flex-wrap:wrap;gap:4px}
.bm-seg{display:inline-flex;border:1px solid var(--line);border-radius:3px;overflow:hidden}
.bm-seg button{min-width:34px;height:26px;padding:0 7px;border:0;border-left:1px solid var(--line);background:var(--btn-bg);color:var(--dim);font:600 10px var(--label);letter-spacing:.04em;cursor:pointer;white-space:nowrap}
.bm-seg button:first-child{border-left:0}.bm-seg button:hover{background:var(--btn-hover);color:var(--ink)}
.bm-seg button.on{background:var(--holo);color:var(--void)}
.bm-seg button:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.bm-ranges{list-style:none;margin:6px 0 0;padding:0;display:grid;gap:3px}
.bm-ranges li{display:grid;grid-template-columns:minmax(0,1fr) auto 44px;gap:6px;align-items:center;font-size:11px}
.bm-ranges .linkbtn{background:none;border:0;padding:0;color:var(--ink);text-align:left;cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:inherit}
.bm-ranges .linkbtn:hover{color:var(--holo);text-decoration:underline}
.bm-ranges .smallnote{text-align:right}
.bm-bchip-txt{display:inline-block;padding:1px 7px;border:1px solid currentColor;border-radius:9px;font:700 9px var(--label);letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}
.bm-bchip-txt.bm-bchip-engaged,.bm-bchip-txt.bm-bchip-close{color:var(--red)}.bm-bchip-txt.bm-bchip-short{color:var(--amber)}.bm-bchip-txt.bm-bchip-medium{color:var(--green)}.bm-bchip-txt.bm-bchip-long{color:var(--holo)}.bm-bchip-txt.bm-bchip-extreme{color:var(--dim)}
.bm-legend{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;font-size:11px;color:var(--dim);margin-bottom:4px}
.bm-lg{display:inline-block;width:10px;height:10px;border-radius:50%;border:2px solid var(--red);margin-right:5px;vertical-align:-1px}
.bm-lg-pc{border-color:var(--holo)}.bm-lg-ally{border-color:var(--green)}.bm-lg-veh{border-color:var(--amber)}
.bm-keys{list-style:none;margin:0;padding:0;display:grid;gap:4px;font-size:11px;color:var(--dim)}
.bm-keys kbd{display:inline-block;padding:0 4px;border:1px solid var(--line);border-radius:3px;background:var(--input-bg);color:var(--ink);font:600 10px var(--label)}
.bm-bar-grp{display:inline-flex;gap:6px;flex-wrap:wrap}
/* narrower: the inspector docks BELOW the stage (never over the map) */
@media(max-width:1100px){
  .bm-enc-work{grid-template-columns:1fr}
  .bm-insp{height:auto;min-height:0;max-height:none;border-top:2px solid var(--holo);order:2}   /* the 900px rule gives .bm-stage order:1 */
  /* no selection: a collapsed legend + shortcuts strip, never hidden outright */
  .bm-insp-min{display:inline-block}
  .bm-insp-mini{max-height:none;overflow:hidden}.bm-insp-mini>:not(.bm-insp-head){display:none}.bm-insp-mini .bm-insp-min{transform:rotate(180deg)}
}
.bm-insp .stepper .cnt{min-width:44px;font-size:13px}
/* ---- drag feedback: ghost at the origin, snapped footprint, the readout line ---- */
.bm-dragfx{pointer-events:none}
.bm-ghost{opacity:.35}.bm-ghost .bm-screen{display:none}
.bm-foot{fill:var(--holo);fill-opacity:.16;stroke:var(--holo);stroke-width:1.5;stroke-dasharray:4 3;vector-effect:non-scaling-stroke}
.bm-dragline{stroke:var(--ink);stroke-opacity:.85;stroke-width:2;stroke-dasharray:6 4;vector-effect:non-scaling-stroke}
.bm-label.bm-drag-label{font-size:12px}
.bm-brush.bm-box{fill:var(--holo);fill-opacity:.08;stroke:var(--holo)}
.bm-tray-ghost{position:fixed;z-index:400;transform:translate(-50%,-130%);padding:4px 10px;border:1px dashed var(--red);background:var(--panel);color:var(--ink);font:700 11px var(--label);pointer-events:none;white-space:nowrap}
.bm-tray-ghost.ok{border-color:var(--holo);border-style:solid}
.bm-chip{touch-action:none}
/* portrait framing editor (inspector) */
.bm-frame-row{display:flex;gap:10px;align-items:center}
.bm-frame-pv{width:112px;height:112px;flex:none;cursor:grab;touch-action:none;border-radius:50%}
.bm-frame-pv:active{cursor:grabbing}
.bm-frame-ring{fill:none;stroke:var(--holo);stroke-width:2}
.bm-frame-ctl{display:grid;gap:6px;min-width:0}
.bm-frame-ctl input[type=range]{width:100%;margin:2px 0 0}
/* initiative on the map: the turn strip + the acting/eligible token halos */
.bm-turn{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;margin:0 0 8px;padding:5px 8px;border:1px solid var(--line);background:var(--panel2)}
.bm-turn-round{font:700 10px var(--disp);letter-spacing:.16em;color:var(--amber)}
.bm-turn-chips{display:flex;flex-wrap:wrap;gap:4px;flex:1;min-width:0}
.bm-turn-chip{padding:2px 7px;border:1px solid var(--line);border-radius:10px;font:600 10px var(--label);color:var(--dim);white-space:nowrap}
.bm-turn-chip.pc{border-color:var(--holo)}.bm-turn-chip.npc{border-color:var(--red)}
.bm-turn-chip.acted{opacity:.4;text-decoration:line-through}
.bm-turn-chip.next{background:var(--amber);border-color:var(--amber);color:var(--void)}
.bm-turn-acts{display:flex;gap:6px;margin-left:auto}
.bm-turn-ring{fill:none;stroke:var(--amber);pointer-events:none}
.bm-turn-active .bm-turn-ring{animation:bmTurnPulse 1.6s ease-in-out infinite}
.bm-turn-elig .bm-turn-ring{stroke-dasharray:3 3;stroke-opacity:.8}
@keyframes bmTurnPulse{0%,100%{stroke-opacity:1}50%{stroke-opacity:.25}}
@media (prefers-reduced-motion:reduce){.bm-turn-active .bm-turn-ring{animation:none}}
.bm-table-turn{color:var(--amber);letter-spacing:.12em}
/* condition badges on a token + the ping */
.bm-condb rect{fill:var(--amber);stroke:var(--void);stroke-width:1;vector-effect:non-scaling-stroke}
.bm-condb text{font:700 8px var(--label);letter-spacing:.06em;fill:var(--void);pointer-events:none}
.bm-condb-more{font:700 9px var(--label);fill:var(--amber);paint-order:stroke;stroke:var(--void);stroke-width:3px}
.bm-ping{pointer-events:none}
.bm-ping circle{fill:none;stroke:var(--amber);stroke-width:3;vector-effect:non-scaling-stroke;transform-box:fill-box;transform-origin:center;animation:bmPing 1.1s ease-out 2 both}
.bm-ping .bm-ping-dot{fill:var(--amber);stroke:none;animation:none}
.bm-ping-table circle{stroke:var(--holo)}.bm-ping-table .bm-ping-dot{fill:var(--holo)}
@keyframes bmPing{0%{transform:scale(.15);opacity:1}100%{transform:scale(1);opacity:0}}
@media (prefers-reduced-motion:reduce){.bm-ping circle{animation:none;opacity:.8}}
.bm-insp-av{width:36px;height:36px;flex:none;border-radius:50%;border:2px solid var(--line)}
.bm-frame>summary{margin-bottom:6px}

/* Dataset Manager pack blurbs — what a ruleset/pack IS, on its own line under
   the name + version so the row's controls keep their column. */
.ds-blurb{display:block; margin-top:2px; font-weight:400; font-size:11px; color:var(--dim); line-height:1.4}
/* Sources rows: the checkbox label plus an optional ⇢ PDF sibling (a book
   mapped in the PDF Library). The label keeps the whole row's slack. */
.dssrc-row{display:flex; align-items:center; gap:4px; min-width:0}
.dssrc-row .dssrc{flex:1; min-width:0}
.dssrc-pdf{flex:none; padding:1px 6px; font-size:10px}
/* Dataset Manager touch floor, second half: the group collapse chevron (it
   measured 11px tall at 375px) and the Sources ⇢ PDF button. Inline prose
   reflinks are left alone — they sit inside a sentence (WCAG 2.5.8's inline
   exception), and a 44px box would break the line. */
@media(pointer:coarse){
  .dssrc-chev{min-width:44px; min-height:44px}
  .dssrc-pdf{min-height:44px}
}

/* Data Foundry review cards. The head row is the whole card when collapsed —
   collapsing is a PHONE affordance (≤640px), so on desktop the toggle and the
   category label are hidden and every body shows. Tokens only. */
.fcard-head{display:flex; gap:8px; align-items:center; flex-wrap:wrap}
.fcard .fcard-name{flex:1; min-width:120px; font-weight:bold}
.fcard-cat{display:none; font-family:var(--label); font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--dim)}
.fcard-tog{display:none}
/* a duplicate badge that opens the record it collides with */
button.badge.fdup{background:transparent; cursor:pointer; line-height:inherit}
button.badge.fdup:hover,button.badge.fdup:focus-visible{background:var(--amber-bg)}
.fqual{font-size:12px; margin:2px 0 4px; line-height:1.6}
.fdone{display:inline-flex; gap:0}
.fdone .btn+.btn{border-left:0}
/* the sticky Commit bar reuses the Data Editor's .dehud plate */
.fcommit{display:flex; flex-wrap:wrap; gap:6px; align-items:center}
@media(max-width:640px){
  .fcard-cat{display:inline}
  .fcard-tog{display:inline-flex; align-items:center; justify-content:center}
  .fcard .fcard-name{min-width:0; flex-basis:100%; order:2}
  .fcard:not(.open) .fcard-body{display:none}
  .fcommit{padding-bottom:calc(10px + env(safe-area-inset-bottom))}
  .fcommit .btn:last-child{flex:1 0 100%}
  /* one row for the target: the select carries its own aria-label */
  .fcommit>.fl{display:none}
  .fcommit select{flex:1; min-width:0}
}


/* ============================================================
   MOBILE: print preview takeover (Print Center pass, P1)
   `body.prpreview` is not a route — it is a full-page body-class takeover — so
   the 375px route sweep that cleared every other surface never walked it, and
   nothing in this file touched #printDoc / .prpage / #prPrevBar at any width.
   On a phone that meant a hard-pinned 816px document panning sideways under a
   nowrap 44px bar whose last control was the only way out.

   Same treatment the PDF viewer chrome got (see "MOBILE: PDF viewer chrome"
   above): let the bar WRAP so every control is reachable, grow the controls to
   the coarse-pointer tap floor, and introduce no theme tokens — this is a
   fixed-dark in-universe surface.

   The page BOX is deliberately untouched. 756x996 is the @page content area and
   the tree connectors are measured in it; the block a few hundred lines up
   records what broke the last time preview and print geometry disagreed. The
   fit-to-width is a transform on the #printDoc WRAPPER, applied by
   prPreviewFit() in js/print.js once pagination and connector measurement have
   settled, so the measured geometry underneath stays at scale 1.
   ============================================================ */
/* The bar is position:fixed, so the document has to reserve its height. That
   used to be a hardcoded 62px, which is only true while the bar is one row —
   prPreviewBar() now measures itself and publishes --pr-barh, and 44px + 18px
   reproduces the old desktop figure exactly when it cannot. */
body.prpreview #printDoc{margin:calc(var(--pr-barh,44px) + 18px) auto 40px}
@media (max-width:640px){
  /* right:auto + 100dvw for the same reason the themed dialog needs it (see
     "MOBILE: themed dialog overlay" above): a position:fixed box sizes to the
     fixed-position initial containing block, which on an emulated or
     pinch-zoomed viewport is WIDER than the layout viewport — so left:0;right:0
     spills the bar (and its Close button) past the visible right edge. */
  #prPrevBar{height:auto; min-height:44px; flex-wrap:wrap; gap:6px 8px; padding:6px 10px;
    right:auto; width:100dvw}
  /* The label is dropped, not wrapped: on a 375px bar it costs a whole 44px row
     (a fifth of the visible height) to name a document the reader is looking
     at. The ink, theme, Print and Close clusters are what must stay reachable.
     The flex:1 spacer is pointless once the bar wraps. */
  #prPrevBar>b{display:none}
  #prPrevBar>span[style]{display:none}
  #prPrevBar .toolbar{flex:0 1 auto}
  /* Print and Close stay together at the end of the last row, and Close can
     never be the control that wraps out of reach. */
  #prPrevBar>.btn{margin-left:auto}
  #prPrevBar>.btn+.btn{margin-left:0}
  body.prpreview{padding:0}
}
@media (pointer:coarse){
  #prPrevBar .btn,#prPrevBar .btn.sm{min-height:34px}
}

/* ---- Print Center: Play workspace packing + search (Print Center pass, P2) ----
   auto-fit put five panels of wildly different heights into a 4-column grid
   track: the fifth wrapped to row 2, row 1 took the height of the tallest
   panel, and a wide screen showed a large dead region on the right. CSS
   multi-column is the masonry fallback — panels pack by height instead of by
   row. Scoped to `> .pc-panel` because the Reference Catalog's three panels
   share that class and must stay a grid. */
.pc-grid-play{display:block; columns:300px 4; column-gap:calc(12px*var(--dsp))}
.pc-grid-play>.pc-panel{break-inside:avoid; margin:0 0 calc(12px*var(--dsp))}
@media(max-width:920px){
  .pc-grid-play{columns:1}
}
/* The Play search sits above the grid, full width, and is the same .pc-tools
   plate the catalog's own search uses. */
.pc-playtools{max-width:420px}

/* ---- Print Center: catalog row actions (Print Center pass, P3) ----
   The row gains the two ways to CHECK a record before printing it: a Codex
   quick view and, when the citing book is actually in the PDF Library, a jump
   to that page. Both are SIBLINGS of the <label> — .pc-row wraps its checkbox,
   so a control inside it would toggle the selection — which is why the row's
   hairline moves out to the wrapper and the label keeps the grid. */
.pc-rowline{display:flex; align-items:center; gap:calc(4px*var(--dsp));
  border-bottom:1px solid var(--line)}
.pc-rowline .pc-row{flex:1; min-width:0; border-bottom:none}
.pc-rowact{flex:0 0 auto; padding:2px 7px; line-height:1.5; opacity:.72}
.pc-rowact:hover,.pc-rowact:focus-visible{opacity:1; border-color:var(--holo); color:var(--holo)}
.pc-rowact:focus-visible{outline:2px solid var(--holo); outline-offset:1px}
@media(pointer:coarse){
  .pc-rowact{min-height:44px; min-width:38px}
}

/* ---- Print Center: sectioned catalog rail (Print Center pass, P4) ----
   The rail carries ~35 categories now, sectioned by the APP_GROUP_ORDER
   taxonomy (data.js) the Codex and Data Editor rails already present. Headings
   only — the buttons stay .pc-group, so this mints no second nav idiom. */
.pc-railgrp{margin:calc(10px*var(--dsp)) 0 calc(2px*var(--dsp)); font:700 9px var(--disp);
  letter-spacing:.14em; text-transform:uppercase; color:var(--faint)}
.pc-railgrp:first-child{margin-top:0}
.pc-rail{max-height:min(74vh,720px); overflow:auto}

/* ---- Print half: reference cards (Print Center pass, P4) ----
   The `card` strategy — many records to a page. Inside #printDoc, so the fixed
   --pr-* palette applies and the screen theme is deliberately invisible.
   INK CONTRACT: the card wash is a background-COLOR, so Clean's sweep drops it
   and leaves the hairline; break-inside:avoid is what lets prSplitSection's
   measured .prcards splitter move whole cards to a continuation. */
#printDoc .prrefcard{border:1px solid var(--pr-line); background-color:#f4f7fa;
  padding:7px 9px; margin:0 0 6px; break-inside:avoid}
#printDoc .prrefcardh{display:flex; align-items:baseline; gap:8px;
  border-bottom:1px solid var(--pr-line); padding-bottom:3px; margin-bottom:4px}
#printDoc .prrefcardh b{font:700 11.5px var(--pr-disp,inherit); color:var(--pr-accent);
  letter-spacing:.02em; flex:1 1 auto}
#printDoc .prrefcardh .prsource{flex:0 0 auto; margin:0}
#printDoc .prrefstats{width:100%; border-collapse:collapse; font-size:9.5px; margin:0 0 3px}
#printDoc .prrefstats td{padding:1px 4px 1px 0; vertical-align:top; color:var(--pr-ink)!important}
#printDoc .prrefstats td:first-child{width:34%; color:var(--pr-faint)!important;
  text-transform:capitalize}
#printDoc .prrefdesc{font-size:10px; line-height:1.45; color:var(--pr-ink)}

/* ============ CODEX PASS (2026-09) ============
   Appended per the file convention, so source order equals cascade order. */
/* A Codex entry with no stat table (qualities, conditions, rules) drops the
   empty left column instead of reserving half the page for it. */
.statgrid.onecol{grid-template-columns:1fr}
/* Codex species page: the six starting characteristics as labelled cells
   (cxSpeciesChars) rather than the old unlabelled |2|2|2|2|2|2| run, which said
   nothing about which number was which. Wraps to as many rows as the width
   allows, so it survives a phone. */
.spchars{display:flex;flex-wrap:wrap;align-items:baseline;gap:calc(4px*var(--dsp)) calc(10px*var(--dsp))}
.spchar{display:inline-flex;align-items:baseline;gap:4px;white-space:nowrap}
.spchar b{color:var(--ink);font-size:1.15em}
.spchar-sep{color:var(--dim);white-space:nowrap}

/* ===== Destiny Pool / Conflict Roller / Critical Roller pass (2026-09-22) ===== */
/* Destiny Pool "How it works": the .rref card, folding like .rmhelp so the
   pool leads on a phone. The summary takes the .rref h4 face. */
.dhelp>summary{cursor:pointer; list-style:none; font-family:var(--label); font-size:11px; letter-spacing:.18em;
  text-transform:uppercase; color:var(--amber); user-select:none}
.dhelp>summary::-webkit-details-marker{display:none}
.dhelp>summary::before{content:"▸ "; font-size:9px}
.dhelp[open]>summary{margin-bottom:5px}
.dhelp[open]>summary::before{content:"▾ "}
/* the stepper is a block flex box — inline it, or it stretches the whole
   label span into a wide empty frame (seen on the Critical Roller) */
.cimodbar .stepper{display:inline-flex; vertical-align:middle}
.cimodbar .stepper .cnt{width:auto; min-width:34px; padding:0 4px; text-align:center; display:inline-flex; align-items:center; justify-content:center}
/* Conflict Roller header: one wrapping row on a wide screen; on a phone the
   two toggle pairs become full-width segmented rows, the character picker
   takes its own row, and the two secondary buttons share the last one. */
@media(max-width:640px){
  .cfhead .moderow{display:grid; grid-template-columns:1fr 1fr; gap:6px; width:100%}
  .cfhead .cfpick{display:flex; align-items:center; gap:8px; width:100%}
  .cfhead .cfpick select{flex:1; min-width:0 !important}
}

/* ════════ DICE CALCULATOR PASS (2026-09-23) ════════
   Appended after the theming layer per the css/CLAUDE.md convention, so
   these win on source order. Tokens only — the die colours are --die-*. */
.dpal:not(.dpal-syms)>.dpal-btn:nth-child(7){grid-column:2} /* Force centred on its own row */
.dsec-tog.dsec-sep{border-top:1px solid var(--line);margin:14px 0 8px;padding-top:10px}
.dsec-n{color:var(--holo);letter-spacing:.1em;margin-left:4px}
.dbig-lbl{color:var(--faint); font-size:10px; letter-spacing:.2em}
/* "Build from characteristic & skill" disclosure at the top of the palette */
.dsec-tog{display:block;width:100%;text-align:left;background:transparent;border:none;border-top:none;margin:0 0 8px;padding:4px 0;cursor:pointer;color:var(--dim)}
.dsec-tog:hover,.dsec-tog:focus-visible{color:var(--holo)}
.dbld{display:flex;flex-direction:column;gap:8px;padding:8px 10px;margin-bottom:10px;border:1px solid var(--line);background:var(--panel2)}
.dbld-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px}
.dbld-h{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);min-width:78px}
.dbld-f{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--dim)}
.dbld-f select{width:auto;min-width:48px}
.dbld-row>select{flex:1 1 140px;min-width:0;max-width:220px}
.dbld-pv{display:inline-flex;align-items:center;gap:2px;min-width:40px}
.dbld-pv .die{width:12px;height:12px;margin:0 1px}
.dbld-row .btn{margin-left:auto}
/* rule quick-view links (palette footer, presets header, result spend hint) */
.drules{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 6px;margin-top:10px;padding-top:8px;border-top:1px solid var(--line);font-size:11px}
.drules-h{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);margin-right:2px}
.drules-sep{color:var(--faint)}
.drule .cxlink{font-size:inherit;letter-spacing:inherit}
.dspend{margin-top:8px;font-size:11.5px;color:var(--dim)}
/* upgrade / downgrade row under the current pool */
/* each label travels with its own two buttons: groups wrap, never a label
   stranded at a line end with its buttons on the next row */
.dupg{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;margin:8px 0 0}
.dupg-grp{display:inline-flex;align-items:center;gap:6px;flex-wrap:nowrap}
.dupg-lbl{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);min-width:74px}
.dupg-lbl+.btn{margin-left:0}
.dupg .die{width:11px;height:11px;margin:0 1px;vertical-align:-1px}
.dupg .btn[disabled]{opacity:.45}
/* shared roll widget (roll-widget.js rwCheckUI) — the pool column and the
   roll column wrap under each other on a narrow host; the result sits right
   under Roll and manual entry folds below it */
.rwbody{display:flex;gap:24px;flex-wrap:wrap}
.rwbody-mt{margin-top:10px}
.rwpool{min-width:0}
.rwroll{flex:1;min-width:260px}
.rwh{margin-bottom:4px}
.rwrow{gap:6px;margin:2px 0}
.rwrow .die{flex:none}
.rwlbl{width:var(--rw-lw,92px);font-size:12px}
.rwn{width:20px;text-align:center}
.rwrow .btn[disabled]{opacity:.45}
.rwfixed{margin:6px 0 0;display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.rwchar{margin-top:6px}
.rwfaces{margin:8px 0;gap:8px;flex-wrap:wrap}
.rwnet{gap:10px;font-size:13px}
.rwnet-mt{margin-top:6px}
.rwman{margin-top:10px}
.rwman>summary{cursor:pointer;font-size:12px;color:var(--dim)}
.rwman>summary:hover,.rwman>summary:focus-visible{color:var(--holo)}
.rwmannote{margin:8px 0 4px}
.rwmangrid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px 8px;max-width:320px}
.rwmangrid label{display:flex;flex-direction:column;align-items:center;gap:3px;font-size:10.5px;text-align:center}
.rwmangrid input{width:100%;text-align:center;margin-top:auto}
.rwmanact{margin-top:8px;gap:8px;align-items:center}
.rwmanerr{color:var(--amber)}
/* Narrative / Numeric segmented control */
.dmode[role=tablist]{gap:0}
.dseg{background:var(--btn-bg);border:1px solid var(--line);color:var(--dim);font-family:var(--label);font-size:11px;letter-spacing:.14em;text-transform:uppercase;padding:7px 14px;cursor:pointer}
.dseg+.dseg{border-left:none}
.dseg:hover{color:var(--holo)}
.dseg.on{color:var(--holo);border-color:var(--holo-dim);background:var(--accent-wash-hover);box-shadow:inset 0 -2px 0 var(--holo)}
.dseg:focus-visible{outline:2px solid var(--holo);outline-offset:1px}
/* phone quick-roll bar — hidden on desktop, pinned to the bottom ≤900px */
.dmobbar{display:none}
@media(max-width:900px){
  .dupg{flex-direction:column;align-items:flex-start;gap:6px}
  .dmobbar{display:flex;align-items:center;gap:10px;position:sticky;bottom:0;z-index:30;margin:10px 0 0;
    padding:8px 12px calc(8px + env(safe-area-inset-bottom));background:linear-gradient(180deg,var(--panel-a),var(--panel-b)),var(--void);
    border-top:1px solid var(--holo-dim);box-shadow:0 -6px 18px var(--shadow)}
  .dmob-pool{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:center;gap:2px;overflow:hidden;max-height:40px}
  .dmob-pool .die{width:13px;height:13px;margin:0 1px}
  .dmob-empty{color:var(--faint);font-size:11px}
  .dmob-res{font-size:12px;white-space:nowrap;max-width:40%;overflow:hidden;text-overflow:ellipsis}
  .dmob-res .sym{font-size:12px}
}
/* the saved pool the builder was loaded from (Update targets it) */
.dsaved-chip.on{border-color:var(--holo-dim);box-shadow:inset 0 -2px 0 var(--holo)}
@media(pointer:coarse){
  .dkeys{display:none}
}

/* Random Generators — phone tap targets for per-result actions (2026-09 pass) */
@media(max-width:640px){.genrow-actions .iconbtn{min-width:34px;min-height:34px;font-size:14px}.genentry{flex-wrap:wrap}.genrow-actions{margin-left:auto}}

/* Settings improvement pass (2026-09) — appended per the cascade convention. */
/* RAW quick-view anchors trailing a Rules-tab impact line. */
.setimpact .setrefs{display:inline-block; font-size:11px; color:var(--faint); margin-left:4px}
/* A dependent sub-setting (fear awards, Tranquility resolution, darkward flip). */
.setsub{margin:8px 0 0 12px; padding-left:10px; border-left:2px solid var(--line)}
/* Table House Rules ▸ "Also switched on inside the tool". */
.set-intool{margin-top:14px; border-top:1px solid var(--line); padding-top:8px}
.set-intool .set-intool-h{font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--faint); margin-bottom:2px}
.set-intool .badge{margin-left:4px}
/* A button that carries its own glyph (☀ ☾ 🖥) drops the pressed ✓, which read as "✓ ☾ DARK". */
.moderow.iconic .btn[aria-pressed="true"]::before{content:none}
/* Theme swatches: a line-token ring keeps a near-black swatch visible on a dark panel. */
.themepick .tpsw i{box-shadow:0 0 0 1px var(--line)}
@media(max-width:520px){
  /* The modules badge drops under its title instead of wrapping beside it. */
  .ptitle .set-modbadge{display:table; margin:6px 0 0}
  /* Two theme cards per row, descriptions clamped, so five themes are not five screens. */
  .setgrid{grid-template-columns:1fr 1fr}
  .themepick{padding:10px}
  .themepick .tpdesc{display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden}
  .themepick.on::after{content:"\2713"; font-size:11px}
}

/* Logo emblem keeps its Medium size at Large text (owner ruling 2026-09-23).
   The mark is one 192px raster squeezed into 20px; at the 1.12 zoom the
   browser resamples it differently and the emblem reads as a different shape.
   Counter-zoom it back to exactly 20px — 1/1.12, kept beside --ts-zoom. */
:root[data-textsize="large"] .logomark{zoom:0.892857}

/* Campaign Manager card: party chips, the tool launchpad, then the management row */
.camp-party{display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin-bottom:calc(10px*var(--dsp))}
.camp-tools{display:flex; flex-wrap:wrap; gap:calc(8px*var(--dsp)); margin-bottom:calc(10px*var(--dsp))}
.camp-manage{margin-bottom:0; padding-top:calc(10px*var(--dsp)); border-top:1px solid var(--line)}
.camp-manage .spacer{flex:1}
@media(max-width:600px){
  .camp-tools{display:grid; grid-template-columns:1fr 1fr}
  .camp-tools .btn{justify-content:center; text-align:center}
}

/* ---- Holocron pass 2 (2026-09) ----
   Inert link chips (record editors: label + ✕ only, no navigation), the
   editor's content preview/hint, and the source-citation input pair. */
.lkchip.inert{cursor:default}
.lkchip.inert:hover{background:var(--accent-wash)}
.edpvbtn{margin-left:6px; vertical-align:middle}
.edpv{min-height:120px; max-height:340px; font-size:13px; line-height:1.6; padding:8px 10px; border:1px solid var(--line); border-radius:4px; background:var(--input-bg)}
.edhint{margin-top:4px}
.edcite{display:flex; flex-wrap:wrap; gap:6px}
.edcite input:first-child{flex:1 1 220px; min-width:0}
.edcite input:last-of-type{flex:0 0 90px}

/* ---- Initiative slot GRID (2026-10-03: compact rows + Details fold) ----
   A slot row shows only what the table needs mid-fight, on one shared set of
   tracks so every control sits in the same column whatever the name's length:
     ⠿ · # · ✓ · who · result + Roll · ⋯
   Everything variable (type badge, name, DOWN/LAST, the notes line) lives in
   the `who` cell. The rest — result inputs, Check skill, Taken by, ▲▼, ⤓ Last,
   ✕ — is in the per-slot fold (S.initOpen) that ⋯ opens, full width under the
   row. Tiers key off the LIST's width (a container query): the same rows render
   in the full-width tracker and in the narrower encounter-run panel. Scoped to
   .initlist .initslot, so the shared .initrow idiom elsewhere is untouched; it
   supersedes the older flex/order rules for these rows. */
.initlist{container-type:inline-size}
.initlist .initslot{display:grid; column-gap:8px; row-gap:6px; align-items:center;
  grid-template-columns:16px 22px 22px minmax(0,1fr) max-content 34px;
  grid-template-areas:
    "grip ord chk who act more"
    "fold fold fold fold fold fold"}
.initlist .initslot>*{min-width:0}
.initlist .initslot .initgrip{grid-area:grip; cursor:grab; touch-action:none; user-select:none; -webkit-user-select:none;
  color:var(--faint); font-size:16px; line-height:1; text-align:center}
.initlist .initslot .initgrip:hover{color:var(--holo)}
.initlist .initslot .initord{grid-area:ord; text-align:right}
.initlist .initslot>.initchk{grid-area:chk; margin:0; justify-self:center}
.initlist .initslot .initwho{grid-area:who}
.initlist .initslot .initact{grid-area:act; display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:4px 10px}
.initlist .initslot .initsum{display:inline-flex; align-items:center; gap:3px; font-size:13px; white-space:nowrap; min-width:72px; justify-content:flex-end}
.initlist .initslot .initroll{display:flex; flex-wrap:wrap; align-items:center; gap:4px 6px}
.initlist .initslot .initmore{grid-area:more; justify-self:end; min-width:30px; justify-content:center}
.initlist .initslot.open .initmore{color:var(--holo); border-color:var(--holo)}
/* the fold: labelled controls that wrap, on the row's full width */
.initlist .initslot .initfold{grid-area:fold; display:flex; flex-wrap:wrap; align-items:center; gap:8px 16px;
  padding:8px 0 2px; border-top:1px dashed var(--line)}
.initlist .initslot .initfld{display:inline-flex; align-items:center; gap:6px}
.initlist .initslot .initfld .fchip{margin:0}
.initlist .initslot .initfacts{margin-left:auto; gap:6px}
.initlist .initslot .initclaim select{max-width:220px}
.initlist .initslot .initskill{max-width:none}
/* the who cell: badge (fixed width, so every name starts at one x) + name */
.initlist .initslot .iname{display:flex; align-items:center; gap:8px; flex:initial; min-width:0}
.initlist .initslot .inittype{flex:0 0 76px; box-sizing:border-box; text-align:center; margin:0}
.initlist .initslot .initnm{min-width:0; overflow-wrap:break-word}
.initlist .initslot .initopen{color:var(--faint); font-weight:400}
/* notes line sits under the NAME (badge 76px + gap 8px), never under the badge */
.initlist .initslot .initnotes{display:flex; flex-wrap:wrap; align-items:center; gap:4px 12px;
  margin:4px 0 0 84px; font-size:11px; font-weight:400; color:var(--dim)}
.initlist .initslot .initpool{display:inline-flex; align-items:center; gap:1px; font-size:12px; white-space:nowrap}
.initlist .initslot .initlast[aria-pressed="true"]{color:var(--amber); border-color:var(--amber)}
.inithead{display:none}
.initskillset{align-items:center; gap:6px; flex-wrap:wrap}
/* the Options panel (S.initOptsOpen), the manual-order banner and the
   Who-acts strip of draggable name chips */
.initopts{margin:6px 0; padding:6px 10px; border:1px solid var(--line); background:var(--panel2)}
.initmanualbar{display:flex; flex-wrap:wrap; align-items:center; gap:6px 12px; margin:6px 0; padding:5px 10px;
  border:1px solid var(--line); border-left:3px solid var(--amber); background:var(--accent-wash); font-size:12px}
.initwhostrip{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin:8px 0}
.initchip{touch-action:none; cursor:grab; user-select:none; -webkit-user-select:none; font-size:12px; padding:3px 9px;
  border:1px solid var(--line); border-left:3px solid var(--holo-dim); background:var(--panel2); color:var(--ink)}
.initchip.npc{border-left-color:var(--red)}
.initchip.used{color:var(--dim)}
.initchip small{color:var(--faint)}
.initchip:hover{border-color:var(--holo)}
.inittimer-mini{display:inline-flex; align-items:center; gap:6px}
.inittimer-mini .inittimer-clock{font-size:16px; min-width:0}
/* drag feedback: the dragged row fades, the drop place shows as an amber line
   (reorder) or a dashed outline (a name landing on a slot) */
.initlist .initslot.initdrag{opacity:.4}
.initlist .initslot.dropbefore{box-shadow:inset 0 3px 0 var(--amber)}
.initlist .initslot.dropafter{box-shadow:inset 0 -3px 0 var(--amber)}
.initlist .initslot.dropclaim{outline:2px dashed var(--holo); outline-offset:-2px}
.initghost{position:fixed; z-index:300; pointer-events:none; padding:4px 10px; font-size:12px; white-space:nowrap;
  background:var(--panel); color:var(--ink); border:1px solid var(--holo); box-shadow:0 4px 18px var(--shadow)}
body.initdragging, body.initdragging *{cursor:grabbing !important; user-select:none}
@media(pointer:coarse){
  .initlist .initslot .initgrip{font-size:20px; padding:6px 0}
  .initchip{padding:7px 11px}
}
@container (max-width:600px){
  /* PHONE: name line on top, the result + Roll under it at the card's width */
  .initlist .initslot{grid-template-columns:16px 22px 22px minmax(0,1fr) 34px;
    grid-template-areas:
      "grip ord chk who  more"
      ".    .   act act  act"
      "fold fold fold fold fold"}
  .initlist .initslot .initact{justify-content:flex-start}
  .initlist .initslot .initsum{justify-content:flex-start}
  .initlist .initslot .iname{flex-wrap:wrap; row-gap:2px}
  .initlist .initslot .inittype{flex:0 0 auto}
  .initlist .initslot .initnm{flex:1 1 120px}
  .initlist .initslot .initnotes{margin-left:0}
  .initlist .initslot .initfacts{margin-left:0}
  .initlist .initslot .initclaim{flex:1 1 100%}
  .initlist .initslot .initclaim select{max-width:none; flex:1}
}

/* ======= ENCOUNTERS PASS 4 — lingering effects ======= */
/* lingering effects (pass 4): chips read as "in play", accent-edged; the
   picker lays out as button | hint | rule link */
.lkchip.encfx{border-color:var(--holo);cursor:default}
.encfxpick{display:grid;grid-template-columns:minmax(170px,max-content) 1fr auto;gap:4px 10px;align-items:center;margin:3px 0}
.encfxpick .btn{justify-content:flex-start;white-space:nowrap}
@media(max-width:640px){
  .encfxpick{grid-template-columns:minmax(0,1fr) auto}
  .encfxpick>.smallnote{grid-column:1 / -1;order:3;margin-top:-2px}
}
/* pass 4 review: the header is a WRAPPING row — the title keeps its line and the
   action buttons flow beside it (or under it) instead of squeezing over it; the
   round badge never splits "ROUND" from its number */
.enchead{flex-wrap:wrap;row-gap:6px}
.enchead::after{display:none}
.enchead .enctitle{flex:1 1 260px;min-width:0}
.enchead .encround{white-space:nowrap}
.enchead .encacts{float:none;margin-left:auto;display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end;max-width:100%}
/* the weapon line is what the GM rolls — read it at body contrast, the stat
   details one step down (not the tertiary --faint) */
.encweap{color:var(--ink)}
.encweap .smallnote{color:var(--dim);font-size:12px}
/* effects & conditions picker: section headers read LARGER than the 10px
   preset buttons under them, so each group announces itself */
.fchip.encfxhead{display:block;font-size:12.5px;letter-spacing:.16em;padding:5px 10px;margin:14px 0 6px;
  color:var(--holo);border-color:var(--holo-dim);border-left:3px solid var(--holo)}
/* pass 4: the Map tools strip — Map settings | Fog of war | Difficult terrain
   as one tab strip, one panel open at a time (they stacked as three bars) */
.bm-tools{margin:0 0 8px;border:1px solid var(--line);background:var(--panel2)}
.bm-tools-tabs{display:flex;flex-wrap:wrap}
.bm-tools-tabs button{flex:1 1 auto;display:flex;align-items:baseline;gap:6px;border:0;border-right:1px solid var(--line);background:none;color:var(--holo);padding:7px 10px;cursor:pointer;font:700 10px var(--disp);letter-spacing:.12em;text-transform:uppercase;text-align:left}
.bm-tools-tabs button:last-child{border-right:0}
.bm-tools-tabs button.on{background:var(--accent-wash);box-shadow:inset 0 -2px 0 var(--holo)}
.bm-tools-tabs button:focus-visible{outline:2px solid var(--focus-ring);outline-offset:-2px}
.bm-tools-st{font:400 11px var(--label);letter-spacing:0;text-transform:none;color:var(--dim)}
.bm-tools-live .bm-tools-st{color:var(--amber)}
.bm-tools-car{margin-left:auto;color:var(--dim)}
.bm-tools-panel{border-top:1px solid var(--line);padding:8px 10px}
.bm-tools-panel .bm-fogbar{margin:0;padding:0;border:0;background:none}
.bm-tools-note{margin:0 0 6px}
.bm-mapset-body{grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));max-height:none;padding:0}
@media(max-width:700px){.bm-tools-tabs button{flex-direction:column;gap:2px;padding:7px 8px;min-height:40px}.bm-tools-car{display:none}.bm-enc-count{display:none}}
@media(pointer:coarse){.bm-tools-tabs button{min-height:40px}}
/* pass 5: lingering-effect badges on tokens — the condition badge shape,
   holo-tinted so an effect never reads as a condition */
.bm-condb .bm-fxb rect{fill:var(--holo)}
.bm-condb .bm-fxb text{fill:var(--void)}
/* ---- Battle Maps pass 3 (docs/battle-maps.md §12) ---- */
.bm-enc-count{margin-left:auto}
.bm-bandnote{color:var(--amber);margin:4px 0}
.bm-rulelinks{margin-top:6px}
.bm-tool-select .bm-svg,.bm-tool-terrain .bm-svg{cursor:crosshair}
/* painted difficult terrain: a tinted hatch, GM-dim; solid enough on the table */
.bm-terrain{pointer-events:none}
.bm-terrain-fill{fill:var(--amber);fill-opacity:.1}
.bm-terrain-hatch{fill:var(--amber);fill-opacity:.45}
.bm-terrain-table .bm-terrain-fill{fill-opacity:.16}
/* blast templates: a tinted area under every token, its colour from the marker's */
.bm-blast{cursor:grab}
.bm-blast-area{fill:var(--amber);fill-opacity:.16;stroke:var(--amber);stroke-width:2;stroke-dasharray:6 4;vector-effect:non-scaling-stroke}
.bm-blast-core{fill:var(--amber)}
.bm-blast-red .bm-blast-area{fill:var(--red);stroke:var(--red)}.bm-blast-red .bm-blast-core{fill:var(--red)}
.bm-blast-green .bm-blast-area{fill:var(--green);stroke:var(--green)}.bm-blast-green .bm-blast-core{fill:var(--green)}
.bm-blast-holo .bm-blast-area{fill:var(--holo);stroke:var(--holo)}.bm-blast-holo .bm-blast-core{fill:var(--holo)}
.bm-blast.bm-sel .bm-blast-area{stroke-dasharray:none;stroke-width:3}
.bm-blast:focus-visible .bm-blast-area{stroke:var(--focus-ring)}
.bm-label.bm-blast-label{font-size:10px;letter-spacing:.08em}
.bm-table-moved{color:var(--amber);font-weight:400;letter-spacing:0;font-family:var(--label);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
/* touch: every map control at least 40px (a finger, not a cursor) */
@media (pointer:coarse){
  .bm-zoom button{height:40px;min-width:40px}
  .bm-nudge button{min-width:40px;height:40px}
  .bm-nudge input[type=color]{width:44px;height:40px}
  .bm-seg button{min-width:44px;height:40px}
  .bm-insp-x,.bm-insp-min{width:40px;height:40px}
  .bm-enc .btn.sm,.bm-lib .btn.sm,.bm-panel .btn.sm,.bm-insp .btn.sm,.bm-tablebar .btn.sm{min-height:34px}
  .bm-insp .stepper button{min-width:34px;min-height:34px}
  .bm-ranges li{min-height:36px}
}

/* ── About: report buttons + drawn info icon (2026-09-27) ── */
.about-report{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}
.about-report .btn{text-decoration:none}
/* "i in a circle", drawn rather than typed: the ⓘ glyph (U+24D8) comes from a
   fallback font and distorts at nav-icon size. Sized in em, so it scales with
   whatever text it sits in (11px nav icon, page title). */
.ic-info{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;
  width:1.1em;height:1.1em;border:.1em solid currentColor;border-radius:50%;vertical-align:-.15em;font-style:normal}
.ic-info::before{content:"i";font-family:var(--body);font-weight:700;font-size:.72em;line-height:1}

/* ---- Dashboard pass 2 ----
   The party panel's rows ARE the Group Vitals strip (partyVitalsStrip ro +
   {dash}), so the name cell carries a sheet link and a player · species ·
   career sub-line; the destiny readout is one row at the panel's foot; the
   Status Board is a single slim line when nominal; the rules strip has its own
   Quick Reference panel. Mobile: the Active Operation select takes the row and
   its buttons share the next one; the guide-hint buttons form a 2×2 grid. */
.dash-ptacts{margin-left:auto; display:inline-flex; align-items:center; gap:10px; flex-wrap:wrap}
.dash-ptacts .reflink{font-size:11px}
.vitrow.dashvit .vitname{display:flex; flex-direction:column; align-items:flex-start; gap:2px; min-width:160px}
.vitrow.dashvit .vitlink{font-weight:600}
.vitrow .vitsub{font-size:11px; font-weight:400; color:var(--faint)}
.dashnow-links{flex-basis:100%; display:flex; flex-wrap:wrap; gap:6px; align-items:center}
.dashdest{display:flex; align-items:center; flex-wrap:wrap; gap:calc(8px*var(--dsp)) calc(12px*var(--dsp));
  margin-top:calc(10px*var(--dsp)); padding-top:calc(8px*var(--dsp)); border-top:1px solid var(--line)}
.dashdest-lbl{font-family:var(--disp); font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--holo)}
.dashdest .dtok-pool{padding:0; gap:6px}
.dashdest .dtok-pool.compact .dtoken{width:28px; height:28px}
.dashdest-go{margin-left:auto; font-size:11px}
.vitrow.dashvit{row-gap:4px}
.alertline.dashstatus{margin:0 0 calc(14px*var(--dsp))}
.dashref .rule-strip:last-child{border-bottom:none; margin-bottom:0; padding-bottom:0}
.dashop select{max-width:320px}
.dashop-btns{display:inline-flex; gap:6px; flex-wrap:wrap}
@media(max-width:560px){
  .dashop select{max-width:none; flex:1 1 100%}
  .dashop-btns{flex:1 1 100%}
  .dashop-btns .btn{flex:1 1 0; min-width:0}
  /* the four tour/guide buttons stay on ONE row: compact type, content-sized
     cells sharing the width, and "Open the Guide" shortens to "Guide" */
  .dash-guidehint-btns{display:flex; flex-wrap:nowrap; gap:5px}
  .dash-guidehint-btns .btn{flex:1 1 auto; min-width:0; padding:6px 6px; font-size:9.5px;
    letter-spacing:.06em; white-space:nowrap; min-height:34px}
  .dgh-long{display:none}
  .dash-ptacts .reflink{display:none}
  .vitrow.dashvit .vitname{flex-basis:100%}
}
/* Six quick actions in an EVEN grid: 6 across on a wide screen, 3×2 in between,
   2×3 on a phone — never a ragged 2+2+1 (overrides the auto-fit tracks above). */
.dashqa{grid-template-columns:repeat(3,minmax(0,1fr))}
@media(min-width:1000px){ .dashqa{grid-template-columns:repeat(6,minmax(0,1fr))} }
@media(max-width:560px){ .dashqa{grid-template-columns:repeat(2,minmax(0,1fr))} }

/* ============================================================
   THEMED DIALOGS — review pass (2026-09-26). core.js dlgOpen/dlgKey.
   Message TYPE rides the title tone: neutral info is the plain holo .ptitle,
   "ok" (a completed action) is green, "warn" amber, "red" a failure — so a
   success never wears the amber ⚠. Tokens only.
   ============================================================ */
.ptitle.ok{color:var(--green)} .ptitle.ok::after{background:linear-gradient(90deg,var(--green-dim),transparent)}
.ptitle.red::after{background:linear-gradient(90deg,var(--red-dim),transparent)}
/* dlgFieldError / dlgPrompt({error}): the bad field is flagged in place and the
   dialog stays open. */
#dlgBox .invalid,#dlgBox .invalid:focus{border-color:var(--red); box-shadow:0 0 0 1px var(--red-dim)}
#dlgBox .dlgErr{color:var(--red); font-size:12px; line-height:1.4; margin-top:5px}
/* The keyboard default (Enter) shows where focus is. */
#dlgBox .modalActions .btn:focus-visible{outline:1px solid var(--focus-ring); outline-offset:2px}
/* A backdrop tap on a form with edited fields does not close it (dlgDirty) —
   the box pulses instead, so the tap visibly registered. */
#dlgBox.nudge{animation:dlgNudge .32s ease-out}
@keyframes dlgNudge{0%,100%{transform:none} 30%{transform:scale(1.015)} 60%{transform:scale(.995)}}
@media (prefers-reduced-motion:reduce){ #dlgBox.nudge{animation:none; border-color:var(--amber)} }
/* A long message (version details, a usage list) scrolls inside the box instead
   of pushing the buttons off a phone screen. */
#dlgBox .dlgMsg{max-height:min(60vh,520px); overflow-y:auto; overscroll-behavior:contain}
/* iOS Safari zooms the page when a focused field is under 16px — every dialog
   field is focused on open, so give them 16px on touch. */
@media (pointer:coarse){
  #dlgBox input:not([type=checkbox]):not([type=radio]),#dlgBox select,#dlgBox textarea{font-size:16px}
}
/* Phones: the buttons share the row as equal tap targets (the spacer steps
   aside); a row that cannot fit wraps, and the button left alone on the second
   line (the primary, last in source order) grows to the full width. */
@media (max-width:640px){
  #dlgBox .dlgMsg{font-size:13.5px}
  #dlgBox .modalActions .spacer{display:none}
  #dlgBox .modalActions .btn{flex:1 1 auto}
}

/* GM Screen pass 2 — the section rail is a tablist beside (not containing) the
   ⊞ Panels / ⚙ Customize toolbar; the sticky wrapper above holds both. On a
   phone the tab strip scrolls sideways (the shared .npc-library-tabs rule), so
   the toolbar takes its own row ABOVE it instead of scrolling off-screen.
   Width/height controls do nothing in a one-column layout, so they hide there.
   Appended at the END per the cascade convention (css/CLAUDE.md). */
.gm-railwrap .gm-tabrail{margin:0; flex:1 1 auto; min-width:0}
.gm-railacts{display:flex; gap:3px; flex:0 0 auto; margin-left:auto}
.gm-subhead{font-family:var(--disp); font-size:10px; letter-spacing:.18em; text-transform:uppercase; color:var(--dim); margin-bottom:6px}
.gm-quick{display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:6px}
.gm-quick .btn{justify-content:flex-start; text-align:left}
.gm-cond{display:flex; flex-direction:column; gap:5px; font-size:13px}
.gm-condname{font-weight:700; margin-right:2px}
.gm-catrow small{display:block; margin-top:1px}
@media (max-width:700px){
  .gm-railwrap{flex-wrap:wrap}
  .gm-railacts{order:-1; width:100%; justify-content:flex-end}
  .gm-railwrap .gm-tabrail{flex-basis:100%}
  .gmgrid .gmacts .gm-wideonly{display:none}
  .gmgrid.gmedit .panel>.ptitle{flex-wrap:wrap; row-gap:4px}
}

/* Characters landing (rosterView, .chmgr.chroster): the button column is split
   into .chmgr-sel (Selected character) and .chmgr-new (New character + Roster
   file) so a phone can put the actions for the selected row directly under the
   list, above the preview. Scoped to .chroster — the Vehicles roster and the
   Crafting landing reuse .chmgr unchanged. */
.chroster .chmgr-sel,.chroster .chmgr-new{display:flex;flex-direction:column;gap:calc(6px*var(--dsp))}
.chroster .chmgr-new .chmgr-grp:first-child{margin-top:calc(10px*var(--dsp))}
@media(max-width:720px){
  .chroster .chmgr-main,.chroster .chmgr-btns{display:contents}
  .chroster .chlist{order:1;margin-bottom:0}
  .chroster .chmgr-sel{order:2}
  .chroster .chprev{order:3}
  .chroster .chmgr-new{order:4}
  .chroster .chmgr-sel,.chroster .chmgr-new{flex-direction:row;flex-wrap:wrap}
  .chroster .chmgr-new .chmgr-grp:first-child{margin-top:0}
  .chroster .chmgr-sel .btn,.chroster .chmgr-new .btn{width:auto;flex:1 1 auto}
  .chroster .chprev{flex-direction:row;align-items:flex-start}
  .chroster .chprev-img{width:72px;height:90px;max-height:none;object-fit:cover}
}

/* ---- GM Options + GM Grants (review pass 2, 2026-09-27) ----
   GM Options rows put the hint UNDER the label instead of right-aligning it
   beside it — at phone width the old two-column row squeezed both into 3-line
   fragments. The rule link sits outside the <label> so it opens the quick view
   instead of toggling the box. .grow-cb wraps only a catalogue checkbox, so the
   row's name can be a Codex link (a link inside the label would toggle it). */
.gmopt-row{display:flex;gap:10px;align-items:flex-start;padding:5px 0}
.gmopt-row input[type=checkbox]{width:16px;height:16px;accent-color:var(--holo);flex:none;margin:2px 0 0}
.gmopt-row label{font-size:12px;cursor:pointer}
.gmopt-hint{color:var(--faint);font-size:10.5px;line-height:1.4;margin-top:1px}
.gmopt-sub{display:flex;flex-wrap:wrap;gap:6px 8px;align-items:center;padding:0 0 4px 26px}
.gmopt-sub-flush{padding-left:0;margin-top:6px}
.grow-cb{display:flex;align-items:center;cursor:pointer;flex:none}
/* The long section hints after an uppercase .fl label read as shouting. */
label.fl .fl-hint{text-transform:none;letter-spacing:normal;font-family:var(--body)}
/* Sheet tab "Show on sheet & print" row — the sheet/print include toggles moved
   here (and to Print ▸ Configure) out of GM Options. A wrapping flex row, so it
   reflows at phone width and at Large text size. */
.sheetshow{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;margin-top:12px;padding-top:8px;border-top:1px solid var(--line)}
.sheetshow-lbl{font-family:var(--label);font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--faint)}
.sheetshow-opt{display:flex;gap:6px;align-items:center;font-size:12px;cursor:pointer}
.sheetshow-opt input{width:15px;height:15px;accent-color:var(--holo);flex:none;margin:0}

/* ---- Overview & Identity pass (2026-09-27) ----
   Name row (input + 🎲), the portrait button stack, the dimmed species-default
   portrait, and a phone layout that puts the portrait (buttons beneath, capped at
   the desktop column's 300px) ABOVE the form instead of after it. */
.ovname{display:flex;gap:6px;align-items:center}
.ovname input{flex:1;min-width:0}
.ovname input.invalid,.ovname input.invalid:focus{border-color:var(--red);box-shadow:0 0 0 1px var(--red-dim)}
.ovname-err{color:var(--red);margin-top:4px}
.ovpbtns{display:flex;flex-direction:column;gap:4px;margin-top:8px}
.ovpbtns .portbtn{margin-top:0;width:100%}
.ovpdefwrap{position:relative}
.ovpdefault{opacity:.5}
.ovpcap{position:absolute;left:0;right:0;bottom:0;padding:4px 6px;text-align:center;font-size:10px;color:var(--dim);background:var(--input-bg);border-top:1px solid var(--line)}
@media(max-width:720px){
  .ovportrait{order:-1;width:100%;max-width:300px;margin:0 auto}
}
.ovrule{margin-left:4px;text-transform:none;letter-spacing:normal;font-weight:normal}

/* ============================================================
   SPECIES STEP PASS 2 (2026-09-27)
   Appended per the cascade convention. Tokens only.
   ============================================================ */
/* stat lines — one row per fact, the label in amber (was inline styles) */
.spstats{font-size:12.5px;line-height:1.55}
.spstats .sprow{padding:2px 0}
.spstats .sprow>b:first-child,.spstats .spamb{color:var(--amber)}
.spstats .spno>b{color:var(--red)}
/* the detail portrait gains the cards' keyboard-reachable ⤢ button, so the
   float moves from the <img> to its wrapper */
.spportrait-wrap{float:right;position:relative;margin:0 0 10px 14px}
.spportrait-wrap .spportrait{float:none;display:block;margin:0}
.spportrait-wrap .imgzoombtn{opacity:.6}
.spportrait-wrap:hover .imgzoombtn,.spportrait-wrap .imgzoombtn:focus-visible{opacity:.92}
.spxpbadge{float:right;margin-left:8px}
.spdesc{font-size:12.5px;line-height:1.65;margin-top:10px}
.spdesc.clamped{max-height:110px;overflow:hidden}
/* browse toolbar: Filters disclosure + count + clear */
.toolbar .spcount{white-space:nowrap}
.toolbar .spclear{font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--holo)}
.spfilt-tgl .badge{margin-left:4px}
.mincharf select{min-width:170px}
@media(max-width:900px){
  .spportrait-wrap{float:none;margin:0 0 10px}
  .spportrait-wrap .spportrait{width:100%;height:auto;max-width:100%;max-height:230px}
}
/* Sheet: Species Abilities cards (species + sub-species traits and picks) */
.spab .spab-grp{text-transform:uppercase;letter-spacing:.12em;font-size:9px;margin:-2px 0 4px}
.spab .spab-desc{font-size:12px;line-height:1.5}
.spab.pending{opacity:.75;border-style:dashed}

/* ── Career step pass 2 (2026-09-27) ── */
/* The chosen career's tile (the browsed one is .on) — spec tiles' .own idiom. */
.pickgrid-career .ftile .fown{font-size:9px;letter-spacing:.1em;text-transform:uppercase;margin-left:6px}
/* A browsed-but-not-chosen career lists its skills read-only (no free picks). */
.crskillro{display:flex;flex-wrap:wrap;gap:5px;margin:4px 0 2px}
.crskillro .fchip{font-size:10px;letter-spacing:.06em;text-transform:none;color:var(--ink)}
.crfrnote{margin:10px 0 0}
/* The reSpecialized primer's Force Star (forceStarSvg, respec-rules.js) —
   one glyph for every Force Star mark, inked from the Force identity token. */
.frstar{width:1.05em;height:1.05em;vertical-align:-.18em;fill:none;stroke:currentColor;stroke-width:2.3;stroke-linejoin:miter;stroke-miterlimit:10;stroke-linecap:butt;color:var(--force-green);flex:none;overflow:visible}
.fchip .frstar{width:1.25em;height:1.25em;vertical-align:-.3em;margin-right:2px}
.pickgrid-career .ftile>.fchip{margin-left:7px;vertical-align:1px}

/* ── Global .spacer fallback (2026-09-27) ── */
/* ~95 views drop <span class="spacer"></span> into a flex row to push the trailing
   cluster right, but only some hosts (.modalActions, .pdfbar, .camp-manage,
   .rule-bulkbar, the Session Planner bars) gave it a rule — elsewhere it was a
   zero-width no-op. An auto margin takes only the free space no flex-grow item
   claims, so rows with a growing input/name keep their layout; every host rule
   (flex:1, the phone display:none / flex-basis:100% breaks) out-specifies it. */
.spacer{margin-inline-start:auto}
/* Phones: these rows wrap, and the auto margin would strand the first action
   flush right with the rest of its cluster left on the next line (Codex entry,
   Market header) — drop it, matching the ≤560px spacer collapses above. Only the
   margin resets, so host flex:1 rules and the .dashnow break still apply. */
@media(max-width:560px){
  .spacer{margin-inline-start:0}
}

/* ── Specializations & Talents pass 2 (2026-09-27) ── */
/* The tree band's status line: OWNED progress / XP, or PREVIEW. */
.treeband-status{font-size:12.5px;color:var(--dim);margin:-2px 0 8px}
.treeband-status .badge{margin-right:4px}
/* Talents owned — a disclosure whose head is the section heading itself. */
.tsum{margin:4px 0 2px}
.tsum-tgl{display:block;width:100%;text-align:left;background:none;border:0;padding:0;cursor:pointer;font:inherit}
.tsum-tgl.cgsechead{font-family:var(--label);font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink)}
.tsum-tgl:focus-visible{outline:2px solid var(--holo);outline-offset:2px}
.tsum-count{letter-spacing:.08em;text-transform:none;color:var(--dim);font-size:12px}
.tsumchips{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 8px}
.tsumchip{border:1px solid var(--line);background:var(--panel2);padding:3px 8px;font-size:13px;line-height:1.4;color:var(--ink)}
.tsumchip b{color:var(--amber)}
.tsum-free{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--green)}
/* scrollIntoView targets on this step clear the sticky header */
#cgSpecBrowseAnchor,.speclayout .crdetail,.spec-treeband{scroll-margin-top:calc(var(--hdr-h,52px) + 12px)}

/* ── Official vs reSpecialized lineage (M8, 2026-09-27) ── */
/* A talent card whose record is reSpecialized's own rewrite (<KEY>_RS twin):
   a small tag in the banner so it never reads as the official talent. Tokens
   only — the card stock's fixed colours are the banner, not this tag. */
.tnode .thead .rsrw{font-size:8px;letter-spacing:.12em;text-transform:uppercase;line-height:1;
  padding:2px 4px;margin-left:4px;border:1px solid currentColor;border-radius:2px;opacity:.85;flex:none;align-self:center}

/* ---- M9 tree versions (js/tree-versions.js): the update dialog + owned-row badge ---- */
.tvopts{display:grid;gap:10px;margin-top:12px}
.tvopt{border:1px solid var(--line);padding:10px 12px;background:var(--panel2)}
.tvopt>b{font-size:13px}
.tvopt .smallnote{margin:4px 0 8px}
.tvdiff{margin:4px 0 8px 18px;padding:0;font-size:12.5px;line-height:1.6}
/* a <button> badge must drop the UA button fill (it rendered amber-on-light-grey) — the button.badge.fdup precedent */
button.tvbadge{cursor:pointer;font:inherit;font-size:10px;background:transparent;-webkit-appearance:none;appearance:none;line-height:inherit}
button.tvbadge:hover,button.tvbadge:focus-visible{background:var(--amber-bg)}
button.tvbadge.stay{opacity:.75}
button.tvbadge.stay:hover,button.tvbadge.stay:focus-visible{background:var(--panel2)}

/* ---- Characteristics tab pass 2 (2026-09-28) ---- */
.ct-rules{margin:-4px 0 10px}
.ct-buy{display:flex;flex-direction:column;align-items:center}
/* a refused buy/refund reads as unavailable (the base .stepper has no :disabled state) */
.chartiles .ct-step button:disabled{opacity:.35;cursor:default}
/* governed skills: phones fold them behind .ct-skills-tgl; wider screens hide
   the toggle and always show the list (pure CSS, no matchMedia at render) */
.ct-skills-tgl{display:none}
.ct-dedi{margin-top:16px}
.ct-dedi-head{font-size:12px;color:var(--dim);margin-bottom:6px}
/* crit rows / roller — styles that used to be inline */
.cirow{flex-wrap:wrap}
.ciroll{font-size:10.5px}
.cirow-name{flex:1;min-width:90px}
.cirow .cieffect{flex-basis:100%}
.cihealed{font-size:11px;color:var(--dim);display:flex;gap:4px;align-items:center}
.cihealed input{accent-color:var(--green)}
.cibar{margin-bottom:8px;align-items:center}
.cidura{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:0 0 8px}
.ciSearchInput{margin:8px 0}
/* phones: each tile becomes a compact row — small hex left, details right */
@media(max-width:540px){
  .chartiles{gap:8px}
  .chartiles .chartile{display:grid;grid-template-columns:52px minmax(0,1fr);column-gap:12px;
    align-items:center;text-align:left;padding:10px 12px}
  .chartiles .chartile>.ct-hex{grid-row:1 / span 4;width:46px;height:52px;align-self:start}
  .chartiles .chartile>:not(.ct-hex):not(.ct-skills-d){grid-column:2}
  .chartiles .ct-val{font-size:23px}
  .chartiles .ct-name{margin-top:0;font-size:14px}
  .chartiles .ct-delta{margin-top:0}
  .chartiles .ct-brk{justify-content:flex-start;margin:6px 0 4px;max-width:none}
  .chartiles .ct-chip.z{display:none}
  .chartiles .ct-buy{flex-direction:row;gap:10px;align-items:center;flex-wrap:wrap}
  .chartiles .ct-step{margin:0}
  .chartiles .ct-skills-d{grid-column:1 / -1;margin-top:6px}
  .chartiles .ct-skills-tgl{display:inline-flex;gap:6px;align-items:center;background:none;border:0;padding:4px 0;
    cursor:pointer;font:inherit;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}
  .chartiles .ct-skills-tgl::before{content:"▸";transition:transform .15s}
  .chartiles .ct-skills-d.open .ct-skills-tgl::before{transform:rotate(90deg)}
  .chartiles .ct-skills-d:not(.open) .ct-skills{display:none}
  .chartiles .ct-skills{margin-top:4px}
}

/* ---- Skills tab pass 2 (2026-09-29) ----
   Header free-rank gaps: one amber segment + Assign button per kind. */
.cgsec-gap{white-space:nowrap}
.cgsec-gap::before{content:"·";margin:0 6px;color:var(--dim)}
.skact .nextxp.short{color:var(--red)}
.skd-grantlbl{font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);align-self:center}
.sklegend{margin:4px 0 0;padding-left:18px}
.sklegend li{margin-bottom:3px}
/* Phones: the compact table (440px floor) scrolled sideways inside a ~275px
   box, hiding the pool and the +/−. Each row becomes a two-line grid instead —
   line 1: C · Skill · Rank, line 2: Characteristic · Dice Pool · +/− and XP —
   keyed by the cells' data-k. Scoped to .skillsplit so the Sheet tab's own
   .skilltbl is untouched. The header row and the Columns toggle (Free/Bought/
   Non-Career live in the detail pop-up) are hidden. */
@media(max-width:640px){
  .skillsplit .skilltbl,.skillsplit.cols-full .skilltbl{display:block;min-width:0;table-layout:auto}
  .skillsplit .skilltbl tbody{display:block}
  .skillsplit .skilltbl th{display:none}
  .skillsplit .skilltbl tr{display:grid;grid-template-columns:22px minmax(0,auto) minmax(0,1fr) auto;
    grid-template-areas:"c name name rank" ". char pool xp";align-items:center;column-gap:8px;row-gap:2px;
    padding:6px 4px;border-bottom:1px solid var(--line)}
  .skillsplit .skilltbl tr:first-child:not([id]){display:none}
  .skillsplit .skilltbl td,.skillsplit.cols-full .skilltbl td{display:block;border:0;padding:0;min-width:0;background:transparent}
  .skillsplit .skilltbl tr:hover,.skillsplit .skilltbl tr:hover td{background:var(--accent-wash)}
  .skillsplit .skilltbl tr.sksel,.skillsplit .skilltbl tr.sksel td{background:var(--accent-wash-mid)}
  .skillsplit .skilltbl td[data-k="career"]{grid-area:c}
  .skillsplit .skilltbl td[data-k="name"]{grid-area:name;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .skillsplit .skilltbl td[data-k="rank"]{grid-area:rank;text-align:right}
  .skillsplit .skilltbl td[data-k="char"]{grid-area:char;font-size:11.5px;color:var(--dim)}
  .skillsplit .skilltbl td[data-k="pool"]{grid-area:pool}
  .skillsplit .skilltbl td[data-k="xp"]{grid-area:xp}
  .skillsplit .skilltbl td[data-k="free"],.skillsplit .skilltbl td[data-k="bought"],.skillsplit .skilltbl td[data-k="nc"]{display:none}
  .skillsplit .tblscroll{overflow-x:visible}
  .cgskill-cols,.sklegend-cols{display:none}
  /* the free-rank gaps stack one per line under the count */
  .cgsec-gap{display:flex;align-items:center;justify-content:space-between;gap:8px;white-space:normal;margin-top:4px}
  .cgsec-gap::before{content:none}
}
.cgsec-count{white-space:nowrap}

/* ---- Chargen Companions tab (npc-view.js cgCompanionsTab) — a table on
   desktop, stacked cards at ≤700px (pure CSS; data-lbl names each field) ---- */
.comptbl .comp-in{width:100%;min-width:10ch;font-size:11px}
.comptbl .comp-role{width:15ch}
.comptbl .comp-bond{min-width:18ch}
.comp-whoin{display:flex;gap:8px;align-items:flex-start;min-width:22ch}
.comp-pic{width:36px;height:36px;object-fit:cover;border:1px solid var(--line);flex:none;background:var(--panel2)}
.comp-nopic{display:inline-flex;align-items:center;justify-content:center;color:var(--faint)}
.comp-name{font-weight:600}
.comptbl .comp-act{white-space:nowrap;text-align:right}
.comptbl .comp-sub{display:flex;gap:4px;align-items:center;margin-top:4px}
.comptbl tr.warn .comp-whoin{box-shadow:inset 2px 0 0 var(--amber);padding-left:6px}
.comp-tals{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:8px 0}
.comp-tals .fchip{text-transform:none;letter-spacing:.04em;font-size:10px;color:var(--ink)}
.comp-adv{margin:6px 0 10px}
.comp-kinds{margin:0 0 8px;flex-wrap:wrap}
.npc-card.comp-linked{opacity:.55;cursor:default}
.compcard .comp-meta{margin:0 0 4px}
@media(max-width:700px){
  .comptbl,.comptbl tbody{display:block}
  .comptbl tr:first-child{display:none}
  .comptbl tr.comprow{display:grid;grid-template-columns:minmax(0,1fr);gap:6px;padding:10px 0;border-bottom:1px solid var(--line)}
  .comptbl tr.comprow>td{display:block;border:0;padding:0;width:auto;min-width:0}
  .comptbl td[data-lbl]::before{content:attr(data-lbl);display:block;font-family:var(--label);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin-bottom:2px}
  .comptbl .comp-act{text-align:left;white-space:normal;display:flex;flex-wrap:wrap;gap:6px}
  .comptbl .comp-sub{flex-wrap:wrap}.comptbl .comp-sub select{flex:1 1 40%;min-width:0}
  .comp-whoin{min-width:0}
}

/* ---- Chargen Vehicles tab (vehicles-view.js cgVehiclesTab) — the .comptbl
   idiom: a table on desktop, stacked cards at ≤700px ---- */
.cgveh-whoin{display:flex;gap:8px;align-items:flex-start;min-width:22ch}
.cgveh-pic{width:56px;height:42px;object-fit:cover;border:1px solid var(--line);flex:none;background:var(--panel2);cursor:zoom-in}
.cgveh-badge{font-size:9px;margin-left:6px;vertical-align:middle}
.cgvehtbl .cgveh-pos{width:13ch;font-size:11px}
.cgvehtbl .cgveh-act{text-align:right}
.cgvehtbl .cgveh-act>*{margin:2px 0 2px 4px}
.cgvehtbl tr.warn .cgveh-whoin{box-shadow:inset 2px 0 0 var(--amber);padding-left:6px}
.cgveh-checks{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:center;margin-top:4px;font-size:11px}
.cgveh-check{display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
.cgveh-check .btn{padding:0 6px}
.cgveh-sig{display:inline-flex;gap:4px;align-items:center;font-size:11px;cursor:pointer}
.cgveh-sig.off{opacity:.6;cursor:not-allowed}
.cgveh-sig input{accent-color:var(--amber)}
.cgveh-kind{display:inline-flex;gap:6px;align-items:center;cursor:pointer;margin-right:12px}
.cgveh-capwarn{color:var(--amber);margin-top:6px}
@media(max-width:700px){
  .cgvehtbl,.cgvehtbl tbody{display:block}
  .cgvehtbl tr:first-child{display:none}
  .cgvehtbl tr.cgvehrow{display:grid;grid-template-columns:minmax(0,1fr);gap:6px;padding:10px 0;border-bottom:1px solid var(--line)}
  .cgvehtbl tr.cgvehrow>td{display:block;border:0;padding:0;width:auto;min-width:0}
  .cgvehtbl td[data-lbl]::before{content:attr(data-lbl);display:block;font-family:var(--label);font-size:8.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin-bottom:2px}
  .cgvehtbl .cgveh-act{text-align:left;display:flex;flex-wrap:wrap;gap:6px;align-items:center}
  .cgvehtbl .cgveh-act>*{margin:0}
  .cgveh-whoin{min-width:0}
  .cgveh-check{white-space:normal}
}

/* ===================== Force page pass 2 (chargen) ===================== */
/* Career Powers band rows: a small display-face label then the chips/select. */
.cprow{margin:4px 0}
.cplbl{font-family:var(--disp);font-size:9.5px;letter-spacing:.1em}
/* Detail pane: the XP-at-a-glance line under the source chips, and the action
   row (roll a Force check, hide from browser) that wraps on a narrow column. */
.pwrxp{margin:2px 0 8px}
.pwracts{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}

/* M9 Previous Versions: why each version is kept (who is on it) — js/tree-versions.js tvPrevSection */
.tvwhy{margin:0 0 10px 18px;padding:0;line-height:1.6}
.tvwhy .reflink{font-size:inherit}

/* O/D/M pass 2 — the character's slice of the party's session-start d100
   (odmSliceNote) and this session's trigger chip; the party-size starting
   chip (odmStartChips) wears a dashed amber edge so it reads as a suggestion,
   never as the selected value. */
.odmslice{margin:-2px 0 6px}
.odmslice .badge.odmtrig{color:var(--amber);border-color:var(--amber-dim);margin-left:4px}
.btn.odmsug{border-style:dashed;border-color:var(--amber-dim)}
/* On a phone the per-row cite chip (Motivation, Obligation, Duty, Strength,
   Weakness) takes its own line sized to leave room for the row's ✕, which
   then sits beside it instead of dropping alone onto a third line (the chip
   plus the ✕ overran a 375px row by a single pixel). A flex line breaks on
   the BASIS, before any shrink, so the basis is what has to change; the chip
   wraps its own text if the book name is longer still. */
@media(max-width:540px){ .initrow .bgfield-cite{flex:1 1 calc(100% - 48px);min-width:0} }

/* Rewards & Extras (pass 2) — one wrapping head row per card (type select ·
   Codex · cite · ✕, so the ✕ never strands alone on a phone line), the folded
   summary, option-group status pills (amber = chosen) with their tier rule, and
   the Table 2-7 holocron picker as a dropdown over two skill selects. */
.rwhead{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px;flex-basis:100%;min-width:0}
.rwhead .rwsel{flex:1 1 220px;min-width:0;width:auto}
.rwhead .rwsum{flex:1 1 160px;min-width:0}
.rwhead .rwdel{margin-left:auto}
.rwopts{flex-basis:100%;border:1px solid var(--line);border-radius:4px;padding:8px;margin-top:4px}
.rwgroup{margin-bottom:10px}
.rwgroup:last-child{margin-bottom:0}
.rwglabel{display:flex;flex-wrap:wrap;align-items:center;gap:4px 10px;margin-bottom:4px}
/* The details a player acts on (XP cost, granted talent, tier rule, field
   labels) — full ink at a readable size; .smallnote's faint 10.5px is for asides. */
.rwnote{font-size:12px;line-height:1.5;color:var(--ink)}
.grholo{display:flex;flex-direction:column;gap:6px;max-width:680px}
.grholo-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px 8px}
.grholo-lbl{flex:0 0 96px}
.grholo-pair{flex:1 1 260px;min-width:0}
.grholo-sk{display:flex;flex-wrap:wrap;gap:6px;flex:1 1 260px;min-width:0}
.grholo-sk select{flex:1 1 140px;min-width:0}
@media(max-width:540px){ .grholo-lbl{flex-basis:100%} }
@media(pointer:coarse){ .rwhead .rwdel,.rwhead .rwfold{min-width:44px} }
/* Phones: keep the ✕ on the select's line (the cite chip wraps below it) and
   the folded summary on one line — the select / summary flex down, not out. */
@media(max-width:540px){
  .rwhead .rwfold,.rwhead .rwsel{order:0}
  .rwhead .rwdel{order:1}
  .rwhead > :not(.rwfold):not(.rwsel):not(.rwdel):not(.rwsum):not(.bh-stat):not(.rwwarn){order:2}
  .rwhead .rwsel{flex:1 1 0;min-width:140px}
  .rwhead .rwsum{flex:1 1 0}
}

/* ===================== Change History pass 2 (chargen) ===================== */
/* The stamp row (a write action), the view filters, session-group recaps,
   long-entry folding and paging. On a phone the filters fold behind one
   "Filters" button so the first entry is on the first screen. */
.chhist-stamp{margin:8px 0 2px}
.chhist-stamp .chhist-print{margin-left:auto}
.chhist-stamphint{margin:0 0 8px}
.chhist-view{margin:0 0 8px}
.chhist-view .chhist-count{margin-left:auto}
.chhist-ftog{display:none}
.chhist td.chhist-when{white-space:nowrap;color:var(--faint);vertical-align:top}
.chhist td.chhist-kind,.chhist td.chhist-body{vertical-align:top}
.chhist .chhist-sep{display:none}
.chhist tr.chhist-shead td{padding-top:10px}
.chhist .chhist-slbl{letter-spacing:.1em;text-transform:uppercase}
.chhist .chhist-recap{margin-left:12px;font-size:11px;color:var(--holo)}
.chhist .chhist-more{margin-top:2px}
.chhist .chhist-more .cxlink{font-size:11px}
.chhist-pager{margin:8px 0;text-align:center}
@media(max-width:640px){
  .chhist-ftog{display:inline-flex;margin:2px 0 8px}
  .chhist-view:not(.open){display:none}
  .chhist-stamp .chhist-print{margin-left:0}
  .chhist .chhist-sep{display:inline}
  .chhist .chhist-br{display:none}
  .chhist .chhist-recap{display:block;margin:2px 0 0}
}
.prsheet .prhistdoc tr.prhist-shead td .prhist-recap{margin-left:10px;text-transform:none;letter-spacing:0;font-family:inherit}

/* ---------- Review pass 2: grouped advisories + CTA notes ---------- */
/* Advisories group by kind (Creation rules / Ongoing / Tree updates); each row
   leads with its step tag so a long list scans by where the fix lives. */
.cgadv-group{font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--faint);margin:10px 0 5px}
.cgadv-group:first-of-type{margin-top:4px}
.cgadv-step{flex:0 0 auto;white-space:nowrap;font-size:9px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--dim);border:1px solid var(--line);border-radius:3px;padding:2px 6px;background:var(--panel)}
.initrow.cgadv .iname{min-width:0;font-weight:400;line-height:1.45}
/* CTA sub-lines: the unspent-XP note and the post-completion pocket money */
.cgfinal-note{margin-top:6px;font-size:12px;color:var(--dim)}
.cgfinal-inline{display:inline-flex;flex-wrap:wrap;gap:6px;margin-left:6px;vertical-align:middle}
/* Phones: the step tag rides its own line above the advisory text */
@media(max-width:600px){
  .initrow.cgadv{flex-wrap:wrap;row-gap:3px}
  /* a zero-height full-width item after the tag forces the line break, so a
     short tag ("Skills") never shares its line the way a long one can't */
  .initrow.cgadv .cgadv-step{order:-1}
  .initrow.cgadv:has(.cgadv-step)::before{content:"";flex:0 0 100%;height:0}
  .initrow.cgadv .iname{flex:1 1 calc(100% - 70px)}
  .cgfinal-inline{display:flex;margin:6px 0 0}
}
/* Chargen: trees the active composition can't show (a reSpecialized character
   under the RAW ruleset) — one note under the XP bar with the way out */
.alertline.cgforeign{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;margin:0 0 8px;line-height:1.5}
.alertline.cgforeign>span{flex:1 1 260px;min-width:0}

/* ---------- Character Sheet tab — pass 2 (2026-10-02) ---------- */
/* Vitals labels: "Wounds"/"Strain", shrinking to W/S in a narrow identity
   column (the .lfull/.labbr container-query idiom the stat tiles use). */
.sheethead-id{container-type:inline-size}
.sheetvital{white-space:nowrap;display:inline-flex;align-items:center;gap:4px}
.sheetvital .labbr{display:none}
@container (max-width:200px){ .sheetvital .lfull{display:none} .sheetvital .labbr{display:inline} }
/* Critical Injuries strip under the steppers (moved out of Weapons & Gear). */
.sheetinj{flex:1 1 100%;border-top:1px solid var(--line);padding-top:6px}
.sheetinj-rows{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:0 16px}
/* The identity block needs room for the portrait AND the vitals steppers, so
   the band stacks (vitals below) before the steppers can spill into the tiles. */
.sheetbanner .sheethead{flex:1 1 360px}
.sheetvitals{flex-basis:340px}
.sheetinj-h{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.sheetinj-h .btn{margin-left:auto}
.injrow{padding:4px 0;border-bottom:1px solid var(--line);font-size:12px}
.injrow-h{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.injrow.healed{opacity:.55}
.injrow .descmore{font-size:11.5px;color:var(--dim)}
/* Section headers: every section shares the ptitle head now (Skills was a
   bordered .rref box). Group sub-headings for skills (General/Combat/
   Knowledge) and talents (Active/Passive). */
.sheetsec-note{font-family:inherit;letter-spacing:0;text-transform:none;font-size:11px;color:var(--faint);margin-left:6px}
.sheetsec-head{margin-bottom:6px}
.skgroup,.talgroup{margin-bottom:8px}
.skgroup-h{font-family:var(--label);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--holo-dim);margin:4px 0 4px}
.skcell .skgov{color:var(--faint);font-size:10.5px}
.skcell.untrained{opacity:.6}
/* Talent card marks: activation + Force / reSpec Conflict / Repeatable. */
.talmarks{display:flex;flex-wrap:wrap;gap:4px;margin:2px 0 4px}
.talact,.talmark{font-family:var(--label);font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;border:1px solid var(--line);padding:1px 5px;color:var(--dim)}
.talact{color:var(--holo);border-color:var(--holo-dim)}
.talmark-cf{color:var(--red);border-color:var(--red)}
/* Power / sig cards: upgrade line, then the record's own text. */
.sheetupg{font-size:12px}
.sheetdesc{margin-top:4px;font-size:12px}
/* Item rows: attachments line; cybernetics line under the gear table. */
.sheetatt{font-size:11px;margin-top:1px}
.sheetcyb{font-size:12px;margin-top:8px;display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px}
@media(pointer:coarse){ .taltoggle{min-width:20px;min-height:20px} }
@media(max-width:640px){
  /* Skill cells go to one line (name · pool · rank · ⚄) — the two-line cells
     made the section ~1,500px tall on a phone. The pool wraps under only when
     it cannot fit. */
  .skgrid{grid-template-columns:minmax(0,1fr)} /* one column: the one-line cell needs the full width */
  .skcell{grid-template-columns:minmax(0,1fr) auto auto auto;grid-template-areas:"name pool rank send";padding:3px 8px}
  .skcell .skpool{justify-content:flex-end;min-height:0}
  /* Banner: characteristics 3-across, stats 4-across, a smaller portrait. */
  .sheetvitals .charrow{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px}
  .sheetvitals .sheetstats{grid-template-columns:repeat(4,minmax(0,1fr))}
  .sheethead-img{width:96px;height:132px}
  /* Weapon/armor/gear/vehicle cards: stats two-up (Skill | Dam, Crit | Range),
     the name, Qualities and the Roll sub-row full width — the Equipment
     catalog card pattern. The <th> row stays hidden (its :first-child rule
     out-specifies this). */
  .sheetbody table.qt tr{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);column-gap:12px}
  .sheetbody table.qt td:first-child,.sheetbody table.qt td[data-label="Qualities"]{grid-column:1/-1}
  .sheetbody td[data-label]::before{min-width:44px}
  /* Weapon cards read Skill | Range, then Dam | Crit (DOM order stays the
     desktop column order). Keyed on the labels, so armor/gear keep theirs. */
  .sheetbody table.qt td[data-label="Skill"]{order:1}
  .sheetbody table.qt td[data-label="Range"]{order:2}
  .sheetbody table.qt td[data-label="Dam"]{order:3}
  .sheetbody table.qt td[data-label="Crit"]{order:4}
  .sheetbody table.qt td[data-label="Qualities"]{order:5}
}

/* Equipment ▸ Inventory: an item's built-in weapon profile (gear/armor usable as
   a weapon, a weapon's other mode) — stat line + Roll row under the item. */
.eqprofile{flex-basis:100%;margin-top:4px;padding-top:4px;border-top:1px dashed var(--line)}
/* ---- Battle Maps pass 4 (docs/battle-maps.md §13) ---- */
.bm-usedby{margin:4px 0 8px}
.bm-usedby .linkbtn{background:none;border:0;padding:0;color:var(--holo);cursor:pointer;font:inherit;text-align:left}
.bm-usedby .linkbtn:hover{text-decoration:underline}
@media(pointer:coarse){.bm-usedby .linkbtn{min-height:20px}}

/* ============================================================
   CHRONICLE + PARTY INVENTORY review pass (2026-10-03) — overdue deadlines,
   row actions, era chips; inventory provenance/controls/bands; and the phone
   layouts none of the three surfaces had. Theme tokens only.
   ============================================================ */
.pty-clock-due.over,.gm-clock-due.over{ color:var(--red) }
.chron-upcoming.over{ border-left-color:var(--red); background:var(--red-wash) }
.chron-upcoming.over .chron-day{ color:var(--red) }
.chron-acts{ flex:0 0 auto; display:inline-flex; align-items:center; gap:2px }
.chron-era{ font-size:10px; vertical-align:middle }
.invrow .iname .invline{ color:var(--faint); font-size:11px; font-weight:400 }
.invrow .invsrc{ display:block; font-size:11px; font-weight:400; font-style:italic; color:var(--dim) }
.invrow .invctl{ display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap }
.invrow .invctl select{ min-width:120px; max-width:200px }
.invrow.done{ opacity:.8 }
.inv-band{ display:flex; align-items:center; gap:8px; margin-top:12px }
.inv-sum{ margin-left:auto }
@media (max-width:640px){
  .pty-clock{ gap:8px; padding:8px 10px }
  .pty-clock-era{ flex:1 1 100% }
  .pty-clock-era input{ flex:1 1 auto; width:auto; min-width:0 }
  .pty-clock-due{ flex:1 1 100%; white-space:normal }
  .pty-clock-acts{ flex:1 1 100%; margin-left:0 }
  .pty-clock-acts .btn{ flex:1 1 40% }
  .pty-clock-acts .pty-clock-stop{ margin-left:0 }
  .chron-row{ gap:6px; padding:7px 6px }
  .chron-upcoming{ flex-wrap:wrap }
  .chron-day{ min-width:48px }
  .chron-upcoming .chron-day{ flex:1 1 100%; min-width:0 }
  .chron-acts{ margin-left:auto }
  .invrow{ gap:6px }
  .invrow .iname{ flex:1 1 calc(100% - 120px) }
  .invrow .invctl{ flex:1 1 100%; order:3 }
  .invrow .invctl select{ flex:1 1 140px; max-width:none }
  .invrow .invnote{ order:4 }
  .inv-sum{ flex:1 1 100%; margin-left:0 }
}

/* ---- Import / export / generators pass (2026-10-03) ---- */
/* Generator dialogs (Generate Build / Generate Adversary): stacked radio rows */
.gen-opts{display:flex;flex-direction:column;gap:6px;margin-top:6px}
.gen-opt{display:flex;gap:8px;align-items:flex-start}
.gen-opt input{accent-color:var(--holo);margin-top:3px}
.gen-form{display:flex;flex-direction:column;gap:14px;margin:14px 0}
/* Create Inquisitor wizard */
.inq-name{color:var(--faint);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.npc-check-row.inq-off{opacity:.45;cursor:not-allowed}
.inq-force{border:1px solid var(--line);margin:6px 0 10px}
@media (max-width:700px){
  .inq-title{flex-wrap:wrap}
  .inq-name{flex:1 1 100%;order:3}
  /* the select already shows the value — the big digit only repeats it */
  .inq-charrow .cv{display:none}
  /* steps: number-only chips except the current one, on one line */
  .inq-steps{flex-wrap:nowrap;overflow-x:auto}
  .inq-steps .btn.ghost .inq-steplbl{display:none}
}

/* ============================================================
   2026-10-03 — dialog peek, catalog compare, touch hints
   ============================================================ */
/* a rule / Codex / PDF lookup opened from inside the dialog (core.js dlgPeek):
   hidden, not torn down, so the form's typing survives until the lookup closes */
#dlg.open.peek{visibility:hidden;pointer-events:none}
/* Catalog ⇄ Compare chips (chargen-equip.js eqDelta) */
.eqcmp{font-size:10px;font-weight:600;white-space:nowrap}
.eqcmp.up{color:var(--green)}
.eqcmp.down{color:var(--red)}
.eqcmp-cap{margin:2px 0 6px}
/* Touch-readable title= hints (js/touch-tips.js) — above every overlay */
#tipPop{position:fixed;z-index:400;display:none;max-width:min(320px,calc(100vw - 16px));
  padding:7px 10px;background:var(--panel);color:var(--ink);border:1px solid var(--holo-dim);
  font-size:12.5px;line-height:1.45;box-shadow:0 4px 18px var(--shadow);pointer-events:none;
  overflow-wrap:break-word;white-space:pre-line}
#tipPop.open{display:block}
@media (hover:none){[title]:not(input):not(textarea):not(img){-webkit-touch-callout:none}}

/* ===== ENCOUNTER RUN TRACKER — combatant cards (2026-10-03)
   encRunRow / encRunVehRow render .encrow cards on a shared .encrows grid; each
   card is a SUBGRID of it, so the vitals and the action buttons sit in the same
   columns on every row whatever the name's length:
     caret | who | vitals | actions   /   · | weapons + chips   /   stat block
   A down row dims its who/vitals/weapons only — never the whole card (the ↺ Up
   button stays readable) and never by nesting: the old flex rows left a div
   open, so every row sat inside the one above and the dimming compounded.
   The tiers are container queries on .encrowsbox (the run panel is narrower
   than the page), mirroring .initlist. */
.encrowsbox{container-type:inline-size}
.encrows{display:grid; grid-template-columns:44px minmax(0,1fr) max-content max-content; column-gap:12px}
.encrow{grid-column:1/-1; display:grid; grid-template-columns:subgrid; row-gap:4px; align-items:center;
  grid-template-areas:"exp who vit act" ". sub sub sub" "blk blk blk blk";
  padding:8px 10px; margin-bottom:6px; border:1px solid var(--line); border-left:3px solid var(--holo-dim); background:var(--panel2)}
@supports not (grid-template-columns:subgrid){
  .encrow{grid-template-columns:44px minmax(0,1fr) max-content max-content; column-gap:12px}
}
.encrow.npc{border-left-color:var(--red)}
.encrow.ally{border-left-color:var(--amber-dim)}
.encrow>*{min-width:0}
.encr-exp{grid-area:exp; align-self:start}
.encr-who{grid-area:who; display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 8px}
.encr-who .iname{font-weight:600}
.encr-vit{grid-area:vit; display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px}
.encr-act{grid-area:act; display:flex; flex-wrap:wrap; align-items:center; justify-content:flex-end; gap:6px}
.encr-sub{grid-area:sub}
.encr-sub .encweap{margin-top:0}
.encr-sub .encconds{display:flex; flex-wrap:wrap; align-items:center; gap:3px; margin-top:4px}
.encr-blk{grid-area:blk; margin-top:6px}
.encalive{font-size:11px}
.encrow .stepper{overflow:visible}
.encrow .stepper .cnt:not(.pcw){width:auto; min-width:52px; padding:0 8px; font-size:15px; box-sizing:border-box}
.encrow.down{background:var(--panel)}
.encrow.down>.encr-who, .encrow.down>.encr-vit, .encrow.down .encweap{opacity:.5}
.encsect .encmeta{font-size:11px; letter-spacing:.04em}
.ptitle.encsect{flex-wrap:wrap; row-gap:2px}
/* the standing count stays whole; the note wraps and shrinks (min-width:0)
   rather than running off a narrow panel */
.ptitle.encsect .encmeta{white-space:nowrap}
.ptitle.encsect .smallnote{flex:1 1 200px; min-width:0; white-space:normal; overflow-wrap:break-word}
@container (max-width:760px){
  /* tablet / narrow panel: the vitals and the actions take their own lines
     under the name, still indented under the caret */
  .encrows{grid-template-columns:44px minmax(0,1fr)}
  .encrow{grid-template-areas:"exp who" ". vit" ". act" ". sub" "blk blk"}
  .encr-act{justify-content:flex-start}
}
@container (max-width:760px){@supports not (grid-template-columns:subgrid){
  .encrow{grid-template-columns:44px minmax(0,1fr)}
}}
@container (max-width:420px){
  /* phone: everything below the name uses the card's full width */
  .encrow{grid-template-areas:"exp who" "vit vit" "act act" "sub sub" "blk blk"}
}
@media(pointer:coarse){ .encrow .btn.sm{min-height:34px} }
/* turn order on the cards (encTurnBadge): ▶ ACTING / NEXT · #n / #n / ✓ ACTED.
   The acting card carries the strip's amber accent so it is findable at a
   glance, and ↓ Jump to (App.encRunJump) flashes it after scrolling there. */
.encturn{display:inline-flex; align-items:center; font:700 9px var(--disp); letter-spacing:.12em; text-transform:uppercase;
  padding:2px 6px; border:1px solid var(--line); border-radius:3px; color:var(--dim); white-space:nowrap; align-self:center;
  min-width:74px; justify-content:center; box-sizing:border-box}
.encturn.now{color:var(--on-amber); background:linear-gradient(180deg,var(--amber-dim),var(--amber-bg)); border-color:var(--amber)}
.encturn.next{color:var(--holo); border-color:var(--holo)}
.encturn.done{color:var(--faint)}
.encrow.turn-now{border-color:var(--amber); border-left-color:var(--amber); box-shadow:inset 3px 0 0 var(--amber); background:var(--accent-wash)}
.encrow.encflash{animation:encflash 1.6s ease-out}
@keyframes encflash{0%,30%{outline:2px solid var(--amber); outline-offset:2px} 100%{outline:2px solid transparent; outline-offset:6px}}
.encnow .encafter{white-space:nowrap}
/* the now-acting strip rides under the header while the tracker scrolls, so
   Advance and ↓ Jump to are always in reach (the .npc-preview offset idiom);
   the wash is laid over an opaque panel so cards never show through it */
/* the strip is a column now: the actor + controls row, then THIS TURN (the
   action-economy chips + what the turn log recorded) and LAST TURN */
.encnow{flex-direction:column; align-items:stretch; gap:4px}
.encnow-top{display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px}
.encnow-line{display:flex; flex-wrap:wrap; align-items:center; gap:4px 10px; font-size:12px; padding-top:4px; border-top:1px dashed var(--line)}
.encnow-line .fchip{margin:0}
.encnow-last{color:var(--dim)}
.encnow-acts{display:inline; min-width:0; flex:1 1 240px; overflow-wrap:break-word}
.encact{color:var(--ink)}
.encnow-last .encact{color:var(--dim)}
.enceconrow{display:inline-flex; flex-wrap:wrap; gap:4px}
.encecon{font:700 9px var(--disp); letter-spacing:.1em; text-transform:uppercase; padding:4px 8px; cursor:pointer;
  border:1px solid var(--line); border-radius:3px; background:var(--btn-bg); color:var(--dim)}
.encecon:hover{border-color:var(--holo); color:var(--holo)}
.encecon.on{color:var(--on-amber); background:linear-gradient(180deg,var(--amber-dim),var(--amber-bg)); border-color:var(--amber)}
.encecon.clr{padding:4px 6px}
/* ▴ collapsed strip (S.settings.encNowMini): one row that never wraps — the
   name truncates before a button drops to a second line */
.encnowtog{min-width:30px; justify-content:center}
.encnow.mini .encnow-top{flex-wrap:nowrap}
.encnow.mini .encnow-who{flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.encnow.mini .spacer{display:none}
.encnow.mini .btn{flex:0 0 auto}
@media(pointer:coarse){ .encecon{min-height:30px; padding:6px 10px} }
.encpanel .encnow{position:sticky; top:var(--hdr-h,52px); z-index:8;
  background:linear-gradient(var(--accent-wash),var(--accent-wash)),var(--panel); box-shadow:0 4px 12px var(--shadow)}
/* the run header's ⋯ More disclosure (Reinforcements / Recover loot / Print) */
.encmore{position:relative; display:inline-block}
.encmore>summary{list-style:none; cursor:pointer}
.encmore>summary::-webkit-details-marker{display:none}
.encmore-menu{position:absolute; right:0; top:calc(100% + 4px); z-index:20; display:flex; flex-direction:column; gap:4px;
  padding:6px; min-width:180px; background:var(--panel); border:1px solid var(--line); box-shadow:0 4px 18px var(--shadow)}
.encmore-menu .btn{justify-content:flex-start; text-align:left}

/* ==== STORAGE, BACKUP & MERGE (2026-10-03) ==== */
/* Storage bar (core.js kvBarPaint): a failed save or a cross-window change.
   Just under the header (--hdr-h, stampHdrH) so it never covers the nav or
   stacks on the update banner; never a modal,
   because a failing save can repeat on every keystroke. */
#storeBar{
  position:fixed; left:50%; top:calc(var(--hdr-h, 56px) + 8px); transform:translateX(-50%);
  z-index:9998; display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  width:min(680px, calc(100vw - 32px)); box-sizing:border-box;
  padding:10px 34px 10px 14px; border:1px solid var(--amber); border-radius:8px;
  background:var(--panel); color:var(--ink); box-shadow:0 6px 24px rgba(0,0,0,.35); font-size:13px;
}
#storeBar[hidden]{display:none}
#storeBar span{flex:1 1 260px; min-width:0}
#storeBar .btn{flex:0 0 auto}
#storeBar .storebar-x{position:absolute; top:4px; right:4px; width:24px; height:24px; padding:0;
  border:0; background:none; color:var(--faint); cursor:pointer; font-size:13px; line-height:1}
#storeBar .storebar-x:hover{color:var(--ink)}
/* Import & merge preview (merge.js) */
.mg-body{max-height:min(62vh,640px); overflow:auto; padding-right:4px}
.mg-sec{padding:8px 0; border-top:1px solid var(--line)}
.mg-h{font-weight:700; color:var(--holo); margin-bottom:4px}
.mg-row{display:flex; flex-wrap:wrap; align-items:center; gap:8px; padding:6px 0; border-bottom:1px dashed var(--line)}
.mg-what{flex:1 1 240px; min-width:0}
.mg-what b{overflow-wrap:anywhere}
.mg-choice{flex:0 1 auto; max-width:100%; flex-wrap:wrap; margin:0}
.mg-bulk{margin:6px 0; gap:6px}
.mg-new summary{cursor:pointer}
