/*
 * Predator Puzzle (user-facing name; default ON for all players) - own
 * stylesheet, `.pp-*` classes only (internal prefix unchanged from the
 * original Perfect Pack naming - see src/js/perfect-pack.js's header
 * comment), consuming the existing :root tokens defined in game.css
 * (cabinet/drawer/brass/parchment/moss/blaze + --font-display/--font-body/
 * --font-stamp/--font-legible). No new tokens are declared here - every
 * color/font in this file is one of game.css's own custom properties, so
 * Predator Puzzle automatically stays in the Field Crate theme and never
 * drifts from it.
 *
 * Structural shape mirrors .fight-overlay/.fight-overlay-content
 * (game.css) - a fixed full-screen overlay, hidden via [hidden], with a
 * centered content column - since Predator Puzzle's forecast/draft/result
 * screens are the same kind of full-screen sequential flow the fight
 * overlay already uses. The first-tap intro (.pp-intro-*, at the bottom of
 * this file) is deliberately NOT full-screen - it is a small centered
 * modal/coach-mark, distinct in weight from the game screens so a player
 * never mistakes it for gameplay.
 */

.pp-overlay{
  position:fixed;
  inset:0;
  z-index:1150;
  background:var(--cabinet);
  color:var(--parchment);
  display:flex;
  flex-direction:column;
  align-items:center;
  padding:28px 20px;
  overflow-y:auto;
}
.pp-overlay[hidden]{ display:none; }

.pp-overlay-content{ max-width:520px; width:100%; margin:auto 0; }

/* Stage B follow-up (scroll-guard fix): the forecast screen specifically
   needs a bounded-height flex layout so its row list can flex/scroll
   internally while the BEGIN DRAFT footer stays pinned to the bottom of
   the *viewport* rather than landing wherever the list's natural height
   happens to end. Scoped to .pp-forecast-screen only - draft/result
   screens keep the original natural-height/auto-centered behavior via
   .pp-overlay-content's margin:auto 0 and are not touched by this rule. */
.pp-forecast-screen{
  max-height:calc(100vh - 56px); /* 100vh minus .pp-overlay's 28px top+bottom padding */
  min-height:0;
}

/* Stage C (teach-on-loss): the non-perfect result screen needs the exact
   same bounded-height treatment for the same reason - its lost-fight list
   can run long enough (up to 14 rows) that the CTA footer would otherwise
   land wherever the list's natural height ends instead of staying pinned
   to the viewport. Scoped to :not(.pp-result-perfect) because the perfect
   run's markup has no scroll/footer wrapper at all (see resultHtml()) and
   its short, fixed content is already safely centered by
   .pp-overlay-content's margin:auto 0 - applying a max-height there would
   be a no-op at best, so it is deliberately left out to keep that path's
   CSS untouched, matching the requirement to leave the perfect-run banner
   exactly as it was. */
.pp-result-screen:not(.pp-result-perfect){
  max-height:calc(100vh - 56px);
  min-height:0;
}

.pp-screen{ display:flex; flex-direction:column; gap:14px; }

.pp-kicker{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.25em;
  text-transform:uppercase;
  color:var(--brass-bright);
}

.pp-title{
  font-family:var(--font-display);
  font-weight:400;
  font-size:22px;
  line-height:1.3;
  color:var(--parchment);
  margin:0;
}

/* Forecast screen headline/subhead (Stage B) - a step up from .pp-title in
   size, since this is the first thing a player reads before the 15-row
   list, and needs to read as a real section header rather than the same
   weight as the result-screen recap heading. */
.pp-headline{
  font-family:var(--font-display);
  font-weight:400;
  font-size:28px;
  line-height:1.25;
  color:var(--parchment);
  margin:0;
}

.pp-subhead{
  font-family:var(--font-legible);
  font-size:15px;
  line-height:1.5;
  color:var(--parchment);
  margin:0;
}

/* Boss identity header on the forecast screen: emblem sprite + boss name +
   tagline. Replaces the old "THE SEASON AHEAD" text-only headline so the
   player immediately knows WHO they are fighting today. Kept horizontal on
   phones (emblem stays alongside the name) since the emblem is small and
   the whole block is above the fold. */
.pp-boss-header{
  display:flex;
  align-items:center;
  gap:14px;
}

.pp-boss-emblem{
  flex:0 0 auto;
  width:72px;
  height:72px;
  display:flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--brass-dim);
  border-radius:10px;
  background:rgba(0,0,0,0.25);
  overflow:hidden;
}

.pp-boss-emblem-img{
  width:100%;
  height:100%;
  display:flex;
  align-items:center;
  justify-content:center;
}
.pp-boss-emblem-img img,
.pp-boss-emblem-img svg{
  width:100%;
  height:100%;
  object-fit:contain;
}

.pp-boss-meta{ display:flex; flex-direction:column; gap:4px; min-width:0; }

.pp-boss-eyebrow{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.25em;
  text-transform:uppercase;
  color:var(--brass-bright);
}

.pp-boss-name{
  font-family:var(--font-display);
  font-weight:400;
  font-size:28px;
  line-height:1.15;
  color:var(--parchment);
  margin:0;
}

.pp-boss-tagline{
  font-family:var(--font-legible);
  font-size:13px;
  line-height:1.4;
  color:var(--parchment-dim);
  margin:0;
  font-style:italic;
}

/* How-fighting-works accordion. Uses native <details>/<summary> so it needs
   no JS wiring; the summary is styled to read like a subtle inline chip
   rather than an OS default triangle button. */
.pp-rules{
  border:1px solid var(--brass-dim);
  border-radius:8px;
  background:rgba(0,0,0,0.20);
  padding:0;
}

.pp-rules-summary{
  cursor:pointer;
  list-style:none;
  padding:10px 14px;
  font-family:var(--font-legible);
  font-size:13px;
  color:var(--parchment);
  display:flex;
  align-items:center;
  gap:8px;
}

.pp-rules-summary::-webkit-details-marker{ display:none; }

.pp-rules-summary::after{
  content:"\002B";
  margin-left:auto;
  color:var(--brass-bright);
  font-size:16px;
  line-height:1;
}

.pp-rules[open] .pp-rules-summary::after{ content:"\002212"; }

.pp-rules-body{
  padding:0 14px 12px 14px;
  display:flex;
  flex-direction:column;
  gap:8px;
}

.pp-rules-body p{
  margin:0;
  font-family:var(--font-legible);
  font-size:13px;
  line-height:1.5;
  color:var(--parchment-dim);
}

.pp-rules-body strong{ color:var(--parchment); font-weight:600; }

.pp-lede{
  font-family:var(--font-legible);
  font-size:14px;
  line-height:1.5;
  color:var(--parchment-dim);
  margin:0;
}

/* Stage B2 (scouting report redesign): the how-it-works clarifier sits
   directly under the headline, above the scouting summary block - a
   single short line naming the row mechanic explicitly ("one of the 5
   candidates, chosen at random") so a first-time player understands what
   the grid below actually represents before they scroll into it. Same
   type scale as .pp-subhead (this screen's existing secondary-text
   convention) rather than .pp-lede, since it needs to read as important
   instruction, not flavor text. */
.pp-forecast-clarifier{
  font-family:var(--font-legible);
  font-size:14px;
  line-height:1.5;
  color:var(--parchment);
  margin:0;
}
.pp-forecast-clarifier strong{
  color:var(--brass-bright);
  font-weight:700;
}

/* Scouting summary block: habitat frequency bar + draft hints, the new
   focal point of the forecast screen. Sits between the clarifier and the
   divider/section-header that introduces the untouched 15-row grid below.
   Kept as a single bordered card (matching .pp-forecast-row's drawer/
   drawer-edge treatment) so it visually reads as one distinct "report"
   module rather than a loose stack of text. */
.pp-scouting-summary{
  display:flex;
  flex-direction:column;
  gap:16px;
  padding:14px;
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:6px;
}

.pp-habitat-summary{
  display:flex;
  flex-direction:column;
  gap:8px;
}
.pp-habitat-summary-label{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--brass-bright);
}
.pp-habitat-bars{
  display:flex;
  flex-direction:column;
  gap:6px;
}

/* One habitat row: fixed-width name column, a flexible bar track that
   never overflows its parent (min-width:0 on the grid item + width:100%
   on the track itself is what keeps this safe at 375px), and a
   fixed-width count column. Grid (not flex) so the name column stays a
   consistent width across every row even when names are different
   lengths, which keeps the bars' left edges aligned. */
.pp-habitat-row{
  display:grid;
  grid-template-columns:minmax(0,88px) 1fr 22px;
  align-items:center;
  gap:8px;
}
.pp-habitat-name{
  font-family:var(--font-legible);
  font-size:11px;
  font-weight:600;
  color:var(--parchment-dim);
  overflow-wrap:break-word;
  line-height:1.2;
}
.pp-habitat-bar-track{
  display:block;
  width:100%;
  min-width:0;
  height:10px;
  border-radius:5px;
  background:var(--cabinet);
  border:1px solid var(--drawer-edge);
  overflow:hidden;
}
.pp-habitat-bar-fill{
  display:block;
  height:100%;
  min-width:3px;
  border-radius:5px;
  background:var(--brass-bright);
}
.pp-habitat-count{
  font-family:var(--font-stamp);
  font-size:12px;
  font-weight:800;
  color:var(--parchment);
  text-align:right;
}

.pp-draft-hints{
  display:flex;
  flex-direction:column;
  gap:8px;
  padding-top:10px;
  border-top:1px solid color-mix(in srgb, var(--brass) 35%, transparent);
}
.pp-draft-hints-label{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--brass-bright);
}
.pp-draft-hints-list{
  list-style:none;
  margin:0; padding:0;
  display:flex;
  flex-direction:column;
  gap:8px;
}
.pp-hint{
  font-family:var(--font-legible);
  font-size:13px;
  line-height:1.45;
  color:var(--parchment-dim);
  overflow-wrap:break-word;
}
.pp-hint strong{
  color:var(--parchment);
  font-weight:700;
}

/* Divider + "Every possible opponent" section header - the explicit
   transition from the scouting summary into the untouched 15-row grid. */
.pp-forecast-divider{
  border-top:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
  margin:2px 0;
}
.pp-forecast-section-header{
  font-family:var(--font-display);
  font-weight:400;
  font-size:17px;
  line-height:1.3;
  color:var(--parchment);
  margin:0;
}

/* Per-row clarifier ("one of these 5 shows up") + the "Fight N" label -
   both live inside .pp-forecast-arena so the existing grid-template-columns
   layout on .pp-forecast-row/.pp-result-row is untouched; they stack as
   block-level text under the arena name rather than widening the row. */
.pp-forecast-fight-label{
  font-family:var(--font-stamp);
  color:var(--brass-bright);
}
.pp-rowclue{
  display:block;
  font-family:var(--font-legible);
  font-weight:400;
  font-size:11px;
  font-style:italic;
  color:var(--parchment-dim);
  margin-top:1px;
}

/* Forecast list: all 15 fixed encounters, shown before any picks are made.
   Stage B follow-up: the scroll/max-height that used to live directly on
   .pp-forecast-list moved up to the new .pp-forecast-scroll wrapper (see
   below) so the list itself is a plain flex column and the wrapper is the
   thing that flexes to fill available space inside .pp-forecast-screen.
   .pp-result-list (result screen, untouched by this fix) keeps the
   original fixed max-height/overflow behavior exactly as before. */
.pp-forecast-list{
  list-style:none;
  margin:0; padding:0;
  display:flex; flex-direction:column; gap:6px;
}

.pp-result-list{
  list-style:none;
  margin:0; padding:0;
  display:flex; flex-direction:column; gap:6px;
  max-height:46vh;
  overflow-y:auto;
}

/* Wraps .pp-forecast-list only. flex:1 1 auto lets it absorb all the extra
   height inside the bounded .pp-forecast-screen; min-height:0 is required
   for a flex child to be allowed to shrink below its content size so
   overflow-y:auto actually engages instead of the column just growing
   taller than the viewport again. */
.pp-forecast-scroll{
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  -webkit-overflow-scrolling:touch;
}

/* Sticky footer: pinned to the bottom of .pp-forecast-screen's own scroll
   context (the screen itself doesn't scroll - only .pp-forecast-scroll
   does - so "sticky" here simply means "always the last thing visible",
   which combined with flex layout keeps it glued to the viewport bottom
   regardless of list length or scroll position). flex-shrink:0 stops it
   from being compressed by the scroll area's flex:1. */
.pp-forecast-footer{
  /* position:sticky + bottom:0 is the belt to the flex-layout's braces:
     .pp-forecast-scroll (not .pp-forecast-screen) is the actual scrolling
     element, so the footer being flex:0 0 auto and last in a
     non-scrolling flex column already keeps it glued to the bottom of the
     visible screen regardless of list length/scroll position - sticky
     here is a defensive no-op in that layout, kept because it is also the
     literal, explicitly-requested signal ("position: sticky" in the DOM)
     that this element is meant to always stay visible, and it costs
     nothing to also be technically true. */
  position:sticky;
  bottom:0;
  flex:0 0 auto;
  display:flex;
  flex-direction:column;
  align-items:stretch;
  gap:8px;
  padding-top:14px;
  background:var(--cabinet);
}

/* Fade hint that list content continues underneath the footer - a thin
   gradient strip anchored to the footer's top edge, using the same
   --cabinet background the overlay itself sits on so it reads as a
   soft edge rather than a hard line. Purely decorative (aria-hidden). */
.pp-forecast-fade{
  position:absolute;
  top:-24px;
  left:0; right:0;
  height:24px;
  background:linear-gradient(to top, var(--cabinet), transparent);
  pointer-events:none;
}

/* "More fights below" affordance - visible until the player scrolls the
   list at least once (see wireForecastScroll in perfect-pack.js, which
   toggles .pp-forecast-more-hidden). Sits above the CTA, not competing
   with it for primary visual weight. */
.pp-forecast-more{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.08em;
  text-transform:uppercase;
  text-align:center;
  color:var(--brass-bright);
  transition:opacity 0.2s ease;
}
.pp-forecast-more-hidden{
  display:none;
}

/* Scroll-first guard hint - small inline warning shown for ~2s if BEGIN
   DRAFT is tapped before the list has been scrolled. Uses --blaze (the
   existing warm alert token) rather than introducing a new color, kept
   small/muted so it reads as a nudge, not an error. */
.pp-forecast-guard-hint{
  font-family:var(--font-legible);
  font-size:12px;
  font-weight:600;
  text-align:center;
  color:var(--blaze-bright);
}
.pp-forecast-guard-hint[hidden]{ display:none; }

.pp-forecast-row, .pp-result-row{
  display:grid;
  grid-template-columns:28px 1fr auto;
  align-items:center;
  gap:10px;
  padding:8px 10px;
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:4px;
}

.pp-forecast-num, .pp-result-num{
  font-family:var(--font-stamp);
  font-weight:800;
  color:var(--brass-bright);
  text-align:center;
}

.pp-forecast-arena, .pp-result-arena{
  font-family:var(--font-legible);
  font-weight:600;
  font-size:13px;
  color:var(--parchment);
}

.pp-forecast-opponents{
  font-family:var(--font-legible);
  font-size:12px;
  color:var(--parchment-dim);
  grid-column:1 / -1;
  display:flex;
  flex-wrap:wrap;
  gap:10px;
}

/* Stage 1 (sprites): one opponent thumbnail - sprite over a name caption,
   stacked so the name stays visible on mobile without hover (see the
   forecastOpponentHtml() comment in perfect-pack.js for why a caption
   beat a tooltip-only treatment). Sized independently of the shared
   .animal-art em-relative rule via an explicit font-size on the sprite
   span, the same pattern the base game itself uses per call site. */
.pp-opponent-thumb{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:2px;
  width:52px;
}
.pp-opponent-sprite{ font-size:28px; line-height:1; }
.pp-opponent-caption{
  font-size:10px;
  line-height:1.2;
  text-align:center;
  color:var(--parchment-dim);
  overflow-wrap:break-word;
}

/* Subtle early/mid/late-season pacing break (Stage B) - a slightly bigger
   gap plus a thin brass hairline above rows 6 and 11, no labels or
   difficulty ratings, just a visual breath in the list. */
.pp-forecast-break{
  margin-top:10px;
  padding-top:16px;
  border-top:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
}

.pp-result-outcome{
  font-family:var(--font-stamp);
  font-weight:800;
  font-size:11px;
  letter-spacing:0.05em;
  padding:2px 8px;
  border-radius:10px;
}

.pp-result-win .pp-result-outcome{ background:var(--moss-dark); color:var(--parchment); }
.pp-result-loss .pp-result-outcome{ background:var(--blaze-dark); color:var(--parchment); }

/* Stage C (teach-on-loss) result screen: non-perfect runs now show a
   scrollable list of ONLY the lost fights (wins are just a count in the
   header above), using the identical sticky-footer + fade-hint pattern
   the Stage B follow-up introduced for the forecast screen. Kept as its
   own class family (.pp-result-scroll / .pp-result-footer / .pp-result-fade)
   rather than reusing .pp-forecast-scroll etc. directly so the two screens
   can be restyled independently later without cross-screen risk, even
   though the rules below are intentionally identical in structure. */
.pp-result-scroll{
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  -webkit-overflow-scrolling:touch;
}

.pp-result-footer{
  position:sticky;
  bottom:0;
  flex:0 0 auto;
  display:flex;
  flex-direction:column;
  align-items:stretch;
  gap:8px;
  padding-top:14px;
  background:var(--cabinet);
}

.pp-result-fade{
  position:absolute;
  top:-24px;
  left:0; right:0;
  height:24px;
  background:linear-gradient(to top, var(--cabinet), transparent);
  pointer-events:none;
}

.pp-result-lost-list{
  list-style:none;
  margin:0; padding:0;
  display:flex; flex-direction:column; gap:6px;
}

/* Each lost-fight row: fight number (brass numeral, matching forecast rows),
   arena pill, and the "Your X lost to their Y [reason]" sentence stacked
   below it. Grid columns mirror .pp-forecast-row/.pp-result-row above
   (28px numeral gutter) for visual consistency across screens. */
.pp-result-lost-row{
  display:grid;
  grid-template-columns:28px 1fr;
  align-items:start;
  gap:10px;
  padding:10px;
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:4px;
}

.pp-result-lost-row .pp-result-num{
  padding-top:2px;
}

.pp-result-lost-body{
  display:flex;
  flex-direction:column;
  gap:4px;
  min-width:0;
}

.pp-result-arena-pill{
  align-self:flex-start;
  font-family:var(--font-stamp);
  font-size:10px;
  font-weight:800;
  letter-spacing:0.06em;
  text-transform:uppercase;
  color:var(--brass-bright);
  background:color-mix(in srgb, var(--brass) 22%, var(--drawer));
  border-radius:10px;
  padding:2px 8px;
}

.pp-result-lost-sentence{
  font-family:var(--font-legible);
  font-size:13px;
  line-height:1.45;
  color:var(--parchment);
  display:flex;
  flex-direction:column;
  gap:4px;
}

/* Stage 1 (sprites): the matchup line - player sprite+label, an arrow
   glyph, opponent sprite+label - sits on its own row above the unchanged
   reason clause. The whole row carries one aria-label with the original
   plain-text sentence ("Your X lost to their Y.") so screen readers get
   one clean announcement instead of reading each decorative sprite and
   arrow individually; every element inside is aria-hidden for that reason. */
.pp-lost-matchup{
  display:flex;
  align-items:center;
  gap:8px;
  flex-wrap:wrap;
}
.pp-lost-side{
  display:inline-flex;
  align-items:center;
  gap:5px;
}
.pp-lost-sprite{ font-size:22px; line-height:1; display:inline-flex; }
.pp-lost-vs{
  color:var(--brass-bright);
  font-weight:700;
  font-size:13px;
}
.pp-lost-reason{ color:var(--parchment-dim); }

.pp-result-screen.pp-result-perfect .pp-title{ color:var(--brass-bright); }


/* Draft screen: progress pips, roster-so-far chips, and the candidate grid
   with a highlight-then-explicit-Lock flow (no one-click lock). */

/* Slot header (Stage B): slot-number eyebrow is deliberately subtler than
   the theme name, which now gets its own line at a larger, more legible
   size - the theme ("Savanna apex cat") is what a player actually needs
   to read at a glance; the slot count is secondary bookkeeping. */
.pp-slot-header{ display:flex; flex-direction:column; gap:2px; }
.pp-slot-eyebrow{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.2em;
  text-transform:uppercase;
  color:var(--parchment-dim);
}
.pp-slot-theme{
  font-family:var(--font-display);
  font-weight:400;
  font-size:24px;
  line-height:1.25;
  color:var(--parchment);
  margin:0;
}

/* One-time contextual hint, slot 1 only - small and muted so it reads as a
   passing tip, not a headline, and doesn't repeat on slots 2-5. */
.pp-slot-hint{
  font-family:var(--font-legible);
  font-size:12px;
  line-height:1.5;
  color:var(--parchment-dim);
  margin:0;
}

.pp-progress-row{ display:flex; gap:6px; }
.pp-progress-pip{
  width:10px; height:10px; border-radius:50%;
  background:var(--drawer-edge);
}
.pp-progress-pip-filled{ background:var(--moss-bright); }
.pp-progress-pip-current{ box-shadow:0 0 0 2px var(--brass-bright); }

.pp-roster-so-far, .pp-candidate-grid{
  display:flex; flex-wrap:wrap; gap:8px;
}

.pp-roster-chip{
  font-family:var(--font-legible);
  font-size:12px;
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:4px;
  padding:4px 8px;
  color:var(--parchment);
  display:inline-flex;
  align-items:center;
  gap:5px;
}
.pp-roster-chip small{ color:var(--parchment-dim); }

/* Stage 1 (sprites): 24x24 sprite to the left of the chip's name, sized via
   an explicit font-size (the shared .animal-art rule is em-relative to its
   parent) rather than a fixed pixel box, so it scales the same way every
   other sprite call site in the app does. */
.pp-roster-chip-sprite{ font-size:24px; line-height:1; display:inline-flex; }

.pp-candidate-card{
  flex:1 1 calc(50% - 8px);
  min-width:130px;
  padding:14px 10px;
  border:1.5px solid var(--drawer-edge);
  border-radius:4px;
  background:var(--drawer);
  color:var(--parchment);
  font-family:var(--font-display-small);
  font-size:13px;
  text-transform:uppercase;
  letter-spacing:0.02em;
  cursor:pointer;
  transition:background 0.12s ease, box-shadow 0.12s ease;
  display:flex;
  align-items:center;
  gap:8px;
}
/* Stage 1 (sprites): 32px sprite left of the candidate name. Slightly
   larger than the roster-chip sprite (24px) since candidate cards are the
   primary decision surface during the draft. */
.pp-candidate-sprite{ font-size:32px; line-height:1; flex:0 0 auto; display:inline-flex; }
.pp-candidate-name{ flex:1 1 auto; text-align:left; min-width:0; }
.pp-candidate-card:hover{ background:color-mix(in srgb, var(--moss) 30%, var(--drawer)); }
.pp-candidate-card.pp-candidate-highlighted{
  background:color-mix(in srgb, var(--brass) 40%, var(--drawer));
  box-shadow:inset 0 0 0 2px var(--brass-bright);
}

.pp-primary-btn{
  display:block; width:100%;
  padding:11px 14px; border:0; border-radius:4px;
  background:var(--blaze); color:var(--parchment);
  font-family:var(--font-display-small); font-weight:400; font-size:14px;
  text-transform:uppercase; letter-spacing:0.03em;
  text-align:center; cursor:pointer;
  transition:background 0.12s ease;
}
.pp-primary-btn:hover{ background:var(--blaze-bright); }
.pp-primary-btn:disabled{
  background:var(--drawer-edge); color:var(--parchment-dim); cursor:default;
}

.pp-secondary-btn{
  display:block; width:100%;
  padding:9px 14px; border:1px solid var(--drawer-edge); border-radius:4px;
  background:transparent; color:var(--parchment-dim);
  font-family:var(--font-legible); font-size:13px;
  text-align:center; cursor:pointer;
}
.pp-secondary-btn:hover{ color:var(--parchment); border-color:var(--brass); }

/* Home-tile accent - own mode-accent so .mode-cta's shared rules
   (game.css) paint it consistently with every sibling tile. */
.perfect-pack-cta{ --mode-accent:#3a2a5c; }

/* Yesterday's score badge on the home tile - only rendered when a player
   actually finished yesterday's puzzle. Sits below the tile subtitle,
   reads as a compact recap chip, and disappears silently the next day
   (see harvestStaleRun + yesterdayResult in perfect-pack.js). */
.pp-tile-yesterday{
  display:inline-block;
  align-self:flex-start;
  margin-top:8px;
  padding:3px 10px;
  font-family:var(--font-stamp, var(--font-legible));
  font-size:11px;
  letter-spacing:0.15em;
  text-transform:uppercase;
  color:var(--brass-bright);
  background:rgba(0,0,0,0.30);
  border:1px solid var(--brass-dim);
  border-radius:999px;
}

@media (min-width:640px){
  .pp-candidate-card{ flex:1 1 calc(33.333% - 8px); }
}

/* ---------------------------------------------------------------------
   First-tap intro ("coach mark") - a small centered modal, NOT a
   full-screen takeover like .pp-overlay above. Shown once per device on
   the tile's first tap, before the forecast screen ever renders. Kept
   visually lighter than the full-screen game screens (soft scrim, tighter
   card, no kicker/progress chrome) so a player can't mistake it for
   gameplay, while still using the same Field Crate tokens as everything
   else in this file.
   --------------------------------------------------------------------- */
.pp-intro-overlay{
  position:fixed;
  inset:0;
  z-index:1200; /* above .pp-overlay's 1150, so it can gate opening a run */
  display:flex;
  align-items:center;
  justify-content:center;
  padding:20px;
  background:rgba(20,16,12,0.62); /* soft backdrop scrim over the cabinet tone */
}
.pp-intro-overlay[hidden]{ display:none; }

.pp-intro-overlay-content{
  width:100%;
  max-width:440px;
}

.pp-intro-card{
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:10px;
  box-shadow:0 18px 48px rgba(0,0,0,0.4);
  padding:22px 20px;
  display:flex;
  flex-direction:column;
  gap:16px;
}

.pp-intro-copy{
  font-family:var(--font-legible);
  font-size:14px;
  line-height:1.55;
  color:var(--parchment);
  margin:0;
}

.pp-intro-btn{
  /* Inherits .pp-primary-btn's block/color/radius rules; only width
     behavior differs so it reads as a modal action, not a full-bleed
     screen button. */
  width:auto;
  align-self:flex-end;
  padding:9px 18px;
}

@media (max-width:420px){
  .pp-intro-btn{ align-self:stretch; width:100%; }
}

/* ==========================================================================
   Stage 2: fight-resolution playback screen. Sits between draft completion
   and the (unchanged) result screen - see perfect-pack.js's startPlayback()/
   render(). Uses the same drawer/parchment/brass tokens as the rest of the
   mode; win/loss chips reuse the moss/blaze pair the game already treats
   as its win/danger colors elsewhere (.pp-result-win, .army-assembled-banner,
   FIGHT arena chips), not new one-off colors. ========================== */

.pp-playback-screen{
  display:flex;
  flex-direction:column;
  gap:18px;
  min-height:min(560px, calc(100vh - 120px)); /* polish pass: fills far more of the
     viewport than the original 360px so the fight scene reads as the
     screen's focal point instead of a small block lost in empty space -
     see .pp-playback-body below for the actual vertical centering. */
  position:relative;
  border-radius:14px;
  transition:background 0.3s ease;
  overflow:hidden;
}

/* Polish pass: subtle full-screen-feeling arena backdrop tint (5-10%
   opacity), applied per fight via the arenaSlug hook in perfect-pack.js.
   Deliberately faint - a signal that the environment changed between
   fights, not a loud background. Six fixed arenas from the Coastal
   Coalition board; colors are simple, minimal, arena-evocative hues laid
   as a soft radial wash rather than new named tokens, since no existing
   per-arena color tokens exist in game.css to reuse. */
.pp-playback-arena-savanna{ background:radial-gradient(ellipse at 50% 38%, rgba(214,158,46,0.10), transparent 70%); }
.pp-playback-arena-wetlands{ background:radial-gradient(ellipse at 50% 38%, rgba(72,133,89,0.10), transparent 70%); }
.pp-playback-arena-arctic{ background:radial-gradient(ellipse at 50% 38%, rgba(148,197,214,0.09), transparent 70%); }
.pp-playback-arena-canopy{ background:radial-gradient(ellipse at 50% 38%, rgba(58,120,66,0.11), transparent 70%); }
.pp-playback-arena-ocean{ background:radial-gradient(ellipse at 50% 38%, rgba(52,109,158,0.10), transparent 70%); }
.pp-playback-arena-midnight{ background:radial-gradient(ellipse at 50% 38%, rgba(90,74,140,0.10), transparent 70%); }

.pp-playback-header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  position:relative;
  z-index:1;
}

.pp-playback-score{
  font-family:var(--font-stamp);
  font-weight:800;
  font-size:14px;
  letter-spacing:0.03em;
  color:var(--brass-bright);
}

.pp-playback-skip-btn{
  font-family:var(--font-stamp);
  font-weight:700;
  font-size:11px;
  letter-spacing:0.04em;
  color:var(--parchment-dim);
  background:transparent;
  border:1px solid var(--drawer-edge);
  border-radius:8px;
  padding:6px 12px;
  cursor:pointer;
  transition:background 0.12s ease, color 0.12s ease;
}
.pp-playback-skip-btn:hover, .pp-playback-skip-btn:focus-visible{
  background:var(--drawer);
  color:var(--parchment);
}

.pp-playback-body{
  flex:1 1 auto;
  display:flex;
  align-items:center;
  justify-content:center;
  min-height:280px;
  position:relative;
  z-index:1;
  /* Polish pass: pulls the fight scene up toward ~40% from the top of the
     screen rather than dead-center of the remaining space, so it reads as
     the deliberate focal point the header sits above, not a block
     floating in the middle of a tall empty column. */
  padding-bottom:8%;
}

.pp-playback-computing{
  font-family:var(--font-stamp);
  font-size:15px;
  letter-spacing:0.08em;
  color:var(--parchment-dim);
  animation: ppPlaybackComputingPulse 1s ease-in-out infinite;
}
@keyframes ppPlaybackComputingPulse{
  0%, 100%{ opacity:0.45; }
  50%{ opacity:1; }
}

.pp-playback-fight{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:10px;
  width:100%;
}

.pp-playback-fight-num{
  font-family:var(--font-stamp);
  font-size:11px;
  letter-spacing:0.05em;
  color:var(--parchment-dim);
}

.pp-playback-arena-pill{
  font-family:var(--font-stamp);
  font-weight:700;
  font-size:11px;
  letter-spacing:0.03em;
  color:var(--brass-bright);
  background:var(--drawer);
  border:1px solid var(--drawer-edge);
  border-radius:999px;
  padding:3px 12px;
}

.pp-playback-clashers{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:22px;
  /* Clearance below the arena pill so the WIN/LOSS chip (30px above the
     player sprite, see .pp-playback-chip) never touches it. */
  margin-top:22px;
}

.pp-playback-side{
  position:relative;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:6px;
  min-width:110px;
  transition:transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Clash beat: sprites drive inward toward the center line, briefly
   overlap and compress to ~90% scale on impact (the "hit"), then spring
   back out to rest - a single move-in/move-out arc timed to
   PLAYBACK_CLASH_MS (500ms) in perfect-pack.js. Kept purely
   transform-based (no layout thrash) so it stays smooth even on low-end
   mobile. */
.pp-playback-clash .pp-playback-side-player{ animation: ppPlaybackClashPlayer 0.5s ease-in-out both; }
.pp-playback-clash .pp-playback-side-opponent{ animation: ppPlaybackClashOpponent 0.5s ease-in-out both; }
@keyframes ppPlaybackClashPlayer{
  0%{ transform:translateX(0) scale(1); }
  45%{ transform:translateX(30px) scale(0.9); }
  60%{ transform:translateX(20px) scale(0.94); }
  100%{ transform:translateX(0) scale(1); }
}
@keyframes ppPlaybackClashOpponent{
  0%{ transform:translateX(0) scale(1); }
  45%{ transform:translateX(-30px) scale(0.9); }
  60%{ transform:translateX(-20px) scale(0.94); }
  100%{ transform:translateX(0) scale(1); }
}

.pp-playback-sprite .animal-art{
  width:1em;
  height:1em;
}
.pp-playback-sprite{
  /* Polish pass: this is the mode's biggest dramatic reveal, so the
     fighters need to dominate the screen rather than read as small icons
     in empty space - bumped from 64px to 128px desktop / 96px mobile
     (font-size drives width/height via the 1em .animal-art rule above). */
  font-size:128px;
  line-height:1;
  display:block;
  filter:drop-shadow(0 6px 14px rgba(0,0,0,0.35));
}
@media (max-width:420px){
  .pp-playback-sprite{ font-size:96px; }
  .pp-playback-clashers{ gap:10px; }
  .pp-playback-side{ min-width:104px; }
}

.pp-playback-name{
  font-family:var(--font-legible);
  font-weight:600;
  font-size:13px;
  color:var(--parchment);
  text-align:center;
}

/* Polish pass: the plain text "VS" read as a matchup preview, not combat -
   replaced with a larger brass-tinted crossed-blades glyph (inline SVG in
   perfect-pack.js's playbackFightHtml) so the divider itself signals a
   fight in progress. */
.pp-playback-vs{
  display:flex;
  align-items:center;
  justify-content:center;
  color:var(--brass-bright);
  font-size:28px;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,0.3));
}
@media (max-width:420px){
  .pp-playback-vs{ font-size:22px; }
}

/* WIN/LOSS chip: appears only once a fight resolves (pp-playback-resolved)
   and stays mounted for the entire PLAYBACK_HOLD_MS pause that follows -
   see resolveCurrentFight()/playbackFightHtml() in perfect-pack.js - so
   players actually register the outcome, not just a flash. Always pinned
   above the PLAYER's sprite (never the opponent's) since this is the
   player's perspective on their own fighter's result. Reuses the
   moss/blaze win-danger pair already established by .pp-result-win and
   the army-assembled banner. Pop-in overshoots past full size (0.5 -> 1.1
   -> 1.0) so the reveal itself has a little punch, then settles. */
.pp-playback-chip{
  position:absolute;
  top:-30px;
  left:50%;
  transform:translateX(-50%);
  font-family:var(--font-stamp);
  font-weight:800;
  font-size:13px;
  letter-spacing:0.06em;
  padding:4px 13px;
  border-radius:999px;
  white-space:nowrap;
  animation: ppPlaybackChipPop 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  z-index:2;
}
.pp-playback-chip-win{ background:var(--moss-dark); color:var(--parchment); border:1px solid var(--moss-bright); }
.pp-playback-chip-loss{ background:var(--blaze-dark); color:var(--parchment); border:1px solid var(--blaze-bright); }
@keyframes ppPlaybackChipPop{
  0%{ opacity:0; transform:translateX(-50%) translateY(4px) scale(0.5); }
  70%{ opacity:1; transform:translateX(-50%) translateY(0) scale(1.1); }
  100%{ opacity:1; transform:translateX(-50%) translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce){
  /* Backstop only - perfect-pack.js already skips startPlayback() entirely
     under reduced motion and jumps straight to the result screen, so in
     practice this screen never mounts for those players. Kept anyway so
     the CSS itself never introduces motion, matching the rest of the
     codebase's belt-and-suspenders convention (see game.css). */
  .pp-playback-computing,
  .pp-playback-clash .pp-playback-side-player,
  .pp-playback-clash .pp-playback-side-opponent,
  .pp-playback-chip{
    animation:none !important;
  }
  .pp-playback-side{ transform:none !important; }
}


/* =======================================================================
   Stage 3: screen transitions + micro-interactions.

   Motion strategy: every transition/animation-duration lives ONLY inside
   an explicit @media (prefers-reduced-motion: no-preference) block. Under
   `reduce`, none of these rules exist at all, so the browser's own
   default (transition-duration: 0s, animation: none) applies - there is
   no override to disable, which is what makes this testable by reading
   the stylesheet text directly (see scripts/test-perfect-pack.mjs): if a
   reduced-motion test finds NONE of the new animated properties inside a
   `no-preference` block, the motion is provably gated correctly. This
   mirrors the belt-and-suspenders convention the Stage 2 playback CSS
   above already uses (explicit reduced-motion backstop block), just
   inverted - gate the motion IN under no-preference, rather than gating it
   OUT under reduce - which is simpler to prove correct by inspection for
   this stage's larger number of animated properties.

   Only transform/opacity are ever animated (GPU-composited, matches the
   Stage 3 spec's performance constraint) - no width/height/margin/
   padding/top/left transitions anywhere below.
   ========================================================================= */

@media (prefers-reduced-motion: no-preference){

  /* --- 1. Screen-to-screen transitions -------------------------------- */
  /* .pp-screen-transition-in is added directly into the HTML string by
     render() (see perfect-pack.js) only when the incoming screen is a
     genuine screen change (forecast <-> draft <-> playback <-> result),
     never on an in-place re-render of the same screen. It is a single
     @keyframes animation (not a transition), so it needs no separate
     "before" frame, no later class removal, and no second DOM layer for
     an outgoing-screen snapshot: the moment content.innerHTML inserts
     this markup, the browser paints the new subtree and the keyframe
     plays on its own for the full duration, fading/sliding the incoming
     screen in from 8px below. This reads as a soft crossfade against
     whatever was on screen a moment ago without literally animating an
     exit copy of it. transform+opacity only. */
  .pp-screen-transition-in{
    animation: ppScreenEnter 0.28s ease-out both;
  }
  @keyframes ppScreenEnter{
    0%{ opacity:0; transform:translateY(8px); }
    100%{ opacity:1; transform:translateY(0); }
  }

  /* --- 2. Candidate hover + selection beats (draft screen) ------------- */
  .pp-candidate-card{
    transition:background 0.12s ease, box-shadow 0.12s ease, transform 0.15s ease-out;
  }
  .pp-candidate-card:hover{
    transform:translateY(-2px);
    box-shadow:0 6px 14px rgba(0,0,0,0.28), 0 0 0 1px var(--brass);
  }
  .pp-candidate-card.pp-candidate-highlighted{
    animation: ppCandidateSelectBeat 0.18s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  }
  @keyframes ppCandidateSelectBeat{
    0%{ transform:scale(0.96); }
    60%{ transform:scale(1.02); }
    100%{ transform:scale(1); }
  }

  /* --- 3. Lock flash (progress pip) ------------------------------------ */
  .pp-progress-pip--lock-flash{
    animation: ppLockFlash 0.2s ease-out;
  }
  @keyframes ppLockFlash{
    0%{ box-shadow:0 0 0 5px var(--brass-bright); }
    100%{ box-shadow:0 0 0 0 transparent; }
  }

  /* --- 4. Roster chip pop-in -------------------------------------------- */
  .pp-roster-chip--enter{
    animation: ppRosterChipPopIn 0.24s cubic-bezier(0.34, 1.56, 0.64, 1) both;
  }
  @keyframes ppRosterChipPopIn{
    0%{ opacity:0; transform:scale(0.5); }
    70%{ opacity:1; transform:scale(1.1); }
    100%{ opacity:1; transform:scale(1); }
  }

  /* --- 5. Forecast row staggered fade-in -------------------------------- */
  /* Rows 1-15 fade in with a small per-row stagger (30ms apart, ~500ms
     total including the last row's own 0.35s run) the first time the
     forecast screen mounts. nth-child covers exactly the 15 fixed
     encounters this board always renders (see forecastHtml) - no JS
     IntersectionObserver needed since the row count is fixed and known. */
  .pp-forecast-row{
    opacity:0;
    animation: ppForecastRowIn 0.35s ease-out both;
  }
  @keyframes ppForecastRowIn{
    0%{ opacity:0; transform:translateY(6px); }
    100%{ opacity:1; transform:translateY(0); }
  }
  .pp-forecast-row:nth-child(1){ animation-delay:0ms; }
  .pp-forecast-row:nth-child(2){ animation-delay:30ms; }
  .pp-forecast-row:nth-child(3){ animation-delay:60ms; }
  .pp-forecast-row:nth-child(4){ animation-delay:90ms; }
  .pp-forecast-row:nth-child(5){ animation-delay:120ms; }
  .pp-forecast-row:nth-child(6){ animation-delay:150ms; }
  .pp-forecast-row:nth-child(7){ animation-delay:180ms; }
  .pp-forecast-row:nth-child(8){ animation-delay:210ms; }
  .pp-forecast-row:nth-child(9){ animation-delay:240ms; }
  .pp-forecast-row:nth-child(10){ animation-delay:270ms; }
  .pp-forecast-row:nth-child(11){ animation-delay:300ms; }
  .pp-forecast-row:nth-child(12){ animation-delay:330ms; }
  .pp-forecast-row:nth-child(13){ animation-delay:360ms; }
  .pp-forecast-row:nth-child(14){ animation-delay:390ms; }
  .pp-forecast-row:nth-child(15){ animation-delay:420ms; }

  /* --- 6. CTA button press-down micro-feedback -------------------------- */
  .pp-primary-btn, .pp-secondary-btn{
    transition:background 0.12s ease, transform 0.08s ease-out;
  }
  .pp-primary-btn:active:not(:disabled), .pp-secondary-btn:active{
    transform:scale(0.98);
  }

  /* --- 7. Scouting summary fade-in (Stage B2) --------------------------- */
  /* Deliberately the lightest touch in this file: a single quick fade, no
     stagger, no transform beyond a tiny lift - this block is informational
     (numbers/hints a player needs to read carefully), not a celebratory
     moment like the roster-chip pop-in or lock-flash above, so it must not
     compete for attention with the headline or distract from reading. */
  .pp-scouting-summary{
    opacity:0;
    animation: ppScoutingSummaryIn 0.22s ease-out both;
  }
  @keyframes ppScoutingSummaryIn{
    0%{ opacity:0; transform:translateY(4px); }
    100%{ opacity:1; transform:translateY(0); }
  }
}
