/* ============================================================================
   DDO Loadout Optimizer — data-forward visual system (U2)
   Mobile-first: base styles target a phone; min-width queries enhance upward.
   One token set + component primitives shared across query, readout, paperdoll,
   sets, coverage, and the item browser so the whole page reads as one system.
   ========================================================================== */

:root {
  /* surfaces — prototype look & feel: bluer, flatter dark palette */
  --bg: #0f1420;
  --bg-2: #131826;
  --panel: #171e2e;
  --panel-2: #1e273a;
  --elev: #26314a;
  --border: #2b3650;
  --border-2: #38466a;

  /* ink */
  --text: #e7ecf6;
  --muted: #9aa6bf;
  --faint: #8b95a5;   /* lifted to clear WCAG AA (~4.5:1) on --bg/--bg-2 for small labels */

  /* accents + roles */
  --accent: #5b8cff;          /* primary — prototype blue */
  --accent-2: #3a63d6;        /* secondary — deeper blue (primary-button gradient partner) */
  --accent-ink: #ffffff;      /* text on the blue accent (prototype used white) */
  --optimal: #43c59e;
  --verified: #43c59e;
  --quarantined: #f0b360;
  /* #449 U5 (R32) — the two notice-class tokens, used by name rather than as
     inline literals. --warn is the value the .wz-util-status site below already
     carried as its own fallback; defining it here is what makes that site (and
     .pd-why.pd-carried, whose fallback was a slightly different amber) resolve
     to ONE ramp. --qualify is deliberately OFF that ramp: the tree already
     carries four near-identical ambers, so a fifth would not read as distinct. */
  --warn: #d9a441;            /* actionable: card edge, class tag, attention pill */
  --qualify: #8fa2c4;         /* qualifying: card edge, class tag, summary marker */
  --set: #f0b45a;             /* set-bonus attribution */
  --artifact: #b98cff;        /* the one equipped Artifact (violet) — distinct from set gold + blue accent */

  /* type scale (fluid) */
  --fs-hero: clamp(2.2rem, 6vw, 3.2rem);
  --fs-xl: clamp(1.3rem, 3.5vw, 1.7rem);
  --fs-lg: 1.1rem;
  --fs-md: 0.95rem;
  --fs-sm: 0.85rem;
  --fs-xs: 0.76rem;

  /* spacing */
  --sp-1: 0.25rem; --sp-2: 0.5rem; --sp-3: 0.75rem;
  --sp-4: 1rem; --sp-5: 1.5rem; --sp-6: 2rem;

  --radius: 12px;
  --radius-sm: 7px;
  --tap: 44px;                /* minimum touch target */
  /* Shared width for the two right-edge buttons that stack vertically — the
     header's support button and the wizard's "Browse items". Sized to the wider
     of the two labels so both can hold their text; they must move together or
     they stop lining up. */
  --stack-btn-w: 11.5rem;
  --shadow: 0 1px 2px rgba(0,0,0,0.4), 0 6px 20px rgba(0,0,0,0.25);
  --ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
  --maxw: 1120px;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* prototype look: flat surface (a single, very subtle top wash instead of the
     twin cyan/violet radial gradients) */
  background:
    radial-gradient(1100px 520px at 100% -12%, rgba(91,140,255,0.05), transparent 62%),
    var(--bg);
  color: var(--text);
  line-height: 1.5;
  font-size: 16px;
  overflow-x: hidden;              /* R11: never let a child force page-level scroll */
}

a { color: var(--accent); }
h1, h2, h3 { line-height: 1.25; }

/* --- shell --------------------------------------------------------------- */
.app-header {
  /* Horizontal padding lives on `.wrap`, not here — see the rule below. */
  padding: var(--sp-5) 0 var(--sp-4);
  border-bottom: 1px solid var(--border);
  background: linear-gradient(180deg, rgba(91,140,255,0.05), transparent);
}
.app-header .wrap, main, .app-footer .wrap { max-width: var(--maxw); margin: 0 auto; }
/* Title on the left, support button opposite it on the right. Wraps to two rows
   on narrow screens rather than squeezing the title.

   The horizontal padding is `main`'s, moved off `.app-header` and onto this
   element so the header column and main's content column are the same box in
   every regime. They are not otherwise: padding on the header's OUTER element
   drops out once `max-width` clamps the wrap, so a compensating margin that
   lines the two up on a wide screen is 16px wrong on a narrow one. Keep these
   values equal to `main`'s padding, including across the 640px breakpoint. */
.app-header .wrap {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: var(--sp-3);
  padding: 0 var(--sp-4);
}
.app-title { min-width: 0; }
.app-header h1 {
  margin: 0; font-size: var(--fs-xl); letter-spacing: -0.01em;
  display: inline-flex; align-items: center; gap: var(--sp-2);
}
.app-header h1::before {
  content: ""; width: 0.7em; height: 0.7em; border-radius: 3px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: 0 0 16px rgba(91,140,255,0.5);
}
.tagline { margin: var(--sp-1) 0 0; color: var(--muted); font-size: var(--fs-sm); }

/* Buy Me a Coffee button (header, opposite the title). The vendor script ships a
   210x60 Poppins button and injects its own <style> AFTER this sheet, marking
   color, background-color, and font-family `!important` — so those three need
   `!important` back; everything else is won on specificity (`.header-support
   .bmc-btn` beats its bare `.bmc-btn`). The box is matched to `.btn.ghost`
   (the wizard's "Browse items", which sits directly below it): same padding,
   radius, font, line box, and the 1px border that makes both 46.8px tall.
   Keep in sync with `.btn` / `.btn.ghost` if those are ever restyled. */
/* `margin-left: auto` keeps the button on the right edge once the header wraps
   to two rows on a phone, matching "Browse items" instead of dropping left. */
.header-support { flex: none; margin-left: auto; }
.header-support .bmc-btn {
  box-sizing: border-box;
  min-width: 0; width: var(--stack-btn-w);   /* vendor pins min-width: 210px */
  height: auto; min-height: var(--tap);      /* vendor pins height: 60px */
  padding: 11px 20px; gap: var(--sp-2);
  justify-content: center;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  /* `line-height: inherit` picks up the body's unitless 1.5 — the same value the
     bare buttons get via `font: inherit`. The vendor pins 27px here and 0 on the
     label; either would mis-size the box. */
  font-family: inherit !important;
  font-size: var(--fs-md); font-weight: 650; line-height: inherit;
  background: linear-gradient(135deg, var(--accent), var(--accent-2)) !important;
  color: var(--accent-ink) !important;
  transition: transform 0.05s, filter 0.15s;
}
.header-support .bmc-btn:hover { filter: brightness(1.08); }
.header-support .bmc-btn:active { transform: translateY(1px); }
/* The cup keeps its brand colors but scales with the label instead of sitting at
   a fixed 32px, which dominated a button this size. */
.header-support .bmc-btn svg { height: 1.25em !important; transform: none; }
.header-support .bmc-btn .bmc-btn-text {
  font-family: inherit !important;
  font-size: inherit; font-weight: inherit; line-height: inherit;
  margin-left: 0; width: auto;   /* vendor stretches the label to 100% */
}
.wip {
  margin: var(--sp-3) 0 0; display: inline-block; color: var(--quarantined);
  font-size: var(--fs-xs); border: 1px solid rgba(224,164,74,0.4);
  background: rgba(224,164,74,0.08); border-radius: var(--radius-sm);
  padding: var(--sp-1) var(--sp-2);
}

main { padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-4); }

/* --- tabs (Solver | Item Browser) ---------------------------------------- */
.tabs {
  display: flex; gap: var(--sp-2);
  border-bottom: 1px solid var(--border);
  margin-bottom: calc(var(--sp-1) * -1);
}
.tab {
  appearance: none; background: transparent; color: var(--muted);
  border: 1px solid transparent; border-bottom: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  min-height: var(--tap); padding: 0 var(--sp-4); cursor: pointer;
  font-size: var(--fs-md); font-weight: 600;
  transition: color 0.15s, background 0.15s, border-color 0.15s;
}
.tab:hover { color: var(--text); background: var(--panel-2); }
.tab[aria-selected="true"] {
  color: var(--text); background: var(--panel);
  border-color: var(--border); position: relative;
}
/* seal the active tab onto its panel by covering the shared border line */
.tab[aria-selected="true"]::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--panel);
}
.tab:focus-visible { outline: none; box-shadow: var(--ring); }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-4);
  box-shadow: var(--shadow);
}
.panel > h2 {
  margin: 0 0 var(--sp-3); font-size: var(--fs-lg);
  display: flex; align-items: center; gap: var(--sp-2);
}
.status { color: var(--muted); font-size: var(--fs-sm); min-height: 1.2em; }

.app-footer {
  padding: var(--sp-4); color: var(--faint); font-size: var(--fs-xs);
  border-top: 1px solid var(--border);
}
.app-footer a { color: var(--accent); }
/* #428 U2 (R28/R29/R31) — the footer is two stacked blocks, not one run-on line.
   The build stamp leads: its own label above a monospaced, tabular value, so two
   successive stamps line up digit-for-digit when a player compares them. */
.app-footer .wrap { display: flex; flex-direction: column; gap: var(--sp-2); }
.footer-build { margin: 0; display: flex; flex-direction: column; gap: 1px; }
.footer-credits { margin: 0; }
.build-label {
  color: var(--muted); font-size: var(--fs-xs);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.build-info {
  color: var(--text); font-size: var(--fs-sm); font-weight: 650;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* --- controls / inputs (shared primitives) ------------------------------- */
.controls { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
input, select, button, textarea { font: inherit; }
.field {
  display: inline-flex; flex-direction: column; gap: 2px;
  color: var(--muted); font-size: var(--fs-xs);
}
.field > span { padding-left: 2px; }
/* A control inside a .field must not inherit a row-oriented flex-basis (e.g.
   `flex: 1 1 8rem` on selects, `1 1 200px` on the affix input): the .field is a
   COLUMN, so that basis lands on the vertical axis and blows the control up to
   128px / 200px tall. Pin every field control flat at one tap height, full width.
   The `.controls` prefix outranks the later `.controls select` / `input[list]`
   rules so this wins regardless of source order. */
.controls .field > input, .controls .field > select, .controls .field > textarea {
  flex: none; width: 100%; height: var(--tap); min-height: var(--tap);
}
input, select {
  background: var(--bg-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  padding: 0 var(--sp-3); min-height: var(--tap); font-size: var(--fs-md);
  /* a <select> otherwise refuses to shrink below its widest <option>, which
     forces page overflow on a phone (e.g. the affix filter). Let it shrink. */
  min-width: 0; max-width: 100%;
}
.controls select { flex: 1 1 8rem; }
input:focus-visible, select:focus-visible, button:focus-visible, .pd-slot:focus-visible {
  outline: none; box-shadow: var(--ring);
}
input[type="number"] { min-width: 4.5rem; }
input[list], input[type="search"] { flex: 1 1 200px; min-width: 0; }

button {
  background: var(--elev); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  min-height: var(--tap); padding: 0 var(--sp-4); cursor: pointer;
  transition: border-color 0.15s, background 0.15s, transform 0.05s;
}
button:hover { border-color: var(--accent); }
button:active { transform: translateY(1px); }
button:disabled { opacity: 0.4; cursor: default; }
button.primary {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  color: var(--accent-ink); border-color: transparent; font-weight: 700;
}
button.primary:hover { filter: brightness(1.08); }

/* --- chips / badges ------------------------------------------------------ */
.badge {
  display: inline-block; padding: 0.1rem 0.5rem; border-radius: 999px;
  font-size: var(--fs-xs); text-transform: capitalize;
}
.badge.verified { background: rgba(67,197,158,0.16); color: var(--verified); border: 1px solid rgba(67,197,158,0.5); }
.badge.quarantined { background: rgba(224,164,74,0.14); color: var(--quarantined); border: 1px solid rgba(224,164,74,0.5); }
.badge.indexed { background: rgba(152,162,179,0.12); color: var(--muted); border: 1px solid var(--border); }
/* #262 — the no-known-drop-source disclosure badge (browse rows) */
.badge.no-drop { background: rgba(224,164,74,0.14); color: var(--quarantined); border: 1px solid rgba(224,164,74,0.5); }

.chip {
  display: inline-block; background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 0.1rem 0.5rem; margin: 0.1rem;
  font-size: var(--fs-xs);
}
.chip.aug { border-color: #3a4a6a; background: #131a26; }
.chip.dino { border-color: #6a5a3a; background: #1a1712; }
.chip.nc { border-color: #5a3a6a; background: #171220; }
.chip.roll { border-color: #3a5a4a; background: #121a16; }
.chip.lamordia { border-color: #6a3a4a; background: #1a1216; }
/* #370 — a declared Lamordia slot the solve left empty: present, clearly not
   filled. Dashed and dimmed so it reads as an open slot at a glance, never as a
   craft the player should go apply. */
.chip.lamordia.unfilled { border-style: dashed; background: transparent; color: var(--faint); }
/* #332 — the ONE Utility-tier presence marker. `counted` is the affirmative
   signal (this effect earns a point). Every other presence effect stays
   deliberately unmarked, and #380 made that the whole rule: a second glyph for
   "rankable but not counted" would have landed on 10,981 of 41,776 chips (28%)
   against ★'s 699, so the legend states the default in words instead. The glyph
   is aria-hidden with an .sr-only sentence beside it, so the meaning is not
   carried by a symbol alone. */
.chip.presence.counted { border-color: #3a6a4a; background: #121a15; }
.chip .chip-mark { opacity: 0.85; font-size: 0.9em; }
.utility-legend { margin: 0.35rem 0 0; font-size: var(--fs-xs); color: var(--muted); }
.utility-legend .chip-mark { opacity: 0.85; }
.utility-legend-note { opacity: 0.8; }
.chip.seal { border-color: #3a5a6a; background: #121a1e; }
.muted { color: var(--muted); font-size: var(--fs-xs); }

/* ============================================================================
   Solver: query + readout
   ========================================================================== */
/* Uniform control grid: every solver field is the same width and height, so the
   size variability (tall selects vs short inputs) is gone. */
#solver .query-controls {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--sp-3); margin-bottom: var(--sp-3); align-items: end;
}
/* target adder + Add/Solve: the affix input is capped to a sensible width (a short
   affix name doesn't need a full-width box) and the buttons baseline-align to it. */
.q-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: flex-end; }
.field.grow { flex: 0 1 18rem; }

/* U3: change-confirmation cue for numeric steppers and reorders. A one-shot
   pulse under normal motion; a static highlight under reduced-motion. */
@keyframes bump-pulse {
  0% { box-shadow: 0 0 0 0 rgba(91,140,255,0.55); }
  100% { box-shadow: 0 0 0 7px rgba(91,140,255,0); }
}
.bumped { animation: bump-pulse 0.5s ease-out; }
input.bumped { background: rgba(91,140,255,0.16); border-color: var(--accent); }
.rank-item.bumped { outline: 2px solid var(--accent); outline-offset: 1px; }
@media (prefers-reduced-motion: reduce) {
  .bumped { animation: none; }
  input.bumped { background: rgba(91,140,255,0.24); border-color: var(--accent); }
  .rank-item.bumped { outline: 2px solid var(--accent); }
}

.ranked { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.ranked > li.hint { color: var(--muted); font-size: var(--fs-sm); padding: var(--sp-2) 0; }
.rank-item {
  display: flex; align-items: center; gap: var(--sp-2);
  background: var(--bg-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: var(--sp-1) var(--sp-2);
}
.rank-order {
  flex: none; width: 1.6em; height: 1.6em; display: grid; place-items: center;
  border-radius: 6px; background: var(--elev); color: var(--accent);
  font-size: var(--fs-sm); font-weight: 700; font-variant-numeric: tabular-nums;
}
.rank-name { flex: 1 1 auto; font-size: var(--fs-md); }
/* on/off "presence" effect badge — a build-around Bool the solver secures (no magnitude) */
.rank-tag {
  display: inline-block; margin-left: 0.5em; padding: 0.05em 0.45em;
  font-size: 0.72em; font-weight: 600; letter-spacing: 0.02em; vertical-align: middle;
  color: var(--muted); border: 1px solid var(--muted); border-radius: 999px;
  opacity: 0.75; text-transform: uppercase; cursor: help;
}
.rank-ctrl { display: flex; gap: var(--sp-1); flex: none; }
.rank-ctrl button {
  min-height: var(--tap); min-width: var(--tap); padding: 0;
  display: grid; place-items: center; font-size: var(--fs-md);
}
.rank-ctrl button.del { color: var(--quarantined); }

/* solve banner */
.solve-banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4);
  border: 1px solid rgba(67,197,158,0.35); border-radius: var(--radius);
  background: linear-gradient(135deg, rgba(67,197,158,0.10), rgba(91,140,255,0.06));
}
.solve-verdict { display: flex; align-items: center; gap: var(--sp-2); font-weight: 700; }
.solve-verdict .dot {
  width: 0.6em; height: 0.6em; border-radius: 50%; background: var(--optimal);
  box-shadow: 0 0 10px var(--optimal);
}
.solve-verdict .label { color: var(--optimal); letter-spacing: 0.02em; }
.solve-verdict .sub { color: var(--muted); font-weight: 400; font-size: var(--fs-sm); }
.solve-scale { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-left: auto; }
.scale-item { display: flex; flex-direction: column; line-height: 1.15; }
.scale-item .n { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
.scale-item .k { font-size: var(--fs-xs); color: var(--faint); text-transform: uppercase; letter-spacing: 0.05em; }

/* --- trust story: tap/keyboard-openable badge explanation (R7) ------------- */
.solve-explain { min-width: 0; }
.solve-explain > summary { list-style: none; cursor: pointer; border-radius: var(--radius-sm); }
.solve-explain > summary::-webkit-details-marker { display: none; }
.solve-explain > summary:focus-visible { outline: none; box-shadow: var(--ring); }
.solve-verdict .explain-hint { color: var(--optimal); font-weight: 400; }
.solve-explain-body {
  margin-top: var(--sp-2); max-width: 56ch; font-size: var(--fs-sm);
  color: var(--text); line-height: 1.5;
}

/* per-item empty-state / craft-carried line inside a gear box (R8, R9) — the
   plain wins-line variant was replaced by .pd-prio (plan 2026-08-12-001) */
.pd-why { font-size: var(--fs-xs); margin-top: 2px; }
.pd-why.muted { color: var(--faint); }
/* #245 — an item picked only for its craftable options reads as a caution, not a win */
.pd-why.pd-carried { color: var(--warn, #c9873a); }
/* plan 2026-08-12-001 U3 — the per-item priority summary. #449 U4 removed the
   at-ceiling green it once carried: one item is not the whole contribution to a
   stat, so the marker asserted at the wrong level. The line is uniformly muted
   now, and the ceiling fact lives on the ranked card (.stat-reach). */
/* #449 U7 (R20/R22) -- a wrapping row of chips, not a comma-run of inline spans.
   The row wraps BETWEEN chips; a chip caps at the row width and wraps its own
   sub-label internally, so a long override disclosure ("your call -- catalog
   says Insightful") cannot push the page into horizontal scroll at 375px. */
.pd-prio {
  display: flex; flex-wrap: wrap; gap: var(--sp-1);
  font-size: var(--fs-xs); color: var(--muted); margin-top: 4px;
}
.pd-chip {
  display: inline-flex; flex-direction: column; gap: 1px; max-width: 100%;
  padding: 2px var(--sp-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--elev);
}
.pd-chip-head { display: flex; align-items: baseline; gap: 4px; }
/* R20 -- tabular numerals so a column of chips reads as a column of numbers
   rather than as ragged text. */
.pd-chip-value {
  font-size: var(--fs-sm); font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--text);
}
.pd-chip-check { font-weight: 700; color: var(--optimal); }
.pd-chip-stat { color: var(--text); }
.pd-chip-sub { color: var(--faint); line-height: 1.25; }
/* R21 -- the rank-1 chip is distinguished by a named accent token AND by border
   WEIGHT, which is the non-colour carrier: colour alone would fail greyscale and
   red-green colour-vision deficiency on a surface denser than the notice cards.
   The extra 1px is absorbed by a matching padding trim so the chip does not
   change size and shift the row. */
.pd-chip.is-rank1 {
  border-width: 2px; border-color: var(--accent);
  padding: 1px calc(var(--sp-2) - 1px);
}

/* readout layout: the paperdoll is the hero (front and centre), the ranked-target
   analysis sits below it. */
.readout-analysis { margin-top: var(--sp-4); }
.readout-header { margin: 0 0 var(--sp-3); font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }
.readout-header strong { color: var(--text); font-weight: 650; }
.readout-doll { display: flex; flex-direction: column; align-items: center; }

/* on-load intro state (before the first solve) — shows the data-forward design */
.readout-intro { display: flex; flex-direction: column; gap: var(--sp-4); }
.solve-banner.ghost { border-color: rgba(91,140,255,0.3); background: linear-gradient(135deg, rgba(91,140,255,0.08), rgba(91,140,255,0.05)); }
.solve-banner.ghost .solve-verdict .dot { background: var(--accent); box-shadow: 0 0 10px var(--accent); }
.solve-banner.ghost .solve-verdict .label { color: var(--accent); }
.intro-cta {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-4); border: 1px dashed var(--border-2); border-radius: var(--radius);
}
.intro-cta strong { font-size: var(--fs-lg); }

.section-title {
  margin: 0 0 var(--sp-3); font-size: var(--fs-sm); color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700;
}

/* hero: ranked-target counters */
.targets { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
.stat-card {
  border: 1px solid var(--border); border-radius: var(--radius);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  padding: var(--sp-3) var(--sp-4);
}
.stat-head { display: flex; align-items: baseline; gap: var(--sp-2); }
.stat-rank {
  flex: none; font-size: var(--fs-xs); font-weight: 700; color: var(--accent-ink);
  background: var(--accent); border-radius: 5px; padding: 0.05rem 0.4rem;
  font-variant-numeric: tabular-nums;
}
.stat-name { flex: 1 1 auto; font-size: var(--fs-md); color: var(--muted); }
.stat-value {
  font-size: var(--fs-hero); font-weight: 800; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1;
  color: #eef3f9;                  /* solid fallback: the headline number must never vanish */
}
/* only opt into the gradient text-clip where it is actually supported, so an
   unsupporting browser keeps the solid color instead of rendering transparent. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .stat-value {
    background: linear-gradient(180deg, #fff, #b9c6d6);
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
}
.stat-value .plus { font-size: 0.5em; color: var(--muted); -webkit-text-fill-color: var(--muted); }
.stat-cap { display: inline-block; margin-top: var(--sp-1); font-size: var(--fs-xs); color: var(--quarantined); }
/* #453 U5 (R11/R12/R13) -- the AT CEILING notice names its stats in the same
   green the ceiling fact already carries everywhere else, with weight alongside
   the colour so the emphasis is not colour-only. No new token. */
.sat-stat { color: var(--optimal); font-weight: 700; }

/* #276 -- the same at-ceiling fact the gear boxes carry, on the receipt card */
.stat-ceiling { display: inline-block; margin-top: var(--sp-1); margin-left: var(--sp-2); font-size: var(--fs-xs); color: var(--optimal); font-weight: 600; }
/* #449 U3 -- the achieved/ceiling box at the bottom of each ranked card.
   Four states, four treatments. Green is reserved for achieved === pool ceiling;
   a shortfall takes the neutral accent (it is not a fault, so no warning colour);
   a cap-bound stat takes the amber .stat-cap idiom so it agrees with the capNote
   inches above it rather than contradicting it with green. */
.stat-reach {
  margin: var(--sp-3) 0 0; padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--elev);
}
.reach-fraction {
  display: block; font-size: var(--fs-md); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--text);
}
.reach-note { margin: var(--sp-1) 0 0; font-size: var(--fs-xs); color: var(--faint); }
/* R29 -- the HATCH IS ON THE WHOLE TRACK, not on the unfilled remainder. There is
   no remainder element: the fill is translucent and drawn over the hatch, so the
   bound signal stays legible at every fill level, including a near-full bar --
   which is exactly the one that otherwise reads as attainable headroom. */
.reach-meter {
  position: relative; height: 8px; margin-top: var(--sp-2);
  border-radius: 999px; overflow: hidden;
  background-color: var(--border-2);
  background-image: repeating-linear-gradient(135deg,
    rgba(255,255,255,0.18) 0 2px, rgba(255,255,255,0) 2px 6px);
}
.reach-fill { display: block; height: 100%; border-radius: 999px; background: rgba(91,140,255,0.62); }
.stat-reach.is-maxed { border-color: var(--optimal); background: rgba(67,197,158,0.10); }
.stat-reach.is-maxed .reach-fraction { color: var(--optimal); }
.stat-reach.is-maxed .reach-fill { background: rgba(67,197,158,0.62); }
.stat-reach.is-shortfall .reach-fraction { color: var(--accent); }
.stat-reach.is-cap-bound { border-color: rgba(240,179,96,0.45); background: rgba(240,179,96,0.08); }
.stat-reach.is-cap-bound .reach-fraction { color: var(--quarantined); }
.stat-reach.is-cap-bound .reach-fill { background: rgba(240,179,96,0.55); }
.stat-reach.is-zero-ceiling .reach-fraction { color: var(--muted); }
/* #449 U3 (R15) -- the one full statement, above the ranked cards. */
.ceiling-statement {
  margin: 0 0 var(--sp-3); font-size: var(--fs-xs); color: var(--faint);
}
/* #277 -- the ceiling notice opens by tap/keyboard; sentences become visible text */
details.saturation-note summary { cursor: pointer; }
details.saturation-note .sat-sentences p { margin: var(--sp-1) 0 0; }
/* attribution list (replaces the old progress bars): which slot(s)/set drive
   each ranked target, value stated as text. */
.attrib { list-style: none; margin: var(--sp-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.attrib-row {
  display: grid; grid-template-columns: auto auto 1fr; align-items: baseline;
  gap: var(--sp-1) var(--sp-2); font-size: var(--fs-xs);
  border-left: 2px solid var(--border-2); padding-left: var(--sp-2);
}
.attrib-row.is-set { border-left-color: var(--set); }
.attrib-row.is-augment { border-left-color: var(--accent-2); }
.attrib-type { color: var(--muted); }
.attrib-val { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 700; }
.attrib-where { color: var(--faint); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attrib-slots { color: var(--text); }
.attrib-set { color: var(--set); }
.attrib-row.is-set .attrib-slots { color: var(--set); }
.attrib-src { color: var(--muted); }
.stat-empty { color: var(--faint); font-size: var(--fs-xs); margin-top: var(--sp-2); }
/* active-set detail: granted stats + yielding slots */
.set-grants { color: var(--text); font-size: var(--fs-xs); margin-top: 2px; }
.set-via { color: var(--set); font-size: var(--fs-xs); margin-top: 2px; }

/* ============================================================================
   Paperdoll
   ========================================================================== */
.paperdoll {
  display: grid; grid-template-columns: 1fr; gap: var(--sp-2);
}
/* the character figure sits in the centre column on wide screens; hidden on the
   single-column phone layout where the slots simply stack. */
.pd-figure { display: none; grid-area: fig; align-self: stretch; justify-self: center; }
.pd-figure svg { width: 100%; max-width: 140px; height: 100%; max-height: 320px; }
/* weapon row: Main Hand · [Off Hand/Rune Arm] · Quiver, beneath the figure */
.pd-weapons { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--sp-2); margin-top: var(--sp-2); }
.setpip { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--set); flex: none; }
.chip.joker { border-color: var(--accent-2); color: var(--accent-2); }
.chip.awaken { border-color: #6a5a2a; background: #1a1710; color: #e6c86a; }
.chip.setbonus { border-color: #3a5a4a; background: #101a16; color: #8fd6b0; }
.chip.thunderforged { border-color: #4a4a6a; background: #14141c; color: #a6b0e6; }
.chip.greensteel { border-color: #2a6a4a; background: #101a14; color: #6ae6a0; }
/* Uniform, fixed-size slot cells: item name, ML, and set name only. */
.pd-slot {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg-2); padding: var(--sp-2) var(--sp-3);
  min-height: 3.5rem; text-align: left; width: 100%; color: var(--text);
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  overflow: hidden;
}
.pd-slot.occupied { border-color: var(--border-2); }
.pd-slot/* #345 — the outbid disclosure and its on-request price. Inherits .scope-note;
   the ask sits on its own line so the sentence stays readable at narrow widths. */
.wz-adv-req { color: var(--accent); font-weight: 600; }
.wz-adv-req-note { margin: var(--sp-2) 0; font-size: var(--fs-xs); color: var(--muted); }
.wz-adv-req-note .wz-clear-req { font-size: var(--fs-xs); padding: 2px var(--sp-2); margin-left: var(--sp-2); }

.outbid-ask { display: block; margin-top: var(--sp-2); }
.outbid-ask .outbid-price { font-size: var(--fs-xs); padding: 2px var(--sp-2); margin-right: var(--sp-2); }
.outbid-priced { display: block; margin-top: var(--sp-2); color: var(--fg); }

.empty { opacity: 0.45; }
/* a set piece gets a themed gold highlight frame (R15). */
.pd-slot.is-set {
  border-color: var(--set);
  box-shadow: 0 0 0 1px var(--set), 0 0 12px rgba(240,180,90,0.22);
  background: linear-gradient(180deg, rgba(240,180,90,0.07), var(--bg-2));
}
.pd-label { font-size: var(--fs-xs); color: var(--faint); text-transform: uppercase; letter-spacing: 0.04em; }
.pd-item { font-size: var(--fs-sm); color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pd-item.muted { color: var(--faint); font-weight: 400; }
.pd-foot { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); }
.pd-ml { color: var(--muted); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; flex: none; }
.pd-setname { color: var(--set); font-size: var(--fs-xs); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ============================================================================
   Header blurb, banner explanation, query collapse, result tabs, deep dive
   ========================================================================== */
.blurb { margin: var(--sp-3) 0 0; max-width: 70ch; color: var(--muted); font-size: var(--fs-sm); line-height: 1.55; }

.solve-explain-body p { margin: 0 0 var(--sp-2); }
.solve-explain-body p:last-child { margin-bottom: 0; }
.solve-explain-body .learn-more { color: var(--faint); font-size: var(--fs-xs); }
.solve-explain-body a { color: var(--accent); }

/* query collapse: after a solve, hide the inputs and show a one-line summary. */
#solver.q-collapsed .query-controls,
#solver.q-collapsed .q-actions,
#solver.q-collapsed #q-ranked { display: none; }
.q-summary {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-2);
}
.q-summary-text { color: var(--muted); font-size: var(--fs-sm); flex: 1 1 auto; }
.q-edit { min-height: 2.4rem; padding: 0 var(--sp-4); flex: none; }

/* result tabs beneath the paperdoll */
.result-tabs { display: flex; gap: var(--sp-2); border-bottom: 1px solid var(--border); margin-bottom: var(--sp-4); flex-wrap: wrap; }
.rtab {
  appearance: none; background: transparent; color: var(--muted);
  border: 1px solid transparent; border-bottom: none;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  min-height: 2.7rem; padding: 0 var(--sp-3); cursor: pointer; font-weight: 600; font-size: var(--fs-sm);
  transition: color 0.15s, background 0.15s;
}
.rtab:hover { color: var(--text); }
.rtab[aria-selected="true"] { color: var(--text); background: var(--panel-2); border-color: var(--border); }
.rtab:focus-visible { outline: none; box-shadow: var(--ring); }

/* loadout deep dive: one block per equipped item */
.deepdive { display: grid; grid-template-columns: 1fr; gap: var(--sp-3); }
.dd-item { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--panel-2); padding: var(--sp-3); }
.dd-item.is-set { border-color: var(--set); box-shadow: 0 0 0 1px rgba(240,180,90,0.35); }
.dd-head { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; }
.dd-slot { font-size: var(--fs-xs); color: var(--faint); text-transform: uppercase; letter-spacing: 0.04em; flex: none; }
.dd-name { font-size: var(--fs-md); font-weight: 700; color: var(--text); flex: 1 1 auto; }
.dd-ml { color: var(--muted); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
.dd-wiki { color: var(--accent); font-size: var(--fs-xs); }
.dd-set { color: var(--set); font-size: var(--fs-xs); display: flex; align-items: center; gap: 5px; margin-top: var(--sp-1); }
/* U10: unrealized-upgrade / unused-augment-slot note. */
.dd-upgrade { margin-top: var(--sp-2); font-size: var(--fs-xs); color: var(--muted); line-height: 1.4;
  border-left: 2px solid var(--accent); padding-left: var(--sp-2); }
.dd-upgrade-tag { display: inline-block; text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.6rem;
  font-weight: 700; color: var(--accent); margin-right: 4px; }
.dd-affixes h5, .dd-crafts h5 { margin: var(--sp-2) 0 var(--sp-1); font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.dd-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); font-size: var(--fs-sm); }
.dd-list li { color: var(--text); }
.dd-chips { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.dd-none { font-size: var(--fs-sm); }

/* alternatives (U4/U5): active-build banner + trade-off cards */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.active-build-bar[hidden] { display: none; }  /* class selector otherwise overrides the [hidden] attribute */
.active-build-bar {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
  border: 1px solid var(--accent-2); border-radius: var(--radius-sm);
  background: linear-gradient(135deg, rgba(91,140,255,0.12), rgba(91,140,255,0.04));
}
.active-build-msg { color: var(--accent-2); font-size: var(--fs-sm); font-weight: 600; flex: 1 1 auto; }
.return-optimum { min-height: 2.4rem; padding: 0 var(--sp-4); flex: none; }

.alt-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr; gap: var(--sp-2); }
.alt-card {
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  background: var(--panel-2); padding: var(--sp-3); cursor: pointer;
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.alt-card:hover { border-color: var(--accent); }
.alt-card[aria-selected="true"] { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2); }
.alt-card:focus-visible { outline: none; box-shadow: var(--ring); }
.alt-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.alt-tag {
  font-size: var(--fs-xs); color: var(--accent-2); background: rgba(91,140,255,0.12);
  border: 1px solid rgba(91,140,255,0.35); border-radius: 999px; padding: 1px var(--sp-2);
}
.alt-gain { color: var(--optimal); font-size: var(--fs-sm); font-weight: 600; }
.alt-grants { color: var(--text); font-size: var(--fs-xs); margin-top: 2px; }
.alt-cost { color: var(--muted); font-size: var(--fs-xs); }

/* ============================================================================
   Sets
   ========================================================================== */
.sets { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.set-card {
  border: 1px solid var(--border); border-left: 3px solid var(--set);
  border-radius: var(--radius-sm); background: var(--bg-2);
  padding: var(--sp-2) var(--sp-3); font-size: var(--fs-sm);
}
.set-card.near { border-left-color: var(--accent); opacity: 0.92; }
.set-card strong { color: var(--text); }
.set-card .meta { color: var(--muted); font-size: var(--fs-xs); }

.scope-note {
  margin-top: var(--sp-4); color: var(--muted); font-size: var(--fs-xs);
  border-left: 2px solid var(--border); padding-left: var(--sp-3); line-height: 1.55;
}

.empty {
  padding: var(--sp-6) var(--sp-4); text-align: center; color: var(--muted);
  border: 1px dashed var(--border); border-radius: var(--radius);
}
.empty button { margin-top: var(--sp-3); }

/* ============================================================================
   Item browser — table on desktop, cards on phone
   ========================================================================== */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.items { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.items th, .items td { text-align: left; padding: var(--sp-2); border-bottom: 1px solid var(--border); vertical-align: top; }
.items th { color: var(--muted); font-weight: 600; position: sticky; top: 0; background: var(--panel); }
.items td.num, .items th.num { text-align: right; font-variant-numeric: tabular-nums; }
.items a { color: var(--accent); }

/* phone: render browser rows as cards (JS adds data-label to each td) */
@media (max-width: 620px) {
  .items.cards, .items.cards thead, .items.cards tbody, .items.cards tr, .items.cards td { display: block; }
  .items.cards thead { display: none; }
  .items.cards tr {
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    margin-bottom: var(--sp-2); padding: var(--sp-2); background: var(--bg-2);
  }
  .items.cards td { border: none; padding: var(--sp-1) 0; display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3); align-items: baseline; }
  .items.cards td::before {
    content: attr(data-label); color: var(--faint); font-size: var(--fs-xs);
    text-transform: uppercase; letter-spacing: 0.04em; flex: 0 0 6.5em;
  }
  .items.cards td > * { flex: 0 1 auto; }
  .items.cards td.num { justify-content: flex-start; text-align: left; }
}

/* ============================================================================
   Responsive enhancement — phone-first base above, widen from here
   ========================================================================== */
@media (min-width: 640px) {
  main { padding: var(--sp-5); }
  .app-header .wrap { padding: 0 var(--sp-5); }   /* stay equal to main's padding */
  .targets { grid-template-columns: repeat(2, 1fr); }
  .deepdive { grid-template-columns: repeat(2, 1fr); }
  .alt-list { grid-template-columns: repeat(2, 1fr); }
}

/* Option-B character paperdoll, front and centre: a large figure with the slots
   in paired anatomy rows around the body, the columns bowed into an arc (head and
   feet tuck inward, the torso pushes out). The grid keeps it robust — a tall slot
   just grows its row, never overlaps. Phone base stacks the slots single-column. */
@media (min-width: 760px) {
  .readout-doll { width: 100%; max-width: 780px; }
  .paperdoll {
    grid-template-columns: 1fr minmax(150px, 190px) 1fr;
    grid-template-areas:
      "helmet  fig necklace"
      "goggles fig trinket"
      "armor   fig cloak"
      "bracers fig belt"
      "ring1   fig ring2"
      "boots   fig gloves";
    align-items: center; gap: var(--sp-3) var(--sp-3);
  }
  .pd-figure { display: block; }
  .pd-figure svg { max-width: 180px; max-height: 560px; }
  .pd-slot.pos-helmet { grid-area: helmet; }
  .pd-slot.pos-goggles { grid-area: goggles; }
  .pd-slot.pos-necklace { grid-area: necklace; }
  .pd-slot.pos-trinket { grid-area: trinket; }
  .pd-slot.pos-armor { grid-area: armor; }
  .pd-slot.pos-cloak { grid-area: cloak; }
  .pd-slot.pos-bracers { grid-area: bracers; }
  .pd-slot.pos-belt { grid-area: belt; }
  .pd-slot.pos-ring1 { grid-area: ring1; }
  .pd-slot.pos-ring2 { grid-area: ring2; }
  .pd-slot.pos-boots { grid-area: boots; }
  .pd-slot.pos-gloves { grid-area: gloves; }
  .pd-slot.pos-misc { grid-column: 1 / -1; }
  /* bow the two columns into an arc around the body: head/feet tuck toward the
     centre, the torso rows push outward — a circular silhouette, grid-robust. */
  .pd-slot.pos-helmet, .pd-slot.pos-boots { transform: translateX(38px); }
  .pd-slot.pos-goggles, .pd-slot.pos-ring1 { transform: translateX(16px); }
  .pd-slot.pos-armor, .pd-slot.pos-bracers { transform: translateX(-10px); }
  .pd-slot.pos-necklace, .pd-slot.pos-gloves { transform: translateX(-38px); }
  .pd-slot.pos-trinket, .pd-slot.pos-ring2 { transform: translateX(-16px); }
  .pd-slot.pos-cloak, .pd-slot.pos-belt { transform: translateX(10px); }
  .pd-weapons { max-width: 32rem; margin: var(--sp-4) auto 0; }
}

/* accessibility: respect reduced motion (JS also checks this) */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ============================================================================
   Guided wizard (wizard.js) — one flow replaces the two tab rows.
   ========================================================================== */
#wizard { display: flex; flex-direction: column; gap: var(--sp-4); }

/* stepper */
.wz-steps { list-style: none; display: flex; align-items: center; gap: 0; padding: 0; margin: 0; flex-wrap: wrap; }
.wz-step { display: flex; align-items: center; gap: var(--sp-2); color: var(--faint); font-size: var(--fs-sm); }
.wz-step:not(:last-child)::after { content: ""; width: 28px; height: 2px; background: var(--border); margin: 0 var(--sp-2); border-radius: 2px; }
.wz-step.done::after { background: var(--optimal); }
.wz-dot { width: 26px; height: 26px; min-height: 0; padding: 0; flex: none; border-radius: 50%;
  background: var(--panel-2); border: 1px solid var(--border-2); display: flex; align-items: center;
  justify-content: center; color: var(--muted); font-size: var(--fs-xs); font-weight: 700; cursor: default; }
.wz-step.done .wz-dot { background: var(--optimal); border-color: var(--optimal); color: var(--bg); cursor: pointer; }
.wz-step.on .wz-dot { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); box-shadow: var(--ring); }
.wz-step.on span { color: var(--text); font-weight: 600; }

.wz-card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--sp-5); box-shadow: var(--shadow); }
.wz-card h2 { margin: 0 0 var(--sp-2); font-size: var(--fs-xl); letter-spacing: -0.01em; }
.wz-eyebrow { margin: 0 0 var(--sp-1); font-size: var(--fs-xs); letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent); font-weight: 700; }
.wz-lead { margin: 0 0 var(--sp-4); color: var(--muted); max-width: 62ch; }
.wz-sub { color: var(--faint); font-weight: 500; font-size: var(--fs-xs); }

/* form: single column, fields proportioned to their input */
.wz-form { display: flex; flex-direction: column; gap: var(--sp-4); max-width: 34rem; }
.wz-field { display: block; }
.wz-label { display: block; font-size: var(--fs-sm); font-weight: 600; margin-bottom: 2px; }
.wz-help { display: block; font-size: var(--fs-xs); color: var(--faint); margin-bottom: var(--sp-2); }
.wz-field input, .wz-field select { width: 100%; background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm); padding: 10px 12px; font-size: var(--fs-md); min-height: var(--tap); }
.wz-field input:focus, .wz-field select:focus { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
input.wz-ml { width: 120px; }
.wz-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3); align-items: stretch; }
@media (max-width: 520px) { .wz-pair { grid-template-columns: 1fr; } }
/* Bottom-align the two controls in a pair so a taller help block on one side
   (e.g. Alignment's two-line note vs Race's one line) doesn't stagger the row —
   both selects/inputs sit on the same baseline. */
.wz-pair > .wz-field { display: flex; flex-direction: column; height: 100%; }
.wz-pair > .wz-field > select,
.wz-pair > .wz-field > input { margin-top: auto; }

/* #428 U6 (R1-R6a) — the character step's three labelled containers.

   R5/R5a/R5b are one idea: a control's width comes from the COLUMN, never from
   its type or its content, so a number input, a text input and a select in the
   same column are the same width and their outlines form a grid rather than a
   ragged stack. `input.wz-ml { width: 120px }` above is exactly the rule that
   made the old step ragged, so it is overridden inside a group. */
.wz-group {
  border: 1px solid var(--border-2); border-radius: var(--radius);
  background: var(--panel-2); padding: var(--sp-4); margin: 0; min-width: 0;
}
.wz-group-legend { font-size: var(--fs-sm); font-weight: 700; padding: 0 var(--sp-2); }
.wz-group-fold > summary { list-style: none; cursor: pointer; display: block; }
.wz-group-fold > summary::-webkit-details-marker { display: none; }
.wz-group-fold > summary::before { content: "▸ "; color: var(--muted); }
.wz-group-fold[open] > summary::before { content: "▾ "; }
.wz-group-fold[open] > summary { margin-bottom: var(--sp-3); }
.wz-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4) var(--sp-3); align-items: start; }
.wz-grid > * { min-width: 0; }
/* Anything that is not a plain field spans the row: a radio ladder, a fold, or
   a checkbox row has no column partner and would otherwise be squeezed. */
.wz-grid > .wz-check, .wz-grid > .wz-ladder, .wz-grid > details, .wz-grid > .wz-picklist,
.wz-grid > .wz-span { grid-column: 1 / -1; }
.wz-grid input, .wz-grid select { width: 100%; }
/* Bottom-align the controls in a row so an uneven help block on one side does
   not stagger it — the same rule .wz-pair carries, for the same reason. */
.wz-grid > .wz-field { display: flex; flex-direction: column; height: 100%; }
.wz-grid > .wz-field > select, .wz-grid > .wz-field > input { margin-top: auto; }
.wz-grid input.wz-ml { width: 100%; }
@media (max-width: 620px) {
  .wz-grid { grid-template-columns: 1fr; }
  /* #431 U5 — `flex: 1` still absorbs the first line's slack once .wz-actions
     wraps, which would strand the terminal save control bottom-LEFT and invert
     the placement KTD8 protects. With the spacer suppressed the wrapped bar
     simply reads in document order.

     Scoped to the RESULTS bar, which is the one that wraps: it carries four
     children (two ghosts, save, and save's status line). The other bars fit,
     and suppressing their spacer would strip the right-alignment of an advance
     control that has room — most visibly on intro, whose single "Get started →"
     would render flush left on a phone and bottom-right everywhere else. */
  .wz-results .wz-actions .wz-spacer { display: none; }
}

/* The required marker, at the field (R2), and the invalid treatment (R8/R11):
   an outline, and no motion. KD4 dropped the repeating flash the requirements
   originally specified — repeated flashing is bounded by WCAG 2.3.1 and is a
   documented trigger for photosensitive and vestibular conditions. */
.wz-req-mark { color: var(--accent); font-weight: 700; }
.wz-invalid {
  outline: 2px solid var(--quarantined); outline-offset: 6px; border-radius: var(--radius-sm);
}
.wz-reqmsg:empty { display: none; }
.wz-reqmsg { color: var(--quarantined); font-size: var(--fs-sm); margin: var(--sp-3) 0 0; }

/* opt-in checkbox: inline box-beside-label, deliberately NOT a .wz-field (whose
   input rule forces width:100%) so the box stays small; the whole row toggles it */
.wz-check { display: flex; align-items: flex-start; gap: var(--sp-2); cursor: pointer; }
.wz-check input[type="checkbox"] { width: 18px; height: 18px; min-height: 0; flex: 0 0 auto;
  margin-top: 2px; accent-color: var(--accent); cursor: pointer; }
.wz-check .wz-check-body { display: block; }
.wz-check .wz-label { margin-bottom: 2px; }
.wz-check .wz-help { margin-bottom: 0; }

/* #346 — the crafting/augment ladder. Radios reuse .wz-check's box-beside-label
   shape; the fieldset groups them so the rungs read as ONE ordered control
   rather than four independent toggles — which is the whole point of a ladder
   over checkboxes. Indented rungs make the "each step down removes more"
   relationship visible without a diagram. */
.wz-ladder { border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  padding: var(--sp-3); margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.wz-ladder > legend { padding: 0 var(--sp-1); }
.wz-ladder > .wz-help { margin: 0 0 var(--sp-1); }
.wz-ladder .wz-check { padding-left: var(--sp-2); }
.wz-ladder input[type="radio"] { width: 18px; height: 18px; min-height: 0; flex: 0 0 auto;
  margin-top: 2px; accent-color: var(--accent); cursor: pointer; }
/* A control that cannot apply is disabled and says why, never live-and-inert. */
.wz-field input:disabled { opacity: .55; cursor: not-allowed; }

.wz-seg { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.wz-chip { background: var(--panel-2); color: var(--muted); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); padding: 9px 14px; min-height: var(--tap); cursor: pointer; font-size: var(--fs-md); font-weight: 550; }
.wz-chip:hover:not(:disabled) { border-color: var(--accent); color: var(--text); }
.wz-chip.on { background: var(--elev); border-color: var(--accent); color: var(--text); }
.wz-chip:disabled { opacity: 0.4; cursor: not-allowed; }
.wz-chip.big { flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; padding: 14px 16px; text-align: left; }
.wz-chip.big strong { display: block; }
.wz-chip.big small { color: var(--faint); font-weight: 500; font-size: var(--fs-xs); }
/* U3 — handedness-gated weapon/off-hand sub-picker revealed under a chosen style. */
.wz-subseg { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px dashed var(--border-2);
  display: flex; flex-direction: column; gap: var(--sp-2); }
.wz-sublabel { display: block; font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.wz-wrap .wz-chip { padding: 6px 11px; min-height: 0; font-size: var(--fs-sm); font-weight: 500; }
.wz-note { font-style: italic; margin: var(--sp-1) 0 0; }
/* TC — weapon/off-hand dropdown pick-list (select + removable tags). */
.wz-picklist { display: flex; flex-direction: column; gap: var(--sp-2); }
.wz-field .wz-pl-select { max-width: 260px; }
.wz-pl-tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.wz-pl-tags:empty { display: none; }
.wz-tag { display: inline-flex; align-items: center; gap: 6px; background: var(--elev); color: var(--text);
  border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 5px 8px 5px 11px;
  font-size: var(--fs-sm); font-weight: 500; cursor: pointer; }
.wz-tag-x { font-size: var(--fs-md); line-height: 1; color: var(--faint); }
.wz-tag:hover .wz-tag-x { color: var(--text); }
.wz-pool { gap: var(--sp-3); }

.wz-file { cursor: pointer; max-width: 34rem; }
.wz-filestat { font-size: var(--fs-sm); color: var(--optimal); margin-top: var(--sp-2); }
.wz-filestat.warn { color: var(--quarantined); }

/* U3 — pre-solve item pinning (Gear pool step) */
.wz-pinbox { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line, rgba(255,255,255,0.08)); max-width: 34rem; }
.wz-pinbox .wz-addrow { margin-top: var(--sp-2); }
.wz-pin-search, #wz-pin-search { width: 100%; }
.wz-pin-hint, .wz-pin-empty, .wz-pin-more { font-size: var(--fs-sm); color: var(--faint); margin: var(--sp-2) 0 0; }
.wz-pin-results { display: flex; flex-direction: column; gap: 2px; margin-top: var(--sp-2); max-height: 15rem; overflow-y: auto; }
.wz-pin-results:empty { display: none; }
.wz-pin-hit { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3);
  width: 100%; text-align: left; appearance: none; background: var(--bg-2); color: var(--text);
  border: 1px solid transparent; border-radius: var(--radius-sm); padding: 7px 10px; cursor: pointer;
  font-family: inherit; font-size: var(--fs-sm); }
.wz-pin-hit:hover:not([disabled]) { border-color: var(--accent); }
.wz-pin-hit[disabled] { opacity: 0.55; cursor: default; }
.wz-pin-hit-slot { color: var(--faint); font-size: var(--fs-xs); white-space: nowrap; }
/* plan 003 U5 (R6) — a one-handed weapon offers a hand target: the primary action
   stays Main hand (unchanged muscle memory) and the Off hand action sits beside it. */
.wz-pin-hit-pair { display: flex; align-items: stretch; gap: var(--sp-2); }
.wz-pin-hit-pair .wz-pin-hit { flex: 1 1 auto; }
.wz-pin-hit-alt { flex: 0 0 auto; width: auto; justify-content: center;
  border-color: var(--border); }
.wz-pin-note { color: var(--faint); font-size: var(--fs-xs); margin-left: auto; }
.wz-pin-note + .wz-pin-x { margin-left: var(--sp-2); }
.wz-pin-list { display: flex; flex-direction: column; gap: var(--sp-2); margin-top: var(--sp-3); }
.wz-pin-row { display: flex; align-items: center; gap: var(--sp-2); background: var(--elev);
  border: 1px solid var(--accent); border-radius: var(--radius-sm); padding: 6px 6px 6px 11px; font-size: var(--fs-sm); }
.wz-pin-name { font-weight: 600; }
.wz-pin-slot { color: var(--faint); font-size: var(--fs-xs); }
.wz-pin-warn { color: var(--quarantined); font-size: var(--fs-xs); margin-left: auto; }
.wz-pin-artwarn { color: var(--quarantined); font-size: var(--fs-sm); margin: var(--sp-2) 0 0;
  padding: var(--sp-2) var(--sp-3); background: rgba(224,164,74,0.10); border-radius: var(--radius-sm); }
.wz-pin-x { margin-left: auto; appearance: none; background: transparent; border: none; color: var(--faint);
  font-size: var(--fs-lg); line-height: 1; cursor: pointer; padding: 0 4px; }
.wz-pin-warn + .wz-pin-x { margin-left: var(--sp-2); }
.wz-pin-x:hover { color: var(--text); }

/* #431 U5 (R5) — the bars gained a save control and its status line, so the
   results bar now holds four children. Wrap rather than overflow; .wz-modal-actions
   already wraps for the same reason. */
.wz-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4); }
.wz-spacer { flex: 1; }
.btn { border: none; border-radius: var(--radius-sm); padding: 11px 20px; min-height: var(--tap);
  font-size: var(--fs-md); font-weight: 650; cursor: pointer; font-family: inherit; }
.btn.primary { background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: var(--accent-ink); }
.btn.primary:hover { filter: brightness(1.08); }
.btn.ghost { background: transparent; color: var(--muted); border: 1px solid var(--border-2); }
.btn.ghost:hover { color: var(--text); border-color: var(--accent); }
.btn.wz-nudge { animation: wz-nudge 0.35s; }
@keyframes wz-nudge { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }

/* priorities editor */
/* composable affix-bundle picker (gear-planner style) */
.wz-bundles { margin-bottom: var(--sp-4); }
.wz-bundle-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin-top: var(--sp-2); }
/* A class rule with `display` beats the UA stylesheet's `[hidden] { display: none }`,
   so the sub-rows shipped with `hidden` set and still rendered — the progressive
   disclosure (Melee reveals Tactics, Caster reveals schools + spell power) has
   never actually hidden anything. Pre-existing; found while verifying the new
   Attributes row, which is deliberately always visible and needs no reveal. */
.wz-bundle-row[hidden] { display: none; }
.wz-bundle-sub { padding-left: var(--sp-2); border-left: 2px solid var(--border); margin-left: 2px; }
.wz-bundle-tag { color: var(--muted); font-size: var(--fs-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; margin-right: var(--sp-1); }
.wz-bundle {
  min-height: auto; padding: 0.3em 0.8em; font-size: var(--fs-sm); font-weight: 600;
  background: var(--panel-2); border-color: var(--border-2); border-radius: 999px;
}
.wz-bundle:hover { border-color: var(--accent); background: var(--elev); }
.wz-addrow { display: flex; gap: var(--sp-2); max-width: 34rem; }
.wz-addrow input { flex: 1; background: var(--panel-2); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); padding: 10px 12px; min-height: var(--tap); }
.wz-addrow input:focus { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
.wz-ranked { list-style: none; padding: 0; margin: var(--sp-3) 0 0; max-width: 34rem; }
.wz-ranked li { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); background: var(--panel-2);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm); padding: 9px 12px; margin-bottom: var(--sp-2); }
.wz-ranked li.dragging { opacity: 0.5; border-color: var(--accent); }
.wz-grip { color: var(--faint); cursor: grab; letter-spacing: -2px; }
.wz-rk { width: 22px; height: 22px; border-radius: var(--radius-sm); background: var(--accent); color: var(--accent-ink);
  font-size: var(--fs-xs); font-weight: 700; display: flex; align-items: center; justify-content: center; }
.wz-nm { flex: 1; font-weight: 550; }
/* U4 — per-priority min/max bound inputs (advanced, secondary affordance) */
.wz-bounds { display: flex; gap: 4px; }
.wz-bound { width: 3.4rem; background: var(--panel); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--r-1, 6px); padding: 2px 4px; font-size: 0.85em; text-align: center; }
.wz-bound::placeholder { color: var(--faint); }
.wz-bound:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
/* plan 005 — the per-row Advanced disclosure. Takes its own full-width line
   below the row rather than competing for space on it: `.wz-ranked li` is a flex
   container, so an auto-width <details> would size to its collapsed summary and
   render the open panel as a ~5rem column of wrapped text. `flex-basis: 100%` is
   what drops it to its own line — the same mechanism `.wz-credits` uses below,
   and the reason it stays on the details rather than moving to `[open]`: a
   width that changes on open would make the summary jump lines under the cursor.

   Quieter than the reorder controls by design. The row's job is the ORDER; this
   is the affordance you reach for only when you have a specific number in mind. */
/* `order: 1` is load-bearing, not cosmetic. `.wz-adv` sits before `.wz-ctl` in
   DOM order (the panel belongs to the row it describes), and `flex-basis: 100%`
   makes it consume a whole flex line — which pushed the reorder buttons onto a
   THIRD line on every magnitude row, while presence rows stayed on one. That is
   exactly the misalignment R1 and R2 exist to remove. Ordering it last puts the
   controls back inline and leaves the panel on its own line beneath them. */
.wz-adv { order: 1; flex-basis: 100%; margin-top: 2px; padding-top: 6px;
  border-top: 1px dashed var(--border-2); font-size: 0.88em; color: var(--muted); }
/* NOT `display: flex`. Chrome drops the disclosure triangle entirely when a
   <summary> is a flex container, so the control rendered as plain grey text with
   nothing marking it as expandable — the one affordance the whole row depends on.
   Left as the default list-item, with an explicit marker so the open/closed state
   is visible in every engine rather than relying on the UA default. */
.wz-adv > summary { cursor: pointer; color: var(--faint); padding: 2px 0;
  min-height: var(--tap-sm, 24px); list-style: none; }
.wz-adv > summary::-webkit-details-marker { display: none; }
.wz-adv > summary::before { content: "▸"; display: inline-block; width: 1em;
  color: var(--faint); transition: transform 0.12s ease; }
.wz-adv[open] > summary::before { transform: rotate(90deg); }
.wz-adv > summary:hover { color: var(--text); }
.wz-adv > summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--r-1, 6px); }
/* R5 — the collapsed row still shows that settings are applied. Part of the
   summary's TEXT, not a decorative dot: a purely visual mark would hide the
   state from screen-reader users instead of from everyone. */
.wz-adv-badge { color: var(--accent); font-weight: 600; }
.wz-adv[open] > summary { color: var(--text); margin-bottom: var(--sp-2); }
.wz-adv-body { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); padding-bottom: 2px; }
.wz-adv-lead, .wz-adv-note { flex-basis: 100%; margin: 0; line-height: 1.45; }
.wz-adv-lead { color: var(--text); }
.wz-adv-note { color: var(--muted); }
.wz-adv-lead strong, .wz-adv-note strong { color: var(--text); }
/* U2 — declared stat credits. Repeatable, unlike the fixed min/max pair, so the
   group takes a full-width line inside the panel rather than competing for space
   with the bound inputs. */
.wz-credits { flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin-top: 2px; }
.wz-credit { display: flex; align-items: center; gap: 3px; }
.wz-credit.is-incomplete { opacity: 0.55; }
.wz-credit.is-incomplete .wz-credit-val { border-style: dashed; }
.wz-credit-val { width: 3.4rem; background: var(--panel); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--r-1, 6px); padding: 2px 4px; font-size: 0.85em; text-align: center; }
.wz-credit-val::placeholder { color: var(--faint); }
.wz-credit-val:focus, .wz-credit-type:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
.wz-credit-type { background: var(--panel); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--r-1, 6px); padding: 2px 4px; font-size: 0.85em; max-width: 9rem; }
.wz-credit button, .wz-credit-add { background: none; border: 1px dashed var(--border-2); color: var(--faint);
  border-radius: var(--r-1, 6px); padding: 1px 6px; font-size: 0.8em; min-height: 0; cursor: pointer; }
.wz-credit button:hover, .wz-credit-add:hover { color: var(--text); border-color: var(--accent); }
/* U3 — a declared credit in the contributor list. Its own treatment, like a set
   bonus, because it is the one contributor the player supplied rather than the
   tool sourcing it from the wiki. */
.attrib-row.is-declared .attrib-type { font-style: italic; }
.attrib-declared { color: var(--muted); font-style: italic; border-bottom: 1px dotted var(--border-2); cursor: help; }
.wz-ctl { display: flex; gap: 4px; }
.wz-ctl button { background: var(--panel); border: 1px solid var(--border-2); color: var(--muted);
  width: 32px; height: 32px; min-height: 0; padding: 0; flex: none; display: flex; align-items: center;
  justify-content: center; border-radius: var(--radius-sm); cursor: pointer; }
.wz-ctl button:hover:not(:disabled) { color: var(--text); border-color: var(--accent); }
.wz-ctl button:disabled { opacity: 0.35; cursor: not-allowed; }
.wz-hint { color: var(--faint); font-style: italic; list-style: none; }
.wz-draghelp { font-size: var(--fs-xs); color: var(--faint); margin: var(--sp-2) 0 0; }
.wz-status { color: var(--quarantined); font-size: var(--fs-sm); min-height: 1.2em; }

.wz-results-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); flex-wrap: wrap; }

.wz-hidden { display: none !important; }

/* solve overlay */
.wz-overlay { position: fixed; inset: 0; background: rgba(8,10,14,0.86); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; z-index: 60; opacity: 0; pointer-events: none; transition: opacity 0.2s; }
.wz-overlay.on { opacity: 1; pointer-events: auto; }
.wz-overlay-box { text-align: center; max-width: 420px; padding: var(--sp-5); }
.wz-ring { width: 70px; height: 70px; margin: 0 auto var(--sp-4); border-radius: 50%;
  border: 5px solid rgba(91,140,255,0.2); border-top-color: var(--accent); animation: wz-spin 0.8s linear infinite; }
@keyframes wz-spin { to { transform: rotate(360deg); } }
.wz-overlay-box h3 { margin: 0 0 var(--sp-1); font-size: var(--fs-lg); }
.wz-ov-sub { color: var(--muted); font-size: var(--fs-sm); margin: 0; font-variant-numeric: tabular-nums; }
.wz-ov-foot { color: var(--faint); font-size: var(--fs-xs); margin: var(--sp-3) 0 0; }

/* Equipped list — plain stacked layout replacing the humanoid paperdoll figure
   (prototype look; full item names, no truncation). */
.pd-equipped { width: 100%; }
.readout-doll { align-items: stretch; }
.pd-list { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); width: 100%; }
@media (max-width: 560px) { .pd-list { grid-template-columns: 1fr; } }
.pd-row { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: var(--sp-2) var(--sp-3); }
.pd-row.empty { opacity: 0.5; }
.pd-row.is-set { border-color: var(--set); box-shadow: inset 0 0 0 1px rgba(240,180,90,0.22); }
.pd-rlabel { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: 0.05em; color: var(--faint); }
.pd-rname { font-size: var(--fs-sm); font-weight: 600; color: var(--text); line-height: 1.3; }
.pd-row.is-set .pd-rname { color: var(--set); }
/* #469 — ONE line, always. `flex-wrap: wrap` is wrong here: a wrapping flex
   container moves an item to a new line rather than shrinking it below its
   content, so the set name below never got to ellipsise and the header grew
   instead. Nowrap plus a shrinkable set name is what holds the line; the ML and
   the status badges are `flex: none` so the truncation lands on the one child
   that has somewhere else to be read in full. */
.pd-rmeta { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: nowrap;
  font-size: var(--fs-xs); color: var(--faint); }
.pd-rmeta > .pd-rml, .pd-rmeta > .pd-badge { flex: none; }
/* #469 — one line, ellipsised. A slot that feeds TWO sets renders both names
   here, which took its header from 97px to 141px and pushed that card's Stats
   section 44px below its neighbour's — the exact raggedness the head is fixed
   for. The names are not lost: the full list is this element's `title`, and the
   Set Bonuses tab names every active set with its contributing pieces. */
.pd-rset { color: var(--set); flex: 0 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* U9: uniform equipment blocks — same floor height whether or not the item
   carries stats/augment/craft info; grid stretch keeps paired blocks equal. */
/* #453 U4 — `min-width: 0` lets the row shrink below its longest child inside
   the paperdoll grid. Without it a flex/grid item floors at max-content and the
   chip's own max-width never binds, so the wrap above would be inert. */
.pd-row { min-height: 92px; display: flex; flex-direction: column; min-width: 0; }

/* #469 — the three card regions. The card is HEAD / BODY / FOOT and the rule
   that makes a grid of them read as a grid rather than as fourteen unrelated
   boxes is that each region occupies the same band on every card:

   - the head is a fixed three-line block (slot, name, meta), so an item whose
     name wraps to two lines does not push its ML onto a different baseline from
     the card beside it. The name RESERVES two lines and is not clamped — the
     equipped list has shown full names since it replaced the paperdoll figure,
     and truncating an item name on a shopping list is the wrong trade;
   - the body grows, so it absorbs the difference between a five-row item and a
     twelve-row one instead of pushing the notes around;
   - the foot is pinned to the bottom edge by that growth, so a note sits in the
     same place on every card that has one and leaves no gap on the cards that
     do not.

   #471 — the SEPARATION between them is no longer two rules across the card.
   Three different devices, each matched to what it divides:

   - HEAD: a top-lit tint that fades out downward, closed by a hairline that
     tapers to nothing at both ends. A full-width rule states a hard boundary
     between two things of equal weight; the head is a label for what follows,
     so it should dissolve into it rather than be fenced off from it.
   - BODY: a vertical SPINE down the left gutter that every row hangs off, with
     each section's label tick attached to it. The sections are a sequence, not
     three stacked boxes, and a spine says sequence where stacked rules say
     boxes. It also does the job the old `.pd-sec ~ .pd-sec` hairline did — the
     boundary is visible — without spending a horizontal line on it.
   - FOOT: a recessed plate, flush to the card's edges and darker than its
     surface, so the notes read as a different KIND of statement from the rows
     above. Depth rather than a line. */
.pd-row { padding: 0; overflow: hidden; }
.pd-card-head { display: flex; flex-direction: column; gap: 1px; position: relative;
  padding: var(--sp-2) var(--sp-3) 10px; border-bottom: 0;
  background:
    linear-gradient(180deg, rgba(91,140,255,0.10) 0%, rgba(91,140,255,0.00) 78%),
    linear-gradient(180deg, rgba(255,255,255,0.035) 0%, rgba(255,255,255,0) 100%); }
.pd-card-head::after { content: ""; position: absolute; bottom: 0;
  left: var(--sp-3); right: var(--sp-3); height: 1px; opacity: 0.55;
  background: linear-gradient(90deg, transparent, var(--border-2) 18%, var(--border-2) 82%, transparent); }
.pd-card-head .pd-rname { min-height: 2.6em; display: flex; align-items: flex-start; }
.pd-rmeta { min-height: 1.4em; }
/* #471 — state moves to the card's TOP EDGE. It was an inset ring around the
   whole card plus a recoloured item name; the ring competed with the new head
   tint for the same pixels, and an edge reads as a label on the card while a
   ring reads as a border on it. The name keeps its colour. */
.pd-row.is-set { border-color: var(--border); box-shadow: inset 0 2px 0 0 var(--set), inset 0 0 0 1px rgba(240,180,90,0.16); }
.pd-row.is-artifact { box-shadow: inset 0 2px 0 0 var(--artifact); }
.pd-row.is-set.is-artifact { box-shadow: inset 0 2px 0 0 var(--artifact), inset 0 0 0 1px rgba(240,180,90,0.16); }
.pd-rbody { margin: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 11px;
  position: relative; padding: 10px var(--sp-3) 10px calc(var(--sp-3) + 9px); }
.pd-rbody::before { content: ""; position: absolute; width: 1px; opacity: 0.8;
  left: calc(var(--sp-3) + 1px); top: 12px; bottom: 10px;
  background: linear-gradient(180deg, var(--accent) 0%, var(--border-2) 22%, var(--border) 78%, transparent); }
.pd-rbody:empty::before { display: none; }
/* An empty foot must take NO space — the alternative is fourteen cards each
   carrying a plate under nothing. */
.pd-card-foot:empty { display: none; }
.pd-card-foot { margin: 0; padding: 8px var(--sp-3); border-top: 0;
  display: flex; flex-direction: column; gap: 4px;
  background: rgba(0,0,0,0.20); box-shadow: inset 0 1px 0 0 rgba(255,255,255,0.045); }
/* #471 — the global `.empty { text-align: center }` empty-state helper leaks
   onto `.pd-row.empty`, which centred the note on every optimizer-left-empty
   card while every other card left-aligned. Found while rebuilding this family. */
.pd-row.empty { text-align: left; }

/* #469 — the body's three sub-sections: Stats, Augments, Craft, always in that
   order. #471 — the divider between them is gone; the spine plus the labelled
   tick is what marks the boundary now. */
.pd-sec { display: flex; flex-direction: column; gap: 4px; }
.pd-sec ~ .pd-sec { padding-top: 0; border-top: 0; }
.pd-slabel { text-transform: uppercase; letter-spacing: 0.09em; color: var(--faint);
  font-size: 0.6rem; position: relative; margin: 0 0 1px -9px; padding-left: 13px; }
.pd-slabel::before { content: ""; position: absolute; left: 0; top: 50%; width: 8px; height: 2px;
  transform: translateY(-50%); border-radius: 1px; background: var(--border-2); }
/* Each section's tick takes the colour of what it introduces: the priority
   accent for stats, the augment blue, the craft amber. */
.pd-sec-stats .pd-slabel::before { background: var(--accent); opacity: 0.7; }
.pd-sec-aug   .pd-slabel::before { background: var(--accent-2); }
.pd-sec-craft .pd-slabel::before { background: var(--warn); opacity: 0.8; }

/* #471 — THE ONE ROW LANGUAGE.
   Every fact on the card is the same three columns:

       [ marker ]  [ where it comes from ]  [ what it gives ]

   A printed affix, a set bonus, a slotted gem and an applied craft are all that
   row; the sections differ only in what fills the middle column. This replaces
   three chip families — stat pills, augment pips, craft pills — that between
   them made a single card speak three visual languages about the same subject.

   The three columns are FIXED, so the middle column lines up down the whole
   card: that alignment is what lets the eye run down "where does each of these
   come from" without reading any of the values. */
.pd-lines { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: 2px; }
.pd-line { display: grid; grid-template-columns: 11px 6.7em 1fr; align-items: baseline;
  column-gap: 6px; min-width: 0; font-size: var(--fs-xs); line-height: 1.4; color: var(--muted); }
.pd-ln-mark { justify-self: center; font-size: 0.68rem; line-height: 1.5; color: var(--border-2); }
.pd-ln-where { color: var(--faint); font-size: 0.63rem; text-transform: uppercase;
  letter-spacing: 0.05em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The value column wraps INSIDE itself rather than widening the row — the 375px
   lesson from #453, carried over unchanged: a long label ("Universal Spell
   Power +15 Exceptional") must never push the page into horizontal scroll. */
.pd-ln-what { min-width: 0; overflow-wrap: anywhere; }

/* #471 — the highlight vocabulary, reduced to what it claims:
   WHITE + BOLD  = credited to your priority list (this is why the item is here);
   WHITE         = on your list, but outbid elsewhere in the build;
   grey          = came along with the item.
   Colour is never the only carrier. Weight separates the top two, and the
   marker is a SHAPE ramp — filled, hollow, ring, dot — so all four classes stay
   apart in monochrome and to a colour-vision difference (#453 R2/R3's rule,
   moved onto the family that replaced the chips). */
.pd-line.is-tracked .pd-ln-what { color: #fff; font-weight: 700; }
.pd-line.is-tracked .pd-ln-mark { color: var(--optimal); }
.pd-line.is-tracked .pd-ln-where { color: var(--muted); }
.pd-line.is-ranked .pd-ln-what { color: #fff; }
.pd-line.is-ranked .pd-ln-mark { color: rgba(67,197,158,0.6); }
.pd-line.is-utility .pd-ln-mark { color: var(--accent-2); }
.pd-line.is-incidental .pd-ln-mark { color: var(--border-2); }
/* #455 R21 — the rank-1 accent. Kept as a second visible mark on the marker
   rather than a hue change, for the reason the chip's border WEIGHT was. */
.pd-line.is-rank1 .pd-ln-mark { color: var(--accent);
  text-shadow: 0 0 6px rgba(91,140,255,0.85); }
/* A declared slot the solve left empty. The row still exists — an item that
   ships with four slots must never read as a three-slot item (#370) — and says
   so plainly; the reason rides the row's title. */
.pd-line.is-empty .pd-ln-what { color: var(--faint); font-style: italic; }
.pd-line.is-empty .pd-ln-mark { color: var(--border-2); }

/* R9 — overflow is HIDDEN, never dropped: the rows stay in the DOM so the fact
   stays on the card, which is the whole point. */
.pd-line.is-overflow { display: none; }
.pd-lines.is-expanded .pd-line.is-overflow { display: grid; }
.pd-more-btn { background: none; border: 0; border-bottom: 1px dotted var(--border-2);
  border-radius: 0; color: var(--muted); font: inherit; font-size: 0.63rem;
  text-transform: uppercase; letter-spacing: 0.06em; padding: 0; cursor: pointer; }
.pd-more-btn:hover { color: var(--text); border-bottom-color: var(--muted); }

/* The qualifiers `pd-prio` used to carry. They wrap under the row's own value
   rather than extending it, so a long #88 override disclosure cannot widen the
   row. `(set)` was retired in #471 — the WHERE column states it. */
.pd-chip-q { display: block; color: var(--faint); font-size: 0.85em; line-height: 1.3; }
.pd-q + .pd-q::before { content: " · "; }
.pd-q-override { color: var(--warn, #d9a441); }

/* #453 U3 (R6) / #471 — what a gem or a craft grants, indented under it. Kept
   nested rather than merged into the card-level row: the loadout is a shopping
   list, and "which gem do I actually go slot" has to survive. */
.pd-sub { list-style: none; margin: 1px 0 2px; padding: 0;
  display: flex; flex-direction: column; gap: 1px; }
.pd-sub li { position: relative; padding-left: 10px; font-size: 0.95em; color: var(--muted); }
.pd-sub li::before { content: ""; position: absolute; left: 1px; top: 0.62em;
  width: 5px; height: 1px; background: var(--border-2); }
.pd-sub li.is-tracked { color: #fff; font-weight: 700; }
.pd-sub li.is-ranked { color: #fff; }

/* #471 — the augment marker carries its slot colour, and Sun/Moon carry the
   SAME glyphs the Set Bonuses tab prints for a set-like Lunar/Solar bonus.
   Those two belong to a separate gem system that accepts no standard augment
   (wiki-ruled) and were previously a coloured dot indistinguishable from a Red
   or a Green one. */
.aug-mark.aug-blue { color: #5a8fd0; }
.aug-mark.aug-red { color: #cf6a63; }
.aug-mark.aug-yellow { color: #cbb45e; }
.aug-mark.aug-green { color: #6fae74; }
.aug-mark.aug-orange { color: #d08a4a; }
.aug-mark.aug-purple { color: #a074c0; }
.aug-mark.aug-colorless, .aug-mark.aug-clear { color: var(--border-2); }
.aug-mark.aug-sun { color: #e6c86a; }
.aug-mark.aug-moon { color: #9cc2ee; }
.pd-line.is-empty .aug-mark { opacity: 0.6; }
.aug-name { color: inherit; font-weight: inherit; }
.pd-line.aug-filled.is-tracked .aug-name { color: #fff; font-weight: 700; }
.pd-line.aug-filled.is-ranked .aug-name { color: #fff; }
/* The augment pip survives for the paperdoll cell and the browse rows, which
   still draw slot colours as pips rather than as row markers. */
.aug-pip { display: inline-flex; align-items: center; padding: 1px 6px; border-radius: 999px; font-size: 0.62rem;
  border: 1px solid var(--border-2); color: var(--muted); }
.aug-pip.aug-blue { border-color: #5a8fd0; color: #9cc2ee; }
.aug-pip.aug-red { border-color: #cf6a63; color: #e6a09a; }
.aug-pip.aug-yellow { border-color: #cbb45e; color: #e6d68f; }
.aug-pip.aug-green { border-color: #6fae74; color: #a6d6aa; }
.aug-pip.aug-orange { border-color: #d08a4a; color: #eab98a; }
.aug-pip.aug-purple { border-color: #a074c0; color: #cbb0e0; }
.aug-pip.aug-colorless, .aug-pip.aug-clear { border-color: var(--border-2); color: var(--faint); }
.craft-mark { padding: 1px 6px; border-radius: var(--radius-sm); border: 1px dashed var(--border-2); font-size: 0.62rem; color: var(--muted); }

/* #471 — ONE foot-note family. The card's bottom edge carried four shapes: an
   amber text line ("here only for its crafts"), a muted one ("included to
   complete the loadout"), a third for the no-drop-source disclosure and a
   fourth, in the BODY, for the owned-mode note. They are all the same KIND of
   statement — something about the whole pick rather than about one stat — so
   they are now one stacked chip that differs only in its role colour and glyph.
   The user reported the mismatch directly; it was four spellings of one idea. */
.pd-note { display: grid; grid-template-columns: 13px 1fr; column-gap: 6px; align-items: start;
  font-size: 0.68rem; line-height: 1.4; color: var(--muted);
  padding: 3px 7px 3px 5px; border-radius: 5px;
  background: rgba(255,255,255,0.028); border-left: 2px solid var(--border-2); }
.pd-note .pd-note-ico { justify-self: center; opacity: 0.9; }
.pd-note b { color: var(--text); font-weight: 600; }
.pd-note.is-craft { border-left-color: var(--warn); }
.pd-note.is-empty { border-left-color: var(--border-2); color: var(--faint); }
.pd-note.is-source { border-left-color: var(--quarantined); }
.pd-note.is-owned { border-left-color: var(--accent-2); }
.pd-note.muted { color: var(--faint); }

/* Chips survive on the Deep Dive's craft block, which is a run of instructions
   rather than a per-slot list. A craft chip's own border encodes WHICH crafting
   system it belongs to (dino amber, Nearly Completed violet, Lamordia rose, …),
   a fact the player needs to find the right station — so the priority link is
   drawn as a ring OUTSIDE that border rather than by repainting it. Both facts
   survive, and the ring is a second visible line, not a hue change. */
.chip.is-tracked { box-shadow: 0 0 0 1px var(--optimal); color: var(--text); font-weight: 600; }
.chip.is-ranked { box-shadow: 0 0 0 1px rgba(67,197,158,0.55); color: var(--text); }
/* #457 — a Deep Dive augment chip carrying its granted stats becomes a small
   block: name on top, grants beneath, matching the gear card's nesting. */
.chip.aug.has-affx { display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: 2px; max-width: 100%; }
.chip.aug.has-affx .aug-head { white-space: normal; }
.chip.aug .pd-sub { font-size: 0.62rem; }

/* Per-slot constraint controls (U6) on the equipped-list rows. */
.pd-row { position: relative; }
.pd-rtop { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
/* #447 / #449 U8 (R23/R25) -- visible at rest. It was `opacity: 0` until
   `.pd-row:hover`, which made it undiscoverable by accident on desktop and
   permanently absent on touch, and put keyboard focus on a fully transparent
   element -- a WCAG 2.4.11 failure, since the focus ring the global rule already
   draws was landing on something invisible. No opacity, no hover gate. */
.pd-ctl { position: relative; min-height: 0; padding: 0; flex: none; width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--faint); font-size: var(--fs-md); line-height: 1; cursor: pointer; }
/* R24 -- the touch target reaches var(--tap) through an overlay that contributes
   NO layout. Padding would reach 44px too and is wrong: .pd-ctl is the tallest
   child of .pd-rtop, a flex row whose height follows it, so padding would raise
   every gear row -- 15 of them on a full paperdoll, measured. The overlay costs
   nothing and stays inside the row's existing padding.

   The 22px -> 34px visual change DOES raise the row, by 12px each (+180px on a
   full paperdoll). That is R24's settled visual size, not a side effect: the
   requirement's "must not change .pd-rtop's rendered height" is about the
   hit-area mechanism, which is why the overlay exists. */
.pd-ctl::after { content: ""; position: absolute; inset: -5px; }
.pd-ctl:hover { color: var(--text); border-color: var(--border-2); }
/* A constrained slot used to be legible from the control being visible AT ALL
   while its neighbours were transparent. Now every control shows, so that signal
   would be lost -- the accent carries it, agreeing with the row border below. */
.pd-row.constrained .pd-ctl { color: var(--text); border-color: var(--accent); }
.pd-row.constrained { border-color: var(--accent); }
.pd-badge { display: inline-block; margin-top: 4px; font-size: 0.68rem; border-radius: 5px; padding: 1px 6px; border: 1px solid var(--border-2); }
.pd-badge.pin { color: var(--accent); border-color: var(--accent); }
.pd-badge.empty { color: var(--quarantined); border-color: var(--quarantined); }
/* U5 — the one equipped Artifact: a distinct violet cue, kept separate from the
   set-piece gold frame so a slot that is BOTH an Artifact and a set piece shows both. */
.pd-badge.artifact { color: var(--artifact); border-color: var(--artifact); }
.pd-row.is-artifact { border-color: var(--artifact); box-shadow: inset 0 0 0 1px rgba(185,140,255,0.28); }
.pd-row.is-artifact.is-set { box-shadow: inset 0 0 0 1px rgba(185,140,255,0.30), inset 3px 0 0 rgba(240,180,90,0.6); }
.dd-item.is-artifact { border-color: var(--artifact); box-shadow: 0 0 0 1px rgba(185,140,255,0.30); }
.dd-artifact { color: var(--artifact); border: 1px solid var(--artifact); border-radius: 5px;
  font-size: var(--fs-xs); padding: 0 6px; font-weight: 600; align-self: center; }
/* #262 — the no-known-drop-source disclosure: Deep Dive tag (mirrors dd-artifact)
   and gear-box row note (mirrors the pd-carried warn treatment) */
.dd-nodrop { color: var(--quarantined); border: 1px solid var(--quarantined); border-radius: 5px;
  font-size: var(--fs-xs); padding: 0 6px; font-weight: 600; align-self: center; }
.pd-rnote.pd-nodrop { color: var(--quarantined); font-size: var(--fs-xs); }
.artifact-notice { margin: var(--sp-3) 0; padding: 10px 14px; border: 1px solid var(--artifact);
  border-left-width: 3px; border-radius: var(--radius-sm); background: rgba(185,140,255,0.08);
  color: var(--text); font-size: var(--fs-sm); }
.pd-menu { display: flex; flex-direction: column; gap: 4px; margin-top: var(--sp-2); padding-top: var(--sp-2);
  border-top: 1px dashed var(--border); }
.pd-menu[hidden] { display: none; }
.pd-menu button { min-height: 0; padding: 6px 8px; background: var(--panel); border: 1px solid var(--border-2);
  color: var(--text); border-radius: var(--radius-sm); font-size: var(--fs-xs); text-align: left; cursor: pointer; }
.pd-menu button:hover:not(:disabled) { border-color: var(--accent); }
.pd-menu button:disabled { opacity: 0.4; cursor: not-allowed; }

/* "Constraints changed — re-solve" bar in the results head. */
.wz-cbar { display: flex; align-items: center; gap: var(--sp-3); background: var(--panel-2);
  border: 1px dashed var(--quarantined); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3);
  margin-bottom: var(--sp-3); font-size: var(--fs-sm); color: var(--muted); }
.wz-cbar .btn { padding: 7px 14px; }

/* #428 U3 (KTD4) — the step body and the save rail, side by side. The rail is
   rendered by render() rather than by any step template, so this shell wraps
   EVERY step: the layout change is the plan's widest blast radius by design.
   The rail sticks while a long step scrolls, so Save never scrolls away.

   #431 U5 (R9) — the rail no longer holds the name input or the save button, so
   it is reference material (which build is loaded, which are saved) rather than
   an action surface. Two consequences: its sticky positioning no longer keeps
   Save on screen, it keeps the saved list on screen; and the `order: -1` that
   put it FIRST below 900px is dropped. That rule existed so a phone player met
   "save this" before a long form — with saving now in the form's own action bar,
   hoisting the rail would only push the actual work down the page. */
.wz-shell { display: grid; grid-template-columns: minmax(0, 1fr) 17rem; gap: var(--sp-4); align-items: start; }
.wz-body { min-width: 0; }
.wz-rail {
  position: sticky; top: var(--sp-4);
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-2);
}
.wz-rail-head { margin: 0; font-weight: 700; font-size: var(--fs-sm); }
.wz-rail-loaded { margin: 0; font-size: var(--fs-sm); color: var(--muted); overflow-wrap: anywhere; }
.wz-rail .wz-help { margin: 0; }
.wz-rail-list { margin-top: var(--sp-2); border-top: 1px solid var(--border-2); padding-top: var(--sp-3); }
.wz-rail-list .wz-label { display: block; }
.wz-rail .wz-charlist li.on { border-color: var(--accent); }
@media (max-width: 900px) {
  .wz-shell { grid-template-columns: 1fr; }
  .wz-rail { position: static; }
}

/* #428 U5 (R19) — the unsaved-changes guard. A modal, not an inline bar: it has
   to PRECEDE the navigation it is guarding, and it offers three answers (save,
   leave anyway, stay) where window.confirm offers two. */
.wz-modal {
  position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.55); padding: var(--sp-4);
}
.wz-modal-panel {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: var(--sp-5); max-width: 30rem; width: 100%;
  display: flex; flex-direction: column; gap: var(--sp-3);
}
.wz-modal-panel p { margin: 0; }
.wz-modal-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.wz-modal-actions .btn { padding: 7px 14px; }

/* Character persistence (U3/U4): the saved-build list, now rendered by the
   #428 save rail. The `.wz-saved` / `.wz-save` wrappers went with the two
   surfaces the rail replaced. */
.wz-charlist { list-style: none; margin: var(--sp-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.wz-charlist li { display: flex; align-items: center; gap: var(--sp-2); background: var(--panel-2);
  border: 1px solid var(--border-2); border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3); }
.wz-charnm { flex: 1; font-weight: 600; overflow-wrap: anywhere; }
.wz-charlist .wz-ctl { display: flex; gap: var(--sp-1); }
.wz-charlist .wz-ctl button { background: transparent; color: var(--muted); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); padding: 3px 9px; font-size: var(--fs-xs); cursor: pointer; }
.wz-charlist .wz-ctl button:hover { color: var(--text); border-color: var(--accent); }
.wz-savestat { font-size: var(--fs-sm); color: var(--muted); }
.wz-data { margin-top: var(--sp-3); border: 1px dashed var(--border-2); border-radius: var(--radius); background: var(--panel-2); }
.wz-data > summary { list-style: none; cursor: pointer; padding: var(--sp-2) var(--sp-3); font-weight: 600; font-size: var(--fs-sm);
  display: flex; align-items: center; gap: var(--sp-2); }
.wz-data > summary::-webkit-details-marker { display: none; }
.wz-data > summary::before { content: "▸"; color: var(--accent); transition: transform 0.15s; }
.wz-data[open] > summary::before { transform: rotate(90deg); }
.wz-data-body { padding: 0 var(--sp-3) var(--sp-3); }
.wz-data-row { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
.wz-data-row .wz-file { flex: 1; min-width: 200px; }
.wz-data-sep { border: none; border-top: 1px dashed var(--border-2); margin: var(--sp-3) 0; }
/* #428 U7 — the relocated "Your data" block. Rendered by two hosts: below the
   Share tab's loadout formats (where the backup-vs-share distinction is stated
   once, side by side), and in the on-demand panel that makes it reachable
   before any solve. Set off by a rule so it reads as a different job. */
.wz-data-block { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--border-2); }
.wz-data-block .wz-label { display: block; margin-bottom: var(--sp-1); }
.wz-data-panel { max-width: 42rem; }
.wz-share-pick { display: flex; flex-direction: column; gap: 2px; max-width: 34rem; }
.wz-share-pick select { width: 100%; background: var(--panel-2); color: var(--text); border: 1px solid var(--border-2);
  border-radius: var(--radius-sm); padding: 7px 10px; }
/* export buttons: a tidy, evenly-sized row directly under the loadout picker */
.wz-share-btns { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); max-width: 34rem; }
.wz-share-btns .btn { flex: 1 1 0; min-width: 6rem; text-align: center; }

/* U13: single-loadout print view — a body-level container the @media print
   rules isolate so only the loadout prints. */
#wz-printarea { display: none; }
@media print {
  body.printing > *:not(#wz-printarea) { display: none !important; }
  #wz-printarea { display: block !important; color: #000; background: #fff; padding: 0; }
  #wz-printarea h1 { font-size: 20px; margin: 0 0 6px; }
  #wz-printarea .pc { font-size: 12px; margin: 0 0 12px; color: #333; }
  #wz-printarea table { width: 100%; border-collapse: collapse; font-size: 11px; }
  #wz-printarea th, #wz-printarea td { border: 1px solid #999; padding: 4px 6px; text-align: left; vertical-align: top; }
  #wz-printarea thead th { background: #eee; }
}

/* Adjust & re-solve panel (U3) — fold-up editor in the results view. */
/* U3/R6 — the Adjust slot sits directly under the tab bar, above the panels, so
   it shows on every tab (the panels toggle; the slot never does). */
.wz-adjust-slot:not(:empty) { margin-bottom: var(--sp-3); }
/* U5 — the Share tab panel (single-loadout exports, distinct from the Character
   step's personal-build backup). */
.wz-share { display: flex; flex-direction: column; gap: var(--sp-3); }
/* U4 — Alternatives Run-analysis prompt + the panel-local computing swirly. */
.alt-intro { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); padding: var(--sp-3) 0; }
.alt-computing { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); padding: var(--sp-5) 0; }
.wz-adjust { margin-top: var(--sp-4); border: 1px dashed var(--border-2); border-radius: var(--radius);
  background: var(--panel-2); }
.wz-adjust > summary { list-style: none; cursor: pointer; padding: var(--sp-3) var(--sp-4); font-weight: 650;
  font-size: var(--fs-sm); color: var(--text); display: flex; align-items: center; gap: var(--sp-2); }
.wz-adjust > summary::-webkit-details-marker { display: none; }
.wz-adjust > summary::before { content: "▸"; color: var(--accent); transition: transform 0.15s; }
.wz-adjust[open] > summary::before { transform: rotate(90deg); }
.wz-adjust > summary:focus-visible { outline: none; box-shadow: var(--ring); border-radius: var(--radius); }
.wz-adjust-body { padding: 0 var(--sp-4) var(--sp-4); }
.wz-adjust-row { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-3); }
.wz-toggle { display: inline-flex; border: 1px solid var(--border-2); border-radius: var(--radius-sm); overflow: hidden; }
.wz-toggle button { min-height: 0; padding: 7px 12px; background: var(--panel); border: none; color: var(--muted);
  font-size: var(--fs-sm); cursor: pointer; }
.wz-toggle button.on { background: var(--accent); color: var(--accent-ink); }

/* Top bar (stepper + Browse trigger) and the on-demand Item Browser overlay (U9). */
.wz-topbar { display: flex; align-items: center; gap: var(--sp-4); flex-wrap: wrap; }
.wz-topbar .wz-steps { flex: 1; }
/* Width is shared with the header support button directly above it, so the two
   right-edge buttons line up as a column. */
.wz-browse-btn { flex: none; width: var(--stack-btn-w); }
.wz-browse-overlay { position: fixed; inset: 0; background: rgba(8,10,14,0.82); backdrop-filter: blur(3px);
  display: none; align-items: flex-start; justify-content: center; z-index: 70; padding: var(--sp-5) var(--sp-4);
  overflow-y: auto; }
.wz-browse-overlay.on { display: flex; }
.wz-browse-panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: var(--sp-5); width: 100%; max-width: var(--maxw); }
.wz-browse-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.wz-browse-head h2 { margin: 0; font-size: var(--fs-xl); }

/* #110 — the blocklist picker (block box mirrors the pin box's bones) */
.wz-block-hit { display: flex; align-items: center; gap: 8px; padding: 6px 8px; cursor: pointer; }
.wz-block-hit-off { opacity: 0.55; cursor: default; }
.wz-block-stage { display: flex; gap: 8px; align-items: center; margin: 6px 0; }

/* #348 (U6) — the pinned Utility container row.
   R2: visually distinct from the ranked rows above it, so a player can see at a
   glance that it is not competing with them. Muted rather than loud: it is the
   lowest-stakes row on the page and should not read as a warning. */
.wz-utility-row { background: var(--panel-2, rgba(255,255,255,0.03)); border-radius: 6px; }
.wz-grip-pinned { cursor: default; opacity: 0.7; letter-spacing: normal; }
.wz-rk-pinned {
  /* Browser-verified fix: `.wz-rk` is a filled, rounded rank badge, so the pinned
     row inherited a 22px blue chip containing a bare "·" — it read as an empty
     rank number on a row that is deliberately NOT ranked. */
  background: transparent; color: var(--faint); font-weight: normal;
}
.wz-util-summary { display: block; font-size: 0.85em; color: var(--faint); margin-top: 2px; }
.wz-util-panel .wz-adv-body { display: block; }
.wz-util-list { list-style: none; margin: var(--sp-2) 0; padding: 0; }
.wz-util-item { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.wz-util-pos { color: var(--faint); min-width: 1.4em; text-align: right; font-variant-numeric: tabular-nums; }
.wz-util-name { flex: 1 1 auto; }
.wz-util-search { display: block; margin-top: var(--sp-2); }
.wz-util-search input { width: 100%; max-width: 26rem; }
.wz-util-sugg { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.wz-util-add { font-size: 0.9em; }
/* The cap refusal (R5) must be readable, not a whisper — it is the one message
   here that explains why an action did nothing. */
.wz-util-status:empty { display: none; }
.wz-util-status { margin-top: 6px; color: var(--warn, #d9a441); font-size: 0.9em; }

/* #88 U10/U11/U12 — the override creation picker, the manager rows, and the
   correction report. Deliberately borrows the pin/blocklist row shapes: a
   correction is the same kind of thing to the player, a standing choice they
   made about their own build. */
.wz-overridebox .wz-adv-note { margin: .25rem 0 .5rem; }
.wz-ov-row { flex-wrap: wrap; align-items: baseline; gap: .4rem .6rem; }
.wz-ov-state { flex: 1 1 100%; font-size: .85em; opacity: .8; }
.wz-ov-row.is-suspended .wz-ov-state { opacity: 1; }
.wz-ov-report, .pd-override-report { flex: 1 1 100%; }
.wz-ov-reporttext {
  width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .8em; line-height: 1.45; resize: vertical;
}
.pd-override { margin: .5rem 0 .25rem; padding: .6rem .7rem; border-radius: 6px; }
.pd-override-lead, .pd-override-empty { font-size: .85em; margin: 0 0 .5rem; opacity: .9; }
.pd-override-row { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin: .3rem 0; }
.pd-override-affix { flex: 1 1 14rem; }
.pd-override-count { opacity: .7; }
.pd-override-note { flex: 1 1 10rem; min-width: 0; }
.pd-override-set { font-size: .85em; opacity: .8; }
.pd-override-status:empty { display: none; }
.pd-override-status { font-size: .85em; margin: .4rem 0 0; }

/* ---- #449 U5 -- the notices panel ---------------------------------------- */
/* R1 -- the banner's own gradient and border, so the panel reads as part of the
   optimization rather than as loose prose stacked beneath it. Separate element
   with a gap: it states what qualifies the verdict, it is not the verdict. */
.notes-panel {
  margin: 0 0 var(--sp-4);
  border: 1px solid rgba(67,197,158,0.35); border-radius: var(--radius);
  background: linear-gradient(135deg, rgba(67,197,158,0.10), rgba(91,140,255,0.06));
}
/* R2/R37 -- the panel's only tap target. Wraps as a row of whole units at 375px;
   min-height holds the tap target in every wrap state. */
.notes-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  min-height: var(--tap); padding: var(--sp-2) var(--sp-4);
  cursor: pointer; font-weight: 600; list-style: none;
}
.notes-summary::-webkit-details-marker { display: none; }
.notes-chevron { display: inline-block; transition: transform 120ms ease; color: var(--faint); }
.notes-panel[open] > .notes-summary .notes-chevron { transform: rotate(90deg); }
/* R3 -- the count is visible without opening, so the player can see there is
   something here without paying to find out. */
.notes-count { color: var(--faint); font-size: var(--fs-xs); font-weight: 500; }
.notes-body { padding: 0 var(--sp-4) var(--sp-3); display: grid; gap: var(--sp-2); }

/* R7/R9 -- the attention pill. The amber FILL and border are the signal; the
   pulse below only decorates them. The repo kills every animation under
   prefers-reduced-motion (see the `* { animation: none !important }` rule), so
   a motion-carried signal would be invisible to exactly the players who asked
   for less motion. Ink is the near-black page ground rather than white: measured
   against this amber, white is 2.25:1 (below the 4.5:1 floor) and --bg is 8.18:1.
   R37 -- wraps as a unit, never breaking internally. */
.notes-pill {
  white-space: nowrap; padding: 2px var(--sp-2); border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 700;
  color: var(--bg); background: var(--warn); border: 1px solid var(--warn);
}
/* R8/KTD3 -- the one-way latch. The pulse reads the ABSENCE of the stamp, which
   renderResults writes at build time from a session flag, so it survives the
   rebuild. Keyed on [open] instead it would re-arm on every collapse AND on
   every solve, load and per-slot constraint change. */
.notes-panel:not([data-notes-seen]) .notes-pill { animation: notes-pulse 1.6s ease-in-out infinite; }
@keyframes notes-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217,164,65,0.55); }
  50%      { box-shadow: 0 0 0 5px rgba(217,164,65,0); }
}
/* R26 -- the qualifying marker names its subjects rather than counting them: a
   bare count says something exists, it does not say the headline totals rest on
   unverified input. Slate, off the amber ramp, and NEVER animated -- it reports
   a standing condition, not something to go and fix. */
.notes-qualify {
  white-space: nowrap; padding: 2px var(--sp-2); border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 600;
  color: var(--qualify); border: 1px solid var(--qualify); background: transparent;
}

/* R4 -- every notice is a bordered sub-card with a title; none renders as bare
   prose. R5 -- the left edge carries the class. */
.notice-card {
  border: 1px solid var(--border); border-left: 3px solid var(--border);
  border-radius: var(--radius-sm); padding: var(--sp-2) var(--sp-3);
  background: var(--elev);
}
.notice-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-2); }
.notice-title { font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.04em; }
/* R28 -- the non-colour carrier. Its own element, so class survives a greyscale
   render or red-green colour-vision deficiency without the title absorbing it. */
.notice-tag {
  font-size: var(--fs-xs); font-weight: 600; padding: 1px 6px;
  border: 1px solid currentColor; border-radius: 999px; opacity: 0.85;
}
.notice-body { margin-top: var(--sp-1); font-size: var(--fs-xs); color: var(--muted); }
.notice-body .notice-sentence { margin: 0; }
.notice-body > p + p { margin-top: var(--sp-1); }

.notice-card.is-actionable { border-left-color: var(--warn); }
.notice-card.is-actionable .notice-title, .notice-card.is-actionable .notice-tag { color: var(--warn); }
.notice-card.is-qualifying { border-left-color: var(--qualify); }
.notice-card.is-qualifying .notice-title, .notice-card.is-qualifying .notice-tag { color: var(--qualify); }
.notice-card.is-informational { border-left-color: var(--optimal); }
.notice-card.is-informational .notice-title, .notice-card.is-informational .notice-tag { color: var(--optimal); }
/* A notice with no table entry renders VISIBLY rather than throwing: the results
   screen survives, and the gap is impossible to miss on the next look. */
.notice-card.is-unclassified { border-left-color: var(--danger, #e2564d); border-style: dashed; }

/* R6 -- the resolution control. A button, not a link: it changes wizard state
   rather than navigating a URL. */
.notice-jump {
  margin-top: var(--sp-2); min-height: var(--tap);
  padding: var(--sp-1) var(--sp-3); font-size: var(--fs-xs); font-weight: 600;
  color: var(--warn); background: transparent;
  border: 1px solid var(--warn); border-radius: var(--radius-sm); cursor: pointer;
}
.notice-jump:hover { background: rgba(217,164,65,0.12); }
