/* ============ PF2e Character Forge — shared styles ============ */
:root {
  --ink: #12141c; --panel: #1a1d28; --panel-2: #212533; --edge: #2e3345;
  --text: #e9e7df; --muted: #a7a394;
  --brass: #c9a227; --brass-dim: #9a7d24; --verdigris: #4f9a8e; --warn: #d8a02f;
  /* the two numbers a shopper scans an item list for: damage and price */
  --dmg: #e07a72; --coin: #7cc47f;
  /* healing sits beside damage on the same line for spells that do both (Heal, Holy
     Host), so it needs its own hue rather than borrowing the price green */
  --heal: #6fbf9a;
  --serif: "Palatino Linotype", Palatino, Georgia, serif;
  --ui: system-ui, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
}
/* dark is the primary theme — light is opt-in via the ◐ Theme toggle only */
:root[data-theme="dark"] { --ink:#12141c; --panel:#1a1d28; --panel-2:#212533; --edge:#2e3345; --text:#e9e7df; --muted:#a7a394; --brass:#c9a227; --brass-dim:#9a7d24; }
:root[data-theme="light"] { --ink:#d9d5c9; --panel:#eceadf; --panel-2:#e2dfd2; --edge:#c4bda9; --text:#23252e; --muted:#6a6656; --brass:#9a7d24; --brass-dim:#b8942a;
  /* the dropdown paints itself on the light panel here, so both need to darken */
  --dmg:#a3322a; --coin:#2f7a3f; --heal:#1f6b52; }

* { box-sizing: border-box; }
body { margin: 0; background: var(--ink); color: var(--text); font-family: var(--ui); line-height: 1.45; }
button { font-family: inherit; }

/* ================= app chrome ================= */
header.top {
  display: flex; align-items: center; gap: 14px; padding: 11px 20px;
  border-bottom: 1px solid var(--edge); background: linear-gradient(180deg, var(--panel-2), var(--panel));
  position: sticky; top: 0; z-index: 20; flex-wrap: wrap;
}
.crest {
  width: 34px; height: 34px; flex: none; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--brass), var(--brass-dim) 70%, #5c4a15);
  display: grid; place-items: center; color: #241d05; font-family: var(--serif); font-weight: 700; font-size: 18px;
}
header.top h1 { font-family: var(--serif); font-size: 18px; margin: 0; }
header.top .tag { font-size: 10.5px; color: var(--muted); font-family: var(--mono); letter-spacing: .1em; }
.pages { display: flex; gap: 6px; margin-left: 14px; }
.pages a { text-decoration: none; font-size: 12.5px; padding: 6px 12px; border: 1px solid var(--edge); border-radius: 5px; color: var(--text); background: var(--panel-2); }
.pages a:hover { border-color: var(--brass); }
.pages a.on { background: var(--brass); color: #241d05; border-color: var(--brass); font-weight: 600; }
.top-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.btn { background: var(--panel-2); color: var(--text); border: 1px solid var(--edge); padding: 6px 12px; border-radius: 5px; cursor: pointer; font-size: 13px; }
.btn:hover { border-color: var(--brass); }
.btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.btn.primary { background: var(--brass); color: #241d05; border-color: var(--brass); font-weight: 600; }

/* The middle track is capped at the SHEET's own measure, not left as 1fr. With 1fr it
   swallowed every pixel a collapsed side column gave up — and since .paper stops at
   --sheetw, that slack became dead space between the sheet and the strip (144px with the
   checklist collapsed, 354px with the rail, 490px with both). Capping the track and
   centring the grid puts the slack OUTSIDE the layout instead, so the strips always sit
   one gap-width from the sheet. Nothing moves when both columns are open: the track was
   already narrower than the cap at every width. */
/* --railw was 400px until 2026-08-29. The rail is tighter than it looks — the empty
   space is in the closed panel headers, while the bodies use the full width — so this
   was measured rather than guessed: the FIRST thing to break on the way down is the
   Equipment panel's armor category filter, whose level select drops below the tick boxes
   at 380px. That row is allowed to wrap now, which buys the sheet 60px at every width
   below its cap. The next constraint after it is the Origin panel's attribute-filter
   rows, which run out of room near 315px — so 340 leaves a little headroom, not none. */
:root { --sheetw: 850px; --railw: 340px; }
.app { display: grid; grid-template-columns: var(--railw) minmax(0,var(--sheetw)) 190px; gap: 20px; max-width: 1500px; margin: 18px auto 50px; padding: 0 16px; align-items: start; justify-content: center; }
/* Either side column can collapse to an icon strip, handing the width back to the sheet.
   Each breakpoint has to restate the collapsed tracks: `.app.rail-min` outranks a plain
   `.app` on specificity, so a rule written only at full width would win inside the
   narrower media queries too and drag a 2-column layout back to 3. */
.app.rail-min { grid-template-columns: var(--minw) minmax(0,var(--sheetw)) 190px; }
.app.steps-min { grid-template-columns: var(--railw) minmax(0,var(--sheetw)) var(--minw); }
.app.rail-min.steps-min { grid-template-columns: var(--minw) minmax(0,var(--sheetw)) var(--minw); }
@media (max-width: 1240px) {
  .app, .app.steps-min { grid-template-columns: var(--railw) minmax(0,var(--sheetw)); }
  .app.rail-min, .app.rail-min.steps-min { grid-template-columns: var(--minw) minmax(0,var(--sheetw)); }
  .steps { display: none; }
}
@media (max-width: 980px) {
  .app, .app.rail-min, .app.steps-min, .app.rail-min.steps-min { grid-template-columns: 1fr; }
  .rail { position: static !important; max-height: none !important; }
  /* stacked, a vertical strip would be a tall thin column of icons above the sheet —
     lay it out as a horizontal bar instead, which is the shape that actually saves space */
  .app.rail-min .railmin, .app.steps-min .stepsmin { flex-direction: row; flex-wrap: wrap; justify-content: flex-start; }
  .app.rail-min .railtop, .app.steps-min .stepstop { justify-content: flex-start; }
}

/* ---- motion ----
   Kept short and functional, matching the durations already here (.12s–.25s).
   The COLLAPSE is animatable precisely because it was built as a class flip on .app with
   no re-render — the grid track interpolates from --railw to --minw and back on the same
   DOM. Most other interactions in this app go through render(), which replaces the rail's
   innerHTML wholesale; a transition can't run on a node that was just created, so those
   need a different technique entirely (see the note in CLAUDE.md).
   The icon strip fades in behind the width change. @starting-style + allow-discrete is
   what lets a display:none → flex element animate at all; where they aren't supported the
   strip simply appears, which is exactly today's behaviour. */
/* set on <html> for the first two frames, so a restored collapsed column snaps into place
   instead of playing a collapse the player never asked for — see forge-app.js */
.no-anim, .no-anim *, .no-anim *::before, .no-anim *::after { transition: none !important; animation: none !important; }
.app { transition: grid-template-columns .24s cubic-bezier(.4, 0, .2, 1); }
.minstrip { opacity: 0; transition: opacity .18s ease .06s, display .18s allow-discrete; }
.app.rail-min .railmin, .app.steps-min .stepsmin { opacity: 1; }
@starting-style {
  .app.rail-min .railmin, .app.steps-min .stepsmin { opacity: 0; }
}
/* Respect the OS setting — the app had no guard at all before there was motion to guard.
   Durations rather than `none`, so transitionend still fires for anything that waits on it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}

/* ================= collapsed side columns (icon strips) =================
   Both strips and both full views are ALWAYS in the DOM; the `rail-min` / `steps-min`
   classes on .app decide which is shown. That is what lets the toggle be a class flip
   rather than a re-render, so collapsing never disturbs scroll or open panels. */
/* One footprint for every control in both strips — the toggles and the panel icons all
   size off these, so a change to one can't leave the other behind. The toggles were
   padding-sized and came out 33x31 against 48x44 icons, which read as an afterthought
   sitting above the row it belongs to.
   Trimmed 48x44 -> 38x34 on request: at 44 the toggle was the tallest thing in either
   column and read as a heavy lid over the list it belongs to. Everything sizes off these
   two tokens INCLUDING the rail's panel headers (see `details.rg > summary`), so the
   header rows come down with them and the strip still lands row-for-row on the panels. */
:root { --minw: 52px; --minbtn-w: 38px; --minbtn-h: 34px; }
/* each toggle sits at its own column's OUTER edge — the rail's on the left, the
   checklist's on the right — so both read as "push this column out of the way" rather
   than crowding the sheet in the middle */
.mintop { display: flex; }
.railtop { justify-content: flex-start; }
.stepstop { justify-content: flex-end; }
.mintog { background: var(--panel); color: var(--brass); border: 1px solid var(--edge);
  border-radius: 6px; cursor: pointer; font-family: var(--mono); font-size: 14px; line-height: 1;
  width: var(--minbtn-w); height: var(--minbtn-h); padding: 0;
  display: flex; align-items: center; justify-content: center; }
.mintog:hover { border-color: var(--brass-dim); color: var(--text); }
.mintog:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* same gap as .rail's, so the icons land on the panel rows they stand for */
.minstrip { display: none; gap: 8px; }
.app.rail-min .railmin, .app.steps-min .stepsmin { display: flex; flex-direction: column; align-items: center; }
.app.rail-min details.rg { display: none; }
.app.steps-min .stepsfull { display: none; }
.app.rail-min .railtop, .app.steps-min .stepstop { justify-content: center; }
/* the icons themselves */
.minicon { position: relative; width: var(--minbtn-w); height: var(--minbtn-h); display: flex; align-items: center; justify-content: center;
  background: var(--panel); color: var(--brass); border: 1px solid var(--edge); border-radius: 6px; cursor: pointer; }
.minicon .g { font-size: 17px; line-height: 1; }
.minicon:hover { border-color: var(--brass-dim); color: var(--text); }
.minicon:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
/* a panel wearing a badge ("PICK", "12 LEFT") still says so collapsed — losing that
   would hide the one cue telling you a section still needs you */
.minicon .mdot { position: absolute; top: 4px; right: 4px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--warn); }
/* the checklist's collapsed form: the required-step count, then one tick per step */
.minprog { font-family: var(--mono); font-size: 9.5px; color: var(--muted); letter-spacing: .06em; padding-bottom: 2px; }
.minstep { width: 26px; height: 26px; border-radius: 50%; cursor: pointer; padding: 0;
  border: 1.5px solid var(--edge); background: var(--panel); color: transparent; font-size: 14px; line-height: 1; }
.minstep.opt { border-style: dashed; }
.minstep.done { background: var(--brass); border-color: var(--brass); color: #241d05; }
.minstep:hover { border-color: var(--brass-dim); }
.minstep:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
.app.steps-min .steps { padding: 8px 6px 10px; }

/* ================= right column: creation checklist ================= */
.steps { position: sticky; top: 62px; max-height: calc(100vh - 76px); overflow-y: auto;
  background: var(--panel); border: 1px solid var(--edge); border-radius: 6px; padding: 12px 12px 14px; }
.steps h3 { margin: 0 0 3px; font-family: var(--mono); font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--brass); font-weight: 400; }
.stepbar { height: 5px; background: var(--panel-2); border-radius: 3px; overflow: hidden; margin: 5px 0 3px; }
.stepbar > div { height: 100%; background: var(--brass); transition: width .25s; }
.stepsum { font-size: 11px; color: var(--muted); margin-bottom: 6px; }
/* The steps carry NO margin between them - the space a reader sees between two rows is
   each button's own vertical padding, doubled. 3px is the floor worth taking: the row is
   a flex box around a 17px tick sitting 1px down, so 3+1+17+3 = 24px, and below this the
   tick (not the padding) sets the height and the rows stop getting closer. */
.stepitem { display: flex; align-items: flex-start; gap: 8px; width: 100%; text-align: left;
  background: transparent; border: 0; padding: 3px 6px; border-radius: 5px; cursor: pointer;
  color: var(--text); font-size: 12.5px; font-family: var(--ui); line-height: 1.35; }
.stepitem:hover { background: var(--panel-2); }
.stepitem .tick { flex: none; width: 17px; height: 17px; border-radius: 50%; border: 1.5px solid var(--edge);
  display: grid; place-items: center; font-size: 10px; color: transparent; margin-top: 1px; }
.stepitem.done .tick { background: var(--good); border-color: var(--good); color: #071613; }
.stepitem.done { color: var(--muted); }
.stepitem.done .lbl { text-decoration: line-through; text-decoration-color: color-mix(in srgb, var(--muted) 60%, transparent); }
.stepitem.opt { color: var(--verdigris); }
.stepitem.opt .tick { border-color: color-mix(in srgb, var(--verdigris) 55%, var(--edge)); }
.stepitem.opt.done { color: var(--muted); }
.stepitem .lbl small { display: block; font-size: 9.5px; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; }
.steps .sep { font-family: var(--mono); font-size: 9.5px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--verdigris); margin: 7px 6px 1px; }
/* "Wealth by level": a whole table row inside one checklist entry, so it gets its own
   block under the label rather than being squeezed into the inline <small> note. The
   strike-through the done state puts on .lbl must not run through the numbers. */
/* inline-block, not block: the done state puts a line-through on .lbl, and CSS text
   decoration PROPAGATES into descendants — a child cannot switch it off with
   text-decoration:none. An atomic inline (inline-block) is not decorated by its
   ancestor, which is the only way to keep the numbers legible once ticked. */
.stepitem .wbl { display: inline-block; width: 100%; margin-top: 3px;
  font-family: var(--mono); font-size: 10px; color: var(--muted); }
.stepitem .wbl .r { display: flex; justify-content: space-between; gap: 6px; line-height: 1.5; }
.stepitem .wbl .r b { font-weight: 400; letter-spacing: .06em; text-transform: uppercase; font-size: 9px; }
.stepitem .wbl .r span { color: var(--text); }
.stepitem.done .wbl .r span { color: var(--good); }
/* stands where Carrying / Short by used to — the block states the table's row for this
   level, and this keeps the target and the starting coin from reading as one list.
   A centred ornament flanked by hairlines that fade outward: the classic printed
   section-break, which is the right register for a character sheet and reads
   unmistakably as a divider rather than as stray punctuation (three plain dots at 9px
   looked like dirt on the panel). The glyph is brass — the app's ornament colour — while
   the rules stay edge-grey so they recede. ❖ is a text-presentation codepoint, like every
   other glyph in this UI; the emoji-capable ornaments would render in full colour. */
.stepitem .wbl .sepdots { display: flex; align-items: center; gap: 7px;
  color: var(--brass); font-size: 11px; line-height: 1; margin: 6px 0 5px; user-select: none; }
.stepitem .wbl .sepdots::before, .stepitem .wbl .sepdots::after { content: ""; flex: 1; height: 1px; }
.stepitem .wbl .sepdots::before { background: linear-gradient(to right, transparent, var(--edge)); }
.stepitem .wbl .sepdots::after { background: linear-gradient(to left, transparent, var(--edge)); }
.stepitem .wbl .itemsline { display: block; margin-top: 2px; padding-top: 2px; border-top: 1px solid var(--edge);
  font-size: 9px; line-height: 1.45; color: var(--muted); }
@media print { .steps { display: none !important; } }

/* ================= left rail (builder) ================= */
.rail { position: sticky; top: 62px; max-height: calc(100vh - 76px); overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding-right: 2px; }
details.rg { background: var(--panel); border: 1px solid var(--edge); border-radius: 6px; }
/* A panel header is the expanded form of the icon that replaces it, so it stands the
   same height: a closed rail and its icon strip then line up row for row, and toggling
   between them doesn't shuffle everything vertically.
   Minus 2px because the height is set on the SUMMARY while the 1px border lives on the
   parent details — the row a reader sees is 1 + summary + 1. `min-height`, not `height`,
   so a header that ever wraps to two lines still grows rather than clipping. */
details.rg > summary {
  cursor: pointer; list-style: none; min-height: calc(var(--minbtn-h) - 2px); padding: 0 12px;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: .14em; text-transform: uppercase; color: var(--brass); display: flex; align-items: center; gap: 8px;
}
details.rg > summary::before { content: "\25B8"; font-size: 10px; transition: transform .12s; }
details.rg[open] > summary::before { transform: rotate(90deg); }
details.rg > summary .badge { margin-left: auto; font-size: 10px; background: var(--warn); color: #241d05; border-radius: 10px; padding: 1px 8px; letter-spacing: .02em; }
.rg-b { padding: 4px 12px 12px; }
.field { display: flex; flex-direction: column; gap: 3px; margin-top: 8px; }
.field > span { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .11em; color: var(--muted); }
input[type=text], select {
  background: var(--ink); color: var(--text); border: 1px solid var(--edge); border-radius: 4px;
  padding: 6px 8px; font-family: var(--ui); font-size: 13px; width: 100%;
}
input:focus-visible, select:focus-visible { outline: 2px solid var(--brass); outline-offset: 1px; }
.row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 6px; }
.tbtn { background: var(--panel-2); color: var(--text); border: 1px solid var(--edge); border-radius: 4px; padding: 4px 9px; cursor: pointer; font-size: 12px; font-family: var(--mono); }
.tbtn.on { background: var(--brass); border-color: var(--brass); color: #241d05; font-weight: 600; }
.tbtn:disabled { opacity: .35; cursor: not-allowed; }
.note { font-size: 11.5px; color: var(--muted); margin-top: 6px; }
.warnnote { font-size: 11.5px; color: var(--warn); margin-top: 6px; }
.cfggrp { margin-top: 10px; }
.cfggrp > .lbl { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .11em; color: var(--muted); }
.chk { display: flex; align-items: center; gap: 7px; font-size: 12.5px; padding: 2px 0; }
.chk input { accent-color: var(--brass); }
/* equipment section headers — larger, fantasy display face */
.eqhead {
  font-family: "Cinzel", var(--serif); font-weight: 700; font-size: 16px;
  letter-spacing: .08em; color: var(--brass);
  display: flex; align-items: baseline; gap: 10px; margin: 4px 0 2px;
}
.eqhead::after { content: ""; flex: 1; height: 1px; align-self: center;
  background: linear-gradient(90deg, color-mix(in srgb, var(--brass) 45%, transparent), transparent); }
.eqhead .eqsub { font-family: var(--mono); font-size: 10px; font-weight: 400;
  letter-spacing: .1em; color: var(--muted); }
.cfggrp + .cfggrp { margin-top: 16px; }

/* coin purse + wealth summary (Equipment panel — rail and page 2 sheet alike) */
.purse { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
/* the four cells must sit on ONE line: the label is what overflows a 400px rail,
   so it never wraps and tightens its tracking instead of pushing its input down */
.purse .field { margin-top: 4px; min-width: 0; }
.purse .field > span { white-space: nowrap; letter-spacing: .04em; overflow: hidden; text-overflow: ellipsis; }
.purse input[type=text] { text-align: right; font-family: var(--mono); min-width: 0; padding-left: 5px; padding-right: 5px; }
.wealth { margin-top: 10px; border: 1px solid var(--edge); border-radius: 4px; padding: 6px 9px; }
.wealth > div { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 2px 0; }
.wealth span { font-family: var(--mono); font-size: 9.5px; text-transform: uppercase; letter-spacing: .11em; color: var(--muted); }
.wealth b { font-family: var(--mono); font-size: 12px; color: var(--text); }
.wealth .tot { border-top: 1px solid var(--edge); margin-top: 3px; padding-top: 5px; }
.wealth .tot b { color: var(--brass); font-size: 13.5px; }

.langgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 10px; margin-top: 5px; max-height: 220px; overflow-y: auto; }
.langgrid .chk { font-size: 11.5px; }

/* The armor row is the tight one: Unarmored/Light/Medium/Heavy plus the level select
   needed 358px inside a 357px rail, so the select wrapped onto its own line by a single
   pixel. Pulling each label against its own box (gap 5 → 3) and trimming the space
   between boxes (12 → 8) buys ~28px, which puts the select back beside "Heavy". */
.gearcats { display: flex; flex-wrap: wrap; align-items: center; gap: 0 8px; margin: 4px 0 6px; }
/* attribute filter over the ancestry and background lists. All six boxes must stay
   on ONE line inside a 400px rail, so the row never wraps and the label is pulled
   tight against its own box (gap 3px) — the space between boxes then reads as the
   separator, and STR/DEX/... can't be mistaken for the neighbouring checkbox. */
.gearcats.attrf { flex-wrap: nowrap; gap: 0 8px; margin: 0 0 1px; }
/* the field caption above it (ANCESTRY / BACKGROUND) is the heading; "BOOSTS" labels a
   control nested under it, so it stays the small muted mono while the caption grows */
.field.field-lg > span { font-size: 12px; letter-spacing: .13em; color: var(--text); font-weight: 600; }
.gearcats .filtlbl { font-family: var(--mono); font-size: 9px; text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); margin-right: 1px; }
.gearcats.attrf .chk { gap: 3px; padding: 0; font-family: var(--mono); font-size: 10.5px; letter-spacing: 0; }
.gearcats.attrf .chk input { width: 12px; height: 12px; margin: 0; flex: none; }
.gearcats .chk { font-size: 11px; color: var(--muted); gap: 3px; padding: 1px 0; }
.gearcats .chk input { flex: none; }
.gearcats .lvlf { width: auto; min-width: 70px; font-size: 11px; padding: 3px 4px; margin-left: auto; }

/* gear item rows: separated so they don't clump */
.field.gearrow { margin-top: 12px; border-top: 1px dotted var(--edge); padding-top: 8px; }
.field.gearrow:first-of-type { border-top: 0; }

/* dropdown row descriptions: always visible, clamped to a few lines. Previously
   hover-only (display:none until :hover) — that changed each row's height on
   mouseover, which shifted the rows under the cursor and made the active/highlighted
   row skip an entry when moving the mouse down the list (moving up "worked" only
   because rows below the cursor don't reflow the ones above it). */
#cbxdrop .cbx-row { flex-wrap: wrap; }
#cbxdrop .cbx-row .cbx-desc { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
  flex-basis: 100%; font-size: 10.5px; color: var(--muted);
  line-height: 1.4; padding: 2px 0 3px 10px; border-left: 2px solid var(--brass); margin: 2px 0 2px 2px; }
/* the mechanical-shorthand line: the item's trait list and anything it grants.
   Its own always-visible line, so the clamped flavor text can never eat it. */
#cbxdrop .cbx-row .cbx-grant { flex-basis: 100%; font-size: 10px; font-family: var(--mono);
  color: var(--brass); line-height: 1.35; padding: 1px 0 0 10px; }
.slotgrp { margin-top: 10px; border-top: 1px dotted var(--edge); padding-top: 8px; cursor: default; user-select: none; }
.slotgrp select { cursor: pointer; }
.slotgrp .lv { font-family: var(--mono); font-size: 10px; color: var(--brass); letter-spacing: .1em; }
.slot { display: flex; flex-direction: column; gap: 2px; margin-top: 5px; }
.slot > span { font-size: 10.5px; color: var(--muted); }
.sprow { display: flex; align-items: center; gap: 6px; padding: 2px 0; font-size: 12.5px; }
.sprow .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color .1s; }

/* each spell's short description is always visible (not hover-only — hover-only
   descriptions changed row height on mouseover, shifting the rows under the cursor;
   see the same fix on #cbxdrop .cbx-desc above) — hovering just highlights the name */
.spitem { border-radius: 4px; cursor: default; user-select: none; }
.spitem .stog { cursor: pointer; }
.spitem:hover { background: color-mix(in srgb, var(--panel-2) 60%, transparent); }
/* a heightened entry is the same spell cast in a higher slot, so it is tinted rather
   than restyled - it belongs in the list, just not mistakable for a native-rank spell */
.spitem.sph .sprow .nm { color: var(--muted); }
.spitem.sph:hover .sprow .nm { color: var(--brass); }
.sprow .hfrom { font-family: var(--mono); font-size: 9.5px; color: var(--verdigris);
  border: 1px solid var(--edge); border-radius: 3px; padding: 0 3px; margin-left: 4px; }
.spitem:hover .sprow .nm { color: var(--brass); font-weight: 600; }
.spitem .spdesc { font-size: 11px; color: var(--muted); line-height: 1.45;
  padding: 1px 8px 6px 10px; border-left: 2px solid var(--brass); margin: 0 0 3px 2px; }
/* Only the PROSE clamps. The clamp used to sit on the whole box, so the meta line under it
   was cut too - and that line carries the numbers and the TRAIT list, which is where the
   Remaster keeps the cast requirement. A trait line sliced mid-word reads as a rendering
   fault rather than as data, so the prose gets the three lines and the stat line is never
   truncated. Splitting it here rather than moving .spmeta2 out of .spdesc keeps the box a
   single bordered unit, and keeps working the suite helpers that read a row's description
   by cloning .spdesc and removing .spmeta2. */
.spitem .spdesc .spprose { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.spitem .spdesc .spmeta2 { margin-top: 2px; color: var(--verdigris); font-size: 10.5px; }
/* technical data picked out inside a spell's prose (see hiTech() in forge-app.js) —
   same verdigris as the meta line above, so numbers read as "stat" wherever they sit.
   Works on both surfaces: --verdigris is a global, not a paper- or rail-scoped var. */
.tk { color: var(--verdigris); font-weight: 600; }
.sprow .tg { margin-left: auto; display: flex; gap: 4px; flex: none; }
.stog { width: 21px; height: 21px; border-radius: 4px; border: 1px solid var(--edge); background: var(--panel-2); cursor: pointer; font-size: 11px; color: var(--muted); line-height: 1; padding: 0; }
.stog.on-book { background: var(--brass); border-color: var(--brass); color: #241d05; }
.stog.on-prep { background: var(--verdigris); border-color: var(--verdigris); color: #071613; }
.stog:disabled { opacity: .3; cursor: not-allowed; }
.srank { margin-top: 9px; }
.srank > .lbl { font-family: var(--mono); font-size: 10px; color: var(--brass); letter-spacing: .1em; }
.sp-more { font-size: 11px; color: var(--muted); font-style: italic; padding: 4px 0 2px; text-align: center;
  border-top: 1px dashed var(--edge); margin-top: 4px; }

/* ================= the paper sheet ================= */
.paper {
  --p-bg: #171a22; --p-box: #20242f; --p-ink: #e6e3d8; --p-fade: #8f8a7c;
  --p-line: #4b5065; --p-soft: #3b4053; --p-soft2: #333848; --p-hint: #2a2f3d;
  --p-red: #b04a45; --p-red-ink: #f6efe3;
  background: var(--p-bg); color: var(--p-ink); border: 1px solid var(--p-line);
  box-shadow: 0 12px 40px rgba(0,0,0,.35); border-radius: 3px;
  /* same token the grid's middle track is capped with, so the two can never drift apart
     and reopen the dead-space gap */
  padding: 20px 22px 24px; max-width: var(--sheetw); font-size: 13px;
}
.paper .ttl { display: flex; align-items: baseline; justify-content: space-between; border-bottom: 3px solid var(--p-red); padding-bottom: 4px; }
.paper .ttl h2 { margin: 0; font-family: var(--serif); font-size: 20px; letter-spacing: .02em; color: var(--p-red); }
.paper .ttl span { font-family: var(--mono); font-size: 9px; letter-spacing: .22em; color: var(--p-fade); }

.pbar {
  background: var(--p-red); color: var(--p-red-ink); text-transform: uppercase;
  font-weight: 700; font-size: 11px; letter-spacing: .16em;
  padding: 4px 16px 3px 9px; margin: 16px 0 8px;
  clip-path: polygon(0 0, 100% 0, calc(100% - 13px) 100%, 0 100%);
}
.pbox { border: 1.5px solid var(--p-line); border-radius: 3px; background: var(--p-box); }
.plbl { font-size: 8px; text-transform: uppercase; letter-spacing: .13em; color: var(--p-fade); font-weight: 700; }
.pnum { font-weight: 700; font-variant-numeric: tabular-nums; }

/* identity strip for pages 2-3 */
.pstrip { display: flex; gap: 16px; flex-wrap: wrap; border-bottom: 1.5px solid var(--p-line); padding: 8px 2px 6px; }
.pstrip .cell .v { font-weight: 700; font-size: 14px; min-height: 17px; }
.pstrip .cell.grow { flex: 1; }

/* TEML */
.teml { display: inline-flex; gap: 2px; vertical-align: middle; }
.tb { width: 13px; height: 13px; border: 1px solid var(--p-line); border-radius: 2px; font-size: 8px; font-weight: 700; display: grid; place-items: center; color: var(--p-fade); background: var(--p-box); }
.tb.fill { background: var(--p-red); border-color: var(--p-red); color: var(--p-red-ink); }

/* header grid */
.phead { display: grid; grid-template-columns: 2.2fr 1.2fr .7fr; gap: 8px; margin-top: 12px; }
.phead2 { display: grid; grid-template-columns: 1.2fr .5fr 1.6fr 1.1fr .6fr; gap: 8px; margin-top: 8px; }
.pfield { border-bottom: 1.5px solid var(--p-line); padding: 1px 2px 2px; min-height: 30px; display: flex; flex-direction: column; justify-content: flex-end; }
.pfield .v { font-size: 14.5px; font-weight: 600; min-height: 18px; }
.pfield .v.big { font-size: 18px; font-family: var(--serif); }
.pfield .plbl { margin-top: 1px; }

/* attributes — compact, modifier only */
.pattr { display: grid; grid-template-columns: repeat(6, minmax(52px, 76px)); gap: 6px; }
.abox { text-align: center; overflow: hidden; }
.abox .hd { background: var(--p-red); color: var(--p-red-ink); font-size: 8px; font-weight: 700; letter-spacing: .16em; padding: 1.5px 0; }
.abox .m { font-size: 14px; font-weight: 700; line-height: 1.4; padding: 1px 0 2px; font-variant-numeric: tabular-nums; }
/* half-step marker: an odd score (19) reads +4 like an 18, but is one boost from +5.
   Superscripted and small so it never competes with the modifier it annotates, and
   absolutely positioned out of the flow so a marked cell's number stays aligned with
   the five unmarked ones (the row is a fixed 6-column grid). */
.abox .m { position: relative; }
.abox .half { position: absolute; top: 2px; margin-left: 1px; font-size: 11px; font-weight: 700;
  line-height: 1; color: var(--p-red); cursor: help; }
.halfnote { font-size: 8.5px; color: var(--p-fade); margin-top: 3px; line-height: 1.35; }
.halfnote b { color: var(--p-red); }
@media (max-width: 700px) { .pattr { grid-template-columns: repeat(3,1fr); } }

/* defense row — compact */
.pdef { display: grid; grid-template-columns: 104px 1fr 160px; gap: 8px; align-items: start; }
@media (max-width: 700px) { .pdef { grid-template-columns: 1fr; } }
.shield {
  width: 58px; height: 62px; margin: 0 auto;
  clip-path: polygon(0 0, 100% 0, 100% 55%, 50% 100%, 0 55%);
  background: var(--p-line); display: grid; place-items: center; padding: 2px;
}
.shield .in { width: 100%; height: 100%; clip-path: polygon(2% 2%, 98% 2%, 98% 54%, 50% 97%, 2% 54%); background: var(--p-box); display: grid; place-items: center; text-align: center; }
.shield .n { font-size: 17px; font-weight: 800; line-height: 1; }
.shield .plbl { font-size: 6.5px; }
.acbrk { display: flex; justify-content: center; gap: 5px; margin-top: 4px; font-size: 8.5px; color: var(--p-fade); text-align: center; }
.acbrk b { display: block; font-size: 11px; color: var(--p-ink); }
.saves3 { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
.savebox { text-align: center; padding-bottom: 4px; overflow: hidden; }
.savebox .hd { background: var(--p-red); color: var(--p-red-ink); font-size: 8px; font-weight: 700; letter-spacing: .13em; padding: 1.5px 0; margin-bottom: 2px; }
.savebox .n { font-size: 16px; font-weight: 700; }
.savebox .brk { font-size: 8.5px; color: var(--p-fade); margin-top: 2px; }
.hpbox { padding: 6px 9px; }
.hpbox .top { display: flex; align-items: baseline; gap: 7px; justify-content: center; }
.hpbox .max { font-size: 28px; font-weight: 800; line-height: 1.1; }
.hpbox .top .plbl { font-size: 10px; }
/* current / max. The max is derived and read-only; the slash and the smaller max let
   the eye land on the number that changes during play. */
.hpbox .top .slash { font-size: 18px; font-weight: 400; color: var(--p-fade); margin: 0 -3px; }
.hpbox .top .maxof { font-size: 17px; font-weight: 700; color: var(--p-fade); line-height: 1.1; }
.hpwrite { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 5px; }
/* still the dashed write-in box it always was, but now with a real field in it — the
   label sits left, the value right, so an empty one still reads as somewhere to write */
/* label ABOVE the value, not beside it: at 8px with .13em tracking "TEMPORARY" fills the
   whole 57px cell on its own, and a side-by-side input collapsed to zero width */
.wcell { border: 1px dashed var(--p-soft); border-radius: 3px; min-height: 30px; padding: 2px 5px;
  display: flex; flex-direction: column; justify-content: center; cursor: text; }
.wcell .plbl { line-height: 1.25; }
.wcell input { width: 100%; border: 0; background: transparent; color: var(--p-ink);
  font: inherit; font-size: 14px; font-weight: 700; text-align: right; padding: 0; line-height: 1.2; }
.wcell input:focus { outline: none; }
.wcell:focus-within { border-style: solid; border-color: var(--p-line); }
@media print {
  /* the Current box's placeholder is the maximum — useful on screen as a hint, but in
     print a greyed number in a blank box reads as a filled-in value */
  .wcell input::placeholder { color: transparent; }
  .wcell:focus-within { border-style: dashed; border-color: var(--p-soft); }
}
.dots { margin-top: 5px; font-size: 8px; color: var(--p-fade); letter-spacing: .07em; text-align: center; }
.dots .dotrow { margin-top: 2px; }
.dot { display: inline-block; width: 9px; height: 9px; border: 1.2px solid var(--p-line); border-radius: 50%; vertical-align: -1px; margin-right: 2px; background: var(--p-box); }

/* hero point pips — clickable on screen, plain circles in print */
.hpip { display: inline-block; width: 15px; height: 15px; border-radius: 50%;
  border: 1.6px solid var(--p-line); background: var(--p-box); cursor: pointer; margin: 0 2px; vertical-align: middle; }
.hpip.on { background: var(--p-red); border-color: var(--p-red); }
.hpip:hover { box-shadow: 0 0 0 2px color-mix(in srgb, var(--p-red) 30%, transparent); }
@media print { .hpip { -webkit-print-color-adjust: exact; print-color-adjust: exact; } }

/* misc row */
.pmisc { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px,1fr)); gap: 6px; }
.mbox { padding: 5px 8px; text-align: center; }
.mbox .r1 { display: flex; align-items: center; gap: 7px; justify-content: center; }
.mbox .n { font-size: 14px; font-weight: 700; }
.mbox .sub { font-size: 9px; color: var(--p-fade); margin-top: 1px; }

/* body two-col */
.pcols { display: grid; grid-template-columns: 1.15fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 700px) { .pcols { grid-template-columns: 1fr; } }

/* skills — rows clickable */
table.psk { width: 100%; border-collapse: collapse; }
table.psk td { padding: 2.5px 3px; border-bottom: 1px solid var(--p-soft2); font-size: 12px; }
/* Two click targets per row: the NAME opens the skill's rules text, the T E M L cell
   spends points. Both are clicks, so both get the hand — every other click target in
   this app does, and a help cursor on the name suggested "hover me" for something that
   only answers to a click.
   The cells between them (ability, total) are inert and say so: the row as a whole used
   to carry `cursor: pointer` from when clicking anywhere on it cycled the rank, which now
   promises a click on two columns that don't take one. */
/* the inert default is deliberately the WEAKEST selector here: written as
   `tr[data-psk] td` it scores (0,2,3) and outranks `td.sknm` at (0,2,2), which silently
   left the name showing an arrow. Plain `td` is (0,1,2) and loses to both live rules. */
/* Both live halves take the plain system hand. A drawn hand-plus-question-mark cursor
   lived here briefly; it was reverted on request — a custom cursor never quite matches
   the OS one it sits beside, and the popup explains itself once opened. */
table.psk td { cursor: default; }
table.psk td.sknm,
table.psk td.tm[data-skcycle], table.psk td.tm[data-lorecycle] { cursor: pointer; }
table.psk td.sknm:hover { color: var(--p-red); }
table.psk td.tot { text-align: right; font-weight: 700; font-variant-numeric: tabular-nums; width: 34px; }
table.psk td.ab { color: var(--p-fade); font-size: 9.5px; text-transform: uppercase; width: 28px; }
table.psk td.tm { width: 66px; }
/* the row still highlights as a unit — that is orientation across to the numbers, not a
   promise of a click; the cursors above are what say which halves are live */
table.psk tr[data-psk]:hover td { background: var(--p-hint); }
table.psk tr.locked { cursor: default; }
table.psk tr.locked:hover td { background: transparent; }
.skhint { font-size: 10px; color: var(--p-fade); margin: 2px 0 4px; }
.skhint b { color: var(--p-red); }

/* strikes */
.strike { padding: 6px 9px; margin-bottom: 6px; }
.strike .r1 { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.strike .wn { font-weight: 700; font-size: 13.5px; }
.strike .atk { font-size: 16px; font-weight: 800; }
.strike .mapv { font-size: 10.5px; font-weight: 700; color: var(--p-fade); font-variant-numeric: tabular-nums; }
.strike .sub { font-size: 10px; color: var(--p-fade); margin-top: 1px; }
.strike .sub b.dmg { color: var(--p-ink); font-size: 11px; }
.strike .sub b.wtrait { color: var(--p-red); font-size: 10px; }
/* the weapon's full trait list on a Strike row — mechanical shorthand (Disarm, Trip,
   Versatile S, Volley 30 ft.), so it sits quieter than the deadly/fatal callout */
.strike .sub .wtraits { color: var(--p-fade); font-size: 10px; }
/* which stance grants this Strike — it isn't a carried weapon */
.strike .stancetag { font-size: 8.5px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--p-fade); border: 1px solid var(--p-soft); border-radius: 3px; padding: 0 4px; margin-left: 5px; }
/* the handwraps a stance attack inherits its runes from - same chip, tinted so it reads
   as an item rather than as the stance that granted the attack */
.strike .hwtag { color: var(--brass-dim); border-color: var(--brass-dim); }
.strike .critnote { font-size: 9px; color: var(--p-fade); }
.strike .actg { margin-left: auto; }
.strike .rel { color: var(--p-fade); font-weight: 400; font-size: 8.5px; letter-spacing: 0; }

/* action-cost glyphs (◆ = 1 action, ⟳ = reaction). --p-red only exists inside .paper,
   so the fallback is what the rail's copy of the same control renders in. */
.actg { color: var(--p-red, var(--brass)); font-weight: 700; font-size: 11px; letter-spacing: 1px; white-space: nowrap; }
/* a feat slot's field + its action cost on one line (rail and page 2 alike) */
.slotrow { display: flex; align-items: center; gap: 6px; }
.slotrow input { flex: 1; min-width: 0; }
.slotrow .slotact { flex: none; }
/* an equipment row: its combobox + the ✕ that discards the item */
.eqline { display: flex; align-items: center; gap: 6px; }
.eqline input { flex: 1; min-width: 0; }
.eqline .xdel { flex: none; height: 27px; width: 27px; font-size: 12px; }
.eqline .xdel:hover { border-color: var(--p-red, #b04a45); color: var(--p-red, #b04a45); }
/* stack counter: identical gear shares one row, so the count needs to be adjustable
   without retyping the item. Fixed-width digits keep the ✕ from shifting as it changes. */
.eqline .qty { flex: none; display: flex; align-items: center; gap: 2px; }
.eqline .qty button { height: 27px; width: 22px; font-size: 13px; line-height: 1; padding: 0; }
.eqline .qty b { min-width: 20px; text-align: center; font-family: var(--mono);
  font-size: 11.5px; color: var(--brass); }
.qea { color: var(--muted); font-weight: 400; }
.qx { color: var(--brass); }
.ract { color: var(--brass); font-size: 9.5px; letter-spacing: 1px; white-space: nowrap; flex: none; }

/* prepared-count controls in the rail */
/* signature spell: sits beside the ✎ and reads as "off" until designated, so a rank
   with none chosen doesn't look like a rank with one */
.sigbtn { color: var(--muted); }
.sigbtn.on-sig { color: var(--brass); border-color: var(--brass); font-weight: 700; }
.prepct { min-width: 15px; text-align: center; font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
.prepct.has { color: var(--verdigris); font-weight: 700; }

/* ================= equipment combobox dropdown ================= */
/* position:FIXED, not absolute — the dropdown stays put on screen while the page
   scrolls behind it, instead of sliding away with the document. cbxRender() therefore
   positions it in raw viewport coordinates (no scrollX/scrollY offsets). */
#cbxdrop {
  position: fixed; z-index: 60; display: none;
  max-height: 320px; overflow-y: auto;
  /* the list is a scroller inside a modal: wheeling past its last row must stop there
     rather than chaining outward and dragging the page behind it */
  overscroll-behavior: contain;
  background: var(--panel); border: 1px solid var(--edge); border-radius: 6px;
  box-shadow: 0 10px 30px rgba(0,0,0,.45); font-size: 12.5px;
}
/* Centred over a dim backdrop when there is nowhere good to put it beside the field —
   page 2's Inventory and Feats fields, and any narrow window. Same treatment as the
   spell popup: the rows carry a name, a stats column, a traits line and a clamped
   description, and none of that survives a list as narrow as the input.
   The veil sits just under the dropdown's own z-index so the rows stay clickable. */
#cbxveil {
  position: fixed; inset: 0; z-index: 59; display: none;
  background: rgba(0, 0, 0, .62);
}
#cbxdrop.mid { box-shadow: 0 18px 50px rgba(0,0,0,.55); }
/* the ✕, above the list (60) so it stays clickable over a sticky level header or the
   brass "Available now" bar it may land on — and opaque, so it reads over either */
#cbxclose {
  position: fixed; z-index: 61; display: none; width: 28px; height: 28px; padding: 0;
  border: 1px solid var(--edge); border-radius: 6px; background: var(--panel-2);
  color: var(--brass); cursor: pointer; font-size: 13px; line-height: 1;
}
#cbxclose:hover { border-color: var(--brass); color: var(--text); }
#cbxclose:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
@media print { #cbxdrop, #cbxveil, #cbxclose { display: none !important; } }
#cbxdrop .cbx-h {
  position: sticky; top: 0; display: flex; align-items: center; gap: 8px;
  padding: 4px 10px 3px; background: var(--panel-2);
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--brass); border-top: 1px solid var(--edge);
}
#cbxdrop .cbx-h:first-child { border-top: 0; }
#cbxdrop .cbx-h::after { content: ""; flex: 1; height: 1px; background: color-mix(in srgb, var(--brass) 35%, transparent); }
/* the "Available now" / "Requirements not met" bucket divider — bolder than a plain
   level header, so the two macro sections read as genuinely separate at a glance */
#cbxdrop .cbx-bucket {
  position: sticky; top: 0; z-index: 1; padding: 5px 10px; background: var(--brass);
  color: #241d05; font-family: var(--mono); font-size: 10px; letter-spacing: .12em;
  text-transform: uppercase; font-weight: 700;
}
#cbxdrop .cbx-row {
  display: flex; align-items: baseline; gap: 10px; padding: 4px 10px; cursor: pointer;
}
#cbxdrop .cbx-row:hover, #cbxdrop .cbx-row.act { background: color-mix(in srgb, var(--brass) 18%, var(--panel)); }
#cbxdrop .cbx-row .cn { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#cbxdrop .cbx-row .cm { flex: none; font-size: 10.5px; color: var(--muted); font-family: var(--mono); }
/* action cost, styled like the rail's spell rows; margin-left keeps it off the name
   when that name is long enough to be ellipsised */
#cbxdrop .cbx-row .ract { margin-left: 2px; margin-right: 2px; }
#cbxdrop .cbx-row .cm .cm-dmg { color: var(--dmg); font-weight: 600; }
#cbxdrop .cbx-row .cm .cm-pr { color: var(--coin); font-weight: 600; }
#cbxdrop .cbx-more { padding: 5px 10px; font-size: 11px; color: var(--muted); font-style: italic; }
/* .lk = locked by level, .um = prerequisite genuinely unmet. Both are refused by
   featPickRow(), so both must LOOK unclickable — a bright row carrying a red ❗ that
   silently refuses the click reads as a bug. Feats whose requirement merely can't be
   auto-checked (⚑) stay fully lit, because those really are pickable. */
#cbxdrop .cbx-row.lk, #cbxdrop .cbx-row.um { opacity: .45; cursor: not-allowed; }
#cbxdrop .cbx-row.lk:hover, #cbxdrop .cbx-row.um:hover { background: transparent; }
/* every picker opens BESIDE its field rather than under it whenever there's room to
   the right (see the positioning block in cbxRender) — each row carries a description,
   so the extra width buys more readable rows and a taller scroll area. The .side class
   is what marks that state; without it the dropdown falls back to its compact
   drop-below look. */
#cbxdrop.side {
  /* lifted a step off the page behind it so the floating panel reads as separate.
     Mixed toward white rather than hardcoded, so it lightens in BOTH themes —
     --panel is near-black in dark mode and near-white in light mode. Derived tints
     below all reference --drop-bg so they follow whichever theme is active. */
  --drop-bg: color-mix(in srgb, var(--panel) 86%, #fff);
  background: var(--drop-bg);
  border-color: color-mix(in srgb, var(--edge) 55%, var(--brass));
  font-size: 13px; box-shadow: 0 14px 44px rgba(0,0,0,.55);
}
#cbxdrop.side .cbx-row { padding: 6px 12px; }
#cbxdrop.side .cbx-row:hover, #cbxdrop.side .cbx-row.act { background: color-mix(in srgb, var(--brass) 20%, var(--drop-bg)); }
#cbxdrop.side .cbx-h { background: color-mix(in srgb, var(--drop-bg) 80%, var(--edge)); }
#cbxdrop.side .cbx-row .cbx-desc { -webkit-line-clamp: 4; font-size: 11.5px; padding-bottom: 4px; }
#cbxdrop.side .cbx-row .cbx-grant { font-size: 11px; }
#cbxdrop.side .cbx-h, #cbxdrop.side .cbx-bucket { padding-left: 12px; padding-right: 12px; }

/* rune selects under a weapon/armor row */
.runerow { display: flex; gap: 4px; margin: 4px 0 2px 10px; }
.runerow select { flex: 1; min-width: 0; font-size: 11px; padding: 3px 4px; color: var(--muted); }
.runerow select:has(option:checked:not([value=""])) { color: var(--brass); border-color: var(--brass); }

/* item level chip on the sheet */
.ilvl { font-size: 9px; color: var(--p-fade); text-transform: uppercase; letter-spacing: .06em; }
.wprofs td { padding: 2px 4px; font-size: 11.5px; }
/* the armor category the character is actually wearing, picked out of the list */
.wprofs tr.worn td { font-weight: 700; color: var(--p-red); }
.langbox, .senbox { padding: 6px 9px; font-size: 12.5px; min-height: 34px; }

/* feats & inventory (page 2) */
.pflist { width: 100%; border-collapse: collapse; }
.pflist td { border-bottom: 1px solid var(--p-soft2); padding: 4px 4px; font-size: 12px; vertical-align: top; }
.pflist td.lv { width: 26px; font-weight: 700; color: var(--p-red); text-align: right; }
.pflist td.ty { width: 96px; font-size: 9px; text-transform: uppercase; letter-spacing: .08em; color: var(--p-fade); padding-top: 7px; }
.pflist .un { color: var(--p-soft); font-style: italic; }
.pflist .fdesc { font-size: 10px; color: var(--p-fade); margin-top: 1px; line-height: 1.35; }

/* long-text clamp: collapsed to 5 lines until "more" is clicked */
.clamp { display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; }
.clamp.expanded { display: block; -webkit-line-clamp: unset; overflow: visible; }
/* --p-red only exists inside .paper; the rail reuses this button for its own chosen
   ancestry/heritage/background/class descriptions, so fall back to the rail's brass */
.morebtn {
  display: inline-block; font-size: 9px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--p-red, var(--brass)); background: none; border: none; cursor: pointer; padding: 1px 0 0; font-family: inherit;
}
.morebtn:hover { text-decoration: underline; }
.invgrid { columns: 2; column-gap: 18px; font-size: 12.5px; }
.invgrid .invitem { break-inside: avoid; padding: 3px 0 5px; border-bottom: 1px solid var(--p-soft2); }
.invgrid .bx { display: inline-block; width: 9px; height: 9px; border: 1.2px solid var(--p-line); margin-right: 6px; }
.invgrid .fdesc { font-size: 10px; color: var(--p-fade); line-height: 1.4; margin: 2px 0 0 15px; }
/* printed coin purse: writable boxes, since coins change every session */
.purseprint { display: flex; gap: 6px; margin-top: 8px; }
.purseprint .pcoin { flex: 1; border: 1px dashed var(--p-soft); border-radius: 3px;
  padding: 2px 6px 3px; display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.purseprint .pcoin.wide { flex: 1.8; border-style: solid; border-color: var(--p-line); }
.purseprint .pcoin b { font-size: 12.5px; font-variant-numeric: tabular-nums; }

/* spells-per-day table (page 3) */
table.sptable { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums; }
table.sptable th { font-size: 8px; text-transform: uppercase; letter-spacing: .12em; color: var(--p-fade);
  text-align: left; padding: 2px 8px 3px 4px; border-bottom: 1.5px solid var(--p-line); }
table.sptable td { font-size: 12px; padding: 2.5px 8px 2.5px 4px; border-bottom: 1px solid var(--p-soft2); }
table.sptable td:first-child { font-weight: 600; }
table.sptable tr:last-child td { border-bottom: 0; }

/* spellcasting (page 3) */
.spmeta { display: grid; grid-template-columns: repeat(3, minmax(120px,1fr)); gap: 8px; margin-bottom: 8px; }
.sprank-sheet { margin-top: 8px; }
.sprank-sheet .hd { display: flex; gap: 8px; align-items: baseline; border-bottom: 1.5px solid var(--p-line); }
.sprank-sheet .hd b { font-size: 12px; text-transform: uppercase; letter-spacing: .1em; }
.sprank-sheet .hd span { font-size: 10px; color: var(--p-fade); margin-left: auto; }
.spl { padding: 3px 0 4px; border-bottom: 1px dotted var(--p-soft2); }
.spl:last-child { border-bottom: 0; }
.spl .r { display: flex; align-items: baseline; gap: 10px; }
.spl .r .actg { margin-left: auto; }
/* remove-from-the-sheet button. `.actg` claims the free space, so this sits after it
   rather than being pushed around by it; never printed. */
.spl .r .spx { flex: none; height: 20px; width: 20px; font-size: 10px; line-height: 1;
  padding: 0; margin-left: 6px; align-self: center; opacity: .45; }
.spl:hover .r .spx { opacity: 1; }
.spl .r .spx:hover { border-color: var(--p-red, #b04a45); color: var(--p-red, #b04a45); }
@media print { .spl .r .spx { display: none; } }
.spl .sn { font-weight: 700; font-size: 12.5px; }
.spl .sm { font-size: 10.5px; color: var(--p-ink); }
.spl .sm b { font-size: 11px; }
.spl .sr { font-size: 10px; color: var(--p-fade); }
/* the curated `fx` rider note (Shield "+1 circumstance AC", Daze "crit: stunned 1").
   Verdigris, NOT the paper's accent red: red is this sheet's structural accent (bars,
   action glyphs, the level column) and made a stat note read as a warning — and the
   very same field already renders verdigris in the rail's list (.spmeta2) and in
   spell prose (.tk). One colour for "technical data" across both surfaces. */
.spl .sr i { color: var(--verdigris); font-style: normal; font-weight: 600; }
/* The trait line is stat data, so it takes the same verdigris the rail's .spmeta2 and the
   popup's .spmstat already give it — measured, those two put the traits in the very same
   element as the damage dice, while .sr's faded grey made the printed sheet the one surface
   where a spell's traits read as an afterthought. Plain weight, matching .spmeta2: .sr i is
   the bold variant, and a six-word trait list set bold outweighs the damage line above it. */
.spl .sr .sptraits { color: var(--verdigris); }
.spl .hnote { color: var(--verdigris); }
.spl .sigtag { font-size: 9px; letter-spacing: .04em; color: var(--p-brass, #9a7d24);
  border: 1px solid currentColor; border-radius: 3px; padding: 0 3px; white-space: nowrap; }
.spl .healn { color: var(--heal); }
/* printed sheets are always light: keep healing legible on white */
@media print { .spl .healn { color: #1f6b52; } }
.spl .hup { font-size: 9.5px; font-weight: 700; color: var(--verdigris); }
.spl .sd { font-size: 10px; color: var(--p-fade); line-height: 1.35; }
.spl.compact { display: inline-block; margin-right: 14px; border-bottom: 0; }
.emptyline { color: var(--p-soft); font-style: italic; font-size: 12px; padding: 4px 0; }

.pfoot { margin-top: 16px; border-top: 1px solid var(--p-soft); padding-top: 6px; font-size: 9px; color: var(--p-fade); display: flex; justify-content: space-between; letter-spacing: .06em; }

footer.foot { max-width: 1280px; margin: 0 auto 40px; padding: 0 16px; color: var(--muted); font-size: 12px; }
.cov { display: inline-block; margin: 2px 8px 2px 0; font-family: var(--mono); font-size: 11px; }

/* ================= print: all sheet pages ================= */
#printall { display: none; }
@media print {
  header.top, .rail, footer.foot, .noprint { display: none !important; }
  body { background: white; }
  .app { display: none !important; }           /* on-screen page is replaced by the full set */
  #printall { display: block; }
  #printall .printpage { page-break-after: always; }
  #printall .printpage:last-child { page-break-after: auto; }
  .paper { box-shadow: none; border: none; max-width: none; padding: 0; border-radius: 0; }
  .pbar, .abox .hd, .savebox .hd, .tb.fill, .shield { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .pbar-block, .pbox, .sprank-sheet, .strike { break-inside: avoid; }
  /* paper always prints the complete text */
  .clamp { display: block; -webkit-line-clamp: unset; overflow: visible; }
  .morebtn { display: none !important; }
}

/* clearable search inputs: × button wipes the field */
.clearwrap { position: relative; display: block; }
.clearwrap input { padding-right: 24px; width: 100%; }
.clearx { position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; line-height: 1; border: 0; border-radius: 50%;
  background: transparent; color: var(--muted); cursor: pointer; font-size: 13px; padding: 0; }
.clearx:hover { background: var(--panel-2); color: var(--text); }

/* ===== paper theming: dark by default, light via toggle, ALWAYS light in print ===== */
:root[data-theme="light"] .paper {
  --p-bg:#faf8f1; --p-box:#ffffff; --p-ink:#17140f; --p-fade:#7c756a;
  --p-line:#37322a; --p-soft:#b9b2a4; --p-soft2:#dad4c6; --p-hint:#f0ebdd;
  --p-red:#6b1a14; --p-red-ink:#f6efe3;
}
@media print {
  .paper, :root[data-theme="dark"] .paper, :root[data-theme="light"] .paper {
    --p-bg:#ffffff; --p-box:#ffffff; --p-ink:#17140f; --p-fade:#7c756a;
    --p-line:#37322a; --p-soft:#b9b2a4; --p-soft2:#dad4c6; --p-hint:#f0ebdd;
    --p-red:#6b1a14; --p-red-ink:#f6efe3;
    background:#ffffff;
  }
}
/* ---- Random Char dialog ---- */
.rcover { position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.55);
  display: flex; align-items: center; justify-content: center; }
.rcbox { background: var(--panel); border: 1px solid var(--edge); border-radius: 8px;
  padding: 16px 18px; width: min(380px, calc(100vw - 32px)); box-shadow: 0 12px 40px rgba(0,0,0,.45); }
.rcbox h3 { margin: 0 0 10px; font-family: var(--mono); font-size: 12px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--brass); font-weight: 400; }
.rcbox .field { display: block; margin: 8px 0; }
.rcbox .field select { width: 100%; }
.rcbox .note { font-size: 12px; color: var(--muted); margin: 10px 0; }
.rcbtns { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }

/* ---- spell detail popup ----
   The read view for a spell, opened by clicking any row in the rail that names one. The
   rail rows clamp their description to 3 lines, which is right for a list of hundreds and
   useless when you want to actually read the spell — so nothing in here is clamped, and
   the box scrolls internally instead (Wall of Stone and the polymorph spells run long).

   TWO MODES, chosen at open time by measuring the room beside the rail:
   `.docked` is the normal one — the backdrop dims and deactivates the whole page EXCEPT
   the spell list, which `body.spm-open .rail` lifts above the cover so it stays lit and
   clickable: click a row, read it, click the next, with everything else out of the way.
   Without a rail to sit beside (under ~980px it goes full width) there is nothing to
   exempt, and it falls back to a plain centred modal over everything. */
.spmcover { position: fixed; inset: 0; z-index: 70; background: rgba(0,0,0,.62);
  display: flex; align-items: center; justify-content: center; }
/* docked: the box is placed explicitly, so don't flex-centre it as well. Vertically
   centred by top/transform rather than by measuring, so the height can stay content-led. */
.spmcover.docked { display: block; }
.spmcover.docked .spmbox { position: fixed; margin: 0; top: 50%; transform: translateY(-50%);
  max-height: min(84vh, 780px); }
/* the spell list is the one thing the backdrop does NOT swallow — raised above the cover
   so it keeps its own colour and its own clicks. z-index works here because .rail is
   already `position: sticky`, i.e. a positioned element; at the narrow breakpoint it
   becomes static, z-index stops applying, and the fallback modal covers it as intended. */
body.spm-open .rail { z-index: 71; }
.spmbox { background: var(--panel); border: 1px solid var(--edge); border-radius: 8px;
  padding: 14px 18px 16px; width: min(620px, calc(100vw - 32px));
  max-height: min(80vh, 760px); overflow-y: auto;
  box-shadow: 0 12px 40px rgba(0,0,0,.45); }
.spmhead { display: flex; align-items: flex-start; gap: 10px; }
.spmtitle { flex: 1; min-width: 0; font-size: 17px; font-weight: 700; line-height: 1.25; }
.spmtitle .actg { margin-left: 7px; color: var(--brass); font-size: 15px; }
/* the close button is a plain .btn shrunk to a square, so it inherits the app's own
   focus ring — it is what gets focus when the popup opens */
.spmbox .spmx { flex: 0 0 auto; padding: 2px 8px; line-height: 1.4; }
.spmsub { font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brass); margin: 3px 0 10px; }
.spmstat { width: 100%; border-collapse: collapse; margin-bottom: 11px; }
.spmstat th { width: 88px; text-align: left; vertical-align: top; padding: 2px 8px 2px 0;
  font-family: var(--mono); font-size: 10px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 400; }
.spmstat td { padding: 2px 0; font-size: 12.5px; color: var(--verdigris); }
/* --text, NOT --ink: in this palette `--ink` is the dark page BACKGROUND (#12141c) and
   `--text` is the foreground. Setting colour from --ink paints near-black prose onto a
   near-black panel. */
.spmtext { font-size: 13px; line-height: 1.55; color: var(--text);
  border-top: 1px solid var(--edge); padding-top: 10px; white-space: pre-wrap; }
.spmhint { margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--edge);
  font-size: 10.5px; color: var(--muted); }
/* ---- the skill popup's "All actions" list ----
   Shut by default: Athletics has ten actions and they would bury the skill's own
   description, which is what the popup opens on. Full width, so it reads as a section of
   the popup rather than a control floating in it. */
.skactbtn { display: block; width: 100%; margin-top: 12px; text-align: left; }
.skactbtn::before { content: "\25B8"; display: inline-block; margin-right: 8px;
  font-size: 10px; transition: transform .12s; }
.skactbtn.on::before { transform: rotate(90deg); }
.skactlist { margin-top: 8px; }
.skact { border-top: 1px solid var(--edge); padding: 9px 0 2px; }
.skacth { display: flex; align-items: baseline; gap: 8px; }
.skacth .n { font-weight: 700; font-size: 13.5px; }
.skacth .ract { color: var(--brass); font-size: 13px; }
/* the proficiency tag pushes right and stays mono — it is a label, not prose */
.skacth .p { margin-left: auto; font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.skactt { font-family: var(--mono); font-size: 9.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--verdigris); margin-top: 2px; }
/* an action this character can't take yet: dimmed, with the reason stated. Dimming alone
   would read as a rendering fault. */
.skact.locked .skacth .n, .skact.locked .skactx { color: var(--muted); }
.skactl { font-size: 11px; color: var(--warn); margin-top: 3px; }
/* the general actions' skill-specific gloss — the skill doc's own sentence about what
   THIS skill does with a shared action, so it leads rather than trails */
.skactn { font-size: 12.5px; line-height: 1.5; color: var(--text); margin-top: 5px;
  border-left: 2px solid var(--brass-dim); padding-left: 9px; }
.skactc { font-size: 11.5px; line-height: 1.5; color: var(--muted); margin-top: 4px; }
.skactc .c { display: block; }
.skactc b { font-weight: 400; color: var(--brass); font-family: var(--mono);
  font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase; margin-right: 5px; }
.skactx { font-size: 12.5px; line-height: 1.5; color: var(--text); margin-top: 5px;
  white-space: pre-wrap; }
/* a recovered rules table. Earn Income's is 22 rows x 7 columns, so the wrapper scrolls
   sideways rather than forcing the popup wider than its measure. */
.skactw { margin-top: 7px; overflow-x: auto; }
.skactcap { font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--brass); margin-bottom: 3px; }
.skacttbl { border-collapse: collapse; font-size: 11.5px; white-space: nowrap; }
.skacttbl th, .skacttbl td { padding: 2px 10px 2px 0; text-align: left; }
.skacttbl th { font-family: var(--mono); font-size: 9px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); font-weight: 400;
  border-bottom: 1px solid var(--edge); padding-bottom: 3px; }
.skacttbl td { color: var(--text); }
/* zebra by BACKGROUND, not by colour — Earn Income's table is 22 rows deep and needs the
   eye guided across it, but recolouring alternate rows would read as those rows meaning
   something different */
.skacttbl tbody tr:nth-child(even) { background: var(--panel-2); }
.skact.locked .skacttbl td { color: var(--muted); }
/* a spell row is a read target as well as a set of toggles — say so on hover, but only
   over the row itself, never over the ✎/★/＋/− cluster, which keeps its own cursor */
.spitem[data-spn] { cursor: pointer; }
.spitem[data-spn] .tg { cursor: default; }
/* which row the panel is currently showing — a browsable list has to say where you are */
.spitem.sp-open { background: var(--panel-2); border-radius: 5px;
  box-shadow: inset 2px 0 0 var(--brass); }
.spitem.sp-open .sprow .nm { color: var(--brass); font-weight: 600; }
/* the popup is a transient overlay: it must never reach the printed sheet */
@media print { .rcover, .spmcover { display: none !important; } }

/* "I feel lucky" sits alone between the page nav and the action cluster; its own
   auto left margin pairs with .top-actions' auto margin, so the two split the free
   space and the button lands centered between Spells and Undo */
.btn.lucky { margin-left: auto; }
