/* ==========================================================================
   PAR — theme.css v1.2
   Art: game-artist-bot · Direction: GDD §7 (approved) — "morning sports page
   meets the clubhouse scorecard". Printed, not appy.

   Layer 1  design tokens (:root, light "Morning Edition")
   Layer 2  dark overrides ("Evening Edition", prefers-color-scheme)
   Layer 3  base + paper grain
   Layer 4  components: hole card, tiles, keyboard, stamps, ghost, scorecard,
            streak panel
   Layer 5  motion (signature: stamp-thump, ghost slide) + reduced-motion

   Conventions for programmer (details in art/NOTES.md):
   - every color/number comes from a --par-* custom property; no literals
     in component rules
   - state on tiles/keys via data-state="empty|tbd|correct|present|absent"
   - animations are class-triggered: .is-revealed (row cascade),
     .is-locked + .par-stamp--<grade> (stamp thump), .is-shown (ghost slide)
   ========================================================================== */

/* -- 1. Tokens · Morning Edition ------------------------------------------
   All pairs measured (WCAG 2.1 relative luminance):
   ink/paper 15.28 · correct fill/paper 4.44 · present fill/paper 3.30
   absent fill/paper 3.58 · letter contrasts ≥ 4.27 on every fill.
   Grayscale (rec709 luma Y): pine 106 / amber 127 / absent 136 — the
   correct/present pair separates by ΔY 21; letters are never color-only.
   NOTE: GDD's literal amber #B8860B measures 2.89:1 vs paper — tuned to
   #9E8206 (3.30:1) to satisfy §7's own "≥3:1" rule. Same hue family. */

:root {
  /* surfaces + text */
  --par-paper:        #F6F1E5;
  --par-ink:          #1C1B18;
  --par-ink-soft:     #4A463D;   /* secondary text, 7.4:1 on paper */
  --par-rule:         #1C1B18;   /* hairline borders — hard 1px, ink */

  /* feedback (Wordle grammar, R3) */
  --par-correct:      #2E7D5B;   /* pine */
  --par-present:      #9E8206;   /* mustard, tuned; see header note */
  --par-absent:       #8E887B;   /* warm gray */

  /* letters on filled tiles — per-state, strongest legal pair each */
  --par-fg-on-correct: #F6F1E5;  /* 4.44 on pine */
  --par-fg-on-present: #1C1B18;  /* 4.63 on amber */
  --par-fg-on-absent:  #1C1B18;  /* 4.27 on gray */
  --par-fg-on-face:    #1C1B18;  /* empty/typing tiles */

  /* accent — ONE accent color: pine (stamps, links, CTA, focus) */
  --par-accent:       #2E7D5B;
  --par-accent-ink:   #F6F1E5;   /* text on accent */

  /* structure */
  --par-tile-face:    #F6F1E5;   /* unfilled tile */
  --par-tile-border:  #1C1B18;
  --par-kb-face:      #F6F1E5;
  --par-shadow:       none;      /* no shadows except the stamp press */
  --par-stamp-shadow: 2px 2px 0 rgba(28, 27, 24, 0.55); /* the press */

  /* type (two families, see art/NOTES.md) */
  --font-display: "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  /* reward layer v1.2 — "calm base, explosive payoff" (GDD §7 ruling):
     gold foil for birdie+ rewards; contrast ledgered in §7 notes */
  --par-gold:         #C9A227;   /* foil fill; ink letters on it 7.12:1 */
  --par-gold-ink:     #1C1B18;

  /* geometry — printed, not bubbly */
  --radius-tile: 2px;
  --radius-card: 2px;
  --border-hard: 1px solid var(--par-rule);
  --tile-size: clamp(3rem, 12vw, 4rem);   /* 200%-zoom safe: rem-based */
  --tile-gap: clamp(0.25rem, 1.2vw, 0.5rem);

  /* motion */
  --thump-ms: 140ms;
  --fill-ms: 120ms;
  --ghost-ms: 480ms;
  --ease-press: cubic-bezier(0.2, 0.9, 0.3, 1.2); /* slight overshoot */
}

/* -- 2. Evening Edition ----------------------------------------------------
   pine lightens, amber warms (GDD §7). Measured: text/bg 14.38;
   letters on fills ≥ 3.25; correct/present Δgray 30. */

@media (prefers-color-scheme: dark) {
  :root {
    --par-paper:        #141310;   /* is the dark ground */
    --par-ink:          #E9E2D0;   /* is the light text */
    --par-ink-soft:     #B5AE9C;

    --par-correct:      #3D8F68;
    --par-present:      #C9962E;   /* warmed amber */
    --par-absent:       #6B665A;

    --par-fg-on-correct: #141310;  /* 4.71 on pine */
    --par-fg-on-present: #141310;  /* 6.98 on amber */
    --par-fg-on-absent:  #E9E2D0;  /* 4.42 on gray */
    --par-fg-on-face:    #E9E2D0;

    --par-accent:       #4FA97B;   /* 4.71 as text on the dark ground */
    --par-accent-ink:   #141310;

    --par-tile-face:    #1E1C18;
    --par-tile-border:  #E9E2D0;
    --par-kb-face:      #1E1C18;
    --par-stamp-shadow: 2px 2px 0 rgba(0, 0, 0, 0.8);
    --par-gold:         #D4AF37;   /* evening foil; ink letters 8.83:1 */
    --par-gold-ink:     #141310;
  }
}

/* -- 3. Base + paper grain ------------------------------------------------ */

body {
  margin: 0;
  background: var(--par-paper);
  color: var(--par-ink);
  font-family: var(--font-mono);
  -webkit-font-smoothing: antialiased;
}

/* Paper grain, ~3% opacity, one tileable SVG, GPU-cheap, no gradients.
   Sits above the background, below content; never intercepts input. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.03;
  background-image: url("../assets/paper-grain.svg"); /* or inline data-uri */
  background-size: 160px 160px;
}

/* -- 4a. Hole card (the printed scorecard) -------------------------------- */

.par-card {
  position: relative;
  z-index: 1;
  max-width: 30rem;
  margin-inline: auto;
  padding: 1.25rem 1rem 1.5rem;
  background: var(--par-paper);
  border: var(--border-hard);
  border-radius: var(--radius-card);
}

.par-card__date {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--par-ink-soft);
  border-bottom: var(--border-hard);
  padding-bottom: 0.5rem;
}

/* the answer, printed at the top — display serif per §7 */
.par-card__answer {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2rem, 9vw, 3rem);
  letter-spacing: 0.08em;
  margin: 0.75rem 0 0.25rem;
}

.par-card__parline {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.08em;
}

/* -- 4b. Letter tiles ------------------------------------------------------ */

.par-row {
  display: flex;
  gap: var(--tile-gap);
  justify-content: center;
  margin-bottom: var(--tile-gap);
}

.par-tile {
  box-sizing: border-box;
  width: var(--tile-size);
  height: var(--tile-size);
  display: grid;
  place-items: center;
  background: var(--par-tile-face);
  border: var(--border-hard);
  border-radius: var(--radius-tile);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: clamp(1.25rem, 5vw, 1.75rem);
  text-transform: uppercase;
  color: var(--par-fg-on-face);
  /* letters ALWAYS visible — color never carries alone (E10) */
}

.par-tile[data-state="correct"] {
  background: var(--par-correct);
  border-color: var(--par-correct);
  color: var(--par-fg-on-correct);
}
.par-tile[data-state="present"] {
  background: var(--par-present);
  border-color: var(--par-present);
  color: var(--par-fg-on-present);
}
.par-tile[data-state="absent"] {
  background: var(--par-absent);
  border-color: var(--par-absent);
  color: var(--par-fg-on-absent);
}

/* -- 4c. On-screen keyboard ------------------------------------------------ */

.par-kb {
  display: grid;
  gap: 0.375rem;
  width: 100%;
  max-width: 30rem;
  margin-inline: auto;
  padding: 0.75rem 0.25rem;
}

.par-kb__row {
  display: flex;
  gap: 0.375rem;
  justify-content: center;
}

.par-key {
  appearance: none;
  min-width: 2.75rem;          /* ≥44px tap target at 1x */
  min-height: 2.75rem;
  padding: 0.5rem 0.25rem;
  border: var(--border-hard);
  border-radius: var(--radius-tile);
  background: var(--par-kb-face);
  color: var(--par-ink);
  font: 700 0.875rem var(--font-mono);
  text-transform: uppercase;
  cursor: pointer;
}
.par-key:active {
  transform: translateY(1px);
}
.par-key[data-state="correct"] {
  background: var(--par-correct); color: var(--par-fg-on-correct); border-color: var(--par-correct);
}
.par-key[data-state="present"] {
  background: var(--par-present); color: var(--par-fg-on-present); border-color: var(--par-present);
}
.par-key[data-state="absent"] {
  background: var(--par-absent); color: var(--par-fg-on-absent); border-color: var(--par-absent);
}
.par-key--wide { min-width: 4.5rem; }

/* keyboard focus, accent, always visible (E10) */
.par-key:focus-visible,
.par-tile:focus-visible {
  outline: 2px solid var(--par-accent);
  outline-offset: 2px;
}

/* -- 4c-b. Action row (mute, undo) — programmer owns behavior, artist the skin.
   Lives under the keyboard in the thumb arc; same printed idiom as the keys. */
.par-btn {
  appearance: none;
  min-height: 2.75rem;            /* same touch target as keys */
  padding: 0.5rem 0.9rem;
  border: var(--border-hard);
  border-radius: var(--radius-tile);
  background: var(--par-kb-face);
  color: var(--par-ink);
  font: 600 0.75rem var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}
.par-btn:active { transform: translateY(1px); }
.par-btn:focus-visible {
  outline: 2px solid var(--par-accent);
  outline-offset: 2px;
}
/* muted = the quiet setting, visually quieter: dashed rule, soft ink */
.par-btn[aria-pressed="true"] {
  color: var(--par-ink-soft);
  border-color: var(--par-ink-soft);
  border-style: dashed;
}

/* -- 4d. Stamps (Birdie / Par / Bogey / Albatross / Double / Over) ---------
   A rubber stamp: double rule, uppercase mono, tilted, pressed ONCE. */

.par-stamp {
  position: relative;
  display: inline-block;
  padding: 0.375rem 0.75rem;
  border: 3px double var(--par-accent);
  border-radius: var(--radius-tile);
  color: var(--par-accent);
  font: 700 1rem var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  transform: rotate(-6deg);
  box-shadow: var(--par-stamp-shadow);
  background: transparent;
}
.par-stamp--albatross,
.par-stamp--birdie { color: var(--par-accent); border-color: var(--par-accent); }
.par-stamp--over   { color: var(--par-ink-soft); border-color: var(--par-ink-soft); }

/* -- 4e. Ghost strip -------------------------------------------------------
   The solver's line, printed on a strip that slides in once. */

.par-ghost {
  overflow: hidden;
  border-top: var(--border-hard);
  margin-top: 1rem;
  padding-top: 0.75rem;
}

.par-ghost__label {
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--par-ink-soft);
}

.par-ghost__strip {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.5rem;
}

.par-ghost__mini {
  width: 2rem;
  height: 2rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--par-ink-soft);
  border-radius: var(--radius-tile);
  font: 700 0.75rem var(--font-mono);
  color: var(--par-ink);
  background: var(--par-tile-face);
}
.par-ghost__mini[data-state="correct"] { background: var(--par-correct); border-color: var(--par-correct); color: var(--par-fg-on-correct); }
.par-ghost__mini[data-state="present"] { background: var(--par-present); border-color: var(--par-present); color: var(--par-fg-on-present); }
.par-ghost__mini[data-state="absent"]  { background: var(--par-absent);  border-color: var(--par-absent);  color: var(--par-fg-on-absent); }

/* -- 4f. Scorecard footer + streak panel ----------------------------------- */

.par-scorecard {
  margin-top: 1rem;
  border: var(--border-hard);
  border-radius: var(--radius-card);
  padding: 0.75rem 1rem;
  font-family: var(--font-mono);
  font-size: 0.875rem;
  letter-spacing: 0.06em;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.par-scorecard__figure { font-weight: 700; }

.par-streak {
  margin-top: 0.75rem;
  display: flex;
  gap: 1.25rem;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  color: var(--par-ink-soft);
}
.par-streak__value {
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--par-ink);
}
.par-streak__birdie { font-size: 0.75rem; } /* the connoisseur's streak, smaller per GDD §3 */

/* -- 5. Motion — two flourishes, nothing else loops ------------------------- */

/* THE SIGNATURE: stamp-thump on lock. 140ms, press + slight rotation. */
@keyframes par-stamp-thump {
  0%   { opacity: 0; transform: rotate(-6deg) scale(1.9); }
  55%  { opacity: 1; transform: rotate(-7deg) scale(0.94); }
  75%  { transform: rotate(-5.5deg) scale(1.03); }
  100% { opacity: 1; transform: rotate(-6deg) scale(1); }
}
.is-locked .par-stamp {
  animation: par-stamp-thump var(--thump-ms) var(--ease-press) both;
}

/* feedback fill, 120ms cascade per tile — set --tile-i (0-based) in JS */
@keyframes par-fill {
  from { opacity: 0.25; transform: scale(0.92); }
  to   { opacity: 1;   transform: scale(1); }
}
.par-tile.is-revealed[data-state="correct"],
.par-tile.is-revealed[data-state="present"],
.par-tile.is-revealed[data-state="absent"] {
  animation: par-fill var(--fill-ms) ease-out both;
  animation-delay: calc(var(--tile-i, 0) * var(--fill-ms));
}

/* THE OTHER SIGNATURE: ghost strip slides in once, like a printed strip. */
@keyframes par-ghost-slide {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0);     opacity: 1; }
}
.par-ghost.is-shown .par-ghost__strip {
  animation: par-ghost-slide var(--ghost-ms) cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Reduced motion: states arrive instantly, nothing travels. */
@media (prefers-reduced-motion: reduce) {
  .is-locked .par-stamp,
  .par-tile.is-revealed[data-state="correct"],
  .par-tile.is-revealed[data-state="present"],
  .par-tile.is-revealed[data-state="absent"],
  .par-ghost.is-shown .par-ghost__strip {
    animation: none;
  }
  .par-key:active { transform: none; }
}

/* -- 200% zoom safety -------------------------------------------------------
   Everything scales in rem/em; the board is width-capped in rem with
   clamp()ed tiles, so 200% text zoom reflows instead of clipping. No
   fixed-px type anywhere. Layout columns collapse via the cap on .par-card
   and .par-kb (max-width 30rem) — verify at 640 CSS px wide, 200%. */

/* -- 6. Celebrations (director ruling 2) ------------------------------------
   One distinct micro-animation per grade. All transform/opacity, one-shot,
   <=700ms, ink/pine/paper only — rubber stamps and newsprint, no cannons.
   TRIGGER CONTRACT: on lock, programmer sets data-grade on the card AND
   keeps .is-locked. Elements:
     .par-confetti  — render ONLY for albatross; 12 <span>s, vars set below
     wing ticks     — pure ::before/::after on the birdie stamp, no markup
   Sympathetic over/DNF copy lives in game copy, not here (GDD §5 no-guilt). */

/* confetti: tiny paper squares, one burst, never looping (GDD §7) */
.par-confetti {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}
.par-confetti span {
  position: absolute;
  left: 50%;
  top: 38%;
  width: 6px;
  height: 6px;
  opacity: 0;
  /* per-piece vars: --dx --dy --rot --c set inline or nth-child below */
  background: var(--pc, var(--par-correct));
  /* NOTE: animation lives on the .is-locked scope below, NOT here —
     otherwise the burst fires at page load and can never re-fire. */
}
.is-locked .par-confetti span {
  animation: par-confetti-burst 560ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: 120ms; /* lands just after the thump */
}
@keyframes par-confetti-burst {
  0%   { opacity: 0; transform: translate(0, 0) rotate(0deg) scale(0.6); }
  12%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1); }
}
/* 12 pieces: alternating pine / ink / amber — the whole palette, no more */
.par-confetti span:nth-child(3n+1) { --pc: var(--par-correct); }
.par-confetti span:nth-child(3n+2) { --pc: var(--par-ink); }
.par-confetti span:nth-child(3n)   { --pc: var(--par-present); }
.par-confetti span:nth-child(1)  { --dx:-58px; --dy:-46px; --rot:-160deg; }
.par-confetti span:nth-child(2)  { --dx:-38px; --dy:-72px; --rot:120deg;  }
.par-confetti span:nth-child(3)  { --dx:-12px; --dy:-88px; --rot:-90deg;  }
.par-confetti span:nth-child(4)  { --dx: 16px; --dy:-80px; --rot:150deg;  }
.par-confetti span:nth-child(5)  { --dx: 44px; --dy:-64px; --rot:-120deg; }
.par-confetti span:nth-child(6)  { --dx: 62px; --dy:-38px; --rot:80deg;   }
.par-confetti span:nth-child(7)  { --dx: 66px; --dy:-8px;  --rot:-70deg;  }
.par-confetti span:nth-child(8)  { --dx: 48px; --dy: 18px; --rot:110deg;  }
.par-confetti span:nth-child(9)  { --dx: 20px; --dy: 30px; --rot:-140deg; }
.par-confetti span:nth-child(10) { --dx:-14px; --dy: 26px; --rot:100deg;  }
.par-confetti span:nth-child(11) { --dx:-40px; --dy: 12px; --rot:-110deg; }
.par-confetti span:nth-child(12) { --dx:-60px; --dy:-14px; --rot:70deg;   }

/* ALBATROSS — the biggest: thump, then the burst (total ~680ms) */
.is-locked[data-grade="albatross"] .par-stamp {
  animation: par-stamp-thump var(--thump-ms) var(--ease-press) both;
}
.is-locked[data-grade="albatross"] .par-confetti span { animation-name: par-confetti-burst; }

/* BIRDIE — thump + single wing-tick flourish (two ink strokes flick out) */
.is-locked[data-grade="birdie"] .par-stamp { animation: par-stamp-thump var(--thump-ms) var(--ease-press) both; }
.par-stamp--birdie::before,
.par-stamp--birdie::after {
  content: "";
  position: absolute;
  top: -7px;
  width: 16px;
  height: 2px;
  background: var(--par-ink);
  opacity: 0;
  transform: scaleX(0);
}
.par-stamp--birdie::before { left: -20px; transform-origin: right center; rotate: 24deg; }
.par-stamp--birdie::after  { right: -20px; transform-origin: left center;  rotate: -24deg; }
.is-locked[data-grade="birdie"] .par-stamp--birdie::before,
.is-locked[data-grade="birdie"] .par-stamp--birdie::after {
  animation: par-wing-tick 300ms ease-out both;
  animation-delay: 140ms; /* after the press lands */
}
@keyframes par-wing-tick {
  0%   { opacity: 0; transform: scaleX(0); }
  35%  { opacity: 1; }
  70%  { opacity: 1; transform: scaleX(1.15); }
  100% { opacity: 0; transform: scaleX(1); }
}

/* PAR — the stamp-thump as built (base rule in §5) */

/* BOGEY — softer thud: smaller press, no overshoot, lands heavy */
.is-locked[data-grade="bogey"] .par-stamp {
  animation: par-soft-thud 200ms cubic-bezier(0.3, 0, 0.4, 1) both;
}
@keyframes par-soft-thud {
  0%   { opacity: 0; transform: rotate(-6deg) scale(1.35); }
  60%  { opacity: 1; transform: rotate(-6deg) scale(0.97); }
  100% { opacity: 1; transform: rotate(-6deg) scale(1); }
}

/* DOUBLE — double-thud: two heavy presses, the second smaller */
.is-locked[data-grade="double"] .par-stamp {
  animation: par-double-thud 480ms cubic-bezier(0.3, 0, 0.4, 1) both;
}
@keyframes par-double-thud {
  0%   { opacity: 0; transform: rotate(-6deg) scale(1.5); }
  25%  { opacity: 1; transform: rotate(-6.5deg) scale(0.96); }
  40%  { transform: rotate(-6deg) scale(1.02); }
  55%  { transform: rotate(-6deg) scale(1) translateY(0); }
  70%  { transform: rotate(-6deg) scale(1.18) translateY(1px); }
  85%  { transform: rotate(-6.5deg) scale(0.98) translateY(1px); }
  100% { opacity: 1; transform: rotate(-6deg) scale(1) translateY(0); }
}

/* OVER / DNF — quiet card: the stamp simply arrives, no thud, no shame */
.is-locked[data-grade="over"] .par-stamp {
  animation: par-quiet-in 300ms ease-out both;
}
@keyframes par-quiet-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Reduced motion: every grade lands as a still, instantly */
@media (prefers-reduced-motion: reduce) {
  .is-locked[data-grade] .par-stamp,
  .par-stamp--birdie::before,
  .par-stamp--birdie::after,
  .par-confetti span { animation: none; opacity: 1; transform: rotate(-6deg); }
  .par-confetti span { transform: none; }
  .par-stamp--birdie::before,
  .par-stamp--birdie::after { opacity: 0; }
}

/* -- 7. Reward layer v1.2 (director FLASH PASS) -----------------------------
   "Restraint applies to chrome, NOT rewards — calm base, explosive payoff."
   Family: ink / pine / paper / gold. No rainbow, no gradients, one-shot,
   hard ceiling 1.2s. See §7 contrast ledger for the gold pair. */

/* gold-foil stamp variant — Programmer adds --gold to birdie/albatross stamps */
.par-stamp--gold {
  background: var(--par-gold);
  color: var(--par-gold-ink);
  border-color: var(--par-gold-ink);
  box-shadow: var(--par-stamp-shadow);
}

/* screen-shake — albatross only, fires with the thump (140ms delay) */
@keyframes par-shake {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-2px, 1px) rotate(-0.2deg); }
  45% { transform: translate(2px, -1px) rotate(0.2deg); }
  70% { transform: translate(-1.5px, 0.5px); }
  85% { transform: translate(1px, -0.5px); }
}
.par-card.is-locked[data-grade="albatross"],
.is-locked[data-grade="albatross"] { /* container-level: card in game, demo box in lookbook */
  animation: par-shake 260ms ease-out 140ms 1;
}

/* confetti escalation — waves 2 + 3 join the v1.1 burst.
   Pieces 1–12: wave 1 squares (as built). 13–18: pine/amber ribbons.
   19–24: wave 3 squares. Last pixel lands ~1.04s — under the 1.2s cap. */
.par-confetti span:nth-child(n+13) {
  width: 3px; height: 14px; border-radius: 1px; /* the ribbons */
  animation-delay: 300ms;
}
.par-confetti span:nth-child(n+13):nth-child(odd)  { --pc: var(--par-correct); }
.par-confetti span:nth-child(n+13):nth-child(even) { --pc: var(--par-present); }
.par-confetti span:nth-child(n+19) {
  width: 6px; height: 6px; border-radius: 0;
  animation-delay: 480ms;
}
.par-confetti span:nth-child(13) { --dx:-70px; --dy:-52px; --rot:-200deg; }
.par-confetti span:nth-child(14) { --dx:-30px; --dy:-95px; --rot:160deg;  }
.par-confetti span:nth-child(15) { --dx: 10px; --dy:-105px; --rot:-80deg; }
.par-confetti span:nth-child(16) { --dx: 48px; --dy:-88px; --rot:190deg;  }
.par-confetti span:nth-child(17) { --dx: 74px; --dy:-58px; --rot:-150deg; }
.par-confetti span:nth-child(18) { --dx: 84px; --dy:-16px; --rot:120deg;  }
.par-confetti span:nth-child(19) { --dx:-84px; --dy:-30px; --rot:140deg;  }
.par-confetti span:nth-child(20) { --dx:-52px; --dy: 40px; --rot:-170deg; }
.par-confetti span:nth-child(21) { --dx:-20px; --dy: 52px; --rot:130deg;  }
.par-confetti span:nth-child(22) { --dx: 26px; --dy: 56px; --rot:-120deg; }
.par-confetti span:nth-child(23) { --dx: 60px; --dy: 38px; --rot:150deg;  }
.par-confetti span:nth-child(24) { --dx: 88px; --dy: 6px;  --rot:-100deg; }

/* birdie puff — single small wave, 6 pieces, 380ms + 120ms delay */
.par-puff {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}
.par-puff span {
  position: absolute;
  left: 50%; top: 40%;
  width: 5px; height: 5px;
  opacity: 0;
  background: var(--pc, var(--par-correct));
}
.is-locked[data-grade="birdie"] .par-puff span {
  animation: par-confetti-burst 380ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: 140ms;
}
.par-puff span:nth-child(odd)  { --pc: var(--par-gold); }
.par-puff span:nth-child(even) { --pc: var(--par-correct); }
.par-puff span:nth-child(1) { --dx:-26px; --dy:-30px; --rot:-90deg;  }
.par-puff span:nth-child(2) { --dx: 24px; --dy:-34px; --rot:80deg;   }
.par-puff span:nth-child(3) { --dx: 32px; --dy: 6px;  --rot:-70deg;  }
.par-puff span:nth-child(4) { --dx: 12px; --dy: 26px; --rot:100deg;  }
.par-puff span:nth-child(5) { --dx:-16px; --dy: 22px; --rot:-110deg; }
.par-puff span:nth-child(6) { --dx:-32px; --dy:-2px;  --rot:60deg;   }
.is-locked[data-grade="birdie"] .par-puff span { animation-name: par-confetti-burst; }

/* v1.2 triggers — gold stamp + puff; quiet grades unchanged from §6 */
.is-locked[data-grade="birdie"] .par-stamp--gold { /* thump via §6 birdie rule */ }
.is-locked[data-grade="albatross"] .par-stamp--gold {
  animation: par-stamp-thump var(--thump-ms) var(--ease-press) both;
}

/* streak-milestone ribbon — pine banner unfurls across the card */
.par-ribbon {
  position: absolute;
  left: 6%; right: 6%; top: 34%;
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  background: var(--par-correct);
  color: var(--par-fg-on-correct);
  border-top: 2px solid var(--par-gold);
  border-bottom: 2px solid var(--par-gold);
  transform: scaleX(0);
  transform-origin: left center;
  font-family: var(--font-mono);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.par-ribbon__label { font-size: 0.7rem; opacity: 0.85; }
.par-ribbon__num {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--par-gold);
}
@keyframes par-ribbon-unfurl {
  0%   { transform: scaleX(0); opacity: 1; }
  60%  { transform: scaleX(1.02); }
  80%  { transform: scaleX(0.99); }
  100% { transform: scaleX(1); opacity: 1; }
}
@keyframes par-ribbon-text {
  0%, 45% { opacity: 0; transform: translateY(3px); }
  100%    { opacity: 1; transform: translateY(0); }
}
/* fires late in the lock sequence: stamp lands, then the banner (≤1.2s total) */
.is-locked[data-milestone] .par-ribbon {
  animation: par-ribbon-unfurl 420ms cubic-bezier(0.2, 0.9, 0.3, 1.1) 520ms both;
}
.is-locked[data-milestone] .par-ribbon .par-ribbon__label,
.is-locked[data-milestone] .par-ribbon .par-ribbon__num {
  animation: par-ribbon-text 260ms ease-out 700ms both;
}

/* reduced-motion: reward layer lands as stills */
@media (prefers-reduced-motion: reduce) {
  .par-card.is-locked[data-grade="albatross"],
  .is-locked[data-grade="albatross"],
  .is-locked[data-milestone] .par-ribbon,
  .is-locked[data-milestone] .par-ribbon .par-ribbon__label,
  .is-locked[data-milestone] .par-ribbon .par-ribbon__num,
  .par-puff span,
  .par-confetti span:nth-child(n+13) {
    animation: none;
  }
  .par-ribbon { transform: none; }
  .par-puff span, .par-confetti span { opacity: 1; transform: none; }
}
