/* =============================================================
   YourWebDesignAgency - site.css
   Rebuilt 29.08.2026, second pass.

   DESIGN READ
   Agency landing page for US small business owners. Plain-spoken,
   utilitarian language. Light paper, near-black type, one saturated
   green, zero corner radius, editorial grid.
   Dials: VARIANCE 8, MOTION 8, DENSITY 3.

   WHY IT LOOKS NOTHING LIKE THE LIVE SITE
   The live site is near-black with a blue glow. That is one of the
   three looks AI design output clusters around, and the operator
   asked to break from it. The break is also load-bearing: the demo
   sites inside the sequence are dark, so a light shell is what lets
   a visitor tell the agency apart from the work it shows.

   MOTION
   - Reveals: IntersectionObserver plus CSS transitions.
   - Scrubbed: CSS scroll-driven animations on view timelines.
   - No scroll listener anywhere in this project.
   ============================================================= */

/* ---------- Fonts (self-hosted, no external requests) ---------- */
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/space-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('../fonts/space-grotesk-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308,
    U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113,
    U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('../fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* =============================================================
   1 - Tokens
   ============================================================= */
/* Three registered colours drive the entire page. They are the only
   ones that get animated; everything else is derived from them with
   color-mix, so the whole interface follows a single scroll-driven
   shift from paper to violet without a second set of tokens.

   Registering them is what makes them interpolable at all: an
   unregistered custom property flips from one value to the next
   with no in-between. */
@property --paper { syntax: '<color>'; initial-value: #f1f0ec; inherits: true; }
@property --ink   { syntax: '<color>'; initial-value: #14151a; inherits: true; }
@property --go    { syntax: '<color>'; initial-value: #0d7346; inherits: true; }

:root {
  /* Paper. Cool, not the warm cream every AI-built agency page uses.
     It does not stay paper: see the shift in section 24. */
  --paper:    #f1f0ec;
  --ink:      #14151a;

  /* Everything below is derived. Change the two above and the whole
     page moves with them, which is exactly what the scroll does. */
  --paper-2:  color-mix(in srgb, var(--paper) 91%, var(--ink));
  --paper-3:  color-mix(in srgb, var(--paper) 82%, var(--ink));

  --ink-2:    color-mix(in srgb, var(--ink) 76%, var(--paper));
  --ink-3:    color-mix(in srgb, var(--ink) 52%, var(--paper));

  --rule:     color-mix(in srgb, var(--ink) 14%, transparent);
  --rule-2:   color-mix(in srgb, var(--ink) 26%, transparent);
  --rule-3:   color-mix(in srgb, var(--ink) 55%, transparent);

  /* One accent, locked for the whole page. Deep green: it reads as
     "live", which is the thing being sold. It lightens to mint when
     the ground turns violet, because the same green would drop to
     1.7:1 on that ground. Same hue, restepped for the surface, which
     is what a design system does rather than a second accent. */
  --go:       #0d7346;
  --go-deep:  color-mix(in srgb, var(--go) 82%, var(--ink));
  --go-wash:  color-mix(in srgb, var(--go) 12%, transparent);
  --on-go:    color-mix(in srgb, var(--paper) 92%, var(--go));

  /* A STATUS pair, not a second brand accent. Deep rose marks the
     state being replaced, the brand green marks what replaces it.
     The visualisation rules reserve status colours for exactly
     this job and keep them out of the categorical set, so the
     one-accent lock still holds. Both carry white text at 8.6:1
     and 8.7:1, well past AA for the display sizes they run at. */
  --bad:      #8f2d3f;

  /* SHAPE LOCK: the mark's own geometry.
     The logo is three overlapping squircles whose corner radius is
     0.287 of their side. A radius-0 page fought that: the one
     rounded thing on screen was the logo, and it read as imported.

     Applying 0.287 literally would round a 40px button into a pill,
     so the ratio is honoured at the size it was drawn for (a ~33px
     square, radius ~9.4px) and held flat above it. That keeps small
     controls in the same family as the mark without turning large
     panels into blobs.

     One system, three steps, no exceptions except the status dot
     and the sequence window, which IS the mark. */
  --r-lg: 20px;   /* frames, big panels                */
  --r-md: 12px;   /* buttons, cards, labels, inputs    */
  --r-sm: 7px;    /* badges, chips, small tiles        */
  --r-mark: .287; /* the ratio itself, for the window  */

  --ease-out:    cubic-bezier(.23, 1, .32, 1);
  --ease-in-out: cubic-bezier(.77, 0, .175, 1);

  --gutter: clamp(1.25rem, 4vw, 4rem);
  --shell:  1280px;
  --band:   clamp(5rem, 11vh, 8.5rem);

  --sans: 'Space Grotesk', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
}

/* =============================================================
   2 - Reset and base
   ============================================================= */
*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(1rem, .96rem + .2vw, 1.07rem);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* `clip`, not `hidden`. `overflow-x: hidden` makes the body a
     scroll container, and every view() timeline on the page then
     reports currentTime: null and never scrubs. Cost: it hides real
     horizontal overflow too, so the probe in CLAUDE.md has to be
     run by hand after layout changes. */
  overflow-x: clip;
}

h1, h2, h3 {
  margin: 0;
  font-weight: 500;
  letter-spacing: -.035em;
  line-height: 1.02;
  text-wrap: balance;
}
h1 { font-size: clamp(2.4rem, 1.1rem + 4vw, 4.6rem); }
h2 { font-size: clamp(1.9rem, 1.2rem + 2.9vw, 3.3rem); }
h3 { font-size: clamp(1.1rem, 1.02rem + .42vw, 1.35rem); letter-spacing: -.02em; }

p { margin: 0; }
a { color: var(--go-deep); text-decoration: none; }
img, svg { display: block; max-width: 100%; }
ul, ol { margin: 0; padding: 0; list-style: none; }
button { font: inherit; }

:focus-visible { outline: 2px solid var(--go); outline-offset: 3px; }
::selection { background: var(--go); color: var(--on-go); }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  padding: .8rem 1.2rem; background: var(--ink); color: var(--paper); font-weight: 600;
}
.skip-link:focus { left: 0; }

.shell { width: min(100% - (var(--gutter) * 2), var(--shell)); margin-inline: auto; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* =============================================================
   3 - Type roles
   ============================================================= */
.mono {
  font-family: var(--mono);
  font-size: .7rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.lede {
  color: var(--ink-2);
  font-size: clamp(1.02rem, .98rem + .35vw, 1.22rem);
  line-height: 1.55;
  max-width: 56ch;
}
.accent { color: var(--go); }

/* =============================================================
   4 - Buttons. Square, per the shape lock.
   ============================================================= */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .92rem 1.6rem;
  border: 1.5px solid var(--ink);
  border-radius: var(--r-md);
  font-size: .95rem; font-weight: 600; letter-spacing: -.01em;
  white-space: nowrap; cursor: pointer;
  transition: transform 150ms var(--ease-out), background-color 180ms ease, color 180ms ease, border-color 180ms ease;
}
.btn:active { transform: translateY(1px); }

.btn-primary { background: var(--ink); color: var(--paper); }
.btn-go      { background: var(--go); border-color: var(--go); color: var(--on-go); }
.btn-ghost   { background: transparent; color: var(--ink); }
.btn-sm      { padding: .62rem 1.1rem; font-size: .86rem; border-width: 1.25px; }

@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover { background: var(--go); border-color: var(--go); color: var(--on-go); }
  .btn-go:hover      { background: var(--go-deep); border-color: var(--go-deep); }
  .btn-ghost:hover   { background: var(--ink); color: var(--paper); }
}

/* =============================================================
   5 - Header
   ============================================================= */
.site-header {
  position: sticky; top: 0; z-index: 40;
  height: 66px; display: flex; align-items: center;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: border-color 220ms ease;
}
.site-header[data-stuck] { border-bottom-color: var(--ink); }

.header-inner { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; }

.brand {
  display: inline-flex; align-items: center; gap: .55rem;
  color: var(--ink); font-weight: 600; letter-spacing: -.03em; font-size: 1.02rem;
}
.brand .mark { width: 20px; height: 18px; flex: none; }
.brand .mark rect { fill: none; stroke: currentColor; }
.brand em { font-style: normal; color: var(--go); }

.site-nav { display: flex; align-items: center; gap: 1.7rem; }
.site-nav a { color: var(--ink-2); font-size: .9rem; font-weight: 500; transition: color 160ms ease; }
@media (hover: hover) and (pointer: fine) { .site-nav a:hover { color: var(--ink); } }

.nav-toggle {
  display: none; width: 40px; height: 40px;
  align-items: center; justify-content: center;
  background: transparent; border: 1.5px solid var(--ink); color: var(--ink); cursor: pointer; border-radius: var(--r-md);
}
.nav-toggle span { display: block; width: 15px; height: 1.5px; background: currentColor;
  box-shadow: 0 -5px 0 currentColor, 0 5px 0 currentColor; }

@media (max-width: 920px) {
  .nav-toggle { display: inline-flex; }
  .site-nav {
    position: absolute; inset: 66px 0 auto;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: .25rem var(--gutter) 1.3rem;
    background: var(--paper); border-bottom: 1.5px solid var(--ink);
    opacity: 0; transform: translateY(-8px); pointer-events: none;
    transition: opacity 190ms var(--ease-out), transform 190ms var(--ease-out);
  }
  .site-nav[data-open] { opacity: 1; transform: none; pointer-events: auto; }
  .site-nav a { padding: .85rem 0; border-bottom: 1px solid var(--rule); font-size: 1rem; }
  .site-nav .btn { margin-top: 1rem; }
}

/* =============================================================
   7 - Reveal system
   ============================================================= */
[data-reveal] {
  opacity: 0; transform: translateY(20px);
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
}
[data-reveal].is-in { opacity: 1; transform: none; }

[data-stagger] > * {
  opacity: 0; transform: translateY(16px);
  transition: opacity 540ms var(--ease-out), transform 540ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 55ms);
}
[data-stagger].is-in > * { opacity: 1; transform: none; }

.line-mask { display: block; overflow: hidden; padding-bottom: .06em; }
.line-mask > span {
  display: block; transform: translateY(105%);
  transition: transform 780ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 85ms);
}
.is-in .line-mask > span { transform: none; }

/* `html[data-motion="calm"]`, NICHT `@media (prefers-reduced-motion:
   reduce)`. Das Attribut ist die EFFEKTIVE Entscheidung: Systemvorgabe
   plus eine ausdrueckliche Wahl des Lesers. Haengte die Regel an der
   Medienabfrage, koennte ein Leser, dessen System faelschlich „reduce"
   meldet, die Bewegung nie einschalten - die Abfrage bliebe wahr und
   wuerde ihm die Transitions weiter wegnehmen. Genau dieser Fall ist am
   02.09.2026 auf zwei von fuenf Geraeten eingetreten. */
html[data-motion="calm"] [data-reveal],
html[data-motion="calm"] [data-stagger] > *,
html[data-motion="calm"] .line-mask > span {
  opacity: 1 !important; transform: none !important;
  transition: opacity 180ms ease !important;
}

/* =============================================================
   8 - Hero
   Type carries the whole thing. The headline is set to the full
   width of the viewport and is meant to run past the right edge:
   the clipping is the device, not an accident.

   One CTA, not two. The conversion research is unambiguous on
   this and the second button was costing the first one weight.
   ============================================================= */
.hero {
  position: relative;
  min-height: calc(100dvh - 66px);
  display: grid; align-content: center;
  padding-block: clamp(2rem, 6vh, 4rem);
  overflow: hidden;
}

/* Three squircles from the mark, blown up and turning. The page
   sells design; its own background may as well be the logo doing
   something. Slow, behind everything, never in the way. */
.hero-orbit {
  position: absolute; z-index: 0; pointer-events: none;
  top: 50%; right: -14vw;
  width: min(78vw, 780px); aspect-ratio: 1;
  translate: 0 -50%;
  opacity: .5;
}
.hero-orbit svg { width: 100%; height: 100%; }
.hero-orbit rect { fill: none; stroke: var(--ink); stroke-width: .6; vector-effect: non-scaling-stroke; }
.hero-orbit .o-1 { transform-origin: 24px 22px; }
.hero-orbit .o-2 { transform-origin: 24px 22px; }
.hero-orbit .o-3 { transform-origin: 24px 22px; }

.hero-inner { position: relative; z-index: 1; }

.hero-eyebrow {
  display: inline-flex; align-items: center; gap: .6rem;
  padding: .38rem .85rem; margin-bottom: clamp(1rem, 3vh, 2rem);
  border: 1.5px solid var(--ink); border-radius: var(--r-md);
  font-family: var(--mono); font-size: .68rem; letter-spacing: .12em; text-transform: uppercase;
}
.hero-eyebrow b { font-weight: 700; }
.hero-eyebrow i { width: 7px; height: 7px; border-radius: 50%; background: var(--go); flex: none; }

/* The oversized line. 14vw is deliberate: at any desktop width the
   second line overshoots the container and is cut by the viewport
   edge, which is what the trend actually is. It is not centred and
   it is not balanced; it is set flush left and allowed to run. */
.hero h1 {
  font-size: clamp(3rem, 14vw, 13rem);
  line-height: .86;
  letter-spacing: -.055em;
  text-wrap: nowrap;
  margin-bottom: clamp(1.4rem, 4vh, 2.6rem);
}
.hero h1 .line-mask { padding-bottom: .04em; }
.hero h1 .row-2 { padding-left: clamp(1rem, 8vw, 8rem); }

.hero-foot {
  display: flex; flex-wrap: wrap; align-items: end;
  justify-content: space-between; gap: clamp(1.2rem, 3vw, 3rem);
}
.hero .lede { max-width: 44ch; }

/* Seven blocks, one per delivery day, filling as the page settles. */
.days { min-width: min(100%, 280px); }
.days-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-bottom: .7rem; }
.days-grid i {
  display: block; height: clamp(30px, 5vw, 52px);
  background: var(--paper-3); border-radius: var(--r-sm);
  transform: scaleY(.16); transform-origin: bottom;
  transition: transform 620ms var(--ease-out), background-color 620ms var(--ease-out);
  transition-delay: calc(var(--d) * 80ms + 260ms);
}
.is-in .days-grid i { transform: scaleY(1); background: var(--ink); }
.is-in .days-grid i:last-child { background: var(--go); }
.days-legend { display: flex; justify-content: space-between; font-family: var(--mono);
  font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
.days-legend b:last-child { color: var(--go); }

html[data-motion="calm"] .days-grid i { transform: scaleY(1); background: var(--ink); transition: none; }
html[data-motion="calm"] .days-grid i:last-child { background: var(--go); }

/* =============================================================
   10 - The sequence
   Full bleed. The demo is not a thumbnail on a page, it is the
   page: the stage runs edge to edge and the whole viewport is the
   specimen.

   Around it sits an even mat on all four sides, the way a framed
   print is matted, with the label set into the top band. The mat
   changes colour at the handover, which is the whole before and
   after signal in one move.

   Colour: the mat is a STATUS pair, not two brand accents. Deep
   rose for the state being replaced, the brand green for what
   replaces it. The design-system rules reserve status colours for
   exactly this and keep them out of the categorical set, so the
   one-accent lock still holds.
   ============================================================= */
@property --p { syntax: '<number>'; initial-value: 0; inherits: true; }

.seq-wrap { position: relative; height: 780vh; }
.seq-stage {
  position: sticky; top: 66px;
  height: calc(100dvh - 66px);
  overflow: hidden;
  background: var(--mat);
  --p: 0;

  /* Act boundaries as fractions of the pin.
     The zoom is down to 6%: two complaints in a row said it took
     too long to reach the demo, and the fix is not a slower reveal
     but less of it. Everything the zoom gave up went into the
     holds, where the arrows stand. */
  --a1: .06;   /* zoom done            */
  --a2: .09;   /* before act settled   */
  --a3: .50;   /* before act done      */
  --a4: .58;   /* handover done        */

  --swap: clamp(0, calc((var(--p) - var(--a3)) / (var(--a4) - var(--a3))), 1);
  --mat: color-mix(in srgb, var(--go) calc(var(--swap) * 100%), var(--bad));
}

/* The mat. Even on all four sides, thick enough to read as a frame
   rather than a border, with the label living in the top band. */
.demo-frame {
  position: absolute; inset: 0;
  padding: var(--mat-w) ;
  --mat-w: clamp(2.6rem, 7vh, 5.5rem);
  --arrive: clamp(0, calc((var(--p) - var(--a1)) * 40), 1);
  opacity: var(--arrive);
}
.demo-bar {
  position: absolute; z-index: 6;
  inset: 0 0 auto; height: var(--mat-w);
  display: grid; place-items: center;
  padding-inline: 1rem;
  color: var(--paper);
  pointer-events: none;
}
.demo-label {
  grid-area: 1 / 1;
  font-size: clamp(1rem, .7rem + 1.9vw, 2.6rem);
  font-weight: 500; letter-spacing: -.035em; text-align: center;
  --in:  clamp(0, calc((var(--p) - var(--s)) * 30), 1);
  --out: clamp(0, calc((var(--e) - var(--p)) * 30), 1);
  opacity: calc(var(--in) * var(--out));
  transform: translateY(calc((1 - var(--in)) * 10px));
}
.demo-label b { font-weight: 700; text-transform: uppercase; letter-spacing: .02em; }

.demo-screen {
  position: relative; width: 100%; height: 100%;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--ink);
}

.seq-screen {
  --zoom: clamp(0, calc(var(--p) / var(--a1)), 1);
  --side: calc(24px + var(--zoom) * (max(100vw, 100dvh) * 1.6 - 24px));
  position: absolute; inset: 0;
  clip-path: inset(
    calc(50% - var(--side) / 2) calc(50% - var(--side) / 2)
    round calc(var(--side) * var(--r-mark))
  );
}

/* The mark. Sized, never scaled: scale(calc(<length>/24)) divides a
   length by a number, which yields a length, and scale() takes only
   a number, so the declaration is dropped and nothing grows. */
.seq-mark {
  position: absolute; top: 50%; left: 50%; z-index: 7; pointer-events: none;
  --zoom: clamp(0, calc(var(--p) / var(--a1)), 1);
  --side: calc(24px + var(--zoom) * (max(100vw, 100dvh) * 1.6 - 24px));
  width: var(--side); height: var(--side);
  margin: calc(var(--side) / -2) 0 0 calc(var(--side) / -2);
  opacity: calc(1 - clamp(0, calc(var(--zoom) / .55), 1));
  rotate: calc(var(--zoom) * -18deg);
}
.seq-mark svg { width: 100%; height: 100%; overflow: visible; }
.seq-mark rect { fill: none; vector-effect: non-scaling-stroke; }
.seq-mark .m-1 { transform: translate(calc(var(--zoom) * -2px), calc(var(--zoom) * 2px)); }
.seq-mark .m-2 { transform: translate(calc(var(--zoom) * -1px), calc(var(--zoom) * 1px)); }

.seq-chrome {
  position: absolute; z-index: 5; inset: 0 0 auto; height: 34px;
  display: flex; align-items: center; gap: .45rem; padding-inline: .8rem;
  background: #101116; border-bottom: 1px solid rgba(255, 255, 255, .12);
  font-family: var(--mono); font-size: .62rem; letter-spacing: .06em; color: rgba(255, 255, 255, .5);
  opacity: clamp(0, calc((var(--p) - var(--a1)) * 40), 1);
}
/* Direct children only. The address bar wraps its three lines in
   <i> too, and an unscoped `.seq-chrome i` turned those into 7px
   dots: the URL strip was one line tall and never scrolled. */
.seq-chrome > i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, .22); flex: none; }
.seq-url { position: relative; margin-left: .6rem; width: 30ch; height: 1.15em; overflow: hidden; }
.seq-url i { position: absolute; top: 0; left: 0; font-style: normal; }
.seq-url i span { display: block; height: 1.15em; white-space: nowrap; }
.seq-url .u-old { opacity: calc(1 - clamp(0, calc((var(--p) - var(--a3)) / (var(--a4) - var(--a3))), 1)); }
.seq-url .u-new { opacity: clamp(0, calc((var(--p) - var(--a3)) / (var(--a4) - var(--a3))), 1); }

.site { position: absolute; inset: 34px 0 0; overflow: hidden; }
.site-new { z-index: 1; }
.site-old {
  z-index: 2;
  --close: clamp(0, calc((var(--p) - var(--a3)) / (var(--a4) - var(--a3))), 1);
  --cside: calc(max(100vw, 100dvh) * 1.5 * (1 - var(--close)));
  clip-path: inset(
    calc(50% - var(--cside) / 2) calc(50% - var(--cside) / 2)
    round calc(var(--cside) * var(--r-mark))
  );
  filter: blur(calc(var(--close) * 8px)) saturate(calc(1 - var(--close) * .9));
  transform: scale(calc(1 - var(--close) * .07));
}

/* Three pages stacked, travelling as one strip. This is what makes
   the demo more than a screenshot: the site scrolls itself. */
.strip { height: 300%; display: grid; grid-template-rows: repeat(3, 1fr); will-change: transform; }
/* STEPPED, NOT CONTINUOUS. The first version ran the strip linearly
   across the whole act, so the page kept travelling while an arrow
   was on screen and the arrow stopped pointing at anything. It has
   to hold still for as long as something is being pointed at.

   `--pg` is a staircase in page units: it sits at 0 while the first
   page is annotated, ramps to 1 over a short travel window, sits at
   1, ramps to 2, sits at 2. Each ramp is 3% of the pin, so the page
   change reads as a deliberate move rather than a jump.

   The arrow timings in index.html live inside the flat parts. Move
   a hold here and the arrows have to move with it. */
.site-old .strip,
.seq-url .u-old {
  --pg: calc(
    clamp(0, calc((var(--p) - .26) / .025), 1) +
    clamp(0, calc((var(--p) - .40) / .025), 1)
  );
  transform: translateY(calc(var(--pg) * -33.3333%));
}
.site-new .strip,
.seq-url .u-new {
  --pg: calc(
    clamp(0, calc((var(--p) - .74) / .025), 1) +
    clamp(0, calc((var(--p) - .88) / .025), 1)
  );
  transform: translateY(calc(var(--pg) * -33.3333%));
}
.site-old .seq-url span, .site-new .seq-url span { transition: none; }

/* --- Act 2: the site being replaced --------------------------- */
.op {
  padding: clamp(.9rem, 2.4vh, 1.9rem) clamp(1rem, 4.5vw, 3.5rem);
  background: #fdfdf8; color: #1b1b1b;
  font-family: 'Times New Roman', Times, serif;
  display: flex; flex-direction: column; overflow: hidden;
}
.op-title { color: #00126b; font-size: clamp(1.05rem, 2.8vw, 2.3rem); font-weight: 700; text-align: center; }
.op-tag { text-align: center; font-size: clamp(.66rem, 1.2vw, .9rem); color: #555; margin-top: .15rem; }
.op-nav {
  margin-top: clamp(.5rem, 1.4vh, 1rem); padding: .25rem 0;
  border-block: 3px double #00126b; text-align: center;
  font-size: clamp(.58rem, 1vw, .8rem);
}
.op-nav a { color: #0000ee; text-decoration: underline; margin-inline: clamp(.3rem, .9vw, .9rem); }
.op-h2 { color: #00126b; font-size: clamp(.85rem, 1.7vw, 1.3rem); margin: clamp(.6rem, 1.6vh, 1.1rem) 0 .4rem; }
.op-body { flex: 1; display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: clamp(.9rem, 2.6vw, 2.2rem); min-height: 0; padding-top: .6rem; }
.op-body p { font-size: clamp(.64rem, 1.05vw, .88rem); line-height: 1.5; margin-bottom: .6rem; }
.op-photo {
  margin-top: .5rem; border: 3px ridge #b9b9b9; background: #e9e9e4;
  height: clamp(60px, 19vh, 175px); display: grid; place-items: center;
  color: #8a8a82; font-size: clamp(.58rem, .95vw, .78rem);
}
.op-side { border: 2px inset #c8c8c0; background: #eeeee8; padding: clamp(.45rem, 1.1vw, .85rem);
  font-size: clamp(.58rem, .95vw, .8rem); line-height: 1.6; align-self: start; }
.op-side b { display: block; color: #00126b; margin-top: .45rem; }
.op-side b:first-child { margin-top: 0; }
.op-table { width: 100%; border-collapse: collapse; font-size: clamp(.58rem, .95vw, .8rem); }
.op-table td, .op-table th { border: 1px solid #b9b9b9; padding: .25rem .45rem; text-align: left; }
.op-table th { background: #e2e2da; color: #00126b; }
.op-form { border: 2px inset #c8c8c0; background: #eeeee8; padding: clamp(.5rem, 1.2vw, .9rem); font-size: clamp(.58rem, .95vw, .8rem); }
.op-form label { display: block; margin-bottom: .45rem; }
.op-form input, .op-form textarea {
  display: block; width: 100%; margin-top: .12rem;
  border: 1px solid #999; background: #fff; font: inherit; padding: .15rem .25rem;
}
.op-submit { margin-top: .5rem; border: 2px outset #ddd; background: #e4e4dc; padding: .12rem .7rem; font: inherit; }
.op-foot {
  border-top: 1px solid #c4c4bc; margin-top: clamp(.4rem, 1.2vh, .9rem); padding-top: .4rem;
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: .5rem;
  font-size: clamp(.54rem, .86vw, .72rem); color: #6a6a62;
}
.op-counter { font-family: var(--mono); background: #111; color: #33ff55; padding: .05rem .35rem; letter-spacing: .08em; }

/* --- Act 4: what replaces it ---------------------------------
   Deliberately a different world from the agency page: dark,
   warm amber, tight mono labels. The agency site is light with a
   green accent, and the point of the contrast is that the studio
   does not put its own look on a client's business. ------------- */
.np {
  padding: clamp(1rem, 2.6vh, 2.1rem) clamp(1.1rem, 4.5vw, 3.5rem);
  background: linear-gradient(168deg, #1b1c21, #131418);
  color: #f2efe9; display: flex; flex-direction: column; gap: clamp(.6rem, 1.9vh, 1.3rem);
  overflow: hidden;
}
.np-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.np-logo { font-weight: 600; letter-spacing: -.03em; font-size: clamp(.8rem, 1.4vw, 1.1rem); }
.np-logo em { font-style: normal; color: #e8933a; }
.np-nav { display: flex; gap: clamp(.6rem, 1.6vw, 1.4rem); font-size: clamp(.6rem, 1vw, .82rem); color: rgba(242, 239, 233, .62); }
.np-nav b { color: #f2efe9; font-weight: 500; }
.np-call {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem 1rem; background: #e8933a; color: #191308;
  font-size: clamp(.64rem, 1.1vw, .86rem); font-weight: 700;
}
.np-h { font-size: clamp(1.35rem, 3.9vw, 3.2rem); font-weight: 500; letter-spacing: -.04em; line-height: 1.04; max-width: 15ch; margin-top: clamp(.3rem, 2.4vh, 1.5rem); }
.np-p { color: rgba(242, 239, 233, .68); font-size: clamp(.68rem, 1.15vw, .96rem); max-width: 44ch; }
.np-acts { display: flex; flex-wrap: wrap; gap: .55rem; }
.np-acts span { padding: .52rem 1.15rem; font-size: clamp(.64rem, 1.1vw, .86rem); font-weight: 600; }
.np-acts .a { background: #e8933a; color: #191308; }
.np-acts .b { border: 1.5px solid rgba(242, 239, 233, .34); color: #f2efe9; }
.np-tiles { margin-top: auto; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(.45rem, 1.1vw, .85rem); }
.np-tile { border: 1px solid rgba(242, 239, 233, .16); padding: clamp(.55rem, 1.3vw, .95rem); }
.np-tile b { display: block; font-size: clamp(.68rem, 1.1vw, .9rem); font-weight: 600; }
.np-tile span { font-size: clamp(.56rem, .95vw, .76rem); color: rgba(242, 239, 233, .5); }
.np-tile:first-child { background: rgba(232, 147, 58, .14); border-color: rgba(232, 147, 58, .4); }
.np-rows { display: grid; gap: 1px; background: rgba(242, 239, 233, .14); border: 1px solid rgba(242, 239, 233, .14); }
.np-row { background: #1b1c21; padding: clamp(.5rem, 1.2vw, .85rem) clamp(.6rem, 1.4vw, 1rem);
  display: flex; justify-content: space-between; gap: 1rem; font-size: clamp(.62rem, 1.05vw, .85rem); }
.np-row b { font-weight: 600; }
.np-row span { color: #e8933a; font-weight: 600; white-space: nowrap; }
.np-slots { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .4rem; }
.np-slot { border: 1px solid rgba(242, 239, 233, .2); padding: .5rem .3rem; text-align: center; font-size: clamp(.56rem, .95vw, .76rem); }
.np-slot.on { background: #e8933a; color: #191308; border-color: #e8933a; font-weight: 700; }
.np-foot {
  display: flex; flex-wrap: wrap; gap: 1.1rem; padding-top: clamp(.35rem, 1.2vh, .8rem);
  border-top: 1px solid rgba(242, 239, 233, .14);
  font-family: var(--mono); font-size: clamp(.52rem, .8vw, .66rem);
  letter-spacing: .1em; text-transform: uppercase; color: rgba(242, 239, 233, .42);
}

/* --- The arrows. Dominant on purpose: this is the argument. --- */
.cues { position: absolute; inset: 34px 0 0; z-index: 4; pointer-events: none; }
.cue {
  position: absolute; inset: 0; margin: 0;
  --in:  clamp(0, calc((var(--p) - var(--s)) * 46), 1);
  --out: clamp(0, calc((var(--e) - var(--p)) * 46), 1);
  opacity: calc(var(--in) * var(--out));
}
.cue-dot {
  position: absolute; left: var(--x); top: var(--y);
  width: 26px; height: 26px; margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 3px solid var(--go);
  background: rgba(13, 115, 70, .2);
  box-shadow: 0 0 0 6px rgba(13, 115, 70, .16);
  transform: scale(calc(.35 + var(--in) * .65));
}
.cue-dot::after {
  content: ""; position: absolute; inset: 5px; border-radius: 50%; background: var(--go);
}
/* Anchored at the dot, rotated toward the label. --len and --ang are
   written by site.js from measured boxes: the dot and the label are
   placed independently, so nothing in CSS knows the distance. */
.cue-line {
  position: absolute; left: var(--x); top: var(--y);
  height: 3px; width: calc(var(--len, 0px) * var(--in));
  transform-origin: 0 50%; rotate: var(--ang, 0deg);
  background: var(--go);
}
.cue-label {
  position: absolute; left: var(--lx); top: var(--ly);
  width: min(34%, 340px); min-width: 190px;
  padding: .85rem 1rem .9rem;
  border: 2px solid var(--go);
  background: var(--paper);
  color: var(--ink);
  box-shadow: 8px 8px 0 rgba(13, 115, 70, .25);
  font-family: var(--sans); font-size: clamp(.74rem, 1.05vw, .9rem); line-height: 1.4;
  transform: translate(calc((1 - var(--in)) * -10px), calc((1 - var(--in)) * 10px));
}
.cue-label b {
  display: block; font-weight: 700; font-size: clamp(.82rem, 1.2vw, 1.02rem);
  letter-spacing: -.02em; margin-bottom: .22rem;
}
.cue-n {
  position: absolute; top: -2px; left: -2px;
  min-width: 26px; height: 26px; padding-inline: .35rem;
  display: grid; place-items: center;
  background: var(--go); color: var(--on-go);
  font-family: var(--mono); font-size: .68rem; font-weight: 700;
  transform: translate(-100%, 0);
}
.cues-new .cue-label { box-shadow: 8px 8px 0 rgba(232, 147, 58, .3); border-color: #e8933a; }
.cues-new .cue-n { background: #e8933a; color: #191308; }
.cues-new .cue-dot { border-color: #e8933a; background: rgba(232, 147, 58, .22); box-shadow: 0 0 0 6px rgba(232, 147, 58, .18); }
.cues-new .cue-dot::after { background: #e8933a; }
.cues-new .cue-line { background: #e8933a; }

/* --- Stage furniture ----------------------------------------- */
.seq-caption {
  position: absolute; z-index: 5; left: 0; right: 0; top: calc(34px + clamp(.6rem, 2.4vh, 1.4rem));
  display: grid; justify-items: center; text-align: center; padding-inline: var(--gutter);
  pointer-events: none;
}
.seq-caption > * { grid-area: 1 / 1; }
.cap {
  display: inline-flex; flex-wrap: wrap; align-items: baseline; justify-content: center; gap: .1rem .7rem;
  padding: .45rem 1.1rem; border: 1.5px solid var(--ink); background: var(--paper);
  --in:  clamp(0, calc((var(--p) - var(--s)) * 46), 1);
  --out: clamp(0, calc((var(--e) - var(--p)) * 46), 1);
  opacity: calc(var(--in) * var(--out));
  transform: translateY(calc((1 - var(--in)) * 8px));
}
.cap h2 { font-size: clamp(.9rem, .84rem + .42vw, 1.16rem); font-weight: 700; letter-spacing: -.02em; }
.cap p { color: var(--ink-2); font-size: clamp(.72rem, .92vw, .85rem); }

.seq-acts {
  position: absolute; z-index: 5; left: 0; right: 0; bottom: clamp(.6rem, 2.2vh, 1.4rem);
  display: flex; justify-content: center; gap: clamp(.5rem, 1.8vw, 1.6rem);
  padding-inline: var(--gutter); pointer-events: none;
}
.seq-acts span {
  font-family: var(--mono); font-size: clamp(.5rem, .76vw, .64rem);
  letter-spacing: .14em; text-transform: uppercase;
  padding-top: .5rem; border-top: 2px solid rgba(255, 255, 255, .2);
  min-width: clamp(44px, 11vw, 124px); text-align: center;
  --on: calc(clamp(0, calc((var(--p) - var(--s)) * 70), 1) * clamp(0, calc((var(--e) - var(--p)) * 70), 1));
  color: color-mix(in srgb, #ffffff calc(var(--on) * 100%), rgba(255, 255, 255, .34));
  border-top-color: color-mix(in srgb, var(--go) calc(var(--on) * 100%), rgba(255, 255, 255, .2));
}

@media (max-width: 900px) {
  .seq-wrap { height: 820vh; }
  .op-body { grid-template-columns: 1fr; }
  .op-side { display: none; }
  .np-tiles { grid-template-columns: 1fr; }
  .np-tile:nth-child(n + 3) { display: none; }
  .np-nav { display: none; }
  .np-slots { grid-template-columns: repeat(2, 1fr); }
  .seq-acts span { min-width: 0; letter-spacing: .06em; }
}

/* The full-width label stack is for phones only. It was on the
   900px breakpoint and fired on tablets too, where the frame is
   still big enough to annotate properly: the labels then covered
   the demo they were pointing at. Below 900px there is genuinely
   no room beside the mock - that is the same line at which the demo
   itself goes single-column - and there the annotations become the
   content. */
@media (max-width: 900px) {
  .cue-label { left: 3%; width: 94%; min-width: 0; padding: .6rem .75rem .6rem 2.5rem; }
  /* The number hangs off the left edge on desktop. At 375px the
     labels start at 3%, so it hung off the screen instead. Inside
     the box, with the text indented to clear it. */
  .cue-n { transform: none; left: 0; top: 0; }
  /* die Slots stehen jetzt in Abschnitt 36, unten verankert */
}

/* Ohne Film gibt es keine Sequenz. Was dann an ihrer Stelle steht,
   stand bis zum 02.09.2026 hier als `@supports not (animation-timeline:
   view())` und griff damit nur im halben Fall. Es steht jetzt
   vollstaendig in **Abschnitt 40** am Fuss dieser Datei, weil dort
   BEIDE Ausfaelle abgedeckt sind: fehlende Unterstuetzung UND
   abgeschaltete Bewegung. Nicht hier ergaenzen. */

/* =============================================================

/* =============================================================
   16 - Closing
   The flooding block that stood here is gone; see section 26.
   panel is gone: the page asks for a decision here, and a block
   that fills with colour as you arrive carries that better than
   one that was already coloured before you got to it.
   ============================================================= */

/* =============================================================
   17 - Footer
   ============================================================= */
.site-footer { border-top: 1.5px solid var(--ink); padding-block: clamp(2.6rem, 5vw, 4rem) 1.8rem; }
.foot-grid { display: grid; grid-template-columns: minmax(0, 1.4fr) repeat(2, minmax(0, 1fr)); gap: clamp(1.8rem, 4vw, 3.2rem); padding-bottom: 2.2rem; border-bottom: 1px solid var(--rule); }
.foot-grid p { color: var(--ink-2); font-size: .92rem; margin-top: .9rem; max-width: 38ch; }
.foot-col h3 { font-family: var(--mono); font-size: .66rem; font-weight: 500; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-3); margin-bottom: .9rem; }
.foot-col a { display: block; padding-block: .34rem; color: var(--ink-2); font-size: .92rem; transition: color 160ms ease; }
@media (hover: hover) and (pointer: fine) { .foot-col a:hover { color: var(--ink); } }
.foot-bottom { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; padding-top: 1.6rem; color: var(--ink-3); font-size: .82rem; }
@media (max-width: 760px) { .foot-grid { grid-template-columns: 1fr; } }

/* =============================================================
   18 - Scroll-driven layer
   Everything above works without this block.
   ============================================================= */
/* STRUKTUR - laeuft in JEDEM Tier, in dem der Film laeuft, auch bei
   reduzierter Bewegung. `--p` ist keine Bewegung, sondern die Stelle im
   Ablauf: daraus leiten die Beats ihre Deckkraft ab. Wer das an
   `prefers-reduced-motion` haengt, nimmt dem Leser nicht die Bewegung,
   sondern den Inhalt - genau der Fehler vom 02.09.2026.
   `html[data-drive="css"]`, weil derselbe Wert sonst zweimal gesetzt
   wuerde: nativ hier und aus site.js. Siehe Abschnitt 41. */
@supports (animation-timeline: view()) {
  /* The timeline on the tall wrapper, not on the sticky stage. A
     pinned element keeps its position in the scrollport, so view()
     measured on the stage itself reports no progress at all and
     nothing ever scrubs. `entry 100% exit 0%` is exactly the
     stretch during which the sticky child is pinned. */
  html[data-drive="css"] .seq-wrap { view-timeline-name: --seq; }
  html[data-drive="css"] .seq-stage {
    animation: seq-drive linear both;
    animation-timeline: --seq;
    animation-range: entry 100% exit 0%;
  }
  @keyframes seq-drive { from { --p: 0; } to { --p: 1; } }
}

/* BEWEGUNG - nur wenn sie auch erlaubt ist. */
@supports (animation-timeline: view()) {
  html[data-drive="css"][data-motion="full"] .hero-field {
    animation: drift linear both;
    animation-timeline: scroll(root);
    animation-range: 0 90vh;
  }
  @keyframes drift { to { transform: translateY(7%); } }

  /* The delivery line fills as the four steps pass. */
  html[data-drive="css"][data-motion="full"] .tl-spine i {
    animation: spine linear both;
    animation-timeline: view();
    animation-range: cover 22% cover 82%;
  }
  @keyframes spine { from { transform: scaleY(0); } to { transform: scaleY(1); } }
}

/* Die Fuellung der Lieferlinie ohne Film: Abschnitt 40. */

/* =============================================================
   19 - Legal pages
   Long-form reading: one narrow column, no motion at all.
   ============================================================= */
.noscript-nav { display: none; }

.breadcrumbs { width: min(100% - (var(--gutter) * 2), 760px); margin: clamp(1.8rem, 4vh, 3rem) auto 0; font-size: .84rem; color: var(--ink-3); }
.breadcrumbs ol { display: flex; flex-wrap: wrap; gap: .5rem; }
.breadcrumbs li + li::before { content: "/"; margin-right: .5rem; color: var(--rule-2); }
.breadcrumbs a { color: var(--ink-2); }

main.legal { width: min(100% - (var(--gutter) * 2), 760px); margin: clamp(1.2rem, 3vh, 2rem) auto clamp(4rem, 9vh, 6.5rem); }
main.legal h1 { font-size: clamp(2rem, 1.5rem + 2vw, 3rem); margin-bottom: .5rem; }
main.legal h2 { font-size: clamp(1.15rem, 1.05rem + .55vw, 1.45rem); margin-top: 2.6rem; margin-bottom: .6rem; padding-top: 1.5rem; border-top: 1.5px solid var(--ink); }
main.legal h3 { margin-top: 1.7rem; margin-bottom: .45rem; }
main.legal p, main.legal li { color: var(--ink-2); }
main.legal p { margin-bottom: .95rem; }
main.legal strong { color: var(--ink); font-weight: 600; }
main.legal a { text-decoration: underline; text-underline-offset: 3px; }
main.legal ul, main.legal ol { margin: 0 0 1.1rem; padding-left: 1.25rem; list-style: disc; }
main.legal ol { list-style: decimal; }
main.legal li { margin-bottom: .42rem; }
main.legal table { width: 100%; border-collapse: collapse; margin-bottom: 1.3rem; font-size: .92rem; }
main.legal th, main.legal td { text-align: left; padding: .65rem .75rem; border-bottom: 1px solid var(--rule); color: var(--ink-2); }
main.legal th { color: var(--ink); font-weight: 600; }
main.legal .table-scroll { overflow-x: auto; }

.updated { font-family: var(--mono); font-size: .7rem; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); margin-bottom: 2.2rem; }
.box { padding: 1.2rem 1.4rem; margin-bottom: 1.3rem; border: 1.5px solid var(--ink); background: var(--paper-2); }
.box p:last-child { margin-bottom: 0; }

/* --- Radio wiring -------------------------------------------
   Ten rules per state instead of a loop, because CSS has no
   loop and :has() is not needed here: the inputs are siblings
   of the grid, so ~ reaches everything. Works with no JS. */
.cfg-radio:focus-visible + label,
.fx, .fxc { display: none; }
#ex-0:checked ~ .cfg-grid [data-for="0"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-1:checked ~ .cfg-grid [data-for="1"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-2:checked ~ .cfg-grid [data-for="2"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-3:checked ~ .cfg-grid [data-for="3"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-4:checked ~ .cfg-grid [data-for="4"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-5:checked ~ .cfg-grid [data-for="5"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-6:checked ~ .cfg-grid [data-for="6"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-7:checked ~ .cfg-grid [data-for="7"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-8:checked ~ .cfg-grid [data-for="8"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-9:checked ~ .cfg-grid [data-for="9"] { display: block; animation: fx-in 380ms var(--ease-out) both; }
#ex-0:checked ~ .cfg-grid label[for="ex-0"] { background: var(--ink); color: var(--paper); }
#ex-0:checked ~ .cfg-grid label[for="ex-0"] > span:last-child { color: var(--paper); }
#ex-0:checked ~ .cfg-grid label[for="ex-0"] .free { color: #63d69d; }
#ex-0:checked ~ .cfg-grid label[for="ex-0"] .never { color: rgba(241,240,236,.62); }
#ex-1:checked ~ .cfg-grid label[for="ex-1"] { background: var(--ink); color: var(--paper); }
#ex-1:checked ~ .cfg-grid label[for="ex-1"] > span:last-child { color: var(--paper); }
#ex-1:checked ~ .cfg-grid label[for="ex-1"] .free { color: #63d69d; }
#ex-1:checked ~ .cfg-grid label[for="ex-1"] .never { color: rgba(241,240,236,.62); }
#ex-2:checked ~ .cfg-grid label[for="ex-2"] { background: var(--ink); color: var(--paper); }
#ex-2:checked ~ .cfg-grid label[for="ex-2"] > span:last-child { color: var(--paper); }
#ex-2:checked ~ .cfg-grid label[for="ex-2"] .free { color: #63d69d; }
#ex-2:checked ~ .cfg-grid label[for="ex-2"] .never { color: rgba(241,240,236,.62); }
#ex-3:checked ~ .cfg-grid label[for="ex-3"] { background: var(--ink); color: var(--paper); }
#ex-3:checked ~ .cfg-grid label[for="ex-3"] > span:last-child { color: var(--paper); }
#ex-3:checked ~ .cfg-grid label[for="ex-3"] .free { color: #63d69d; }
#ex-3:checked ~ .cfg-grid label[for="ex-3"] .never { color: rgba(241,240,236,.62); }
#ex-4:checked ~ .cfg-grid label[for="ex-4"] { background: var(--ink); color: var(--paper); }
#ex-4:checked ~ .cfg-grid label[for="ex-4"] > span:last-child { color: var(--paper); }
#ex-4:checked ~ .cfg-grid label[for="ex-4"] .free { color: #63d69d; }
#ex-4:checked ~ .cfg-grid label[for="ex-4"] .never { color: rgba(241,240,236,.62); }
#ex-5:checked ~ .cfg-grid label[for="ex-5"] { background: var(--ink); color: var(--paper); }
#ex-5:checked ~ .cfg-grid label[for="ex-5"] > span:last-child { color: var(--paper); }
#ex-5:checked ~ .cfg-grid label[for="ex-5"] .free { color: #63d69d; }
#ex-5:checked ~ .cfg-grid label[for="ex-5"] .never { color: rgba(241,240,236,.62); }
#ex-6:checked ~ .cfg-grid label[for="ex-6"] { background: var(--ink); color: var(--paper); }
#ex-6:checked ~ .cfg-grid label[for="ex-6"] > span:last-child { color: var(--paper); }
#ex-6:checked ~ .cfg-grid label[for="ex-6"] .free { color: #63d69d; }
#ex-6:checked ~ .cfg-grid label[for="ex-6"] .never { color: rgba(241,240,236,.62); }
#ex-7:checked ~ .cfg-grid label[for="ex-7"] { background: var(--ink); color: var(--paper); }
#ex-7:checked ~ .cfg-grid label[for="ex-7"] > span:last-child { color: var(--paper); }
#ex-7:checked ~ .cfg-grid label[for="ex-7"] .free { color: #63d69d; }
#ex-7:checked ~ .cfg-grid label[for="ex-7"] .never { color: rgba(241,240,236,.62); }
#ex-8:checked ~ .cfg-grid label[for="ex-8"] { background: var(--ink); color: var(--paper); }
#ex-8:checked ~ .cfg-grid label[for="ex-8"] > span:last-child { color: var(--paper); }
#ex-8:checked ~ .cfg-grid label[for="ex-8"] .free { color: #63d69d; }
#ex-8:checked ~ .cfg-grid label[for="ex-8"] .never { color: rgba(241,240,236,.62); }
#ex-9:checked ~ .cfg-grid label[for="ex-9"] { background: var(--ink); color: var(--paper); }
#ex-9:checked ~ .cfg-grid label[for="ex-9"] > span:last-child { color: var(--paper); }
#ex-9:checked ~ .cfg-grid label[for="ex-9"] .free { color: #63d69d; }
#ex-9:checked ~ .cfg-grid label[for="ex-9"] .never { color: rgba(241,240,236,.62); }

@keyframes fx-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
html[data-motion="calm"] #ex-0:checked ~ .cfg-grid [data-for],
html[data-motion="calm"] .fx,
html[data-motion="calm"] .fxc { animation: none !important; }

/* --- The add-on panels inside the preview ------------------- */
.cfg-slot { margin-top: auto; }
.fx {
  border-top: 1px solid rgba(232, 147, 58, .45);
  padding-top: .6rem;
}
.fx-h {
  font-family: var(--mono); font-size: .58rem; letter-spacing: .16em; text-transform: uppercase;
  color: #e8933a; margin-bottom: .5rem;
}
.fx-note { color: rgba(242, 239, 233, .55); font-size: .66rem; margin-top: .45rem; line-height: 1.45; }

.fx-diff { display: grid; gap: .3rem; font-size: .68rem; }
.fx-diff li { color: rgba(242, 239, 233, .82); }
.fx-diff s { color: rgba(242, 239, 233, .38); margin-right: .45rem; }

.fx-logos { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; }
.fx-logo {
  display: grid; justify-items: center; gap: .25rem;
  border: 1px solid rgba(242, 239, 233, .18); padding: .55rem .3rem;
  font-size: .6rem; letter-spacing: -.01em; color: rgba(242, 239, 233, .8);
}
.fx-logo svg { width: 26px; height: 26px; color: #e8933a; }

.fx-shop { display: grid; grid-template-columns: repeat(3, 1fr); gap: .45rem; }
.fx-shop span {
  display: grid; gap: .18rem; padding: .5rem .55rem;
  border: 1px solid rgba(242, 239, 233, .18); font-size: .68rem; color: #e8933a; font-weight: 600;
}
.fx-shop b { font-weight: 500; font-size: .66rem; color: rgba(242, 239, 233, .85); }

.fx-lang { display: inline-flex; border: 1px solid rgba(242, 239, 233, .22); }
.fx-lang span { padding: .22rem .6rem; font-size: .62rem; font-weight: 600; color: rgba(242, 239, 233, .55); }
.fx-lang .on { background: #e8933a; color: #191308; }

.fx-book { display: grid; grid-template-columns: repeat(4, 1fr); gap: .4rem; }

.fx-switch { display: grid; gap: .35rem; }
.fx-switch span {
  padding: .42rem .6rem; border: 1px solid rgba(242, 239, 233, .18);
  font-size: .68rem; color: rgba(242, 239, 233, .6);
}
.fx-switch .on { border-color: #e8933a; color: #f2efe9; background: rgba(232, 147, 58, .14); }

.fx-photo {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .5rem .6rem; border: 1px solid rgba(242, 239, 233, .18);
  font-size: .68rem; color: rgba(242, 239, 233, .7);
}
.fx-photo b { color: #e8933a; font-weight: 700; white-space: nowrap; }

.fxc { display: none; }
.fxc b { display: block; font-size: 1rem; font-weight: 600; margin: .3rem 0 .35rem; }
.fxc p:last-child { color: var(--ink-2); font-size: .9rem; max-width: 52ch; }

/* =============================================================
   20 - Shape pass
   The radius family from § 1, applied to everything the earlier
   radius-0 build left square. Collected here rather than scattered
   so the shape lock can be read in one place and audited in one
   place. Nothing below changes layout, only corners.
   ============================================================= */
.cue-label      { border-radius: var(--r-md); }
.cue-n          { border-radius: var(--r-sm) 0 var(--r-sm) 0; }
.cap            { border-radius: var(--r-md); }
.seq-chrome     { border-radius: 0; }              /* sits inside the window, which already rounds */
.cfg-screen     { border-radius: var(--r-md); }
.np-call, .np-acts span, .np-tile, .np-slot,
.np-rows, .fx-logo, .fx-shop span, .fx-lang,
.fx-switch span, .fx-photo { border-radius: var(--r-sm); }
.np-rows        { overflow: hidden; }
.fx-lang        { overflow: hidden; }
.note           { border-radius: 0; }              /* a rule, not a box */
.box            { border-radius: var(--r-md); }
.op, .op-title, .op-nav, .op-side, .op-photo, .op-table,
.op-form, .op-submit, .op-counter { border-radius: 0; }

/* The demo site being replaced stays square on purpose. Rounded
   corners are a 2015 idea; a page built in 2011 does not have them,
   and the contrast between the two frames is part of the argument. */

@media (prefers-reduced-motion: no-preference) {
  /* The three squares of the mark are the page's structural motif.
     Where a group of three panels appears, they carry the same
     stagger the mark has: each one a step further along. */
  .rail-item:nth-child(1) { --i: 0; }
  .rail-item:nth-child(2) { --i: 1; }
  .rail-item:nth-child(3) { --i: 2; }
}

/* =============================================================
   22 - Page-wide scroll motion
   An agency page that argues for scroll-driven design has to be
   one. Every entry below is answerable in one sentence, which is
   the bar the taste rules set: if it cannot be justified it is
   not here.
   ============================================================= */

/* Where am I. The page is thirteen thousand pixels tall and one
   section holds the reader for ten screens; a progress line is the
   only thing that answers that question honestly. */
.scroll-progress {
  position: absolute; inset: auto 0 -1.5px; height: 1.5px;
  background: var(--go); transform: scaleX(0); transform-origin: left;
  pointer-events: none;
}

/* Die Fortschrittslinie ist eine Ortsangabe, keine Bewegung: sie
   beantwortet „wo bin ich auf 26.000 Pixeln". Sie laeuft auch bei
   reduzierter Bewegung - ein wachsender Balken loest keinen Schwindel
   aus, und ohne sie ist die Seite auf einem kurzen Fenster orientierungslos. */
@supports (animation-timeline: scroll()) {
  html[data-drive="css"] .scroll-progress {
    animation: progress linear both;
    animation-timeline: scroll(root);
  }
  @keyframes progress { to { transform: scaleX(1); } }
}

@supports (animation-timeline: view()) {
  /* The hero hands off rather than simply scrolling away: it
     settles back a little as the argument below takes over. One
     kinetic moment on the page, at the top, where it belongs. */
  html[data-drive="css"][data-motion="full"] .hero-inner {
    animation: handoff linear both;
    animation-timeline: view();
    animation-range: exit 0% exit 90%;
  }
  @keyframes handoff {
    to { transform: scale(.965); opacity: .35; }
  }
}

/* Short viewports. The annotation area is the screen box minus two
   34px chrome insets, so on a 700px-tall window it is around 200px
   and two stacked labels at full size do not fit: the lower row ran
   past the bottom edge and was clipped. Tightening the type buys
   the 60px needed without cutting a word. */
@media (max-height: 760px) {
  .cue-label {
    font-size: .68rem;
    line-height: 1.32;
    padding: .55rem .7rem .6rem;
    width: min(36%, 300px);
  }
  .cue-label b { font-size: .76rem; }
  .cue-n { min-width: 22px; height: 22px; font-size: .6rem; }
  .demo-label { font-size: clamp(.82rem, .72rem + .7vw, 1.2rem); }
  .demo-bar { padding-block: .4rem; }
}

/* =============================================================
   23 - The flashy layer
   The brief asked for a page that moves, and an agency selling
   design has no business arguing for scroll-driven work on a
   static page. Everything here is still answerable in a sentence,
   which is the only rule that survives the volume.
   ============================================================= */
@property --q { syntax: '<number>'; initial-value: 0; inherits: true; }

/* Reine Bewegung, beide. Sie tragen keine Aussage und fallen bei
   reduzierter Bewegung ersatzlos weg. */
@supports (animation-timeline: view()) {

  /* The mark, blown up behind the hero, turning. Three squares
     at three rates, so it never lines back up into a logo and
     never reads as a watermark either. */
  html[data-drive="css"][data-motion="full"] .hero-orbit .o-1 { animation: turn-a linear both; animation-timeline: scroll(root); animation-range: 0 180vh; }
  html[data-drive="css"][data-motion="full"] .hero-orbit .o-2 { animation: turn-b linear both; animation-timeline: scroll(root); animation-range: 0 180vh; }
  html[data-drive="css"][data-motion="full"] .hero-orbit .o-3 { animation: turn-c linear both; animation-timeline: scroll(root); animation-range: 0 180vh; }
  @keyframes turn-a { to { rotate: 62deg;  translate: -6% 4%; } }
  @keyframes turn-b { to { rotate: -44deg; translate: 5% -3%; } }
  @keyframes turn-c { to { rotate: 26deg;  translate: -2% -6%; } }

  /* Type that moves. The headline runs past the right edge and
     keeps drifting as you scroll, which is the whole point of
     setting it that big. Transform only: animating letter
     spacing on a 13rem line relayouts every frame. */
  html[data-drive="css"][data-motion="full"] .hero h1 {
    animation: kinetic linear both;
    animation-timeline: view();
    animation-range: entry 70% exit 60%;
  }
  @keyframes kinetic {
    from { transform: translateX(-3vw) scale(.94); }
    to   { transform: translateX(2vw)  scale(1.02); }
  }
}

/* Ohne Film laeuft nichts davon, und die Seite steht einfach still.
   Nichts oben ist fuer das Lesen tragend - das war der Test, den jeder
   Eintrag bestehen musste. Hier stand bis zum 02.09.2026 ein leerer
   `@supports not`-Block; die Grundstellung steht in Abschnitt 40. */

/* =============================================================
   24 - The shift
   The page does not stay on paper. Across the middle of the scroll
   it walks from paper to a deep violet, and every derived token in
   section 1 walks with it: rules, muted text, panel fills, the
   accent. One animation, three properties, the whole interface.

   The flip from dark type on light to light type on dark happens
   inside two percent of the range, not across it. A slow cross-fade
   would spend a full screen at a muddy midpoint where neither ink
   nor paper has contrast against the ground. Fast is the honest
   move: it reads as a cut in a film, which is what it is.
   ============================================================= */
/* Farbe ist keine Bewegung. Die Wanderung laeuft auch bei reduzierter
   Bewegung weiter: nichts verschiebt sich, die Flaeche wechselt den Ton.
   Setzte sie hier aus, blieben die Akte auf Papier stehen - und mehrere
   Kleinschriften sind ueber `--go-deep` fuer den violetten Grund
   abgestimmt, nicht fuer Papier. */
@supports (animation-timeline: scroll()) {
  /* `html[...]` statt `:root`: ein verschachteltes `:root` wuerde zum
     Nachfahren von <html> - und <html> ist kein Nachfahre von sich
     selbst. Deshalb steht der Selektor direkt. */
  html[data-drive="css"] {
    animation: shift linear both;
    animation-timeline: scroll(root);
    /* Percentages of the whole page scroll. The demo sequence ends
       around 30%, so the walk starts once the film is over and is
       complete well before the legal pages at the foot. */
    animation-range: 31% 56%;
  }
  @keyframes shift {
    0%       { --paper: #f1f0ec; --ink: #14151a; --go: #0d7346; }
    52%      { --paper: #ddd4ee; --ink: #14151a; --go: #0d7346; }
    60%      { --paper: #cbbce6; --ink: #191331; --go: #0b6b40; }
    /* the cut */
    62%      { --paper: #33215c; --ink: #f3effc; --go: #34d399; }
    100%     { --paper: #2a1a4d; --ink: #f6f2ff; --go: #34d399; }
  }
}

/* Without scroll-driven animations the page simply stays on paper.
   Nothing below the fold depends on the violet to be readable. */

/* =============================================================
   25 - Act II: the four steps, played
   The deck of sticky cards was still a list. This is the film
   treatment the demo already had, applied to the process: one
   pinned stage, four beats, each one filling the screen in turn
   with the day counter running underneath.

   Same machinery as the demo sequence, deliberately: one
   registered number across the pin, every beat deriving its own
   window from it in calc. Learning it once is enough.
   ============================================================= */
@property --r { syntax: '<number>'; initial-value: 0; inherits: true; }

/* Height comes from the beat count, so an act never has to be
   retimed by hand when a beat is added or removed. */
.act { position: relative; height: calc(var(--beats, 4) * 135vh); }
.act-stage {
  position: sticky; top: 66px; height: calc(100dvh - 66px);
  display: grid; align-content: center;
  overflow: hidden;
  --r: 0;
}
/* All four beats share one grid cell, so the container sizes to the
   tallest of them and every beat is centred on the same line. An
   earlier version stacked them with position:absolute inside a
   relative box that had no height of its own; it happened to look
   right because the content overflowed symmetrically, which is not
   a thing to rely on. */
.act-beats { display: grid; align-items: center; }
.beat {
  grid-area: 1 / 1;
  display: grid; align-content: center; gap: clamp(.6rem, 2vh, 1.2rem);
  --in:  clamp(0, calc((var(--r) - var(--s)) * 34), 1);
  --out: clamp(0, calc((var(--e) - var(--r)) * 34), 1);
  opacity: calc(var(--in) * var(--out));
  transform: translateY(calc((1 - var(--in)) * 34px)) scale(calc(.97 + var(--in) * .03));
}
.beat-n {
  font-family: var(--mono); font-size: clamp(.7rem, .62rem + .4vw, .82rem);
  letter-spacing: .2em; text-transform: uppercase;
  /* --go-deep, not --go. This act runs across the light lavender
     part of the shift, where the plain accent measures 3.68:1 and
     this is small text. The deep step is mixed toward whatever the
     ink currently is, so it stays legible on both grounds. */
  color: var(--go-deep);
}
.beat h3 {
  font-size: clamp(2.2rem, 1.1rem + 5vw, 5.4rem);
  font-weight: 500; letter-spacing: -.05em; line-height: .95;
  max-width: 16ch;
}
.beat p { color: var(--ink-2); font-size: clamp(1rem, .95rem + .4vw, 1.35rem); max-width: 46ch; }
.beat-when {
  justify-self: start; margin-top: .6rem;
  font-family: var(--mono); font-size: .78rem; letter-spacing: .12em; text-transform: uppercase;
  padding: .4rem .9rem; border: 1.5px solid var(--ink); border-radius: var(--r-sm);
}

/* The counter under the beats. Seven blocks, filling as the days
   pass, so the section shows the thing it is describing. */
.act-days {
  position: absolute; z-index: 2;
  left: 0; right: 0; bottom: clamp(1.5rem, 6vh, 4rem);
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 5px;
}
.act-days i {
  display: block; height: clamp(8px, 1.4vh, 14px);
  border-radius: 3px;
  background: var(--rule);
}
.act-days i.on { background: var(--go); }

.act-rail {
  position: absolute; z-index: 2; left: 0; right: 0; top: clamp(1rem, 4vh, 2.5rem);
  height: 2px; background: var(--rule);
}
.act-rail i {
  display: block; height: 100%; background: var(--go);
  transform: scaleX(var(--r, 0)); transform-origin: left;
}

@media (max-width: 760px) {
  .act { height: calc(var(--beats, 4) * 118vh); }
  .beat h3 { font-size: clamp(1.9rem, 1.2rem + 4vw, 3rem); }
}

/* Ohne Film gibt es keinen Akt: die Beats stapeln sich zu einer
   gewoehnlichen Liste, statt vier Schirme leer zu lassen. Diese Regeln
   standen bis zum 02.09.2026 hier unter `@supports not
   (animation-timeline: view())` und waren doppelt unvollstaendig - sie
   griffen bei abgeschalteter Bewegung ueberhaupt nicht, und der
   Zuschnitt aus Abschnitt 37 hat den Beat danach wieder weggeschnitten.
   Vollstaendig in **Abschnitt 40**. */

/* =============================================================
   26 - Closing
   The flooding panel is gone. Its fill rose from the bottom and
   cut a hard horizontal edge straight through the headline, which
   is exactly the kind of near-miss that makes a page look
   unfinished. The page now arrives on violet by itself, so the
   closing block needs no colour of its own: it is type, one line
   of copy and two buttons on the ground the page already stands on.
   ============================================================= */
.close {
  display: grid; gap: clamp(1.2rem, 3vh, 2rem);
  padding-block: clamp(1rem, 4vh, 3rem);
}
.close h2 {
  font-size: clamp(2.6rem, 1.2rem + 6vw, 6.5rem);
  letter-spacing: -.05em; line-height: .92; max-width: 14ch;
}
.close .lede { color: var(--ink-2); }
.close-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: .4rem; }

/* Act II on its timeline, and the beat windows it drives. */
/* STRUKTUR, wie `--p` in Abschnitt 18: die Stelle im Akt, nicht die
   Bewegung. Laeuft deshalb auch bei reduzierter Bewegung. */
@supports (animation-timeline: view()) {
  html[data-drive="css"] .act { view-timeline-name: --act; }
  html[data-drive="css"] .act-stage {
    animation: act-drive linear both;
    animation-timeline: --act;
    animation-range: entry 100% exit 0%;
  }
  @keyframes act-drive { from { --r: 0; } to { --r: 1; } }
}

/* =============================================================
   27 - Act II, staged
   The four beats were a headline and a line of copy each. That is
   a slide deck, not a film. Every beat now carries a scene built
   from the product: a checkout that pays itself, a form that fills
   itself in, a site that builds itself, an inbox that answers
   itself. Each element inside a scene arrives on its own cue.

   No stock photography. Two candidates were downloaded and looked
   at; a landscape and a bowl of tea behind "you order" would be
   decoration, and the honest cinematic material here is the thing
   being sold. The licence question is settled in CLAUDE.md in case
   that decision is ever revisited.

   --bp is a beat's own progress, 0 to 1 inside its own window, so a
   scene can be choreographed without knowing where it sits in the
   act. --i is an element's cue number.
   ============================================================= */
.beat {
  --bp: clamp(0, calc((var(--r) - var(--s)) / (var(--e) - var(--s))), 1);
  grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}
.beat-copy { display: grid; gap: clamp(.5rem, 1.6vh, 1rem); align-content: center; }

/* One cue system for every scene.

   The cues share the middle 70% of a beat and never its edges, and
   the spacing is the beat`s own cue count divided into that window.
   A beat with eight cues paces them tighter than one with two, and
   the last is always fully in before the beat starts leaving.

   The spacing used to be a flat .115 regardless of count. With
   eight cues the last one began at bp .945 and would have finished
   at 1.02, after the beat had already begun fading: it was never
   seen at all. Every beat now declares --cues. */
.st {
  --span: .70;
  --at: calc(.09 + var(--i, 0) * (var(--span) / var(--cues, 6)));
  --on: clamp(0, calc((var(--bp) - var(--at)) * 12), 1);
  opacity: var(--on);
  transform: translateY(calc((1 - var(--on)) * 16px));
}

.scene {
  position: relative;
  border: 2px solid var(--ink);
  border-radius: var(--r-lg);
  background: var(--paper-2);
  padding: clamp(1rem, 2.4vw, 1.8rem);
  display: grid; gap: clamp(.55rem, 1.4vh, .95rem);
  align-content: start;
  min-height: clamp(240px, 44vh, 400px);
  box-shadow: 10px 10px 0 -2px var(--rule);
}
.scene-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding-bottom: .6rem; border-bottom: 1.5px solid var(--rule-2);
  font-family: var(--mono); font-size: .62rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3);
}
.scene-row {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .5rem .7rem;
  border: 1.5px solid var(--rule-2); border-radius: var(--r-sm);
  font-size: clamp(.76rem, .72rem + .2vw, .9rem);
}
.scene-row b { font-weight: 600; }
.scene-row span { color: var(--ink-3); font-family: var(--mono); font-size: .72rem; }
.scene-row.done { border-color: var(--go); }
.scene-row.done span { color: var(--go); }

/* Beat 1: the amount, set large, and a button that presses. */
.scene-amount {
  font-size: clamp(2.4rem, 1.4rem + 4vw, 4.6rem);
  font-weight: 500; letter-spacing: -.05em; line-height: 1;
}
.scene-amount small { font-size: .3em; letter-spacing: 0; color: var(--ink-3); margin-left: .4em; }
.scene-btn {
  display: block; text-align: center;
  padding: .7rem 1rem; border-radius: var(--r-md);
  background: var(--go); color: var(--on-go);
  font-weight: 700; font-size: .92rem;
  /* presses once, on its own cue */
  transform: scale(calc(1 - var(--press, 0) * .04));
}

/* Beat 2: fields that type themselves. The caret is the thing that
   sells it, so it only shows on the field currently filling. */
.scene-field { display: grid; gap: .2rem; }
.scene-field label {
  font-family: var(--mono); font-size: .6rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3);
}
.scene-field .val {
  border-bottom: 1.5px solid var(--rule-2); padding-bottom: .25rem;
  font-size: clamp(.82rem, .78rem + .2vw, .96rem); font-weight: 500;
  white-space: nowrap; overflow: hidden;
  /* the value is revealed left to right, like typing */
  clip-path: inset(0 calc((1 - var(--on)) * 100%) 0 0);
}
.scene-clock {
  font-family: var(--mono); font-size: clamp(1.4rem, 1rem + 1.6vw, 2.2rem);
  font-weight: 300; letter-spacing: -.02em;
}

/* Beat 3: the site assembling, then stamped live. */
.scene-build { display: grid; gap: .45rem; }
.scene-bar { height: 10px; border-radius: 3px; background: var(--rule-2); }
.scene-bar.wide { height: clamp(22px, 3.4vh, 34px); background: var(--ink); }
.scene-bar.accent { background: var(--go); }
.scene-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.scene-tiles i { display: block; height: clamp(26px, 4vh, 40px); border-radius: var(--r-sm); background: var(--rule-2); }
.scene-stamp {
  position: absolute; right: clamp(.8rem, 2vw, 1.6rem); bottom: clamp(.8rem, 2vw, 1.6rem);
  padding: .35rem .9rem; border: 2.5px solid var(--go); border-radius: var(--r-sm);
  color: var(--go); font-family: var(--mono); font-weight: 700; font-size: .8rem;
  letter-spacing: .18em;
  rotate: -8deg;
  transform: scale(calc(.7 + var(--on) * .3));
}

/* Beat 4: a thread that answers itself. */
.scene-msg {
  max-width: 82%; padding: .6rem .8rem; border-radius: var(--r-md);
  font-size: clamp(.76rem, .72rem + .2vw, .9rem); line-height: 1.4;
  border: 1.5px solid var(--rule-2);
}
.scene-msg.them { justify-self: start; background: var(--paper); }
.scene-msg.us { justify-self: end; background: var(--go); color: var(--on-go); border-color: var(--go); }
.scene-msg em { display: block; font-style: normal; font-family: var(--mono); font-size: .62rem;
  letter-spacing: .1em; opacity: .7; margin-top: .25rem; }

@media (max-width: 900px) {
  .beat { grid-template-columns: 1fr; gap: clamp(1rem, 3vh, 1.8rem); }
  .scene { min-height: 0; }
  .beat h3 { font-size: clamp(1.8rem, 1.2rem + 3.4vw, 2.8rem); }
  .beat p { font-size: 1rem; }
}

/* Calm: die Bewegung faellt weg, die ABFOLGE bleibt.
   `opacity: 1 !important` stand hier bis zum 02.09.2026 - damit waren
   alle 95 Einsaetze eines Akts gleichzeitig da, und der Film war eine
   Wand aus Text. Die Deckkraft folgt jetzt weiter `--on`: die Szene baut
   sich auf, sie bewegt sich nur nicht mehr. Wer alles auf einmal sichtbar
   braucht, ist im statischen Tier - das macht Abschnitt 40. */
html[data-motion="calm"] .st { transform: none !important; }
html[data-motion="calm"] .scene-field .val { clip-path: none !important; }
html[data-motion="calm"] .scene-stamp { transform: none !important; }

/* =============================================================
   28 - The rest of the film
   Everything below the four steps used to be an ordinary page:
   a price panel, a table, an accordion, a list of facts. The brief
   is a film from top to bottom, so each of those became an act on
   the same machinery as the two before it. Nothing that carries
   information was dropped; what was dropped is the furniture that
   held it (panels, table rows, disclosure arrows).

   All acts share .act / .act-stage / .beat / .st from sections
   25 and 27. Only the scenes differ, and they are below.
   ============================================================= */

/* A beat that is one enormous statement rather than a copy column
   and a scene. Used where the content IS the image: the price, the
   closing line. */
.beat-solo { grid-template-columns: minmax(0, 1fr); text-align: left; }
.beat-solo h3, .beat-solo h2 {
  font-size: clamp(2.6rem, 1.1rem + 7vw, 8rem);
  letter-spacing: -.055em; line-height: .9; max-width: 14ch;
}

/* --- The price, played ---------------------------------------- */
.money { display: flex; align-items: baseline; flex-wrap: wrap; gap: clamp(.6rem, 2vw, 1.6rem); }
.money b {
  font-size: clamp(4rem, 2rem + 11vw, 13rem);
  font-weight: 500; letter-spacing: -.06em; line-height: .82;
}
.money i {
  font-style: normal; font-family: var(--mono);
  font-size: clamp(.8rem, .7rem + .5vw, 1.05rem);
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3);
}
.money .plus { color: var(--go); }

/* A list where every line arrives on its own cue and nothing sits
   in a box. The rule under each line is the only structure. */
.reel { display: grid; gap: 0; }
.reel li {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 1rem;
  padding-block: clamp(.5rem, 1.5vh, .85rem);
  border-bottom: 1.5px solid var(--rule);
  font-size: clamp(.92rem, .86rem + .35vw, 1.15rem);
}
.reel li:last-child { border-bottom: 0; }
.reel .k {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--go-deep); padding-top: .45em;
  min-width: 2.4rem;
}
.reel .out { color: var(--ink-3); }

/* --- The alternatives, drawn ---------------------------------- */
.chart { display: grid; gap: clamp(.6rem, 1.8vh, 1.1rem); }
.chart-axis {
  display: flex; justify-content: space-between;
  font-family: var(--mono); font-size: .64rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3);
  padding-bottom: .4rem; border-bottom: 2px solid var(--ink);
}
.chart-row { display: grid; gap: .35rem; }
.chart-row .lbl {
  display: flex; justify-content: space-between; gap: 1rem; align-items: baseline;
  font-size: clamp(.86rem, .8rem + .3vw, 1.05rem);
}
.chart-row .lbl b { font-weight: 600; }
.chart-row .lbl span { font-family: var(--mono); font-size: .76rem; color: var(--ink-2); white-space: nowrap; }
.chart-track { position: relative; height: 16px; }
.chart-track::before { content: ""; position: absolute; inset: 7px 0 auto; height: 2px; background: var(--rule); }
.chart-bar {
  position: absolute; top: 3px; height: 10px;
  left: var(--from); width: var(--w); min-width: 8px;
  background: var(--ink-2); border-radius: 5px;
  transform: scaleX(var(--on, 0)); transform-origin: left;
}
.chart-tick {
  position: absolute; top: -2px; left: var(--at);
  width: 5px; height: 20px; margin-left: -2.5px;
  background: var(--go); border-radius: 3px;
  transform: scaleY(var(--on, 0));
}
.chart-row.mine .lbl b, .chart-row.mine .lbl span { color: var(--go); }
.chart-note { color: var(--ink-2); font-size: clamp(.85rem, .8rem + .25vw, 1rem); max-width: 66ch; margin-top: .6rem; }

/* --- Questions, played ---------------------------------------- */
.qa { display: grid; gap: clamp(1rem, 3vh, 2rem); }
.qa-item { display: grid; gap: .5rem; }
.qa-item h3 {
  font-size: clamp(1.3rem, .95rem + 1.6vw, 2.2rem);
  letter-spacing: -.035em; max-width: 22ch;
}
.qa-item p { color: var(--ink-2); font-size: clamp(.95rem, .9rem + .3vw, 1.12rem); max-width: 62ch; }

/* --- Facts, played -------------------------------------------- */
.facts-reel { display: grid; gap: clamp(.8rem, 2.4vh, 1.5rem); }
.facts-reel li { display: grid; gap: .25rem; border-top: 1.5px solid var(--rule); padding-top: clamp(.6rem, 1.8vh, 1rem); }
.facts-reel li:first-child { border-top: 0; padding-top: 0; }
.facts-reel .k {
  font-family: var(--mono); font-size: .64rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--go-deep);
}
.facts-reel .v { font-size: clamp(1rem, .92rem + .5vw, 1.35rem); }
.facts-reel .v strong { font-weight: 600; }
.facts-reel .v a { text-decoration: underline; text-underline-offset: 3px; }

/* --- The extras reel ------------------------------------------ */
.extra-price {
  font-family: var(--mono); font-size: clamp(1rem, .85rem + .8vw, 1.6rem);
  font-weight: 600; letter-spacing: -.01em; color: var(--go);
}
.extra-list { display: grid; gap: .45rem; margin-top: .3rem; }
.extra-list li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding-bottom: .4rem; border-bottom: 1.5px solid var(--rule);
  font-size: clamp(.86rem, .8rem + .3vw, 1.02rem);
}
.extra-list li span { font-family: var(--mono); font-size: .8rem; white-space: nowrap; color: var(--go-deep); }
.extra-list li.no span { color: var(--ink-3); font-family: var(--sans); white-space: normal; text-align: right; }

/* The preview panel inside the extras act reuses the demo site's
   own world, so an add-on is always shown on the thing it changes. */
.mini {
  border: 2px solid var(--ink); border-radius: var(--r-lg);
  background: linear-gradient(168deg, #1b1c21, #131418);
  color: #f2efe9; overflow: hidden;
  padding: clamp(.8rem, 2vw, 1.4rem);
  display: grid; gap: .6rem; align-content: start;
  min-height: clamp(230px, 40vh, 360px);
  box-shadow: 10px 10px 0 -2px var(--rule);
}
.mini-bar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.mini-logo { font-weight: 600; letter-spacing: -.025em; font-size: .95rem; }
.mini-logo em { font-style: normal; color: #e8933a; }
.mini-call { padding: .34rem .8rem; border-radius: var(--r-sm); background: #e8933a; color: #191308; font-size: .72rem; font-weight: 700; }
.mini-h { font-size: clamp(1.05rem, .9rem + .9vw, 1.7rem); font-weight: 500; letter-spacing: -.035em; line-height: 1.05; max-width: 16ch; }
.mini-p { color: rgba(242, 239, 233, .62); font-size: .78rem; max-width: 40ch; }

/* --- Closing ------------------------------------------------- */
.beat-close { justify-items: start; }
.beat-close .close-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: .6rem; }

@media (max-width: 900px) {
  .beat-solo h3, .beat-solo h2 { font-size: clamp(2rem, 1.2rem + 5vw, 3.4rem); }
  .money b { font-size: clamp(3rem, 2rem + 8vw, 5rem); }
  .mini { min-height: 0; }
}

/* The hero's single call to action. It lost its rule when the hero
   was rebuilt; a lone button did not visibly suffer, which is
   exactly why it went unnoticed until the class audit. */
.hero-actions { display: flex; flex-wrap: wrap; gap: .75rem; }

/* =============================================================
   29 - Motion the acts share
   The page-wide entries in section 22 pointed at a rail, a price
   panel and a configurator that the film replaced. Their targets
   are gone, so the rules went with them and these took their
   place: the same gestures, aimed at what is actually on screen.
   ============================================================= */
@supports (animation-timeline: view()) {

  /* A heading wipe was tried here and cut. The act timeline runs
     across all beats, so a wipe on it would have taken the whole
     act to uncover one headline; per-beat clipping belongs in the
     cue system, where the headings already arrive. The rule was
     left as a no-op keyframe for a moment, which is worse than
     nothing: it counted as an animation and did not animate. */

  /* The stage drifts a little against the page as an act plays,
     so a pinned screen never feels frozen. Two percent, which is
     felt rather than seen. Reine Bewegung - faellt calm weg. */
  html[data-drive="css"][data-motion="full"] .act-beats {
    animation: act-drift linear both;
    animation-timeline: --act;
    animation-range: entry 100% exit 0%;
  }
  @keyframes act-drift {
    from { transform: translateY(14px); }
    to   { transform: translateY(-14px); }
  }
}

/* The chart's value column is nowrap so a range never breaks in the
   middle of a figure. On a phone that pushed the whole row to 510px
   inside a 325px column, and `overflow-x: clip` on the body hid the
   result instead of showing a scrollbar. Below 700px the label and
   its figure stack, and the figure is allowed to wrap. */
@media (max-width: 700px) {
  .chart, .chart-row, .beat-copy, .reel, .facts-reel, .extra-list { min-width: 0; }
  .chart-row .lbl { flex-direction: column; align-items: flex-start; gap: .1rem; }
  .chart-row .lbl span { white-space: normal; }
  .extra-list li { flex-direction: column; align-items: flex-start; gap: .15rem; }
  .extra-list li span { white-space: normal; text-align: left; }
  .reel li { grid-template-columns: auto minmax(0, 1fr); }
  .money { gap: .4rem .8rem; }
}

/* =============================================================
   30 - Gestures, one per act
   Every cue on the page arrived the same way: up and fade. Six
   acts doing the identical move is what makes a long page feel
   monotonous even when each part is fine on its own.

   Each act now declares a gesture and all of its cues use it. The
   timing stays shared, so the pacing is still one system; only the
   direction of arrival changes.
   ============================================================= */
[data-gesture="grow"] .st {
  transform: scale(calc(.92 + var(--on) * .08));
  transform-origin: left center;
}
[data-gesture="slide"] .st {
  transform: translateX(calc((1 - var(--on)) * -26px));
}
[data-gesture="wipe"] .st {
  /* The wipe carries the reveal, so opacity gets out of the way
     early rather than cross-fading against the clip. */
  opacity: clamp(0, calc(var(--on) * 4), 1);
  clip-path: inset(0 calc((1 - var(--on)) * 102%) 0 0);
  transform: none;
}
[data-gesture="tilt"] .st {
  transform: translateY(calc((1 - var(--on)) * 18px)) rotate(calc((1 - var(--on)) * -1.4deg));
  transform-origin: left center;
}
[data-gesture="drop"] .st {
  transform: translateY(calc((var(--on) - 1) * 22px));
}

/* Dieselbe Trennung: die Geste faellt weg, der Einsatz bleibt. Der
   Wisch wird zur Ueberblendung, statt zu verschwinden. */
html[data-motion="calm"] [data-gesture] .st {
  transform: none !important; clip-path: none !important;
  opacity: var(--on) !important;
}

/* =============================================================
   31 - Five more devices, so no two acts look alike
   The four steps got mock interfaces and they worked. Repeating
   that device in every act would have been the same monotony in a
   different costume, so each of the remaining acts gets its own
   visual language.
   ============================================================= */


/* --- B: the twelve months. The monthly fee is a subscription you
   can stop, so the image is a year filling in with a visible end. */
.months { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.months i {
  display: block; aspect-ratio: 1;
  border: 1.5px solid var(--rule-2); border-radius: var(--r-sm);
  background: transparent;
}
.months i.on { background: var(--go); border-color: var(--go); }
.months-note {
  grid-column: 1 / -1; margin-top: .5rem;
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3);
}

/* --- C: the index numeral. The questions act is typographic and
   nothing else: an outlined number the height of the block. ----- */
.numeral {
  position: absolute; z-index: 0; pointer-events: none;
  right: clamp(-1rem, -2vw, 0rem); top: 50%; translate: 0 -50%;
  font-size: clamp(9rem, 30vw, 26rem);
  font-weight: 500; letter-spacing: -.06em; line-height: .8;
  color: transparent;
  -webkit-text-stroke: 2px var(--rule-2);
  opacity: .9;
}

/* --- D: the register extract. Who you are paying is a legal fact,
   so it is set as a document rather than as an interface. ------- */
.extract {
  border: 2px solid var(--ink); border-radius: var(--r-md);
  background: var(--paper-2);
  padding: clamp(1rem, 2.4vw, 1.6rem);
  font-family: var(--mono);
  font-size: clamp(.68rem, .64rem + .2vw, .8rem);
  line-height: 1.7;
  display: grid; gap: .35rem;
  box-shadow: 8px 8px 0 -2px var(--rule);
}
.extract-head {
  display: flex; justify-content: space-between; gap: 1rem;
  padding-bottom: .5rem; margin-bottom: .3rem;
  border-bottom: 1.5px solid var(--rule-2);
  letter-spacing: .16em; text-transform: uppercase; color: var(--ink-3);
  font-size: .6rem;
}
.extract dl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .2rem .9rem; margin: 0; }
.extract dt { color: var(--ink-3); }
.extract dd { margin: 0; }
.extract-seal {
  justify-self: end; margin-top: .4rem;
  padding: .25rem .7rem; border: 1.5px solid var(--go); border-radius: var(--r-sm);
  color: var(--go); letter-spacing: .16em; font-size: .6rem;
}

/* --- E: the finale counter. The hero opened with seven blocks
   filling; the close brings them back, full, with the price. ---- */
.finale { display: grid; gap: clamp(.8rem, 2.4vh, 1.4rem); }
.finale-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.finale-grid i {
  display: block; height: clamp(34px, 6vh, 62px);
  border-radius: var(--r-sm); background: var(--ink);
}
.finale-grid i:last-child { background: var(--go); }
.finale-legend {
  display: flex; justify-content: space-between;
  font-family: var(--mono); font-size: .68rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3);
}
.finale-legend b:last-child { color: var(--go); }

@media (max-width: 900px) {
  .numeral { font-size: clamp(6rem, 34vw, 12rem); right: -.5rem; }
  .months { grid-template-columns: repeat(6, 1fr); }
}

/* =============================================================
   32 - Wiring the new devices to the beat clock
   The devices in section 31 are static markup; what makes them
   filmic is that they move on the same --bp their beat already
   carries. No new timeline, no new numbers to keep in sync.
   ============================================================= */
.beat { position: relative; }


/* The year fills month by month, and stops where the list stops:
   nine on, three off, because the point is that it ends when you
   say so rather than running to a fixed term. */
.months i {
  --lit: clamp(0, calc((var(--bp, 0) - (var(--m, 0) * .055)) * 20), 1);
  transform: scale(calc(.7 + var(--lit) * .3));
}
.months i.on {
  background: color-mix(in srgb, var(--go) calc(var(--lit) * 100%), transparent);
  border-color: color-mix(in srgb, var(--go) calc(var(--lit) * 100%), var(--rule-2));
}
.months i:nth-child(1) { --m: 0; }  .months i:nth-child(2) { --m: 1; }
.months i:nth-child(3) { --m: 2; }  .months i:nth-child(4) { --m: 3; }
.months i:nth-child(5) { --m: 4; }  .months i:nth-child(6) { --m: 5; }
.months i:nth-child(7) { --m: 6; }  .months i:nth-child(8) { --m: 7; }
.months i:nth-child(9) { --m: 8; }  .months i:nth-child(10) { --m: 9; }
.months i:nth-child(11) { --m: 10; } .months i:nth-child(12) { --m: 11; }

/* The index numeral drifts against its beat, so the questions act
   has depth without a second layer of content. */
.numeral {
  /* `--drift` ist der Bewegungsfaktor: 1 normal, 0 bei reduzierter
     Bewegung (Abschnitt 41). Er sitzt hier und nicht als
     `translate: none`, weil die zweite Komponente die senkrechte
     Zentrierung traegt - die muss bleiben. */
  translate: calc(var(--bp, 0) * var(--drift, 1) * -3vw) -50%;
  opacity: clamp(0, calc(var(--bp, 0) * 3), .9);
}

/* The register extract lands as a whole, like a stamped document
   rather than a list assembling itself. */
.extract {
  --show: clamp(0, calc((var(--bp, 0) - .45) * 8), 1);
  opacity: var(--show);
  transform: translateY(calc((1 - var(--show)) * 20px)) rotate(calc((1 - var(--show)) * .8deg));
}
.extract-seal { transform: scale(calc(.6 + var(--show) * .4)) rotate(calc((1 - var(--show)) * -12deg)); }

/* The seven blocks come back at the close, filling one last time.
   The hero opened with this; ending on it closes the loop. */
.finale-grid i {
  --lit: clamp(0, calc((var(--bp, 0) - .5 - (var(--d, 0) * .045)) * 24), 1);
  transform: scaleY(calc(.14 + var(--lit) * .86));
  transform-origin: bottom;
}
.finale-grid i:nth-child(1) { --d: 0; } .finale-grid i:nth-child(2) { --d: 1; }
.finale-grid i:nth-child(3) { --d: 2; } .finale-grid i:nth-child(4) { --d: 3; }
.finale-grid i:nth-child(5) { --d: 4; } .finale-grid i:nth-child(6) { --d: 5; }
.finale-grid i:nth-child(7) { --d: 6; }

/* Die fuenf Bildsprachen: nichts verschiebt, dreht oder waechst mehr -
   aber jede kommt weiter zu ihrem Einsatz. Der Seitenbaum stand vorher
   vollstaendig da, bevor der Beat ueberhaupt begann.

   `.numeral` laeuft ueber `translate`, nicht `transform`: ein
   `transform: none` hat sie deshalb nie erreicht. Und ein blankes
   `translate: 0` waere falsch, weil die zweite Komponente die
   senkrechte Zentrierung traegt (`top: 50%; translate: 0 -50%`). Der
   Drift haengt darum an `--drift`, das calm auf 0 steht - siehe
   Abschnitt 33. */
html[data-motion="calm"] .sitemap,
html[data-motion="calm"] .extract { transform: none !important; }
html[data-motion="calm"] .extract-seal { transform: none !important; }
html[data-motion="calm"] .months i,
html[data-motion="calm"] .finale-grid i { transform: none !important; }
html[data-motion="calm"] .sm-node,
html[data-motion="calm"] .sm-line { transform: none !important; }
html[data-motion="calm"] .sm-row::before { transform: none !important; }
html[data-motion="calm"] .months i.on { background: var(--go); border-color: var(--go); }

/* =============================================================
   33 - The site tree
   A fan of five overlapping sheets stood here and was cut: the
   operator could not tell what it was, and he was right.
   Overlapping paper says "documents". It does not say "a website
   with five pages". A tree says it without needing a caption.
   ============================================================= */
.sitemap {
  display: grid; justify-items: center; gap: 0;
  opacity: clamp(0, calc(var(--bp, 0) * 4), 1);
}
.sm-node {
  display: grid; place-items: center;
  padding: .5rem .85rem;
  border: 2px solid var(--ink); border-radius: var(--r-md);
  background: var(--paper);
  font-size: clamp(.72rem, .68rem + .25vw, .88rem); font-weight: 600;
  white-space: nowrap;
  --lit: clamp(0, calc((var(--bp, 0) - .18 - (var(--n, 0) * .12)) * 14), 1);
  opacity: calc(.25 + var(--lit) * .75);
  transform: translateY(calc((1 - var(--lit)) * 10px));
}
.sm-home {
  --n: -1;
  background: var(--ink); color: var(--paper);
}
/* The trunk draws itself before the children arrive. */
.sm-line {
  width: 2px; height: clamp(18px, 4vh, 32px);
  background: var(--ink);
  transform: scaleY(clamp(0, calc((var(--bp, 0) - .12) * 12), 1));
  transform-origin: top;
}
.sm-row {
  display: grid; grid-template-columns: repeat(2, auto);
  gap: .5rem clamp(.5rem, 1.6vw, 1rem);
  justify-content: center;
  position: relative;
  padding-top: clamp(10px, 2vh, 16px);
}
/* The bar the four children hang from, drawn after the trunk. */
.sm-row::before {
  content: ""; position: absolute; inset: 0 12% auto; height: 2px;
  background: var(--ink);
  transform: scaleX(clamp(0, calc((var(--bp, 0) - .2) * 10), 1));
  transform-origin: center;
}
.sm-cap {
  margin-top: clamp(.6rem, 1.8vh, 1rem);
  font-family: var(--mono); font-size: .64rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
  opacity: clamp(0, calc((var(--bp, 0) - .6) * 8), 1);
}

@media (min-width: 901px) {
  .sm-row { grid-template-columns: repeat(4, auto); }
}

/* =============================================================
   34 - The contact form
   Deliberately NOT a pinned act, and this is the one place on the
   page where that is the right call: a scrubbed beat leaves the
   screen when you scroll, and filling in a form means scrolling.
   Pinning it would make it unusable, which no amount of motion
   would make up for.

   So it arrives like an act and then stops moving. The fields cue
   in once on entry and stay put, the way the rest of the page cues
   inside a beat, and after that it behaves like a form.

   Native validation, no script. The browser already knows how to
   say "this field is required" in the visitor's own language, and
   a form that depends on JavaScript is a form that sometimes does
   not send.
   ============================================================= */
.talk { position: relative; padding-block: clamp(4rem, 12vh, 8rem); overflow: hidden; }
.talk-inner { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); gap: clamp(2rem, 5vw, 4.5rem); align-items: start; }
.talk h2 { font-size: clamp(2.2rem, 1.2rem + 4vw, 4.4rem); letter-spacing: -.045em; line-height: .95; max-width: 12ch; }
.talk .lede { margin-top: 1rem; }
.talk-note { margin-top: 1.4rem; color: var(--ink-3); font-size: .88rem; max-width: 42ch; }
.talk-note a { text-decoration: underline; text-underline-offset: 3px; }

.form { display: grid; gap: clamp(.9rem, 2.4vh, 1.3rem); }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(.9rem, 2vw, 1.3rem); }
.field { display: grid; gap: .35rem; min-width: 0; }
.field label {
  font-family: var(--mono); font-size: .64rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.field .req { color: var(--go); }
.field input, .field select, .field textarea {
  font: inherit; font-size: 1rem; color: var(--ink);
  background: transparent;
  border: 0; border-bottom: 2px solid var(--rule-2);
  border-radius: 0;
  padding: .55rem 0;
  transition: border-color 200ms var(--ease-out);
}
.field textarea { min-height: 6.5rem; resize: vertical; }
.field select { appearance: none; padding-right: 1.5rem; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-bottom-color: var(--go);
}
/* :user-invalid, not :invalid: an empty required field is invalid
   from the moment the page loads, and shouting at somebody before
   they have typed anything is not validation, it is nagging. */
.field input:user-invalid, .field textarea:user-invalid, .field select:user-invalid {
  border-bottom-color: var(--bad);
}
.field .hint { font-size: .78rem; color: var(--ink-3); }

.form-consent {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .7rem;
  align-items: start;
  font-family: var(--sans); font-size: .88rem; letter-spacing: 0;
  text-transform: none; color: var(--ink-2);
}
.form-consent input { width: 20px; height: 20px; accent-color: var(--go); margin-top: .15rem; }
.form-consent a { text-decoration: underline; text-underline-offset: 3px; }

.form button { justify-self: start; margin-top: .4rem; }

/* The cue system again, but fired once by the reveal observer
   instead of scrubbed, because this block has to hold still. */
.talk [data-stagger] > * { transition-delay: calc(var(--i, 0) * 70ms); }

@media (max-width: 900px) {
  .talk-inner { grid-template-columns: 1fr; }
  .form-row { grid-template-columns: 1fr; }
}

/* =============================================================
   35 - The guide
   Five long-form articles carried over from the live site. They
   are the one part of the old site the single page cannot absorb:
   the film has no place for a thousand words on what a website
   costs, and those articles are what people find in search.

   So they keep their own layout, a plain reading column, and only
   the surface is brought onto the new palette. Long-form reading
   gets no scroll choreography at all; a person reading an article
   wants the text to hold still.
   ============================================================= */
main.article {
  width: min(100% - (var(--gutter) * 2), 720px);
  margin: clamp(1.2rem, 3vh, 2rem) auto clamp(4rem, 9vh, 6.5rem);
}
main.article h1 { font-size: clamp(2rem, 1.4rem + 2.6vw, 3.4rem); margin-bottom: .7rem; }
main.article h2 {
  font-size: clamp(1.2rem, 1.08rem + .7vw, 1.6rem);
  margin-top: 2.6rem; margin-bottom: .6rem;
  padding-top: 1.5rem; border-top: 1.5px solid var(--ink);
}
main.article h3 { margin-top: 1.7rem; margin-bottom: .45rem; }
main.article p, main.article li { color: var(--ink-2); }
main.article p { margin-bottom: 1rem; }
main.article strong { color: var(--ink); font-weight: 600; }
main.article a { text-decoration: underline; text-underline-offset: 3px; }
main.article ul, main.article ol { margin: 0 0 1.1rem; padding-left: 1.25rem; list-style: disc; }
main.article ol { list-style: decimal; }
main.article li { margin-bottom: .42rem; }

.article-meta {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-3); margin-bottom: 1.4rem;
  display: flex; flex-wrap: wrap; gap: .8rem;
}
.standfirst {
  font-size: clamp(1.05rem, 1rem + .4vw, 1.28rem);
  line-height: 1.55; color: var(--ink-2) !important; margin-bottom: 2rem;
}
.keypoints, .callout, .notice-static {
  padding: 1.2rem 1.4rem; margin-bottom: 1.6rem;
  border: 1.5px solid var(--ink); border-radius: var(--r-md);
  background: var(--paper-2);
}
.keypoints > b, .callout > b, .notice-static > b { display: block; margin-bottom: .5rem; font-weight: 600; color: var(--ink); }
.keypoints p:last-child, .callout p:last-child, .notice-static p:last-child { margin-bottom: 0; }
.callout { border-left-width: 5px; border-left-color: var(--go); }

.toc {
  padding: 1.1rem 1.3rem; margin-bottom: 2rem;
  border: 1.5px solid var(--rule-2); border-radius: var(--r-md);
}
.toc > b {
  display: block; margin-bottom: .5rem;
  font-family: var(--mono); font-size: .64rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.toc ol, .toc ul { margin: 0; padding-left: 1.1rem; }
.toc li { margin-bottom: .3rem; }

.article-cta {
  margin-top: 2.6rem; padding-top: 1.6rem; border-top: 2px solid var(--ink);
}
.cta-actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 1rem; }

.eyebrow {
  font-family: var(--mono); font-size: .66rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--go-deep);
}
.lead { font-size: 1.1rem; color: var(--ink-2); }

/* The guide index lists the articles as cards. */
.guide-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: clamp(1rem, 2.4vw, 1.6rem); }
.guide-card {
  display: grid; gap: .5rem; padding: clamp(1.2rem, 2.6vw, 1.7rem);
  border: 1.5px solid var(--ink); border-radius: var(--r-md);
  background: var(--paper-2);
}
.guide-card b { font-size: 1.05rem; font-weight: 600; letter-spacing: -.02em; }
.guide-card span, .guide-meta { color: var(--ink-2); font-size: .92rem; }
.guide-meta { font-family: var(--mono); font-size: .66rem; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); }
.guide-more { margin-top: 2rem; display: grid; gap: .4rem; }

/* Structural leftovers from the old markup, kept so the articles
   render without being rewritten line by line. */
.wrap { width: min(100% - (var(--gutter) * 2), var(--shell)); margin-inline: auto; }
main.article .section { padding-block: 0; }
.section-head { margin-bottom: 1.2rem; }
.page-hero { padding-block: clamp(2.5rem, 7vh, 4.5rem) clamp(1.5rem, 4vh, 2.5rem); }
.page-hero-inner { width: min(100% - (var(--gutter) * 2), 720px); margin-inline: auto; }
.cards { display: grid; gap: clamp(1rem, 2.4vw, 1.6rem); }
.cards-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.card { border: 1.5px solid var(--ink); border-radius: var(--r-md); background: var(--paper-2); }
.card-body { display: grid; gap: .5rem; padding: clamp(1.2rem, 2.6vw, 1.7rem); }
.card-body b { font-weight: 600; }
.cta-band { display: grid; gap: 1rem; padding: clamp(1.4rem, 3vw, 2.2rem); border: 2px solid var(--ink); border-radius: var(--r-lg); background: var(--paper-2); }
.steps { display: grid; gap: 1rem; }
.step { display: grid; gap: .3rem; padding-top: .9rem; border-top: 1.5px solid var(--rule); }
.step b { font-weight: 600; }
.step span { color: var(--ink-2); font-size: .94rem; }
.data { width: 100%; border-collapse: collapse; font-size: .93rem; }
.data th, .data td { text-align: left; padding: .65rem .75rem; border-bottom: 1px solid var(--rule); color: var(--ink-2); }
.data th { color: var(--ink); font-weight: 600; }
.table-scroll { overflow-x: auto; margin-bottom: 1.4rem; }

/* =============================================================
   36 - The phone, properly
   The earlier mobile pass only ever checked the top of the page:
   `scrollWidth` and a few boxes at scroll position zero. That
   catches horizontal overflow and nothing else, and every real
   problem on a phone was further down, inside a pinned act.

   Walking every act at several scroll positions with a screenshot
   found six. They are fixed below, each with the reason.
   ============================================================= */
@media (max-width: 900px) {

  /* 1 -- The act rail sat 32px under the page progress line and
     the two read as one doubled, broken-looking bar. It moves to
     the foot of the stage, where it is the only line. */
  .act-rail { top: auto; bottom: 0; }

  /* 2 -- The arrow labels sat at 4% and 28% of the demo and cut
     the mock's own headline in half. That was the overlap the
     operator saw. They move to the foot of the frame, where the
     mock has empty space, and the two that appear together stack
     in reading order rather than side by side. */
  .cue-label {
    left: 3%; width: 94%; min-width: 0;
    top: auto;
    padding: .5rem .7rem .55rem 2.1rem;
    font-size: .7rem; line-height: 1.35;
  }
  .cue-label b { font-size: .76rem; }
  /* die Slot-Positionen stehen in Abschnitt 38b - nicht hier eine
     zweite Fassung anlegen, die von der spaeteren ueberstimmt wird */
  .cue-n { transform: none; left: 0; top: 0; min-width: 20px; height: 20px; font-size: .58rem; }

  /* 3 -- The connectors ran the height of the frame to reach a
     label at the bottom and crossed the mock diagonally. The dot
     alone is enough at this size; the label is directly below it. */
  .cue-line { height: 2px; opacity: .85; }

  /* 4 -- The index numeral was a full-height watermark sitting
     directly behind the answer text. On a narrow column there is
     nowhere for it to hide, so it stops being a watermark and
     becomes a corner marker. */
  .numeral {
    top: 0; right: 2%; translate: 0 0;
    font-size: clamp(3.4rem, 15vw, 5rem);
    -webkit-text-stroke-width: 1.5px;
    opacity: .55;
  }

  /* 5 -- The slide gesture starts 26px to the left. At 3% inset
     that is past the screen edge, so a row visibly entered from
     off-canvas. Half the distance keeps the gesture and stays in
     the frame. */
  [data-gesture="slide"] .st { transform: translateX(calc((1 - var(--on)) * -12px)); }

  /* 6 -- Tighter type inside acts. The beats are centred in a
     fixed-height stage, so content taller than the stage is
     clipped at both ends: the act rail cut through a headline and
     the closing paragraph ran off the bottom. */
  .beat { gap: .5rem; }
  .beat h3, .beat-solo h3, .beat-solo h2 { font-size: clamp(1.6rem, 8.5vw, 2.4rem); }
  .beat p { font-size: .95rem; }
  .beat-n { font-size: .66rem; letter-spacing: .16em; }
  .qa-item h3 { font-size: clamp(1.35rem, 7vw, 1.9rem); }
  .qa-item p { font-size: .95rem; }
  .qa { gap: 1.1rem; }
  .chart { gap: .5rem; }
  .chart-row .lbl { font-size: .88rem; }
  .chart-row .lbl span { font-size: .72rem; }
  .chart-note { font-size: .82rem; }
  .money b { font-size: clamp(2.6rem, 17vw, 4rem); }
  .reel li { font-size: .88rem; padding-block: .4rem; }
  .facts-reel .v { font-size: .95rem; }
  .extra-list li { font-size: .88rem; }
  .mini { min-height: 0; }
  .scene { min-height: 0; }
}

/* Very short phones. The stage is the viewport minus the header,
   so on a 640px-tall screen an act has barely 570px to work with
   and even the tightened type above needs help. */
@media (max-width: 900px) and (max-height: 760px) {
  .beat h3, .beat-solo h3, .beat-solo h2 { font-size: clamp(1.4rem, 7vw, 1.9rem); }
  .beat p, .qa-item p { font-size: .88rem; }
  .qa-item h3 { font-size: 1.2rem; }
  /* NICHT ausblenden. Der Hinweis, dass die Zahlen Orientierung und
     keine eigene Erhebung sind, ist die ehrliche Haelfte der Grafik.
     Ein kleiner Bildschirm ist kein Grund, eine Einschraenkung
     wegzulassen - er ist ein Grund, sie enger zu setzen. */
  .chart-note { font-size: .76rem; line-height: 1.45; }
}

/* -------------------------------------------------------------
   36b - Tippziele
   Gemessen, nicht geschaetzt: 72 bedienbare Elemente durchgezaehlt.
   Vier lagen unter den 44px, die ein Finger braucht. Die uebrigen
   Treffer der Messung sitzen INNERHALB der Demo-Attrappe - das ist
   ein Bild einer Website von 2011, kein Bedienelement, und ihre
   winzigen Links sind dort das Argument.
   ------------------------------------------------------------- */
@media (max-width: 900px) {
  /* Der Menueknopf war 40x40. */
  .nav-toggle { width: 46px; height: 46px; }

  /* Elf Fusszeilen-Links standen auf 34px Hoehe. */
  .foot-col a {
    min-height: 44px;
    display: flex; align-items: center;
  }

  /* Die Wortmarke im Kopf ist der Weg zurueck nach oben und war
     26px hoch. Der Kasten waechst, das Zeichen nicht. */
  .brand { min-height: 44px; display: inline-flex; align-items: center; }

  /* Die Einwilligungs-Box war 20px. Sie ist Pflicht vor dem
     Absenden, also darf sie kein Praezisionsziel sein. */
  .form-consent input[type="checkbox"] { width: 24px; height: 24px; flex: none; }
  .form-consent { align-items: flex-start; gap: .7rem; padding-block: .4rem; }
}

/* Die drei Rechtslinks im schmalen Fuss der Guide- und Rechtsseiten
   stehen in einer Zeile, durch Mittelpunkte getrennt. Ein
   min-height wuerde die Trennzeichen aus der Flucht heben, also
   waechst hier die Trefferflaeche ueber Innenabstand: die Zeile
   sieht gleich aus, der Finger hat 41px statt 17px. */
@media (max-width: 900px) {
  .foot-bottom a { display: inline-block; padding-block: .75rem; }
}

/* =============================================================
   37 - Unsichtbare Beats fangen keine Klicks mehr
   Der schwerste Fehler des Projekts, gefunden am 30.08.2026 auf
   einem echten Telefon: **Der Kaufknopf im Preis-Akt liess sich
   nicht anklicken.**

   Alle Beats eines Akts liegen in derselben Rasterzelle
   (`grid-area: 1 / 1`). Sichtbar ist immer nur einer, die anderen
   stehen auf `opacity: 0` - aber **Deckkraft 0 heisst nicht, dass
   ein Element aus dem Hit-Testing verschwindet.** Der spaetere
   Beat malt oben und nimmt weiter jeden Klick entgegen. Gemessen
   mit `elementFromPoint` auf der Mitte des Knopfes: getroffen
   wurde `LI.st out` aus einem voellig anderen Beat.

   Das betraf **jeden** Link in jedem gepinnten Akt, nicht nur
   diesen Knopf: vier Kaufknoepfe, die AGB-, Datenschutz- und
   Impressumslinks und beide `mailto:`.

   `pointer-events` laesst sich nicht aus einer Zahl berechnen,
   `visibility` nicht mit `calc` schalten. `clip-path` dagegen
   wirkt auf das Hit-Testing: Was weggeschnitten ist, faengt
   nichts. `--vis` kippt bei etwa 1,7 % Deckkraft von 0 auf 1,
   also lange bevor ein Auge etwas sieht.

   Das negative `-5rem` haelt Schlagschatten UND die Index-Ziffer
   der Fragen im Bild - die sitzt auf dem Handy ueber dem Beat und
   wurde von einer zu knappen Maske gekoepft.
   Ohne das schneidet die Maske an der Beat-Kante ab.
   ============================================================= */
.beat {
  --vis: clamp(0, calc(var(--in) * var(--out) * 60), 1);
  clip-path: inset(calc((1 - var(--vis)) * 200% - 5rem));
}

/* =============================================================
   38 - Ein gepinnter Beat ist hoehenbegrenzt
   Auf einem echten Telefon wurden Kisten unten abgeschnitten: der
   Preis-Beat war 821px hoch in einer 601px-Buehne, der Registerauszug
   726 in 601, der Vergleich 642 in 601.

   Ursache: **Jede Schriftgroesse in den Beats war breitenbasiert**
   (`clamp(1.6rem, 8.5vw, 2.4rem)`). Die Buehne ist aber
   `100vh` minus Kopfzeile. Auf einem kurzen, breiten Telefon
   waechst also die Schrift, waehrend der Platz schrumpft - genau
   der falsche Zusammenhang. Mein erster Handy-Durchgang hat das
   nicht gefunden, weil ich auf 375x812 und 360x640 geprueft habe,
   beides Seitenverhaeltnis 1:2,17 und 1:1,78; das Geraet des
   Betreibers liegt bei 1:1,85 mit mehr Breite.

   `min(vw, vh)` macht die Groesse zur Funktion der knapperen der
   beiden Achsen. Damit kann kein Fenster mehr die Schrift
   aufblasen, waehrend es die Buehne verkleinert.
   ============================================================= */
@media (max-width: 900px) {
  .beat { gap: clamp(.35rem, 1.2svh, .9rem); }

  .beat h3, .beat-solo h3, .beat-solo h2 {
    font-size: clamp(1.25rem, min(7.6vw, 3.7svh), 2.3rem);
  }
  .beat p, .lede { font-size: clamp(.8rem, min(4vw, 2.05svh), 1.02rem); }
  .beat-n { font-size: clamp(.58rem, 1.5svh, .7rem); }

  /* Die Frageliste ist der laengste Beat der Seite. */
  .qa { gap: clamp(.55rem, 2.2svh, 1.3rem); }
  .qa-item h3 { font-size: clamp(1.05rem, min(6vw, 3svh), 1.75rem); }
  .qa-item p  { font-size: clamp(.8rem, min(4vw, 2.05svh), 1.02rem); }

  /* Acht Zeilen mit Rahmen unten - hier steckt die meiste Hoehe. */
  .reel li {
    font-size: clamp(.76rem, min(3.7vw, 1.95svh), 1rem);
    padding-block: clamp(.18rem, .95svh, .6rem);
    gap: .6rem;
  }

  /* Der Vergleich: vier Zeilen aus Titel, Wert und Balken. */
  .chart { gap: clamp(.28rem, 1.1svh, .7rem); }
  .chart-row { gap: clamp(.1rem, .5svh, .3rem); }
  .chart-row .lbl { font-size: clamp(.8rem, min(4.2vw, 2.1svh), 1rem); }
  .chart-row .lbl span { font-size: clamp(.68rem, min(3.4vw, 1.7svh), .82rem); }
  .chart-note { font-size: clamp(.72rem, min(3.4vw, 1.75svh), .85rem); line-height: 1.45; }

  /* Der Registerauszug und die vier Pflichtangaben. */
  .facts-reel .k { font-size: clamp(.58rem, 1.5svh, .7rem); }
  .facts-reel .v { font-size: clamp(.8rem, min(4vw, 2.05svh), 1rem); }
  .extract { font-size: clamp(.68rem, min(3.2vw, 1.7svh), .85rem); padding: clamp(.6rem, 1.6svh, 1rem); }
  .extract-head { font-size: clamp(.56rem, 1.4svh, .68rem); }

  .extra-list li { font-size: clamp(.78rem, min(3.7vw, 1.9svh), 1rem); }
  .scene, .mini { padding: clamp(.6rem, 1.8svh, 1.2rem); gap: clamp(.35rem, 1.1svh, .7rem); }
}

/* -------------------------------------------------------------
   38b - Vier Einzelstuecke, die auf dem Foto falsch lagen
   ------------------------------------------------------------- */
@media (max-width: 900px) {
  /* (1) `.money` ist ein Flexrudel mit Umbruch. Bei 412px passten
     drei der vier Teile in die erste Zeile, und „A MONTH" stand
     allein darunter unter dem falschen Betrag. Zwei Spalten
     erzwingen die gemeinte Paarung: Betrag, dann seine Einheit. */
  .money {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    align-items: baseline; row-gap: clamp(.1rem, .8svh, .5rem); column-gap: .7rem;
  }
  .money b { font-size: clamp(2.4rem, min(15vw, 8.5svh), 6rem); }

  /* (2) Die Index-Ziffer der Fragen stand mittig hinter dem Text
     und wurde von mir nur nach oben geschoben - dort beginnt aber
     der Text. Sie sitzt jetzt UEBER dem Beat, in der Leerfläche,
     die das zentrierte Layout ohnehin laesst. */
  .numeral {
    top: auto; bottom: calc(100% + .3rem); right: 0;
    translate: 0 0;
    font-size: clamp(2.8rem, min(14vw, 7svh), 4.6rem);
    -webkit-text-stroke-width: 1.5px;
    opacity: .5;
  }

  /* (3) Zwoelf Monate in sechs Spalten sind zwei Reihen grosser
     Kacheln und fressen den halben Beat. In einer Reihe lesen sie
     sich als das, was sie sind: ein Jahresstreifen. */
  .months { grid-template-columns: repeat(12, 1fr); gap: 3px; }
  .months-note { font-size: .6rem; }

  /* (4) Die beiden gleichzeitigen Pfeil-Etiketten standen 23 % und
     3 % ueber dem Rand. Bei drei Textzeilen reicht das nicht: das
     obere lief in das untere. Fester Abstand statt Prozent, weil
     die Etikettenhoehe nicht von der Rahmenhoehe abhaengt.
     NACHTRAG: Auch das war noch falsch. Die Punkte sitzen bei 8 bis
     70 % der Rahmenhoehe, und der Etikettenstapel lag genau darueber -
     ein Pfeil zeigte dann auf ein anderes Etikett statt auf das
     Modul darunter. Die Etiketten stehen jetzt in den Luecken
     zwischen den Punkten: eins bei 27 %, die uebrigen ganz unten. */
  .cue:nth-child(1) .cue-label { top: 27%; bottom: auto; }
  .cue:nth-child(2) .cue-label { top: auto; bottom: 2%; }
  .cue:nth-child(3) .cue-label { top: auto; bottom: 2%; }
  .cue:nth-child(4) .cue-label { top: auto; bottom: 2%; }
}

/* -------------------------------------------------------------
   38c - Ein leerer Rahmen ist kein Zustand, den jemand sehen soll
   Die Szenenpanele behalten ihre volle Hoehe, waehrend ihr Inhalt
   auf Einsatz wartet - `opacity: 0` nimmt keinen Platz weg. Vom
   Beat-Anfang bis zum ersten Einsatz stand also eine grosse leere
   Kiste da, und auf einem Foto sieht das nach einem Ladefehler aus.
   Der erste Einsatz kommt jetzt fast sofort.
   ------------------------------------------------------------- */
.st { --at: calc(.03 + var(--i, 0) * (var(--span) / var(--cues, 6))); }

/* -------------------------------------------------------------
   38d - Die drei Beats, die auch danach noch zu hoch waren
   Gemessen statt geschaetzt. Der Preis-Beat bestand aus
   134px Text + 421px Liste + 198px Seitenbaum = 769 in 601.
   Jeder Posten wird dort gekuerzt, wo er tatsaechlich Hoehe hat.
   ------------------------------------------------------------- */
@media (max-width: 900px) {
  /* Der Seitenbaum stellte die vier Unterseiten in zwei Reihen zu
     zwei Kacheln. In einer Reihe zu vieren sagt er dasselbe auf
     einem Drittel der Hoehe - und „vier Kinder an einer Leiste"
     liest sich dabei sogar deutlicher. */
  .sm-row { grid-template-columns: repeat(4, auto); gap: .4rem; }
  .sm-node { padding: .3rem .45rem; font-size: clamp(.58rem, 2.6vw, .72rem); }
  .sm-line { height: clamp(12px, 2.4svh, 20px); }
  .sm-cap { font-size: .62rem; }

  /* Acht Listenzeilen mal sechs Pixel Innenabstand sind fast
     hundert Pixel, die niemand sieht. */
  .reel li { padding-block: clamp(.1rem, .55svh, .45rem); }

  /* Der Registerauszug im Wer-bezahlt-wird-Akt. */
  .extract { line-height: 1.5; }
  .extract-row { padding-block: .1rem; }
  .extract-seal { font-size: .6rem; padding: .25rem .5rem; }
  .facts-reel { gap: clamp(.3rem, 1.2svh, .8rem); }

  /* Der Vergleich lag nur 21px darueber. */
  .chart-row .bar { height: 10px; }
}

/* -------------------------------------------------------------
   38e - Der Rest sitzt in der Zeilenhoehe
   Nach der Messung fehlten noch 28px im Preis-Beat, 13 im
   Register-Beat, 2 im Vergleich. Auf 360px bricht fast jede
   Listenzeile auf zwei Zeilen um, und bei acht Zeilen kostet
   jede ueberfluessige Zehntel Zeilenhoehe rund 30px.
   ------------------------------------------------------------- */
@media (max-width: 900px) {
  .reel li        { line-height: 1.3; }
  .facts-reel .v  { line-height: 1.32; }
  .extract dd, .extract dt { line-height: 1.42; }
  .chart-row .lbl { line-height: 1.3; }
  .chart-note     { line-height: 1.4; }
}

/* -------------------------------------------------------------
   38f - Sehr kurze Fenster
   Bei 320x568 (iPhone SE der ersten Reihe, und jedes Telefon mit
   hochgestellter Systemschrift) blieben sechs Beats zu hoch.

   Der Grund ist eine Eigenart von `clamp`, die leicht zu uebersehen
   ist: **Die Untergrenze gewinnt genau dann, wenn der Platz am
   knappsten ist.** `clamp(.76rem, 1.95svh, 1rem)` hoert bei 390px
   Fensterhoehe auf zu schrumpfen. Die Groesse muss also nicht nur
   hoehenbasiert sein, ihr Boden muss es auch.
   ------------------------------------------------------------- */
@media (max-width: 900px) and (max-height: 620px) {
  .beat { gap: clamp(.25rem, 1svh, .6rem); }
  .beat h3, .beat-solo h3, .beat-solo h2 { font-size: clamp(1rem, min(6.4vw, 3.3svh), 1.7rem); }
  .beat p, .lede { font-size: clamp(.7rem, min(3.5vw, 1.9svh), .9rem); }
  .qa-item h3 { font-size: clamp(.92rem, min(5vw, 2.7svh), 1.4rem); }
  .qa-item p  { font-size: clamp(.7rem, min(3.5vw, 1.9svh), .9rem); }
  .reel li    { font-size: clamp(.66rem, min(3.3vw, 1.8svh), .9rem); padding-block: .08rem; }
  .facts-reel .v { font-size: clamp(.7rem, min(3.5vw, 1.9svh), .9rem); }
  .extract    { font-size: clamp(.6rem, min(2.9vw, 1.55svh), .78rem); }
  .chart-row .lbl { font-size: clamp(.7rem, min(3.6vw, 1.9svh), .9rem); }
  .chart-row .lbl span { font-size: clamp(.6rem, min(3vw, 1.55svh), .76rem); }
  .extra-list li { font-size: clamp(.68rem, min(3.4vw, 1.8svh), .9rem); }
  .money b { font-size: clamp(2rem, min(13vw, 7.5svh), 4.5rem); }
  .sm-node { padding: .22rem .34rem; font-size: clamp(.5rem, 2.4vw, .62rem); }
  .scene, .mini { padding: clamp(.45rem, 1.5svh, .9rem); }
}

@media (max-width: 900px) and (max-height: 620px) {
  /* Die letzten drei Pixel des Register-Beats bei 320x568. */
  .facts-reel { gap: clamp(.18rem, .8svh, .45rem); }
  .extract-row { padding-block: 0; }
}

/* =============================================================
   38g - Breite Fenster sind auch nicht unendlich hoch
   Beim Nachmessen am Telefon fiel derselbe Fehler auf dem Desktop
   auf, wo er seit dem Umbau steckte und niemandem aufgefallen war:
   Auf 1280x800 lief der Vergleichs-Akt **132px** aus der Buehne,
   auf 1440x950 noch 43. Ein 1366x768-Laptop ist immer noch das
   verbreitetste Notebook-Format der Welt.

   Ursache ist dieselbe wie auf dem Telefon: Die Display-Ueberschrift
   ist reine `vw`-Groesse und stand bei 118px, ihr Block bei 320px -
   in einer 884px-Buehne, die sich noch einen 515px-Chart teilen
   muss. Breite sagt nichts darueber, wie viel Hoehe da ist.

   Deshalb bekommt sie hier eine Hoehendecke statt einer kleineren
   Breitenzahl: Auf einem hohen Bildschirm aendert sich nichts, auf
   einem flachen greift die Decke.
   ============================================================= */
@media (min-width: 901px) and (max-height: 1050px) {
  .beat-solo h2, .beat-solo h3 { font-size: min(7vw, 10.4svh); }
  .beat h3 { font-size: min(2.6rem, 5.2svh); }
  .chart { gap: clamp(.5rem, 1.7svh, 1.2rem); }
  .chart-row .lbl { font-size: min(1.05rem, 2.1svh); }
  .chart-note { font-size: min(1.02rem, 2svh); line-height: 1.5; }
  .beat p, .lede { font-size: min(1.25rem, 2.4svh); }
  .reel li { font-size: min(1.12rem, 2.2svh); padding-block: clamp(.3rem, 1.1svh, .85rem); }
  .qa-item h3 { font-size: min(1.9rem, 3.9svh); }
  .qa-item p { font-size: min(1.15rem, 2.3svh); }
  .facts-reel .v { font-size: min(1.15rem, 2.3svh); }
  .extract { font-size: min(.95rem, 1.9svh); }
}

/* =============================================================
   39 - Drei Fehler vom Telefon, die keine Groessenfrage waren
   ============================================================= */

/* -------------------------------------------------------------
   39a - Der Wisch zerschneidet Absaetze
   Der Vergleichs-Akt hat die Geste „wischt von links auf". Auf
   einer Zeile liest sich das als Enthuellen. Auf dem Handy bricht
   der Hinweis unter der Grafik auf **sieben** Zeilen um, und der
   Wisch schneidet dann jede einzelne mitten im Wort ab: „About
   these figu / agencies, freelan / subscriptions cc". Auf dem Foto
   sieht das nach einem Darstellungsfehler aus, und genau das ist
   es auch.

   Ein Wisch gehoert auf eine Zeile. Fliesstext steigt auf.
   ------------------------------------------------------------- */
[data-gesture="wipe"] p.st,
[data-gesture="wipe"] .chart-note,
[data-gesture="wipe"] .lede {
  clip-path: none;
  opacity: var(--on);
  transform: translateY(calc((1 - var(--on)) * 14px));
}

/* -------------------------------------------------------------
   39b - Ein Punkt, der auf jedem Untergrund sichtbar ist
   Der Pfeilpunkt der Nachher-Seite ist bernsteinfarben und lag auf
   dem bernsteinfarbenen „Call 555-0142"-Knopf: gleiche Farbe auf
   gleicher Farbe, der Punkt war weg. Auf dem Desktop faellt das
   nicht auf, weil er dort woanders sitzt.

   Eine Marke, die ueber fremdem Inhalt liegen kann, darf sich
   nicht auf einen Untergrund verlassen. Zwei Ringe - erst hell,
   dann dunkel - trennen sie von allem: von Weiss durch den
   dunklen, von Schwarz und von Bernstein durch den hellen.
   ------------------------------------------------------------- */
.cue-dot {
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, .92),
    0 0 0 4px rgba(12, 14, 18, .55),
    0 0 0 9px rgba(13, 115, 70, .18);
}
.cues-new .cue-dot {
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, .92),
    0 0 0 4px rgba(12, 14, 18, .55),
    0 0 0 9px rgba(232, 147, 58, .2);
}

/* -------------------------------------------------------------
   39c - Der Beat sass systematisch 14px zu tief
   Auf jedem kurzen Fenster wurde unten etwas abgeschnitten,
   obwohl der Beat rechnerisch in die Buehne passte: 604px Inhalt
   in 617px Buehne, und trotzdem 7px weg.

   Ursache: `.act-beats` ist ein Raster mit allen Beats in einer
   Zelle, also **so hoch wie der hoechste** von ihnen - nicht so
   hoch wie die Buehne. Die Buehne hat diesen Block dann als Ganzes
   zentriert, und ein kuerzerer Beat landete um die halbe Differenz
   zum hoechsten daneben.

   Jetzt nimmt die Halterung die volle Buehnenhoehe an und zentriert
   darin selbst. Damit steht jeder Beat auf derselben Mittellinie,
   unabhaengig davon, wie hoch seine Geschwister sind.
   ------------------------------------------------------------- */
.act-stage { align-content: stretch; }
.act-beats { align-content: center; min-height: 0; }

/* =============================================================
   40 - Ohne Film: die lesbare Grundstellung
   Angelegt am 02.09.2026, nachdem der Betreiber die Seite auf einem
   Schul-PC und auf Arch Linux/Hyprland (Skalierung 1.6) geoeffnet
   hatte und dort **nur die Startseite und das Kontaktformular** zu
   sehen waren. Alles dazwischen - der Demo-Ablauf und die sechs
   Akte - war leer.

   URSACHE. Der ganze Mittelteil der Seite ist ein Film: die Beats
   sind unsichtbar (`opacity: calc(var(--in) * var(--out))`) und
   werden erst von einer Scroll-Zeitleiste sichtbar gemacht, die
   `--r` bzw. `--p` von 0 auf 1 dreht. Diese Zeitleisten liegen in
   Bloecken mit ZWEI Bedingungen:

       @supports (animation-timeline: view())
       @media (prefers-reduced-motion: no-preference)

   Der Rueckfall dagegen hing nur an der ERSTEN. Ist die Bewegung
   abgeschaltet - Windows „Animationen anzeigen" aus, wie es auf
   verwalteten Schul-Rechnern die Regel ist, oder
   `gtk-enable-animations=false` unter Hyprland -, dann gilt:

       @supports (animation-timeline: view())        -> WAHR,
         also greift kein `@supports not`-Rueckfall
       @media (prefers-reduced-motion: no-preference) -> FALSCH,
         also laeuft keine Animation

   `--r` und `--p` bleiben auf 0, jedes Zeitfenster bleibt zu, und
   die Akte stehen als fuenf bis sieben Schirme Leere da. Startseite
   und Kontaktformular ueberleben, weil die beide am
   `[data-reveal]`-System haengen, und DAS hatte seinen
   `prefers-reduced-motion: reduce`-Zweig (Abschnitt 7).

   Nicht die Bildschirmgroesse und nicht die Skalierung 1.6 - die
   sind bloss das, was beide Rechner gemeinsam hatten. Gegengeprueft:
   in Chrome bei 1200x675 und 1920x1080 laeuft der Film einwandfrei;
   schaltet man die sieben `no-preference`-Bloecke ab, ist die Seite
   an derselben Stelle leer.

   DIE UMKEHRUNG. Der Film haengt jetzt an `html[data-film]`. Das
   Attribut steht als `data-film="off"` IM AUSGELIEFERTEN HTML und
   wird von einem Inline-Skript im `<head>` nur dann entfernt, wenn
   der Film wirklich laufen darf. Damit fallen alle Fehler in die
   richtige Richtung: kein JavaScript, keine Unterstuetzung,
   abgeschaltete Bewegung, blockiertes Inline-Skript - in jedem
   dieser Faelle bleibt das Attribut stehen und der Text ist lesbar.
   Der Film ist die Ausnahme, nicht der Text.

   Ein Rueckfall in zwei `@media`/`@supports`-Bloecken haette
   denselben Rumpf zweimal gebraucht; genau daran ist der alte
   gescheitert (Abschnitt 37 kam spaeter und hat den Beat wieder
   weggeschnitten, ohne dass es jemandem auffiel). Ein Attribut ist
   ein Selektor: EIN Rumpf, und er gewinnt gegen alles davor.

   DER EINE HEBEL sind die drei Treibervariablen. Jedes Zeitfenster
   auf der Seite - `.months`, `.sitemap`, `.numeral`, `.extract`,
   `.finale-grid`, `.chart-bar`, `.scene-field .val` - liest
   `--bp`, `--on` oder `--p`. Stehen die auf 1, steht die ganze
   Seite in ihrem angekommenen Zustand, ohne dass hier ein einziges
   dieser Elemente genannt werden muss. Wer ein neues Fenster baut,
   leitet es aus `--bp`/`--on`/`--p` ab - dann ist es hier von
   selbst mit abgedeckt.
   ============================================================= */

/* 0 - Ankersprünge. Im Film faengt die gepinnte Buehne den Kopf ab,
       weil sie selbst auf `top: 66px` sitzt. Statisch tut das niemand,
       also braucht der Anker das Polster hier. NICHT unbedingt setzen:
       mit Film wuerde der Sprung 66px zu frueh landen, die Buehne waere
       noch nicht gepinnt, `--r` noch negativ - und der Anker landete auf
       einem leeren Bild. */
html[data-film="off"] { scroll-padding-top: calc(66px + 1rem); }

/* 0b - Das Reveal-System braucht JavaScript, um `.is-in` zu setzen.
       Laeuft keins, bleiben Hero und Kontaktformular auf `opacity: 0`
       stehen - also ausgerechnet die zwei Abschnitte, die am
       02.09.2026 als einzige noch zu sehen waren. Ohne diese Regel
       war die Seite ohne JavaScript vollstaendig leer, und das ist
       niemandem aufgefallen, weil `@media (prefers-reduced-motion:
       reduce)` es auf den Geraeten des Betreibers verdeckt hat.
       Gefunden am 02.09.2026 beim Umbau auf Abschnitt 41. */
html[data-film="off"] [data-reveal],
html[data-film="off"] [data-stagger] > *,
html[data-film="off"] .line-mask > span {
  opacity: 1 !important; transform: none !important;
}

/* 1 - Jedes Zeitfenster steht offen. */
html[data-film="off"] .seq-stage { --p: 1; }
html[data-film="off"] .beat      { --in: 1; --out: 1; --bp: 1; --vis: 1; }
html[data-film="off"] .st        { --on: 1; }

/* 2 - Der Demo-Ablauf wird ein gewoehnlicher Abschnitt: die alte
       Seite, dann die neue, jede in ihrem Rahmen. Die Pfeile fallen
       weg - sie zeigen auf Stellen einer Seite, die sich hier nicht
       mehr bewegt. */
html[data-film="off"] .seq-wrap { height: auto; }
html[data-film="off"] .seq-stage {
  position: static; height: auto; overflow: visible;
  display: grid; gap: 1.5rem; padding-block: var(--band);
  background: var(--paper);
}
html[data-film="off"] .demo-frame {
  position: static; padding: 0; opacity: 1;
  display: grid; gap: 1.5rem;
}
html[data-film="off"] .demo-screen {
  position: static; height: auto; overflow: visible;
  background: none; border-radius: 0;
}
html[data-film="off"] .seq-screen { position: static; clip-path: none; display: grid; gap: 1.5rem; }
/* Die Hoehe ist NICHT frei waehlbar. Die Attrappe ist in `vw`
   gesetzt und fuer einen bildschirmgrossen Kasten gezeichnet; ein
   flacherer Rahmen zeigt nur ihre obere Ecke, vergroessert. Der alte
   Rueckfall stand auf `min(70vh, 540px)` und tat genau das - die
   Ueberschrift „BRAMFIELD PLUMBING" lief aus dem Bild. Der Rahmen
   behaelt deshalb die Geometrie der Buehne, nur ohne Pin. `svh`, nicht
   `vh`: die Regel aus der vierzehnten Ueberarbeitung gilt hier
   genauso. */
html[data-film="off"] .site {
  position: relative; inset: auto; height: calc(100svh - 66px);
  border: 1.5px solid var(--ink);
  clip-path: none !important; filter: none !important; transform: none !important;
}
html[data-film="off"] .strip { height: 100%; grid-template-rows: 1fr; transform: none !important; }
html[data-film="off"] .strip > *:nth-child(n + 2) { display: none; }
html[data-film="off"] .seq-caption { position: static; margin-bottom: 1rem; }
html[data-film="off"] .cap { opacity: 1 !important; transform: none !important; }
html[data-film="off"] .cap + .cap { display: none; }

/* Die Beschriftung „vorher"/„nachher" haengt im Film in der
   Mattenleiste (`.demo-bar`), und die hat dort nur EINE Rasterzelle
   fuer beide Fassungen - im Film wechseln sie sich ab. Statisch
   wuerden sie uebereinander liegen. Deshalb tragen die beiden
   Rahmen hier ihre eigene Beschriftung, und die Leiste geht weg. */
.seq-static { display: none; }
html[data-film="off"] .demo-bar,
html[data-film="off"] .seq-mark,
html[data-film="off"] .cues,
html[data-film="off"] .seq-acts,
html[data-film="off"] .seq-chrome { display: none; }
/* Die Rahmen bleiben randlos wie im Film - nur die Beschriftung
   ruecken die Seitenraender ein. */
html[data-film="off"] .seq-static {
  display: block; margin-bottom: -.7rem; padding-inline: var(--gutter);
  font-family: var(--mono); font-size: .7rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
html[data-film="off"] .seq-static b { color: var(--ink); font-weight: 700; }

/* 3 - Die Akte werden Listen. Die Beats liegen im Film alle in
       derselben Rasterzelle und werden einzeln eingeblendet; hier
       stehen sie untereinander. Die Zaehlleiste und die Tagesbloecke
       fallen weg: beide messen einen Fortschritt, den es ohne Film
       nicht gibt. */
html[data-film="off"] .act { height: auto; }
html[data-film="off"] .act-stage {
  position: static; height: auto; overflow: visible;
  padding-block: var(--band); align-content: start;
}
html[data-film="off"] .act-beats {
  display: grid; gap: clamp(2rem, 5vh, 3.5rem);
  align-content: start; align-items: start;
}
html[data-film="off"] .beat {
  grid-area: auto; position: relative;
  opacity: 1 !important; transform: none !important; clip-path: none !important;
}
html[data-film="off"] .beat h3 { font-size: clamp(1.6rem, 1.2rem + 1.6vw, 2.4rem); }
html[data-film="off"] .st {
  opacity: 1 !important; transform: none !important; clip-path: none !important;
}
html[data-film="off"] .scene-field .val { clip-path: none !important; }
html[data-film="off"] .tl-spine i { transform: scaleY(1); }
html[data-film="off"] .act-days,
html[data-film="off"] .act-rail,
html[data-film="off"] .scroll-progress { display: none; }

/* =============================================================
   41 - Calm: der Film OHNE Bewegung
   Angelegt am 02.09.2026, zweiter Anlauf. Der erste (Abschnitt 40) hat
   bei abgeschalteter Systembewegung die gepinnten Akte in eine Liste
   fallen lassen. Das war lesbar und trotzdem falsch: der Betreiber
   zaehlte 2 von 5 eigenen Geraeten, auf denen der Film nie lief -
   Schul-PC und Arch/Hyprland -, und beide bekamen damit dauerhaft die
   Notfassung.

   DER DENKFEHLER war, `prefers-reduced-motion` als einen Schalter zu
   behandeln. Es sind zwei Dinge in einem:

   | | gehoert dem Leser | gehoert dem Film |
   |---|---|---|
   | STRUKTUR - ein Schritt je Schirm, vom eigenen Scrollen getrieben | nein | ja |
   | BEWEGUNG - Parallaxe, Drehung, Skalierung, Unschaerfe | **ja** | nein |

   `reduce` heisst „bewege nichts", nicht „zeig mir den Inhalt nicht".
   Die Beats verschwinden zu lassen, weil jemand keine Parallaxe
   vertraegt, verwechselt das eine mit dem anderen. Calm behaelt die
   Abfolge und nimmt die Bewegung: nichts verschiebt sich, dreht sich
   oder waechst - Dinge erscheinen und verschwinden. Das ist die
   uebliche Empfehlung fuer `reduce` (Bewegung durch Ueberblendung oder
   Schnitt ersetzen), nicht ein Trick, um die Vorgabe zu umgehen.

   WARUM DAS NOETIG WAR, und nicht bloss huebsch: Auf beiden Geraeten hat
   niemand um reduzierte Bewegung gebeten. Ein verwaltetes
   Windows-Abbild hat „Animationen anzeigen" aus Leistungsgruenden aus;
   eine Wayland-Sitzung ohne GTK-Einstellungsdienst meldet
   `gtk-enable-animations` als falsch, weil den Wert niemand liefert.
   Beides ist ein Nebenprodukt der Einrichtung, keine Entscheidung eines
   Menschen. Eine Vorgabe, die auf 2 von 5 Geraeten falsch anliegt, darf
   nicht darueber entscheiden, ob die Seite Inhalt hat. Ueber die
   Bewegung darf sie weiter entscheiden - und ein Leser, den sie falsch
   trifft, kann sie in Abschnitt 42 selbst einschalten.

   WAS BEWUSST BLEIBT, obwohl es sich bewegt:
   - `.strip` - der Seitenwechsel in der Attrappe. Er ist eine Treppe
     mit Haltephasen (2,5 % Fahrt), also im Ergebnis ein Schnitt, und
     die Pfeile zeigen auf bestimmte Seiten. Ohne ihn zeigen sie ins
     Leere.
   - `.act-rail i` und `.scroll-progress` - wachsende Balken. Sie sind
     Ortsangaben auf einer 26.000px hohen Seite, kein Effekt.
   - Die Farbwanderung. Farbe ist keine Bewegung, und mehrere
     Kleinschriften sind fuer den violetten Grund abgestimmt.
   ============================================================= */

/* Die Beats: die Abfolge bleibt, das Anheben faellt weg. Der Zuschnitt
   ueber `--vis` BLEIBT - er ist die Hit-Testing-Maske aus Abschnitt 37,
   keine Bewegung. Wer ihn hier mit abraeumt, macht die Kaufknoepfe
   wieder tot. */
html[data-motion="calm"] .beat { transform: none; }

/* Die Pfeile stehen sofort in ihrer Endform, statt zu wachsen. */
html[data-motion="calm"] .cue-dot   { transform: none; }
html[data-motion="calm"] .cue-label { transform: none; }
html[data-motion="calm"] .cue-line  { width: var(--len, 0px); }

/* Die Uebergabe. Statt einer Maske, die sich zusammenzieht, samt
   Unschaerfe und Verkleinerung, blendet die alte Seite aus und gibt die
   neue frei. Dieselbe Aussage, kein Bild, das sich bewegt. */
html[data-motion="calm"] .site-old {
  clip-path: none !important; filter: none !important; transform: none !important;
  opacity: calc(1 - var(--close, 0));
}

/* Der Zoom auf die Marke ist die groesste Bewegung der Seite: eine
   Maske, die von 24px auf Bildschirmgroesse aufgeht. Calm faellt sie
   weg - die Attrappe steht von Anfang an im Rahmen. */
html[data-motion="calm"] .seq-screen { clip-path: none; }
html[data-motion="calm"] .seq-mark   { display: none; }
html[data-motion="calm"] .demo-frame { opacity: 1; }
html[data-motion="calm"] .seq-chrome { opacity: 1; }

/* Balken und Knopf stehen auf ihrem Wert, statt hineinzuwachsen. */
html[data-motion="calm"] .chart-bar  { transform: none; }
html[data-motion="calm"] .chart-tick { transform: none; }
html[data-motion="calm"] .scene-btn  { transform: none; }

/* Fliesstext, der im Wisch-Akt aufsteigt (Abschnitt 39a). */
html[data-motion="calm"] [data-gesture="wipe"] p.st,
html[data-motion="calm"] [data-gesture="wipe"] .chart-note,
html[data-motion="calm"] [data-gesture="wipe"] .lede { transform: none !important; }

/* Der Drift der Index-Ziffer, siehe Abschnitt 33. */
html[data-motion="calm"] { --drift: 0; }

/* Sanftes Scrollen ist selbst Bewegung. Die Medienabfrage in Abschnitt 3
   schaltet es bei `reduce` ab und deckt damit auch den Fall ohne
   JavaScript. Wer die Bewegung ausdruecklich einschaltet, bekommt sie
   hier zurueck - das Attribut ist spezifischer als `html`. */
html[data-motion="full"] { scroll-behavior: smooth; }

/* =============================================================
   42 - Der Schalter fuer eine falsch anliegende Vorgabe
   Auf zwei von fuenf Geraeten des Betreibers meldete das System
   `prefers-reduced-motion: reduce`, ohne dass ein Mensch das gewaehlt
   hatte - ein verwaltetes Windows-Abbild mit abgeschalteten Animationen
   und eine Wayland-Sitzung ohne GTK-Einstellungsdienst. Fuer diesen
   Leser gibt es keinen Weg, die Vorgabe im Browser zu widerrufen.

   Also gibt die Seite ihm einen. Der Schalter erscheint **nur** im
   Calm-Modus, sitzt unter dem Hero-Knopf, ist eine Zeile Monospace und
   merkt sich die Wahl in `localStorage`. Die Gegenrichtung braucht er
   nicht: wer Bewegung nicht will, stellt sie im System ab, und das
   respektiert die Seite von selbst.

   Die Reihenfolge ist Absicht: die Systemvorgabe gilt, bis der Leser
   ausdruecklich widerspricht. Nicht umgekehrt.
   ============================================================= */
.motion-optin { display: none; }
html[data-motion="calm"] .motion-optin {
  display: inline-flex; align-items: center; gap: .5rem;
  margin-top: 1rem; padding: .5rem .8rem;
  background: none; border: 1.5px solid var(--rule-2); border-radius: var(--r-md);
  color: var(--ink-3); font-family: var(--mono);
  font-size: .68rem; letter-spacing: .1em; text-transform: uppercase;
  cursor: pointer;
  /* 44px Mindesthoehe, wie jedes andere Bedienelement der Seite */
  min-height: 44px;
  transition: color 160ms ease, border-color 160ms ease;
}
html[data-motion="calm"] .motion-optin::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--go); flex: none;
}
@media (hover: hover) and (pointer: fine) {
  html[data-motion="calm"] .motion-optin:hover { color: var(--ink); border-color: var(--ink); }
}
html[data-motion="calm"] .motion-optin:focus-visible {
  outline: 2px solid var(--go); outline-offset: 2px;
}

/* =============================================================
   43 - Der Prueflauf: scrubbt die Zeitleiste WIRKLICH?
   `CSS.supports("animation-timeline", "view()")` sagt nur, dass der
   Browser die Eigenschaft PARST. Es sagt nicht, dass sie etwas tut.
   Genau diese Luecke hat den ganzen Vorfall vom 02.09.2026 erzeugt:
   `@supports` war wahr, der Rueckfall griff deshalb nicht, und
   gelaufen ist trotzdem nichts.

   site.js baut daraus einen echten Beweis: dieses Geruest kurz an den
   Body haengen, im eigenen Scroll-Container auf die Haelfte scrollen,
   zwei Frames warten, die Breite des Balkens messen. Steht sie bei
   rund 50 %, scrubbt die Zeitleiste. Steht sie bei 0 oder 100, nicht -
   und dann uebernimmt der JS-Antrieb (Abschnitt 41).

   Aus dem Bild geschoben, nicht auf `opacity: 0` oder
   `visibility: hidden`: beides koennte eine Engine als Grund nehmen,
   die Animation nicht zu rechnen - und dann prueft der Prueflauf sich
   selbst kaputt.
   ============================================================= */
.tl-probe { position: absolute; left: -9999px; top: 0; width: 200px; pointer-events: none; }
.tl-probe-scroll { height: 40px; overflow: auto; }
.tl-probe-wrap { position: relative; height: 400px; view-timeline-name: --tlprobe; }
.tl-probe-stage { position: sticky; top: 0; height: 40px; }
.tl-probe-bar { width: 0; height: 2px; background: currentColor; }
@supports (animation-timeline: view()) {
  .tl-probe-bar {
    animation: tl-probe-run linear both;
    animation-timeline: --tlprobe;
    animation-range: entry 100% exit 0%;
  }
  @keyframes tl-probe-run { from { width: 0; } to { width: 100%; } }
}
