/* ===========================================================================
   GROWTH SYSTEM v4.1 — the homepage hero as a stepped visual story

   Two scenes sit side by side on a 200vw rail: the workshop and the street.
   The rail moves in VIEWPORT units, so the part of the shop showing at the
   right edge is exactly --peek wide at every window size. Inside each scene
   the composition is laid out in WORLD units (1920x1080 scaled by --u). The
   world is sized to leave the peek free, so the shop never sits under the
   service titles.

   story.js writes plain numbers on the section — --step, --spin, --pan,
   --arrive, --inside, --expand — and every rule below is a CSS transition on
   a value derived from them. Nothing animates per frame in JavaScript.

   The default (no JS, reduced motion) is the static composition at the end
   of this file: everything visible, stacked, in normal flow. Phones with
   motion get .story-m: the same street, played once when it scrolls in.
   =========================================================================== */

.story{
  --nav-h: 0px;                    /* measured by story.js */
  --peek: clamp(150px, 15vw, 300px);   /* the part of the shop showing at the right edge */
  --dock: 16vw;                    /* where the street settles once panned in */
  /* the gears are meant to run off the bottom edge, so the world is fitted
     to 980 of its 1080 units; its width leaves the peek clear */
  --u: calc(min((100svh - var(--nav-h)) / 980, (100vw - var(--peek)) / 1920));
  --ease: cubic-bezier(.62, .02, .2, 1);
  --soft: cubic-bezier(.33, 0, .2, 1);
  position: relative;
  background: var(--ground);
}

/* ---- the scroll track and the held stage -------------------------------- */
.story__track{ height: auto; }
.story__stage{ position: relative; overflow: hidden; }
.story-live{ margin-top: calc(-1 * var(--nav-h)); }
.story-live .story__track{ height: calc(var(--steps, 7) * 100svh); }
.story-live .story__stage{ position: sticky; top: 0; height: 100svh; }

.story__rail{ display: flex; width: 200vw; height: 100%; }
.story-live .story__rail{
  transform: translate3d(calc(-1 * var(--peek) - var(--pan, 0) * (100vw - var(--peek) - var(--dock))), 0, 0);
  transition: transform 1200ms var(--ease);
  will-change: transform;
}
.scene{ position: relative; flex: none; width: 100vw; height: 100%; overflow: hidden; }
.scene__in{ position: absolute; left: 0; right: 0; top: var(--nav-h); bottom: 0; }
/* the rail rests one peek to the left, so the workshop is inset to match */
.story-live .scene--work .scene__in{ left: var(--peek); }
/* the street reaches back past its own left edge (the tree) once it docks */
.story-live .scene--street{ overflow: visible; z-index: 1; }

/* ---- the workshop scene fades as it leaves ------------------------------ */
.story-live .scene--work{ opacity: calc(1 - var(--pan, 0)); transition: opacity 640ms var(--soft); }

.story__hero{ position: absolute; left: calc(96 * var(--u)); top: calc(96 * var(--u)); width: calc(1040 * var(--u)); }
.story__hero .label{ margin-bottom: var(--s-5); }
.hero__h{ margin-block: var(--s-4) var(--s-6); }
.hero__line{ display: block; }
.hero__actions{ display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-6); margin-top: var(--s-7); }
.story-live .story__hero .display-xl{ font-size: calc(104 * var(--u)); line-height: .94; }
.story-live .story__hero .lede{ font-size: calc(27 * var(--u)); max-width: 40ch; }
.story-live .story__hero .label{ font-size: calc(14 * var(--u)); letter-spacing: .16em; margin-bottom: calc(29 * var(--u)); }
.story-live .story__hero .hero__actions{ margin-top: calc(56 * var(--u)); gap: calc(38 * var(--u)); }
.story-live .btn{ padding: calc(18 * var(--u)) calc(29 * var(--u)); font-size: calc(17 * var(--u)); }
.story-live .btn__arrow{ width: calc(18 * var(--u)); height: calc(11 * var(--u)); }
.story-live .tlink{ font-size: calc(14 * var(--u)); }
.story-live .tlink svg{ width: calc(18 * var(--u)); height: calc(11 * var(--u)); }

/* first paint: the copy arrives once, then holds */
.hero__line, .hero__lede, .hero__actions{ opacity: 0; transform: translateY(.5em); }
.html-ready .hero__line, .html-ready .hero__lede, .html-ready .hero__actions{
  opacity: 1; transform: none; transition: opacity 700ms var(--ease-out), transform 700ms var(--ease-out); }
.html-ready .hero__line:nth-child(2){ transition-delay: 90ms; }
.html-ready .hero__lede{ transition-delay: 320ms; }
.html-ready .hero__actions{ transition-delay: 440ms; }

/* ---- the three service positions ---------------------------------------- */
.story__titles{ position: absolute; left: calc(1180 * var(--u)); top: calc(96 * var(--u)); width: calc(660 * var(--u));
  margin: 0; padding: 0; list-style: none; display: grid; gap: calc(30 * var(--u)); }
.ttl{ display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: calc(19 * var(--u)); }
.ttl__link{ display: grid; gap: .2em; }
.ttl__n{ color: var(--ink-2); }
.ttl__words{ font-family: var(--font-display); font-weight: 800; font-stretch: 112%;
  font-size: clamp(1.5rem, .8rem + 2.2vw, 2.6rem); letter-spacing: -.03em; line-height: .96; }
.ttl__icon{ width: calc(92 * var(--u)); height: auto; }
.story-live .ttl__n{ font-size: calc(16 * var(--u)); }
.story-live .ttl__words{ font-size: calc(46 * var(--u)); }
.story-live .mark{ width: calc(53 * var(--u)); height: calc(53 * var(--u)); }
.story-live .mark::after{ width: calc(29 * var(--u)); height: calc(29 * var(--u)); }

/* a row lights when the step reaches it; the mark and its number stay, as the
   position, so the empty slots read as 01 02 03 before anything arrives */
.story-live .ttl{ --on: clamp(0, calc(var(--step, 0) - var(--i) + 1), 1); }
.story-live .ttl__n{ opacity: calc(.55 + .45 * var(--on)); transition: opacity 640ms var(--soft); }
.story-live .ttl__words,
.story-live .ttl__icon{
  opacity: var(--on);
  transform: translate3d(calc((1 - var(--on)) * 16 * var(--u)), 0, 0);
  transition: opacity 700ms var(--soft), transform 700ms var(--soft);
}
.story-live .ttl__icon{ transition-delay: 130ms; }
.story-live .mark{ opacity: calc(.32 + .68 * var(--on)); transition: opacity 640ms var(--soft); }

/* ---- the gearbox --------------------------------------------------------- */
.story__gearbox{ position: absolute; inset: 0; }
.gear{ position: absolute; left: 0; top: 0;
  width: calc(var(--d, 600) * var(--u)); height: calc(var(--d, 600) * var(--u));
  transform: translate3d(calc((var(--cx, 0) - var(--d, 600) / 2) * var(--u)), calc((var(--cy, 0) - var(--d, 600) / 2) * var(--u)), 0)
             rotate(var(--phase, 0deg)); }
.story-live .gear{
  transform: translate3d(calc((var(--cx, 0) - var(--d, 600) / 2) * var(--u)), calc((var(--cy, 0) - var(--d, 600) / 2) * var(--u)), 0)
             rotate(calc(var(--phase, 0deg) + var(--spin, 0) * var(--ratio, 1) * .26turn));
  transition: transform 1300ms var(--soft);
  will-change: transform;
}

/* ===========================================================================
   THE STREET
   Laid out in world units from the shop's left edge. The ground is 901u.
   =========================================================================== */
.story__ground{ position: absolute; left: 0; right: 0; top: calc(var(--nav-h) + 901 * var(--u)); height: 1.5px; background: var(--ink); opacity: 0; }
.story-live .story__ground{ opacity: var(--pan, 0); transition: opacity 700ms var(--soft) 200ms; }

.street{ position: absolute; inset: 0; }
.street__world{ position: absolute; left: calc(40 * var(--u)); top: 0; width: calc(1000 * var(--u)); height: 100%; }
.street__line{ display: none; }

/* the shopfront and what is drawn onto it */
.shop{ position: absolute; left: 0; top: calc(407 * var(--u)); width: calc(1000 * var(--u)); z-index: 3; }
.shop__img{ display: block; width: 100%; max-width: none; height: auto; }
.shop__sign{ position: absolute; left: calc(270 * var(--u)); top: calc(19 * var(--u)); width: calc(461 * var(--u)); height: calc(69 * var(--u));
  display: grid; place-items: center; pointer-events: none;
  font-family: var(--font-display); font-weight: 800; font-stretch: 118%; text-transform: uppercase;
  font-size: calc(34 * var(--u)); letter-spacing: .04em; line-height: 1; color: var(--green); }

/* the automatic doors: two glass leaves that slide into the wall */
.door{ position: absolute; left: calc(449 * var(--u)); top: calc(241 * var(--u)); width: calc(106 * var(--u)); height: calc(215 * var(--u));
  overflow: hidden; }
.door__p{ position: absolute; top: 0; bottom: 0; width: 50%;
  border: calc(3 * var(--u)) solid var(--graphite);
  background:
    linear-gradient(118deg, transparent 30%, rgba(255,255,255,.95) 30% 34%, transparent 34% 42%, rgba(255,255,255,.95) 42% 44%, transparent 44%),
    rgba(214, 232, 228, .9);
  transform: translate3d(0, 0, 0); }
.door__p--l{ left: 0; border-right-width: calc(2 * var(--u)); }
.door__p--r{ right: 0; border-left-width: calc(2 * var(--u)); }
/* a small push-bar on each leaf */
.door__p::after{ content: ""; position: absolute; top: 46%; width: calc(4 * var(--u)); height: 14%; background: var(--graphite); }
.door__p--l::after{ right: calc(6 * var(--u)); } .door__p--r::after{ left: calc(6 * var(--u)); }
/* the motion sensor over the doorway: its light goes green while the doors are open */
.door__sensor{ position: absolute; left: calc(487 * var(--u)); top: calc(226 * var(--u)); width: calc(30 * var(--u)); height: calc(10 * var(--u));
  background: var(--graphite); }
.door__sensor::after{ content: ""; position: absolute; right: calc(4 * var(--u)); top: calc(3 * var(--u)); width: calc(4 * var(--u)); height: calc(4 * var(--u));
  border-radius: 50%; background: var(--orange); }

@keyframes door-l{ 0%, 100%{ transform: translate3d(0, 0, 0); } 28%, 82%{ transform: translate3d(-96%, 0, 0); } }
@keyframes door-r{ 0%, 100%{ transform: translate3d(0, 0, 0); } 28%, 82%{ transform: translate3d(96%, 0, 0); } }
@keyframes sensor{ 0%, 12%, 90%, 100%{ background: var(--orange); } 18%, 84%{ background: #3BD37A; } }

/* customers through the glass */
.pane__fig{ position: absolute; left: calc(var(--x) * var(--u)); top: calc(292 * var(--u));
  width: calc(58 * var(--u)); height: calc(124 * var(--u)); max-width: none;
  object-fit: contain; object-position: bottom;
  transform: scaleX(var(--f, 1));
  filter: grayscale(1) brightness(.55) contrast(1.1);
  opacity: .44; }

/* the street around the shop */
.bld{ position: absolute; left: calc(var(--x) * var(--u)); top: calc(var(--y) * var(--u)); width: calc(var(--d) * var(--u)); max-width: none; height: auto; }
/* the back row loses its own pavement slab: it stands behind the shop, not on the kerb */
.bld--back{ z-index: 1; --cb: 11%; clip-path: inset(0 0 var(--cb) 0); }
.bld--next{ z-index: 2; }
.prop{ position: absolute; max-width: none; height: auto; z-index: 2; }
.prop--tree{ left: calc(-330 * var(--u)); top: calc(435 * var(--u)); width: calc(266 * var(--u)); }
.prop--lamp{ left: calc(1012 * var(--u)); top: calc(365 * var(--u)); width: calc(119 * var(--u)); }

/* ---- the walkers --------------------------------------------------------- */
.crowd{ position: absolute; left: calc(502 * var(--u)); top: calc(901 * var(--u)); width: 0; height: 0; z-index: 4; }
.fig{ position: absolute; left: 0; bottom: 0;
  width: calc(90 * var(--u)); height: calc(200 * var(--u)); max-width: none;
  object-fit: contain; object-position: bottom;
  transform: translate3d(calc((var(--dx, 0) - 45) * var(--u)), 0, 0) scaleX(var(--f, 1));
  opacity: 0; }

/* ---- the closing plate --------------------------------------------------- */
.story__cta{ position: absolute; right: calc(var(--dock) + var(--gutter)); top: calc(96 * var(--u)); width: min(calc(640 * var(--u)), 48vw);
  display: grid; gap: var(--s-5); justify-items: start; }
.story__cta-line{ font-family: var(--font-display); font-weight: 800; font-stretch: 112%;
  font-size: clamp(1.6rem, .9rem + 2.6vw, 3.2rem); letter-spacing: -.03em; line-height: .96; }
.story__motto{ font-family: var(--font-display); font-weight: 700; font-stretch: 106%; font-size: var(--t-h3); letter-spacing: -.015em; line-height: 1.15;
  padding-left: var(--s-4); border-left: 3px solid var(--orange); }
.story__motto .em{ color: var(--em); }
.story__cta .btn{ justify-self: stretch; }

/* ===========================================================================
   LIVE (desktop, stepped)
   =========================================================================== */
.story-live .story__cta{ gap: calc(29 * var(--u)); opacity: var(--pan, 0);
  transition: opacity 780ms var(--soft) 300ms; }
.story-live .story__cta .label{ font-size: calc(14 * var(--u)); letter-spacing: .16em; }
.story-live .story__cta-line{ font-size: calc(58 * var(--u)); }
.story-live .story__motto{ font-size: calc(30 * var(--u)); opacity: var(--expand, 0);
  transform: translate3d(0, calc((1 - var(--expand, 0)) * 12 * var(--u)), 0);
  transition: opacity 800ms var(--soft) 900ms, transform 800ms var(--soft) 900ms; }

/* the tree and lamp arrive with the street */
.story-live .prop{ opacity: var(--pan, 0); transition: opacity 700ms var(--soft) 350ms; }

/* customers walk in (step 5) */
.story-live .fig{
  transform: translate3d(calc(((1 - var(--arrive, 0)) * var(--dx, 0) + var(--arrive, 0) * var(--rest, 0) - 45) * var(--u)), 0, 0)
             scaleX(var(--f, 1));
  opacity: calc(var(--arrive, 0) * (1 - var(--inside, 0)));
  transition: transform 2400ms var(--soft), opacity 800ms linear;
  transition-delay: calc(var(--i) * 200ms);
  will-change: transform;
}
/* Each walker fades up, crosses the pavement and is gone as it reaches the
   door, so they read as entering one by one instead of piling up on the mat. */
@keyframes walk-in{
  0%   { opacity: 0; }
  10%  { opacity: 1; }
  74%  { opacity: 1; }
  100% { opacity: 0; }
}
.story-live[data-step="5"] .fig{
  animation: walk-in 2700ms var(--soft) both;
  animation-delay: calc(var(--i) * 300ms);
}
/* the doors open as the first customer reaches them and close behind the last */
.story-live[data-step="5"] .door__p--l{ animation: door-l 4800ms var(--soft) both; }
.story-live[data-step="5"] .door__p--r{ animation: door-r 4800ms var(--soft) both; }
.story-live[data-step="5"] .door__sensor::after{ animation: sensor 4800ms linear both; }

/* inside, and the street fills in (step 6) */
.story-live .pane__fig{ opacity: calc(.44 * var(--inside, 0)); transition: opacity 950ms var(--soft); }
.story-live .pane__fig:nth-of-type(odd){ transition-delay: 220ms; }
.story-live .bld{
  clip-path: inset(calc((1 - var(--expand, 0)) * 100%) 0 var(--cb, 0%) 0);
  opacity: calc(.25 + .75 * var(--expand, 0));
  transition: clip-path 1300ms var(--soft), opacity 1300ms var(--soft);
  transition-delay: calc(var(--i) * 260ms);
}
.story-live .bld--next{ transform: translate3d(0, calc((1 - var(--expand, 0)) * 24 * var(--u)), 0);
  transition: clip-path 1300ms var(--soft), opacity 1300ms var(--soft), transform 1300ms var(--soft); transition-delay: 520ms; }

@media (prefers-reduced-motion: reduce){
  .story-live .story__rail, .story-live .gear, .story-live .fig, .story-live .bld{ transition-duration: 1ms; }
  .story-live[data-step="5"] .fig, .story-live[data-step="5"] .door__p, .story-live[data-step="5"] .door__sensor::after{ animation: none; }
}

/* ===========================================================================
   STATIC COMPOSITION — no JS, reduced motion, phones
   Everything visible, stacked, in normal flow. No pin, no rail, no lock.
   =========================================================================== */
.story:not(.story-live) .story__stage{ overflow: visible; }
.story:not(.story-live) .story__rail{ display: grid; width: auto; height: auto;
  max-width: var(--max); margin-inline: auto; padding: clamp(3rem, 2rem + 4vw, 5rem) var(--gutter) var(--s-9); gap: var(--s-9); }
@media (min-width: 900px){ .story:not(.story-live) .story__rail{ padding-left: calc(var(--rail) + var(--gutter)); } }
.story:not(.story-live) .scene{ width: auto; height: auto; }
.story:not(.story-live) .scene__in{ position: static; display: grid; gap: var(--s-8); }

.story:not(.story-live) .story__hero{ position: static; width: auto; max-width: 44rem; }
.story:not(.story-live) .story__hero .display-xl{ font-size: clamp(2.6rem, 1.6rem + 4.5vw, 5.6rem); }
.story:not(.story-live) .story__titles{ position: static; width: auto; gap: var(--s-5); }
.story:not(.story-live) .ttl__icon{ width: clamp(2.75rem, 1.9rem + 3.6vw, 4rem); }

.story:not(.story-live) .story__gearbox{ --u: calc(min(100vw, 1200px) / 960);
  position: relative; inset: auto; width: 100%; height: calc(510 * var(--u)); }
.story:not(.story-live) .gear{ --ox: -1076; --oy: -580;
  transform: translate3d(calc((var(--cx, 0) - var(--d, 600) / 2 + var(--ox)) * var(--u)), calc((var(--cy, 0) - var(--d, 600) / 2 + var(--oy)) * var(--u)), 0) rotate(var(--phase, 0deg)); }
/* phones and tablets: the three-gear chain (world x 1040-1833) is centred in
   the box instead of hanging off its left edge */
@media (max-width: 899px){ .story:not(.story-live) .gear{ left: 50%; --ox: -1437; } }
@media (prefers-reduced-motion: no-preference){
  .story:not(.story-live) .gear{ animation: gear-turn calc(44s * var(--n, 12) / 12) linear infinite; }
  .story:not(.story-live) .gear:nth-child(even){ animation-direction: reverse; }
}
@keyframes gear-turn{ to{ transform: translate3d(calc((var(--cx, 0) - var(--d, 600) / 2 + var(--ox)) * var(--u)), calc((var(--cy, 0) - var(--d, 600) / 2 + var(--oy)) * var(--u)), 0) rotate(calc(var(--phase, 0deg) + 360deg)); } }

/* The street, framed on the shop: the world is shown from x -60 to 1140 and
   from y 190 down to the kerb, so the lamp frames the shop and the back row
   rises behind it. The tree sits outside that frame. */
.story:not(.story-live) .street{ --u: calc(min(100vw - 2 * var(--gutter), 1100px) / 1200);
  position: relative; inset: auto; width: 100%; height: calc(725 * var(--u)); overflow: hidden; }
.story:not(.story-live) .prop--tree{ display: none; }
.story:not(.story-live) .street__world{ left: calc(60 * var(--u)); top: calc(-190 * var(--u)); height: calc(915 * var(--u)); }
.story:not(.story-live) .street__line{ display: block; position: absolute; left: calc(-400 * var(--u)); width: calc(2000 * var(--u)); top: calc(901 * var(--u)); height: 1.5px; background: var(--ink); z-index: 5; }
.story:not(.story-live) .bld--next{ display: none; }
.story:not(.story-live) .story__ground{ display: none; }
.story:not(.story-live) .story__cta{ position: static; width: auto; max-width: 34rem; }

/* ---- phones and tablets with motion: the street plays once, in view ------
   story.js adds .story-m, then .is-playing when the street is on screen and
   .is-grown once the customers are inside. */
.story-m .ttl{ opacity: 0; transform: translate3d(-14px, 0, 0); transition: opacity 600ms var(--soft), transform 600ms var(--soft); }
.story-m .ttl.in{ opacity: 1; transform: none; }
.story-m .bld, .story-m .pane__fig, .story-m .story__motto{ opacity: 0; }
.story-m .bld{ clip-path: inset(100% 0 var(--cb, 0%) 0); transition: clip-path 1200ms var(--soft), opacity 1200ms var(--soft); transition-delay: calc(var(--i) * 300ms); }
.story-m .pane__fig{ transition: opacity 900ms var(--soft); }
.story-m .story__motto{ transform: translateY(10px); transition: opacity 700ms var(--soft) 700ms, transform 700ms var(--soft) 700ms; }
.story-m.is-grown .bld{ clip-path: inset(0 0 var(--cb, 0%) 0); opacity: 1; }
.story-m.is-grown .pane__fig{ opacity: .44; }
.story-m.is-grown .story__motto{ opacity: 1; transform: none; }

@keyframes walk-m{
  0%   { opacity: 0; transform: translate3d(calc((var(--dx) - 45) * var(--u)), 0, 0) scaleX(var(--f)); }
  12%  { opacity: 1; }
  76%  { opacity: 1; transform: translate3d(calc((var(--rest) - 45) * var(--u)), 0, 0) scaleX(var(--f)); }
  100% { opacity: 0; transform: translate3d(calc((var(--rest) - 45) * var(--u)), 0, 0) scaleX(var(--f)); }
}
/* three of the five walk in on a phone: the far ones would start off-screen */
.story-m.is-playing .fig:nth-child(-n+3){ animation: walk-m 3000ms var(--soft) both; animation-delay: calc(var(--i) * 380ms); }
.story-m.is-playing .door__p--l{ animation: door-l 5000ms var(--soft) both; }
.story-m.is-playing .door__p--r{ animation: door-r 5000ms var(--soft) both; }
.story-m.is-playing .door__sensor::after{ animation: sensor 5000ms linear both; }

/* phones and portrait tablets never take the lock */
@media (max-width: 899px){
  .story-live .story__track{ height: auto; }
  .story-live .story__stage{ position: relative; height: auto; }
}

/* v4.2: the headline block clears the fixed left rail. 96 world units is only
   50-61px on common laptop screens, which put the text under the 60px rail. */
@media (min-width: 900px){
  .story-live .story__hero{ left: max(calc(96 * var(--u)), calc(var(--rail) + var(--gutter) * .75)); }
}

/* ===========================================================================
   v4.5 — owner round 5. Additive overrides only; the rules above are intact.

   1  State 3 holds a beat before the track moves, and the world zooms in as
      the street arrives, so Growth Partner reads before the camera travels.
   4  The walk is faster and starts further out, and the lights come up inside
      as the customers reach the door.
   =========================================================================== */

/* --- 1. the held beat, and the zoom -------------------------------------- */
/* Arriving at state 4 only. Going back from 4 to 3 sets data-step to 3, so
   the return is not delayed and reverse scrolling stays responsive. */
.story-live[data-step="4"] .story__rail{ transition-delay: 900ms; }

/* The camera pushes in from 94% to the designed composition as --pan runs, so
   state 6 still frames exactly what the layout was drawn for. */
.story-live .street__world{
  transform: scale(calc(.94 + .06 * var(--pan, 0)));
  transform-origin: 50% 72%;
  transition: transform 1400ms var(--ease);
}
.story-live[data-step="4"] .street__world{ transition-delay: 900ms; }

/* --- 4. a faster walk over a longer approach ----------------------------- */
.story-live .fig{
  transition: transform 1500ms var(--soft), opacity 800ms linear;
  transition-delay: calc(var(--i) * 120ms);
}
.story-live[data-step="5"] .fig{
  animation: walk-in 1800ms var(--soft) both;
  animation-delay: calc(var(--i) * 180ms);
}
.story-live[data-step="5"] .door__p--l{ animation-duration: 3200ms; }
.story-live[data-step="5"] .door__p--r{ animation-duration: 3200ms; }
.story-live[data-step="5"] .door__sensor::after{ animation-duration: 3200ms; }


@media (prefers-reduced-motion: reduce){
  .story-live .street__world{ transition-duration: 1ms; }
  .story-live[data-step="4"] .story__rail,
  .story-live[data-step="4"] .street__world{ transition-delay: 0ms; }
  .story-live .shop__lit{ transition-duration: 1ms; transition-delay: 0ms; }
}

/* --- 5. the business itself grows a bay ---------------------------------- */
/* tools/widen-store.js splices one extra window bay into v41/store.png band
   by band, never scaling, so the pen weight stays honest (design/05:61).
   The wide drawing is anchored on its RIGHT edge, where it is pixel-identical
   to the narrow one, so the door, the walkers, the window glow and the pane
   figures all keep their measured positions. Only the new bay grows, leftward
   into the gap between the shop and the tree. */
.shop__img--wide{ display: none; }
.story-live .shop__img--wide,
.story-m .shop__img--wide{
  display: block; position: absolute; top: 0; right: 0;
  width: calc(1142 * var(--u)); height: auto; max-width: none;
  clip-path: inset(0 0 0 calc((1 - var(--expand, 0)) * 142 * var(--u)));
  transition: clip-path 1400ms var(--soft) 300ms;
}
/* the tree steps aside, and the sign recentres over its widened board */
.story-live .prop--tree{
  transform: translate3d(calc(var(--expand, 0) * -158 * var(--u)), 0, 0);
  transition: opacity 700ms var(--soft) 350ms, transform 1400ms var(--soft) 300ms;
}
.story-live .shop__sign{
  transform: translate3d(calc(var(--expand, 0) * -71 * var(--u)), 0, 0);
  transition: transform 1400ms var(--soft) 300ms;
}

/* phones: the same growth, on the in-view timer */
.story-m .shop__img--wide{ clip-path: inset(0 0 0 calc(142 * var(--u))); }
.story-m.is-grown .shop__img--wide{ clip-path: inset(0 0 0 0); }
.story-m .prop--tree, .story-m .shop__sign{ transition: transform 1400ms var(--soft); }
.story-m.is-grown .prop--tree{ transform: translate3d(calc(-158 * var(--u)), 0, 0); }
.story-m.is-grown .shop__sign{ transform: translate3d(calc(-71 * var(--u)), 0, 0); }

@media (prefers-reduced-motion: reduce){
  .story-live .shop__img--wide, .story-live .prop--tree, .story-live .shop__sign{
    transition-duration: 1ms; transition-delay: 0ms;
  }
}

/* --- 2. the new hero icons ----------------------------------------------- */
/* Each service row is its own grid, so an auto-sized icon column let the three
   drawings land at different widths and heights. A fixed column and a fixed
   height line them up with each other and with the words beside them. */
.story-live .ttl{ grid-template-columns: auto minmax(0, 1fr) calc(104 * var(--u)); }
.story-live .ttl__icon{ width: auto; height: calc(88 * var(--u)); justify-self: center; }
.story:not(.story-live) .ttl__icon{ width: auto; height: clamp(2.6rem, 1.8rem + 3.4vw, 3.8rem); }


/* --- 4b. the lights come on inside (v4.5 fix) ----------------------------
   The first attempt positioned two soft rectangles by estimate, and they did
   not sit on the glass. This overlay is derived from the drawing itself: the
   window panes were found by flood-filling the near-white glass inside the
   shopfront band, dropping the door and pillar outlines by density, and
   trimming the sill bleed. It registers with store-wide.png pixel for pixel,
   so it is positioned and clipped exactly like it. */
.shop__lit{ display: none; }
.story-live .shop__lit,
.story-m .shop__lit{
  display: block; position: absolute; top: 0; right: 0;
  width: calc(1142 * var(--u)); height: auto; max-width: none;
  pointer-events: none; mix-blend-mode: multiply;
  opacity: calc(.82 * var(--lit, 0));
  clip-path: inset(0 0 0 calc((1 - var(--expand, 0)) * 142 * var(--u)));
  transition: opacity 1000ms var(--soft) 650ms, clip-path 1400ms var(--soft) 300ms;
}
.story-m .shop__lit{ opacity: 0; clip-path: inset(0 0 0 calc(142 * var(--u))); }
.story-m.is-grown .shop__lit{ opacity: .82; clip-path: inset(0 0 0 0); }

/* v4.6: the workshop does not dissolve as the camera travels. It leaves with
   the rail and stays whole until it is off screen. The fade existed to hide
   the rightmost --dock (16vw) of the work scene, which stays in frame once the
   rail has panned — but that strip is empty ground, so there is nothing to
   hide. */
.story-live .scene--work{ opacity: 1; transition: none; }

/* ===========================================================================
   v4.6 audit fixes — motion safety.

   An accessibility preference should not have to out-specify a decorative
   rule: in v4.6 a 0,4,0 walking loop beat a 0,2,0 reduced-motion guard and
   kept animating for people who had asked for no motion. So this block is
   marked important on purpose. (v4.7 replaced that loop with the queue.)
   The pause covers anything still animating when the section is off screen,
   because data-step stays at "6" once the hero has been scrolled past.
   =========================================================================== */
@media (prefers-reduced-motion: reduce){
  .qp, .door__p, .door__sensor::after,
  .mgear img, .shop__lit, .shop__img--wide{
    animation: none !important;
    transition: none !important;
  }
}

/* v5.1a: v4.8 and v4.9 added five more infinite decorative animations after
   this pause was written — the clouds, the birds, the sensor blink, the tree
   sway and the lamp glow — and none of them were added to it, so they ran for
   the life of the page once the hero had been scrolled past. */
.story.is-off .door__p,
.story.is-off .cloud,
.story.is-off .bird,
.story.is-off .prop--tree,
.story.is-off .lamp-glow,
.story.is-off .door__sensor::after{ animation-play-state: paused !important; }

/* --- v4.6 audit fixes — the camera move ---------------------------------- */
/* The rail leaves the work scene's rightmost --dock (16vw) in frame, so after
   the pan a strip of cut-off words and a gear fragment sat at the left edge.
   Not fading was the point, so instead the workshop's contents travel further
   than the rail and clear the frame entirely. Foreground moving faster than
   the camera also reads as parallax rather than as a clipped layer. */
.story-live .scene--work .scene__in{
  transform: translate3d(calc(var(--pan, 0) * -24vw), 0, 0);
  transition: transform 1200ms var(--ease);
}
.story-live[data-step="4"] .scene--work .scene__in{ transition-delay: 900ms; }

/* Scroll anchoring fights the sticky stage: reloading anywhere past the pan
   restored the page some 3,500px further down, inside the next section. */
.story, .story__track, .story__stage{ overflow-anchor: none; }


/* ===========================================================================
   v4.6 BLOCKER fix — the shopfront was losing its left corner.

   The clip-path reveal assumed store-wide.png was store.png with the new bay
   added at its LEFT edge, so that clipping 199px off the left would reproduce
   the original drawing. It is not. Measured by column-ink correlation: the two
   files agree at offset 0 up to x=435 and the whole-image best offset is 199 —
   widen-store.js splices the bay into the MIDDLE, at x=425. Clipping the left
   199px therefore removed the building's own left corner and left the extra
   bay showing, in every state before the growth. That is the "cropped
   building" the owner reported, and it was there from v4.5.

   An edge wipe cannot reveal a middle splice. The two drawings share their
   right-hand two thirds exactly, so they cross-fade instead: the door, the
   sensor, the walkers' rest positions and the right window never move, and
   only the left third resolves from two bays to three.
   =========================================================================== */
.story-live .shop__img:not(.shop__img--wide), .story-m .shop__img:not(.shop__img--wide){
  opacity: calc(1 - var(--expand, 0));
  transition: opacity 1400ms var(--soft) 300ms;
}
.story-live .shop__img--wide,
.story-m .shop__img--wide{
  clip-path: none;
  opacity: var(--expand, 0);
  transition: opacity 1400ms var(--soft) 300ms;
}
.story-m .shop__img--wide{ opacity: 0; }
.story-m.is-grown .shop__img--wide{ opacity: 1; }
.story-m.is-grown .shop__img:not(.shop__img--wide){ opacity: 0; }

/* one lit overlay per drawing, each riding its own shopfront */
.story-live .shop__lit, .story-m .shop__lit{
  clip-path: none;
  transition: opacity 1400ms var(--soft) 300ms;
}
.story-live .shop__lit--narrow, .story-m .shop__lit--narrow{
  width: calc(1000 * var(--u));
  opacity: calc(.82 * var(--lit, 0) * (1 - var(--expand, 0)));
}
.story-live .shop__lit--wide, .story-m .shop__lit--wide{
  width: calc(1142 * var(--u));
  opacity: calc(.82 * var(--lit, 0) * var(--expand, 0));
}
.story-m .shop__lit{ opacity: 0; }
.story-m.is-grown .shop__lit--wide{ opacity: .82; }


/* ===========================================================================
   v4.7 — hero task spec, applied to the current design.

   A line forms at the door instead of a crowd walking past (tasks 4-5): four
   customers join at state 5 and four more at state 6, each stepping a little
   forward into place as it fades up. The first four keep their places when the
   line grows, and scrolling back takes the newcomers away first. Everyone
   faces the door. --qx is the figure's centre from the middle of the doorway.
   =========================================================================== */
.qp{ position: absolute; left: 0; bottom: 0; z-index: 1;
  width: calc(90 * var(--u)); height: calc(200 * var(--u)); max-width: none;
  object-fit: contain; object-position: bottom; pointer-events: none;
  opacity: 0;
  transform: translate3d(calc((var(--qx, 0) - 45) * var(--u)), 0, 0); }

.story-live .qp--a{ --on: var(--arrive, 0); }
.story-live .qp--b{ --on: var(--expand, 0); }
.story-live .qp{
  opacity: var(--on, 0);
  transform: translate3d(calc((var(--qx, 0) - 45 - 26 * (1 - var(--on, 0))) * var(--u)), 0, 0);
  transition: opacity 700ms var(--soft), transform 1000ms var(--soft);
  transition-delay: calc(var(--k, 0) * 160ms);
}
/* the second four wait for the bay to start opening */
.story-live .qp--b{ transition-delay: calc(500ms + var(--k, 0) * 160ms); }

/* phones: the same line, on the in-view timer */
.story-m .qp{ transform: translate3d(calc((var(--qx, 0) - 71) * var(--u)), 0, 0);
  transition: opacity 700ms var(--soft), transform 1000ms var(--soft);
  transition-delay: calc(var(--k, 0) * 160ms); }
.story-m.is-playing .qp--a,
.story-m.is-grown .qp--b{ opacity: 1; transform: translate3d(calc((var(--qx, 0) - 45) * var(--u)), 0, 0); }

/* the static composition (no JS) shows the short line */
.story:not(.story-live):not(.story-m) .qp--a{ opacity: 1; }

/* Task 1: the tree is part of the scene from the first frame, beside the
   storefront crop, and travels with it. It no longer fades in with the pan. */
.story-live .prop--tree{ opacity: 1; }
/* Placed in the gap between the gears and the drawn edge of the building
   (the shop box starts at 0u but its ink starts at 108u), so the planter
   clears the pink gear and the canopy only just meets the storefront corner.
   When the bay opens the building reaches out to -34u, so the tree steps
   aside far enough that the new window stays clear. */
.story-live .prop--tree{
  left: calc(-130 * var(--u));
  transform: translate3d(calc(var(--expand, 0) * -130 * var(--u)), 0, 0);
}
/* The v4.7 figures stand with their feet together, so they are narrower than
   the v4.2 walkers and a 200-unit box drew them almost as tall as the 215-unit
   door. 158 puts a person at about three quarters of the door, where the
   walkers used to sit. */
.qp{ height: calc(158 * var(--u)); }

/* The figure in the new bay only belongs where the bay exists. The static
   composition (no JS, reduced motion) never grows the store, so there it
   stood on bare ground outside the building. */
.story:not(.story-live):not(.story-m) .pane__fig--bay{ display: none; }

/* ===========================================================================
   v4.8 — the street is scenery. The back row and the second location are
   there whenever the street is on screen: no clip-path wipe, no fade, no rise.
   =========================================================================== */
.story-live .bld, .story-m .bld{
  clip-path: inset(0 0 var(--cb, 0%) 0);
  opacity: 1; transform: none; transition: none;
}


/* ===========================================================================
   v4.8 — the regenerated store (owner: the old expansion looked cut out).
   v48/store.png is ONE drawing: the main shop plus an annex on its left that
   the business expands into, with its own fascia, awning and finished corner.
   Nothing is spliced. Before the growth, the drawing is clipped along the main
   shop's own outline — fascia edge, the awning's slanted end and scallop, the
   pillar, its base — so the annex is simply not shown yet. On growth every
   point of that outline sweeps out to the left edge and uncovers the annex.
   The outline was measured on the keyed file, not estimated. --g is the
   growth: the scroll state on desktop, the in-view timer on phones, 0 static.
   Drawn at 0.46 units per pixel, placed so the door's centre stays at world x
   502 and the ground line at world y 901.
   =========================================================================== */
.shop{ left: calc(-175 * var(--u)); top: calc(383.5 * var(--u)); width: calc(1111 * var(--u)); --g: 0; }
.story-live .shop{ --g: var(--expand, 0); }
.story-m.is-grown .shop{ --g: 1; }
.story .shop .shop__img, .story .shop .shop__lit{ clip-path: polygon(calc(27.495% * (1 - var(--g, 0))) 0.000%, 100% 0.000%, 100% 100.000%, calc(26.087% * (1 - var(--g, 0))) 100.000%, calc(26.087% * (1 - var(--g, 0))) 91.828%, calc(26.749% * (1 - var(--g, 0))) 91.828%, calc(26.749% * (1 - var(--g, 0))) 40.598%, calc(25.590% * (1 - var(--g, 0))) 40.246%, calc(25.093% * (1 - var(--g, 0))) 39.719%, calc(24.845% * (1 - var(--g, 0))) 39.192%, calc(24.596% * (1 - var(--g, 0))) 38.664%, calc(24.224% * (1 - var(--g, 0))) 37.610%, calc(24.224% * (1 - var(--g, 0))) 35.501%, calc(24.472% * (1 - var(--g, 0))) 34.446%, calc(24.845% * (1 - var(--g, 0))) 33.392%, calc(25.590% * (1 - var(--g, 0))) 30.756%, calc(26.377% * (1 - var(--g, 0))) 28.120%, calc(27.039% * (1 - var(--g, 0))) 25.483%, calc(27.619% * (1 - var(--g, 0))) 23.286%, calc(27.992% * (1 - var(--g, 0))) 21.968%, calc(28.116% * (1 - var(--g, 0))) 20.650%, calc(27.495% * (1 - var(--g, 0))) 19.859%); transition: clip-path 1400ms var(--soft) 300ms, opacity 1000ms var(--soft) 650ms; }
.story-live .shop .shop__img, .story-m .shop .shop__img, .story-m.is-grown .shop .shop__img{ opacity: 1; display: block; width: 100%; }
.story .shop .shop__lit{ display: block; position: absolute; left: 0; right: auto; top: 0; width: 100%; height: auto; max-width: none;
  pointer-events: none; mix-blend-mode: multiply; opacity: 0; }
.story-live .shop .shop__lit{ opacity: calc(.82 * var(--lit, 0)); }
.story-m.is-playing .shop .shop__lit{ opacity: .82; }

/* the door, its sensor and the sign, remapped to the new drawing */
.door{ left: calc(611.8 * var(--u)); top: calc(252.1 * var(--u)); width: calc(130.6 * var(--u)); height: calc(222.6 * var(--u)); }
.door__sensor{ left: calc(662 * var(--u)); top: calc(237 * var(--u)); }
.shop__sign{ left: calc(434 * var(--u)); top: calc(18.4 * var(--u)); width: calc(451 * var(--u)); height: calc(67 * var(--u)); }
/* the annex has its own fascia, so the main sign no longer moves when it opens */
.story-live .shop__sign, .story-m.is-grown .shop__sign{ transform: none; }
/* silhouettes stand behind the new windows; the annex one sits a little lower */
.pane__fig{ top: calc(var(--y, 301) * var(--u)); }
/* the tree steps far enough aside that the annex stays clear */
.story-live .prop--tree{ transform: translate3d(calc(var(--expand, 0) * -210 * var(--u)), 0, 0); }
/* The doorway, re-measured as plain dark runs across and down the glass
   (x 1320-1622, y 533-1040 px). The first pass demanded a solid 8px
   neighbourhood, which shaved the edges and left a dark sliver beside the
   automatic doors. */
.door{ left: calc(607.2 * var(--u)); top: calc(245.2 * var(--u)); width: calc(138.9 * var(--u)); height: calc(233.2 * var(--u)); }
.door__sensor{ left: calc(661.6 * var(--u)); top: calc(230 * var(--u)); }
.door{ width: calc(117 * var(--u)); }   /* to the frame's inner edge, read off a zoomed capture */
.door__sensor{ left: calc(650.7 * var(--u)); }

/* --- v4.8 sky life ---------------------------------------------------------
   Clouds drift up and back down, each on its own period so they never move in
   step. Two birds cross the sky, flapping inside the loop while CSS carries
   them on a rising-and-dipping path. None of it is tied to scroll: the sky
   sits in the street's world, so it travels with the camera, but its motion
   runs on its own clock. */
.sky{ position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.cloud{ position: absolute; height: auto; max-width: none; opacity: .96;
  animation: cloud-bob var(--cd, 8s) ease-in-out infinite alternate; animation-delay: var(--cdl, 0s); }
.cloud--a{ left: calc(-90 * var(--u)); top: calc(96 * var(--u));  width: calc(250 * var(--u)); --cd: 7.5s; --cb: 16; }
.cloud--b{ left: calc(400 * var(--u)); top: calc(10 * var(--u));  width: calc(170 * var(--u)); --cd: 9.5s; --cb: 22; --cdl: -3s; }
.cloud--c{ left: calc(690 * var(--u)); top: calc(150 * var(--u)); width: calc(280 * var(--u)); --cd: 11s;  --cb: 12; --cdl: -6s; }
@keyframes cloud-bob{
  from{ transform: translate3d(0, calc(var(--cb, 16) * -1 * var(--u)), 0); }
  to  { transform: translate3d(calc(var(--cb, 16) * .6 * var(--u)), calc(var(--cb, 16) * var(--u)), 0); }
}

.bird{ position: absolute; left: 0; top: 0; width: calc(44 * var(--u)); will-change: transform;
  animation: bird-fly var(--bd, 24s) linear infinite; animation-delay: var(--bdl, 0s); }
.bird img{ display: block; width: 100%; height: auto; max-width: none; }
.bird--a{ --bd: 24s; --by: 70; }
.bird--b{ --bd: 31s; --by: 150; --bdl: -14s; width: calc(34 * var(--u)); }
@keyframes bird-fly{
  0%   { transform: translate3d(calc(-520 * var(--u)), calc((var(--by) + 20) * var(--u)), 0); }
  25%  { transform: translate3d(calc(40 * var(--u)),   calc((var(--by) - 25) * var(--u)), 0); }
  50%  { transform: translate3d(calc(600 * var(--u)),  calc((var(--by) + 15) * var(--u)), 0); }
  75%  { transform: translate3d(calc(1160 * var(--u)), calc((var(--by) - 30) * var(--u)), 0); }
  100% { transform: translate3d(calc(1780 * var(--u)), calc((var(--by) + 10) * var(--u)), 0); }
}

@media (prefers-reduced-motion: reduce){
  .cloud{ animation: none; }
  .bird{ display: none; }   /* the flapping is inside the image and cannot be paused */
}
/* birds stay in the open sky above the headline block, and the text always
   paints above anything flying past it */
.bird--a{ --by: 18; }
.bird--b{ --by: 44; }
.story__cta{ z-index: 6; }

/* The street's ground line sits behind the whole scene, so the gears and the
   street stand on it rather than being crossed by it. It also waits out the
   held beat entering state 4: before, the camera was held but the line was
   not, so it faded in across the gears while the workshop was still in view. */
.story-live .story__rail{ position: relative; z-index: 1; }
.story-live .story__ground{ z-index: 0; }
.story-live[data-step="4"] .story__ground{ transition-delay: 1100ms; }

/* --- v4.9 more life ----------------------------------------------------- */
/* The door sensor beeps: a red LED blinking on and off whenever the street is
   on screen. It still turns green while the doors open for the line, then
   goes back to blinking. */
@keyframes sensor-blink{
  0%, 52%  { background: #F0341E; box-shadow: 0 0 calc(7 * var(--u)) calc(1.5 * var(--u)) rgba(240, 52, 30, .85); }
  53%, 100%{ background: #6E1A10; box-shadow: none; }
}
.story-live .door__sensor::after, .story-m .door__sensor::after{ animation: sensor-blink 1.3s linear infinite; }
.story-live[data-step="5"] .door__sensor::after{ animation: sensor 3200ms linear both, sensor-blink 1.3s linear 3200ms infinite; }

/* The tree sways from its base. The individual rotate property composes with
   the transform that moves the tree aside when the annex opens. */
@keyframes tree-sway{ from{ rotate: -1deg; } to{ rotate: 1.3deg; } }
.story-live .prop--tree, .story-m .prop--tree{ transform-origin: 50% 100%; animation: tree-sway 5.5s ease-in-out infinite alternate; }

/* The street lamp comes on with the shop lights and glows softly. */
.lamp-glow{ position: absolute; left: calc(1000 * var(--u)); top: calc(360 * var(--u)); width: calc(143 * var(--u)); height: calc(143 * var(--u));
  border-radius: 50%; pointer-events: none; z-index: 2; opacity: 0;
  background: radial-gradient(circle, rgba(255, 252, 228, .95) 0%, rgba(255, 246, 200, .55) 35%, rgba(255, 240, 180, 0) 70%); }
@keyframes lamp-pulse{ from{ transform: scale(.94); } to{ transform: scale(1.06); } }
.story-live .lamp-glow{ opacity: calc(.85 * var(--lit, 0)); transition: opacity 1000ms var(--soft) 650ms; animation: lamp-pulse 3.2s ease-in-out infinite alternate; }
.story-m.is-playing .lamp-glow{ opacity: .85; transition: opacity 1000ms var(--soft); animation: lamp-pulse 3.2s ease-in-out infinite alternate; }

@media (prefers-reduced-motion: reduce){
  .prop--tree, .lamp-glow{ animation: none !important; }
}

/* --- v4.9 phones (and the no-JS / reduced-motion composition) -------------
   The gears sat a full grid gap below the service rows, with a band of empty
   yellow above them; they now tuck in under the rows. The street frame showed
   the world from x -60, which since the v4.8 store cut the annex and the end
   of the line off the left edge; it now shows x -120 to 1150, so the grown
   store, the whole line and the lamp all fit. */
.story:not(.story-live) .story__gearbox{ margin-top: calc(var(--s-3) - var(--s-8)); }
.story:not(.story-live) .street{ --u: calc(min(100vw - 2 * var(--gutter), 1100px) / 1270); }
.story:not(.story-live) .street__world{ left: calc(120 * var(--u)); }
/* the added buildings stand further back than the café, level with the back row */
.bld--side{ z-index: 1; }

/* --- v4.9 shoppers behind the glass --------------------------------------
   Animated now, so they are placed by their centre (--x) in a box wide enough
   that the reaching figure is never squeezed, and shown a touch stronger than
   the old silhouettes so the movement reads through the glass. */
.pane__fig{ left: calc((var(--x) - 45) * var(--u)); width: calc(90 * var(--u)); height: calc(124 * var(--u)); transform: none; }
.story-live .pane__fig{ opacity: calc(.56 * var(--inside, 0)); }
.story-m.is-grown .pane__fig{ opacity: .56; }
.story:not(.story-live):not(.story-m) .pane__fig{ opacity: .5; }

/* Critic, must fix: when the automatic doors slid open they revealed the
   drawing's solid dark door, not a shop. Behind the panels there is now a
   warm lit interior, the same light as the windows. */
.door{ background: linear-gradient(to bottom, #F3D48A 0%, #FBE7B4 55%, #F6DDA0 100%); box-shadow: inset 0 calc(6 * var(--u)) calc(10 * var(--u)) rgba(60, 40, 10, .25); }

/* Critic, nice to fix: the second four joined the line before the annex wall
   behind them had finished appearing, so they briefly floated over bare
   ground. They now arrive as the wall completes. */
.story-live .qp--b{ transition-delay: calc(1150ms + var(--k, 0) * 160ms); }
/* a little air left of the annex awning on phones */
.story:not(.story-live) .street{ --u: calc(min(100vw - 2 * var(--gutter), 1100px) / 1310); }
.story:not(.story-live) .street__world{ left: calc(150 * var(--u)); }

/* ===========================================================================
   v5.0 — the mobile round. Additive overrides only; the rules above are intact.

   The owner: "the mobile view needs a bit of work done and the gear animation
   is misplaced." Everything here is scoped to max-width 899px, which is the
   branch phones and portrait tablets take (`.story:not(.story-live)`), so the
   stepped desktop hero is untouched.
   =========================================================================== */

/* --- 1. the gearbox goes where the headline puts it ----------------------
   On a phone the two scenes flatten into one grid and document order put the
   gearbox third: headline, then the three service rows, then the engine that
   is supposed to be driving them, stranded between the row list and the
   shopfront. The desktop composition has the gears beside the headline, and
   that is the order the copy asks for — "Build the system behind your growth",
   then the system, then the three things it is made of.

   Done with `order` on the existing grid, so no markup moves. The gearbox is
   aria-hidden and holds nothing focusable, so the visual order and the focus
   order still agree. The negative top margin tucks the gears under the
   headline block; the full gap below them is kept so the rows still read as a
   separate list. */
@media (max-width: 899px){
  .story:not(.story-live) .story__hero{ order: 1; }
  .story:not(.story-live) .story__gearbox{ order: 2; margin-top: calc(var(--s-5) - var(--s-8)); }
  .story:not(.story-live) .story__titles{ order: 3; }
}

/* --- 2. the service rows had a 2px gutter ---------------------------------
   `.ttl` sets `gap: calc(19 * var(--u))`, and `--u` on `.story` is the desktop
   hero's unit: `(100vw - peek) / 1920`, which on a phone is about 0.125, so
   the gap computed to 2.375px. The square mark all but touched the "01
   Customer Acquisition" it belongs to. The icon column is fixed too — v4.5
   gave it a fixed width for `.story-live` only, so on a phone the three icons
   were each a different width. */
@media (max-width: 899px){
  .story:not(.story-live) .ttl{ gap: var(--s-3); grid-template-columns: auto minmax(0, 1fr) 2.5rem; }
  /* The three icon drawings are not square — 229, 250 and 263 wide against 256
     tall — and v4.5 gave the column a fixed height for `.story-live` only, so
     on a phone a 2.5rem width against that height stretched each one by a
     different amount. The box keeps its size so the rows stay aligned; the art
     is contained inside it. */
  .story:not(.story-live) .ttl__icon{ width: 2.5rem; object-fit: contain; }
  /* the mark is a 44px square holding a 24px dot, which is a lot of the row on
     a phone once the gutter is real */
  .story:not(.story-live) .mark{ width: 2rem; height: 2rem; }
  .story:not(.story-live) .mark::after{ width: 1.125rem; height: 1.125rem; }
  /* "Customer Acquisition" is the long one — 10.9px of line per point of
     display size. It only ever fitted on one line because the gutter was 2px.
     This tracks the viewport so all three rows stay one line and the same
     height from 360px up; at 320 the longest still wraps, and the row simply
     grows to suit. */
  .story:not(.story-live) .ttl__words{ font-size: clamp(1rem, .125rem + 5vw, 2.6rem); }
}

/* --- 3. the motto no longer leaves a hole --------------------------------
   `.story-m .story__motto{opacity:0}` was lifted only by `.is-grown`, which
   story.js sets on a 4300ms timer started when the street first touches the
   viewport, and the fade itself then waits another 700ms. A visitor reading at
   any normal pace reaches the closing plate first and sees 94px of empty
   yellow between "More customers, walking through your door." and the booking
   button, because the motto keeps its height while invisible.

   The motto is copy, not scenery. It now rides the same in-view reveal as the
   rest of the page instead of the street's growth clock: story.js adds
   `is-grown` when the closing plate itself comes into view (see story.js), and
   the 700ms hold in front of the fade is dropped on phones. */
@media (max-width: 899px){
  .story-m .story__motto{ transition: opacity 700ms var(--soft), transform 700ms var(--soft); }
}

/* --- 4. the door sensor blinks on phones too -----------------------------
   v4.9 gave the sensor a permanent blink and chained it after the walk-in
   animation for `.story-live` (line 703). The phone rule at line 317 is
   0,3,0 and out-specifies the 0,2,0 blink, so on a phone the sensor ran the
   walk-in keyframes once and then held the last frame — dead orange — for
   exactly as long as the street was on screen. Chained the same way. */
.story-m.is-playing .door__sensor::after{
  animation: sensor 5000ms linear both, sensor-blink 1.3s linear 5000ms infinite; }

/* --- 5. the queue stops half-vanishing on scroll-back --------------------
   `is-grown` is a permanent latch and `is-playing` is toggled, so leaving the
   street and coming back left customers 5-8 standing at the far end of the
   line while 1-4 — the ones at the door — had to fade in again. Once the shop
   has grown the whole line stays. The second four also get the v4.9 delay that
   was only ever given to the desktop branch, so they arrive as the annex wall
   behind them finishes instead of floating over bare ground. */
.story-m.is-grown .qp--a{ opacity: 1; transform: translate3d(calc((var(--qx, 0) - 45) * var(--u)), 0, 0); }
.story-m .qp--b{ transition-delay: calc(1150ms + var(--k, 0) * 160ms); }

/* ===========================================================================
   v5.1 — the street round.
   =========================================================================== */

/* 1. Mirrored buildings, so the two reused drawings do not read as a repeat.
   The flip is on the image alone, so --x/--y placement is unaffected.
   v5.1a: it has to out-specify `.story-live .bld, .story-m .bld{ transform:
   none }` from the v4.8 block, which is (0,2,0) against this rule's (0,1,0) —
   so the mirroring was dead on both JS branches and survived only in the
   static composition nobody looks at. Measured: computed transform was `none`
   on both flipped buildings. */
.bld--flip,
.story-live .bld--flip,
.story-m .bld--flip{ transform: scaleX(-1); }

/* 2. The cafe is part of the terrace now, so it shows on a phone too. It was
   hidden there when it sat on top of grey-shutters. */
.story:not(.story-live) .bld--next{ display: block; }

/* 3. The phone street frame. The owner: "like here it seems empty add more
   buildings so its more full and the birds as well."

   The frame was 1310 world units wide and the shop is 1111 of them, so the
   shop filled 83% of it and the only things left to see were the lamp and a
   150-unit sliver of the next building. It is 1620 units now, shifted so the
   new building left of the shop and the first two of the terrace are both in
   frame: the shop drops to about 69% of the width and the street reads as a
   street. Widening also gives the annex awning more room on the left, not
   less, so the v4.8/v4.9 clipping this offset was twice re-tuned for cannot
   come back. */
@media (max-width: 899px){
  .story:not(.story-live) .street{ --u: calc(min(100vw - 2 * var(--gutter), 1100px) / 1620); }
  .story:not(.story-live) .street__world{ left: calc(430 * var(--u)); }
}

/* 4. The sky was cropped out entirely on a phone. The frame starts at world
   y 190 and every cloud and both birds live between y 3 and y 207, so the
   owner's "empty" sky above the shop was empty because its contents were
   above the crop. On a phone they move down into the band between the frame
   top and the shop's roofline, and the birds keep flying — they are animated
   WebPs, so reduced motion still gets them hidden by the rule further up. */
/* Not phone-only: the crop is the same world y 190 in the desktop static
   composition — reduced motion, or JS blocked — and the clouds were above it
   there too, so that hero had a bare yellow sky under the nav. The positions
   are in world units, so one set serves both frames. */
.story:not(.story-live) .cloud--a{ top: calc(232 * var(--u)); }
.story:not(.story-live) .cloud--b{ top: calc(198 * var(--u)); }
.story:not(.story-live) .cloud--c{ top: calc(286 * var(--u)); }
@media (max-width: 899px){
  /* The birds are sized in world units, and the phone's world unit is a third
     of the desktop one, so at the shared 44 units they came out 9px across —
     present, but far too small to read as birds. Sized to land at roughly the
     same number of screen pixels a desktop bird gets. */
  .story:not(.story-live) .bird{ width: calc(132 * var(--u)); }
  .story:not(.story-live) .bird--b{ width: calc(102 * var(--u)); }
  .story:not(.story-live) .bird--a{ top: calc(206 * var(--u)); left: calc(120 * var(--u)); }
  .story:not(.story-live) .bird--b{ top: calc(244 * var(--u)); left: calc(560 * var(--u)); }
}

/* v5.1a: the building west of the shop belongs to the phone's framed street
   only. The stepped desktop hero runs the two scenes on one 200vw track and
   shows the shop's left end at the right edge during the workshop states, so
   anything west of the shop is drawn over the gears and the headline. */
.story-live .bld--west{ display: none; }

/* v5.1a: the owner's second mark on the desktop hero points at the pocket of
   flat yellow between the copy column and the three service positions — at
   1600x900 it measured 309px wide and 336px tall, and states 0-3 hold it
   longer than any other stage. The gear cluster sat tucked into the bottom
   corner below it. Moving the whole group up and toward the copy lets the
   engine read as part of the headline's sentence rather than a corner
   ornament. One transform on the box, so the meshing maths is untouched. */
@media (min-width: 900px){
  .story-live .story__gearbox{ transform: translate3d(calc(-150 * var(--u)), calc(-70 * var(--u)), 0); }
}

/* ===========================================================================
   v5.4 — the phone-only round (owner marks 11-14, 2026-09-24)

   Every rule here is width-gated. `.story:not(.story-live)` is NOT "the phone":
   it is also desktop under reduced motion and desktop with JS off. `.story-m`
   is closer — story.js only adds it below 900px with motion allowed — but it is
   absent on a reduced-motion phone, where the static composition renders. So
   structural changes carry a media query and animation changes ride .story-m.
   =========================================================================== */

/* --- 11. the gearbox goes, on phones only --------------------------------
   v5.0 deliberately moved this to sit between the headline and the service
   rows. The owner now wants it gone below 900px; desktop keeps it. Later in
   the file than the v5.0 `order: 2` rule, and the same 0,3,0 specificity, so
   it wins on source order. */
@media (max-width: 899px){
  .story:not(.story-live) .story__gearbox{ display: none; }
}

/* --- 12. the buildings come back, on phones only -------------------------
   Mark 1 emptied the street; mark 12 wants it back on a phone. STREET_SHOWN is
   true again so all seven are in the DOM, and they are hidden from 900px up.
   `.bld--next` needs the 0,3,0 form to out-rank the `display: block` rule the
   v5.1 round added for the static composition. */
@media (min-width: 900px){
  .story-live .bld,
  .story:not(.story-live) .bld,
  .story:not(.story-live) .bld--next{ display: none; }
}

/* --- 13. a wipe up, not a fade -------------------------------------------
   "dont add fade but a fade swip up when we scroll to the store and they are
   automatically there". The clip-path already wipes upward; the opacity fade
   riding alongside it was the thing that read as a fade. Opacity is pinned on
   and only the wipe transitions. The delay per building drops from 300ms to
   110ms so the row resolves while the street is still on screen rather than
   trickling in, and story.js now latches `is-grown` shortly after the street
   arrives instead of waiting out a 4300ms timer. */
@media (max-width: 899px){
  .story-m .bld{ opacity: 1; transition: clip-path 900ms var(--soft);
    transition-delay: calc(var(--i) * 110ms); }
  .story-m.is-grown .bld{ opacity: 1; }
}

/* --- 14. the store does not grow, on phones only -------------------------
   The growth is one variable: `--g` on .shop drives the clip-path polygon that
   holds the annex back. `.story-m.is-grown .shop{ --g: 1 }` is what opens it,
   so putting --g back to 0 at the same 0,3,0 specificity, later in the file,
   is the whole fix.

   Two things travel with it. The tree steps left to make room for the annex,
   which it must not do if no annex appears. And the figure in the new bay
   belongs only where the bay exists — the static composition already hides it
   for exactly this reason (`.story:not(.story-live):not(.story-m)`), and a
   phone that no longer grows needs the same treatment or it stands on bare
   ground outside the building. */
@media (max-width: 899px){
  .story-m.is-grown .shop{ --g: 0; }
  .story-m.is-grown .prop--tree{ transform: none; }
  .story-m .pane__fig--bay{ display: none; }
}

/* ===========================================================================
   v5.4 mark 15 — the poster header

   The owner's reference: a knockout eyebrow chip, then a heavy headline in
   which one phrase reverses out in white on a rotated orange lozenge.

   Three things keep it inside the house rules:
     - border-radius 50% on a wide box IS an ellipse, so no radius scale is
       invented. Every radius on this site is still 50% or 0.
     - white on sunset orange measures 3.29:1, which passes AA for LARGE text
       only. `.story__cta-line` is 800-weight and never below 25.6px, so it
       qualifies. This treatment must not travel to body copy.
     - where the neighbouring lines overlap the shape, ink on orange is 5.28:1,
       so the overlap is legible by design rather than by luck.

   Rotation is the one genuine departure; nothing else on the site rotates.

   Measured while building this (review/v54/type/poster.html):
     - padding on the inline box widened its line by 23px against its
       neighbours, so the shape is carried by the ::before inset instead and
       `.hit` keeps vertical padding only;
     - an ellipse narrows at its ends, and at a shallow height the first and
       last letters sat outside it at 1.07:1 — hence the generous height;
     - the ::before does NOT appear in a querySelectorAll sweep for overflowing
       elements, because it is a pseudo-element. Its horizontal outset stays
       inside the page gutter so it can never raise documentElement.scrollWidth.
       overflow-x: clip would have hidden it — and every real overflow the hero
       suite exists to catch.
   =========================================================================== */
.label--chip{ display: inline-block; background: var(--graphite); color: var(--white);
  padding: .5em .85em; letter-spacing: .2em; border-radius: 0; }

.story__cta-line .hit{ position: relative; display: inline-block; white-space: nowrap;
  color: var(--white); padding: .02em 0 .06em; transform: rotate(-3deg);
  transform-origin: left center; z-index: 1; }
/* The vertical outset is smaller here than in the specimen because the hero sets
   line-height .96 against the specimen's .86 — the same -.13em/-.19em that read
   as a designed overlap at poster size cut into "customers," at 58u. */
.story__cta-line .hit::before{ content: ""; position: absolute; inset: -.07em -.2em -.15em;
  background: var(--orange); border-radius: 50%; z-index: -1; }

/* Reduced motion keeps the shape and drops the tilt: the rotation is decoration,
   the colour is the message. */
@media (prefers-reduced-motion: reduce){
  .story__cta-line .hit{ transform: none; }
}
