:root {
    --bg-dark: #121212;
    --card-bg: #1e1e1e;
    --accent: #2ecc71;
    --text-main: #f1f2f6;
    --text-muted: #a4b0be;
    --warning: #f1c40f;
    --danger: #e74c3c;
}

/* ── R1 item 1: dark-mode scrollbars everywhere ──────────────────────────
   Firefox (scrollbar-width/-color) + WebKit (::-webkit-scrollbar*). Applied
   universally so every scrollable panel matches the dark theme instead of
   the OS default bright scrollbar. Elements with their own more-specific
   scrollbar rule (e.g. .trophy-cat-tabs, .time-nav — hidden scrollbars)
   still win the cascade over this low-specificity `*`/`html` rule. */
html { scrollbar-width: thin; scrollbar-color: #444 #121212; }
* { scrollbar-width: thin; scrollbar-color: #444 #1a1a1a; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: #1a1a1a; }
*::-webkit-scrollbar-thumb { background: #444; border-radius: 5px; border: 2px solid #1a1a1a; }
*::-webkit-scrollbar-thumb:hover { background: #5a5a5a; }
*::-webkit-scrollbar-corner { background: #1a1a1a; }

/* 100dvh (dynamic viewport) so the app fits the ACTUALLY-visible area, not the
   large-viewport 100vh that hides behind mobile browser chrome / gesture-nav —
   that was clipping the bottom-docked activity feed off-screen. 100vh first as
   the fallback for browsers without dvh.
   EMOJI FALLBACK: every text stack in the app closes with 'Noto Color Emoji',
   the emoji font the card art already loads. Windows 10's own emoji font stops
   at Unicode 11, so newer emoji (🪦 🪤 🫎 🦫 🪶 …) drew as empty boxes, and
   others (🧥 🔧 🛒 …) as near-invisible black outlines. Letters still use the
   system font; only glyphs it lacks fall through to Noto. A symbol meant as a
   TEXT arrow (◀ ▶ ⬇ ↩) carries U+FE0E so it keeps its text glyph. Form
   controls don't inherit the body font, so they get the same stack here.
   The 🪦 tombstone does NOT rely on this: it is an inline SVG (uiCombat.js),
   because the Noto subset only downloads on first use, and not at all offline. */
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji'; background-color: var(--bg-dark); color: var(--text-main); margin: 0; padding: 0; display: flex; flex-direction: column; height: 100vh; height: 100dvh; overflow: hidden; }

button, input, select, textarea { font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji'; }

.log-emoji, .rli-emoji, .tc-emoji, .stat-emoji, .sd-emoji, .pack-item-emoji { font-family: 'Noto Color Emoji', sans-serif; }

.screen { display: none !important; height: 100%; width: 100%; }
.screen.active { display: flex !important; flex-direction: column; }
.view { display: none; }
.view.active { display: block; }
#login-screen.active { justify-content: center; justify-content: safe center; align-items: center; overflow-y: auto; }  /* safe: a short screen scrolls instead of clipping the title off the top */
/* Homestead line for a player with no Outpost (index.html #hs-no-outpost). */
.hs-no-outpost { display: block; width: 100%; margin: 8px 0 0; padding: 8px 12px; background: rgba(255,255,255,.04);
    border: 1px dashed #555; border-radius: 8px; color: #ccc; font-size: .82rem; line-height: 1.4; text-align: center; cursor: pointer; }
.hs-no-outpost[hidden] { display: none; }
/* Under the invite code after a kick rotated it (leader only; app.js showCodeChangedNote). */
.code-changed-note { margin-top: 6px; font-size: 0.8rem; color: #f1c40f; line-height: 1.35; }
.hs-no-outpost-go { color: var(--accent); font-weight: bold; white-space: nowrap; }
/* Login intro (js/ui/loginIntro.js): a line about the game, "Sign in with
   Google", then three sample cards that look and move like the Strike Deck.
   Each card IS a .sd-card-wrap (the deck's 150×211 card, the real 260×364 card
   scaled 0.58, see .sd-card-wrap below), placed by the deck's own row layout,
   so the size is never restated here. The cards sit BELOW the button (beside
   it on a short landscape screen), so the button is always above the fold. */
/* The intro line would otherwise stretch the box edge to edge (it used to
   shrink-wrap the button): a centred column, two columns on short landscape. */
#login-screen .login-box { width: min(100%, 360px); padding: 0 16px; box-sizing: border-box; }
#login-screen .login-title { text-align: center; }
.login-intro { color: #bbb; font-size: .9rem; line-height: 1.45; margin: 0 0 12px; text-align: center; }
/* Height is set by loginIntro.js from the row layout. Swiping on the space
   around the cards scrolls the page as usual; on a card it drags the card
   (touch-action: none), so a drag never scrolls the page under it. */
.login-samples { position: relative; width: 100%; margin: 14px 0 0; }
.login-sample { touch-action: none; }
.login-sample .tc-icon-row, .login-sample-focus .tc-icon-row { display: none; }   /* card history icons mean nothing before sign-in */
/* Before sign-in the samples use the DEVICE's emoji first: the game's .tc-emoji
   puts the Noto web font first, which made the login page fetch ~1.2 MB of
   emoji font for three cards. Noto stays last, for a glyph the device lacks. */
.login-sample *, .login-sample-focus * { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Apple Color Emoji', 'Segoe UI Emoji', emoji, sans-serif, 'Noto Color Emoji' !important; }
.login-sample:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The enlarged card: scaled by loginIntro.js fitFocus (--lf-s) so the card, its
   tier glow and Close all fit inside the overlay, clear of a notch or home bar,
   on any screen either way up. Nothing scrolls, so nothing is cut off: the
   game's .cfm-panel scrolls (overflow) and clipped the glow along its edge. */
.login-focus { padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
                        max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left)); }
.login-focus .cfm-panel { --lf-s: 1; overflow: visible; max-height: none; padding: 18px; }
.login-sample-focus { position: relative; width: calc(260px * var(--lf-s)); height: calc(364px * var(--lf-s)); }
.login-sample-focus .trading-card { position: absolute; top: 0; left: 0; transform: scale(var(--lf-s)); transform-origin: top left; }
.login-focus .cfm-close { width: max(160px, calc(260px * var(--lf-s))); max-width: none; }
/* Short landscape screens: the cards go BESIDE the button, title tighter. */
@media (max-height: 520px) and (orientation: landscape) {
    #login-screen .login-box { width: min(100%, 760px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        column-gap: 20px; align-items: center; }
    #login-screen .login-box > * { grid-column: 1; }
    #login-screen #login-samples { grid-column: 2; grid-row: 1 / span 5; margin: 0; }
    #login-screen #ep-auth-section { grid-column: 1 / -1; }
    .login-intro { font-size: .8rem; margin-bottom: 8px; }
    #login-screen .login-title { margin-bottom: 6px; font-size: 1.6rem; }
}

/* ── Z5 HEADER REDESIGN ──────────────────────────────────────────────────────
   Header-LOCAL palette. Deliberately NOT repointing --accent: that variable is
   green (#2ecc71) and drives ~100 rules across the whole app, so making it gold
   here would restyle every screen. The header's gold is --warning's hue, bound
   to its own names so the header can move without dragging the app with it.

   HEIGHT IS LOAD-BEARING. Measured before this change and held identical after:
     1280px → 115px   768px → 130px   390/380px → 182px
   The header sits above a `height:100dvh; overflow:hidden` flex column, so every
   pixel added here is a pixel taken from the view below it. That is why the
   sizing knobs below (15px/20px padding, 1.5rem h1, .85rem tabs, 3px underline)
   are UNCHANGED from the pre-Z5 rules — only colour and decoration moved. The
   badge is 34px, which fits inside the h1's existing 33px line box plus its own
   1px of overhang without becoming the tallest item in the row. */
:root {
    --hdr-gold:      #f1c40f;   /* active tab, wordmark, badge ring */
    --hdr-cream:     #efe7d2;   /* wordmark fill */
    --hdr-muted:     #7c8ea8;   /* inactive tab — blue-gray, not neutral gray */
    --hdr-grad-from: #05070c;   /* near-black, top-left */
    --hdr-grad-to:   #0f2745;   /* deep blue, bottom-right */
    --hdr-nav-from:  #04060a;
    --hdr-nav-to:    #0a1729;
}

/* Diagonal black → deep blue. Scope is HEADER-ONLY (see Z5 report): the app
   body keeps --bg-dark, so the gradient reads as a masthead rather than a
   page-wide wash. Both rows share one continuous gradient via `header`, then
   the nav row lays its own darker gradient on top so the two bands are
   distinguishable without a hard seam. */
header {
    background: linear-gradient(135deg, var(--hdr-grad-from) 0%, #091423 45%, var(--hdr-grad-to) 100%);
    display: flex; flex-direction: column;
    border-bottom: 1px solid #16324f; z-index: 10;
}
.header-top { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; border-bottom: 1px solid rgba(120,170,220,.13);}
.header-top h1 {
    margin: 0; font-size: 1.5rem; text-transform: uppercase; letter-spacing: 2px;
    color: var(--hdr-cream);
    font-weight: 800;
    text-shadow: 0 1px 0 rgba(0,0,0,.6);
    white-space: nowrap;
}

/* Brand lockup: badge · wordmark · rule · tagline, baseline-aligned.
   AA1: flex-shrink 0 is load-bearing. .header-top is a no-wrap flex row and
   BOTH children shrink by default, so a signed-in player with a real username,
   an Outpost name and the Install button gave the right-hand group ~319px of
   content at 390px — which crushed this lockup to 43px. The h1 carries
   white-space:nowrap (Z5), so the wordmark then OVERFLOWED its 3px box and
   painted straight over the username. That is the reported "logo overlapping
   the username": not a positioning bug, a shrink-to-zero bug.
   Z5 measured this row signed-OUT, where both fields are empty and the right
   group is small enough that nothing shrinks — which is why it looked clean. */
.brand-lockup { display: flex; align-items: center; gap: 11px; flex-shrink: 0; }
/* 32px is not a taste call — it is the exact height of the h1's line box
   (1.5rem at normal line-height). One pixel more and the badge, not the
   wordmark, becomes the tallest item in .header-top and the row grows. */
.brand-badge {
    width: 32px; height: 32px; flex-shrink: 0; display: block;
    filter: drop-shadow(0 2px 4px rgba(0,0,0,.55));
}
.brand-rule { width: 1px; height: 22px; background: rgba(241,196,15,.38); flex-shrink: 0; }
.brand-tagline {
    font-size: .62rem; text-transform: uppercase; letter-spacing: 2.2px;
    color: var(--hdr-gold); opacity: .78; font-weight: 600; white-space: nowrap;
}

.nav-tabs {
    display: flex; justify-content: center; position: relative;
    background: linear-gradient(135deg, var(--hdr-nav-from) 0%, var(--hdr-nav-to) 100%);
}
/* BASE .tab-btn IS DELIBERATELY UNCHANGED from pre-Z5. The login screen reuses
   this class for its Sign In / Sign Up / submit buttons, so every Z5 change
   below is scoped to `.nav-tabs` — the login screen is provably untouched. */
.tab-btn { background: transparent; color: var(--text-muted); border: none; border-bottom: 3px solid transparent; padding: 15px 20px; font-weight: bold; cursor: pointer; transition: 0.2s; flex: 1; text-align: center; text-transform: uppercase; font-size: 0.85rem; letter-spacing: 1px; border-radius: 0; min-width: auto; max-width: none;}

/* flex row + centred icon/label. `gap` is the ONLY box change vs pre-Z5, and it
   is horizontal, so it cannot affect header height. */
.nav-tabs .tab-btn {
    color: var(--hdr-muted);
    display: flex; align-items: center; justify-content: center; gap: 7px;
}
.tab-ico {
    width: 15px; height: 15px; flex-shrink: 0;
    fill: none; stroke: currentColor; stroke-width: 2;
    stroke-linecap: round; stroke-linejoin: round;
    /* The icon must never be the tallest thing in the tab, or it would grow the
       nav row past its measured height. 15px sits inside the .85rem line box. */
    max-height: 15px;
}
/* 18px == the line box .tab-btn had before Z5 (0.85rem at normal line-height).
   Pinned rather than left to `normal` because the button is now a flex row, and
   a flex line sizes to its tallest item — the label must keep owning that, so
   the nav row height is unchanged from the pre-Z5 measurement. */
.tab-label { line-height: 18px; white-space: nowrap; }
.tab-btn:hover { color: #fff; background: rgba(255,255,255,0.02); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); background: transparent; }
.tab-btn.battle-btn { color: var(--warning); transition: all 0.3s; }
.tab-btn.battle-btn.active { border-bottom-color: var(--warning); }

/* Gold active state + blue-gray inactive — nav only. */
.nav-tabs .tab-btn:hover { color: #dce7f5; background: rgba(120,170,220,0.05); }
.nav-tabs .tab-btn.active { color: var(--hdr-gold); border-bottom-color: var(--hdr-gold); background: rgba(241,196,15,.06); }
.nav-tabs .tab-btn.battle-btn { color: #b98f4a; }
.nav-tabs .tab-btn.battle-btn.active { color: var(--hdr-gold); border-bottom-color: var(--hdr-gold); }

/* Below ~820px the top row runs out of room: wordmark + rule + tagline + the
   user chip + Install + Log Out cannot share one line, and a wrap would push
   .header-top past its measured 63px — which is a height regression, not a
   cosmetic one. The tagline is the first thing to go (the wordmark and the
   badge carry the brand on their own); the divider goes with it, since a rule
   with nothing on its right is just a stray tick. */
@media (max-width: 820px) {
    .brand-rule, .brand-tagline { display: none; }
}
/* AA1: the right-hand group is what yields now. It gets min-width:0 so it may
   actually shrink (a flex item won't go below its content's min-content width
   without this), the username truncates with an ellipsis instead of forcing the
   row wider, and below 560px the Outpost name steps out — it has a permanent
   home on the Outpost tab, whereas the username is the tap target for Settings
   and has to stay reachable. */
.header-top > div:not(.brand-lockup) { min-width: 0; }
/* .sub-btn is shared with the Homestead's button rows, where `flex: 1` (basis
   0) is what makes those buttons share a row evenly. In the header that same
   rule collapses the button to a zero basis, so "⬇ Install App" wrapped onto
   two lines and stood 46px tall inside a 32px row — pushing .header-top to 77px
   for anyone who has actually been offered the install prompt. (Z5 measured
   63px because #btn-install-app defaults to display:none and only appears once
   beforeinstallprompt fires, so the measurement never saw it.)
   In the header these size to content. */
.header-top .sub-btn { flex: 0 0 auto; white-space: nowrap; }
#user-display-name {
    display: inline-block; max-width: 110px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    vertical-align: bottom;
}
#header-outpost {
    max-width: 130px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    display: inline-block; vertical-align: bottom;
}
@media (max-width: 560px) {
    /* The buttons carry inline `padding` from index.html, so overriding it
       needs !important — the alternative is unpicking inline styles that the
       install-prompt JS also writes to (display), which is riskier.
       Without nowrap the squeezed buttons wrapped their OWN label onto a second
       line, which is what made the right group 58px tall and pushed .header-top
       from 63px to 89px. Shrinking a flex row makes items taller, not narrower,
       unless you forbid the internal wrap. */
    .header-top > div:not(.brand-lockup) { gap: 8px !important; }
    /* Trims the chip from 34px to the h1's 32px line box, so the row stays at
       the 63px Z5 measured and locked in. */
    #user-header-btn { padding: 3px 9px !important; }
    #header-outpost { display: none; }
    #user-display-name { max-width: 76px; }
    #btn-install-app, #btn-logout { white-space: nowrap; flex-shrink: 0; }
    /* Log Out leaves the bar: the Settings panel behind the username chip
       already carries a Sign Out button, so nothing becomes unreachable. */
    #btn-logout { display: none !important; }
    /* Install collapses to its glyph. It is a transient prompt, not a
       permanent control, so the label is the cheapest thing in the row. */
    #btn-install-app { padding: 6px 10px !important; font-size: 0 !important; }
    #btn-install-app::after { content: '\2B07\FE0E'; font-size: .95rem; }
}
@media (max-width: 400px) {
    #user-display-name { max-width: 60px; }
}

/* The badge stays 32px — the brand mark never degrades. The WORDMARK shrinks
   instead, and that is load-bearing rather than cosmetic: .header-top is a
   no-wrap flex row, so every px the lockup gains is a px taken from the user
   chip + Install + Log Out group on the right. At 390px the full-size wordmark
   squeezed that group until its buttons broke onto two lines and the row grew
   63px -> 75px. Sizing the wordmark so the lockup is no wider than the bare h1
   used to be gives the right-hand group its original width back. */
@media (max-width: 420px) {
    .brand-lockup    { gap: 8px; }
    .header-top      { padding: 15px 14px; }
    .header-top h1   { font-size: 1.08rem; letter-spacing: 1.3px; }
}

.layout-wrapper { display: flex; flex: 1; overflow: hidden; }
.main-content { flex: 1; display: flex; flex-direction: column; overflow-y: auto; position: relative; }
/* Only max-height transitions (the mobile bottom-dock animation). The desktop
   width collapse is intentionally instant: a `width`/`flex-basis` transition on
   a flex item whose main size is driven by flex layout stays stuck at the start
   value here, so the rail snaps — fine for a dock, and the arrow still animates. */
.activity-sidebar { width: 320px; background-color: #151515; border-left: 2px solid #333; display: flex; flex-direction: column; transition: max-height 0.25s ease; }
/* Outpost-wide "Hide Live Activity Feed" (leader-set, read from the outpost doc
   by every member — see applyFeedVisibility in uiLog.js).
   !important is load-bearing, not decoration: main.js writes
   sidebar.style.display on EVERY tab switch and uiPvp does the same entering a
   battle, so a non-important rule would be beaten by those inline styles the
   moment the player changed tab. An !important author rule outranks inline. */
/* `feed-pending` is the SAME hide, for the one case the remembered value cannot
   cover: an account this device has never seen. It holds the feed back until
   the outpost doc answers, so a first open shows the feed appearing rather than
   being yanked away. applyFeedVisibility clears it, and a watchdog clears it
   anyway if the read never lands. */
body.feed-hidden .activity-sidebar,
body.feed-pending .activity-sidebar { display: none !important; }
.sidebar-header { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 15px; background: #1a1a1a; border-bottom: 1px solid #333; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; color: var(--text-muted); cursor: pointer; }
.sidebar-header .feed-dock-arrow { display: inline-block; transition: transform 0.2s ease; font-size: 0.7rem; flex-shrink: 0; }
.feed-container { flex: 1; overflow-y: auto; padding: 15px; display: flex; flex-direction: column; gap: 12px; }

/* ── Desktop right-dock: collapse the 320px feed rail to a thin spine that
   expands on click. Same toggleFeedDock() / .feed-docked class as mobile. ── */
@media (min-width: 769px) {
    /* grow0 shrink0 so the rail honors its `width` (flex-basis stays auto→width,
       which transitions cleanly — a flex-basis transition did NOT animate here
       and left the collapse stuck). min-width:0 defeats the flex content-floor
       (min-width:auto) that otherwise refuses to shrink below content size. */
    .activity-sidebar { flex-grow: 0; flex-shrink: 0; min-width: 0; }
    .sidebar-header .feed-dock-arrow { transform: rotate(90deg); }   /* ▶ = collapse toward the right edge */
    .activity-sidebar.feed-docked { width: 46px; }
    .activity-sidebar.feed-docked .feed-container { display: none; }
    .activity-sidebar.feed-docked .sidebar-header { flex-direction: column; padding: 14px 0; gap: 12px; }
    .activity-sidebar.feed-docked .feed-header-label { writing-mode: vertical-rl; white-space: nowrap; font-size: 0.72rem; }
    .activity-sidebar.feed-docked .feed-dock-arrow { transform: rotate(-90deg); }   /* ◀ = expand back into view */
}
.container { flex: 1; padding: 20px; max-width: 950px; margin: 0 auto; width: 100%; box-sizing: border-box; }

/* BOUNTY & LOG LAYOUT */
.log-layout { display: flex; gap: 25px; align-items: flex-start; }
.bounty-sidebar { width: 300px; flex-shrink: 0; display: flex; flex-direction: column; gap: 15px; }
.animal-grid-container { flex: 1; }
/* Stacked mode: align-items must flip to stretch — in a column flex the
   cross axis is HORIZONTAL, so the inherited flex-start was collapsing
   .animal-grid-container to min-content width (~142px → single-column grid). */
@media (max-width: 900px) { .log-layout { flex-direction: column-reverse; align-items: stretch; } .bounty-sidebar { width: 100%; } }

.bounty-card { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 15px; display: flex; gap: 15px; align-items: center; position: relative; overflow: hidden; }
.bounty-info { flex: 1; }
.bounty-title { margin: 0 0 5px 0; font-size: 0.95rem; color: #fff; }
.bounty-desc { margin: 0 0 8px 0; font-size: 0.75rem; color: var(--text-muted); font-style: italic; }   /* the contract text: Remy's flavor */
/* The one-line requirement (rules/bounties.js bountyRequirementText), under the contract text on every surface. */
.bounty-target { margin: 0 0 6px 0; font-size: 0.75rem; color: var(--text-main, #ddd); font-weight: 600; }
#silas-contract-text { margin-bottom: 6px; }
.bounty-reward { font-size: 0.8rem; font-weight: bold; color: var(--warning); }
.bounty-progress-ring { width: 50px; height: 50px; border-radius: 50%; display: flex; align-items: center; justify-content: center; position: relative; background: #333; flex-shrink: 0; }
.bounty-progress-ring::before { content: ""; position: absolute; inset: 4px; background: #1a1a1a; border-radius: 50%; }
.bounty-progress-text { position: relative; z-index: 1; font-size: 0.8rem; font-weight: bold; color: #fff; }
/* Two-part contracts (Seasoned Hunter: species AND battles) show one ring per
   half, STACKED vertically, each labelled so neither number is ambiguous. A
   one-part contract renders a single unlabelled ring exactly as before.
   Vertical rather than side-by-side: two 50px rings plus their gap took ~110px
   out of the card's width, and .bounty-info is what gave that back — on a
   narrow phone the horizontal pair squeezed the contract text into a tall thin
   column. Measured on the Seasoned Hunter card at 375px: the description goes
   from 178px wide to 238px (+34%), and because it then needs fewer lines the
   card gets SHORTER too, 212px -> 179px. Stacking costs nothing here. */
.bounty-rings { display: flex; flex-direction: column; gap: 10px; align-items: center; flex-shrink: 0; }
.bounty-ring-group { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.bounty-ring-label { font-size: 0.55rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); font-weight: bold; }
.bounty-timer { position: absolute; top: 0; right: 0; background: #333; color: #fff; font-size: 0.6rem; padding: 3px 8px; border-bottom-left-radius: 8px; font-weight: bold; }
.bounty-timer.urgent { background: var(--danger); }

/* ── R1 item 4: bounty-completion flash ──────────────────────────────────
   The row that just finished pulses gold for BOUNTY_FLASH_MS (uiLog.js)
   instead of silently disappearing from Active Bounties. */
.bounty-card-complete {
    border-color: var(--warning);
    animation: bountyCompletePulse 1.6s ease-in-out;
}
@keyframes bountyCompletePulse {
    0%   { box-shadow: 0 0 0 rgba(241, 196, 15, 0); background: #1a1a1a; }
    15%  { box-shadow: 0 0 24px rgba(241, 196, 15, 0.6); background: #2a2410; }
    100% { box-shadow: 0 0 0 rgba(241, 196, 15, 0); background: #1a1a1a; }
}
.bounty-complete-ribbon {
    position: absolute; top: 0; right: 0; background: var(--warning); color: #000;
    font-size: 0.65rem; padding: 3px 10px; border-bottom-left-radius: 8px; font-weight: 900;
    letter-spacing: 0.5px;
}

.animal-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: 15px; }
.animal-card { background-color: var(--card-bg); border-radius: 12px; cursor: pointer; transition: transform 0.1s; display: flex; flex-direction: column; align-items: center; padding: 15px 5px; border: 2px solid transparent; position: relative; }
.animal-card:active { transform: scale(0.96); }
.log-emoji { font-size: 3rem; margin-bottom: 5px; }
.animal-name { margin: 0; font-size: 1.1rem; font-weight: bold; text-align: center;}
.animal-pts { margin: 5px 0 0; font-size: 0.85rem; font-weight: bold;}
.bait-badge { position: absolute; top: -10px; right: -10px; background: #e74c3c; color: #fff; font-size: 0.75rem; font-weight: bold; padding: 5px 8px; border-radius: 12px; border: 2px solid var(--bg-dark); box-shadow: 0 2px 5px rgba(0,0,0,0.5); z-index: 5; pointer-events: none;}
.bait-badge.bait-active { background: rgba(231, 76, 60, 0.85); color: white; border-radius: 6px; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; }
.bait-badge.bait-expired { background: rgba(100, 100, 100, 0.7); color: #aaa; border-radius: 6px; padding: 2px 6px; font-size: 0.7rem; font-weight: bold; text-decoration: line-through; }
/* Deployed but not yet switched on — effective at the next local midnight.
   Amber rather than the active red: it must not read as "this is catching now",
   because on its deployment day it catches nothing. Dashed border carries the
   same "not yet" meaning without relying on colour alone. */
.bait-badge.bait-pending { background: rgba(243, 156, 18, 0.85); color: #1a1206; border: 1px dashed rgba(255, 220, 150, 0.9); border-radius: 6px; padding: 1px 5px; font-size: 0.66rem; font-weight: bold; }

.sub-controls { display: flex; gap: 8px; margin-bottom: 10px; background: #222; padding: 6px; border-radius: 8px; align-items: center;}
.sub-btn { flex: 1; padding: 6px; border: 1px solid var(--accent); border-radius: 20px; background: transparent; color: var(--accent); cursor: pointer; font-size: 0.82rem; text-align: center; font-weight: bold; }
.sub-btn.active { background: var(--accent); color: #000; }
select.sub-btn { appearance: none; text-align-last: center; }
select.sub-btn option { background: var(--bg-dark); color: #fff; }
.sort-toggle-btn .sort-arrow { display: inline-block; margin-left: 4px; font-size: 0.75em; transition: transform 0.15s ease; }
.action-btn { background: var(--accent); color: #000; border: none; padding: 10px; border-radius: 8px; font-weight: bold; cursor: pointer; transition: 0.2s;}
.action-btn:hover { opacity: 0.9; }
.action-btn:disabled { opacity: 0.3; cursor: not-allowed; }

.chart-container { display: flex; flex-direction: column; gap: 0; border-radius: 8px; overflow: hidden; border: 1px solid #333; }
.stat-group { background: var(--card-bg); border-bottom: 1px solid #262626; }
.stat-group:last-child { border-bottom: none; }
.stat-group:nth-child(even) .bar-row { background: rgba(255,255,255,0.03); }
.bar-row { display: flex; align-items: center; gap: 10px; padding: 7px 12px; cursor: pointer; transition: background 0.2s; }
.bar-row:hover { background: #2a2a2a; }
.icon-badge { width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: #111; }
.stat-emoji { font-size: 1.2rem; }
.bar-label { width: 85px; font-size: 0.9rem; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.bar-track { flex: 1; background: #111; height: 18px; border-radius: 9px; position: relative; }
.bar-fill { height: 100%; border-radius: 9px; transition: width 0.5s ease-out; }
.bar-value { display: none; }
.bar-count { font-size: .82rem; font-weight: 800; min-width: 28px; text-align: right; flex-shrink: 0; }
.stat-details { display: none; padding: 20px; background: #151515; border-top: 1px solid #333; }
.stat-details.active { display: block; }

.calendar-wrapper { display: flex; flex-direction: column; gap: 15px; max-width: 70%; margin: 0 auto; }
.month-block { background: #1e1e1e; border-radius: 6px; padding: 8px; border: 1px solid #2a2a2a;}
.month-title { margin: 0 0 8px 0; font-size: 0.9rem; color: var(--text-main); text-align: center; }
.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; text-align: center; }
.day-header { font-size: 0.65rem; color: var(--text-muted); padding-bottom: 4px; font-weight: bold; }
.cal-cell { aspect-ratio: 1; position: relative; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; border-radius: 3px; background: #2a2a2a; color: #666; font-weight: bold; }
.cal-cell.empty { background: transparent; color: transparent; }
.cal-cell-badge { position: absolute; bottom: 1px; right: 1px; font-size: 0.5rem; line-height: 1; font-weight: 800; color: #fff; background: rgba(0,0,0,0.55); border-radius: 2px; padding: 0 1px; pointer-events: none; }

@media (min-width: 900px) {
    .calendar-wrapper { max-width: 45%; gap: 10px; }
    .month-block { padding: 6px; }
    .month-title { font-size: 0.8rem; margin-bottom: 5px; }
    .month-grid { gap: 2px; }
    .cal-cell { font-size: 0.6rem; border-radius: 2px; }
    .day-header { font-size: 0.55rem; }
    .cal-cell-badge { font-size: 0.42rem; }
}
.cal-cell.active-cell { color: #fff; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.3); text-shadow: 1px 1px 1px rgba(0,0,0,0.5); cursor: pointer; transition: transform 0.1s;}
.cal-cell.active-cell:hover { transform: scale(1.1); border: 1px solid #fff; z-index: 2;}

.feed-item { background: #222; border-radius: 8px; padding: 12px; border-left: 4px solid var(--accent); font-size: 0.9rem; }
.feed-meta { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--text-muted); margin-bottom: 5px; }
.feed-pts { font-weight: bold; color: var(--accent); }
.feed-text strong { color: #fff; }

/* U5-2: dry-spell call-out + Shame button */
.feed-dryspell { border-left-color: #c99a2e; background: #241f16; }
.feed-dryspell .dryspell-weeks { font-weight: 700; color: #c99a2e; }
.shame-btn {
    margin-top: 8px; padding: 5px 14px; font-size: 0.8rem; font-weight: 700;
    color: #fff; background: #7a3ea1; border: none; border-radius: 6px; cursor: pointer;
    transition: background .15s ease, transform .1s ease;
}
.shame-btn:hover:not(:disabled) { background: #9450c4; }
.shame-btn:active:not(:disabled) { transform: translateY(1px); }
.shame-btn:disabled { background: #3a3a3a; color: #8a8a8a; cursor: default; }
.shame-roast {
    margin-top: 7px; font-size: 0.82rem; font-style: italic; color: #f0a63c;
    max-height: 0; opacity: 0; overflow: hidden; transition: max-height .3s ease, opacity .3s ease;
}
.shame-roast.shown { max-height: 120px; opacity: 1; }
/* A shame RECEIVED — visually distinct from the amber "someone else is dry"
   call-out, because this one is about you. Red accent, no button. */
.feed-shamed { border-left-color: var(--danger); background: #2a1a1a; }
.feed-shamed .dryspell-weeks { color: #e07a6a; }

.lb-row { display: grid; grid-template-columns: 2fr 1.5fr 1fr 1fr 1.3fr; align-items: center; gap: 8px; padding: 15px; border-bottom: 1px solid #333; }
.lb-row:last-child { border-bottom: none; }
.lb-rank-name { font-size: 1.2rem; font-weight: bold; }
.lb-score { font-size: 1.2rem; font-weight: bold; color: var(--accent); }
.rank-1 .lb-rank-name, .rank-1 .lb-score, .rank-1 .lb-total { color: var(--warning); font-size: 1.4rem;}
.lb-header { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-muted); opacity: 0.8; }
.lb-col { text-align: right; font-size: 0.85rem; color: var(--text-muted); }
.lb-location { text-align: left; font-size: 0.8rem; color: var(--text-muted); }
.lb-total { font-weight: 800; font-size: 1.05rem; color: var(--accent); }
.lb-wl { font-size: 0.68rem; color: var(--text-muted); margin-left: 8px; }
.lb-wl-inline { display: block; font-size: .6rem; color: #555; margin-top: 1px; }
.lb-you { background: rgba(46, 204, 113, 0.08); }

.dev-cheat-btn { background: #ff00ff; color: #fff; border: 2px solid #fff; padding: 8px 15px; border-radius: 8px; font-weight: 900; cursor: pointer; box-shadow: 0 0 10px rgba(0,0,0,0.5); text-transform: uppercase; font-size: 0.8rem; transition: 0.2s;}
.dev-cheat-btn:hover { transform: scale(1.05); }

/* THE ARENA */
.arena-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #333; padding-bottom: 15px; margin-bottom: 20px; }
/* flex:1 so this fills the header's remaining width (the dev-button group to its
   left is empty/hidden in production). */
.arena-header-actions { display: flex; flex: 1; gap: 15px; align-items: center; flex-wrap: wrap; }
.hs-btn-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; justify-content: flex-start; }

/* Cash/Pelts readout — plain text, non-interactive. No border/background/hover:
   this is a display value, not a button. margin-left:auto (not justify-content:
   space-between on the parent) so it hugs the right edge of whichever flex LINE
   it ends up on — on narrow widths it wraps to its own line below the button
   row, and space-between would otherwise strand a lone wrapped item at the left. */
.homestead-readout { display: flex; gap: 16px; margin-left: auto; font-size: 1rem; font-weight: 700; white-space: nowrap; }
.homestead-readout .hr-cash  { color: var(--warning); }
.homestead-readout .hr-pelts { color: #c8a44a; }

/* Homestead secondary buttons: unified neutral style (Remy/Pippa/Outpost/Battle
   Rules previously carried divergent per-sprint inline colors) — full text+icon
   on desktop, icon-only on mobile. Invade (.hs-cta) is intentionally NOT part of
   this family; it stays the loud primary CTA. */
.hs-sec-btn {
    display: inline-flex; align-items: center; gap: 6px;
    background: #1a1a1a; border: 1px solid #333; color: #ccc;
    transition: 0.15s;
}
.hs-sec-btn:hover { border-color: #555; color: #fff; }
.hs-sec-btn .hs-icon { font-size: 1rem; line-height: 1; }

.arena-layout { display: flex; gap: 30px; align-items: flex-start; }
.arena-left { flex: 1; min-width: 0; }
.arena-right { width: 300px; flex-shrink: 0; display: flex; flex-direction: column; gap: 15px; }
@media (max-width: 900px) {
    .arena-layout { flex-direction: column; }
    .arena-right { width: 100%; }
    /* align-items:flex-start would let arena-left size to the deck's min-content
       (overflowing cards) and drag the whole column wider than the viewport —
       pin both columns to the container width in stacked mode. */
    .arena-left { width: 100%; min-width: 0; }
    /* The always-visible inline card inspector is desktop-only — mobile uses the
       tap popup (openCardFocusModal) exclusively. Without this, both showed at
       once on mobile, wasting vertical space and duplicating the same info. */
    #focus-card, #focus-actions { display: none !important; }
}

.list-section { margin-bottom: 25px; }
.section-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; padding-bottom: 8px; margin-bottom: 12px; }
.section-header h3 { margin: 0; color: #fff; font-size: 1.1rem; }
.deck-stats { font-size: 0.85rem; color: var(--text-muted); font-weight: bold; }

/* REVERTED MOD SLOTS (Wide Banners) */
.mod-slots { display: flex; gap: 15px; }
.mod-slot { flex: 1; padding: 15px; border: 2px dashed #444; border-radius: 8px; text-align: center; color: #666; font-size: 0.85rem; background: rgba(0,0,0,0.2); transition: 0.2s; min-height: 60px; display: flex; flex-direction: column; justify-content: center; align-items: center; box-sizing: border-box;}
.mod-slot.drag-over { border-color: var(--accent); background: rgba(46, 204, 113, 0.1); color: var(--accent); transform: scale(1.02); }
.mod-slot.filled { border: 2px solid #3498db; background: rgba(52,152,219,0.1); color: #fff; cursor: pointer; padding: 10px; flex-direction: row; gap: 10px;}
/* AA4 ROOT CAUSE. This rule carried `pointer-events: none` and
   `cursor: default`, added by commit 2403698 ("Visual bug batch … locked mod
   slots"), which PREDATES Y1. Y1 later attached
   `locked.onclick = () => showLockedSlotPopup(locked)` in buildLockedModSlot —
   a handler this rule made permanently unreachable. So the locked ACTIVE slot
   has never popped, from the moment Y1 shipped; nothing regressed it later.
   The locked STORAGE slot uses .inventory-card-locked instead, which has
   `cursor: pointer` and no pointer-events override — which is exactly why the
   explainer fired on inactive and not active.
   Now mirrors .inventory-card-locked: still dimmed, but tappable. */
.mod-slot-locked { opacity: 0.35; border: 1px dashed #3a3a3a !important; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.mod-slot-locked:hover { opacity: 0.6; border-color: var(--warning) !important; }

/* AA5: the ghost chip that flies between the active slots and the storage row on
   every equip / unequip / auto-equip / swap. position:fixed + a transitioned
   left/top/width/height, driven from real measured rects, so it lands exactly on
   the destination whatever the layout. pointer-events:none — it must never eat a
   tap on the slot it's flying over. */
.mod-fly-ghost {
    position: fixed; z-index: 9700; pointer-events: none;
    display: flex; align-items: center; gap: 6px; overflow: hidden;
    padding: 0 8px; box-sizing: border-box;
    background: #16283d; border: 2px solid #3498db; border-radius: 10px;
    box-shadow: 0 6px 20px rgba(0,0,0,.55), 0 0 14px rgba(52,152,219,.45);
    transition: left .42s cubic-bezier(.34,1.3,.5,1), top .42s cubic-bezier(.34,1.3,.5,1),
                width .42s ease, height .42s ease, opacity .42s ease;
    opacity: 0;
}
.mod-fly-ghost.go { opacity: 1; }
.mod-fly-ghost .mfg-icon { font-size: 1.1rem; flex-shrink: 0; }
.mod-fly-ghost .mfg-name {
    font-size: .7rem; font-weight: 700; color: #fff; white-space: nowrap;
    overflow: hidden; text-overflow: ellipsis;
}

/* AA6: the swap notice. Positioned in JS onto #mod-slots-container instead of
   the screen edge, because a corner toast put the sentence describing the swap
   in a different part of the screen from the chips performing it. Delayed ~360ms
   so it appears as the ghosts land — one moment, not two.
   pointer-events:none for the same reason as the ghost: it must not eat a tap on
   a slot it is sitting over. */
.mod-swap-notice {
    position: fixed; z-index: 9750; pointer-events: none;
    max-width: min(92vw, 340px);
    padding: 9px 14px; box-sizing: border-box;
    background: #10151d; color: #efe7d2;
    border: 1px solid #3d5a7a; border-radius: 10px;
    font-size: .78rem; font-weight: 600; line-height: 1.3; text-align: center;
    box-shadow: 0 8px 26px rgba(0,0,0,.65), 0 0 16px rgba(52,152,219,.28);
    opacity: 0; transform: translateY(6px) scale(.96);
    transition: opacity .22s ease, transform .22s cubic-bezier(.34,1.4,.5,1);
}
.mod-swap-notice.show { opacity: 1; transform: translateY(0) scale(1); }
/* R5: temporary third slot (Master Tracker consumable) — gold glow + battles-left badge */
.mod-slot-temp { border-color: #f1c40f !important; box-shadow: 0 0 12px rgba(241,196,15,0.35) !important; animation: temp-slot-glow 2s ease-in-out infinite; position: relative; }
.mod-slot-temp.empty { border-style: dashed; color: #f1c40f; }
@keyframes temp-slot-glow {
    0%, 100% { box-shadow: 0 0 8px rgba(241,196,15,0.25); }
    50%      { box-shadow: 0 0 16px rgba(241,196,15,0.55); }
}
.mod-slot-temp-badge { position: absolute; top: -8px; right: -6px; background: #f1c40f; color: #000; font-size: 0.62rem; font-weight: bold; padding: 2px 6px; border-radius: 8px; white-space: nowrap; box-shadow: 0 1px 4px rgba(0,0,0,0.5); }
.mod-lock-icon { font-size: 1.4rem; filter: grayscale(1); }

.inventory-bar { padding: 15px; border: 1px solid #333; border-radius: 8px; background: #151515; min-height: 50px; display: flex; flex-direction: column; gap: 14px; }
/* Type-zoned inventory: one band per item.type category, each with its own
   subtle tint so mods/bait/gear/consumables read as distinct groups instead of
   one undifferentiated row. A zone with no items simply isn't rendered — the
   layout height comes entirely from what's actually equipped. */
.inventory-zone { border-radius: 7px; padding: 10px 10px 12px; }
.inventory-zone-label { font-size: 0.68rem; font-weight: 800; letter-spacing: 1.2px; text-transform: uppercase; margin-bottom: 8px; opacity: 0.75; }
.inventory-zone-items { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.zone-mods        { background: linear-gradient(135deg, rgba(52,152,219,0.10) 0%, rgba(52,152,219,0.02) 100%); }
.zone-mods        .inventory-zone-label { color: #5dade2; }
.zone-bait        { background: linear-gradient(135deg, rgba(230,126,34,0.10) 0%, rgba(230,126,34,0.02) 100%); }
.zone-bait        .inventory-zone-label { color: #eb984e; }
.zone-gear        { background: linear-gradient(135deg, rgba(46,204,113,0.10) 0%, rgba(46,204,113,0.02) 100%); }
.zone-gear        .inventory-zone-label { color: #58d68d; }
.zone-consumables { background: linear-gradient(135deg, rgba(155,89,182,0.10) 0%, rgba(155,89,182,0.02) 100%); }
.zone-consumables .inventory-zone-label { color: #bb8fce; }
.inventory-card { background: #1a1a1a; border: 1px solid #555; padding: 8px 12px; border-radius: 6px; display: flex; align-items: center; gap: 8px; cursor: pointer; transition: 0.2s; user-select: none;}
.inventory-card:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
.inventory-card.active { border-color: var(--accent); background: rgba(46, 204, 113, 0.05); }
.inventory-card.dragging { opacity: 0.5; border: 1px dashed var(--warning); }
.inventory-card-empty { background: transparent; border: 1px dashed #444; color: #666; cursor: default; opacity: 0.7; }
.inventory-card-empty:hover { transform: none; box-shadow: none; }
.inventory-card-empty .inv-name { color: #666; font-weight: normal; }
.inv-icon { font-size: 1.2rem; }
.inv-name { font-size: 0.85rem; font-weight: bold; color: #ccc;}
.bait-timer { background: #e74c3c; color: #fff; font-size: 0.7rem; padding: 2px 5px; border-radius: 4px; margin-left: 5px; font-weight: bold;}

.warning-banner { background: rgba(231, 76, 60, 0.1); border-left: 4px solid var(--danger); padding: 10px 15px; font-size: 0.85rem; color: #ccc; margin-bottom: 15px; border-radius: 4px; }

.reserve-list { display: flex; flex-direction: column; gap: 8px; max-height: 400px; overflow-y: auto; padding-right: 5px;}
/* Highlight while a strike-deck card is being dragged over the reserve zone (return-to-reserve) */
.reserve-list.deck-drag-over { background: rgba(46,204,113,0.06); border-radius: 10px; outline: 1px dashed rgba(46,204,113,0.3); }
.reserve-list-item { background: #1a1a1a; border: 1px solid #333; border-radius: 8px; padding: 6px 10px 7px; display: flex; flex-direction: column; align-items: stretch; gap: 4px; cursor: default; transition: 0.2s; user-select: none; }
.reserve-list-item:hover { background: #222; border-color: #555; }
.reserve-list-item.active { border-color: var(--accent); background: rgba(46, 204, 113, 0.05); }
.rli-emoji { font-size: 1.5rem; }
.rli-name { font-weight: bold; color: #fff; font-size: 1.1rem; }

/* RESERVE AURA GLOW */
.reserve-list-item.is-aura { border-color: #2ecc71; box-shadow: inset 0 0 10px rgba(46,204,113,0.1); }
.reserve-aura-label { font-size: 0.6rem; background: #2ecc71; color: #000; padding: 2px 4px; border-radius: 4px; font-weight: bold; margin-left: 10px; text-transform: uppercase; }

/* RESERVE COLUMN LAYOUT */
.rli-animal-header { display: flex; align-items: center; gap: 10px; pointer-events: none; margin-bottom: 3px; }
.rli-wgt { font-size: 0.8rem; color: var(--text-muted); margin-left: auto; }
.rli-tier-columns { display: flex; gap: 8px; pointer-events: auto; }
.rli-tier-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 2px; min-height: 46px; border-radius: 6px; cursor: pointer; border: 1px solid transparent; transition: 0.15s; }
.rli-tier-col:hover { background: rgba(255,255,255,0.04); border-color: #444; }
.rli-col-label { font-size: 0.65rem; font-weight: bold; text-transform: uppercase; letter-spacing: 0.05em; }
.rli-tier-base .rli-col-label { color: #888; }
.rli-tier-alpha .rli-col-label { color: #f1c40f; }
.rli-tier-apex .rli-col-label {
    /* Same prismatic-foil text treatment as the card's own APEX badge —
       distinctly "shiny", not just Alpha's flat gold recolored blue. */
    background-image: linear-gradient(124deg, #ff2400, #e8b71d, #1de840, #1ddde8, #2b1de8, #dd00f3, #ff2400);
    background-size: 400% 400%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: rainbowFoil 3s linear infinite;
    filter: drop-shadow(0 0 1px rgba(0,0,0,0.7));
}
.rli-empty-col { font-size: 0.7rem; color: #444; line-height: 30px; }
/* S1-2: a tier the player owns zero of — inert (no popup) and visibly dimmed
   so it reads as "nothing here" rather than a clickable column. */
.rli-tier-col.rli-tier-empty { cursor: default; opacity: 0.4; }
.rli-tier-col.rli-tier-empty:hover { background: transparent; border-color: transparent; }
.rli-tier-col.rli-tier-empty .rli-col-label { color: #555; }
.rli-card-stack { display: flex; flex-wrap: wrap; justify-content: center; gap: 2px; min-height: 30px; }
.mini-card { width: 22px; height: 30px; border-radius: 3px; background: linear-gradient(160deg, #1e3d1e, #2a5a2a); border: 1px solid #3a6a3a; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s; }
.mini-card:hover { transform: translateY(-3px); }
.mini-card-pine { font-size: 0.55rem; line-height: 1; font-family: 'Noto Color Emoji', sans-serif; }
.mini-card.mini-rabid { background: linear-gradient(160deg, #3d1e1e, #5a2a2a); border-color: #c0392b; }
.mini-card.mini-poached { border-color: #8e44ad; border-width: 2px; }
.mini-card.mini-rabid.mini-poached { background: linear-gradient(160deg, #3d1e1e, #5a2a2a); border-color: #8e44ad; }

/* ── Mini card modifier dot ── */
.mini-card { position: relative; }
.mini-card-dot { position: absolute; top: 1px; right: 1px; font-size: .35rem; color: #c8a44a; line-height: 1; pointer-events: none; }

/* Stamp dot — opposite corner from the weight-modifier dot above, so a card
   that's BOTH poach-modified AND stamped shows both indicators at once.
   One glyph covers either stamp type (Corporate Sponsor $ / Trapper's Brand
   pelt) — which one(s) apply is in the mini-card's title tooltip. */
.mini-card-dot-stamp { position: absolute; bottom: 1px; left: 1px; font-size: .4rem; color: #5ab5ed; line-height: 1; pointer-events: none; text-shadow: 0 0 2px rgba(0,0,0,0.9); }

/* ── Focus panel card navigator ── */
.focus-card-nav { display: flex; align-items: center; justify-content: center; gap: 10px; margin: 8px 0 4px; }
.fcn-btn { background: #1a1a1e; border: 1px solid #333; border-radius: 6px; color: #888; padding: 3px 10px; font-size: .78rem; cursor: pointer; }
.fcn-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.fcn-btn:disabled { opacity: .25; cursor: default; }
.fcn-count { font-size: .72rem; color: #555; min-width: 60px; text-align: center; }
.fcn-badges { display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; margin-top: 4px; }
.fcn-badge { display: inline-block; font-size: .6rem; font-weight: 800; letter-spacing: .8px; padding: 2px 6px; border-radius: 4px; }
.fcn-rabid   { background: rgba(192,57,43,.15);  color: #e74c3c; border: 1px solid #c0392b; }
.fcn-poached { background: rgba(155,89,182,.12); color: #9b59b6; border: 1px solid #9b59b6; }
.fcn-mod     { background: rgba(241,196,15,.10); color: #c8a44a; border: 1px solid #b8941a; }

.mod-durability { font-size: 0.58rem; color: #888; margin-top: 3px; text-align: center; }
.mod-durability.last-use { color: #f39c12; font-weight: 700; }


/* PACK OPENING - REPAIRED ANIMATION & STRICT CARD SIZES */
@keyframes popCard { to { opacity: 1; transform: scale(1) translateY(0); } }
/* Shared pack-opening cards — Remy's Trading Post AND Pippa's Market use the
   SAME popup (js/ui/uiPackOpen.js), so the two can't diverge. A real .trading-card
   (buildModCard / buildItemCard) scaled into a fixed wrapper, popped in on open.
   NO hover-scale: in-shop cards are read, not lifted like in-battle cards — the
   wrapper stays put on hover (the inner .trading-card's own :hover glow is enough
   feedback). Vertical/mobile-friendly: the flex grid wraps to a column when narrow. */
.pack-pick-grid {
    display: flex; flex-wrap: wrap; gap: 16px; justify-content: center;
    max-width: 860px; margin-bottom: 32px;
}
.pack-pick-wrap {
    width: 156px; height: 219px; position: relative; flex-shrink: 0;
    cursor: pointer; opacity: 0;
    transform: scale(0.8) translateY(40px);
    animation: popCard 0.5s forwards cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.pack-pick-wrap .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.6); transform-origin: top left;
    box-shadow: -4px 6px 16px rgba(0,0,0,0.7);
}
.pack-pick-wrap.picked { opacity: 0.35; pointer-events: none; }
/* cardFx.packOpen: the spin replaces popCard, and CSS keeps the resting state (so .picked still dims) */
.pack-pick-wrap.fx-spun { opacity: 1; transform: none; animation: none; }
.pack-pick-wrap.fx-spun.picked { opacity: 0.35; }
.pack-pick-wrap.fx-spun.declined { opacity: .45; }
.pack-pick-stamp {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    font-weight: 900; font-size: 0.8rem; letter-spacing: 2px;
    background: rgba(0,0,0,0.5); border-radius: 12px; z-index: 5; pointer-events: none;
}

.pack-purchased-stamp {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-weight: 900; font-size: .75rem; letter-spacing: 2px;
    color: var(--accent); pointer-events: none;
    background: rgba(0,0,0,0.4); border-radius: inherit;
    z-index: 5;
}

/* MODALS & OVERLAYS */
#reward-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 10, 10, 0.95); z-index: 3000; flex-direction: column; justify-content: center; align-items: center; overflow: hidden;}
#reward-overlay.active { display: flex; }
.mult-item { background: #222; padding: 10px 20px; border-radius: 8px; font-weight: bold; font-size: 1.2rem; display: flex; justify-content: space-between; width: 280px; border-left: 4px solid var(--warning); box-shadow: 0 4px 15px rgba(0,0,0,0.5); opacity: 0; transform: translateX(-50px); position: relative;}
.mult-item.show { animation: slideMult 0.4s forwards cubic-bezier(0.175, 0.885, 0.32, 1.275); }
.mult-text { color: var(--text-main); }
.mult-val { color: var(--warning); }
.rew-total-box { text-align: center; margin-bottom: 20px;}
.rew-total-label { color: var(--text-muted); text-transform: uppercase; letter-spacing: 2px; font-size: 1rem;}
.rew-total { font-size: 5rem; font-weight: bold; color: var(--accent); text-shadow: 0 0 20px rgba(46, 204, 113, 0.5); font-family: 'Impact', sans-serif, 'Noto Color Emoji';}

/* ── The one cash/pelt reward float ───────────────────────────────────────
   Replaces `.floating-money`, which was position:absolute right:-40px inside a
   fixed 280px row: on a phone that drew it past the screen edge inside an
   `overflow:hidden` overlay (measured at 375px: 15px clipped at rest, 26px at
   the animation's scale(1.5) peak; at 320px, 42px and 53px). Centring with
   translate(-50%) makes clipping impossible at any width.
   Size and duration are both 2× the old treatment: 1.5rem -> 3rem, and 1s total
   with the fade from 500ms -> 2s total with the fade from 1000ms. `top` is a
   percentage so it sits in the same place on any screen height. */
.reward-float {
    position: fixed; left: 50%; top: 56%;
    z-index: 9995; pointer-events: none;
    display: flex; flex-direction: column; align-items: center;
    text-align: center; white-space: nowrap;
    animation: rewardFloatUp 2s cubic-bezier(.2,.8,.3,1) forwards;
}
.reward-float-amount {
    font-size: 3rem; font-weight: 900; line-height: 1;
    font-family: 'Impact', sans-serif, 'Noto Color Emoji'; letter-spacing: 1px;
}
.reward-float-sub {
    font-size: .8rem; font-weight: 700; margin-top: 4px;
    color: var(--text-muted); letter-spacing: .5px;
}
.reward-float--cash  .reward-float-amount { color: #2ecc71; text-shadow: 0 0 18px rgba(46,204,113,.65), 0 2px 0 #000; }
.reward-float--pelts .reward-float-amount { color: #d9a441; text-shadow: 0 0 18px rgba(217,164,65,.65), 0 2px 0 #000; }

/* Fade begins at 50% = 1000ms — that midpoint is what toast.js's
   REWARD_FLOAT_HOLD_MS mirrors. Vertical drift only; no horizontal travel,
   which is what used to carry the number off the side of a phone. */
@keyframes rewardFloatUp {
    0%   { opacity: 0; transform: translate(-50%, 24px) scale(.55); }
    12%  { opacity: 1; transform: translate(-50%, 0)    scale(1.18); }
    22%  { opacity: 1; transform: translate(-50%, -4px) scale(1); }
    50%  { opacity: 1; transform: translate(-50%, -34px) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -150px) scale(.9); }
}
/* Narrow phones: keep the number inside the screen even at 320px. */
@media (max-width: 380px) {
    .reward-float-amount { font-size: 2.5rem; }
}
.log-bonus-float {
    position: fixed; left: 50%; top: 40%;
    font-size: 2rem; font-weight: 800; color: var(--warning);
    z-index: 9990; pointer-events: none;
    animation: floatUp 1.8s ease-out forwards;
}
.log-bonus-float.streak-retro { color: #e67e22; text-shadow: 0 0 12px rgba(230,126,34,0.6); }
.retro-main { display: block; font-size: 1.1rem; font-weight: 800; }
.retro-sub  { display: block; font-size: 0.6rem; opacity: 0.75; letter-spacing: 0.5px; margin-top: 1px; }
.flash-gold { animation: battleFlash 1s ease-out; }

@keyframes battleFlash { 0%, 100% { background-color: transparent; box-shadow: none; } 50% { background-color: rgba(241, 196, 15, 0.3); box-shadow: 0 -5px 20px rgba(241, 196, 15, 0.8); } }
@keyframes dropIn { 0% { transform: scale(2) translateY(-50px); opacity: 0; } 100% { transform: scale(1) translateY(0); opacity: 1; } }
@keyframes slideMult { 0% { opacity: 0; transform: translateX(-50px); } 100% { opacity: 1; transform: translateX(0); } }
.shake-active { animation: shake 0.3s cubic-bezier(.36,.07,.19,.97) both; }
@keyframes shake { 10%, 90% { transform: translate3d(-2px, 0, 0); } 20%, 80% { transform: translate3d(4px, 0, 0); } 30%, 50%, 70% { transform: translate3d(-6px, 0, 0); } 40%, 60% { transform: translate3d(6px, 0, 0); } }
.pop-active { animation: popScale 0.3s ease-out; }
@keyframes popScale { 0% { transform: scale(1); } 50% { transform: scale(1.4); color: #fff;} 100% { transform: scale(1); } }

/* ═══════════════════════════════════════════════════════════════════
   LOG REVEAL (js/ui/logFx.js) — mounted inside #reward-overlay.
   Tuned in the Logging Lab (_logging-lab.html); the numbers live in logFx's
   LOG_FX. The card is SCALED to fit the total + every banner (the old
   #rew-card could shrink under its 364px card, so banners covered it).
   ═══════════════════════════════════════════════════════════════════ */
.ll-root { position: absolute; inset: 0; overflow: hidden; --ll-foil: 1; }
.ll-shake, .ll-layout { position: absolute; inset: 0; }
.ll-layout { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; }
.ll-total { margin: 0 0 6px; position: relative; }
.ll-slot { position: relative; flex: 0 0 auto; }
.ll-pose { position: absolute; left: 50%; top: 50%; width: 260px; height: 364px; transform-style: preserve-3d; opacity: 0; will-change: transform; }
.ll-face { position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.ll-face .trading-card { position: absolute; top: 0; left: 0; }
.ll-back { transform: rotateY(180deg); }
.ll-banners { display: flex; flex-direction: column; align-items: center; }   /* sized with CSS zoom (logFx layout): zoom frees layout space, a transform doesn't */
.ll-banners .mult-item { flex: 0 0 auto; }
.ll-canvas, .ll-fxdom { position: absolute; inset: 0; pointer-events: none; }
.ll-fxdom .fx-co { position: absolute; }
/* Side column: short landscape screens (the fix for 844×390) */
.ll-root[data-mode=side] .ll-layout { display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto;
    grid-template-areas: "card total" "card banners"; column-gap: 28px; row-gap: 4px; align-content: center; justify-content: center; align-items: end; }
.ll-root[data-mode=side] .ll-slot { grid-area: card; align-self: center; }
.ll-root[data-mode=side] .ll-total { grid-area: total; margin: 0; }
.ll-root[data-mode=side] .ll-banners { grid-area: banners; align-self: start; }
/* the automatic sway drives the hover glint on every device (uiCardEffects hides it without a mouse) */
.ll-root .glint-layer { transition: none; }
/* Foil speed (uiCardEffects' layers, same keyframes) */
.ll-root .holo-rainbow { animation-duration: calc(3.5s / var(--ll-foil)); }
.ll-root .holo-gold    { animation-duration: calc(4.5s / var(--ll-foil)); }
/* Banners: Anton on the value, like battle's numbers */
.ll-anton .mult-val { font-family: 'Anton', Impact, sans-serif, 'Noto Color Emoji'; font-weight: 400; letter-spacing: 1px; font-size: 1.35rem; }
.ll-streak { border-left-color: #fff; }
.ll-lv2 { border-left-color: #ffe678; } .ll-lv3 { border-left-color: #ffb43c; } .ll-lv4 { border-left-color: #ff6e96; } .ll-lv5 { border-left-color: #b478ff; }
/* The streak value is the SAME size as every other banner (Jim: bigger text
   stretched the banner). It glows in its level's colour instead, stronger
   with the level, and flares once as it lands — then holds, never loops.
   Readability (Jim: white text in a white glow was hard to read): the text
   takes the level colour, a tight dark edge keeps the letters crisp, and the
   glow sits OUTSIDE the letters, never white. */
.ll-streak { --lv-c: 241,196,15; --lv-g: .5; }   /* level 1: the plain gold of every other value */
.ll-lv2 { --lv-c: 255,230,120; --lv-g: .7; } .ll-lv3 { --lv-c: 255,180,60; --lv-g: .85; }
.ll-lv4 { --lv-c: 255,110,150; --lv-g: 1; }  .ll-lv5 { --lv-c: 180,120,255; --lv-g: 1.2; }
.ll-streak .mult-val { color: rgb(var(--lv-c));
    text-shadow: 0 0 2px rgba(0,0,0,.95), 0 1px 1px rgba(0,0,0,.9),
                 0 0 calc(14px * var(--lv-g)) rgba(var(--lv-c), .55), 0 0 calc(28px * var(--lv-g)) rgba(var(--lv-c), .3);
    animation: llValFlare .9s ease-out .15s both; }
@keyframes llValFlare {
    0%   { text-shadow: 0 0 2px rgba(0,0,0,.95), 0 1px 1px rgba(0,0,0,.9), 0 0 0 rgba(var(--lv-c), 0), 0 0 0 rgba(var(--lv-c), 0); }
    35%  { text-shadow: 0 0 2px rgba(0,0,0,.95), 0 1px 1px rgba(0,0,0,.9),
                        0 0 calc(22px * var(--lv-g)) rgba(var(--lv-c), .9), 0 0 calc(48px * var(--lv-g)) rgba(var(--lv-c), .6); }
}
/* Streak treatment 2: the aura pulse (grows with the level). OFF unless LOG_FX.streakAuraPulse
   (logFx only adds .ll-aura then); --swing = how far it breathes (LOG_FX.streakAuraSwing). */
.ll-total.ll-aura::before { content: ''; position: absolute; left: 50%; top: 60%; width: 260px; height: 140px; transform: translate(-50%, -50%);
    background: radial-gradient(ellipse, rgba(var(--glow-c), calc(.55 * var(--glow))), transparent 70%); pointer-events: none;
    animation: llAura var(--pulse, 1.2s) ease-in-out infinite; z-index: -1; }
@keyframes llAura { 0%, 100% { opacity: .55; scale: calc(1 - var(--swing, .15)); } 50% { opacity: 1; scale: calc(1 + var(--swing, .15)); } }
/* Streak treatment 3: the number heats up and jitters (Balatro, minus the fire) */
/* (the number's streak jitter is a settling burst driven by logFx — streakHeatMs) */
/* Alpha / Apex light rays */
.ll-rays { position: absolute; left: 50%; top: 50%; width: 900px; height: 900px; transform: translate(-50%, -50%); opacity: 0; pointer-events: none;
    background: repeating-conic-gradient(from 0deg, rgba(255,230,160,0) 0deg 8deg, rgba(255,230,160,.4) 11deg 13deg, rgba(255,230,160,0) 16deg 24deg);
    -webkit-mask-image: radial-gradient(circle, #000 18%, transparent 62%); mask-image: radial-gradient(circle, #000 18%, transparent 62%); }
.ll-rays.on { animation: llRays 2.6s ease-out forwards; }
@keyframes llRays { 0% { opacity: 0; transform: translate(-50%,-50%) rotate(0deg) scale(.6); } 15% { opacity: var(--ray-a, .7); } 100% { opacity: 0; transform: translate(-50%,-50%) rotate(40deg) scale(1.1); } }
/* BAIT SUCCESS! + tier unlock — battle's kill-tier callout weight (Anton, 5px stroke, glow) */
.fx-co.k-bait  { color: #ffe066; -webkit-text-stroke: 5px #6b2a00; text-shadow: 0 4px 0 #2a1000, 0 0 26px rgba(255,170,40,.9); }
.fx-co.k-bait .sub { color: #fff3c2; -webkit-text-stroke-color: #6b2a00; }
.fx-co.k-alpha { color: #ffd84a; -webkit-text-stroke: 5px #5a3b00; text-shadow: 0 4px 0 #2a1a00, 0 0 24px rgba(241,196,15,.9); }
.fx-co.k-apex  { color: #f5ecff; -webkit-text-stroke: 5px #3d1a66; text-shadow: 0 4px 0 #1a0833, 0 0 26px rgba(190,130,255,.95), 0 0 50px rgba(120,220,255,.6); }
.fx-co.k-alpha .sub, .fx-co.k-apex .sub { color: #fff; }
/* Bait OVERDRIVE (logFx baitOverdrive, LOG_FX.baitStyle = 'overdrive') */
.ll-dim { position: absolute; inset: 0; background: #000; opacity: 0; pointer-events: none; }
.ll-flash { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.ll-cardaura { position: absolute; left: 50%; top: 50%; width: 520px; height: 600px; transform: translate(-50%, -50%); border-radius: 50%; opacity: 0; pointer-events: none;
    background: radial-gradient(closest-side, rgba(var(--aura, 255,200,80), .95), rgba(var(--aura, 255,200,80), .4) 45%, transparent 78%); mix-blend-mode: screen; }
.ll-root.ll-green .ll-cardaura { mix-blend-mode: normal; }
.ll-root.ll-cardonly .ll-total, .ll-root.ll-cardonly .ll-banners { display: none; }
.fx-co.k-od-alpha { color: #ffd84a; -webkit-text-stroke: .07em #5a3b00; text-shadow: 0 .06em 0 #2a1a00, 0 0 .3em rgba(241,196,15,.9); }
.fx-co.k-od-apex { color: transparent; -webkit-text-stroke: 0; letter-spacing: 3px;
    background: linear-gradient(180deg, #fff 0%, #ffe3ff 30%, #b98cff 62%, #6fe3ff 100%); -webkit-background-clip: text; background-clip: text;
    filter: drop-shadow(0 .05em 0 #2a0a55) drop-shadow(0 0 .16em rgba(190,130,255,.95)) drop-shadow(0 0 .4em rgba(110,220,255,.6)); }
.fx-co.k-od-sub { color: #fff; -webkit-text-stroke: .09em #000; letter-spacing: 6px; text-shadow: 0 0 .45em rgba(190,130,255,.85); }
.fx-co.k-od-sub.k-od-gold { text-shadow: 0 0 .45em rgba(241,196,15,.85); }
/* Green-screen capture (labs only — the game never sets .ll-green): the Apex foil
   and tab carry a strong green that a chroma key would punch holes through.
   Same rainbow, greens removed. */
.ll-root.ll-green .trading-card.card-apex::before {
    background: linear-gradient(124deg, rgba(255,36,0,.38), rgba(232,183,29,.38), rgba(29,200,232,.38), rgba(43,29,232,.38), rgba(221,0,243,.38), rgba(255,36,0,.38));
    background-size: 400% 400%; }
.ll-root.ll-green .trading-card.card-apex > .tc-tier-tab span { background-image: linear-gradient(124deg, #ff2400, #e8b71d, #1dc8e8, #2b1de8, #dd00f3, #ff2400); }
.ll-root.ll-green .holo-rainbow { background: linear-gradient(var(--ha), transparent 20%, rgba(255,90,255,.16) 30%, rgba(90,190,255,.2) 45%, rgba(255,245,90,.13) 55%, rgba(195,90,255,.16) 70%, transparent 80%); }
@media (prefers-reduced-motion: reduce) {
    .ll-rays.on, .ll-total.ll-aura::before { animation: none; }
    .ll-streak .mult-val { animation: none; }   /* no flare; the steady glow stays */
}

.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 2000; justify-content: center; align-items: center; }
.modal-overlay.active { display: flex; }
.modal-content { background: var(--card-bg); padding: 25px; border-radius: 12px; text-align: center; width: 80%; max-width: 320px; border: 2px solid var(--accent); }
.modal-content input[type="date"] { width: 100%; padding: 12px; margin: 15px 0; border-radius: 5px; border: 1px solid #555; background: var(--bg-dark); color: white; font-size: 1.1rem; box-sizing: border-box; }
.modal-buttons { display: flex; gap: 10px; }
.modal-btn { flex: 1; padding: 12px; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; text-transform: uppercase;}
.btn-confirm { background: var(--accent); color: #000; }
.btn-cancel { background: #555; color: white; }

.ecosystem-report-item { margin-bottom: 10px; padding: 10px; background: #222; border-left: 3px solid var(--accent); border-radius: 4px; }
@media (max-width: 768px) {
    .layout-wrapper { flex-direction: column; }
    /* Bottom dock. safe-area-inset-bottom keeps the feed clear of the
       gesture-nav pill (Pixel etc.); box-sizing so the inset padding is part
       of the max-height, not added on top. Arrow is up/down here, not the
       desktop left/right — reset the desktop rotate. */
    .activity-sidebar { width: 100%; border-left: none; border-top: 2px solid #333;
        max-height: calc(250px + env(safe-area-inset-bottom, 0px)); box-sizing: border-box;
        padding-bottom: env(safe-area-inset-bottom, 0px); }
    .sidebar-header { flex-direction: row; padding: 15px; }
    .sidebar-header .feed-dock-arrow { transform: none; }
    .feed-header-label { writing-mode: horizontal-tb; }
    .activity-sidebar.feed-docked { max-height: calc(44px + env(safe-area-inset-bottom, 0px)); overflow: hidden; width: 100%; }
    .activity-sidebar.feed-docked .feed-container { display: flex; }   /* mobile collapses by height, not by hiding */
    .activity-sidebar.feed-docked .feed-dock-arrow { transform: rotate(180deg); }
}


/* ========================================================
   V17 FINAL UI POLISH: PERFECT HEIGHTS & ANIMATED DROPS
   ======================================================== */

.tier-tabs { display: flex; gap: 5px; margin-bottom: 5px; }
.tier-btn { flex: 1; background: #111; border: 1px solid #444; color: #444; padding: 8px 0; border-radius: 4px; font-weight: 900; font-size: 0.8rem; cursor: pointer; transition: 0.2s; opacity: 0.5;}

.tier-btn.owned-base { opacity: 1; color: var(--text-main); border-color: #555; }
.tier-btn.owned-alpha { opacity: 1; color: #f1c40f; border-color: #f1c40f; }
.tier-btn.owned-apex { opacity: 1; color: #3498db; border-color: #3498db; text-shadow: 0 0 5px rgba(52,152,219,0.5); }

.tier-btn.active { transform: scale(1.05); background: #222; opacity: 1; }
.tier-btn.owned-base.active { color: var(--accent); border-color: var(--accent); }
.tier-btn.owned-alpha.active { box-shadow: 0 0 10px rgba(241,196,15,0.4); background: rgba(241,196,15,0.1); }
.tier-btn.owned-apex.active { border-color: transparent; color: #fff; background: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3); background-size: 1800% 1800%; animation: rainbowFoil 3s ease infinite; text-shadow: 1px 1px 2px rgba(0,0,0,0.8); box-shadow: 0 0 15px rgba(52,152,219,0.5); }

.tc-obscured { filter: grayscale(100%) brightness(0.5); }

/* Tier button ownership/selection states */
.tier-btn-locked {
    opacity: 0.35 !important;
    cursor: not-allowed !important;
    filter: grayscale(1);
}
.tier-btn-selected {
    outline: 2px solid #fff;
    outline-offset: 2px;
}
.tier-btn-alpha {
    background: linear-gradient(135deg, #2a1f00, #3d2e00);
    border-color: #f1c40f;
    color: #f1c40f;
    box-shadow: 0 0 12px rgba(241,196,15,0.4);
}
.tier-btn-apex {
    background: linear-gradient(135deg, #0a1a2e, #0d2040);
    border-color: #3498db;
    color: #3498db;
    box-shadow: 0 0 12px rgba(52,152,219,0.4);
    animation: apexBtnPulse 2.5s ease-in-out infinite;
}
@keyframes apexBtnPulse {
    0%, 100% { box-shadow: 0 0 12px rgba(52,152,219,0.4); }
    50%       { box-shadow: 0 0 22px rgba(52,152,219,0.7); }
}
.tier-btn-selected.tier-btn-alpha {
    outline-color: #f1c40f;
    color: #ffd633;
    text-shadow: 0 0 6px rgba(255, 214, 51, 0.5);
}
.tier-btn-selected.tier-btn-apex {
    outline-color: #3498db;
    color: #5ab5ed;
    text-shadow: 0 0 6px rgba(90, 181, 237, 0.5);
}

/* BASE tier — owned */
.tier-btn-base-owned {
    border-color: #2ecc71;
    color: #2ecc71;
}

/* BASE tier — owned AND selected */
.tier-btn-base-owned.tier-btn-selected {
    background: linear-gradient(135deg, #0a2010, #0d2e18);
    border-color: #2ecc71;
    color: #5ee68e;
    text-shadow: 0 0 6px rgba(94, 230, 142, 0.5);
    box-shadow: 0 0 12px rgba(46, 204, 113, 0.35);
    outline: 2px solid #2ecc71;
    outline-offset: 2px;
}


/* TIGHTENED STRIKE DECK LAYOUT */
.strike-deck-unified {
    border: 2px dashed #444; border-radius: 12px;
    min-height: 220px; /* Squeezed down to hug the cards tightly */
    background: rgba(0,0,0,0.2); transition: 0.2s; position: relative;
    /* A column: the hand, then (phones, while moving a card) the numbered
       Move targets under it. The cards are placed by uiDeck.js. */
    display: flex; flex-direction: column; align-items: stretch; justify-content: center;
    margin-top: 15px; padding: 35px 15px 15px 15px; z-index: 1;
    /* INSET by the overhang (DECK_OVERHANG in rules/deckLayout.js — keep the
       two equal). The cards hang 16px past this dotted line, and the Homestead
       scrolls inside #arena-setup-scene (overflow-y: auto, which clips
       sideways too) whose edge was exactly the old dotted line — it cut the
       overhang off (Jim's screenshot). Pulling the zone in by 16px puts the
       overhang inside the scroll area on every screen. */
    margin-inline: 16px;
    /* visible (not hidden): a card lifted mid-drag follows the pointer and can
       extend past these bounds; hidden would clip it mid-gesture. */
    overflow: visible;
}
.strike-deck-unified.drag-over { border-color: var(--accent); background: rgba(46, 204, 113, 0.1); }

/* The hand: every card is absolutely placed by uiDeck.js (rules/deckLayout.js
   for where, ui/springs.js for how it gets there). JS sets the height. */
.strike-deck-hand { display: block; width: 100%; position: relative; z-index: 10; }

/* ONE centered row of full-size cards, card 1 in FRONT (Jim, 2026-09-25).
   The transform is written every frame by the spring, so nothing here may
   animate or transition it. That is what the old snapDrop entry animation got
   wrong: an animation with fill-mode forwards outranks an inline style, so it
   pinned every card at rotate(0) and the arc applyDeckArc computed never
   showed. The deck has been a flat line since. */
.sd-card-wrap {
    width: 150px;   /* 260 × 0.58 */
    height: 211px;  /* 364 × 0.58 */
    position: absolute; left: 0; top: 0;
    transform-origin: 50% 50%;
    will-change: transform;
    overflow: visible;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
}
@media (pointer: fine) { .sd-card-wrap { cursor: grab; } }
.sd-card-wrap * {
    user-select: none;
    -webkit-user-select: none;
}
.sd-card-wrap.sd-held { cursor: grabbing; }

/* CANONICAL TRADING CARD — 260×364px, scaled per context via transform */
.trading-card {
    background: #1e1e1e; border: 2px solid #555; border-radius: 12px; padding: 12px;
    width: 260px; height: 364px; box-sizing: border-box; display: flex; flex-direction: column;
    /* VISIBLE, not hidden (2026-09-25): the ALPHA/APEX tab hangs off the bottom
       edge (.tc-tier-tab). Nothing inside needs the card to clip for it — the
       photo zone, ability box and name each clip themselves, and the tier
       shimmers and holo/glint layers sit at inset:0 with inherited corners. */
    overflow: visible;
    transition: border-color 0.3s; position: relative;
}
.trading-card:hover { border-color: var(--accent); box-shadow: 0 10px 30px rgba(46, 204, 113, 0.15); }

#focus-card {
    margin: 0 auto 12px;
    /* inherits width:260px height:364px overflow:hidden from .trading-card */
}

.tc-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-shrink: 0;}
.tc-name { font-weight: bold; font-size: 1.2rem; color: #fff; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.tc-count { font-size: 1rem; font-weight: bold; color: #ccc; }
.tc-image-box { height: 30%; flex: none; flex-shrink: 0; position: relative; background: #111; border-radius: 8px; display: flex; justify-content: center; align-items: center; border: 1px solid #2a2a2a; box-shadow: inset 0 5px 15px rgba(0,0,0,0.5); overflow: hidden; }
.tc-emoji { font-size: 3.5rem; filter: drop-shadow(0 5px 5px rgba(0,0,0,0.5)); }
.tc-stats { display: flex; justify-content: space-between; font-weight: bold; font-size: 1.2rem; padding: 8px 5px 0 5px; flex-shrink: 0; }
.tc-hp { color: var(--danger); }
.tc-atk { color: #9b59b6; }
/* Pierce needle (cards.js, PIERCE_SVG from pierceIcon.js; an inline SVG, so no
   font dependency): just left of ATK, a soft warm glow behind it. Tappable on
   every card — the battle fighters included — through cardInfo.js's capture-phase
   handler, like ℹ️/🎥, so a tap explains Block + Pierce and never attacks. */
.tc-pierce-btn {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    width: 1.15em; height: 1.15em; margin-right: .12em; padding: 0; vertical-align: -0.12em;
    font-size: .82em; line-height: 1; cursor: pointer; border: none; border-radius: 50%;
    background: radial-gradient(circle, rgba(255,190,70,.55) 0%, rgba(255,150,40,.22) 45%, transparent 72%);
    filter: drop-shadow(0 1px 1px rgba(0,0,0,.8));
}
.tc-pierce-btn .pierce-icon { display: block; width: 1em; height: 1em; pointer-events: none; overflow: visible; }
.tc-pierce-btn::after { content: ''; position: absolute; inset: -6px -4px; }   /* a bigger touch zone than the drawn icon */
.tc-pierce-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.tc-pierce-btn.tc-badge-new { animation: piercePulse .7s ease-in-out 3; }
@keyframes piercePulse { 50% { transform: scale(1.35); background: radial-gradient(circle, rgba(255,210,90,.9) 0%, rgba(255,150,40,.4) 50%, transparent 75%); } }
@media (prefers-reduced-motion: reduce) { .tc-pierce-btn.tc-badge-new { animation: none; } }
.card-info-popup-pierce .cip-pierce p { margin: 0; line-height: 1.45; }
.card-info-popup-pierce .cip-title { letter-spacing: 1px; display: flex; align-items: center; gap: 5px; }
.card-info-popup-pierce .cip-title .pierce-icon { width: 1.25em; height: 1.25em; flex: none; }
.tc-ability { flex: 1; min-height: 0; margin-top: 6px; background: #000; color: #ccc; border-radius: 6px; padding: 6px 10px; font-size: 0.75rem; line-height: 1.24; border: 1px solid rgba(0,0,0,0.3); overflow: hidden; word-wrap: break-word; white-space: normal;}
.tc-ability-block + .tc-ability-block { margin-top: 0.4em; }

/* ── Card Shaders ── */

/* ── ALPHA CARD — subtle gold shimmer sweep ── */
.trading-card.card-alpha {
    background: linear-gradient(160deg, #1e1e1e 0%, #2a230a 60%, #1e1e1e 100%);
    box-shadow:
        0 0 18px rgba(241,196,15,0.35),
        inset 0 0 24px rgba(241,196,15,0.08);
    border-color: #f1c40f !important;
    overflow: visible;   /* see .trading-card — the tier tab hangs past the edge */
    position: relative;
}

/* Sweeping gold highlight — always on, slow and calm */
.trading-card.card-alpha::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(
        115deg,
        transparent           0%,
        transparent          35%,
        rgba(255,220,80,0.07) 45%,
        rgba(255,240,120,0.13) 50%,
        rgba(255,220,80,0.07) 55%,
        transparent          65%,
        transparent         100%
    );
    background-size: 250% 100%;
    animation: alphaGoldSweep 4s ease-in-out infinite;
    mix-blend-mode: screen;
    pointer-events: none;
    z-index: 1;
}

/* Keep card content above shimmer */
.trading-card.card-alpha > * {
    position: relative;
    z-index: 2;
}

@keyframes alphaGoldSweep {
    0%   { background-position: 200% 0; }
    100% { background-position: -50% 0; }
}

/* ── APEX CARD — full prismatic rainbow foil ── */
.trading-card.card-apex {
    position: relative;
    background: #1a1a1a;
    border-color: #3498db !important;
    box-shadow:
        0 0 28px rgba(52,152,219,0.55),
        0 0 60px rgba(155,89,182,0.2),
        inset 0 0 20px rgba(52,152,219,0.08);
    overflow: visible;   /* see .trading-card — the tier tab hangs past the edge */
}

/* Layer 1 — rotating rainbow foil */
.trading-card.card-apex::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(
        124deg,
        rgba(255, 36,  0, 0.38),
        rgba(232,183, 29, 0.38),
        rgba( 29,232, 64, 0.38),
        rgba( 29,221,232, 0.38),
        rgba( 43, 29,232, 0.38),
        rgba(221,  0,243, 0.38),
        rgba(255, 36,  0, 0.38)
    );
    background-size: 400% 400%;
    animation: rainbowFoil 3s linear infinite;
    mix-blend-mode: color-dodge;
    pointer-events: none;
    z-index: 1;
}

/* Layer 2 — diagonal specular glint sweep on top */
.trading-card.card-apex::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: repeating-linear-gradient(
        -55deg,
        transparent            0px,
        transparent            5px,
        rgba(255,255,255,0.04) 5px,
        rgba(255,255,255,0.04) 6px
    );
    background-size: 18px 18px;
    animation: apexGlintSlide 2.5s linear infinite;
    pointer-events: none;
    z-index: 2;
    mix-blend-mode: screen;
}

/* Card content stays above both effect layers */
.trading-card.card-apex > * {
    position: relative;
    z-index: 3;
}

@keyframes rainbowFoil {
    0%   { background-position:   0% 50%; }
    50%  { background-position: 100% 50%; }
    100% { background-position:   0% 50%; }
}

@keyframes apexGlintSlide {
    0%   { background-position:  0px  0px; }
    100% { background-position: 36px 36px; }
}

/* ── Tier overlay — lives inside .tc-image-box (bottom-left) ── */
/* ── Tier tab — ALPHA / APEX hangs off the card's BOTTOM edge (Jim, 2026-09-25) ──
   Centered, half on the card and half off it, so it can never collide with the
   name, the photo badges or the ability text. 10% larger than the mockup at
   Jim's request. "Alpha" is title case, "APEX" capitals (cardBadges.tierTabLabel).

   OPAQUE ON PURPOSE. The tab crosses the card's border and the page behind it,
   so it paints its own solid chip: nothing beneath it (border line, page,
   another card) can show through. Children always paint over their parent's
   own border, so the chip also covers the border where they meet.

   The selectors carry the tier class because the tier shimmers lift every
   direct child of the card with .trading-card.card-alpha > * { position:
   relative; z-index: 2 } — a two-class rule that would otherwise pull the tab
   back into the flow. */
.trading-card.card-alpha > .tc-tier-tab,
.trading-card.card-apex  > .tc-tier-tab {
    position: absolute;
    left: 50%;
    bottom: -11px;
    transform: translateX(-50%);
    z-index: 6;
    font-size: 0.7rem;
    font-weight: 900;
    line-height: 1.25;
    padding: 2px 12px;
    border-radius: 5px;
    background: #111;
    white-space: nowrap;
    pointer-events: none;
    box-shadow: 0 2px 6px rgba(0,0,0,0.6);
}
.trading-card.card-alpha > .tc-tier-tab {
    color: #f1c40f;
    letter-spacing: 1px;
    border: 1px solid rgba(241,196,15,0.85);
}
.trading-card.card-apex > .tc-tier-tab {
    letter-spacing: 1.5px;
    border: 1px solid rgba(52,152,219,0.85);
}
/* The APEX rainbow is clipped to the LETTERS (the inner span), so the tab keeps
   its solid chip. Same foil language as the card's own shader. */
.trading-card.card-apex > .tc-tier-tab span {
    background-image: linear-gradient(124deg, #ff2400, #e8b71d, #1de840, #1ddde8, #2b1de8, #dd00f3, #ff2400);
    background-size: 400% 400%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: rainbowFoil 3s linear infinite;
}
/* Three containers are sized exactly to their card AND clip, so a tab hanging
   past the edge would be cut in half. There it tucks just inside instead. */
.companion-card-wrap    .trading-card > .tc-tier-tab,
.sacrifice-card-preview .trading-card > .tc-tier-tab,
.silas-reward-wrap      .trading-card > .tc-tier-tab { bottom: 4px; }

/* ── Status tray — positioned inside .tc-image-box (top-right) ── */
.tc-image-box .tc-status-tray {
    position: absolute;
    top: 4px;
    right: 4px;
    z-index: 6;
}

/* ── Mod durability badge — top-right of .tc-image-box, mirroring the tier
   overlay's bottom-left corner. Distinct from .stat-mod (a stat buff/debuff
   chip): this is a countdown, so it reads as pips or a plain number rather
   than a colored +/- delta. Only mods with a fixed maxUses render one at
   all — see buildModCard in cards.js for the absent-by-default cases. */
.mod-durability-badge {
    position: absolute;
    top: 4px;
    right: 4px;
    z-index: 6;
    display: flex;
    gap: 2px;
    align-items: center;
    padding: 3px 5px;
    border-radius: 5px;
    background: rgba(0,0,0,0.75);
    border: 1px solid rgba(255,255,255,0.15);
    font-size: 0.6rem;
    font-weight: 800;
    color: #ddd;
    letter-spacing: 0.5px;
    pointer-events: none;
}
.mod-durability-badge.mod-durability-last {
    border-color: rgba(231,76,60,0.7);
    box-shadow: 0 0 6px rgba(231,76,60,0.5);
}
.mod-durability-pip {
    width: 6px; height: 6px;
    border-radius: 50%;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.3);
}
.mod-durability-pip.filled {
    background: #2ecc71;
    border-color: #2ecc71;
    box-shadow: 0 0 4px rgba(46,204,113,0.7);
}
.mod-durability-badge.mod-durability-last .mod-durability-pip.filled {
    background: #e74c3c;
    border-color: #e74c3c;
    box-shadow: 0 0 4px rgba(231,76,60,0.8);
}

/* STRIKE DECK — canonical card scaled 0.58× inside the wrapper */
.sd-card-wrap .trading-card {
    position: absolute;
    top: 0; left: 0;
    transform: scale(0.58);         /* 150 / 260 */
    transform-origin: top left;
    box-shadow: -5px 5px 15px rgba(0,0,0,0.8);
}

/* The card under the pointer, and the card a phone's Move has picked up. Hover
   lift, the gap the neighbours open, the lean and the snap into place are all
   spring motion (uiDeck.js); only the ring + shadow are CSS. */
.sd-card-wrap.sd-held .trading-card { box-shadow: 0 34px 52px rgba(0,0,0,.72), 0 0 0 3px rgba(46,204,113,.75); }
.sd-card-wrap.sd-selected .trading-card { box-shadow: 0 22px 40px rgba(0,0,0,.7), 0 0 0 4px var(--warning); }
.sd-card-wrap.sd-held .attack-order-badge,
.sd-card-wrap.sd-selected .attack-order-badge { background: var(--accent); border-color: #0a4d26; color: #062a14; }
/* A 🌲 tile whose card is out in the hand (being dragged, or flying home). */
.mini-card.sd-picked { opacity: .2; }
/* A finger may press-and-hold a 🌲 tile to drag it (TOUCH_REORDER 'drag'):
   no iOS callout or text selection during the hold. */
.mini-card { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }

/* Phone reorder: Move (in the card popup) lifts the card and shows one
   numbered target per position, evenly spaced across the zone so each is a
   full finger wide however tightly the cards overlap. */
.deck-move-bar { display: grid; gap: 8px; margin-top: 12px; }
/* The `hidden` attribute alone loses to display:grid above (same trap as
   .beta-banner[hidden]); without this the "Tap a number to move" line and
   Cancel stayed on screen after the card was placed. */
.deck-move-bar[hidden] { display: none; }
.deck-move-hint { display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: .8rem; color: var(--text-muted); }
.deck-move-hint strong { color: #fff; }
.deck-move-cancel {
    font: inherit; font-size: .75rem; color: var(--text-muted); background: none;
    border: 1px solid #3a3a3a; border-radius: 6px; padding: 4px 10px; cursor: pointer;
}
.deck-move-pins { position: relative; height: 44px; }
.deck-move-pin {
    position: absolute; top: 2px; width: 40px; height: 40px; margin-left: -20px; border-radius: 50%;
    border: 2px dashed var(--warning); background: rgba(241,196,15,.08); color: var(--warning);
    font: inherit; font-weight: 900; font-size: .95rem; cursor: pointer; font-variant-numeric: tabular-nums;
}
.deck-move-pin.here { border-style: solid; background: var(--warning); color: #2b2200; }
.deck-move-pin:focus-visible, .deck-move-cancel:focus-visible { outline: 2px solid var(--warning); outline-offset: 2px; }

/* THE AURA BUBBLE */
.aura-top-bubble {
    position: absolute; top: -28px; left: 50%; transform: translateX(-50%);
    background: #2ecc71; color: #000; font-size: 0.75rem; font-weight: 900;
    padding: 4px 12px; border-radius: 12px; box-shadow: 0 0 12px rgba(46,204,113,0.8);
    border: 2px solid #1a1a1a; letter-spacing: 1px; z-index: 200; pointer-events: none;
    white-space: nowrap;
}

/* ORDER BADGE — absolute, outside the card in the top-right corner of .sd-card-wrap */
.attack-order-badge {
    position: absolute;
    top: -8px;
    right: -8px;   /* top-RIGHT: with card 1 in front, every later card's top-left is tucked under the card before it */
    width: 22px;
    height: 22px;
    background: #111;
    border: 2px solid #555;
    border-radius: 50%;
    font-size: 0.68rem;
    font-weight: 900;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 200;
    pointer-events: none;
}

/* Tighter Strike Deck Area */
.strike-deck-unified {
    min-height: 220px; /* Hugs the cards */
    padding: 30px 15px 15px 15px; /* Less negative space */
}

/* --- ARENA COMBAT UI --- */
.combat-board { display: flex; flex-direction: column; height: 100%; background: radial-gradient(circle at center, #1a1a1a 0%, #050505 100%); }
.combat-header { text-align: center; padding: 15px; border-bottom: 2px solid #333; background: #111; z-index: 10;}
.combat-zone { flex: 1; display: flex; justify-content: space-around; align-items: center; position: relative; overflow: hidden; padding: 20px;}

.combatant-side { display: flex; flex-direction: column; align-items: center; width: 220px; z-index: 5;}
.c-player-name { font-size: 1.5rem; font-weight: bold; text-transform: uppercase; margin-bottom: 10px; color: var(--text-muted);}
.battle-wl-badge {
    font-size: .68rem; color: #666; margin-left: 6px;
    display: inline-block; text-transform: none; font-weight: 600;
}
.wl-flash { color: var(--accent); font-weight: 700; animation: wlFlash 0.6s ease 2; }
@keyframes wlFlash { 0%,100%{opacity:1;} 50%{opacity:.2;} }

/* Combat Cards */
.combat-card-wrap { position: relative; }
.combat-card { 
    width: 200px; height: 280px; background: var(--card-bg); border: 4px solid #555; border-radius: 12px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    box-shadow: 0 10px 30px rgba(0,0,0,0.8); transition: all 0.3s;
}
.combat-card.empty { border-style: dashed; background: transparent; box-shadow: none; opacity: 0.3; }
.cc-icon { font-size: 5rem; text-shadow: 0 4px 10px rgba(0,0,0,0.5); }
.cc-name { font-size: 1.5rem; font-weight: bold; margin-top: 10px; text-transform: uppercase; text-shadow: 1px 1px 2px #000; text-align: center;}
.cc-stats { display: flex; gap: 15px; margin-top: 10px; font-size: 1.2rem; font-weight: bold;}

/* The "Tap to Attack" Pulse */
.ready-to-tap { cursor: pointer; animation: tapPulse 1.5s infinite; border-color: var(--accent) !important; }
@keyframes tapPulse { 0%, 100% { box-shadow: 0 0 15px var(--accent); transform: scale(1); } 50% { box-shadow: 0 0 30px var(--accent); transform: scale(1.05); } }

/* Health Bars */
.hp-bar-overheal {
    position: absolute; top: 0; left: 100%; height: 100%;
    background: #3498db; /* Blue for bonus HP — starts at end of green bar */
    border-radius: 0 10px 10px 0;
    transition: width 0.3s ease, left 0.3s ease; z-index: 2;
}
.hp-bar-container { width: 100%; height: 20px; background: #222; border-radius: 10px; margin-top: 15px; overflow: hidden; border: 2px solid #000; position: relative;}
.hp-bar-fill { height: 100%; background: var(--accent); transition: width 0.3s ease, background-color 0.3s;}
.hp-text {
    text-align: center;
    font-size: 0.82rem;
    font-weight: bold;
    color: #fff;
    text-shadow:
        1px  1px 2px #000,
       -1px -1px 2px #000,
        1px -1px 2px #000,
       -1px  1px 2px #000;
    margin-bottom: -19px; /* pull down so it overlaps the 20px bar */
    position: relative;
    z-index: 10;
    pointer-events: none;
    line-height: 20px;   /* match bar height */
}

/* Combat Log */
.battle-log { height: 150px; background: #0a0a0a; border-top: 2px solid #333; overflow-y: auto; padding: 10px 20px; font-family: monospace, 'Noto Color Emoji'; font-size: 0.9rem; color: #aaa;}
.log-line { border-bottom: 1px solid #222; padding: 4px 0; }
.log-line.p1 { color: #3498db; }
.log-line.p2 { color: #e74c3c; }
.log-line.sys { color: var(--warning); font-weight: bold; }

/* --- ANIMATIONS & VFX --- */
.fct {
    position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);
    font-size: 2.5rem; font-weight: bold; font-family: 'Impact', sans-serif, 'Noto Color Emoji';
    pointer-events: none; z-index: 100; text-shadow: 2px 2px 0 #000, 0 0 10px rgba(0,0,0,0.8);
    animation: floatUp 1.2s forwards cubic-bezier(0.175, 0.885, 0.32, 1.275);
    /* U4-4: short numeric overlays ("⬡ BLOCKED 1", "-2 HP", "DODGED!") must
       never wrap a trailing number onto its own line — the general rule is
       nowrap. The two variants that carry genuinely long text (companion
       .ability lines and the .rabiesdeath banner) opt back into wrapping with
       a bounded width below. */
    white-space: nowrap;
}
.fct.dmg { color: var(--danger); font-size: 3.5rem; }
.fct.heal { color: var(--accent); }
/* Companion/ability text can be long ("🩸 +1 ATK / +2 HP (Deer in Deck)") —
   opt back into wrapping within a tidy column so it never runs off the arena
   (esp. mobile). Short ability text still fits on one line. */
.fct.ability { color: var(--warning); font-size: 1.5rem; white-space: normal; max-width: 15rem; text-align: center; }
/* Gold BLOCK feedback (Thick Hide) */
/* Gold BLOCK feedback (Thick Hide etc.) — chip background + border makes it
   unambiguous that damage was stopped, not just another number floating by. */
.fct.block {
    color: #f1c40f; font-size: 2.1rem; font-weight: 900; letter-spacing: 1px;
    background: rgba(0,0,0,.55); border: 3px solid #f1c40f; border-radius: 10px;
    padding: 2px 14px;
    text-shadow: 0 0 8px #000, 0 0 20px rgba(241,196,15,.9), 0 0 34px rgba(241,196,15,.55);
}
/* Impact ring on the defending card — a "hit something solid" beat, visually
   distinct from .anim-shake (which reads as taking real damage) so blocking
   never looks like getting hit. */
@keyframes blockImpactRing {
    0%   { box-shadow: 0 0 0 0 rgba(241,196,15,.9); filter: brightness(1); }
    35%  { box-shadow: 0 0 0 16px rgba(241,196,15,0); filter: brightness(1.7); }
    100% { box-shadow: 0 0 0 0 rgba(241,196,15,0); filter: brightness(1); }
}
.card-block-impact { animation: blockImpactRing .5s ease-out; }
.block-spark {
    position: fixed; pointer-events: none; z-index: 9300; border-radius: 50%;
    background: radial-gradient(circle, #fff9e0 0%, #f1c40f 55%, transparent 80%);
}
/* Red chip/parasite damage — small so short "-N HP" strings stay tidy */
.fct.reddmg { color: var(--danger); font-size: 1.5rem; font-weight: 900; letter-spacing: .5px; text-shadow: 0 0 8px rgba(231,76,60,0.6), 2px 2px 0 #000; }
/* ☣ Rabies permanent-death banner — deliberately its OWN class (not .reddmg,
   which chip/parasite damage also uses) so this can be big and urgent without
   also blowing up that shared small text. Fixed width + wrap keeps the long
   string readable instead of overflowing far past the card. */
.fct.rabiesdeath {
    color: var(--danger); font-size: 2.15rem; font-weight: 900; letter-spacing: .5px;
    line-height: 1.18; text-align: center; width: 230px; white-space: normal;
    text-shadow: 0 0 10px #000, 0 0 22px rgba(231,76,60,.95), 0 0 40px rgba(231,76,60,.7), 2px 2px 0 #000;
}
@keyframes floatUp { 0% { opacity: 0; transform: translate(-50%, 0) scale(0.5); } 20% { opacity: 1; transform: translate(-50%, -30px) scale(1.2); } 80% { opacity: 1; transform: translate(-50%, -60px) scale(1); } 100% { opacity: 0; transform: translate(-50%, -80px) scale(0.8); } }

/* Clashing */
.clash-left { animation: strikeLeft 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); z-index: 50; }
.clash-right { animation: strikeRight 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94); z-index: 50; }
@keyframes strikeLeft { 0%, 100% { transform: translateX(0) scale(1); } 50% { transform: translateX(-150px) scale(1.1) rotate(-5deg); } }
@keyframes strikeRight { 0%, 100% { transform: translateX(0) scale(1); } 50% { transform: translateX(150px) scale(1.1) rotate(5deg); } }

/* Getting Hit */
.anim-shake { animation: dmgShake 0.4s; }
@keyframes dmgShake { 0%, 100% { transform: translateX(0); filter: brightness(1); } 20%, 60% { transform: translateX(-10px) rotate(-3deg); filter: brightness(2) drop-shadow(0 0 20px red); border-color: red;} 40%, 80% { transform: translateX(10px) rotate(3deg); filter: brightness(2); border-color: red;} }

.combat-card.dead { filter: grayscale(100%) brightness(0.3); transform: rotate(-15deg) translateY(20px) scale(0.9); border-color: #111; }
.combat-card.dead .cc-icon { opacity: 0.1; }
.tombstone { position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%); font-size: 5rem; z-index: 20; animation: dropIn 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }

/* Big hit screen flash */
.screen-flash-red {
    position: fixed; inset: 0;
    background: rgba(231, 76, 60, 0.3);
    z-index: 8000; pointer-events: none;
    animation: screenFlashFade 0.45s forwards;
}
@keyframes screenFlashFade {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

/* Heavy camera shake (big hits) */
@keyframes heavyShake {
    0%   { transform: translate(0, 0); }
    15%  { transform: translate(-5px, -3px); }
    30%  { transform: translate(5px, 3px); }
    45%  { transform: translate(-4px, 2px); }
    60%  { transform: translate(4px, -2px); }
    75%  { transform: translate(-2px, 1px); }
    100% { transform: translate(0, 0); }
}
.heavy-shake { animation: heavyShake 0.4s forwards; }

/* Death overlay — tombstone inside the card wrapper */
.death-overlay {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 2.8rem;
    background: rgba(0, 0, 0, 0.72);
    border-radius: inherit;
    z-index: 200;
    animation: deathAppear 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
    pointer-events: none;
}
@keyframes deathAppear {
    from { opacity: 0; transform: scale(0.4); }
    to   { opacity: 1; transform: scale(1); }
}

.status-badge { position: absolute; top: -15px; right: -15px; font-size: 2rem; animation: pulse 1.5s infinite; z-index: 20; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5));}

/* Triage Screen */
.triage-grid { display: flex; gap: 15px; flex-wrap: wrap; justify-content: center; margin-top: 15px;}
.triage-card { background: #222; border: 2px solid #444; border-radius: 8px; padding: 10px; width: 100px; text-align: center; }
.triage-card.resurrected {
    border-color: #9b59b6;
    background: linear-gradient(135deg, #1a0a2a, #2a1040);
    box-shadow: 0 0 12px rgba(155, 89, 182, 0.45);
    animation: resurrectPulse 1.5s ease-in-out 2;
}
@keyframes resurrectPulse {
    0%, 100% { box-shadow: 0 0 8px rgba(155,89,182,.4); }
    50%       { box-shadow: 0 0 20px rgba(155,89,182,.9); }
}
.triage-card.poached { border-color: #9b59b6; box-shadow: 0 0 15px rgba(155, 89, 182, 0.3); }

/* --- ARENA COMBAT LAYOUT OVERRIDES --- */
.combat-zone {
    display: flex; justify-content: center; align-items: stretch;
    gap: 60px; padding: 20px; flex: 1; position: relative; width: 100%; box-sizing: border-box;
    overflow: visible; /* companions fan outside column bounds — must not clip */
}

.combatant-side {
    flex: 1; display: flex; flex-direction: column; align-items: center;
    max-width: 480px; position: relative; z-index: 1;
    background: rgba(255, 255, 255, 0.02); border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: 16px; padding: 20px;
}

#comb-p1 { background: radial-gradient(circle at top center, rgba(52, 152, 219, 0.08) 0%, rgba(255,255,255,0.02) 60%); }
#comb-p2 { background: radial-gradient(circle at top center, rgba(231, 76, 60, 0.08) 0%, rgba(255,255,255,0.02) 60%); }

.zone-title {
    text-align: center; color: #888; font-size: 0.75rem; font-weight: bold; 
    letter-spacing: 2px; text-transform: uppercase; margin: 0 0 15px 0; width: 100%;
}

.combat-layout-wrap {
    display: flex; align-items: flex-start; justify-content: center; width: 100%;
}

.vs-text {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    font-size: 4rem; font-family: 'Impact', 'Noto Color Emoji'; color: rgba(255, 255, 255, 0.08); font-style: italic; z-index: 1; pointer-events: none;
}

/* --- COMPACT MOD RECTANGLES --- */
.combat-mod-zone {
    width: 100%; background: rgba(0, 0, 0, 0.25); border: 1px solid rgba(255, 255, 255, 0.05);
    border-radius: 10px; padding: 12px; margin-bottom: 15px; box-sizing: border-box;
}

.cb-mod-group {
    display: flex; justify-content: center; gap: 15px; width: 100%; min-height: auto;
}

.mod-rect-slot {
    flex: 1; max-width: 160px; height: 44px; background: rgba(40, 40, 40, 0.4);
    border: 2px dashed rgba(255, 255, 255, 0.1); border-radius: 6px;
    display: flex; align-items: center; justify-content: center;
    font-size: 0.8rem; font-weight: bold; color: rgba(255, 255, 255, 0.2);
    text-transform: uppercase; letter-spacing: 1px; position: relative;
    user-select: none; box-sizing: border-box; transition: all 0.2s ease;
    /* V1-2: flex-centres the label BOX, but a long name (e.g. "Stockholm
       Syndrome") wraps to 2 lines and the wrapped line defaults to left-aligned,
       reading as left-justified. Centre the text lines too. */
    text-align: center;
}

.mod-rect-slot.equipped {
    background: linear-gradient(135deg, #1e272e 0%, #0f1418 100%);
    border: 2px solid #3498db; color: #fff; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.4);
}

.mod-rect-slot.equipped:hover { border-color: #fff; transform: translateY(-2px); }

/* Hidden full-scale popup card */
.mod-rect-slot .trading-card-popup-container {
    position: absolute; top: 0; left: 0; width: 200px; height: 280px;
    transform: scale(0); transform-origin: top left; opacity: 0; pointer-events: none;
    z-index: 5000; transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.25s ease;
}

/* Player manual zoom */
.mod-rect-slot.zoomed-active .trading-card-popup-container {
    position: fixed; top: 50% !important; left: 50% !important;
    transform: translate(-50%, -50%) scale(1.4) !important; opacity: 1;
    pointer-events: auto; box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.8), 0 25px 60px rgba(0, 0, 0, 0.95);
}


.mod-rect-slot .trading-card-popup-container .trading-card {
    position: absolute;
    top: 0; left: 0;
    transform: scale(0.769);        /* 200 / 260 */
    transform-origin: top left;
}

/* --- BATTLE CARDS — scale 0.846× (220/260) */
.main-fighter-column { display: flex; flex-direction: column; align-items: center; }
.combat-card-wrapper-main { width: 220px; height: 308px; position: relative; z-index: 20; }
.combat-card-wrapper-main .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.846);        /* 220 / 260 */
    transform-origin: top left;
    box-shadow: 0 10px 40px rgba(0,0,0,0.8);
}

/* ── Mobile battle chrome compression (R2) ───────────────────────────────────
   A landscape phone has ~360-430px of height. The OLD approach kept the full
   header chrome (black combat-header bar, 1.5rem player names, a padded
   "Habitat Mods" box, a 150px battle log) and instead SHRANK the fighter cards
   ~12% (220→180) to claw back room — and even then the card overflowed and
   scrolled, because ~256px (71% of a 360px viewport) was spent on chrome
   BEFORE the card even started.

   R2 inverts that: keep the cards at their FULL combat size (220×308) and
   collapse the redundant chrome instead. While a battle is on screen
   (body.in-battle) on a short viewport:
     • combat-header black bar → 0 height (cancel button floats, position:fixed)
     • player names → tiny
     • "Habitat Mods" label hidden, mod box padding minimized
     • combatant-side / combat-zone padding minimized
     • accessories (HP bar + piles) tightened
     • battle log → a short bottom strip instead of 150px
   Desktop (tall viewports) is untouched.

   S2-2: the trigger is now "short viewport OR (landscape AND phone-width)", so
   the compressed chrome engages on ANY landscape however it was reached — our
   own orientation lock OR the phone's native rotation control — not just when
   max-height happened to clear 520px. `and (max-width: 900px)` keeps desktops
   (which are landscape but wide) on the full layout. */
@media (max-height: 520px), (orientation: landscape) and (max-width: 900px) {
    body.in-battle header { display: none; }

    /* Reclaim the app container's 20px top padding — during battle the scene
       owns the whole viewport, and that 20px pushed the fighter card down. */
    body.in-battle .container { padding-top: 0; }

    /* Collapse the black header bar; keep the cancel button reachable by pinning
       it to the viewport corner (it was absolutely positioned inside this bar). */
    body.in-battle .combat-header { padding: 0; border: none; background: transparent; min-height: 0; }
    body.in-battle #btn-cancel-match {
        position: fixed; top: 4px; right: 4px; z-index: 200;
        padding: 3px 8px; font-size: 0.6rem;
    }

    /* Cards stay full size (220×308) — the whole point. The old 180×252 shrink
       is intentionally NOT applied here. */
    body.in-battle .combat-zone { padding: 3px 8px; gap: 12px; align-items: flex-start; }
    body.in-battle .combatant-side { padding: 1px 6px 3px; border-radius: 10px; }

    body.in-battle .c-player-name { font-size: 0.75rem; margin-bottom: 1px !important; }

    /* Mod box: drop the "Habitat Mods" label and its generous padding; the two
       slots still read fine on their own and now sit right above the card. */
    body.in-battle .combat-mod-zone { padding: 2px 6px; margin-bottom: 3px; }
    body.in-battle .combat-mod-zone .zone-title { display: none; }
    body.in-battle .cb-mod-group { gap: 8px; }
    body.in-battle .mod-rect-slot { height: 28px; font-size: 0.62rem; }

    body.in-battle .main-fighter-accessories { margin-top: 3px; gap: 3px; }
    body.in-battle .pile-fan { height: 40px; }

    /* Battle log: a short bottom strip, not 150px. Players watch the cards; the
       log is glanceable, and still scrolls internally for older lines. */
    body.in-battle .battle-log { height: 42px; padding: 4px 12px; font-size: 0.72rem; }
}
@media (max-width: 900px) and (orientation: portrait) {
    /* A battle playing in portrait (lock refused): strip the header so the
       scene gets the full viewport. */
    body.in-battle header { display: none; }
}

.main-fighter-accessories { margin-top: 15px; width: 220px; display: flex; flex-direction: column; gap: 10px; align-items: center; }
.main-fighter-accessories .hp-bar-container { width: 100%; position: relative; }

/* --- PILE ROW (draw + dead fanned stacks) --- */
.pile-row {
    display: flex; gap: 10px; align-items: flex-start; user-select: none;
    background: rgba(0,0,0,0.35); padding: 6px 10px; border-radius: 12px;
    border: 1px solid rgba(255,255,255,0.05);
}
.pile-wrap {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
}
.pile-fan {
    position: relative; height: 62px; cursor: default; min-width: 38px;
}
.pile-fan.clickable { cursor: pointer; }
.pile-fan.clickable:hover .pile-badge { border-color: rgba(255,255,255,0.5); }
.pile-badge {
    font-size: 0.75rem; font-weight: 700; color: #eee;
    background: rgba(0,0,0,0.55); padding: 1px 5px; border-radius: 9px;
    border: 1px solid rgba(255,255,255,0.15); min-width: 18px; text-align: center;
    transition: border-color 0.15s;
}
.pile-label { font-size: 0.6rem; color: #777; text-transform: uppercase; letter-spacing: 0.5px; }

/* Pile inspect overlay */
.pile-inspect-box {
    background: #111; border: 1px solid #333; border-radius: 14px;
    padding: 16px; max-width: 92vw; max-height: 80vh; overflow-y: auto;
    display: flex; flex-direction: column; gap: 12px;
}
.pile-inspect-title {
    font-size: 1rem; font-weight: 700; color: #eee; text-align: center;
    text-transform: uppercase; letter-spacing: 1px;
}
.pile-inspect-grid {
    display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
}
.pile-inspect-card {
    width: 54px; height: 72px; border-radius: 7px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    font-size: 1.3rem; border: 1px solid rgba(255,255,255,0.2); cursor: default;
}
.pile-inspect-card.draw-card {
    background: linear-gradient(135deg, #1a3a5c 0%, #0d1f30 100%);
}
.pile-inspect-card.dead-card {
    background: linear-gradient(135deg, #3a1a1a 0%, #1f0d0d 100%);
    border-color: rgba(231,76,60,0.4); cursor: pointer;
}
.pile-inspect-card.dead-card:hover {
    border-color: rgba(231,76,60,0.85); box-shadow: 0 0 8px rgba(231,76,60,0.4);
}
.pile-inspect-hint {
    font-size: 0.65rem; color: #555; text-align: center; margin-top: 4px;
}

/* Flying ghost card animations (append to .pile-fan which has position:relative) */
@keyframes pileGhostDraw {
    0%   { opacity: 0.9; transform: translateY(0)    scale(1);   }
    60%  { opacity: 0.7; transform: translateY(-30px) scale(0.9); }
    100% { opacity: 0;   transform: translateY(-60px) scale(0.7); }
}
@keyframes pileGhostDeath {
    0%   { opacity: 0.9; transform: translateY(0)   scale(1);   }
    100% { opacity: 0;   transform: translateY(30px) scale(0.8); }
}
.pile-ghost {
    position: absolute; width: 38px; height: 50px; border-radius: 5px;
    display: flex; align-items: center; justify-content: center;
    font-size: 1.3rem; pointer-events: none; z-index: 20;
    border: 1px solid rgba(255,255,255,0.3);
}
.pile-ghost.ghost-draw {
    bottom: 0; left: 0;
    background: linear-gradient(135deg, #1a3a5c 0%, #0d1f30 100%);
    animation: pileGhostDraw 0.38s ease-out forwards;
}
.pile-ghost.ghost-death {
    bottom: 0; left: 0;
    background: linear-gradient(135deg, #3a1a1a 0%, #1f0d0d 100%);
    animation: pileGhostDeath 0.38s ease-in forwards;
}

.tc-status-tray { position: absolute; top: 5px; right: 5px; display: flex; gap: 3px; z-index: 10; }
.status-tray-icon {
    font-size: 0.85rem; background: rgba(0, 0, 0, 0.7); width: 18px; height: 18px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.2); animation: statusPulse 1.5s infinite ease-in-out;
}
@keyframes statusPulse {
    0%, 100% { transform: scale(1); filter: drop-shadow(0 0 2px rgba(0,0,0,0.5)); }
    50% { transform: scale(1.1); filter: drop-shadow(0 0 5px var(--danger)); }
}

.hp-bar-overheal { position: absolute; top: 0; left: 100%; height: 100%; background: #3498db; border-radius: 0 10px 10px 0; transition: width 0.3s ease, left 0.3s ease; z-index: 2; }

.ready-to-tap .trading-card { cursor: pointer; animation: cardGlow 1.5s infinite; }
@keyframes cardGlow {
    0%, 100% { box-shadow: 0 10px 30px rgba(0,0,0,0.8); border-color: var(--accent); }
    50% { box-shadow: 0 0 40px var(--accent); border-color: #fff; }
}

.anim-trigger-glow { animation: cardTrigger 1s forwards; }
@keyframes cardTrigger { 0%, 100% { transform: scale(1.1); filter: brightness(1); } 50% { transform: scale(1.2); filter: brightness(1.5) drop-shadow(0 0 30px var(--accent)); } }

.mod-anticipation-anim { animation: modShakePop 1s cubic-bezier(0.36, 0.07, 0.19, 0.97) forwards; z-index: 100 !important; }
@keyframes modShakePop {
    0% { transform: translateX(0); filter: brightness(1); }
    20%, 40% { transform: translateX(4px) rotate(2deg); filter: brightness(1.2); }
    30%, 50% { transform: translateX(-4px) rotate(-2deg); }
    60% { transform: translateY(-5px) scale(1.05); filter: brightness(1.5) drop-shadow(0 0 20px var(--accent)); }
    100% { transform: translateY(0) scale(1); filter: brightness(1); }
}

.fct.ability { color: #f1c40f; font-size: 2rem; font-weight: 900; text-shadow: 0 0 20px rgba(241, 196, 15, 0.8), 2px 2px 0 #000; z-index: 999; }

/* Corporate Sponsor / Trapper's Brand payouts, and the Raccoon's combat cash.
   These are real cash/pelt earnings, so they get the reward palette and a size
   that matches the .reward-float used everywhere else. They deliberately keep
   the in-battle POSITION (on the card that earned it — that attribution is the
   point) and the in-battle DURATION: spawnVFX's timing is interlocked with the
   combat event queue, and stretching it would desync every following strike. */
.fct.payout { font-size: 3rem; font-weight: 900; letter-spacing: 1px; }
.fct.payout.cash  { color: #2ecc71; text-shadow: 0 0 18px rgba(46,204,113,.7), 2px 2px 0 #000; }
.fct.payout.pelts { color: #d9a441; text-shadow: 0 0 18px rgba(217,164,65,.7), 2px 2px 0 #000; }

.fct-long {
    animation-duration: 2.5s !important;
}

/* --- EMERGENCY BUG FIXES: Z-INDEX & TYPOGRAPHY --- */

/* 1. Elevate the entire Mod Zone so it stacks higher than the fighter cards */
.combat-mod-zone {
    position: relative; 
    z-index: 100 !important; 
}

/* 2. Lower the main fighter area so popups easily clear it */
.combat-layout-wrap {
    position: relative;
    z-index: 10 !important; 
}

/* 3. Break the active popup completely out of the layout hierarchy */
.mod-rect-slot.zoomed-active {
    z-index: 9999 !important;
}

/* 4. Stop the ALL CAPS and spacing inheritance from the rectangle buttons */
.mod-rect-slot .trading-card-popup-container {
    text-transform: none !important;
    letter-spacing: normal !important;
}

/* (The bait-catch animation moved to the log reveal: js/ui/logFx.js — its
   BAIT SUCCESS! / UNLOCKED! callouts are .fx-co.k-bait / .k-alpha / .k-apex.) */

/* Trophy toast notification */
.trophy-toast {
    position: fixed;
    bottom: 80px;
    right: 16px;
    background: linear-gradient(135deg, #1a1a2e, #16213e);
    border: 1.5px solid #f1c40f;
    border-radius: 14px;
    padding: 12px 16px;
    display: flex;
    gap: 12px;
    align-items: center;
    z-index: 9998;
    max-width: 300px;
    box-shadow: 0 4px 24px rgba(0,0,0,0.6), 0 0 24px rgba(241,196,15,0.15);
    transform: translateX(120%);
    transition: transform 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    pointer-events: none;
}
.trophy-toast.show { transform: translateX(0); }
.trophy-toast-icon { font-size: 2.2rem; flex-shrink: 0; }
.trophy-toast-body { display: flex; flex-direction: column; }
.trophy-toast-title { font-size: 0.65rem; color: #f1c40f; font-weight: 800; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 2px; }
.trophy-toast-name { font-size: 0.95rem; font-weight: 700; color: #fff; }
.trophy-toast-reward { font-size: 0.82rem; color: #2ecc71; font-weight: 600; margin-top: 3px; }
.trophy-toast-silas { font-size: 0.78rem; color: #e67e22; font-style: italic; margin-top: 3px; }

/* Silas modal content */
.silas-modal-content {
    background: linear-gradient(160deg, #1a110a 0%, #0e0904 60%, #12100d 100%);
    border: 1.5px solid #6a5030;
    border-radius: 16px;
    padding: 28px 24px 20px;
    max-width: 480px;
    width: 92%;
    max-height: 88vh;
    overflow-y: auto;
    box-shadow: 0 8px 40px rgba(0,0,0,0.8), 0 0 30px rgba(180,100,20,0.12);
    position: relative;
}

/* Header row */
.silas-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 16px;
}
.silas-portrait-wrap {
    flex-shrink: 0;
    border-radius: 10px;
    overflow: hidden;
    border: 1.5px solid #5a3a18;
    box-shadow: 0 0 16px rgba(180,100,20,0.3);
}
.silas-identity { flex: 1; }
.silas-name-plate {
    font-family: var(--font-heading, serif), 'Noto Color Emoji';
    font-size: 1.6rem;
    font-weight: 900;
    letter-spacing: 4px;
    color: #c8a060;
    text-shadow: 0 0 12px rgba(200,160,80,0.4);
}
.silas-title-plate {
    font-size: 0.7rem;
    color: #7a6040;
    letter-spacing: 1px;
    margin-top: 2px;
    font-style: italic;
}

/* Divider */
.silas-divider {
    height: 1px;
    background: linear-gradient(90deg, transparent, #5a3a18, transparent);
    margin: 0 0 16px;
}

/* Dialogue */
.silas-dialogue {
    font-size: 0.95rem;
    color: #c8b090;
    font-style: italic;
    line-height: 1.65;
    margin-bottom: 16px;
    min-height: 48px;
}
.silas-dialogue::before { content: '"'; color: #7a5030; font-size: 1.4rem; }
.silas-dialogue::after  { content: '"'; color: #7a5030; font-size: 1.4rem; }

/* R1 item 4: names the specific bounty in the completion popup — distinct from
   the italic flavor line below it (not spoken dialogue, so no italic). */
.silas-bounty-complete-banner {
    display: block;
    font-style: normal;
    font-weight: 900;
    color: var(--warning);
    font-size: 1.05rem;
    margin-bottom: 8px;
    text-shadow: 0 0 10px rgba(241,196,15,0.4);
}

#remy-lore-line {
    font-size: 0.72rem;
    color: rgba(160, 130, 85, 0.55);
    font-style: italic;
    margin-top: -8px;
    margin-bottom: 12px;
    min-height: 1em;
    line-height: 1.4;
}

/* Contract details */
.silas-bounty-details {
    background: rgba(80,50,20,0.15);
    border: 1px solid #4a3020;
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 16px;
    font-size: 0.85rem;
    color: #b0a080;
    line-height: 1.9;
}
.silas-contract-header {
    font-size: 0.65rem;
    letter-spacing: 3px;
    color: #8a6030;
    font-weight: 800;
    margin-bottom: 8px;
    text-transform: uppercase;
}

/* Item reward choices */
.silas-item-choices { margin-bottom: 16px; }
.silas-choices-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 10px;
    margin-top: 10px;
}
/* MOD REVEAL / CARD ZOOM OVERLAY — sized to the screen by uiCombat.sizeCardZoom
   (--zoom-scale: 0.769 at the least, which is the old fixed 200×280, up to 1.4). */
#mod-reveal-card-wrap {
    width: calc(260px * var(--zoom-scale, 0.769));
    height: calc(364px * var(--zoom-scale, 0.769));
    position: relative;
}
#mod-reveal-card-wrap .trading-card {
    position: absolute;
    top: 0; left: 0;
    transform: scale(var(--zoom-scale, 0.769));
    transform-origin: top left;
}

/* S2-4: pre-battle opponent-mod reveal (header + all mods, then shrink to slot)
   The overlay's own layout is here rather than inline in uiCombat: an inline
   style outranks a stylesheet rule, so while it was inline the landscape media
   query below could not re-arrange the stack, which is exactly what the cards
   needed in order to stop being squeezed. uiCombat still sets the backdrop and
   pointer-events inline, because those two really are per-call. */
#opp-mod-reveal-overlay {
    position: fixed; inset: 0; z-index: 9100; box-sizing: border-box;
    display: flex; flex-direction: column; align-items: center;
    /* `safe center` + overflow:auto is the backstop. This overlay is a TAP GATE
       — revealEscape lists .opp-mod-reveal-ok among the buttons a stranded
       player has to be able to reach — so on any size the tiers below did not
       anticipate, the content must stay scrollable rather than centre itself
       off the top edge, which plain `center` does once content exceeds the
       box. */
    justify-content: safe center; overflow: auto;
    gap: 14px; padding: 16px;
}
.opp-mod-reveal-row {
    display: flex; gap: 18px; align-items: center; justify-content: center;
    flex-wrap: wrap; max-width: 100%;
}
.opp-mod-reveal-header {
    font-size: 1.05rem; font-weight: 900; letter-spacing: 1px; color: #e74c3c;
    text-shadow: 0 0 12px rgba(231,76,60,0.5); text-align: center; padding: 0 16px;
}
/* SIZING. This overlay exists so the player READS what they are up against, so
   it follows .cfm-card-wrap's rule — "render the real 260×364 card; scale down
   only if the viewport is too narrow" — rather than shrinking on principle. The
   card is SCALED as a unit, never resized: see the long note on
   .triage-drama-wrap for why (resizing reflows .tc-ability and clips it
   mid-sentence; scale brings every font, the emoji and the durability pips down
   together).

   ONE number drives it. The wrap's box used to be a separate pair of literals
   (173×242) beside the scale (0.665) with the arithmetic only in a comment —
   two places to keep in step. Now the box is derived from --opp-mod-scale, so
   a tier only has to set the scale. */
.opp-mod-reveal-card {
    --opp-mod-scale: 1;                                /* .tc-ability → 12px: the real card */
    width:  calc(260px * var(--opp-mod-scale));
    height: calc(364px * var(--opp-mod-scale));
    position: relative; flex-shrink: 0;
    will-change: transform, opacity;
}
.opp-mod-reveal-card .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(var(--opp-mod-scale)); transform-origin: top left;
}
/* U4-6 / V1-1: explicit "Got it" acknowledgement for the PvP opponent-mod
   reveal. It's the last flex child (a clear footer BELOW the mod cards) with a
   solid, high-contrast pill + its own margin, so it never reads as sitting on
   top of a card. flex-shrink:0 keeps it its full height on short screens. */
.opp-mod-reveal-ok {
    margin-top: 12px; flex-shrink: 0; padding: 11px 34px; font-size: 1rem; font-weight: 800;
    letter-spacing: .5px; color: #fff; background: #e74c3c; border: 2px solid #ff8a7d;
    border-radius: 10px; cursor: pointer; box-shadow: 0 6px 22px rgba(0,0,0,0.6), 0 0 0 4px rgba(231,76,60,0.18);
    animation: oppModOkIn .3s ease-out both .2s;
}
.opp-mod-reveal-ok:hover { background: #ff6455; }
.opp-mod-reveal-ok:active { transform: translateY(1px); }
@keyframes oppModOkIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* LANDSCAPE PHONE — the battle case, and the one that was unreadable.
   Measured before this change on a 812×375 viewport: the stack came to 286px
   against 375px of viewport, so 89px sat unused while the card was squeezed to
   scale(0.446) — .tc-ability rendered at 5.35px and .tc-name at 6.42px.
   Shrinking further was never what the space required.

   The stack is what forced it: header + cards + button in one column means the
   cards only get whatever the chrome leaves (251px → scale 0.69 at best). So
   this borrows the fix S2-3 already made for the poach-reveal panel, which is
   the same three parts against the same 375px of height: lay it out
   HORIZONTALLY in landscape — cards in their own column, header and button
   stacked beside them — and the cards get the full height instead of the
   leftovers. */
@media (max-height: 520px), (orientation: landscape) and (max-width: 900px) {
    #opp-mod-reveal-overlay {
        display: grid;
        grid-template-columns: auto minmax(128px, 1fr);
        /* The two 1fr spacer rows centre the header/button PAIR against the
           cards. With `auto 1fr` they both hug the top of a 328px column, which
           reads as the text having come loose from the cards it labels. */
        grid-template-rows: 1fr auto auto 1fr;
        align-items: center; justify-content: center;
        gap: 0 18px; padding: 8px 14px;
    }
    #opp-mod-reveal-overlay > .opp-mod-reveal-row    { grid-column: 1; grid-row: 1 / -1; align-self: center; }
    #opp-mod-reveal-overlay > .opp-mod-reveal-header { grid-column: 2; grid-row: 2; align-self: end; text-align: left; padding: 0; }
    #opp-mod-reveal-overlay > .opp-mod-reveal-ok     { grid-column: 2; grid-row: 3; align-self: start; justify-self: start; margin-top: 10px; }

    .opp-mod-reveal-card { --opp-mod-scale: 0.94; }    /* .tc-ability → 11.3px; 364 × 0.94 + 16 fits a 360px screen */
    .opp-mod-reveal-header { font-size: 0.92rem; }
    .opp-mod-reveal-ok { padding: 9px 26px; font-size: 0.92rem; }
    #opp-mod-reveal-overlay > .opp-mod-reveal-row { flex-wrap: nowrap; }
}

/* NARROW landscape (640×360-class Androids and the 568px iPhone SE). Measured:
   at 640 wide, two cards at 0.9 need 486px but the text column left only 466 —
   the row WRAPPED, went 673px tall and ran off a 360px screen. The column is
   what has to give first (the header wraps to a second line perfectly well),
   then the scale.
     two cards at 640: 640 − 104 − 12 − 28 = 496 available, 520×0.78 + 12 = 418
     two cards at 568: 424 available, 418 needed */
/* THREE MODS — reachable only through the Master Tracker's temporary third
   active slot (activeSlotCap is 2 permanently), stepped by COUNT from JS the
   same way #triage-drama-row earns `.dense`.

   Three cards are bound by WIDTH, not height, which flips the layout decision:
   the side-by-side arrangement above buys height at the cost of a 128px text
   column, and that column is precisely what three cards cannot spare. So three
   go back to the stack and take their height out of the CHROME instead —
   exactly what the in-battle landscape block does for the rest of the battle
   screen (header collapsed, zone titles dropped, log down to a strip).
   The binding constraint is HEIGHT, and it is 360, not 375: most Android
   phones report 360 in landscape. Chrome measured in the running overlay (not
   estimated) is 86px — header 17, two 6px gaps, the button's 4px margin and its
   37px box, 16px of padding — which leaves 274px for the card, so 364 × 0.74 =
   269 fits with clearance. 0.77 was 6px over and pushed the "Got it" button off
   a 360px screen. */
/* Desktop / tablet: three cards at 0.9 = 738px wide, 328px tall — room to spare. */
#opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-card { --opp-mod-scale: 0.9; }
@media (max-height: 520px), (orientation: landscape) and (max-width: 900px) {
    #opp-mod-reveal-overlay.opp-mod-many {
        display: flex; flex-direction: column; align-items: center; justify-content: center;
        gap: 6px; padding: 8px 14px;
    }
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-card { --opp-mod-scale: 0.74; }   /* .tc-ability → 8.9px */
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-row { gap: 12px; flex-wrap: nowrap; }
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-header { font-size: 0.82rem; text-align: center; }
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-ok { margin-top: 4px; padding: 7px 22px; font-size: 0.85rem; }
}

/* NARROW landscape — 640×360-class Androids and the 568px iPhone SE. LAST on
   purpose: it has to outrank the two blocks above at equal specificity.
   Measured: at 640 wide, two cards at 0.9 needed 486px but the text column left
   only 466 — the row WRAPPED, went 673px tall and ran off a 360px screen. The
   column gives first (the header wraps to a second line perfectly well), then
   the scale.
     two cards at 640: 640 − 104 − 12 − 28 = 496 available, 520×0.78 + 12 = 418
     two cards at 568: 424 available, 418 needed
     three cards at 640: 640 − 28 = 612 available, 780×0.7 + 24 = 570 */
@media (max-height: 520px) and (max-width: 680px),
       (orientation: landscape) and (max-width: 680px) {
    #opp-mod-reveal-overlay { grid-template-columns: auto minmax(104px, 1fr); gap: 0 12px; }
    .opp-mod-reveal-card { --opp-mod-scale: 0.9; }     /* .tc-ability → 10.8px; two cards at 640: 520×0.9+12 = 480 of 496 */
    .opp-mod-reveal-header { font-size: 0.82rem; }
    .opp-mod-reveal-ok { padding: 8px 18px; font-size: 0.85rem; }
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-card { --opp-mod-scale: 0.7; }
}

/* PORTRAIT PHONE. Only reachable mid-battle if the landscape lock has failed,
   but three cards at 0.7 are 182px wide, so only ONE fits a 375px row — they
   stacked into an 800px column. 0.6 puts two per row and the whole reveal back
   inside the screen. */
/* Taller landscape phones (390px+ high: 844×390, 851×393): the real card. */
@media (orientation: landscape) and (min-width: 681px) and (max-width: 900px) and (min-height: 385px) {
    .opp-mod-reveal-card { --opp-mod-scale: 1; }       /* 364 + 16 = 380 */
}
/* The 568px iPhone SE: two cards at 0.9 are wider than its 424px column. */
@media (orientation: landscape) and (max-width: 600px) {
    .opp-mod-reveal-card { --opp-mod-scale: 0.78; }
}
@media (max-width: 480px) and (orientation: portrait) {
    .opp-mod-reveal-card { --opp-mod-scale: 0.86; }    /* two cards stack; 0.86 keeps both + the button on an 812px screen */
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-card { --opp-mod-scale: 0.6; }
    #opp-mod-reveal-overlay.opp-mod-many .opp-mod-reveal-row { gap: 10px; }
}

/* (The reward card is sized by the log reveal now — js/ui/logFx.js layout().) */

/* SILAS REWARD CARDS — canonical card scaled 0.538× */
.silas-reward-wrap {
    width: 140px;   /* 260 × 0.538 */
    height: 196px;  /* 364 × 0.538 */
    position: relative;
    overflow: hidden;
    cursor: pointer;
}
.silas-reward-wrap .trading-card {
    position: absolute;
    top: 0; left: 0;
    transform: scale(0.538);        /* 140 / 260 */
    transform-origin: top left;
}
.silas-reward-wrap:hover .trading-card {
    box-shadow: 0 6px 20px rgba(0,0,0,0.5);
    outline: 2px solid #c8a060;
}

/* Buttons */
.silas-actions {
    display: flex;
    gap: 10px;
    justify-content: flex-end;
    margin-top: 8px;
}
.silas-btn {
    padding: 9px 20px;
    border-radius: 8px;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.5px;
    cursor: pointer;
    border: none;
    transition: opacity 0.15s, transform 0.1s;
}
.silas-btn:hover { opacity: 0.88; transform: translateY(-1px); }
.silas-btn-dismiss  { background: #2e2216; color: #8a7050; border: 1px solid #4a3020; }
.silas-btn-accept   { background: linear-gradient(135deg, #7a5020, #a06828); color: #f0d080; }

/* Mobile adjustments */
@media (max-width: 480px) {
    .silas-modal-content { padding: 20px 16px 16px; }
    .silas-header { flex-direction: column; align-items: flex-start; gap: 10px; }
    .silas-choices-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ===================== TROPHY CASE ===================== */

/* Tier tabs + stats bar pin together as one unit while the (many-screens-tall
   on phones) trophy list scrolls underneath. A scrolled-away tab row reads as
   "the tabs are missing" — the mobile bug this replaces. */
.trophy-sticky-head {
    position: sticky; top: 0; z-index: 11;
    background: var(--bg-dark);
}

/* Tier tabs — This Year (annual objectives) vs Lifetime (prestige) */
.trophy-tier-tabs {
    display: flex; gap: 8px; align-items: center;
    padding: 12px 16px 0;
}
.trophy-tier-tab {
    padding: 8px 18px; border-radius: 10px 10px 0 0; font-size: 0.85rem;
    font-weight: 700; background: #141414; color: #555;
    border: 1px solid #222; border-bottom: none; cursor: pointer;
    transition: 0.15s;
}
.trophy-tier-tab:hover { color: #999; }
.trophy-tier-tab.active {
    background: var(--card-bg); color: var(--accent);
    border-color: #2a2a2a;
    box-shadow: inset 0 2px 0 var(--accent);
}
.trophy-reset-countdown {
    margin-left: auto; font-size: 0.72rem; color: #666;
    padding-right: 4px; white-space: nowrap;
}

/* Lifetime tab: compact badge-style prestige grid (no cash — they carry none) */
.trophy-grid.lifetime-mode {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 8px;
    align-items: start;
}
.trophy-grid.lifetime-mode .trophy-section-header { grid-column: 1 / -1; }
.trophy-grid.lifetime-mode .trophy-card { padding: 10px 12px; }
.trophy-grid.lifetime-mode .tc-icon { font-size: 1.9rem; }
.trophy-grid.lifetime-mode .tc-desc { font-size: 0.68rem; }

/* Stats bar */
.trophy-stats-bar {
    display: flex; gap: 16px; align-items: center;
    padding: 12px 16px; background: var(--card-bg);
    border-bottom: 1px solid #222; flex-wrap: wrap;
    /* Pinning handled by the .trophy-sticky-head wrapper (tabs + this bar). */
}
.trophy-stat { text-align: center; }
.trophy-stat-val { display: block; font-size: 1.3rem; font-weight: 900; color: #f1c40f; }
.trophy-stat-label { font-size: 0.65rem; color: #666; letter-spacing: 1px; text-transform: uppercase; }
.trophy-progress-wrap {
    flex: 1; min-width: 80px; height: 4px;
    background: #222; border-radius: 2px; overflow: hidden;
}
.trophy-progress-bar { height: 100%; background: #f1c40f; border-radius: 2px; transition: width 0.4s ease; }

/* Category tabs */
.trophy-cat-tabs {
    display: flex; gap: 6px; padding: 10px 12px;
    overflow-x: auto; white-space: nowrap;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    border-bottom: 1px solid #1a1a1a;
}
.trophy-cat-tabs::-webkit-scrollbar { display: none; }
.trophy-cat-tab {
    padding: 5px 12px; border-radius: 20px; font-size: 0.75rem;
    font-weight: 600; background: #1a1a1a; color: #666;
    border: 1px solid #2a2a2a; cursor: pointer; white-space: nowrap;
    transition: all 0.15s;
}
.trophy-cat-tab.active { background: #f1c40f; color: #000; border-color: #f1c40f; }

/* Trophy grid */
.trophy-grid {
    padding: 12px;
    display: flex; flex-direction: column; gap: 8px;
}
.trophy-section-header {
    font-size: 0.65rem; letter-spacing: 3px; color: #f1c40f;
    font-weight: 800; padding: 8px 4px 4px; margin-top: 8px;
}
.trophy-section-header.locked-header { color: #444; margin-top: 16px; }

/* Trophy card */
.trophy-card {
    display: flex; align-items: flex-start; gap: 12px;
    background: #111; border: 1px solid #2a2a2a;
    border-radius: 10px; padding: 12px;
    position: relative; transition: border-color 0.2s;
}
.trophy-card.legendary-glow { box-shadow: 0 0 16px rgba(155, 89, 182, 0.25); }
.tc-icon { font-size: 1.8rem; flex-shrink: 0; line-height: 1; }
.tc-body { flex: 1; min-width: 0; }
.tc-name { font-size: 0.9rem; font-weight: 700; margin-bottom: 2px; }
.tc-desc { font-size: 0.78rem; line-height: 1.4; }
.tc-date { font-size: 0.7rem; color: #666; margin-top: 4px; font-style: italic; }
.tc-progress-wrap {
    height: 3px; background: #222; border-radius: 2px; overflow: hidden; margin-top: 6px;
}
.tc-progress-bar { height: 100%; border-radius: 2px; transition: width 0.3s; }
.tc-progress-label { font-size: 0.68rem; margin-top: 3px; font-weight: 600; }
.tc-rewards { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 5px; }
.tc-reward-cash, .tc-reward-silas, .tc-reward-item {
    font-size: 0.65rem; padding: 2px 7px; border-radius: 10px; font-weight: 700;
}
.tc-reward-cash  { background: rgba(46,204,113,0.15); color: #2ecc71; }
.tc-reward-silas { background: rgba(230,126,34,0.15); color: #e67e22; }
.tc-reward-item  { background: rgba(155,89,182,0.15); color: #9b59b6; }
.tc-diff-badge {
    position: absolute; top: 8px; right: 8px;
    width: 18px; height: 18px; border-radius: 50%;
    font-size: 0.6rem; font-weight: 900; color: #000;
    display: flex; align-items: center; justify-content: center;
}

@media (max-width: 480px) {
    .trophy-stats-bar { gap: 12px; }
    .tc-icon { font-size: 1.5rem; }
}

/* Simplified series card — same height as one-off cards */
.series-card { position: relative; }

.tc-tier-chip {
    display: inline-block;
    font-size: 0.6rem;
    font-weight: 800;
    letter-spacing: 1px;
    color: #666;
    text-transform: uppercase;
    margin-bottom: 2px;
}

.tc-chain {
    font-size: 0.72rem;
    color: #e67e22;
    margin-top: 4px;
    font-style: italic;
}

/* Earned cards — full color, prominent */
.trophy-card.earned {
    background: #131008;
}

/* Locked cards — dimmed, recedes */
.trophy-card.locked {
    background: #0e0e0e;
    filter: saturate(0.3);
}

/* In-progress series — slightly dim but not as much as locked */
.series-card.in-progress {
    background: #101010;
    filter: saturate(0.7);
}

.trophy-card.legendary-glow {
    box-shadow: 0 0 16px rgba(155,89,182,0.25);
    filter: none !important;
}

/* Bounty origin line */
.bounty-origin {
    font-size: 0.7rem;
    color: #6a5040;
    font-style: italic;
    margin: 3px 0;
}

/* Hold button */
.silas-btn-hold {
    background: #1e1a14;
    color: #9b59b6;
    border: 1px solid #4a3060;
    font-style: italic;
}

/* Pending contract card */
.pending-contract-card {
    background: #0e0e12;
    border: 1px solid #2a2040;
    border-radius: 10px;
    padding: 10px 12px;
    margin-bottom: 8px;
}
.pc-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 4px;
}
.pc-name { font-size: 0.88rem; font-weight: 700; color: #c8b090; }
.pc-diff { font-size: 0.7rem; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; }
.pc-desc { font-size: 0.78rem; color: #6a5a48; font-style: italic; margin-bottom: 4px; line-height: 1.4; }
.pc-meta { font-size: 0.72rem; color: #555; margin-bottom: 4px; }
.pc-window { font-size: 0.72rem; color: #8a7050; font-weight: 600; margin-bottom: 4px; }
.pc-req { margin-bottom: 4px; }
.pc-reward { font-size: 0.72rem; color: #555; margin-bottom: 8px; }
.pc-actions { display: flex; gap: 8px; }
.pc-btn {
    padding: 5px 12px; border-radius: 6px; font-size: 0.78rem;
    font-weight: 700; cursor: pointer; border: none;
}
.pc-activate { background: linear-gradient(135deg, #7a5020, #a06828); color: #f0d080; }
.pc-cancel   { background: #1a1410; color: #5a4030; border: 1px solid #2a1e10; }

/* Collapsible section utility */
.hidden { display: none !important; }

/* Expired bounty card */
.bounty-expired-card {
    opacity: 0.45;
    filter: saturate(0.2);
    border-color: #2a2a2a !important;
    pointer-events: none;
}
.bounty-timer-expired {
    font-size: 0.72rem;
    color: #555;
    font-weight: 700;
    margin-bottom: 4px;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

/* Empty bounty state */
.bounty-empty {
    font-size: 0.82rem;
    color: #555;
    text-align: center;
    padding: 16px 8px;
    line-height: 1.6;
}

/* Consumable use toast */
.consumable-toast {
    position: fixed;
    top: 80px;
    left: 50%;
    transform: translateX(-50%) translateY(-16px);
    background: linear-gradient(135deg, #1a1a2e, #16213e);
    border: 1.5px solid #2ecc71;
    border-radius: 12px;
    padding: 14px 20px;
    color: #eee;
    font-size: 0.85rem;
    line-height: 1.5;
    text-align: center;
    white-space: pre-line;
    z-index: 9998;
    opacity: 0;
    max-width: 260px;
    box-shadow: 0 4px 20px rgba(0,0,0,0.6), 0 0 20px rgba(46,204,113,0.15);
    transition: opacity 0.3s ease, transform 0.3s ease;
    pointer-events: none;
}
.consumable-toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

/* Active gear display */
.active-gear-card {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #0e1a10;
    border: 1px solid #27ae60;
    border-radius: 8px;
    padding: 7px 10px;
    margin-bottom: 6px;
    font-size: 0.82rem;
}
.gear-icon  { font-size: 1.2rem; flex-shrink: 0; }
.gear-name  { flex: 1; color: #c8e0b0; font-weight: 600; }
.gear-timer { color: #27ae60; font-size: 0.72rem; font-weight: 700; white-space: nowrap; }
.gear-empty { color: #444; font-size: 0.78rem; padding: 4px 0; }

/* ── Companion stack — fans behind active card ───────────────────────────── */
.companion-row { display: none; }   /* retired — superseded by .companion-stack */

.fighter-anchor { position: relative; overflow: visible; }
.main-fighter-column { position: relative; overflow: visible !important; }
#comb-p1, #comb-p2 { overflow: visible !important; }

.companion-stack {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    overflow: visible;
    pointer-events: none;
    z-index: 5;       /* below active card (z-index: 20) */
}

.companion-card-wrap {
    position: absolute;
    overflow: hidden;
    border-radius: 8px;
    cursor: pointer;
    pointer-events: auto;
    box-shadow: -3px 3px 12px rgba(0,0,0,0.6);
    transition: box-shadow 0.15s ease, opacity 0.3s ease;
}
.companion-card-wrap:hover { box-shadow: 0 0 14px rgba(46,204,113,0.8); }

.companion-card-inner {
    position: absolute;
    top: 0; left: 0;
    transform-origin: top left;
}

.companion-card-wrap.cmp-activating { box-shadow: 0 0 20px rgba(46,204,113,0.95); }

/* V3-3: a companion whose bonus comes from the DEAD PILE reads as dead —
   desaturated + a 🪦 marker — so it's clearly distinct from a live-deck partner. */
.companion-card-wrap.companion-graved .companion-card-inner { filter: grayscale(0.65) brightness(0.72); }
.companion-tombstone {
    position: absolute; top: 1px; right: 2px; z-index: 6;
    font-size: 1.25rem; line-height: 1; pointer-events: none;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.95));
}


/* ── Mod slot feedback VFX ──────────────────────────────────────────────── */
@keyframes modVibrate {
    0%, 100% { transform: translate(0, 0); }
    20% { transform: translate(-3px, 1px); }
    40% { transform: translate(3px, -1px); }
    60% { transform: translate(-2px, 2px); }
    80% { transform: translate(2px, -2px); }
}
.mod-rect-slot.mod-firing {
    animation: modVibrate 0.45s ease-in-out;
}

.mod-effect-fly {
    position: fixed;
    z-index: 8500;
    font-size: 1.1rem;
    font-weight: 900;
    color: #2ecc71;
    text-shadow: 0 0 8px rgba(46,204,113,0.8), 2px 2px 0 #000;
    pointer-events: none;
    transition: transform 0.7s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.7s ease;
}
.mod-effect-fly.fly-active {
    opacity: 0;
}

/* ── Stat modifier badges (+X / -X on card stats) ──────────────────────── */
.stat-mod {
    font-size: 0.62em;
    font-weight: 900;
    padding: 1px 5px;
    border-radius: 5px;
    margin-left: 3px;
    vertical-align: middle;
    background: rgba(0,0,0,0.4);
    animation: statModGlow 1.5s ease-in-out infinite;
}
.stat-buff   { color: #2ecc71; }
.stat-debuff { color: #e74c3c; }

/* Poach provenance line (card footer + desktop inspector) — small, muted. */
.tc-provenance {
    margin-top: 5px; font-size: 0.6rem; line-height: 1.3;
    color: #8a7a62; font-style: italic; text-align: center;
    letter-spacing: 0.3px; flex-shrink: 0;
}
@keyframes statModGlow {
    0%, 100% { text-shadow: 0 0 4px currentColor;  box-shadow: 0 0 4px rgba(46,204,113,0.2); }
    50%      { text-shadow: 0 0 11px currentColor; box-shadow: 0 0 9px rgba(46,204,113,0.5); }
}

/* ── Generic confirmation modal ─────────────────────────────────────────── */
#confirm-modal { z-index: 9500; }  /* above Silas (5000) and all other overlays */

.confirm-modal-content {
    background: #1e1e1e;
    border: 2px solid #555;
    border-radius: 12px;
    padding: 20px 24px;
    max-width: 380px;
    width: 90%;
    box-shadow: 0 10px 40px rgba(0,0,0,0.8);
    /* Card-bearing bodies (the mod-limit picker renders up to 4 real card
       faces) can exceed the viewport; without these the overflow was CLIPPED —
       partial cards, no way to scroll. Small confirm bodies are unaffected. */
    max-height: 88vh;
    overflow-y: auto;
}
.confirm-title { font-size: 1.15rem; font-weight: 700; color: #eee; margin-bottom: 8px; }
.confirm-body  { font-size: 0.95rem; color: #ccc; margin-bottom: 18px; line-height: 1.4; }
.confirm-actions { display: flex; justify-content: flex-end; gap: 10px; }
.confirm-actions .btn-secondary {
    background: #333; color: #ccc; border: 1px solid #555;
    padding: 8px 16px; border-radius: 6px; cursor: pointer;
}
.confirm-actions .btn-primary {
    background: #27ae60; color: #fff; border: none;
    padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: 600;
}
.confirm-actions .btn-primary:hover   { background: #2ecc71; }
.confirm-actions .btn-secondary:hover { background: #444; }
.confirm-actions button:disabled      { opacity: .55; cursor: default; }

/* ── Bug / suggestion report modal ─────────────────────────────────────────
   Borrows .confirm-modal-content's frame (same overlay, same actions row) and
   adds only what a form needs. Left-aligned because .modal-content centres its
   text, which reads wrong for labelled fields. */
.feedback-modal-content {
    background: #1e1e1e;
    border: 2px solid #555;
    border-radius: 12px;
    padding: 20px 24px;
    max-width: 420px;
    width: 90%;
    box-shadow: 0 10px 40px rgba(0,0,0,0.8);
    max-height: 88vh;
    overflow-y: auto;
    text-align: left;
}
.feedback-field { margin-bottom: 12px; }
.feedback-field label {
    display: block; font-size: .72rem; color: #888; margin-bottom: 4px;
    text-transform: uppercase; letter-spacing: .06em; font-weight: 700;
}
.feedback-field select,
.feedback-field textarea {
    width: 100%; box-sizing: border-box; padding: 8px;
    background: #1a1a1e; border: 1px solid #333; border-radius: 8px;
    color: #eee; font-size: .88rem; font-family: inherit;
}
.feedback-field textarea { resize: vertical; min-height: 90px; line-height: 1.4; }
.feedback-field select:disabled,
.feedback-field textarea:disabled { opacity: .55; }
.feedback-error {
    background: #2a1414; border-left: 3px solid var(--danger);
    color: #e88; font-size: .8rem; padding: 8px 10px;
    border-radius: 6px; margin-bottom: 12px; line-height: 1.4;
}

/* ── Badge tray (Jim, 2026-09-25) ──────────────────────────────────────────
   ONE right-aligned row of icon chips, built from rules/cardBadges.js. It
   replaced three badges pinned to fixed corners, which collided: Trapper's
   Brand, the Radio Collar and the tier label all sat on the SAME bottom-left
   spot, so an Apex card with Trapper's Brand lost its APEX label under the
   pelt badge. The per-badge text (+$4/hit, PROTECTED) moved into the tap
   popup (cardInfo.showCardBadgePopup). The max-width keeps the row clear of
   the ℹ️/🎥 icons in the other corner; past it, chips shrink to 12px rather
   than wrap up into the photo. */
.tc-badge-tray {
    position: absolute;
    right: var(--tc-icon-inset);
    bottom: var(--tc-icon-inset);
    z-index: 8;
    display: flex;
    gap: 3px;
    max-width: calc(100% - var(--tc-icons-w) - var(--tc-icon-inset) * 2 - 6px);
}
.tc-badge-chip {
    appearance: none; -webkit-appearance: none;
    margin: 0; padding: 0; border: 0;
    width: 18px; height: 18px; flex: 0 1 18px; min-width: 12px;
    border-radius: 4px;
    display: flex; align-items: center; justify-content: center;
    line-height: 1; font-size: 0.8rem;
    font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
    background: rgba(0,0,0,0.6);
    box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14);
    color: #fff;
    cursor: pointer;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.9));
    transition: transform 0.12s, box-shadow 0.12s;
}
.tc-badge-chip:hover { transform: translateY(-1px) scale(1.08); box-shadow: inset 0 0 0 1px rgba(241,196,15,0.7); }
.tc-badge-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* The chip a consumable JUST granted, in the stamp/upgrade reveal: it pulses
   gold a few times so the eye finds the new badge (uiConsumables.showUpgradeReveal). */
.tc-badge-chip.tc-badge-new { animation: badgeNew 0.7s ease-in-out 4; z-index: 1; }
@keyframes badgeNew {
    0%, 100% { transform: scale(1);   box-shadow: inset 0 0 0 1px rgba(255,255,255,0.14); }
    50%      { transform: scale(1.5); box-shadow: inset 0 0 0 1px #f1c40f, 0 0 12px 3px rgba(241,196,15,0.85); }
}
@media (prefers-reduced-motion: reduce) { .tc-badge-chip.tc-badge-new { animation: none; box-shadow: inset 0 0 0 1px #f1c40f; } }

/* ── Permanent stat upgrade: foil number (Jim, 2026-09-25) ───────────────────
   A PERMANENT +ATK / +HP shimmers in rainbow foil, so it reads differently from
   a passing combat buff; the green +N beside it stays where upgrades have
   always shown. Only numbers wrapped in .tc-stat-num (cards.js does that only
   for an upgraded stat) are touched. */
.tc-stat-perm .tc-stat-num {
    background: linear-gradient(100deg, #ff7a7a, #ffe066, #7dffa5, #74c8ff, #c98bff, #ff7a7a);
    background-size: 300% 100%;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: statFoil 3.4s linear infinite;
    filter: drop-shadow(0 0 1px rgba(0,0,0,0.6));
}
@keyframes statFoil { to { background-position: 300% 0; } }
/* Tapping the ⚔️ / ❤️ chip flashes the green +N it produced. */
.stat-mod.tc-stat-flash { display: inline-block; animation: statFlash 1.1s ease-out; }
@keyframes statFlash {
    0%   { transform: scale(1); }
    22%  { transform: scale(1.6); text-shadow: 0 0 10px #2ecc71; }
    100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .tc-stat-perm .tc-stat-num, .stat-mod.tc-stat-flash { animation: none; }
}

/* ── Badge / banner popup rows (inside .card-info-popup) ───────────────── */
/* No font-family of its own: the popup inherits the body stack, which closes
   with Noto (see the EMOJI FALLBACK note on body), so "+🧥6 Pelts" renders. */
.card-badge-popup .cbp-list { display: grid; gap: 2px; }
/* announceBadge (cardInfo.js): the explanation opens by itself the moment a
   card earns the badge, so it arrives with a pop and a gold edge rather than
   the plain fade a tap gets, and the banner it explains pulses. */
.card-info-popup.cbp-announce {
    animation: cbpAnnounce .5s cubic-bezier(.2,1.35,.3,1) both;
    border-color: rgba(241,196,15,.75);
    box-shadow: 0 8px 28px rgba(0,0,0,.6), 0 0 22px rgba(241,196,15,.35);
}
@keyframes cbpAnnounce {
    from { opacity: 0; transform: translateY(10px) scale(.55); }
    to   { opacity: 1; transform: none; }
}
.rabid-stamp.stamp-announce, .poached-stamp.stamp-announce { animation: stampAnnounce .7s ease-in-out 3; }
/* First look (cardInfo.explainNewBadges): the banner or chip flashes BEFORE its
   explanation arrives, so the eye is already on it. */
.rabid-stamp.badge-flash, .poached-stamp.badge-flash, .tc-badge-chip.badge-flash { animation: badgeFlash .45s ease-in-out 2; }
@keyframes badgeFlash {
    50% { filter: brightness(2.1); box-shadow: 0 0 16px 4px rgba(241,196,15,.85); transform: scale(1.12); }
}
@keyframes stampAnnounce { 50% { filter: brightness(1.7); box-shadow: 0 0 14px 3px rgba(255,255,255,.55); } }
@media (prefers-reduced-motion: reduce) {
    .card-info-popup.cbp-announce, .rabid-stamp.stamp-announce, .poached-stamp.stamp-announce, .badge-flash { animation: none; }
}
.cbp-row { display: flex; gap: 7px; align-items: flex-start; padding: 5px 7px; border-radius: 6px; }
.cbp-row.hit { background: rgba(241,196,15,0.09); box-shadow: inset 0 0 0 1px rgba(241,196,15,0.35); }
.cbp-glyph {
    flex: 0 0 20px; text-align: center; font-size: 16px; line-height: 1.25;
    font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
}
.cbp-title { font-weight: 700; font-size: 0.9rem; color: #fff; line-height: 1.25; }
.cbp-body  { font-size: 0.8rem; color: var(--text-muted); line-height: 1.35; margin-top: 1px; }

/* ── Sell inventory item button ─────────────────────────────────────────── */
.btn-sell-item {
    width: 100%;
    padding: 8px 0;
    background: transparent;
    border: 1px solid #555;
    border-radius: 8px;
    color: #aaa;
    font-size: 0.85rem;
    cursor: pointer;
    margin-top: 4px;
    transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.btn-sell-item:hover {
    background: #2a1a1a;
    border-color: #e74c3c;
    color: #e74c3c;
}

/* ── Mod picker modal ────────────────────────────────────────────────────── */
.mod-picker-incoming { margin-bottom: 8px; }
.mod-picker-new-card {
    width: 130px; height: 182px;
    position: relative; overflow: hidden;
    display: inline-block; vertical-align: top;
}
.mod-picker-new-card .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.5);
    transform-origin: top left;
}

.mod-picker-grid { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.mod-picker-item {
    width: 130px; height: 182px;
    position: relative; overflow: hidden;
    cursor: pointer; border-radius: 10px;
    border: 2px solid transparent;
    transition: border-color 0.15s;
}
.mod-picker-item .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.5);
    transform-origin: top left;
}
.mod-picker-item.selected { border-color: #e74c3c; box-shadow: 0 0 12px rgba(231,76,60,0.5); }
.mod-picker-item:hover:not(.selected) { border-color: #555; }
.mod-picker-sell-tag {
    position: absolute; bottom: 0; left: 0; right: 0;
    background: rgba(0,0,0,0.75);
    text-align: center;
    font-size: 0.7rem;
    color: #aaa;
    padding: 3px 0;
    pointer-events: none;
}
.mod-picker-item.selected .mod-picker-sell-tag { color: #e74c3c; }

/* ── Sacrifice-as-bait modal layout ─────────────────────────────────────── */
.sacrifice-modal-layout {
    display: flex;
    gap: 14px;
    align-items: flex-start;
    margin-top: 4px;
}
.sacrifice-card-preview {
    width: 130px;
    height: 182px;
    position: relative;
    overflow: hidden;
    flex-shrink: 0;
    border-radius: 8px;
}
.sacrifice-card-preview .trading-card {
    position: absolute;
    top: 0; left: 0;
    transform: scale(0.5);
    transform-origin: top left;
}
.sacrifice-details {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-top: 2px;
}
.sacrifice-attracts-label {
    font-size: 0.75rem;
    color: #777;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.sacrifice-targets { display: flex; flex-wrap: wrap; gap: 5px; }
.sacrifice-target {
    background: #252525;
    border: 1px solid #3a3a3a;
    border-radius: 6px;
    padding: 3px 8px;
    font-size: 0.82rem;
    color: #ccc;
}
.sacrifice-duration  { font-size: 0.88rem; color: #bbb; }
.sacrifice-persistent { color: #2ecc71; }
.sacrifice-quality   { font-size: 0.85rem; font-weight: 700; }

/* ===== MOBILE BREAKPOINTS ===== */

@media (max-width: 680px) {
    /* Nav: five tabs at nowrap need ~500px of min-content — on real phones
       (390-430 CSS px) justify-content:center clipped BOTH edges, pushing the
       LOG tab off-screen left (measured x=-70 at 430px). Wrapping here covers
       every phone width; the old wrap rule only kicked in at ≤400px. */
    .nav-tabs { flex-wrap: wrap; }

    /* Arena: right panel already stacks at 900px. NOT overflow-x:hidden: the
       Strike Deck's cards hang 16px past the zone's dotted line (Jim), and
       clipping here cut them off on phones. The zone itself is inset by the
       same 16px (.strike-deck-unified margin-inline), so the overhang stays
       inside this column and inside #arena-setup-scene, which scrolls and so
       clips sideways; nothing widens the page. */
    .arena-left { min-width: 0; }
    .arena-right { width: 100%; }

    /* Homestead secondary buttons -> icon-only squares (aria-label carries the name). */
    .hs-sec-btn {
        width: 40px; height: 40px; padding: 0;
        justify-content: center; border-radius: 10px; font-size: 1.15rem;
    }
    .hs-sec-btn .hs-label { display: none; }
    .hs-sec-btn .hs-icon { font-size: 1.25rem; }
    .arena-header-actions { gap: 8px; }
    .hs-btn-row { gap: 8px; }
    .homestead-readout { font-size: 0.82rem; gap: 10px; }


    /* Reserve grid: tighter columns */
    #animal-grid {
        grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    }

    /* Combat zone: stack vertically on small screens */
    .combat-zone {
        flex-direction: column;
        align-items: stretch;
        gap: 16px;
    }

    .combatant-side {
        max-width: 100%;
        width: 100%;
    }

    /* VS label: pull out of absolute positioning, rotate 90° */
    .vs-text {
        position: static;
        transform: rotate(90deg);
        margin: 4px auto;
        display: block;
        font-size: 2.5rem;
    }

    /* Pile fan row: center on narrow screens */
    .pile-row {
        justify-content: center;
    }

    /* Triage grid */
    .triage-grid {
        gap: 8px;
    }
    .triage-card {
        width: 80px;
        padding: 8px 5px;
        font-size: 0.78rem;
    }

    /* Log layout: already stacks at 900px, just tighten bounty sidebar */
    .bounty-sidebar { width: 100%; }
}

@media (max-width: 400px) {
    /* Scale down trading cards in the reserve grid slightly */
    .trading-card {
        font-size: 0.72rem;
    }

    /* NOTE: .sd-card-wrap keeps its 150×211 size at all widths — the cards are
       never shrunk (Jim). rules/deckLayout.js overlaps them just enough instead. */

    /* Combat combatant names */
    .c-player-name {
        font-size: 1.1rem;
    }
    .zone-title {
        font-size: 0.65rem;
        letter-spacing: 1px;
    }

    /* Pile labels */
    .pile-label {
        font-size: 0.65rem;
    }

    /* Reserve grid: single wider column minimum */
    #animal-grid {
        grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
    }

    /* Nav tabs: allow wrap */
    .nav-tabs {
        flex-wrap: wrap;
        gap: 4px;
    }

    /* Combat header */
    .combat-header {
        padding: 10px;
    }
    .c-hp-bar-wrap { width: 120px; }
}
.sacrifice-warning   { font-size: 0.78rem; color: #e74c3c; margin-top: 2px; }

/* ═══════════════════════════════════════════════════════
   SPRINT 4 VFX
   ═══════════════════════════════════════════════════════ */

/* ── Global card back ──────────────────────────────────── */
.card-back-tc {
    background: #0f3d24 !important;
    border: 6px solid #f4f1e8 !important;
    outline: 2px solid #0c2e1c;
    position: relative;
    overflow: hidden;
    justify-content: center;
    align-items: center;
}
.card-back-tc::before {
    content: '';
    position: absolute;
    inset: 8px;
    border-radius: 6px;
    border: 2px solid rgba(244,241,232,.55);
    background:
        repeating-linear-gradient(45deg, transparent 0 9px, rgba(244,241,232,.07) 9px 18px),
        repeating-linear-gradient(-45deg, transparent 0 9px, rgba(244,241,232,.07) 9px 18px);
}
.card-back-tc::after {
    content: '';
    position: absolute;
    width: 72px; height: 72px;
    border-radius: 50%;
    background: radial-gradient(circle, #f4f1e8 0%, #f4f1e8 62%, transparent 63%);
    box-shadow: 0 0 0 3px #0c2e1c inset;
}
.cb-tree {
    position: relative;
    z-index: 2;
    font-size: 2.6rem;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,.4));
}
.cb-corner {
    position: absolute;
    font-size: .85rem;
    color: #f4f1e8;
    z-index: 2;
    opacity: .9;
}
.cb-tl { top: 7px; left: 9px; }
.cb-br { bottom: 7px; right: 9px; transform: rotate(180deg); }

/* ── Draw flip overlay ─────────────────────────────────── */
.draw-flip-cover {
    border-radius: 12px;
    overflow: hidden;
    transform-origin: center;
    backface-visibility: hidden;
    pointer-events: none;
}
@keyframes flipAway {
    0%   { transform: perspective(700px) rotateY(0deg); }
    48%  { transform: perspective(700px) rotateY(89deg); opacity: 1; }
    50%  { transform: perspective(700px) rotateY(90deg); opacity: 0; }
    100% { transform: perspective(700px) rotateY(90deg); opacity: 0; }
}
.draw-flip-cover.flip-away {
    animation: flipAway .48s cubic-bezier(.3,0,.3,1) both;
}

/* ── Companion choreography ────────────────────────────── */
@keyframes donorWiggle {
    0%,100% { transform: scale(1) rotate(0deg); }
    20%     { transform: scale(1.06) rotate(-2deg); }
    50%     { transform: scale(1.06) rotate(2deg); }
    80%     { transform: scale(1.03) rotate(-1deg); }
}
@keyframes donorGlow {
    0%   { box-shadow: 0 0 0 0 rgba(46,204,113,0); }
    25%  { box-shadow: 0 0 22px 7px rgba(46,204,113,.7); }
    100% { box-shadow: 0 0 0 0 rgba(46,204,113,0); }
}
.donor-activate { animation: donorWiggle .7s ease both; }
.donor-activate .trading-card { animation: donorGlow .7s ease both; }

.fly-token {
    position: fixed;
    z-index: 9500;
    font-weight: 900;
    font-size: 2.1rem;
    color: #2ecc71;
    text-shadow: 0 0 16px rgba(46,204,113,.95), 0 2px 3px #000;
    pointer-events: none;
    white-space: nowrap;
    transition: transform .55s cubic-bezier(.3,0,.4,1), opacity .2s ease;
}
.fly-token.atk {
    color: #f39c12;
    text-shadow: 0 0 16px rgba(243,156,18,.95), 0 2px 3px #000;
}

@keyframes receiverFlash {
    0%   { box-shadow: 0 0 0 0 rgba(46,204,113,0); }
    30%  { box-shadow: 0 0 30px 10px rgba(46,204,113,.8); border-color: #2ecc71 !important; }
    100% { box-shadow: 0 0 0 0 rgba(46,204,113,0); }
}
.receiver-flash { animation: receiverFlash .6s ease both; }

/* ── Skunk spray particles ─────────────────────────────── */
.spray-particle {
    position: fixed;
    border-radius: 50%;
    pointer-events: none;
    background: radial-gradient(circle, rgba(130,215,65,.7) 0%, rgba(95,170,45,.4) 45%, rgba(60,120,30,0) 75%);
    filter: blur(5px);
}
.fog-puff {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(120,200,60,.6) 0%, rgba(90,160,40,.32) 45%, rgba(60,120,30,0) 75%);
    filter: blur(7px);
    opacity: 0;
}
@keyframes fogIn {
    0%   { opacity: 0; transform: translateY(34px) scale(.45); }
    35%  { opacity: 1; }
    100% { opacity: .8; transform: translateY(-10px) scale(1.7); }
}
@keyframes fogDrift {
    0%   { transform: translate(0,0) scale(1.5); }
    50%  { transform: translate(9px,-10px) scale(1.72); }
    100% { transform: translate(-7px,4px) scale(1.5); }
}
.fog-puff.in {
    animation: fogIn 1.1s ease both, fogDrift 4.5s ease-in-out 1.1s infinite alternate;
}
.stunned-tint .trading-card {
    filter: sepia(.45) hue-rotate(50deg) saturate(1.6) brightness(.9);
    transition: filter .8s ease;
}
@keyframes stunStars {
    0%,100% { transform: rotate(-8deg); }
    50%     { transform: rotate(8deg); }
}
.stun-badge-vfx {
    position: fixed;
    font-size: 1.8rem;
    z-index: 9300;
    animation: stunStars 1.2s ease-in-out infinite;
    filter: drop-shadow(0 0 7px rgba(120,200,60,.8));
    pointer-events: none;
}

/* ── Embedded quills ───────────────────────────────────── */
.embedded-quill {
    position: absolute;
    height: 4px;
    width: 95px;
    pointer-events: none;
    opacity: 0;
    transform-origin: left center;
    /* Sharp right tip via clip-path */
    clip-path: polygon(0 15%, 88% 15%, 100% 50%, 88% 85%, 0 85%);
    background: linear-gradient(to right, #2c241a 0%, #6b5a44 35%, #e8dcc8 100%);
    box-shadow: 0 1px 3px rgba(0,0,0,.5);
}
@keyframes quillShudder {
    0%,100% { translate: 0 0; }
    20%     { translate: 1px -2px; }
    50%     { translate: -1px 2px; }
    80%     { translate: 0 -1px; }
}
@keyframes quillFlashRed {
    0%   { background: linear-gradient(to right, #2c241a, #6b5a44 35%, #e8dcc8);
           filter: drop-shadow(0 0 0 rgba(231,76,60,0)); }
    35%  { background: linear-gradient(to right, #5e1410, #c0392b 35%, #ff8a7a);
           filter: drop-shadow(0 0 10px rgba(231,76,60,1)); }
    100% { background: linear-gradient(to right, #2c241a, #6b5a44 35%, #e8dcc8);
           filter: drop-shadow(0 0 0 rgba(231,76,60,0)); }
}
.quill-proc {
    animation: quillShudder .15s linear 3, quillFlashRed .6s ease both;
}
@keyframes quillFadeOut {
    to { opacity: 0; transform: translateX(14px) scale(.8); }
}
.quill-fading {
    animation: quillFadeOut .7s ease both;
}
.quill-dmg-text {
    position: fixed;
    font-weight: 900;
    font-size: 2rem;
    color: var(--danger);
    text-shadow: 0 0 12px rgba(231,76,60,.95), 0 2px 3px #000;
    pointer-events: none;
    z-index: 9400;
    transform: translate(-50%, 0);
    animation: quillDmgFloat 1s ease both;
}
@keyframes quillDmgFloat {
    0%   { transform: translate(-50%,0) scale(.7); opacity: 0; }
    20%  { opacity: 1; transform: translate(-50%,0) scale(1.15); }
    100% { transform: translate(-50%, -48px) scale(1); opacity: 0; }
}

/* ── Triage dramatic sequence ──────────────────────────── */
/* Drawn by js/ui/triageFx.js and tuned in the Post-Battle Lab. Its knobs reach
   these rules as custom properties (set on the scene / the row); every var()
   fallback below is the shipped value, so nothing changes until a commit.
     --td-scale / --td-gap / --td-k   card scale, gap, stamp-size factor (row)
     --ts-ms / --ts-from / --ts-from-deg / --ts-deg / --ts-over   the stamp slam
     --tr-k (rumble strength) · --tss-k / --tss-ms (stage shake)            */
#triage-drama-row {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    justify-content: center;
    align-items: flex-start;
    max-width: 580px;   /* forces wrap at 3 cards: 3×160px + 2×16px gap */
    margin: 0 auto 28px;
    min-height: 180px;
}
.triage-drama-wrap {
    position: relative;
    flex: 0 0 calc(260px * var(--td-scale, 0.6154));   /* 160px */
    padding-bottom: 30px;   /* room for the Survives label below the card */
}
/* Dense mode — set by uiTriage when the reveal has >3 cards.
   The official outcome rule shows every DEPLOYED card (up to 6), not just the
   casualties (1–3) the row was originally sized for, which pushed the whole
   screen past three viewport-heights of scrolling. Shrinking the cards keeps a
   full 6-card deck to two tight rows so the outcome reads at a glance. */
#triage-drama-row.dense { max-width: 660px; gap: var(--td-gap, 10px); min-height: 150px; }
#triage-drama-row.dense .triage-drama-wrap {
    flex: 0 0 calc(260px * var(--td-scale, 0.4769)); padding-bottom: 24px;
    height: calc(364px * var(--td-scale, 0.4769));   /* 124 × 174 at the shipped 0.4769 */
}
#triage-drama-row.dense .triage-drama-wrap .trading-card { transform: scale(var(--td-scale, 0.4769)); }
#triage-drama-row.dense .triage-stamp { font-size: calc(1.15rem * var(--td-k, 1)); letter-spacing: 1px; border-width: 3px; padding: 1px 6px; }

/* Triage cards use the real buildAnimalCard renderer (canonical 260×364), and
   are SCALED to fit the column rather than resized into it.
 *
 * This used to set width/height/padding directly and then hand-tune five font
 * sizes (header, emoji, stats, ability, stamps) to compensate — with a comment
 * conceding that "overflow:hidden clips any tight internal text". It did:
 * measured on a dense card, .tc-ability reflowed to 6.72px with scrollHeight 83
 * against clientHeight 26, so the ability text was cut mid-sentence.
 *
 * transform: scale() is what every other small surface already uses (the card
 * picker at 0.5, the flip reveal at 0.7692, the strike deck) and it scales the
 * card as a unit — every font, the emoji and the stamps included — so nothing
 * reflows and nothing clips. The layout box stays 260×364, so the wrap carries
 * an explicit height and the card is taken out of flow; the wrap is already
 * position:relative for the outcome stamp, which is unaffected because it is
 * positioned against the WRAP, not the card.
 *
 * Nothing animates `transform` on .trading-card in this row (the rumble is on
 * the wrap, triage-card-dead only sets a filter, and card-rabies-tear belongs
 * to the battle arena), so the scale cannot be clobbered mid-animation. */
.triage-drama-wrap { height: calc(364px * var(--td-scale, 0.6154)); }        /* 224px */
.triage-drama-wrap .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(var(--td-scale, 0.6154));                /* 260 → 160 */
    transform-origin: top left;
}
@keyframes triageRumble {
    0%,100% { transform: translate(0,0) rotate(0); }
    20%     { transform: translate(calc(-3px * var(--tr-k, 1)), calc(1px * var(--tr-k, 1))) rotate(calc(-1.2deg * var(--tr-k, 1))); }
    40%     { transform: translate(calc(3px * var(--tr-k, 1)), calc(-1px * var(--tr-k, 1))) rotate(calc(1.2deg * var(--tr-k, 1))); }
    60%     { transform: translate(calc(-3px * var(--tr-k, 1)), calc(-1px * var(--tr-k, 1))) rotate(calc(-.8deg * var(--tr-k, 1))); }
    80%     { transform: translate(calc(2px * var(--tr-k, 1)), calc(1px * var(--tr-k, 1))) rotate(calc(.9deg * var(--tr-k, 1))); }
}
.triage-rumbling { animation: triageRumble .14s linear infinite; }
.triage-flash-layer {
    position: absolute;
    inset: 0;
    background: #fff;
    border-radius: 12px;
    opacity: 0;
    pointer-events: none;
    z-index: 5;
}
@keyframes triageFlash { 0%{opacity:0;} 25%{opacity:1;} 100%{opacity:0;} }
.triage-flash-layer.go { animation: triageFlash .35s ease both; }
.triage-card-dead .trading-card {
    filter: grayscale(1) brightness(.45);
    transition: filter .4s ease;
}
@keyframes stampAppear {
    0%   { transform: scale(var(--ts-from, 3.2)) rotate(var(--ts-from-deg, -18deg)); opacity: 0; }
    55%  { transform: scale(1) rotate(var(--ts-deg, -12deg)); opacity: 1; }
    65%  { transform: scale(var(--ts-over, 1.18)) rotate(var(--ts-deg, -12deg)); }
    100% { transform: scale(1) rotate(var(--ts-deg, -12deg)); opacity: 1; }
}
.triage-stamp {
    position: absolute;
    top: 38%; left: 50%;
    translate: -50% -50%;
    font-size: calc(1.9rem * var(--td-k, 1));
    font-weight: 900;
    letter-spacing: 3px;
    border-radius: 8px;
    padding: 2px 10px;
    background: rgba(0,0,0,.45);
    z-index: 6;
    pointer-events: none;
    animation: stampAppear var(--ts-ms, .45s) cubic-bezier(.2,1.2,.3,1) both;
}
.triage-stamp.dead {
    color: var(--danger);
    text-shadow: 0 0 18px rgba(231,76,60,.9), 2px 2px 0 #000;
    border: 4px solid var(--danger);
}
.triage-stamp.poached {
    color: #3498db;
    text-shadow: 0 0 18px rgba(52,152,219,.9), 2px 2px 0 #000;
    border: 4px solid #3498db;
}
/* RABID (a rabid survivor) / RABIES (a rabies death): slams in AFTER the
   outcome stamp, directly UNDER it and one layer beneath it (Jim, 2026-09-28). */
.triage-stamp.rabid {
    top: calc(38% + 3.5rem); white-space: nowrap;
    z-index: 5;
    font-size: calc(1.2rem * var(--td-k, 1)); letter-spacing: 2px; padding: 1px 8px;
    color: #c8ff4a; background: rgba(8,20,0,.72);
    text-shadow: 0 0 12px rgba(90,255,40,.8), 2px 2px 0 #000;
    border: 3px solid #7aff3a;
}
#triage-drama-row.dense .triage-stamp.rabid { top: calc(38% + 2.2rem * var(--td-k, 1)); font-size: calc(.78rem * var(--td-k, 1)); border-width: 2px; padding: 0 5px; }
.triage-stamp.survives {
    color: var(--accent);
    text-shadow: 0 0 18px rgba(46,204,113,.9), 2px 2px 0 #000;
    border: 4px solid var(--accent);
}
/* Valhalla's Call — a survivor the ROLL never touched. Gold rather than the
   survives green so it reads as "a mod did this", and a notch smaller because
   PROTECTED is three characters longer than SURVIVES and would otherwise run
   past the card edge on a phone. */
.triage-stamp.protected {
    color: #f1c40f;
    text-shadow: 0 0 18px rgba(241,196,15,.9), 2px 2px 0 #000;
    border: 4px solid #f1c40f;
    font-size: calc(1.45rem * var(--td-k, 1));
    letter-spacing: 2px;
}
/* ── Mod-triggered steal (Phase 3) ────────────────────────────────────────
   Deliberately distinct from the standard poach reveal: violet + a hard slam
   rather than the poach flip, because this is something a mod caused, not the
   battle's own outcome. */
.mod-steal-overlay {
    position: fixed; inset: 0; z-index: 9600; background: rgba(0,0,0,.92);
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
/* pointer-events:none is load-bearing, not cosmetic: the slam animation scales
   this wrapper to 2.4x, which visually covers the Continue button beneath it.
   Without this, that scaled card intercepts the click for the length of the
   animation — the same "card covers the only exit" trap that made the old
   poach reveal unclosable. The card is decorative; only the button is
   interactive, so it must never be able to take a click at any scale. */
.mod-steal-card {
    position: relative; width: 200px; height: 280px; margin: 10px auto;
    pointer-events: none;
}
.mod-steal-card .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.7692); transform-origin: top left;
}
.triage-stamp.mod-stolen {
    color: #9b59b6;
    text-shadow: 0 0 18px rgba(155,89,182,.95), 2px 2px 0 #000;
    border: 4px solid #9b59b6;
    font-size: 1.5rem;
}
@keyframes modStealSlam {
    0%   { transform: scale(2.4) rotate(10deg); opacity: 0; filter: brightness(2.4); }
    55%  { transform: scale(.94) rotate(-3deg); opacity: 1; filter: brightness(1.3); }
    75%  { transform: scale(1.05) rotate(1deg); }
    100% { transform: scale(1) rotate(0deg); filter: brightness(1); }
}
.mod-steal-slam { animation: modStealSlam .62s cubic-bezier(.2,1.1,.3,1.05) both; }
/* The mod that fired is gone (server already wrote that back) — show it break. */
@keyframes modShatter {
    0%   { opacity: 0; transform: translateY(-6px) scale(1.25); filter: brightness(2.2); letter-spacing: 3px; }
    45%  { opacity: 1; transform: translateY(0) scale(1); }
    100% { opacity: .75; transform: translateY(0) scale(.97); filter: grayscale(1) brightness(.7); }
}
.mod-steal-destroyed {
    margin-top: 10px; min-height: 1.3em;
    color: var(--danger); font-weight: 900; font-size: .82rem; letter-spacing: 1px;
    text-shadow: 0 0 10px rgba(231,76,60,.8), 1px 1px 0 #000;
    opacity: 0;
}
.mod-steal-destroyed.go { animation: modShatter .8s ease both; }
@keyframes triageScreenShake {
    0%,100% { transform: translate(0,0); }
    25%     { transform: translate(calc(-5px * var(--tss-k, 1)), calc(3px * var(--tss-k, 1))); }
    50%     { transform: translate(calc(5px * var(--tss-k, 1)), calc(-3px * var(--tss-k, 1))); }
    75%     { transform: translate(calc(-3px * var(--tss-k, 1)), calc(-2px * var(--tss-k, 1))); }
}
.triage-stage-shake { animation: triageScreenShake var(--tss-ms, .3s) linear; }
.triage-survives .trading-card {
    border-color: var(--accent) !important;
    box-shadow: 0 0 14px rgba(46,204,113,.4);
    transition: border-color .3s, box-shadow .3s;
}
/* …and the Valhalla card overrides that glow to match its own stamp. Listed
   after .triage-survives so it wins on source order (both are one class deep). */
.triage-protected .trading-card {
    border-color: #f1c40f !important;
    box-shadow: 0 0 18px rgba(241,196,15,.55);
}

/* ── Triage poach flip (win only) ──────────────────────── */
/* Poach reveal is an OVERLAY, not a row in the triage column. Inline, it sat
   between the drama row and the Return Home button: on a phone that pushed the
   button below the fold, and (before .triage-flip-face .trading-card got its
   scale) the oversized card painted straight over the button and swallowed the
   taps. An overlay can't collide with the page under it at any viewport, and
   the reveal reads as its own moment rather than one more row to scroll past.
   JS toggles display:none <-> flex. */
#triage-poach-reveal {
    position: fixed;
    inset: 0;
    z-index: 9500;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 20px;
    background: radial-gradient(circle at 50% 45%, rgba(48,14,66,0.94) 0%, rgba(0,0,0,0.95) 72%);
    animation: poachOverlayIn .3s ease-out;
}
@keyframes poachOverlayIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
#triage-poach-reveal h3 {
    color: #9b59b6;
    letter-spacing: 3px;
    text-transform: uppercase;
    margin-bottom: 14px;
}
#triage-poach-dismiss {
    margin-top: 22px;
    background: #9b59b6;
    color: #fff;
    padding: 13px 38px;
    font-size: 1.05rem;
    animation: labelSpring .45s cubic-bezier(.2,1.1,.3,1.1) both;
}
.triage-flip-scene {
    perspective: 900px;
    width: 200px;
    height: 280px;
    margin: 0 auto;
    position: relative;
}
.triage-flip-inner {
    width: 100%; height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform .9s cubic-bezier(.3,0,.25,1);
}
.triage-flip-face {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
}
/* .trading-card is a fixed 260x364 box; this face is 200x280 (same 5:7
   ratio). Without this scale it overflows past the box's bottom edge and,
   because this element is position:relative, paints ABOVE any later static
   sibling (e.g. a "Continue" button right below it) regardless of DOM
   order — silently covering and blocking it. */
.triage-flip-face .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.7692); transform-origin: top left;
}
.triage-flip-back-face { transform: rotateY(180deg); }
.triage-flipped { transform: rotateY(180deg); }
@keyframes poachGlow {
    0%   { box-shadow: 0 0 0 0 rgba(155,89,182,0); }
    40%  { box-shadow: 0 0 42px 14px rgba(155,89,182,.85); }
    100% { box-shadow: 0 0 16px 4px rgba(155,89,182,.35); }
}
.triage-poach-glow .trading-card {
    border-color: #9b59b6 !important;
    animation: poachGlow 1s ease both;
}
@keyframes labelSpring {
    0%   { transform: translateY(14px) scale(.6); opacity: 0; }
    60%  { transform: translateY(0) scale(1.15); opacity: 1; }
    100% { transform: translateY(0) scale(1); opacity: 1; }
}
.triage-poach-label {
    margin-top: 12px;
    color: #9b59b6;
    font-weight: 900;
    letter-spacing: 2px;
    font-size: .95rem;
    text-shadow: 0 0 12px rgba(155,89,182,.8);
    animation: labelSpring .5s cubic-bezier(.2,1.1,.3,1.1) .7s both;
    opacity: 0;
    animation-fill-mode: both;
}

/* ── Upgrade reveal (Alpha Dominance / Apex Mutation) ───────────────────────
   A punchier sibling of #consumable-result-overlay, reserved for upgrade-type
   consumables: the card(s) sit face-down and visibly vibrating (something is
   about to happen), THEN flip with a bright flash and a large "UPGRADED!"
   stamp — vs. the plain instant reveal every other consumable gets. Reuses the
   .triage-flip-* flip vocabulary rather than inventing a second one. */
.upgrade-reveal-overlay {
    display: none;
    position: fixed; inset: 0; z-index: 9600;
    flex-direction: column; align-items: center; justify-content: center;
    gap: 18px; padding: 20px; cursor: pointer;
    background: radial-gradient(circle at 50% 45%, rgba(20,40,10,0.95) 0%, rgba(0,0,0,0.96) 72%);
    animation: poachOverlayIn .25s ease-out;
}
.upgrade-reveal-cards {
    display: flex; gap: 22px; flex-wrap: wrap; justify-content: center;
}
.upgrade-flip-scene {
    perspective: 900px;
    width: 200px; height: 280px;
    position: relative;
}
.upgrade-flip-inner {
    width: 100%; height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform .85s cubic-bezier(.3,0,.25,1);
}
.upgrade-flip-inner.flipped { transform: rotateY(180deg); }
.upgrade-flip-face { position: absolute; inset: 0; backface-visibility: hidden; }
.upgrade-flip-face .trading-card { position: absolute; top: 0; left: 0; transform: scale(0.7692); transform-origin: top left; }
.upgrade-flip-back { transform: rotateY(180deg); }
@keyframes upgradeVibrate {
    0%, 100% { transform: translate(0, 0) rotate(0deg); }
    20%      { transform: translate(-3px, 1px) rotate(-1.2deg); }
    40%      { transform: translate(3px, -1px) rotate(1.2deg); }
    60%      { transform: translate(-2px, -2px) rotate(-0.8deg); }
    80%      { transform: translate(2px, 2px) rotate(0.8deg); }
}
.upgrade-flip-scene.vibrating { animation: upgradeVibrate 0.14s linear infinite; }
@keyframes upgradeCardGlow {
    0%   { box-shadow: 0 0 0 0 rgba(46,204,113,0); }
    100% { box-shadow: 0 0 46px 16px rgba(46,204,113,.9); }
}
.upgrade-flip-back .trading-card { animation: upgradeCardGlow .5s ease both; }
.upgrade-reveal-flash {
    position: fixed; inset: 0; background: #fff;
    opacity: 0; pointer-events: none; z-index: 9601;
}
.upgrade-reveal-flash.go { animation: upgradeFlashPop .45s ease-out; }
@keyframes upgradeFlashPop {
    0%   { opacity: 0; }
    12%  { opacity: .9; }
    100% { opacity: 0; }
}
.upgrade-reveal-label {
    font-size: 2.4rem;
    font-weight: 900;
    letter-spacing: 3px;
    color: var(--accent);
    text-shadow: 0 0 18px rgba(46,204,113,.9), 0 0 40px rgba(46,204,113,.5), 2px 2px 0 #000;
    opacity: 0;
    text-align: center;
}
.upgrade-reveal-label.pop { animation: upgradeLabelPop .6s cubic-bezier(.2,1.3,.3,1.1) both; }
@keyframes upgradeLabelPop {
    0%   { transform: scale(0.2); opacity: 0; }
    55%  { transform: scale(1.25); opacity: 1; }
    75%  { transform: scale(0.95); }
    100% { transform: scale(1); opacity: 1; }
}
.upgrade-reveal-sub {
    display: block;
    margin-top: 6px;
    font-size: .85rem;
    letter-spacing: 1px;
    color: #eee;
    text-shadow: none;
}
.upgrade-reveal-hint { font-size: .72rem; color: #666; }
@media (max-width: 460px) {
    .upgrade-reveal-label { font-size: 1.7rem; }
    .upgrade-flip-scene { width: 160px; height: 224px; }
}

/* ── Battle win reward pop (+N over the YOU WIN title) ──── */
#arena-triage-scene { position: relative; }
.battle-win-reward {
    position: absolute;
    top: 8%;
    left: 50%;
    transform: translateX(-50%) scale(0.5);
    font-size: 2.2rem;
    font-weight: 900;
    color: var(--accent);
    text-shadow: 0 0 20px rgba(46,204,113,0.8), 0 0 40px rgba(46,204,113,0.4);
    z-index: 50;
    pointer-events: none;
    animation: winRewardPop 2.2s ease-out forwards;
}
/* Cash variant of the pop above: one step lower so it never lands on the
   score pop, gold instead of green, and slightly delayed so the two arrive as
   a beat rather than at once. */
.battle-win-cash {
    top: 20%;
    font-size: 1.5rem;
    color: #e8b923;
    text-shadow: 0 0 18px rgba(232,185,35,0.75), 0 0 36px rgba(232,185,35,0.35);
    animation-delay: 0.35s;
    animation-fill-mode: both;
}
@keyframes winRewardPop {
    0%   { opacity: 0; transform: translateX(-50%) scale(0.3) translateY(20px); }
    15%  { opacity: 1; transform: translateX(-50%) scale(1.15) translateY(0); }
    25%  { transform: translateX(-50%) scale(1) translateY(0); }
    75%  { opacity: 1; transform: translateX(-50%) scale(1) translateY(-10px); }
    100% { opacity: 0; transform: translateX(-50%) scale(0.9) translateY(-30px); }
}

/* ── PvP first-strike countdown ──────────────────────────────────────────────
   Do NOT reuse winRewardPop here. Its keyframes bake in translateX(-50%)
   because .battle-win-reward is position:absolute at left:50% and needs that to
   self-centre. #pvp-countdown is a full-width block inside a centred panel and
   needs no such correction, so the transform shoved it half its CONTAINER's
   width to the left — a measured -180px at every keyframe in a 360px panel,
   i.e. the number sat well left of centre and hung outside the panel entirely.
   winRewardPop also ends at translateY(-30px)/opacity:0, so each digit drifted
   upward and vanished before the next one landed. This pops in place, holds at
   opacity 1, and never translates. */
@keyframes countdownPop {
    0%   { opacity: 0; transform: scale(0.4); }
    30%  { opacity: 1; transform: scale(1.18); }
    45%  { opacity: 1; transform: scale(1); }
    100% { opacity: 1; transform: scale(1); }
}
#pvp-countdown {
    display: flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    white-space: nowrap;
}
/* The countdown dialog has fixed, known content — it must NEVER scroll. The
   generic .pvp-panel is overflow-y:auto (other dialogs need it), which grew a
   scrollbar here on short viewports; this panel opts out and instead SHRINKS
   its content to fit. */
.pvp-countdown-panel { overflow: hidden; }
@media (max-height: 560px) {
    .pvp-countdown-panel { padding: 12px 24px; }
    .pvp-countdown-panel #pvp-countdown { font-size: 2.6rem; }
    .pvp-countdown-panel #pvp-countdown.fight { font-size: 2rem; }
    .pvp-countdown-panel .pvp-title { margin: 2px 0 6px; }
}
/* "FIGHT!" is 6 glyphs where the digits are 1 — at 4.2rem + 4px tracking it
   overflows the panel on a phone. */
#pvp-countdown.fight { font-size: 3rem; }

/* ═══════════════════════════════════════════════════════════════════
   CARD FOCUS POPUP (universal card-detail — Spec 9)
   ═══════════════════════════════════════════════════════════════════ */
.card-focus-overlay {
    position: fixed; inset: 0; z-index: 9400;
    background: rgba(0,0,0,0.85);
    display: flex; align-items: center; justify-content: center; padding: 16px;
}
.cfm-panel {
    display: flex; flex-direction: column; align-items: center; gap: 14px;
    max-height: 92vh; overflow-y: auto;
}
/* touch-action: pan-y — vertical drags still scroll the popup, but sideways
   ones reach the swipe-to-browse handler (uiArena.openCardFocusModal) instead
   of being claimed by the browser, which cancelled every swipe on a phone. */
.cfm-card-wrap { flex-shrink: 0; touch-action: pan-y; }
/* Paging slides the card 34px sideways. Inside this panel (overflow-y: auto)
   that overflow flashed a horizontal scrollbar for the length of the slide —
   measured at 39px. The side padding gives the slide room inside the panel,
   and the panel never DRAWS a scrollbar (a short phone still scrolls it by
   finger or wheel). Jim: a flashing scrollbar "feels like a bug". */
.cfm-panel { overflow-x: hidden; padding-inline: 40px; scrollbar-width: none; }
.cfm-panel::-webkit-scrollbar { display: none; }
/* Strike Deck browsing: ◀ n of N ▶ ABOVE the card. Not beside it (a 260px card
   plus an arrow each side is wider than a 360px phone) and not below it, where
   a thumb reaching for the next arrow could land on Return to Reserve. */
.cfm-nav-row { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: -2px; }
.cfm-nav {
    width: 36px; height: 36px; border-radius: 50%; padding: 0;
    border: 1px solid #3a3a3a; background: #161616; color: #ddd;
    font: inherit; font-size: .8rem; cursor: pointer; transition: border-color .15s, color .15s, opacity .15s;
}
.cfm-nav:disabled { opacity: .22; cursor: default; }
.cfm-nav:not(:disabled):hover { border-color: var(--accent); color: var(--accent); }
.cfm-nav:focus-visible { outline: 2px solid var(--warning); outline-offset: 2px; }
.cfm-nav-swipe { min-height: 20px; }
.cfm-pos { min-width: 64px; text-align: center; font-size: .78rem; color: var(--text-muted); font-weight: 700; font-variant-numeric: tabular-nums; }
@keyframes cfmInFromRight { from { transform: translateX(34px) rotate(1.5deg); opacity: .15; } to { transform: none; opacity: 1; } }
@keyframes cfmInFromLeft  { from { transform: translateX(-34px) rotate(-1.5deg); opacity: .15; } to { transform: none; opacity: 1; } }
.cfm-card-wrap.cfm-in-right { animation: cfmInFromRight .22s cubic-bezier(.2,.9,.3,1.25); }
.cfm-card-wrap.cfm-in-left  { animation: cfmInFromLeft  .22s cubic-bezier(.2,.9,.3,1.25); }
@media (prefers-reduced-motion: reduce) { .cfm-card-wrap.cfm-in-right, .cfm-card-wrap.cfm-in-left { animation: none; } }
/* Render the real 260×364 card; scale down only if the viewport is too narrow. */
.cfm-card-wrap .trading-card { cursor: default; }
.cfm-card-wrap .trading-card:hover { border-color: inherit; box-shadow: none; }
.cfm-action { width: 260px; max-width: 80vw; font-size: 1rem; padding: 13px; }
.cfm-action:disabled { background: #2a2a2a; color: var(--accent); opacity: 1; cursor: default; }
.cfm-close {
    width: 260px; max-width: 80vw; padding: 10px; background: none;
    border: 1px solid #333; border-radius: 8px; color: #888; cursor: pointer; font-size: 0.9rem;
}
@media (max-width: 300px) {
    .cfm-card-wrap { transform: scale(0.85); }
}

/* ── PvP deck-assembly banner (Homestead, while a battle is pending) ──── */
.pvp-pending-banner {
    display: flex; align-items: center; justify-content: space-between;
    gap: 14px; flex-wrap: wrap;
    background: linear-gradient(135deg, #2a1416 0%, #1a1012 100%);
    border: 1px solid var(--danger);
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 16px;
    box-shadow: 0 0 24px rgba(231, 76, 60, 0.25);
    animation: pvpPendingPulse 2.6s ease-in-out infinite;
}
@keyframes pvpPendingPulse {
    0%, 100% { box-shadow: 0 0 18px rgba(231, 76, 60, 0.18); }
    50%      { box-shadow: 0 0 30px rgba(231, 76, 60, 0.38); }
}
.pvp-pending-text { flex: 1; min-width: 180px; }
.pvp-pending-title {
    color: var(--danger); font-weight: 900; font-size: 0.95rem;
    letter-spacing: 1px; margin-bottom: 3px;
}
.pvp-pending-sub { color: #bba; font-size: 0.78rem; line-height: 1.4; }
/* Challenger's "deck locked, waiting for a reply" banner — a calmer blue than
   the red act-now target banner: informational, not a call to action. */
.pvp-pending-banner--sent {
    background: linear-gradient(135deg, #12202a 0%, #101820 100%);
    border-color: var(--accent);
    box-shadow: 0 0 20px rgba(52, 152, 219, 0.20);
    animation: none;
}
.pvp-pending-banner--sent .pvp-pending-title { color: var(--accent); }
.pvp-sent-btn {
    flex-shrink: 0; padding: 10px 16px; font-size: 0.85rem;
    background: none; border: 1px solid var(--accent); color: var(--accent);
}
.pvp-ready-btn { flex-shrink: 0; padding: 12px 20px; font-size: 0.95rem; }
.pvp-ready-btn:disabled { background: #2a2a2a; color: #666; cursor: not-allowed; opacity: 1; }
@media (max-width: 768px) {
    .pvp-pending-banner { flex-direction: column; align-items: stretch; text-align: center; }
    .pvp-ready-btn, .pvp-sent-btn { width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════
   PVP — Outpost challenge overlays (Spec 8)
   ═══════════════════════════════════════════════════════════════════ */
/* `.pvp-overlay` had NO rule: uiPvp's overlay() helper set the backdrop
   inline, so the class was decorative there and every overlay built by hand
   elsewhere (uiTriage's POACH BLOCKED / MOD SPENT / mod-steal reveals) was a
   plain static block appended to the end of <body> — it rendered BELOW the
   page instead of over it, and its Continue button could sit past the bottom
   of the viewport. Found while adding the defensive-mod reveal, which copied
   the same markup. The inline styles in overlay() still win where they are
   set, so this only supplies what was missing. */
.pvp-overlay {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.9);
    z-index: 9500;
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
}
.pvp-panel {
    background: #111;
    border: 1px solid var(--accent);
    border-radius: 14px;
    padding: 24px;
    width: 360px;
    max-width: 92vw;
    max-height: 88vh;
    overflow-y: auto;
    box-shadow: 0 0 40px rgba(0,0,0,0.7);
}
.pvp-title { color: var(--accent); margin: 0 0 12px; font-size: 1.15rem; }
.pvp-body  { color: #bbb; font-size: 0.86rem; line-height: 1.5; margin: 0 0 8px; }

/* S2-3: the poach-reveal panel (title + 280px flip card + Continue) is taller
   than a landscape phone (~360px), so the stacked Continue button spilled up
   over the 3D-transformed card. In landscape, lay it out HORIZONTALLY — card in
   its own left column, title/body/button stacked in the right column — so the
   button and card can never share space. The card keeps its 200×280 size (no
   rescale), and total panel height is just the card + padding, which fits. */
@media (max-height: 520px), (orientation: landscape) and (max-width: 900px) {
    .pvp-poach-panel {
        display: grid;
        grid-template-columns: 200px minmax(150px, 1fr);
        grid-template-areas:
            "card title"
            "card body"
            "card btn";
        grid-template-rows: auto auto 1fr;
        align-items: start; gap: 6px 20px;
        width: auto; max-width: 96vw; max-height: 96vh; padding: 14px 20px;
    }
    .pvp-poach-panel .pvp-title { grid-area: title; margin: 0; text-align: left; }
    .pvp-poach-panel .pvp-body  { grid-area: body;  margin: 0; text-align: left; }
    .pvp-poach-panel .triage-flip-scene { grid-area: card; margin: 0 !important; align-self: center; }
    .pvp-poach-panel .pvp-ok { grid-area: btn; align-self: end; justify-self: start; margin-top: 8px; }
}
.pvp-subhead { color: #777; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 1px; margin: 16px 0 8px; }
.pvp-member-list { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.pvp-member {
    display: flex; justify-content: space-between; align-items: center;
    padding: 12px 14px; background: #1a1a1e; border: 1px solid #333;
    border-radius: 10px; color: #eee; font-size: 0.9rem; cursor: pointer;
    transition: 0.15s;
}
.pvp-member:hover { border-color: var(--danger); background: #241618; }
.pvp-member-go { color: var(--danger); font-weight: 700; font-size: 0.8rem; flex-shrink: 0; }
/* Name + "Pop: N" (2026-10-14). A long name ellipsizes; the dot, the Pop and the
   Invade label keep their size and place, at 360px too. */
.pvp-member { gap: 10px; text-align: left; }
.pvp-member-who { display: flex; align-items: center; flex: 1 1 auto; min-width: 0; }
.pvp-member-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvp-pop { flex-shrink: 0; margin-left: 8px; font-size: 0.7rem; font-weight: 400; color: #7c8ea8; white-space: nowrap; }
.pvp-member-busy {
    opacity: 0.45; cursor: not-allowed; color: #888;
}

/* ── AA3: Invade traffic lights ──────────────────────────────────────────────
   The dot is its own hit target inside the row button, so it needs a real size
   and a pointer — a 10px decorative circle would be an unhittable tap target
   on a phone. 18px box, 11px visible dot. */
.pvp-dot {
    display: inline-block; width: 11px; height: 11px; border-radius: 50%;
    margin-right: 9px; vertical-align: middle; flex-shrink: 0;
    cursor: pointer; position: relative;
    box-shadow: 0 0 0 3px rgba(0,0,0,.35);
}
/* Enlarges the TAP area without enlarging the dot (Fitts' law on mobile). */
.pvp-dot::after {
    content: ''; position: absolute; inset: -9px; border-radius: 50%;
}
.pvp-dot-red    { background: #e74c3c; box-shadow: 0 0 0 3px rgba(0,0,0,.35), 0 0 7px rgba(231,76,60,.65); }
.pvp-dot-yellow { background: #f1c40f; box-shadow: 0 0 0 3px rgba(0,0,0,.35), 0 0 7px rgba(241,196,15,.6); }
.pvp-dot-green  { background: #2ecc71; box-shadow: 0 0 0 3px rgba(0,0,0,.35), 0 0 7px rgba(46,204,113,.6); }
.pvp-dot:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

.pvp-legend {
    display: flex; flex-wrap: wrap; gap: 6px 14px; justify-content: center;
    margin: 2px 0 10px; font-size: .68rem; color: #7c8ea8;
}
.pvp-legend span { display: inline-flex; align-items: center; }
.pvp-legend .pvp-dot { width: 8px; height: 8px; margin-right: 5px; cursor: default; box-shadow: none; }
.pvp-legend .pvp-dot::after { content: none; }

/* Sits ABOVE the Invade overlay (z 9500) and the confirm layer — a tooltip that
   renders behind the panel that raised it is worse than no tooltip. */
.pvp-dot-tip {
    position: fixed; z-index: 9990; max-width: 230px;
    background: #10151d; color: #e6e0d4; border: 1px solid #2c3a4d;
    border-radius: 8px; padding: 8px 11px; font-size: .74rem; line-height: 1.35;
    box-shadow: 0 8px 22px rgba(0,0,0,.6);
    opacity: 0; transform: translateY(4px); transition: opacity .13s ease, transform .13s ease;
    pointer-events: none;
}
.pvp-dot-tip.show { opacity: 1; transform: translateY(0); }
.pvp-dot-tip::after {
    content: ''; position: absolute; left: 50%; margin-left: -5px; bottom: -5px;
    width: 9px; height: 9px; background: #10151d;
    border-right: 1px solid #2c3a4d; border-bottom: 1px solid #2c3a4d;
    transform: rotate(45deg);
}
.pvp-dot-tip.below::after { bottom: auto; top: -5px; transform: rotate(225deg); }
.pvp-member-busy:hover { border-color: #333; background: #1a1a1e; }
.pvp-outgoing {
    display: flex; justify-content: space-between; align-items: center;
    padding: 8px 12px; background: #16130d; border: 1px solid #2a2418;
    border-radius: 8px; color: #c8b090; font-size: 0.82rem; margin-bottom: 6px;
    /* Countdown sits between the name and Cancel; wrap rather than squeeze on a
       narrow phone with a long display name. */
    flex-wrap: wrap; row-gap: 4px;
}
.pvp-outgoing-countdown {
    color: var(--text-muted); font-size: 0.76rem; white-space: nowrap;
    font-variant-numeric: tabular-nums;   /* the minute-tick shouldn't reflow neighbours */
}
.pvp-cancel {
    background: #1a1410; border: 1px solid var(--danger); color: var(--danger);
    border-radius: 6px; padding: 4px 12px; font-size: 0.76rem; font-weight: 700; cursor: pointer;
}
.pvp-rules {
    color: #ddd; font-size: 0.86rem; line-height: 1.55; white-space: pre-wrap;
    font-family: inherit; margin: 0 0 16px;
}
/* Remy framing on the Battle Rules popup — matches his tutorial/NPC presentation. */
.pvp-rules-remy { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.pvp-remy-avatar {
    font-size: 2.4rem; width: 54px; height: 54px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    background: radial-gradient(circle at 50% 40%, #2e2216, #14100a);
    border: 2px solid #7a5020; border-radius: 50%;
}
.pvp-remy-name { color: #c8a44a; font-weight: 800; letter-spacing: 1px; font-size: 0.9rem; }

/* ═══════════════════════════════════════════════════════════════════
   F3 — 3D CARD TILT + FOIL EFFECTS
   Cards tilt 4° toward the cursor. Alpha = gold shimmer.
   Apex = rainbow holographic foil, pointer-tracked on hover.
   Mobile: no tilt/scale (hover media query gates it).
   ═══════════════════════════════════════════════════════════════════ */

/* Register --ha so CSS can animate it as an angle */
@property --ha {
    syntax: '<angle>';
    inherits: false;
    initial-value: 135deg;
}

/* Tilt and scale — desktop hover only */
@media (hover: hover) {
    .trading-card {
        transform-style: preserve-3d;
        will-change: transform;
        transition: transform 300ms cubic-bezier(0.2, 0.8, 0.3, 1),
                    box-shadow 0.2s ease;
    }
    .trading-card:hover {
        box-shadow: 0 16px 48px rgba(0,0,0,0.65), 0 6px 16px rgba(0,0,0,0.45);
    }
}

/* Shared foil / glint base */
.holo-layer, .glint-layer {
    position: absolute; inset: 0;
    border-radius: inherit;
    pointer-events: none;
    z-index: 4;
}

/* ── GLINT — bright specular spot that follows the cursor ── */
.glint-layer {
    opacity: 0;
    transition: opacity 0.15s ease;
    background: radial-gradient(
        circle at var(--gx, 50%) var(--gy, 50%),
        rgba(255,255,255,0.16) 0%,
        rgba(255,255,255,0.05) 35%,
        transparent 65%
    );
}
@media (hover: hover) {
    .trading-card:hover .glint-layer { opacity: 1; }
}

/* ── ALPHA — gold shimmer sweep ── */
@keyframes goldShimmer {
    from { background-position: 220% center; }
    to   { background-position: -220% center; }
}
.holo-gold {
    mix-blend-mode: screen;
    opacity: 0.9;
    background: linear-gradient(
        105deg,
        transparent 25%,
        rgba(255,200,50,0.07)  35%,
        rgba(255,215,0,0.22)   45%,
        rgba(255,185,30,0.28)  50%,
        rgba(255,215,0,0.22)   55%,
        rgba(255,200,50,0.07)  65%,
        transparent 75%
    );
    background-size: 200% 100%;
    animation: goldShimmer 4.5s linear infinite;
}

/* ── APEX — rainbow holographic foil ── */
@keyframes holoIdle {
    0%   { --ha: 115deg; opacity: 0.28; }
    50%  { --ha: 158deg; opacity: 0.52; }
    100% { --ha: 115deg; opacity: 0.28; }
}
.holo-rainbow {
    mix-blend-mode: color-dodge;
    background: linear-gradient(
        var(--ha),
        transparent      20%,
        rgba(255,90,255,0.16)  30%,
        rgba(90,190,255,0.20)  40%,
        rgba(255,245,90,0.13)  50%,
        rgba(90,255,145,0.16)  60%,
        rgba(195,90,255,0.16)  70%,
        transparent      80%
    );
    animation: holoIdle 3.5s ease-in-out infinite;
}
/* Pointer-driven override suspends idle animation */
.holo-rainbow.tracking { animation: none; opacity: 0.55; }
/* poach-badge removed — replaced by .poached-stamp inside tc-image-box */

/* ═══════════════════════════════════════
   RABIES MECHANIC
   ═══════════════════════════════════════ */

/* Cracked red border on rabid cards — applies in all contexts */
.trading-card.card-rabid,
.sd-card-wrap .trading-card.card-rabid,
#focus-card.card-rabid {
    border-color: #c0392b !important;
    box-shadow: 0 0 10px rgba(192, 57, 43, 0.4), inset 0 0 8px rgba(192,57,43,0.1) !important;
}

/* ── Status banners — ONE slim strip across the top of the photo (Jim, 2026-09-25) ──
   The explanation line (.stamp-sub) moved into the tap popup, which cut the
   strip from ~60px (two stacked banners with subtext) to 20px. A single status
   spans the full width; a card that is BOTH rabid and poached splits the strip
   in half (.stamp-half, set by cards.js). Tappable: cardInfo.showCardBadgePopup. */
.rabid-stamp,
.poached-stamp {
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 6;
    cursor: pointer;
    text-align: center;
    font-weight: 900;
    letter-spacing: 1.5px;
    line-height: 1.2;
    padding: 2px 4px 3px;
    border-radius: 2px 2px 0 0;
    white-space: nowrap;
    text-shadow: 0 1px 3px rgba(0,0,0,.7);
}
.rabid-stamp {
    background: rgba(180, 20, 20, 0.92);
    border-bottom: 2px solid rgba(255, 80, 80, 0.7);
    color: #fff;
    font-size: 0.6rem;
}
.poached-stamp {
    background: rgba(120, 50, 160, 0.92);
    border-bottom: 2px solid rgba(180, 100, 255, 0.7);
    color: #f0d0ff;
    font-size: 0.6rem;
}
/* Hidden on the card face, kept in the markup: it is the popup's text, so the
   popup can never say something different from what the card was built with. */
.stamp-sub {
    display: none;
    font-size: 0.47rem;
    font-weight: 600;
    letter-spacing: 0.3px;
    opacity: 0.9;
    margin-top: 1px;
}
.rabid-stamp.stamp-half   { right: 50%; border-right: 1px solid rgba(0,0,0,0.35); border-radius: 2px 0 0 0; }
/* Bitten THIS battle (results screen, uiTriage): after its ☣ RABID stamp slams
   in, the banner drops onto the card with a red flash — the reveal Jim asked
   for, distinct from a card that was rabid already (it just wears its banner).
   Until then its rabies stat chips are held back with it (.rabid-pending). */
.rabid-stamp.rabid-strip-in { animation: rabidStripIn .55s cubic-bezier(.2,.9,.3,1.3) both; transform-origin: top center; }
@keyframes rabidStripIn {
    0%   { transform: scaleY(0);    opacity: 0; filter: brightness(2.6); }
    55%  { transform: scaleY(1.18); opacity: 1; filter: brightness(1.8); }
    100% { transform: scaleY(1);    opacity: 1; filter: none; }
}
.rabid-pending .stat-rabid { visibility: hidden; }
@media (prefers-reduced-motion: reduce) { .rabid-stamp.rabid-strip-in { animation: none; } }
.poached-stamp.stamp-half { left: 50%; border-radius: 0 2px 0 0; }

/* Where a tap on the CARD must win, banners and tray chips let it through:
   the fighter you tap to attack (#arena-card-p1 onclick=tapToAttack), the
   opponent's fighter, the companion fan, and the battle-results row. Chips stay
   live on the fighters and companions, like the ℹ️ icon beside them. */
.combat-card-wrapper-main .rabid-stamp, .combat-card-wrapper-main .poached-stamp,
.companion-card-wrap .rabid-stamp,      .companion-card-wrap .poached-stamp,
.triage-drama-wrap .rabid-stamp,        .triage-drama-wrap .poached-stamp,
.triage-drama-wrap .tc-badge-chip { pointer-events: none; }

/* Red flash when rabies effect fires */
@keyframes rabiesFlash {
    0%   { box-shadow: 0 0 0 0 rgba(192,57,43,0); }
    30%  { box-shadow: 0 0 28px 8px rgba(192,57,43,0.8); border-color: #e74c3c !important; }
    100% { box-shadow: 0 0 0 0 rgba(192,57,43,0); }
}
.card-rabies-flash { animation: rabiesFlash 0.6s ease both; }

/* In-battle tear animation — bigger swing, harder flash, longer hold so a
   permanent death reads as unmistakably heavier than a normal loss. */
@keyframes cardTear {
    0%   { transform: perspective(600px) rotateY(0deg) scaleX(1); filter: brightness(1); }
    12%  { transform: perspective(600px) rotateY(0deg) scaleX(1.08); filter: brightness(2.2) saturate(0); }
    35%  { transform: perspective(600px) rotateY(-14deg) translateX(-14px) scaleX(0.94); filter: brightness(0.35) saturate(0); }
    65%  { transform: perspective(600px) rotateY(-26deg) translateX(-26px) scaleX(0.88); filter: brightness(0.18) saturate(0); }
    100% { transform: perspective(600px) rotateY(-34deg) translateX(-32px) scaleX(0.82); opacity: 0.15; filter: brightness(0.08) saturate(0); }
}
.card-rabies-tear {
    animation: cardTear 0.9s cubic-bezier(.4,0,.2,1) forwards;
    position: relative;
}
.card-rabies-tear::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        115deg,
        transparent 44%,
        rgba(231,76,60,0.95) 48%,
        rgba(255,255,255,0.5) 50%,
        rgba(231,76,60,0.95) 52%,
        transparent 56%
    );
    pointer-events: none;
    z-index: 10;
    border-radius: inherit;
    animation: rabiesSlashFlash 0.9s ease forwards;
}
@keyframes rabiesSlashFlash {
    0%   { opacity: 0; }
    15%  { opacity: 1; }
    100% { opacity: 0.75; }
}

/* Moose Trample: incoming card wobbles/flips on entry before taking spill damage */
@keyframes trampleEntry {
    0%   { transform: rotateY(0deg) scale(1); }
    25%  { transform: rotateY(45deg) scale(0.95); }
    75%  { transform: rotateY(-15deg) scale(1.02); }
    100% { transform: rotateY(0deg) scale(1); }
}
.card-trample-entry { animation: trampleEntry 0.5s ease-out; }

/* Tick Infestation mod card pulses red when its pre-battle salvo fires */
@keyframes modPulse {
    0%, 100% { box-shadow: none; }
    50%      { box-shadow: 0 0 10px 3px rgba(231,76,60,0.7); border-color: #e74c3c; }
}
.mod-rect-slot.mod-pulse { animation: modPulse 0.4s ease-in-out 2; }

/* V1-3: Speed Fight toggle — top-left of the combat header, opposite the
   dev-only Cancel button. Lights up green when active. */
.speed-fight-btn {
    position: absolute; left: 15px; top: 15px; z-index: 5;
    padding: 7px 14px; font-size: 0.82rem; font-weight: 800; letter-spacing: .3px;
    color: #fff; background: #2c3e50; border: 2px solid #46607a; border-radius: 8px;
    cursor: pointer; user-select: none; transition: background .15s ease, border-color .15s ease, transform .1s ease;
}
.speed-fight-btn:hover { background: #34506b; }
.speed-fight-btn:active { transform: translateY(1px); }
.speed-fight-btn.active {
    background: #1e824c; border-color: #2ecc71; box-shadow: 0 0 12px rgba(46,204,113,0.5);
}
/* Z4: 2x is green, 4x is amber. The label already reads "Speed: 2x"/"Speed: 4x",
   but the button sits in the corner of a busy combat screen — colour is what
   actually gets read at a glance, and two identical-looking "on" states in a
   three-state cycle is the classic way to lose track of which one you're in. */
.speed-fight-btn.speed-max {
    background: #9c5700; border-color: #f39c12; box-shadow: 0 0 12px rgba(243,156,18,0.55);
}
@media (max-height: 520px) {
    .speed-fight-btn { left: 10px; top: 8px; padding: 5px 10px; font-size: 0.72rem; }
}

/* Triage drama: torn card overlay for rabies_death outcome */
.triage-card-rabies .trading-card {
    filter: grayscale(1) brightness(0.4);
    transition: filter 0.3s ease;
}
.triage-tear-line {
    position: absolute;
    inset: 0;
    background: linear-gradient(
        115deg,
        transparent 48%,
        rgba(231,76,60,0.7) 49%,
        rgba(231,76,60,0.7) 51%,
        transparent 52%
    );
    border-radius: 12px;
    pointer-events: none;
    z-index: 7;
}
/* U4-5: the slash draws itself across the card, so the rabies death reads as a
   live cut rather than a red line that was just always there. */
.triage-tear-line.triage-tear-animate {
    clip-path: inset(0 100% 0 0);
    animation: triageTearSlash .34s ease-out .1s forwards;
    /* The glow follows the SLASH (drop-shadow traces the gradient's painted
       line). It was a box-shadow, which glowed around the element's whole
       rectangle — a thin red frame round the card slot on the results screen. */
    filter: drop-shadow(0 0 5px rgba(231,76,60,0.9));
}
/* In the results row the wrap's box runs past the card (padding-bottom leaves
   room under it), so the slash and the white reveal flash stop at the card's
   bottom edge instead of covering the empty strip below it. */
.triage-drama-wrap > .triage-tear-line,
.triage-drama-wrap > .triage-flash-layer { bottom: 30px; }
#triage-drama-row.dense .triage-drama-wrap > .triage-tear-line,
#triage-drama-row.dense .triage-drama-wrap > .triage-flash-layer { bottom: 24px; }
@keyframes triageTearSlash {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0 0 0 0); }
}

/* ── Tutorial spotlight ── */
.tutorial-overlay {
    position: fixed; inset: 0; z-index: 7000;
    background: rgba(0,0,0,0.72);
    pointer-events: none;
}
.tutorial-bubble {
    position: fixed; z-index: 7001;
    background: #111; border: 1px solid #2ecc71;
    border-radius: 14px; padding: 20px 22px;
    max-width: 300px; box-shadow: 0 8px 32px rgba(0,0,0,0.6);
    pointer-events: all;
}
.tutorial-bubble h3 {
    margin: 0 0 8px; color: var(--accent);
    font-size: .95rem; letter-spacing: .5px;
}
.tutorial-bubble p {
    margin: 0 0 16px; color: #aaa;
    font-size: .82rem; line-height: 1.55;
}
.tutorial-bubble-footer {
    display: flex; justify-content: space-between; align-items: center;
}
.tutorial-step-dots {
    display: flex; gap: 5px;
}
.tutorial-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: #333;
}
.tutorial-dot.active { background: var(--accent); }
.tutorial-btn-skip { font-size: .72rem; color: #555; background: none; border: none; cursor: pointer; }
.tutorial-btn-skip:hover { color: #888; }
.tutorial-btn-next {
    padding: 8px 18px; background: var(--accent); color: #000;
    border: none; border-radius: 8px; font-weight: 700;
    font-size: .82rem; cursor: pointer;
}

/* ── Tutorial: Remy-styled first step ── */
.tutorial-bubble-remy {
    border-color: #4a3728 !important;
    background: #0e0a08 !important;
    max-width: 340px;
    padding: 28px 26px;
}
.tutorial-remy-header {
    text-align: center;
    margin-bottom: 12px;
    padding-bottom: 10px;
    border-bottom: 1px solid #2a1e14;
}
.tutorial-remy-avatar { font-size: 2.8rem; margin-bottom: 4px; }
.tutorial-remy-name {
    font-size: .72rem;
    font-weight: 800;
    color: #c8a44a;
    letter-spacing: 1px;
    text-transform: uppercase;
}
.tutorial-bubble-remy p { color: #c4b49a; font-style: italic; font-size: .88rem; line-height: 1.6; }

/* ══════════════════════════════════════════════════════════════════
   CANONICAL REMY COMPONENT (Spec W1)
   ONE treatment for every Remy surface — tutorial, Trading Post,
   Battle Rules, mod-slot nudge, bounty offer/complete, season close.
   Never style a Remy popup independently again: compose these classes.
     .remy-head       header row (avatar + identity), bronze underline
       .remy-head-avatar / .remy-head-id / .remy-head-name / .remy-head-role
     .remy-quote      the italic spoken line (auto curly quotes)
     .remy-box        structured content box beneath the quote
   ══════════════════════════════════════════════════════════════════ */
/* Monthly Winners: the settling notice shown for the first week of a month,
   while the just-ended month is still taking late logs. Composes .remy-head /
   .remy-quote (see the Remy block below) and adds only the surrounding box. */
.mw-pending {
    background: rgba(80,50,20,0.15);
    border: 1px solid #4a3020; border-radius: 10px;
    padding: 14px 16px; margin-bottom: 12px;
}
.mw-pending .remy-head { padding-bottom: 10px; margin-bottom: 10px; }
.mw-pending .remy-head-avatar { width: 42px; height: 42px; font-size: 1.5rem; }
.mw-pending .remy-head-name { font-size: 1rem; letter-spacing: 2px; }
.mw-pending .remy-quote { margin-bottom: 8px; }
.mw-pending-note {
    font-size: 0.72rem; color: #7a6040; letter-spacing: 0.4px; font-style: italic;
}

.remy-head {
    display: flex; align-items: center; gap: 14px;
    padding-bottom: 12px; margin-bottom: 14px;
    border-bottom: 1px solid transparent;
    border-image: linear-gradient(90deg, #6a5030, #c8a060, #6a5030) 1;
}
.remy-head-avatar {
    flex-shrink: 0;
    width: 54px; height: 54px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 2rem; line-height: 1;
    background: radial-gradient(circle at 35% 30%, #3a2a18, #150e07);
    border: 1.5px solid #6a5030;
    box-shadow: 0 0 14px rgba(180,100,20,0.3);
}
.remy-head-id   { flex: 1; min-width: 0; }
.remy-head-name {
    font-family: var(--font-heading, serif), 'Noto Color Emoji';
    font-size: 1.25rem; font-weight: 900; letter-spacing: 3px;
    color: #c8a060; text-transform: uppercase;
    text-shadow: 0 0 12px rgba(200,160,80,0.4);
    line-height: 1.1;
}
.remy-head-role {
    font-size: 0.68rem; color: #7a6040; letter-spacing: 1px;
    margin-top: 3px; font-style: italic;
}
.remy-quote {
    font-size: 0.92rem; color: #c8b090; font-style: italic;
    line-height: 1.65; margin-bottom: 14px;
}
.remy-quote::before { content: '"'; color: #7a5030; font-size: 1.4rem; }
.remy-quote::after  { content: '"'; color: #7a5030; font-size: 1.4rem; }
.remy-box {
    background: rgba(80,50,20,0.15);
    border: 1px solid #4a3020; border-radius: 10px;
    padding: 12px 14px; margin-bottom: 14px;
    font-size: 0.85rem; color: #b0a080; line-height: 1.7;
}
.remy-box-header {
    font-size: 0.62rem; letter-spacing: 3px; color: #8a6030;
    font-weight: 800; margin-bottom: 8px; text-transform: uppercase;
}
@media (max-width: 480px) {
    .remy-head-avatar { width: 46px; height: 46px; font-size: 1.7rem; }
    .remy-head-name   { font-size: 1.1rem; letter-spacing: 2px; }
}

/* ── SPEAKER OVERRIDE: Pippa in the NPC popup ───────────────────────────────
   The pelt confirmation is Pippa's line, not Remy's, so #silas-modal is
   re-pointed at her (js/ui/uiSilas.js setSpeaker) rather than duplicated. The
   STRUCTURE stays the canonical .remy-head component — only the palette moves
   from Remy's bronze to the pink she already wears in Pippa's Market
   (#e0699e, see .fm-modal-inner below). Added by class, so nothing here can
   leak onto a Remy surface. */
.silas-modal-content.speaker-pippa .remy-head {
    border-image: linear-gradient(90deg, #5a2440, #e0699e, #5a2440) 1;
}
.silas-modal-content.speaker-pippa .remy-head-avatar {
    background: radial-gradient(circle at 35% 30%, #3a1828, #150a10);
    border-color: #7a3050;
    box-shadow: 0 0 14px rgba(224,105,158,0.3);
    /* Her portrait is three glyphs (🌸👩‍🌾🌼), not one, and it has to fit the
       SAME circle Remy's single 🧔 sits in — 54px here, 46px under 480px. Both
       of these are load-bearing and were measured in the running app, not
       guessed: nowrap (without it the trio broke onto three lines inside the
       circle at phone width) and the negative tracking (at -1px the trio is
       55-59px wide and spills out of a 54px circle). text-indent puts back the
       trailing letter-space so the cluster stays optically centred. */
    font-size: 0.95rem; letter-spacing: -3px; text-indent: 3px; white-space: nowrap;
}
.silas-modal-content.speaker-pippa .remy-head-name {
    color: #e0699e; text-shadow: 0 0 12px rgba(224,105,158,0.4);
}
.silas-modal-content.speaker-pippa .remy-head-role { color: #a06a85; }
.silas-modal-content.speaker-pippa .remy-quote     { color: #e8c0d4; }
.silas-modal-content.speaker-pippa .remy-quote::before,
.silas-modal-content.speaker-pippa .remy-quote::after { color: #8a4060; }
@media (max-width: 480px) {
    /* Same fit, against the 46px circle the base component drops to here. */
    .silas-modal-content.speaker-pippa .remy-head-avatar {
        font-size: 0.82rem; letter-spacing: -2.5px; text-indent: 2.5px;
    }
}

/* W1-3: the element Remy's dialogue is pointing at gets a live gold pulse,
   so "On the LOG tab" visibly connects to the actual LOG button. */
.remy-anchor-highlight {
    position: relative; z-index: 7002;
    animation: remyAnchorPulse 1.4s ease-in-out infinite;
    border-radius: 8px;
}
@keyframes remyAnchorPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(200,160,80,0.9), 0 0 14px rgba(200,160,80,0.5); }
    50%      { box-shadow: 0 0 0 6px rgba(200,160,80,0), 0 0 22px rgba(200,160,80,0.85); }
}

/* ══ W3-3: Active Field Gear strip (LOG page) ══════════════════════
   Deliberately compact: it sits ABOVE the animal grid, so it must never
   grow tall enough to push the grid down. Past MAX_INLINE_GEAR items it
   collapses to a single summary line (see uiLog.renderFieldGearBar). */
.fg-bar {
    background: rgba(46,204,113,0.06);
    border: 1px solid rgba(46,204,113,0.22);
    border-radius: 10px;
    padding: 8px 10px;
    margin-bottom: 10px;
}
.fg-head {
    font-size: 0.68rem; letter-spacing: 1.5px; text-transform: uppercase;
    color: #2ecc71; font-weight: 800; margin-bottom: 6px;
}
.fg-rows { display: flex; flex-direction: column; gap: 4px; }
.fg-item {
    display: flex; align-items: center; gap: 8px; width: 100%;
    background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.06);
    border-radius: 7px; padding: 6px 9px; cursor: pointer; text-align: left;
    color: #ccc; font-size: 0.8rem; font-family: inherit;
    transition: background .15s ease, border-color .15s ease;
}
.fg-item:hover { background: rgba(46,204,113,0.12); border-color: rgba(46,204,113,0.35); }
.fg-icon { font-size: 1.05rem; line-height: 1; flex-shrink: 0; }
.fg-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fg-time { flex-shrink: 0; color: #2ecc71; font-weight: 700; font-size: 0.74rem; }
.fg-summary {
    display: block; width: 100%; background: none; border: none; cursor: pointer;
    color: #2ecc71; font-size: 0.8rem; font-weight: 700; font-family: inherit;
    padding: 2px; text-align: left;
}
.fg-summary.fg-collapse { margin-top: 6px; color: #6a8; font-weight: 600; }

/* ── Shared NPC modal structure ─────────────────────────────────── */
.npc-modal {
    background: #111; border: 1px solid #2a2a2a;
    border-radius: 16px; padding: 28px;
    width: 420px; max-width: 95vw; max-height: 90vh; overflow-y: auto;
}
.npc-header {
    text-align: center; margin-bottom: 18px;
    padding-bottom: 14px; border-bottom: 1px solid #1e1e1e;
}
.npc-avatar   { font-size: 2.4rem; margin-bottom: 6px; }
.npc-name     { font-weight: 800; font-size: 1.05rem; letter-spacing: 1px; }
.npc-dialogue { color: #777; font-size: .8rem; font-style: italic; margin-top: 5px; }
.npc-balance  { text-align: center; font-size: 1rem; color: #c8a44a;
                font-weight: 700; margin-bottom: 18px; }
/* #555 text on a #2a2a2a border read as a DISABLED control — players didn't
   believe it was clickable. Same quiet-secondary role, but at contrast a live
   button actually has. */
.npc-close-btn {
    width: 100%; margin-top: 16px; padding: 10px;
    background: #1a1a1a; border: 1px solid #4a4a4a;
    border-radius: 8px; color: #c8c8c8; cursor: pointer;
    font-size: .82rem; font-weight: 600; font-family: inherit;
    transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.npc-close-btn:hover { background: #242424; border-color: #6a6a6a; color: #fff; }

/* ── Pippa's Market specific ── pink/floral accent, distinct from Remy's
   green/gold, on the same shared .npc-modal structure/width. ──────────── */
.fm-modal-inner { border-color: #4a1f35; }
.fm-modal-inner .npc-header { border-color: #4a1f35; }
.fm-modal-inner .npc-balance { color: #e0699e; }
.fm-pack-grid   { display: flex; flex-direction: column; gap: 10px; }
.fm-pack-card {
    background: #0d0d0d; border: 1px solid #2a1520;
    border-radius: 10px; padding: 14px;
    display: flex; flex-direction: column; gap: 5px;
}
.fm-pack-icon   { font-size: 1.4rem; }
.fm-pack-name   { font-weight: 700; color: #ddd; font-size: .88rem; }
.fm-pack-desc   { color: #666; font-size: .76rem; line-height: 1.4; }
.fm-pack-cost   { color: #e0699e; font-weight: 700; font-size: .82rem; }
.fm-pack-btn {
    padding: 8px 0; background: #e0699e; color: #000;
    border: none; border-radius: 8px; font-weight: 700;
    font-size: .8rem; cursor: pointer; margin-top: 4px;
    transition: background .15s ease;
}
.fm-pack-btn:hover:not(:disabled) { background: #ec8bb4; }
.fm-btn-disabled { background: #1e1418 !important; color: #55424c !important;
                   cursor: not-allowed; }

/* Pippa's pack-opening now uses the SHARED pack-opening popup (.pack-pick-*,
   defined near the top with Remy's) — the old inline .fm-pick-* pick area is
   gone. Pippa keeps only her pack-SELECTION cards (.fm-pack-*) above. */

/* Remy Trading Post — vertical layout matching Pippa's */
.remy-pack-grid   { display:flex; flex-direction:column; gap:10px; }
.remy-pack-card   { background:#0d0d0d; border:1px solid #2a2a2a; border-radius:10px;
                    padding:14px; display:flex; flex-direction:column; gap:5px; }
.remy-pack-name   { font-weight:700; color:#ddd; font-size:.88rem; }
.remy-pack-desc   { color:#666; font-size:.76rem; }
.remy-pack-cost   { color:var(--accent); font-weight:700; font-size:.82rem; }
.remy-pack-btn    { padding:8px 0; background:var(--accent); color:#000; border:none;
                    border-radius:8px; font-weight:700; font-size:.8rem; cursor:pointer;
                    margin-top:4px; transition:background .15s; }
.remy-pack-btn:hover { background:#27ae60; }
.remy-pack-btn-purple { background:#9b59b6; color:#fff; }
.remy-pack-btn-purple:hover { background:#8e44ad; }
.remy-pack-btn-gold   { background:#f1c40f; color:#000; }
.remy-pack-btn-gold:hover { background:#f39c12; }

/* ═══════════════════════════════════════════════════════
   CONSUMABLE RESULT OVERLAY
   ═══════════════════════════════════════════════════════ */
/* Fix 2D — consumable result overlay card sizing */
#consumable-result-cards .trading-card {
    transform: scale(0.78);
    transform-origin: top center;
    margin: 0 -14px;
}

/* Fix 2 — smaller calendar on mobile */
@media (max-width: 480px) {
    .calendar-wrapper { padding: 8px 4px; gap: 10px; max-width: 100%; }
    .month-grid { gap: 2px; }
    .cal-cell { font-size: .55rem; border-radius: 4px; }
    .month-title { font-size: .7rem; }
}

/* ═══════════════════════════════════════════════════════
   OUTPOST COMPARISON (stats tab species table)
   ═══════════════════════════════════════════════════════ */
/* Fix 5D — Outpost species comparison table */
.outpost-stats-table { font-size: .78rem; }
.ost-header, .ost-row { display: grid; grid-template-columns: 120px repeat(auto-fit, minmax(60px, 1fr)); gap: 4px; align-items: center; padding: 4px 0; border-bottom: 1px solid #111; }
.ost-header { font-weight: 700; color: #666; font-size: .7rem; border-bottom: 1px solid #2a2a2a; margin-bottom: 4px; }
.ost-species { color: #ccc; white-space: nowrap; overflow: hidden; }
.ost-member { color: #888; text-align: center; font-size: .65rem; }
.ost-cell { position: relative; height: 22px; background: #111; border-radius: 4px; overflow: hidden; }
.ost-bar { position: absolute; inset: 0; height: 100%; opacity: .25; transition: width .3s ease; }
.ost-count { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-weight: 700; color: #ccc; font-size: .72rem; }
.ost-leader .ost-bar { opacity: .5; }
.ost-leader .ost-count { color: var(--accent); }

/* Rotate-to-battle prompt (#rotate-overlay) removed — see js/ui/uiOrientation.js. */

/* ── Season Close ceremony (Trophy T2, Fable spec P2) ── */
.sc-panel { max-width: 480px; width: 100%; max-height: 88vh; overflow-y: auto; }
.sc-rules { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.sc-rule { font-size: .78rem; line-height: 1.45; padding: 8px 10px; border-radius: 8px; background: #161616; }
.sc-rule.sc-keep { color: #9c9; border-left: 3px solid var(--accent); }
.sc-rule.sc-lose { color: #c99; border-left: 3px solid var(--danger); }
.sc-counter { text-align: center; font-size: .82rem; color: var(--text-muted); }
.sc-counter .sc-count { color: var(--accent); font-weight: 800; }
.sc-summary-block { background: #161616; border-radius: 8px; padding: 8px 10px; margin-bottom: 8px; }
.sc-summary-label { font-size: .68rem; color: #777; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }
.sc-chip { display: inline-block; background: #222; border: 1px solid #333; border-radius: 14px;
    padding: 3px 9px; margin: 2px; font-size: .74rem; color: #ddd; }
.sc-chip em { color: #3498db; font-style: normal; font-size: .64rem; }
.sc-chip-empty { color: #666; font-style: italic; }

/* ── Season Close v2: real-card pickers + Remy voice + December banner ── */
.sc-panel-wide { max-width: 640px; }
.sc-remy-speech { color: #c4b49a; font-style: italic; }
.sc-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px;
    max-height: 52vh; overflow-y: auto; margin: 10px 0; padding: 2px; }
.sc-card-pick { position: relative; cursor: pointer; border-radius: 10px;
    transition: transform .1s, box-shadow .15s; }
.sc-card-pick .trading-card { width: 100%; box-sizing: border-box; pointer-events: none; }
.sc-card-pick:hover { transform: translateY(-3px); }
.sc-card-pick .sc-keep-stamp { display: none; }
.sc-card-pick.sc-selected { box-shadow: 0 0 0 2px var(--accent), 0 0 16px rgba(46,204,113,.4); }
.sc-card-pick.sc-selected .sc-keep-stamp { display: flex; }

#season-warning-banner { position: fixed; left: 50%; transform: translateX(-50%);
    bottom: 16px; z-index: 8900; display: flex; align-items: center; gap: 12px;
    max-width: 560px; width: calc(100% - 24px); padding: 12px 14px;
    background: #14100a; border: 1.5px solid #7a5020; border-radius: 12px;
    box-shadow: 0 6px 24px rgba(0,0,0,.6); }
.sc-warn-avatar { width: 42px !important; height: 42px !important; font-size: 1.6rem !important; }
.sc-warn-text { flex: 1; font-size: .78rem; color: #c4b49a; line-height: 1.5; }
.sc-warn-text strong { color: #c8a44a; display: block; margin-bottom: 2px; }
.sc-warn-teaser { display: block; margin-top: 4px; color: #666; font-style: italic; font-size: .7rem; }
.sc-warn-dismiss { background: none; border: 1px solid #3a2e1c; border-radius: 8px;
    color: #997; font-size: .85rem; padding: 4px 9px; cursor: pointer; flex-shrink: 0; }

/* ── Stats page: Spotify-style controls + comparison overlay (Fable spec) ── */
.stats-topbar { display: flex; justify-content: space-between; align-items: center;
    gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.year-nav-wrap { display: flex; align-items: center; gap: 4px; min-width: 0; flex: 1; }
.time-nav { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; min-width: 0; }
.time-nav::-webkit-scrollbar { display: none; }
.year-pill { background: transparent; border: 1px solid #333; color: var(--text-muted);
    padding: 6px 14px; border-radius: 20px; white-space: nowrap; cursor: pointer;
    font-size: .82rem; font-weight: 600; transition: all .2s; flex-shrink: 0; }
.year-pill:hover { border-color: #666; color: #ddd; }
.year-pill.active { background: rgba(46, 204, 113, 0.15); border-color: var(--accent);
    color: var(--accent); font-weight: 800; }
.pill-page-arrow { display: none; align-items: center; justify-content: center;
    background: #1a1a1a; border: 1px solid #333; color: var(--text-muted);
    width: 24px; height: 24px; border-radius: 50%; cursor: pointer; font-size: .6rem;
    flex-shrink: 0; padding: 0; }
.pill-page-arrow:hover { color: #fff; border-color: #666; }

.month-filter { display: flex; align-items: center; background: var(--card-bg);
    border: 1px solid #333; border-radius: 20px; overflow: hidden; transition: all .2s; }
.month-filter.active-filter { border-color: var(--accent); background: rgba(46, 204, 113, 0.08); }
.month-filter.active-filter .month-label { color: var(--accent); font-weight: 800; }
.month-filter.disabled { opacity: 0.3; pointer-events: none; }
.month-filter .arrow-btn { background: transparent; border: none; color: var(--text-muted);
    padding: 6px 10px; cursor: pointer; font-size: .7rem; }
.month-filter .arrow-btn:hover { color: #fff; background: rgba(255,255,255,0.05); }
.month-label { font-size: .8rem; color: #ddd; min-width: 64px; text-align: center;
    user-select: none; cursor: pointer; }

.list-header { display: flex; justify-content: space-between; align-items: center;
    font-size: .72rem; color: var(--text-muted); text-transform: uppercase;
    letter-spacing: .5px; padding: 0 5px 8px; border-bottom: 1px solid #2a2a2a;
    margin-bottom: 10px; user-select: none; }
.header-right { display: flex; align-items: center; gap: 14px; }
.list-header .sortable { cursor: pointer; display: flex; align-items: center; gap: 4px;
    transition: color .2s; }
.list-header .sortable:hover { color: #fff; }
.list-header .sortable.active { color: var(--accent); font-weight: 800; }
.vs-header-wrap { display: flex; align-items: center; gap: 4px; min-width: 110px;
    justify-content: flex-end; }
.compare-dropdown { background: transparent; color: var(--text-muted); border: none;
    font-size: .72rem; text-transform: uppercase; outline: none; cursor: pointer;
    appearance: none; font-family: inherit; text-align: right; padding-right: 12px;
    max-width: 110px;
    background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23888%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
    background-repeat: no-repeat; background-position: right top 50%; background-size: 8px auto; }
.compare-dropdown:hover { color: #fff; }
.compare-dropdown option { background: var(--card-bg); color: #fff; text-transform: none; }
.compare-dropdown option:disabled { color: #555; }
.vs-sort-btn { display: flex; align-items: center; justify-content: center;
    width: 18px; height: 18px; border-radius: 4px; cursor: pointer; color: var(--text-muted); }
.vs-sort-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
.vs-sort-btn.active { color: var(--accent); font-weight: 800; }

/* Comparison overlay: striped bar layered over your own fill; the stripe mix
   reads on any species color underneath (game-theme adaptation of the
   prototype's treatment). */
.bar-track { overflow: visible; }
.bar-overlay-compare { position: absolute; top: 0; left: 0; height: 100%;
    border-radius: 9px; z-index: 3; pointer-events: none; transition: width .4s ease;
    background: repeating-linear-gradient(-45deg,
        rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.32) 4px,
        rgba(0, 0, 0, 0.28) 4px, rgba(0, 0, 0, 0.28) 8px);
    border-right: 2px solid #fff; box-shadow: 2px 0 6px rgba(0,0,0,0.7); }
.delta-pill { font-size: .68rem; font-weight: 800; width: 52px; text-align: right;
    flex-shrink: 0; }
.delta-pill.pos { color: var(--accent); }
.delta-pill.neg { color: var(--danger); }
.delta-pill.neu { color: var(--text-muted); }

/* ── W5: shared card picker ───────────────────────────────────────────────────
   The "choose one of your cards" step. Selective Breeding is the first user,
   but nothing here is Selective-Breeding-specific — the grid scrolls, so it
   holds a 60-card collection as happily as a 3-card one. */
.card-picker-overlay {
    display: none;
    position: fixed; inset: 0; z-index: 9600;
    flex-direction: column; align-items: center; justify-content: flex-start;
    gap: 10px; padding: 22px 14px 18px;
    background: radial-gradient(circle at 50% 30%, rgba(20,40,10,0.95) 0%, rgba(0,0,0,0.97) 72%);
    animation: poachOverlayIn .25s ease-out;
}
.card-picker-overlay.active { display: flex; }
.card-picker-title {
    color: var(--accent); font-weight: 900; font-size: 1.05rem;
    letter-spacing: 1px; text-align: center;
}
.card-picker-sub {
    color: var(--text-muted); font-size: .78rem; text-align: center;
    max-width: 420px; line-height: 1.35;
}
.card-picker-grid {
    display: flex; flex-wrap: wrap; gap: 14px; justify-content: center;
    width: 100%; max-width: 900px;
    overflow-y: auto; overflow-x: hidden;
    flex: 1 1 auto; padding: 8px 4px 4px;
    align-content: flex-start;
}
/* Each entry is a fixed-size well holding a scaled-down real trading card —
   same trick .upgrade-flip-face uses, so the picker can never blow out the
   grid the way a full-size 260px card would at 380px. */
.card-picker-cell {
    position: relative;
    width: 130px; height: 182px; flex: 0 0 auto;
    cursor: pointer; border-radius: 10px;
    transition: transform .15s ease, box-shadow .15s ease;
}
.card-picker-cell .trading-card {
    position: absolute; top: 0; left: 0;
    transform: scale(0.5); transform-origin: top left;
    pointer-events: none;
}
.card-picker-cell:hover, .card-picker-cell:focus-visible {
    transform: translateY(-4px) scale(1.04);
    box-shadow: 0 8px 22px rgba(46, 204, 113, 0.35);
    outline: none;
}
.card-picker-cell::after {
    content: 'CHOOSE';
    position: absolute; left: 0; right: 0; bottom: -2px;
    text-align: center; font-size: .58rem; font-weight: 900; letter-spacing: 1px;
    color: #000; background: var(--accent); border-radius: 0 0 8px 8px;
    padding: 2px 0; opacity: 0; transition: opacity .15s ease;
}
.card-picker-cell:hover::after, .card-picker-cell:focus-visible::after { opacity: 1; }
.card-picker-cancel {
    flex: 0 0 auto;
    background: transparent; color: var(--text-muted);
    border: 1px solid #333; border-radius: 8px;
    padding: 8px 22px; font-size: .78rem; font-weight: 700; cursor: pointer;
}
.card-picker-cancel:hover { color: #fff; border-color: #555; }
@media (max-width: 480px) {
    .card-picker-cell { width: 104px; height: 146px; }
    .card-picker-cell .trading-card { transform: scale(0.4); }
}

/* ── X1: pre-launch "for the record" stamp on the reward card ──────────────────
   Replaces the card-suck-in for sightings dated before LAUNCH_DATE. The points
   still counted, so the reward screen still runs — this says plainly that the
   one thing that DIDN'T happen is the card. */
.prelaunch-stamp {
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 6px; text-align: center; padding: 12px;
    background: rgba(0,0,0,0.72);
    border: 2px dashed var(--text-muted);
    border-radius: 12px;
    color: var(--text-muted);
    font-weight: 900; font-size: .82rem; letter-spacing: 2px;
    animation: prelaunchStampIn .35s cubic-bezier(.2,.9,.3,1.4) both;
    z-index: 3;
}
.prelaunch-stamp span {
    display: block; font-size: .6rem; font-weight: 700;
    letter-spacing: .4px; line-height: 1.3; opacity: .85;
}
@keyframes prelaunchStampIn {
    from { opacity: 0; transform: scale(1.5) rotate(-8deg); }
    to   { opacity: 1; transform: scale(1)   rotate(-8deg); }
}

/* ── X3-2: Monthly Winners hall of fame (below the leaderboard rankings) ──────
   One row per player, one medal per month won. Gold/warning language, matching
   the #1-rank treatment the rankings already use. */
/* Reads as its own panel rather than a continuation of the rankings above it:
   warm gold-tinted ground against the rankings' neutral dark, with a gold hairline
   instead of the near-invisible #1a1a1a divider. */
.mw-section {
    border-top: 1px solid rgba(241, 196, 15, .22);
    background: linear-gradient(180deg, rgba(241, 196, 15, .07) 0%, rgba(241, 196, 15, .02) 100%);
    border-radius: 0 0 10px 10px;
    margin-top: 10px;
    padding: 14px 15px 10px;
}
.mw-header {
    color: var(--warning);
    font-size: .78rem; font-weight: 900;
    text-transform: uppercase; letter-spacing: 1px;
}
.mw-sub {
    color: var(--text-muted); font-size: .68rem;
    margin: 2px 0 12px; opacity: .8;
}
.mw-row {
    display: flex; align-items: center; gap: 12px;
    padding: 8px 0; border-bottom: 1px solid #1a1a1a;
}
.mw-row:last-child { border-bottom: none; }
.mw-name {
    flex: 0 0 auto; min-width: 110px; max-width: 40%;
    font-size: .85rem; font-weight: 700; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mw-count {
    color: var(--warning); font-size: .68rem; font-weight: 900;
    margin-left: 6px; opacity: .9;
}
/* Scrolls inside itself — a long shelf must never widen the page. */
.mw-medals {
    display: flex; gap: 10px; align-items: flex-start;
    overflow-x: auto; padding-bottom: 2px;
    flex: 1 1 auto;
}
.mw-medal-wrap { flex: 0 0 auto; text-align: center; }
.mw-medal {
    width: 31px; height: 31px; border-radius: 50%;
    /* The medal sits in a flex column; without this the row's cross-axis
       sizing squeezed its height and the "circle" rendered as an ellipse. */
    flex: 0 0 auto;
    display: flex; align-items: center; justify-content: center;
    font-size: .54rem; font-weight: 900; letter-spacing: .5px;
    color: #3a2c00;
    background: radial-gradient(circle at 32% 28%, #ffe89a 0%, var(--warning) 45%, #b8900c 100%);
    border: 2px solid #7a5f08;
    box-shadow: 0 2px 6px rgba(241, 196, 15, 0.3), inset 0 -2px 3px rgba(0,0,0,0.25);
}
.mw-medal-year {
    font-size: .5rem; color: var(--text-muted);
    margin-top: 3px; letter-spacing: .3px;
}
@media (max-width: 480px) {
    .mw-name  { min-width: 78px; font-size: .78rem; }
    .mw-medal { width: 32px; height: 32px; font-size: .55rem; }
}

/* X3-3: Remy's insufficient-cash button state — the gold-side twin of Pippa's
   .fm-btn-disabled, so both shops refuse a purchase the same way. */
.remy-btn-disabled { background: #1a1a12 !important; color: #55502c !important;
                     border-color: #2a2a1c !important; cursor: not-allowed; }

/* ── Y1: locked-slot explainer ────────────────────────────────────────────────
   Anchored over the icon that was tapped (position: fixed, placed in JS from
   viewport coords) rather than a corner toast, so the message is attached to
   the thing it describes. Shared by locked ACTIVE mod slots and locked
   INACTIVE storage slots. */
.locked-slot-popup {
    position: fixed; z-index: 9700;
    max-width: 220px;
    background: #14140e;
    border: 1px solid var(--warning);
    border-radius: 10px;
    padding: 9px 12px;
    color: #f4e9c0; font-size: .74rem; font-weight: 600; line-height: 1.35;
    box-shadow: 0 6px 20px rgba(0,0,0,0.55);
    pointer-events: none;
    opacity: 0; transform: translateY(4px) scale(.96);
    transition: opacity .16s ease, transform .16s cubic-bezier(.2,.9,.3,1.3);
}
.locked-slot-popup.show { opacity: 1; transform: translateY(0) scale(1); }
.lsp-lock { margin-right: 6px; }
/* Arrow — --lsp-arrow is the anchor's centre relative to the popup's left edge,
   so it keeps pointing at the icon even when the popup was clamped to the
   viewport and is no longer centred on it. */
.locked-slot-popup::after {
    content: ''; position: absolute;
    left: var(--lsp-arrow, 50%); transform: translateX(-50%);
    bottom: -6px;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 6px solid var(--warning);
}
.locked-slot-popup.below::after {
    bottom: auto; top: -6px;
    border-top: none;
    border-bottom: 6px solid var(--warning);
}

/* Locked INACTIVE mod-storage slot in the Inventory — same 🔒 language as the
   locked active slot, so both read as "earnable" rather than "broken". */
.inventory-card-locked {
    border-style: dashed !important;
    border-color: #3a3a2a !important;
    background: #141410;
    color: #55503a;
    cursor: pointer;
    opacity: .75;
}
.inventory-card-locked:hover { opacity: 1; border-color: var(--warning) !important; }
.inventory-card-locked .inv-icon { filter: grayscale(1); opacity: .8; }

/* ── Z2: poach-denial + mod-destruction reveals ───────────────────────────────
   Steal framing has POLARITY. .mod-claimed is the winner's version of
   .mod-stolen — same stamp vocabulary, opposite emotional read. */
.triage-stamp.mod-claimed {
    color: var(--accent);
    border-color: var(--accent);
    text-shadow: 0 0 12px rgba(46, 204, 113, .55);
}
.triage-stamp.mod-saved {
    color: var(--accent);
    border-color: var(--accent);
    text-shadow: 0 0 12px rgba(46, 204, 113, .55);
}
.triage-stamp.mod-denied {
    color: var(--text-muted);
    border-color: #444;
    text-shadow: none;
}
.poach-denied-overlay .pvp-panel { max-width: 380px; }
.poach-denied-card { position: relative; }

/* The card is dragged toward the thief and then snaps home — the defender sees
   how close it came; the attacker sees it slip out of reach. */
@keyframes poachYankedBack {
    0%   { transform: translateX(0) rotate(0deg); }
    35%  { transform: translateX(46px) rotate(7deg); }
    55%  { transform: translateX(52px) rotate(8deg); }
    100% { transform: translateX(0) rotate(0deg); }
}
@keyframes poachSlippedAway {
    0%   { transform: translateX(0) rotate(0deg); opacity: 1; }
    40%  { transform: translateX(-34px) rotate(-6deg); opacity: 1; }
    100% { transform: translateX(0) rotate(0deg); opacity: .55; }
}
.poach-yanked-back  { animation: poachYankedBack .65s cubic-bezier(.35,-0.4,.3,1.5) both; }
.poach-slipped-away { animation: poachSlippedAway .65s ease-out both; }

/* Mod destroyed — one row per broken mod, staggered in. */
.mod-broken-overlay .pvp-panel { max-width: 400px; }
.mod-broken-row {
    display: flex; align-items: center; gap: 12px;
    background: #16110e;
    border: 1px solid #3a2a1c;
    border-radius: 10px;
    padding: 10px 12px; margin: 8px 0;
    text-align: left;
    opacity: 0; transform: translateY(8px);
    transition: opacity .3s ease, transform .3s cubic-bezier(.2,.9,.3,1.3);
}
.mod-broken-row.go { opacity: 1; transform: translateY(0); }
.mod-broken-icon { font-size: 1.6rem; filter: grayscale(.7); flex: 0 0 auto; }
.mod-broken-text { min-width: 0; }
.mod-broken-name {
    font-weight: 800; font-size: .86rem; color: #e8ddd0;
    text-decoration: line-through; text-decoration-color: var(--danger);
}
.mod-broken-why { font-size: .7rem; color: var(--text-muted); margin-top: 2px; line-height: 1.3; }

/* A member VS comparison that genuinely cannot be answered for the selected
   month (their snapshot predates speciesByMonth). Says so rather than drawing
   a row of zeros, which would read as "they logged nothing". */
.stats-vs-notice {
    margin: 0 0 10px; padding: 8px 12px;
    background: rgba(241, 196, 15, .08);
    border: 1px solid rgba(241, 196, 15, .35);
    border-radius: 8px;
    color: #d8c9a8; font-size: .74rem; line-height: 1.45; text-align: center;
}

/* ── Spec B: the outcome rule, stated on the resolve screen ────────────────
   The per-card death chance is a probability the player never sees roll, so
   without this the screen just showed cards dying for no stated reason — and
   a mod that halved the chance was completely invisible. Deliberately quiet
   type: it is a rules footnote under the drama row, not a headline. */
#triage-rule-text {
    max-width: 460px; width: 100%;
    margin: 14px auto 0;
    padding: 10px 14px;
    background: rgba(0,0,0,.32);
    border: 1px solid var(--border-color, #3a2a1c);
    border-radius: 10px;
    text-align: center;
}
.triage-rule-line {
    font-size: .8rem; line-height: 1.45; color: #cbbfae; margin: 0;
}
.triage-rule-line + .triage-rule-line { margin-top: 6px; }
.triage-rule-pct   { font-weight: 800; color: #e8ddd0; }
.triage-rule-was   { text-decoration: line-through; opacity: .6; }
.triage-rule-mods  { color: var(--accent); font-weight: 700; }
.triage-rule-zero  { color: var(--accent); font-weight: 800; }

/* Defensive-mod reveal — same overlay vocabulary as POACH BLOCKED / MOD SPENT,
   because it answers the same question: did the card I equipped do anything?
   Reuses .mod-broken-row for the per-mod rows; only the "destroyed" styling of
   the name is undone, since these mods are alive and just did their job. */
.mod-defense-overlay .pvp-panel { max-width: 400px; }
.mod-defense-overlay .mod-broken-name { text-decoration: none; color: var(--accent); }
.mod-defense-overlay .mod-broken-icon { filter: none; }
.mod-defense-overlay .mod-broken-row  { border-color: var(--accent); background: #101a12; }
.mod-defense-readout {
    font-size: 2.2rem; font-weight: 900; color: var(--accent);
    line-height: 1; margin: 6px 0 2px; letter-spacing: 1px;
}
.mod-defense-readout .was {
    font-size: 1.1rem; color: var(--text-muted);
    text-decoration: line-through; margin-right: 10px; letter-spacing: 0;
}

/* ── Z3: outpost leadership + pack decline ────────────────────────────────── */
.lb-leader-badge {
    color: var(--warning); margin-left: 6px; font-size: .8rem;
    text-shadow: 0 0 6px rgba(241,196,15,.5);
}
.lb-kick {
    margin-left: 8px; padding: 1px 7px;
    background: transparent; border: 1px solid #4a2a2a; border-radius: 6px;
    color: #a05555; font-size: .7rem; font-weight: 800; cursor: pointer;
    line-height: 1.4; vertical-align: middle;
}
.lb-kick:hover:not(:disabled) { background: var(--danger); border-color: var(--danger); color: #fff; }
.lb-kick:disabled { opacity: .4; cursor: wait; }

/* "Don't take this card" — the third exit from the at-cap mod picker. */
.mod-picker-decline {
    display: block; width: 100%; margin-top: 12px; padding: 9px 12px;
    background: transparent; border: 1px dashed #444; border-radius: 8px;
    color: var(--text-muted); font-size: .76rem; font-weight: 700; cursor: pointer;
}
.mod-picker-decline:hover { border-color: var(--text-muted); color: #ddd; }

/* A declined pack card reads as passed-over, not taken. */
.pack-pick-wrap.declined { opacity: .45; filter: grayscale(.8); }

/* ── Remy's field rules banner (Log page) ────────────────────────────────────
   Deliberately one collapsed line: it sits directly above the animal grid, so
   anything taller would push the grid down and compete with it. Brown/gold
   bordered with a small avatar, matching Remy's treatment elsewhere.
   Built on <details> so the expand needs no JS and survives a script failure. */
.remy-rules {
    background: linear-gradient(90deg, rgba(90,62,28,.38), rgba(60,42,20,.22));
    border: 1px solid #6b4d24; border-left: 3px solid var(--warning);
    border-radius: 8px; margin: 0 0 8px; overflow: hidden;
}
.remy-rules-line {
    display: flex; align-items: center; gap: 7px;
    padding: 6px 10px; cursor: pointer; list-style: none;
    font-size: .72rem; color: #d8c9a8; line-height: 1.25;
}
/* Kill the native disclosure triangle in both engines. */
.remy-rules-line::-webkit-details-marker { display: none; }
.remy-rules-line::marker { content: ''; }
.remy-rules-avatar { font-size: .95rem; flex-shrink: 0; }
/* WRAPS — it used to be `white-space: nowrap` + `text-overflow: ellipsis`, a
   deliberate one-line teaser (the full text is one tap away in <details>). But
   at 375px that ellipsised ~78px of a ~325px line, cutting the rule off
   mid-sentence, which reads as broken rather than as a teaser. The Homestead
   banner already wrapped — these three declarations are lifted verbatim from
   its .remy-rules-hs override, so the two instances now behave identically and
   the override no longer has to restate them. */
.remy-rules-short {
    flex: 1; min-width: 0;
    white-space: normal; overflow: visible; text-overflow: clip;
}
.remy-rules-more {
    flex-shrink: 0; font-size: .62rem; font-weight: 700; letter-spacing: .4px;
    text-transform: uppercase; color: var(--warning); opacity: .85;
    /* Now that the text wraps, a centred label would drift to the middle of a
       two-line block — pin it to the first line. (Also lifted from -hs.) */
    align-self: flex-start; padding-top: 1px;
}
.remy-rules[open] .remy-rules-more { opacity: 0; }
.remy-rules-full {
    padding: 0 10px 8px 32px; font-size: .72rem; line-height: 1.4; color: #c9bda2;
}
/* Default <p> margins would balloon a three-rule list; keep the block tight. */
.remy-rules-full p { margin: 0 0 5px; }
.remy-rules-full p:last-child { margin-bottom: 0; color: #d8c9a8; }
/* Collapsed: the summary already carries the gist, so the full text is hidden
   until opened rather than duplicated. */
.remy-rules:not([open]) .remy-rules-full { display: none; }
.remy-rules[open] .remy-rules-short { display: none; }

/* ── Monthly Winner announcement (Spec C) ─────────────────────────────────── */
.mw-announce {
    position: fixed; inset: 0; z-index: 4000;
    display: flex; align-items: center; justify-content: center;
    background: rgba(0,0,0,.72);
    opacity: 0; transition: opacity .26s ease;
    padding: 16px;
}
.mw-announce.shown { opacity: 1; }

.mw-announce-card {
    position: relative; z-index: 2;
    /* border-box or the 22px padding + border sit OUTSIDE the 340px and the
       card overflows a 375px phone. */
    box-sizing: border-box;
    width: min(340px, 100%);
    text-align: center;
    padding: 26px 22px 22px;
    border-radius: 16px;
    background: linear-gradient(170deg, #241d05 0%, var(--bg-panel, #1b1b1b) 62%);
    border: 1px solid #7a5f08;
    box-shadow: 0 18px 46px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,232,154,.14);
    transform: translateY(14px) scale(.94);
    transition: transform .3s cubic-bezier(.2,.9,.3,1.3);
}
.mw-announce.shown .mw-announce-card { transform: none; }

.mw-announce-kicker {
    font-size: .62rem; letter-spacing: 2.4px; text-transform: uppercase;
    color: var(--warning, #f1c40f); opacity: .85; margin-bottom: 14px;
}
.mw-announce-medal {
    width: 86px; height: 86px; margin: 0 auto; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 1.28rem; font-weight: 900; letter-spacing: 1.4px;
    color: #3a2c00;
    background: radial-gradient(circle at 32% 28%, #ffe89a 0%, var(--warning, #f1c40f) 45%, #b8900c 100%);
    border: 3px solid #7a5f08;
    box-shadow: 0 4px 16px rgba(241,196,15,.42), inset 0 -3px 5px rgba(0,0,0,.28);
    animation: mw-medal-in .62s cubic-bezier(.2,.9,.3,1.5) .1s both;
}
@keyframes mw-medal-in {
    from { transform: scale(.3) rotate(-24deg); opacity: 0; }
    to   { transform: none; opacity: 1; }
}
.mw-announce-year {
    margin-top: 7px; font-size: .68rem; letter-spacing: 1.2px;
    color: var(--text-muted, #8a8a8a);
}
.mw-announce-title {
    margin: 14px 0 8px; font-size: 1.16rem; color: #ffe89a;
}
.mw-announce-body {
    margin: 0 0 18px; font-size: .8rem; line-height: 1.5;
    color: var(--text-muted, #9a9a9a);
}
.mw-announce-btn {
    width: 100%; padding: 11px 0; border-radius: 10px; cursor: pointer;
    font-size: .82rem; font-weight: 700; letter-spacing: .4px;
    color: #3a2c00; border: 1px solid #7a5f08;
    background: linear-gradient(180deg, #ffe89a 0%, var(--warning, #f1c40f) 100%);
}
.mw-announce-btn:hover { filter: brightness(1.07); }

/* Confetti burst. Each mote carries its own --x/--r/--d/--t. */
.mw-announce-particles {
    position: absolute; inset: 0; overflow: hidden;
    pointer-events: none; z-index: 1;
}
.mw-announce-particles i {
    position: absolute; top: -6vh; left: 50%;
    width: 8px; height: 12px; border-radius: 2px;
    opacity: 0;
    animation: mw-confetti var(--t, 2s) linear var(--d, 0s) 1 both;
}
@keyframes mw-confetti {
    0%   { transform: translate(0, 0) rotate(0deg);                 opacity: 1; }
    100% { transform: translate(var(--x, 0), 108vh) rotate(var(--r, 360deg)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
    .mw-announce-particles { display: none; }
    .mw-announce-medal, .mw-announce-card { animation: none; transition: none; transform: none; }
}

/* ── Homestead variant of Remy's field-rules banner ───────────────────────────
   The banner shares the Log page's markup exactly (.remy-rules > .remy-rules-line),
   so its colour, flex layout, avatar placement and padding all come from rules the
   Log page already depends on. Everything below is PURELY ADDITIVE — placement and
   wrapping. That split is deliberate: the previous version was a <button> whose
   whole appearance hung on this one rule, so when the rule was missing the browser's
   button defaults (black text, inline-block, centred) took over and the banner was
   unreadable. Now the worst case if this block goes missing is a single ellipsised
   line, i.e. exactly the Log page's look. */

/* Own full-width line inside the header's wrapping flex row, which puts it below
   the action buttons but still ABOVE the header's 2px bottom divider. */
.remy-rules-hs {
    flex: 1 0 100%;
    /* .remy-rules is content-box, so a 100% basis put the 3px accent border and
       1px right border OUTSIDE the line and left the banner 4px wider than the
       header it sits in. */
    box-sizing: border-box;
    margin: 4px 0 0;
    cursor: pointer;
}
.remy-rules-hs:hover { border-color: var(--warning); }
.remy-rules-hs:hover .remy-rules-more { opacity: 1; }
.remy-rules-hs:focus-visible { outline: 2px solid var(--warning); outline-offset: 2px; }

/* Wrapping used to live here. It's now in the BASE .remy-rules-short, because
   the Log page needed the same treatment — a truncated field rule read as
   broken there too. All that remains Homestead-specific is the cross-fade for
   the rotating line, which the Log page's banner doesn't do. */
.remy-rules-hs .remy-rules-short { transition: opacity .3s ease; }
.remy-rules-hs .remy-rules-short.is-fading { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
    .remy-rules-hs .remy-rules-short { transition: none; }
}

/* BB3 — the Radio Collar marker is a 🛡️ chip in the badge tray now (.tc-badge-tray). */

/* ── Shame roster (once-ever per sender, so the list is a standing record) ── */
.shame-roster-chip {
    cursor: pointer;
    border-left: 3px solid var(--danger);
    background: rgba(231, 76, 60, .07);
}
.shame-roster-chip:hover { background: rgba(231, 76, 60, .14); }
.shame-roster-overlay .pvp-panel { max-width: 420px; }
.shame-roster-list {
    max-height: 46vh; overflow-y: auto;
    margin: 10px 0 4px; text-align: left;
}
.shame-roster-row {
    background: #16110e; border: 1px solid #3a2a1c;
    border-radius: 10px; padding: 9px 12px; margin-bottom: 8px;
}
.shame-roster-who {
    display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
    font-size: .85rem; color: #e8ddd0;
}
.shame-roster-when { font-size: .7rem; color: var(--text-muted); flex: 0 0 auto; }
.shame-roster-roast {
    font-size: .74rem; color: #cbbfae; line-height: 1.4; margin-top: 4px;
}

/* ── Deck AND mods locked into a pending invade ──────────────────────────────
   The deck snapshot AND the mod loadout both freeze when the challenge is sent,
   so edits afterwards never affected the battle — but nothing SHOWED that, and
   the UI had already promised the deck was locked. Grey them out and take them
   out of the tab order; the JS guards in uiArena / uiModsGear are the actual
   enforcement.

   ONE rule set, two surfaces. The mod slots were added as a second locked
   surface later; giving them their own copy of these declarations is how the
   two would end up looking subtly different, so the selector list grew instead
   and .deck-locked / .deck-lock-note are shared verbatim by both. */
.deck-locked { position: relative; }
.deck-locked #strike-deck-hand,
.deck-locked > .mod-slots {
    filter: grayscale(1) brightness(.62);
    pointer-events: none;
    user-select: none;
}
.deck-locked #strike-deck-hand *,
.deck-locked > .mod-slots * { pointer-events: none !important; }
/* The mod slots sit in a .list-section with a header above them, so the note
   needs clearance rather than sitting flush on the bottom edge as it does over
   the deck hand. */
.deck-locked > .mod-slots { padding-bottom: 30px; }
.deck-lock-note {
    position: absolute; inset: auto 0 0 0;
    margin: 0 auto 6px; width: fit-content;
    background: rgba(20,16,12,.94);
    border: 1px solid var(--danger);
    border-radius: 999px;
    padding: 5px 14px;
    font-size: .74rem; font-weight: 700; letter-spacing: .3px;
    color: #f2d9c9;
    cursor: pointer;
    pointer-events: auto;   /* stays tappable so it can explain itself */
    z-index: 3;
}
.deck-lock-note:hover { border-color: #ff6b52; }

/* Tier-gated bounty requirement — derived from requiredTier (see
   tierRequirementNote), so it appears on every Alpha/Apex contract and cannot
   drift out of step with the mint rule. */
.bounty-req {
    margin: 4px 0 0;
    font-size: .72rem;
    line-height: 1.35;
    color: #e0b978;
    border-left: 2px solid #7a5c2a;
    padding-left: 7px;
}

/* ── Pack-opening overlay: scrollable on a phone ─────────────────────────────
   .modal-overlay centres its child with align-items:center and no overflow. A
   pack of 12 cards is ~1980px tall; in an 812px iPhone viewport flex centring
   pushed the excess EQUALLY above and below, and with nothing scrollable both
   ends were unreachable — measured at 375x812: first card top -828px, last card
   1120px past the fold, overlay scrollTop pinned at 0 and body overflow hidden
   so the page could not scroll either. Only the middle few cards could be
   picked. Scoped to this overlay rather than .modal-overlay, so the small
   confirm dialogs that rely on true centring are untouched.

   align-items:flex-start stops the top from ever being clipped; margin:auto on
   the child restores centring whenever the content DOES fit. */
#pack-opening-overlay {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;   /* momentum scrolling on iOS */
    align-items: flex-start;
    padding: 20px 0;
    box-sizing: border-box;
}
#pack-opening-overlay > .modal-content {
    margin: auto;
    /* The grid sets its own bottom margin; keep the action row clear of the
       iPhone home indicator when the list runs long. */
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
}

/* The lock badge sits ABOVE the deck. Its original z-index of 3 put it behind
   a hovered card, the one moment the player is most likely to be looking for
   it. Every card now lives inside #strike-deck-hand, a stacking context at
   z-index 10, so no card's own z-index (a lifted card reaches 1000) can climb
   past this sibling at 200. The badge keeps its own pointer-events so it stays
   tappable while the cards beneath are inert. */
.deck-lock-note { z-index: 200; }

/* ── Push notification settings (Settings panel) ───────────────────────────
   Borrows the feedback-field vocabulary so the Settings panel reads as one
   surface rather than two designs stacked. */
.push-row { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; margin-bottom: 10px; }
.push-row input[type="checkbox"] { margin-top: 3px; flex-shrink: 0; accent-color: var(--accent); }
.push-row input:disabled { cursor: default; }
.push-title { display: block; font-size: .72rem; font-weight: 700; letter-spacing: .5px; color: #bbb; }
.push-sub   { display: block; font-size: .66rem; color: #666; margin-top: 2px; line-height: 1.4; }
.sound-volume-row { display: flex; align-items: center; gap: 10px; margin: -2px 0 4px 22px; }
.sound-volume-row input[type="range"] { flex: 1; min-width: 0; accent-color: var(--accent); }
#sound-volume-pct { font-size: .68rem; color: #888; width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.push-note {
    font-size: .68rem; line-height: 1.5; padding: 8px 10px;
    border-radius: 6px; margin-top: 8px;
}
/* Three tones, because the three states need different urgency: something the
   player can act on now, something merely informational, and a real failure. */
.push-note-action { background: #1a2233; border-left: 3px solid #5dade2; color: #a9c9e6; }
.push-note-muted  { background: #1a1a1e; border-left: 3px solid #444;    color: #888; }
.push-note-error  { background: #2a1414; border-left: 3px solid var(--danger); color: #e88; }

/* ── App footer (spec: copyright + Privacy/Terms links) ───────────────────
   Injected by js/ui/uiFooter.js into #footer-mount-settings (end of the
   Settings panel — that panel already scrolls on its own, see
   max-height:calc(100dvh - 60px) on #settings-panel, so appending here can
   never get clipped) and #footer-mount-homestead (end of the Homestead's
   .arena-right column, itself inside #arena-setup-scene's own
   overflow-y:auto — same reasoning). Neither mount point is fixed-position,
   so this can never overlap a nav bar or float over gameplay. */
.app-footer {
    margin-top: 18px;
    padding-top: 12px;
    border-top: 1px solid #2a2a2a;
    font-size: .68rem;
    line-height: 1.6;
    color: #555;
    text-align: center;
}
.app-footer-links { display: block; margin-top: 2px; }
.app-footer a { color: #777; text-decoration: underline; }
.app-footer a:hover { color: var(--accent); }
/* ── Persistent beta banner (spec: Persistent Fixed Bottom Beta Banner) ─────
   The LAST flex child of #app-screen — pinned to the viewport bottom because
   body is a non-scrolling 100dvh flex column, not because of position:fixed.
   .layout-wrapper (flex:1) gives up exactly this row's height, so the mobile
   feed dock and all five tabs' shared .main-content scroll area end at the
   banner's top edge: nothing is hidden behind it, and there is no bottom
   padding to keep in sync across tabs. No z-index: every overlay, modal and
   toast in the app is position:fixed and covers it, which is what an overlay
   is for. Show/hide + the --beta-banner-h measurement live in uiFooter.js. */
.beta-banner {
    flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    box-sizing: border-box;
    width: 100%;
    padding: 7px 14px;
    /* The bottom-most strip of the page now, so it takes over the gesture-pill
       clearance the mobile feed dock used to carry (see the handoff below). */
    padding-bottom: calc(7px + env(safe-area-inset-bottom, 0px));
    background: #121212;
    border-top: 1px solid #2a2a2a;
    font-size: .72rem;
    line-height: 1.45;
    color: #8f8f8f;
    text-align: center;
}
/* The `hidden` attribute alone loses to display:flex above. */
.beta-banner[hidden] { display: none; }
.beta-banner a { color: var(--accent); text-decoration: underline; white-space: nowrap; }
.beta-banner a:hover { color: #fff; }

/* Viewport-anchored UI near the bottom edge keeps the SAME clearance from the
   banner that it used to keep from the edge. --beta-banner-h is the banner's
   measured height, 0px whenever it isn't showing (battle, login screen). */
#season-warning-banner { bottom: calc(16px + var(--beta-banner-h, 0px)); }
.trophy-toast          { bottom: calc(80px + var(--beta-banner-h, 0px)); }

/* Mobile feed dock handoff: the dock used to be the bottom-most strip, so it
   padded itself (and grew its max-height) by the safe-area inset. With the
   banner below it, that clearance belongs to the banner — keeping it on both
   would open a dead gap between them. Scoped to .beta-banner-on so the dock
   gets its own clearance back the moment the banner hides. */
@media (max-width: 768px) {
    body.beta-banner-on .activity-sidebar { max-height: 250px; padding-bottom: 0; }
    body.beta-banner-on .activity-sidebar.feed-docked { max-height: 44px; }
}

/* ── Card info / media icons + popup (Spec: Card Info Icon) ──────────────────
   The icon row sits INSIDE .tc-image-box, which is position:relative, so the
   icons ride along with any transform the card's container applies (battle
   fighters and companions are scaled). The POPUP is position:fixed and lives
   on <body> for exactly that reason — rendered inside a scaled card it would
   inherit the scale and render blurry at the wrong size. */
/* BOTTOM-left, not top-left. The top edge of the photo zone belongs to the
   RABID / POACHED banners, which span it full-width (top:0; left:0; right:0)
   and stack to ~60px on a card that is both — measured, they sat directly over
   the icons in their old top-left home.

   Every number below derives from --tc-icon so the icons and the badges that
   must make room for them can never disagree. The small card surfaces override
   that ONE value (see below) rather than restating the geometry. */
.tc-image-box {
    /* 27px -> 22px (Jim, 2026-09-25): 20% smaller, still an easy tap — each
       icon carries a larger invisible touch zone (::after, below). */
    --tc-icon: 22px;
    --tc-icon-gap: 4px;
    --tc-icon-inset: 5px;
    /* What the badge tray in the other corner must stop short of. */
    --tc-icons-w: calc(var(--tc-icon) * 2 + var(--tc-icon-gap));
}
.tc-icon-row {
    position: absolute;
    bottom: var(--tc-icon-inset); left: var(--tc-icon-inset);
    display: flex; gap: var(--tc-icon-gap);
    /* Above the badge tray (z8), so an icon can never end up under a chip. */
    z-index: 9;
}
.tc-info-btn, .tc-media-btn {
    width: var(--tc-icon); height: var(--tc-icon); padding: 0; line-height: 1;
    display: flex; align-items: center; justify-content: center;
    font-size: calc(var(--tc-icon) * 0.72); cursor: pointer;
    /* Same emoji stack the rest of the app uses (see .mini-card-pine) — without
       it these render as flat monochrome glyphs in the default Arial stack. */
    font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif;
    /* Plain emoji — no chip, no ring. The drop-shadow replaces the background as
       the thing that keeps them legible over a light patch of the artwork. */
    background: none; border: none; border-radius: 0; color: #eee;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.95));
    opacity: 0.95; transition: opacity 0.15s, transform 0.15s;
    position: relative;
}
/* The touch zone is bigger than the drawn icon: 4px more above and below and
   2px more each side, about 26 x 30px at the 22px size. The sides stop at 2px
   so the zones of ℹ️ and 🎥 (4px apart) meet rather than overlap. */
.tc-info-btn::after, .tc-media-btn::after { content: ''; position: absolute; inset: -4px -2px; }

/* The dense triage row is the ONE measured collision. Its photo zone is only
   ~49px tall, and a card that is both rabid AND poached stacks two banners
   down to y44 — leaving no clear strip for the icons, which would then sit on
   top of a POACHED/RABID warning. That banner is load-bearing information and
   the icons are a convenience, so on this surface (a two-second reveal
   animation, not somewhere anyone browses provenance) the icons stand down.
   The Reserve is one tap away and shows the same card in full.
   NOTE: .card-picker-cell needs no override — it already scale(0.5)s the whole
   card and sets pointer-events:none on it, so the icons there scale with
   everything else and were never tappable to begin with. */
#triage-drama-row.dense .trading-card .tc-icon-row { display: none; }

.tc-info-btn:hover, .tc-media-btn:hover { opacity: 1; transform: scale(1.12); }
.tc-info-btn:focus-visible, .tc-media-btn:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 1px; opacity: 1;
}

.card-info-popup {
    position: fixed; z-index: 12000; max-width: min(280px, calc(100vw - 24px));
    box-sizing: border-box;
    background: #14141a; border: 1px solid #3a3a44; border-radius: 10px;
    padding: 10px 12px; box-shadow: 0 8px 28px rgba(0,0,0,0.6);
    font-size: 0.72rem; color: #ddd;
    opacity: 0; transform: translateY(4px); transition: opacity 0.14s, transform 0.14s;
    pointer-events: auto;
}
.card-info-popup.show { opacity: 1; transform: translateY(0); }
.card-info-popup::after {
    content: ''; position: absolute; left: var(--cip-arrow, 50%);
    margin-left: -6px; border: 6px solid transparent;
    top: 100%; border-top-color: #3a3a44;
}
.card-info-popup.below::after { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: #3a3a44; }
.cip-title {
    font-weight: 700; color: var(--accent); font-size: 0.74rem;
    margin-bottom: 6px; padding-bottom: 5px; border-bottom: 1px solid #2a2a32;
    padding-right: 22px;   /* room for the X */
}
/* The X on every card popup (cardInfo.js showPopup). Drawn small, with a
   finger-sized touch zone around it. */
.cip-close {
    position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; border: none; border-radius: 50%; color: #888; cursor: pointer;
}
.cip-close::after { content: ''; position: absolute; inset: -8px; }
.cip-close:hover, .cip-close:focus-visible { color: #eee; background: rgba(255,255,255,.08); outline: none; }
.cip-rows { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0; }
.cip-rows dt { color: #777; font-size: 0.68rem; white-space: nowrap; }
.cip-rows dd { margin: 0; color: #e8e8e8; font-size: 0.68rem; }
.cip-empty, .cip-media { color: #bbb; line-height: 1.45; }
.cip-foot {
    margin-top: 7px; padding-top: 6px; border-top: 1px solid #2a2a32;
    color: #777; font-size: 0.65rem; font-style: italic;
}

/* Send-time status re-check notice, inside the "Lock In Your Deck?" dialog.
   Empty by default and rendered only when the target's traffic light CHANGED
   between the Invade modal painting and this dialog — so an untouched dialog
   shows no box at all rather than a permanently reassuring one nobody reads. */
.pvp-recheck:empty { display: none; }
.pvp-recheck {
    margin-top: 10px; padding: 8px 10px; border-radius: 8px;
    font-size: 0.78rem; line-height: 1.45; text-align: left;
}
.pvp-recheck-changed {
    background: rgba(232, 185, 35, 0.12);
    border: 1px solid rgba(232, 185, 35, 0.45);
    color: #e8b923;
}
.pvp-recheck-block {
    background: rgba(192, 57, 43, 0.14);
    border: 1px solid rgba(192, 57, 43, 0.5);
    color: #e74c3c;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BATTLE FX — js/ui/battleFx.js (designed + tuned in the Battle Lab, 2026-09)
   ═══════════════════════════════════════════════════════════════════════════ */
/* Anton, bundled (SIL OFL — fonts/Anton-OFL.txt). Not from Google Fonts: the
   service worker deliberately never caches googleapis, so a hosted font would
   be missing offline and flash in late on a first battle. Impact, the old
   battle font, does not exist on Android at all. */
@font-face {
    font-family: 'Anton';
    src: url('../fonts/Anton-Regular.woff2') format('woff2');
    font-display: swap;
}

/* The effect layers (created by battleFx on the first battle). */
#fx-canvas { position: fixed; inset: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 8600; }
#fx-flash  { position: fixed; inset: 0; pointer-events: none; z-index: 8590; opacity: 0; }
#fx-dom    { position: fixed; inset: 0; pointer-events: none; z-index: 8700; }
.combat-card-wrapper-main { will-change: transform; }
/* The fighter is spring-driven now; the tap cue is the card's glow alone
   (a CSS scale pulse on the wrapper would fight the spring's transform). */
.combat-card-wrapper-main.ready-to-tap { animation: none; }
/* Skunk-sprayed: sickly green until the stun is spent. */
.combat-card-wrapper-main.fx-stunned { filter: sepia(.5) hue-rotate(40deg) saturate(1.8) brightness(.85); transition: filter .5s ease; }

/* Callouts — ONE text system for every battle overlay. */
.fx-co {
    position: absolute; left: 0; top: 0; transform: translate(-50%, -50%);
    font-family: 'Anton', Impact, 'Arial Black', sans-serif, 'Noto Color Emoji';
    font-weight: 400; line-height: 1; letter-spacing: 1px; white-space: nowrap; text-align: center;
    text-transform: uppercase; paint-order: stroke fill; will-change: transform, opacity; pointer-events: none;
    opacity: 0;   /* battleFx positions + fades it in on creation */
}
.fx-co .sub { display: block; font-size: .42em; letter-spacing: 3px; margin-top: .12em; -webkit-text-stroke-width: 2px; }
.fx-co.k-dmg    { color: #fff;    -webkit-text-stroke: 5px #7a0b0b; text-shadow: 0 4px 0 #2a0000, 0 0 24px rgba(255,40,30,.75); }
.fx-co.k-dmg .sub { color: #ffd84a; -webkit-text-stroke-color: #6b1a00; }
/* Kill tiers (battleFx KILL_TIERS): the word under a killing blow's number grows
   and heats up with the excess damage. min(…em, …vw) keeps "ANNIHILATED!" on a
   375px screen however big the number above it is. */
.fx-co .sub.t-kill { font-size: min(.46em, 9vw);  color: #fff3c4; }
.fx-co .sub.t-mega { font-size: min(.54em, 8.5vw); color: #ffb02e; -webkit-text-stroke-color: #5a1500; }
.fx-co .sub.t-anni { font-size: min(.62em, 7vw);  color: #ff4a2e; -webkit-text-stroke-color: #2e0000;
                     text-shadow: 0 3px 0 #1a0000, 0 0 20px rgba(255,60,30,.95); }
.fx-co .sub.t-mass { font-size: min(.72em, 7.5vw); color: #ff1a1a; -webkit-text-stroke-color: #120000; letter-spacing: 4px;
                     text-shadow: 0 3px 0 #000, 0 0 12px #000, 0 0 34px rgba(255,0,0,1);
                     animation: fx-massacre-flicker .16s steps(2) 5; }
@keyframes fx-massacre-flicker { 50% { color: #fff; } }
.fx-co.k-chip   { color: #ff7a6a; -webkit-text-stroke: 4px #3a0000; text-shadow: 0 3px 0 #1a0000, 0 0 16px rgba(255,40,30,.6); }
/* BLOCK PIERCED! (battleFx pierceFX): white over a dark stroke, a hot-orange glow
   (a dark-edged glow, never white on white). */
.fx-co.k-pierce { color: #fff; -webkit-text-stroke: 5px #3a1a00; text-shadow: 0 4px 0 #1a0a00, 0 0 22px rgba(255,140,30,.9); letter-spacing: 2px; }
.fx-co.k-pierce .sub { color: #ffd84a; -webkit-text-stroke-color: #3a1a00; }
.fx-co.k-heal   { color: #eaffef; -webkit-text-stroke: 5px #0b5d2a; text-shadow: 0 4px 0 #042a12, 0 0 22px rgba(46,204,113,.8); }
.fx-co.k-block  { color: #ffe066; -webkit-text-stroke: 5px #5a3b00; text-shadow: 0 4px 0 #2a1a00, 0 0 22px rgba(241,196,15,.8); }
.fx-co.k-block .sub { color: #fff3c2; }
.fx-co.k-dodge  { color: #fff;    -webkit-text-stroke: 5px #0b4d6b; text-shadow: 0 4px 0 #032130, 0 0 22px rgba(80,200,255,.8); font-style: italic; }
.fx-co.k-cash   { color: #8dffad; -webkit-text-stroke: 5px #0b4d22; text-shadow: 0 4px 0 #042a12, 0 0 20px rgba(46,204,113,.7); }
.fx-co.k-pelts  { color: #ffcf7a; -webkit-text-stroke: 5px #5a3000; text-shadow: 0 4px 0 #2a1600, 0 0 20px rgba(217,164,65,.7); }
.fx-co.k-status { color: #e4ff6a; -webkit-text-stroke: 5px #2e4a00; text-shadow: 0 4px 0 #142000, 0 0 22px rgba(180,255,60,.7); }
.fx-co.k-rabies { color: #ff3b30; -webkit-text-stroke: 5px #2a0000; text-shadow: 0 0 10px #000, 0 0 28px rgba(231,76,60,.95); }
.fx-co.k-rabies .sub { color: #fff; }
/* The rabies verdict (battleFx.rabiesDeath): big, centred, the MASSACRE!!
   treatment in rabies colours — acid-green word, the red flicker underneath.
   Dark strokes and glows only (a light glow on light text is unreadable). */
.fx-co.k-rabies:has(.t-rabies) { color: #c8ff4a; -webkit-text-stroke: .075em #0c2400; letter-spacing: 3px;
                     text-shadow: 0 .06em 0 #051200, 0 0 .18em #000, 0 0 .5em rgba(90,255,40,.85); }
.fx-co .sub.t-rabies { font-size: min(.5em, 9vw); color: #ff1a1a; -webkit-text-stroke: .09em #120000; letter-spacing: 4px;
                       text-shadow: 0 3px 0 #000, 0 0 12px #000, 0 0 34px rgba(255,0,0,1);
                       animation: fx-massacre-flicker .16s steps(2) 5; }
.fx-co.k-ability, .fx-co.k-mod {
    /* Wraps instead of growing wide: a long value ("Enemy heavy card(s)
       pre-damaged!") otherwise stretched the ribbon across the other fighter. */
    white-space: normal; width: max-content; max-width: min(86vw, 290px);
    padding: .28em 1.1em .34em; -webkit-text-stroke: 4px #2a1a00;
    background: linear-gradient(90deg, transparent, rgba(18,12,4,.94) 14%, rgba(18,12,4,.94) 86%, transparent);
}
.fx-co.k-ability { color: #ffd84a; text-shadow: 0 3px 0 #000, 0 0 18px rgba(241,196,15,.6); }
.fx-co.k-ability .sub { color: #fff; -webkit-text-stroke-color: #000; }
.fx-co.k-mod { color: #bfe3ff; -webkit-text-stroke-color: #06213a; text-shadow: 0 3px 0 #000, 0 0 18px rgba(52,152,219,.7);
    background: linear-gradient(90deg, transparent, rgba(6,20,36,.94) 14%, rgba(6,20,36,.94) 86%, transparent); }
.fx-co.k-mod .sub { color: #fff; -webkit-text-stroke-color: #000; }
.fx-co.k-token { color: #fff; -webkit-text-stroke: 4px #0b4d22; text-shadow: 0 0 18px rgba(46,204,113,.9); }
.fx-co.k-token.atk { -webkit-text-stroke-color: #6b3a00; text-shadow: 0 0 18px rgba(243,156,18,.95); }
/* The companion bonus token speaks the same language. */
.fly-token { font-family: 'Anton', Impact, 'Arial Black', sans-serif, 'Noto Color Emoji'; font-weight: 400; letter-spacing: 1px;
             -webkit-text-stroke: 4px #0b4d22; paint-order: stroke fill; }
.fly-token.atk { -webkit-text-stroke-color: #6b3a00; }

.fx-tomb { position: absolute; left: 0; top: 0; will-change: transform, opacity; filter: drop-shadow(0 10px 12px rgba(0,0,0,.8)); }
.fx-tomb svg { display: block; width: 100%; height: 100%; }

/* Porcupine quills — a dark tip (the left end is the point), banded ivory shaft.
   They live in a layer INSIDE the fighter card, so they ride its lunges. */
.fx-quills { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 60; overflow: visible; }
.fx-quill {
    position: absolute; left: 0; top: 0; height: 5px; transform-origin: 0 50%; pointer-events: none;
    background: linear-gradient(90deg, #0d0b09 0 11%, #4a3d30 11% 15%, #f4ecdb 15% 60%, #2a231c 60% 64%, #efe5d0 64% 86%, #6b5a46 86%);
    clip-path: polygon(0 50%, 13% 0, 100% 22%, 100% 78%, 13% 100%);
    filter: drop-shadow(0 1px 1px rgba(0,0,0,.8));
}
.fx-quill.hot { filter: drop-shadow(0 0 6px #ff2a1a) sepia(1) saturate(6) hue-rotate(-40deg) brightness(1.2); }

/* HP: the bar snaps to the new value and a pale ghost drains down behind it,
   so the size of a hit reads at a glance. */
.hp-bar-container .hp-bar-fill { position: relative; z-index: 1; transition: background-color 0.3s; }
.hp-bar-ghost { position: absolute; top: 0; left: 0; height: 100%; background: #fff6d8; z-index: 0;
                transition: width .42s cubic-bezier(.2,.8,.3,1) .26s; }

@media (prefers-reduced-motion: reduce) {
    .hp-bar-ghost, .combat-card-wrapper-main.fx-stunned { transition: none; }
    .fx-co .sub.t-mass, .fx-co .sub.t-rabies { animation: none; }   /* no flicker */
}

/* ── Battle layout ────────────────────────────────────────────────────────────
   Desktop: the same board, less empty space (Jim: "too much negative space"). */
@media (min-width: 901px) {
    body.in-battle .combat-header { padding: 6px 10px; min-height: 0; }
    body.in-battle .combat-zone { padding: 10px 14px; gap: 28px; }
    body.in-battle .combatant-side { padding: 10px 12px 12px; }
    body.in-battle .c-player-name { font-size: 1rem; margin-bottom: 6px !important; }
    body.in-battle .combat-mod-zone { padding: 6px 8px; margin-bottom: 8px; }
    body.in-battle .combat-mod-zone .zone-title { display: none; }
    body.in-battle .main-fighter-accessories { margin-top: 6px; gap: 6px; }
    body.in-battle .battle-log { height: 72px; padding: 4px 14px; }
}
/* Landscape phones — RAIL (Jim, 2026-09-26). Each side's name + Habitat Mods
   sit in ONE row ABOVE its full-size card (slots wide enough to read); the
   Draw/Fallen piles and Speed Fight share the slim column beside the card; the
   text log is hidden — a landscape phone has no height to spare for it. The
   fighter only shrinks by the pixels it must (battleFx.fitFighter →
   --fighter-scale): full size at 844×390 and 667×375, ~95% at 740×360.
     p1:  [ name ][   mod   |   mod   ]      p2:  [   mod   |   mod   ][ name ]
          [speed][                    ]           [                    ][     ]
          [     ][       CARD         ]           [       CARD         ][     ]
          [piles][                    ]           [                    ][piles]
          [     ][      HP bar        ]           [      HP bar        ][     ]   */
@media (max-height: 520px), (orientation: landscape) and (max-width: 900px) {
    body.in-battle .combat-zone { padding: 3px 8px 2px; gap: 8px; }
    body.in-battle .combatant-side {
        display: grid; column-gap: 8px; justify-content: center; align-content: start;
        grid-template-rows: auto auto 1fr auto; padding: 2px 8px 3px;
    }
    body.in-battle #comb-p1 { grid-template-columns: 84px auto; }
    body.in-battle #comb-p2 { grid-template-columns: auto 84px; }
    body.in-battle .combat-layout-wrap,
    body.in-battle .main-fighter-column,
    body.in-battle .main-fighter-accessories { display: contents; }
    /* the side column: name, Speed Fight (p1), piles */
    body.in-battle #comb-p1 > .c-player-name, body.in-battle #piles-p1, body.in-battle #comb-p1 > .speed-fight-btn { grid-column: 1; }
    body.in-battle #comb-p2 > .c-player-name, body.in-battle #piles-p2 { grid-column: 2; }
    /* the card column: mods, card, HP */
    body.in-battle #comb-p1 .combat-mod-zone, body.in-battle #comb-p1 .fighter-anchor,
    body.in-battle #comb-p1 .hp-bar-container, body.in-battle #hp-txt-p1 { grid-column: 2; }
    body.in-battle #comb-p2 .combat-mod-zone, body.in-battle #comb-p2 .fighter-anchor,
    body.in-battle #comb-p2 .hp-bar-container, body.in-battle #hp-txt-p2 { grid-column: 1; }
    body.in-battle .combatant-side > .c-player-name { grid-row: 1; align-self: center; margin: 0 0 3px !important; text-align: center; font-size: .74rem; line-height: 1.1; }
    body.in-battle .combat-mod-zone { grid-row: 1; margin: 0 0 3px; padding: 2px; width: calc(220px * var(--fighter-scale, 1)); }
    body.in-battle .cb-mod-group { flex-direction: row; gap: 3px; }
    body.in-battle .mod-rect-slot {
        flex: 1 1 0; min-width: 0; max-width: none; height: 28px; padding: 0 3px;
        font-size: .66rem; letter-spacing: 0; line-height: 1.02; overflow: hidden;
        /* Title Case reads better small, and "NECROMANCER'S" in capitals is
           wider than the slot — measured, every mod name in items.js fits. */
        text-transform: none; font-weight: 700;
    }
    body.in-battle #comb-p1 > .speed-fight-btn {
        position: static; grid-row: 2; align-self: start; margin-top: 2px;
        padding: 4px 3px; font-size: .58rem; line-height: 1.15; border-width: 1px; border-radius: 8px; white-space: normal;
    }
    body.in-battle .pile-row { grid-row: 3 / 5; align-self: end; flex-direction: column; align-items: center; gap: 4px; padding: 4px 3px; }
    body.in-battle .pile-wrap { flex-direction: row; gap: 5px; }
    body.in-battle .pile-fan { height: 44px; }
    body.in-battle .pile-label { display: none; }
    body.in-battle .fighter-anchor { grid-row: 2 / 4; }
    body.in-battle .hp-bar-container { grid-row: 4; margin-top: 3px; height: 14px; }
    body.in-battle .hp-text { grid-row: 4; align-self: stretch; margin: 3px 0 0; display: flex; align-items: center; justify-content: center; position: relative; z-index: 3; line-height: 1; font-size: .68rem; }
    body.in-battle .battle-log { display: none; }
    body.in-battle .combat-card-wrapper-main { width: calc(220px * var(--fighter-scale, 1)); height: calc(308px * var(--fighter-scale, 1)); }
    body.in-battle .combat-card-wrapper-main .trading-card { transform: scale(calc(.846 * var(--fighter-scale, 1))); }
    body.in-battle .hp-bar-container { width: calc(220px * var(--fighter-scale, 1)); }
}
/* Small landscape phones (iPhone SE, 667×375): the max-width:680px rule above
   stacks the fighters vertically for PORTRAIT phones — it used to do that in
   landscape too, which made the battle ~780px taller than the screen. */
@media (orientation: landscape) and (max-width: 680px) {
    body.in-battle .combat-zone { flex-direction: row; align-items: flex-start; gap: 4px; padding: 2px 4px; }
    body.in-battle .combatant-side { width: auto; max-width: none; padding: 2px 4px; column-gap: 5px; }
    body.in-battle .vs-text { position: absolute; transform: translate(-50%, -50%); margin: 0; }
    body.in-battle #comb-p1 { grid-template-columns: 76px auto; }
    body.in-battle #comb-p2 { grid-template-columns: auto 76px; }
}

/* ── BIOME GATE (js/ui/uiBiomeGate.js, js/ui/biomeSurvey.js) ─────────────────
   The "not in your area yet" page (onboarding in a closed region) and its survey,
   plus the paused-logging state for an existing account in a closed region. */
.biome-sorry { position: fixed; inset: 0; z-index: 9100; background: rgba(0,0,0,0.94); overflow-y: auto;
    display: flex; justify-content: center; align-items: flex-start; padding: 24px 16px; box-sizing: border-box; }
.biome-sorry-panel { background: #111; border: 1px solid #2ecc71; border-radius: 16px; padding: 24px 22px; width: 560px; max-width: 100%; box-sizing: border-box; }
.biome-sorry-remy { font-size: 2.6rem; text-align: center; }
.biome-sorry-title { color: var(--accent); text-align: center; margin: 4px 0 12px; letter-spacing: .5px; }
.biome-sorry-line { color: #ccc; font-size: .9rem; line-height: 1.55; margin: 0 0 10px; }
.biome-sorry-thanks { color: var(--accent); text-align: center; font-weight: 700; margin: 14px 0; }
.biome-sorry-row { display: flex; gap: 8px; justify-content: space-between; flex-wrap: wrap; margin-top: 14px; }
.biome-sorry-btn { padding: 10px 14px; border-radius: 10px; border: 1px solid #333; background: #1a1a1e; color: #ddd; font-weight: 700; cursor: pointer; }
.biome-sorry-btn.primary { width: 100%; margin-top: 12px; background: var(--accent); color: #000; border: none; font-size: 1rem; padding: 13px; }
.biome-sorry-btn:disabled { opacity: .6; cursor: default; }
.bs-form { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.bs-q { color: #eee; font-size: .85rem; font-weight: 700; margin-top: 10px; }
.bs-hint { color: #777; font-weight: 400; }
.bs-sub { color: #888; font-size: .72rem; text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
.bs-input { width: 100%; padding: 10px; background: #1a1a1e; border: 1px solid #333; border-radius: 8px; color: #eee; font-size: .88rem; box-sizing: border-box; font-family: inherit; }
.bs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 6px; }
.bs-animal { display: flex; align-items: center; gap: 6px; padding: 6px 8px; background: #1a1a1e; border: 1px solid #2a2a2e; border-radius: 8px; color: #ccc; font-size: .78rem; cursor: pointer; }
.bs-animal:has(input:checked) { border-color: var(--accent); background: rgba(46,204,113,.12); color: #fff; }
.bs-animal input { accent-color: var(--accent); margin: 0; }
.bs-animal-icon { font-size: 1.1rem; }
.bs-choices { display: flex; flex-wrap: wrap; gap: 6px 14px; color: #ccc; font-size: .85rem; }
.bs-choice input { accent-color: var(--accent); }
.bs-error { color: var(--danger); font-size: .8rem; min-height: 1.2em; margin-top: 6px; }
/* Phase 6: logging paused in a closed region */
.biome-paused-banner { background: rgba(241,196,15,.12); border: 1px solid rgba(241,196,15,.55); color: #f1c40f; border-radius: 10px;
    padding: 10px 14px; margin-bottom: 12px; font-weight: 700; font-size: .88rem; text-align: center; }
#animal-grid.log-paused .animal-card { opacity: .35; filter: grayscale(1); cursor: not-allowed; }
/* Onboarding (Jim, 2026-09-30): say where it's open BEFORE the pick, say a
   closed pick is closed on the button itself, beta note, and a way back out. */
.onboard-open-biome { background: rgba(46,204,113,.08); border: 1px solid rgba(46,204,113,.35); border-radius: 10px;
    padding: 8px 12px; margin: 0 0 16px; color: #cfe; font-size: .82rem; text-align: center; }
.onboard-biome-link { background: none; border: none; padding: 0; font: inherit; font-weight: 800; color: var(--accent); cursor: pointer;
    text-decoration: underline dotted; text-underline-offset: 3px; }
.onboard-biome-popup { position: fixed; inset: 0; z-index: 9050; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; padding: 16px; }
.onboard-biome-popup[hidden] { display: none; }
.onboard-biome-popup-panel { background: #15191c; border: 1px solid rgba(46,204,113,.5); border-radius: 12px; padding: 16px 20px; min-width: 200px; max-width: 92vw; text-align: center; }
.onboard-biome-popup-panel h3 { margin: 0 0 8px; color: var(--accent); font-size: 1rem; }
/* the open states, in the biome-name popup (onboarding AND the Sorry page) */
.onboard-biome-popup-panel ul { list-style: none; margin: 0 0 12px; padding: 0; color: #dde; line-height: 1.7; font-size: .9rem; }
.onboard-biome-popup-close { padding: 8px 18px; border-radius: 8px; border: 1px solid #333; background: #1a1a1e; color: #ddd; font-weight: 700; cursor: pointer; }
#onboard-state option.region-open, #settings-state option.region-open { color: var(--accent); font-weight: 700; }
#onboard-state optgroup.region-open-group, #settings-state optgroup.region-open-group { color: var(--accent); }
.onboard-state-gap { height: 14px; }
.onboard-beta { color: #9aa; font-size: .72rem; line-height: 1.45; margin: 0 0 10px; padding: 6px 10px;
    border: 1px dashed #3a3f47; border-radius: 8px; }
.onboard-beta strong { color: #f1c40f; }
/* EARLY ACCESS label (js/ui/earlyAccess.js) — onboarding and the app banner. */
.ea-label { color: #f1c40f; font-weight: 800; letter-spacing: .06em; white-space: nowrap; }
.ea-link { background: none; border: none; padding: 0; font: inherit; color: var(--accent); text-decoration: underline; cursor: pointer; white-space: nowrap; }
.ea-link:hover { color: #fff; }
.ea-sep { color: #555; }
.ea-nowrap { white-space: nowrap; }
/* Its explainer: the small-popup pattern, but long — the panel scrolls on a short
   phone and Close stays at the bottom of the panel, always reachable. */
.ea-popup { z-index: 9600; }   /* above the onboarding modal (9000) and every overlay */
.ea-popup-panel { display: flex; flex-direction: column; width: 440px; max-width: 100%; max-height: calc(100dvh - 32px); text-align: left; box-sizing: border-box; }
.ea-popup-panel h3 { text-align: center; flex-shrink: 0; }
.ea-popup-body { overflow-y: auto; min-height: 0; color: #ccd; font-size: .86rem; line-height: 1.5; -webkit-overflow-scrolling: touch; }
.ea-popup-body p { margin: 0 0 8px; }
.onboard-biome-popup-panel .ea-popup-body ul { list-style: disc; padding-left: 1.2em; margin: 0 0 10px; color: #ccd; font-size: .86rem; line-height: 1.5; }
.ea-popup-body li { margin-bottom: 6px; }
.ea-popup-panel .onboard-biome-popup-close { flex-shrink: 0; align-self: center; margin-top: 4px; }
/* Report a bug works from the onboarding modal too (z 9000), so the form sits above it. */
#feedback-modal { z-index: 9550; }
.onboard-submit { width: 100%; padding: 13px; background: var(--accent); color: #000; border: none; border-radius: 10px;
    font-weight: 800; font-size: 1rem; line-height: 1.3; cursor: pointer; }
.onboard-submit.onboard-closed { background: #f1c40f; }
.onboard-account { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 4px 8px;
    margin-top: 6px; color: #666; font-size: .75rem; }
.onboard-switch { background: none; border: none; color: #9ab; text-decoration: underline; cursor: pointer; font-size: .75rem; padding: 4px; }

/* ── REPLAY BATTLE (js/ui/battleReplay.js) ─────────────────────────────────── */
.triage-exit-row { display: flex; gap: 10px; justify-content: center; align-items: flex-end; flex-wrap: wrap; }
.replay-btn { background: #1d2025; color: #eee; border: 1px solid #3a3f47; padding: 15px 22px; font-size: 1.05rem; margin-top: 16px; }
.replay-bar { position: absolute; top: 8px; left: 50%; transform: translateX(-50%); z-index: 60; display: flex; gap: 10px; align-items: center;
    background: rgba(0,0,0,.72); border: 1px solid rgba(241,196,15,.6); color: #f1c40f; border-radius: 999px; padding: 5px 6px 5px 14px;
    font-weight: 800; font-size: .78rem; letter-spacing: .5px; pointer-events: auto; }
.replay-bar .action-btn { padding: 5px 12px; font-size: .75rem; margin: 0; background: #2a2d33; color: #eee; border: 1px solid #444; }
#arena-combat-scene.is-replay { position: relative; }

/* A newly opened mod slot, announced once (uiModsGear announceSlotUnlocks):
   a soft gold glow that breathes twice and fades. Reduced motion never adds
   the class (the JS skips the whole effect). */
.slot-unlock-glow { animation: slotUnlockGlow 2.4s ease-out 1; }
@keyframes slotUnlockGlow {
    0%   { box-shadow: 0 0 0 0 rgba(255, 216, 74, 0); }
    18%  { box-shadow: 0 0 18px 6px rgba(255, 216, 74, 0.85); }
    45%  { box-shadow: 0 0 8px 2px rgba(255, 216, 74, 0.35); }
    65%  { box-shadow: 0 0 16px 5px rgba(255, 216, 74, 0.7); }
    100% { box-shadow: 0 0 0 0 rgba(255, 216, 74, 0); }
}
@media (prefers-reduced-motion: reduce) { .slot-unlock-glow { animation: none; } }
