/* ============================================================================
   components.css — reusable widgets. Depends on tokens.css + base.css.

   ---------------------------------------------------------------------------
   CONTRACT: markup this file assumes index.html will provide
   ---------------------------------------------------------------------------

   BUTTONS
     <button class="btn btn-primary">label</button>
     Modifiers: .btn-secondary, .btn-ghost, .btn-outline-danger, .btn-icon
                (icon-only, square), size modifiers .btn--sm / .btn--lg.
     An icon inside a button is `<svg class="icon">…</svg>` (from
     AW.Icons.html(), see js/fx.js) — just drop it before/after the label text.

   CARDS
     <article class="card" data-active="true|false">
       <div class="card-media"><img …></div>
       <div class="card-body">…</div>
     </article>
     data-active="true" is what turns on the --spirit rim/glow (species
     picker cards, the active metamorphosis-stage row, etc). Plain
     .card--interactive adds hover lift without needing data-active.

   DICE (real 3D cube, 6 faces)
     <div class="dice-scene">
       <div class="dice-cube" id="diceCube">
         <div class="dice-face f1"><div class="pip-grid">9× <span class="pip"></span></div></div>
         <div class="dice-face f2">…same 9 pips…</div>
         <div class="dice-face f3">…</div>
         <div class="dice-face f4">…</div>
         <div class="dice-face f5">…</div>
         <div class="dice-face f6">…</div>
       </div>
     </div>
     IMPORTANT: every .dice-face contains the SAME nine `.pip` spans in the
     same order — which pips are visible is decided by CSS purely from the
     .f1…f6 class (nth-child selectors below), not by different markup per
     face. This is what lets AW.FX.rollDie(cubeEl, faceValue, onDone) do its
     job by only ever touching `cubeEl.style.transform` — it never has to
     know or edit face content.
     Face → value → rotation is fixed (opposite faces sum to 7, standard
     die): f1=1 (front, 0°), f6=6 (back), f3=3 (right), f4=4 (left),
     f2=2 (top), f5=5 (bottom). js/fx.js's rollDie() encodes the exact same
     table — do not renumber one without the other.

   EGG (Hatchery)
     The real click target js/ui/hatchery.js wires up is a single flat
     `<div id="eggTapZone">` (no nested .egg-glow/.egg-shell) — it gets the
     full gold-shell paint AND a ::before glow directly, so this works:
       <div id="eggTapZone"></div>
     The original two-layer authoring form below still works identically if
     index.html ever nests it that way instead:
       <div class="egg" id="hatcheryEgg">
         <div class="egg-glow"></div>
         <div class="egg-shell">
           <div class="egg-rune"><!-- optional AW.Icons.html('egg') --></div>
         </div>
       </div>
     Either way, js/fx.js injects <svg class="egg-cracks">…</svg> itself the
     first time AW.FX.crackEgg()/resetEgg() runs — never hand-author crack
     paths. Tap/click handler stays app logic's job (run your hatch-progress
     code, then call AW.FX.crackEgg(tapsRemaining, totalTaps)).

   MODALS
     app.js's real generic modal host toggles visibility with the `hidden`
     PROPERTY only (`overlay.hidden = false/true`) — no data-open step — so
     #awModalOverlay/#awModalPanel default to fully visible/settled the
     moment `hidden` is removed:
       <div id="awModalOverlay" class="modal-overlay" hidden>
         <div id="awModalPanel" class="modal-panel" role="dialog" aria-modal="true">
           <!-- content is injected fresh per open via innerHTML, using
                .modal-head / .modal-body / .modal-foot (the short names
                app.js/settings.js/chronicle.js actually emit) -->
         </div>
       </div>
     `data-open="false"` on the overlay is an OPT-IN progressive enhancement
     for a two-step animated exit (fade + scale down before you set `hidden`
     back) — set it only if you want that; the modal is correct without it.
     .modal-header/.modal-footer (long form) work identically to
     .modal-head/.modal-foot for any modal authored directly in index.html.

   TOASTS
     app.js's real toast() queries a single persistent element by id and
     toggles `.is-visible` — there is no dynamic stack of .toast[data-mood]
     elements in the current wiring:
       <div id="awToast" class="toast">
         <span id="awToastIcon" class="icon-slot"></span>
         <span id="awToastMessage"></span>
       </div>
     .toast defaults to hidden/faded; `.is-visible` reveals + animates it in.
     An optional `.toast-stack` wrapper (for a future multi-toast queue) and
     `data-mood="success|weak|fail|critical|ambient"` (matching
     AW.Audio.playChord's vocabulary 1:1) both still work if used — #awToast
     just also works completely unwrapped, with no mood set, as it is today.

   METERS / STAT BARS
     <div class="meter">
       <div class="meter-label"><span>Affection</span><span class="meter-value">45/100</span></div>
       <div class="meter-track"><div class="meter-fill" style="width:45%"></div></div>
     </div>
     Add data-tone="spirit" on .meter-fill only for the ONE bar that is
     conceptually "the bond/soul resonance with THIS species" if you want it
     to shine in --spirit instead of gold — every other stat bar stays gold.

   CHIPS / BADGES
     <span class="chip">label</span>  /  <span class="badge">label</span>
     <span class="badge badge--spirit">label</span> for a species-tinted rim
     (text stays --text-heading/--text-on-gold, never colored by species).

   NAV TABS
     <nav class="nav-tabs">
       <button class="nav-tab" data-view="hatchery" aria-current="page">…</button>
       <button class="nav-tab" data-view="sanctuary">…</button>
       …
     </nav>
     Exactly one .nav-tab carries aria-current="page" at a time — swap the
     attribute (not a class) when the orchestrator's router changes view;
     that single attribute change is 100% of what's needed for the active
     tab's gold underline + glow to move, no JS style writes required.
   ============================================================================ */


/* ---------------------------------------------------------------------- */
/* BUTTONS                                                                  */
/* ---------------------------------------------------------------------- */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-cinzel);
  font-weight: 700;
  font-size: var(--fs-sm);
  letter-spacing: var(--ls-wide);
  line-height: 1.2;
  padding: 0.75em 1.4em;
  border-radius: var(--radius-md);
  /* bare <button class="btn"> with no variant (a tertiary "Close"/"Cancel"
     in dynamically-built modal markup) still needs to look like a button —
     this default IS the .btn-secondary treatment; .btn-primary/.btn-ghost/
     .btn-outline-danger below override every one of these properties. */
  color: var(--text-accent);
  background: color-mix(in oklab, var(--surface-2) 70%, transparent);
  border: var(--border-thin) solid var(--gold-leaf);
  transition: transform var(--dur-fast) var(--ease-standard),
              box-shadow var(--dur-fast) var(--ease-standard),
              filter var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard),
              background var(--dur-fast) var(--ease-standard),
              border-color var(--dur-fast) var(--ease-standard);
  /* Confirmed live in-browser: white-space:nowrap + .btn-primary's
     overflow:hidden (needed to contain the shimmer sweep) + flex-shrink's
     default of 1 combined to silently CLIP button labels — a button inside
     a flex-wrap row would rather shrink below its own text's width than
     wrap to a new row, and the clipped sliver is exactly the missing
     first/last letter reported ("Continue to the Hatchery →", every
     "Choose <Species>" button, etc). flex-shrink:0 stops the squeeze;
     allowing the label to wrap is the fallback for any button still too
     wide for a narrow .btn-block/.btn-primary card at 100% width. */
  white-space: normal;
  flex-shrink: 0;
  text-align: center;
  user-select: none;
}
.btn:hover { color: var(--text-heading); border-color: var(--gold-hi); background: var(--surface-raised); }
.btn:active { transform: translateY(1px) scale(0.98); }
.btn:disabled { opacity: 0.45; filter: grayscale(0.4); pointer-events: none; }
.btn .icon { width: 1.15em; height: 1.15em; }

/* Primary — the gold hammered-foil button. Text is --ink on gold: ≥9.3:1,
   fixing the previous build's ~2.1:1 failure (light text on gold). */
.btn-primary {
  color: var(--text-on-gold);
  background-image: var(--gold-foil);
  background-size: 220% 100%, 220% 100%, 100% 100%;
  background-position: 0 0, 0 0, 0 0;
  border-color: color-mix(in oklab, var(--gold-dim) 70%, black);
  box-shadow: var(--shadow-md), 0 0 0 1px color-mix(in oklab, var(--gold-hi) 40%, transparent) inset;
  overflow: hidden;
}
.btn-primary:hover { background-image: var(--gold-foil-hover); box-shadow: var(--shadow-lg), var(--shadow-gold-glow); }
.btn-primary:focus-visible { box-shadow: var(--shadow-focus), var(--shadow-gold-glow); }

/* the REAL shimmer: a diagonal light sweep on ::after, continuously looping.
   This is the one animation explicitly exempted from prefers-reduced-motion
   (see base.css) because it's subtle (a soft highlight pass, not motion of
   the button itself) and continuous rather than a one-shot attention-grab. */
.btn-primary::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg,
    transparent 30%,
    rgba(255,255,255,0.45) 47%,
    rgba(255,255,255,0.65) 50%,
    rgba(255,255,255,0.45) 53%,
    transparent 70%);
  background-size: 260% 100%;
  background-position: 200% 0;
  animation: btn-shimmer var(--dur-shimmer) linear infinite;
  pointer-events: none;
  mix-blend-mode: overlay;
}
@keyframes btn-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -60% 0; }
}

/* Secondary — gold outline on dark surface, gold text (7.1:1, see tokens.css) */
.btn-secondary {
  color: var(--text-accent);
  background: color-mix(in oklab, var(--surface-2) 70%, transparent);
  border-color: var(--gold-leaf);
}
.btn-secondary:hover { color: var(--text-heading); border-color: var(--gold-hi); background: var(--surface-raised); }

/* Ghost — text-only, for tertiary actions inside dense toolbars */
.btn-ghost {
  color: var(--text-muted);
  background: transparent;
  border-color: transparent;
}
.btn-ghost:hover { color: var(--text-heading); background: color-mix(in oklab, var(--surface-2) 55%, transparent); }

/* Destructive actions intentionally do NOT use --seal-red (reserved for the
   keepsake export only, per art direction). Danger is communicated with a
   heavier ink-toned border + explicit label/icon instead of hue-coding. */
.btn-outline-danger {
  color: var(--text-heading);
  background: var(--surface-sunken);
  border-color: var(--iron-gall);
}
.btn-outline-danger:hover { border-color: var(--gold-dim); background: var(--surface-2); }

/* .btn-icon is used BOTH as a size/shape modifier on ".btn .btn-icon" AND,
   in dynamically-built modal markup (app.js/settings.js/chronicle.js), on
   its own with no ".btn" alongside it — so it carries its own complete
   button chrome (a quiet ghost-style icon button) rather than depending on
   ".btn" for color/background/transitions. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5em;
  aspect-ratio: 1;
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  background: transparent;
  border: var(--border-thin) solid transparent;
  transition: color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.btn-icon:hover { color: var(--text-heading); background: var(--surface-2); }
.btn-icon .icon { width: 1.15em; height: 1.15em; }
.btn--sm { font-size: var(--fs-xs); padding: 0.55em 1em; }
.btn--lg { font-size: var(--fs-md); padding: 0.9em 1.8em; }


/* ---------------------------------------------------------------------- */
/* CARDS                                                                    */
/* ---------------------------------------------------------------------- */

/* .species-card is aliased in here throughout: it's the real class name
   js/ui/hatchery.js already renders (data-species + data-active="true|false"
   on an <article>, no separate ".card" class alongside it) — rather than
   asking that working, already-tested code to change, .card's entire visual
   contract is extended to match it 1:1, including the [data-active] rim. */
.card, .species-card {
  position: relative;
  background:
    var(--noise-grain),
    linear-gradient(160deg, color-mix(in oklab, var(--surface-2) 96%, white 4%) 0%, var(--surface-2) 55%, var(--surface-sunken) 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-gold-dim);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  transition: box-shadow var(--dur-base) var(--ease-standard),
              border-color var(--dur-base) var(--ease-standard),
              transform var(--dur-base) var(--ease-standard);
}
/* Reported live: "Bond & Vitals" and every other plain <div class="card"> —
   the ones used directly (Sanctuary vitals, Sky Roll, Oracle, Threads,
   Journal Prompt, every Settings card) with no .card-media/.card-body
   wrapper inside — had ZERO padding of their own (padding only ever lived
   on .card-body, for the .species-card variant). Content sat flush against
   the card's own edge, and since the card also clips via border-radius +
   overflow:hidden, the first/last letter of anything touching that edge
   (almost always the h3.card-title, first child in the card) got eaten by
   the rounded corner's clip path. .species-card is excluded here — its
   full-bleed .species-card-media image is deliberately flush to the edge,
   and .species-card-body already carries its own padding. */
.card:not(.species-card) { padding: var(--space-5); }

.card--interactive, .species-card { cursor: pointer; }
.card--interactive:hover, .card--interactive:focus-visible,
.species-card:hover, .species-card:focus-visible {
  transform: translateY(-3px);
  border-color: color-mix(in oklab, var(--gold-leaf) 55%, transparent);
  box-shadow: var(--shadow-lg);
}

/* the ONLY place a card's rim reacts in --spirit rather than gold — this is
   the species-card / active-stage "this one is chosen" signal */
.card[data-active="true"], .species-card[data-active="true"] {
  border-color: var(--spirit-rim);
  box-shadow: var(--shadow-lg), var(--shadow-spirit-glow);
}

.card-media, .species-card-media {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  background: var(--surface-0);
}
.card-media img, .species-card-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform var(--dur-slow) var(--ease-standard);
}
.card--interactive:hover .card-media img,
.species-card:hover .species-card-media img { transform: scale(1.06); }
.card-media::after, .species-card-media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(12,8,6,0.85) 0%, rgba(12,8,6,0.15) 55%, transparent 100%);
  pointer-events: none;
}

.card-body, .species-card-body { position: relative; padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); }
.card-title, .species-card-body h3 { font-family: var(--font-cinzel); color: var(--text-heading); font-size: var(--fs-lg); font-weight: 700; }
.card-subtitle, .species-latin { font-family: var(--font-scroll); color: var(--text-accent); font-size: var(--fs-sm); font-style: italic; }
.card-text, .species-desc { color: var(--text-muted); font-size: var(--fs-sm); line-height: var(--lh-normal); }
.card-actions { display: flex; gap: var(--space-2); padding-top: var(--space-2); border-top: var(--border-hairline) solid color-mix(in oklab, var(--gold-leaf) 20%, transparent); }
.card-actions .btn { flex: 1; }
/* .species-card-body's own Choose-<Species> button (rendered as a single
   full-width .btn.btn-primary.btn-block, no .card-actions wrapper) */
.species-card-body .btn { margin-top: var(--space-1); }
.btn-block { display: flex; width: 100%; }


/* ---------------------------------------------------------------------- */
/* DICE — real 3D cube, 6 faces                                            */
/* ---------------------------------------------------------------------- */

.dice-scene {
  width: var(--dice-size);
  height: var(--dice-size);
  margin-inline: auto;
  perspective: 640px;
}

.dice-cube {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transform: rotateX(0deg) rotateY(0deg);
  transition: transform var(--dur-dice) var(--ease-out-back);
  will-change: transform;
}

.dice-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    var(--noise-grain),
    radial-gradient(120% 120% at 30% 20%, var(--parchment) 0%, var(--parchment-shade) 70%, #C4AC81 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-thin) solid var(--gold-dim);
  border-radius: var(--radius-sm);
  box-shadow: inset 0 0 12px rgba(0,0,0,0.25), 0 2px 4px rgba(0,0,0,0.4);
}
/* the six placements — opposite faces sum to 7, see contract note above */
.dice-face.f1 { transform: translateZ(var(--dice-half)); }                          /* value 1 */
.dice-face.f6 { transform: rotateY(180deg) translateZ(var(--dice-half)); }           /* value 6 */
.dice-face.f3 { transform: rotateY(90deg) translateZ(var(--dice-half)); }            /* value 3 */
.dice-face.f4 { transform: rotateY(-90deg) translateZ(var(--dice-half)); }           /* value 4 */
.dice-face.f2 { transform: rotateX(90deg) translateZ(var(--dice-half)); }            /* value 2 */
.dice-face.f5 { transform: rotateX(-90deg) translateZ(var(--dice-half)); }           /* value 5 */

.pip-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 6%;
  width: 68%;
  height: 68%;
}
.pip {
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, color-mix(in oklab, var(--ink) 80%, #442d16) 0%, var(--ink) 75%);
  box-shadow: inset 0 1px 1px rgba(255,255,255,0.15), 0 1px 1px rgba(0,0,0,0.5);
  opacity: 0;
  align-self: center;
  justify-self: center;
  width: 100%;
  height: 100%;
}
/* which of the 9 pips light up, purely from the face's own class */
.f1 .pip:nth-child(5) { opacity: 1; }
.f2 .pip:nth-child(1), .f2 .pip:nth-child(9) { opacity: 1; }
.f3 .pip:nth-child(1), .f3 .pip:nth-child(5), .f3 .pip:nth-child(9) { opacity: 1; }
.f4 .pip:nth-child(1), .f4 .pip:nth-child(3), .f4 .pip:nth-child(7), .f4 .pip:nth-child(9) { opacity: 1; }
.f5 .pip:nth-child(1), .f5 .pip:nth-child(3), .f5 .pip:nth-child(5), .f5 .pip:nth-child(7), .f5 .pip:nth-child(9) { opacity: 1; }
.f6 .pip:nth-child(1), .f6 .pip:nth-child(3), .f6 .pip:nth-child(4),
.f6 .pip:nth-child(6), .f6 .pip:nth-child(7), .f6 .pip:nth-child(9) { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  /* js/fx.js also short-circuits rollDie() to skip the spin outright and
     jump straight to the resting transform — this is the CSS-side backstop */
  .dice-cube { transition-duration: 0.001ms; }
}


/* ---------------------------------------------------------------------- */
/* EGG — Hatchery interactive soul-core                                    */
/* ---------------------------------------------------------------------- */

/* #eggTapZone is js/ui/hatchery.js's real click target for the egg ritual —
   it has no separate .egg/.egg-shell wrapper pair in that module (only
   sibling #eggTapsRemaining / #eggProgressBar for the text+bar). Rather than
   depend on index.html nesting a whole .egg > .egg-glow + .egg-shell tree
   inside it, #eggTapZone gets the FULL merged treatment below (position +
   sizing from .egg, gold-shell paint from .egg-shell, and its own ::before
   standing in for .egg-glow) so it looks right as a single flat element too;
   if index.html DOES nest the full .egg/.egg-shell/.egg-glow structure
   inside/around #eggTapZone instead, these rules simply layer harmlessly. */
.egg, #eggTapZone {
  position: relative;
  width: min(56vw, 220px);
  aspect-ratio: 3 / 4;
  margin-inline: auto;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

#eggTapZone::before {
  content: "";
  position: absolute;
  inset: -18%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle, var(--spirit-glow) 0%, transparent 70%);
  filter: blur(6px);
  animation: egg-pulse 3.6s ease-in-out infinite;
  pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
  #eggTapZone::before { animation: none; opacity: 0.75; }
}

.egg-glow {
  position: absolute;
  inset: -18%;
  border-radius: 50%;
  background: radial-gradient(circle, var(--spirit-glow) 0%, transparent 70%);
  filter: blur(6px);
  animation: egg-pulse 3.6s ease-in-out infinite;
  pointer-events: none;
}
@keyframes egg-pulse {
  0%, 100% { opacity: 0.55; transform: scale(0.96); }
  50%      { opacity: 0.95; transform: scale(1.04); }
}

.egg-shell {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50% 50% 46% 46% / 62% 62% 38% 38%;
  background:
    var(--noise-grain),
    radial-gradient(120% 100% at 32% 18%, var(--gold-hi) 0%, var(--gold-leaf) 38%, var(--parchment-shade) 68%, var(--iron-gall) 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-thick) solid var(--gold-leaf);
  box-shadow: var(--shadow-lg), inset 0 0 30px rgba(0,0,0,0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--dur-fast) var(--ease-standard), filter var(--dur-slow) var(--ease-standard);
}
/* same paint as .egg-shell, deliberately NOT bundled into that selector —
   #eggTapZone already owns width/height/aspect-ratio from the .egg rule
   above, so repeating this list keeps that sizing rule the sole authority
   instead of two same-specificity ID/class rules fighting over "width" */
#eggTapZone {
  border-radius: 50% 50% 46% 46% / 62% 62% 38% 38%;
  background:
    var(--noise-grain),
    radial-gradient(120% 100% at 32% 18%, var(--gold-hi) 0%, var(--gold-leaf) 38%, var(--parchment-shade) 68%, var(--iron-gall) 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-thick) solid var(--gold-leaf);
  box-shadow: var(--shadow-lg), inset 0 0 30px rgba(0,0,0,0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform var(--dur-fast) var(--ease-standard), filter var(--dur-slow) var(--ease-standard);
}
.egg:active .egg-shell, #eggTapZone:active { transform: scale(0.97); }

.egg-rune {
  width: 2.4rem;
  height: 2.4rem;
  color: var(--gold-hi);
  filter: drop-shadow(0 0 6px var(--spirit-glow));
  opacity: 0.85;
  pointer-events: none;
}

/* injected by js/fx.js the first time crackEgg()/resetEgg() runs */
.egg-cracks {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
.egg-cracks .crack {
  fill: none;
  stroke: var(--iron-gall);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
  transition: stroke-dashoffset var(--dur-slow) var(--ease-standard), opacity var(--dur-fast) linear;
}
.egg-cracks .crack.is-visible {
  opacity: 1;
  stroke-dashoffset: 0;
  filter: drop-shadow(0 0 4px var(--spirit-glow));
}

.egg.is-hatching .egg-shell,
#eggTapZone.is-hatching { animation: egg-shake 0.5s var(--ease-standard); }
.egg.is-hatching .egg-glow { animation: egg-pulse 0.8s ease-in-out infinite; }
@keyframes egg-shake {
  0%, 100% { transform: rotate(0deg); }
  20% { transform: rotate(-3deg) translateX(-2px); }
  40% { transform: rotate(3deg) translateX(2px); }
  60% { transform: rotate(-2deg); }
  80% { transform: rotate(2deg); }
}

@media (prefers-reduced-motion: reduce) {
  .egg-glow { animation: none; opacity: 0.75; }
  .egg.is-hatching .egg-shell { animation: none; }
}


/* ---------------------------------------------------------------------- */
/* MODALS                                                                   */
/* ---------------------------------------------------------------------- */

/* #awModalOverlay/#awModalPanel are the REAL ids app.js's generic modal host
   uses (openModal()/closeModal() toggle the `hidden` PROPERTY only — no
   data-open step at all) — so visibility below defaults to fully shown
   whenever not [hidden], and [data-open="true|false"] is kept ONLY as an
   opt-in progressive enhancement for a two-step animated entrance/exit
   (harmless, unused, if a caller never sets that attribute). Without this
   default-visible fallback, a modal shown purely via `hidden = false` would
   sit in the DOM at opacity:0 forever. */
.modal-overlay, #awModalOverlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal-overlay);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: color-mix(in oklab, var(--ink) 70%, transparent);
  backdrop-filter: blur(3px);
  opacity: 1;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.modal-overlay[data-open="false"], #awModalOverlay[data-open="false"] { opacity: 0; }

.modal-panel, #awModalPanel {
  position: relative;
  z-index: var(--z-modal);
  width: min(560px, 100%);
  max-height: min(88vh, 900px);
  overflow-y: auto;
  overflow-x: hidden;
  background:
    var(--noise-grain-coarse),
    linear-gradient(165deg, var(--surface-2) 0%, var(--surface-sunken) 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-gold);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  transform: scale(1) translateY(0);
  transition: transform var(--dur-base) var(--ease-out-back);
}
.modal-overlay[data-open="false"] .modal-panel,
#awModalOverlay[data-open="false"] #awModalPanel { transform: scale(0.94) translateY(10px); }

/* .modal-head/.modal-foot are the class names actually emitted by the
   dynamically-built modal content in app.js/settings.js/chronicle.js;
   .modal-header/.modal-footer stay supported as the documented long-form
   names for any modal markup authored directly in index.html. */
.modal-header, .modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-5) var(--space-3);
  border-bottom: var(--border-hairline) solid color-mix(in oklab, var(--gold-leaf) 25%, transparent);
}
.modal-header h1, .modal-header h2, .modal-header h3,
.modal-head h1, .modal-head h2, .modal-head h3 { margin: 0; font-size: var(--fs-lg); }
.modal-close {
  width: 2rem; height: 2rem;
  display: grid; place-items: center;
  border-radius: var(--radius-pill);
  color: var(--text-muted);
}
.modal-close:hover { color: var(--text-heading); background: var(--surface-2); }

.modal-body { padding: var(--space-5); color: var(--text-body); line-height: var(--lh-normal); }
.modal-body p { margin-bottom: var(--space-3); }
.modal-footer, .modal-foot {
  display: flex;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

@media (prefers-reduced-motion: reduce) {
  .modal-panel, #awModalPanel { transform: none; }
}


/* ---------------------------------------------------------------------- */
/* COACHMARKS — js/coachmarks.js                                           */
/* ---------------------------------------------------------------------- */

/* The "?" trigger js/coachmarks.js injects into each .view (views.css gives
   .view position:relative for this). Reuses .btn-icon's own chrome. */
.coachmark-trigger {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: var(--z-decor);
}

/* #awCoachmarkHost is created lazily by js/coachmarks.js (mirrors fx.js's
   ensureFlashOverlay pattern) — a transparent, full-viewport click-catcher
   so tapping anywhere outside the panel dismisses it, exactly like
   #awModalOverlay's own `e.target === overlay` check. */
#awCoachmarkHost {
  position: fixed;
  inset: 0;
  z-index: var(--z-coachmark);
}
#awCoachmarkHost[hidden] { display: none; }

.aw-coachmark {
  position: fixed;
  width: min(320px, calc(100vw - 32px));
  background:
    var(--noise-grain-coarse),
    linear-gradient(165deg, var(--surface-2) 0%, var(--surface-sunken) 100%);
  background-blend-mode: overlay, normal;
  border: var(--border-gold);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-5);
  color: var(--text-body);
  animation: aw-coachmark-in var(--dur-base) var(--ease-out-back);
}
@keyframes aw-coachmark-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
/* pointer nub aimed roughly at the trigger button above/right of the panel */
.aw-coachmark::before {
  content: "";
  position: absolute;
  top: -7px;
  right: 22px;
  width: 14px; height: 14px;
  background: var(--surface-2);
  border-left: var(--border-gold);
  border-top: var(--border-gold);
  transform: rotate(45deg);
}
.aw-coachmark-close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
}
.aw-coachmark-title {
  font-family: var(--font-cinzel);
  font-size: var(--fs-md);
  color: var(--text-heading);
  margin: 0 var(--space-6) var(--space-2) 0;
}
.aw-coachmark-text {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-body);
  margin: 0;
}

@media (max-width: 767px) {
  .aw-coachmark {
    top: auto !important;
    left: 0 !important;
    right: 0;
    bottom: 0;
    width: 100%;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }
  .aw-coachmark::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .aw-coachmark { animation: none; }
}


/* ---------------------------------------------------------------------- */
/* TOASTS                                                                   */
/* ---------------------------------------------------------------------- */

/* .toast carries its OWN fixed top-center position and its own hidden/shown
   state (opacity + `.is-visible`) because the real toast in app.js is a
   single persistent #awToast element toggled by adding/removing exactly
   that one class — no wrapper required. Nesting several .toast elements
   inside an explicit .toast-stack (for a future multi-toast queue) also
   still works: .toast-stack .toast below neutralizes the fixed
   positioning/hidden-by-default behavior so stacked children are simply
   visible-by-existing, laid out by the stack's own flex column. */
.toast {
  position: fixed;
  top: var(--space-4);
  left: 50%;
  z-index: var(--z-toast);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-pill);
  background: color-mix(in oklab, var(--ink) 88%, transparent);
  border: var(--border-thin) solid var(--gold-leaf);
  color: var(--text-heading);
  font-family: var(--font-cinzel);
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-lg);
  max-width: min(92vw, 420px);
  opacity: 0;
  pointer-events: none;
  transform: translateX(-50%) translateY(-14px) scale(0.96);
  transition: opacity var(--dur-base) var(--ease-out-back), transform var(--dur-base) var(--ease-out-back);
}
.toast.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0) scale(1);
}
.toast .icon, .toast .icon-slot .icon { width: 1.1em; height: 1.1em; flex-shrink: 0; }

.toast-stack {
  position: fixed;
  top: var(--space-4);
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  pointer-events: none;
  width: min(92vw, 420px);
}
.toast-stack .toast {
  position: static;
  max-width: 100%;
  opacity: 1;
  pointer-events: auto;
  transform: none;
  animation: toast-in var(--dur-base) var(--ease-out-back);
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-14px) scale(0.95); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.toast[data-mood="success"]  { border-color: var(--gold-hi); }
.toast[data-mood="weak"]     { border-color: var(--gold-dim); color: var(--text-muted); }
.toast[data-mood="fail"]     { border-color: var(--iron-gall); color: var(--text-muted); }
.toast[data-mood="critical"] { border-color: var(--spirit-rim); box-shadow: var(--shadow-lg), var(--shadow-spirit-glow); }
.toast[data-mood="ambient"]  { border-color: color-mix(in oklab, var(--gold-leaf) 40%, transparent); }

@media (prefers-reduced-motion: reduce) {
  .toast { transform: translateX(-50%); }
  .toast.is-visible { transform: translateX(-50%); }
}


/* ---------------------------------------------------------------------- */
/* METERS / STAT BARS                                                       */
/* ---------------------------------------------------------------------- */

.meter { display: flex; flex-direction: column; gap: var(--space-1); }
.meter-label {
  display: flex; justify-content: space-between;
  font-family: var(--font-cinzel);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.meter-value { color: var(--text-heading); font-weight: 700; }
.meter-track {
  width: 100%;
  height: 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  border: var(--border-hairline) solid color-mix(in oklab, var(--gold-leaf) 25%, transparent);
  overflow: hidden;
}
.meter-fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--gold-dim), var(--gold-leaf) 55%, var(--gold-hi));
  transition: width var(--dur-slow) var(--ease-standard);
}
.meter-fill[data-tone="spirit"] {
  background: linear-gradient(90deg, color-mix(in oklab, var(--spirit) 55%, var(--ink)), var(--spirit) 60%, var(--spirit-rim));
}


/* ---------------------------------------------------------------------- */
/* CHIPS / BADGES                                                           */
/* ---------------------------------------------------------------------- */

.chip, .badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-family: var(--font-cinzel);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  line-height: 1;
  /* Reported live: on a fully-rounded pill (border-radius:999px), the curve's
     radius equals half the badge height (~11px here) — with only 0.75em of
     horizontal padding the first/last letter sat INSIDE that curved zone
     instead of on the flat part of the pill, so the rounded edge visually
     ate the first stroke of the first letter. More horizontal padding alone
     (vertical unchanged, corners stay just as round) moves the text onto the
     flat section so nothing sits under the curve. */
  padding: 0.4em 1.15em;
  border-radius: var(--radius-pill);
  color: var(--text-heading);
  background: color-mix(in oklab, var(--surface-2) 80%, transparent);
  border: var(--border-hairline) solid color-mix(in oklab, var(--gold-leaf) 45%, transparent);
}
.badge--spirit { border-color: var(--spirit-rim); box-shadow: 0 0 12px -2px var(--spirit-glow); }


/* ---------------------------------------------------------------------- */
/* NAV TABS                                                                 */
/* ---------------------------------------------------------------------- */

.nav-tabs {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  background: color-mix(in oklab, var(--ink) 75%, transparent);
  border: var(--border-hairline) solid color-mix(in oklab, var(--gold-leaf) 22%, transparent);
  border-radius: var(--radius-lg);
  overflow-x: auto;
  scrollbar-width: none;
}
.nav-tabs::-webkit-scrollbar { display: none; }

.nav-tab {
  position: relative;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  font-family: var(--font-cinzel);
  font-weight: 600;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  transition: color var(--dur-fast) var(--ease-standard), background var(--dur-fast) var(--ease-standard);
  white-space: nowrap;
}
.nav-tab:hover { color: var(--text-heading); }
.nav-tab .icon { width: 1.1em; height: 1.1em; }

.nav-tab[aria-current="page"] {
  color: var(--text-on-gold);
  background: var(--gold-leaf);
  box-shadow: var(--shadow-sm);
}
.nav-tab[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 20%; right: 20%; bottom: -3px;
  height: 2px;
  background: var(--gold-hi);
  border-radius: var(--radius-pill);
  box-shadow: 0 0 8px var(--gold-hi);
}
