/* ═══ SFTi — THE CHAPTER HEAD. How a shop section announces itself. ══════════════════

   Operator 2026-08-12: "the Chapter theme from rnd index was supposed to be applied to index/
   and shop/ … they got the svgs and no gap down, but failed at the theme and animation swap."
   This card carries the WHOLE idiom from /rnd/index.c.html onto this page's real content — the
   eyebrow, the spine, the pinned head, the travelling underline, the aperture, the curtain and
   the cinematic arrival — and it is the SAME structure app/index/css/section-title.css declares,
   deliberately, so the two public surfaces speak one treatment rather than two dialects of it.

   ── THE SPACING IS MOVED, NOT ADDED ──
   A pinned head needs a painted box, and padding stacked on top of the margins this section
   already owned is exactly the dead vertical band that must not appear. `.section-title` carries
   `margin: 2rem 0 1.2rem` (app/shop/css/layout.css). The head takes that budget and SPLITS it:

       margin 1.1rem + padding 0.9rem = 2rem     above, unchanged
       padding 0.7rem + margin 0.5rem = 1.2rem   below, unchanged

   The head's outer box opens and closes exactly where a bare title did, so the header-to-content
   distance and the rhythm between sections are both untouched.

   ⚠ MEASURED COST OF THE PIN ON THIS PAGE, stated because shop's titles are not index's:
   "WHAT STATIKFINTECH SELLS" renders 100.8px — three lines at 390px — so a pinned head here
   stands about 157px, and with the 144px fixed header roughly 301px of an 844px phone is spoken
   for while that chapter is on screen. Index's titles are one or two lines and cost far less.
   The pin is carried because the theme is what was asked for; if that trade reads wrong on the
   phone, THIS is the declaration to change and the number above is what makes it decidable. */

/* ── THE CHAPTER — the box that makes the pin RELEASE ────────────────────────────────────
   ★★ THIS WRAPPER IS NOT DECORATION AND DELETING IT BREAKS THE PAGE. A `position: sticky` element
   is constrained by its CONTAINING BLOCK — the nearest block-container ancestor. With every head a
   direct child of `main`, `main` IS that ancestor for all of them, and `main`'s box spans the whole
   document: each head docks when it arrives and NEVER releases, so they pile up at the same
   `--header-clearance`. Measured on the shipped page: `<section class="chapter">` x0 against four
   heads. Inside its own section a head's constraint ends with its own content, so the next chapter
   pushes it out of the dock. The ELEMENT is the fix; `position: relative` below is only the anchor
   for absolutely positioned chapter furniture, since the aperture resolves against the head itself. */
.chapter {
  position: relative;
}

.chapter-head {
  position: sticky;
  /* NEVER `top: 0` — app/shared/css/header.css pins the wordmark `fixed; z-index: 100`, so
     viewport-top belongs to the header and a head docking at 0 is painted over by it. The dock is
     the header's OWN measured height, written by app/shared/js/header-clearance.js off its border
     box, because env(safe-area-inset-top) makes that header genuinely taller on a notched phone
     and a literal would dock under it there. */
  top: var(--header-clearance);
  z-index: 12;                    /* over this chapter's cards, under the nav (9999) */
  margin: 1.1rem 0 0.5rem;
  padding: 0.9rem 0 0.7rem;
  /* the page's own canvas colour, faded out downward — the same technique the header uses for its
     phase-out. A pinned head with a flat fill and a hard bottom edge reads as a CUT sliding down
     the page; this one has no edge to see. No backdrop-filter (#218: a composited fixed element
     tracks the layout viewport on iOS and drifts). */
  background: linear-gradient(180deg,
      rgba(10, 10, 15, 0.97) 0%,
      rgba(10, 10, 15, 0.92) 55%,
      rgba(10, 10, 15, 0.70) 80%,
      rgba(10, 10, 15, 0.00) 100%);
}

/* the head owns the rhythm now, so the title inside it carries none of its own */
.chapter-head .section-title { margin: 0; }

.chapter-no {
  display: block;
  margin-bottom: 0.15rem;
  padding-left: 1.2rem;          /* lines up with .section-title's spine inset */
  font-family: 'Orbitron', monospace;
  font-size: 0.6rem;
  font-weight: 700;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--accent-red);
  opacity: 0.85;
}

/* ── THE TRAVELLING UNDERLINE. It tracks DOCUMENT PROGRESS, which is the reason the head is worth
   pinning at all rather than merely announcing: pinned, it doubles as a progress read. `--doc-p`
   is published once per animation frame by app/shared/js/reveal.js.

   ★ THIS RULE IS WHY THAT SCRIPT IS NOT OPTIONAL. With no publisher the transform resolves to its
   `0` fallback — scaleX(0) — and the line is invisible on every chapter. The previous port of this
   theme read that fact and concluded the line should be STATIC. That is backwards: the missing
   publisher was the defect, not the moving rule. Writing the publisher is the fix.

   It starts at the spine's inset and stops at the column edge, so it underlines the two lines
   above it rather than cutting the frame — the one decoration this page is deliberately without.
   The deck's three accents in the wordmark's own order. */
.chapter-head::after {
  content: '';
  position: absolute;
  left: 1.2rem;
  right: 0;
  bottom: 2px;
  height: 1px;
  background: linear-gradient(90deg, var(--accent-red), var(--accent-gold), var(--accent-silver));
  transform: scaleX(var(--doc-p, 0));
  transform-origin: left center;
  opacity: 0.7;
}

/* ── THE APERTURE behind the head. A conic ring masked to a thin annulus so it reads as an iris
   rather than a pie chart, sitting BEHIND the slab so it looks like light bleeding through.

   ★ THE BOX DOES NOT MOVE. Spinning it with `transform: rotate()` makes its axis-aligned bounding
   box 190·(|cos θ|+|sin θ|) — up to 268.7px against a declared 190px — which pushes the document's
   own scrollWidth past the frame and silently widens the backdrop and the nav with it. The mask is
   an annulus centred on the box and therefore angularly SYMMETRIC, so rotating it was always a
   no-op; the only thing the spin ever showed was the conic's colour sweep, and that is driven
   directly through a registered custom property. Identical motion, static box, zero overflow. */
@property --iris-turn {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.aperture {
  position: absolute;
  top: -46px;
  right: -14px;
  width: 190px;
  height: 190px;
  border-radius: 50%;
  pointer-events: none;
  z-index: -1;
  background: conic-gradient(from var(--iris-turn),
      rgba(230, 57, 70, 0.00),
      rgba(230, 57, 70, 0.42),
      rgba(212, 175, 55, 0.50),
      rgba(192, 192, 192, 0.30),
      rgba(230, 57, 70, 0.00));
  -webkit-mask: radial-gradient(circle, transparent 56%, #000 58%, #000 74%, transparent 76%);
          mask: radial-gradient(circle, transparent 56%, #000 58%, #000 74%, transparent 76%);
  animation: irisTurn 34s linear infinite;
  opacity: 0.55;
}
@keyframes irisTurn { to { --iris-turn: 360deg; } }

/* ── THE CURTAIN — full bleed between chapters. `main` pads to 5%, so a band that must reach the
   frame breaks the column with `-50vw + 50%`. body already carries `overflow-x: hidden`
   (app/shop/css/reset.css), which is what makes 100vw safe here.
   ONE margin, not two: with an explicit `width: 100vw` a second negative margin over-constrains
   the box, and an over-constrained block resolves by ignoring one side — which would make the
   bleed depend on writing direction instead of on arithmetic. */
.curtain {
  position: relative;
  margin-left: calc(-50vw + 50%);
  width: 100vw;
  height: 132px;
  overflow: hidden;
  display: flex;
  align-items: center;
  background:
    linear-gradient(180deg, transparent, rgba(230, 57, 70, 0.055) 42%, rgba(212, 175, 55, 0.05) 60%, transparent),
    radial-gradient(70% 120% at 50% 50%, rgba(212, 175, 55, 0.07), transparent 70%);
}
.curtain::before, .curtain::after {
  content: '';
  position: absolute;
  left: 6%; right: 6%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(212, 175, 55, 0.55), transparent);
}
.curtain::before { top: 0; }
.curtain::after  { bottom: 0; }

/* doubled inside the span so the loop has no visible restart, and the band is `overflow: hidden`
   rather than scrollable — a horizontal scroller here would be read by app/shared/js/swipe.js as
   owning the touch, and the page flick would die on every curtain. */
.curtain-run {
  white-space: nowrap;
  font-family: 'Orbitron', monospace;
  font-size: clamp(2.2rem, 11vw, 3.4rem);
  font-weight: 900;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: transparent;
  -webkit-text-stroke: 1px rgba(212, 175, 55, 0.20);
  animation: curtainRun 40s linear infinite;
}
@keyframes curtainRun {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ── THE CINEMATIC ARRIVAL. Cards arrive up, scaled and out of a slight blur, staggered by their
   own `--i`. app/shared/js/reveal.js adds `.is-in` and numbers the children; EVERYTHING here hangs
   off `html.reveal-on`, which only that script sets, so if it never runs nothing is hidden.

   ⚠ `.tier-grid` IS DELIBERATELY NOT A REVEAL TARGET, and this is the one place the rule would be
   dangerous rather than merely decorative. The tier cards are BUILT AFTER LOAD by /app/shop/js/
   from GET /api/v1/billing/plans, so they do not exist when reveal.js numbers children and
   attaches its observer. They would take the `opacity: 0` resting state from this rule and never
   receive the `.is-in` that lifts it — the page's PRICES, permanently invisible. A reveal that can
   hide content is worse than no reveal, so the injected grid keeps its own entrance. */
html.reveal-on .card-grid[data-reveal] > *,
html.reveal-on .carousel-track[data-reveal] > * {
  opacity: 0;
  transform: translateY(34px) scale(0.94);
  filter: blur(6px);
  transition: opacity 0.7s ease,
              transform 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              filter 0.7s ease;
  transition-delay: calc(var(--i, 0) * 90ms);
}
html.reveal-on .card-grid.is-in > *,
html.reveal-on .carousel-track.is-in > * {
  opacity: 1;
  transform: none;
  filter: none;
}

@media (min-width: 769px) {
  .aperture { right: 2%; width: 230px; height: 230px; top: -60px; }
  .curtain  { height: 168px; }
}

@media (prefers-reduced-motion: reduce) {
  .aperture, .curtain-run { animation: none; }
  html.reveal-on .card-grid[data-reveal] > *,
  html.reveal-on .carousel-track[data-reveal] > * {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: none !important;
  }
}
