/* ==========================================================================
   Styles

   --accent is the logo's own blue, #0038F0, read off the mark rather than
   guessed. It is also the page's only colour: every figure, every heading and
   every icon is either that blue, near-black ink, or grey. Nothing else is
   introduced, which is what holds the dashboard, the three steps and the two
   pieces of artwork together as one page.

   The surfaces are WHITE on white. There is nowhere lighter to go, so a card
   does not separate itself by being brighter — it separates by a hairline
   border and a soft shadow underneath. That is the mockup's own logic and it
   is why the borders are 1px and the shadows are wide and faint rather than
   dark.

   NO RADIUS: --r-card and --r-btn are both 0 and every box that could take a
   corner reads them, so the whole page rounds or stays square from two lines
   here rather than from thirty edits.
   ========================================================================== */

:root {
  /* Surfaces & ink */
  --page:        #ffffff;
  --surface:     #ffffff;
  --band:        #f6f9fd;   /* the one step down, behind the three steps */
  --ink:         #0d1220;
  --ink-2:       #33405a;
  --muted:       #7c889d;
  --line:        #e6ecf6;
  --line-strong: #d4deed;

  /* The logo's own blue */
  --accent:      #0038f0;
  --accent-deep: #0a2ba8;
  --accent-soft: #e0e7ff;

  --base-blue:   #0052ff;

  /* Shape. The artwork is blocks with hard edges and no fillets, and the page
     is cut to match: right angles everywhere.

     Both tokens below are read by the cards, the buttons and the toast, so
     setting them to 10px is the whole of "make it rounded" — there is no
     other corner on the page. */
  --r-btn:       0px;
  --r-card:      0px;

  /* Depth. A card is white on white, so the shadow is the only thing holding
     it off the page: one tight shadow for the edge and one wide, faint one
     for the lift. Dark shadows turn a six-card row into a comic strip, so
     both are pitched under 10% and tinted blue rather than grey — a neutral
     shadow on this much white reads as dirt. */
  --card-shadow: 0 1px 2px rgba(17, 38, 86, .05), 0 10px 24px rgba(17, 38, 86, .07);
  --card-lift:   0 2px 4px rgba(17, 38, 86, .07), 0 18px 34px rgba(11, 60, 200, .13);

  --wrap:        1040px;

  --font: "Inter", system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;

  color-scheme: light;
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--page);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration: none; }

:where(a, button):focus-visible {
  outline: 2.5px solid var(--accent-deep);
  outline-offset: 3px;
  border-radius: var(--r-btn);
}

/* Present to screen readers, absent on screen */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 20px;
}

/* The short blue bar that sits under a heading. Used three times — the hero
   tagline and both lines of type on the footer artwork — so it is one class
   rather than three ::after rules. */
.rule {
  display: block;
  width: 54px;
  height: 5px;
  margin: 18px 0 0;
  background: var(--accent);
}

.rule--mid { margin: 14px auto 0; }

/* ==========================================================================
   Hero

   Full-bleed, with the copy laid over the clip. The artwork puts the printer
   on the RIGHT and sky on the left, so the type takes the left half and the
   machine is never covered.

   The frame is 2.2:1, cropping the clip's 3:2 — and the ratio is FIXED, with
   no cap in vh or px. That is the whole trick. A vh cap crops by an amount
   that depends on the window's HEIGHT, and a px cap by an amount that depends
   on its width, so either one slides the crop around as the viewport changes.
   This clip cannot take a sliding crop, because of what is in it.

   Measured off images/hero_poster.webp, which is frame 0, as fractions of its
   448px height:

     BASE mark, top-left corner   4.9% – 9.4%
     the cat's ear tips           from 32%
     the coin pile                62% – 96%

   A fixed ratio crops a fixed 15.9% off each side, which is the only band
   that works: past 9.4% the corner mark is gone COMPLETELY rather than served
   as a sliced fragment — which is what a 76vh cap did between roughly 1030
   and 1140px wide — and well short of 32%, so the ears, the thing that makes
   the machine a cat, are never touched. The bottom 15.9% is the floor and the
   lowest coins, and the pile reading off the edge is how the artwork meets
   the dashboard.

   It also means the page draws no BASE mark of its own up there: on a phone
   the clip is uncropped and carries its own, and two would be worse than
   none.
   ========================================================================== */

.hero { position: relative; }

.hero__frame {
  position: relative;
  width: 100%;
  aspect-ratio: 2.2 / 1;  /* see above: fixed, so the crop cannot slide onto
                             the corner mark or the ears */
  overflow: hidden;
  /* The sky's own blue, so the frame is never a white flash before the
     poster decodes. */
  background: #bfdcf7;
}

.hero__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero__overlay {
  position: absolute;
  inset: 0;
  display: flex;
}

/* space-between, so the eyebrow holds the top and the copy hangs from the
   BOTTOM of the frame. That is where the mockup puts it, and on this artwork
   it is also the readable half: the clip's left third is saturated sky at the
   top and pale blocks and floor lower down, so type that starts at 44% sits
   on the pale part. Bottom-anchored it stays there at every height the frame
   can take, which a top margin in vh does not. */
.hero__rail {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  /* Inset a little more than .wrap's 20px, so the wordmark sits just
     inside the dashboard's left edge rather than flush with it. */
  padding: 24px clamp(20px, 4vw, 40px) 28px;
}

.hero__eyebrow {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 16px;
}

.hero__onchain {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* The rule that closes the line, as in the mockup's top-right corner */
.hero__onchain i {
  display: block;
  width: 30px;
  height: 3px;
  background: var(--accent);
}

/* Off the very bottom of the frame: flush against the edge the buttons read
   as falling out of the picture. */
.hero__copy { padding-bottom: clamp(6px, 5vh, 54px); }

/* The wordmark. 800 rather than 700: the artwork's own lettering is heavy,
   and at this size the lighter weight reads as a different logo. Tracking is
   pulled in because Inter's default spacing at 5rem is loose enough to look
   accidental. */
.hero__wordmark {
  margin: 0;
  font-size: clamp(40px, 6.2vw, 74px);
  font-weight: 800;
  line-height: .96;
  letter-spacing: -.035em;
  text-transform: uppercase;
  color: var(--accent);
}

.hero__tagline {
  margin: 14px 0 0;
  font-size: clamp(16px, 1.9vw, 22px);
  font-weight: 700;
  line-height: 1.24;
  letter-spacing: -.005em;
  text-transform: uppercase;
  color: var(--accent);
}

.hero__lede {
  margin: 22px 0 0;
  font-size: clamp(14px, 1.45vw, 16.5px);
  font-weight: 400;
  line-height: 1.5;
  color: #6f7d92;
}

/* ==========================================================================
   Actions — the three labelled buttons under the hero copy
   ========================================================================== */

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 10px;
  margin: 28px 0 0;
}

/* Square, icon only. The name lives in an .sr-only span inside and in
   title=, so the control still announces itself — what goes is the painted
   label, not the label.

   Square matters: with no text to set the width, padding that once framed
   "X (Twitter)" would leave a 21px glyph adrift in a lozenge three times its
   width. Width and height are one value. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-btn);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  font: inherit;
  color: var(--ink-2);
  cursor: pointer;
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.btn:hover {
  transform: translateY(-2px);
  border-color: #c9d8f5;
  box-shadow: var(--card-lift);
}

.btn:active { transform: translateY(0); box-shadow: var(--card-shadow); }

.btn__icon { width: 22px; height: 22px; flex: none; color: var(--accent); }

.btn__icon--stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}


/* Copied toast */
.ca-wrap { position: relative; display: inline-flex; }

.toast {
  position: absolute;
  top: calc(100% + 10px);
  left: 50%;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: var(--r-btn);
  background: var(--ink);
  color: #fff;
  font-size: 13.5px;
  white-space: nowrap;
  box-shadow: 0 8px 22px rgba(13,18,32,.24);
  opacity: 0;
  transform: translate(-50%, -6px) scale(.96);
  pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
  z-index: 4;
}

.toast.is-on { opacity: 1; transform: translate(-50%, 0) scale(1); }

.toast__check { width: 15px; height: 15px; color: #7ddb6a; flex: none; }
.toast.is-error .toast__check { color: #ff8a7a; }

/* ---- overlay mode: the hero composition, scaled -------------------------
   Above the phone breakpoint the type sits ON the artwork, and the artwork is
   a full-bleed picture — so it grows with the viewport WIDTH. Type on a px
   clamp does not, and the two drift apart: at 1440 the clip is a 2.14x
   blow-up of a 672px frame while the wordmark has stopped at its 74px
   ceiling, so the printer and the coin pile grow into the column and the lede
   ends up lying across a coin.

   Every length here is vw instead, so the whole composition is the same
   drawing at 820px and at 2560px — the relationship between the wordmark, the
   copy and the machine cannot drift, because there is nothing fixed left to
   drift against. The ratios are the mockup's own: 6.2vw of its 1024px width
   is its 64px wordmark.

   padding-bottom is 8vw = 12% of the frame's height (the frame is 3:2, so its
   height is 66.7vw), which lands the buttons at 85% of the hero exactly as
   the mockup does.
   ------------------------------------------------------------------------ */
@media (min-width: 761px) {
  /* --hero-scale is the one number that sizes the whole overlay. Every length
     below is a vw multiplied through it, so the composition stays one drawing
     at every width AND the whole thing gets bigger or smaller from this line
     alone — no re-tuning nine values and hoping they still agree.

     1 is the mockup read against the viewport: 6.25vw is its own 64px at its
     own 1024px width. But the band here is cropped to 2.2:1 rather than left
     at the mockup's 3:2, and the type is sized off the WIDTH while the band
     lost a third of its HEIGHT — so matching the mockup means matching the
     ratio that survives the crop, which is the wordmark against the BAND:

       mockup   64px wordmark in a 670px band   = 9.6%
       scale 1                                  = 13.7%
       scale .8                                 = 11.0%
       scale .7                                 =  9.6%   ← the mockup

     .7 is not a taste call, it is that measurement.

     Floors, no ceilings. A ceiling would stop a control growing while the
     artwork behind it kept going, which is the drift this block exists to
     prevent; the floors are there because 1.46vw is 11px on an 800px window
     and a label is for reading. */
  :root { --hero-scale: .7; }

  .hero__copy { padding-bottom: 5.5vw; }   /* 12% of the frame's height */

  /* Artwork-scale type */
  .hero__wordmark { font-size: calc(6.25vw * var(--hero-scale)); }
  .hero__tagline  { font-size: calc(1.85vw * var(--hero-scale)); margin-top: calc(1vw * var(--hero-scale)); }
  .hero__lede     { font-size: max(13px, calc(1.46vw * var(--hero-scale))); margin-top: calc(1.5vw * var(--hero-scale)); }

  .hero__copy .rule {
    width: calc(5vw * var(--hero-scale));
    height: max(4px, calc(.49vw * var(--hero-scale)));
    margin-top: calc(1.3vw * var(--hero-scale));
  }

  /* Controls, with a floor on every one of them: these are things to read and
     press, not brushwork. The first pass at this block shipped a 6.6px button
     label and a 38px tap target at 820px. */
  .hero__onchain { font-size: max(10px, calc(1.07vw * var(--hero-scale))); gap: max(8px, calc(.9vw * var(--hero-scale))); }
  .hero__onchain i { width: max(20px, calc(2.1vw * var(--hero-scale))); height: max(2px, calc(.22vw * var(--hero-scale))); }

  .actions { gap: max(8px, calc(.78vw * var(--hero-scale))); margin-top: calc(2vw * var(--hero-scale)); }

  /* One value for both sides — a square that grows with the band like
     everything else in here. The icon keeps roughly 39% of the box, which is
     what stops it reading as a dot in a plate at 1920. */
  .btn {
    width: max(52px, calc(6vw * var(--hero-scale)));
    height: max(52px, calc(6vw * var(--hero-scale)));
  }

  .btn__icon {
    width: max(20px, calc(2.32vw * var(--hero-scale)));
    height: max(20px, calc(2.32vw * var(--hero-scale)));
  }
}

/* ==========================================================================
   Dashboard — six cards across, icon over label over figure
   ========================================================================== */

.stats {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 12px;
  margin: 34px 0 0;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 20px 12px 22px;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  background: var(--surface);
  box-shadow: var(--card-shadow);
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}

.stat:hover {
  transform: translateY(-3px);
  border-color: #c9d8f5;
  box-shadow: var(--card-lift);
}

/* One spec for every glyph on the page that is not a button: a drawn outline
   in the accent, no tile behind it. The mockup's icons sit directly on the
   card — a filled block behind each one would put six more rectangles in a
   row that already has six. */
.stat__icon,
.step__icon {
  display: grid;
  place-items: center;
  color: var(--accent);
}

.stat__icon svg,
.step__icon svg {
  width: 30px;
  height: 30px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Two lines' worth of room whether the label needs it or not, so all six
   figures sit on one line across the row. Without this the cards with a
   one-line label lift their figure 14px above the others. */
.stat__label {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 2.6em;
  margin: 10px 0 0;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: .005em;
  color: var(--ink-2);
}

.stat__value {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 5px;
  margin: 4px 0 0;
  font-size: clamp(17px, 1.75vw, 23px);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--accent);
  /* Same-width digits, so a figure that ticks up does not jostle the label
     beside it — and so the six cards line up as a set rather than as six
     independent numbers. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.stat__unit {
  font-size: .6em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--ink-2);
}

/* No source for this metric yet — don't dress a dash up as a figure */
.stat__value .is-empty { color: var(--muted); font-weight: 500; }

/* The dollar value of the reward-token figure above it, in parentheses. */
.stat__sub {
  margin: 3px 0 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.stat__sub b { font-weight: 500; }

/* Blinking "…" placeholder, shown until the first data load resolves */
.dots {
  display: inline-flex;
  align-items: flex-end;
  gap: .18em;
  padding-bottom: .12em;
}

.dots i {
  width: .2em;
  height: .2em;
  border-radius: 50%;
  background: currentColor;
  opacity: .25;
  animation: dot-blink 1.25s infinite ease-in-out;
}

.dots i:nth-child(2) { animation-delay: .18s; }
.dots i:nth-child(3) { animation-delay: .36s; }

@keyframes dot-blink {
  0%, 65%, 100% { opacity: .22; }
  32%           { opacity: 1; }
}

/* Live-data legend. Discreet by design: it should be findable when someone
   wonders how current a figure is, and invisible the rest of the time. */
.legend {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 18px 0 0;
  font-size: 12.5px;
  letter-spacing: .01em;
  color: var(--muted);
}

.legend__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--muted);
  flex: none;
}

/* Reading the chain right now */
.legend.is-live .legend__dot {
  background: #46a24b;
  box-shadow: 0 0 0 0 rgba(70,162,75,.45);
  animation: legend-pulse 2.4s infinite ease-out;
}

/* Showing what was last read, because something did not answer */
.legend.is-stale .legend__dot { background: var(--accent); }

@keyframes legend-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(70,162,75,.45); }
  70%  { box-shadow: 0 0 0 6px rgba(70,162,75,0); }
  100% { box-shadow: 0 0 0 0 rgba(70,162,75,0); }
}

/* ==========================================================================
   How it works — three steps, divided by hairlines

   Not cards. The dashboard above is already a row of six boxes, and putting
   three more under it turns the whole page into one grid; the divider does
   the same separating work without adding a shape.
   ========================================================================== */

.steps-band {
  margin: 44px 0 0;
  padding: 44px 0 48px;
  background: var(--band);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.step {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 4px 22px;
}

.step + .step { border-left: 1px solid var(--line-strong); }

.step__icon svg { width: 34px; height: 34px; }

.step__title {
  margin: 14px 0 0;
  font-size: 15px;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
}

.step__text {
  margin: 10px 0 0;
  font-size: 14.5px;
  line-height: 1.5;
  color: #6f7d92;
  /* No <br> in the markup any more — the breaks have to fall wherever the
     column width puts them, so even the lines out rather than leaving one
     word alone on the second. */
  text-wrap: balance;
}

/* ==========================================================================
   Footer artwork

   images/footer.png is 2172×724 (3:1) and already carries the BASE mark on
   its right-hand block, so the type only goes where the picture is empty:
   the left block's face, and the sky over the middle.

   Both blocks are positioned in PERCENTAGES of the image, not in pixels, so
   they stay on the same part of the artwork at every width instead of
   drifting off it.
   ========================================================================== */

/* The picture's own bottom-edge colour, sampled from it: the mean of its last
   row is rgb(199,213,248), and that row barely varies across the width. On a
   phone the content runs on BELOW the image rather than over it, and this is
   what it runs on to — so the page ends in the floor of the artwork instead
   of in a white strip. */
.foot {
  margin: 0;
  background: #c7d5f8;
}

/* Its own 3:1, uncropped. The band was cropped to 3.9:1 to lift the blocks
   and put "cats build higher" on one of them — but it now has to hold the two
   ecosystem lockups and the fine print as well, and 3:1 is the TALLEST this
   picture can be without `cover` cropping the SIDES instead of the top, which
   is where both BASE blocks live. So the slogan sits on the sky above the
   blocks and the lockups sit on the floor below them. */
.foot__art {
  position: relative;
  aspect-ratio: 3 / 1;
  overflow: hidden;
}

.foot__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 100%;
}

.foot__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.foot__rail {
  position: relative;
  height: 100%;
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 28px;
}

.foot__left,
.foot__mid {
  position: absolute;
  margin: 0;
  font-weight: 800;
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1.04;
}

/* On the left block's face, ABOVE the BASE mark the artwork already has
   there. */
/* On the left block's face, above the BASE mark the artwork already has
   there. The size is vw and the band's height is width/3.9, so the type and
   the space it has to live in scale TOGETHER — the clearance over that mark
   is then the same at every width. A px floor in the clamp is what broke it
   first time: at 820px the band shrank to 210px while 19px type did not, and
   "HIGHER" sat on top of the mark. The floor here is below the width this
   overlay exists at, so it never binds. */
.foot__left {
  left: 28px;
  top: 7%;
  font-size: clamp(15px, 2vw, 38px);
  letter-spacing: -.02em;
}

.foot__left .rule { width: 42px; height: 4px; margin-top: 8px; }

/* Centred, low enough to sit on the pale part of the sky rather than the
   saturated top — blue type on deep blue sky is the one placement in this
   picture that does not read. */
.foot__mid {
  left: 50%;
  top: 38%;
  transform: translateX(-50%);
  text-align: center;
  /* vw for the same reason as .foot__left — it shares that band. */
  font-size: clamp(12px, 1.45vw, 20px);
  top: 30%;
  letter-spacing: .04em;
  line-height: 1.32;
}

.foot__mid .rule { width: 40px; height: 4px; }

/* ==========================================================================
   Ecosystem
   ========================================================================== */

/* On the floor of the artwork, not under it. pointer-events comes back on
   here: the overlay turns them off so the picture is not covered by an
   invisible sheet, but these two are links. */
.eco {
  position: absolute;
  left: 50%;
  /* Down on the floor, which starts at 78% of the picture. At 15% the block
     straddled the horizon and the lockups sat on mid-blue sky. */
  bottom: 11%;
  transform: translateX(-50%);
  width: 100%;
  max-width: 680px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 24px;
  pointer-events: auto;
}

.eco__half {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(8px, .9vw, 16px);
  padding: 4px 8px;
  text-align: center;
  transition: transform .18s ease;
}

.eco__half:hover { transform: translateY(-2px); }

.eco__kicker {
  font-size: clamp(11px, .94vw, 17px);
  color: var(--ink-2);
  line-height: 1.3;
}

/* Both lockups have the resolution to run large — 550×112 and 800×261 — so
   they are sized by height and left to keep their own ratios. The
   Stonks.Exchange bar is much the wider of the two, which makes it read
   smaller at a matched height, so it gets a little more.

   In vw, because they now sit ON the artwork: the band's height is width/3,
   so a fixed px block slides up and down the picture as the viewport changes
   — 22% of the band at 1024 and 12% at 1920, which is the difference between
   standing on the floor and floating in the sky. */
.eco__logo { height: clamp(28px, 2.9vw, 54px); width: auto; }
.eco__logo--stonks { height: clamp(32px, 3.3vw, 62px); }

.foot__fine {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2%;
  margin: 0;
  text-align: center;
  /* --muted is pitched for white; on the floor's pale blue it goes soft. */
  color: #5d6a84;
  font-size: 13px;
}


/* ==========================================================================
   Entrance and interaction

   Every rule here is behind `html.anim`, which the script at the foot of
   index.html adds only when JS runs AND the visitor has not asked for less
   motion. So the resting page — no JS, old browser, thrown exception, an
   IntersectionObserver that never fires — is the finished page, at full
   opacity. Nothing is hidden waiting to be revealed; that is the failure mode
   where a site ships blank, and it is not available here.

   `translate` and `scale` rather than `transform`. They are separate
   properties, and a finished animation with fill:both keeps ownership of
   whatever it animated — animating `transform` would outrank .stat:hover's
   own transform and silently kill every hover lift on the page once the
   reveal had run.
   ========================================================================== */

@keyframes rise {
  from { opacity: 0; translate: 0 16px; }
  to   { opacity: 1; translate: 0 0; }
}

/* The hero's own pieces, dealt out in reading order. --d is set per element
   by the script. */
.anim [data-rise] { opacity: 0; }

.anim [data-rise].is-in {
  animation: rise .72s cubic-bezier(.16, .84, .32, 1) both;
  animation-delay: var(--d, 0ms);
}

/* Everything below the fold waits for its own arrival. .rise is what the
   script marks; .is-in is what the observer adds when it comes into view. */
.anim .rise { opacity: 0; }

.anim .rise.is-in {
  animation: rise .66s cubic-bezier(.16, .84, .32, 1) both;
  animation-delay: var(--d, 0ms);
}

/* Interaction. The cards already lift on hover; these are the smaller
   movements that make the lift feel like one object rather than a box and its
   contents moving apart. */
.stat__icon,
.step__icon {
  transition: scale .22s cubic-bezier(.16, .84, .32, 1);
}

.stat:hover .stat__icon,
.step:hover .step__icon { scale: 1.12; }

.btn__icon { transition: scale .18s ease, color .18s ease; }
.btn:hover .btn__icon { scale: 1.1; color: var(--accent-deep); }

/* The rules under the headings draw themselves in rather than fading: a 5px
   bar at full opacity from nothing reads as a glitch, growing reads as a
   stroke. transform-origin is the left edge, where the type starts. */
@keyframes draw {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

.anim .rule { transform-origin: left center; }
.anim .rule--mid { transform-origin: center; }

.anim [data-rise].rule.is-in {
  animation: rise .5s cubic-bezier(.16, .84, .32, 1) both,
             draw .55s cubic-bezier(.16, .84, .32, 1) both;
  animation-delay: var(--d, 0ms);
}

/* The hero clip itself, once: a very slow settle, so arriving on the page has
   a beat of movement even before the video has buffered a frame. */
@keyframes settle {
  from { scale: 1.045; }
  to   { scale: 1; }
}

.anim .hero__media {
  animation: settle 1.8s cubic-bezier(.22, .68, .3, 1) both;
}

/* ==========================================================================
   Responsive
   ========================================================================== */

/* Six figures need about 150px each. Below that the row breaks to three and
   then to two, rather than squeezing "12,482,390" to 11px. */
@media (max-width: 1000px) {
  .stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
  .stat__label { min-height: 0; }
}

@media (max-width: 860px) {
  .step { padding: 4px 14px; }
  .step__text { font-size: 13.5px; }
  .eco__logo { height: 38px; }
  .eco__logo--stonks { height: 42px; }
}

/* The overlay stops here. Below this width the hero copy cannot sit over the
   clip: the wordmark, three lines of tagline, three of lede and a row of
   buttons need more height than a 3:2 frame has at phone width, and cropping
   the clip taller to make room takes the printer out of it. So the clip
   becomes a banner and the copy moves underneath it, on white — which is
   where every other word on the page already sits. */
@media (max-width: 760px) {
  /* The copy stays OVER the clip on a phone, as it does everywhere else.

     It has to be a PORTRAIT frame to do that — 3:2 at 390px is a 260px band
     and the copy is about 250px, so there is no room beside the machine and
     none under it either. 4:5 gives the height; `cover` then crops the WIDTH
     rather than the height, so the vertical composition is the clip's own —
     sky on top, the printer through the middle, coins along the bottom.

     And it needs the scrim. There is no arrangement that puts this much type
     over this picture and keeps it readable unaided: the sky is only the top
     third, and everything below it is a saturated blue machine. So the
     gradient is clear across the top, where the ears and the wordmark on the
     machine are worth seeing, and opaque by the bottom, where the words are.
     That is the trade — the artwork keeps its head, the copy gets its
     contrast. */
  /* Near enough the clip's own frame to count as uncropped. `cover` shows
     R/1.5 of the width at a frame ratio R, so the portrait frames this went
     through were magnifying it badly:

       2:3   shows 44% of the width   2.2x
       4:5   shows 53%                1.9x
       1.4:1 shows 93%                1.07x

     At 1.4:1 the crop is 3.5% off each side — the outermost block and the
     edge of the slab — and the scene reads whole, at the scale it was drawn.
     The 7% it does give up is what makes room for the copy without shrinking
     the type to nothing. */
  .hero__frame {
    aspect-ratio: 1.4 / 1;
    overflow: hidden;
    background: #bfdcf7;
  }

  .hero__media {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* Centred: at 93% visible there is nothing to choose between, and the
       machine sits where it sits in the clip.

       The nameplate still has to stay clear of the overlay wordmark — they
       cannot overlap at any opacity, because white type on blue ghosts
       through a white scrim and the two read as one word. It starts at 53%
       of the clip's width, so the wordmark below is sized to end before
       that. */
    object-position: 50% 50%;
  }

  .hero__overlay { position: absolute; inset: 0; display: flex; }

  /* NO scrim. It read as a white gradient washed over the clip — which is
     exactly what it was — and made the header look half transparent.

     What it had been doing was carrying the contrast for the two display
     lines, which sit over sky. Measured against the clip's own pixels at 360
     and 390px, with the accent blue:

       wordmark  28px/800  on rgb(95,173,249)   3.13   (needs 3.0 — large)
       tagline   13px/700  on rgb(103,181,249)  3.38   (needs 4.5 — FAILS)
       lede      12px/400  on rgb(180,208,239)  6.53   (passes; it is low
                                                        enough to sit on the
                                                        blocks, not the sky)

     So the two that needed help take --accent-deep instead of --accent: same
     blue, two stops down, and it measures 5.79 on that sky. Ink does the work
     the wash was doing, and the clip is left alone. */

  .hero__rail {
    position: relative;   /* over the scrim */
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 12px 16px 14px;
  }

  .hero__copy { padding-bottom: 0; }

  /* Sized to the frame it now sits in, not to a page it used to sit on.
     Two limits: the whole stack plus the eyebrow has to clear 279px, and the
     wordmark has to END before 53% of the width, where the machine's own
     nameplate starts. 7.2vw is 28px at 390 and stops at 49%. */
  .hero__wordmark {
    font-size: clamp(24px, 7.2vw, 34px);
    color: var(--accent-deep);
  }

  .hero__tagline {
    font-size: clamp(12px, 3.3vw, 15px);
    margin-top: 7px;
    color: var(--accent-deep);
  }

  /* Top-right, over the deepest part of the sky and the edge of the slab —
     the same reasoning as the two lines above. */
  .hero__onchain { color: var(--accent-deep); }

  /* --muted is pitched for white. Over a 64% wash on the machine it goes
     soft, so the one sentence here that is not display type gets the darker
     ink instead. */
  .hero__lede {
    font-size: clamp(11.5px, 3.1vw, 14px);
    margin-top: 10px;
    /* --muted is pitched for white; this sits on the artwork. 6.5:1 measured
       against the blocks it actually lands on. */
    color: var(--ink-2);
  }

  .hero__copy .rule { width: 34px; height: 4px; margin-top: 9px; }

  .actions { margin-top: 12px; gap: 8px; }
  .btn { width: 44px; height: 44px; }
  .btn__icon { width: 18px; height: 18px; }

  /* Two figures per row */
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 26px; }
  .stat { padding: 16px 10px 18px; }
  .stat__icon svg { width: 26px; height: 26px; }
  .stat__label { font-size: 10.5px; margin-top: 8px; }
  .stat__value { font-size: clamp(16px, 4.6vw, 20px); }

  .steps-band { margin-top: 34px; padding: 32px 0 34px; }

  /* Still three across. They are three words and one short sentence each —
     the row is what makes them read as one sequence, and stacking them turned
     a three-beat explanation into three unrelated blocks.

     The <br>s came out of the markup for this: at ~120px a column a hard
     break strands single words, and hiding a <br> is not available — it
     removes the break WITHOUT leaving a space. `text-wrap: balance` evens the
     lines out instead, wherever they happen to fall. */
  .steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; }
  .step { padding: 0 6px; }
  .step + .step { border-left: 1px solid var(--line-strong); }
  .step__icon svg { width: 26px; height: 26px; }
  .step__title { margin-top: 10px; font-size: 11.5px; letter-spacing: .06em; }
  .step__text { margin-top: 7px; font-size: 11.5px; line-height: 1.4; }

  /* The footer type comes off the artwork here, unlike the hero's. The hero
     could be made portrait to hold its copy; this picture cannot. 3:1 at
     390px is a 130px band, and anything SQUARER than 3:1 makes `cover` crop
     the SIDES rather than the top — the two BASE blocks are at the far left
     and right edges, so a 2:1 box took the middle 67%, which is nothing but
     sky and floor, and the footer rendered as 195px of nothing.

     So the picture stays the band it is and the words run on beneath it — on
     .foot's background, which IS the picture's own bottom-edge colour. There
     is no white under the footer; there is no seam either. */
  .foot__art {
    aspect-ratio: auto;
    overflow: visible;
  }

  .foot__img {
    height: auto;
    aspect-ratio: 3 / 1;
  }

  .foot__overlay { position: static; }
  .foot__rail { height: auto; padding: 26px 20px 30px; text-align: center; }

  .foot__left,
  .foot__mid,
  .eco,
  .foot__fine {
    position: static;
    left: auto;
    right: auto;
    top: auto;
    bottom: auto;
    transform: none;
    text-align: center;
  }

  .foot__left { font-size: 26px; }
  .foot__left .rule { margin: 12px auto 0; }

  .foot__mid { margin-top: 20px; font-size: 15px; }

  .eco { width: auto; margin: 30px auto 0; gap: 20px; }
  .eco__kicker { font-size: 12.5px; }

  .foot__fine { margin-top: 28px; }
}

@media (max-width: 480px) {
  .wrap { padding: 0 14px; }
  .hero__rail { padding: 18px 14px 4px; }

  .chainmark__mark { width: 23px; height: 23px; }
  .chainmark b { font-size: 17px; }
  .hero__onchain { font-size: 10px; letter-spacing: .12em; gap: 8px; }
  .hero__onchain i { width: 20px; }

  /* Three squares fit any phone with room to spare now that nothing has to
     hold a label, so they keep their size instead of stretching to share the
     row. */
  .actions { gap: 8px; }
  .btn { width: 48px; height: 48px; }
  .btn__icon { width: 19px; height: 19px; }

  .stats { gap: 8px; }
  .stat { padding: 14px 8px 16px; }
  .stat__icon svg { width: 24px; height: 24px; }
  .stat__label { font-size: 10px; line-height: 1.25; }
  .stat__value { font-size: clamp(15px, 5vw, 19px); }
  .stat__sub { font-size: 12px; }

  .foot__left { font-size: 23px; }
  .foot__fine { margin-top: 22px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
  }
  /* …but the loading placeholder still needs to be visible */
  .dots i { opacity: .55; }
}
