/* ===========================================================================
   leCore 1/1 — stylesheet

   COLOR IS MEASURED, NOT CHOSEN. Every value below comes from quantising
   moose's own render (cobie_last.png) to 8 colours and converting to oklch:

       24.6%  oklch(0.383 0.081 277)   ground
       16.5%  oklch(0.484 0.078 274)   raised surface
       12.9%  oklch(0.550 0.028 006)   warm mid
       10.9%  oklch(0.816 0.055 103)   the emissive highlight
        9.3%  oklch(0.287 0.066 294)   shadow

   So the neutrals arrive already tinted toward h≈277 — no pure greys anywhere —
   and the single accent is the warm highlight, pulled to h≈82 to sit between the
   portrait's cream and the amber of the jellyfish render. That is the studiorig
   'classic' 4:1 key:fill ratio expressed as a palette: cold volumetric ground,
   warm subject. Chroma is clamped hard at both lightness extremes so near-black
   doesn't go neon and near-white doesn't go muddy.

   60-30-10: ground and surfaces carry the page, ink carries the content, and the
   accent appears on exactly two things — the mint action and the converged badge.
   =========================================================================== */

:root {
  /* ground → surface, all tinted h=277 */
  --void:        oklch(0.171 0.032 277);
  --ground:      oklch(0.232 0.048 277);
  --surface:     oklch(0.287 0.055 277);
  --surface-up:  oklch(0.352 0.058 276);
  --line:        oklch(0.418 0.052 275);
  --line-soft:   oklch(0.330 0.042 276);

  /* ink */
  --ink:         oklch(0.936 0.012 280);
  --ink-2:       oklch(0.760 0.022 278);
  --ink-3:       oklch(0.598 0.030 277);

  /* the one accent — the emissive highlight off the render */
  --key:         oklch(0.816 0.108 82);
  --key-dim:     oklch(0.640 0.088 80);
  --key-deep:    oklch(0.430 0.062 78);
  --key-ghost:   oklch(0.816 0.108 82 / 0.13);

  /* semantics, derived in the same space so they belong to the family */
  --warn:        oklch(0.740 0.130 62);
  --bad:         oklch(0.640 0.150 24);
  --good:        oklch(0.760 0.090 152);

  /* Type: the MONO is the primary voice. This surface's content is measured
     numbers — pass, ms, seed, digest — and a UI that sets them in a soft
     humanist sans is lying about what it is. The grotesque carries prose only. */
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "IBM Plex Mono", Menlo, monospace;
  --sans: "Helvetica Neue", Helvetica, "Segoe UI", system-ui, sans-serif;

  /* scale — 1.25 minor third, deliberately shallow: this is an instrument,
     not an article, so nothing needs to shout across three steps */
  --t-xs: 0.6875rem;
  --t-sm: 0.8125rem;
  --t-md: 0.9375rem;
  --t-lg: 1.25rem;
  --t-xl: 1.9rem;

  --r: 3px;               /* barely rounded. A 16px radius on an instrument reads as a toy. */
  --gap: clamp(1rem, 2.2vw, 1.75rem);

  --ease: cubic-bezier(0.2, 0, 0.15, 1);      /* no overshoot anywhere: nothing here is bouncy */
  --ease-out: cubic-bezier(0.15, 0.7, 0.2, 1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--void);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--t-md);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* The page sits on a very slow radial that mimics the rig: key from upper-left,
   cool fill everywhere else. It is the same light as the render, at 2% strength. */
body {
  min-height: 100vh;
  background:
    radial-gradient(120% 90% at 18% -10%, oklch(0.40 0.06 277) 0%, transparent 55%),
    radial-gradient(90% 70% at 95% 105%, oklch(0.30 0.05 300) 0%, transparent 60%),
    var(--void);
  display: flex;
  flex-direction: column;
}

/* ---------- masthead ---------- */
.masthead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--gap);
  padding: clamp(1rem, 2.5vw, 1.6rem) var(--gap);
  border-bottom: 1px solid var(--line-soft);
}
.mark { font-family: var(--mono); font-size: var(--t-lg); letter-spacing: -0.02em; }
.mark-l { color: var(--ink); }
.mark-r { color: var(--key-dim); margin-left: 0.4ch; }

.masthead-meta { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }

.chip {
  font-family: var(--mono); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.08em;
  padding: 0.25rem 0.55rem;
  border: 1px solid var(--line); border-radius: var(--r);
  color: var(--ink-3);
}
.chip[data-net="mainnet-beta"] { color: var(--warn); border-color: var(--warn); }

/* ---------- the bench: stage + rail ---------- */
.bench {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(310px, 0.85fr);
  gap: var(--gap);
  padding: var(--gap);
  align-items: start;
  max-width: 1240px; width: 100%; margin: 0 auto;
}

/* ---------- stage ---------- */
.stage { display: flex; flex-direction: column; gap: 0.85rem; min-width: 0; }

.plate {
  position: relative;
  aspect-ratio: 4 / 5;
  background:
    linear-gradient(168deg, var(--surface) 0%, var(--ground) 58%, var(--void) 100%);
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  overflow: hidden;
  display: grid; place-items: center;
  transition: border-color 360ms var(--ease), box-shadow 360ms var(--ease);
}
/* converged: the plate itself picks up the key. The accent's ONLY other home. */
.plate[data-state="done"] {
  border-color: var(--key-deep);
  box-shadow: 0 0 0 1px var(--key-deep), 0 30px 70px -40px oklch(0.816 0.108 82 / 0.5);
}
.plate[data-state="error"] { border-color: var(--bad); }

.frame, .source {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}
/* The source photo sits UNDER the render, dimmed — so you can see what the engine
   was given versus what it made of it. It fades out as the render arrives. */
.source { filter: grayscale(1) brightness(0.5) blur(1px); opacity: 0.30; transition: opacity 700ms var(--ease); }
.plate[data-state="rendering"] .source { opacity: 0.14; }
.plate[data-state="done"] .source { opacity: 0; }

.frame { opacity: 0; transition: opacity 260ms var(--ease-out); }
.frame[data-shown="1"] { opacity: 1; }

.plate-empty { text-align: left; padding: 2rem; max-width: 34ch; z-index: 1; }
.plate-empty-line { margin: 0 0 0.4rem; font-size: var(--t-lg); color: var(--ink-2); }
.plate-empty-sub  { margin: 0; font-size: var(--t-sm); color: var(--ink-3); }
.plate[data-state="rendering"] .plate-empty,
.plate[data-state="done"] .plate-empty,
.plate[data-state="error"] .plate-empty { display: none; }

.plate-error {
  z-index: 2; max-width: 40ch; padding: 1rem 1.1rem;
  font-family: var(--mono); font-size: var(--t-sm); line-height: 1.45;
  color: var(--bad);
  border: 1px solid var(--bad); border-radius: var(--r);
  background: var(--void);
}

/* The scanline is the ONE piece of decoration, and it is honest: it sweeps once
   per completed pass, so its cadence is the render's real cadence. When passes
   slow down (as they do — pass k costs k), the sweep visibly slows with them. */
.scanline {
  position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 3;
  background: linear-gradient(90deg, transparent, var(--key), transparent);
  opacity: 0.55;
  animation: sweep 1400ms var(--ease) infinite;
}
@keyframes sweep { from { transform: translateY(0); } to { transform: translateY(calc(100cqh)); } }
.plate { container-type: size; }

/* ---------- pass bar: one tick per pass, filled as they land ---------- */
.passbar { display: flex; gap: 2px; height: 4px; }
.tick {
  flex: 1; background: var(--line-soft); border-radius: 1px;
  transition: background-color 240ms var(--ease), transform 240ms var(--ease);
  transform-origin: bottom;
}
.tick[data-done="1"] { background: var(--key-deep); }
.tick[data-current="1"] { background: var(--key); transform: scaleY(2); }

.stage-note {
  margin: 0; min-height: 1.4em;
  font-family: var(--mono); font-size: var(--t-xs);
  color: var(--ink-3); letter-spacing: 0.01em;
}

/* ---------- rail ---------- */
.rail { display: flex; flex-direction: column; gap: 0.9rem; min-width: 0; }

.seek { display: flex; flex-direction: column; gap: 0.45rem; }
.seek-label, .tier-ratio, .readout dt {
  font-family: var(--mono); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.11em; color: var(--ink-3);
}
.seek-row {
  display: flex; align-items: stretch;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--ground);
  transition: border-color 180ms var(--ease), background-color 180ms var(--ease);
}
.seek-row:focus-within { border-color: var(--key-dim); background: var(--surface); }
.seek-at {
  display: grid; place-items: center; padding: 0 0.1rem 0 0.7rem;
  font-family: var(--mono); color: var(--ink-3); font-size: var(--t-md);
}
.seek-input {
  flex: 1; min-width: 0;
  background: none; border: 0; outline: 0;
  padding: 0.7rem 0.5rem;
  font-family: var(--mono); font-size: var(--t-md); color: var(--ink);
}
.seek-input::placeholder { color: var(--ink-3); }
.seek-hint { margin: 0; font-size: var(--t-xs); color: var(--ink-3); min-height: 1.3em; }
.seek-hint[data-tone="bad"] { color: var(--bad); }

/* ---------- buttons: all 8 states, and they differ ---------- */
.go, .ghost, .mint {
  font-family: var(--mono); font-size: var(--t-sm);
  border-radius: var(--r); cursor: pointer;
  transition: background-color 160ms var(--ease), color 160ms var(--ease),
              border-color 160ms var(--ease), transform 90ms var(--ease), opacity 160ms var(--ease);
}
.go {
  border: 0; border-left: 1px solid var(--line);
  background: var(--surface); color: var(--ink);
  padding: 0 1rem; letter-spacing: 0.03em;
}
.go:hover:not(:disabled)  { background: var(--surface-up); }
.go:active:not(:disabled) { transform: translateY(1px); background: var(--line-soft); }
.go:disabled              { color: var(--ink-3); cursor: not-allowed; opacity: 0.6; }
.go[data-busy="1"]        { color: var(--key-dim); }

.ghost {
  background: transparent; color: var(--ink-2);
  border: 1px solid var(--line); padding: 0.35rem 0.75rem;
}
.ghost:hover  { color: var(--ink); border-color: var(--ink-3); }
.ghost:active { transform: translateY(1px); }
.ghost[data-on="1"] { color: var(--key); border-color: var(--key-deep); }

/* focus-visible is a real, separate state — not the hover style reused */
.go:focus-visible, .ghost:focus-visible, .mint:focus-visible, .seek-input:focus-visible {
  outline: 2px solid var(--key);
  outline-offset: 2px;
}

/* ---------- tier card ---------- */
.tier {
  border: 1px solid var(--line-soft); border-radius: var(--r);
  background: var(--ground); padding: 0.8rem 0.9rem;
  transition: border-color 300ms var(--ease), background-color 300ms var(--ease);
}
.tier[data-tier="verified"] { border-color: var(--key-deep); background: var(--key-ghost); }
.tier-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; }
.tier-name { font-family: var(--mono); font-size: var(--t-sm); color: var(--ink); letter-spacing: 0.02em; }
.tier[data-tier="verified"] .tier-name { color: var(--key); }
.tier-blurb { margin: 0.4rem 0 0; font-size: var(--t-sm); color: var(--ink-2); }
.tier-upsell { margin: 0.55rem 0 0; font-size: var(--t-xs); color: var(--ink-3); line-height: 1.45; }
.tier-upsell em { color: var(--ink-2); font-style: normal; text-decoration: underline; text-underline-offset: 2px; }
.tier[data-tier="verified"] .tier-upsell { display: none; }

/* ---------- readout ---------- */
.readout {
  margin: 0; border: 1px solid var(--line-soft); border-radius: var(--r);
  background: var(--ground); overflow: hidden;
}
.readout-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  padding: 0.4rem 0.9rem;
  border-bottom: 1px solid var(--line-soft);
}
.readout-row:last-child { border-bottom: 0; }
.readout dt { flex: none; }
.readout dd {
  margin: 0; font-family: var(--mono); font-size: var(--t-sm);
  color: var(--ink); text-align: right;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;   /* the digest overflows; it must truncate, not wrap */
  min-width: 0;
}
.readout dd[data-fresh="1"] { color: var(--key); }

/* ---------- the one terminal action ---------- */
.mint {
  display: flex; flex-direction: column; align-items: flex-start; gap: 0.15rem;
  width: 100%; text-align: left;
  padding: 0.8rem 1rem;
  border: 1px solid var(--key-deep);
  background: var(--key-ghost); color: var(--key);
}
.mint:hover:not(:disabled)  { background: var(--key); color: var(--void); border-color: var(--key); }
.mint:active:not(:disabled) { transform: translateY(1px); }
.mint:disabled {
  border-color: var(--line-soft); background: transparent;
  color: var(--ink-3); cursor: not-allowed;
}
.mint[data-busy="1"] { color: var(--ink-2); border-color: var(--line); background: var(--surface); cursor: progress; }
.mint-label { font-size: var(--t-md); letter-spacing: 0.01em; }
.mint-sub { font-size: var(--t-xs); opacity: 0.8; }

.fineprint { margin: 0; font-size: var(--t-xs); color: var(--ink-3); line-height: 1.5; }

/* ---------- colophon ---------- */
.colophon {
  border-top: 1px solid var(--line-soft);
  padding: 1.2rem var(--gap);
  max-width: 1240px; width: 100%; margin: 0 auto;
}
.colophon p { margin: 0; max-width: 72ch; font-size: var(--t-xs); color: var(--ink-3); line-height: 1.6; }
.colophon strong { color: var(--ink-2); font-weight: 400; }

/* ---------- responsive: RE-COMPOSE, not reflow ----------
   Under 900px the rail stops being a sidebar and becomes a control strip: the
   seek row goes full width at the top (it is the first thing you do), the stage
   follows, and the readout collapses to a horizontal scroll of instruments so it
   never pushes the mint button below the fold. */
@media (max-width: 900px) {
  .bench { grid-template-columns: 1fr; }
  .rail { order: -1; }
  .plate { aspect-ratio: 1 / 1; }
  .readout { display: flex; overflow-x: auto; scrollbar-width: none; }
  .readout-row {
    flex-direction: column; align-items: flex-start; gap: 0.1rem;
    border-bottom: 0; border-right: 1px solid var(--line-soft);
    padding: 0.5rem 0.85rem; white-space: nowrap;
  }
  .readout dd { text-align: left; max-width: 18ch; }
}
@media (max-width: 460px) {
  .masthead { flex-direction: column; align-items: flex-start; gap: 0.7rem; }
  .plate-empty { padding: 1.2rem; }
}

/* ---------- reduced motion: the sweep is decoration and goes; the frame
     cross-fade is INFORMATION (a new pass landed) and only shortens. ---------- */
@media (prefers-reduced-motion: reduce) {
  .scanline { animation: none; opacity: 0.3; }
  * { transition-duration: 60ms !important; }
}

/* ===========================================================================
   THE REVEAL  —  Explore → ANTICIPATE → REVEAL → Decide

   The audit found the product's most important moment firing three CSS rules.
   Everything below exists to fix that, under one hard constraint: the claim of
   this page is "watch it converge", so the CONVERGENCE may never be faked or
   hidden. Concealment therefore goes on the RESOLUTION, not the process — you
   watch it develop behind a shroud that thins on real progress, and the final
   level-6 encode snaps it away. Anticipation is built from measured pass times,
   not from a timer that lies.
   =========================================================================== */

:root {
  /* Bloom hues. HIGHER chroma than the base accent, and they exist ONLY inside
     the reveal — the accent is rationed all the way through so that letting it
     off the leash for 1.2s actually registers. */
  --bloom-core: oklch(0.94 0.150 88);
  --bloom-mid:  oklch(0.84 0.185 78);
  --bloom-edge: oklch(0.66 0.150 62);
  --gold:       oklch(0.86 0.170 92);

  --ease-snap: cubic-bezier(0.16, 1, 0.3, 1);   /* decelerate hard; no overshoot */
}

/* ---------- grain: the plate carries the render's own dither ---------- */
.grain {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  opacity: 0.30; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}

/* ---------- the shroud: wrapping paper that thins on REAL progress ----------
   --progress is set from JS as passes land. Nothing here moves on a timer. */
.plate { --progress: 0; }
.shroud {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  backdrop-filter: blur(calc((1 - var(--progress)) * 22px)) saturate(calc(0.3 + var(--progress) * 0.7));
  background:
    radial-gradient(80% 60% at 50% 45%, transparent 0%, oklch(0.171 0.032 277 / calc((1 - var(--progress)) * 0.55)) 100%),
    linear-gradient(180deg, oklch(0.171 0.032 277 / calc((1 - var(--progress)) * 0.4)) 0%, transparent 40%);
  transition: backdrop-filter 900ms var(--ease), background 900ms var(--ease), opacity 620ms var(--ease-snap);
}
/* Earned blur, and this is the earning: it is a function of measured convergence,
   it decreases monotonically, and it reaches exactly 0 when the render is done. */
.plate[data-phase="reveal"] .shroud,
.plate[data-phase="owned"] .shroud { opacity: 0; }

/* ---------- final passes: the plate breathes ----------
   Passes cost O(k), so the last ones are genuinely the longest wait. That is a
   real drumroll and it gets treated as one. */
@keyframes breathe {
  0%, 100% { transform: scale(1);      filter: brightness(1); }
  50%      { transform: scale(1.008);  filter: brightness(1.06); }
}
.plate[data-phase="final"] { animation: breathe 2.1s var(--ease) infinite; }
.plate[data-phase="final"] { border-color: var(--key-deep); }

/* ---------- the snap ---------- */
@keyframes snap {
  0%   { transform: scale(1.035); filter: brightness(1.55) contrast(0.86) saturate(0.7); }
  55%  { transform: scale(0.997); filter: brightness(1.06) contrast(1.02) saturate(1.06); }
  100% { transform: scale(1);     filter: none; }
}
.plate[data-phase="reveal"] .frame { animation: snap 780ms var(--ease-snap) both; }

/* ---------- the bloom: the ONE moment the accent is let off the leash ---------- */
.bloom {
  position: absolute; inset: -18%; z-index: 5; pointer-events: none; opacity: 0;
  background:
    radial-gradient(closest-side, var(--bloom-core) 0%, var(--bloom-mid) 38%, var(--bloom-edge) 62%, transparent 76%);
  mix-blend-mode: screen;
}
@keyframes bloom-out {
  0%   { opacity: 0;    transform: scale(0.32); }
  18%  { opacity: 0.92; transform: scale(0.62); }
  100% { opacity: 0;    transform: scale(1.25); }
}
.plate[data-phase="reveal"] .bloom { animation: bloom-out 1150ms var(--ease-snap) both; }

/* ---------- edge ignition: the frame border catches, then settles ---------- */
@keyframes ignite {
  0%   { box-shadow: 0 0 0 1px var(--key-deep), 0 0 0 0 transparent; }
  22%  { box-shadow: 0 0 0 1px var(--bloom-core), 0 0 60px 8px oklch(0.84 0.185 78 / 0.55); }
  100% { box-shadow: 0 0 0 1px var(--key-deep), 0 30px 70px -40px oklch(0.816 0.108 82 / 0.5); }
}
.plate[data-phase="reveal"],
.plate[data-phase="owned"] { animation: ignite 1400ms var(--ease-snap) both; border-color: var(--key-deep); }

/* ---------- motes: light particulate drifting up ----------
   Not confetti. This is the suspended particulate in moose's own jellyfish
   render — the same visual idea as the thing being minted. */
.motes { position: absolute; inset: 0; z-index: 6; pointer-events: none; overflow: hidden; }
.mote {
  position: absolute; bottom: -6px;
  width: var(--s); height: var(--s); border-radius: 50%;
  background: var(--bloom-core);
  opacity: 0;
  filter: blur(0.4px);
  animation: rise var(--d) var(--ease) var(--delay) both;
}
@keyframes rise {
  0%   { opacity: 0;    transform: translate3d(0, 0, 0) scale(0.6); }
  12%  { opacity: 0.95; }
  70%  { opacity: 0.55; }
  100% { opacity: 0;    transform: translate3d(var(--dx), calc(var(--rise) * -1), 0) scale(1.1); }
}

/* ---------- the prize card: what you actually won ----------
   Replaces the sha256 as the payoff. The hash stays in the rail for anyone who
   wants to verify; it is a receipt, and a receipt is not a prize. */
.prize {
  position: absolute; z-index: 7; left: 50%; bottom: 6%;
  transform: translateX(-50%);
  display: flex; align-items: baseline; gap: 0.7rem;
  padding: 0.55rem 0.95rem;
  font-family: var(--mono);
  border: 1px solid var(--key-deep); border-radius: var(--r);
  background: oklch(0.171 0.032 277 / 0.82);
  backdrop-filter: blur(8px);
  white-space: nowrap; max-width: 92%; overflow: hidden;
}
@keyframes prize-in {
  0%   { opacity: 0; transform: translateX(-50%) translateY(14px) scale(0.94); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}
.plate[data-phase="reveal"] .prize,
.plate[data-phase="owned"] .prize { animation: prize-in 620ms var(--ease-snap) 620ms both; }
.prize-tier {
  font-size: var(--t-xs); text-transform: uppercase; letter-spacing: 0.13em;
  color: var(--key);
}
.prize-handle { font-size: var(--t-md); color: var(--ink); }
.prize-seed   { font-size: var(--t-xs); color: var(--ink-3); }

/* ---------- RARE PULL: verified reads as a different event, not a badge ---------- */
.prize[data-tier="verified"] { border-color: var(--gold); }
.prize[data-tier="verified"] .prize-tier { color: var(--gold); }
.plate[data-tier="verified"] { --bloom-core: oklch(0.96 0.180 95); --bloom-mid: oklch(0.86 0.210 88); }

/* a gold sweep that only a verified pull ever gets */
@keyframes sweep-gold {
  0%   { opacity: 0; transform: translateX(-120%) skewX(-14deg); }
  30%  { opacity: 0.85; }
  100% { opacity: 0; transform: translateX(120%) skewX(-14deg); }
}
.plate[data-tier="verified"][data-phase="reveal"]::after {
  content: ''; position: absolute; inset: 0; z-index: 6; pointer-events: none;
  width: 45%;
  background: linear-gradient(90deg, transparent, oklch(0.96 0.18 95 / 0.55), transparent);
  mix-blend-mode: screen;
  animation: sweep-gold 1100ms var(--ease) 420ms both;
}

/* ---------- pass bar upgraded: a charge meter with a live head ---------- */
.tick[data-current="1"] {
  background: var(--key);
  box-shadow: 0 0 10px 1px oklch(0.816 0.108 82 / 0.7);
}
@keyframes tick-land {
  0%   { transform: scaleY(3.4); background: var(--bloom-core); }
  100% { transform: scaleY(1);   background: var(--key-deep); }
}
.tick[data-landed="1"] { animation: tick-land 420ms var(--ease-snap); }
.passbar[data-final="1"] .tick[data-done="0"] { background: var(--key-deep); opacity: 0.35; }

/* ---------- eta: anticipation from measured data ---------- */
.stage-line { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.eta {
  margin: 0; flex: none;
  font-family: var(--mono); font-size: var(--t-xs);
  color: var(--key-dim); letter-spacing: 0.04em;
}
.eta[data-soon="1"] { color: var(--key); }

/* ---------- mint button: it has a prize to sell now ---------- */
@keyframes mint-ready {
  0%   { box-shadow: 0 0 0 0 oklch(0.816 0.108 82 / 0.5); }
  100% { box-shadow: 0 0 0 14px oklch(0.816 0.108 82 / 0); }
}
.mint:not(:disabled)[data-ready="1"] { animation: mint-ready 1800ms var(--ease) 3; }

/* ---------- reduced motion: the reveal still HAPPENS, it just stops moving ----------
   Cutting it to nothing would delete the product's payoff for the people who
   asked for less motion. The bloom and prize still appear; they just do not
   travel, pulse or drift. */
@media (prefers-reduced-motion: reduce) {
  .plate[data-phase="final"] { animation: none; }
  .plate[data-phase="reveal"] .frame,
  .plate[data-phase="reveal"] .bloom,
  .plate[data-phase="reveal"] .prize,
  .plate[data-phase="owned"] .prize,
  .plate[data-phase="reveal"],
  .plate[data-phase="owned"] { animation-duration: 1ms; }
  .plate[data-phase="reveal"] .prize,
  .plate[data-phase="owned"] .prize { opacity: 1; }
  .motes, .plate[data-tier="verified"][data-phase="reveal"]::after { display: none; }
  .mint:not(:disabled)[data-ready="1"] { animation: none; }
}


/* ---------- live price chip ----------
   The price moves while you are looking at it, so the number needs to register a CHANGE,
   not just a value. It flashes to the accent and counts up; the "+1% each mint" sits under
   it permanently so the movement is explained before it happens rather than after. */
.price-chip { display: inline-flex; align-items: baseline; gap: 0.5ch; }
.price-now { color: var(--ink); font-variant-numeric: tabular-nums; }
.price-meta { color: var(--ink-3); font-size: 0.92em; }

@keyframes price-tick {
  0%   { color: var(--bloom-core); transform: translateY(-2px); }
  100% { color: var(--ink);        transform: translateY(0); }
}
.price-now[data-moved="1"] { animation: price-tick 900ms var(--ease-snap); }
.price-chip[data-moved="1"] { border-color: var(--key); }

/* ===========================================================================
   DOPAMINE PASS — reward schedule, not decoration.

   The audit finding was not "too few animations", it was ONE reward at the end
   of a 70-second wait. Below, every pass that lands is its own reward event,
   escalating toward the reveal: the plate pumps, the tick pops, numbers ROLL
   rather than swap, and the edge flashes. Nothing here fires on a timer — each
   one is triggered by a real frame arriving, so the rhythm is the render's.

   FAST: every micro-response is 90–220ms. The slow, considered easing from the
   first pass is kept ONLY for the shroud and the reveal, where the whole point
   is that they take their time.
   =========================================================================== */

:root {
  --ease-pop: cubic-bezier(0.22, 1.4, 0.36, 1);   /* the ONE overshoot in the file, and
                                                     it is on reward feedback only —
                                                     things that "land". Never on nav. */
  --fast: 110ms;
}

/* ---------- numbers ROLL, they don't swap ---------- */
.roll { display: inline-block; font-variant-numeric: tabular-nums; }
@keyframes roll-up {
  0%   { transform: translateY(0.55em); opacity: 0; }
  100% { transform: translateY(0);      opacity: 1; }
}
.roll[data-rolling="1"] { animation: roll-up 220ms var(--ease-pop); }

/* ---------- every pass landing pumps the plate ---------- */
@keyframes pass-pump {
  0%   { transform: scale(1);     filter: brightness(1); }
  35%  { transform: scale(1.012); filter: brightness(1.14) saturate(1.1); }
  100% { transform: scale(1);     filter: brightness(1); }
}
.plate[data-pump="1"] { animation: pass-pump 260ms var(--ease-pop); }

/* ---------- edge flash: the whole frame registers the hit ---------- */
@keyframes edge-hit {
  0%   { box-shadow: inset 0 0 0 0 var(--key), 0 0 0 0 transparent; }
  30%  { box-shadow: inset 0 0 34px -6px var(--key), 0 0 26px -8px oklch(0.816 0.108 82 / 0.6); }
  100% { box-shadow: inset 0 0 0 0 transparent, 0 0 0 0 transparent; }
}
.plate[data-pump="1"]::before {
  content: ''; position: absolute; inset: 0; z-index: 8; pointer-events: none;
  border-radius: var(--r);
  animation: edge-hit 420ms var(--ease-out);
}

/* ---------- the tick that just landed POPS, and the bar builds pressure ---------- */
.tick { transition: background-color var(--fast) var(--ease-pop), transform var(--fast) var(--ease-pop); }
@keyframes tick-pop {
  0%   { transform: scaleY(5);   background: var(--bloom-core); }
  60%  { transform: scaleY(1.6); background: var(--key); }
  100% { transform: scaleY(1);   background: var(--key-deep); }
}
.tick[data-landed="1"] { animation: tick-pop 380ms var(--ease-pop); }
/* the bar itself brightens as it fills — pressure, visible without reading a number */
.passbar { --fill: 0; }
.passbar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -3px; height: 1px;
  background: linear-gradient(90deg, var(--key) calc(var(--fill) * 100%), transparent 0);
  opacity: calc(0.25 + var(--fill) * 0.75);
  transition: opacity 200ms var(--ease);
}
.stage-foot { position: relative; }
.passbar { position: relative; }

/* ---------- price chip: slot-machine energy ---------- */
@keyframes price-slam {
  0%   { transform: scale(1.18) translateY(-3px); color: var(--bloom-core); }
  55%  { transform: scale(0.985); }
  100% { transform: scale(1) translateY(0); color: var(--ink); }
}
.price-now[data-moved="1"] { animation: price-slam 520ms var(--ease-pop); }
.price-chip { transition: border-color var(--fast) var(--ease), box-shadow 240ms var(--ease); }
.price-chip[data-moved="1"] {
  border-color: var(--key);
  box-shadow: 0 0 22px -6px oklch(0.816 0.108 82 / 0.75);
}

/* ---------- burn ticker: a number that only ever goes up ---------- */
.burn { display: inline-flex; align-items: baseline; gap: 0.45ch; }
.burn-n { color: var(--key-dim); font-variant-numeric: tabular-nums; }
.burn-l { color: var(--ink-3); font-size: 0.9em; }

/* ---------- everything you touch answers in ~110ms ---------- */
.go, .ghost, .mint, .seek-row { transition-duration: var(--fast) !important; }
.go:active:not(:disabled), .ghost:active, .mint:active:not(:disabled) { transform: scale(0.97); }
.mint:hover:not(:disabled) { transform: translateY(-1px); }

/* ---------- the mint button, once armed, will not be ignored ---------- */
@keyframes mint-breathe {
  0%, 100% { box-shadow: 0 0 0 0 oklch(0.816 0.108 82 / 0.0); }
  50%      { box-shadow: 0 0 26px -4px oklch(0.816 0.108 82 / 0.55); }
}
.mint:not(:disabled)[data-ready="1"] { animation: mint-breathe 2.2s var(--ease) infinite; }

@media (prefers-reduced-motion: reduce) {
  .plate[data-pump="1"], .plate[data-pump="1"]::before,
  .tick[data-landed="1"], .roll[data-rolling="1"],
  .price-now[data-moved="1"], .mint:not(:disabled)[data-ready="1"] { animation: none; }
  .go:active:not(:disabled), .ghost:active, .mint:active:not(:disabled),
  .mint:hover:not(:disabled) { transform: none; }
}

/* ===========================================================================
   THE UNBOXING — sealed → thump → leak → CRACK → doors open → confetti

   The previous "reveal" was a shroud fading out, which is developing, not
   unboxing. An unboxing needs a thing that is SHUT, pressure that builds
   against it, a discrete moment where it gives, and a payoff.

   Honesty is preserved where it matters: the pass bar and the readout still
   report every real pass while the box is shut, so the convergence is not
   faked — you simply do not get to see the picture early. That is what a box IS.
   =========================================================================== */

.box {
  position: absolute; inset: 0; z-index: 9; pointer-events: none;
  --leak: 0;                      /* 0..1, set from real pass progress */
}

/* two doors meeting at the centre */
.box-door {
  position: absolute; top: 0; bottom: 0; width: 50.5%;
  background:
    linear-gradient(160deg, oklch(0.30 0.055 277) 0%, oklch(0.205 0.045 277) 55%, oklch(0.16 0.035 277) 100%);
  box-shadow: inset 0 0 60px -20px oklch(0 0 0 / 0.9);
  transition: transform 900ms var(--ease-snap), opacity 700ms var(--ease-snap);
  transform-origin: center left;
  backface-visibility: hidden;
}
.box-door-l { left: 0;  transform-origin: center left;  }
.box-door-r { right: 0; transform-origin: center right; }

/* the corrugation — a box, not a slab */
.box-door::after {
  content: ''; position: absolute; inset: 0; opacity: 0.5;
  background: repeating-linear-gradient(90deg,
    transparent 0 12px, oklch(1 0 0 / 0.028) 12px 13px);
}

/* THE SEAM. Light leaks from it, and the leak is real progress — the box is
   visibly closer to bursting because the render is closer to done. */
.box-seam {
  position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%);
  width: calc(2px + var(--leak) * 7px);
  background: linear-gradient(180deg,
    transparent 0%, var(--bloom-mid) 12%, var(--bloom-core) 50%, var(--bloom-mid) 88%, transparent 100%);
  opacity: calc(0.25 + var(--leak) * 0.75);
  filter: blur(calc(1px + var(--leak) * 5px));
  box-shadow: 0 0 calc(10px + var(--leak) * 60px) calc(var(--leak) * 12px) oklch(0.84 0.185 78 / calc(var(--leak) * 0.7));
  transition: width 420ms var(--ease-pop), opacity 420ms var(--ease), box-shadow 420ms var(--ease);
}

/* the seal across the seam, stamped 1/1 */
.box-seal {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  display: grid; place-items: center;
  width: 74px; height: 74px; border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, oklch(0.62 0.14 68), oklch(0.40 0.11 55));
  box-shadow: 0 6px 18px -6px oklch(0 0 0 / 0.8), inset 0 -3px 8px -3px oklch(0 0 0 / 0.6);
  border: 1px solid oklch(0.70 0.13 72);
  transition: transform 380ms var(--ease-pop), opacity 320ms var(--ease);
}
.box-seal-mark {
  font-family: var(--mono); font-size: var(--t-sm); letter-spacing: 0.06em;
  color: oklch(0.94 0.05 80);
}

/* ---------- THUMP: every landed pass knocks the box ---------- */
@keyframes box-thump {
  0%   { transform: translate3d(0,0,0) rotate(0deg); }
  18%  { transform: translate3d(-3px, 2px, 0) rotate(-0.35deg) scale(1.006); }
  42%  { transform: translate3d(3px, -2px, 0) rotate(0.3deg); }
  68%  { transform: translate3d(-2px, 1px, 0) rotate(-0.15deg); }
  100% { transform: translate3d(0,0,0) rotate(0deg); }
}
.box[data-thump="1"] { animation: box-thump 320ms var(--ease-pop); }
@keyframes seal-strain { 0%,100% { transform: translate(-50%,-50%) scale(1); }
                          50%     { transform: translate(-50%,-50%) scale(1.09); } }
.box[data-thump="1"] .box-seal { animation: seal-strain 320ms var(--ease-pop); }

/* ---------- CRACK: the seal gives, right before the doors go ---------- */
@keyframes seal-snap {
  0%   { transform: translate(-50%,-50%) scale(1) rotate(0deg); opacity: 1; }
  35%  { transform: translate(-50%,-50%) scale(1.35) rotate(6deg); opacity: 1; }
  100% { transform: translate(-50%,-50%) scale(0.2) rotate(-28deg); opacity: 0; }
}
.plate[data-phase="crack"] .box-seal { animation: seal-snap 420ms var(--ease-pop) both; }
.plate[data-phase="crack"] .box-seam { width: 26px; opacity: 1; filter: blur(10px); }
@keyframes box-rattle {
  0%,100% { transform: translate3d(0,0,0); }
  20% { transform: translate3d(-6px,3px,0) rotate(-0.7deg); }
  45% { transform: translate3d(6px,-3px,0) rotate(0.7deg); }
  70% { transform: translate3d(-4px,2px,0) rotate(-0.4deg); }
}
.plate[data-phase="crack"] .box { animation: box-rattle 420ms linear; }

/* ---------- OPEN: the doors are thrown outward ---------- */
.plate[data-phase="reveal"] .box-door-l,
.plate[data-phase="owned"] .box-door-l {
  transform: perspective(1100px) rotateY(-118deg) translateX(-6%);
  opacity: 0;
}
.plate[data-phase="reveal"] .box-door-r,
.plate[data-phase="owned"] .box-door-r {
  transform: perspective(1100px) rotateY(118deg) translateX(6%);
  opacity: 0;
}
.plate[data-phase="reveal"] .box-seam,
.plate[data-phase="owned"] .box-seam { opacity: 0; transition-duration: 500ms; }

/* ---------- FLASHBANG on the crack ---------- */
.flashbang {
  position: absolute; inset: 0; z-index: 11; pointer-events: none;
  background: var(--bloom-core); opacity: 0;
}
@keyframes flashbang {
  0%   { opacity: 0; }
  8%   { opacity: 0.92; }
  100% { opacity: 0; }
}
.plate[data-phase="crack"] .flashbang { animation: flashbang 560ms var(--ease-out) both; }

/* ---------- CONFETTI ---------- */
.confetti {
  position: absolute; inset: 0; z-index: 12; pointer-events: none;
  width: 100%; height: 100%;
}

@media (prefers-reduced-motion: reduce) {
  .box[data-thump="1"], .box[data-thump="1"] .box-seal,
  .plate[data-phase="crack"] .box, .plate[data-phase="crack"] .box-seal,
  .plate[data-phase="crack"] .flashbang { animation: none; }
  .confetti { display: none; }
}

/* ===========================================================================
   MOBILE — measured at 375x812, not guessed.

   The desktop pass collapsed the grid correctly but got the ORDER wrong for a
   phone: rail-first put the seek form, the tier card, the readout AND the mint
   button all above the plate, so you typed a handle, scrolled down to watch the
   box, then scrolled back up to mint. On a phone the sequence is vertical and
   the composition has to follow it:

        type  ->  WATCH  ->  read  ->  commit (in the thumb zone)

   So: seek pinned to the top of the rail, plate immediately after it, readout
   below, and the mint action lifted out of the flow into a sticky bar at the
   bottom where a thumb actually reaches.
   =========================================================================== */

@media (max-width: 760px) {
  /* --- iOS SAFARI ZOOM. Any input under 16px makes Safari zoom the whole page
     on focus, and the user has to pinch back out. Measured at 14.06px, which is
     exactly the trap. 16px is not a taste choice here, it is the threshold. --- */
  .seek-input { font-size: 16px; }

  /* --- 44px minimum tap targets (measured: 27 / 27 / 38 / 38) --- */
  .ghost { min-height: 44px; padding: 0.6rem 0.85rem; }
  .seek-row { min-height: 52px; }
  .seek-input { padding: 0.85rem 0.5rem; }
  .go { min-height: 52px; padding: 0 1.15rem; }

  /* --- RE-COMPOSE: seek, then the plate, then everything else --- */
  .bench { display: flex; flex-direction: column; gap: 0.9rem; padding-bottom: 96px; }
  .rail { display: contents; }          /* let its children order independently */
  .seek   { order: 1; }
  .stage  { order: 2; }
  .tier   { order: 3; }
  .readout{ order: 4; }
  .fineprint { order: 5; }

  /* --- MINT: out of the flow, into the thumb zone. env(safe-area-inset-bottom)
     keeps it clear of the iPhone home indicator. --- */
  .mint {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    width: auto; margin: 0;
    border-radius: 0; border-left: 0; border-right: 0; border-bottom: 0;
    padding: 0.85rem 1rem calc(0.85rem + env(safe-area-inset-bottom, 0px));
    background: oklch(0.232 0.048 277 / 0.96);
    backdrop-filter: blur(12px);
    min-height: 56px;
  }
  .mint:not(:disabled) { background: var(--key-ghost); }
  .mint:hover:not(:disabled) { transform: none; }   /* no hover on touch */

  /* --- masthead: one scrollable strip, no wrapping --- */
  .masthead { flex-direction: column; align-items: stretch; gap: 0.65rem; }
  .masthead-meta {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  .masthead-meta::-webkit-scrollbar { display: none; }
  .chip, .ghost { flex: none; white-space: nowrap; }

  /* --- the plate gets the screen. 4:5 is the artwork's real aspect; the desktop
     pass squashed it to 1:1 on narrow, which crops the render. --- */
  .plate { aspect-ratio: 4 / 5; }

  /* Taller motes/confetti travel on a portrait screen so the burst clears the frame */
  .mote { --rise: 60%; }
}

/* Very narrow (iPhone SE and down) */
@media (max-width: 400px) {
  .prize { gap: 0.45rem; padding: 0.45rem 0.7rem; }
  .prize-seed { display: none; }        /* seed lives in the readout; the card keeps the win */
  .box-seal { width: 58px; height: 58px; }
}

/* --- TOUCH DEVICES, any width: hover states are a lie on a finger. Keeping them
   leaves buttons stuck in their hover look after a tap. --- */
@media (hover: none) {
  .go:hover:not(:disabled),
  .ghost:hover,
  .mint:hover:not(:disabled) { background: inherit; color: inherit; transform: none; }
  .go:active:not(:disabled), .ghost:active, .mint:active:not(:disabled) { transform: scale(0.97); }
}

/* --- dynamic viewport: 100vh on mobile is the WRONG height while the browser
   chrome is showing, which pushes the sticky bar off-screen until you scroll. --- */
@supports (height: 100dvh) {
  body { min-height: 100dvh; }
}

/* --- FLEXBOX min-width:auto. A flex item whose content is wider than the container
   sizes to MAX-CONTENT, not to the container -- so the two deliberately-scrollable
   strips (.readout, .masthead-meta) pushed the document 2px wide and gave the whole
   page a horizontal scrollbar. min-width:0 is what lets a flex item actually be
   narrower than its content and scroll internally, which was the intent. --- */
.masthead-meta, .readout { min-width: 0; max-width: 100%; }
@media (max-width: 760px) {
  .bench > * { min-width: 0; max-width: 100%; }
}

/* --- COLUMN FLEX CROSS-AXIS. `.bench` carries align-items:start from the desktop
   GRID rules, where it correctly stops the two columns stretching to equal height.
   Reused on the mobile flex COLUMN it means the opposite: the cross axis is now
   horizontal, so every child shrinks to its content width. With min-width:0 also
   set, the stage collapsed to 15px at 320px wide -- the plate vanished entirely.
   Stretch is what a single-column layout actually wants. --- */
@media (max-width: 760px) {
  .bench { align-items: stretch; }
  .stage, .seek, .tier, .readout, .fineprint { width: 100%; }
}

/* ===========================================================================
   MOBILE FIXES — measured, second pass.

   1. NO HORIZONTAL SCROLL STRIP. Making the masthead scrollable was the wrong
      call: it produced a 425px scroll region and a visible scrollbar on a page
      that otherwise has none. A phone masthead should WRAP, not scroll. Two
      rows, no bar, nothing hidden off-edge.

   2. THE STICKY MINT BAR HAS TO BE VISIBLE WHEN DISABLED. It was
      `background: transparent` with muted text, so on a dark page the bar
      simply did not read as a bar and the button looked missing. A disabled
      control still has to announce that it exists and say what it is waiting for.
   =========================================================================== */

@media (max-width: 760px) {
  .masthead-meta {
    flex-wrap: wrap;            /* was nowrap + overflow-x:auto */
    overflow-x: visible;
    gap: 0.4rem;
    row-gap: 0.4rem;
  }
  .chip { font-size: 0.66rem; padding: 0.28rem 0.5rem; }
  /* price is the number people came for: give it the full row it needs */
  #chip-price { order: -1; }

  /* the readout stays scrollable but must never show a bar */
  .readout { scrollbar-width: none; }
  .readout::-webkit-scrollbar { display: none; }

  /* --- the sticky bar reads as a BAR in every state --- */
  .mint {
    background: oklch(0.232 0.048 277 / 0.97);
    border-top: 1px solid var(--line);
    box-shadow: 0 -14px 30px -18px oklch(0 0 0 / 0.9);
  }
  .mint:disabled {
    background: oklch(0.232 0.048 277 / 0.97);   /* NOT transparent */
    border-top-color: var(--line-soft);
    color: var(--ink-2);
    opacity: 1;
  }
  .mint:disabled .mint-sub { color: var(--ink-3); }
  .mint:not(:disabled) {
    background: oklch(0.30 0.075 80 / 0.97);
    border-top-color: var(--key);
    color: var(--key);
  }
  .mint-label { font-size: 1rem; }
}

/* --- [hidden] MUST WIN. The `hidden` attribute is only `display:none` in the UA
   stylesheet, so ANY author `display` rule beats it. `.prize { display: flex }` did
   exactly that: the prize card sat in the middle of the empty plate on first paint,
   reading "OPEN @—" before anything had been rendered. Every element this page hides
   with the attribute is at risk of the same thing, so fix the class of bug, not the
   one instance. --- */
[hidden] { display: none !important; }

/* --- X IS OPTIONAL AND MUST LOOK IT. `Connect X` sat at identical weight beside
   `Connect wallet`, which reads as two required steps. It is not: a wallet alone
   mints anything on this site. X only unlocks the verified tier on your OWN handle,
   so it drops to a quiet secondary affordance and says what it is for. --- */
#btn-x {
  border-style: dashed;
  border-color: var(--line-soft);
  color: var(--ink-3);
  font-size: var(--t-xs);
}
#btn-x:hover { color: var(--ink-2); border-color: var(--line); }
#btn-x[data-on="1"] { border-style: solid; color: var(--key); border-color: var(--key-deep); }

/* the wallet button is the one that matters: it is the only thing minting requires */
#btn-wallet { border-color: var(--key-deep); color: var(--key); }
#btn-wallet[data-on="1"] { background: var(--key-ghost); }

@media (max-width: 760px) {
  /* on a phone, put the required action first and let the optional one trail */
  #btn-wallet { order: -2; }
  #btn-x { order: 10; }
}

/* --- MINT GOES ABOVE THE IMAGE. The sticky bottom bar was the wrong call: it sat
   below the fold on a tall plate and read as missing. Put it in the flow, directly
   under the seek row and directly above the stage, so it is on screen the moment
   you land and never moves. --- */
@media (max-width: 760px) {
  .mint {
    position: static;
    order: 2;                 /* seek(1) -> MINT(2) -> stage(3) */
    width: 100%;
    border: 1px solid var(--line);
    border-radius: var(--r);
    padding: 0.8rem 1rem;
    box-shadow: none;
    backdrop-filter: none;
  }
  .stage   { order: 3; }
  .tier    { order: 4; }
  .readout { order: 5; }
  .fineprint { order: 6; }
  .bench { padding-bottom: var(--gap); }   /* no sticky bar to clear any more */

  .mint:not(:disabled) { border-color: var(--key); }
}
