/*
 * The stage: layout for the pinned scenes, and the initial states that make a
 * reveal possible without hiding anything from a reader who has no JavaScript.
 *
 * The rule that governs the whole file: NOTHING is hidden except under
 * `html.scenes`. That class is added before first paint by
 * web/public/js/scene-ready.js, and only when the page is going to animate - so
 * with no JavaScript, or under prefers-reduced-motion, every selector below is
 * inert and the page is a long static document with each scene at its end state.
 *
 * Read web/public/js/scene-ready.js before changing anything here.
 */

/* ===================================================================== stage */

/* ScrollTrigger and `scroll-behavior: smooth` fight: a smooth programmatic
 * scroll animates the position out from under the trigger's own sync, and anchor
 * jumps land in the middle of a pin. Instant jumps are also simply more
 * predictable on a page where an anchor can be three viewports inside a scene. */
html.scenes { scroll-behavior: auto; }

/*
 * THE PIN IS OPT-IN, exactly like the hiding.
 *
 * This was the first version's bug and it is worth stating, because it looked
 * right: the pin was the default and a reduced-motion query unpinned it, scoped
 * to `html.scenes`. But the class is absent precisely when nothing will animate -
 * so with no JavaScript the query never matched, and the scene became three
 * viewports of track that refused to scroll while nothing moved. Measured, not
 * guessed: `position: sticky` and a 2700px track with the class off.
 *
 * One class now governs both halves of the register. No class means no hiding AND
 * no pinning: the scene is an ordinary section at its end state, which is the same
 * page a crawler sees and the same page a reduced-motion reader gets.
 */
.scene { position: relative; }

.scene-stage {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: var(--banx-space-5);
  padding: var(--banx-space-7) var(--banx-gutter);
}

html.scenes .scene {
  /* The track. Its height IS the scene's duration - the one knob that decides
   * whether a scene feels brisk or interminable. Per-scene overrides go on the
   * scene's own class, never here. */
  min-height: var(--banx-scene-track);
}

html.scenes .scene-stage {
  position: sticky;
  top: 0;
  /* svh, not vh: on mobile `vh` includes the browser chrome that is about to
   * collapse, so a 100vh stage is taller than the screen it is meant to fill. */
  height: 100svh;
  padding: calc(var(--banx-nav-h) + var(--banx-space-4)) var(--banx-gutter) var(--banx-space-5);

  /* `minmax(0, 1fr)` on the body row, and `min-height: 0` on everything inside
   * it, is what stops a scene clipping.
   *
   * Measured before this: four of the five scenes overflowed a 100svh stage and
   * `overflow: hidden` silently cut them - a headline sheared off at the top, the
   * counters gone from the bottom, and worse the shorter the viewport. The cause
   * was sizing each figure as a fraction of the viewport and hoping the total
   * fit. Now the figure takes the row that is LEFT, so it shrinks instead of
   * overflowing and there is no budget to get wrong. */
  grid-template-rows: auto minmax(0, 1fr);
  overflow: hidden;
}

html.scenes .scene-stage > * { min-height: 0; }

/* ------------------------------------------------------------------ scene head
 * The kicker stays in frame for the whole scene, and for a Designed mechanism
 * that is a content requirement rather than a layout choice: an animation of a
 * system that does not exist yet must say so while it plays.
 */
.scene-head {
  width: min(100%, var(--banx-content));
  margin-inline: auto;
}

.scene-kicker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--banx-space-3);
  align-items: center;
  color: var(--banx-subtle);
  font-family: var(--banx-font-mono);
  font-size: var(--banx-text-2xs);
  letter-spacing: var(--banx-tracking-caps);
  text-transform: uppercase;
}

.scene-kicker .n {
  color: var(--banx-accent-text);
  /* A rule after the number, drawn rather than typed, so it holds its width
   * whatever the numeral. */
  padding-right: var(--banx-space-3);
  border-right: 1px solid var(--banx-line);
}

/* --------------------------------------------------------------- scene canvas
 * Where a scene's own elements live. Grid rather than flow: a stage positions
 * things against each other, not one after another.
 */
.scene-body {
  display: grid;
  align-content: center;
  width: min(100%, var(--banx-content));
  min-height: 0;
  margin-inline: auto;
}

.scene-body > * { min-height: 0; }

/* Display type on a stage scales with the SMALLER of width and height.
 *
 * A text column only has to worry about its measure; a stage headline also has to
 * leave room for a figure below it in a frame whose height is fixed. Sized on vw
 * alone, the same headline that fits at 1440x900 eats the figure at 1440x720. */
.scene h2 {
  font-size: clamp(1.5rem, min(4.2vw, 5.4svh), 3.4rem);
  letter-spacing: var(--banx-tracking-display);
  line-height: var(--banx-leading-display);
  text-wrap: balance;
}

.scene .scene-lede {
  max-width: 62ch;
  margin-top: var(--banx-space-3);
  color: var(--banx-muted);
  font-size: clamp(.92rem, 2.1svh, 1.12rem);
  line-height: 1.5;
}

/* ============================================================ initial states
 * Everything below is scoped to html.scenes and nothing below is scoped to
 * anything else. This is the boundary the whole design rests on.
 */

/* A reveal: present in the markup, transparent until its scene reaches it. */
html.scenes [data-reveal] {
  opacity: 0;
  /* will-change on a handful of elements is a hint; on every element it is a
   * memory leak. Only the reveal targets get it, and only while a scene is the
   * thing being scrolled. */
  will-change: opacity, transform;
}

/* A figure that counts. It starts at its own start value in the markup, so with
 * no JavaScript the reader sees a real number rather than a zero. */
html.scenes [data-count] { font-variant-numeric: tabular-nums; }

/* ============================================================ reduced motion
 * Belt and braces. scene-ready.js already declines to add the class, so none of
 * the initial states above apply - but a reader can turn reduced motion on after
 * load, and then the class is already on the element.
 */
@media (prefers-reduced-motion: reduce) {
  /* The reveals only need resetting if the class somehow IS on - a reader can
   * turn reduced motion on after load, and scene-ready.js has already run. */
  html.scenes [data-reveal] { opacity: 1; will-change: auto; }

  /* Unpin unconditionally, NOT scoped to html.scenes. The pin is opt-in above,
   * so normally there is nothing to undo; this is the after-load case, where the
   * class is present and the animation will not run. */
  .scene { min-height: 0; }

  .scene-stage {
    position: static;
    height: auto;
    padding: var(--banx-space-7) var(--banx-gutter);
    overflow: visible;
  }
}

/* ============================================== below a landscape desktop
 * A pinned scene needs a stage, and a stage is a WIDTH and a HEIGHT at once.
 *
 * The threshold is 1024px and it is measured, not chosen. Below it the two-column
 * scene layouts collapse to one column, which makes every scene taller - and the
 * waterfall, with six bar rows, then overflowed a held stage by 106px at 768x1024
 * and 142px at 700x900. Holding a view still while content is cut off the bottom
 * is the worst of both registers.
 *
 * So the pin is a desktop treatment. Below 1024px scenes unpin and play as
 * ordinary sections; the reveals stay, because they cost nothing and still read as
 * motion. On a phone that is also simply better: holding the view while a thumb
 * flicks reads as a stuck page.
 */
@media (max-width: 1023px) {
  /* Unscoped for the same reason as the reduced-motion block: these have to win
   * whether or not the class is on. */
  .scene,
  html.scenes .scene { min-height: 0; }

  .scene-stage,
  html.scenes .scene-stage {
    position: static;
    height: auto;
    padding: var(--banx-section-sm) var(--banx-gutter);
    overflow: visible;
  }
}

/* ============================================================ scene 01 clocks
 * Four concentric rings on the right, the four cadences as rows on the left.
 *
 * The dial is the subject and the rows are its legend, so the dial gets the
 * larger column and the rows are set small and mono. The first version of this
 * scene put four bars across the full width, which read as a chart - this reads
 * as an instrument.
 */
.clocks {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  gap: var(--banx-space-5) var(--banx-space-7);
  align-content: center;
}

/* The head is capped at 46ch, so spanning both columns never widened it by a
 * pixel: it only reserved the whole first row, and the half of that row beside
 * the headline was the largest empty area on the page. The head takes its own
 * column and the dial runs the full height beside it, which is the composition
 * the scene wanted all along - copy left, instrument right.
 *
 * 46ch still holds the headline to two lines. Given the whole 592px column it
 * would set as "One transaction. Four" / "clocks.", breaking mid-sentence. */
.clocks-head { grid-column: 1; max-width: 46ch; }
.clocks-rows-col { grid-column: 1; align-self: center; }
.clocks-foot { grid-column: 1 / -1; }

/* ---- the dial ---------------------------------------------------------- */

.clocks-dial {
  display: grid;
  place-items: center;
  align-self: center;
  /* The dial is square and must never be the thing that overflows a 100svh
   * stage, so its size is bounded by the stage's height as well as its width. */
  width: 100%;
  /* Two rows of height to work with now rather than one, so the bound rises
   * with it. Both bounds still hold: never wider than its column, never taller
   * than a share of the stage it has to share with the head and the foot. */
  max-width: min(100%, 56svh);
  margin-inline: auto;
  /* The ring labels are positioned against this box. It is square (a square
   * viewBox at width:100%, height:auto), so a percentage here is a viewBox
   * coordinate over 720. */
  position: relative;
  grid-row: 1 / 3;
  grid-column: 2;
}

.clocks-dial svg { display: block; width: 100%; height: auto; overflow: visible; }

/* The spiral the transaction travels. Brighter and thicker than a track, because
 * a track is a target and this is what actually happened. */
.clocks-trail {
  fill: none;
  stroke: var(--banx-mint);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ---- the ring labels --------------------------------------------------- */
/*
 * One label per ring, placed ON the ring it belongs to.
 *
 * Without them the dial was four unlabelled circles beside a list of four
 * cadences, with nothing at all saying which circle was which. The figure is
 * the link: each label carries the same value, in the same mono face and the
 * same mint, as the right-hand column of its row.
 *
 * HTML rather than SVG <text>, for the reason the fold's orbit labels are: text
 * inside a viewBox scales with the box, so 13px on a desktop renders at about
 * 5px on a phone. The offsets are r/720 about a centre at 360, taken at 0, 60,
 * 105 and 145 degrees clockwise from twelve. Outward and clockwise, which is
 * the direction time runs in - and it keeps every label on the dial's right
 * half, away from the rows column.
 */
.clocks-tag {
  position: absolute;
  padding: 3px 9px;
  color: var(--banx-accent-text);
  font-family: var(--banx-font-mono);
  font-size: clamp(.66rem, 1.9vw, .78rem);
  white-space: nowrap;
  background: var(--banx-bg);
  border: 1px solid var(--banx-mint-line);
  border-radius: 999px;
  transform: translate(-50%, -50%);
}

/* On the spiral, not on a ring. Each pair is the crossing point of the path
 * itself: the transaction reaches radius r at t = r / rMax, and the spiral's angle
 * there is t * TURNS * 360 - 90, which for 108, 176, 244 and 312 of 312 comes to
 * 48.5, 2.3, 316.2 and 270 degrees. Computed, not placed by eye - the closest two
 * badges are 17.7% of the box apart. */
.clocks-tag.is-exec { left: 59.95%; top: 61.23%; }
.clocks-tag.is-mini { left: 74.42%; top: 50.98%; }
.clocks-tag.is-evm { left: 74.44%; top: 26.52%; }
/* The outermost badge sits at t = 0.985 and not at 1.0. The spiral ENDS at 1.0,
 * which is exactly where the transaction settles, and a badge with an opaque
 * ground there covers the dot in the final frame - the one frame a reader
 * lingers on. A step back along the path is a long step round it at four turns,
 * which is why this pair looks unrelated to the others. */
.clocks-tag.is-l1 { left: 34.29%; top: 10.31%; }

/* The subject of the diagram, at its centre, where there was nothing. The halo
 * pulses behind it as the transaction arrives - the HTML is a later sibling
 * than the SVG, so it draws over the halo rather than under it.
 *
 * It carries the page's own ground, because the spiral now starts at radius zero
 * and its first coil passes straight through this label. The ring tags are
 * bounded the same way for the same reason. */
.clocks-centre {
  position: absolute;
  left: 50%;
  top: 50%;
  margin: 0;
  padding: 3px 9px;
  background: var(--banx-bg);
  border-radius: 999px;
  color: var(--banx-text);
  font-family: var(--banx-font-mono);
  font-size: var(--banx-text-2xs);
  letter-spacing: var(--banx-tracking-caps);
  text-transform: uppercase;
  transform: translate(-50%, -50%);
}

/* ---- the rows ---------------------------------------------------------- */

/* Rules as borders on the rows, NOT as a container background showing through
 * 1px gaps. The gap trick is how every other grid in this design draws its
 * hairlines, and here it was a bug: a row at opacity 0 let the container's
 * line-coloured background through, so the two unrevealed rows rendered as one
 * solid grey block. `opacity` hides an element's border with it, so a border per
 * row leaves nothing behind. */
.clocks-rows {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.clocks-row {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  gap: var(--banx-space-4);
  align-items: center;
  padding: var(--banx-space-4) var(--banx-space-2);
}

.clocks-row + .clocks-row { border-top: 1px solid var(--banx-line); }

/* The mark ties a row to its ring: same accent, same size as the travelling
 * dot, so the eye connects the two without a leader line. */
.clocks-row-mark {
  width: 8px;
  height: 8px;
  background: var(--banx-mint);
  border-radius: 50%;
}

.clocks-row-text { display: grid; gap: 1px; }
.clocks-row-text b { font-size: var(--banx-text-base); font-weight: 600; letter-spacing: -.01em; }

.clocks-row-text small {
  color: var(--banx-subtle);
  font-family: var(--banx-font-mono);
  font-size: var(--banx-text-2xs);
}

.clocks-row-time {
  color: var(--banx-accent-text);
  font-size: var(--banx-text-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

/* ---- the live readout -------------------------------------------------- */

.clocks-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--banx-space-3) var(--banx-space-6);
  align-items: baseline;
  padding-top: var(--banx-space-4);
  border-top: 1px solid var(--banx-line);
}

.clocks-live { display: flex; gap: var(--banx-space-3); align-items: baseline; }

.clocks-live .k {
  color: var(--banx-subtle);
  font-family: var(--banx-font-mono);
  font-size: var(--banx-text-2xs);
  letter-spacing: var(--banx-tracking-caps);
  text-transform: uppercase;
}

.clocks-live .v { color: var(--banx-accent-text); font-size: var(--banx-text-sm); }

.clocks-note {
  flex: 1 1 22rem;
  color: var(--banx-subtle);
  font-family: var(--banx-font-mono);
  font-size: var(--banx-text-2xs);
  line-height: 1.6;
}

/* ---- initial states, under the class only ------------------------------ */

html.scenes .clocks [data-reveal] { opacity: 0; transform: translateY(14px); }
html.scenes .clocks [data-row] { opacity: 0; transform: translateX(-12px); }


/* The ring labels and the centre label. They had no rest state and so were the
 * only things on screen before the scene drew anything: four cadence values and
 * "1 tx" floating over an empty dial, which reads as a diagram that failed to
 * load rather than one that has not started. Each label now arrives with the
 * ring it belongs to. */
html.scenes .clocks [data-tag],
html.scenes .clocks [data-mid] { opacity: 0; }

/* Both halves of the dash, always - and a GAP LONGER THAN THE PATH.
 *
 * `stroke-dasharray: 1` with no offset means the dash covers the whole path, so
 * every ring was FULLY DRAWN before its timeline started and then jumped to
 * hidden when GSAP set its from-state. Declaring the offset here is what makes
 * the rest state actually undrawn. Every rule in this file that sets a dasharray
 * must set an offset with it.
 *
 * THE GAP IS 200 AND NOT 100, AND THAT IS THE WHOLE OF THE SECOND FIX.
 *
 * A dasharray of `100` is `100 100`, and the pattern REPEATS: at an offset of
 * 100 the gap covers 0-100 and the next dash begins at exactly 100, which on a
 * path whose pathLength is 100 is the path's own end. A dash that begins where
 * the path ends has zero length, and `stroke-linecap: round` paints a
 * zero-length dash as a disc of the stroke's width - so the undrawn spiral was
 * rendering a mint dot at its outer end, on screen before the scene had drawn
 * anything at all. A gap of 200 puts the next dash at 200, off the end of the
 * path, and nothing is painted. The drawing is unchanged: at every offset from
 * 100 down to 0 the first dash covers 0 to (100 - offset) either way.
 *
 * Scoped to html.scenes, so with no JavaScript there is no dash at all and a
 * reader sees four complete rings. */
html.scenes .clocks [data-trail] { stroke-dasharray: 100 200; stroke-dashoffset: 100; }

@media (prefers-reduced-motion: reduce) {
  html.scenes .clocks [data-reveal],
  html.scenes .clocks [data-row],
  html.scenes .clocks [data-tag],
  html.scenes .clocks [data-mid] { opacity: 1; transform: none; }
  html.scenes .clocks [data-trail] { stroke-dasharray: none; stroke-dashoffset: 0; }
}

/* Below the pin breakpoint the dial stacks above its rows: two columns of a
 * square diagram and a list is a desktop composition. */
@media (max-width: 1023px) {
  .clocks { grid-template-columns: minmax(0, 1fr); }
  /* One column, so the row and column spans above have to be released or the
   * dial stays pinned to a column 2 that no longer exists. */
  .clocks-head,
  .clocks-rows-col { grid-column: auto; }
  .clocks-dial {
    grid-row: auto;
    grid-column: auto;
    max-width: min(100%, 30rem);
    order: -1;
  }
  .clocks-text { max-width: none; }
}
