/* SigBridge home v11: the motion layer (2026-09-28), v10's with the last page of the plan's settle removed (that
   section is now Before you book) and the questions' answers opening and closing gently (section 5b below: the answer's
   row eases from 0fr to 1fr, the only rule here that animates a size, and the plus turns to a minus). v10's notes follow.
   v10: v9's with What goes in your plan turning the makers' photos
   (section 4 below) in place of the coverage plan drawing and the rack; How it works (section 3) holds while its
   office plan changes. The first screen: the cards float in over the photo and the tablet's screen draws the plan in a
   gentle loop that lights the cards in step. The rest is v7's.
   Every animation and transition on the page lives here, and every rule is scoped to html.motion, which the head
   script sets only when the reader has no reduced-motion preference. Without it the page is static and complete:
   posters instead of clips, every stage of How it works shown with its still, every label and card visible, the
   plan complete on the tablet.
   Scroll-timeline rules are also scoped to html.sda (set when CSS.supports('animation-timeline: view()')).
   Elsewhere motion.js drives the same states with classes and custom properties (How it works: --s0 to --s5).
   Only transform, opacity and stroke-dashoffset animate (and the colours of a few small marks). Apart from the
   opening's three cards, which float in as the photo arrives, and How it works (one stage's words and labels show at a
   time while the frame holds; all of them stay in the page for screen readers), nothing that carries text starts
   hidden: labels only settle, dim a little or move, fully readable.
   Infinite loops sit inside [data-loop] containers and pause when their container is off screen (.is-off)
   or the tab is hidden (html.is-hidden).
   Colours are the brand book's: Web Blue rgba(0, 5, 71), SigBridge Blue rgba(43, 96, 255),
   Electric Blue rgba(48, 197, 255) and white, at various opacities. */

/* In-page links glide rather than jump */
.motion { scroll-behavior: smooth; }

/* ---------- 0. Loops pause off screen and in a hidden tab ---------- */
.motion [data-loop].is-off, .motion [data-loop].is-off *, .motion [data-loop].is-off *::before, .motion [data-loop].is-off *::after,
.motion.is-hidden [data-loop], .motion.is-hidden [data-loop] *, .motion.is-hidden [data-loop] *::before, .motion.is-hidden [data-loop] *::after {
  animation-play-state: paused !important;
}

/* ---------- 1. Small things answer the pointer: buttons, header links, situations ---------- */
.motion .btn { position: relative; transition: transform 0.18s var(--ease-out); }
.motion .btn::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: 0 10px 22px -10px rgba(43, 96, 255, 0.9); opacity: 0; transition: opacity 0.18s ease;
}
.motion .btn-light::after { box-shadow: 0 10px 24px -10px rgba(0, 5, 71, 0.55); }
.motion .btn:active { transform: translateY(0) scale(0.97); transition-duration: 0.06s; }
@media (hover: hover) and (pointer: fine) {
  .motion .btn:hover { transform: translateY(-2px); }
  .motion .btn:hover::after { opacity: 1; }
  .motion .btn:active { transform: translateY(0) scale(0.97); }
  .motion .btn:active::after { opacity: 0; }

  /* Header links: an underline draws in */
  .motion .header-nav a { position: relative; }
  .motion .header-nav a::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -6px; height: 2px; border-radius: 2px;
    background: var(--blue); transform: scaleX(0); transform-origin: 0 50%; transition: transform 0.24s var(--ease-out);
  }
  .motion .header-nav a:hover::after { transform: none; }

  /* The situations: the icon nudges toward its words */
  .motion .going-list .gi { transition: transform 0.28s var(--ease-out); }
  .motion .going-list a:hover .gi { transform: translateX(3px); }
  .motion .going-list a span { transition: color 0.2s ease, text-decoration-color 0.2s ease; }
}

/* ---------- 2. The first screen ---------- */
/* The signal rings breathe outward from the S of the logo: two slow ripples (one on a phone). Each ripple is its own
   HTML ring centred on the S like the static rings; only its transform and opacity animate. A ripple starts at the
   edge of the clear zone around the logo and fades in as it leaves it, so it never enters the clear space. */
.motion .hr-ripple {
  display: block; position: absolute; z-index: -1; pointer-events: none;
  left: calc(max(0px, (100% - var(--measure) - 2 * var(--gutter)) / 2) + var(--gutter) + var(--s-x) - 1300px * var(--rs));
  top: calc(var(--s-y) - 1300px * var(--rs));
  width: calc(2600px * var(--rs)); height: calc(2600px * var(--rs));
  border: 4px solid var(--blue); border-radius: 50%;
  box-shadow: 0 0 24px 2px rgba(43, 96, 255, 0.22);
  opacity: 0; transform: scale(0.225); will-change: transform, opacity;
  animation: hr-grow 8s cubic-bezier(0.4, 0, 0.6, 1) 2s infinite, hr-fade 8s linear 2s infinite;
}
.motion .hr-ripple-2 { animation-delay: 6s; }
.motion .hr-set { transform: translate(calc(var(--px, 0) * var(--depth) * 1px), calc(var(--py, 0) * var(--depth) * 1px)); }
.motion .hr-near { --depth: 3; }
.motion .hr-mid { --depth: 7; }
.motion .hr-far { --depth: 12; }
@keyframes hr-grow { from { transform: scale(0.225); } to { transform: scale(1); } }
@keyframes hr-fade { 0% { opacity: 0; } 16% { opacity: 0.45; } 50% { opacity: 0.26; } 100% { opacity: 0; } }

/* The opening's photo. The picture itself never fades or moves (it is the first screen's largest paint).
   The cards float in one after another once the photo has arrived (is-ready, from the head script, which also sets it
   at the load event and after 3 s, so it always comes), rise past their place a little and settle. */
.motion .hero-photo .hc { opacity: 0; transform: translateY(16px) scale(0.96); }
.motion .hero-photo.is-ready .hc { animation: hc-in 0.9s var(--ease-out) both; }
.motion .hero-photo.is-ready .hc-wifi { animation-delay: 0.1s; }
.motion .hero-photo.is-ready .hc-door { animation-delay: 0.35s; }
.motion .hero-photo.is-ready .hc-plan { animation-delay: 0.6s; }
@keyframes hc-in {
  0% { opacity: 0; transform: translateY(16px) scale(0.96); }
  55% { opacity: 1; transform: translateY(-3px) scale(1.005); }
  100% { opacity: 1; transform: none; }
}

/* The tablet comes alive. Under motion the screen starts lit and empty; motion.js then runs passes of 11.4 s,
   setting data-stage (1 to 5) and the cumulative classes s1 to s5 on the figure: s1 the walls draw (with the
   closet and the desk), s2 the access points land and their WiFi reach blooms, s3 the door's swing and its reader,
   s4 the front desk phone rings, s5 the plan is complete. The complete plan holds to 10.8 s (tp-out), fades, and the
   next pass starts from the empty screen (tp-snap: no transitions for that one frame). Each card lights with its
   layer: its icon fills, grows a little and wears a halo while its layer is new; the plan's card closes its ring
   and lifts when the plan is complete. Only transform, opacity and stroke-dashoffset animate; card words never hide
   once the cards are in. Without motion everything is shown complete (the still frame). */
.motion .hero-photo .tp-walls path, .motion .hero-photo .tp-swing { stroke-dasharray: 1; stroke-dashoffset: 0; transition: stroke-dashoffset 1.25s var(--ease); }
.motion .hero-photo .tp-walls .tp-wall-in { transition-delay: 0.45s; }
.motion .hero-photo:not(.s1) .tp-walls path { stroke-dashoffset: 1; }
.motion .hero-photo .tp-closet, .motion .hero-photo .tp-desk { transition: opacity 0.5s ease 1.1s; }
.motion .hero-photo:not(.s1) .tp-closet, .motion .hero-photo:not(.s1) .tp-desk { opacity: 0; }

/* s2: the access points land one after another and their reach blooms; one ripple leaves each */
.motion .hero-photo .tp-ap .tp-dot { transition: opacity 0.3s ease, transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1); transition-delay: calc(var(--k) * 0.2s); }
.motion .hero-photo:not(.s2) .tp-ap .tp-dot { opacity: 0; transform: scale(0.3); }
.motion .hero-photo .tp-reach { transition: opacity 0.6s ease, transform 1s var(--ease-out); transition-delay: calc(0.1s + var(--k) * 0.2s); }
.motion .hero-photo:not(.s2) .tp-reach { opacity: 0; transform: scale(0.3); }
.motion .hero-photo.s2:not(.tp-out) .tp-ripple { animation: tp-ripple 1.4s var(--ease-out) calc(0.25s + var(--k) * 0.2s) 1 both; }

/* s3: the door's swing draws and the reader lands, its light blinking twice */
.motion .hero-photo .tp-swing { transition-duration: 0.7s; }
.motion .hero-photo:not(.s3) .tp-swing { stroke-dashoffset: 1; }
.motion .hero-photo .tp-reader .tp-dev { transition: opacity 0.3s ease 0.3s, transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1) 0.3s; }
.motion .hero-photo:not(.s3) .tp-reader .tp-dev { opacity: 0; transform: scale(0.3); }
.motion .hero-photo.s3:not(.tp-out) .tp-reader circle { animation: tp-blink 0.5s ease 0.8s 2 both; }

/* s4: the front desk phone lands and rings */
.motion .hero-photo .tp-phone .tp-dev { transition: opacity 0.3s ease, transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1); }
.motion .hero-photo:not(.s4) .tp-phone .tp-dev { opacity: 0; transform: scale(0.3); }
.motion .hero-photo.s4:not(.tp-out) .tp-wave { animation: tp-wave 1.1s ease-out 0.4s 2 both; }
.motion .hero-photo.s4:not(.tp-out) .tp-wave-2 { animation-delay: 0.62s; }

/* The cards light with their layers. A photo that failed to load (is-broken, from the head script) gets no loop
   (motion.js), so its cards never wait unlit: they keep the still frame's look, icons lit and the plan's ring shown. */
.motion .hero-photo .hc-icon { transition: background-color 0.4s ease, color 0.4s ease, box-shadow 0.5s ease, transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1); }
.motion .hero-photo:not(.s2):not(.is-broken) .hc-wifi .hc-icon, .motion .hero-photo:not(.s3):not(.is-broken) .hc-door .hc-icon, .motion .hero-photo:not(.s5):not(.is-broken) .hc-plan .hc-icon,
.motion .hero-photo.tp-out:not(.is-broken) .hc-icon { background-color: rgba(43, 96, 255, 0.12); color: var(--blue); }
.motion .hero-photo[data-stage="2"] .hc-wifi .hc-icon, .motion .hero-photo[data-stage="3"] .hc-door .hc-icon,
.motion .hero-photo[data-stage="5"]:not(.tp-out) .hc-plan .hc-icon { transform: scale(1.08); box-shadow: 0 0 0 6px rgba(43, 96, 255, 0.2); }
.motion .hero-photo[data-stage="2"] .hc-wifi .hc-icon { transition-delay: 0.2s; }
.motion .hero-photo[data-stage="3"] .hc-door .hc-icon { transition-delay: 0.3s; }
.motion .hero-photo .hc-plan .hc-card { transition: transform 0.6s var(--ease-out), box-shadow 0.6s ease; }
.motion .hero-photo .hc-plan .hc-card::after { transition: opacity 0.45s ease, transform 0.7s cubic-bezier(0.3, 1.4, 0.5, 1); }
.motion .hero-photo:not(.s5):not(.is-broken) .hc-plan .hc-card::after, .motion .hero-photo.tp-out:not(.is-broken) .hc-plan .hc-card::after { opacity: 0; transform: scale(1.04); }
.motion .hero-photo.s5:not(.tp-out) .hc-plan .hc-card { transform: translateY(-4px); box-shadow: 0 30px 48px -20px rgba(0, 5, 71, 0.95), 0 0 40px -8px rgba(43, 96, 255, 0.6); }
/* Where the photo is narrow (the same bands as the smaller cards in the page's styles), the plan's card sits just
   below the fingers under the tablet, so it glows and rings without lifting */
@media (min-width: 601px) and (max-width: 767px), (min-width: 1025px) and (max-width: 1260px) {
  .motion .hero-photo.s5:not(.tp-out) .hc-plan .hc-card { transform: none; }
}

/* The end of a pass: the plan fades together; then the empty screen is set without transitions */
.motion .hero-photo .tp-plan { transition: opacity 0.5s ease; }
.motion .hero-photo.tp-out .tp-plan { opacity: 0; }
.motion .hero-photo.tp-snap, .motion .hero-photo.tp-snap *, .motion .hero-photo.tp-snap *::after { transition: none !important; }

@keyframes tp-ripple { 0% { opacity: 0.9; transform: scale(0.45); } 100% { opacity: 0; transform: scale(1.4); } }
@keyframes tp-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }
@keyframes tp-wave { 0% { opacity: 0; } 30% { opacity: 1; } 100% { opacity: 0; } }

/* ---------- 3. How it works: the frame holds while the plan changes ---------- */
/* Two layouts, chosen here at first paint (no measurement): beside (a wide, not too tall window: the words on the
   left, the scene on the right) and stacked (phones and tall tablets: the rail, the scene, then the words). Below
   500 px of height neither applies and the section keeps its still version. The track is the frame plus a hold of 3.6
   screens of 0.84 (0.72 when stacked), shared between the stages as motion.js's HW_SPAN says (one share each, one and a
   half for stage 5: about 0.55 of a screen per stage; until 2026-10-02 the hold was 5.5 screens, half the page);
   motion.js turns the scroll position into --s0 to --s5 on the scene's hosts, the rail's fill and is-current on the
   reader's stage, and measures --hw-head when stacked (the heading scrolls away above the frame unless the frame has
   room for it and the whole scene). */
.motion .hw-scene .hw-box { --s0: 0; --s1: 0; --s2: 0; --s3: 0; --s4: 0; --s5: 0; --walk: 0; }
@media (min-width: 900px) and (min-aspect-ratio: 5/4) and (min-height: 500px), (max-width: 899.98px) and (min-height: 500px), (max-aspect-ratio: 1249/1000) and (min-height: 500px) {
  .motion .hw:not(.hw-nofit) .hw-pin { position: sticky; overflow: hidden; }
  .motion .hw:not(.hw-nofit) .hw-scene { display: grid; place-items: center; min-height: 0; container-type: size; }
  .motion .hw:not(.hw-nofit) .hw-scene .hw-box { width: min(100cqw, 133.333cqh); }
  .motion .hw:not(.hw-nofit) .hw-still, .motion .hw:not(.hw-nofit) .hw-words::before { display: none; }
  /* The rail: five numbered stops (buttons: a press jumps to that stage) on one line that fills as the reader goes */
  .motion .hw:not(.hw-nofit) .hw-rail { display: block; position: relative; margin-top: 40px; max-width: 340px; }
  .motion .hw-rail ol { position: relative; display: flex; justify-content: space-between; }
  .motion .hw-rail li button {
    appearance: none; border: 0; padding: 0; cursor: pointer;
    display: grid; place-items: center; width: 32px; height: 32px; border-radius: 50%; background: var(--navy);
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.3); color: rgba(255, 255, 255, 0.75);
    font-family: var(--text); font-size: 14px; line-height: 1; font-weight: 600;
    transition: background-color 0.35s ease, color 0.35s ease, box-shadow 0.45s ease, transform 0.5s cubic-bezier(0.3, 1.5, 0.5, 1);
  }
  .motion .hw-rail li.is-done button { background: var(--blue); box-shadow: inset 0 0 0 2px var(--blue); color: var(--white); }
  .motion .hw-rail li button:focus-visible { outline-color: var(--white); }
  .motion .hw-rail li.is-current button { background: var(--white); color: var(--navy); transform: scale(1.1); box-shadow: 0 0 0 5px rgba(48, 197, 255, 0.22), 0 0 22px 2px rgba(48, 197, 255, 0.45); }
  .motion .hw-rail-line { position: absolute; left: 16px; right: 16px; top: 15px; height: 2px; border-radius: 1px; background: rgba(255, 255, 255, 0.18); }
  .motion .hw-rail-fill { position: absolute; inset: 0; border-radius: inherit; background: var(--electric); transform-origin: 0 50%; transform: scaleX(0); }
  /* The stage words: all five in one place; the reader's stage shows, the others wait above or below it */
  .motion .hw:not(.hw-nofit) .hw-stages { display: grid; gap: 0; margin-top: 32px; }
  .motion .hw:not(.hw-nofit) .hw-stage { grid-area: 1 / 1; display: block; opacity: 0; transform: translateY(-14px); transition: opacity 0.2s ease, transform 0.3s ease; }
  .motion .hw:not(.hw-nofit) .hw-stage.is-current ~ .hw-stage { transform: translateY(14px); }
  .motion .hw:not(.hw-nofit) .hw-stage.is-current,
  .motion .hw:not(.hw-live):not(.hw-nofit) .hw-stage:first-child { opacity: 1; transform: none; transition: opacity 0.5s var(--ease-out) 0.14s, transform 0.6s var(--ease-out) 0.14s; }
  .motion .hw:not(.hw-nofit) .hw-words h3 { font-size: 30px; line-height: 38px; }
  .motion .hw:not(.hw-nofit) .hw-words p { margin-top: 14px; }
  /* Layers: the world (it zooms) and the plan's still part (it never repaints while the rest changes) always; the
     people and the van only in the stage where they move (data-at, set by motion.js at a stage's edge) */
  .motion .hw-scene .hw-world { will-change: transform; }
  .motion .hw-scene .hw-svg-base { will-change: opacity; }
  .motion .hw[data-at="1"] .hw-scene .hw-walkers, .motion .hw[data-at="3"] .hw-scene .hw-crew,
  .motion .hw[data-at="5"] .hw-scene .hw-van, .motion .hw[data-at="5"] .hw-scene .hw-tech { will-change: transform; }
  .motion .hw[data-at="1"] .hw-scene .hw-shade { will-change: opacity; }
  /* Looked after: a calm pulse spreads over the building and SigBridge's rings ripple, while stage 5 is on screen */
  /* (is-s5 is set only while stage 5 is near the screen, and the loops stop in a hidden tab) */
  .motion:not(.is-hidden) .hw.is-s5 .hw-scene .hw-pulse { animation: hw-pulse 4.2s cubic-bezier(0.2, 0.6, 0.35, 1) infinite; will-change: transform, opacity; }
  .motion:not(.is-hidden) .hw.is-s5 .hw-scene .hw-node-ripple { animation: hw-ripple 4.2s cubic-bezier(0.2, 0.6, 0.35, 1) 0.8s infinite; will-change: transform, opacity; }
  /* The alert: a ring keeps pulsing round the access point while the alert is up (is-alert), so it reads as a problem
     even when the reader stops there */
  .motion:not(.is-hidden) .hw.is-alert .hw-scene .hw-alarm::before { animation: hw-alarm 1.3s cubic-bezier(0.2, 0.6, 0.35, 1) infinite; }
  .motion:not(.is-hidden) .hw.is-alert .hw-scene .hw-alarm::after { animation: hw-alarm 1.3s cubic-bezier(0.2, 0.6, 0.35, 1) 0.65s infinite; }
}
/* Beside */
@media (min-width: 900px) and (min-aspect-ratio: 5/4) and (min-height: 500px) {
  .motion .hw:not(.hw-nofit) .hw-track { height: calc(100vh + 3.6 * 84vh); height: calc(100svh + 3.6 * 84svh); }
  .motion .hw:not(.hw-nofit) .hw-pin { top: 0; height: 100vh; height: 100svh; padding-block: clamp(24px, 6svh, 64px); }
  .motion .hw:not(.hw-nofit) .hw-grid { height: 100%; display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 7fr); grid-template-rows: minmax(0, 1fr); column-gap: var(--gutter); align-items: center; }
  .motion .hw:not(.hw-nofit) .hw-copy { grid-column: 1; grid-row: 1; padding-right: 8px; }
  .motion .hw:not(.hw-nofit) .hw-scene { grid-column: 2; grid-row: 1; height: 100%; }
}
@media (min-width: 900px) and (max-width: 1180px) and (min-aspect-ratio: 5/4) and (min-height: 500px) {
  .motion .hw:not(.hw-nofit) .hw-grid { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
}
/* Stacked: one column; the heading at the top, then the rail, the scene and the words centred in the rest. The scene
   is as wide as the column (4:3) and gives up height, never width, when the frame is short; motion.js then lets the
   heading scroll away above the frame (the frame is --hw-head taller and sits that much above the window's top). */
@media (max-width: 899.98px) and (min-height: 500px), (max-aspect-ratio: 1249/1000) and (min-height: 500px) {
  .motion .hw:not(.hw-nofit) .hw-track { height: calc(100vh + var(--hw-head, 0px) + 3.6 * 72vh); height: calc(100svh + var(--hw-head, 0px) + 3.6 * 72svh); }
  .motion .hw:not(.hw-nofit) .hw-pin {
    top: calc(-1 * var(--hw-head, 0px)); height: calc(100vh + var(--hw-head, 0px)); height: calc(100svh + var(--hw-head, 0px));
    padding-block: 0 16px; container-type: size;
  }
  .motion .hw:not(.hw-nofit) .hw-grid { height: 100%; display: flex; flex-direction: column; justify-content: center; }
  .motion .hw:not(.hw-nofit) .hw-copy { display: contents; }
  .motion .hw:not(.hw-nofit) .hw-head { order: 1; flex: none; padding-top: 24px; padding-bottom: 20px; }
  .motion .hw:not(.hw-nofit) .hw-rail { order: 2; flex: none; width: 100%; max-width: 420px; margin-top: 0; }
  .motion .hw:not(.hw-nofit) .hw-scene { order: 3; flex: 0 1 calc((100cqw - 2 * var(--gutter)) * 0.75); margin-top: 16px; }
  .motion .hw:not(.hw-nofit) .hw-stages { order: 4; flex: none; margin-top: 18px; }
  /* With the heading above the frame, the rest centres in the window, never closer than 20 px to its top (the reader's
     stop keeps its glow) */
  .motion .hw.hw-headout:not(.hw-nofit) .hw-grid { justify-content: flex-start; padding-top: 20px; }
  .motion .hw.hw-headout:not(.hw-nofit) .hw-rail { margin-top: auto; }
  .motion .hw.hw-headout:not(.hw-nofit) .hw-stages { margin-bottom: auto; }
  .motion .hw:not(.hw-nofit) .hw-words h3 { font-size: 22px; line-height: 28px; }
  .motion .hw:not(.hw-nofit) .hw-words p { margin-top: 6px; font-size: 16px; line-height: 24px; }
  .motion .hw-rail li button { width: 28px; height: 28px; font-size: 13px; }
  .motion .hw-rail-line { left: 14px; right: 14px; top: 13px; }
}
@keyframes hw-pulse { 0% { transform: translate(-50%, -50%) scale(0.05); opacity: 0.9; } 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; } }
@keyframes hw-ripple { 0% { transform: scale(0.2); opacity: 0.9; } 100% { transform: scale(1); opacity: 0; } }
@keyframes hw-alarm { 0% { transform: scale(1.05); opacity: 0.95; } 100% { transform: scale(2.2); opacity: 0; } }

/* ---------- 4. What goes in your plan: each product turns between the maker's two photos ---------- */
/* The second photo (the three-quarter view; the raised front view for the closet's units) is display: none in the
   page's styles, so without motion it is never fetched; here it waits at opacity 0. motion.js adds .is-ready once it
   has loaded, then toggles .is-b while the product is on screen. The photo arriving rises early and the photo leaving
   falls late, so the product never thins in the middle of the turn; the four parts also come a little closer while
   they show their second view. */
.motion .gp .gp-b { display: block; opacity: 0; }
.motion .gp.is-ready .gp-a { transition: opacity 1.5s cubic-bezier(0.25, 0.6, 0.35, 1); }
.motion .gp.is-ready .gp-b { transition: opacity 1.5s cubic-bezier(0.6, 0, 0.75, 0.4); }
.motion .gp.is-b .gp-a { opacity: 0; transition-timing-function: cubic-bezier(0.6, 0, 0.75, 0.4); }
.motion .gp.is-b .gp-b { opacity: 1; transition-timing-function: cubic-bezier(0.25, 0.6, 0.35, 1); }
.motion .gear .gp-stage { transition: transform 6s cubic-bezier(0.4, 0, 0.2, 1); }
.motion .gear .gp.is-b .gp-stage { transform: scale(1.025); }

/* ---------- 5. Reveals as sections arrive (never hiding text) ---------- */
/* The call flow drawing: leaders draw, dots pop, labels settle */
.motion [data-reveal] .lines .leader { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset 0.7s var(--ease); }
.motion [data-reveal].in .lines .leader { stroke-dashoffset: 0; }
.motion [data-reveal].in .lines .leader:nth-of-type(1) { transition-delay: 0.5s; }
.motion [data-reveal].in .lines .leader:nth-of-type(2) { transition-delay: 0.65s; }
.motion [data-reveal].in .lines .leader:nth-of-type(3) { transition-delay: 0.8s; }
.motion [data-reveal].in .lines .leader:nth-of-type(4) { transition-delay: 0.95s; }
.motion [data-reveal] .sheet .dot { transform: scale(0.3); opacity: 0; transition: transform 0.5s cubic-bezier(0.3, 1.5, 0.5, 1), opacity 0.3s ease; }
.motion [data-reveal].in .sheet .dot { transform: none; opacity: 1; transition-delay: 0.3s; }
.motion [data-reveal] .co { translate: 0 6px; transition: translate 0.6s var(--ease-out) 0.9s; }
.motion [data-reveal].in .co { translate: none; }

/* Monitoring: the pieces arrive in the order the story reads, then the status line and the alert keep moving */
.motion [data-reveal] .m-node img { opacity: 0; transform: scale(0.86); transition: opacity 0.5s ease, transform 0.7s cubic-bezier(0.3, 1.4, 0.5, 1); }
.motion [data-reveal] .m-node .rack img { opacity: 1; transform: none; transition: none; }
.motion [data-reveal] .m-closet .rack { opacity: 0; transform: scale(0.86); transition: opacity 0.5s ease 0.15s, transform 0.7s cubic-bezier(0.3, 1.4, 0.5, 1) 0.15s; }
.motion [data-reveal].in .m-closet .rack { opacity: 1; transform: none; }
.motion [data-reveal].in .m-node img { opacity: 1; transform: none; }
.motion [data-reveal].in .m-hub img { transition-delay: 0.45s; }
.motion [data-reveal].in .m-remote img { transition-delay: 0.9s; }
.motion [data-reveal].in .m-onsite img { transition-delay: 1.05s; }
.motion [data-reveal].in .m-status::after { opacity: 1; animation: wire-flow 2.4s linear infinite; }
.motion [data-reveal].in .m-alert::after { animation: alert-ring 2.4s ease-out 1.2s infinite; }
.motion [data-reveal].in .rings-monitor .ripple { animation: ring-out 4.8s cubic-bezier(0.2, 0.6, 0.35, 1) 0.6s infinite; }
@media (max-width: 600px) {
  .motion [data-reveal].in .m-status::after { animation-name: wire-flow-v; }
}

/* Certifications and the front desk jobs: badges and icons settle */
.motion .certs[data-reveal] .cert-list img { scale: 0.8; transition: scale 0.6s cubic-bezier(0.3, 1.5, 0.5, 1); }
.motion .certs[data-reveal].in .cert-list img { scale: 1; }
.motion .certs[data-reveal].in .cert-list li:nth-child(2) img { transition-delay: 0.1s; }
.motion .certs[data-reveal].in .cert-list li:nth-child(3) img { transition-delay: 0.2s; }
.motion .desk-list[data-reveal] .job-icon { opacity: 0; transform: scale(0.6); transition: opacity 0.4s ease, transform 0.55s cubic-bezier(0.3, 1.5, 0.5, 1); }
.motion .desk-list[data-reveal].in .job-icon { opacity: 1; transform: none; }
.motion .desk-list[data-reveal].in li:nth-child(2) .job-icon { transition-delay: 0.06s; }
.motion .desk-list[data-reveal].in li:nth-child(3) .job-icon { transition-delay: 0.12s; }
.motion .desk-list[data-reveal].in li:nth-child(4) .job-icon { transition-delay: 0.18s; }
.motion .desk-list[data-reveal].in li:nth-child(5) .job-icon { transition-delay: 0.24s; }
.motion .desk-list[data-reveal].in li:nth-child(6) .job-icon { transition-delay: 0.3s; }

/* ---------- 5b. Before you book: an answer opens and closes gently ----------
   The section's own script sets .is-ready (answers folded but the first) and, a frame later, .is-live, so the
   transitions start only after the first state is drawn. A closing answer stays visible until its row has folded. */
.motion .faq.is-live .faq-a { transition: grid-template-rows 0.4s var(--ease), visibility 0s linear 0.4s; }
.motion .faq.is-live .faq-item.is-open .faq-a { transition: grid-template-rows 0.4s var(--ease), visibility 0s linear 0s; }
.motion .faq.is-live .faq-a-inner { transition: opacity 0.25s ease; }
.motion .faq.is-live .faq-item.is-open .faq-a-inner { transition: opacity 0.35s ease 0.1s; }
.motion .faq.is-live .faq-icon { transition: background-color 0.25s ease; }
.motion .faq.is-live .faq-icon::before, .motion .faq.is-live .faq-icon::after { transition: transform 0.3s var(--ease), background-color 0.25s ease; }

/* ---------- 6. Phones: lighter ---------- */
@media (max-width: 600px) {
  .motion .hr-ripple-2 { display: none; }
}

/* ---------- 7. Page transitions ---------- */
/* Moving between SigBridge pages cross-fades, with the header held and the opening easing in, where the browser
   supports cross-document view transitions and both pages opt in. Elsewhere pages load as before. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  .site-header { view-transition-name: site-header; }
  .hero { view-transition-name: hero; }
  ::view-transition-old(root) { animation: vt-out 0.24s var(--ease) both; }
  ::view-transition-new(root) { animation: vt-in 0.36s var(--ease-out) both; }
  ::view-transition-old(hero) { animation: vt-hero-out 0.28s var(--ease) both; }
  ::view-transition-new(hero) { animation: vt-hero-in 0.46s var(--ease-out) both; }
}

/* ---------- Keyframes shared with v6 ---------- */
@keyframes ring-out { 0% { transform: scale(0.2); opacity: 0.9; } 100% { transform: scale(1); opacity: 0; } }
@keyframes wire-flow { from { transform: translateX(-100%); } to { transform: translateX(250%); } }
@keyframes wire-flow-v { from { transform: translateY(-100%); } to { transform: translateY(250%); } }
@keyframes alert-ring { 0% { transform: scale(1); opacity: 0.9; } 100% { transform: scale(2.2); opacity: 0; } }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }
@keyframes vt-hero-out { to { opacity: 0; transform: translateY(-12px); } }
@keyframes vt-hero-in { from { opacity: 0; transform: translateY(16px); } }
