/* =====================================================================
   Marketing-site motion.

   Everything here is progressive enhancement: elements are only hidden
   or transformed once JS tags them, so a no-JS visitor sees a complete,
   readable page instantly. Three rules govern the whole file:

     1. Animate transform and opacity only. Never width/top/left.
     2. Anything pointer-driven is gated behind (hover: hover) and
        (pointer: fine). A touch device must never wait for a hover.
     3. prefers-reduced-motion switches the lot off, not just some of it.

   JS writes scalars into custom properties (--sy, --mx, --my) and CSS
   decides what to do with them, so the scroll loop stays cheap.
   ===================================================================== */

/* ---------------------------------------------------------- reveals */
.rv { opacity: 0; transform: translateY(22px);
  transition: opacity .65s cubic-bezier(.22,.7,.3,1), transform .65s cubic-bezier(.22,.7,.3,1); }
.rv.in { opacity: 1; transform: none; }

/* directional variants, for content that reads better arriving from
   the side it sits on */
.rv-l { transform: translateX(-26px); }
.rv-r { transform: translateX(26px); }
.rv-scale { transform: scale(.955); }
.rv-l.in, .rv-r.in, .rv-scale.in { transform: none; }

/* ------------------------------------------------- scroll progress */
.scroll-progress { position: fixed; inset: 0 0 auto 0; height: 2.5px; z-index: 60;
  background: var(--ob-gradient-fee); transform-origin: 0 50%;
  transform: scaleX(var(--progress, 0)); pointer-events: none;
  opacity: 0; transition: opacity .3s; }
.scroll-progress.on { opacity: 1; }

/* --------------------------------------------------- header states */
/* The header lifts off the page once you leave the top of it, and
   quietly loses height so more of the page shows while scrolling. */
.top { transition: box-shadow .3s, background-color .3s, border-color .3s; }
.top.is-stuck { box-shadow: 0 6px 24px -14px rgba(10, 46, 40, .5);
  background: color-mix(in srgb, var(--ob-ground) 94%, transparent); }
.top .ob-container { transition: min-height .3s cubic-bezier(.22,.7,.3,1); }
.top.is-stuck .ob-container { min-height: 56px; }
.top .brandlink img { transition: height .3s cubic-bezier(.22,.7,.3,1); }
.top.is-stuck .brandlink img { height: 30px; }

/* ----------------------------------------------------- parallax */
/* --sy is the element's own progress through the viewport, roughly
   -1 (entering from below) to 1 (leaving at the top). Depth is per
   element via --depth, so one loop drives every layer.

   NOTE: .px, .rv and .tilt each own the element's `transform`, so never
   put two of them on the same element - the later rule silently wins
   and one effect goes missing. Wrap instead. */
.px { transform: translate3d(0, calc(var(--sy, 0) * var(--depth, 18px) * -1), 0); }
.px-slow { --depth: 26px; }
.px-fast { --depth: 60px; }

/* the hero's soft colour field drifts as you scroll, which is what
   stops a large flat block from reading as a screenshot */
/* The height follows the hero rather than being a flat 520px. A short
   hero (pricing, coverage) was completely filled by the glow, which
   overflow: clip then sliced off square: the band ended in a hard
   horizontal edge and read as a different background colour rather
   than as a light behind the page.

   The mask is the belt to that braces. Whatever the hero's height, the
   bottom of the glow dissolves into the page instead of stopping. */
.aurora { position: absolute; left: -10%; right: -10%; top: -20%;
  height: min(520px, 125%); z-index: -1;
  pointer-events: none; filter: blur(60px); opacity: .5;
  -webkit-mask-image: linear-gradient(180deg, #000 40%, transparent 92%);
  mask-image: linear-gradient(180deg, #000 40%, transparent 92%);
  transform: translate3d(0, calc(var(--sy, 0) * 40px), 0); }
.aurora i { position: absolute; display: block; border-radius: 50%; }
.aurora .a1 { width: 460px; height: 340px; left: 4%; top: 10%;
  background: radial-gradient(circle, rgba(0, 200, 120, .5), transparent 68%);
  animation: drift-a 19s ease-in-out infinite alternate; }
.aurora .a2 { width: 400px; height: 320px; right: 8%; top: 0;
  background: radial-gradient(circle, rgba(47, 200, 194, .45), transparent 68%);
  animation: drift-b 23s ease-in-out infinite alternate; }
.aurora .a3 { width: 300px; height: 260px; left: 42%; top: 34%;
  background: radial-gradient(circle, rgba(255, 194, 75, .3), transparent 68%);
  animation: drift-c 27s ease-in-out infinite alternate; }
@keyframes drift-a { to { transform: translate(70px, 34px) scale(1.14); } }
@keyframes drift-b { to { transform: translate(-64px, 46px) scale(1.1); } }
@keyframes drift-c { to { transform: translate(38px, -32px) scale(1.18); } }
/* only heroes that actually carry an aurora get clipped, so a page
   with content deliberately breaking its hero bounds is left alone */
.hero:has(> .aurora) { position: relative; overflow: clip; }

/* ------------------------------------------------- pointer effects */
/* Tilt and magnetism are a mouse language. Touch gets a press state
   instead, further down. */
@media (hover: hover) and (pointer: fine) {
  .tilt { transform-style: preserve-3d;
    transition: transform .4s cubic-bezier(.22,.7,.3,1), box-shadow .4s; }
  .tilt.is-tilting { transition: transform .08s linear, box-shadow .4s;
    transform: perspective(900px)
               rotateX(calc(var(--my, 0) * -4deg))
               rotateY(calc(var(--mx, 0) * 5deg))
               translate3d(0, -3px, 0); }
  .tilt.is-tilting { box-shadow: 0 18px 44px -22px rgba(10, 46, 40, .5); }

  /* buttons lean very slightly toward the cursor as it approaches */
  .mag { transition: transform .35s cubic-bezier(.22,.7,.3,1); }
  .mag.is-near { transform: translate3d(calc(var(--mx, 0) * 4px),
                                        calc(var(--my, 0) * 3px), 0); }

  /* cards lift, and their icon leads the movement */
  .ob-card.lift { transition: transform .35s cubic-bezier(.22,.7,.3,1),
                              box-shadow .35s, border-color .35s; }
  .ob-card.lift:hover { transform: translateY(-5px);
    border-color: color-mix(in srgb, var(--ob-leaf) 45%, var(--ob-line));
    box-shadow: 0 20px 44px -26px rgba(10, 46, 40, .55); }
  .ob-card.lift .ob-icon { transition: transform .35s cubic-bezier(.34,1.4,.64,1); }
  .ob-card.lift:hover .ob-icon { transform: scale(1.12) rotate(-4deg); }

  /* a quiet underline that grows from the left on nav and text links */
  .ob-btn-ghost { position: relative; }
  .ob-btn-ghost::after { content: ''; position: absolute; left: 14px; right: 14px;
    bottom: 5px; height: 1.5px; background: var(--ob-accent-ink);
    transform: scaleX(0); transform-origin: 0 50%;
    transition: transform .32s cubic-bezier(.22,.7,.3,1); }
  .ob-btn-ghost:hover::after { transform: scaleX(1); }
}

/* Touch: no hover, so give the finger something instead. */
@media (hover: none), (pointer: coarse) {
  .ob-card.lift:active, .tilt:active { transform: scale(.985); }
  .ob-card.lift, .tilt { transition: transform .18s ease; }
}

/* every button acknowledges the press, on any input device */
.ob-btn { transition: transform .16s cubic-bezier(.22,.7,.3,1), filter .2s; }
.ob-btn:active { transform: translateY(1px) scale(.985); }

/* ------------------------------------------------------- counters */
.count { font-variant-numeric: tabular-nums; }

/* the stat strip: each figure gets a hairline that draws in under it */
.strip .ob-container > div { position: relative; }
.strip .ob-container > div::after { content: ''; position: absolute; left: 0; bottom: -8px;
  width: 26px; height: 2px; border-radius: 2px; background: var(--ob-leaf);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .5s .25s cubic-bezier(.22,.7,.3,1); }
.strip .ob-container > div.in::after { transform: scaleX(1); }

/* --------------------------------------------- video, lazy by click */
/* A facade: one thumbnail and a button until the visitor asks for the
   video. Nothing from YouTube loads, and no cookie is set, before the
   click - which also keeps the page fast on a phone. */
.video-band { background: linear-gradient(160deg, var(--ob-kelp), #06231E 70%);
  color: #E9F5EF; padding: 74px 0; position: relative; overflow: clip; }
.video-band h2 { color: #fff; }
.video-band .vb-lead { color: #A9CBBE; max-width: 60ch; }
.video-band .glow { position: absolute; width: 700px; height: 460px; left: 50%; top: 26%;
  transform: translate3d(-50%, calc(var(--sy, 0) * 30px), 0); z-index: 0;
  background: radial-gradient(circle, rgba(0, 200, 120, .22), transparent 70%);
  filter: blur(40px); pointer-events: none; }
.video-band .ob-container { position: relative; z-index: 1; }

.lyt { position: relative; display: block; width: 100%; border: 0; padding: 0;
  aspect-ratio: 16 / 9; border-radius: 18px; overflow: hidden; cursor: pointer;
  background: #06231E center/cover no-repeat; box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .8);
  transition: transform .4s cubic-bezier(.22,.7,.3,1), box-shadow .4s; }
.lyt::after { content: ''; position: absolute; inset: 0;
  background: linear-gradient(180deg, transparent 40%, rgba(0, 0, 0, .45));
  opacity: .8; transition: opacity .4s; }
.lyt .play { position: absolute; left: 50%; top: 50%; z-index: 2;
  transform: translate(-50%, -50%); width: 82px; height: 82px; border-radius: 50%;
  background: var(--ob-gradient); display: grid; place-items: center;
  box-shadow: 0 12px 34px -10px rgba(0, 200, 120, .8);
  transition: transform .35s cubic-bezier(.34,1.4,.64,1); }
.lyt .play svg { margin-left: 5px; }
/* a slow ring pulsing outward, so the play button reads as live */
.lyt .play::before { content: ''; position: absolute; inset: -6px; border-radius: 50%;
  border: 2px solid rgba(0, 200, 120, .55); animation: play-ring 2.6s ease-out infinite; }
@keyframes play-ring { 0% { transform: scale(1); opacity: .9; }
  100% { transform: scale(1.55); opacity: 0; } }
/* the caption carries its own scrim: the two thumbnails are a dark and
   a light frame, and white-on-gradient alone is not legible on both */
.lyt .cap { position: absolute; left: 16px; bottom: 14px; z-index: 2; font-size: 13.5px;
  font-weight: 700; color: #E9F5EF; padding: 6px 13px; border-radius: 999px;
  background: rgba(6, 35, 30, .72); backdrop-filter: blur(4px); }
@media (hover: hover) and (pointer: fine) {
  .lyt:hover { transform: translateY(-4px); box-shadow: 0 40px 80px -30px rgba(0, 0, 0, .9); }
  .lyt:hover .play { transform: translate(-50%, -50%) scale(1.09); }
  .lyt:hover::after { opacity: .55; }
}
.lyt:focus-visible { outline: 3px solid var(--ob-leaf); outline-offset: 3px; }
.lyt.is-playing { cursor: default; background: #000; }
.lyt.is-playing .play, .lyt.is-playing .cap, .lyt.is-playing::after { display: none; }
.lyt iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* the light/dark switch under the player */
.vswitch { display: inline-flex; margin-top: 18px; border-radius: 999px; padding: 3px;
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .14); }
.vswitch button { font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  border: 0; background: none; color: #A9CBBE; padding: 7px 16px; border-radius: 999px;
  transition: background .25s, color .25s; }
.vswitch button.on { background: rgba(0, 200, 120, .22); color: #fff; }
.vswitch button:focus-visible { outline: 2px solid var(--ob-leaf); outline-offset: 2px; }

/* ------------------------------------------------- page transition */
/* A short fade on the way out of a page, so navigation feels like one
   surface rather than a hard cut. Only for same-origin, plain clicks:
   the handler leaves modified clicks, new tabs and assets alone. */
body.ob-root { transition: opacity .22s ease; }
body.ob-root.is-leaving { opacity: 0; }

/* ------------------------------------------------ the marquee rail */
/* Used on the Designer page for the goals. Duplicated content scrolls
   seamlessly; it pauses on hover and on focus within. */
.rail { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent,
  #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent,
  #000 8%, #000 92%, transparent); }
/* Two groups, translated by exactly one group's width. The previous
   version laid all the chips out in one row and shifted by 50%, which
   is only seamless while the chips are wider than the rail: on a large
   screen five chips do not fill the width, so the row ran off to the
   left and the copy arrived from the right with a gap in front of it.

   min-width: 100% makes each group span the rail whatever the screen,
   and space-around distributes the chips inside it. Both groups are
   identical, so the loop still has no seam. */
.rail-track { display: flex; width: max-content;
  animation: rail 34s linear infinite; }
/* 100vw, not 100%: the track is width: max-content, so a percentage
   here would resolve against a box whose own width depends on this
   one. A viewport unit is definite and breaks that circle. The rail is
   full bleed, so 100vw is the width it needs to cover. */
.rail-group { display: flex; gap: 12px; flex: none; min-width: 100vw;
  justify-content: space-around; padding-right: 12px; box-sizing: border-box; }
.rail:hover .rail-track, .rail:focus-within .rail-track { animation-play-state: paused; }
@keyframes rail { to { transform: translateX(-50%); } }

/* ------------------------------------------------- hero fees demo */
/* (unchanged behaviour: the card plays as a sequence, rows land one
   after another, the tonnage and fee count up, checks pop) */
.panel.demo-live > * { opacity: 0; transform: translateY(12px);
  transition: opacity .45s cubic-bezier(.22,.7,.3,1), transform .45s cubic-bezier(.22,.7,.3,1); }
.panel.demo-live > *.on { opacity: 1; transform: none; }
.pill-pop-once { animation: pill-pop .45s cubic-bezier(.34,1.56,.64,1); }
.demo-phase .ob-small { display: inline-flex; align-items: center; gap: 8px; }
.demo-spin { width: 13px; height: 13px; border-radius: 50%; flex: none;
  border: 2px solid var(--ob-line); border-top-color: var(--ob-leaf);
  animation: demo-spin .8s linear infinite; }
@keyframes demo-spin { to { transform: rotate(360deg); } }
.panel.demo-live .demo-phase { transition: opacity .35s; }
.panel.demo-live .demo-phase.off { opacity: 0; height: 0; overflow: hidden; }

.demo-calc b { font-variant-numeric: tabular-nums; }
.demo-calc .ob-data { font-variant-numeric: tabular-nums; }
#ob-demo-pill { min-width: 96px; text-align: center; transition: background .25s, color .25s; }
#ob-demo-pill.is-wait { background: var(--ob-line); color: var(--ob-ink-2); }
#ob-demo-pill.is-on { animation: pill-pop .45s cubic-bezier(.34,1.56,.64,1); }
#ob-demo-pill .tick { display: inline-block; vertical-align: -1px; }
#ob-demo-pill.is-on .tick { animation: tick-draw .4s .1s both; }
@keyframes pill-pop { 0% { transform: scale(.72); } 100% { transform: scale(1); } }
@keyframes tick-draw { 0% { opacity: 0; transform: scale(.3) rotate(-18deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); } }

/* =====================================================================
   One switch for the lot. Anyone who has asked their system to calm
   down gets a completely static page, including the decorative loops
   that never depended on JS.
   ===================================================================== */
@media (prefers-reduced-motion: reduce) {
  .rv, .rv-l, .rv-r, .rv-scale {
    opacity: 1 !important; transform: none !important; transition: none !important; }
  .px, .aurora, .video-band .glow { transform: none !important; }
  .aurora i, .lyt .play::before, .rail-track { animation: none !important; }
  .aurora { opacity: .35; }
  .tilt.is-tilting, .mag.is-near { transform: none !important; }
  .ob-card.lift:hover { transform: none; }
  .ob-card.lift:hover .ob-icon { transform: none; }
  .strip .ob-container > div::after { transition: none; transform: scaleX(1); }
  .top .ob-container, .top .brandlink img { transition: none; }
  .demo-spin { animation: none; }
  .demo-phase { display: none; }
  #ob-demo-pill.is-on, #ob-demo-pill.is-on .tick, .pill-pop-once { animation: none; }
  body.ob-root, body.ob-root.is-leaving { opacity: 1 !important; transition: none !important; }
  .scroll-progress { display: none; }
  .ob-btn:active { transform: none; }
}

/* ---------------------------------------------------------- live figures
   Every number about the brain is written in by assets/brain.js from the
   public coverage feed. Until it lands the authored words stand, so
   nothing here should read as a placeholder; the two classes below only
   let the page tighten its spacing without an inline style. */
.flush { padding-top: 0; }
.measure-66 { max-width: 66ch; }
.covnote-tight { margin-bottom: 10px; }
html.brain-live [data-brain] { font-variant-numeric: tabular-nums; }
