/* Version C: the descent. A stack of five cards; each lifts off, travels to its place and becomes its own 3D object as the visitor scrolls.
   The animated layout only applies under html.dc-on, so without JavaScript or with reduced motion this is a plain page with every line visible.
   The 3D drawing is sized in em from --em on .dc-art; each scene is drawn at data-w x data-h em and scaled by --ss to fit its slot.
   A scene fades as one object: the engine sets opacity on its .sc-g wrapper, which is flat, so the rig keeps its 3D and nothing shows through.
   A card becomes its scene's main window: it lands flat on the window's footprint, the window shows over it there wearing the same face,
   then grows from it into its tilt and sheds that face as its own screen fills in (the skin below, --mo on each .sc).
   --o stays 1 on the faces of a scene that is on screen. Never put opacity on a preserve-3d element: that flattens its children. */
:root {
  --sc-canvas: #0a0f24;
  --sc-surface: #0e1640;
  --sc-ink: #ffffff;
  --sc-ink-soft: #c7cde0;
  --sc-accent: #8fb0ff;
  --sc-accent-ink: #0a0f24;
  --sc-font-display: "Schibsted Grotesk", system-ui, sans-serif;
  --sc-font-text: "Schibsted Grotesk", system-ui, sans-serif;
}
/* Safari's rubber band shows the page behind the header: keep it night. */
html.dc-on, html:has(.has-descent) { background: #0a0f24; }
.has-descent { letter-spacing: normal; text-rendering: auto; background: #0a0f24; }
.has-descent svg.icon, .has-descent svg.chev { display: inline-block; }

/* Copy, in every mode */
.dc-copy h2 { font-size: var(--f1); font-weight: 800; letter-spacing: -0.03em; line-height: 1.04; text-wrap: balance; }
.dc-copy p { margin-top: 14px; font-size: var(--f3); line-height: 1.35; max-width: 30ch; color: var(--on-ink-2); text-wrap: pretty; }
.dc-copy--hero p { margin-top: clamp(18px, 2vw, 28px); font-size: var(--f4); line-height: 1.45; max-width: 46ch; }

/* Without JavaScript, and with reduced motion: a plain list */
html:not(.dc-on) .dc [data-sc-cue] { opacity: 1; }
html:not(.dc-on) .dc-map, html:not(.dc-on) .dc-anchor, html:not(.dc-on) .dc-sky, html:not(.dc-on) .dc-flood, html:not(.dc-on) .sc, .dc-calm .sc, .dc-calm .dc-sky, .dc-calm .dc-flood { display: none; }
html:not(.dc-on) .dc-stage { display: flex; flex-direction: column; }
html:not(.dc-on) .dc-art { order: 2; margin-top: 32px; }
html:not(.dc-on) .dc-copy { width: min(100% - 2 * var(--gutter), 1280px); margin: 32px auto 0; }
html:not(.dc-on) .dc-copies { width: auto; }
html:not(.dc-on) .dc-copy h2 { font-size: var(--f2); }

/* The cards: five windows of one product, stacked on the floor. Each face is the skin (.sk) its scene's main window starts from, so the card
   becomes that window: build.mjs gives the card and its window one markup, and both wear the rules below. A skin is a title bar with its
   three dots and a placeholder title, a wordless hint of that area's screen (.sk__body), and a status bar on the near edge (the part the
   card above leaves in view) with the area's icon, its short name and a status pill. Flat UI on a near-solid navy, brand blue for lines and
   fills, green for good news. Card tops are opaque: a label on the plate beneath must not show through a lifting card.
   Units: a skin is 56 of its em wide (the card on the floor, the window's copy sized by descent.js); --sk-u is the window's own em in the
   skin's em, so the title bar, the dots and the corner are the window's to the pixel (1.4 on the floor, the window's value once the card
   stands up), and --sk-h is the card face's height in its em (26 on the floor, the window's proportion once it stands up). Each area paints
   with its window's navy (--sk-win, --sk-bar) and places its dots like its window (--sk-pl, --sk-gap, in the window's em).
   On the card, set by descent.js as it lifts: --fade the edge faces and the glow (gone once it stands flat), --sk-e the rim (brighter on
   the floor, the window's hairline once it lands), --shade its shadow (gone as the window shows over it), --rest the brightness of the
   waiting stack (0 while an object is live). The card's thickness is two rounded plates the shape of the card, just under it in depth and
   shifted toward the viewer (the front one down, the side one down and left), so the rim follows the rounded corners with nothing showing
   through. Two flat elements, faded by opacity alone: no masks and no stacked layers, so the scroll stays cheap. */
.dc .pl { --sk-u: 1.4; --sk-h: 26; --sk-e: 0.34; transform: translateZ(calc(var(--n) * var(--gap))); }
.dc-art { --gap: 10em; }
.dc .pl__rig { position: absolute; inset: 0; transform-style: preserve-3d; }
.dc .pl__front, .dc .pl__side {
  opacity: calc(var(--fade, 1) * var(--fade, 1) * var(--rest, 1));
  left: 0;
  right: auto;
  width: 100%;
  top: calc(50% - var(--sk-h) * 0.5em);
  height: calc(var(--sk-h) * 1em);
  border-radius: calc(1.1em * var(--sk-u));
  transform-origin: 50% 50%;
  pointer-events: none;
}
.dc .pl__front { background: linear-gradient(90deg, #4f78f6, #2447b8); transform: translate3d(0, 1.8em, -0.02em); }
.dc .pl__side { background: linear-gradient(0deg, #1a2f86, #2f52d8); transform: translate3d(-1.1em, 1.8em, -0.04em); }
.dc .pl__shade, .dc .pl__top { position: absolute; inset: auto 0; top: calc(50% - var(--sk-h) * 0.5em); height: calc(var(--sk-h) * 1em); border-radius: calc(1.1em * var(--sk-u)); }
.dc .pl__shade { box-shadow: calc(1.6em * var(--sk-u)) calc(2.4em * var(--sk-u)) calc(4em * var(--sk-u)) rgba(2, 5, 20, 0.55), 0 0 2.4em rgba(79, 120, 246, calc(0.16 * var(--fade, 1))); opacity: calc(var(--shade, 1) * var(--rest, 1)); }
.dc .pl__top { opacity: var(--rest, 1); border: 0; background: none; box-shadow: none; overflow: hidden; }

/* The skin, on the card and on its window */
.dc .sk { --sk-win: #10183a; --sk-bar: #0d1431; --sk-pl: 1.1; --sk-gap: 0.45; font-family: var(--sc-font-text); font-style: normal; font-weight: 400; line-height: 1.2; letter-spacing: 0; text-align: left; color: #ffffff; }
.dc .sk--domain { --sk-win: #111a3d; --sk-bar: #0e1533; --sk-pl: 1; --sk-gap: 0.5; }
.dc .sk--exchange { --sk-bar: #0e1535; --sk-gap: 0.5; }
.dc .sk--security { --sk-win: #121a3c; --sk-bar: #0e1533; }
.dc .sk::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 calc(0.09em * var(--sk-u)) rgba(196, 210, 255, var(--sk-e, 0.14)); pointer-events: none; }
.dc .sk__bar { position: absolute; left: 0; right: 0; top: 0; display: flex; align-items: center; gap: calc(1em * var(--sk-u)); height: calc(2.6em * var(--sk-u)); padding: 0 0 0 calc(var(--sk-pl) * var(--sk-u) * 1em); background: var(--sk-bar); box-shadow: inset 0 calc(-0.09em * var(--sk-u)) 0 rgba(196, 210, 255, 0.1); }
.dc .sk__dots { display: flex; flex: none; gap: calc(var(--sk-gap) * var(--sk-u) * 1em); }
.dc .sk__dots b { width: calc(0.9em * var(--sk-u)); height: calc(0.9em * var(--sk-u)); border-radius: 50%; background: #ff5f57; }
.dc .sk__dots b + b { background: #febc2e; }
.dc .sk__dots b + b + b { background: #28c840; }
.dc .sk__ttl { flex: none; width: calc(8em * var(--sk-u)); height: calc(0.75em * var(--sk-u)); border-radius: calc(0.375em * var(--sk-u)); background: rgba(199, 205, 224, 0.26); }
.dc .sk__body { position: absolute; left: 0; right: 0; bottom: 0; top: calc(2.6em * var(--sk-u)); background: var(--sk-win); }
.dc .sk__status { position: absolute; left: 0; right: 0; bottom: 0; display: flex; align-items: center; gap: 1.6em; height: 5.8em; padding: 0 1.6em; background: #141e46; box-shadow: inset 0 1px 0 rgba(196, 210, 255, 0.14); }
.dc .sk .pl__ic svg { width: 2.2em; height: 2.2em; fill: none; stroke: #ffffff; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.dc .sk .pl__tag--vps .pl__ic svg { fill: #e5362a; stroke: none; }
/* The window's copy of its card: over the whole screen, inside the window's own corners, shown only while the scene forms (data-sc-forming,
   --mo under 1). It sheds itself in order as the window grows, so no part of it is ever seen over the real screen: the status bar over
   --mo 0 to 0.2, the title bar 0.04 to 0.22 (the real title and pill show through), the skeleton's bars 0.04 to 0.24, leaving the bare
   panel, which is then wiped away from the top over 0.18 to 0.58 behind a short edge, so the real rows come in from the top as they fill
   in on their own clock. All flat. */
.dc .sk--win { position: absolute; inset: 0; z-index: 2; border-radius: inherit; pointer-events: none; visibility: hidden; }
.sc[data-sc-forming] .sk--win { visibility: inherit; }
.sk--win > .sk__status { opacity: calc(1 - clamp(0, var(--mo, 1) / 0.2, 1)); }
.sk--win > .sk__bar { opacity: calc(1 - clamp(0, (var(--mo, 1) - 0.04) / 0.18, 1)); }
.sk--win > .sk__body > * { opacity: calc(1 - clamp(0, (var(--mo, 1) - 0.04) / 0.2, 1)); }
.sk--win > .sk__body {
  --wy: calc(clamp(0, (var(--mo, 1) - 0.18) / 0.4, 1) * 108%);
  -webkit-mask-image: linear-gradient(180deg, transparent calc(var(--wy) - 8%), #000 var(--wy)); mask-image: linear-gradient(180deg, transparent calc(var(--wy) - 8%), #000 var(--wy));
}
.sk--win::after { opacity: calc(1 - clamp(0, (var(--mo, 1) - 0.1) / 0.3, 1)); }
.dc .pl__tag { position: static; display: flex; align-items: center; gap: 1em; min-width: 0; }
.dc .pl__ic { display: flex; align-items: center; justify-content: center; flex: none; width: 3.4em; height: 3.4em; border-radius: 0.6em; background: rgba(79, 120, 246, 0.24); box-shadow: inset 0 0 0 1px rgba(143, 176, 255, 0.42); }
.dc .pl__tag--vps .pl__ic { background: #ffffff; box-shadow: none; }
.dc .pl__label, .dc .pl__tag--vps .pl__label { color: #ffffff; font-size: 2.5em; font-weight: 700; line-height: 1; letter-spacing: -0.01em; white-space: nowrap; }
.dc .pl__pill { position: relative; flex: none; width: 7.4em; height: 2.4em; margin-left: auto; border-radius: 1.2em; background: rgba(52, 211, 153, 0.14); box-shadow: inset 0 0 0 1px rgba(52, 211, 153, 0.4); }
.dc .pl__pill::before, .dc .pl__pill::after { content: ""; position: absolute; top: 50%; }
.dc .pl__pill::before { left: 0.9em; width: 0.8em; height: 0.8em; margin-top: -0.4em; border-radius: 50%; background: #34d399; }
.dc .pl__pill::after { left: 2.3em; right: 1em; height: 0.5em; margin-top: -0.25em; border-radius: 0.25em; background: rgba(52, 211, 153, 0.55); }

/* The screen under the bar, drawn from a few parts: rows, text bars (--w), status dots, meters (--v), chips. It keeps the card's own em
   whatever the window's shape, so a short window shows fewer rows and a tall one more room under them. */
.dc .pl__ui { position: absolute; left: 2em; right: 2em; top: 1.4em; bottom: 7.2em; display: grid; gap: 1.6em; overflow: hidden; }
.dc .pl__list { display: grid; align-content: start; gap: 0.8em; min-width: 0; }
.dc .pl__row { display: flex; align-items: center; gap: 1.2em; height: 3.8em; padding: 0 1.2em; border-radius: 0.6em; background: rgba(255, 255, 255, 0.035); box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.09); }
.dc .pl__txt { flex: none; width: var(--w); height: 0.9em; border-radius: 0.45em; background: rgba(199, 205, 224, 0.42); }
.dc .pl__txt--dim { background: rgba(139, 147, 184, 0.3); }
.dc .pl__dot { flex: none; width: 1em; height: 1em; margin-left: auto; border-radius: 50%; background: #34d399; box-shadow: 0 0 0 0.35em rgba(52, 211, 153, 0.16); }
.dc .pl__row > .pl__dot:first-child { margin-left: 0; }
.dc .pl__meter { flex: none; width: 8em; height: 0.8em; margin-left: auto; border-radius: 0.4em; background: linear-gradient(#4f78f6, #4f78f6) 0 0 / var(--v) 100% no-repeat, rgba(79, 120, 246, 0.22); }
.dc .pl__chip { flex: none; width: 3.6em; height: 1.8em; border-radius: 0.4em; background: rgba(79, 120, 246, 0.3); box-shadow: inset 0 0 0 1px rgba(143, 176, 255, 0.45); }
/* Servers: a load line beside three machines, each up, each with its meter. */
.dc .pl__ui--server { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); }
.dc .pl__chart { padding: 1.2em 0 0; border-radius: 0.6em; overflow: hidden; background: linear-gradient(rgba(196, 210, 255, 0.08), rgba(196, 210, 255, 0.08)) 0 33% / 100% 1px no-repeat, linear-gradient(rgba(196, 210, 255, 0.08), rgba(196, 210, 255, 0.08)) 0 66% / 100% 1px no-repeat, rgba(255, 255, 255, 0.035); box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.09); }
.dc .pl__chart svg { display: block; width: 100%; height: 100%; }
.dc .pl__line { fill: none; stroke: #4f78f6; stroke-width: 0.3em; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.dc .pl__area { fill: rgba(79, 120, 246, 0.2); }
/* Domains: the name in its field, then its records, each a type chip, a value and a check. */
.dc .pl__ui--domain { grid-template-rows: 3em auto; gap: 0.7em; }
.dc .pl__field { display: flex; align-items: center; gap: 1.2em; padding: 0 1.4em; border-radius: 1.5em; background: rgba(255, 255, 255, 0.05); box-shadow: inset 0 0 0 1px rgba(143, 176, 255, 0.34); }
.dc .pl__ui--domain .pl__list { gap: 0.6em; }
.dc .pl__ui--domain .pl__row { height: 2.8em; }
/* Exchange: folders, then the inbox, the newest message unread. */
.dc .pl__ui--exchange { grid-template-columns: 11em minmax(0, 1fr); }
.dc .pl__nav { display: grid; align-content: start; gap: 1.4em; padding: 1.3em 1.2em; border-radius: 0.6em; background: rgba(255, 255, 255, 0.035); }
.dc .pl__nav .pl__txt:first-child { background: #4f78f6; }
.dc .pl__avatar { flex: none; width: 2.6em; height: 2.6em; border-radius: 50%; background: #2447b8; box-shadow: inset 0 0 0 1px rgba(143, 176, 255, 0.5); }
.dc .pl__lines { display: grid; flex: 1; gap: 0.7em; min-width: 0; }
.dc .pl__row.is-new { box-shadow: inset 0.25em 0 0 #4f78f6, inset 0 0 0 1px rgba(143, 176, 255, 0.24); }
.dc .pl__row.is-new .pl__txt:not(.pl__txt--dim) { background: rgba(255, 255, 255, 0.78); }
/* Security: the checks that pass, and the shield that holds. */
.dc .pl__ui--security { grid-template-columns: minmax(0, 1fr) 11.6em; }
.dc .pl__check { display: flex; flex: none; align-items: center; justify-content: center; width: 2.2em; height: 2.2em; border-radius: 50%; background: rgba(52, 211, 153, 0.16); box-shadow: inset 0 0 0 1px rgba(52, 211, 153, 0.5); }
.dc .pl__check svg { width: 1.4em; height: 1.4em; fill: none; stroke: #34d399; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.dc .pl__ring { display: flex; align-items: center; justify-content: center; align-self: center; justify-self: center; width: auto; height: min(10.6em, 100%); aspect-ratio: 1; border-radius: 50%; background: rgba(52, 211, 153, 0.08); box-shadow: inset 0 0 0 0.6em #34d399, 0 0 0 1.2em rgba(52, 211, 153, 0.07); }
.dc .pl__ring svg { width: 4.4em; height: 4.4em; fill: none; stroke: #ffffff; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
/* Canadian: a quiet map with one place marked; the leaf is on the status bar. */
.dc .pl__ui--canada { left: 0; right: 0; top: 0; bottom: 5.8em; }
.dc .pl__map { position: absolute; inset: 0; background: radial-gradient(rgba(170, 190, 255, 0.34) 0.16em, transparent 0.24em) 0 0 / 1.6em 1.6em; -webkit-mask-image: radial-gradient(closest-side, #000 40%, transparent); mask-image: radial-gradient(closest-side, #000 40%, transparent); }
.dc .pl__pin { position: absolute; left: 60%; top: 50%; width: 2.2em; height: 2.2em; margin: -1.1em 0 0 -1.1em; border-radius: 50%; background: #ffffff; box-shadow: 0 0 0 0.6em rgba(79, 120, 246, 0.6), 0 0 0 2.6em rgba(143, 176, 255, 0.12), 0 0 0 4.8em rgba(143, 176, 255, 0.05); }
.dc-stack { position: absolute; inset: 0; transform-style: preserve-3d; }
/* Flat on purpose: the stack and the scenes never cut through each other. The stack sinks by --drop while an object is live.
   The stack is drawn at 0.92 of the art's em (STACK in descent.js, which solves the lift paths from the same number). */
.dc-move { position: absolute; inset: 0; perspective: 340em; perspective-origin: var(--ox, 50%) var(--oy, 60%); }
.dc-on .dc-art .scene { left: var(--cx, 50%); top: var(--cy, 70%); translate: 0 var(--drop, 0px); font-size: 0.92em; }

/* No flood: the descent ends on the same navy as every scene and the packages carry on with it. descent.js still sets --flood and
   --prelight on .dc-stage (the dust dims with them); nothing paints them, so the finale adds no full-screen layer. The peak's copy is
   styled like every scene's. Once the pin ends the stage slides under the header, navy under navy: no shadow rides the header's edge,
   its hairline is the only line. */
.dc-flood { display: none; }
/* The seam: once the pin ends (--out above 0) the stage's bottom fifth fades into the night, so whatever light the scene or the peak's
   floor pool leaves at the stage's clipped bottom edge never meets the packages on a line. Nothing while pinned; full by 5% of the slide. */
.dc-on .dc-stage::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 18%; z-index: 2; pointer-events: none; background: linear-gradient(rgba(10, 15, 36, 0), var(--night)); opacity: min(1, var(--out, 0) * 20); }

/* The claims under the Security copy: the three lines the wide plates carry, as a short list where the plates are not drawn
   (phones and portrait tablets, the plain page without JavaScript, and reduced motion, which draws no scenes). Never on wide screens
   under the descent, where the plates carry them. Each line has its own cue (descent.js), so they land one at a time across the hold. */
.dc-claims { display: none; margin: 14px 0 0; padding: 0; list-style: none; max-width: 30ch; color: var(--on-ink-2); font-size: var(--f5); line-height: 1.35; }
.dc-claims li { position: relative; padding-left: 1.1em; text-wrap: pretty; }
.dc-claims li + li { margin-top: 6px; }
.dc-claims li::before { content: ""; position: absolute; left: 0; top: 0.5em; width: 0.4em; height: 0.4em; border-radius: 50%; background: var(--sc-accent); }
html:not(.dc-on) .dc-claims, .dc-calm .dc-claims { display: block; }
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .dc-on .dc-claims { display: block; }
}
/* A wordless scroll cue under the hero copy on phones, where there is no map pill: a chevron drifting down, gone with the copy. */
.dc-cue { display: none; }
.dc-on .dc-copy--hero .dc-cue { display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: 4px 0 0 -14px; color: var(--on-ink-2); }

/* With JavaScript */
.dc-on .dc.hero { display: block; min-height: 0; padding: 0; overflow: visible; }
.dc-on .dc-stage { top: var(--header); height: calc(100vh - var(--header)); height: calc(100svh - var(--header)); height: calc(100lvh - var(--header)); background: var(--night); color: #ffffff; }
.dc-on .dc-anchor { display: block; position: absolute; top: calc(var(--at) * (100% - 100vh)); }
/* Scene stops: one flick of a touchpad or a thumb carries the page to the next scene at most. Each scene's anchor, where its picture is
   complete, is a stop the scroll never passes over (1px tall: Chrome ignores an empty box). Mandatory, because Chrome only holds a flick at
   a stop under mandatory; Safari holds it under both. The body is one snap area taller than the screen, so wherever no stop is in view (the
   top of the hero, the silence before the peak, everything below the descent) the scroll rests where it is left. Only while the scenes
   run (dc-snap, set by descent.js, in Safari only: in Chrome every short scroll slid back to its scene), never under reduced motion, never
   in edit mode. */
html.dc-snap:not(.co-pe) { scroll-snap-type: y mandatory; }
html.dc-snap:not(.co-pe) body { scroll-snap-align: start; }
html.dc-snap:not(.co-pe) .dc-anchor { height: 1px; scroll-snap-align: start; scroll-snap-stop: always; }
html.dc-on { scroll-behavior: auto; }
.dc-on .dc-art { --em: min(1.9vw, 0.6vh); position: absolute; inset: 0; width: auto; height: auto; margin: 0; }
.dc-on .dc-art .stage { perspective: none; }
.dc-on .dc-copies { position: absolute; inset: 0 auto 0 50%; transform: translateX(-50%); pointer-events: none; }
.dc-on .dc-copy { position: absolute; left: 0; right: 0; top: clamp(16px, 3.5vh, 32px); pointer-events: auto; }
.dc-on .dc-copy h2 { font-size: var(--f2); }
.dc-on .dc-copy p { font-size: var(--f4); }
.dc-on .dc-copy--hero { opacity: 1; }
.dc-on .dc-copy:not(.dc-copy--hero) { opacity: 0; }
.dc-sky { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.dc-map { display: none; }

@media (min-width: 960px), (min-width: 700px) and (orientation: landscape) {
  .dc-on .dc-art { --em: min(0.66vw, 1.08vh, 12px); }
  .dc-on .dc-copy { top: 50%; right: auto; width: min(44%, 580px); margin-top: var(--dc-mid, -100px); }
  .dc-on .dc-copy--r { left: auto; right: 0; }
  .dc-on .dc-copy--hero { width: min(50%, 660px); margin-top: -160px; }
  .dc-on .dc-copy--hero .dc-cue { display: none; }
  .has-art .dc-copy--hero .hero__sub { max-width: 40ch; }
  .dc-on .dc-copy h2 { font-size: var(--f1); }
  .dc-on .dc-copy p { font-size: var(--f3); }
  .dc-on .dc-copy--hero p { font-size: var(--f4); }
  .dc-on .dc-map {
    position: absolute; left: 50%; bottom: clamp(8px, 2vh, 18px); transform: translateX(-50%); z-index: 3;
    display: flex; gap: 2px; padding: 2px; border-radius: 3px; background: rgba(10, 15, 36, 0.72); box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.18);
    /* Gone by --out 0.15, so it does not ride up the screen after the unpin. */
    opacity: calc(1 - var(--out, 0) / 0.15);
  }
  .dc-map a { display: inline-flex; align-items: center; min-height: 40px; padding: 0 14px; border-radius: 2px; color: var(--on-ink-2); font-size: var(--f6); font-weight: 600; white-space: nowrap; }
  .dc-map a:hover { color: #ffffff; background: rgba(255, 255, 255, 0.1); }
  /* Only the tab coming on eases in; the one going off snaps, so two tabs are never half lit at once. */
  .dc-map a.is-on { background: #ffffff; color: var(--night); transition: background-color 0.18s cubic-bezier(0.23, 1, 0.32, 1), color 0.18s; }
}
@media (max-height: 520px) {
  .dc-on { --f0: clamp(1.5rem, 5.5vw, 2.25rem); --f1: clamp(1.375rem, 4vw, 1.75rem); --f3: 1rem; --f4: 0.9375rem; }
  .dc-on .dc-copy--hero { margin-top: -90px; }
  .dc-on .dc-map { display: none; }
}

/* Scenes: --q arrival, --mo the window's growth out of its card, --x departure, --m slow travel while it holds, --o 1 on screen (the fade
   is opacity on the .sc-g wrapper, and only covers the card while the window shows over it).
   The arrival: the card lands flat on its window's footprint, the window shows over it there (both wear the same skin) and then grows
   from that footprint: at --mo 0 the scene is scaled by --ms, turned --mr deg in the screen's plane and moved --mx --my scene em (set by
   descent.js from the window's measured box, so the window's start is exactly the card's landing), at --mo 1 it is untouched; each scene
   scales its own tilt by --mo, so the window starts flat and turns into it as it grows. Satellites ([data-sc-sat]) come in from --mo 0.55.
   Each .sc is its own camera: solids foreshorten, the rig leaves --zx em toward the camera (60, 40 on phones)
   while its wrapper fades; --away (a sideways slide) stays 0, departures fade where they stand; on phones the rig also drops 10em (--down).
   A scene is seen through two perspectives (its own 300em and the stage's 340em on .dc-move), so one em of depth in a rig magnifies as if
   the perspective were about 145em (measured: the bar at z 10em grows 1.074x on a phone); departures move more on screen than --zx says. */
/* The wrapper repeats the box and the camera of .dc-move exactly, so the picture is the same at opacity 1. */
.sc-g { position: absolute; inset: 0; perspective: 340em; perspective-origin: var(--ox, 50%) var(--oy, 60%); }
.sc {
  --mk: calc(1 - (1 - var(--mo, 1)) * (1 - var(--ms, 1)));
  position: absolute; width: 0; height: 0; perspective: 300em; transform-style: preserve-3d; visibility: hidden;
  transform: scale(var(--ss, 1)) translate(calc((1 - var(--mo, 1)) * var(--mx, 0) * 1em), calc((1 - var(--mo, 1)) * var(--my, 0) * 1em)) rotate(calc((1 - var(--mo, 1)) * var(--mr, 0) * 1deg)) scale3d(var(--mk), var(--mk), var(--mk));
}
.sc__rig { position: absolute; left: 0; top: 0; transform-style: preserve-3d; translate: calc(var(--x, 0) * var(--away, 0) * 24em) calc(var(--x, 0) * var(--down, 0) * 10em) calc(var(--x, 0) * var(--zx, 60) * 1em); }
/* Satellites are flat but keep their own clock on opacity, so they come in through a filter while the scene forms, and keep none after. */
.sc[data-sc-forming] [data-sc-sat] { filter: opacity(clamp(0, (var(--mo, 1) - 0.55) / 0.45, 1)); }
/* descent.js measures each window here, once per layout: the scene at its scale, flat, unmoved, no camera, no departure. */
.sc-g--off { content-visibility: hidden; }
.dc-meas .sc-g--off { content-visibility: visible; }
.dc-meas .sc { transform: scale(var(--ss, 1)) !important; }
.dc-meas .sc__rig { translate: none !important; rotate: none !important; }
/* A scene may wrap its rig in .sc__cam for its own slow sway; the wrapper is a plain 3D group here. */
.sc__cam { position: absolute; left: 0; top: 0; transform-style: preserve-3d; }
.sc i, .sc b, .sc span { display: block; font-style: normal; }
/* Lettering spans (data-chars, split in descent.js) stay in the line. */
.sc .ch { display: inline; }
.sc svg { max-width: none; }
.sc:not(.is-live) * { animation-play-state: paused !important; }
.sc.is-leaving *, .sc.is-leaving *::before, .sc.is-leaving *::after { animation: none !important; }
/* The peak's landing ring (--land) widens once as the leaf lands and stays as the floor pool. Regular departures draw no ring. */
.sc[data-peak]::after {
  content: ""; position: absolute; left: -50em; top: -50em; width: 100em; height: 100em; border-radius: 50%; pointer-events: none;
  border: 0.35em solid rgba(255, 255, 255, 0.85);
  background: radial-gradient(closest-side, rgba(143, 176, 255, 0.3), rgba(143, 176, 255, 0.1) 60%, transparent);
  transform: translateY(24em) translateZ(-5em) rotateX(76deg) scale(calc(0.2 + var(--land, 0) * 2.2));
  opacity: calc(var(--land, 0) * (1 - var(--land, 0)) * 3 + var(--land, 0) * var(--land, 0) * 0.45);
}
/* Pointer parallax on hover devices: the camera looks toward the pointer (rotateY(px * 3deg) rotateX(py * -2deg), written as axis and angle in descent.js).
   The rotate property composes with each scene's own transform, so no scene has to carry it. Touch devices get nothing here. */
@media (hover: hover) and (pointer: fine) {
  .dc-on .sc__rig { rotate: var(--cax, 0) var(--cay, 1) 0 calc(var(--cang, 0) * 1deg); }
}

/* Boxes, under one light rig for the whole page: key light top-left, cool fill from the floor. The top face carries the specular,
   right and bottom faces are the darkest, a thin bright line sits only on the top-left edges of the front. A scene may override per face. */
.box { position: absolute; width: var(--w); height: var(--h); transform-style: preserve-3d; }
.fc { position: absolute; inset: 0; }
.fc--fr { transform: translateZ(calc(var(--d) / 2)); box-shadow: inset 0.1em 0.1em 0 rgba(255, 255, 255, 0.22); }
.fc--bk { transform: rotateY(180deg) translateZ(calc(var(--d) / 2)); }
.fc--r, .fc--l { left: calc((var(--w) - var(--d)) / 2); right: auto; width: var(--d); }
.fc--r { transform: rotateY(90deg) translateZ(calc(var(--w) / 2)); }
.fc--l { transform: rotateY(-90deg) translateZ(calc(var(--w) / 2)); }
.fc--t, .fc--b { top: calc((var(--h) - var(--d)) / 2); bottom: auto; height: var(--d); }
.fc--t { transform: rotateX(90deg) translateZ(calc(var(--h) / 2)); }
.fc--b { transform: rotateX(-90deg) translateZ(calc(var(--h) / 2)); }
.fc--bk::after, .fc--r::after, .fc--l::after, .fc--t::after, .fc--b::after { content: ""; position: absolute; inset: 0; pointer-events: none; }
.fc--t::after { background: linear-gradient(135deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0.06) 60%, transparent); }
.fc--l::after { background: linear-gradient(180deg, rgba(255, 255, 255, 0.1), transparent 70%); }
.fc--r::after { background: rgba(3, 7, 26, 0.38); }
.fc--b::after { background: rgba(3, 7, 26, 0.5); }
.fc--bk::after { background: rgba(3, 7, 26, 0.3); }
/* Floor light and contact shadow a scene can place as flat siblings of its rig: ambient fill under the object, one dark shadow offset to the bottom-right. */
.sc__floor, .sc__shadow { position: absolute; left: -40em; top: 0; width: 80em; height: 80em; border-radius: 50%; pointer-events: none; transform-origin: 50% 0; transform: rotateX(90deg); }
.sc__floor { background: radial-gradient(closest-side, rgba(70, 110, 255, 0.25), transparent); }
.sc__shadow { left: -26em; width: 56em; height: 30em; margin-left: 2em; background: radial-gradient(closest-side, rgba(3, 7, 26, 0.85), transparent 70%); }

/* The landing: the packages open on the night the descent ends on, so the stage sliding away and the packages rising read as one surface.
   The dot grid fades in over the first 240px, so no texture starts on a line, and one faint lift of the brand blue sits under the heading,
   at zero on the seam itself. */
.has-descent .packages {
  background:
    linear-gradient(var(--night), transparent 240px),
    radial-gradient(rgba(170, 190, 255, 0.16) 1px, transparent 1.4px) 0 0 / 26px 26px,
    radial-gradient(80% 380px at 50% 380px, rgba(79, 120, 246, 0.07), transparent),
    var(--night);
  color: #ffffff;
}
.has-descent .packages__note { color: var(--on-ink-2); }
.has-descent .packages__ovh { color: #b7c9ff; }
@media (hover: hover) { .has-descent .packages__ovh:hover { color: #ffffff; } }
.has-descent .band {
  background-image:
    linear-gradient(var(--night), transparent 180px),
    radial-gradient(rgba(196, 210, 255, 0.13) 1px, transparent 1.4px),
    radial-gradient(110% 120% at 8% 0%, #2f5cf0 0%, #1b3a96 42%, #0d1a52 78%, var(--night) 100%);
  background-size: auto, 26px 26px, auto;
}

@media (prefers-reduced-motion: no-preference) {
  .dc-on .dc-cue .chev { animation: dc-cue 1.8s ease-in-out infinite; }
  @keyframes dc-cue { 0%, 100% { transform: translateY(-3px); opacity: 0.45; } 50% { transform: translateY(3px); opacity: 1; } }
}
@media (prefers-reduced-motion: reduce) {
  .dc-map a { transition: none; }
}
@media (max-width: 699px) and (max-height: 700px) {
  .dc-on .dc-art { --em: min(1.9vw, 0.5vh); }
}

/* One offer (since 2026-11-07), kept simple: one card on the night. On the left the offer's name, how it works in one line, an optional
   "from" price (hidden while empty) and the button; on the right what is included, a plain list with a hairline between services: each
   one's name and what it covers, and its extras as quieter lines under it. The plinth and the short list stay in the markup for the other
   versions and are hidden here; the explanations are always open (the template opens the details, and its summary is hidden). Also
   included floats in front of the card's lower left corner on a computer (below). On a phone the card is one column, the button last. */
.has-descent { --cols: minmax(0, 5fr) minmax(0, 7fr); --cols-gap: clamp(28px, 3.6vw, 56px); --win: #121a3c; --win-bar: #0e1533; --hair: rgba(196, 210, 255, 0.14); }
.has-descent .packages :focus-visible { outline-color: #ffffff; }
.has-descent .packages h2 { font-size: var(--f1); font-weight: 800; letter-spacing: -0.03em; line-height: 1.04; text-wrap: balance; }
.has-descent .packages__note { margin-top: 16px; }

.has-descent .pkgs { display: block; max-width: none; margin-top: clamp(32px, 4.4vw, 64px); }
/* Nothing in the offer may make the page scroll sideways, whatever an entrance does on the way in (on a phone the card rises, below). */
.has-descent .packages { overflow-x: clip; }
.has-descent .pkg {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  padding: clamp(26px, 3.6vw, 52px) clamp(20px, 3.4vw, 52px);
  border: 0;
  border-radius: 18px;
  background: var(--win);
  box-shadow: inset 0 0 0 1px var(--hair), 28px 40px 90px rgba(2, 5, 20, 0.55);
  color: #ffffff;
}
.has-descent .pkg__plinth,
.has-descent .pkg__list,
.has-descent .pkg__more > summary { display: none; }
.has-descent .pkg__lead > .pkg__name { margin: 0; font-size: var(--f2); font-weight: 800; letter-spacing: -0.025em; line-height: 1.08; text-wrap: balance; }
.has-descent .pkg__lead > .pkg__for { margin: 14px 0 0; padding: 0; background: none; color: #c7cde0; font-size: var(--f4); line-height: 1.45; }
.has-descent .pkg__lead > .pkg__price { margin: 22px 0 0; font-size: var(--f2); font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.has-descent .pkg__lead > .pkg__price:empty { display: none; }
.has-descent .pkg > .pkg__cta { justify-self: start; min-height: 52px; margin-top: 28px; padding: 0 28px; border: 0; border-radius: 3px; background: #ffffff; color: var(--ink); }
@media (hover: hover) { .has-descent .pkg > .pkg__cta:hover { background-color: #dfe6ff; } }
.has-descent .pkg > .pkg__cta:active { background-color: #c7d3fb; }
.has-descent .pkg > .pkg__more { margin: 28px 0 0; padding: 0; border: 0; background: none; box-shadow: none; }

.has-descent .pkg__gloss { display: grid; margin: 0; }
.has-descent .pkg__gloss > div { display: grid; grid-template-columns: minmax(0, 1fr); gap: 4px 28px; margin: 0; padding: 18px 0; border: 0; box-shadow: inset 0 1px 0 rgba(196, 210, 255, 0.12); }
.has-descent .pkg__gloss > div:first-child { padding-top: 0; box-shadow: none; }
.has-descent .pkg__gloss dt { margin: 0; color: #ffffff; font-size: var(--f4); font-weight: 700; line-height: 1.3; }
.has-descent .pkg__gloss dd { margin: 0; color: #c7cde0; font-size: var(--f5); line-height: 1.5; }
/* An extra sits under its service with no rule of its own, quieter: its name, then what it does. */
.has-descent .pkg__gloss > .pkg__gloss-sub { margin-top: -10px; padding: 0 0 18px; box-shadow: none; }
.has-descent .pkg__gloss > .pkg__gloss-sub + .pkg__gloss-sub { margin-top: -8px; }
.has-descent .pkg__gloss > .pkg__gloss-sub dt { color: #dfe6ff; font-size: var(--f5); font-weight: 600; }
.has-descent .pkg__gloss > .pkg__gloss-sub dd { color: #a9b2d4; font-size: var(--f6, 0.875rem); }
@media (min-width: 600px) {
  .has-descent .pkg__gloss > div { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
  .has-descent .pkg__gloss > .pkg__gloss-sub { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
  .has-descent .pkg__gloss > .pkg__gloss-sub dt { padding-left: 14px; border-left: 2px solid rgba(196, 210, 255, 0.25); }
}
@media (min-width: 900px) {
  .has-descent .pkg { grid-template-columns: var(--cols); grid-template-rows: auto 1fr; column-gap: var(--cols-gap); min-height: 640px; }
  .has-descent .pkg > .pkg__lead { grid-column: 1; grid-row: 1; }
  .has-descent .pkg > .pkg__cta { grid-column: 1; grid-row: 2; align-self: start; }
  .has-descent .pkg > .pkg__more { grid-column: 2; grid-row: 1 / span 2; margin-top: 0; }
}

/* Also included: a small card of its own. On a phone it follows the offer; on a computer it floats in front of the offer card's lower left
   corner, past its edge, with a deeper shadow, the satellite the scenes' windows also carry. */
.has-descent .both {
  position: relative;
  margin-top: 16px;
  padding: 22px clamp(18px, 2.4vw, 26px) 10px;
  border-radius: 14px;
  background: var(--win-bar);
  box-shadow: inset 0 0 0 1px var(--hair), 18px 28px 60px rgba(2, 5, 20, 0.55);
}
.has-descent .both h3 { margin: 0; color: #8b93b8; font-size: var(--f6, 0.875rem); font-weight: 700; letter-spacing: 0.02em; }
.has-descent .both ul { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0; margin: 10px 0 0; padding: 0; }
.has-descent .both li { display: flex; flex-direction: row; align-items: center; gap: 14px; padding: 12px 0; border-top: 1px solid rgba(196, 210, 255, 0.1); font-size: var(--f5); font-weight: 600; }
.has-descent .both li:first-child { border-top: 0; }
.has-descent .both .tile { flex: none; display: grid; place-items: center; width: 36px; height: 36px; border-radius: 10px; background: rgba(79, 120, 246, 0.12); color: #b7c9ff; }
.has-descent .both .tile svg { width: 20px; height: 20px; }
@media (min-width: 600px) and (max-width: 899px) {
  .has-descent .both ul { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 20px; }
  .has-descent .both li:nth-child(2) { border-top: 0; }
}
@media (min-width: 900px) {
  .has-descent .packages .wrap { display: grid; grid-template-columns: var(--cols); column-gap: var(--cols-gap); }
  .has-descent .packages__head { grid-column: 1 / -1; display: grid; grid-template-columns: var(--cols); column-gap: var(--cols-gap); align-items: start; }
  .has-descent .packages__note { margin-top: 0; }
  .has-descent .pkgs { grid-column: 1 / -1; grid-row: 2; }
  .has-descent .both {
    grid-column: 1;
    grid-row: 2;
    align-self: end;
    justify-self: start;
    z-index: 2;
    width: min(100%, 360px);
    margin: 0 0 -56px clamp(-40px, -2.4vw, -16px);
  }
}

/* The footer is where the page lands: night, with the name standing on the bottom edge. */
.has-descent .footer { position: relative; padding: clamp(36px, 5vw, 64px) 0 0; background: var(--night); }
.has-descent .footer::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: var(--band-pad);
  background: linear-gradient(transparent, var(--night));
  pointer-events: none;
}
@media (max-width: 899px) {
  .has-descent .footer__brand { grid-column: 1; }
  .has-descent .lang { grid-column: 2; grid-row: 1; }
  .has-descent .footer__nav { grid-column: 1 / -1; }
}
.has-descent .footer__proud { margin-top: 0; }
.has-descent .footer__nav ul { display: flex; flex-wrap: wrap; gap: 0 clamp(20px, 2.6vw, 36px); }
.has-descent .footer__mark { display: block; overflow: visible; width: 100%; height: auto; margin-top: clamp(24px, 4vw, 56px); fill: #18224f; }
.has-descent .footer__mark text { font-family: "Schibsted Grotesk", system-ui, sans-serif; font-weight: 800; font-size: 138px; }
@media (min-width: 900px) {
  .has-descent .footer__grid { grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 24px clamp(28px, 4vw, 56px); }
  .has-descent .footer__bottom { margin-top: 32px; }
}

/* After the descent, the hero's way. Each part plays its entrance once, as it arrives: descent.js puts rv-in on the part when it is on
   screen at load or as it comes in, and until then the entrance waits on its first frame under html.dc-rv, which the head script sets
   before the first paint (fill backwards, so a finished entrance leaves the element's own styles alone). The language is the hero's, on
   its curve, cubic-bezier(0.16, 1, 0.3, 1): a heading arrives word by word, each word rising 0.4em out of a 10px blur over 0.9s, 80ms
   apart, as the H1 does; the line under it rises 14px over 0.9s once its words are under way, as the hero's line does; list items rise
   12px one after the other. The table lands as one object with its words and marks following, in its own block below; the ledger's icons
   act out their lines; the contact cards and the form have their own blocks. Word spans are inline-block at rest too, as in the hero, so a
   heading breaks the same way moving or not. Delays are custom properties, not animation-delay rules, so the animation shorthand can never
   reset them. On a phone the quote's aside has no box of its own: its heading, its line, its contact heading and its cards are parts of
   their own, so the contact heading's words start at 0 there. A part above the screen, or one that has left it again (rv-now), simply
   shows; without JavaScript, with reduced motion and in print nothing is hidden. Nothing loops. */
.has-descent :is(.packages__head h2, .both h3, .quote__aside h2, .quote__contact-title, .pkg__name) .w { display: inline-block; }
.has-descent .pkg__name { --rv-w0: 0.15s; }
.has-descent .quote__contact-title { --rv-w0: 0.4s; }
.has-descent .packages__note > :nth-child(1) { --rv-d: 0.35s; }
.has-descent .packages__note > :nth-child(2) { --rv-d: 0.47s; }
.has-descent .quote__sub { --rv-d: 0.3s; }
.has-descent .quote__privacy { --rv-d: 0.6s; }
.has-descent .form-card { --rv-i: 1; }
.has-descent .quote__contact li:nth-child(1) { --rv-i: 3; }
.has-descent .quote__contact li:nth-child(2) { --rv-i: 4; --rv-k: 1; }
.has-descent .both li:nth-child(2) { --rv-k: 1; }
.has-descent .both li:nth-child(3) { --rv-k: 2; }
.has-descent .both li:nth-child(4) { --rv-k: 3; }
.has-descent .both li:nth-child(n + 5) { --rv-k: 4; }
@media (max-width: 899px) {
  .has-descent .quote__contact-title { --rv-w0: 0s; }
  .has-descent .quote__contact { --rv-li0: 0.15s; }
  .has-descent .quote__contact li:nth-child(1) { --rv-i: 1; }
  .has-descent .quote__contact li:nth-child(2) { --rv-i: 2; }
}
@media screen and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent :is(.packages__head h2, .both h3, .quote__aside h2, .quote__contact-title, .pkg__name) .w {
    animation: dc-word 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: calc(var(--rv-w0, 0s) + var(--i, 0) * 0.08s);
  }
  html.dc-rv .has-descent :is(.packages__note > *, .quote__sub, .quote__form > .quote__privacy) {
    animation: dc-sub 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: var(--rv-d, 0.3s);
  }
  html.dc-rv .has-descent .both li {
    animation: dc-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: calc(0.3s + var(--rv-k, 0) * 0.1s);
  }
  html.dc-rv .has-descent .quote__contact li {
    animation: dc-rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: calc(var(--rv-li0, 0.55s) + var(--rv-k, 0) * 0.12s);
  }
  html.dc-rv .has-descent .quote__form > .form-card { animation: dc-rise 0.56s cubic-bezier(0.16, 1, 0.3, 1) 0.05s backwards paused; }
  /* Also included is a card now: its frame rises with the part, before its heading and items, so no empty card ever shows. */
  html.dc-rv .has-descent .both { animation: dc-rise 0.56s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; }
  html.dc-rv .has-descent .both.rv-in { animation-play-state: running; }
  html.dc-rv .has-descent .both.rv-now { animation: none; }
  html.dc-rv .has-descent .footer > .wrap { animation: dc-fade 0.6s ease-out backwards paused; }
  html.dc-rv .has-descent :is(.packages__head h2 .w, .both h3 .w, .quote__aside h2 .w, .quote__contact-title .w, .packages__note > *, .quote__sub, .quote__form > .quote__privacy, .both li, .quote__contact li, .quote__form > .form-card):is(.rv-in, .rv-in *),
  html.dc-rv .has-descent .rv-in > .wrap { animation-play-state: running; }
  html.dc-rv .has-descent :is(.packages__head h2 .w, .both h3 .w, .quote__aside h2 .w, .quote__contact-title .w, .packages__note > *, .quote__sub, .quote__form > .quote__privacy, .both li, .quote__contact li, .quote__form > .form-card):is(.rv-now, .rv-now *),
  html.dc-rv .has-descent .rv-now > .wrap { animation: none; }
  @keyframes dc-word { from { opacity: 0; transform: translateY(0.4em); filter: blur(10px); } }
  @keyframes dc-sub { from { opacity: 0; translate: 0 14px; } }
  @keyframes dc-rise { from { opacity: 0; translate: 0 12px; } }
  @keyframes dc-fade { from { opacity: 0; } }

  /* Buttons and links answer at once: colour and background in 150ms. */
  .has-descent .packages__ovh,
  .has-descent .pkg__more summary,
  .has-descent .footer__nav a { transition: color 0.15s ease-out, text-decoration-color 0.15s ease-out; }
}
/* The offer arrives like the hero: the card lands (it rises 32px and tips back from 8deg in 0.8s), the name's words come in as the hero's
   do and its line rises under them; from 0.5s the services come in one after the other, 80ms apart, each from 12px to the left out of a
   6px blur, and the price and the button rise 8px once the list is in. Once everything in it has played descent.js marks the card rv-now,
   so the finished card is exactly the one drawn above; a card above the screen, or left again, simply shows. */
.has-descent .pkg__gloss > :nth-child(1) { --rv-row: 0; }
.has-descent .pkg__gloss > :nth-child(2) { --rv-row: 1; }
.has-descent .pkg__gloss > :nth-child(3) { --rv-row: 2; }
.has-descent .pkg__gloss > :nth-child(4) { --rv-row: 3; }
.has-descent .pkg__gloss > :nth-child(5) { --rv-row: 4; }
.has-descent .pkg__gloss > :nth-child(6) { --rv-row: 5; }
.has-descent .pkg__gloss > :nth-child(7) { --rv-row: 6; }
.has-descent .pkg__gloss > :nth-child(8) { --rv-row: 7; }
.has-descent .pkg__gloss > :nth-child(n+9) { --rv-row: 8; }
@media screen and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent .pkgs { transform-origin: 50% 0; animation: dc-land 0.8s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; }
  /* On a phone the card is too tall to tip: its lower edge would come forward past the screen's edges. It rises instead. */
  @media (max-width: 899px) { html.dc-rv .has-descent .pkgs { animation-name: dc-rise; } }
  html.dc-rv .has-descent .pkg__lead > .pkg__for { animation: dc-line 0.8s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; animation-delay: 0.45s; }
  html.dc-rv .has-descent .pkg__gloss > div { animation: dc-row 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; animation-delay: calc(0.5s + var(--rv-row, 0) * 0.08s); }
  html.dc-rv .has-descent :is(.pkg__lead > .pkg__price, .pkg > .pkg__cta) { animation: dc-lift 0.4s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; animation-delay: 1.05s; }
  html.dc-rv .has-descent .pkgs.rv-in,
  html.dc-rv .has-descent .pkgs.rv-in :is(.pkg__lead > .pkg__for, .pkg__lead > .pkg__price, .pkg > .pkg__cta, .pkg__gloss > div, .pkg__name .w) { animation-play-state: running; }
  html.dc-rv .has-descent .pkgs.rv-now,
  html.dc-rv .has-descent .pkgs.rv-now :is(.pkg__lead > .pkg__for, .pkg__lead > .pkg__price, .pkg > .pkg__cta, .pkg__gloss > div, .pkg__name .w) { animation: none; }
  @keyframes dc-land { from { opacity: 0; transform: perspective(1400px) translateY(32px) rotateX(8deg); } to { transform: perspective(1400px) translateY(0) rotateX(0deg); } }
  @keyframes dc-row { from { opacity: 0; translate: -12px 0; filter: blur(6px); } }
  @keyframes dc-lift { from { opacity: 0; translate: 0 8px; } }
  @keyframes dc-line { from { opacity: 0; translate: 0 10px; } }
}

/* The ledger's icons act out their lines once, each as its item arrives, 100ms apart after the heading's words, all done by 1.45s: the
   first settles like a leaf, 8px down while it turns from -14deg to rest; the second is uncovered from its left edge as it travels 8px
   right, so an arrow arrives before the box closes behind it; the third turns once round its own centre, which reads as a hand sweeping a
   clock face; the fourth winds back counter-clockwise from 200deg. The motion belongs to the item's place, not to its symbol, so any icon
   chosen in the editor for a line gets that line's motion, turning round the middle of its own box. The items rise with the reveal above;
   the icons start 50ms into their item and ease out more softly than the site's arrivals, because the item is still fading in over its
   first 0.2s and a sharper curve would spend the motion there, unseen. */
@media screen and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent .both li .tile svg { transform-box: fill-box; transform-origin: 50% 50%; }
  html.dc-rv .has-descent .both li:nth-child(1) .tile svg { animation: dc-settle 0.7s cubic-bezier(0.33, 1, 0.68, 1) 0.35s backwards paused; }
  html.dc-rv .has-descent .both li:nth-child(2) .tile svg { animation: dc-enter 0.6s cubic-bezier(0.33, 1, 0.68, 1) 0.45s backwards paused; }
  html.dc-rv .has-descent .both li:nth-child(3) .tile svg { animation: dc-turn 0.9s cubic-bezier(0.45, 0, 0.55, 1) 0.55s backwards paused; }
  html.dc-rv .has-descent .both li:nth-child(4) .tile svg { animation: dc-rewind 0.8s cubic-bezier(0.33, 1, 0.68, 1) 0.65s backwards paused; }
  html.dc-rv .has-descent .both li:is(.rv-in, .rv-in *) .tile svg { animation-play-state: running; }
  html.dc-rv .has-descent .both li:is(.rv-now, .rv-now *) .tile svg { animation: none; }
  @keyframes dc-settle { from { translate: 0 -8px; rotate: -14deg; } }
  @keyframes dc-enter { from { translate: -8px 0; clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }
  @keyframes dc-turn { from { rotate: -360deg; } }
  @keyframes dc-rewind { from { rotate: 200deg; } }
}

/* A 1px press on every button and link card; the focus ring stays as it is. On a phone the package button's border is the rest of its panel,
   so the press moves its face inside the border instead of moving the panel. */
.has-descent .contact:active,
.has-descent .lang__btn:active,
.has-descent .packages__ovh:active,
.has-descent .pkg__more summary:active { translate: 0 1px; }
@media (max-width: 899px) {
  .has-descent .pkgs > .pkg > .pkg__cta:active { translate: none; border-top-width: 9px; border-bottom-width: calc(var(--cell) - 1px); }
}

/* The quote form, in motion. It builds itself as its part arrives: the card opens from its top edge down (0.45s), then Forminator's rows
   rise 8px into it one by one, 70ms apart, the send button last, and the privacy line under the card follows, all done by about 1.1s. The
   rows step in their order in the form, so a field added in Forminator takes its place in the cascade. Forminator shows its form only when
   its own script runs: a form first drawn after the card has opened has its rows rise by themselves as it appears, at once (descent.js
   marks the card co-late), and so does a form that first appears on screen in a part that otherwise simply shows (co-rise: a refresh, a
   jump), so the form never just appears; a form that first appears off screen, above, simply shows. Once the rows have played (co-built) a
   row that appears later, like the field that "Autre" reveals, simply shows. The Turnstile row keeps the widget's room from the start
   (71px, the flexible and normal sizes; 146px compact), so the send button never moves when Cloudflare's widget arrives on the visitor's
   first interaction; the widget fades in as it first takes its height (descent.js marks it co-ts-in). The card's own shadow sits inside the
   opening, so nothing appears at the end. A package carried from the table (descent.js, after the glide) lights its tile: the blue sweeps
   in from the left with one soft ring fading out, and the tile ends in the form's own selected look; until then the tile holds its plain
   look (co-carry), never half lit. While the form is filled: a small green check inside a field that lost focus with a valid value
   (descent.js sets co-ok, and an error on the field always hides it), error lines that slide down into place under their field, a thin line
   sweeping along the bottom of the send button only while a send is in flight (the one loop), and the sent message as a notice like the
   ones in the scenes. With reduced motion every state still shows, without movement; without JavaScript none of the classes exist and the
   form is as it was. */
/* The rows' steps count the form's children: its message box comes first, so the first row is the second child. A hidden row (the field that
   "Autre" reveals) only adds one step to the rows after it. Plain nth-child on purpose: WP Rocket's minifier writes "2 of .x" as "2of .x",
   which the browser throws away, so the "of" form never reached the live page. */
.has-descent .form-card .forminator-row:nth-child(3) { --co-row: 1; }
.has-descent .form-card .forminator-row:nth-child(4) { --co-row: 2; }
.has-descent .form-card .forminator-row:nth-child(5) { --co-row: 3; }
.has-descent .form-card .forminator-row:nth-child(6) { --co-row: 4; }
.has-descent .form-card .forminator-row:nth-child(n + 7) { --co-row: 5; }
.has-descent .form-card.co-late { --co-late: 0s; }
/* The card's opening and the privacy line's later start replace the reveal's rise for those two; the section's id keeps these ahead of the
   reveal's own selectors whatever that list grows to, so the card carries its own running and showing rules. Only the card's surface opens:
   it is drawn on a layer of its own behind the fields, so the form takes clicks and taps from the first frame (a clip on the card itself
   would swallow a click made while it opens, as on a jump straight to the form). */
@media screen and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent #quote .quote__form > .form-card { animation: none; position: relative; isolation: isolate; background: none; box-shadow: none; }
  html.dc-rv .has-descent #quote .quote__form > .form-card::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    background: var(--panel);
    box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.24), var(--shadow-band);
    pointer-events: none;
    animation: co-open 0.45s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: calc(var(--rv-i, 0) * 0.05s);
  }
  html.dc-rv .has-descent #quote .quote__form.rv-in > .form-card::before { animation-play-state: running; }
  html.dc-rv .has-descent #quote .quote__form.rv-now > .form-card::before { animation: none; }
  html.dc-rv .has-descent #quote .quote__form > .quote__privacy { animation-delay: 0.65s; }
  html.dc-rv .has-descent .quote__form .forminator-row {
    animation: co-row 0.4s cubic-bezier(0.16, 1, 0.3, 1) backwards paused;
    animation-delay: calc(var(--co-late, 0.25s) + var(--co-row, 0) * 0.07s);
  }
  html.dc-rv .has-descent .quote__form.rv-in .forminator-row,
  html.dc-rv .has-descent .quote__form .co-rise .forminator-row { animation-play-state: running; }
  html.dc-rv .has-descent .quote__form.rv-now .form-card:not(.co-rise) .forminator-row,
  html.dc-rv .has-descent .quote__form .co-built .forminator-row { animation: none; }
  @keyframes co-open { from { clip-path: inset(0 -72px 100% -72px); } to { clip-path: inset(-48px -72px -96px -72px); } }
  @keyframes co-row { from { opacity: 0; translate: 0 8px; } }
}
.has-descent .form-card .forminator-turnstile { position: relative; min-height: 71px; }
.has-descent .form-card .forminator-turnstile[data-size="compact"] { min-height: 146px; }
/* Until the widget arrives its room is drawn as the widget's own outline, a quiet box with a shield, so the gap reads as the check to come
   and not as a hole; it leaves as the widget fades in over it. */
.has-descent .form-card .forminator-turnstile::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 65px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.03) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa6d0' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3l7 3v5c0 4.5-3 8-7 10-4-2-7-5.5-7-10V6z'/%3E%3Cpath d='M9 12l2 2 4-4'/%3E%3C/svg%3E") 18px 50% / 24px no-repeat;
  box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.16);
  opacity: 0.8;
  pointer-events: none;
}
.has-descent .form-card .forminator-turnstile[data-size="compact"]::before { height: 140px; }
.has-descent .form-card .forminator-turnstile.co-ts-in::before { opacity: 0; }
@media (prefers-reduced-motion: no-preference) {
  .has-descent .form-card .forminator-turnstile.co-ts-in > * { animation: co-ts 0.5s ease-out backwards; }
  .has-descent .form-card .forminator-turnstile::before { transition: opacity 0.5s ease-out; }
  @keyframes co-ts { from { opacity: 0; } }
}

/* The carried package: held plain through the glide, then lit. */
.has-descent .form-card label.forminator-radio.co-carry { background: var(--cf-fill); border-color: var(--cf-line); transition: none; }
@media (prefers-reduced-motion: no-preference) {
  .has-descent .form-card label.forminator-radio.co-lit:has(input:checked) {
    background: #2f5cf0 linear-gradient(#2f5cf0, #2f5cf0) 0 0 / 100% 100% no-repeat;
    transition: none;
    animation: co-fill 0.45s cubic-bezier(0.16, 1, 0.3, 1) backwards, co-ring 0.6s ease-out;
  }
  @keyframes co-fill { from { background-color: var(--cf-fill); background-size: 0% 100%; border-color: var(--cf-line); } to { background-color: var(--cf-fill); } }
  @keyframes co-ring { from { box-shadow: 0 0 0 0 rgba(143, 176, 255, 0.6); } to { box-shadow: 0 0 0 10px rgba(143, 176, 255, 0); } }
}

/* A field filled correctly: a green check inside it, at its right edge, top right in the message box (descent.js sets --ok-y). */
.has-descent .form-card .co-field { position: relative; }
/* Room for it, so text never runs under it; the message box takes it once something is typed, so its placeholder keeps its lines. */
.has-descent .form-card .co-field > .forminator-input,
.has-descent .form-card .co-field > .forminator-textarea:not(:placeholder-shown) { padding-right: 42px; }
.has-descent .form-card .co-field::after {
  content: "";
  position: absolute;
  top: var(--ok-y, 0px);
  right: 12px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(52, 211, 153, 0.16) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2334d399' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") 50% 50% / 13px no-repeat;
  opacity: 0;
  pointer-events: none;
}
.has-descent .form-card .co-field.co-ok:not(.forminator-has_error)::after { opacity: 1; }

/* Sending: a thin line along the bottom of the button while the request is out. Sent: the message as a notice. */
.has-descent .form-card .forminator-button-submit.forminator-button-onload { position: relative; overflow: hidden; }
.has-descent .form-card .forminator-button-submit.forminator-button-onload::after { content: ""; position: absolute; left: 0; bottom: 0; width: 100%; height: 3px; background: var(--blue); pointer-events: none; }
.has-descent .form-card .forminator-response-message.forminator-success {
  position: relative;
  padding: 16px 20px 16px 60px;
  border-radius: 12px;
  background: #182252;
  box-shadow: inset 0 0 0 1px rgba(196, 210, 255, 0.18), 0 14px 32px rgba(2, 5, 20, 0.45);
  color: #ffffff;
  font-weight: 600;
}
.has-descent .form-card .forminator-response-message.forminator-success::before {
  content: "";
  position: absolute;
  left: 16px;
  top: 50%;
  width: 32px;
  height: 32px;
  margin-top: -16px;
  border-radius: 50%;
  background: rgba(52, 211, 153, 0.16) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2334d399' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5l4.5 4.5L19 7.5'/%3E%3C/svg%3E") 50% 50% / 18px no-repeat;
}
@media (prefers-reduced-motion: no-preference) {
  .has-descent .form-card .co-field::after { scale: 0.5; transition: opacity 0.2s ease-out, scale 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
  .has-descent .form-card .co-field.co-ok:not(.forminator-has_error)::after { scale: 1; }
  .has-descent .form-card .co-field.forminator-has_error::after { transition: none; }
  .has-descent .form-card .forminator-error-message,
  .has-descent .form-card .forminator-response-message:not(.forminator-success) { animation: co-slip 0.2s cubic-bezier(0.16, 1, 0.3, 1); }
  .has-descent .form-card .forminator-response-message.forminator-success { animation: co-sent 0.5s cubic-bezier(0.16, 1, 0.3, 1) backwards; }
  .has-descent .form-card .forminator-button-submit.forminator-button-onload::after { width: 36%; animation: co-send 1.1s cubic-bezier(0.65, 0, 0.35, 1) infinite; }
  @keyframes co-slip { from { opacity: 0; translate: 0 -4px; } }
  @keyframes co-sent { from { opacity: 0; translate: 0 10px; } }
  @keyframes co-send { from { translate: -100% 0; } to { translate: 280% 0; } }
}

/* The direct contact, in motion. The cards arrive after the heading's words, the first at 0.55s into their part, the second 120ms later (on
   a phone the heading is a part of its own above them, already under way, so they start at 0.4s). Each card slides in 16px from the left as
   it fades (0.7s); its tile lands, from 0.6 of its size, and one soft ring fades outward from it; its icon acts out its line, keyed to the
   item's place, not to its symbol, so any icon chosen in the editor gets its line's motion: the first is pushed in from the tile's left
   edge, uncovered as it comes, like a letter slid through a slot (its clip travels against it, so the edge it comes out of stays where the
   tile's is: 10px left of the 24px icon in the 44px tile); the second rings once, a quick swing of 12deg that dies back to rest in 0.5s
   (the only swing on the page). Its label rises out of a blur, as the hero's words do, and then its value types itself in, one character
   every 25ms (faster for a long value, so any value is typed within 0.9s), with a thin caret after the character just typed, before the
   first one until typing starts, that blinks twice after the last one and goes. descent.js splits the value into one span per character
   (--k its index, --n the count) on a copy that screen readers skip, keeps the plain text in a visually hidden copy so the link's name
   stays the address or the number, and puts the plain text back once the caret has gone, so the finished card is the one drawn above (a
   long value that would wrap one character differently as plain text keeps its letters, finished: co-done). A character waits as
   transparent ink, not at opacity 0, so the carets it carries can show before it does. The cards replace the reveal's rise; the section's
   id keeps these ahead of the reveal's own selectors, so they carry their own running and showing rules (an item's place is read through
   :where, so those two rules stay ahead of the icons' own). A part that simply shows (rv-now) shows its cards finished, and its values are
   never split. */
.has-descent .contact__value > .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; user-select: none; }
@media screen and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent #quote .quote__contact > li { --co-at: 0.55s; animation: co-slide 0.7s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; animation-delay: var(--co-at); }
  html.dc-rv .has-descent #quote .quote__contact > li:nth-child(2) { --co-at: 0.67s; }
  html.dc-rv .has-descent #quote .quote__contact .tile {
    animation: co-land 0.6s cubic-bezier(0.16, 1, 0.3, 1) backwards paused, co-halo 0.6s ease-out paused;
    animation-delay: calc(var(--co-at) + 0.08s), calc(var(--co-at) + 0.3s);
  }
  html.dc-rv .has-descent #quote .quote__contact > li:where(:nth-child(1)) .tile svg { animation: co-post 0.6s cubic-bezier(0.22, 1, 0.36, 1) backwards paused; animation-delay: calc(var(--co-at) + 0.22s); }
  html.dc-rv .has-descent #quote .quote__contact > li:where(:nth-child(2)) .tile svg { transform-origin: 50% 50%; animation: co-swing 0.5s ease-in-out paused; animation-delay: calc(var(--co-at) + 0.3s); }
  html.dc-rv .has-descent #quote .quote__contact .quote__label { animation: co-label 0.6s cubic-bezier(0.16, 1, 0.3, 1) backwards paused; animation-delay: calc(var(--co-at) + 0.14s); }
  html.dc-rv .has-descent #quote .quote__contact .co-typed { --co-dt: min(0.025s, 0.9s / var(--n, 1)); --co-t: calc(var(--co-at) + 0.36s); }
  html.dc-rv .has-descent #quote .quote__contact .co-k { position: relative; animation: co-key 1ms linear backwards paused; animation-delay: calc(var(--co-t) + var(--k, 0) * var(--co-dt)); }
  html.dc-rv .has-descent #quote .quote__contact .co-k::after,
  html.dc-rv .has-descent #quote .quote__contact .co-k:first-child::before {
    content: ""; position: absolute; left: 100%; top: 0.12em; bottom: 0.06em; margin-left: 0.03em; border-right: 0.09em solid #8fb0ff; opacity: 0; pointer-events: none;
  }
  html.dc-rv .has-descent #quote .quote__contact .co-k:first-child::before { left: auto; right: 100%; margin: 0 0.03em 0 0; animation: co-caret 0.18s linear paused; animation-delay: calc(var(--co-t) - 0.18s); }
  html.dc-rv .has-descent #quote .quote__contact .co-k::after { animation: co-caret var(--co-dt) linear paused; animation-delay: calc(var(--co-t) + var(--k, 0) * var(--co-dt)); }
  html.dc-rv .has-descent #quote .quote__contact .co-k:last-child::after { animation-name: co-blink; animation-duration: 1.4s; }
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-in, .rv-in *) :is(li, .tile, .tile svg, .quote__label, .co-k),
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-in, .rv-in *) .co-k::after,
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-in, .rv-in *) .co-k::before { animation-play-state: running; }
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-now, .rv-now *) :is(li, .tile, .tile svg, .quote__label, .co-k),
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-now, .rv-now *) .co-k::after,
  html.dc-rv .has-descent #quote .quote__contact:is(.rv-now, .rv-now *) .co-k::before,
  html.dc-rv .has-descent #quote .quote__contact .co-typed.co-done .co-k,
  html.dc-rv .has-descent #quote .quote__contact .co-typed.co-done .co-k::after,
  html.dc-rv .has-descent #quote .quote__contact .co-typed.co-done .co-k::before { animation: none; }
  @keyframes co-slide { from { opacity: 0; translate: -16px 0; } }
  @keyframes co-land { from { opacity: 0; scale: 0.6; } }
  @keyframes co-halo { from { box-shadow: 0 0 0 0 rgba(143, 176, 255, 0.55); } to { box-shadow: 0 0 0 12px rgba(143, 176, 255, 0); } }
  @keyframes co-post { from { translate: -34px 0; clip-path: inset(-4px -4px -4px 24px); } to { clip-path: inset(-4px -4px -4px -10px); } }
  @keyframes co-swing { 0%, 100% { rotate: 0deg; } 16% { rotate: 12deg; } 36% { rotate: -9deg; } 56% { rotate: 5deg; } 76% { rotate: -2deg; } }
  @keyframes co-label { from { opacity: 0; translate: 0 0.4em; filter: blur(6px); } }
  @keyframes co-key { from { color: transparent; } }
  @keyframes co-caret { from, to { opacity: 1; } }
  @keyframes co-blink { 0%, 24% { opacity: 1; } 25%, 49% { opacity: 0; } 50%, 74% { opacity: 1; } 75%, 100% { opacity: 0; } }
}
@media screen and (max-width: 899px) and (prefers-reduced-motion: no-preference) {
  html.dc-rv .has-descent #quote .quote__contact > li { --co-at: 0.4s; }
  html.dc-rv .has-descent #quote .quote__contact > li:nth-child(2) { --co-at: 0.52s; }
}
/* On a computer: the card's edge brightens and its icon steps 2px right, both in 150ms; the 1px press above stays. */
@media (hover: hover) {
  .has-descent .contact:hover { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.72); }
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .has-descent .contact .tile svg { transition: translate 0.15s cubic-bezier(0.16, 1, 0.3, 1); }
  .has-descent .contact:hover .tile svg { translate: 2px 0; }
}

/* Servers: a live server console. One flat window (the server's own screen) turned toward the copy, and one toast at another depth.
   The window arrives and leaves as one object under the engine's fade on .sc-g; nothing here fades for the arrival or the departure.
   The rig is the only preserve-3d element: the window and the toast are flat, so their inner parts may animate their own opacity.
   Inside, time based and only while the scene is live (the engine pauses every animation of a scene that is not, and the animations
   below only exist under .is-live, so the screen fills again each time the scene comes back):
     0.10 to 0.70s the three cards rise in; 0.35 to 1.45s the CPU line draws; 0.50 to 1.50s the memory and disk meters fill;
     0.45 to 1.10s the four checklist rows come in, then each flips from pending to done at 0.95, 1.25, 1.55 and 1.85s;
     2.25s the backup toast rises in. The one loop: from 1.45s the CPU line drifts left, one period every 24s, seamless.
   Arrival (--mo, set by descent.js): the window starts as the card that lands on it, flat and facing the reader at the card's size (the
   engine scales the scene from that footprint), and turns into its tilt as it grows; the toast shows from --mo 0.55 (descent.css).
   Hold (--m): the window turns 7deg toward the reader across the scene's window and the toast moves 4em further out, so the layers separate.
   The base styles are the finished screen, which is what reduced motion and a scene that is not live show.
   Sharpness: the browser rasterises a layer under a perspective transform at its own CSS size and then magnifies it, so text drawn at
   the art's em and grown by --ss came out soft (blurred at 2.1x on phones). The window and the toast are each drawn --ss times larger
   (--k) and scaled back by 1/--k in their own transform, so their text is rasterised at about its size on screen.
   Positions and depths in those two are divided by --k, so the scene keeps its geometry in scene em. */
.sc--server { --ease: cubic-bezier(0.16, 1, 0.3, 1); --k: var(--ss, 1); }
/* No tabular-nums here: in this face it also widens the period, the comma and the colon, which spread vps.votreentreprise.ca, 3,1 and 3:02
   apart. No number on this screen changes, so nothing needs to align. */
.srv { transform: rotateX(calc(8deg * var(--mo, 1))) rotateY(calc((-21deg + var(--m, 0) * 7deg) * var(--mo, 1))); line-height: 1.25; color: #c7cde0; }

/* The window: near-solid navy, one inner hairline, a soft offset shadow, a title bar with the host name and its status */
.srv__win { position: absolute; left: calc(-26em / var(--k)); top: calc(-19.5em / var(--k)); width: 52em; font-size: calc(var(--k) * 1em); transform-origin: 0 0; transform: scale(calc(1 / var(--k))) translateZ(0); border-radius: 1.1em; overflow: hidden; background: #10183a; box-shadow: inset 0 0 0 0.09em rgba(196, 210, 255, 0.14), 1.6em 2.4em 4em rgba(2, 5, 20, 0.55); }
.sc--server .srv__bar { display: flex; align-items: center; gap: 1em; height: 2.6em; padding: 0 1.1em; background: #0d1431; box-shadow: inset 0 -0.09em 0 rgba(196, 210, 255, 0.1); }
.sc--server .srv__dots { display: flex; gap: 0.45em; }
.srv__dots b { width: 0.9em; height: 0.9em; border-radius: 50%; background: #ff5f57; }
.srv__dots b + b { background: #febc2e; }
.srv__dots b + b + b { background: #28c840; }
.srv__host { font-size: 1.25em; font-weight: 600; color: #ffffff; white-space: nowrap; }
.sc--server .srv__pill { display: flex; align-items: center; gap: 0.45em; margin-left: auto; padding: 0.3em 0.8em; border-radius: 99em; font-size: 0.9em; font-weight: 600; white-space: nowrap; color: #34d399; background: rgba(52, 211, 153, 0.12); }
.srv__led { width: 0.6em; height: 0.6em; border-radius: 50%; background: #34d399; }

.sc--server .srv__body { display: grid; grid-template-columns: minmax(0, 1fr) 15em; gap: 1.1em; padding: 1.5em; }
.sc--server .srv__cpu { display: flex; flex-direction: column; }
.srv__card { padding: 1.1em 1.2em; border-radius: 0.8em; background: rgba(196, 210, 255, 0.03); box-shadow: inset 0 0 0 0.09em rgba(196, 210, 255, 0.09); }
.srv__label { font-size: 0.9em; font-weight: 500; color: #8b93b8; white-space: nowrap; }

/* CPU: the line is two identical periods of 320 units; it draws its first period, then the group drifts one period and starts over */
.srv__plot { flex: 1 0 8.6em; margin-top: 0.7em; background: linear-gradient(rgba(196, 210, 255, 0.08) 0.09em, transparent 0.09em) 0 0 / 100% 33.4%; box-shadow: inset 0 -0.09em 0 rgba(196, 210, 255, 0.14); }
.srv__chart { display: block; width: 100%; height: 100%; overflow: hidden; }
.srv__line { fill: none; stroke: #4f78f6; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 2 2; stroke-dashoffset: 0; }
.srv__area { fill: rgba(36, 71, 184, 0.3); }

/* Memory and disk */
.sc--server .srv__stats { display: grid; gap: 1.1em; align-content: start; }
.srv__val { margin-top: 0.3em; font-size: 1.7em; font-weight: 600; color: #ffffff; white-space: nowrap; letter-spacing: -0.01em; }
.srv__meter { height: 0.45em; margin-top: 0.8em; border-radius: 0.3em; background: rgba(196, 210, 255, 0.1); overflow: hidden; }
.srv__meter i { width: calc(var(--v, 0.5) * 100%); height: 100%; border-radius: 0.3em; background: #4f78f6; transform-origin: left; }

/* The maintenance checklist: a status ring, the task, its status word */
.srv__checks { grid-column: 1 / -1; padding-bottom: 0.5em; }
.sc--server .srv__row { display: grid; grid-template-columns: 1.7em minmax(0, 1fr) auto; align-items: center; column-gap: 0.9em; min-height: 2.9em; padding: 0.35em 0; box-shadow: inset 0 0.09em 0 rgba(196, 210, 255, 0.08); }
.srv__checks .srv__label + .srv__row { margin-top: 0.55em; }
.sc--server .srv__st { display: grid; place-items: center; width: 1.7em; height: 1.7em; border-radius: 50%; border: 0.13em solid #34d399; background: #34d399; }
.srv__st svg { width: 1.1em; height: 1.1em; fill: none; stroke: #0e1533; stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.srv__txt { font-size: 1.1em; color: #c7cde0; }
.sc--server .srv__flag { display: grid; justify-items: end; font-size: 1em; font-weight: 600; white-space: nowrap; }
.srv__flag > span { grid-area: 1 / 1; }
.srv__wait { color: #f5b041; opacity: 0; }
.srv__ok { color: #34d399; }

/* The toast: a backup that finished, in front of the window's lower left corner */
.srv__toast { position: absolute; left: calc(-31em / var(--k)); top: calc(15.6em / var(--k)); font-size: calc(var(--k) * 1em); transform-origin: 0 0; display: grid; grid-template-columns: 2.7em auto; column-gap: 0.9em; align-items: center; padding: 1em 1.5em 1em 1em; border-radius: 0.9em; white-space: nowrap; background: #182252; box-shadow: inset 0 0 0 0.09em rgba(196, 210, 255, 0.18), 1.2em 1.8em 3em rgba(2, 5, 20, 0.6); transform: translateZ(calc((6em + var(--m, 0) * 4em) / var(--k))) scale(calc(1 / var(--k))); }
.sc--server .srv__ti { grid-row: span 2; display: grid; place-items: center; width: 2.7em; height: 2.7em; border-radius: 0.7em; color: #34d399; background: rgba(52, 211, 153, 0.14); }
.srv__ti svg { width: 1.45em; height: 1.45em; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.srv__tt { font-size: 1.1em; font-weight: 600; color: #ffffff; }
.srv__tm { margin-top: 0.15em; font-size: 0.9em; color: #8b93b8; }

/* The sequence, only while the scene is live */
.sc--server.is-live .srv__card { animation: srv-rise 0.6s var(--ease) 0.1s both; }
.sc--server.is-live .srv__stat { animation-delay: calc(0.2s + var(--i, 0) * 0.1s); }
.sc--server.is-live .srv__checks { animation-delay: 0.3s; }
.sc--server.is-live .srv__line { animation: srv-draw 1.1s var(--ease) 0.35s backwards; }
.sc--server.is-live .srv__area { animation: srv-show 0.8s ease 1s backwards; }
.sc--server.is-live .srv__drift { animation: srv-drift 24s linear 1.45s infinite; }
.sc--server.is-live .srv__meter i { animation: srv-fill 1s var(--ease) calc(0.5s + var(--i, 0) * 0.12s) both; }
.sc--server.is-live .srv__row { --at: calc(0.95s + var(--i, 0) * 0.3s); animation: srv-rise 0.5s var(--ease) calc(0.45s + var(--i, 0) * 0.12s) both; }
.sc--server.is-live .srv__st { animation: srv-ring 0.4s var(--ease) var(--at) both; }
.sc--server.is-live .srv__st svg { animation: srv-pop 0.45s var(--ease) var(--at) both; }
.sc--server.is-live .srv__txt { animation: srv-dim 0.4s ease var(--at) both; }
.sc--server.is-live .srv__wait { animation: srv-leave 0.3s ease var(--at) both; }
.sc--server.is-live .srv__ok { animation: srv-rise 0.45s var(--ease) calc(var(--at) + 0.1s) both; }
.sc--server.is-live .srv__toast { animation: srv-toast 0.6s var(--ease) 2.25s both; }
@keyframes srv-rise { from { opacity: 0; translate: 0 0.9em; } }
@keyframes srv-toast { from { opacity: 0; translate: 0 calc(0.9em / var(--k)); } }
@keyframes srv-show { from { opacity: 0; } }
@keyframes srv-draw { from { stroke-dashoffset: 2; } to { stroke-dashoffset: 1; } }
@keyframes srv-drift { to { transform: translateX(-320px); } }
@keyframes srv-fill { from { transform: scaleX(0); } }
@keyframes srv-ring { from { background: transparent; border-color: #f5b041; } }
@keyframes srv-pop { from { opacity: 0; scale: 0.4; } }
@keyframes srv-dim { from { color: #8b93b8; } }
@keyframes srv-leave { from { opacity: 1; } to { opacity: 0; translate: 0 -0.6em; } }

@media (prefers-reduced-motion: reduce) {
  .sc--server * { animation: none !important; }
}

/* Phones and portrait tablets: one column, turned less so the near edge stays inside the gutter */
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .srv { transform: rotateX(calc(6deg * var(--mo, 1))) rotateY(calc((-10deg + var(--m, 0) * 4deg) * var(--mo, 1))); }
  .srv__win { left: calc(-16em / var(--k)); top: calc(-22em / var(--k)); width: 32em; }
  .sc--server .srv__body { grid-template-columns: minmax(0, 1fr); gap: 0.9em; padding: 1.2em; }
  .sc--server .srv__stats { grid-template-columns: 1fr 1fr; gap: 0.9em; }
  .srv__val { font-size: 1.45em; }
  .srv__plot { flex-basis: 7em; }
  .srv__toast { left: calc(-15em / var(--k)); top: calc(17.4em / var(--k)); transform: translateZ(calc((4em + var(--m, 0) * 3em) / var(--k))) scale(calc(1 / var(--k))); }
}

/* Domains: a live domain panel. One window turned toward the copy, a notice in front of it at another depth.
   The engine moves the rig (--x departure), grows the scene from the card that lands on its window (--mo: flat and at the card's size at 0,
   this tilt at 1) and fades the scene as one object on its flat wrapper; this file only tilts the rig (scaled by --mo),
   lets it turn a little through the hold (--m) and fills the screen on a clock while the scene is live (.sc.is-live). At a normal scroll pace a
   scene is on screen about 2s, so the whole story is told by about 1.5s:
     0.1s   the name types in, one character every 28ms, caret in front of it, the field shows its focus ring (done by about 0.6s in French)
     0.42s  the three rows come in, 80ms apart, each with its status in progress
     0.66s  the rows turn green one after another, 170ms apart (the old word goes, then the new one comes), the last by about 1.2s,
            and the bar under the heading fills in three steps
     1.2s   the domain's pill turns to its active status
     1.3s   the notice drops in, in front of the window, in place by about 1.5s
     2.4s   the one loop: every 6s a light passes down the rows, as if the panel checked them again
   Every reveal starts from the class, so the screen fills again each time the scene comes back on. The base styles are the finished screen,
   so reduced motion (and a scene that is not live) shows the final state with nothing moving.
   The window is flat: rows, statuses and characters fade on their own. The rig is the only 3D group and carries no opacity.
   Sizes: the window and the notice are set at 1.75 scene em; inside, title 1.25em, row text 1.1em, labels 0.9em.
   Sharpness (as in server.css): the browser rasterises a layer under a perspective transform at its own CSS size and then magnifies it, so text
   drawn at the art's em and grown by --ss came out soft on phones (about 1.6x). The window and the notice are each drawn --ss times larger (--dk;
   not --k, which descent.js sets on each typed character) and scaled back by 1/--dk in their own transform, so their text is rasterised at about
   its size on screen. Their positions and depths, the notice's drop and the 1px hairlines inside them are divided or multiplied by --dk, so the
   scene keeps its geometry in scene em. */
.sc--domain { --dk: var(--ss, 1); }
.dom {
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  transform: translateZ(calc(var(--x, 0) * var(--back, 0) * -1em)) rotateX(calc((9deg - var(--m, 0) * 2deg) * var(--mo, 1))) rotateY(calc((22deg - var(--m, 0) * 8deg) * var(--mo, 1)));
}

/* Window: near-solid navy, an inner hairline drawn over the title bar, a soft offset shadow */
.dom .dom__win {
  position: absolute; left: calc(-14.5em / var(--dk)); top: calc(-13.9em / var(--dk)); width: 29em; box-sizing: border-box; overflow: hidden;
  font-size: calc(var(--dk) * 1.75em); transform-origin: 0 0; transform: scale(calc(1 / var(--dk))) translateZ(0); font-family: var(--sc-font-text, "Schibsted Grotesk", system-ui, sans-serif); line-height: 1.25; color: #c7cde0;
  border-radius: 1.1em; background: #111a3d;
  box-shadow: 0 1.4em 3em -0.6em rgba(2, 5, 20, 0.8), 0 0.3em 0.9em rgba(2, 5, 20, 0.45);
}
.dom .dom__win::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 calc(var(--dk) * 1px) rgba(196, 210, 255, 0.14); pointer-events: none; }
.dom .dom__top {
  display: flex; align-items: center; gap: 1em; height: 2.6em; padding: 0 0.85em 0 1em;
  background: #0e1533; border-bottom: calc(var(--dk) * 1px) solid rgba(196, 210, 255, 0.08);
}
.dom .dom__dots {
  flex: none; width: 3.7em; height: 0.9em;
  background:
    radial-gradient(circle closest-side, #ff5f57 92%, transparent) 0 0 / 0.9em 0.9em no-repeat,
    radial-gradient(circle closest-side, #febc2e 92%, transparent) 1.4em 0 / 0.9em 0.9em no-repeat,
    radial-gradient(circle closest-side, #28c840 92%, transparent) 2.8em 0 / 0.9em 0.9em no-repeat;
}
.dom .dom__title { flex: 1; min-width: 0; font-size: 1.25em; font-weight: 600; color: #ffffff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Status pills: both states sit in one cell, right aligned, so each hugs its own word and the swap is a crossfade */
.dom .dom__flip { flex: none; display: grid; justify-items: end; align-items: center; }
.dom .dom__flip > .dom__st { grid-area: 1 / 1; }
.dom .dom__st {
  display: flex; align-items: center; gap: 0.5em; height: 2em; padding: 0 0.8em 0 0.65em; box-sizing: border-box;
  font-size: 0.9em; font-weight: 600; white-space: nowrap; border-radius: 1em;
}
.dom .dom__st--wait { color: #f5b041; background: rgba(245, 176, 65, 0.1); box-shadow: inset 0 0 0 calc(var(--dk) * 1px) rgba(245, 176, 65, 0.24); opacity: 0; }
.dom .dom__st--ok { color: #34d399; background: rgba(52, 211, 153, 0.1); box-shadow: inset 0 0 0 calc(var(--dk) * 1px) rgba(52, 211, 153, 0.26); }
.dom .dom__spin { flex: none; width: 0.85em; height: 0.85em; box-sizing: border-box; border-radius: 50%; border: 0.15em solid rgba(245, 176, 65, 0.28); border-top-color: #f5b041; }
.dom .dom__led { flex: none; width: 0.55em; height: 0.55em; border-radius: 50%; background: #34d399; box-shadow: 0 0 0 0.22em rgba(52, 211, 153, 0.2); }
.dom .dom__st > svg { flex: none; width: 1em; height: 1em; fill: none; stroke: #34d399; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }

/* Body: the name field, the heading with its progress bar, three rows */
.dom .dom__body { padding: 1.45em 1.6em 1.1em; }
.dom .dom__label { font-size: 0.9em; font-weight: 500; color: #8b93b8; margin-bottom: 0.6em; }
.dom .dom__bar {
  position: relative; display: flex; align-items: center; gap: 0.7em; height: 3.4em; padding: 0 1em; box-sizing: border-box;
  border-radius: 0.75em; background: #0a1130; box-shadow: inset 0 0 0 calc(var(--dk) * 1px) rgba(196, 210, 255, 0.14);
}
.dom .dom__bar::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 calc(var(--dk) * 1px) #4f78f6, 0 0 0 0.28em rgba(79, 120, 246, 0.22); opacity: 0; pointer-events: none; }
.dom .dom__bar > svg { flex: none; width: 1.35em; height: 1.35em; fill: none; stroke: #8fb0ff; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.dom .dom__typed { position: relative; min-width: 0; font-size: 1.45em; font-weight: 600; letter-spacing: -0.005em; color: #ffffff; white-space: nowrap; }
/* The name is one span per character (data-chars, split by descent.js with --k its index). The caret stands after the character just typed,
   before the first one until typing starts, and blinks twice after the last one before it goes. A character waits as transparent ink, not
   at opacity 0, so the carets it carries can show before it does */
.dom .dom__typed > .ch { position: relative; display: inline-block; }
.dom .dom__typed > .ch::after, .dom .dom__typed > .ch:first-child::before {
  content: ""; position: absolute; left: 100%; top: 0.1em; bottom: 0.04em; margin-left: 0.04em; border-right: 0.08em solid #8fb0ff; opacity: 0;
}
.dom .dom__typed > .ch:first-child::before { left: auto; right: 100%; margin: 0 0.04em 0 0; }

.dom .dom__head { display: flex; align-items: center; justify-content: space-between; gap: 1em; margin: 1.5em 0 0.7em; font-size: 0.9em; font-weight: 500; color: #8b93b8; }
.dom .dom__head > span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dom .dom__prog { position: relative; flex: none; width: 7em; height: 0.4em; border-radius: 1em; overflow: hidden; background: rgba(196, 210, 255, 0.1); }
.dom .dom__prog::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: #34d399; transform-origin: 0 50%; }

/* A row reads the same whether its label is a record type (MX) or a feature (DNSSEC): icon, label, status. No value column */
.dom .dom__rec {
  display: flex; align-items: center; gap: 0.85em; height: 3.4em; margin: 0 -1.6em; padding: 0 1.6em;
  border-top: calc(var(--dk) * 1px) solid rgba(196, 210, 255, 0.08);
}
.dom .dom__rec > svg {
  flex: none; width: 2.3em; height: 2.3em; padding: 0.55em; box-sizing: border-box; border-radius: 0.6em; background: rgba(79, 120, 246, 0.16);
  fill: none; stroke: #c4d2ff; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round;
}
.dom .dom__name { flex: 1; min-width: 0; font-size: 1.1em; font-weight: 700; letter-spacing: 0.03em; color: #ffffff; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The notice: a satellite 6 of its em in front of the window, over its lower left corner; it drifts a little against the window through the hold */
.dom .dom__toast {
  position: absolute; left: calc(-16.2em / var(--dk)); top: calc(9.2em / var(--dk)); font-size: calc(var(--dk) * 1.75em); transform-origin: 0 0; font-family: var(--sc-font-text, "Schibsted Grotesk", system-ui, sans-serif); line-height: 1.25;
  display: flex; align-items: center; gap: 0.75em; padding: 0.75em 1.25em 0.75em 0.8em; border-radius: 0.9em;
  background: #18224d; box-shadow: inset 0 0 0 calc(var(--dk) * 1px) rgba(196, 210, 255, 0.18), 0 1.2em 2.4em -0.5em rgba(2, 5, 20, 0.8), 0 0.25em 0.7em rgba(2, 5, 20, 0.4);
  transform: translateZ(calc(6em / var(--dk))) translateY(calc((0.5 - var(--m, 0)) * 1.4em / var(--dk))) scale(calc(1 / var(--dk)));
}
.dom .dom__tick { flex: none; display: flex; align-items: center; justify-content: center; width: 2.1em; height: 2.1em; border-radius: 50%; background: rgba(52, 211, 153, 0.16); }
.dom .dom__tick > svg { width: 1.1em; height: 1.1em; fill: none; stroke: #34d399; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
.dom .dom__tt > span { white-space: nowrap; font-weight: 600; color: #ffffff; }
.dom .dom__tt > .dom__sub { margin-top: 0.15em; font-size: 0.9em; font-weight: 500; color: #8b93b8; }

/* The clock. Nothing here runs outside a live scene, and nothing runs at all with reduced motion */
@media (prefers-reduced-motion: no-preference) {
  .sc.is-live .dom__typed > .ch { animation: dom-key 1ms linear both; animation-delay: calc(0.1s + var(--k, 0) * 0.028s); }
  .sc.is-live .dom__typed > .ch:first-child::before { animation: dom-show 0.1s linear; }
  .sc.is-live .dom__typed > .ch::after { animation: dom-show 0.028s linear; animation-delay: calc(0.1s + var(--k, 0) * 0.028s); }
  .sc.is-live .dom__typed > .ch:last-child::after { animation: dom-blink 1.6s linear; animation-delay: calc(0.1s + var(--k, 0) * 0.028s); }
  .sc.is-live .dom__bar::after { animation: dom-focus 1.1s ease-out; }
  .sc.is-live .dom__rec {
    animation: dom-rise 0.45s var(--ease) both, dom-check 6s ease-in-out infinite;
    animation-delay: calc(0.42s + var(--i, 0) * 0.08s), calc(2.4s + var(--i, 0) * 0.15s);
  }
  .sc.is-live .dom__rec .dom__st--wait { animation: dom-out 0.12s ease-in both; animation-delay: calc(0.66s + var(--i, 0) * 0.17s); }
  .sc.is-live .dom__rec .dom__st--ok { animation: dom-pop 0.35s var(--ease) both; animation-delay: calc(0.74s + var(--i, 0) * 0.17s); }
  .sc.is-live .dom__flip--pill > .dom__st--wait { animation: dom-out 0.12s ease-in both; animation-delay: 1.2s; }
  .sc.is-live .dom__flip--pill > .dom__st--ok { animation: dom-pop 0.35s var(--ease) both; animation-delay: 1.28s; }
  .sc.is-live .dom__spin { animation: dom-spin 0.8s linear 2; }
  .sc.is-live .dom__prog::before { animation: dom-fill 1.25s var(--ease) both; }
  .sc.is-live .dom__toast { animation: dom-drop 0.45s var(--ease) both; animation-delay: 1.3s; }
}
@keyframes dom-key { from { color: transparent; } to { color: #ffffff; } }
@keyframes dom-show { from, to { opacity: 1; } }
@keyframes dom-blink { 0%, 24% { opacity: 1; } 25%, 49% { opacity: 0; } 50%, 74% { opacity: 1; } 75%, 100% { opacity: 0; } }
@keyframes dom-focus { 0%, 70% { opacity: 1; } 100% { opacity: 0; } }
@keyframes dom-rise { from { opacity: 0; translate: 0 0.55em; } to { opacity: 1; translate: 0 0; } }
@keyframes dom-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes dom-pop { from { opacity: 0; scale: 0.9; } to { opacity: 1; scale: 1; } }
@keyframes dom-spin { to { rotate: 1turn; } }
/* The bar takes a third with each row turning green: rows at 0.66s, 0.83s and 1s of a 1.25s run */
@keyframes dom-fill {
  0%, 52.8% { transform: scaleX(0); background-color: #4f78f6; }
  66.4% { transform: scaleX(0.3333); }
  80% { transform: scaleX(0.6667); }
  100% { transform: scaleX(1); background-color: #34d399; }
}
@keyframes dom-drop { from { opacity: 0; translate: 0 calc(-0.6em / var(--dk)); } to { opacity: 1; translate: 0 0; } }
@keyframes dom-check { 0%, 100% { background-color: rgba(79, 120, 246, 0); } 5% { background-color: rgba(79, 120, 246, 0.1); } 14% { background-color: rgba(79, 120, 246, 0); } }

/* Narrow screens: the window is centred, so it turns less, and it is 24 of its em wide so the text stays readable at phone width.
   The rig gives back part of the engine's forward move on departure (--back), because the window fills most of the width */
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .dom { --back: 16; transform: translateZ(calc(var(--x, 0) * var(--back, 0) * -1em)) rotateX(calc((9deg - var(--m, 0) * 2deg) * var(--mo, 1))) rotateY(calc((10deg - var(--m, 0) * 6deg) * var(--mo, 1))); }
  .dom .dom__win { left: calc(-12em / var(--dk)); width: 24em; }
  .dom .dom__toast { left: calc(-11.6em / var(--dk)); }
}

/* Exchange: a live mailbox. One window (title bar with the mailbox address and a Synced pill, a sidebar of four app tiles, the inbox) with an
   invitation card floating in front of it at its own depth. Every word comes from content.json (scene.exchange), the tiles from icons.exchange.tiles.
   Arrival (--mo, set by descent.js): the window starts as the card that lands on it, flat, at the card's size and turned 6deg like it
   (scenes.json rz; the engine scales and turns the scene from that footprint), and settles into this tilt as it grows; the invitation shows
   from --mo 0.55 (descent.css). The engine fades the scene as one object only while the card is under it.
   Hold (--m): the window turns 7deg toward the reader while the card in front comes 4em closer, so the two layers part as the camera moves.
   Departure (--x): nothing here, the engine carries it.
   Time, only while the scene is live (the animations are declared under .is-live, so each visit plays from the start):
     0 to 0.8s the title bar and the tiles come in, 0.1 to 0.92s the five inbox rows drop in one by one, 0.4s the invitation card rises,
     0.8s a cursor glides to Accept, presses it at 1.42 to 1.52s, the button turns to Accepted by 1.8s and the cursor slides off it. Those play
     once, so a visitor who stays about two seconds sees the whole story.
     The one loop is the inbox (--mb-k, a clock in seconds, 15.6s around): a new mail arrives at the top at 3.6s, 7.4s and 11.2s, pushing the
     list down one row, with a short tint; the unread badge counts the unread rows on screen. At 15s the list fades and fills again.
   Reduced motion: no animation, the clock rests at 3s (the inbox filled, nothing arriving) and the invitation shows Accepted.
   Every element in the window and the card is flat; only .mb__tilt is preserve-3d, and it carries no opacity and no filter.
   Sharpness (as in server.css): the browser rasterises a layer under a perspective transform at its own CSS size and then magnifies it, so text
   drawn at the art's em and grown by --ss came out soft on phones (about 1.9x). The window and the card are each drawn --ss times larger (--k)
   and scaled back by 1/--k in their own transform, so their text is rasterised at about its size on screen. Their positions and depths, the
   card's rise and the 1px hairlines inside them are divided or multiplied by --k, so the scene keeps its geometry in scene em.
   That only works while the window is a compositing layer of its own. Nothing kept it one: WebKit composited it only while the entrance
   animations of its title bar and tiles ran (a transformed element with composited children), and when they ended it was painted into the
   tilted group's layer at scene size and magnified by --ss again, which is the soft inbox seen on an iPhone from about 1s on. translateZ(0)
   in its transform makes it a layer for good (the card already is one, through its translateZ). Measured in WebKit's composited output at
   402x874 dpr 3: the settled inbox went from 0.42 to 0.63-0.66 edge steepness, the card's level (0.62). */
@property --mb-k { syntax: '<number>'; inherits: true; initial-value: 3; }
@property --mb-n { syntax: '<integer>'; inherits: false; initial-value: 0; }
.sc--exchange { --k: var(--ss, 1); }

.sc--exchange .mb__tilt {
  position: absolute; left: 0; top: 0; transform-style: preserve-3d;
  transform: rotateY(calc((-21deg + var(--m, 0) * 7deg) * var(--mo, 1))) rotateX(calc(8deg * var(--mo, 1)));
}

/* The window */
.sc--exchange .mb__win {
  --rh: 5.4em;
  position: absolute; left: calc(-28em / var(--k)); top: calc(-20em / var(--k)); width: 56em; height: 38em; overflow: hidden;
  font-size: calc(var(--k) * 1em); transform-origin: 0 0; transform: scale(calc(1 / var(--k))) translateZ(0);
  display: grid; grid-template-columns: 6em minmax(0, 1fr); grid-template-rows: 2.6em minmax(0, 1fr);
  border-radius: 1.1em; background: #10183a; color: #ffffff; font-weight: 400; line-height: 1.2; text-align: left;
  box-shadow: 1.4em 2.2em 4.4em rgba(3, 7, 26, 0.62);
}
.sc--exchange .mb__win::after, .sc--exchange .mb__inv::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 calc(var(--k) * 1px) rgba(196, 210, 255, 0.14); pointer-events: none; }
.sc--exchange .mb__bar { grid-column: 1 / -1; display: flex; align-items: center; gap: 1em; padding: 0 0.9em 0 1.1em; background: #0e1535; border-bottom: calc(var(--k) * 1px) solid rgba(196, 210, 255, 0.1); }
.sc--exchange .mb__dots { display: flex; gap: 0.5em; }
.sc--exchange .mb__dots i { width: 0.9em; height: 0.9em; border-radius: 50%; background: #ff5f57; }
.sc--exchange .mb__dots i + i { background: #febc2e; }
.sc--exchange .mb__dots i + i + i { background: #28c840; }
.sc--exchange .mb__title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: center; font-size: 1.1em; font-weight: 600; color: #c7cde0; }
.sc--exchange .mb__pill { display: flex; align-items: center; gap: 0.45em; height: 1.8em; padding: 0 0.75em; border-radius: 0.9em; background: rgba(52, 211, 153, 0.12); color: #34d399; font-size: 0.9em; font-weight: 600; white-space: nowrap; }
.sc--exchange .mb__pill i { width: 0.55em; height: 0.55em; border-radius: 50%; background: #34d399; }

/* Sidebar: four app tiles, the first one open */
.sc--exchange .mb__side { display: flex; flex-direction: column; align-items: center; gap: 0.9em; padding-top: 1.4em; background: #0c1330; border-right: calc(var(--k) * 1px) solid rgba(196, 210, 255, 0.08); }
.sc--exchange .mb__tile { display: flex; align-items: center; justify-content: center; width: 3.6em; height: 3.6em; border-radius: 0.8em; }
.sc--exchange .mb__tile svg { width: 1.9em; height: 1.9em; fill: none; stroke: #ffffff; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; opacity: 0.55; }
.sc--exchange .mb__tile--on { background: #2447b8; }
.sc--exchange .mb__tile--on svg { opacity: 1; }

/* The inbox */
.sc--exchange .mb__main { display: flex; flex-direction: column; min-width: 0; padding: 1.5em 1.6em 0 1.5em; }
.sc--exchange .mb__head { display: flex; align-items: center; gap: 0.7em; height: 2.4em; margin-bottom: 0.7em; }
.sc--exchange .mb__folder { font-size: 1.25em; font-weight: 700; white-space: nowrap; }
.sc--exchange .mb__badge {
  --mb-n: calc(clamp(0, (var(--mb-k) - 0.3) * 20, 1) + clamp(0, (var(--mb-k) - 0.38) * 20, 1) + clamp(0, (var(--mb-k) - 3.9) * 20, 1) + clamp(0, (var(--mb-k) - 7.7) * 20, 1) + clamp(0, (var(--mb-k) - 11.5) * 20, 1));
  display: flex; align-items: center; justify-content: center; min-width: 1.9em; height: 1.55em; padding: 0 0.5em; border-radius: 0.8em;
  background: #2447b8; font-size: 0.9em; font-weight: 700; font-variant-numeric: tabular-nums;
  counter-reset: mb var(--mb-n); opacity: calc(min(1, var(--mb-n)) * (1 - clamp(0, (var(--mb-k) - 15) / 0.4, 1)));
}
.sc--exchange .mb__badge::after { content: counter(mb); }
.sc--exchange .mb__filter { margin-left: auto; }
.sc--exchange .mb__filter svg { display: block; width: 1.7em; height: 1.7em; fill: none; stroke: #8b93b8; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }

/* The list clips at its lower edge and fades out over its last quarter, so a row pushed down leaves softly. The track carries every row:
   the five of the filled inbox at positions 0 to 4 and the three arrivals stacked above it at -1 to -3. Each arrival moves the track
   down one row (0.7s, eased out), and each row fades in and drops 1em into place from its own time --s on the clock. */
.sc--exchange .mb__list {
  position: relative; flex: 1; overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent 98%); mask-image: linear-gradient(180deg, #000 70%, transparent 98%);
  opacity: calc(1 - clamp(0, (var(--mb-k) - 15) / 0.4, 1));
}
.sc--exchange .mb__track {
  --a1: clamp(0, (var(--mb-k) - 3.6) / 0.7, 1);
  --a2: clamp(0, (var(--mb-k) - 7.4) / 0.7, 1);
  --a3: clamp(0, (var(--mb-k) - 11.2) / 0.7, 1);
  position: absolute; inset: 0;
  transform: translateY(calc((3 - (1 - var(--a1)) * (1 - var(--a1)) * (1 - var(--a1)) - (1 - var(--a2)) * (1 - var(--a2)) * (1 - var(--a2)) - (1 - var(--a3)) * (1 - var(--a3)) * (1 - var(--a3)) - 3 * clamp(0, (var(--mb-k) - 15.5) * 20, 1)) * var(--rh)));
}
.sc--exchange .mb__row {
  --p: clamp(0, (var(--mb-k) - var(--s)) / 0.5, 1);
  position: absolute; left: 0; right: 0; top: calc(var(--r) * var(--rh)); height: var(--rh); box-sizing: border-box;
  display: grid; grid-template-columns: 1.1em 2.6em minmax(0, 1fr) auto; grid-template-rows: auto auto; column-gap: 1em; row-gap: 0.3em; align-content: center;
  padding: 0 0.7em 0 0.2em; border-bottom: calc(var(--k) * 1px) solid rgba(196, 210, 255, 0.08);
  opacity: var(--p); translate: 0 calc((1 - var(--p)) * (1 - var(--p)) * (1 - var(--p)) * -1em);
}
.sc--exchange .mb__row > * { position: relative; }
/* A mail that has just arrived is tinted for a moment */
.sc--exchange .mb__row--in::before { content: ""; position: absolute; inset: 0.25em 0; border-radius: 0.6em; background: rgba(79, 120, 246, 0.16); opacity: calc(1 - clamp(0, (var(--mb-k) - var(--s) - 1.2) / 1.6, 1)); }
.sc--exchange .mb__dot { grid-row: 1 / 3; align-self: center; justify-self: center; width: 0.6em; height: 0.6em; border-radius: 50%; }
.sc--exchange .mb__row--new .mb__dot { background: #4f78f6; }
/* The avatar holds the sender's name and shows its first letter only */
.sc--exchange .mb__av {
  grid-row: 1 / 3; grid-column: 2; align-self: center; width: 2.6em; height: 2.6em; border-radius: 50%; overflow: hidden; white-space: nowrap;
  font-size: 1.15em; line-height: 2.6em; font-weight: 700; text-indent: 0.86em; color: transparent; background: #2447b8;
}
.sc--exchange .mb__av::first-letter { color: #ffffff; }
.sc--exchange .mb__row:nth-child(5n + 2) .mb__av { background: #4f78f6; }
.sc--exchange .mb__row:nth-child(5n + 3) .mb__av { background: #3d4a8f; }
.sc--exchange .mb__row:nth-child(5n + 4) .mb__av { background: #1f5f8b; }
.sc--exchange .mb__row:nth-child(5n) .mb__av { background: #5a6db3; }
.sc--exchange .mb__from, .sc--exchange .mb__subj { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sc--exchange .mb__from { grid-column: 3; font-size: 1.1em; font-weight: 500; color: #c7cde0; }
.sc--exchange .mb__time { grid-column: 4; grid-row: 1; align-self: center; font-size: 0.9em; color: #8b93b8; white-space: nowrap; }
.sc--exchange .mb__subj { grid-column: 3 / 5; grid-row: 2; font-size: 1em; color: #8b93b8; }
.sc--exchange .mb__row--new .mb__from { font-weight: 700; color: #ffffff; }
.sc--exchange .mb__row--new .mb__time { color: #c7cde0; }
.sc--exchange .mb__row--new .mb__subj { color: #c7cde0; }

/* The invitation card, in front of the window */
.sc--exchange .mb__inv {
  position: absolute; left: calc(-33em / var(--k)); top: calc(13.5em / var(--k)); width: 25em; box-sizing: border-box; padding: 1.5em 1.6em 1.4em;
  font-size: calc(var(--k) * 1em); transform-origin: 0 0;
  border-radius: 1.1em; background: #141d44; color: #ffffff; font-weight: 400; line-height: 1.2; text-align: left;
  box-shadow: 1em 1.6em 3.2em rgba(3, 7, 26, 0.6);
  transform: translateZ(calc((5em + var(--m, 0) * 4em) / var(--k))) scale(calc(1 / var(--k)));
}
.sc--exchange .mb__inv-hd { display: flex; align-items: center; gap: 0.55em; font-size: 0.9em; font-weight: 600; color: #8b93b8; }
.sc--exchange .mb__inv-hd svg { width: 1.6em; height: 1.6em; fill: none; stroke: #4f78f6; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
.sc--exchange .mb__inv-title { margin-top: 0.7em; font-size: 1.25em; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc--exchange .mb__inv-when { margin-top: 0.35em; font-size: 1em; color: #c7cde0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sc--exchange .mb__inv-foot { display: flex; align-items: center; justify-content: space-between; gap: 1em; margin-top: 1.2em; }
.sc--exchange .mb__ppl { display: flex; padding-left: 0.5em; }
.sc--exchange .mb__ppl i { width: 2.2em; height: 2.2em; margin-left: -0.5em; border-radius: 50%; border: 0.2em solid #141d44; background: #4f78f6; }
.sc--exchange .mb__ppl i + i { background: #5a6db3; }
.sc--exchange .mb__ppl i + i + i { background: #1f5f8b; }
.sc--exchange .mb__btn { display: grid; height: 2.6em; padding: 0 1.1em; border-radius: 0.6em; background: rgba(52, 211, 153, 0.14); font-size: 1em; font-weight: 600; }
.sc--exchange .mb__btn > span { grid-area: 1 / 1; display: flex; align-items: center; justify-content: center; gap: 0.4em; white-space: nowrap; }
.sc--exchange .mb__btn-a { color: #ffffff; opacity: 0; }
.sc--exchange .mb__btn-b { color: #34d399; }
.sc--exchange .mb__btn-b span { display: inline; }
.sc--exchange .mb__btn-b svg { width: 1.2em; height: 1.2em; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }
/* The scripted cursor: its tip rests on the button; the shadow sits on the wrapper because the arrow itself is clipped */
.sc--exchange .mb__cur { position: absolute; right: 4.4em; bottom: 0.9em; width: 1.5em; height: 2.1em; opacity: 0; filter: drop-shadow(0 0.15em 0.3em rgba(3, 7, 26, 0.7)); }
.sc--exchange .mb__cur i { width: 100%; height: 100%; background: #ffffff; clip-path: polygon(0 0, 0 84%, 24% 66%, 42% 100%, 58% 93%, 41% 60%, 80% 60%); }

/* Time, only while the scene is live */
.sc--exchange.is-live .mb__win { animation: mb-clock 15.6s linear infinite; }
.sc--exchange.is-live .mb__dots, .sc--exchange.is-live .mb__title, .sc--exchange.is-live .mb__pill, .sc--exchange.is-live .mb__tile, .sc--exchange.is-live .mb__folder, .sc--exchange.is-live .mb__filter {
  animation: mb-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.sc--exchange.is-live .mb__title { animation-delay: 0.08s; }
.sc--exchange.is-live .mb__pill { animation-delay: 0.16s; }
.sc--exchange.is-live .mb__tile { animation-delay: calc(0.1s + var(--i, 0) * 0.06s); }
.sc--exchange.is-live .mb__folder { animation-delay: 0.16s; }
.sc--exchange.is-live .mb__filter { animation-delay: 0.22s; }
.sc--exchange.is-live .mb__inv { animation: mb-rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.4s both; }
/* The tap, on one 2.5s timeline: the cursor shows at 0.8s, glides 0.92 to 1.42s, presses to 1.52s, lets go by 1.66s, slides off by 2.05s
   and fades by 2.5s; the button darkens 1.44 to 1.52s, Accept leaves 1.52 to 1.64s, Accepted lands 1.56 to 1.8s. */
.sc--exchange.is-live .mb__cur { animation: mb-cur 2.5s linear both; }
.sc--exchange.is-live .mb__btn { animation: mb-btn 2.5s linear both; }
.sc--exchange.is-live .mb__btn-a { animation: mb-btn-a 2.5s linear both; }
.sc--exchange.is-live .mb__btn-b { animation: mb-btn-b 2.5s linear both; }
@keyframes mb-clock { from { --mb-k: 0; } to { --mb-k: 15.6; } }
@keyframes mb-in { from { opacity: 0; translate: 0 0.8em; } }
@keyframes mb-rise { from { opacity: 0; translate: 0 calc(1.2em / var(--k)); } }
@keyframes mb-cur {
  0%, 32% { opacity: 0; translate: 5em 4.5em; }
  36.8% { opacity: 1; translate: 4.4em 4em; animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1); }
  56.8% { opacity: 1; translate: 0 0; scale: 1; }
  60.8% { scale: 0.82; }
  66.4% { scale: 1; translate: 0 0; animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); }
  82% { opacity: 1; translate: 1.4em 1.2em; }
  100% { opacity: 0; translate: 1.8em 1.6em; }
}
@keyframes mb-btn { 0%, 57.6% { background-color: #2447b8; scale: 1; } 60.8% { background-color: #1c3790; scale: 0.95; } 66.4% { scale: 1; } 69.6%, 100% { background-color: rgba(52, 211, 153, 0.14); scale: 1; } }
@keyframes mb-btn-a { 0%, 60.8% { opacity: 1; } 65.6%, 100% { opacity: 0; } }
@keyframes mb-btn-b { 0%, 62.4% { opacity: 0; translate: 0 0.4em; } 72%, 100% { opacity: 1; translate: 0 0; } }

/* Phones and portrait tablets (the engine's narrow layout): the object is centred under the copy, so the window is narrower and turned
   less, and the card sits over its lower right corner. */
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .sc--exchange .mb__tilt { transform: rotateY(calc((-13deg + var(--m, 0) * 6deg) * var(--mo, 1))) rotateX(calc(8deg * var(--mo, 1))); }
  .sc--exchange .mb__win { --rh: 5.2em; left: calc(-17em / var(--k)); top: calc(-24em / var(--k)); width: 34em; height: 37em; grid-template-columns: 5em minmax(0, 1fr); }
  .sc--exchange .mb__bar { gap: 0.8em; padding: 0 0.7em 0 0.9em; }
  .sc--exchange .mb__title { text-align: left; }
  .sc--exchange .mb__tile { width: 3.2em; height: 3.2em; }
  .sc--exchange .mb__main { padding: 1.3em 1.1em 0 1.1em; }
  .sc--exchange .mb__row { column-gap: 0.8em; padding-right: 0.3em; grid-template-columns: 0.8em 2.6em minmax(0, 1fr) auto; }
  .sc--exchange .mb__inv { left: calc(-4em / var(--k)); top: calc(7.5em / var(--k)); width: 22em; padding: 1.3em 1.4em 1.3em; }
}
@media (prefers-reduced-motion: reduce) {
  .sc--exchange.is-live .mb__win, .sc--exchange.is-live .mb__win *, .sc--exchange.is-live .mb__inv, .sc--exchange.is-live .mb__inv * { animation: none; }
}

/* Security: a live screen, the simplest picture of encryption. One window, read left to right: on the left your computer with a short plain
   message in a field, in the middle a lock on an encrypted channel, on the right your server. Each character of the message scrambles through
   two cipher glyphs and leaves the field, the channel carries a band of cipher text through the lock, and on the server side each character
   lands as a glyph, flips once and settles back into the same plain word. Under the window, at its own depth, three status chips carry the
   claims (content.json scene.security.claims, also the copy's list on phones) and their dots turn green after the first pass; above it a
   small notice says the keys were exchanged.
   The window is flat UI on one plane (.sec__win), tilted toward the page centre; the chips and the notice sit 4 and 8 rig em nearer, so the
   layers part as the rig turns through the hold (--m). Arrival and departure belong to the engine (it grows the scene from the card that
   lands on the window, moves .sc__rig and fades .sc-g), so nothing here fades the window or the rig; only flat parts inside animate their
   own opacity. The tilt is scaled by --mo, so the window starts flat, facing the reader like the card, and turns as it grows.
   The message is split by descent.js (data-chars: one span.ch per character, --k its index, --n the count). Spaces are non-breaking, so the
   field is one line and clips a longer message at its edge with a soft fade. The cipher glyphs are decorative: two per character, set in
   ::before and ::after by position (7n and 5n patterns), so any length works; the stagger spreads over 0.8s whatever the length. Each glyph
   is clipped to its own character's box, so a glyph wider than a narrow letter or a space never draws over its neighbours.
   One clock drives the screen: --sec-t, seconds into the pass (0 to 5.4, --T, on a loop), with --sec-cyc 0 through the first pass and 1
   after it and --sec-run 1 while it runs (each moving part runs its own synchronised copy, see Motion). No character or glyph has an
   animation of its own: each character computes how far it is into its own turn (--sec-u, from the clock, --k and --n), and from that the
   colour of its letter and of its two glyphs; the lock computes its glow, the band its clip. These are painted properties (colour, clip, a
   small translate), so the scene keeps no composited layer per character or per part, whatever the length of the message: measured on a
   MacBook in Brave, one animated layer per glyph (about 150) under the Canada card cost the hand-off about 20 frames a second. Nothing here
   needs will-change: the window is already its own layer in the 3D group, and a will-change layer would keep the raster scale it had
   while small, blurring the text as the window grows.
   Time (start E 0.25s, stagger S 0.8s, flight L 0.45s, loop --T 5.4s). At a normal scroll pace a scene is on screen about 2s, so the pass
   is told inside that: 0 to 0.6s the panes, the channel, the notice and the chips come in; from 0.25s the pass: characters leave over 0.8s
   and each lands 0.45s after it left, so the last one reads by about 1.75s and is white by 1.9s; the server's check comes in at about 1.9s
   and the dots turn green from 1.95s; the message holds about 2.9s, clears, and the pass replays every 5.4s, the one loop. The base values
   (no animation: reduced motion, a scene that is not live yet or is leaving) are the final state: the message delivered, the cipher band
   in the channel, the chips green. The custom properties are registered with @property (Chrome 85, Safari 16.4, Firefox 128); the maths
   uses pow() and mod(). */
@property --sec-t { syntax: '<number>'; inherits: true; initial-value: 2.6; }
@property --sec-e { syntax: '<number>'; inherits: true; initial-value: 1; }
@property --sec-cyc { syntax: '<number>'; inherits: true; initial-value: 1; }
@property --sec-run { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --sec-drift { syntax: '<number>'; inherits: false; initial-value: 1; }
@property --sec-u { syntax: '<number>'; inherits: true; initial-value: 0; }
.sec {
  --T: 5.4s;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
  --win: #121a3c; --bar: #0e1533; --well: #0a1130; --line: rgba(196, 210, 255, 0.14);
  --ink: #ffffff; --ink-2: #c7cde0; --ink-3: #8b93b8; --blue: #4f78f6; --fill: #2447b8; --ok: #34d399;
  font-family: var(--sc-font-text); letter-spacing: 0; color: var(--ink);
  transform: rotateX(calc(8deg * var(--mo, 1))) rotateY(calc((19deg - var(--m, 0) * 7deg) * var(--mo, 1)));
}
/* All the drawing sits in one 3D group drawn at 1.25 times the rig's em (twice on phones), so the engine scales it up less and the text stays
   crisp; the rig's own em, which the engine's arrival and departure use, is untouched. scenes.json w x h counts rig em: 62 x 30 of this
   group's em is 78 x 38 there (phones 33 x 38 at 2em: 66 x 76). Depths here are in the group's em too. */
.sec .sec__all { position: absolute; left: 0; top: 0; transform-style: preserve-3d; font-size: 1.25em; }
.sec svg { fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* A cool glow behind the window, far back, so the navy screen separates from the night */
.sec .sec__shade { position: absolute; left: -40em; top: -26em; width: 80em; height: 52em; background: radial-gradient(closest-side, rgba(79, 120, 246, 0.22), transparent 72%); transform: translateZ(-6.4em); }

/* The window */
.sec .sec__win {
  position: absolute; left: -31.5em; top: -10.3em; width: 58em; height: 15.5em; overflow: hidden;
  border-radius: 1.1em; background: var(--win);
  box-shadow: inset 0 0 0 0.09em var(--line), 1.4em 2.2em 4em rgba(3, 7, 26, 0.6);
}
.sec .sec__bar { display: flex; align-items: center; gap: 1.1em; height: 2.6em; padding: 0 0.9em 0 1.1em; background: var(--bar); box-shadow: inset 0 -0.09em 0 rgba(196, 210, 255, 0.08); }
.sec .sec__dots { display: flex; gap: 0.45em; }
.sec .sec__dots i { width: 0.9em; height: 0.9em; border-radius: 50%; background: #ff5f57; }
.sec .sec__dots i + i { background: #febc2e; }
.sec .sec__dots i + i + i { background: #28c840; }
.sec .sec__title { font-size: 1.25em; font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.sec .sec__pill { display: flex; align-items: center; gap: 0.4em; margin-left: auto; height: 1.9em; padding: 0 0.85em 0 0.65em; border-radius: 1em; background: rgba(52, 211, 153, 0.12); color: var(--ok); font-size: 0.95em; font-weight: 600; white-space: nowrap; }
.sec .sec__pill svg { width: 1.15em; height: 1.15em; stroke-width: 2.25; }

/* Body: sender | channel | server */
.sec .sec__body { position: absolute; left: 0; right: 0; top: 2.6em; bottom: 0; display: grid; grid-template-columns: 18em minmax(0, 1fr) 18em; align-items: center; padding: 0 1.8em; }
.sec .sec__pane { display: flex; flex-direction: column; gap: 0.8em; min-width: 0; }
.sec .sec__who { display: flex; align-items: center; gap: 0.55em; height: 1.6em; font-size: 1em; font-weight: 600; color: var(--ink-2); white-space: nowrap; }
.sec .sec__who svg { width: 1.5em; height: 1.5em; color: #8fb0ff; flex: none; }
.sec .sec__ok { display: grid; place-items: center; margin-left: auto; width: 1.6em; height: 1.6em; border-radius: 50%; background: rgba(52, 211, 153, 0.16); color: var(--ok); }
.sec .sec__ok svg { width: 1em; height: 1em; stroke-width: 2.75; }
.sec .sec__field {
  position: relative; height: 3em; padding: 0 0.9em; overflow: hidden; white-space: nowrap;
  border-radius: 0.6em; background: var(--well); box-shadow: inset 0 0 0 0.08em var(--line);
  font-size: 1.2em; line-height: 3em; font-weight: 500; color: var(--ink);
}
/* The soft edge where a long message is clipped: the window's own colour laid over the last 1.1em, which reads exactly like fading the field
   out (the window behind it is flat). A mask did the same, but a masked field whose positioned characters run past its edge made Chrome
   composite the fields (7 more layers for a long message). */
.sec .sec__field::after { content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 1.1em; background: linear-gradient(90deg, rgba(18, 26, 60, 0), var(--win)); }
.sec .sec__msg { display: inline; }
.sec .sec__msg .ch { position: relative; }
.sec .sec__msg .ch::before, .sec .sec__msg .ch::after {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; overflow: hidden;
  font-family: var(--mono); font-size: 0.86em; font-weight: 500; line-height: 1; color: transparent;
}

/* The channel: a track from field to field, the cipher text riding in it, the lock in the middle with a collar of the window's own colour,
   so the band goes in one side and comes out the other. The idle stream drifts slowly and dim; the hot one is the message in transit,
   on its own lighter band that hides the idle glyphs under it, moving with the band's front. */
.sec .sec__chan { position: relative; align-self: center; height: 3.6em; margin-top: 2.4em; }
.sec .sec__track { position: absolute; left: 0.6em; right: 0.6em; top: 50%; height: 2.6em; margin-top: -1.3em; overflow: hidden; border-radius: 1.3em; background: var(--well); box-shadow: inset 0 0 0 0.08em var(--line); }
.sec .sec__flow { position: absolute; inset: 0; overflow: hidden; }
.sec .sec__flow::before {
  content: "7f3Kq9xA2e/Lw8Zt0cR5+vN1bH6mJ4yD8sG2uP0k7f3Kq9xA2e/Lw8Zt0cR5+vN1bH6mJ4yD8sG2uP0k";
  position: absolute; left: 0; top: 50%; translate: 0 -50%; white-space: nowrap;
  font-family: var(--mono); font-size: 1.05em; line-height: 1; letter-spacing: 0.1em; color: rgba(120, 152, 255, 0.55);
}
.sec .sec__flow--hot { background: #17245f; }
.sec .sec__flow--hot::before { color: #dfe7ff; text-shadow: 0 0 0.6em rgba(143, 176, 255, 0.55); }
.sec .sec__lock {
  position: absolute; left: 50%; top: 50%; display: grid; place-items: center; width: 4.2em; height: 4.2em; margin: -2.1em 0 0 -2.1em; border-radius: 50%;
  background: var(--fill); color: #ffffff;
  box-shadow: 0 0 0 0.55em var(--win), inset 0 0 0 0.09em rgba(196, 210, 255, 0.35), 0 0 1.6em 0.2em rgba(79, 120, 246, 0.35);
}
.sec .sec__lock svg { width: 2em; height: 2em; }

/* Chips: the three claims, nearer than the window, hanging from its lower right corner */
.sec .sec__chips { position: absolute; right: -25em; top: 3.3em; display: flex; flex-direction: column; align-items: flex-end; gap: 0.6em; transform: translateZ(3.2em); }
.sec .sec__chip {
  display: flex; align-items: center; gap: 0.65em; padding: 0.62em 1em 0.62em 0.85em; border-radius: 0.7em; white-space: nowrap;
  background: #18215a; box-shadow: inset 0 0 0 0.08em rgba(196, 210, 255, 0.16), 0.6em 1em 2.2em rgba(3, 7, 26, 0.55);
  font-size: 1.15em; font-weight: 500; color: #e6eaf8;
}
.sec .sec__dot { width: 0.62em; height: 0.62em; border-radius: 50%; flex: none; background: var(--ok); box-shadow: 0 0 0 0.24em rgba(52, 211, 153, 0.18); }

/* The notice: keys exchanged, nearest of all, over the top edge above the channel */
.sec .sec__toast {
  position: absolute; left: -11em; top: -12.9em; display: flex; align-items: center; gap: 0.7em; padding: 0.55em 0.75em 0.55em 0.55em; border-radius: 0.8em; white-space: nowrap;
  background: #1b2660; box-shadow: inset 0 0 0 0.08em rgba(196, 210, 255, 0.18), 0.8em 1.4em 2.6em rgba(3, 7, 26, 0.6);
  font-size: 1.05em; font-weight: 600; color: var(--ink);
  transform: translateZ(6.4em);
}
.sec .sec__key { display: grid; place-items: center; width: 2.1em; height: 2.1em; border-radius: 50%; background: rgba(79, 120, 246, 0.22); color: #8fb0ff; }
.sec .sec__key svg { width: 1.3em; height: 1.3em; }
.sec .sec__tick { display: grid; place-items: center; width: 1.5em; height: 1.5em; border-radius: 50%; background: rgba(52, 211, 153, 0.16); color: var(--ok); }
.sec .sec__tick svg { width: 0.95em; height: 0.95em; stroke-width: 2.75; }

/* Cipher glyphs, two per character, by position: decorative, never words */
.sec .sec__pane--from .ch:nth-child(7n+1)::before { content: "x"; }
.sec .sec__pane--from .ch:nth-child(7n+2)::before { content: "7"; }
.sec .sec__pane--from .ch:nth-child(7n+3)::before { content: "K"; }
.sec .sec__pane--from .ch:nth-child(7n+4)::before { content: "f"; }
.sec .sec__pane--from .ch:nth-child(7n+5)::before { content: "2"; }
.sec .sec__pane--from .ch:nth-child(7n+6)::before { content: "Q"; }
.sec .sec__pane--from .ch:nth-child(7n+7)::before { content: "9"; }
.sec .sec__pane--from .ch:nth-child(5n+1)::after { content: "Z"; }
.sec .sec__pane--from .ch:nth-child(5n+2)::after { content: "4"; }
.sec .sec__pane--from .ch:nth-child(5n+3)::after { content: "c"; }
.sec .sec__pane--from .ch:nth-child(5n+4)::after { content: "/"; }
.sec .sec__pane--from .ch:nth-child(5n+5)::after { content: "8"; }
.sec .sec__pane--to .ch:nth-child(7n+1)::after { content: "f"; }
.sec .sec__pane--to .ch:nth-child(7n+2)::after { content: "2"; }
.sec .sec__pane--to .ch:nth-child(7n+3)::after { content: "Q"; }
.sec .sec__pane--to .ch:nth-child(7n+4)::after { content: "9"; }
.sec .sec__pane--to .ch:nth-child(7n+5)::after { content: "x"; }
.sec .sec__pane--to .ch:nth-child(7n+6)::after { content: "7"; }
.sec .sec__pane--to .ch:nth-child(7n+7)::after { content: "K"; }
.sec .sec__pane--to .ch:nth-child(5n+1)::before { content: "c"; }
.sec .sec__pane--to .ch:nth-child(5n+2)::before { content: "/"; }
.sec .sec__pane--to .ch:nth-child(5n+3)::before { content: "8"; }
.sec .sec__pane--to .ch:nth-child(5n+4)::before { content: "Z"; }
.sec .sec__pane--to .ch:nth-child(5n+5)::before { content: "4"; }

/* Motion, only while live (the engine pauses a scene that is not live and drops the animations of one that is leaving). Each part that moves
   runs its own copy of the clock (--sec-t; the 5.4 in the keyframes is --T in seconds), all started together, so they never drift apart. A
   copy holds still while its part has nothing to do: an animated value that does not change costs the browser almost nothing, one that
   changes restyles every element under it each frame. So the sender's characters are restyled only during their pass (0.3s to 1.57s), the
   server's during theirs (0.7s to 1.95s, and the clear from 4.85s into the next pass), the channel while the band crosses (0.4s to 1.5s),
   the check while it comes and goes. The two message clocks also tick 30 times a second (steps of about 33ms) instead of every frame: the
   flips are steps anyway, the slides and fades are a few tenths of a second, and restyling the characters on every other frame halves the
   cost (the per-frame pass measured about 1.6 times the old one in headless Chrome, the stepped one about 0.8). Remove the steps() to go back
   to every frame. The entrance has its own one-shot clock (--sec-e, 1s) and the idle stream its own drift. */
.sc--security.is-live .sec__all { animation: sec-enter 1s linear forwards, sec-first var(--T) step-end forwards; }
.sc--security.is-live .sec__pane--from .sec__msg { animation: sec-clock-from var(--T) linear infinite; }
.sc--security.is-live .sec__pane--to .sec__msg { animation: sec-clock-to var(--T) linear infinite; }
.sc--security.is-live .sec__chan { animation: sec-clock-chan var(--T) linear infinite; }
.sc--security.is-live .sec__ok { animation: sec-clock-ok var(--T) linear infinite; }
.sc--security.is-live .sec__flow:not(.sec__flow--hot)::before { animation: sec-drift 14s linear infinite; }
.sc--security.is-live .sec__dot { animation: sec-dot 0.45s ease calc(1.95s + var(--i) * 0.12s) backwards; }
@keyframes sec-enter { from { --sec-e: 0; } to { --sec-e: 1; } }
@keyframes sec-first { from { --sec-cyc: 0; } to { --sec-cyc: 1; } }
@keyframes sec-clock-from { 0% { --sec-t: 0.3; } 5.556% { --sec-t: 0.3; animation-timing-function: steps(38, end); } 29.074%, 100% { --sec-t: 1.57; } }
@keyframes sec-clock-to {
  0% { --sec-t: 0; animation-timing-function: steps(14, end); }
  8.333% { --sec-t: 0.45; animation-timing-function: steps(1, end); }
  12.963% { --sec-t: 0.7; animation-timing-function: steps(38, end); }
  36.111% { --sec-t: 1.95; animation-timing-function: steps(1, end); }
  89.815% { --sec-t: 4.85; animation-timing-function: steps(17, end); }
  100% { --sec-t: 5.4; }
}
@keyframes sec-clock-chan { 0%, 7.407% { --sec-t: 0.4; --sec-run: 1; } 27.778%, 100% { --sec-t: 1.5; --sec-run: 1; } }
@keyframes sec-clock-ok {
  0%, 34.63% { --sec-t: 1.87; }
  37.63% { --sec-t: 2.032; animation-timing-function: steps(1, end); }
  88.13% { --sec-t: 4.759; }
  90.13%, 100% { --sec-t: 4.867; }
}
@keyframes sec-drift { from { --sec-drift: 0; } to { --sec-drift: 1; } }
@keyframes sec-dot { from { background: #59618a; box-shadow: 0 0 0 0.24em rgba(139, 147, 184, 0.12); } }
/* What the clocks drive. Steps are clamp(0, (u - at) * 1000, 1); eases are 1 - (1 - x)^6, close to cubic-bezier(0.16, 1, 0.3, 1). */
/* Entrances, once: fade and a 0.9em rise over 0.5s, staggered */
.sec .sec__pane--from { --c-at: 0; }
.sec .sec__chan { --c-at: 0.08; }
.sec .sec__pane--to { --c-at: 0.16; }
.sec .sec__chip { --c-at: calc(0.3 + var(--i) * 0.1); }
.sec .sec__toast { --c-at: 0.2; }
.sec .sec__pane, .sec .sec__chan, .sec .sec__chip, .sec .sec__toast {
  --c-in: calc(1 - pow(1 - clamp(0, (var(--sec-e) - var(--c-at)) / 0.5, 1), 6));
  opacity: var(--c-in); translate: 0 calc((1 - var(--c-in)) * 0.9em);
}
.sec .sec__toast { translate: 0 calc((var(--c-in) - 1) * 0.9em); }
.sec .sec__tick { --c-in: calc(1 - pow(1 - clamp(0, (var(--sec-e) - 0.5) / 0.4, 1), 6)); opacity: var(--c-in); scale: calc(0.6 + 0.4 * var(--c-in)); }
/* Leaving, per character, u seconds into its turn (u = t - 0.25 - k / n * 0.8): glyph A at 58ms, glyph B at 127ms, B slides 1.4em off from
   259ms to 518ms while the plain letter comes back, white again at 461ms. Each part is one declaration with --sec-u inlined: every character
   is restyled each frame of its pass, and each declaration that reads a variable costs a parse, so fewer and plainer is cheaper. */
.sec .sec__pane--from .ch {
  --sec-u: calc(var(--sec-t) - 0.25 - var(--k, 0) / var(--n, 1) * 0.8);
  color: rgb(255 255 255 / calc(2 - clamp(0, (var(--sec-u) - 0.058) * 1000, 1) - pow(1 - clamp(0, (var(--sec-u) - 0.259) / 0.202, 1), 2)));
}
.sec .sec__pane--from .ch::before { color: rgb(143 176 255 / calc(clamp(0, (var(--sec-u) - 0.058) * 1000, 1) - clamp(0, (var(--sec-u) - 0.127) * 1000, 1))); }
.sec .sec__pane--from .ch::after {
  color: rgb(143 176 255 / calc(clamp(0, (var(--sec-u) - 0.127) * 1000, 1) * pow(1 - clamp(0, (var(--sec-u) - 0.259) / 0.259, 1), 6)));
  translate: calc((1 - pow(1 - clamp(0, (var(--sec-u) - 0.259) / 0.259, 1), 6)) * 1.4em) 0;
}
/* Landing (u = t - 0.7 - k / n * 0.8): a glyph slides in from the channel (150ms), flips once at 210ms and settles into the plain letter at
   280ms, light blue to white by 450ms; the letter holds to 4.15s and clears in 0.2s. The last letters clear early in the next pass (the
   second term, from the second pass on, --sec-cyc), 0.6s before the sender starts again. */
.sec .sec__pane--to .ch {
  --sec-u: calc(var(--sec-t) - 0.7 - var(--k, 0) / var(--n, 1) * 0.8);
  color: rgb(calc(159 + 96 * clamp(0, max((var(--sec-u) - 0.28) / 0.17, var(--sec-u) * -1000), 1)) calc(184 + 71 * clamp(0, max((var(--sec-u) - 0.28) / 0.17, var(--sec-u) * -1000), 1)) 255 / calc(clamp(0, (var(--sec-u) - 0.28) * 1000, 1) * (1 - clamp(0, (var(--sec-u) - 4.15) / 0.2, 1)) + var(--sec-cyc) * (1 - clamp(0, (var(--sec-u) + 1.25) / 0.2, 1))));
}
.sec .sec__pane--to .ch::after {
  color: rgb(143 176 255 / calc((1 - pow(1 - clamp(0, var(--sec-u) / 0.15, 1), 6)) * (1 - clamp(0, (var(--sec-u) - 0.21) * 1000, 1))));
  translate: calc(pow(1 - clamp(0, var(--sec-u) / 0.15, 1), 6) * -1.4em) 0;
}
.sec .sec__pane--to .ch::before { color: rgb(143 176 255 / calc(clamp(0, (var(--sec-u) - 0.21) * 1000, 1) - clamp(0, (var(--sec-u) - 0.28) * 1000, 1))); }
/* The server's check: in once the last character reads (1.87s to 2.03s), out just before the message clears (4.76s to 4.87s) */
.sec .sec__ok { --c-on: clamp(0, (var(--sec-t) - 1.87) / 0.162, 1); opacity: calc(var(--c-on) - clamp(0, (var(--sec-t) - 4.759) / 0.108, 1)); scale: calc(0.6 + 0.4 * var(--c-on)); }
/* The band in transit: its front enters with the first glyph (0.4s) and is across when the first character lands (0.7s), its back follows
   0.8s later, gone as the last character lands (1.5s). The idle stream drifts one pattern in 14s; the band's own glyphs one in 1.65s. */
.sec .sec__flow--hot {
  --c-front: calc(1 - clamp(0, (var(--sec-t) - 0.4) / 0.3, 1));
  --c-gone: clamp(0, (var(--sec-t) - 1.2) / 0.3, 1);
  clip-path: inset(0 calc(var(--c-front) * var(--sec-run) * 100%) 0 calc(var(--c-gone) * var(--sec-run) * 100%));
}
.sec .sec__flow::before { transform: translateX(calc((var(--sec-drift) - 1) * 50%)); }
.sec .sec__flow--hot::before { transform: translateX(calc((mod(var(--sec-t), 1.65) / 1.65 - 1) * 50% * var(--sec-run))); }
/* The lock glows while the band crosses it (0.49s to 1.47s) */
.sec .sec__lock {
  --c-g: calc(clamp(0, (var(--sec-t) - 0.493) / 0.108, 1) - clamp(0, (var(--sec-t) - 1.303) / 0.162, 1));
  background: rgb(calc(36 + 17 * var(--c-g)) calc(71 + 25 * var(--c-g)) calc(184 + 46 * var(--c-g)));
  box-shadow: 0 0 0 0.55em var(--win), inset 0 0 0 0.09em rgb(196 210 255 / calc(0.35 + 0.25 * var(--c-g))), 0 0 calc(1.6em + var(--c-g) * 1em) calc(0.2em + var(--c-g) * 0.5em) rgb(79 120 246 / calc(0.35 + 0.35 * var(--c-g)));
}
@media (prefers-reduced-motion: reduce) {
  .sc--security.is-live *, .sc--security.is-live *::before, .sc--security.is-live *::after { animation: none !important; }
}

/* Phones and portrait tablets: the three parts stacked under the copy, sender on top, the channel running down through the lock, the server
   below; bigger text so the message stays readable. The chips are left out: the copy's own list carries the claims there. */
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .sec { transform: rotateX(calc(7deg * var(--mo, 1))) rotateY(calc((9deg - var(--m, 0) * 6deg) * var(--mo, 1))); }
  .sec .sec__all { font-size: 2em; }
  .sec .sec__shade { transform: translateZ(-5em); }
  .sec .sec__shade { left: -24em; top: -24em; width: 48em; height: 48em; }
  .sec .sec__win { left: -15.25em; top: -15.4em; width: 30.5em; height: 32.8em; }
  .sec .sec__body { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr) auto; align-items: stretch; padding: 1.7em 1.7em 1.9em; }
  .sec .sec__who { font-size: 1.2em; }
  .sec .sec__field { font-size: 1.4em; }
  .sec .sec__chan { align-self: stretch; height: auto; margin: 0.4em 0; }
  .sec .sec__track { left: 50%; right: auto; top: 0.3em; bottom: 0.3em; width: 2.8em; height: auto; margin: 0 0 0 -1.4em; }
  .sec .sec__flow::before { left: 50%; top: 0; translate: -50% 0; writing-mode: vertical-lr; text-orientation: upright; font-size: 1.2em; letter-spacing: 0; line-height: 1.15; }
  .sec .sec__lock { width: 3.6em; height: 3.6em; margin: -1.8em 0 0 -1.8em; }
  .sec .sec__lock svg { width: 1.8em; height: 1.8em; }
  .sec .sec__chips { display: none; }
  .sec .sec__toast { left: -1.5em; top: -15.9em; font-size: 1.15em; transform: translateZ(4em); }
  /* Down the channel: the idle stream one pattern in 30s, the band's glyphs one in 6s, the band growing from the top and leaving at the bottom */
  .sc--security.is-live .sec__flow:not(.sec__flow--hot)::before { animation-duration: 30s; }
  .sec .sec__flow::before { transform: translateY(calc((var(--sec-drift) - 1) * 50%)); }
  .sec .sec__flow--hot::before { transform: translateY(calc((var(--sec-t) / 6 - 1) * 50% * var(--sec-run))); }
  .sec .sec__flow--hot { clip-path: inset(calc(var(--c-gone) * var(--sec-run) * 100%) 0 calc(var(--c-front) * var(--sec-run) * 100%) 0); }
}

/* Canada: the finale. The four screens the visitor just scrolled through come back as four tiles and lock together into one dashboard
   window, on the same navy as every other scene; its status pill then turns to "Hébergé au Canada" with the small red leaf (the only red
   on the page) and a green dot. A real ending instead of a fifth scene: everything in one place, run from Canada.
   Groups: .sc__rig (the engine's departure, its camera and the pointer), .fin (the tilt toward the copy, scaled by --mo so the frame
   starts flat under the landing Canadien card, easing toward the reader through the hold on --m), .fin__plane (one flat layer: the frame and the four tiles, never preserve-3d, so nothing in it can
   cut through anything else). Each tile carries the words and icons of its own scene (the same deck keys, so an edit there shows here
   too) and the area's card icon.
   Bento, on a computer: domain | server on top, security | exchange below, each tile on the side its scene was drawn on (scenes.json
   side: server and exchange on the right, domain and security on the left), so every tile comes in from where the visitor last saw it.
   On phones the tiles stack in one column, in the order of the scenes: at 390px a 2x2 tile is about 165px wide, too narrow for the
   host name and the folder name at the scenes' row size, and one column keeps every word whole.
   Motion, all scrubbed on the engine's numbers so scrolling back plays it backwards and every frame of it is deterministic:
     --mo (descent.js, p 0.705 to 0.745): the Canadien card lands at p 0.70 on the frame, flat and at its size, the scene shows under it
       (its wrapper's fade, p 0.700 to 0.705) and the dashboard grows from that footprint into its tilt, shedding the card's face.
     --q (the peak's arrival, p 0.68 to 0.77)
       server, domain, exchange then security dock one after another (from q 0.36, 0.47, 0.58, 0.69, each over 0.28 of q): a 5em slide
       in from its side on an ease-out quartic, so it decelerates hard and stops clean in its slot, no overshoot; it fades in over the
       first 30% of its own slide, while it is still moving. The frame stands 6% larger while they come in and closes around them
       (q 0.47 to 0.99), the last tile seated just before it shuts. The first tile is in once the frame has grown about 40% of the way and
       the card's face is wiped from its top half, so no tile slides over the card.
     --land (p 0.76 to 0.88, set by the engine on the peak)
       0.04 to 0.20: the pill flips. "En cours" leaves upward in amber, "Hébergé au Canada" rises in green, the dot turns green, the pill's
       tint comes up and the leaf grows in from 0.4 to full size. At Félix's pace (about 2s a scene) that is about 1.2s after the dashboard
       starts to show, and it then holds.
   The one loop, time based under .is-live (the engine pauses a scene that is not live): the CPU sparkline drifts one period every 24s,
   seamless; it is an HTML wrapper's transform, so it runs on the compositor and repaints nothing.
   Cost: no 3D solids, no gradients that animate, no blur, no per-character work; the plane, the frame and the four tiles are the only
   layers, and once the pill has flipped nothing changes but the rig's slow turn and the sparkline.
   Sharpness (as in server.css and exchange.css): the browser rasterises a layer under a perspective transform at its own CSS size and then
   magnifies it, so text drawn at the art's em and grown by --ss comes out soft. The plane is drawn --ss times larger (--fk; not --k, which
   descent.js sets on typed characters) and scaled back by 1/--fk in its own transform, with translateZ(0) so it stays a layer of its own
   for good; inside it every length is in the plane's em, so slides and gaps stay in scene em, and 1px hairlines are multiplied by --fk.
   Every variable falls back to its end value (--q 1, --land 1), so without the engine, under reduced motion and in a frozen capture the
   finished dashboard shows. Every word comes from content.json; the words new to this scene are scene.canada. */
.sc--canada { --fk: var(--ss, 1); --f: clamp(0, (var(--land, 1) - 0.04) / 0.16, 1); }
/* The landing ring in descent.css belongs to the old peak's leaf: the finale lands on the navy with nothing drawn under it. */
.sc.sc--canada[data-peak]::after { content: none; }

.fin { transform: rotateX(calc(7deg * var(--mo, 1))) rotateY(calc((-16deg + var(--m, 0) * 8deg) * var(--mo, 1))); font-family: var(--sc-font-text); line-height: 1.25; letter-spacing: 0; color: #c7cde0; text-align: left; }
.sc--canada .fin__plane { position: absolute; left: 0; top: 0; width: 64em; font-size: calc(var(--fk) * 1em); transform-origin: 0 0; transform: scale(calc(1 / var(--fk))) translate(-50%, -50%) translateZ(0); }

/* The frame: the window itself, behind the tiles. Near-solid navy, an inner hairline, a soft offset shadow, the title bar. */
.sc--canada .fin__frame {
  --c: clamp(0, (var(--q, 1) - 0.47) / 0.52, 1);
  position: absolute; inset: 0; overflow: hidden; border-radius: 1.1em; background: #10183a;
  box-shadow: 1.6em 2.4em 4.4em rgba(2, 5, 20, 0.6);
  transform: scale(calc(1 + (1 - var(--c)) * (1 - var(--c)) * 0.06)); will-change: transform;
}
.sc--canada .fin__frame::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 calc(var(--fk) * 1px) rgba(196, 210, 255, 0.14); pointer-events: none; }
.sc--canada .fin__bar { display: flex; align-items: center; gap: 1em; height: 2.6em; padding: 0 0.85em 0 1.1em; background: #0d1431; box-shadow: inset 0 calc(var(--fk) * -1px) 0 rgba(196, 210, 255, 0.1); }
.sc--canada .fin__dots { display: flex; flex: none; gap: 0.45em; }
.sc--canada .fin__dots i { width: 0.9em; height: 0.9em; border-radius: 50%; background: #ff5f57; }
.sc--canada .fin__dots i + i { background: #febc2e; }
.sc--canada .fin__dots i + i + i { background: #28c840; }
.sc--canada .fin__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 1.25em; font-weight: 600; color: #ffffff; white-space: nowrap; }

/* The status pill. Both words share one cell (it is as wide as the longer one), left aligned so the waiting word sits by its dot; while it
   waits the pill has no tint, so the room kept for the longer word does not show. */
.sc--canada .fin__pill { position: relative; display: flex; flex: none; align-items: center; gap: 0.45em; margin-left: auto; height: 1.95em; padding: 0 0.7em 0 0.8em; border-radius: 1em; font-size: 1.05em; font-weight: 600; white-space: nowrap; }
.sc--canada .fin__pill::before { content: ""; position: absolute; inset: 0; border-radius: inherit; background: rgba(52, 211, 153, 0.12); opacity: var(--f); }
.sc--canada .fin__led { position: relative; flex: none; width: 0.6em; height: 0.6em; border-radius: 50%; background: #f5b041; }
.sc--canada .fin__led i { position: absolute; inset: 0; border-radius: 50%; background: #34d399; opacity: var(--f); }
.sc--canada .fin__say { position: relative; display: grid; justify-items: start; }
.sc--canada .fin__say > span { grid-area: 1 / 1; }
.sc--canada .fin__wait { color: #f5b041; opacity: calc(1 - min(1, var(--f) * 2.5)); translate: 0 calc(var(--f) * -0.5em); }
.sc--canada .fin__ok { --o2: clamp(0, (var(--f) - 0.3) / 0.5, 1); color: #34d399; opacity: var(--o2); translate: 0 calc((1 - var(--o2)) * 0.5em); }
.sc--canada .fin__leaf { --o3: clamp(0, (var(--f) - 0.45) / 0.55, 1); position: relative; flex: none; width: 1.3em; height: 1.3em; fill: #e5362a; opacity: var(--o3); transform: scale(calc(0.4 + var(--o3) * 0.6)); }

/* The bento: ten columns, so a row can split 4 | 6 or 6 | 4 */
.sc--canada .fin__grid { position: relative; display: grid; grid-template-columns: repeat(10, minmax(0, 1fr)); gap: 1.1em; padding: 4em 1.4em 1.4em; }
.sc--canada .fin__tile {
  --t: clamp(0, (var(--q, 1) - var(--a)) / 0.28, 1); --u: calc(1 - var(--t));
  position: relative; display: grid; grid-template-columns: 2.8em minmax(0, 1fr); grid-template-rows: 2.8em minmax(5.6em, 1fr) auto;
  grid-template-areas: "ic name" "vis vis" "st st"; column-gap: 0.9em; row-gap: 1em; align-items: center; min-width: 0;
  padding: 1.2em 1.3em 1.15em; border-radius: 0.8em; background: #162050; box-shadow: inset 0 0 0 calc(var(--fk) * 1px) rgba(196, 210, 255, 0.13);
  transform: translateX(calc(var(--dir) * var(--u) * var(--u) * var(--u) * var(--u) * 5em)); opacity: clamp(0, var(--t) * 3.4, 1); will-change: transform, opacity;
}
.sc--canada .fin__tile--dom { grid-column: 1 / 5; grid-row: 1; --a: 0.47; --dir: -1; }
.sc--canada .fin__tile--srv { grid-column: 5 / 11; grid-row: 1; --a: 0.36; --dir: 1; }
.sc--canada .fin__tile--sec { grid-column: 1 / 7; grid-row: 2; --a: 0.69; --dir: -1; }
.sc--canada .fin__tile--mail { grid-column: 7 / 11; grid-row: 2; --a: 0.58; --dir: 1; counter-reset: fin-new; }

/* A tile: the area's card icon, its name, its picture, its status */
.sc--canada .fin__ic { grid-area: ic; display: grid; place-items: center; width: 2.8em; height: 2.8em; border-radius: 0.7em; background: #2447b8; color: #ffffff; }
.sc--canada .fin__ic svg { width: 1.5em; height: 1.5em; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sc--canada .fin__name { grid-area: name; min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 1.25em; font-weight: 600; color: #ffffff; white-space: nowrap; }
.sc--canada .fin__st { grid-area: st; justify-self: start; display: flex; align-items: center; gap: 0.45em; height: 1.9em; padding: 0 0.8em 0 0.7em; border-radius: 1em; background: rgba(52, 211, 153, 0.12); color: #34d399; font-size: 0.95em; font-weight: 600; white-space: nowrap; }
.sc--canada .fin__dot { flex: none; width: 0.6em; height: 0.6em; border-radius: 50%; background: #34d399; }
.sc--canada .fin__vis { grid-area: vis; min-width: 0; }

/* Server: the CPU line, two identical periods of 320 units in a wrapper twice the box's width */
.sc--canada .fin__spark { position: relative; align-self: stretch; overflow: hidden; background: linear-gradient(rgba(196, 210, 255, 0.07) calc(var(--fk) * 1px), transparent calc(var(--fk) * 1px)) 0 0 / 100% 33.4%; box-shadow: inset 0 calc(var(--fk) * -1px) 0 rgba(196, 210, 255, 0.14); }
.sc--canada .fin__drift { position: absolute; left: 0; top: 0; bottom: 0; width: 200%; }
.sc--canada .fin__drift svg { display: block; width: 100%; height: 100%; }
.sc--canada .fin__line { fill: none; stroke: #4f78f6; stroke-width: 0.16em; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.sc--canada .fin__area { fill: rgba(36, 71, 184, 0.3); }

/* Domain: the three records, each with its green dot */
.sc--canada .fin__recs { display: flex; flex-wrap: wrap; gap: 0.6em; align-self: center; }
.sc--canada .fin__recs b { display: flex; align-items: center; gap: 0.45em; height: 2.1em; padding: 0 0.8em 0 0.7em; border-radius: 0.5em; background: rgba(196, 210, 255, 0.06); box-shadow: inset 0 0 0 calc(var(--fk) * 1px) rgba(196, 210, 255, 0.1); font-size: 1em; font-weight: 600; color: #c7cde0; white-space: nowrap; }
.sc--canada .fin__recs b::before { content: ""; flex: none; width: 0.5em; height: 0.5em; border-radius: 50%; background: #34d399; }

/* Security: your computer, the lock on the channel, your server */
.sc--canada .fin__chan { display: flex; align-items: center; gap: 0.6em; align-self: center; }
.sc--canada .fin__end { flex: none; display: grid; place-items: center; width: 2.7em; height: 2.7em; border-radius: 0.65em; background: rgba(196, 210, 255, 0.06); box-shadow: inset 0 0 0 calc(var(--fk) * 1px) rgba(196, 210, 255, 0.1); color: #c7cde0; }
.sc--canada .fin__end svg, .sc--canada .fin__lock svg { width: 1.45em; height: 1.45em; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.sc--canada .fin__wire { flex: 1; min-width: 1em; height: 0.18em; border-radius: 0.1em; background: repeating-linear-gradient(90deg, #4f78f6 0 0.7em, transparent 0.7em 1.1em); }
.sc--canada .fin__lock { flex: none; display: grid; place-items: center; width: 3.3em; height: 3.3em; border-radius: 50%; background: rgba(52, 211, 153, 0.14); box-shadow: inset 0 0 0 0.13em #34d399; color: #34d399; }
.sc--canada .fin__lock svg { stroke-width: 2.1; }

/* Exchange: the inbox and its unread count, which counts the senders drawn (one avatar per unread mail), so no number is typed in */
.sc--canada .fin__avs { display: flex; align-self: center; padding-left: 0.55em; }
.sc--canada .fin__av {
  flex: none; width: 2.8em; height: 2.8em; margin-left: -0.55em; border-radius: 50%; overflow: hidden; white-space: nowrap;
  font-size: 1.05em; line-height: 2.8em; font-weight: 700; text-indent: 0.98em; color: transparent; background: #2447b8;
  box-shadow: 0 0 0 0.2em #162050; counter-increment: fin-new;
}
.sc--canada .fin__av::first-letter { color: #ffffff; }
.sc--canada .fin__av + .fin__av { background: #1f5f8b; }
.sc--canada .fin__av + .fin__av + .fin__av { background: #5a6db3; }
.sc--canada .fin__tile--mail .fin__name { display: flex; align-items: center; gap: 0.45em; }
.sc--canada .fin__tile--mail .fin__name span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.sc--canada .fin__badge { flex: none; min-width: 1.75em; height: 1.45em; padding: 0 0.45em; border-radius: 0.75em; background: #4f78f6; color: #ffffff; font-size: 0.72em; line-height: 1.45em; font-weight: 700; text-align: center; font-variant-numeric: tabular-nums; }
.sc--canada .fin__badge::after { content: counter(fin-new); }

/* The one loop */
.sc--canada.is-live .fin__drift { animation: fin-drift 24s linear infinite; }
@keyframes fin-drift { to { transform: translateX(-50%); } }

@media (prefers-reduced-motion: reduce) {
  .sc--canada { --f: 1; }
  .sc--canada .fin__frame, .sc--canada .fin__tile { transform: none; opacity: 1; }
  .sc--canada * { animation: none !important; }
}

/* Phones and portrait tablets: one column under the copy, in the order of the scenes, turned less so the near edge stays in the gutter */
@media (max-width: 699px), (max-width: 959px) and (orientation: portrait) {
  .fin { transform: rotateX(calc(6deg * var(--mo, 1))) rotateY(calc((-8deg + var(--m, 0) * 4deg) * var(--mo, 1))); }
  .sc--canada .fin__plane { width: 34em; }
  .sc--canada .fin__grid { grid-template-columns: minmax(0, 1fr); gap: 0.75em; padding: 3.7em 1.1em 1.1em; }
  .sc--canada .fin__tile { grid-column: 1; grid-template-columns: 2.6em minmax(0, 1fr) auto; grid-template-rows: auto auto; grid-template-areas: "ic name vis" "ic st vis"; column-gap: 0.9em; row-gap: 0.4em; padding: 0.95em 1em; }
  .sc--canada .fin__tile--srv { grid-row: 1; }
  .sc--canada .fin__tile--dom { grid-row: 2; }
  .sc--canada .fin__tile--mail { grid-row: 3; }
  .sc--canada .fin__tile--sec { grid-row: 4; }
  .sc--canada .fin__ic { width: 2.6em; height: 2.6em; }
  .sc--canada .fin__ic svg { width: 1.4em; height: 1.4em; }
  .sc--canada .fin__name { align-self: end; font-size: 1.1em; }
  .sc--canada .fin__st { align-self: start; height: 1.75em; font-size: 0.85em; }
  .sc--canada .fin__spark { align-self: center; width: 8em; height: 3.6em; }
  .sc--canada .fin__recs { flex-wrap: nowrap; gap: 0.4em; }
  /* On a phone the records shrink to their green dots, so the domain name keeps its room whatever Félix names them (SSL/TLS, ANYCAST...). */
  .sc--canada .fin__recs b { justify-content: center; width: 1.9em; height: 1.9em; padding: 0; gap: 0; font-size: 0; }
  .sc--canada .fin__recs b::before { width: 0.5rem; height: 0.5rem; }
  .sc--canada .fin__chan { gap: 0.4em; }
  .sc--canada .fin__wire { min-width: 0.8em; width: 0.8em; }
  .sc--canada .fin__end { width: 2.3em; height: 2.3em; }
  .sc--canada .fin__end svg, .sc--canada .fin__lock svg { width: 1.25em; height: 1.25em; }
  .sc--canada .fin__lock { width: 2.7em; height: 2.7em; }
  .sc--canada .fin__av { width: 2.5em; height: 2.5em; line-height: 2.5em; text-indent: 0.84em; }
}
