@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=JetBrains+Mono:wght@400;500;700&display=swap');

:root{
  --ink:#0B0E14;
  --panel:#151920CC;
  --panel-solid:#151920;
  --panel-raised:#1C212A;
  --well:#0A0C10;
  --line:#333B48;
  --parchment:#D6A374;
  --text:#DCD5C4;
  --muted:#8B93A0;
  --brass:#C08552;
  --brass-dim:#8A6244;
  --brass-wash:rgba(192,133,82,0.16);
  --ember:#A3462F;
  --ember-dim:#6E2E1F;
  --ember-wash:rgba(163,70,47,0.22);
  --verdant:#6B8F5A;
  --sapphire:#4F6E9E;
  --sapphire-wash:rgba(79,110,158,0.22);
  --binder:#231609;
  --binder-raised:#2E1D0C;
  --binder-line:#4A3620;
  --radius:6px;
  --shadow: 0 10px 28px rgba(0,0,0,0.5);
  /* Button scale -- see the "Buttons: one system" block below. These are
     the ONLY place a button height/padding/type-size is decided; nothing
     downstream re-specifies them per class. Mobile-first: the base values
     are the at-the-table touch sizes (>=44px for everything you tap
     mid-combat); the min-width:821px block near the bottom of this file
     tightens all three sizes for mouse use in one place, instead of the
     dozen scattered per-class @media overrides this used to take. */
  --btn-h:48px;    --btn-px:16px;  --btn-fs:14px;
  --btn-h-sm:44px; --btn-px-sm:13px; --btn-fs-sm:12.5px;
  --btn-h-xs:44px; --btn-px-xs:10px; --btn-fs-xs:12px;
  --btn-radius:6px;
  --btn-gap:7px;
  --btn-icon:16px;
  /* Interactive bits living inside a .chip (condition value steppers,
     the remove-X). A condition chip in the tracker is a control, not a
     label, so on a phone it matches .btn-sm's height and its two
     stacked arrows split that height between them. */
  --chip-btn:32px;
  --chip-step-w:28px; --chip-step-min:16px;
  /* Initiative box + its stepper pair (Combat tracker). */
  --init-box:48px; --init-fs:16px; --init-arrow-w:30px;
  /* Proficiency ranks: one consistent color per rank everywhere a rank
     appears (Untrained/Trained/Expert/Master/Legendary), app-wide -- not
     just wherever this happened to get built first. Deliberately NOT
     reusing --brass (already means "clickable link" throughout the app)
     for Legendary, to avoid exactly the rank-color/link-color confusion
     this system exists to prevent. */
  --rank-untrained:#8B93A0;
  --rank-untrained-wash:rgba(139,147,160,0.16);
  --rank-trained:#6B8F5A;
  --rank-trained-wash:rgba(107,143,90,0.18);
  --rank-expert:#4F6E9E;
  --rank-expert-wash:rgba(79,110,158,0.2);
  --rank-master:#8B5FBF;
  --rank-master-wash:rgba(139,95,191,0.2);
  --rank-legendary:#D4AF37;
  --rank-legendary-wash:rgba(212,175,55,0.2);
}
[data-theme="light"]{
  --ink:#F7EFDC;
  --panel:#FBF6E9CC;
  --panel-solid:#FBF6E9;
  --panel-raised:#F1E6C9;
  --well:#F6EFD6;
  --line:#C9B888;
  --parchment:#5C3D12;
  --text:#241A0C;
  --muted:#4A3C24;
  --brass:#7A5420;
  --brass-dim:#9C6E2E;
  --brass-wash:rgba(122,84,32,0.14);
  --ember:#8B3A2B;
  --ember-dim:#B0503A;
  --ember-wash:rgba(139,58,43,0.14);
  --verdant:#4E6B3C;
  --sapphire:#33475F;
  --sapphire-wash:rgba(51,71,95,0.14);
  --binder:#8A6B3F;
  --binder-raised:#977847;
  --binder-line:#6E5330;
  --shadow: 0 10px 28px rgba(80,60,20,0.18);
  --rank-untrained:#5A5240;
  --rank-untrained-wash:rgba(90,82,64,0.14);
  --rank-trained:#4E6B3C;
  --rank-trained-wash:rgba(78,107,60,0.16);
  --rank-expert:#33475F;
  --rank-expert-wash:rgba(51,71,95,0.16);
  --rank-master:#6B3F94;
  --rank-master-wash:rgba(107,63,148,0.16);
  --rank-legendary:#8A6A12;
  --rank-legendary-wash:rgba(138,106,18,0.16);
}
*{box-sizing:border-box;}
html,body{height:100%;}
body{
  margin:0;
  display:flex; flex-direction:column; height:100dvh;
  background:var(--ink);
  background-image:
    repeating-linear-gradient(45deg, rgba(140,150,168,0.04) 0px, rgba(140,150,168,0.04) 1px, transparent 1px, transparent 10px),
    repeating-linear-gradient(-45deg, rgba(140,150,168,0.04) 0px, rgba(140,150,168,0.04) 1px, transparent 1px, transparent 10px);
  color:var(--text);
  font-family:'Source Serif 4',serif;
  -webkit-font-smoothing:antialiased;
  overflow:hidden;
}
[data-theme="light"] body{
  background-image:
    repeating-linear-gradient(45deg, rgba(122,84,32,0.028) 0px, rgba(122,84,32,0.028) 1px, transparent 1px, transparent 10px),
    repeating-linear-gradient(-45deg, rgba(122,84,32,0.028) 0px, rgba(122,84,32,0.028) 1px, transparent 1px, transparent 10px);
}
h1,h2,h3,.display{font-family:'Fraunces',serif; letter-spacing:0.01em; color:var(--parchment);}
.mono{font-family:'JetBrains Mono',monospace;}
button{font-family:inherit;}
::selection{background:var(--brass-dim); color:var(--ink);}

/* ---------- App shell ---------- */
#app{display:flex; flex:1; min-height:0;}

.rail{
  width:220px; flex-shrink:0;
  background:linear-gradient(180deg,var(--binder-raised),var(--binder));
  border-right:2px solid var(--binder-line);
  box-shadow:inset -10px 0 16px -12px rgba(0,0,0,0.55);
  display:flex; flex-direction:column;
  padding:20px 14px;
  min-height:0; overflow-y:auto;
}
.rail::-webkit-scrollbar{width:8px;} .rail::-webkit-scrollbar-thumb{background:var(--binder-line);border-radius:6px;}
.brand{display:flex; align-items:center; gap:10px; padding:4px 8px 22px; border-bottom:1px solid var(--binder-line); margin-bottom:16px;}
.brand-mark{width:30px;height:30px; flex-shrink:0;}
.brand-text{font-family:'Fraunces',serif; font-weight:700; font-size:19px; color:var(--brass-dim); line-height:1;}
[data-theme="light"] .brand-text{color:#F2E7CC;}
.brand-sub{font-size:10px; letter-spacing:0.14em; text-transform:uppercase; color:#B3A484; margin-top:3px;}
[data-theme="light"] .brand-sub{color:#EFE1BE;}

.nav-item{
  display:flex; align-items:center; gap:12px;
  padding:11px 12px; margin-bottom:4px;
  border-radius:4px; cursor:pointer; color:#CBBC9C;
  font-size:14px; font-weight:500;
  border:1px solid transparent;
  transition:background .15s, color .15s;
  position:relative;
}
[data-theme="light"] .nav-item{color:#F5EAD0;}
.nav-item svg{width:18px;height:18px; flex-shrink:0;}
.nav-item:hover{background:rgba(255,255,255,0.06); color:#F0E6CC;}
.nav-item.active{background:var(--brass); color:var(--ink); border-color:var(--brass); font-weight:700;}
[data-theme="light"] .nav-item.active{color:#FBF6E9;}
.nav-item.active::before{
  content:""; position:absolute; left:-14px; top:50%; transform:translateY(-50%);
  width:4px; height:22px; background:var(--brass-dim); border-radius:0;
}
.rail-foot{margin-top:auto; padding:10px 8px; font-size:11px; color:#A8987A; line-height:1.5; border-top:1px solid var(--binder-line);}
.nav-section-label{
  font-size:10px; text-transform:uppercase; letter-spacing:0.09em; font-weight:700;
  color:#8A7A57; padding:14px 12px 6px;
}
[data-theme="light"] .nav-section-label{color:#D9C79A;}
.nav-item:first-of-type, .brand + .nav-item{margin-top:0;}

/* scroll-padding-top keeps anything scrolled INTO view clear of the sticky
   combat bar. Without it the browser aligns a target to the top of the
   scrollport, which is underneath the bar, so scrollIntoView and anchor
   jumps landed on content the bar was covering. Sized for the strip plus
   main's own top padding; harmless on tabs that have no sticky bar. */
.main{flex:1; overflow-y:auto; padding:20px 26px 80px; scroll-padding-top:88px;}
.main::-webkit-scrollbar{width:10px;} .main::-webkit-scrollbar-thumb{background:var(--panel-raised);border-radius:6px;}

.topline{display:flex; align-items:baseline; justify-content:space-between; margin-bottom:16px; flex-wrap:wrap; gap:10px;}
/* Page-header action strips are inline-styled `display:flex` divs with
   no wrap of their own. Buttons no longer wrap their own labels (see the
   .btn block), so a two-button strip like Characters' "Import from
   Pathbuilder" + "New Character" has to be allowed to wrap as a strip
   instead -- otherwise it overflows a 375px screen. */
.topline > div{flex-wrap:wrap; justify-content:flex-end;}
.topline h1{font-size:26px; margin:0; font-weight:600;}
.topline p{color:var(--muted); font-size:13px; margin:4px 0 0;}

.home-hero{text-align:center; padding:14px 0 32px;}
.home-hero-mark{width:52px; height:52px; margin-bottom:12px;}
.home-hero h1{font-size:34px; margin:0 0 6px; font-weight:700;}
.home-hero p{color:var(--muted); font-size:14px; margin:0;}
/* Section boxes: a colored header bar (small icon + title) over a dense
   two-column list of icon+label rows -- the Terraria-wiki category-box
   pattern, translated into Ledger's own brass/ink identity rather than
   copying Terraria's brown palette. One box per category, stacked in a
   loose two-column masonry-ish flow on wide screens via .home-grid so the
   page doesn't read as one long single-file list. */
.home-grid{column-count:2; column-gap:14px;}
.home-section{
  background:var(--panel-solid); border:1px solid var(--line); border-radius:8px;
  margin-bottom:14px; overflow:hidden; break-inside:avoid;
  display:inline-block; width:100%;
}
.home-section-bar{
  display:flex; align-items:center; gap:8px; padding:9px 14px;
  background:var(--brass-wash); border-bottom:1px solid var(--brass-dim);
}
.home-section-bar-icon{width:18px; height:18px; color:var(--brass); flex-shrink:0;}
.home-section-bar-icon svg{width:100%; height:100%;}
.home-section-bar-title{font-size:12.5px; font-weight:700; text-transform:uppercase; letter-spacing:0.06em; color:var(--brass);}
.home-section-bar-meta{margin-left:auto; font-size:11px; color:var(--muted); text-transform:none; letter-spacing:normal; font-weight:500;}
.home-row-list{display:grid; grid-template-columns:1fr 1fr; gap:0; padding:6px;}
.home-row{
  display:flex; align-items:center; gap:8px; padding:7px 10px; border-radius:5px;
  cursor:pointer; font-size:13px; color:var(--text); transition:background .15s;
}
.home-row:hover{background:var(--brass-wash);}
.home-row-icon{width:16px; height:16px; color:var(--brass); flex-shrink:0;}
.home-row-icon svg{width:100%; height:100%;}
.home-row-label{flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;}
.home-row-count{font-size:11px; color:var(--muted); flex-shrink:0; margin-left:6px;}
.home-updates-body{padding:12px 16px;}

/* bottom nav for mobile */
.bottom-nav{display:none;}

/* ---------- shared bits ---------- */
.card{background:var(--panel-solid); border:1px solid var(--line); border-radius:var(--radius); padding:16px;}
/* ---------- Buttons: one system ----------
   Every button in the app is a `.btn`, described by two independent
   axes plus one shape:

     size    (none) | .btn-sm | .btn-xs      height + type scale
     intent  (none) neutral
             | .btn-brass   primary / commit
             | .btn-ghost   quiet, secondary
             | .btn-danger  destructive
     shape   .icon-btn                       square, icon only

   Write the classes in that order -- `btn btn-ghost btn-sm`. (Both
   orders work; the app had 25 `btn btn-sm btn-ghost` and 8
   `btn btn-ghost btn-sm` for the same thing, which is how you can tell
   there was no system.)

   Height comes from --btn-h*, never from padding, so a button with an
   icon, a button with a long label and a button with neither all land
   on the same height. Two things used to break that, and they are what
   "the buttons look uneven" actually was:

   1. A bare inline `<svg viewBox=...>` (no width/height attribute) has
      no CSS intrinsic size, so inside `.btn`'s own display:inline-flex
      it stretched to fill the line box. Measured on the Combat toolbar:
      Start Combat's icon 30.2px, Next Turn's 33.1px, Add Combatant's
      26.6px, Clear All's 19.8px -- four different glyph sizes in two
      adjacent rows, inflating those buttons to 58px and 44px tall.
      This is the SAME bug already fixed once for `.icon-btn svg`
      (see the 2026-07-31 note in DESIGN-NOTES); `.btn svg` and
      `.chip button svg` never got the same treatment, and the chip's
      remove-X was rendering at literally 0x0 -- an invisible,
      un-tappable button for removing a condition mid-fight.
   2. Nothing stopped labels wrapping, so an identical
      `btn btn-ghost btn-sm` measured 52px in the Combat toolbar and
      35px in the row below it. white-space:nowrap + min-height fixes
      both at once.

   min-height (not height) so the handful of buttons that deliberately
   hold two lines -- the encounter picker rows -- still grow. */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--btn-gap);
  min-height:var(--btn-h); padding:4px var(--btn-px);
  background:var(--panel-raised); color:var(--text); border:1px solid var(--line);
  border-radius:var(--btn-radius); font-size:var(--btn-fs); font-weight:500;
  font-family:'Source Serif 4',serif; line-height:1.2; white-space:nowrap;
  cursor:pointer; flex-shrink:0;
  transition:background .15s, border-color .15s, transform .05s;
}
.btn svg{width:var(--btn-icon); height:var(--btn-icon); flex-shrink:0;}
/* Two-part list rows (`justify-content:space-between`, name + meta)
   must not push their own button wider than its container. */
.btn > span{min-width:0; overflow:hidden; text-overflow:ellipsis;}
.btn:hover{background:var(--well); border-color:var(--brass-dim);}
.btn:active{transform:scale(0.97);}
.btn-brass{background:var(--brass); color:var(--ink); border-color:var(--brass); font-weight:700;}
[data-theme="light"] .btn-brass{color:#FBF6E9;}
.btn-brass:hover{background:var(--brass-dim);}
.btn-ghost{background:transparent; border-color:var(--line);}
.btn-danger{background:transparent; color:var(--ember); border-color:var(--ember-dim);}
.btn-danger:hover{background:var(--ember-wash);}
.btn-sm{min-height:var(--btn-h-sm); padding:3px var(--btn-px-sm); font-size:var(--btn-fs-sm);}
.btn-sm svg{width:14px; height:14px;}
/* .btn-xs is the size the one-off `.roll-btn` and `.hp-btn` classes were
   hand-rolling: a dense inline control inside a stat block or an HP row.
   On a phone it is the SAME 44px height as .btn-sm on purpose -- these
   are exactly the controls Bryan taps mid-combat, so they get a real
   touch target and express their lower rank through type size and width
   instead of through height. Desktop pulls them apart again. */
.btn-xs{min-height:var(--btn-h-xs); padding:2px var(--btn-px-xs); font-size:var(--btn-fs-xs); gap:5px; border-radius:5px;}
.btn-xs svg{width:13px; height:13px;}
.btn:disabled{opacity:0.4; cursor:not-allowed;}
/* Square icon-only button. Same height as .btn-sm so an icon button and
   a labelled button sitting in the same strip line up exactly. */
.icon-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:var(--btn-h-sm); height:var(--btn-h-sm); padding:0;
  background:none; border:1px solid transparent; border-radius:var(--btn-radius);
  color:var(--muted); cursor:pointer; flex-shrink:0;
  transition:background .15s, border-color .15s, color .15s;
}
.icon-btn:hover{color:var(--text); background:var(--brass-wash);}
/* See note 1 above -- a bare inline <svg> has no intrinsic size and
   collapses (or balloons) as a flex item without this. */
.icon-btn svg{width:var(--btn-icon); height:var(--btn-icon); flex-shrink:0;}
/* Icon button that also needs to read as a discrete control (sitting
   loose in a row of labelled buttons rather than in an action strip). */
.icon-btn.bordered, .quick-adjust-row .icon-btn{border-color:var(--line); background:var(--panel-raised);}
.quick-adjust-row .icon-btn:hover{border-color:var(--brass-dim); color:var(--brass);}
/* Encounter Builder monster-quantity stepper (2026-08-05): a bare
   type="number" input needed the on-screen keyboard just to bump a
   count by one -- the field Bryan flagged as "clunky" is exactly the
   one a GM adjusts most during a fight (adds joining, one of a Swarm
   dying). Same .icon-btn size as its row-neighbors (Reroll/Duplicate/
   Remove) so it doesn't read as a smaller, lesser control. Typing a
   number directly still works -- this adds the tap affordance, it
   doesn't replace direct entry. */
.qty-stepper{display:flex; align-items:center; gap:2px; flex-shrink:0; border:1px solid var(--line); border-radius:var(--btn-radius); background:var(--panel-raised);}
.qty-stepper .icon-btn{border:none; background:none;}
.qty-stepper input{width:30px; flex-shrink:0; text-align:center; padding:0; border:none; background:none; -moz-appearance:textfield;}
.qty-stepper input:focus{box-shadow:none;}
/* Monster row Lvl/Qty labels (2026-08-06): Bryan flagged the two bare
   numbers as unclear -- a placeholder/title tooltip isn't a substitute
   for a real label on mobile, where there's no hover. Deliberately NOT
   the base `label` rule (11px + 5px margin-bottom reads fine above a
   full-width field but is too tall stacked on a compact 44px-wide one in
   an already-dense row) -- smaller and tighter, just enough to remove
   the ambiguity without growing the row's height noticeably. */
.mini-field-label{font-size:9px; text-transform:uppercase; letter-spacing:0.04em; color:var(--muted); display:block; margin-bottom:2px; font-weight:600;}

input,select,textarea{
  background:var(--well); border:1px solid var(--line); color:var(--text);
  border-radius:5px; padding:8px 10px; font-size:13.5px; font-family:inherit; width:100%;
}
input:focus,select:focus,textarea:focus{outline:none; border-color:var(--brass-dim); box-shadow:0 0 0 3px var(--brass-wash);}
/* Checkboxes and radios are not text fields, and the shared rule above
   gives every input width:100%. A checkbox inside a flex label therefore
   claimed the whole row -- measured 142px wide in the Flag a Problem form
   at 375px -- shoving its own label text to x=196 and clipping the longer
   options off the right edge. Every checkbox in that form rendered with no
   readable label. */
input[type=checkbox], input[type=radio]{
  width:18px; height:18px; flex:0 0 auto; padding:0; min-width:0;
  accent-color:var(--brass); cursor:pointer;
}
/* A checklist option is a sentence, not a field heading. The global label
   rule uppercases and letter-spaces every label, which on real option text
   ("Missing or incomplete description") both shouts and inflates the line
   width enough to make the overflow above worse. */
.check-option{
  display:flex; align-items:center; gap:10px;
  font-size:13.5px; text-transform:none; letter-spacing:normal;
  color:var(--text); font-weight:400; margin-bottom:0;
  cursor:pointer; min-height:32px;
}
input[type=number]{-moz-appearance:textfield;}
input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none; margin:0;}
label{font-size:11px; text-transform:uppercase; letter-spacing:0.06em; color:var(--muted); display:block; margin-bottom:5px; font-weight:600;}
.field{margin-bottom:12px;}
.row{display:flex; gap:10px;}
.row > *{flex:1;}
.grid{display:grid; gap:14px;}
.grid-2{grid-template-columns:1fr 1fr;}
.grid-3{grid-template-columns:1fr 1fr 1fr;}
.grid-4{grid-template-columns:repeat(4,1fr);}
.chip{
  display:inline-flex; align-items:center; gap:5px; padding:4px 9px; border-radius:16px;
  background:var(--brass-wash); border:1px solid var(--brass-dim); color:var(--brass); font-size:11.5px; font-weight:600;
}
.chip.danger{background:var(--ember-wash); border-color:var(--ember-dim); color:var(--ember);}
.rank-tag{font-size:9px; text-transform:uppercase; letter-spacing:0.03em; opacity:0.7; margin-right:1px;}
/* Proficiency rank chips -- visually distinct from Trait chips (.chip)
   on purpose: Traits are all one color (brass) by AoN convention, so a
   glance at chip COLOR alone tells you which system you're looking at.
   Ranks are color-coded by rank instead (grey/green/blue/purple/gold),
   never brass, so the two never get confused with each other even
   though both use the same rounded-pill shape. */
.rank-chip{
  display:inline-flex; align-items:center; gap:5px; padding:4px 9px; border-radius:16px;
  font-size:11.5px; font-weight:600; border:1px solid; white-space:nowrap;
}
.rank-chip .rank-tag{opacity:0.85;}
.rank-chip.rank-untrained{background:var(--rank-untrained-wash); border-color:var(--rank-untrained); color:var(--rank-untrained);}
.rank-chip.rank-trained{background:var(--rank-trained-wash); border-color:var(--rank-trained); color:var(--rank-trained);}
.rank-chip.rank-expert{background:var(--rank-expert-wash); border-color:var(--rank-expert); color:var(--rank-expert);}
.rank-chip.rank-master{background:var(--rank-master-wash); border-color:var(--rank-master); color:var(--rank-master);}
.rank-chip.rank-legendary{background:var(--rank-legendary-wash); border-color:var(--rank-legendary); color:var(--rank-legendary);}
.rank-chip.term-link{cursor:pointer;}
.rank-chip.term-link:hover{filter:brightness(1.15);}
/* The chip's own remove-X. Was `padding:0` around a bare inline <svg>
   with no intrinsic size, so it measured 0x13px -- a zero-width,
   effectively un-tappable control for dropping a condition mid-fight.
   Same root cause as the .icon-btn/.btn svg bug documented above. */
.chip button{
  display:inline-flex; align-items:center; justify-content:center;
  background:none; border:none; color:inherit; cursor:pointer;
  font-size:13px; line-height:1; padding:0; flex-shrink:0;
  width:var(--chip-btn); height:var(--chip-btn); border-radius:50%;
}
.chip button:hover{background:var(--brass-wash);}
.chip button svg{width:12px; height:12px; flex-shrink:0;}
.divider{height:1px; background:var(--line); margin:18px 0;}
.empty{text-align:center; padding:50px 20px; color:var(--muted);}
.empty svg{width:42px;height:42px; opacity:0.35; margin-bottom:12px;}
.empty p{font-size:13.5px; max-width:340px; margin:6px auto 0;}
/* Above .fullscreen-view's z-index:150 on purpose -- a rabbit-hole
   card opened from a full page's own body text must render on top of
   that page, not behind it. */
.modal-overlay{position:fixed; inset:0; background:rgba(5,6,9,0.8); backdrop-filter:blur(2px); display:flex; align-items:flex-start; justify-content:center; padding:5vh 16px; z-index:160; overflow-y:auto;}
/* Center short cards for easier reach/swipe; "safe" falls back to the
   flex-start above (never crops the top) when content is taller than
   the viewport, so tall scrolling cards keep their existing behavior. */
.modal-overlay{align-items:safe center;}
.fullscreen-view{position:fixed; inset:0; z-index:150; background:var(--ink); overflow-y:auto;}
/* "Back to my character," app-wide (Bryan, 2026-08-07) -- bottom-left, distinct corner
   from the wizard's own .cc-preview-fab (bottom-right, and only ever present during a
   build anyway). z-index above .modal-overlay's own 160, on purpose: has to stay
   reachable "regardless of how deep someone's rabbit-holed" through Overlay cards. */
.my-char-fab{position:fixed; bottom:20px; left:20px; z-index:200; width:52px; height:52px; border-radius:50%; background:var(--brass); border:none; color:var(--ink); box-shadow:var(--shadow); align-items:center; justify-content:center; cursor:pointer;}
.my-char-fab svg{width:24px; height:24px; display:block;}
.my-char-fab:hover{filter:brightness(1.08);}
@media (max-width:600px){
  .my-char-fab{bottom:calc(80px + env(safe-area-inset-bottom)); left:16px; width:46px; height:46px;}
  .my-char-fab svg{width:20px; height:20px;}
}
.fullscreen-header{
  position:sticky; top:0; z-index:2; display:flex; justify-content:space-between; align-items:center;
  padding:14px 20px; background:var(--panel-solid); border-bottom:1px solid var(--line);
}
.fullscreen-body{max-width:760px; margin:0 auto; padding:24px 20px 60px;}
.fullscreen-body h1{font-size:24px;}
.modal{background:var(--panel-solid); border:1px solid var(--line); border-radius:8px; padding:22px; width:100%; max-width:560px; box-shadow:var(--shadow); margin-bottom:5vh;}
.modal h2{margin:0 0 16px; font-size:19px;}
/* .icon-btn now lives with the rest of the button system, above --
   including the `.icon-btn svg` explicit-size rule from the 2026-07-31
   invisible-glyph bug, which .btn and .chip button now share. */
/* Inline action-cost glyphs substituted in for "[one-action]"-style text
   by formatActionIcons() (glossary.js). display:inline-block (not flex)
   so it sits inline within a paragraph like a word would; explicit
   height with width:auto keeps the 2-/3-action icons' wider viewBox
   from squashing -- same lesson as the .icon-btn svg bug above, a bare
   svg here would otherwise collapse to 0 the same way. */
.action-icon{display:inline-block; vertical-align:-2px; margin:0 1px;}
.action-icon svg{height:14px; width:auto; display:block;}
.search-input{position:relative;}
.badge{font-size:10px; padding:2px 7px; border-radius:4px; font-weight:700; text-transform:uppercase; letter-spacing:0.04em;}
.badge.pc{background:var(--sapphire-wash); color:var(--sapphire);}
.badge.monster{background:var(--ember-wash); color:var(--ember);}
/* Solid brass fill, not the wash the other badges use -- this one has to
   read at a glance across a table, and it's the only badge that's ever
   filled, so "the filled one is up now" needs no legend. */
.badge.turn{background:var(--brass); color:var(--ink);}
[data-theme="light"] .badge.turn{color:#FBF6E9;}
.badge.dmpc{background:var(--ember-wash); color:var(--ember);}
.badge.neutral,.badge.unused{background:transparent; border:1px solid var(--line); color:var(--muted);}

/* Characters > Party board -- one card per group (Active Party/DMPC/
   Neutral/Unused), each a flat list of rows with a group <select> per
   row. See partyGroupOf() in app/characters.js for why this is a select
   instead of drag-and-drop. */
.party-group{margin-bottom:14px;}
.party-group-head{display:flex; align-items:baseline; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:8px;}
.party-group-head h3{margin:0; font-size:15px; font-family:'Fraunces',serif; display:flex; align-items:baseline; gap:6px;}
.party-group-head .combatant-meta{margin:0;}
.party-row{display:flex; align-items:center; justify-content:space-between; gap:10px; padding:9px 0; border-top:1px solid var(--line);}
.party-row:first-child{border-top:none;}
.party-row-info{display:flex; flex-direction:column; gap:2px; cursor:pointer; min-width:0; flex:1;}
.party-row-info strong{font-size:13.5px; font-weight:600;}
.party-row select{width:auto; flex:0 0 auto; font-size:12.5px; padding:5px 8px;}

/* ---------- Combat ---------- */
/* Only the primary band is sticky -- see Combat.view()'s comment for why
   the toolbar is split in two. Kept opaque and given a little horizontal
   padding so rows scrolling underneath never bleed out at either edge. */
.combat-toolbar{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:8px; position:sticky; top:0; z-index:10; background:var(--panel-solid); padding:8px 4px; border-radius:6px;}
/* main is the scroll container and carries padding:20px 16px 28px. A sticky
   element pins to the PADDING BOX, so top:0 parks the banner 20px below the
   visible top edge -- and rows keep scrolling up through that 20px band in
   full view above it. Reproduced: at scrollTop 1400, elementFromPoint just
   above the banner returned an HP stepper button, which is what showed up in
   the screenshots. Not a z-index problem; the banner was already z-index:10.
   This paints the band with the banner's own background, spread past main's
   16px side padding so nothing slips by at the edges. */
.combat-toolbar::before{
  content:""; position:absolute; bottom:100%; left:-16px; right:-16px; height:20px;
  background:var(--panel-solid);
}
/* Round / Start Combat / Next Turn stays readable while a stat-block card
   is open -- you're reading a monster in order to run its turn, so losing
   the turn controls behind the card is exactly the wrong trade.
   .modal-overlay is position:fixed; inset:0; z-index:160 and covers the
   whole viewport, so this has to do two things: lift the banner above that
   layer, and inset the card so it starts below the banner rather than
   underneath it. --combat-banner-h is published by Combat.afterRender(),
   measured from the live element because the banner wraps to a second row
   at narrow widths and its height is not a constant. */
/* While a card is open the banner is pinned to the viewport, not merely
   raised. `position:sticky` was never enough here: the toolbar lives
   inside .combat-list, and on mobile .combat-sidepanel has order:-1, so a
   whole Current Turn stat block renders ABOVE it and pushes it down the
   page -- measured at y=615 on an 812px screen. It only becomes sticky
   once you scroll past that panel, so with a card open it was floating
   across the middle of the card and chopping whatever sat behind it (the
   title in one screenshot, the Adjust/Reset row in another). Fixed to the
   top means its position is knowable, which is what lets the overlay inset
   below it reliably. */
body.combat-card-open .combat-toolbar{
  position:fixed; top:0; left:0; right:0; z-index:170;
  margin:0; border-radius:0;
}
body.combat-card-open .combat-toolbar::before{content:none;}
/* Inset the overlay below the banner rather than padding it. Padding only
   sets where the card STARTS: the overlay is its own scroll container, so
   once you scrolled the card its content travelled straight back up under
   the banner and got chopped -- the title and the Adjust/Reset/Full Entry
   row disappearing behind it. Moving the overlay's top edge down means the
   banner is outside its scroll box entirely, so no amount of scrolling can
   put content under it. */
body.combat-card-open .modal-overlay{
  top: var(--combat-banner-h, 0px);
  padding-top:10px;
  align-items:flex-start;
}
.combat-toolbar-more{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:18px; padding-left:4px;}
.combat-layout{display:flex; gap:22px; align-items:flex-start;}
.combat-list{flex:1; min-width:280px;}
.combat-sidepanel{width:360px; flex-shrink:0; position:sticky; top:20px;}
.combat-sidepanel.layout-side{width:560px;}
.layout-toggle{display:flex; gap:6px; margin-bottom:10px; justify-content:flex-end;}
/* A toggle, not a button -- but it still sits on the same height scale
   so it doesn't read as a fourth arbitrary size. */
.layout-toggle button{
  min-height:var(--btn-h-xs); padding:0 var(--btn-px-xs); border-radius:var(--btn-radius);
  border:1px solid var(--line); background:var(--panel-raised);
  color:var(--muted); cursor:pointer; font-size:var(--btn-fs-xs); display:flex; align-items:center; justify-content:center; gap:4px;
  font-family:'Source Serif 4',serif; white-space:nowrap;
}
.layout-toggle button.active{background:var(--brass-wash); border-color:var(--brass-dim); color:var(--brass);}
.sidepanel-panels{display:flex; flex-direction:row; gap:14px; align-items:flex-start;}
.layout-side .sidepanel-window{flex:1; min-width:0;}
.layout-stack .sidepanel-panels{flex-direction:column; height:calc(100vh - 100px);}
.layout-stack .sidepanel-window{flex:1 1 0; min-height:0; max-height:none;}
.sidepanel-window{
  background:var(--panel-solid); border:1px solid var(--line); border-radius:6px;
  flex:1 1 0; min-height:0; max-height:calc(100vh - 150px); overflow-y:auto; display:flex; flex-direction:column;
}
.sidepanel-window-head{padding:14px 16px 8px; position:sticky; top:0; background:var(--panel-solid); z-index:1;}
.sidepanel-window-head h3{margin:2px 0 0; font-size:16px; font-family:'Fraunces',serif;}
.sidepanel-window-body{padding:0 16px 16px;}
.sidepanel-heading{font-size:10.5px; text-transform:uppercase; letter-spacing:0.08em; color:var(--brass); font-weight:700;}
.combatant.selected-turn{border-color:var(--sapphire);}
.combatant.selected-turn::before{background:var(--sapphire);}
/* Sits in the sticky toolbar next to Start Combat / Next Turn, so it
   takes the same height token -- it used to be padding-sized and ended
   up a different height from the buttons beside it. */
.round-badge{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:var(--btn-h); padding:0 var(--btn-px);
  font-family:'Fraunces',serif; font-weight:700; font-size:15px; color:var(--brass);
  background:var(--brass-wash); border:1px solid var(--brass-dim); border-radius:var(--btn-radius);
  white-space:nowrap;
}
.combatant{
  display:flex; align-items:center; gap:8px; background:var(--panel-solid);
  border:1px solid var(--line); border-radius:6px; padding:9px 12px 9px 24px; margin-bottom:7px;
  position:relative; transition:border-color .15s, background .15s; cursor:pointer;
}
.combatant.active-turn{border-color:var(--brass); background:var(--brass-wash); box-shadow:0 0 0 1px var(--brass), 0 4px 12px rgba(212,160,74,0.15);}
.combatant.active-turn::before{
  content:""; position:absolute; left:-1px; top:50%; transform:translateY(-50%);
  width:6px; height:80%; background:var(--brass); border-radius:0 4px 4px 0;
}
.combatant.down{opacity:0.55;}
.combatant.ready-hold{opacity:0.7; border-style:dashed; border-color:var(--brass-dim);}
.ready-badge{
  position:absolute; right:8px; top:4px; font-size:8px; font-weight:700;
  letter-spacing:0.1em; color:var(--brass); background:var(--brass-wash);
  padding:1px 5px; border-radius:3px; border:1px solid var(--brass-dim);
}
.monster-num{
  position:absolute; left:4px; top:6px; font-family:'JetBrains Mono'; font-weight:700;
  font-size:10px; color:var(--muted); width:16px; text-align:center;
}
.init-box{
  width:var(--init-box); height:var(--init-box); border-radius:50%; background:var(--well); border:1.5px solid var(--line);
  display:flex; align-items:center; justify-content:center; text-align:center; font-family:'JetBrains Mono'; font-weight:700;
  font-size:var(--init-fs); color:var(--text); flex-shrink:0; padding:0;
}
.init-stack{display:flex; align-items:center; gap:4px; flex-shrink:0;}
.init-col{display:flex; flex-direction:column; align-items:center; gap:4px; flex-shrink:0;}
/* Deliberate exception to the 44px rule: this is a stepper pair bolted
   to a directly-editable number input, and giving each arrow its own
   44px would make the initiative column 90px tall and shove the name
   and HP off the row entirely. Instead the pair spans the full height
   of the init box beside it, so together they are a 44px+ target split
   in two, and the box itself stays typeable. Was 14x13px. */
.init-arrows{display:flex; flex-direction:column; gap:2px; height:var(--init-box);}
.init-arrows button{
  width:var(--init-arrow-w); flex:1 1 0; border:1px solid var(--line);
  background:var(--panel-raised); color:var(--muted);
  cursor:pointer; font-size:9px; line-height:1; padding:0; border-radius:4px;
}
.init-arrows button:hover{color:var(--brass); border-color:var(--brass-dim);}
/* Condition value stepper (Frightened 2 -> 1). Was 9x7px on desktop and
   18x13px on a phone -- below any usable tap target for something you
   adjust every single round. Now the pair fills the chip's full height,
   so each half is (chip height / 2) tall by --chip-step-w wide. */
.chip-arrows{display:inline-flex; flex-direction:column; gap:0; margin:0 1px; vertical-align:middle; flex-shrink:0; align-self:stretch;}
.chip-arrows button{
  width:var(--chip-step-w); flex:1 1 0; min-height:var(--chip-step-min);
  font-size:9px; border:none; background:none; color:inherit; opacity:0.75;
  cursor:pointer; padding:0; line-height:1; border-radius:4px;
}
.chip-arrows button:hover{opacity:1; background:var(--brass-wash);}
.combatant-name{font-weight:600; font-size:13px; color:var(--text); display:flex; align-items:center; gap:5px; flex-wrap:wrap;}
/* The name is the row's primary control -- it opens the monster's stat
   block -- so it reads as the headline of the card rather than a label
   above the HP bar. Styled as text, not a button: a bordered control here
   would compete with the real buttons on the row, and the row already has
   enough of those. */
.combatant-name-btn{
  background:none; border:none; padding:0; margin:0; cursor:pointer; text-align:left;
  font-family:'Source Serif 4',serif; font-size:17px; font-weight:600; line-height:1.25;
  color:var(--text); letter-spacing:0.01em;
}
.combatant-name-btn:hover{color:var(--brass);}
.combatant-name-btn:focus-visible{outline:2px solid var(--brass); outline-offset:2px; border-radius:3px;}
.combatant.active-turn .combatant-name-btn{color:var(--brass);}
.combatant-meta{font-size:11px; color:var(--muted); margin-top:1px;}
/* Book citation. Deliberately quieter and more bookish than
   .combatant-meta -- Bryan flagged the old "PF2e Monster Core · p.313"
   rendering as "good info but somehow weirdly immersion-breaking",
   reading like a database field rather than a source note. Italic,
   dimmer, and phrased as prose ("Monster Core, page 313") so it sits
   under the content as a footnote instead of labelling it. */
.source-note{font-size:10.5px; font-style:italic; color:var(--muted); opacity:0.8; letter-spacing:0.01em;}
/* A condition chip in the tracker is a control (value stepper + remove),
   not a trait label -- so it matches the `+ Condition` button sitting
   next to it exactly, instead of the old 40px-chip / 35px-button
   mismatch that made the row look ragged. */
.combatant-conditions{display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; align-items:center;}
.combatant-conditions .chip{min-height:var(--btn-h-sm); padding:2px 5px 2px 11px; gap:3px;}
.combatant-right{margin-left:auto; align-self:flex-start; display:flex; gap:4px; flex-shrink:0;}
/* The selected combatant's stat block, rendered inside its own row. Phone
   only -- desktop has the real side-by-side panel and doesn't need it, and
   showing both would render the same block twice. Hidden here rather than
   skipped in JS so rotating or resizing can't strand it. */
/* All 42 conditions, visible at once rather than hidden behind a native
   <select>. Scrolls inside the modal so a small screen doesn't push the
   value field and Add button off the bottom. */
.cond-chip-grid{
  display:flex; flex-wrap:wrap; gap:6px;
  max-height:42vh; overflow-y:auto; -webkit-overflow-scrolling:touch;
  padding:2px; margin-bottom:2px;
}
.cond-chip-grid .cond-chip{white-space:nowrap;}
.cond-chip-grid .cond-chip.active{
  background:var(--brass); color:var(--ink); border-color:var(--brass); font-weight:600;
}
[data-theme="light"] .cond-chip-grid .cond-chip.active{color:#FBF6E9;}
.hp-controls{display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:6px;}
.hp-input{width:56px; text-align:center; padding:6px 2px; font-size:14px; flex-shrink:0;}
/* The six HP quick-adjust buttons used to be a `.hp-btn` one-off
   (26x20px desktop / 36x32px phone) wrapping raggedly at the end of the
   HP row. They are ordinary .btn-xs buttons now, laid out in an explicit
   grid so they never wrap unevenly: two rows of three on a phone (all
   the +N on one line, all the -N on the next, which is also easier to
   hit without looking), one row of six inline with the HP field on
   desktop -- exactly where they used to sit, so the desktop row keeps
   its old height. `flex:1 1 100%` is what forces the phone break. */
.hp-steppers{display:grid; grid-template-columns:repeat(3,1fr); gap:5px; flex:1 1 100%; min-width:0;}
.hp-steppers .btn{padding:2px 4px; font-variant-numeric:tabular-nums;}
.hp-bar-wrap{width:100%; height:5px; background:var(--well); border-radius:3px; overflow:hidden; margin-top:6px;}
.hp-bar{height:100%; background:var(--verdant); transition:width .2s, background .2s;}
/* Hero Points -- three tappable stars (Pathbuilder reference, 2026-08-06), tap star N to
   set the total to N (a plain star-rating toggle: tapping the currently-topmost filled
   star drops it by one, same widget doing both increment and decrement in one gesture). */
.hero-point-stars{display:flex; gap:6px;}
.hero-point-star{background:none; border:none; padding:2px; cursor:pointer; color:var(--line); flex-shrink:0;}
.hero-point-star svg{width:26px; height:26px; display:block;}
.hero-point-star.filled{color:var(--brass);}
.hero-point-star:hover{color:var(--brass-dim);}
/* Compact T/E/M/L proficiency indicator (Pathbuilder density pattern #1, 2026-08-06) --
   4 dots, filled left-to-right up to the current rank. Denser than a full rank-word chip,
   used on the character sheet's Skills table. */
.prof-dots{display:inline-flex; gap:3px; vertical-align:middle;}
.prof-dot{width:8px; height:8px; border-radius:50%; background:var(--line); display:inline-block;}
.prof-dot.filled{background:var(--brass);}
/* Currency -- Platinum/Gold/Silver/Copper, real-world coin colors (Pathbuilder density
   pattern #4, 2026-08-06). One shared ICONS.coin recolored per denomination via CSS,
   rather than four separate detailed coin SVGs. */
.currency-row{display:flex; gap:10px; flex-wrap:wrap;}
.currency-field{display:flex; align-items:center; gap:5px;}
.coin-icon{width:18px; height:18px; flex-shrink:0;}
.coin-icon svg{width:18px; height:18px; display:block;}
.coin-platinum{color:#cfd8e3;}
.coin-gold{color:#d4af37;}
.coin-silver{color:#b0b0b0;}
.coin-copper{color:#b87333;}
.currency-input{width:56px; text-align:right; padding:4px 6px; font-size:13px;}
/* Dice-roll buttons inside a stat block. Formerly the `.roll-btn`
   one-off; now just `.btn .btn-xs .btn-ghost` with a brass hover, since
   that is all it ever was. Kept as a modifier only for the muted
   default colour that distinguishes "roll this" from "do this". */
.btn-roll{color:var(--muted);}
.btn-roll:hover{color:var(--brass); border-color:var(--brass-dim);}
.attack-roll-row{display:flex; flex-wrap:wrap; gap:5px; margin-top:5px;}
.attack-roll-row .btn{flex:1 1 auto; min-width:52px;}
.roll-toast{
  position:fixed; bottom:24px; right:24px; z-index:300; width:260px;
  background:var(--panel-solid); border:1px solid var(--brass-dim); border-radius:8px;
  padding:14px 16px; box-shadow:var(--shadow);
}
@media (max-width:820px){
  .chip{padding:6px 11px; font-size:12.5px;}
}

.entity-grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:14px;}
.wiki-table-wrap{overflow-x:auto; border:1px solid var(--line); border-radius:6px; background:var(--panel-solid);}
.wiki-table{width:100%; border-collapse:collapse; font-size:12.5px;}
.wiki-table thead th{
  text-align:left; padding:9px 12px; font-size:10.5px; text-transform:uppercase; letter-spacing:0.05em;
  color:var(--brass); border-bottom:1.5px solid var(--line); cursor:pointer; user-select:none; white-space:nowrap;
  background:var(--well);
}
.wiki-table thead th:hover{color:var(--brass-dim);}
.wiki-table tbody td{padding:8px 12px; border-bottom:1px solid var(--line); vertical-align:top; color:var(--text);}
.wiki-table tbody tr{cursor:pointer; transition:background .1s;}
.wiki-table tbody tr:last-child td{border-bottom:none;}
.wiki-table tbody tr:hover{background:var(--brass-wash);}
.wiki-table tbody tr:nth-child(even){background:rgba(255,255,255,0.02);}
[data-theme="light"] .wiki-table tbody tr:nth-child(even){background:rgba(0,0,0,0.02);}
.wiki-table tbody tr:nth-child(even):hover{background:var(--brass-wash);}
.wiki-table .wiki-name{font-weight:600; color:var(--parchment);}
/* display:flex on a <td> itself breaks its normal table-cell layout
   (height no longer matches sibling cells, vertical-align stops
   applying) -- keep the cell a plain table-cell and align the icon
   inline with the name text instead. */
.wiki-table .wiki-name.with-icon .game-icon{vertical-align:-3px; margin-right:7px;}
.entity-card{
  background:var(--panel-solid); border:1px solid var(--line); border-radius:6px; padding:16px;
  cursor:pointer; transition:border-color .15s, transform .1s;
  /* Flex column so .card-actions' margin-top:auto can pin the button row
     to the bottom. Grid already stretches cards in a row to equal height,
     so without this the buttons float wherever the text ends and the row
     of cards looks ragged. */
  display:flex; flex-direction:column;
}
.entity-card:hover{border-color:var(--brass-dim); transform:translateY(-1px);}
/* The flag and bookmark buttons share ONE row. They used to stack --
   BugReports.cardButton() emitted its own block-level div, so the sibling
   bookmark button was pushed onto a second line and every card carried
   88px of buttons under 36-68px of content. That was the dead whitespace
   on the Artifacts list, and it was in all four .entity-card tabs.
   margin-top:auto pins the row to the bottom so cards that DO differ in
   content height still line their buttons up. */
.card-actions{display:flex; justify-content:flex-end; align-items:center; gap:4px; margin-top:auto; padding-top:6px;}
.entity-card h3{margin:0 0 3px; font-size:16px; font-weight:600; font-family:'Fraunces',serif; display:flex; align-items:center; gap:7px;}
.entity-card .sub{font-size:12px; color:var(--muted); margin-bottom:10px;}
.game-icon{color:var(--brass-dim); opacity:0.85;}
.game-icon svg{width:100%; height:100%;}
.stat-strip{display:flex; gap:14px; font-family:'JetBrains Mono'; font-size:12.5px;}
.stat-strip div span{display:block; color:var(--muted); font-size:9.5px; text-transform:uppercase; letter-spacing:0.05em; margin-bottom:2px;}

/* ---------- Dice ---------- */
.die-grid{display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-bottom:18px;}
@media(max-width:520px){.die-grid{grid-template-columns:repeat(4,1fr);}}
.die-btn{
  aspect-ratio:1; display:flex; flex-direction:column; align-items:center; justify-content:center;
  background:var(--panel-raised); border:1.5px solid var(--line); border-radius:8px; cursor:pointer;
  color:var(--text); font-family:'JetBrains Mono'; font-weight:700; font-size:16px; gap:2px;
  transition:border-color .15s, background .15s;
}
.die-btn:hover{border-color:var(--brass); background:var(--brass-wash); color:var(--brass);}
.die-btn small{font-size:9px; color:var(--muted); font-weight:500; text-transform:uppercase;}
.roll-result{
  text-align:center; padding:26px 20px; background:var(--panel-solid); border:1px solid var(--line);
  border-radius:8px; margin-bottom:16px;
}
.roll-result .total{font-family:'Fraunces',serif; font-weight:700; font-size:52px; color:var(--brass); line-height:1;}
/* Quantity control above the die grid: one shared "how many" rather than
   a stepper per die (eight steppers doesn't fit a 375px screen, and the
   real use is "set 12, tap d12 once"). The die tiles relabel themselves
   to ×N so what a tap will do is visible before you make it. */
.dice-qty{display:flex; align-items:center; gap:8px; flex-wrap:wrap;}
/* The label takes its own row on purpose. With 44px thumb targets the
   controls alone fill a 375px screen, so letting the label share the line
   made it wrap at an arbitrary point and look broken. Own row = the same
   deliberate two-line shape at every width. */
.dice-qty label{
  flex:0 0 100%; margin:0 0 2px;
  font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.05em;
}
/* Reset pushes itself to the far end rather than crowding the stepper. */
.dice-qty .btn{margin-left:auto;}
/* 44px, not the ~34 this started at: these are thumb targets used at a
   table mid-combat, and 44 is the standard minimum. Measured at 375px
   afterwards to confirm the row still fits without wrapping. */
.qty-step{
  width:44px; height:44px; flex:0 0 44px; border-radius:8px; cursor:pointer;
  background:var(--panel-raised); border:1.5px solid var(--line); color:var(--brass);
  font-size:18px; font-weight:700; line-height:1; display:flex; align-items:center; justify-content:center;
  transition:border-color .15s, background .15s, transform .09s ease;
}
.qty-step:hover{border-color:var(--brass); background:var(--brass-wash);}
.qty-step:active{transform:scale(0.9);}
.qty-num{
  width:64px; text-align:center; font-size:16px; font-weight:700; padding:6px 4px;
  background:var(--panel-raised); border:1.5px solid var(--line); border-radius:8px; color:var(--text);
}
/* Spinners would sit on top of the real steppers and are unusable at
   thumb size -- the +/- buttons are the affordance. */
.qty-num::-webkit-outer-spin-button, .qty-num::-webkit-inner-spin-button{-webkit-appearance:none; margin:0;}
.qty-num{-moz-appearance:textfield; appearance:textfield;}

/* The rolling moment. Testers said the Dice screen "just pops a number"
   and didn't feel like rolling anything, so the total spins through
   plausible faces (Dice.animateRoll) and then lands with a bounce.
   .tumbling is the spinning state: dimmed and very slightly small, so the
   landing reads as the number arriving rather than just stopping.
   Deliberately cheap -- transform and opacity only, both compositor
   properties, because this runs on a phone at the table. */
.roll-result .total{transition:opacity .12s ease;}
.roll-result .total.tumbling{opacity:0.55; transform:scale(0.94);}
.roll-result .total.settled{animation:die-land 320ms cubic-bezier(.18,.89,.32,1.28);}
@keyframes die-land{
  0%{transform:scale(0.82); opacity:0.5;}
  55%{transform:scale(1.14); opacity:1;}
  100%{transform:scale(1); opacity:1;}
}
/* The die you actually tapped acknowledges the tap. Separate from the
   result so it fires even before the total has landed. */
.die-btn:active{transform:scale(0.9) rotate(-6deg); border-color:var(--brass);}
.die-btn{transition:border-color .15s, background .15s, transform .09s ease;}

.roll-result .degree{font-size:13px; font-weight:700; text-transform:uppercase; letter-spacing:0.06em; margin-top:8px;}
.roll-result .formula{font-family:'JetBrains Mono'; font-size:12.5px; color:var(--muted); margin-top:6px;}
.degree.crit-success{color:var(--verdant);}
.degree.success{color:var(--sapphire);}
.degree.failure{color:var(--brass-dim);}
.degree.crit-failure{color:var(--ember);}
.history-row{display:flex; justify-content:space-between; align-items:center; padding:9px 0; border-bottom:1px solid var(--line); font-size:13px;}
.history-row:last-child{border-bottom:none;}
.history-row .formula{font-family:'JetBrains Mono'; color:var(--muted); font-size:11.5px;}

/* ---------- Reference ---------- */
.ref-tabs{display:flex; gap:8px; margin-bottom:16px; flex-wrap:wrap;}
/* Filter pills. Also a toggle rather than a button, but they're tapped
   constantly on a phone, so they share .btn-sm's height. */
.ref-tab{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:var(--btn-h-sm); padding:0 var(--btn-px-sm); border-radius:999px;
  background:var(--panel-raised); border:1px solid var(--line);
  font-size:var(--btn-fs-sm); color:var(--muted); cursor:pointer; white-space:nowrap;
}
.ref-tab.active{background:var(--brass-wash); border-color:var(--brass-dim); color:var(--brass);}
/* Encounter Builder template-picker description (2026-08-05): the 7
   template descriptions run 106-268 characters, so tapping between
   pills to compare them changed this paragraph's real height by up to
   45px on a 375px phone (measured: Duo 30px / Swarm 75px) -- everything
   below (Chaos, Creature Type, Source, Generate) jumped that much on
   every tap. min-height reserves room for the longest one at each
   breakpoint so switching templates never moves the controls under
   your thumb. Sized from the actual longest description (Swarm/Squad,
   268 chars), not a round number. */
.template-desc{min-height:30px;}
@media (max-width:820px){ .template-desc{min-height:75px;} }
.threat-slider{margin:2px 0 4px;}
.threat-slider input[type="range"]{
  -webkit-appearance:none; appearance:none; width:100%; height:8px; border-radius:4px;
  background:linear-gradient(to right, var(--muted), var(--brass), var(--ember));
  outline:none; cursor:pointer;
}
.threat-slider input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none; appearance:none; width:20px; height:20px; border-radius:50%;
  background:var(--brass); border:2px solid var(--ink); cursor:pointer;
}
.threat-slider input[type="range"]::-moz-range-thumb{
  width:20px; height:20px; border-radius:50%; background:var(--brass); border:2px solid var(--ink); cursor:pointer;
}
.threat-slider-label{text-align:center; font-weight:700; margin-top:6px; color:var(--brass); font-size:13.5px;}
/* Live threat gauge -- read-only (no <input>), driven by the encounter
   draft's actual current monster list, distinct from the interactive
   target-threat button row it sits above. Grey->blue->green->red->near-
   black as the computed XP total climbs, per Bryan's explicit gradient. */
.threat-gauge{margin:2px 0 4px;}
.threat-gauge-track{
  position:relative; width:100%; height:8px; border-radius:4px;
  background:linear-gradient(to right, #888888 0%, #3b6fa0 25%, #3e8f5c 50%, #a3462f 75%, #24100c 100%);
}
.threat-gauge-marker{
  position:absolute; top:50%; width:16px; height:16px; margin-left:-8px; margin-top:-8px;
  border-radius:50%; background:var(--text); border:2px solid var(--ink);
}
.threat-gauge-label{text-align:center; font-weight:700; margin-top:6px; color:var(--brass); font-size:13.5px;}
/* Editable variant of .stat-strip -- same compact monospace stat
   display used elsewhere (Characters/Ancestries cards), but with real
   <input> fields for the encounter builder's per-monster stat editing,
   so it visually matches a real stat block instead of a bespoke form. */
.stat-strip input{width:56px; background:transparent; border:none; border-bottom:1px solid var(--line); color:var(--text); font:inherit; padding:0 0 1px;}
.stat-strip input:focus{outline:none; border-color:var(--brass);}
.ref-entry{padding:13px 0; border-bottom:1px solid var(--line);}
.ref-entry:last-child{border-bottom:none;}
.ref-entry h4{margin:0 0 4px; font-size:14.5px; font-family:'Fraunces',serif; display:flex; align-items:center; gap:8px;}
.ref-entry p{margin:0; font-size:13px; color:var(--text); line-height:1.55;}
.ref-tag{font-size:9.5px; text-transform:uppercase; letter-spacing:0.05em; padding:2px 7px; border-radius:4px; background:var(--sapphire-wash); color:var(--sapphire); font-weight:700;}
.search-group{margin-bottom:20px;}
.search-group-label{font-size:11px; text-transform:uppercase; letter-spacing:0.07em; color:var(--brass); font-weight:700; margin-bottom:6px;}
.spell-results{background:var(--panel-raised); border:1px solid var(--line); border-radius:8px; padding:12px 14px; margin:14px 0;}
.spell-results p{margin:0 0 6px; font-size:13px; line-height:1.5; color:var(--text);}
.spell-results p:last-child{margin-bottom:0;}
.spell-results p strong{color:var(--brass);}
.spell-heightened{margin-top:14px; padding-top:12px; border-top:1px dashed var(--line);}
.spell-heightened-label{font-size:11px; text-transform:uppercase; letter-spacing:0.07em; color:var(--muted); font-weight:700; margin-bottom:6px;}
.spell-heightened p{margin:0 0 5px; font-size:13px; line-height:1.5; color:var(--text);}
.item-tiers{margin-top:14px; padding-top:12px; border-top:1px dashed var(--line);}
.item-tier{background:var(--panel-raised); border:1px solid var(--line); border-radius:6px; padding:10px 12px; margin-bottom:8px;}
.item-tier:last-child{margin-bottom:0;}
.item-tier-head{display:flex; justify-content:space-between; align-items:baseline; margin-bottom:4px;}
.item-tier-head strong{color:var(--brass); text-transform:capitalize; font-size:13px;}
.item-tier p{margin:0; font-size:12.5px; line-height:1.5; color:var(--text);}
.concept-quickread{background:var(--brass-wash); border:1px solid var(--brass-dim); border-radius:8px; padding:12px 14px; margin:10px 0 16px;}
.concept-quickread p{margin:0; font-size:13.5px; line-height:1.6; color:var(--text);}
.concept-deepdive-label{font-size:11px; text-transform:uppercase; letter-spacing:0.08em; color:var(--muted); font-weight:700; margin-bottom:4px;}
.concept-article p{margin:0 0 12px; font-size:13.5px; line-height:1.65; color:var(--text);}
.concept-article h4{margin:18px 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.05em; color:var(--brass); font-weight:700; font-family:'Source Serif 4',serif;}
.concept-article h4:first-child{margin-top:4px;}
.concept-article ul{margin:0 0 12px; padding-left:20px; font-size:13.5px; line-height:1.6; color:var(--text);}
.concept-article ul li{margin-bottom:3px;}
mark{background:var(--brass-wash); color:var(--brass); padding:0 2px; border-radius:3px;}
/* Bold alone, no underline -- Bryan 2026-08-06: the underline read as
   "a bit much/distracting" at the density these appear in stat blocks.
   Native inherited text color is deliberate (see the 2026-08-04 note). */
.term-link{font-weight:700; cursor:pointer;}
.term-link:hover{opacity:0.7;}
.term-link, [data-tip]{position:relative;}
/* Tooltip. Rendered BELOW its element and sized to fit, both for the same
   reason: it lives inside panels that clip.
   The old rule was a hard 220px box positioned upward. In the Combat
   tracker's side-by-side layout each panel is only ~273px wide and is
   `overflow:auto`, so measured on a Greater Shadow's trait row, 4 of 8
   chips pushed their tooltip up to 98px past the panel's right edge and
   up to 257px above its top. Both directions got clipped, and what
   survived was a sliver of wrapped 12px text lying across the header and
   the chips underneath -- which is exactly the "garbled text bleeding
   through" in the screenshots, not a stacking bug.
   Below-and-clamped fixes the reported case: a stat block always has room
   under a chip, and max-content with a ceiling stops short tips ("Undead")
   from reserving a 220px box they never fill. Centering halves the
   worst-case horizontal overrun versus anchoring hard left. */
[data-tip]:hover::after{
  content:attr(data-tip);
  position:absolute; left:50%; transform:translateX(-50%);
  top:calc(100% + 6px); bottom:auto; z-index:200;
  background:var(--well); color:var(--text); border:1px solid var(--line);
  border-radius:6px; padding:8px 10px; font-size:12px; line-height:1.4;
  width:max-content; max-width:200px; overflow-wrap:anywhere;
  white-space:normal; box-shadow:var(--shadow); pointer-events:none;
}
@media (hover:none){ [data-tip]:hover::after{ content:none; } }
.combatant-detail{margin-top:10px; padding-top:10px; border-top:1px dashed var(--line);}
.stat-edit{width:100%; text-align:center; padding:5px 2px; font-family:'JetBrains Mono';}
.stat-edit-row{display:flex; align-items:flex-start; gap:6px; flex-wrap:wrap; margin-bottom:8px;}
.stat-edit-row > div{display:flex; flex-direction:column; align-items:center; gap:3px; width:52px;}
.stat-edit-row label{margin:0; font-size:9.5px; text-transform:uppercase; letter-spacing:0.03em; color:var(--muted); text-align:center;}
.quick-adjust-row{display:flex; align-items:center; gap:6px; margin:6px 0 10px; flex-wrap:wrap;}

/* ---------- Combat: spellcasting ---------- */
.spellcasting-block{margin-top:8px; padding-top:6px; border-top:1px dashed var(--line);}
.spellcasting-head{margin:0 0 6px; font-size:12.5px;}
.spell-rank-row{display:flex; flex-wrap:wrap; align-items:baseline; gap:6px; margin:4px 0;}
.spell-rank-label{font-size:10.5px; text-transform:uppercase; letter-spacing:0.04em; color:var(--brass-dim); font-weight:700; flex-shrink:0; min-width:64px;}
.spell-list{display:flex; flex-wrap:wrap; gap:6px;}
.spell-chip{display:inline-flex; align-items:center; gap:4px; font-size:12.5px; background:var(--panel-raised); border:1px solid var(--line); border-radius:6px; padding:3px 7px;}
.spell-save-mod{width:44px; padding:2px 3px; font-size:11px; text-align:center;}

.settings-heading{margin:0 0 10px; font-size:14px; text-transform:uppercase; letter-spacing:0.06em; color:var(--muted); font-weight:600;}
.settings-heading:not(:first-of-type){margin-top:2px;}
.settings-wrap{max-width:460px;}
/* Segmented control: a toggle, not a button, so it keeps its own look --
   but it sits inline with real buttons, so it takes its HEIGHT from the
   same --btn-h* tokens. Otherwise it reads as one more arbitrary size. */
.segmented{display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden;}
.segmented button{
  min-height:var(--btn-h-sm); padding:0 18px;
  background:var(--panel-raised); color:var(--muted); border:none; border-right:1px solid var(--line);
  cursor:pointer; font-family:'Source Serif 4',serif; font-size:13.5px; display:flex; align-items:center; gap:6px;
}
.segmented button:last-child{border-right:none;}
/* --fill: stretch to the container and split it evenly between segments. */
.segmented--fill{display:flex; width:100%;}
.segmented--fill button{flex:1 1 0; justify-content:center; padding:0 10px;}
.segmented button.active{background:var(--brass); color:var(--ink); font-weight:600;}
[data-theme="light"] .segmented button.active{color:#FBF6E9;}
/* A text link that sits inside a row of controls. Deliberately NOT a
   .btn -- it leaves the app, so it should not look like an action. */
.link-inline{color:var(--brass); font-size:12px; text-decoration:none;}
.link-inline:hover{text-decoration:underline;}
.google-signin-btn{
  display:inline-flex; align-items:center; gap:10px; padding:9px 16px 9px 12px;
  background:#fff; color:#3c4043; border:1px solid #dadce0; border-radius:6px;
  font-family:'Source Serif 4',serif; font-size:13.5px; font-weight:600; cursor:pointer;
}
.google-signin-btn:hover{background:#f8f9fa; box-shadow:0 1px 3px rgba(0,0,0,0.2);}
.drive-connected-badge{
  display:flex; align-items:center; gap:12px; padding:4px 2px;
}
.drive-check{
  width:30px; height:30px; border-radius:50%; background:var(--verdant); color:#fff;
  display:flex; align-items:center; justify-content:center; font-size:15px; font-weight:700; flex-shrink:0;
}
.drive-check.syncing{background:var(--brass); animation:drive-pulse 1s ease-in-out infinite;}
@keyframes drive-pulse{0%,100%{opacity:1;} 50%{opacity:0.4;}}
.book-row{display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:10px 0; border-bottom:1px solid var(--line);}
.book-row:last-child{border-bottom:none;}
.book-loaded{display:inline-flex; align-items:center; gap:5px; color:var(--verdant); font-size:12.5px; font-weight:600; flex-shrink:0; padding-top:2px;}
.book-owned-unparsed{color:var(--muted); font-size:12px; font-style:italic; flex-shrink:0; padding-top:2px; white-space:nowrap;}
.book-bullets{margin:8px 0 2px; padding-left:18px; font-size:12px; line-height:1.55; color:var(--muted);}
.book-bullets li{margin-bottom:3px;}

html{background:var(--ink); height:100%;}
#app{padding-top:env(safe-area-inset-top);}

@media (max-width:820px){
  .rail{display:none;}
  .bottom-nav{
    display:flex; position:static; flex-shrink:0; background:var(--binder);
    border-top:1px solid var(--binder-line); padding:8px 4px calc(8px + env(safe-area-inset-bottom));
    justify-content:flex-start; overflow-x:auto; -webkit-overflow-scrolling:touch;
  }
  .bottom-nav::-webkit-scrollbar{display:none;}
  .bottom-nav .nav-item{flex-direction:column; gap:4px; font-size:10px; padding:8px 6px; text-align:center; flex-shrink:0; min-width:62px; color:#CBBC9C; min-height:52px; justify-content:center;}
  [data-theme="light"] .bottom-nav .nav-item{color:#F5EAD0;}
  .bottom-nav .nav-item.active::before{display:none;}
  .bottom-nav .nav-item svg{width:21px;height:21px;}
  .main{padding:20px 16px 28px;}
  .grid-2,.grid-3,.grid-4{grid-template-columns:1fr 1fr;}
  .home-grid{column-count:1;}
  .home-row-list{grid-template-columns:1fr;}
  .combatant{flex-wrap:wrap;}
  .combatant-right{margin-left:0; width:100%; align-items:flex-start;}
  .party-row{flex-wrap:wrap;}
  .party-row select{flex:1 1 100%;}
  .combat-layout{flex-direction:column;}
  /* Desktop's side-by-side layout is fine as-is. Mobile stacking put the
     stat block dead last (later DOM sibling of .combat-list), so order:-1
     hoists it to the top.

     That fixed the wrong problem for the SELECTED combatant, and a first
     rebuild that expanded the block inside its own row fixed it in a way
     that kept leaking: a nested scroll container inside a scrolling list
     cost a scroll lurch, a horizontal slide, scroll chaining and a lost
     reading position. Stat blocks now open as cards on the shared Overlay
     stack, which had already solved all of that. Only Current Turn stays
     pinned here, because that one you want regardless of scroll position.

     Collapse the panel entirely when nothing in it survives on mobile,
     or its padding and border leave an empty slab above the tracker. */
  .combat-sidepanel{width:100%; position:static; max-height:none; order:-1;}
  .combat-sidepanel:not(.has-active){display:none;}
  .sidepanel-window--selected{display:none;}
  .sidepanel-placeholder{display:none;}
  .layout-toggle{display:none;}
  .combat-sidepanel.layout-side{width:100%;}
  .layout-side .sidepanel-panels{flex-direction:column;}
  .layout-stack .sidepanel-panels{height:auto;}
  .layout-stack .sidepanel-window{flex:none; max-height:60vh;}
  .sidepanel-panels{max-height:none; overflow:visible;}
  .sidepanel-window{max-height:60vh; flex:none;}

  /* Full width of the row, on its own line (.combatant is flex-wrap here),
     so the block reads as belonging to the card it opened from. Capped and
     scrollable because a full creature stat block is far taller than a
     phone screen -- without this, opening one buries every row below it. */
  /* Touch sizing for every button variant now comes from the --btn-*
     tokens in :root, which are mobile-first -- this block used to carry
     nine separate per-class overrides (.init-box, .init-arrows button,
     .chip-arrows button, .hp-btn, .icon-btn, .roll-btn, .btn-sm,
     .ref-tab, ...) that each drifted independently. Only the trait-chip
     type scale is a genuine one-off. */
  .chip{padding:6px 11px; font-size:12.5px;}
}

/* ---------- Buttons: desktop tightening ----------
   The ONE place button sizing differs for mouse use. Everything above
   is mobile-first at >=44px; this pulls the three sizes apart again so
   a desk-bound stat block isn't all 44px slabs. */
@media (min-width:821px){
  :root{
    --btn-h:38px;    --btn-px:14px;   --btn-fs:13.5px;
    --btn-h-sm:32px; --btn-px-sm:11px; --btn-fs-sm:12.5px;
    --btn-h-xs:26px; --btn-px-xs:8px;  --btn-fs-xs:11.5px;
    --chip-btn:22px; --chip-step-w:18px; --chip-step-min:11px;
    --init-box:40px; --init-fs:14px;  --init-arrow-w:18px;
  }
  /* Back inline with the HP field, one row of six, as it was before. */
  .hp-steppers{grid-template-columns:repeat(6,1fr); gap:4px; flex:1 1 auto;}
}

/* ── Accessibility: keyboard focus indicators ── */
:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
  border-radius: 2px;
}
button:focus-visible,
[role="button"]:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--ember);
  outline-offset: 2px;
}

/* ── Accessibility: respect reduced motion ── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Safe-area insets for fixed/sticky elements (notch support) ── */
.rail {
  padding-top: max(0px, env(safe-area-inset-top));
  padding-bottom: max(0px, env(safe-area-inset-bottom));
}
.bottom-nav {
  padding-bottom: max(0px, env(safe-area-inset-bottom));
}

/* ── Print styles ── */
@media print {
  .rail, .bottom-nav, .icon-btn, .btn { display: none !important; }
  .main { margin: 0; padding: 0; max-width: none; }
  .modal-overlay { position: static; background: none; }
  body { background: white; color: black; }
  .card, .stat-block, table { page-break-inside: avoid; }
}


/* Sticky combat toolbar visual separation */
.combat-toolbar {
  border-bottom: 1px solid var(--brass-dim);
  box-shadow: 0 2px 8px rgba(0,0,0,0.3);
  background: var(--panel-solid);
}
/* Phone: this band is pinned for the whole fight, so every pixel it
   takes is a pixel of initiative list the GM can't see. Compacted to a
   single row at 375px -- Round badge + Start Combat + Next Turn -- which
   is the entire turn-to-turn control set. Everything else lives in
   .combat-toolbar-more and scrolls away. */
@media (max-width:600px){
  .combat-toolbar{gap:7px; padding:6px 4px; margin-bottom:10px;}
  /* Horizontal padding only -- height keeps coming from --btn-h, so
     these stay level with the round badge and with each other. The old
     rule set vertical padding too, which is exactly how the toolbar
     ended up a different height from every other button strip. */
  .combat-toolbar .btn{padding-left:11px; padding-right:11px; font-size:13px; gap:5px;}
  .combat-toolbar-more{margin-bottom:14px;}
}

/* Mobile selected row emphasis */@media (max-width:600px){.combatant.selected-turn{border-left:4px solid var(--brass);}}

/* ============ CHARACTER CREATOR WIZARD ============ */
/* Namespaced cc- to stay out of the way of every other tab's classes.
   Kept deliberately light on chrome -- Bryan's mobile-clean constraint means
   this can't grow its own toolbar/header on top of FullPage's existing one. */
/* Bottom padding clears the fixed .cc-preview-fab (below) -- without it, scrolling to
   the end of a step puts Continue directly behind the floating button instead of past
   it, the opposite of what a "reach Continue faster" fix should do. */
.cc-wizard{display:flex; flex-direction:column; gap:14px; padding-bottom:64px;}
.cc-progress{display:flex; gap:5px;}
.cc-progress-dot{flex:1; height:4px; border-radius:2px; background:var(--line);}
.cc-progress-dot.cc-done{background:var(--brass-dim);}
.cc-progress-dot.cc-current{background:var(--brass);}
.cc-step-label{font-size:12px; color:var(--muted); text-transform:uppercase; letter-spacing:0.04em;}
.cc-nav{display:flex; justify-content:space-between; align-items:center; margin-top:8px; padding-top:14px; border-top:1px solid var(--line);}
.cc-selected{border-color:var(--brass) !important; box-shadow:0 0 0 1px var(--brass);}

/* Sticky sub-header (progress+title) and floating character preview -- Bryan's desktop
   pass, 2026-08-06 (DESIGN-NOTES.md, Step 1 + Step 4 asks). top:75px matches FullPage's
   own .fullscreen-header height for this wizard specifically (see CharacterCreator.
   progressBar()'s comment for why that's safe to hardcode here). background matches
   .fullscreen-view's so scrolled content doesn't show through underneath.  */
.cc-sticky-header{position:sticky; top:75px; z-index:5; background:var(--ink); padding:10px 0 8px; margin:-10px 0 0;}
.cc-preview-fab{position:fixed; bottom:20px; right:20px; z-index:10; width:52px; height:52px; border-radius:50%; background:var(--brass); border:none; color:var(--ink); box-shadow:var(--shadow); display:flex; align-items:center; justify-content:center; cursor:pointer;}
.cc-preview-fab svg{width:22px; height:22px;}
.cc-preview-fab:hover{filter:brightness(1.08);}
.cc-preview-panel{position:fixed; bottom:82px; right:20px; z-index:10; width:280px; max-width:calc(100vw - 40px); max-height:60vh; overflow-y:auto; background:var(--panel-solid); border:1px solid var(--line); border-radius:10px; padding:14px; box-shadow:var(--shadow);}
@media (max-width:600px){
  .cc-preview-fab{bottom:16px; right:16px; width:46px; height:46px;}
  .cc-preview-fab svg{width:20px; height:20px;}
  .cc-preview-panel{bottom:70px; right:12px; left:12px; width:auto; max-width:none;}
}

/* A wizard table row that expands into a picker/preview below the selected row (ancestry
   -> heritage, background -> grants) -- not itself a click target, so it shouldn't look
   or act like one the way a normal .wiki-table row does. */
/* Reference tables that just present numbers -- nothing to open, so they
   must not advertise themselves as clickable the way a content-index table
   does. Same reasoning as .cc-subrow below, generalised so any static table
   can opt out rather than each one re-solving it. */
/* Delivery state on a flagged report. The reporter could not previously tell
   whether their flag had actually reached Bryan or was still stuck on their
   phone -- which is how a whole table's worth of reports went unnoticed. */
.flag-delivery.ok{color:var(--brass);}
.flag-delivery.wait{color:var(--muted);}
.flag-delivery.bad{color:var(--ember);}

.wiki-table.static tbody tr{cursor:default;}
.wiki-table.static tbody tr:hover,
.wiki-table.static tbody tr:nth-child(even):hover{background:inherit;}
.wiki-table.static tbody tr:nth-child(even){background:rgba(255,255,255,0.02);}
[data-theme="light"] .wiki-table.static tbody tr:nth-child(even){background:rgba(0,0,0,0.02);}

.wiki-table tr.cc-subrow{cursor:default;}
.wiki-table tr.cc-subrow:hover{background:inherit;}
.wiki-table tr.cc-subrow td{padding:12px;}

/* Required-field flag: only applied once the player has tried to continue past an
   unfinished step (see CharacterCreator.fieldError) -- never shown on first arrival. */
.cc-required{outline:2px solid var(--ember); outline-offset:4px; border-radius:8px;}

.cc-radio-list{display:flex; flex-direction:column; gap:8px;}
/* The row's own tag is a real <label>, wrapping its <input> so tapping
   anywhere in the row (not just the small radio itself) selects it --
   native browser behavior, not something to give up by switching to a
   div. But the app styles every literal <label> element uppercase for
   short form-field labels ("Heritage", "Tradition", etc.), and that
   text-transform inherits to everything inside -- including the full
   multi-sentence heritage/feat description text nested in here, which
   was never meant to be a "label" in that sense. Neutralized here rather
   than changing the wrapper tag (Bryan + Exploratory QA report, 2026-08-06
   -- "CHAMELEON GNOME / THE COLOR OF YOUR HAIR AND SKIN IS MUTABLE...").
   The QA report's guess was .combatant-meta itself carrying the
   transform; it doesn't -- this global <label> rule does, and
   .combatant-meta was just the class on the affected text. */
.cc-radio-row{display:flex; gap:10px; align-items:flex-start; padding:10px; border:1px solid var(--line); border-radius:8px; cursor:pointer; text-transform:none;}
.cc-radio-row:hover{border-color:var(--brass-dim);}
.cc-radio-row input{margin-top:3px;}
.cc-chip-row{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:14px;}
.cc-preview{background:var(--well); border-radius:8px; padding:12px 14px; font-size:13px;}
.cc-preview ul{margin:8px 0 0; padding-left:18px;}
.cc-flag{color:var(--ember); font-size:12px; margin-top:6px;}
.cc-filter{width:100%; margin-bottom:12px; padding:9px 12px; border-radius:8px; border:1px solid var(--line); background:var(--well); color:var(--text); font:inherit;}
/* Used to be max-height:340px + overflow-y:auto -- a nested scroll container inside
   .fullscreen-view's own scroll, the exact bug class DESIGN-NOTES.md already documents
   for the old inline combat stat block (nested scrollers chain/jitter/lose place; the fix
   that actually held was removing the nested scroller, not patching it with
   overscroll-behavior). The Equipment step's item list was the worst-hit -- Bryan reported
   the scroll as "not broken, but feels broken" (2026-08-06). Now flows in the page's own
   scroll like every other list in this wizard (Ancestry/Background use .entity-grid the
   same way); category chips on the Equipment step do the job max-height used to. */
.cc-list{display:flex; flex-direction:column; gap:4px;}
.cc-eq-row{display:flex; justify-content:space-between; align-items:center; padding:7px 4px; border-bottom:1px solid var(--line); font-size:13.5px;}
.cc-score{font-family:'JetBrains Mono'; font-size:14px;}

@media (max-width:600px){
  .cc-nav{flex-wrap:wrap; gap:8px;}
  .cc-nav > div{width:100%; display:flex; gap:8px;}
  .cc-nav > div .btn{flex:1;}
}