/* ============================================================================
   The RIDE, MOBILITY and THE BUILD picture mosaics, and the scroll reveals
   ----------------------------------------------------------------------------
   Shipped. Loaded from index.html together with fx.js. Every selector is gated on
   an attribute on <html>, both default to on:

       data-mos = "1"  the pictures are a mosaic; "0" restores the old carousel
       data-fx  = "1"  copy, headings and the hero move on scroll (always on;
                       ?fx=0 is an escape hatch, there is no button)

   Append ?mos=0 or ?fx=0 to any URL to compare against the old behaviour — the
   carousel code is still there and still works, it is simply not what is shown.

   WHAT CHAMPN.CLUB ACTUALLY DOES, having read it rather than guessed:

     grid-cols-3 · auto-rows-[240px] · some tiles row-span-2

   That is the whole trick. A plain three-column grid with a FIXED row height,
   and a few tiles spanning two rows. Nothing is measured, nothing is packed by
   script, there is no masonry library — the irregularity comes from a handful of
   spans in the markup. Each tile is overflow-hidden with the picture scaling to
   1.06 over 700ms on hover, and that hover is most of what reads as "expensive".

   Here the tiles are placed explicitly instead of by span, because these three
   strips have six, three and six pictures in a known order and auto-placement
   would put the wide bike shot in a portrait hole.
   ========================================================================== */

:root{--fxE:cubic-bezier(.22,.61,.36,1)}

/* ---------- reveal: headings and copy ------------------------------------
   .rv is added by fx.js, never in the markup — if the script fails, nothing is
   left at opacity:0 and the page is simply the old page. */
html[data-fx="1"] .rv{opacity:0;transform:translateY(26px);
  transition:opacity .85s var(--fxE) var(--rvd,0ms),transform .85s var(--fxE) var(--rvd,0ms)}
html[data-fx="1"] .rv.in{opacity:1;transform:none}

/* ---------- the picture layer -------------------------------------------
   fx.js copies each tile's background onto an <i> so it can be scaled on its
   own. The tile keeps its own background as well, untouched — openLightbox()
   reads it with getComputedStyle, and a tile that had given its picture away
   would open an empty lightbox. */
.mosimg{display:none}
.moscap{position:relative;z-index:1}

html[data-mos="1"] .strip[data-mos] .mosimg{display:block;position:absolute;inset:0;
  background-repeat:no-repeat;will-change:transform}

/* ---------- mosaic --------------------------------------------------------
   EVERY rule below is `.strip[data-mos]`, never a bare `.strip`, and that is not
   tidiness. fx.js writes data-mos onto the strips it has PREPARED — the ones whose
   tiles it gave a .mosimg and put under the observer, which are the three its
   selector names (#ride, #mobility, #sustainability). THE BUILD in section 04 was
   not on that list at first, and that is how this rule was learned.

   With a bare `.strip` the grid and `opacity:0` reached THE BUILD too, while
   the `.in` that restores opacity never could, because nothing observed it. Four
   photographs went to zero opacity and stayed there. No error, no warning, nothing
   in the console — the section simply had a hole in it where the pictures were.

   If a fourth strip ever appears, it is safe by default: until it is added to that
   selector it gets no attribute, so none of this applies to it. */
html[data-mos="1"] .strip[data-mos]{display:grid;grid-template-columns:repeat(3,1fr);
  gap:14px;grid-auto-rows:clamp(128px,15.5vw,200px);
  max-width:var(--col);margin-inline:auto;padding:30px 40px}
html[data-mos="1"] .strip[data-mos] .stage{display:contents}
html[data-mos="1"] .strip[data-mos] .carnav{display:none}

html[data-mos="1"] .strip[data-mos] .item{position:relative;inset:auto;width:auto;height:auto;
  flex:none;aspect-ratio:auto;overflow:hidden;background-color:var(--surf2);
  /* stripTick() drives .item transforms on touch; the mosaic must ignore that,
     so the reveal moves the PICTURE and never the tile. */
  transform:none!important;
  /* Every tile takes the mouse (30 Sep 2026). The carousel in index.html stacks its slides and
     sets pointer-events:none on each one but .on, so the hidden ones stay untouchable. A mosaic
     has no stack, but it inherited that rule: on a desktop with a mouse (900px+, pointer:fine)
     only the FIRST picture of each strip opened the lightbox or zoomed on hover, and a click on
     any other fell through to the strip. No error; it simply did nothing. element.click() from a
     script skips hit-testing, so only a real click shows it. ?mos=0 keeps the carousel rule. */
  pointer-events:auto;
  opacity:0;transition:opacity .9s var(--fxE) var(--rvd,0ms)}
html[data-mos="1"] .strip[data-mos] .item.in{opacity:1}

/* the picture settles from slightly oversized — reads better here than a rise,
   and leaves .item's transform free for the rule above */
html[data-mos="1"] .strip[data-mos] .item .mosimg{transform:scale(1.12);
  transition:transform 1.1s var(--fxE) var(--rvd,0ms)}
html[data-mos="1"] .strip[data-mos] .item.in .mosimg{transform:scale(1)}
/* must stay last: same specificity as the rule above, so order decides */
html[data-mos="1"] .strip[data-mos] .item:hover .mosimg{transform:scale(1.06);transition-duration:.7s}

/* ---------- placement: RIDE has six, MOBILITY three -----------------------
   The generic five below is what RIDE had until 1 Oct 2026 (graffiti, silver, motor,
   belt, logo); no strip has five today but it stays as the fallback. RIDE's six have
   their own rules further down (.strip.ride[data-mos="6"]).
       ┌─────────────┬───────┐        ┌─────────────┬─────────┐
       │             │ motor │        │             │ product │
       │  graffiti   ├───────┤        │  on the road├─────────┤
       │             │ belt  │        │             │  black  │
       ├─────────────┼───────┤        └─────────────┴─────────┘
       │             │       │
       │   silver    │ logo  │
       │             │       │
       └─────────────┴───────┘                                            */
@media(min-width:720px){
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(1){grid-column:1/3;grid-row:3/5}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(2){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(3){grid-column:3;grid-row:1}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(4){grid-column:3;grid-row:2}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(5){grid-column:3;grid-row:3/5}

  html[data-mos="1"] .strip[data-mos="3"] .item:nth-child(1){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip[data-mos="3"] .item:nth-child(2){grid-column:3;grid-row:1}
  html[data-mos="1"] .strip[data-mos="3"] .item:nth-child(3){grid-column:3;grid-row:2}

  /* THE BUILD has four, laid out as a pinwheel rather than two rows of two —
     big/tall, then tall/big with the sides swapped. Two-by-two reads as a table;
     this reads as a decision, and it gives the terrace and the kitchen the room
     they need while the arch and the arrival stay upright, which suits them.
       ┌─────────────┬───────┐
       │   terrace   │ arch  │
       ├───────┬─────┴───────┤
       │kitchen│   arrival   │
       └───────┴─────────────┘                                                */
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(1){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(2){grid-column:3;grid-row:1/3}
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(3){grid-column:1;grid-row:3/5}
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(4){grid-column:2/4;grid-row:3/5}

  /* THE BUILD grew a fifth picture and would otherwise have inherited [data-mos="5"],
     which is the BIKE strip's composition: a tall right-hand cell in slot 5 and a wide
     one in slot 2. That is the wrong way round for these photographs — the arch is the
     portrait here and the sunset is the panorama, so the bike layout would have laid
     the upright picture on its side and squeezed the wide one into a column.
     The composition follows the PICTURES, and the pictures changed: the kitchen and
     the arrival photographs were replaced by two 3:2 interiors, which left the arch as
     the only portrait in the set. Two upright cells for one upright picture no longer
     works, so the second band swaps sides — two small landscape cells stacked on the
     left, Corsica wide on the right. Every tile now matches the shape of what is in it:
     1.37 for the 1.33 terrace, 0.67 for the 0.75 arch, 1.39 twice for the 1.50
     interiors, 1.37 for the 1.33 sunset. The sides still swap between the bands, so it
     still reads as a decision rather than a table.
       ┌─────────────┬───────┐
       │   terrace   │ arch  │
       ├───────┬─────┴───────┤
       │living │             │
       ├───────┤   corsica   │
       │kitchen│             │
       └───────┴─────────────┘                                                */
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(1){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(2){grid-column:3;grid-row:1/3}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(3){grid-column:1;grid-row:3}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(4){grid-column:1;grid-row:4}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(5){grid-column:2/4;grid-row:3/5}

  /* THE BUILD has SEVEN since 2 Oct 2026. The terrace, the big cell top left, is gone for good
     (docs/OWNER-DECISIONS.md, THE BUILD). In came the drone photo
     of the house ("unverbauter und spektakulärer Meerblick") and Isola Gallinara at sunset. Every
     picture keeps its own shape: the pergola and the BEZZERA upright (3:4), the two living rooms in
     small cells (3:2), and both sunsets wide — Corsica must be, its horizon sits at 84 % of the photo,
     and in a small cell it would be under the caption. With the drone photo upright too, that is
     exactly twelve cells: the same four rows as with six, so the strip is exactly as tall as before
     (the owner's rule: nothing above the booking gets longer, docs/OWNER-DECISIONS.md, Design).
       ┌───────┬───────────────┐
       │       │   gallinara   │   the house and the sea,
       │ house ├───────┬───────┤   the island at sunset
       │       │       │       │
       ├───────┤pergola│bezzera│   the two uprights, framed by
       │ big   │       │       │   the two sunsets
       ├───────┼───────┴───────┤
       │ small │    corsica    │   the two living rooms under the house
       └───────┴───────────────┘
     The two sunsets share the right-hand columns but never touch: the uprights stand between them.
     Shapes at 1440px: the house and the uprights 0.67 (all of the house, whose photo is 4:3, in a crop
     of its middle half), the living rooms 1.39, both sunsets 2.85. Placement follows the markup order
     (top row, then left to right), so the lightbox swipes through them as the eye reads them, here
     and on the phone. The four rows are equal now; with six they were 1, 1, 0.9, 1.1 — the same sum. */
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(1){grid-column:1;grid-row:1/3}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(2){grid-column:2/4;grid-row:1}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(3){grid-column:2;grid-row:2/4}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(4){grid-column:3;grid-row:2/4}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(5){grid-column:1;grid-row:3}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(6){grid-column:1;grid-row:4}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(7){grid-column:2/4;grid-row:4}

  /* RIDE has SIX since 1 Oct 2026: the graffiti wall gave its big cell to the jump above the sea
     (Tom: "statt des trail ready mit der bunten wand") and a second jump came in ("und bring noch
     das foto unter bei 01"). Both are 3:2 action photographs and both get a big cell, so the rider
     is large enough to feel the take-off. At their shapes the six add up to exactly the twelve cells
     the five had — two big, four small — so the strip keeps its four rows and its height (the owner's
     rule: nothing above the booking gets longer, docs/OWNER-DECISIONS.md, Design):
       ┌───────────────┬───────┐
       │               │ bike  │   the jump above the sea, with the clean bike
       │  jump · sea   ├───────┤
       │               │ motor │   and the motor that drives it
       ├───────────────┼───────┤
       │               │ belt  │   the jump in the pines, with the belt
       │  jump · pines ├───────┤
       │               │  app  │   and the app, right above its button
       └───────────────┴───────┘
     The bands do not swap sides like THE BUILD's: the eye then meets the tiles in markup order, so
     the lightbox swipes through them as they are read here and on the phone. What the bike gave up
     (a big cell) the product shots keep in shape: the small cells are 1.39:1 at 1440px, and the
     clean bike, the motor and the logo are `contain` on their own colour (index.html), so all of
     each stays in the frame. The big cells are 1.37:1 at 1440px and up to 1.86:1 on a window of
     920-940px (1.76 at 1000px); the photographs' background-position (index.html) keeps the rider
     and both wheels in all of them. */
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(1){grid-column:3;grid-row:1}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(2){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(3){grid-column:3;grid-row:2}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(4){grid-column:1/3;grid-row:3/5}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(5){grid-column:3;grid-row:3}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(6){grid-column:3;grid-row:4}

  /* RIDE has SEVEN since 2 Oct 2026: our own S16 photo in the bike's small cell, and the fleet in the garage
     (Tom: "nimm auch das Bild mit allen Rädern in der Garage da rein"), upright 3:4, gets the tall cell beside
     the jump in the pines. The SUPERNOVA handlebar light (in the belt's place since 2 Oct 2026, the belt is in BIKE
     DETAILS now) and the logo share a fifth row — the logo is `contain` on black, so it takes the wide cell.
     One row more than six (Tom asked for the picture):
       ┌───────────────┬───────┐
       │               │ sea   │
       │  NICOLAI S16  ├───────┤
       │               │ motor │
       ├───────────────┼───────┤
       │               │       │
       │  jump · pines │ fleet │
       │               │       │
       ├───────┬───────┴───────┤
       │ nova  │      app      │
       └───────┴───────────────┘ */
  /* 3 Oct 2026 (Tom: "das NICOLAI S16 Bild oben rechts statt Airtime größer machen, Airtime hingegen kleiner oben rechts,
     finde das Rad ohne alles so hübsch"): the S16 (first in the markup) takes the big cell, the jump above the sea the small one. */
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(1){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(2){grid-column:3;grid-row:1}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(3){grid-column:3;grid-row:2}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(4){grid-column:1/3;grid-row:3/5}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(5){grid-column:3;grid-row:3/5}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(6){grid-column:1;grid-row:5}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(7){grid-column:2/4;grid-row:5}

  /* MOBILITY HAS ONE since 2 Oct 2026 (Tom: "die 3 Bilder ersetzt du erstmal mit diesem hier", until the
     beach shoot): the four scooters on the terrace, across all three columns and both rows the three had,
     so the strip is exactly as tall as before (474px at 1440x900; the owner's rule: nothing above the
     booking gets longer). [data-mos="1"] on the STRIP is its tile count, the one on <html> the mode. */
  html[data-mos="1"] .strip[data-mos="1"] .item{grid-column:1/4;grid-row:1/3}
  /* TALLER (Tom, 2 Oct 2026: "dass man auch den Roller vorne ganz sieht"): the tile's own 5:4 instead of
     the two grid rows (2.1:1), so the front scooter is whole — 860x688 in the laptop column. He asked for
     the height himself, so the owner's "nothing above the booking gets longer" gives way here. */
  html[data-mos="1"] .strip[data-mos="1"]{grid-auto-rows:auto}
  html[data-mos="1"] .strip[data-mos="1"] .item{grid-row:auto;aspect-ratio:5/4}
}

/* phones: two columns, auto-placed. The big pictures take the full width, the
   details sit beside each other — same rhythm, half the room. */
@media(max-width:719px){
  html[data-mos="1"] .strip[data-mos]{grid-template-columns:repeat(2,1fr);gap:10px;
    grid-auto-rows:clamp(104px,27vw,150px);padding:26px 24px}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(1),
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(2),
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(1),
  html[data-mos="1"] .strip[data-mos="3"] .item:nth-child(1){grid-column:span 2;grid-row:span 2}
  html[data-mos="1"] .strip[data-mos="5"] .item:nth-child(5),
  html[data-mos="1"] .strip[data-mos="4"] .item:nth-child(4){grid-column:span 2}

  /* and the same correction on phones: the generic five-rule makes tiles 1 AND 2 big,
     which would hand the upright arch a landscape cell. THE BUILD keeps its four as
     they were — one big, two details side by side, the arrival wide — and puts the
     sunset underneath, full width, where a horizon belongs. */
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(2){grid-column:auto;grid-row:span 2}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(3),
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(4){grid-column:auto;grid-row:auto}
  html[data-mos="1"] .strip.thebuild[data-mos="5"] .item:nth-child(5){grid-column:span 2;grid-row:auto}

  /* THE BUILD with seven on a phone: the drone photo big — on a phone its caption runs to three lines
     in an upright cell and would cover the house — then Gallinara full width, the two uprights side by
     side (each the 3:4 of its photograph, 0.75 at 390px), the two living rooms side by side and Corsica
     full width at the foot as before. Auto-placed, in the markup's order; the sunsets are four rows
     apart. Seven rows where six had six: one row more (+115px at 390px, +114 at 360, +160 at 719),
     because the house needs the big cell here. */
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(1){grid-column:span 2;grid-row:span 2}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(2),
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(7){grid-column:span 2}
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(3),
  html[data-mos="1"] .strip.thebuild[data-mos="7"] .item:nth-child(4){grid-row:span 2}

  /* RIDE with six on a phone: the same two bands as on the laptop, one under the other — each jump
     full width (1.55:1 at 390px, as good as the 3:2 photograph itself), its two details side by side
     under it. Six rows, exactly what the five had (big, big, two details, the logo wide), so the
     strip is no taller. Placed by hand rather than auto-placed: the clean bike comes first in the
     markup but sits beside the motor under the first jump. */
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(2){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(1){grid-column:1;grid-row:3}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(3){grid-column:2;grid-row:3}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(4){grid-column:1/3;grid-row:4/6}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(5){grid-column:1;grid-row:6}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(6){grid-column:2;grid-row:6}
  /* ...and with SEVEN (2 Oct 2026): the fleet takes both rows of the left column under the second jump,
     the SUPERNOVA light and the logo stack beside it — one row more than six. */
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(1){grid-column:1/3;grid-row:1/3}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(2){grid-column:1;grid-row:3}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(3){grid-column:2;grid-row:3}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(4){grid-column:1/3;grid-row:4/6}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(5){grid-column:1;grid-row:6/8}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(6){grid-column:2;grid-row:6}
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(7){grid-column:2;grid-row:7}
  /* ...but above ~556px the rows stop growing (150px cap) while the width does not, so a jump's
     cell widens to 2.16:1 at 719px (671x310) and shows only 69% of the photo's height. The laptop
     positions then cut 3-4px off both helmets (sea: helmet from 8.1%, front tyre to 75%; pines:
     helmet from 11.1%, rear tyre to 76%). These keep helmet and tyres in at every phone width;
     at 390px, where the cell is nearly the photo's own 3:2, they change next to nothing.
     !important because fx.js copies the tile's inline background, position included, onto
     .mosimg; the lightbox reads only the picture, never its position. */
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(2) .mosimg{background-position:40% 22%!important}
  html[data-mos="1"] .strip.ride[data-mos="6"] .item:nth-child(4) .mosimg,
  html[data-mos="1"] .strip.ride[data-mos="7"] .item:nth-child(4) .mosimg{background-position:center 30%!important}

  /* MOBILITY's one picture on a phone: both columns and three rows, the height the big cell and the two
     details had (336px at 390px), so the strip is no taller. Nearly square at 320-430px (1.02:1 at 390),
     so far more of the upright photo shows than on a laptop: `center 70%` keeps the sea and the mountains
     at the top and the front scooter's wheel at the foot (18-92% of the photo's height at 390px); at
     719px (1.43:1) the band is 33-86%, the sea still in. */
  html[data-mos="1"] .strip[data-mos="1"] .item{grid-column:1/3;grid-row:span 3}
}

/* the caption was a bare text node in a flex box; as a span it needs the same
   place at the bottom, and it must sit above the picture layer */
html[data-mos="1"] .strip[data-mos] .item{display:flex;align-items:flex-end;padding:12px}

/* ---------- CAPTIONS: a dark band under the words, on every picture ----------
   The captions carry an inline colour picked for ONE background each — rgba(0,0,0,.45)
   for the white studio shots, the theme ink for the rest. On "PRODUCT", "PINION E1.12 …"
   and "BELT DRIVE …" that was grey on white in the carousel and the touch filmstrip, and
   in the mosaic the scrim was a fixed 58% of the tile, so a caption that wrapped to three
   lines climbed out of it onto the white. The band now belongs to the caption itself: it
   is as tall as the words plus a short lead-in, so however many lines a language needs,
   the darkness is under every one of them — ≥.62 black where the first line starts,
   which is ≥5:1 for white type even on a pure white photograph, .8 at the foot.
   One rule for all three presentations (mosaic, ?mos=0 carousel on a mouse, ?mos=0
   filmstrip on touch) and both themes: over a photograph the type is always white.
   The explicit colour beats the inherited inline one without !important. */
.strip .item .moscap{position:absolute;left:0;right:0;bottom:0;z-index:1;
  padding:20px var(--capx,12px) var(--capx,12px);
  color:#fff;line-height:1.4;text-shadow:0 1px 3px rgba(0,0,0,.45);pointer-events:none;
  background:linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,.62) 16px,rgba(0,0,0,.8) 100%)}
/* PHONE MOSAIC: the small tiles are ~166x105px, and a three-line caption with its 20px
   lead-in covered 70% of the picture it names ("PINION E1.12 …", "LIVING ROOM …"). Tighter
   tracking gets the same words onto fewer lines, and a shorter lead-in still reaches .62
   before the first line starts, so white type keeps ≥5:1 even on a white photograph. */
@media(max-width:719px){
  html[data-mos="1"] .strip[data-mos] .item .moscap{letter-spacing:.12em;line-height:1.35;
    padding:10px 10px 9px;
    background:linear-gradient(180deg,rgba(0,0,0,0) 0,rgba(0,0,0,.62) 8px,rgba(0,0,0,.8) 100%)}
}
/* Japanese has no spaces, so a caption broke wherever the tile ran out of room — "本格エスプ / レッソ",
   "大きいほう / のアパートメント" (30 Sep 2026). Between phrases instead, as on the ALL DAY TURBO stage below
   (Chrome; other browsers keep their own wrapping). */
.strip .item .moscap:lang(ja){word-break:auto-phrase}
/* the carousel frames its slide with 22px, not 12 */
@media (min-width:900px) and (pointer:fine){ html:not([data-mos="1"]) .strip .item{--capx:22px} }
/* fx.js never ran (blocked, failed, or no IntersectionObserver): the caption is still a bare
   text node, which cannot sit above a picture layer — so the band goes UNDER it instead, as
   a negative-z pseudo inside an isolated tile: above the tile's own background, below its
   text. The inline colour needs the !important here, and only here. */
.strip .item:not(:has(.moscap)){isolation:isolate;color:#fff!important;text-shadow:0 1px 3px rgba(0,0,0,.45)}
.strip .item:not(:has(.moscap))::before{content:"";position:absolute;left:0;right:0;bottom:0;height:60%;
  z-index:-1;pointer-events:none;background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.62) 45%,rgba(0,0,0,.8))}

/* ---------- MOBILITY's one picture in the other presentations ----------------
   (the mosaic's placement is above, "MOBILITY HAS ONE"). On a phone the crop moves down in every
   presentation — the tile's own background too, which the ?mos=0 filmstrip paints: !important, because
   the position is in the tile's inline style and fx.js copies it onto .mosimg. */
@media(max-width:719px){
  .strip[data-mos="1"] .item,
  .strip[data-mos="1"] .item .mosimg{background-position:center 76%!important}   /* 70 → 76: the front tyre clear of the edge */
}
/* ?mos=0: the touch filmstrip and the mouse carousel (900px+) show the one picture full width at the
   mosaic tile's own height — a 72% slide would leave an empty quarter, a third-width one two empty
   thirds — and the carousel drops its buttons, which would count "1 / 1" and go nowhere. */
html[data-mos="0"] .strip[data-mos="1"] .item{flex:0 0 100%;aspect-ratio:auto;height:calc(3 * clamp(104px,27vw,150px) + 20px)}
@media(min-width:720px){   /* 5:4 like the mosaic tile (TALLER, above) */
  html[data-mos="0"] .strip[data-mos="1"] .item{height:auto;aspect-ratio:5/4}
}
@media (min-width:900px) and (pointer:fine){
  html[data-mos="0"] .strip[data-mos="1"] .stage{height:auto;aspect-ratio:5/4}
  html[data-mos="0"] .strip[data-mos="1"] .item{height:100%}
  html[data-mos="0"] .strip[data-mos="1"] .carnav{display:none}
}

/* ---------- hero: the logo leaves before the picture does ------------------
   Ready for the drone video. Drop this in as the FIRST child of <section class="hero">
   and it lands behind everything and starts lagging on its own:

     <video class="herobg" autoplay muted loop playsinline poster="assets/…jpg">
       <source src="assets/hero.mp4" type="video/mp4">
     </video>

   Until then the hero background is the CSS gradient on .hero, which cannot be
   translated without moving the whole section — so today only the logo moves, and
   the video half switches itself on the moment the element exists.

   NOTE .hero-logo carries `animation: logoIn … both`, and a filled animation beats
   an inline transform. So the parallax moves .hero-halo, the wrapper — which also
   carries the glow, so mark and halo travel together instead of separating. */
.hero{position:relative}

/* The video sits at z-index 0 and the scrim at 1, so the hero's own gradient has to
   step aside — a background on .hero paints UNDER a positioned child, which is why
   the first version of this had the video invisible behind it. */
/* Hidden until the script has actually chosen a source. Without that, a scriptless
   visit would lay out an empty 128%-tall video box and let it hang out of the hero. */
.hero .herobg{display:none}
.hero.hasvideo .herobg{display:block;position:absolute;left:0;right:0;top:0;height:128%;
  width:100%;object-fit:cover;z-index:0;pointer-events:none;will-change:transform}

/* 128% tall, anchored at the TOP (Tom, 30 Sep 2026, on the new Effektversion: "die szene
   hast du zu tief eingefasst, man sieht keinen kopf" / "auch die szene davor hat kaum das
   meer sichtbar"). It used to be pulled up 14%, which cut the top ~11% of every frame —
   exactly where this edit puts the riders' heads, the horizon and the sea. The extra 28%
   now hangs below the hero instead (overflow:hidden, see below) and costs only foreground
   gravel under the slogan. The parallax still works: it translates the video DOWN by up to
   12% of the hero height (LAGS in fx.js) while the hero itself moves up by the full scroll,
   so the strip that opens above the video is always above the top of the screen, never on it.
   overflow:hidden is the other half of that decision and is NOT optional — without
   it the oversized video simply spills past the hero and lies across the top of the
   next section as a bright band over its copy. */
.hero.hasvideo{overflow:hidden}

/* WIDE SCREENS: THE TEXT KEEPS THE PAGE'S COLUMN, THE PICTURE FILLS THE HERO.
   The picture used to be held to var(--col) too, because the only clip it had was the
   portrait 9:16 cut, and stretched across 1920px object-fit cropped the rider and the trail
   out of frame. There is a landscape cut now (hero-wide.mp4, 1600x900 since 30 Sep 2026), and the column had
   turned into its own bug: the script picked the cut for the 940px box, which is portrait on
   every laptop, so a 1440x900 window played the PORTRAIT clip in a 940px strip with the
   gradient showing as dark bars either side (design review, 29 Sep 2026). The video spans
   the hero again and the inline script in index.html measures the full width, so a wide
   window gets the wide cut. The logo stays in the column.
   The slogan and the scroll cue no longer do (29 Sep 2026): .hero-low carries its own width now,
   the phone's composition scaled by --hw (index.html, HERO), and the column's 40px sides would
   have pushed the slogan away from the logo again. */
@media(min-width:1000px){
  .hero-mid{width:100%;max-width:var(--col);margin-inline:auto}
}
/* NO background override, and no background on the video either. Both used to be
   var(--bg) — near-black — and that black was what showed in the seconds before the
   poster had arrived on a weak line: the hero went dark, then jumped to a picture.
   Without them the hero's own teal gradient stays visible underneath, so the loading
   state is the brand rather than a hole. */
/* :not(.herobg) is load-bearing. A blanket `> *` also hits the video — it is a child
   too — and `.hero.hasvideo>*` has the same specificity as `.hero .herobg` while
   coming later, so it won the cascade and put position:relative back on the video.
   The video rejoined the flow, made the hero 1483px tall instead of 700, and pushed
   the logo and the whole slogan below the fold. */
.hero.hasvideo>*:not(.herobg){position:relative;z-index:2}

/* Two scrims, because the hero asks two different things of the picture. The radial
   one darkens the middle where the logo stands; the linear one weights the foot,
   where the slogan and the scroll arrow live. Measured on the actual frames: the
   footage sits at ~50% luminance, and white type needs it below ~30% to clear 4.5:1.

   The radial one is deliberately WEAK. Measured on the sea frame the water reads 56%
   luminance — and the mark with its wordmark is large, so the 3:1 floor applies and
   the raw picture already clears it with nothing on top. 60% was killing the one shot
   the whole section exists for: you could not see the sea. 20% seats the logo without
   drowning what is behind it. */
.hero.hasvideo::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(52% 34% at 50% 30%, rgba(0,0,0,.20), rgba(0,0,0,0) 74%),
    linear-gradient(180deg, rgba(0,0,0,.24) 0%, rgba(0,0,0,.04) 16%,
                            rgba(0,0,0,.14) 38%, rgba(0,0,0,.60) 70%,
                            rgba(0,0,0,.92) 100%)}
/* The top third is LIGHTER since 30 Sep 2026 (was .46 → .12 → .40 at 40%): the new footage
   keeps the sea, the horizon and the riders' heads there, and the old weighting darkened
   exactly that band. The foot keeps its full weight for the slogan; the header chips carry
   their own backgrounds. */

/* Over a photograph the type is always light, whatever theme the visitor picked —
   the light theme would otherwise put near-black text on a darkened video.

   `color` here is NOT redundant next to --ink, and leaving it out was a real bug:
   colour INHERITS AS A COMPUTED VALUE. .slogan and .hero-logo declare no colour of
   their own, so they inherit whatever body resolved from the theme's --ink long
   before this rule applies — redefining the variable deeper in the tree cannot reach
   them. Only elements that use var(--ink) themselves pick the override up, which is
   why the kicker went white in light mode and the logo and slogan stayed near-black
   and invisible on the video. Setting colour on .hero fixes every descendant at once.

   The teal is LIGHTENED here, and that is not taste. Measured on the rendered page:
   the scrim leaves ~35% luminance where "STAY HERE. GO ANYWHERE." sits. White on
   that is 7:1, but brand teal #25A599 is only 2.3:1 — under the 4.5:1 floor and
   genuinely hard to read on the sandy frames. #9EE9E2 is the same hue and saturation, lifted to 77% lightness, and clears 4.6:1.
   It applies ONLY over video; everywhere else the brand teal is untouched. */
.hero.hasvideo{--ink:#ffffff;--ink2:#eef2f1;--ink3:#dfe6e4;--grey:#ccd6d4;
  --lineRGB:255,255,255;--tealT:#9EE9E2;--magT:#EBB4F2;
  color:#ffffff}

/* A hairline shadow buys back the worst case — a bright pebble exactly behind a
   thin mono stroke — without making the type look like it is floating. */
.hero.hasvideo .stayline,.hero.hasvideo .slogan,.hero.hasvideo .scrollcue .stext{
  text-shadow:0 1px 10px rgba(0,0,0,.55)}

html[data-fx="1"] .hero-halo{will-change:transform,opacity}

/* The halo behind the mark is a BLACK glow in the dark theme (--haloRGB is 0,0,0) at
   min(88vw,360px). Over a gradient it lifts the logo; over the sea it WAS the dark
   blob in the middle of the view. The logo carries two drop-shadows of its own, so
   this only has to whisper. */
.hero.hasvideo .hero-halo::before{
  background:radial-gradient(circle,rgba(0,0,0,.26) 0%,rgba(0,0,0,.12) 45%,transparent 70%)}

/* The logo's own filter is drop-shadow(0 14px 38px rgba(var(--haloRGB),.65)) — and
   --haloRGB is 0,0,0 in the dark theme and 255,255,255 in the light one. So the mark
   carried a 38px BLACK shadow on dark and a white glow on light: the same page looked
   clean over the sea in one theme and had a muddy blob around the logo in the other.
   That was the vignette, not the scrim I built.
   Pinned to the light-theme behaviour in both, because over a photograph a light glow
   lifts the mark without dirtying what is behind it. The tight dark shadow stays —
   that one is the edge definition against busy footage. */
.hero.hasvideo .hero-logo{
  filter:drop-shadow(0 14px 38px rgba(255,255,255,.65)) drop-shadow(0 2px 10px rgba(0,0,0,.5))}

/* ---------- nobody gets moved who asked not to be ------------------------- */
@media(prefers-reduced-motion:reduce){
  html[data-fx="1"] .rv{opacity:1;transform:none;transition:none}
  html[data-mos="1"] .strip[data-mos] .item{opacity:1;transition:none}
  html[data-mos="1"] .strip[data-mos] .item .mosimg,
  html[data-mos="1"] .strip[data-mos] .item:hover .mosimg{transform:none;transition:none}
}

/* ============================================================================
   ENDLESS BATTERY — the scroll story before USP 01
   ----------------------------------------------------------------------------
   A tall section with a sticky stage. Everything inside is driven from one scroll
   progress value in fx.js; nothing here animates on its own.

   LIGHT ON PURPOSE. The bike is a studio shot on white and the battery is black —
   on the site's near-black ground the first would be a white box and the second
   would be invisible. A bright stage solves both, and after a dark video hero the
   page opening up is the beat itself.

   FOUR STEPS, 350vh (design pass, Tom, 29 Sep 2026). It had grown to 850vh — 750vh of
   scrolling for nine sentences, with the lines stacking and cross-fading over each other
   — and it was the single biggest reason the price section, where the menu and the
   bottom bar arrive, sat 23 screens down. Now every sentence is still there, grouped:
     1  the bike · Imagine … · A NICOLAI S16. · A Pinion motor with the gearbox built in.
     2  No chain. No derailleur. No heavy cassette. · Quiet on the way down. Strong …
     3  five batteries · And now imagine the battery never runs out … · the swap sentence
     4  Welcome to [the ALL DAY TURBO logo]™
   and only ever ONE step is on the stage: the old one has faded out completely at the
   point where the next one begins to fade in (fx.js, STEPS). 250vh of travel, weighted by
   what each step has to say, and the first and last step get the entry and the exit
   screen on top. The stage is never left empty (Tom, 30 Sep 2026, a friend's S25: "Beim
   Scrollen kommt eine Seite mit keinem Text") — see NO EMPTY STAGE in fx.js.

   ONLY WITH ?story=inline SINCE 2 OCT 2026: by default the story is a popup (THE STORY AS A POPUP, below) and the
   section is never in the page flow; these rules then style what fx.js moved into the popup, under that block's own.
   The scroll story stays here so Tom can still compare, until he retires it.

   THREE STATES, set on <html> as data-adt (30 Sep 2026):
     absent   the LIST — every sentence one under the other, bike and batteries above,
              the same layout reduced motion gets. That is what anyone sees whose browser
              does not run the story: fx.js blocked or failed, ?fx=0, no
              IntersectionObserver, reduced motion. It used to be one sentence out of
              eight across 350vh of white, because the steps were hidden by default.
     "1"      set in index.html's <head> when the story is going to play, so the pinned
              stage is there from the first paint and nothing shifts. Until fx.js runs it
              shows the last frame of the story: the closing line with the logo, where it
              will stand at step 4 — a finished poster of the idea rather than a blank
              screen. Not the bike and its batteries any more (1 Oct 2026): the logo is
              taller than the wordmark text was, and on a short or wide screen (a phone held
              sideways, a laptop) the closing line now reaches up to where the bike stands,
              which it never meets on the stage (step 4 comes after the bike has gone). A
              watchdog there drops back to the list at DOMContentLoaded if fx.js did not
              take over.
     "live"   fx.js drives it; a throw in the story releases it to the list. */
/* PURE white, not the theme's #f3f5f5: the bike is a studio shot on #ffffff, and one
   step off leaves a visible rectangle around it instead of a bike floating on a page.
   Sampled from the file rather than assumed. --adtH is read by the snap markers too. */
.adt{--adtH:350vh;background:#ffffff;color:#0b100f;position:relative;height:var(--adtH)}
/* LIGHT THEME: THE SECTION FLIPS. It is built as the one INVERSE block of the page —
   white against a dark site. In the light theme the site is light, so the same idea
   needs the opposite: dark against light. The accent flips back to the bright brand
   teal, which is unreadable on white (3.0:1) and correct here (6.5:1 on this ground). */
html[data-theme="light"] .adt{background:#0a0a0a;color:#f2f5f4}
html[data-theme="light"] .adtlines .t{color:#25A599}
html[data-theme="light"] .adtlines .m{color:#25A599}
html[data-theme="light"] .adtlines p.adtbsub{color:#e9eeec}
html[data-theme="light"] .adtbatt img{filter:drop-shadow(0 8px 18px rgba(0,0,0,.55))}
@media(prefers-reduced-motion:reduce){.adt{height:auto}}

/* COLLAPSED, once the sequence has been played and the visitor has moved past it.
   The section folds down to a band the height of its own picture — the story is told,
   the room it needed is given back. fx.js does the fold at the one safe moment and
   corrects the scroll position by exactly the height it removed, so nothing jumps. */
.adt.done{height:auto}
.adt.done .adtstage{position:static;height:auto;padding:clamp(20px,5vh,54px) 24px}
.adt.done .adtlines,.adt.done .adtbatt{display:none}

/* ---- EXPERIMENT: settle on each step (?snap=1) ---------------------------
   Markers sit at the same fractions of the scroll span that fx.js animates on.
   scroll-snap-type lives on the scroll container, so it has to be on <html>; with
   proximity and targets only inside this one section, nothing else on the page can
   snap. Off by default — a scroll that takes over from the reader is exactly the
   kind of thing that feels broken on a phone, so this ships as something to try,
   not as the behaviour. */
html[data-snap="1"]{scroll-snap-type:y proximity}
.adtsnap{display:none}
html[data-snap="1"][data-adt] .adtsnap{display:block;position:absolute;left:0;width:1px;height:1px;
  pointer-events:none;scroll-snap-align:start;
  top:calc((var(--adtH) - 100svh) * var(--sp))}

/* Centred in the stage, not pinned to its top: on a tall phone the story used to sit
   in the upper half with the lower 45% of the screen white. `safe` keeps the bike on
   screen when the stage is shorter than its content (a phone in landscape) — there it
   falls back to the top, which is what flex-start did; a browser without `safe` keeps
   the first declaration. The block never moves between steps: the steps share one
   grid cell of constant height (below), and the batteries' row is always laid out.
   EVERY svh IN THE STORY HAS A vh FALLBACK (30 Sep 2026). Without one, a browser that has
   no svh gave the stage no height at all: it took the height of its content, 517px on a
   412px landscape screen, with the closing line below the edge. Two ways, depending on
   whether the declaration contains var():
     - no var() (this height, the bike's and the batteries' max-height, the swap
       sentence's margin): a vh declaration in front, the pattern .adtshot uses in
       index.html. A browser without svh drops the svh one and keeps the vh one; browsers
       with svh take the second one, so nothing changes there.
     - with var() (the three font sizes, which carry var(--ts), and the snap markers'
       top): the vh versions are in an @supports not (height:1svh) block right after the
       closing line's rule. A vh line in front does NOT work there. A declaration with
       var() is only checked once the variable is filled in, so a browser without svh
       keeps the svh one, finds it invalid too late, and falls back to the inherited
       value: the steps at 16px instead of 18px and the closing line at 16px instead of
       26px on a phone, every snap marker at 0 (measured in Chrome 105, 30 Sep 2026). */
.adtstage{position:sticky;top:0;height:100vh;height:100svh;display:flex;flex-direction:column;
  align-items:center;justify-content:flex-start;justify-content:safe center;gap:clamp(14px,3vh,32px);
  padding:clamp(14px,4vh,46px) 24px 24px;overflow:hidden}
@media(prefers-reduced-motion:reduce){.adtstage{position:relative;height:auto;padding:64px 24px}}

.adtart{display:flex;flex-direction:column;align-items:center;gap:clamp(10px,2.4vh,26px)}

/* THE ARTWORK MUST BE BOUNDED BY HEIGHT, NOT ONLY BY WIDTH. The stage above is
   100svh with overflow:hidden — bounded by HEIGHT — so a bike sized purely off the
   viewport WIDTH filled a wide, short stage and every statement was clipped away below
   the edge (measured at 812x375). max-height with width:auto keeps the aspect ratio and
   only bites where the height is genuinely short. */
.adtbike{display:block;width:auto;max-width:min(60vw,480px);max-height:36vh;max-height:36svh;height:auto}

/* five slots, always laid out — only their opacity and offset change, so nothing
   reflows as they arrive and the bike never jumps.
   NO will-change ON THEM, ON PURPOSE (Tom, 30 Sep 2026, on his iPhone: "super oft fehlen die
   batterien"). It gave each of the five pictures a compositing layer of its own, and the phone
   showed step 3 with those layers empty. They are painted with the stage now, like the bike
   (fx.js, PLAIN PAINT); app/tests/adt-story-chrome.mts fails if will-change comes back.
   THE INVISIBLE 64px SHADOW HOLDS THE REAL ONE. Painted with the stage, Safari repaints a
   battery only inside its box and its box-shadow/outline extent, not the reach of its
   drop-shadow() filter: every step of the fade left the soft shadow cut into rectangles and
   grey boxes (iOS Simulator, 30 Sep 2026). A transparent spread shadow paints nothing and
   widens that area by 64px on every side, which covers the whole drop-shadow in both themes
   (0 8px 18px in the light one reaches ~62px). Not an outline: forced colours would draw it.
   THE ROW'S TWO INVISIBLE SHADOWS ARE THE AREA IT IS REPAINTED IN (Tom, 1 Oct 2026, a friend's
   iPhone: "Wenn du den zweiten S16-Abschnitt zu schnell überscrollst und zurück willst, bleiben
   die Akkus"). WebKit, while iOS scrolls, does not wipe a battery from where it WAS, so fx.js
   repaints the whole row whenever one changes (THE ROW IS REPAINTED AS ONE). That repaint reaches
   as far as the row's own box and its shadows, and the row is only as tall as one battery: the
   first shadow widens it by 64px on every side (each battery's drop-shadow, as above), the second,
   40px lower, takes it down to where a battery starts its rise (BATTDRIFT in fx.js) plus its
   shadow below that (62px): 64px above, 64px either side, 104px below. Without them the iOS
   Simulator still left pieces of batteries behind, just under the row's own box. adt-story-chrome.mts measures both. */
.adtbatt{display:flex;gap:clamp(8px,2vw,20px);align-items:center;justify-content:center;
  box-shadow:0 0 0 64px transparent,0 40px 0 64px transparent}
.adtbatt img{display:block;width:auto;max-width:clamp(58px,13vw,118px);max-height:9vh;max-height:9svh;
  height:auto;filter:drop-shadow(0 6px 14px rgba(0,0,0,.22));box-shadow:0 0 0 64px transparent}

/* THE STEPS SHARE ONE GRID CELL. The block is as tall as the tallest step from the first
   frame, so nothing moves as they change; each step only fades and drifts in a place
   that was always its own. Hidden only while the story is pinned (html[data-adt], see
   THREE STATES above): until fx.js runs the poster is the closing line, and without it
   every step is shown as the list.
   NOT position:relative (1 Oct 2026): the closing line is placed on the STAGE, not in this
   block (THE STATEMENT below), so the stage is its containing block. */
.adtlines{width:min(92vw,780px);display:grid}
.adtstep{grid-area:1/1;display:flex;flex-direction:column;gap:clamp(11px,2vh,22px);
  will-change:transform,opacity}
html[data-adt] .adtstep{opacity:0}
/* the poster is the story's LAST frame, as at p=1: the bike and its batteries have left with
   step 3 (THREE STATES above). fx.js paints the art's opacity inline from its first frame, and
   "live" no longer matches this rule anyway. */
html[data-adt="1"] .adtart{opacity:0}
.adtlines p{margin:0;text-align:center;
  font-family:var(--mono);font-weight:500;line-height:1.32;
  /* height-bounded for the same reason as the artwork above. The 18px floor is what
     keeps the type readable on a narrow phone; it is itself capped by height and only
     gives way when the screen is genuinely short. 430x860 and 375x667 land on 18px,
     1440x900 on 30px. */
  font-size:calc(clamp(min(18px,4svh),min(3.6vw,3.4svh),30px) * var(--ts));letter-spacing:.01em;
  /* NO ONE-WORD LINES on the stage: "No heavy / cassette.", "Strong on the way / up.",
     "… never runs / out...". Balancing keeps the same number of lines and only moves the
     breaks, so no step changes height. The swap sentence below takes `pretty` instead: on a
     phone it runs past the six lines a browser balances, and what it needs is only that its
     last word does not stand alone ("app."). Unsupported browsers wrap as before. */
  text-wrap:balance}
/* Japanese has no spaces to break at, so the browser split inside words (カ / セット);
   auto-phrase breaks between phrases instead (Chrome; others keep their current wrapping). */
.adtlines p:lang(ja){word-break:auto-phrase}
/* THE SWAP SENTENCE IS THE ANSWER to "yes, but how?", and it was the least readable line
   on the stage: 13px on a phone, 20 on a laptop, grey #4a5450. It is the longest sentence
   of the section and the one that carries the idea, so it gets the size and the ink of
   running text read at arm's length: 16px on a phone, 24px on a laptop, and the stage's
   own near-black (18:1 on white; #e9eeec on the light theme's black stage). Still the
   quieter voice next to the claim above it — smaller, and a beat later. */
.adtlines p.adtbsub{margin-top:clamp(4px,1.4vh,16px);margin-top:clamp(4px,1.4svh,16px);
  font-size:calc(clamp(min(16px,3.4svh),min(2.9vw,2.9svh),24px) * var(--ts));
  font-weight:400;line-height:1.5;letter-spacing:0;color:#141a19;will-change:transform,opacity;
  text-wrap:pretty}
/* THE STATEMENT THE WHOLE SECTION IS BUILT TOWARDS. It gets the full width of the
   screen, not the 780px column the steps share — left/margin rather than a transform,
   because fx.js writes transform on this element (finDrop) and would overwrite a centring
   translate. Height-bounded like everything else in here.
   PARKED LOW BY THE CSS ITSELF (1 Oct 2026, the logo): its bottom at 72% of the stage, the
   line fx.js drops it to (finDrop, which now measures 0 and stays the safety net). It used to
   hang from 38% of the steps' block and be dropped from there, which on a short screen (a phone
   held sideways: the block starts at 70% of the screen) left room for a line of text and not
   for a logo; on the stage, the logo's height is what decides, and that is bounded below
   (the font size). finDrop only ever moved it DOWN to 72%, so where the hang already ended
   lower it stayed there: a phone held upright is where it was (72%), a tablet, a laptop and a
   phone held sideways now end it higher than before (at 1440x900 its bottom at 648px instead
   of 816, 72% instead of 91%; 844x390 72 instead of 92%). The greeting and the logo are two
   items of a column, so a translation can put the logo first ("{logo}へようこそ") and the gap is
   always between the two.
   ONE SIZE FOR BOTH: the logo is 8em wide (.adtmark) — the width the wordmark text "ALL DAY
   TURBO™" had (8.2em, measured) — so greeting and logo keep the proportion the greeting and the
   wordmark had, on every screen. The size is the one the line always had, and on top of it at
   most 8.2svh (the logo then 40svh tall, the whole line 51svh: it fits between the header row
   and the 72% line, on a phone held sideways too) and 11vw - 3px (the logo 88vw - 24px wide:
   inside the stage, with room for the ™ that hangs out on the right). A phone held upright is
   unchanged (26px, the logo 208px wide at 390); a laptop gets 74px instead of 89 at 1440x900. */
.adtlines p.adtfin{position:absolute;top:auto;bottom:28%;left:50%;right:auto;
  margin-left:-48vw;width:96vw;
  display:flex;flex-direction:column;align-items:center;gap:.3em;
  font-size:min(calc(clamp(min(26px,5.4svh),min(6.2vw,10svh),116px) * var(--ts)),8.2svh,calc(11vw - 3px));
  line-height:1.1;letter-spacing:-.015em;opacity:1}
/* THE LOGO (Tom, 1 Oct 2026: "besser wäre, du benutzt das original logo … füge ein kleines TM
   hinter Turbo hinzu"). --lw is its width, 8em of the closing line's size (above). The logo is
   centred on its own box, the ™ outside it, as a ™ sits after a word. */
.adtlines .adtfin .adtmark{--lw:8em;position:relative;display:block;
  flex:none;width:var(--lw);letter-spacing:0}
.adtmark picture,.adtmark img{display:block}
.adtmark img{width:100%;height:auto}
/* ONE FILE, INVERTED ON THE BLACK STAGE. Tom's white logo is his black one inverted, to the
   pixel (the same alpha, white = 255 - black; measured in headless Chrome against his white
   file: at most 1/255 apart at 390@3 and 1440@2), so the light theme shows the black file
   through invert(1) instead of a second file. A second file came only when the theme was
   switched, and until it had, step 4 showed the greeting and a lone ™ (found in review,
   1 Oct 2026); the inverted one is the picture already decoded, white in the next frame. */
html[data-theme="light"] .adtmark img{filter:invert(1)}
/* THE ™: text, so it becomes ® in one place (index.html). Small — its letters 29% of the
   height of the logo's — right after the O of TURBO, which ends at 99.65% of the file's width,
   and its top on the top of the logo's flat letters (T, U, R, B: 83.79% of the file's height;
   the -.137em take off the room Archivo leaves above a ™ in a line-height:1 box). Sized from
   the logo's width (like the hero's slogan from --hw; the logo carries --ts already), so it
   scales with the logo everywhere: measured in headless Chrome from 208 to 708px of logo, its
   top within 1.4px of the letters' top on a phone (a device pixel or so), 0.5px on a laptop,
   and 1.3-6px after the O. Archivo bold, the closest of our faces to the logo's
   heavy letters. The colour of the logo, per theme. */
.adtmark .adttm{position:absolute;left:calc(99.65% + var(--lw) * .006);top:83.79%;margin-top:-.137em;
  font-family:var(--sans);font-weight:700;font-size:calc(var(--lw) * .068);line-height:1;
  letter-spacing:0;color:#000}
html[data-theme="light"] .adtmark .adttm{color:#fff}

/* THE vh FALLBACKS FOR THE DECLARATIONS WITH var() (30 Sep 2026; why they cannot sit in
   front of the svh ones is at the stage above). The same values with vh for svh, the
   same selectors, after the originals, so each one replaces exactly its own line. Only a
   browser without svh reads this; in every other one nothing changes. */
@supports not (height:1svh){
  html[data-snap="1"][data-adt] .adtsnap{top:calc((var(--adtH) - 100vh) * var(--sp))}
  .adtlines p{font-size:calc(clamp(min(18px,4vh),min(3.6vw,3.4vh),30px) * var(--ts))}
  .adtlines p.adtbsub{font-size:calc(clamp(min(16px,3.4vh),min(2.9vw,2.9vh),24px) * var(--ts))}
  .adtlines p.adtfin{font-size:min(calc(clamp(min(26px,5.4vh),min(6.2vw,10vh),116px) * var(--ts)),8.2vh,calc(11vw - 3px))}
}

@media(prefers-reduced-motion:reduce){
  /* Reduced motion means no ANIMATION, not no CONTENT: every sentence, laid out as a
     list, bike and batteries above them. Before this pass the closing line and the
     battery block kept their absolute positions here and landed on top of the list —
     all nine sentences printed over each other in one heap. Everything is in the flow
     now; the closing line keeps its full-width box, centred under the column. */
  .adtlines{display:flex;flex-direction:column;gap:clamp(26px,5vh,48px)}
  .adtstep{opacity:1;transform:none}
  .adtlines p.adtfin{position:static;opacity:1;transform:none;
    margin:.3em 0 0 calc(50% - 48vw)}
}
/* NOT DRIVEN = THE LIST (Tom, 30 Sep 2026, the S25 report; THREE STATES above): the same
   layout as reduced motion, box for box, for every browser the story does not play in.
   Reduced motion is one of them — its block above is kept, and is now covered by this one.
   !important on opacity and transform because fx.js writes both inline: if the story is
   released mid-scroll, the frame it was on must not survive into the list. */
html:not([data-adt]) .adt{height:auto}
html:not([data-adt]) .adtstage{position:relative;height:auto;padding:64px 24px}
html:not([data-adt]) .adtlines{display:flex;flex-direction:column;gap:clamp(26px,5vh,48px)}
html:not([data-adt]) .adtstep,html:not([data-adt]) .adtart,html:not([data-adt]) .adtbatt>*,
html:not([data-adt]) .adtlines p.adtbsub{opacity:1!important;transform:none!important}
html:not([data-adt]) .adtlines p.adtfin{position:static;opacity:1!important;transform:none!important;
  margin:.3em 0 0 calc(50% - 48vw)}

/* Accents on the key words. The bright brand teal is NOT usable here: measured on this
   white stage, #25A599 is 3.0:1 — under the floor and genuinely hard to read. The dark
   variant the light theme already defines carries the same hue at 6.2:1.
   Teal for every emphasis: the colour rule of 29 Sep 2026 (magenta = action, teal =
   emphasis inside text) retired the magenta that marked what the bike does not have and
   the payoff. .m stays in the markup and in all fifteen translations; it is teal now. */
.adtlines .t{color:#0d6d64}
.adtlines .m{color:#0d6d64}

/* TWO LINES BY CONSTRUCTION, not by hoping the browser breaks at the space (iOS Safari once
   kept "Benvenuto in ALL DAY TURBO™" on one line and ran it off the screen): the closing line
   is a column (above), the greeting one item and the logo the other, in every language and
   every engine. */

/* ============================================================================
   THE STORY AS A POPUP — the default since 2 Oct 2026 (?story=inline: the scroll story above)
   ----------------------------------------------------------------------------
   Tom, 2 Oct 2026, on the version he had asked to look at: "wow, das im Stil einer Instagram Story ist der Hammer,
   wirklich gut, sehr sehr gut gemacht … Jetzt schon mal ins normale Staging aufnehmen" — his "what the heck?" carousel.
   Asked for on 1 Oct 2026, after a friend tried the site on a Samsung phone: "der ganze weiße Bereich mit dem NICOLAI
   Rad am Anfang … hat auf meinen Freund gar nicht so gewirkt, im Gegenteil, er dachte … es ist eine nervige NICOLAI-Werbung,
   und konnte sie nicht wegklicken. Das Scrollen war dort so empfindlich, dass er sich gar nicht zurecht fand, die 5
   Akkus: er dachte das sind Striche … Ich frag mich, ob ein provokanter Button über den Sponsoren auf 01 RIDE nicht
   besser wäre … klickt man drauf kommt die ganze weiße Animation aber als Popup, ähnlich wie das Finale-Dilemma oder
   der Preisvergleich. Vielleicht baust du mir so eine Version und ich gucke sie mir an?"

   Under html[data-story], which index.html's <head> sets on every page unless the visit asked for ?story=inline (kept
   until ?story=popup or a new visit) or the browser has no IntersectionObserver; without it not one rule below matches
   and the page has the scroll story (or its list). With it the white section (#endless) is not in the page flow at
   all — ~350vh less to scroll — and 01 RIDE has a button right above SPONSORS that opens it as a popup (fx.js, 2d). Inside, the SAME section: its sentences, its bike, its five batteries and its logo, the same i18n strings,
   moved into the popup, not copied, and the app's swap-station screenshot that only the popup shows (slide 4, THE APP
   SLIDE below). Not scrubbed by the scroll any more: five steps that play by themselves at a calm
   pace (the time each gets follows the length of its text in the page's language), with a tap or a swipe, the two
   buttons or the arrow keys to go on or back, the five segments at the top to show where it is, and "play again" at
   the end. The page behind does not move, the rest of it is out of reach (inert) until the popup closes — the X, Esc,
   a tap outside the card, or the phone's back gesture. Reduced motion: nothing plays by itself and nothing moves; every
   step is a tap away.

   The rules carry html[data-story] in front so they outweigh the story's own list state (html:not([data-adt]) …, THREE
   STATES above), and !important where that state has it (opacity, transform). */

/* out of the page flow from the first paint; fx.js moves the section into the popup ("1" until it has, "live" after) */
html[data-story] body>#endless{display:none}

/* ---- the button above SPONSORS, in 01 RIDE: an action, so magenta (.btn) — the question, with the slogan's battery ---- */
html[data-story] #ride .wrap>.storycta{margin-top:34px}
html[data-story] #ride .wrap>.storycta+.sponsors{margin-top:34px}
.storybtn{max-width:100%;justify-content:flex-start;text-align:left;line-height:1.35;padding:15px 18px;gap:.75em}
.storybtn>span{text-wrap:balance}
.storybtn .sbatt{flex:none;width:auto;height:2.1em;display:block}
.storybtn::after{margin-left:auto;padding-left:.2em}

/* ---- the popup: a card on the scrim, like the price comparison on a laptop; on a phone the card fills the screen
   but for a frame of dimmed page around it, so it reads as something laid over the page that can be closed. The frame
   is 44px at the top: a finger on a 12px strip lands on the card (Chrome moves a tap onto the nearest thing that takes
   one, measured: 4-6px from the card's edge the tap went to the stage and turned the step), 44px is a place to tap. ---- */
.storypop{position:fixed;inset:0;z-index:96;display:flex;align-items:center;justify-content:center;
  padding:max(44px,calc(env(safe-area-inset-top) + 20px)) max(12px,env(safe-area-inset-right)) max(12px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left));
  background:rgba(var(--scrim));opacity:0;transition:opacity .25s ease;touch-action:pinch-zoom;overscroll-behavior:contain;
  -webkit-tap-highlight-color:transparent}
.storypop[hidden]{display:none}
.storypop.open{opacity:1}
html.storyopen body{overflow:hidden}
/* the teal pill and the FAQ tab (index.html .faqtab, 1 Oct 2026) are out of sight while it is open, as under every popup */
html.storyopen .vlpill,html.storyopen .faqtab{opacity:0!important;visibility:hidden!important;pointer-events:none!important}
.spcard{position:relative;display:flex;flex-direction:column;width:min(100%,920px);height:min(100%,820px);
  background:#ffffff;color:#0b100f;box-shadow:0 24px 70px rgba(0,0,0,.55);overflow:hidden;outline:none;
  transform:translate3d(0,18px,0) scale(.985);transition:transform .34s cubic-bezier(.2,.8,.2,1)}
.storypop.open .spcard{transform:none}
html[data-theme="light"] .spcard{background:#0a0a0a;color:#f2f5f4}
@media(min-width:760px){.storypop{padding:max(24px,env(safe-area-inset-top)) 24px max(24px,env(safe-area-inset-bottom))}}

/* the top row: four segments (where the story is, and how long the step has left while it plays) and the close X */
.sptop{flex:none;display:flex;align-items:center;gap:14px;padding:12px 12px 2px 18px}
.spsegs{flex:1;display:flex;gap:6px;min-width:0}
.spsegs i{flex:1;height:4px;border-radius:2px;background:rgba(11,16,15,.14);overflow:hidden;position:relative}
.spsegs b{position:absolute;inset:0;background:#0d6d64;transform-origin:0 50%;transform:scaleX(0)}
html[data-theme="light"] .spsegs i{background:rgba(255,255,255,.22)}
html[data-theme="light"] .spsegs b{background:#25A599}
.spclose,.spbtn{flex:none;display:grid;place-items:center;border:0;padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
.spclose{width:44px;height:44px;background:var(--magenta);color:var(--onMag)}
.spclose svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}
/* the bottom row: back (the quieter, outlined) and on (magenta: the action), which turns into "play again" at the end */
.spnav{flex:none;display:flex;align-items:center;justify-content:space-between;padding:6px 12px max(12px,env(safe-area-inset-bottom,0px))}
.spbtn{width:52px;height:52px;background:transparent;color:inherit;border:1px solid rgba(11,16,15,.38)}
html[data-theme="light"] .spbtn{border-color:rgba(255,255,255,.42)}
.spbtn svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
.spbtn.spnext{background:var(--magenta);border-color:var(--magenta);color:var(--onMag)}
.spbtn:disabled{opacity:.28;cursor:default}
.spbtn .spagain,.spbtn.again .sponw{display:none}
.spbtn.again .spagain{display:block}
.spclose:focus-visible,.spbtn:focus-visible,.spcard:focus-visible{outline:2px solid currentColor;outline-offset:3px}
.spclose:focus-visible,.spbtn.spnext:focus-visible{outline-color:#0b100f}
html[data-theme="light"] .spclose:focus-visible,html[data-theme="light"] .spbtn.spnext:focus-visible{outline-color:#f2f5f4}
.spcard:focus-visible{outline-offset:-2px}

/* ---- the stage: the section itself, between the two rows. One grid: the picture's cell above, the text's below (the
   three steps share it, so it is as tall as the tallest from the start and the picture never moves when a step
   changes), the closing line across both. A size container: the type and the batteries are sized from the room the
   card has, not from the window. ---- */
html[data-story] .storypop #endless{display:block;flex:1 1 auto;min-height:0;height:auto;position:relative;
  background:transparent;color:inherit;container-type:size;container-name:spstage}
html[data-story] .storypop .adtstage{position:relative;top:auto;height:100%;display:grid;
  grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(min(30cqh,150px),1fr) auto;align-items:center;justify-items:center;
  gap:clamp(10px,3cqh,30px);padding:clamp(8px,2.4cqh,24px) clamp(16px,5cqw,52px) 4px;overflow:hidden;
  touch-action:pinch-zoom;-webkit-user-select:none;user-select:none;cursor:pointer}
html[data-story] .storypop.spscroll .adtstage{overflow:hidden auto;overscroll-behavior:contain;touch-action:pan-y pinch-zoom}
/* one finger is the story's (a tap, a swipe, a hold; the page behind never scrolls), two are the browser's: a pinch zooms
   (touch-action pinch-zoom, and fx.js lets a two-finger move through), so a visitor who needs it bigger can have it — the
   larger-text switch is out of reach while the popup is open (found in review, 1 Oct 2026; WCAG 1.4.4) */
/* the step on the stage runs past its foot (fx.js .spmore): its last 44px fade out, so it shows there is more to scroll to */
html[data-story] .storypop.spmore .adtstage{-webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 44px),transparent);
  mask-image:linear-gradient(to bottom,#000 calc(100% - 44px),transparent)}
html[data-story] .storypop .adtart,html[data-story] .storypop .adtlines{display:contents}
html[data-story] .storypop .adtbike{grid-area:1/1;align-self:center;width:auto;height:auto;max-width:min(100%,600px);max-height:100%}
/* THE BATTERIES READ AS BATTERIES (the friend saw "Striche"): five lying packs 58px wide on a phone were five dark
   dashes. Here they are a stack in the picture's own cell, each as big as the cell lets it be: the end caps, the plug
   and the maker's mark show, the same photograph, the same soft shadow. They come in one after the other from the
   side, as if slotted onto a charging shelf. Measured (headless Chrome, step 3, the first pack): 360x87 on a
   laptop (1440x900), 290x70 at 390x844, 272x66 at 412x780; with a phone browser's bars 186x45 at 390x664 (iPhone
   Safari), 171x41 at 360x668 (Samsung Chrome), 150x36 at 360x640. */
html[data-story] .storypop .adtbatt{grid-area:1/1;align-self:stretch;justify-self:stretch;min-height:0;display:flex;
  flex-flow:row wrap;align-content:center;align-items:center;justify-content:center;gap:10px;box-shadow:none;
  container-type:size}
html[data-story] .storypop .adtbatt img{width:min(78%,300px);max-width:none;max-height:none;height:auto;
  width:min(88cqw,360px,calc((100cqh - 40px) / 5 * 420 / 101));box-shadow:none}
/* A SHORT, WIDE CELL — an iPhone SE's Safari (375x548), a 320px phone, a phone held sideways: one column of five gave
   each pack a fifth of a short cell, 60-80px wide, the "Striche" again (found in review, 1 Oct 2026). There they stand
   in two columns, 2 + 2 + 1, each about twice that: 153x37 at 375x548, 127x31 at 320x568, 157x38 at 844x390. Where one
   column is as big or bigger (every taller cell, a laptop), it stays one column. */
@container (min-aspect-ratio:3/2) and (max-height:330px){
  html[data-story] .storypop .adtbatt img{width:min(calc((100cqw - 10px) / 2),300px,calc((100cqh - 20px) / 3 * 420 / 101))}
}
html[data-story] .storypop .adtstep{grid-area:2/1;align-self:start;width:100%;max-width:780px;
  gap:clamp(8px,1.8cqh,18px)}
html[data-story] .storypop .adtlines p{font-size:calc(clamp(16px,min(4.4cqw,3.6cqh),28px) * var(--ts))}
html[data-story] .storypop .adtlines p.adtbsub{font-size:calc(clamp(14.5px,min(3.5cqw,2.9cqh),21px) * var(--ts));
  margin-top:clamp(2px,1cqh,10px)}
html[data-story] .storypop .adtlines p.adtfin{grid-area:1/1/3/2;position:static;align-self:center;justify-self:center;
  width:auto;left:auto;right:auto;bottom:auto;margin:0;
  font-size:min(calc(clamp(24px,8.5cqw,62px) * var(--ts)),10.5cqw,11cqh)}
/* wide and short (a phone held sideways, a low laptop window): the picture left, the text right */
@container spstage (min-aspect-ratio:8/5){
  html[data-story] .storypop .adtstage{grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);grid-template-rows:minmax(0,1fr)}
  html[data-story] .storypop .adtbike,html[data-story] .storypop .adtbatt{grid-area:1/1}
  /* centred, but never past the stage's top: taller than the stage (larger text held sideways) it starts at the top
     and its end is scrolled to (.spscroll, .spmore), instead of losing its first line above, where nothing scrolls to
     (found in review, 1 Oct 2026). Where "safe" is unknown the line before stays. */
  html[data-story] .storypop .adtstep{grid-area:1/2;align-self:center;align-self:safe center}
  html[data-story] .storypop .adtlines p.adtfin{grid-area:1/1/2/3}
}

/* ---- THE APP SLIDE (slide 4; Tom, 2 Oct 2026, sending the screenshot: "das kannst du ins Karussell einbauen, das ist
   super! also in das horizontale"): the app's swap station, the proof of "… Locate and reserve them in the app." Only
   the popup shows it (hidden in the markup until fx.js has moved it there; out of sight in the scroll story and its
   list, which never had it). The figure takes the whole stage, like the closing line: the screenshot as tall as the
   stage leaves beside its caption, the caption under it (beside it from 8:5, below).
   AS TALL AS THE STAGE ON THE SCREEN — 100cqh less the stage's padding and 12px for the picture's shadow (the stage
   clips, and cut it by a straight line) — not as its grid: where step 3's text is long (German on a 320px phone) the
   grid's two rows are taller than the stage, and a figure stretched over them ran past its foot.
   THE WHOLE SCREENSHOT, NEVER CROPPED: it sits in its cell without taking part in the layout (absolute in its grid
   cell, centred, at most the cell's width and height, its own shape kept), so a short stage makes it smaller, never cut,
   and it never pushes the caption out; the cell keeps at least min(42cqh,220px) beside a tall caption, and if both do
   not fit, the stage scrolls as for a long step (fx.js .spscroll).
   THE PHONE LOOK of the ALL DAY TURBO popup's two screenshots (index.html .adtshot): 18px corners, a 2px frame, a soft
   shadow — the frame in the colour .adtshot's frame has on a card of the stage's ground (the stage is white on the dark site,
   black on the light one).
   Measured (headless Chrome, 2 Oct 2026), the picture: 285x554 at 390x844, 198x383 at 390x664 (iPhone Safari with its
   bars), 176x341 at 360x640 (de), 131x253 at 320x568 (de), 99x190 at 844x390 (held sideways, the caption beside it),
   301x585 at 1440x900, 255x495 at 1440x700 (beside); at most 309x601 (430x932, zh: a one-line caption) — the card's
   820px cap, the same on a phone and a laptop, which is why there is one file (921x1802, ≤ 3× that on a phone) and no
   phone file. ---- */
.adtapp{display:none}
html[data-story] .storypop .adtapp{grid-area:1/1/3/2;align-self:start;justify-self:stretch;min-width:0;
  height:calc(100cqh - clamp(8px,2.4cqh,24px) - 16px);min-height:min-content;
  position:relative;margin:0;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(min(42cqh,220px),1fr) auto;
  justify-items:stretch;align-items:stretch;row-gap:clamp(10px,2.6cqh,26px)}
/* its cell is named with both its lines (1/1/2/2): an absolute item's "auto" line is the figure's padding edge, so 1/1
   alone gave it the whole figure and it lay under the caption */
html[data-story] .storypop .adtapp img{grid-area:1/1/2/2;position:absolute;inset:0;margin:auto;display:block;
  width:auto;height:auto;max-width:100%;max-height:100%;box-sizing:border-box;
  border-radius:18px;border:2px solid #aab5b2;background:#000;box-shadow:0 6px 14px rgba(0,0,0,.22);
  pointer-events:none;-webkit-user-drag:none;-webkit-touch-callout:none}
/* the shadow reaches 8px above the picture, inside the stage's top padding (the stage clips: a larger one was cut there
   by a straight line); on the black stage of the light theme a shadow does not show, the frame does */
html[data-theme="light"][data-story] .storypop .adtapp img{border-color:#3a3f3e;box-shadow:none}
html[data-story] .storypop .adtapp figcaption{grid-area:2/1/3/2;justify-self:center;width:100%;max-width:780px;margin:0;
  text-align:center;font-family:var(--mono);font-weight:500;line-height:1.32;letter-spacing:.01em;text-wrap:balance;
  font-size:calc(clamp(14.5px,min(3.8cqw,3.1cqh),22px) * var(--ts))}
html[data-story] .storypop .adtapp figcaption:lang(ja){word-break:auto-phrase}
/* wide and short (8:5, as above): the screenshot left, its caption right */
@container spstage (min-aspect-ratio:8/5){
  html[data-story] .storypop .adtapp{grid-area:1/1/2/3;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
    grid-template-rows:minmax(0,1fr);column-gap:clamp(10px,3cqh,30px)}
  html[data-story] .storypop .adtapp figcaption{grid-area:1/2/2/3;align-self:center;align-self:safe center}
}

/* ---- the steps: one on the stage at a time (fx.js sets now / past / next, slide by slide: SLIDES in 2d). The old one
   is gone before the new one comes (it leaves in .22s, the new one starts at .2s); forward, the new one rises in and
   the old one lifts away, back, the other way round. Slide 4, the app's swap station (.adtapp, Tom, 2 Oct 2026), is in
   the :is() lists with the others. ---- */
html[data-story] .storypop .adtstage :is(.adtstep,.adtfin,.adtbike,.adtbatt,.adtapp){visibility:hidden;opacity:0!important;
  transition:opacity .22s ease,transform .22s ease,visibility 0s linear .22s}
html[data-story] .storypop .adtstage :is(.adtstep,.adtfin,.adtbike,.adtbatt,.adtapp).sp-next{transform:translate3d(0,16px,0)!important}
html[data-story] .storypop .adtstage :is(.adtstep,.adtfin,.adtbike,.adtbatt,.adtapp).sp-past{transform:translate3d(0,-12px,0)!important}
html[data-story] .storypop .adtstage :is(.adtstep,.adtfin,.adtbike,.adtbatt,.adtapp).sp-now{visibility:visible;opacity:1!important;
  transform:none!important;
  transition:opacity .45s ease .2s,transform .6s cubic-bezier(.2,.8,.2,1) .2s,visibility 0s linear 0s}
/* the five, one after the other once the step is there; the swap sentence after the fifth, as on the scroll */
html[data-story] .storypop .adtbatt>*{opacity:0!important;transform:translate3d(34px,0,0)!important;
  transition:opacity .2s ease,transform .2s ease}
html[data-story] .storypop .adtbatt.sp-now>*{opacity:1!important;transform:none!important;
  transition:opacity .4s ease calc(.3s + var(--i,0) * .14s),transform .6s cubic-bezier(.2,.8,.2,1) calc(.3s + var(--i,0) * .14s)}
html[data-story] .storypop .adtlines p.adtbsub{opacity:0!important;transform:translate3d(0,10px,0)!important;
  transition:opacity .2s ease,transform .2s ease}
html[data-story] .storypop .adtstep.sp-now p.adtbsub{opacity:1!important;transform:none!important;
  transition:opacity .5s ease 1.15s,transform .6s cubic-bezier(.2,.8,.2,1) 1.15s}
/* opening on a step: it is put in place without a transition (fx.js, a frame of .spjump) */
html[data-story] .storypop.spjump *{transition:none!important}
@media(prefers-reduced-motion:reduce){
  .storypop,.spcard{transition:none}
  .spcard{transform:none}
  html[data-story] .storypop *{transition:none!important}
  html[data-story] .storypop .adtstage :is(.adtstep,.adtfin,.adtbike,.adtbatt,.adtapp,.adtbatt>*,p.adtbsub){transform:none!important}
}
