/*
 * ============================================================================
 * Oriens — "The Rising" design system (docs/design/IDENTITY.md)
 * ============================================================================
 *
 * The shared chrome vocabulary for every player-facing page. Anchored to the
 * Warsong 16-bit SRPG grammar already established on the fight reveal
 * (battle_reveal_controller.js injects the same tokens at fight time) — but
 * Oriens stakes its own identity: deep INDIGO/night base + warm AMBER-GOLD
 * accents, with a signature warm glow bleeding up from the EASTERN (right) edge
 * of every screen. Never SaaS white, never Warsong forest-green.
 *
 * This file is the reusable component set. Apply these classes in views; don't
 * re-invent panels/buttons inline. The fight-reveal `br-*` tokens are lifted
 * here verbatim (same colors, same bevel recipe) so the reveal and the chrome
 * around it read as one world.
 *
 * Component vocabulary:
 *   .or-page          page background — indigo night + subtle east-glow
 *   .br-window        dark bevelled Warsong panel (chunky)
 *   .br-window-lt     lighter bevelled panel (insets, sub-cards)
 *   .or-panel-amber   panel with a warm amber rail (highlights / win-condition)
 *   .or-panel-danger  panel with a red rail (stakes / loss)
 *   .br-pixel         the Press Start 2P pixel display font (headers + numbers)
 *   .or-eyebrow       small pixel eyebrow label, dim ink
 *   .or-h1 / .or-h2   serif display headings on dark ink
 *   .or-btn           amber/gold primary button
 *   .or-btn-ghost     bevelled secondary (dark) button
 *   .or-btn-danger    red-rail destructive button
 *   .or-chip          small bevelled stat/meta chip
 *   .or-hud           the trainer HUD band
 *   .br-hpbar/.br-hpfill   segmented Warsong bar (lifted from the reveal)
 *   .or-bar / .or-bar-fill segmented progress bar, themeable via color
 * ============================================================================
 */

:root {
  /* Warsong panel tokens — IDENTICAL to battle_reveal_controller.js so the
     reveal and the chrome share one palette. */
  --br-pixel: 'Press Start 2P', ui-monospace, monospace;
  --br-panel: #1b2330;
  --br-panel-2: #232c3d;
  --br-border: #5b6b86;
  --br-border-lt: #8a9bbd;
  --br-ink: #e7ecf6;
  --br-ink-dim: #9fb0cc;
  --br-gold: #f4c860;
  --br-green: #5ad17a;

  /* "The Rising" base — deep indigo/night, distinct from the reveal's slate so
     the page reads as a world the panels sit inside, not one flat slab. */
  --or-night-0: #0c1020; /* deepest, page edges */
  --or-night-1: #131a30; /* base field */
  --or-night-2: #1b2440; /* raised */
  --or-amber: #f4c860;
  --or-amber-deep: #d99a2b;
  --or-amber-ink: #f7d98a;
  --or-danger: #e06a5a;
  --or-danger-deep: #b3402f;
  /* Parent identity colors for the merge reveal (task #14161). Parent A reads warm
     ember, parent B reads cool teal — one color per bloodline, carried downstream
     onto everything the child inherited from that parent (tile frame, the source
     name under a stat's number, that parent's tick on the track, its trait/resist
     chips). Kept clear of --or-amber (gold), which stays reserved for the standout
     flare — "its own" blood the cross invented, distinct from either parent. */
  --or-ember: #ef7f4a;
  --or-ember-ink: #ffa877;
  --or-teal: #37b3ac;
  --or-teal-ink: #79d8d0;
  /* Level-up growth colors (Zac 2026-06-27): the post-fight attributes display
     color-codes the points a level just added. BLUE = the trait YOU chose to push
     (the surge pick); YELLOW = the natural per-level growth you got for free.
     Replaces the old single green-outline "grew" treatment. */
  --or-blue: #5aa9f4;
  --or-blue-ink: #9fccff;
  --or-yellow: #f4d35a;
  --or-yellow-ink: #f7e08a;
}

/* ===========================================================================
   PAGE BACKGROUND — indigo night + the eastern horizon glow
   ===========================================================================
   The signature motif: a low, warm amber glow bleeding up from the EAST
   (right) edge — it reads as dawn. Ambient only; subtle. Built from two
   layered radial gradients fixed to the viewport so the glow stays anchored to
   the screen's east edge as the page scrolls (grand + a little wrong). */
.or-page {
  position: relative;
  background-color: var(--or-night-1);
  background-image:
    /* the eastern glow — warm amber rising from the lower-right */
    radial-gradient(120% 90% at 108% 78%, rgba(244,200,96,0.16) 0%, rgba(244,200,96,0.06) 26%, rgba(244,200,96,0) 52%),
    /* a fainter higher band so the glow feels like a horizon, not a spotlight */
    radial-gradient(90% 70% at 104% 40%, rgba(217,154,43,0.08) 0%, rgba(217,154,43,0) 55%),
    /* the night field, deepening to the west/top */
    linear-gradient(160deg, var(--or-night-1) 0%, var(--or-night-0) 70%, #080b16 100%);
  background-attachment: fixed, fixed, fixed;
  color: var(--br-ink);
}
/* A whisper of star-grain over the night so the dark field isn't dead-flat.
   z-index:-1 keeps it BEHIND all normal-flow content without forcing a
   position/z-index on every page child — which would have clobbered the
   sticky nav's `position: sticky`. */
.or-page::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: -1;
  background-image: radial-gradient(circle, rgba(160,176,204,0.14) 1px, transparent 1px);
  background-size: 46px 46px;
  opacity: 0.5;
}

/* ===========================================================================
   PIXEL FONT + HEADINGS
   =========================================================================== */
.br-pixel {
  font-family: var(--br-pixel);
  -webkit-font-smoothing: none;
  line-height: 1.5;
}
/* Numerals inside a pixel-font header (2026-06-18 playtest A7): Press Start 2P
   renders "8" ambiguously at small sizes — "8 FIGHTERS" reads as "& FIGHTERS".
   Swap just the digits to a plain monospace so the count is unmistakable, while
   the surrounding label keeps the pixel face. Scoped to bracket headers for now,
   not every numeral in the app. */
.br-pixel-num { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.or-eyebrow {
  font-family: var(--br-pixel);
  font-size: 9px;
  letter-spacing: 0.08em;
  color: var(--or-amber);
  text-transform: uppercase;
  -webkit-font-smoothing: none;
}
@media (min-width: 640px) { .or-eyebrow { font-size: 10px; } }
/* Stat-group cluster label (Body / Edge / Spirit) for the grouped stat shape
   (shared/_stat_shape, NPE visual overhaul A 2026-06-26). The keystone grouping
   headers a player MUST read to parse the silhouette — so NOT the 9px pixel
   eyebrow (the "text too small" complaint). A readable amber section label:
   uppercase + letterspaced for structure, but at a body-legible size. */
.or-stat-group-label {
  font-family: var(--br-pixel);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--or-amber-ink);
  text-transform: uppercase;
  -webkit-font-smoothing: none;
}
@media (min-width: 640px) { .or-stat-group-label { font-size: 12px; } }

/* Disclosure caret for the compact stat-shape "See full stats ▸" tap (restraint
   pass, Zac 2026-06-26). Rotates to ▾ when its <details> is open so the toggle
   reads its own state; the native marker is hidden (marker:content-none on the
   summary) so this single glyph is the only affordance. */
.or-disclosure-caret {
  display: inline-block;
  transition: transform 0.12s ease-out;
}
details[open] > summary .or-disclosure-caret { transform: rotate(90deg); }

.or-h1 {
  font-family: var(--br-pixel);
  color: var(--br-ink);
  -webkit-font-smoothing: none;
  line-height: 1.35;
  text-shadow: 2px 2px 0 #0b0e18;
}
.or-h2 {
  font-family: var(--br-pixel);
  color: var(--or-amber);
  -webkit-font-smoothing: none;
  line-height: 1.4;
  text-shadow: 1px 1px 0 #0b0e18;
}
.or-ink      { color: var(--br-ink); }
.or-ink-dim  { color: var(--br-ink-dim); }
.or-gold     { color: var(--or-amber); }

/* ===========================================================================
   PANELS — the bevelled Warsong window (lifted from the fight reveal)
   =========================================================================== */
.br-window {
  background: linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  border: 2px solid var(--br-border);
  box-shadow: inset 1px 1px 0 var(--br-border-lt),
              inset -2px -2px 0 #11161f,
              0 2px 0 #0c0f16;
  color: var(--br-ink);
}
.br-window-lt {
  background: var(--br-panel-2);
  border: 2px solid var(--br-border);
  box-shadow: inset 1px 1px 0 rgba(138,155,189,0.5), inset -1px -1px 0 #11161f;
  color: var(--br-ink);
}
/* Amber-railed panel: a highlight / win-condition surface. Warm left rail +
   faint amber wash, still on the dark bevel so it belongs to the world. */
.or-panel-amber {
  background: linear-gradient(180deg, #2a2a25 0%, #211f1c 100%);
  border: 2px solid var(--or-amber-deep);
  border-left-width: 5px;
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.28),
              inset -2px -2px 0 #15110a,
              0 2px 0 #0c0f16;
  color: var(--or-amber-ink);
}
/* Danger-railed panel: stakes / loss / destructive. */
.or-panel-danger {
  background: linear-gradient(180deg, #2c211f 0%, #241a18 100%);
  border: 2px solid var(--or-danger-deep);
  border-left-width: 5px;
  box-shadow: inset 1px 1px 0 rgba(224,106,90,0.25),
              inset -2px -2px 0 #1a100d,
              0 2px 0 #0c0f16;
  color: #f3c7bf;
}

/* ===========================================================================
   BUTTONS
   =========================================================================== */
.or-btn,
.or-btn-ghost,
.or-btn-danger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1;
  padding: 0.7rem 1.15rem;
  border: 2px solid;
  cursor: pointer;
  text-decoration: none;
  transition: transform 0.08s ease-out, filter 0.12s ease-out, background 0.12s ease-out;
  -webkit-tap-highlight-color: transparent;
}
/* Primary — amber/gold. The signature action. */
.or-btn {
  color: #2a1d05;
  background: linear-gradient(180deg, #f7d472 0%, var(--or-amber) 55%, var(--or-amber-deep) 100%);
  border-color: #8a5e16;
  box-shadow: inset 1px 1px 0 #ffe9a6, inset -2px -2px 0 #9c6c1c, 0 2px 0 #5a3c0e;
}
.or-btn:hover { filter: brightness(1.06); }
.or-btn:active { transform: translateY(1px); box-shadow: inset 1px 1px 0 #9c6c1c, inset -1px -1px 0 #ffe9a6; }
/* Large primary — the "this is THE action" size, for a hero CTA like Forge
   (Zac playtest 2026-07-15: the forge button should be big). */
.or-btn-lg {
  font-size: 1.05rem;
  padding: 0.95rem 1.6rem;
  gap: 0.5rem;
}
/* Secondary — dark bevel, gold ink. */
.or-btn-ghost {
  color: var(--or-amber-ink);
  background: linear-gradient(180deg, #2b3650 0%, #222b40 100%);
  border-color: var(--br-border);
  box-shadow: inset 1px 1px 0 var(--br-border-lt), inset -2px -2px 0 #11161f, 0 2px 0 #0c0f16;
}
.or-btn-ghost:hover { filter: brightness(1.12); color: #fff; }
.or-btn-ghost:active { transform: translateY(1px); }
/* Destructive — red rail. */
.or-btn-danger {
  color: #fff;
  background: linear-gradient(180deg, #e0796a 0%, var(--or-danger) 55%, var(--or-danger-deep) 100%);
  border-color: #7d2b1f;
  box-shadow: inset 1px 1px 0 #f3b3a8, inset -2px -2px 0 #6a2114, 0 2px 0 #44150c;
}
.or-btn-danger:hover { filter: brightness(1.06); }
.or-btn-danger:active { transform: translateY(1px); }
/* Disabled — flat, no juice. */
.or-btn:disabled, .or-btn-ghost:disabled, .or-btn-danger:disabled,
.or-btn[aria-disabled="true"], .or-btn-ghost[aria-disabled="true"] {
  filter: grayscale(0.5) brightness(0.8);
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: none;
  transform: none;
}
/* Keyboard focus ring — amber, on the dark world. */
.or-btn:focus-visible, .or-btn-ghost:focus-visible, .or-btn-danger:focus-visible,
.or-link:focus-visible {
  outline: 2px solid var(--or-amber);
  outline-offset: 2px;
}

/* Inline text link in the amber world. */
.or-link {
  color: var(--or-amber-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  font-weight: 600;
}
.or-link:hover { color: #fff; }

/* ===========================================================================
   CHIPS — small bevelled meta / stat tags
   =========================================================================== */
.or-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3rem;
  padding: 0.15rem 0.5rem;
  background: #20283c;
  border: 1px solid var(--br-border);
  box-shadow: inset 1px 1px 0 rgba(138,155,189,0.35);
  color: var(--br-ink);
  font-size: 0.75rem;
  white-space: nowrap;
}
.or-chip-label { font-size: 9px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--br-ink-dim); }
.or-chip-amber {
  background: #2a2415;
  border-color: var(--or-amber-deep);
  color: var(--or-amber-ink);
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.3);
}

/* Threat chip (2026-06-18 playtest C11) — the synthesized "how dangerous is this
   opponent" read, colour-coded by tone so a glance reads the danger that the
   W-L record hid. Tough = red rail, Favored = green, Even = neutral steel. */
/* The threat pill carries whole clauses ("You outclass it — hits harder than it
   looks"), so unlike other chips it must WRAP inside a narrow fight card instead
   of running past the card's overflow:hidden and clipping mid-sentence
   (desktop UX pass, task #14072). */
.or-chip-threat { font-weight: 600; letter-spacing: 0.02em; white-space: normal; }
.or-chip-threat-danger {
  background: #2c1715;
  border-color: var(--or-danger);
  color: #f4b0a4;
  box-shadow: inset 1px 1px 0 rgba(224,106,90,0.3);
}
.or-chip-threat-good {
  background: #16291b;
  border-color: var(--br-green);
  color: #a6e6ba;
  box-shadow: inset 1px 1px 0 rgba(90,209,122,0.3);
}
.or-chip-threat-neutral {
  background: #20283c;
  border-color: var(--br-border-lt);
  color: var(--br-ink);
}

/* ===========================================================================
   TRAINER HUD — the character band under the nav
   ===========================================================================
   A compact character HUD: trainer identity prominent, the six stat chips
   DEMOTED (small, tucked) — they rarely change mid-game. */
.or-hud {
  background:
    radial-gradient(120% 200% at 100% 50%, rgba(244,200,96,0.10) 0%, rgba(244,200,96,0) 55%),
    linear-gradient(180deg, #1a2236 0%, #141a2b 100%);
  border-bottom: 2px solid #0c0f16;
  box-shadow: inset 0 1px 0 rgba(138,155,189,0.3), 0 2px 0 #0c0f16;
}

/* ===========================================================================
   BARS — segmented Warsong progress bars
   ===========================================================================
   .br-hpbar/.br-hpfill are lifted from the reveal verbatim. .or-bar is a
   general progress bar that takes its fill color from `currentColor` on the
   fill element, so a view sets it with a text-color class. */
.br-hpbar {
  position: relative; height: 14px; background: #11161f;
  border: 2px solid #0c0f16; box-shadow: inset 1px 1px 0 #2b3548; overflow: hidden;
}
.br-hpfill {
  position: absolute; inset: 0 auto 0 0; height: 100%;
  background-image: repeating-linear-gradient(90deg,
    currentColor 0, currentColor 8px, rgba(0,0,0,0.28) 8px, rgba(0,0,0,0.28) 10px);
  transition: width 0.3s linear;
}
.or-bar {
  position: relative; height: 14px; background: #11161f;
  border: 2px solid #0c0f16; box-shadow: inset 1px 1px 0 #2b3548; overflow: hidden;
}
.or-bar-fill {
  display: block; height: 100%;
  background-image: repeating-linear-gradient(90deg,
    currentColor 0, currentColor 6px, rgba(0,0,0,0.3) 6px, rgba(0,0,0,0.3) 8px);
  transition: width 0.3s linear;
}

/* DAMAGE RECAP bars (games/_damage_recap_side) — a thin per-attack bar sized by
   that ability's share of the bigger side's total, colored inline by its §19a
   damage type. The opponent side mirrors (fill anchored to the RIGHT) so the two
   columns read outward from the center gap like the two sprites in the reveal.
   Color comes from an inline background-color (the damage hue), not currentColor.
   The bar grows from 0 on first paint so the recap "draws in" after the reveal. */
.dr-track {
  position: relative; height: 9px; background: #11161f;
  border: 2px solid #0c0f16; box-shadow: inset 1px 1px 0 #2b3548; overflow: hidden;
}
.dr-fill {
  position: absolute; top: 0; bottom: 0; left: 0; height: 100%; width: 0;
  background-image: repeating-linear-gradient(90deg,
    rgba(255,255,255,0) 0, rgba(255,255,255,0) 6px, rgba(0,0,0,0.32) 6px, rgba(0,0,0,0.32) 8px);
  animation: dr-grow 520ms ease-out forwards;
}
/* Mirror the opponent side: anchor the fill to the right edge. */
.dr-track-r .dr-fill { left: auto; right: 0; }
@keyframes dr-grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.dr-fill { transform-origin: left center; }
.dr-track-r .dr-fill { transform-origin: right center; }
@media (prefers-reduced-motion: reduce) {
  .dr-fill { animation: none; transform: none; }
}

/* SURGE STAT PREVIEW (games/_surge_stat_preview) — a mini current→projected bar
   inside a surge option, on the SAME fixed 0–10 frame as the attributes panel's
   potential bars, so the surge reads as "push THIS bar further". The NOW segment
   is the same green as the attributes bar; the GROW slice is amber (the surge
   color) and pulses so the gain is felt before it's taken. */
.sp-frame {
  display: block; position: relative; height: 8px; background: transparent;
  border: 2px solid var(--br-border, #5b6b86); border-radius: 1px; overflow: hidden;
}
.sp-now {
  position: absolute; left: 0; top: 0; bottom: 0; height: 100%;
  background: var(--br-green, #5ad17a);
}
.sp-grow {
  position: absolute; top: 0; bottom: 0; height: 100%;
  background: var(--or-amber, #f4c860);
  background-image: repeating-linear-gradient(90deg,
    rgba(0,0,0,0) 0, rgba(0,0,0,0) 4px, rgba(0,0,0,0.28) 4px, rgba(0,0,0,0.28) 6px);
  animation: sp-pulse 1.4s ease-in-out infinite;
}
@keyframes sp-pulse { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .sp-grow { animation: none; opacity: 1; }
}

/* POTENTIAL BAR (shared/_potential_bar) — the current-vs-cap stat bar, folded
   into the Traits panel on the champion detail page (CHARACTER_GENOME.md
   §19f/§12b). Absolute 0–10 frame + a fixed compressed overflow strip past it,
   so every bar is the same width and directly comparable. Two-tone read:
   BRIGHT green (have, solid) and a muted DARK GREEN (can-grow-to, headroom —
   §12b: Zac swapped the old yellow/black hatch for dark green, less jarring).
   Still distinguished by TEXTURE (now = solid segments, headroom = diagonal
   hatch) so the two greens read apart for colorblind players; blue (can't-reach)
   is a clearly different hue family (steel). */
/* PHRASING-CONTENT UNIFY (2026-06-21): the bar is now all-<span> so it's valid
   inside a button_to (the setup species/champion cards). The wrap/frame/overflow
   spans need explicit display so their height + flex layout behave like the old
   divs did. */
.pb-wrap { display: flex; align-items: stretch; gap: 3px; }
.pb-row { display: block; }
/* Dotted-underline affordance on a stat name that carries a consequence tooltip,
   so a player KNOWS to hover (Zac 2026-06-21: "mouseovers on stats, explaining
   what they influence" — discoverable, not hidden). */
.pb-stat-name {
  text-decoration: underline dotted rgba(138, 155, 189, 0.55);
  text-underline-offset: 3px;
  cursor: help;
}
/* The 0–10 mortal frame: fixed width, the comparable scale. No fill of its own
   (Zac 2026-06-21: "remove the black fill between the potential and 10") — the bar
   reads as bright green (now) + empty outline box (room to grow), and PAST the box
   edge there's nothing. Positions still sit on the fixed 0–10 coordinate space, so
   bars stay length-comparable; only the dark well is gone. */
.pb-frame {
  display: block;
  position: relative; flex: 1 1 auto; height: 14px; background: transparent;
  overflow: visible; /* the breakpoint ◆ tick overhangs the bar top/bottom */
}
.pb-seg { display: block; position: absolute; top: 0; bottom: 0; height: 100%; transition: width 0.3s linear, left 0.3s linear; }
/* Next-perk breakpoint marker: a thin amber tick + ◆ head at the next 4/7/10
   threshold this stat hasn't reached, naming (via title) the ability it unlocks. */
/* display:contents so the wrap doesn't create a box — the tick + popover keep
   positioning relative to .pb-frame (the offset parent). The wrap exists only to
   scope the perk-marker Stimulus controller (tap-reveal on touch, #4). */
.pb-breakpoint-wrap { display: contents; }
/* PERK MARKER (redesign, Zac 2026-06-27: "the checkmarks look small and odd; the
   yellow lines with triangles look weird and aren't on an integer boundary"). The
   full-height line through the bar is GONE. The marker is now a clean "pin" sitting
   just ABOVE the bar at the threshold cell boundary (integer-aligned by the grid):
   a clear glyph + a short tick onto the bar's top edge. It clears the value text,
   which is left-aligned while thresholds (4/7/10 → 40/70/100%) sit mid-to-right. */
.pb-breakpoint {
  position: absolute; top: -13px; bottom: auto; width: auto; height: auto;
  margin-left: 0; transform: translateX(-50%);
  padding: 0; border: 0; appearance: none; background: transparent;
  z-index: 3; cursor: help; pointer-events: auto;
  color: var(--or-amber, #f4c860); line-height: 1;
}
.pb-breakpoint::before {
  content: "◆"; display: block; font-size: 11px; line-height: 1;
  text-shadow: 0 0 4px rgba(244, 200, 96, 0.85);
}
/* the short connector tick from the glyph down onto the bar's top edge — marks the
   exact integer boundary without a line cutting through the fill */
.pb-breakpoint::after {
  content: ""; position: absolute; left: 50%; top: 12px; transform: translateX(-50%);
  width: 2px; height: 5px; background: currentColor; opacity: 0.8;
}
/* ALREADY-UNLOCKED perk marker — a green ✓ at a REACHED threshold, distinct from
   the amber ◆ that marks the next perk still ahead. */
.pb-breakpoint-unlocked { color: var(--br-green, #5ad17a); }
.pb-breakpoint-unlocked::before {
  content: "✓"; font-size: 12px;
  text-shadow: 0 0 4px rgba(90, 209, 122, 0.85);
}
/* Tap-reveal popover for the breakpoint marker (touch — no hover, #4). Anchored
   under the bar, naming the perk + its effect. Hidden until the marker is tapped
   (perk-marker controller toggles .hidden). */
.pb-breakpoint-pop {
  display: block; position: absolute; top: 18px; left: 0; right: 0; z-index: 30;
  background: #1b2330; border: 1px solid var(--or-amber, #f4c860);
  box-shadow: 0 2px 8px rgba(0,0,0,0.5);
  border-radius: 2px; padding: 7px 9px; min-width: 180px;
}
.pb-breakpoint-pop.hidden { display: none; }
/* Legible tooltip text (Zac 2026-06-26: perk mouseovers "tiny and hard to read").
   Bumped to the >=13px floor; the head is the perk name, the desc its effect. */
.pb-breakpoint-pop-head {
  display: block; font-size: 14px; font-weight: 600; color: var(--or-amber, #f4c860);
  line-height: 1.3;
}
.pb-breakpoint-pop-desc {
  display: block; font-size: 13px; color: var(--br-ink, #c9d4e8); margin-top: 3px;
  line-height: 1.35;
}
/* GREEN — have. SOLID now (Zac 2026-06-27: "the bars fill up in chunks that aren't
   integers"). The old 8px pixel-ticks read as fractional cells unrelated to the
   0–10 scale; the .pb-grid overlay below now draws the integer cell dividers, so a
   bar reads as N filled cells of 10. */
.pb-green { left: 0; background: var(--br-green); }
/* GREW-THIS-LEVEL segment — the last gained points of the fill, in the growth
   color (BLUE picked / YELLOW natural, set inline). Sits ON TOP of the green's
   right end so the gain reads as filled cells (Zac 2026-06-27). */
.pb-grew-seg { z-index: 1; }
/* INTEGER GRID — 10 cells. A faint dark divider at every 10% overlays the whole
   frame (fill + empty), so the bar reads as 10 discrete cells and the fill ends on
   an integer boundary. Drawn ABOVE the fill segments, BELOW the perk markers, and
   pointer-transparent so it never eats a marker tap. The right edge (100%) is the
   frame border, so only the 9 internal dividers are drawn. */
.pb-grid {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background-image: repeating-linear-gradient(90deg,
    transparent 0, transparent calc(10% - 1px),
    rgba(12, 15, 22, 0.55) calc(10% - 1px), rgba(12, 15, 22, 0.55) 10%);
}
/* HEADROOM — room to grow, [current -> potential]. An EMPTY BOX (2026-06-21,
   Zac: "show green up to current, empty box up to potential"): transparent fill
   with a faint outline, so the bar reads "you're here (green), your ceiling is
   the edge of this box" — no second color to decode. Replaces the old dark-green
   hatch. The right edge IS the potential; nothing is drawn past it (the blue
   "can't-reach" segment is gone — it was info new players didn't need). */
.pb-headroom {
  background: transparent;
  border: 1px solid rgba(138,155,189,0.40);
  box-sizing: border-box;
}
/* The compressed overflow strip past the frame — gold "broke through the
   mortal ceiling". Fixed width, loud glow (reserved for the rare supra-mortal
   case per the beats-doc contrast discipline). Dormant at range=10. */
.pb-overflow {
  display: block;
  position: relative; flex: 0 0 28px; height: 14px;
  background: #1a1305; border: 2px solid var(--or-amber-deep);
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.4), 0 0 8px rgba(244,200,96,0.55);
  overflow: hidden;
}
.pb-over-green {
  left: 0;
  background-image: repeating-linear-gradient(90deg,
    #f7d472 0, #f7d472 4px, rgba(0,0,0,0.3) 4px, rgba(0,0,0,0.3) 6px);
  box-shadow: 0 0 6px rgba(244,200,96,0.7);
}
.pb-over-yellow {
  background-image: repeating-linear-gradient(45deg,
    var(--or-amber-deep) 0, var(--or-amber-deep) 3px, rgba(0,0,0,0.3) 3px, rgba(0,0,0,0.3) 6px);
  opacity: 0.85;
}
/* Supra-mortal mark (items 32 & 33 retone, 2026-06-21): a small quiet gold ✦
   beside a >10 value — NOT the old loud uppercase "beyond mortal" tag. The gold
   number + ✦ + the calm tooltip carry the "grew past the mortal peak" read. */
.pb-overflow-mark {
  font-size: 11px;
  text-shadow: 0 0 5px rgba(244,200,96,0.6);
}
/* GREW-THIS-LEVEL highlight (Zac 2026-06-27: "show the stat increases in a
   different COLOR, no green outline"). A stat that gained points this level reads
   as the growth moment by COLORING ITS VALUE — not a green frame around the row.
   Two kinds, so the player sees WHICH points they chose vs got for free:
     BLUE   = the trait YOU pushed (the surge pick).
     YELLOW = natural per-level growth (free, every level).
   The "+N" badge and the current value both take the kind's color, the fill
   pulses once on land. No outline, no green — the bar keeps its exact width +
   grid alignment (the 0–10 frames stay length-comparable across rows). */
.pb-grew-picked .pb-grew-badge,
.pb-grew-picked [data-testid="potential-bar-current"] {
  color: var(--or-blue) !important;
}
.pb-grew-natural .pb-grew-badge,
.pb-grew-natural [data-testid="potential-bar-current"] {
  color: var(--or-yellow) !important;
}
.pb-grew-picked  .pb-grew-badge { text-shadow: 0 0 6px rgba(90, 169, 244, 0.55); }
.pb-grew-natural .pb-grew-badge { text-shadow: 0 0 6px rgba(244, 211, 90, 0.55); }
.pb-grew-picked  .pb-green { animation: pb-grew-pulse 1100ms ease-out 1; }
.pb-grew-natural .pb-green { animation: pb-grew-pulse 1100ms ease-out 1; }
@keyframes pb-grew-pulse {
  0%   { filter: brightness(1.9) saturate(1.3); }
  100% { filter: brightness(1) saturate(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pb-seg { transition: none; }
  .pb-grew-picked .pb-green,
  .pb-grew-natural .pb-green { animation: none; }
}

/* A bevelled dark table for ranked / world data — replaces SaaS white tables. */
.or-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; color: var(--br-ink); }
.or-table thead th {
  font-family: var(--br-pixel);
  font-size: 8px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--br-ink-dim);
  -webkit-font-smoothing: none;
  text-align: left;
  padding: 0.5rem 0.75rem;
  background: #161d2e;
  border-bottom: 2px solid #0c0f16;
}
.or-table tbody td { padding: 0.55rem 0.75rem; border-bottom: 1px solid rgba(91,107,134,0.25); }
.or-table tbody tr:nth-child(odd) td { background: rgba(255,255,255,0.015); }
.or-table tbody tr:hover td { background: rgba(244,200,96,0.06); }

/* ── Cooldown charge gauge (battle reveal, #13243) ──────────────────────────
   The per-ability "power bar" battle_reveal_controller.js#renderCooldowns draws
   under each ability name: FULL = ready to fire, drains to empty the instant it
   fires, refills across the fight. The JS sets the fill's width + color inline;
   these rules give the track and fill an actual BOX. They had no CSS at all, so
   the bar was an inline span with no height — invisible — and each tile read as
   an empty dark bar with a 6px name (the "empty black bar" bug). */
.br-charge-track {
  display: block;
  width: 100%;
  height: 4px;
  margin-top: 2px;
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid var(--br-border);
  border-radius: 1px;
  overflow: hidden;
}
.br-charge-fill {
  display: block;
  height: 100%;
  transition: width 140ms linear;
}

/* Celebratory pop for the tournament-win modal (WU6 item 20): the panel bounces
   in so the win lands with a beat instead of snapping on. Reduced-motion users
   get the panel statically (no scale animation). */
@keyframes juice-bounce-in {
  0%   { transform: scale(0.7); opacity: 0; }
  55%  { transform: scale(1.06); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
.juice-bounce-in {
  animation: juice-bounce-in 0.42s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@media (prefers-reduced-motion: reduce) {
  .juice-bounce-in { animation: none; }
}

/* ===========================================================================
   LOCATION MENU + BACKDROP (NPE overhaul D)
   ===========================================================================
   A consistent menu rendered over a location backdrop. The backdrop is a
   layered gradient treatment (no art asset required yet — real academy art
   would slot straight into the background-image). The Academy variant reads as
   a stone training yard at dusk: warm amber from the east over a cool slate. */
.or-location-backdrop {
  border: 1px solid var(--br-border);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.04),
              inset -1px -1px 0 rgba(0, 0, 0, 0.5),
              0 6px 16px rgba(0, 0, 0, 0.35);
  /* The menu sits in a translucent scrim so the option text stays legible over
     the backdrop. */
  position: relative;
}
.or-location-academy {
  background-color: #1a2236;
  background-image:
    /* a low amber wash from the lower-right — the east glow, a torchlit yard */
    radial-gradient(120% 80% at 100% 100%, rgba(244, 200, 96, 0.22) 0%, rgba(244, 200, 96, 0.05) 38%, rgba(244, 200, 96, 0) 64%),
    /* a cooler highlight up top so the panel has depth */
    radial-gradient(90% 70% at 12% 0%, rgba(138, 155, 189, 0.14) 0%, rgba(138, 155, 189, 0) 55%),
    /* faint stone-block grid, like a yard wall */
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.16) 0px, rgba(0, 0, 0, 0.16) 1px, transparent 1px, transparent 38px),
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.12) 0px, rgba(0, 0, 0, 0.12) 1px, transparent 1px, transparent 64px),
    linear-gradient(165deg, #20294a 0%, #161d33 70%, #10162a 100%);
}
/* ---------------------------------------------------------------------------
   PER-TOWN BACKDROPS (NPE world-feel pass, Zac 2026-06-27: "got to a new town
   and nothing seemed different"). Each locale paints a distinct procedural scene
   — palette + motif scrim — so Hollowtide ≠ Aurelis ≠ Emberlow ≠ the Academy at
   a glance. No art asset; same layered-gradient approach as the Academy yard.
   Real town art would slot straight into background-image later. */

/* EMBERLOW — a soot-stained forge town: ember-orange glow over cold iron, with
   a faint horizontal "smelter haze". */
.or-location-emberlow {
  background-color: #1c1410;
  background-image:
    radial-gradient(120% 90% at 80% 100%, rgba(255, 138, 76, 0.26) 0%, rgba(255, 138, 76, 0.06) 40%, rgba(255, 138, 76, 0) 66%),
    radial-gradient(70% 50% at 20% 10%, rgba(255, 196, 140, 0.10) 0%, rgba(255, 196, 140, 0) 55%),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.18) 0px, rgba(0, 0, 0, 0.18) 1px, transparent 1px, transparent 30px),
    linear-gradient(165deg, #2a1c12 0%, #1d130c 70%, #140c07 100%);
}
/* GREYMOOR — a marsh frontier: cold sage-green fog, low and flat. */
.or-location-greymoor {
  background-color: #161e1a;
  background-image:
    radial-gradient(120% 80% at 50% 100%, rgba(143, 174, 122, 0.20) 0%, rgba(143, 174, 122, 0.04) 44%, rgba(143, 174, 122, 0) 70%),
    radial-gradient(90% 60% at 30% 0%, rgba(120, 140, 130, 0.12) 0%, rgba(120, 140, 130, 0) 58%),
    repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.03) 0px, rgba(255, 255, 255, 0.03) 1px, transparent 1px, transparent 22px),
    linear-gradient(170deg, #1e2722 0%, #161e1a 72%, #0f1612 100%);
}
/* AURELIS — a wealthy trade town: gilded warm light, polished. */
.or-location-aurelis {
  background-color: #221c10;
  background-image:
    radial-gradient(110% 80% at 50% 0%, rgba(255, 215, 94, 0.22) 0%, rgba(255, 215, 94, 0.05) 42%, rgba(255, 215, 94, 0) 66%),
    radial-gradient(80% 60% at 85% 100%, rgba(255, 240, 200, 0.10) 0%, rgba(255, 240, 200, 0) 55%),
    repeating-linear-gradient(90deg, rgba(255, 215, 94, 0.05) 0px, rgba(255, 215, 94, 0.05) 1px, transparent 1px, transparent 54px),
    linear-gradient(165deg, #2c2414 0%, #211a0f 70%, #18130a 100%);
}
/* HOLLOWTIDE — a coastal fishing town: sea-blue, with a faint tide-line wash. */
.or-location-hollowtide {
  background-color: #0c1c2a;
  background-image:
    radial-gradient(120% 90% at 50% 100%, rgba(95, 208, 255, 0.22) 0%, rgba(95, 208, 255, 0.05) 42%, rgba(95, 208, 255, 0) 68%),
    radial-gradient(80% 60% at 18% 0%, rgba(200, 235, 255, 0.10) 0%, rgba(200, 235, 255, 0) 55%),
    repeating-linear-gradient(0deg, rgba(95, 208, 255, 0.05) 0px, rgba(95, 208, 255, 0.05) 1px, transparent 1px, transparent 26px),
    linear-gradient(170deg, #102634 0%, #0c1c2a 72%, #07131d 100%);
}
/* CITY (Cor Valis) — the high seat: regal gold over deep indigo, a banner glow. */
.or-location-city {
  background-color: #161430;
  background-image:
    radial-gradient(110% 80% at 50% 0%, rgba(255, 224, 138, 0.22) 0%, rgba(255, 224, 138, 0.05) 40%, rgba(255, 224, 138, 0) 64%),
    radial-gradient(90% 70% at 50% 100%, rgba(138, 120, 220, 0.14) 0%, rgba(138, 120, 220, 0) 58%),
    repeating-linear-gradient(90deg, rgba(255, 224, 138, 0.04) 0px, rgba(255, 224, 138, 0.04) 1px, transparent 1px, transparent 60px),
    linear-gradient(165deg, #211c44 0%, #161430 70%, #0e0c22 100%);
}
/* CINDERREACH — the ember frontier (Batch 3 B): molten orange lava-glow over
   charred basalt, hotter and more volcanic than Emberlow's forge. Procedural
   FALLBACK; the real painting (oriens/backdrops/cinderreach.jpg) is layered over
   this via an inline background-image (locale_backdrop_style) where it's used. */
.or-location-cinderreach {
  background-color: #1a0f0a;
  background-image:
    radial-gradient(120% 90% at 82% 100%, rgba(255, 110, 40, 0.30) 0%, rgba(255, 110, 40, 0.07) 40%, rgba(255, 110, 40, 0) 66%),
    radial-gradient(70% 60% at 18% 20%, rgba(255, 170, 90, 0.12) 0%, rgba(255, 170, 90, 0) 55%),
    repeating-linear-gradient(24deg, rgba(255, 90, 30, 0.05) 0px, rgba(255, 90, 30, 0.05) 1px, transparent 1px, transparent 28px),
    linear-gradient(165deg, #2b1410 0%, #1c0d08 68%, #120705 100%);
}
/* MIREFEN — the poison fen (Batch 3 C): a sickly green-amber miasma hanging over
   black bog water, deeper and more toxic than Greymoor's cold sage fog. Procedural
   FALLBACK; the real painting (oriens/backdrops/mirefen.jpg) is layered over this
   via an inline background-image (locale_backdrop_style) where it's used. */
.or-location-mirefen {
  background-color: #0e150c;
  background-image:
    radial-gradient(120% 90% at 50% 8%, rgba(143, 209, 79, 0.24) 0%, rgba(143, 209, 79, 0.06) 40%, rgba(143, 209, 79, 0) 66%),
    radial-gradient(80% 60% at 78% 100%, rgba(198, 176, 74, 0.12) 0%, rgba(198, 176, 74, 0) 56%),
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.24) 0px, rgba(0, 0, 0, 0.24) 1px, transparent 1px, transparent 24px),
    linear-gradient(170deg, #17210f 0%, #101809 70%, #090f06 100%);
}
/* RIVEN WASTES — the death frontier: bruised red over near-black, ominous. */
.or-location-wastes {
  background-color: #18100f;
  background-image:
    radial-gradient(120% 90% at 50% 100%, rgba(201, 138, 138, 0.18) 0%, rgba(201, 138, 138, 0.04) 42%, rgba(201, 138, 138, 0) 68%),
    radial-gradient(80% 60% at 70% 0%, rgba(120, 80, 80, 0.12) 0%, rgba(120, 80, 80, 0) 55%),
    repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.22) 0px, rgba(0, 0, 0, 0.22) 1px, transparent 1px, transparent 18px),
    linear-gradient(165deg, #221413 0%, #170f0e 72%, #0d0807 100%);
}
/* NEUTRAL — the fallback for an unmapped locale: the steel academy-ish slate. */
.or-location-neutral {
  background-color: #1a2236;
  background-image:
    radial-gradient(100% 70% at 50% 0%, rgba(138, 155, 189, 0.12) 0%, rgba(138, 155, 189, 0) 55%),
    linear-gradient(165deg, #20294a 0%, #161d33 70%, #10162a 100%);
}

/* The menu's inner scrim so the options read over any backdrop. */
.or-location-backdrop > div {
  background: linear-gradient(180deg, rgba(12, 16, 32, 0.34) 0%, rgba(12, 16, 32, 0.58) 100%);
}

/* ---------------------------------------------------------------------------
   TOWN ARRIVAL BANNER (NPE world-feel pass). The "you got somewhere new" beat —
   the town motif + name + character over its own backdrop. Reuses
   .or-location-backdrop's bevel + scrim; the motif sits in a soft disc. */
.or-town-banner > div {
  background: linear-gradient(180deg, rgba(10, 13, 26, 0.30) 0%, rgba(10, 13, 26, 0.50) 100%);
}
.or-town-motif {
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5));
}
.or-town-banner { animation: or-town-in 500ms ease-out; }
@keyframes or-town-in { 0% { opacity: 0; transform: translateY(6px); } 100% { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) { .or-town-banner { animation: none; } }

/* A single menu option row — a bevelled panel that lifts on hover when active. */
.or-menu-option {
  border-radius: 8px;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--br-border);
  background: rgba(27, 35, 48, 0.82);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.05);
}
.or-menu-option-active {
  cursor: pointer;
  transition: transform 0.12s ease-out, box-shadow 0.12s ease-out, border-color 0.12s ease-out;
}
a.or-menu-option-active:hover,
.or-menu-option-active:hover {
  transform: translateY(-1px);
  border-color: var(--or-amber);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.08), 0 4px 10px rgba(0, 0, 0, 0.35);
}
.or-menu-option-locked {
  opacity: 0.72;
  background: rgba(20, 26, 38, 0.7);
  border-style: dashed;
}
/* The ONE live action (Train) gets an amber accent so the eye lands on it
   first — the restraint pass makes everything else recede (Zac 2026-06-26). */
.or-menu-option-primary {
  border-color: rgba(244, 200, 96, 0.55);
  background: rgba(40, 33, 18, 0.55);
  box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.06),
              inset 0 0 0 1px rgba(244, 200, 96, 0.12),
              0 2px 10px rgba(0, 0, 0, 0.3);
}
/* The not-now strip: compact, dim, clearly "later" — never an equal-weight
   block competing with Train. */
.or-menu-option-later {
  opacity: 0.62;
  padding: 0.1rem 0;
}
.or-menu-icon {
  font-size: 1.4rem;
  line-height: 1.4;
  flex-shrink: 0;
  width: 1.9rem;
  text-align: center;
}
.or-menu-lock { font-size: 0.85rem; opacity: 0.85; }
@media (prefers-reduced-motion: reduce) {
  .or-menu-option-active { transition: none; }
  a.or-menu-option-active:hover,
  .or-menu-option-active:hover { transform: none; }
}

/* ===========================================================================
   PROGRESSION TIMELINE (NPE overhaul D)
   ===========================================================================
   A vertical timeline: a connecting line + a node per milestone. */
.or-timeline-line {
  position: absolute;
  left: 0.375rem;       /* centres under the 0.75rem-wide dot */
  top: 1.15rem;
  bottom: -0.35rem;
  width: 2px;
  background: linear-gradient(180deg, var(--br-border) 0%, rgba(91, 107, 134, 0.4) 100%);
}
.or-timeline-dot {
  position: absolute;
  left: 0;
  top: 0.2rem;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  border: 2px solid var(--br-border);
  background: var(--br-panel);
}
.or-timeline-dot-now {
  border-color: var(--or-amber);
  background: var(--or-amber);
  box-shadow: 0 0 0 3px rgba(244, 200, 96, 0.22);
}
.or-timeline-dot-done {
  border-color: var(--br-green);
  background: var(--br-green);
}
.or-timeline-dot-ahead {
  border-color: var(--br-border-lt);
  background: var(--br-panel-2);
}

/* ===========================================================================
   CREATURE IDENTITY CARD — the rarity frame escalation (Creature Identity
   Card, Zac 2026-06-27)
   ===========================================================================
   The frame WEIGHT does the "oh wow" before any reading: a vermin reads dull
   and plain, a divine roars gold. Seven bands climb in border weight, glow, and
   warmth. The card MUST look great with no art (prod has no image-gen key), so
   the frame + aura carry it. Every band is a self-contained .or-card-<band>
   class the partial sets from CreatureIdentity#band. */
.or-card {
  position: relative;
  display: block;
  border-radius: 6px;
  border: 2px solid var(--br-border);
  background: linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  box-shadow: inset 1px 1px 0 rgba(138,155,189,0.3), 0 2px 0 #0c0f16;
  color: var(--br-ink);
  transition: transform 0.08s ease-out, filter 0.12s ease-out;
}
/* A clickable/selectable card lifts on hover so the whole tile reads as a tap
   target (the surfaces wire it as a link/label). */
a.or-card:hover, label.or-card:hover, button.or-card:hover { filter: brightness(1.08); }
a.or-card:active, label.or-card:active, button.or-card:active { transform: translateY(1px); }
.or-card:focus-within { outline: 2px solid var(--or-amber); outline-offset: 2px; }
/* SELECTED creature card — the WHOLE tile lights (Zac 2026-06-27): a bold amber
   ring + border so "this one's picked" is unmistakable, not a ring around an inner
   div. :has(:checked) styles the label.or-card from the hidden radio/checkbox it
   wraps. A merge candidate (or-merge-candidate) carries the ring up to its footer
   too, so the whole tile reads as one selected unit. */
.or-card:has(input:checked) {
  border-color: var(--or-amber);
  box-shadow: 0 0 0 2px var(--or-amber), inset 1px 1px 0 rgba(244,200,96,0.3), 0 2px 0 #0c0f16;
}
/* A merge candidate is a card+footer tile — ring the WHOLE wrapper, recolor both
   borders, and drop the inner card's own ring so no amber line cuts the join.
   UNMISTAKABLE SELECTION (Zac playtest 2026-07-15): the old 2px amber ring read
   nearly identical to the plain border. Selected now gets a THICK 3px ring, a warm
   outer glow, an amber-tinted fill on both halves, and a ✓ badge in the corner —
   so "this one's picked" is obvious at a glance, mobile included. */
.or-merge-candidate { position: relative; }
/* Scope the "selected" treatment to the SELECTION checkbox (inside the .or-card
   label) — NOT any checked input. The "which one to breed from" specimen radios
   also live in the wrapper with a checked primary, so a bare :has(input:checked)
   lit every specimen-bearing card as selected (Zac playtest fix, 2026-07-15). */
.or-merge-candidate:has(> .or-card input:checked) {
  border-radius: 8px;
  box-shadow: 0 0 0 3px var(--or-amber), 0 0 20px rgba(244,200,96,0.5);
}
.or-merge-candidate:has(> .or-card input:checked) > .or-card,
.or-merge-candidate:has(> .or-card input:checked) > .or-merge-foot {
  border-color: var(--or-amber);
  background: linear-gradient(180deg, rgba(244,200,96,0.18) 0%, rgba(244,200,96,0.06) 100%);
}
.or-merge-candidate:has(> .or-card input:checked) > .or-card {
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.35);
}
/* The corner ✓ badge — a hard "chosen" mark no one can miss. */
.or-merge-candidate:has(> .or-card input:checked)::after {
  content: "✓";
  position: absolute;
  top: -9px;
  right: -9px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 700;
  color: #1a1205;
  background: var(--or-amber);
  border-radius: 999px;
  box-shadow: 0 1px 5px rgba(0,0,0,0.6);
  z-index: 4;
  pointer-events: none;
}

/* MERGE CANDIDATE TILE (Zac 2026-06-27: "see full stats is a disembodied panel").
   The forge candidate is the identity card + a connected footer holding the
   provenance + the tap-to-expand stat sheet. The footer butts flush under the
   card — the card loses its bottom radius, the footer carries the matching frame
   and rounds the bottom — so the whole thing reads as ONE creature tile, not a
   panel floating in the page background below the card. */
.or-merge-candidate > .or-card {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
.or-merge-foot {
  background: var(--br-panel);
  border: 2px solid var(--br-border);
  border-top: 0;
  border-bottom-left-radius: 6px;
  border-bottom-right-radius: 6px;
  padding: 11px 14px 14px;
}
/* The expanded stat sheet needs room to breathe under the "See full stats"
   summary — it read crammed against the toggle before (Zac playtest 2026-07-15). */
.or-merge-foot[open] > summary { margin-bottom: 10px; }

/* vermin — the floor. Thin, cold, no glow. Deliberately unimpressive. */
.or-card-vermin {
  border-color: #3a4254;
  background: linear-gradient(180deg, #1a2030 0%, #141925 100%);
  box-shadow: inset 1px 1px 0 rgba(120,135,165,0.18);
  filter: saturate(0.7);
}
/* minor — plain steel, a hair more presence. */
.or-card-minor {
  border-color: #4d5a72;
}
/* standard — the baseline a player reads as "a real creature." */
.or-card-standard {
  border-color: var(--br-border);
  box-shadow: inset 1px 1px 0 rgba(138,155,189,0.35), 0 2px 0 #0c0f16;
}
/* elite — warm border, an amber rail, a first vignette so the field has depth.
   The first "this one's notable." */
.or-card-elite {
  border-color: #8a7642;
  border-width: 2px;
  background:
    radial-gradient(110% 80% at 50% 0%, rgba(244,200,96,0.07) 0%, rgba(244,200,96,0) 55%),
    radial-gradient(120% 120% at 50% 120%, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0) 60%),
    linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.22), 0 0 16px rgba(244,200,96,0.12), 0 2px 0 #0c0f16;
}
/* apex — a bold gold frame, a clear aura, a warm-floored field. Stands out hard
   in a grid; the first tier that reads "powerful." */
.or-card-apex {
  border-color: var(--or-amber-deep);
  border-width: 3px;
  background:
    radial-gradient(120% 90% at 50% -5%, rgba(244,200,96,0.15) 0%, rgba(244,200,96,0) 58%),
    radial-gradient(130% 130% at 50% 125%, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0) 58%),
    linear-gradient(180deg, #262316 0%, #1b190f 100%);
  box-shadow: inset 1px 1px 0 rgba(244,200,96,0.34), 0 0 24px rgba(244,200,96,0.22), 0 2px 0 #0c0f16;
}
/* mythic — heavy gold, a strong corona, a deep warm field with a dark floor so
   the gold reads against shadow. A creature you stop scrolling for. */
.or-card-mythic {
  border-color: var(--or-amber);
  border-width: 3px;
  background:
    radial-gradient(130% 95% at 50% -8%, rgba(255,221,130,0.20) 0%, rgba(244,200,96,0.03) 50%, rgba(244,200,96,0) 72%),
    radial-gradient(140% 130% at 50% 130%, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0) 55%),
    linear-gradient(180deg, #2e2c1c 0%, #1d1b12 100%);
  box-shadow:
    inset 1px 1px 0 rgba(255,233,166,0.45),
    0 0 32px rgba(244,200,96,0.34),
    0 0 0 1px rgba(255,221,130,0.25),
    0 2px 0 #0c0f16;
}
/* divine — it ROARS, and it's a little SCARY. The brightest gold frame in the
   game, a double aura that pulses like a living thing, a deep blackened-gold
   field, and a hot inner rim. The frame alone should make a player want it (and
   fear it) before they read a word. Reduced-motion gets it static but still loud. */
.or-card-divine {
  border-color: #fff0bf;
  border-width: 4px;
  background:
    radial-gradient(140% 100% at 50% -12%, rgba(255,234,160,0.30) 0%, rgba(255,200,96,0.06) 48%, rgba(244,200,96,0) 74%),
    radial-gradient(150% 130% at 50% 135%, rgba(20,8,0,0.72) 0%, rgba(20,8,0,0) 52%),
    linear-gradient(180deg, #3b3318 0%, #241c0c 60%, #160f06 100%);
  box-shadow:
    inset 1px 1px 0 rgba(255,247,210,0.6),
    inset 0 0 22px rgba(255,221,130,0.28),
    0 0 44px rgba(255,221,130,0.5),
    0 0 0 1px rgba(255,221,130,0.4),
    0 2px 0 #0c0f16;
  animation: or-divine-pulse 3.0s ease-in-out infinite;
}
@keyframes or-divine-pulse {
  0%, 100% {
    box-shadow: inset 1px 1px 0 rgba(255,247,210,0.6), inset 0 0 20px rgba(255,221,130,0.22),
                0 0 36px rgba(255,221,130,0.4), 0 0 0 1px rgba(255,221,130,0.35), 0 2px 0 #0c0f16;
  }
  50% {
    box-shadow: inset 1px 1px 0 rgba(255,247,210,0.78), inset 0 0 30px rgba(255,221,130,0.4),
                0 0 58px rgba(255,221,130,0.62), 0 0 0 1px rgba(255,221,130,0.55), 0 2px 0 #0c0f16;
  }
}
@media (prefers-reduced-motion: reduce) {
  .or-card-divine { animation: none; }
}

/* The band-tier label chip — a small caps word ("DIVINE") that names the tier,
   colour escalating with the band so it reinforces the frame. */
.or-band-chip {
  display: inline-flex; align-items: center;
  padding: 0.1rem 0.45rem;
  font-family: var(--br-pixel);
  font-size: 8px; letter-spacing: 0.08em; text-transform: uppercase;
  border: 1px solid var(--br-border);
  background: #20283c; color: var(--br-ink-dim);
  border-radius: 3px;
}
.or-band-chip-elite, .or-band-chip-apex { border-color: var(--or-amber-deep); color: var(--or-amber-ink); background: #2a2415; }
.or-band-chip-mythic, .or-band-chip-divine {
  border-color: var(--or-amber); color: #2a1d05;
  background: linear-gradient(180deg, #f7d472 0%, var(--or-amber) 100%);
  text-shadow: 0 1px 0 rgba(255,255,255,0.3);
}

/* The epithet — the soul line. The card's most-read words, so it leads with
   real character: a warm gold serif display, italic for a hand-authored feel,
   a faint glow so it reads as the "title" not a caption. Bigger than the band
   chip, smaller than the name — the second thing the eye lands on. */
.or-epithet {
  font-family: Georgia, 'Times New Roman', serif;
  font-style: italic;
  font-size: 15px; line-height: 1.3;
  color: var(--or-amber-ink);
  letter-spacing: 0.01em;
  text-shadow: 0 1px 0 #0b0e18, 0 0 10px rgba(244, 200, 96, 0.18);
  /* Never hard-clip mid-word inside a narrow card (task #14072). */
  overflow-wrap: break-word;
}
@media (min-width: 640px) { .or-epithet { font-size: 16px; } }
/* Reserve one epithet line on a headline-only pick face (species / merge picker),
   so an epithet-less species reads the same card height as one with an epithet
   (Zac playtest 2026-07-03, task #13864). Empty → invisible, but holds the line. */
.or-epithet-reserve { min-height: 1.3em; }

/* The scouter Power read — a HUD readout, not a label. The "POWER" tag is a tiny
   pixel eyebrow; the number is a chunky pixel figure with a warm glow so it
   reads like a scouter scanning a target. */
.or-scouter {
  display: inline-flex; align-items: baseline; gap: 0.34rem;
  font-family: var(--br-pixel); font-size: 13px;
  color: var(--or-amber);
  text-shadow: 0 0 8px rgba(244, 200, 96, 0.45), 1px 1px 0 #0b0e18;
}
/* The "POWER" word demoted to an eyebrow tag so the NUMBER is the figure. */
.or-scouter-tag {
  font-family: var(--br-pixel); font-size: 8px; letter-spacing: 0.08em;
  color: var(--or-amber-deep); text-transform: uppercase;
  text-shadow: none; align-self: center;
}
.or-scouter-you { color: var(--br-ink-dim); font-size: 9px; text-shadow: none; }

/* Accent rail — a coloured edge keyed to the creature's dominant axis, so a
   glance reads "this is a BODY creature" / "a SPIRIT creature" by colour.
   Pushed harder than the old thin line: a bold 5px rail PLUS a soft inner glow
   in the same hue via a ::after overlay — so even a plain Standard has a clear
   identity colour. The overlay (not box-shadow) keeps the band's frame glow
   intact (the band owns box-shadow; the accent owns the rail + this wash). */
.or-card[class*="or-accent-"] { overflow: hidden; }
.or-card[class*="or-accent-"]::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  border-radius: inherit;
}
.or-accent-body   { border-left: 5px solid #e0796a; }
.or-accent-edge   { border-left: 5px solid #5ad17a; }
.or-accent-spirit { border-left: 5px solid #7aa2ff; }
.or-accent-body::after   { background: linear-gradient(90deg, rgba(224,121,106,0.20) 0%, rgba(224,121,106,0) 28%); }
.or-accent-edge::after   { background: linear-gradient(90deg, rgba(90,209,122,0.20) 0%, rgba(90,209,122,0) 28%); }
.or-accent-spirit::after { background: linear-gradient(90deg, rgba(122,162,255,0.22) 0%, rgba(122,162,255,0) 28%); }
/* Keep the card's real content above the accent wash. */
.or-card[class*="or-accent-"] > * { position: relative; z-index: 1; }

/* Reserve the keyword row's vertical space even on a card with zero chips, so a
   grid of creature cards reads uniform height (Zac playtest 2026-07-03, task
   #13864: resistance-rich species filled the card while basic ones sat near-
   empty). Holds ~2 chip rows — the common 0-3 chip spread lands inside it. */
.or-kw-row { min-height: 2.6rem; }

/* Keyword-icon row — the 2-4 defining chips. Bigger icon, terse label. */
.or-kw {
  display: inline-flex; align-items: center; gap: 0.25rem;
  padding: 0.1rem 0.4rem;
  background: #20283c; border: 1px solid var(--br-border); border-radius: 3px;
  font-size: 0.7rem; color: var(--br-ink); white-space: nowrap;
}
.or-kw-icon { font-size: 0.85rem; line-height: 1; }
.or-kw-extreme { border-color: var(--or-amber); color: var(--or-amber-ink); background: #2a2415; }
.or-kw-vulnerability { border-color: var(--or-danger-deep); color: #f3c7bf; background: #2c1715; }

/* ---- World map (world-map Phase 1: one continuous world-space camera) -------
 * ONE world plane (0..1000) the camera pans/zooms via a CSS transform on the
 * stage. The stage is 1000px = the world (1 unit = 1px); nodes sit at left/top %
 * of it and the SVG terrain shares its 0..1000 viewBox. LOD is driven by the
 * stage's data-map-lod (world | region): the CSS below swaps node/label
 * prominence per tier. Travel is never JS-gated — the server writes the opening
 * transform inline (map_camera_controller then takes over). */
.or-map-shell { max-width: 42rem; margin: 0 auto; padding: 1.5rem 1rem; }
@media (min-width: 1024px) { .or-map-shell { max-width: 64rem; } }
.or-map-head { margin-bottom: 1rem; }

/* The framed window the camera looks through. touch-action:none so a drag/pinch
   drives the camera, not the page scroll. The void gradient reads as unknown
   territory around the world's edge (contract §5, blank space is deliberate). */
.or-map-viewport {
  position: relative;
  width: 100%;
  height: 72vh;
  min-height: 340px;
  max-height: 680px;
  overflow: hidden;
  touch-action: none;
  cursor: grab;
  background: radial-gradient(120% 90% at 50% 42%, #111a2e 0%, var(--or-night-0) 68%, #05070f 100%);
  border-radius: 12px;
}
.or-map-viewport:active { cursor: grabbing; }

/* The world plane. Pinned at the viewport centre (left/top:50%) so centring a
   world point is a pure translate (see the controller); transform-origin 0 0
   keeps the scale math simple. */
.or-map-stage {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 1000px;
  height: 1000px;
  transform-origin: 0 0;
  will-change: transform;
}
/* A warm ground halo under the world so the plane reads as land, the margin as
   void. */
.or-map-stage::before {
  content: "";
  position: absolute;
  inset: -60px;
  border-radius: 48px;
  background: radial-gradient(92% 90% at 50% 46%, rgba(40, 50, 42, 0.55) 0%, rgba(21, 27, 35, 0.32) 62%, transparent 100%);
}

.or-map-terrain {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
  z-index: 1;
}

/* Region silhouettes — the terrain patch, tinted per region. Faded at region
   zoom (nodes lead), raised at world zoom (the region shape + name lead). */
.or-map-silhouette {
  fill: var(--region-tint, #3f6d4a);
  stroke: var(--region-tint, #3f6d4a);
  stroke-width: 3;
  stroke-linejoin: round;
  fill-opacity: 0.15;
  stroke-opacity: 0.5;
  transition: fill-opacity 0.25s ease, stroke-opacity 0.25s ease;
}
[data-map-lod="world"] .or-map-silhouette { fill-opacity: 0.32; stroke-opacity: 0.72; }

/* Terrain features (procedural, cosmetic — never topology, §3). Muted per kind so
   they read on any region tint. */
.or-map-feat { opacity: 0.6; }
.or-map-mtn-face { fill: #6b7385; stroke: #363c49; stroke-width: 1.5; stroke-linejoin: round; }
.or-map-mtn-cap  { stroke: #d3dbea; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.or-map-tree     { fill: #3f7050; stroke: #21382a; stroke-width: 1; }
.or-map-river-line { stroke: #4d84b8; stroke-width: 5; stroke-linecap: round; opacity: 0.85; }
.or-map-marsh-fill { fill: #5a6a45; opacity: 0.5; }
.or-map-reed { stroke: #85975a; stroke-width: 1.6; stroke-linecap: round; }

/* Roads. Faint topology by default; the road leaving where you stand is lit; a
   Cup/capability-locked road dashes. Widths are world units, so roads thin as you
   pull out to world view and thicken as you close in. */
.or-map-road { stroke: rgba(159, 176, 204, 0.22); stroke-width: 3.5; }
.or-map-road-open { stroke: var(--or-amber); stroke-width: 6; stroke-opacity: 0.92; }
.or-map-road-locked { stroke: rgba(159, 176, 204, 0.34); stroke-width: 3.5; stroke-dasharray: 9 8; }

/* Locale node markers — HTML on top of the terrain, positioned by world coords.
   The wrap counter-scales by 1/--map-zoom (the stage's current camera scale,
   server-seeded inline and kept live by map_camera_controller), so a card keeps a
   CONSTANT screen size — map-marker style — instead of outgrowing the placement
   floor at region zoom and stacking on its neighbours. The 1.45 boost is the
   card's screen size in units of its authored 62px width (≈90px on screen). */
.or-map-node-wrap {
  position: absolute;
  transform: translate(-50%, -50%) scale(calc(1.45 / var(--map-zoom, 1.9)));
  z-index: 2;
}
.or-map-node-form { margin: 0; }

.or-map-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: 62px;
  min-height: 44px;
  padding: 5px 4px;
  border-radius: 10px;
  border: 1.5px solid var(--or-amber-deep);
  background: linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  color: var(--br-ink);
  text-align: center;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
}
.or-map-node-name { font-weight: 700; font-size: 12px; line-height: 1.12; }
.or-map-node-tier {
  font-size: 9px; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--br-ink-dim);
}

.or-map-node-here {
  border-color: var(--or-amber);
  box-shadow: 0 0 0 3px rgba(244, 200, 96, 0.28), 0 2px 8px rgba(0, 0, 0, 0.4);
}
.or-map-node-here .or-map-node-tier { color: var(--or-amber-ink); }
.or-map-here-pin { color: var(--or-amber); font-size: 11px; line-height: 1; }

.or-map-node-open { cursor: pointer; border-color: var(--or-amber); }
.or-map-node-open:hover { filter: brightness(1.12); }
.or-map-node-open:active { transform: translateY(1px); }
.or-map-node-open:focus-visible { outline: 2px solid var(--or-amber); outline-offset: 2px; }
.or-map-travel-cue { font-size: 9px; font-weight: 700; color: var(--or-amber); letter-spacing: 0.02em; }

.or-map-node-far { opacity: 0.72; border-color: rgba(159, 176, 204, 0.35); }
.or-map-node-far:hover { opacity: 1; }
.or-map-node-far:focus-visible { outline: 2px solid var(--or-amber); outline-offset: 2px; }
.or-map-node-far .or-map-node-name { color: var(--br-ink-dim); font-weight: 600; }

.or-map-node-wrap[data-role="frontier"] .or-map-node { border-color: #7a3b3b; }
.or-map-node-wrap[data-role="city"] .or-map-node-name { color: var(--or-amber-ink); }

/* Themed tier-2 towns — each reads as its OWN biome on the map: ember-amber by
   default, Mirefen miasma-green. Keyed by locale so a new themed town drops in
   with its own tint. */
.or-map-node-wrap[data-role="themed_town"] .or-map-node { border-color: #b5622c; }
.or-map-node-wrap[data-locale="mirefen"] .or-map-node { border-color: #5f8a3b; }
.or-map-node-wrap[data-locale="mirefen"] .or-map-node-name { color: #a7d97a; }

/* Travel gate. A locked road (Cup / capability) renders dashed and faint so it
   reads as "there, but shut"; the locked node is INERT — a wall, not a link —
   with a lock pin + the unlock condition. A danger destination is open but
   flagged. */
.or-map-node-locked {
  opacity: 0.72; cursor: default;
  border-color: rgba(159, 176, 204, 0.32); border-style: dashed;
  background: linear-gradient(180deg, rgba(20, 22, 28, 0.9) 0%, rgba(14, 15, 20, 0.9) 100%);
}
.or-map-node-locked .or-map-node-name { color: var(--br-ink-dim); font-weight: 600; }
.or-map-lock-pin { font-size: 11px; line-height: 1; opacity: 0.85; }
.or-map-lock-cue { font-size: 8.5px; font-weight: 700; color: var(--br-ink-dim); letter-spacing: 0.01em; }
.or-map-danger-cue { font-size: 8.5px; font-weight: 700; color: #d98b5f; letter-spacing: 0.01em; }

/* At world zoom, nodes collapse to quiet dots so the region shapes + names lead
   (contract §4); at region zoom they're full cards. */
[data-map-lod="world"] .or-map-node {
  width: 13px; min-height: 0; height: 13px; padding: 0; gap: 0;
  border-radius: 50%; overflow: hidden;
}
[data-map-lod="world"] .or-map-node > * { display: none; }
[data-map-lod="world"] .or-map-node-here {
  width: 17px; height: 17px;
  box-shadow: 0 0 0 4px rgba(244, 200, 96, 0.3);
}

/* Region name labels — lead at world zoom, shrink to a quiet tag at region zoom. */
.or-map-labels { position: absolute; inset: 0; pointer-events: none; z-index: 3; }
.or-map-region-label {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  text-align: center; white-space: nowrap;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85), 0 0 10px rgba(0, 0, 0, 0.6);
  transition: opacity 0.25s ease;
}
.or-map-region-name {
  font-family: var(--br-pixel); font-size: 13px; color: var(--or-amber-ink);
  letter-spacing: 0.02em;
}
.or-map-region-sub {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--br-ink-dim);
}
.or-map-region-label.is-current .or-map-region-sub { color: var(--or-amber); }
[data-map-lod="region"] .or-map-region-label { opacity: 0.4; }
[data-map-lod="region"] .or-map-region-name { font-size: 10px; }
[data-map-lod="region"] .or-map-region-sub { display: none; }

/* Region shortcut strip — replaces the zone tabs. A tap flies the camera there;
   with JS off it's the plain jump-list. */
.or-map-jumpbar { display: flex; gap: 6px; margin-bottom: 8px; flex-wrap: wrap; }
.or-map-jump {
  flex: 1 1 0; min-width: 96px;
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  padding: 7px 9px; border-radius: 9px;
  border: 1.5px solid var(--or-amber-deep);
  background: linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  color: var(--br-ink); text-align: left; cursor: pointer;
  transition: filter 0.1s ease;
}
.or-map-jump:hover { filter: brightness(1.1); }
.or-map-jump:focus-visible { outline: 2px solid var(--or-amber); outline-offset: 2px; }
.or-map-jump.is-current {
  border-color: var(--or-amber);
  box-shadow: 0 0 0 2px rgba(244, 200, 96, 0.22), 0 2px 8px rgba(0, 0, 0, 0.4);
  background: linear-gradient(180deg, #2a2415 0%, var(--br-panel) 100%);
}
.or-map-jump-label { font-weight: 700; font-size: 12.5px; line-height: 1.1; }
.or-map-jump-badge {
  font-size: 9px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
  color: #0f1014; background: var(--or-amber); border-radius: 999px; padding: 1px 6px;
}
.or-map-jump-badge-here {
  font-size: 9px; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--or-amber-ink);
}

/* Legend swatches — miniatures of the actual node cards (same panel gradient,
   same border treatments per state), so the key looks like the thing it explains
   rather than a row of checkboxes. */
.or-map-legend-dot {
  display: inline-block; width: 20px; height: 13px; border-radius: 4px;
  border: 1.5px solid var(--or-amber-deep);
  background: linear-gradient(180deg, var(--br-panel-2) 0%, var(--br-panel) 100%);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}
.or-map-legend-here { border-color: var(--or-amber); box-shadow: 0 0 0 2px rgba(244, 200, 96, 0.28), 0 1px 3px rgba(0, 0, 0, 0.4); }
.or-map-legend-here::before {
  content: "◆"; display: block; text-align: center;
  color: var(--or-amber); font-size: 7px; line-height: 10px;
}
.or-map-legend-open { border-color: var(--or-amber); }
.or-map-legend-locked {
  border-color: rgba(159, 176, 204, 0.32); border-style: dashed; opacity: 0.72;
  background: linear-gradient(180deg, rgba(20, 22, 28, 0.9) 0%, rgba(14, 15, 20, 0.9) 100%);
}
.or-map-legend-far { border-color: rgba(159, 176, 204, 0.35); opacity: 0.72; }

/* MERGE FORECAST ESCALATION (task #13938) — a low-stability pairing must FEEL
   different from a murmured lean. merge_forecast_controller.js stamps
   data-breeds="wild"/"volatile" on the forecast readout when the selected cross
   reads that low; neutral / true-breeding crosses carry no attribute and keep
   the plain br-window-lt panel. "wild" warms the panel amber; "volatile" (the
   deepest scatter a pairing can carry) adds a slow amber pulse. */
[data-merge-forecast-target="readout"][data-breeds="wild"],
[data-merge-forecast-target="readout"][data-breeds="volatile"] {
  border-color: var(--or-amber-deep);
  color: var(--or-amber-ink);
  box-shadow: inset 1px 1px 0 rgba(244, 200, 96, 0.28), inset -1px -1px 0 #15110a;
}
[data-merge-forecast-target="readout"][data-breeds="volatile"] {
  animation: or-forecast-pulse 1.8s ease-in-out infinite;
}
@keyframes or-forecast-pulse {
  0%, 100% { box-shadow: inset 1px 1px 0 rgba(244, 200, 96, 0.28), inset -1px -1px 0 #15110a; }
  50%      { box-shadow: inset 1px 1px 0 rgba(244, 200, 96, 0.28), inset -1px -1px 0 #15110a,
                         0 0 0 3px rgba(244, 200, 96, 0.25); }
}
@media (prefers-reduced-motion: reduce) {
  [data-merge-forecast-target="readout"][data-breeds="volatile"] { animation: none; }
}

/* FORGING WAIT STATE (surge-honesty fix 2, 2026-07-07) — while a freshly-forged
   hybrid's real sprite is still generating, the reveal shows its genome emblem.
   Rather than an apologetic "art still rendering" placeholder, dress that interim
   as the creature STILL TAKING SHAPE IN THE FORGE: the emblem drops to a dark,
   ember-lit silhouette and the frame pulses with forge-heat. Pure CSS, keyed off
   the view's `generating` flag; when the sprite lands hybrid_reveal reloads the
   page WITHOUT `generating`, so the treatment falls away with no JS teardown. */
.or-forging {
  box-shadow:
    inset 0 0 18px rgba(217, 154, 43, 0.35),
    0 0 26px rgba(244, 200, 96, 0.45),
    0 0 0 1px rgba(244, 200, 96, 0.40);
  animation: or-forging-pulse 2.2s ease-in-out infinite;
}
@keyframes or-forging-pulse {
  0%, 100% {
    box-shadow: inset 0 0 16px rgba(217, 154, 43, 0.30),
                0 0 22px rgba(244, 200, 96, 0.38), 0 0 0 1px rgba(244, 200, 96, 0.35);
  }
  50% {
    box-shadow: inset 0 0 26px rgba(217, 154, 43, 0.52),
                0 0 40px rgba(255, 140, 60, 0.60), 0 0 0 1px rgba(244, 200, 96, 0.60);
  }
}
/* The emblem itself — dropped to a dark, ember-warmed silhouette while it forges.
   The blur stays light so the body_plan shape still reads (a silhouette, not mush);
   the static filter is what a reduced-motion viewer keeps (dark shape + ember glow,
   no pulse). */
.or-forging-shape {
  filter: brightness(0.40) contrast(1.15) blur(1.3px) drop-shadow(0 0 10px rgba(255, 140, 60, 0.55));
  animation: or-forging-heat 2.2s ease-in-out infinite;
}
@keyframes or-forging-heat {
  0%, 100% { filter: brightness(0.38) contrast(1.15) blur(1.4px) drop-shadow(0 0 8px rgba(255, 120, 45, 0.45)); }
  50%      { filter: brightness(0.52) contrast(1.20) blur(1.1px) drop-shadow(0 0 16px rgba(255, 160, 70, 0.70)); }
}
@media (prefers-reduced-motion: reduce) {
  .or-forging, .or-forging-shape { animation: none; }
}

/* THE REVEAL SCENE (Zac playtest 2026-07-15): the forged family tree floated on a
   flat panel — the creatures had "no background." A CSS-only backdrop sets them in
   a place: a warm overhead forge-glow spilling onto the child, a cool night field,
   a soft floor shadow, and a faint pixel grid for depth. No AI art (asset gen is
   ledger-gated) — pure gradients. */
.or-reveal-scene {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 70% at 50% 6%, rgba(244, 200, 96, 0.16) 0%, rgba(244, 200, 96, 0) 55%),
    radial-gradient(140% 120% at 50% 120%, rgba(6, 9, 18, 0.70) 0%, rgba(6, 9, 18, 0) 60%),
    linear-gradient(180deg, var(--or-night-2) 0%, var(--or-night-1) 45%, var(--or-night-0) 100%);
}
/* Faint pixel grid — a texture, low-contrast and center-masked so it reads as
   depth, not noise, and fades toward the frame edges. */
.or-reveal-scene::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(138, 155, 189, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(138, 155, 189, 0.05) 1px, transparent 1px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(120% 100% at 50% 32%, #000 0%, transparent 78%);
          mask-image: radial-gradient(120% 100% at 50% 32%, #000 0%, transparent 78%);
  pointer-events: none;
  z-index: 0;
}
/* Keep the family tree above the grid texture. */
.or-reveal-scene > * { position: relative; z-index: 1; }

/* ABILITY CHIP (class-select, Zac playtest 2026-07-15) — a class card's ability
   rendered as a <button> so it can reveal its effect on hover/focus/tap through the
   `tooltip` controller. It's a type="button" (never submits), styled as an or-chip
   with the UA button chrome reset off. The ⓘ cue marks it inspectable. */
.or-ability-chip {
  font-family: inherit;
  line-height: 1.1;
  cursor: help;
  -webkit-appearance: none;
          appearance: none;
}
.or-ability-chip:hover,
.or-ability-chip:focus-visible {
  filter: brightness(1.12);
  outline: none;
}
.or-ability-chip:focus-visible { box-shadow: 0 0 0 2px var(--or-amber); }
.or-ability-chip-cue { margin-left: 3px; opacity: 0.6; font-size: 0.85em; }

/* The floating tooltip node the controller appends to <body> (fixed, so a card's
   overflow never clips it). One shows at a time; positioned above its trigger. */
.or-tooltip {
  position: fixed;
  transform: translate(-50%, -100%);
  max-width: 230px;
  margin-top: -2px;
  padding: 7px 10px;
  background: #0f141d;
  border: 1px solid var(--or-amber-deep);
  border-radius: 6px;
  color: var(--br-ink);
  font-size: 12px;
  line-height: 1.4;
  white-space: normal;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
  z-index: 200;
  pointer-events: none;
}

/* ============================================================================
   JUICED MERGE REVEAL (merge_reveal_controller, task #14152)

   The forge screen animates: parents settle, attributes cascade with the child
   bar filling BETWEEN the two parents' values, traits + resistances land, then
   the sprite emerges. Progressive enhancement — the server renders the FULL
   final card; the controller ARMS it (this .or-mr-armed root parks every beat)
   and reveals beat by beat. No JS / reduced-motion / skip → the final card
   stands exactly as drawn, no parking. So every rule that HIDES a beat is
   scoped under .or-mr-armed; the base rules are the readable final state.
   ============================================================================ */

/* Parked pre-animation state — only while the controller has armed the card. */
.or-mr-armed .or-mr-beat { opacity: 0; transform: translateY(7px); }
.or-mr-armed .or-mr-beat.is-in {
  opacity: 1; transform: translateY(0);
  transition: opacity 0.42s ease, transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
}

/* PARENT IDENTITY (task #14161). Each parent tile wears a colored frame — A ember,
   B teal — so the color the row source-names use is grounded in a face at the top.
   `-lit-*` is the beat glow: the tile pulses brighter the moment its children
   fill. */
.or-mr-pframe {
  display: inline-block; padding: 3px; border-radius: 10px;
  border: 2px solid transparent; transition: box-shadow 0.4s ease, border-color 0.4s ease;
}
.or-mr-pframe-a { border-color: var(--or-ember); box-shadow: 0 0 0 1px rgba(239,127,74,0.35); }
.or-mr-pframe-b { border-color: var(--or-teal);  box-shadow: 0 0 0 1px rgba(55,179,172,0.35); }
.or-mr-parent-lit-a .or-mr-pframe-a { box-shadow: 0 0 16px 2px rgba(239,127,74,0.75); }
.or-mr-parent-lit-b .or-mr-pframe-b { box-shadow: 0 0 16px 2px rgba(55,179,172,0.75); }

/* LEGEND — one compact line that teaches the two track states once: the bright
   fill is born-with, the hatching is room to grow. The parent colors aren't in the
   legend — each parent's NAME already rides in its color under every number and on
   the tiles, so a legend swatch for them would just repeat it. */
.or-mr-legend {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 4px 12px; font-size: 10px; color: var(--br-ink-dim); margin: 6px 0 2px;
}
.or-mr-leg { display: inline-flex; align-items: center; gap: 5px; }
.or-mr-leg-fill {
  width: 16px; height: 8px; border: 1px solid #0c0f16; background: var(--br-green, #5ad17a);
  background-image: repeating-linear-gradient(90deg,
    rgba(0,0,0,0) 0, rgba(0,0,0,0) 4px, rgba(0,0,0,0.30) 4px, rgba(0,0,0,0.30) 6px);
}
.or-mr-leg-ghost {
  width: 16px; height: 8px; border: 1px dashed var(--or-amber-deep);
  background: repeating-linear-gradient(90deg,
    rgba(244,200,96,0.28) 0, rgba(244,200,96,0.28) 3px, rgba(244,200,96,0.08) 3px, rgba(244,200,96,0.08) 6px);
}

/* GROUP micro-header — BODY / EDGE / SPIRIT, dim and small, so the fixed stat
   order reads as clusters, never a flat list. */
.or-mr-group-head {
  font-size: 8px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--br-ink-dim); opacity: 0.7; margin: 7px 0 1px 3px; line-height: 1;
}

/* ATTRIBUTE ROW — stat label | the child's number (the hero) + the parent it took
   after, named in that parent's color | the track. */
.or-mr-attrs { display: flex; flex-direction: column; gap: 5px; }
.or-mr-row {
  display: grid; grid-template-columns: 3.1rem 3.9rem 1fr;
  align-items: center; gap: 8px;
}
.or-mr-label {
  font-size: 9px; letter-spacing: 0.03em; text-transform: uppercase;
  color: var(--br-ink-dim); line-height: 1.05;
}
/* The value block: the child's number, large and bold (the hero), over the source
   parent's NAME in tiny colored text. */
.or-mr-valcell { display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 0; }
.or-mr-value {
  font-size: 18px; font-weight: 800; line-height: 1; color: var(--br-ink);
  font-variant-numeric: tabular-nums;
}
/* The source name wraps to at most two short lines rather than ellipsizing — a
   truncated "Craghide …" was unreadable (task #14161 critic). */
.or-mr-source {
  font-size: 8px; line-height: 1.1; max-width: 3.9rem; text-align: center;
  overflow-wrap: anywhere; word-break: break-word;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.or-mr-source-a { color: var(--or-ember-ink); }
.or-mr-source-b { color: var(--or-teal-ink); }
.or-mr-source-both { color: var(--br-ink-dim); }
.or-mr-source-own { color: var(--or-amber-ink); font-weight: 700; }

.or-mr-track {
  position: relative; height: 11px; background: #11161f;
  border: 2px solid #0c0f16; box-shadow: inset 1px 1px 0 #2b3548; overflow: hidden;
}
/* Born-with value — green, striped like the stat bars. Grows via scaleX so its
   final WIDTH (the value) is set inline and the animation is the fill. */
.or-mr-fill {
  position: absolute; left: 0; top: 0; bottom: 0; height: 100%;
  transform-origin: left center;
  background: var(--br-green, #5ad17a);
  background-image: repeating-linear-gradient(90deg,
    rgba(0,0,0,0) 0, rgba(0,0,0,0) 6px, rgba(0,0,0,0.30) 6px, rgba(0,0,0,0.30) 8px);
}
/* Ghost "can grow to" — a hatched amber extension past the fill to the honest
   StatBars ceiling. Behind the fill, so it reads as headroom the bar can reach. */
.or-mr-ghost {
  position: absolute; left: 0; top: 0; bottom: 0; height: 100%;
  background: repeating-linear-gradient(90deg,
    rgba(244,200,96,0.28) 0, rgba(244,200,96,0.28) 3px, rgba(244,200,96,0.10) 3px, rgba(244,200,96,0.10) 6px);
}
/* Fill pulse — the moment a row fills during its parent's beat, the fill flashes
   that parent's color, then settles back to green. */
.or-mr-pulse-a .or-mr-fill { animation: or-mr-pulse-a 0.7s ease-out; }
.or-mr-pulse-b .or-mr-fill { animation: or-mr-pulse-b 0.7s ease-out; }
@keyframes or-mr-pulse-a {
  0% { background-color: var(--or-ember); box-shadow: 0 0 10px 1px rgba(239,127,74,0.7); }
  100% { background-color: var(--br-green); box-shadow: none; }
}
@keyframes or-mr-pulse-b {
  0% { background-color: var(--or-teal); box-shadow: 0 0 10px 1px rgba(55,179,172,0.7); }
  100% { background-color: var(--br-green); box-shadow: none; }
}
/* Standout row — the "whoa": child ran past BOTH parents (its own blood). Gold
   track + a flare pulse when it fills. */
.or-mr-row[data-mr-standout="true"] .or-mr-track { border-color: var(--or-amber-deep); }
.or-mr-row[data-mr-standout="true"] .or-mr-value { color: var(--or-amber-ink); }
.or-mr-row[data-mr-standout="true"] .or-mr-fill {
  background-color: var(--or-amber);
  background-image: repeating-linear-gradient(90deg,
    rgba(0,0,0,0) 0, rgba(0,0,0,0) 6px, rgba(0,0,0,0.28) 6px, rgba(0,0,0,0.28) 8px);
}
.or-mr-flare .or-mr-track { animation: or-mr-flarepulse 0.7s ease-out; }
@keyframes or-mr-flarepulse {
  0%   { box-shadow: inset 1px 1px 0 #2b3548, 0 0 0 0 rgba(244,200,96,0.0); }
  35%  { box-shadow: inset 1px 1px 0 #2b3548, 0 0 12px 2px rgba(244,200,96,0.85); }
  100% { box-shadow: inset 1px 1px 0 #2b3548, 0 0 0 0 rgba(244,200,96,0.0); }
}

/* Parked bar state (armed): fill collapsed, ghost hidden. The FILL grows only when
   its row is marked `is-filled` (during that row's parent beat); the GHOST sweeps
   in only once the root gains `or-mr-ghosts-in` (the late "potential" beat, across
   all rows at once). Base (unarmed / skipped / no-JS) shows all of it at full —
   every rule that hides is scoped under .or-mr-armed. */
.or-mr-armed .or-mr-fill  { transform: scaleX(0); }
.or-mr-armed .or-mr-ghost { opacity: 0; }
.or-mr-armed .or-mr-row.is-filled .or-mr-fill {
  transform: scaleX(1);
  transition: transform 0.46s cubic-bezier(0.22, 1, 0.36, 1);
}
.or-mr-armed.or-mr-ghosts-in .or-mr-ghost {
  opacity: 1; transition: opacity 0.55s ease;
}

/* The exit CTA is the LAST beat when armed: parked (and un-clickable) until it
   reveals just after the sprite finale, so the way out never lights before the
   payoff. Base state (unarmed / skip / no-JS) leaves it fully lit and clickable. */
.or-mr-armed .or-mr-cta { pointer-events: none; }
.or-mr-armed .or-mr-cta.is-in { pointer-events: auto; }

/* CHIPS AREA — trait chips + resistance pills, tinted by the parent that carries
   them and flown in from that parent's side. */
.or-mr-chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.or-mr-chip, .or-mr-pill {
  display: inline-block; font-size: 11px; line-height: 1;
  padding: 4px 8px; border-radius: 999px;
  background: #1b2330; border: 1px solid var(--or-amber-deep); color: var(--or-amber-ink);
}
/* Carrying-parent tint (both chips and pills share the from-classes). */
.or-mr-from-a { border-color: var(--or-ember); color: var(--or-ember-ink); }
.or-mr-from-b { border-color: var(--or-teal);  color: var(--or-teal-ink); }
.or-mr-from-both { border-color: var(--br-border-lt); color: var(--br-ink); }
.or-mr-armed .or-mr-chip, .or-mr-armed .or-mr-pill { opacity: 0; }
.or-mr-armed .or-mr-from-a { transform: translateX(-22px) scale(0.9); }
.or-mr-armed .or-mr-from-b { transform: translateX(22px) scale(0.9); }
.or-mr-armed .or-mr-from-both { transform: translateY(10px) scale(0.9); }
.or-mr-armed .or-mr-chip.is-in, .or-mr-armed .or-mr-pill.is-in {
  opacity: 1; transform: none;
  transition: opacity 0.32s ease, transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Sprite finale — the child scales in with an ember flash. */
.or-mr-emerge { animation: or-mr-emerge 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
@keyframes or-mr-emerge {
  0%   { transform: scale(0.82); filter: brightness(1.8) saturate(0.6); }
  40%  { filter: brightness(1.35); }
  100% { transform: scale(1); filter: none; }
}

@media (prefers-reduced-motion: reduce) {
  .or-mr-armed .or-mr-beat,
  .or-mr-armed .or-mr-beat.is-in,
  .or-mr-armed .or-mr-fill,
  .or-mr-armed .or-mr-row.is-filled .or-mr-fill,
  .or-mr-armed .or-mr-ghost,
  .or-mr-armed.or-mr-ghosts-in .or-mr-ghost,
  .or-mr-armed .or-mr-chip,
  .or-mr-armed .or-mr-pill { opacity: 1; transform: none; transition: none; }
  .or-mr-pulse-a .or-mr-fill,
  .or-mr-pulse-b .or-mr-fill,
  .or-mr-flare .or-mr-track,
  .or-mr-emerge { animation: none; }
  .or-mr-pframe { transition: none; }
}
