/* Underworld Character Builder — visual layer
 *
 * Direction: verified style `dark-mode-oled` (dark only; requires 4.5:1 text,
 * keyboard operation, visible focus, reduced-motion support) with the verified
 * pairing "Classic Elegant" (Playfair Display display face + Inter body).
 * Palette is taken from the rulebook cover — near-black ground, blood red,
 * gold, and the pale celestial blue of the angel half — rather than a generic
 * slate. Density dial 8/10: this is a data-dense tool, so an 8-32px scale.
 */

/* ---- Tokens ---------------------------------------------------------- */
:root {
  /* Ground */
  --ink:        #0A0A0C;
  --ink-2:      #121116;
  --ink-3:      #1A181F;
  --ink-4:      #24212B;
  --line:       rgba(237, 230, 218, 0.12);
  --line-soft:  rgba(237, 230, 218, 0.06);

  /* Text — 16:1 and 8:1 on the ground */
  --parchment:       #EDE6DA;
  --parchment-dim:   #A89F93;
  --parchment-faint: #776F66;

  /* Brand */
  --blood:      #A8142A;
  --blood-hi:   #C81D33;
  --blood-text: #FF8A95;   /* red that stays readable as text on dark */
  --gold:       #C9A227;
  --gold-hi:    #E0B733;
  --celestial:  #9FC4E8;

  /* Semantic */
  --success: #5BB07D;
  --warn:    #E0A93B;
  --danger:  #F0727D;

  /* Type */
  --font-display: 'Playfair Display', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
               'Helvetica Neue', Arial, sans-serif;

  /* Space — dense scale */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s7: 32px; --s8: 48px;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.5);
  --shadow-2: 0 8px 28px rgba(0, 0, 0, 0.55);

  --tap: 36px;
}

@media (pointer: coarse) { :root { --tap: 44px; } }

* { box-sizing: border-box; }

html { color-scheme: dark; }

body {
  margin: 0;
  background:
    radial-gradient(ellipse 80% 50% at 18% -8%, rgba(168, 20, 42, 0.16), transparent 60%),
    radial-gradient(ellipse 70% 45% at 88% 4%, rgba(159, 196, 232, 0.07), transparent 60%),
    var(--ink);
  background-attachment: fixed;
  color: var(--parchment);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
  font-variant-numeric: tabular-nums;
}

/* Grain: felt, not seen. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 200;
  opacity: 0.022;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.12;
  letter-spacing: -0.015em;
  text-wrap: balance;
  margin: 0;
}

/* ---- Focus + skip link ---------------------------------------------- */
:focus-visible {
  outline: 2px solid var(--gold-hi);
  outline-offset: 2px;
  border-radius: 3px;
}
/* Keep focus clear of the sticky header (WCAG 2.2 Focus Not Obscured).
   --header-h is republished by a ResizeObserver in app.js, because the header
   height changes with wrapping, orientation and font loading -- the old
   hardcoded 160px/220px understated the real 393-411px on phones, so focused
   fields and anchor targets landed underneath it. */
:root { --header-h: 160px; --sticky-h: 160px; --nav-h: 0px; }
/* Desktop pins the whole header; the section nav lives in the sidebar. */
html { scroll-padding-top: calc(var(--header-h) + var(--s3)); }

.skip {
  position: absolute;
  left: -9999px;
  top: var(--s2);
  z-index: 300;
  display: inline-flex;
  align-items: center;
  min-height: 44px;          /* was 39px -- under the touch minimum */
  background: var(--gold);
  color: #1A1304;
  padding: var(--s2) var(--s4);
  border-radius: var(--radius-sm);
  font-weight: 600;
}
.skip:focus { left: var(--s2); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* ---- Header ---------------------------------------------------------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 100;
  background: rgba(10, 10, 12, 0.88);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  border-bottom: 1px solid var(--line);
}

.topbar-row {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s3) var(--s5);
  max-width: 1500px;
  margin: 0 auto;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-width: 0;
}
.brand-mark { flex: none; color: var(--blood-text); }
.brand h1 {
  font-size: 19px;
  letter-spacing: 0.01em;
}
.brand h1 em {
  display: block;
  font-family: var(--font-body);
  font-style: normal;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--parchment-faint);
  margin-top: 2px;
}

.identity {
  display: flex;
  gap: var(--s3);
  flex: 1 1 320px;
  min-width: 0;
}
.identity .field { flex: 1 1 0; min-width: 0; }

.topbar-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }

/* ---- Stat strip ------------------------------------------------------ */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 1px;
  /* <dl> carries a UA default margin of 1em 0. It was invisible while the
     whole header was pinned, but it added 32px of header height and, once
     only the strip stays pinned, left the strip 16px out of alignment with
     the sticky offset -- the top row of labels was clipped off-screen. */
  margin: 0;
  background: var(--line-soft);
  border-top: 1px solid var(--line);
}
.stat {
  background: var(--ink);
  padding: var(--s2) var(--s4) var(--s3);
  text-align: center;
}
.stat dt {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--parchment-faint);
  margin: 0 0 2px;
}
.stat dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: 23px;
  line-height: 1.1;
  color: var(--parchment);
}
.stat dd small {
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--parchment-faint);
  font-weight: 500;
}
.stat--accent dd { color: var(--gold); }
.stat--over dd { color: var(--danger); }
.stat--frags dd { color: var(--celestial); }

/* ---- Shell ----------------------------------------------------------- */
.shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  /* 1fr tracks take their min-content floor, which lets a horizontally
     scrolling child push the page wide. minmax(0,...) removes that floor. */
  gap: var(--s6);
  max-width: 1500px;
  margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s8);
  align-items: start;
}

/* ---- Section nav ----------------------------------------------------- */
.sectionnav { position: sticky; top: calc(var(--header-h) + var(--s2)); min-width: 0; }
#main { min-width: 0; }
.sectionnav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }

.navbtn {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--parchment-dim);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background 0.22s var(--ease), color 0.22s var(--ease),
              border-color 0.22s var(--ease);
}
.navbtn { -webkit-tap-highlight-color: transparent; }
@media (hover: hover) and (pointer: fine) {
  .navbtn:hover { background: var(--ink-2); color: var(--parchment); }
}
.navbtn svg { flex: none; opacity: 0.72; }
.navbtn[aria-current='true'] {
  background: linear-gradient(90deg, rgba(168, 20, 42, 0.28), rgba(168, 20, 42, 0.04));
  border-color: rgba(200, 29, 51, 0.5);
  color: var(--parchment);
  font-weight: 600;
}
.navbtn[aria-current='true'] svg { opacity: 1; color: var(--blood-text); }
.navbtn .tally {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  color: var(--parchment-faint);
  font-variant-numeric: tabular-nums;
}
.navbtn[aria-current='true'] .tally { color: var(--gold); }
.navbtn .flag { color: var(--danger); margin-left: auto; }

/* ---- Panels ---------------------------------------------------------- */
.panel { display: none; }
.panel.is-active { display: block; animation: rise 0.34s var(--ease-out) both; }
@keyframes rise { from { opacity: 0; transform: translateY(8px); } }

.panel-head { margin-bottom: var(--s5); }
.panel-head h2 { font-size: clamp(22px, 2.6vw, 30px); }
.panel-head p {
  margin: var(--s2) 0 0;
  color: var(--parchment-dim);
  max-width: 68ch;
  font-size: 14px;
}

.card {
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--s5);
  margin-bottom: var(--s5);
  box-shadow: var(--shadow-1);
}
.card > h3 {
  font-size: 13px;
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: var(--s4);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line-soft);
}
.card > h3 .hint {
  float: right;
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  color: var(--parchment-faint);
  font-size: 12px;
}

/* ---- Fields ---------------------------------------------------------- */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--s4);
}

.field label,
.field > .label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--parchment-dim);
  margin-bottom: var(--s1);
}

input[type='text'], input[type='number'], select {
  width: 100%;
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  background: var(--ink-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--parchment);
  font: inherit;
  font-size: 14px;
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
input::placeholder { color: var(--parchment-faint); }
input:focus, select:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(201, 162, 39, 0.18);
}
select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23A89F93' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s3) center;
  padding-right: var(--s7);
  cursor: pointer;
}
select:disabled, input:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.readout {
  display: block;
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  background: var(--ink-3);
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  color: var(--parchment);
  font: inherit;
  font-size: 14px;
  font-weight: 650;
}

.help { font-size: 12px; color: var(--parchment-faint); margin-top: var(--s1); }

.check {
  display: flex;
  align-items: center;
  gap: var(--s3);
  min-height: var(--tap);
  cursor: pointer;
  font-size: 14px;
}
.check input { width: 18px; height: 18px; accent-color: var(--blood-hi); flex: none; cursor: pointer; }

/* ---- Skill rows + steppers ------------------------------------------ */
.skills { display: grid; gap: 2px; }

.skill {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--s3);
  min-height: var(--tap);
  padding: var(--s2) var(--s3);
  border-radius: var(--radius-sm);
  transition: background 0.18s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .skill:hover { background: var(--ink-3); }
}
.skill.is-owned { background: rgba(201, 162, 39, 0.06); }
.skill.is-locked { opacity: 0.56; }

.skill-name { min-width: 0; font-size: 14px; }
.skill-name b { font-weight: 500; }
.skill.is-owned .skill-name b { font-weight: 650; color: var(--parchment); }
.skill-name .lock {
  display: block;
  font-size: 11.5px;
  color: var(--blood-text);
  margin-top: 1px;
}
.skill-cost {
  font-size: 12px;
  color: var(--parchment-faint);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.skill-cost b { color: var(--parchment-dim); font-weight: 600; }
.skill-cost .frag { color: var(--celestial); }

.stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--ink-4);
}
.stepper button {
  width: var(--tap);
  height: var(--tap);
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
  color: var(--parchment-dim);
  font: inherit;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.16s var(--ease), color 0.16s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .stepper button:hover:not(:disabled) { background: var(--blood); color: #fff; }
}
/* On touch, :active IS the feedback -- there is no hover to lean on. */
.stepper button:active:not(:disabled) { background: var(--blood-hi); color: #fff; }
.stepper button:disabled { opacity: 0.3; cursor: not-allowed; }
.stepper input {
  width: 46px;
  min-height: var(--tap);
  text-align: center;
  border: 0;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  border-radius: 0;
  background: transparent;
  font-weight: 650;
  padding: 0;
  -moz-appearance: textfield;
}
.stepper input::-webkit-outer-spin-button,
.stepper input::-webkit-inner-spin-button { appearance: none; margin: 0; }
.stepper input:focus { box-shadow: none; border-color: var(--gold); }

/* ---- Buttons --------------------------------------------------------- */
/* touch-action: manipulation removes the double-tap-to-zoom gesture, which
   otherwise fires when a stepper is tapped quickly twice. The callout/select
   suppression stops a long-press on +/- raising the iOS text-selection
   bubble instead of simply repeating. */
.btn, .stepper button, .navbtn {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: var(--tap);
  padding: 0 var(--s4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--ink-3);
  color: var(--parchment);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  transition: transform 0.22s var(--ease), background 0.22s var(--ease),
              border-color 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .btn:hover { background: var(--ink-4); border-color: rgba(237, 230, 218, 0.22); }
}
.btn:active { transform: translateY(1px); background: var(--ink-4); }
.btn--primary {
  background: var(--blood);
  border-color: var(--blood-hi);
  color: #fff;
}
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover { background: var(--blood-hi); box-shadow: 0 4px 18px rgba(168, 20, 42, 0.35); }
}
.btn--ghost { background: transparent; }
.btn--danger { color: var(--danger); }
.btn svg { flex: none; }

/* ---- Issue panel ----------------------------------------------------- */
.issues {
  border: 1px solid rgba(240, 114, 125, 0.4);
  background: rgba(240, 114, 125, 0.08);
  border-radius: var(--radius-md);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s5);
}
.issues:empty { display: none; }
.issues h2 {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--danger);
  margin-bottom: var(--s2);
}
.issues ul { margin: 0; padding-left: var(--s5); display: grid; gap: var(--s1); }
.issues li { font-size: 13.5px; color: var(--parchment); }
.issues li.warn { color: var(--warn); }
.issues--ok {
  border-color: rgba(91, 176, 125, 0.38);
  background: rgba(91, 176, 125, 0.07);
}
.issues--ok h2 { color: var(--success); }

/* ---- Pyramid --------------------------------------------------------- */
.pyramid { display: grid; gap: var(--s1); margin-bottom: var(--s4); }
.pyr-row {
  display: grid;
  grid-template-columns: 70px repeat(10, 1fr);
  align-items: center;
  gap: var(--s1);
}
.pyr-label {
  font-size: 11.5px;
  color: var(--parchment-faint);
  font-weight: 600;
  letter-spacing: 0.05em;
}
.pip {
  height: 15px;
  border-radius: 3px;
  background: var(--ink-3);
  border: 1px solid var(--line-soft);
}
.pip.on {
  background: linear-gradient(180deg, var(--blood-hi), var(--blood));
  border-color: var(--blood-hi);
}
.pip.next {
  border: 1px dashed var(--gold);
  background: rgba(201, 162, 39, 0.12);
}
.pyr-note {
  font-size: 12.5px;
  color: var(--parchment-dim);
  margin-bottom: var(--s4);
}
.pyr-note b { color: var(--gold); }

/* ---- Sheet ----------------------------------------------------------- */
.sheet {
  background: var(--ink-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--s6);
}
.sheet h3 { font-family: var(--font-display); font-size: 21px; margin-bottom: var(--s2); }
.sheet-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s3);
  margin: var(--s4) 0 var(--s6);
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--line);
}
.sheet-meta div span {
  display: block;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--parchment-faint);
  font-weight: 600;
}
.sheet-meta div b { font-size: 15px; font-weight: 600; }

.sheet-group { margin-bottom: var(--s5); }
.sheet-group h4 {
  font-family: var(--font-body);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: var(--s2);
}
.sheet-list { display: grid; gap: 1px; }
.sheet-line {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s2) var(--s3);
  font-size: 13.5px;
  border-radius: 4px;
}
.sheet-line:nth-child(odd) { background: rgba(237, 230, 218, 0.025); }
.sheet-line i { font-style: normal; color: var(--parchment-faint); font-variant-numeric: tabular-nums; }

.tag {
  display: inline-block;
  padding: 2px var(--s2);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  border: 1px solid var(--line);
  color: var(--parchment-dim);
}
.tag--gold { color: var(--gold); border-color: rgba(201, 162, 39, 0.4); }
.tag--frag { color: var(--celestial); border-color: rgba(159, 196, 232, 0.35); }

/* ---- Toast ----------------------------------------------------------- */
/* Save and Copy report what happened. Anchored bottom-centre, clear of the
   iOS home indicator, and never covering the pinned stat strip. */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--s5) + env(safe-area-inset-bottom, 0px));
  z-index: 250;
  max-width: min(440px, calc(100vw - var(--s6)));
  padding: var(--s3) var(--s5);
  background: var(--ink-4);
  border: 1px solid var(--line);
  border-left: 3px solid var(--gold);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  color: var(--parchment);
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 14px);
  transition: opacity 0.24s var(--ease), transform 0.24s var(--ease);
}
.toast.is-up { opacity: 1; transform: translate(-50%, 0); }
.toast--error {
  border-left-color: var(--danger);
  font-weight: 500;
  text-align: left;
}
.toast:empty { display: none; }

/* ---- Responsive ------------------------------------------------------ */

/* Touch devices, any width.
   iOS Safari force-zooms the page whenever a focused field is under 16px and
   never zooms back out -- with 93 fields at 14px that was the single biggest
   source of "glitchy" on iPhone. Raised only for coarse pointers so the
   deliberate desktop density is untouched. */
@media (pointer: coarse) {
  input[type='text'], input[type='number'], select, textarea,
  .stepper input, .readout {
    font-size: 16px;
  }
  .stepper input { width: 54px; }

  /* Three fixed gradient layers and a blurred sticky header are both classic
     mobile scroll-jank sources; neither earns its cost on a phone. */
  body { background-attachment: scroll, scroll, scroll; }
  .topbar {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: var(--ink);
  }
  /* The grain is a fixed full-viewport layer composited every frame. */
  body::after { display: none; }
}

@media (max-width: 1020px) {
  .shell { grid-template-columns: minmax(0, 1fr); gap: var(--s4); padding: var(--s4) var(--s4) var(--s8); }

  /* Only the stat strip stays pinned.
     The header is ~393px tall on a phone -- 47% of an iPhone 13 and 59% of an
     SE2 -- which left barely any room to work in. Sticking the header at a
     NEGATIVE offset equal to its own upper row scrolls the brand, the action
     buttons and the identity fields away on first scroll and leaves the six
     live numbers pinned, which are the only part worth the real estate. */
  .topbar { top: calc(var(--sticky-h) - var(--header-h)); }

  /* Below 1020px the section strip is pinned under the stat strip, so the
     overlap to clear is those two, not the whole header. */
  html { scroll-padding-top: calc(var(--sticky-h) + var(--nav-h) + var(--s3)); }

  .sectionnav {
    position: sticky;
    top: calc(var(--sticky-h) - 1px);
    z-index: 90;
    margin: 0 calc(var(--s4) * -1);
    /* 100vw includes the scrollbar gutter and can exceed the visual viewport,
       which is a horizontal-overflow trap. The parent already constrains it. */
    max-width: none;
    background: var(--ink);
    border-bottom: 1px solid var(--line-soft);
  }
  .sectionnav ul {
    grid-auto-flow: column;
    grid-auto-columns: max-content;
    width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    gap: var(--s1);
    padding: var(--s2) var(--s4);
    scrollbar-width: thin;
    /* The strip holds 1150px of tabs in a 390px viewport, so the Character
       Sheet tab sat off-screen with nothing to say the strip scrolls. Snap
       plus a right-edge fade makes it discoverable; app.js scrolls the active
       tab into view after each render. */
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
  }
  .sectionnav ul.is-scroll-end {
    mask-image: none;
    -webkit-mask-image: none;
  }
  .navbtn { white-space: nowrap; scroll-snap-align: end; }
  .navbtn .tally, .navbtn .flag { margin-left: var(--s2); }
}

@media (max-width: 680px) {
  .topbar-row { padding: var(--s2) var(--s4); gap: var(--s2) var(--s3); }
  .brand h1 { font-size: 16px; }
  /* Decorative strapline -- the <h1> text carries the name on its own. */
  .brand h1 em { display: none; }
  .brand-mark { width: 22px; height: 22px; }

  /* Label beside the field instead of above it: same visible label, one 44px
     row for both fields instead of a 60px stacked block. */
  .identity { flex: 1 1 100%; gap: var(--s2); }
  .identity .field { display: flex; align-items: center; gap: var(--s2); }
  .identity .field label {
    margin: 0;
    flex: none;
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--parchment-faint);
  }

  .topbar-actions { width: 100%; }
  .topbar-actions .btn { flex: 1 1 0; min-width: 0; padding: 0 var(--s2); }

  .stat { padding: var(--s1) var(--s2) 5px; }
  .stat dt { font-size: 9px; letter-spacing: 0.08em; }
  .stat dd { font-size: 18px; }
  .stat dd small { font-size: 11px; }
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }

  .card { padding: var(--s4); }
  .skill { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--s1); }
  .skill-cost { grid-column: 1; font-size: 11.5px; }
  .pyr-row { grid-template-columns: 48px repeat(10, 1fr); }
  .pip { height: 18px; }
  .sheet { padding: var(--s4); }
  .panel-head p { font-size: 13.5px; }
  .card > h3 .hint { float: none; display: block; margin-top: 2px; }
}

/* Landscape phones: vertical space is the scarce resource, not width, so the
   680px-wide rules never fire even though the screen is only 390px tall.
   Repeat the header compaction on the height axis. */
@media (max-height: 520px) and (pointer: coarse) {
  .topbar-row { padding: var(--s2) var(--s4); gap: var(--s2) var(--s3); }
  .brand h1 { font-size: 16px; }
  .brand h1 em { display: none; }
  .brand-mark { width: 22px; height: 22px; }
  .identity { flex: 1 1 320px; gap: var(--s2); }
  .identity .field { display: flex; align-items: center; gap: var(--s2); }
  .identity .field label {
    margin: 0;
    flex: none;
    font-size: 10.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--parchment-faint);
  }
  .stats { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .stat { padding: var(--s1) var(--s2) 5px; }
  .stat dd { font-size: 16px; }
  .stat dt { font-size: 8.5px; letter-spacing: 0.05em; }
  .panel.is-active { animation: none; }
}

/* Very narrow (Fold closed, 320px). The stat strip deliberately stays
   3-across: dropping to 2 would make it three rows and TALLER, not shorter. */
@media (max-width: 360px) {
  .topbar-actions .btn { font-size: 12.5px; gap: var(--s1); }
  .identity .field label { font-size: 10px; }
  .stat dt { font-size: 8.5px; letter-spacing: 0.05em; }
}

/* ---- Motion + print -------------------------------------------------- */
@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;
  }
}

@media print {
  body { background: #fff; color: #000; }
  body::after, .topbar, .sectionnav, .topbar-actions, .issues, .no-print { display: none !important; }
  .shell { display: block; max-width: none; padding: 0; }
  .panel { display: none !important; }
  .panel#panel-sheet { display: block !important; animation: none; }
  .sheet, .card { background: #fff; border-color: #bbb; box-shadow: none; }
  .sheet h3, .sheet-group h4, .card > h3 { color: #000; }
  .sheet-line i, .sheet-meta div span { color: #444; }
  .sheet-line:nth-child(odd) { background: #f4f4f4; }
}


/* ---- UW-XTOOL: cross-tool switcher ------------------------------- */
/* Lives in .topbar-row, which already wraps. --header-h is measured from .topbar
   by the ResizeObserver in app.js, so the added height is absorbed automatically
   and the mobile negative-offset pin of the stat strip still lands correctly. */
.xtool { display: flex; gap: var(--s1); flex: 0 0 auto; }
.xtool-link {
  display: inline-flex; align-items: center; min-height: var(--tap);
  padding: var(--s2) var(--s3); border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 500; color: var(--parchment-dim);
  text-decoration: none; white-space: nowrap;
  transition: background 180ms var(--ease), color 180ms var(--ease);
}
.xtool-link:hover { background: var(--ink-3); color: var(--parchment); }
.xtool-link:focus-visible { outline: 2px solid var(--gold-hi); outline-offset: 2px; }
@media print { .xtool { display: none !important; } }
@media (prefers-reduced-motion: reduce) { .xtool-link { transition: none; } }

/* ---- My Characters (saved roster) ------------------------------------
 * Built entirely on the existing tokens and on .card / .btn / .tag, so the
 * roster inherits the dark-mode-oled direction rather than restating it.
 * --tap is already 36px desktop / 44px coarse pointer, which is the touch
 * target rule; nothing here overrides it.
 */
.roster-note {
  font-size: 13px;
  line-height: 1.55;
  color: var(--parchment-dim);
  margin-bottom: var(--s3);
}
.roster-note:last-child { margin-bottom: 0; }
.roster-note strong { color: var(--parchment); font-weight: 600; }
.roster-note code {
  font-size: 12px;
  padding: 1px 4px;
  border-radius: var(--radius-sm);
  background: var(--ink-4);
  color: var(--parchment);
}
.roster-note--warn { color: var(--parchment-faint); }

.roster-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.roster-list {
  list-style: none;
  margin: 0 0 var(--s4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.roster-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--ink-3);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
}
.roster-row.is-open {
  border-color: rgba(201, 162, 39, 0.4);
  background: var(--ink-4);
}
.roster-id {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;          /* a 1fr/flex track cannot shrink without this */
}
.roster-name {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2);
  font-family: var(--font-display);
  font-size: 17px;
  color: var(--parchment);
}
.roster-row.is-open .roster-name { color: var(--gold-hi); }
.roster-meta { font-size: 13px; color: var(--parchment-dim); }
.roster-when { font-size: 12px; color: var(--parchment-faint); }

.roster-row-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  flex: none;
}
.roster-empty > h3 { color: var(--parchment-dim); }

/* Phone: the row becomes a stack and the two buttons share the width, so
   neither lands under the sticky stat strip or shrinks below --tap. */
@media (max-width: 560px) {
  .roster-row { flex-direction: column; align-items: stretch; }
  .roster-row-actions { width: 100%; }
  .roster-row-actions .btn { flex: 1 1 0; justify-content: center; }
  .roster-actions .btn { flex: 1 1 100%; justify-content: center; }
}
