/* ══ AeroLogic hero — Variation 4 · Minimal Fintech ════════════════════
   One elevated object on an almost empty page. The market drifts past at
   8% ink; the message that got through is the only thing with an edge, a
   shadow and full contrast.
   ═══════════════════════════════════════════════════════════════════ */

.fn {
  --ink:      #0A0A0B;
  --ink-2:    #4A4A50;
  --ink-3:    #6F6F76;
  /* --ink-4 is NON-TEXT ONLY. At 2.35:1 on this ground it cannot carry
     text at any size; on a near-white page there is no AA-compliant
     fourth text tier, so the muted text tier is --ink-3 and this step
     is reserved for marks, rules and inactive states — the same
     restriction --signal already carries. */
  --ink-4:    #A8A8AE;

  --paper:    #FEFEFE;
  --paper-2:  #FFFFFF;   /* the card sits a shade above the page */
  --hair:     #E8E8EA;
  /* lifted from var(--hair-2): --ink-3 measured 4.46:1 on the old value, four
     hundredths under AA. At #F5F5F7 the muted tier clears on every
     ground the page uses. */
  --hair-2:   #F5F5F7;

  /* Brand accent. --signal is 3.41:1 on this ground — NON-TEXT ONLY, the
     same restriction the green carried; --signal-d is the text tier at
     6.65:1, against the green's 6.56:1, so the pair keeps the exact
     relationship the rest of the page was built on. */
  --signal:   #E16602;
  --signal-d: #E16602;
  --signal-w: rgba(225,102,2,.08);

  /* Market direction is data, not brand, so it did not follow the brand
     to orange: --signal-d and --down sit 21° apart on the hue wheel at
     almost the same lightness, and up-versus-down is the one colour
     distinction a market page cannot afford to blur. Up keeps the
     green; only the brand moved. */
  --up:       #0A6B39;
  --down:     #B03A2E;

  --gut: clamp(20px, 5vw, 72px);

  /* ── type scale ────────────────────────────────────────────────────
     Four voices, and nothing between them. Before this block the page
     carried fourteen static sizes between 10px and 19px and fifteen
     negative tracking values, several a thousandth of an em apart — a
     difference no reader can see and no maintainer can honour.
     ═══════════════════════════════════════════════════════════════ */
  --fs-display: clamp(50px, 6.6vw, 108px);   /* the hero line, once */
  --fs-h2:      clamp(31px, 4.1vw, 62px);    /* every section heading */
  --fs-deck:    clamp(14.5px, 1.1vw, 17px);  /* every section deck */
  --fs-body:    15px;
  --fs-sm:      13.5px;
  --fs-xs:      12.5px;
  --fs-2xs:     11.5px;
  --fs-3xs:     11px;
  --fs-lead:    clamp(14.5px, 1.12vw, 16.5px);  /* card titles, CTAs, questions */
  --fs-read:    clamp(13.5px, 1.05vw, 15px);    /* answers, quotes, long-measure */
  /* the two statement numerals — 900+ and the price — are the only type
     on the page that earns tracking past the -.045em floor; below ~100px
     this value would be too tight to read. */
  --tr-numeral: -.05em;

  /* tracking tightens as type grows; the floor is -.045em */
  --tr-display: -.045em;
  --tr-h2:      -.04em;
  --tr-lead:    -.02em;   /* 15px+ UI text, brand, buttons */
  --tr-body:    -.012em;
  --tr-deck:    -.006em;  /* long-measure reading text */
  --tr-label:   .16em;    /* section label, uppercase */
  --tr-tag:     .06em;    /* inline tag riding content, uppercase */

  --lh-display: .96;
  --lh-h2:      1.05;
  --lh-deck:    1.6;
  --lh-ui:      1.55;
  --lh-tight:   1.45;   /* short UI text that wraps to 2–3 lines */

  /* ── space scale ───────────────────────────────────────────────────
     Section rhythm was five near-identical pairs; the eye reads the
     inconsistency long before it reads the 10px. One rhythm now, with
     the hero and footer keeping their own tighter bookend measure.
     ═══════════════════════════════════════════════════════════════ */
  --sec-pt:    clamp(84px, 14vh, 180px);
  --sec-pb:    clamp(76px, 12vh, 150px);
  --sec-gap:   clamp(44px, 6.5vh, 82px);  /* header block → content */
  --deck-gap:  clamp(18px, 2.6vh, 26px);  /* heading → deck */

  /* inner steps, ~1.4× apart. Before this the page held fifty-five
     one-off clamp pairs; these five cover the repeats. */
  --sp-1: clamp(12px, 1.4vw, 18px);
  --sp-2: clamp(16px, 2.2vw, 26px);
  --sp-3: clamp(20px, 2.8vw, 32px);
  --sp-4: clamp(28px, 3.6vw, 48px);
  --sp-5: clamp(40px, 4.8vw, 64px);
  --sp-6: clamp(44px, 7vh, 92px);

  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: Onest, "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Below 620px the section rhythm tightens once, on the token — the two
   sections that used to override their own padding here left the other
   four at desktop rhythm on a phone. */
@media (max-width: 620px) {
  .fn {
    --sec-pt:  clamp(70px, 12vh, 120px);
    --sec-pb:  clamp(64px, 10vh, 104px);
    --sec-gap: clamp(34px, 5vh, 56px);
  }
}

/* ══════════════════════════════════════════════════════════════════════
   AOS, TUNED BACK TO THIS PAGE
   The section reveals are AOS's now (aos@2.3.4, by CDN, initialised in
   index.html). Two of its defaults would have restyled the page on the
   way in, so they are set back to what the hand-written reveal did:

   · fade-up ships a 100px rise. This page rose 18–20px. At 100px the
     text arrives from off the screen and the reveal becomes the thing
     you notice; at 20px the section simply settles.
   · the curve. AOS's built-ins are all softer or overshoot — the
     nearest, ease-out-back, bounces — so the page's own arrival curve
     is restated here rather than approximated by an easing name.

   Both need to outrank AOS's own rules and both do it the same way: a
   selector repeated to (0,3,0) and a stylesheet that loads second.

   One thing the markup had to give up rather than override: the stagger
   was 80ms a step, and AOS ships delay rules only in 50ms increments —
   data-aos-delay="80" matches no rule at all and silently lands on zero.
   The steps are 100ms now, which is a real rule and reads the same.

   AOS is not given the asset animations. The bento illustrations and
   the how-it-works diagram are choreography — staggered draws, paths
   plotting themselves, loops — and AOS does opacity and transform on
   one element. Those keep their own triggers in main2.js.
   ═══════════════════════════════════════════════════════════════════ */
[data-aos="fade-up"][data-aos="fade-up"] { transform: translate3d(0, 20px, 0); }

[data-aos][data-aos][data-aos] {
  transition-timing-function: cubic-bezier(.16, .84, .28, 1);
}

.fn *, .fn *::before, .fn *::after { box-sizing: border-box; }
:where(.fn a) { color: inherit; text-decoration: none; }
.fn button { font: inherit; color: inherit; background: none; border: 0; }

/* ── browser surfaces ─────────────────────────────────────────────── */
.fn ::selection { background: rgba(225,102,2,.16); color: var(--ink); }
.fn { caret-color: var(--signal-d); scrollbar-color: var(--ink-3) var(--paper); scrollbar-width: thin; }
.fn ::-webkit-scrollbar { width: 11px; height: 11px; }
.fn ::-webkit-scrollbar-track { background: var(--paper); }
.fn ::-webkit-scrollbar-thumb { background: var(--hair); border-radius: 99px; border: 3px solid var(--paper); }
.fn ::-webkit-scrollbar-thumb:hover { background: var(--ink-4); }
.fn :focus-visible { outline: 1.5px solid var(--signal-d); outline-offset: 4px; border-radius: 3px; }

.fn-vh {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* ── nav ──────────────────────────────────────────────────────────── */
/* Sticky on every viewport — below 900px there's otherwise no control
   on screen once the hero has scrolled past. The shrink (padding, logo
   height) is scoped to 901px+ below; mobile stays sticky at full size. */
.fn-nav {
  display: flex; align-items: center; gap: clamp(24px, 4vw, 56px);
  padding: 22px var(--gut);
  position: sticky; top: 0; z-index: 50;
  background: rgba(254,254,254,.88);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
  transition: padding .28s cubic-bezier(.2,.8,.2,1), border-color .28s ease;
}
.fn-nav.is-scrolled { border-bottom-color: var(--hair); }
/* One image, mark and wordmark together, rather than the mark beside
   type set in the page's own face — the lockup is the brand's, and the
   height is what's fixed: the width follows the file so the ratio can
   never be pulled out of shape by a stylesheet. */
.fn-brand { display: inline-flex; align-items: center; }
.fn-brand img { display: block; height: 30px; width: auto; transition: height .28s cubic-bezier(.2,.8,.2,1); }
/* centred independently of the flex flow, so brand and CTA can sit at
   the two ends without fighting the links group for flex space */
.fn-links {
  display: flex; gap: clamp(20px, 2.6vw, 32px);
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
}
.fn-links a {
  font-size: var(--fs-body); font-weight: 400; color: var(--ink-2);
  transition: color .18s ease;
}
.fn-links a:hover { color: var(--ink); }
.fn-navcta {
  margin-left: auto;
  padding: 8px 17px 9px; border-radius: 99px;
  background: var(--ink); color: var(--paper-2);
  font-size: var(--fs-body); font-weight: 500; letter-spacing: var(--tr-body);
  transition: background .18s ease;
}
.fn-navcta:hover { background: var(--signal-d); }

@media (min-width: 901px) {
  .fn-nav.is-scrolled { padding-top: 14px; padding-bottom: 14px; }
  /* .fn-nav.is-scrolled .fn-brand img { height: 23px; } */
}

/* the nav is now sticky, so in-page anchors (#what, #how, #proof …)
   need clearance or their heading lands underneath it */
html { scroll-padding-top: 84px; }
/* the bar is 64px here, not 89 — clearance follows it down, or every
   anchored heading lands with 25px of dead air above it */
@media (max-width: 900px) { html { scroll-padding-top: 72px; } }

/* ── hero: one centred column of argument ─────────────────────────── */
.fn-hero {
  display: flex; flex-direction: column; align-items: center;
  padding: clamp(52px, 9vh, 116px) var(--gut) clamp(40px, 6vh, 72px);
  text-align: center;
}

.fn-h1 {
  margin: 0; max-width: 15ch;
  font-size: var(--fs-display);
  font-weight: 500;
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  text-wrap: balance;
}
.fn-stop { color: var(--signal); }

.fn-deck {
  margin: calc(var(--deck-gap) * 1.25) 0 0; max-width: 60ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  color: var(--ink-2);
  text-wrap: pretty;
}

.fn-actions {
  display: flex; align-items: center; flex-wrap: wrap; justify-content: center;
  gap: 12px 22px; margin-top: clamp(28px, 3.6vh, 40px);
}
.fn-cta {
  padding: 14px 26px 15px; border-radius: 99px;
  background: var(--ink); color: var(--paper-2);
  font-size: var(--fs-body); font-weight: 500; letter-spacing: var(--tr-body);
  box-shadow: 0 10px 26px -14px rgba(10,10,11,.55);
  transition: background .2s ease, box-shadow .25s ease, transform .25s cubic-bezier(.2,.8,.2,1);
}
.fn-cta:hover {
  background: var(--signal-d);
  box-shadow: 0 16px 34px -14px rgba(10,107,57,.55);
  transform: translateY(-1px);
}
.fn-alt {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 14px 8px 15px;
  font-size: var(--fs-body); font-weight: 500; color: var(--ink-2);
  transition: color .18s ease;
}
.fn-alt svg { transition: transform .28s cubic-bezier(.2,.8,.2,1); }
.fn-alt:hover { color: var(--ink); }
.fn-alt:hover svg { transform: translateX(3px); }

/* ── the stage: noise band with one legible object in it ──────────── */
.fn-stage {
  position: relative; width: 100%;
  margin-top: var(--sp-6);
  display: flex; justify-content: center;
}

/* the market, drifting past at the edge of visibility */
.fn-noise {
  position: absolute; inset: -30px 0;
  overflow: hidden; pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent);
}
.fn-chip {
  position: absolute;
  padding: 5px 11px 6px; border-radius: 99px;
  border: 1px solid var(--hair-2);
  background: var(--paper-2);
  font-size: var(--fs-xs); font-weight: 500; letter-spacing: var(--tr-deck);
  color: var(--ink-4);
  white-space: nowrap;
  opacity: 0;
  animation: fn-drift var(--dur) linear var(--delay) infinite;
}
.fn-chip span { margin-left: 7px; font-weight: 400; color: #C9C9CE; }
@keyframes fn-drift {
  0%   { opacity: 0;   transform: translate3d(0, 0, 0) scale(.94); }
  14%  { opacity: .95; }
  76%  { opacity: .95; }
  100% { opacity: 0;   transform: translate3d(var(--dx), var(--dy), 0) scale(.94); }
}

/* ── the card ─────────────────────────────────────────────────────── */
.fn-cardwrap { position: relative; z-index: 1; width: min(520px, 100%); }

.fn-card {
  display: grid;                       /* panels share one cell, so the
                                          card never jumps height mid-swap */
  padding: 22px 24px 20px;
  text-align: left;
  border: 1px solid var(--hair);
  border-radius: 14px;
  background: var(--paper-2);
  box-shadow:
    0 1px 2px rgba(10,10,11,.04),
    0 10px 22px -14px rgba(10,10,11,.14),
    0 34px 60px -34px rgba(10,10,11,.20);
}

.fn-panel { grid-area: 1 / 1; }
.fn-panel[hidden] { display: block; visibility: hidden; opacity: 0; pointer-events: none; }
.fn-panel.is-on  { animation: fn-swap .5s cubic-bezier(.2,.8,.2,1); }
@keyframes fn-swap {
  from { opacity: 0; transform: translateY(9px); filter: blur(4px); }
  to   { opacity: 1; transform: none; filter: none; }
}

.fn-panel-head {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 15px; border-bottom: 1px solid var(--hair-2);
}
.fn-kind {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 11px 6px; border-radius: 99px;
  background: var(--signal-w);
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: var(--tr-deck);
  color: var(--signal-d);
}
.fn-kind i { width: 6px; height: 6px; border-radius: 50%; background: var(--signal); }
.fn-time {
  margin-left: auto;
  font-size: var(--fs-xs); font-weight: 400; color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.fn-tick {
  display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap;
  margin: 17px 0 0;
}
.fn-tick b { font-size: 27px; font-weight: 600; letter-spacing: var(--tr-h2); }
.fn-tick span { font-size: var(--fs-sm); font-weight: 400; color: var(--ink-3); }
.fn-tick .fn-px {
  font-size: 21px; font-weight: 600; font-style: normal;
  letter-spacing: var(--tr-h2); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.fn-tick .fn-up,
.fn-tick .fn-dn { font-size: var(--fs-sm); font-weight: 600; font-variant-numeric: tabular-nums; }

.fn-rows { margin: 15px 0 0; }
.fn-rows > div {
  display: flex; align-items: baseline; gap: 16px;
  padding: 9px 0;
}
.fn-rows > div + div { border-top: 1px solid var(--hair-2); }
.fn-rows dt {
  flex: 0 0 76px;
  font-size: var(--fs-xs); font-weight: 400; color: var(--ink-3);
}
.fn-rows dd {
  margin: 0; flex: 1;
  font-size: var(--fs-sm); font-weight: 500; letter-spacing: var(--tr-body); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.fn-up { color: var(--up); }
.fn-dn { color: var(--down); }
.fn-rows .fn-tag {
  margin-left: 2px; font-style: normal; font-weight: 400;
  font-size: var(--fs-3xs, 11px); letter-spacing: var(--tr-tag, 0);
  color: var(--ink-4, var(--ink-3));
}

/* ── the four dots ────────────────────────────────────────────────── */
.fn-dots {
  display: flex; justify-content: center; align-items: center; gap: 2px;
  margin-top: 12px;
}
/* the pill is a fixed 22px box scaled down, not an animated width:
   growing a real width relayouts the whole row on every swap */
/* the hit area is a full 44px square; the 22px pill is drawn inside it,
   so the target passes on touch without the control growing visually */
.fn-dots button {
  position: relative; width: 44px; height: 44px; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.fn-dots button::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 22px; height: 7px; border-radius: 99px;
  background: #D7D7DB;
  transform: translate(-50%, -50%) scaleX(.318);
  transition: transform .38s cubic-bezier(.2,.8,.2,1), background-color .3s ease;
}
.fn-dots button:hover::before { background: var(--ink-4); }
.fn-dots button.is-on::before {
  background: var(--signal);
  transform: translate(-50%, -50%) scaleX(1);
}

/* ── arrival ──────────────────────────────────────────────────────── */
.fn-h1, .fn-deck, .fn-actions, .fn-stage {
  animation: fn-in .9s cubic-bezier(.16,.84,.28,1) backwards;
}
.fn-h1      { animation-delay: .05s }
.fn-deck    { animation-delay: .14s }
.fn-actions { animation-delay: .22s }
.fn-stage   { animation-delay: .3s }
@keyframes fn-in { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

/* ── responsive ───────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .fn-links { display: none; }
  .fn-h1 { max-width: 13ch; }
  .fn-noise { inset: -20px 0; }
}

@media (max-width: 620px) {
  .fn-h1 { font-size: clamp(40px, 12.5vw, 62px); max-width: 100%; }
  .fn-actions { flex-direction: column; align-self: stretch; }
  .fn-cta { text-align: center; }
  .fn-alt { justify-content: center; }
  /* the drifting chips read as clutter at this width, not as the market */
  .fn-noise { display: none; }
  .fn-card { padding: 18px 18px 16px; border-radius: 12px; }
  .fn-tick b { font-size: 24px; }
  .fn-rows > div { flex-direction: column; gap: 3px; }
  .fn-rows dt { flex: none; }
}

@media (prefers-reduced-motion: reduce) {
  .fn-h1, .fn-deck, .fn-actions, .fn-stage { animation: none; opacity: 1; transform: none; }
  .fn-panel.is-on { animation: none; }
  .fn-chip { animation: none; opacity: .95; }
  .fn * { transition-duration: .01ms !important; }
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 2 · The scale of the problem
   Same world, same tokens, same centred composition as the hero above.
   The hero holds one legible object in a field of noise; this section
   does the same thing with the number itself, pressed between the eight
   conditions a trader is expected to check on every one of 912 stocks.
   ═══════════════════════════════════════════════════════════════════ */

.fn-sc {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  text-align: center;
}

/* the section head sits deliberately below the hero's h1 in weight and
   scale: the number is the largest thing on this screen, not this line */
.fn-sc-h {
  margin: 0; max-width: 21ch;
  font-size: var(--fs-h2);
  font-weight: 500; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  text-wrap: balance;
}

/* ── the stack: four conditions, the number, four more ─────────────── */
.fn-sc-stack {
  width: 100%; max-width: 1180px;
  margin-top: var(--sp-6);
}

.fn-sc-band {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 0; padding: 0; list-style: none;
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
}
.fn-sc-term {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  padding: 15px 12px 16px;
  font-size: clamp(11px, .88vw, 12.5px);
  font-weight: 500; letter-spacing: var(--tr-tag); text-transform: uppercase;
  color: var(--ink-3);
  animation: fn-sc-pull 9.6s linear infinite;
  animation-delay: calc(var(--i) * -1.2s);
}
.fn-sc-term + .fn-sc-term { box-shadow: inset 1px 0 0 var(--hair-2); }

/* the marker each condition raises when it wants you */
.fn-sc-term i {
  flex: none; width: 5px; height: 5px; border-radius: 50%;
  background: #D2D2D6;
  animation: fn-sc-mark 9.6s linear infinite;
  animation-delay: calc(var(--i) * -1.2s);
}

/* one condition at a time, forever, in rotation: something is always
   asking for you, and it is never the same thing twice running */
@keyframes fn-sc-pull {
  0%, 9%, 100% { color: var(--ink-3); }
  1.5%, 6%     { color: var(--ink); }
}
@keyframes fn-sc-mark {
  0%, 9%, 100% { background: #D2D2D6; box-shadow: 0 0 0 0 rgba(225,102,2,0); }
  1.5%         { background: var(--signal); box-shadow: 0 0 0 0 rgba(225,102,2,.4); }
  6%           { background: var(--signal); box-shadow: 0 0 0 7px rgba(225,102,2,0); }
}

/* ── the number ───────────────────────────────────────────────────── */
.fn-sc-figure { padding: clamp(28px, 5.5vh, 68px) 0 clamp(30px, 5.5vh, 70px); }

.fn-sc-n {
  margin: 0;
  font-size: clamp(104px, 25.5vw, 392px);
  font-weight: 500;
  line-height: .8;
  letter-spacing: var(--tr-numeral);
  font-variant-numeric: lining-nums;
}
/* NumberFlow renders into a shadow root whose :host declares its own
   line-height, and a declared value beats an inherited one — so the .8
   above never reached it and the number arrived 237px taller than the
   span it replaced, pushing "saham Indonesia" down the page. Both the
   line-height and the mask are set back here. The mask is the gradient
   the digits roll behind; .25em of it at this size is 98px of fade top
   and bottom, which is most of the extra height. .08em is still 31px. */
.fn-sc-count {
  line-height: .8;
  --number-flow-mask-height: .08em;
}

/* Set at full size the plus renders as a fat cross that fights the
   digits. Raised and small it reads as notation: 900, and counting. */
.fn-sc-plus {
  display: inline-block;
  font-size: .33em; font-weight: 500;
  letter-spacing: 0;
  color: var(--signal);
  margin-left: .1em;
  /* the plus should not shift the digits off the section's centre line */
  margin-right: -.46em;
  transform: translateY(-1.38em);
}

.fn-sc-lab {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-deck);
  font-weight: 400; letter-spacing: var(--tr-body);
  color: var(--ink-2);
}


/* ── responsive ───────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .fn-sc-h { max-width: 17ch; }
  .fn-sc-band { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* wrapped onto two rows, the second pair needs its own rule */
  .fn-sc-term:nth-child(n+3) { box-shadow: inset 0 1px 0 var(--hair-2); }
  .fn-sc-term:nth-child(4)   { box-shadow: inset 1px 0 0 var(--hair-2), inset 0 1px 0 var(--hair-2); }
  .fn-sc-term:nth-child(3)   { box-shadow: inset 0 1px 0 var(--hair-2); }
}

@media (max-width: 620px) {
  .fn-sc-n { font-size: clamp(88px, 33vw, 150px); }
  .fn-sc-term { gap: 7px; padding: 13px 8px 14px; }
}

@media (prefers-reduced-motion: reduce) {
  /* the rotation is the section's only motion; without it the band is
     simply a list of eight conditions, which still reads correctly */
  .fn-sc-term, .fn-sc-term i { animation: none; }
}


/* ── shared section utilities & arrival ────────────────────────────── */
/* only one treatment is shown at a time; [hidden] has to out-specify the
   display: flex the sections set on themselves */
.fn-sc[hidden] { display: none; }


/* ══════════════════════════════════════════════════════════════════════
   SECTION 3 · THE MEMBERSHIP — a bento of live intelligence
   Not a feature grid. Eight cards of different weight and proportion,
   each showing the thing it names rather than describing it: a scan in
   progress, a chart at the moment a condition is met, a filing feed, the
   index changing character, the morning note, the room.

   Rules kept: no new tokens, no new face, one accent. Every card renders
   complete with animation and script switched off — the motion only ever
   adds. No figure on this section is a claim about performance.
   ═══════════════════════════════════════════════════════════════════ */

.bx {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  border-top: 1px solid var(--hair-2);
}

/* ── the head ─────────────────────────────────────────────────────── */
.bx-top {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  margin-bottom: var(--sec-gap);
}
.bx-h {
  margin: 0; max-width: 18ch;
  font-size: var(--fs-h2);
  font-weight: 500; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  text-wrap: balance;
}
.bx-deck {
  margin: var(--deck-gap) 0 0; max-width: 58ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ── the bento ────────────────────────────────────────────────────── */
/* Three columns and three rows, named rather than auto-placed, so the
   shape is legible in one glance and the markup is free to run in
   reading order underneath it.

   The middle column is half again as wide as the two beside it (6fr to
   4fr) and the lead card holds both of its rows, so the pair of signals
   it carries is the one thing on the page with real size. The two
   narrow columns each carry a stack of two, which is what gives the
   middle column its height — the lead card never asks for any of its
   own.

   The closing band reuses the same three tracks rather than inventing a
   split of its own: the mark takes the left column, Tips takes the
   middle and the right. That puts the one seam in the band exactly on
   the grid line the cards above it already share, so the section reads
   as one object and not as a grid with a strip under it — and it gives
   Tips the width its picture was drawn at, a 1240×230 band rather than
   a column. */
.bx-grid {
  display: grid;
  width: 100%; max-width: 1340px;
  grid-template-columns: minmax(0, 4fr) minmax(0, 6fr) minmax(0, 4fr);
  grid-template-rows: minmax(212px, auto) minmax(212px, auto) minmax(176px, auto);
  grid-template-areas:
    "out   lead  rev"
    "swing lead  corp"
    "logo  tips  tips";
  gap: clamp(12px, 1.05vw, 18px);
}

.bx-out   { grid-area: out; }
.bx-swing { grid-area: swing; }
.bx-lead  { grid-area: lead; }
.bx-rev   { grid-area: rev; }
.bx-corp  { grid-area: corp; }
.bx-tips  { grid-area: tips; }
.bx-logo  { grid-area: logo; }

/* ── the closing band ─────────────────────────────────────────────── */
/* Deliberately the emptiest thing in the section. It carries the same
   edge, radius and ground as the six cards so it sits in the grid
   rather than under it, but nothing else — the space is the point, and
   the mark is what the space is for.

   The mark sits at the centre of a field of rings that leave it and run
   off the edge of the panel. Six cards above have just finished saying
   that the product is a thing that watches and then sends; the closing
   band says it once more without a word, and it is the only place in
   the section where the picture is allowed to reach the crop. */
.bx-logo {
  position: relative; overflow: hidden;
  display: grid; place-items: center;
  padding: var(--sp-3);
  border: 1px solid var(--hair);
  border-radius: 16px;
  background: var(--paper-2);
  background-image: radial-gradient(64% 64% at 50% 50%, var(--signal-w), transparent 70%);
}
.bx-logo-mark {
  position: relative;   /* over the field */
  width: clamp(52px, 5.2vw, 84px); height: auto;
  display: block;
}

/* ── the ripple ───────────────────────────────────────────────────── */
/* MagicUI's Ripple, ported out of React. The component is eight nested
   circles that hold their positions and pulse: each one 70px larger
   than the last, .03 less opaque, its animation .06s later, and every
   one of them shrinking to scale(.9) at the middle of the cycle and
   coming back. The wave is the stagger, not the scaling — which is why
   this reads as water rather than as a sonar sweep.

   Three deliberate departures from the source:

   · size is a percentage of the panel rather than a pixel prop, so the
     field fills the cell at one column, at two, and full width on a
     phone without a different mainCircleSize at every breakpoint. The
     ratio between the main circle and the step is a little wider than
     the component's 3:1: the innermost ring is pushed out so the mark
     sits in clear ground rather than against a line, and the step is
     opened a touch so the rings read as separate passes.
   · bg-foreground/25 becomes the accent, and the source's .03-per-step
     fade is nearly flattened: the ring is the drawing here, so every
     one of the eight is held at close to the same weight and the field
     reads as a set of orange lines rather than as one ring dissolving
     into the ground. What keeps eight near-opaque layers from stacking
     into a slab is the fill, dropped to a twentieth: at the centre the
     eight overlaps still land under a fifth of an ink, so the mark sits
     on a glow instead of a blob. The vertical mask, not the opacity
     ramp, is what carries the field away.
   · shadow-xl on each circle is dropped, as every drop shadow in every
     other port on this page was. */
.lg-ripple {
  position: absolute; inset: 0;
  pointer-events: none; user-select: none; -webkit-user-select: none;
  -webkit-mask-image: linear-gradient(to bottom, #fff, transparent);
          mask-image: linear-gradient(to bottom, #fff, transparent);
}
.lg-c {
  position: absolute; top: 50%; left: 50%;
  width: calc(48% + var(--i) * 14%);
  aspect-ratio: 1;
  border: 1px solid var(--signal);
  border-radius: 50%;
  background: rgba(225,102,2,.045);
  opacity: calc(.62 - var(--i) * .018);
  transform: translate(-50%, -50%) scale(1);
  animation: lg-ripple var(--lg-duration, 3.2s) ease calc(var(--i) * .06s) infinite;
}

@keyframes lg-ripple {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  50%      { transform: translate(-50%, -50%) scale(.9); }
}

@media (prefers-reduced-motion: reduce) {
  /* the rings are a field before they are an animation; they simply
     stop where they stand */
  .lg-c { animation: none; }
}

/* ── the card ─────────────────────────────────────────────────────── */
.bx-card {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column;
  padding: var(--sp-2);
  border: 1px solid var(--hair);
  border-radius: 16px;
  background: var(--paper-2);
  box-shadow: 0 1px 2px rgba(10,10,11,.035),
              0 14px 30px -24px rgba(10,10,11,.22);
  transition: border-color .25s ease, box-shadow .3s ease, transform .3s cubic-bezier(.2,.8,.2,1);
}
.bx-card:hover {
  border-color: #DCDCE0;
  box-shadow: 0 1px 2px rgba(10,10,11,.04),
              0 22px 44px -26px rgba(10,10,11,.3);
  transform: translateY(-2px);
}

.bx-head { display: flex; align-items: center; gap: 12px; }
.bx-name {
  margin: 0;
  font-size: var(--fs-lead);
  font-weight: 600; letter-spacing: var(--tr-lead); color: var(--ink);
}
/* the live mark goes only on the cards that are genuinely watching
   something; a briefing and a chat room are not live */
.bx-live {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: auto;
  font-size: var(--fs-3xs); font-weight: 500; letter-spacing: var(--tr-tag);
  text-transform: uppercase; color: var(--ink-3);
}
.bx-live i {
  width: 5px; height: 5px; border-radius: 50%; background: var(--signal);
}
/* the same slot, for a card that is filed at a time rather than watched */
.bx-stamp {
  margin-left: auto;
  font-size: var(--fs-3xs); font-weight: 500; letter-spacing: var(--tr-tag);
  text-transform: uppercase; color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

.bx-desc {
  margin: 7px 0 0; max-width: 34ch;
  font-size: var(--fs-sm); line-height: var(--lh-ui);
  color: var(--ink-3);
  text-wrap: pretty;
}
.bx-lead .bx-desc, .bx-tips .bx-desc { max-width: none; }

/* every visual hangs from the bottom of its card, so the copy at the top
   of eight cards of four different heights still lines up */
.bx-vis { position: relative; margin-top: auto; padding-top: var(--sp-2); }

.bx-scanfoot {
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
  margin-top: 14px;
  font-size: var(--fs-xs); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.bx-scanfoot b { font-weight: 600; color: var(--ink); }
.bx-scanfoot span:last-child { margin-left: auto; }

/* ── the two tall cards ───────────────────────────────────────────── */
/* Spanning two rows, these get their height from the stack beside them.
   Their visuals have to grow into it — pinned to the bottom of the card
   the way the short ones are, they leave a void above and the card reads
   as unfinished rather than as spacious. */
.bx-lead .bx-vis, .bx-corp .bx-vis { display: flex; flex-direction: column; flex: 1; }
@keyframes bx-pull {
  0%, 22%, 100% { opacity: .78; }
  3%, 17%       { opacity: 1; }
}
/* ── arrival ──────────────────────────────────────────────────────────
   Per card, not per section. This grid is six cards and the better part
   of two screens tall; gated on the section, the illustrations at the
   bottom drew themselves — arcs plotting, dots running their routes,
   the beam sweeping the candles — while the reader was still on the
   first card, and were sitting finished by the time anyone arrived.

   Which is also why the .bx-card entrance drops the calc(var(--d) * 60ms)
   stagger it used to carry. That existed to walk six cards on in order
   from a single trigger; with each card waiting for its own arrival the
   reader's scrolling is the stagger, and a card that pauses 360ms after
   coming into view just reads as slow.
   ═══════════════════════════════════════════════════════════════════ */
.bx.js-r .bx-card, .bx.js-r .bx-logo { opacity: 0; transform: translateY(16px); }
.bx.js-r .bx-card.is-live, .bx.js-r .bx-logo.is-live {
  opacity: 1; transform: none;
  transition: opacity .7s cubic-bezier(.16,.84,.28,1),
              transform .7s cubic-bezier(.16,.84,.28,1);
}

/* ── responsive ───────────────────────────────────────────────────── */
@media (max-width: 1180px) {
  /* Two tracks. The middle column has nothing left to give: a chart with
     two alert tables under it stops reading as a chart long before it
     stops fitting, so the lead card takes the full width across the top
     and the other four pair off beneath it, keeping the four/two/one
     rhythm the desktop grid has. The closing band keeps its split,
     which at two tracks is an even one. */
  .bx-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: none;
    grid-auto-rows: minmax(196px, auto);
    grid-template-areas:
      "lead  lead"
      "out   swing"
      "rev   corp"
      "logo  tips";
  }
}

@media (max-width: 760px) {
  /* One track, and the mark closes the section rather than sitting in
     the middle of it. */
  .bx-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    grid-auto-rows: auto;
    grid-template-areas:
      "lead"
      "out"
      "swing"
      "rev"
      "corp"
      "tips"
      "logo";
  }
  .bx-desc { max-width: 42ch; }
  /* A square field sliced into a very wide, very short cell arrives as a
     set of near-vertical stripes rather than as rings, so the band is
     held to roughly the proportion the picture was drawn at. */
  .bx-logo { min-height: clamp(280px, 62vw, 440px); }
  .bx-logo-mark { width: clamp(58px, 11vw, 88px); }
  /* the circles are sized off the panel's width, which at full bleed is
     twice what it is in a column — so the base and the step both come
     down to hold the same ring spacing, and the same clearance around
     the mark, that the column gets */
  .lg-c { width: calc(32% + var(--i) * 9.4%); }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .bx-card, .bx.js-r .bx-logo { opacity: 1; transform: none; }
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 4 · THE PHILOSOPHY — from market noise to a clearer decision
   The reduction is not described, it is drawn: 900+ enters on the left,
   six stages later one decision leaves on the right, and the drawing
   says where AeroLogic stops by drawing a frame around the part it does
   and leaving the last two stages outside it.

   Ownership is set twice over, so it survives being read in greyscale:
   by that frame, which is labelled, and by colour — the accent belongs
   to the machine's half, the reader's ink to the reader's. Nothing else
   is coloured, which is why the two connectors read as a handover.
   ═══════════════════════════════════════════════════════════════════ */

.fl {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  border-top: 1px solid var(--hair-2);
}

/* ── the head ─────────────────────────────────────────────────────── */
.fl-top {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; margin-bottom: var(--sec-gap);
}
.fl-h {
  margin: 0; max-width: 17ch;
  font-size: var(--fs-h2);
  font-weight: 500; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  text-wrap: balance;
}
.fl-deck {
  margin: var(--deck-gap) 0 0; max-width: 56ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ── the flow ─────────────────────────────────────────────────────────
   Ported from assets/aerologic-flow.html — see the note on the markup.
   The asset is one 1932-wide SVG; here the row is HTML so it can turn
   into a column below 960px with every label at full size, and the
   icons keep the source's own path data inside an 88-unit window cut
   from the original 1932 canvas.

   The asset's second pass is a quiet drawing: no filled plates, no
   panel wash, one hairline around the engine and a single accent that
   appears only where something is actually moving. That restraint is
   the point of the port — the diagram carries six labels and it can
   only carry them if almost nothing else asks to be looked at.
   ═══════════════════════════════════════════════════════════════════ */
.fw {
  width: 100%; max-width: 1240px; margin: 0;
  --fw-ic:      clamp(58px, 5.8vw, 78px);       /* the icon's own window */
  --fw-col:     clamp(100px, 8.8vw, 124px);     /* one stage, either half */
  --fw-card-w:  clamp(128px, 12.6vw, 172px);
  --fw-card-h:  clamp(100px, 10vw, 130px);
  --fw-top:     clamp(30px, 3.4vw, 46px);       /* frame edge → icon top */
  --fw-gap:     clamp(12px, 1.6vw, 30px);       /* between two stages   */
  --fw-lift:    1.02;                           /* the frame, working   */
}

.fw-flow {
  display: flex; align-items: flex-start; justify-content: center;
  gap: clamp(8px, 1vw, 16px);
}

/* every column in the row is the same shape — an icon, a name, a line
   of explanation — so the icons sit on one axis without being told to */
.fw-src, .fw-stage {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
}

/* ── the source ───────────────────────────────────────────────────────
   In the asset this is a white card on a white ground, held only by a
   hairline: the count is the loudest thing on the page and the box
   around it says nothing at all. The card is centred on the axis the
   six icons sit on, exactly as the source centres it on cy=347.
   ═══════════════════════════════════════════════════════════════════ */
.fw-src {
  flex: none; width: var(--fw-card-w);
  margin-top: calc(var(--fw-top) + var(--fw-ic) / 2 - var(--fw-card-h) / 2);
}
.fw-card {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  width: 100%; height: var(--fw-card-h);
  border: 1px solid var(--hair); border-radius: 14px;
  background: var(--paper);
}
.fw-src-n {
  font-size: clamp(26px, 2.7vw, 38px); font-weight: 700; line-height: 1;
  letter-spacing: var(--tr-numeral); color: var(--signal);
  font-variant-numeric: tabular-nums;
}
.fw-src-n span { font-weight: 600; }
.fw-src-k {
  font-size: clamp(13px, 1.15vw, 15.5px); font-weight: 600;
  letter-spacing: -.005em; color: var(--ink);
}
.fw-src > .fw-d { margin-top: var(--sp-2); }

/* ── the stages ───────────────────────────────────────────────────── */
.fw-icw { display: grid; place-items: center; width: var(--fw-ic); height: var(--fw-ic); }
.fw-ic  { display: block; width: 100%; height: 100%; overflow: visible; }

.fw-k {
  margin: var(--sp-1) 0 0;
  font-size: clamp(13px, 1.15vw, 15.5px); font-weight: 700;
  letter-spacing: -.005em; color: var(--ink);
}
.fw-d {
  margin: 7px 0 0; max-width: 21ch;
  font-size: var(--fs-xs); line-height: var(--lh-tight);
  letter-spacing: var(--tr-body); color: var(--ink-3);
  text-wrap: pretty;
}

/* ── the two zones ────────────────────────────────────────────────────
   The asset draws the engine as a rounded frame whose top edge breaks
   open around the wordmark. Here that notch is the label's own ground
   sitting on the border, which is the same drawing by other means. The
   half beyond the frame is given no frame and no label at all: what
   the reader does is not a stage of a machine.
   ═══════════════════════════════════════════════════════════════════ */
.fw-zone { position: relative; padding: var(--fw-top) var(--sp-2) var(--sp-3); }
.fw-zone--bot {
  flex: none;
  border: 1px solid var(--hair); border-radius: 18px;
  /* the frame's resting state, stated so the working state below has
     something to return to; the shadow is the second pixel of the edge
     — a thicker border would resize the box and shove the row along */
  box-shadow: 0 0 0 0 rgba(225,102,2,0);
  transform: scale(1);
}
/* no frame here, so no inset for one: the padding .fw-zone sets is what
   holds the panel's stages off its border, and inherited unframed it
   only pushed the drawing's right edge 26px in from its left one */
.fw-zone--you { flex: none; padding-left: 0; padding-right: 0; }

.fw-zone-k {
  position: absolute; top: 0; left: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0; padding: 0 14px;
  background: var(--paper);
  font-size: clamp(15.5px, 1.5vw, 20px); font-weight: 700;
  letter-spacing: -.012em; color: var(--ink);
  white-space: nowrap;
}
/* the source runs the mark through a desaturating filter here: on the
   frame it is a name, not a badge, and the orange is spent elsewhere */
/* .fw-zone-k img {
  display: block; width: clamp(20px, 1.9vw, 25px); height: auto;
  filter: grayscale(1) brightness(.66) contrast(1.04);
} */

.fw-stages {
  position: relative; z-index: 1;
  display: flex; align-items: flex-start; justify-content: center;
  gap: var(--fw-gap);
  margin: 0; padding: 0; list-style: none;
}
/* every stage is one measure wide in both halves. The two halves are
   separate flex containers, so columns left to divide their own parent
   come out 109px inside the frame and 111px outside it — close enough
   to look like a mistake and not close enough to look deliberate. */
.fw-stage { flex: 0 0 var(--fw-col); min-width: 0; }

/* the chevron the asset sets between two stages inside one half */
.fw-stage + .fw-stage { position: relative; }
.fw-stage + .fw-stage::before {
  content: ""; position: absolute;
  top: calc(var(--fw-ic) / 2); left: 0;
  width: 8px; height: 8px;
  margin: -4px 0 0 calc(-1 * var(--fw-gap) / 2 - 4px);
  border-top: 1.6px solid var(--signal); border-right: 1.6px solid var(--signal);
  border-radius: 0 2px 0 0;
  transform: rotate(45deg);
}
.fw-zone--you .fw-stage + .fw-stage::before { border-color: var(--ink-4); }

/* ── the connectors between zones ─────────────────────────────────────
   Solid hairlines with a terminus dot, not the dashes of the first
   pass. The entry has one dot because the line runs into the frame;
   the handover has two, because it is a line between two parties.
   ═══════════════════════════════════════════════════════════════════ */
/* the connectors are the only elastic thing in the row: with the seven
   blocks rigid, a narrower viewport shortens the two lines rather than
   pulling the columns out of step with each other */
.fw-link {
  position: relative; align-self: flex-start; flex: 1 1 auto;
  width: auto; min-width: 26px; max-width: 74px;
  height: 1.8px; border-radius: 1px;
  margin-top: calc(var(--fw-top) + var(--fw-ic) / 2 - .9px);
  background: var(--signal);
}
.fw-link::before, .fw-link::after {
  content: ""; position: absolute; top: 50%;
  width: 8px; height: 8px; margin-top: -4px;
  border-radius: 50%; background: var(--signal);
}
.fw-link::before { left: -13px; }
.fw-link::after  { right: -13px; }
.fw-link--in::after { display: none; }
/* the second connector leaves AeroLogic, so it arrives in the reader's
   ink rather than the brand's orange */
.fw-link--out,
.fw-link--out::before,
.fw-link--out::after { background: var(--ink-4); }

/* the dot that crosses the connector, the one thing the asset animates
   along a path; parked and invisible outside its span of the cycle */
.fw-link b {
  position: absolute; top: 50%; left: 0;
  width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  border-radius: 50%; background: var(--signal);
  opacity: 0;
}
.fw-link--out b { background: var(--ink-4); }


/* ── below 1080 the row turns into a column ───────────────────────────
   The asset cannot do this at all: at this width its labels would be
   under four pixels. Each stage becomes a line — icon on the left,
   name and explanation on the right — and the connectors turn with it.

   The turn happens at 1080 rather than at a tablet width because what
   breaks first is not the diagram but its sentences: six stage columns
   plus the source card and both connectors leave roughly 100px a
   column, and a line of explanation at that width is five words tall.
   ═══════════════════════════════════════════════════════════════════ */
@media (max-width: 1080px) {
  .fw {
    --fw-ic: 76px; --fw-top: 0px; --fw-gap: var(--sp-3);
    --fw-card-w: 76px; --fw-card-h: 62px;
    /* the panel is four stages tall here, and two per cent of that is a
       visible shove rather than a lift */
    --fw-lift: 1.012;
  }
  /* a column has no reason to be as wide as the row was; left to
     stretch, the engine becomes a wide box holding a thin left margin
     of content */
  .fw-flow {
    flex-direction: column; align-items: stretch; gap: 0;
    max-width: 470px; margin: 0 auto;
  }

  /* the icon keeps its own size while its column widens to the card's,
     so all seven blocks still hang off one vertical axis */
  .fw-ic { width: 74%; height: 74%; }

  /* minmax(0,1fr) rather than 1fr: a grid track's automatic minimum is
     its content, and one unbreakable line of explanation would push the
     whole column past the viewport */
  .fw-src, .fw-stage {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    align-content: center; row-gap: 5px; column-gap: var(--sp-2);
    text-align: left;
  }
  .fw-src { width: auto; margin: 0; padding-left: var(--sp-2); }
  .fw-card {
    grid-column: 1; grid-row: 1 / span 2; align-self: center;
    width: var(--fw-card-w); height: var(--fw-card-h);
    border-radius: 12px; gap: 2px;
  }
  .fw-src-n { font-size: 22px; }
  .fw-src-k { font-size: var(--fs-2xs); }
  .fw-src > .fw-d { grid-column: 2; grid-row: 1 / span 2; align-self: center; margin: 0; }

  /* .fw-stages runs down here, where a flex-basis is a height */
  .fw-stage { flex: 0 0 auto; }
  .fw-icw { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
  .fw-k { grid-column: 2; margin: 0; align-self: end; }
  .fw-d { grid-column: 2; margin: 0; max-width: 34ch; align-self: start; }

  .fw-zone { padding: var(--sp-4) var(--sp-2) var(--sp-3); }
  .fw-zone--you { padding: var(--sp-3) var(--sp-2) 0; }
  .fw-stages { flex-direction: column; align-items: stretch; gap: var(--fw-gap); }

  /* the chevron becomes the step down between two stages */
  .fw-stage + .fw-stage::before {
    top: 0; left: calc(var(--sp-2) + var(--fw-ic) / 2);
    margin: calc(-1 * var(--fw-gap) / 2 - 4px) 0 0 -4px;
    transform: rotate(135deg);
  }

  .fw-link {
    align-self: flex-start; flex: none;
    width: 1.8px; min-width: 0; max-width: none; height: 34px;
    margin: 13px 0 13px calc(var(--sp-2) + var(--fw-ic) / 2 - .9px);
  }
  .fw-link::before, .fw-link::after {
    top: auto; left: 50%; margin: 0 0 0 -4px;
  }
  .fw-link::before { top: -13px; }
  .fw-link::after  { bottom: -13px; }
  .fw-link b { top: 0; left: 50%; margin: -4.5px 0 0 -4.5px; }
}

/* ── motion ───────────────────────────────────────────────────────────
   The source runs a requestAnimationFrame loop: one dot crosses the
   whole diagram in 10.8s, each stage answers as the dot lands on it,
   and the colour drains out of the dot on the leg that leaves the
   frame. Here that is one 10.8s cycle of delays taken straight off the
   asset's LEGS table — the same sequence, told by when each element
   answers rather than by where a dot is, so it reads the same whether
   the flow runs across or down. Only the two connectors still carry a
   travelling dot, because there the path is a straight line either way.
   ═══════════════════════════════════════════════════════════════════ */
.fl.js-r .fw-stage { opacity: 0; transform: translateY(10px); }
.fl.js-r.is-in .fw-stage {
  opacity: 1; transform: none;
  transition: opacity .42s ease calc(var(--n) * .07s),
              transform .5s cubic-bezier(.16,.9,.3,1) calc(var(--n) * .07s);
}

/* svg children scale about themselves, not about the viewBox origin */
.fw-badge, .fw-dot { transform-box: fill-box; transform-origin: 50% 50%; }
.fw-draw { stroke-dasharray: var(--len); }

.fl.js-r.is-in .fw-zone--bot    { animation: fw-frame 10.8s ease-in-out infinite; }
.fl.js-r.is-in .fw-stage .fw-ic { animation: fw-icon  10.8s ease-out var(--t) infinite; }
.fl.js-r.is-in .fw-link--in  b  { animation: fw-run-in  10.8s linear infinite; }
.fl.js-r.is-in .fw-link--out b  { animation: fw-run-out 10.8s linear infinite; }
.fl.js-r.is-in .fw-wave  { animation: fw-draw  10.8s ease-in-out 1.55s infinite; }
.fl.js-r.is-in .fw-lens  { animation: fw-draw  10.8s ease-in-out 4.1s  infinite; }
.fl.js-r.is-in .fw-badge { animation: fw-badge 10.8s ease-out    5.45s infinite; }
.fl.js-r.is-in .fw-dot   { animation: fw-dot   10.8s ease-out calc(2.8s + var(--d) * .18s) infinite; }

/* the frame answers the dot arriving: it thickens by a pixel and the
   whole panel — icons, names, explanations, the wordmark on the edge —
   comes up two per cent, then settles as the work leaves for the reader.
   It holds for the four stages inside it (1550ms → the handover at
   6100ms) rather than blinking once, because what it is saying is
   "this part is mine", not "something happened here". */
@keyframes fw-frame {
  0%,   11%   { border-color: var(--hair);
                box-shadow: 0 0 0 0 rgba(225,102,2,0);
                transform: scale(1); }
  15.5%, 57%  { border-color: var(--signal);
                box-shadow: 0 0 0 1.4px rgba(225,102,2,.26);
                transform: scale(var(--fw-lift)); }
  64%,  100%  { border-color: var(--hair);
                box-shadow: 0 0 0 0 rgba(225,102,2,0);
                transform: scale(1); }
}
/* slow rise, short hold, long release — the asset's own beat() curve */
@keyframes fw-icon {
  0%          { transform: scale(1); }
  2.9%        { transform: scale(1.05); }
  4.7%        { transform: scale(1.05); }
  11.5%, 100% { transform: scale(1); }
}
/* the leg into the frame: 250ms → 1550ms of a 10800ms cycle */
@keyframes fw-run-in {
  0%,   2.3%  { left: 0;    opacity: 0; }
  4.5%        { opacity: 1; }
  12%         { opacity: 1; }
  14.4%       { left: 100%; opacity: 0; }
  14.5%, 100% { left: 0;    opacity: 0; }
}
/* the handover: 6100ms → 7600ms, and slower, because it is a handover */
@keyframes fw-run-out {
  0%,   56.5% { left: 0;    opacity: 0; }
  58.5%       { opacity: 1; }
  68%         { opacity: 1; }
  70.4%       { left: 100%; opacity: 0; }
  70.5%, 100% { left: 0;    opacity: 0; }
}
/* Screening and Analisis — the trace is plotted again as it lands */
@keyframes fw-draw {
  0%          { stroke-dashoffset: var(--len); }
  8.3%, 100%  { stroke-dashoffset: 0; }
}
/* Alert — the notification indicator beats once */
@keyframes fw-badge {
  0%          { transform: scale(1); }
  3%          { transform: scale(1.24); }
  10.5%, 100% { transform: scale(1); }
}
/* Filter — the indicator dots come back one after another */
@keyframes fw-dot {
  0%          { opacity: 1; transform: scale(1); }
  1.9%        { opacity: .2; transform: scale(1); }
  4.7%        { opacity: 1; transform: scale(1.3); }
  7.4%, 100%  { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .fl.js-r .fw-stage { opacity: 1; transform: none; }
  .fl.js-r.is-in .fw-zone--bot,
  .fl.js-r.is-in .fw-stage .fw-ic, .fl.js-r.is-in .fw-link b,
  .fl.js-r.is-in .fw-wave, .fl.js-r.is-in .fw-lens,
  .fl.js-r.is-in .fw-badge, .fl.js-r.is-in .fw-dot { animation: none; }
  .fw-draw { stroke-dasharray: none; }
}



/* ── responsive ───────────────────────────────────────────────────── */


@media (max-width: 760px) {
  /* a wide diagram at 320px is an unreadable diagram — it gets to keep
     its own width and scroll inside the section rather than shrink */
  .fl-plot { overflow-x: auto; }
}

/* ══════════════════════════════════════════════════════════════════════
   SECTION 5 · THE MEMBERSHIP — five treatments of one offer
   One plan, one price, one call. No tiers, no comparison table, no annual
   saving, no countdown, no scarcity, no bonus, no trial, no guarantee and
   no cancellation term the product has not committed to. The offer is
   Rp99.000 a month and everything the membership includes; that is the
   whole of it, and the restraint is the pitch.
   ═══════════════════════════════════════════════════════════════════ */

.mb {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  border-top: 1px solid var(--hair-2);
}
.mb[hidden] { display: none; }

.mb-h {
  margin: 0; max-width: 17ch;
  font-size: var(--fs-h2);
  font-weight: 500; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  text-wrap: balance;
}
.mb-support {
  margin: var(--deck-gap) 0 0; max-width: 58ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ── the price, shared across the five ────────────────────────────── */
.mb-plan {
  font-size: var(--fs-2xs); font-weight: 600;
  letter-spacing: var(--tr-label); text-transform: uppercase; color: var(--ink-3);
}
.mb-price {
  margin: 0;
  font-weight: 500; letter-spacing: var(--tr-numeral); line-height: .86;
  font-variant-numeric: lining-nums;
  white-space: nowrap;
}
.mb-cur { font-size: .34em; font-weight: 500; letter-spacing: var(--tr-lead); }
.mb-per {
  font-size: .17em; font-weight: 400; letter-spacing: var(--tr-body);
  color: var(--ink-3);
}

/* ── the call ─────────────────────────────────────────────────────── */
.mb-cta {
  display: inline-block;
  padding: 15px 30px 16px; border-radius: 99px;
  background: var(--ink); color: var(--paper-2);
  font-size: var(--fs-lead); font-weight: 500; letter-spacing: var(--tr-body);
  box-shadow: 0 12px 30px -16px rgba(10,10,11,.55);
  transition: background .2s ease, box-shadow .25s ease, transform .25s cubic-bezier(.2,.8,.2,1);
}
.mb-cta:hover {
  background: var(--signal-d);
  box-shadow: 0 18px 40px -16px rgba(10,107,57,.55);
  transform: translateY(-1px);
}

/* the editorial treatment sets everything else from the left margin, so
   its fine print does the same rather than floating centred in a column */

/* ══ 1 · Pure Membership ═══════════════════════════════════════════════
   One offer at the centre of an almost empty page. The field around it
   is doing the work: nothing competes with the price and the call.
   ═══════════════════════════════════════════════════════════════════ */
.mb1 { text-align: center; }
.mb1 .mb-offer {
  display: flex; flex-direction: column; align-items: center;
  margin-top: clamp(66px, 11vh, 150px);
}
.mb1 .mb-price {
  margin: clamp(16px, 2.4vh, 26px) 0 clamp(34px, 5vh, 56px);
  font-size: clamp(72px, 11vw, 168px);
}


/* ── membership responsive ─────────────────────────────────────────── */
@media (max-width: 620px) {
  .mb-cta { display: block; width: 100%; text-align: center; }
  .mb1 .mb-price { font-size: clamp(60px, 20vw, 104px); }
}

/* ══════════════════════════════════════════════════════════════════════
   SECTION 5 · MEMBER EVIDENCE — five treatments
   The one section on this site that cannot be designed with invented
   content. No call, no testimonial, no result and no figure here is
   made up: every screenshot is an empty slot carrying the spec for the
   capture that belongs in it, and every field the client has to supply
   is bracketed and grey so it can never be mistaken for copy.

   What the section does claim is only structural: AeroLogic detected and
   alerted, the member reviewed and decided, and the outcome is the
   member's own. The accent marks the member's half of that chain, the same
   way it marks their half of the section above.
   ═══════════════════════════════════════════════════════════════════ */

.ev {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  border-top: 1px solid var(--hair-2);
}
.ev[hidden] { display: none; }

.ev-top {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; margin-bottom: var(--sec-gap);
}
.ev-h {
  margin: 0; max-width: 17ch;
  font-size: var(--fs-h2);
  font-weight: 500; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  text-wrap: balance;
}
.ev-support {
  margin: var(--deck-gap) 0 0; max-width: 58ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  color: var(--ink-2);
  text-wrap: pretty;
}

/* ══ the evidence slot ════════════════════════════════════════════════
   An empty frame, not a broken image. It states what capture belongs
   here, at what size, and what has to be blurred out of it first.

   TO FILL: replace the <div class="ev-slot-in"> with
     <img class="ev-shot" src="…" alt="…" width="…" height="…">
   and keep the figure's aspect-ratio, or set it to the real one.
   ═══════════════════════════════════════════════════════════════════ */
.ev-slot {
  position: relative; margin: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: var(--ar, 4 / 5);
  /* the ratio sets the shape; the cap stops a 540px column from turning
     a placeholder into 270px of void above and below its own label */
  max-height: clamp(300px, 32vw, 440px);
  border: 1px dashed #DBDBE0;
  border-radius: 14px;
  background: #FCFCFD;
}

.ev-slot-in {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  max-width: 30ch; padding: 22px;
  text-align: center;
}
.ev-slot-k {
  margin: 0;
  font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: var(--tr-body); color: var(--ink-2); text-wrap: balance;
}
.ev-slot-s { margin: 0; font-size: var(--fs-xs); line-height: var(--lh-ui); color: var(--ink-3); text-wrap: pretty; }
.ev-slot-r {
  margin: 6px 0 0; padding: 5px 11px 6px;
  border-radius: 99px; background: var(--hair-2);
  font-size: var(--fs-3xs); line-height: var(--lh-tight); color: var(--ink-3);
}
/* a real capture, once it is dropped in */
.ev-shot { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; }
.ev-slot:has(.ev-shot) { border-style: solid; border-color: var(--hair); background: var(--paper-2); }

/* ── a real capture ───────────────────────────────────────────────── */
/* The screenshots are deliberately NOT restyled to match the page. A
   Telegram capture recoloured into the site's palette stops being
   evidence and becomes an illustration; the dark ground and the double
   ticks are doing the credibility work. All the page adds is a frame. */
.ev-fig { margin: 0; }
.ev-fig img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--hair);
  border-radius: 14px;
  background: #17212B;
  box-shadow: 0 1px 2px rgba(10,10,11,.04),
              0 20px 40px -30px rgba(10,10,11,.35);
}
.ev-cap {
  margin: 11px 0 0;
  font-size: var(--fs-2xs); line-height: var(--lh-ui); color: var(--ink-3);
}
.ev-cap-x { display: block; margin-top: 3px; color: var(--ink-3); }
.ev-fig + .ev-fig { margin-top: clamp(16px, 2vw, 26px); }
/* a supporting view of the same call: set smaller than the message it
   belongs to, so the two never read as two separate pieces of evidence */
.ev-fig--sub { max-width: 66%; }
/* except where the column is already narrow — a supporting view shrunk
   twice stops being readable, which defeats the point of showing it */

@media (max-width: 900px) { .ev-fig--sub { max-width: 82%; } }
@media (max-width: 620px) { .ev-fig--sub { max-width: 100%; } }

/* ── a field the client supplies ──────────────────────────────────── */
/* scoped to .ev so it out-specifies the value rules it sits inside —
   a bracketed field must never render at full ink and read as content */
/* An unfilled field has to look unfilled without going below AA. The
   distinction moved from colour to a dashed underline + faint tint, so
   it now reads as "blank to fill" at 4.94:1 instead of 2.35:1. */
.ev .ph, .ph {
  color: var(--ink-3); font-weight: 400;
  font-feature-settings: "tnum";
  background: rgba(225,102,2,.045);
  border-bottom: 1px dashed #B9B9C0;
  padding: 0 2px 1px;
  border-radius: 2px;
}

/* ── the honest labels ────────────────────────────────────────────── */
.ev-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-3xs); font-weight: 500;
  letter-spacing: var(--tr-tag); text-transform: uppercase; color: var(--ink-3);
}
.ev-tag::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: #D2D2D6;
}

/* ══ 1 · Call → Hasil ══════════════════════════════════════════════════
   One pair per slide: what was sent on the left, what came of it on the
   right, an arrow in the gap, and a data strip under each capture.

   The slide is one grid, not two stacks side by side. The two frames
   share a row, so a tall capture on one side can never leave the other
   side's strip floating half a screen above the capture it belongs to,
   and the arrow always lands between the two frames rather than at some
   average of two column heights.

   The strip is where the pair is checked without opening the image, so
   it had to read as well empty as filled: an unfilled strip keeps its
   height and says in words that the number is not measured yet. It
   never shows zeroes, and never a dash standing in for a figure nobody
   has captured.
   ═══════════════════════════════════════════════════════════════ */
.ev-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 0 0 var(--sp-1);
  font-size: var(--fs-3xs); font-weight: 700;
  letter-spacing: var(--tr-label); text-transform: uppercase;
  color: var(--signal-d);
}
.ev-eyebrow i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--signal);
}
.ev-eyebrow svg { color: var(--ink-4); }

.ev-deck { width: 100%; max-width: 1120px; }

/* with no script every pair renders, one under the next; the script
   leaves one standing and reveals the controls that swap them */
.ev-track { display: grid; }
.ev-slide + .ev-slide { margin-top: var(--sp-5); }

.ev-slide {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  /* The strip reads above its capture: the figures are what a reader
     came to check, and the screenshot is the receipt for them. It also
     puts the two strips on one line across the pair, so the call's
     levels and the member's return sit at the same height and the
     comparison is made without scrolling between them. The turn arrow
     stays in the frame row, so it centres against the captures. */
  grid-template-rows: auto auto 1fr;
  grid-template-areas:
    "ck  .   rk"
    "cs  .   rs"
    "cf  tn  rf";
  column-gap: clamp(14px, 2vw, 30px);
  row-gap: var(--sp-1);
}
/* display:grid would otherwise win over the hidden attribute */
.ev-slide[hidden] { display: none; }

/* ── the two column labels ────────────────────────────────────────── */
/* Each side names itself and what the reader is looking at, then runs a
   rule to the edge of its own column — the rule is what makes the pair
   read as two halves of one chain rather than two unrelated figures. */
.ev-col-k {
  display: flex; align-items: center; gap: 9px; margin: 0;
  font-size: var(--fs-2xs); line-height: 1;
}
.ev-col-k b {
  font-weight: 700; letter-spacing: var(--tr-label); text-transform: uppercase;
}
.ev-col-k span { color: var(--ink-3); letter-spacing: var(--tr-body); }
.ev-col-k::after {
  content: ""; flex: 1 1 auto; height: 1px;
  background: linear-gradient(90deg, var(--rule), rgba(0,0,0,0));
}
.ev-k--call { grid-area: ck; --rule: rgba(225,102,2,.42); }
.ev-k--call b { color: var(--signal-d); }
/* the outcome is the member's, and the page marks the member's half in
   the market's own up-colour, not in the brand's */
.ev-k--res { grid-area: rk; --rule: rgba(10,107,57,.36); }
.ev-k--res b { color: var(--up); }

/* ── the frames ───────────────────────────────────────────────────── */
.ev-frame { display: flex; flex-direction: column; min-width: 0; height: 100%; }
.ev-frame--call { grid-area: cf; }
.ev-frame--res  { grid-area: rf; }

/* inside a pair an empty frame stands in for a capture that does not
   exist yet, so it takes the height its neighbour sets instead of its
   own ratio — the pair stays a pair, and the missing half is exactly as
   large as the half that exists */
.ev-slide .ev-slot--fill {
  aspect-ratio: auto; height: 100%; max-height: none;
  min-height: clamp(240px, 26vw, 380px);
}
/* Two real captures are never the same shape — this pair is a 1.57:1
   message beside a 1:1 card — and a pair whose halves end at different
   heights stops reading as one chain. Each is letterboxed into the same
   4:3 frame on its own ground colour, so nothing is cropped and no seam
   shows: the screenshots keep their own dark, the page only holds them. */
.ev-fig--shot { --shot-bg: #17212B; }
.ev-fig--shot img {
  aspect-ratio: 4 / 3; height: auto;
  object-fit: contain; background: var(--shot-bg);
}

/* ── the arrow in the gap ─────────────────────────────────────────── */
.ev-turn {
  grid-area: tn; align-self: center; justify-self: center;
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid var(--hair); background: var(--paper-2);
  color: var(--signal);
  box-shadow: 0 1px 2px rgba(10,10,11,.05),
              0 16px 30px -20px rgba(10,10,11,.45);
}

/* ── the strip under a capture ────────────────────────────────────── */
.ev-strip {
  display: flex; align-items: stretch; flex-wrap: wrap;
  border: 1px solid var(--hair); border-radius: 14px;
  background: var(--paper-2); overflow: hidden;
}
.ev-strip--call { grid-area: cs; }
.ev-strip--res  { grid-area: rs; }

/* the identity is the strip's header row, not a first cell: the column
   runs ~420px at desktop, and an inline identity leaves entry, two
   targets and a stop about 55px each — a width where every label wraps */
.ev-strip-id {
  display: flex; align-items: center; gap: 11px;
  flex: 1 1 100%; min-width: 0; padding: 12px 15px;
  border-bottom: 1px solid var(--hair-2);
}
.ev-plate {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--signal-w); color: var(--signal-d);
}
/* the outcome is the member's, and it is marked in the market's up-colour */
.ev-plate--up { background: rgba(10,107,57,.09); color: var(--up); }
.ev-strip-tx { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ev-strip-t {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--fs-sm); font-weight: 600;
  letter-spacing: var(--tr-body); color: var(--ink);
}
.ev-badge {
  padding: 2px 7px 3px; border-radius: 99px; background: var(--hair-2);
  font-size: var(--fs-3xs); font-weight: 600; color: var(--ink-2);
}
.ev-strip-s { font-size: var(--fs-3xs); line-height: var(--lh-tight); color: var(--ink-3); }

/* ── the figure the section is for ────────────────────────────────── */
/* The realized return arrived here as a pill the size of a tag, sitting
   directly beneath a screenshot that prints the same number four times
   larger. Restating a member's own figure smaller than the capture
   above it reads as the page hedging on it. It is the one number this
   whole section exists to show, so it is set as a number: the market's
   up-colour, tabular, and the largest thing in the strip. */
.ev-gain {
  margin-left: auto; padding-left: 12px; flex: none;
  font-size: clamp(23px, 2.1vw, 32px);
  font-weight: 600; line-height: 1; letter-spacing: var(--tr-h2);
  color: var(--up); font-variant-numeric: tabular-nums;
}
/* the outcome strip carries a wash of the same up-colour, so the pair
   reads call → result at a glance and not as two identical boxes */
.ev-strip--res:not(.ev-strip--empty) {
  border-color: rgba(10,107,57,.16);
  background:
    linear-gradient(to bottom, rgba(10,107,57,.045), rgba(10,107,57,0) 62%),
    var(--paper-2);
}
.ev-strip--res:not(.ev-strip--empty) .ev-strip-id { border-bottom-color: rgba(10,107,57,.12); }
.ev-strip--res:not(.ev-strip--empty) .ev-cell { border-left-color: rgba(10,107,57,.12); }

/* the two prices are what the return is computed from, so they are read
   as figures too — one step up from an ordinary cell value */
.ev-cells--res dd { font-size: var(--fs-lead); font-weight: 600; }

/* two to four cells, never more: the column is ~420px wide at desktop,
   and a fifth cell is where a label starts wrapping to three lines */
.ev-cells { display: flex; flex-wrap: wrap; flex: 1 1 100%; margin: 0; min-width: 0; }
.ev-cell {
  flex: 1 1 92px; min-width: 0; padding: 11px 14px 12px;
  border-left: 1px solid var(--hair-2);
}
.ev-cell:first-child { border-left: 0; }
.ev-cell dt {
  font-size: var(--fs-3xs); letter-spacing: var(--tr-tag);
  text-transform: uppercase; color: var(--ink-3);
}
.ev-cell dd {
  margin: 4px 0 0; font-size: var(--fs-sm); font-weight: 500;
  letter-spacing: var(--tr-body); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* the percentage the message itself prints next to a level, kept at the
   size of an annotation so the level stays the number being read. Only a
   move the message itself signs — a target, a stop — takes direction
   colour; an unexplained figure stays neutral rather than being read as
   a gain by the page's own styling. */
.ev-cell dd span { font-size: var(--fs-2xs); font-weight: 600; color: var(--ink-3); }
.ev-cell dd .fn-up { color: var(--up); }
.ev-cell dd .fn-dn { color: var(--down); }

/* the unfilled strip: same box, dashed edge, one sentence */
.ev-strip--empty {
  align-items: center; justify-content: center;
  border-style: dashed; border-color: #DBDBE0; background: #FCFCFD;
}
.ev-strip--empty p {
  margin: 0; padding: 15px 18px; max-width: 48ch; text-align: center;
  font-size: var(--fs-2xs); line-height: var(--lh-ui); color: var(--ink-3);
  text-wrap: pretty;
}

/* ── the controls ─────────────────────────────────────────────────── */
/* No auto-advance here. The hero cycles because it is a demonstration;
   evidence is read, and moving it out from under a reader mid-sentence
   is the one thing this section cannot do. */
.ev-nav {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(10px, 1.6vw, 20px);
  margin-top: var(--sp-3);
}
.ev-nav[hidden] { display: none; }
.ev-arw {
  width: 44px; height: 44px; border-radius: 50%; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--hair); background: var(--paper-2); color: var(--ink-2);
  transition: border-color .22s ease, color .22s ease, transform .22s ease;
}
.ev-arw:hover { border-color: var(--ink-4); color: var(--ink); }
.ev-arw:active { transform: scale(.95); }

/* same 44px hit area as the hero's dots, with the mark drawn inside it */
.ev-dots { display: flex; align-items: center; gap: 0; }
.ev-dots button {
  position: relative; width: 40px; height: 44px; padding: 0; cursor: pointer;
}
.ev-dots button::before {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 9px; height: 9px; border-radius: 50%;
  background: #D7D7DB; transform: translate(-50%, -50%);
  transition: background-color .3s ease, transform .3s cubic-bezier(.2,.8,.2,1);
}
.ev-dots button:hover::before { background: var(--ink-4); }
.ev-dots button.is-on::before {
  background: var(--signal); transform: translate(-50%, -50%) scale(1.15);
}

/* ── the capture that is not a pair ───────────────────────────────── */
/* The topic list is context, not a call and not an outcome, so it sits
   under the carousel rather than being forced into one of its halves. */
.ev-extra {
  display: flex; flex-direction: column; gap: 12px;
  width: 100%; max-width: 1120px; margin-top: var(--sp-5);
}
.ev-extra .ev-tag { align-self: flex-start; }
.ev-slot--wide { --ar: 16 / 5; width: 100%; max-height: 300px; }

/* ── the headline numbers ─────────────────────────────────────────── */
/* Same full-colour ground as .jn-card, the closing CTA — this is the
   other place on the page that earns to stand out from six sections of
   near-white. Dividers and label move to white-on-orange rather than
   the hairline/--ink-3 pair the rest of the section uses. */
.ev-stats {
  display: flex; flex-wrap: wrap; list-style: none;
  width: 100%; max-width: 1120px; margin: var(--sp-5) 0 0; padding: 0;
  border-radius: clamp(18px, 2vw, 28px); overflow: hidden;
  background: var(--signal);
  box-shadow: 0 24px 60px -34px rgba(148, 69, 4, .55);
}
.ev-stat {
  flex: 1 1 160px; min-width: 0; padding: clamp(22px, 3.4vh, 34px) 18px;
  text-align: center;
}
.ev-stat b {
  display: block; font-size: clamp(28px, 3.2vw, 40px); font-weight: 700;
  line-height: 1; letter-spacing: var(--tr-numeral); color: var(--paper-2);
  font-variant-numeric: tabular-nums;
}
.ev-stat span {
  display: block; margin-top: 10px; font-size: var(--fs-3xs); font-weight: 600;
  letter-spacing: var(--tr-tag); text-transform: uppercase;
  color: rgba(255, 255, 255, .82);
}

@media (max-width: 640px) {
  .ev-stats { flex-direction: column; }
}

/* ── the risk line ────────────────────────────────────────────────── */
.ev-note {
  display: flex; align-items: flex-start; gap: 10px;
  width: 100%; max-width: 1120px; margin: var(--sp-3) 0 0;
  padding: 14px 18px;
  border: 1px solid var(--hair); border-radius: 14px; background: var(--hair-2);
  font-size: var(--fs-2xs); line-height: var(--lh-ui); color: var(--ink-3);
  text-wrap: pretty;
}
.ev-note svg { flex: none; margin-top: 1px; color: var(--ink-4); }


/* ── responsive ───────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* two captures side by side at this width are two unreadable
     captures; the pair turns and the chain reads down the page, with
     the arrow turned with it */
  .ev-slide {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "ck" "cs" "cf" "tn" "rk" "rs" "rf";
  }
  .ev-turn { transform: rotate(90deg); margin: var(--sp-1) 0; }
  .ev-slide .ev-slot--fill { min-height: clamp(220px, 52vw, 340px); }
}

@media (max-width: 620px) {
  .ev-slot-in { padding: 16px; }
  .ev-slot-s { font-size: var(--fs-xs); }
  .ev-slot--wide { --ar: 4 / 3; max-height: none; }
  /* the cells stop being one row and become a small table, which is the
     only way four labelled values stay readable at this width */
  .ev-cells { display: grid; grid-template-columns: 1fr 1fr; }
  .ev-cell:nth-child(n+3) { border-top: 1px solid var(--hair-2); }
  .ev-cell:nth-child(2n+1) { border-left: 0; }
}

/* ══════════════════════════════════════════════════════════════════════
   SECTION 6b · panggilan penutup
   Kartu tunggal di atas ground halaman, dibelah dua tanpa garis pemisah:
   heading dan deskripsinya di kiri, tombolnya sendirian di kanan.

   Satu syarat memerintah seluruh blok ini: baris "Gabung Komunitas
   AeroLogic," tidak boleh pecah, pada lebar berapa pun. Karena itu
     · kolom kanan diberi lebar `auto` — ia menyusut ke lebar tombolnya
       dan menyerahkan sisa kartu ke kolom kiri;
     · jarak antar kolom dipersempit dari --sp scale ke ramp sendiri;
     · heading memakai white-space: nowrap (yang <br> tetap tembus) plus
       ramp ukuran yang dihitung agar 27 karakter itu selalu muat.
   ═══════════════════════════════════════════════════════════════════ */
.jn {
  /* satu tempat untuk ground kartu, karena setiap keputusan warna teks di
     bawah ini diukur terhadapnya */
  --jn-ground: #E16602;

  display: flex; justify-content: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
  border-top: 1px solid var(--hair-2);
}

/* Kartu ini satu-satunya bidang penuh warna di halaman, dan itu memang
   tugasnya: setelah enam section di atas kertas nyaris putih, ajakan
   terakhir berdiri dengan membalik ground-nya. Konsekuensinya seluruh
   tangga warna teks ikut terbalik — #E16602 hanya 3.44:1 terhadap putih,
   jadi teks di atasnya berjalan gelap, bukan terang. */
.jn-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(24px, 3vw, 48px);
  width: 100%; max-width: 1120px;
  padding: clamp(44px, 7vh, 88px) clamp(24px, 4.5vw, 72px);
  border: 0;
  border-radius: clamp(18px, 2vw, 28px);
  background: var(--jn-ground);
  box-shadow: 0 24px 60px -34px rgba(148, 69, 4, .55);
}
/* --signal-d hanya 1.95:1 di atas ground ini; cincin fokusnya harus ikut
   membalik atau ia hilang justru bagi yang paling membutuhkannya. */
.jn-card :focus-visible { outline-color: var(--ink); }

/* ── kolom kiri: kalimat dan deskripsinya ─────────────────────────── */
/* 36px adalah ukurannya, dan ia bertahan datar dari desktop sampai ~667px.
   Di bawah itu 36px tidak lagi muat satu baris — baris pertama menuntut
   ~13.3em, sementara kartu di 320px hanya menyisakan 232px — jadi suku
   5.4vw mengambil alih dan menyusutkannya. Yang dikorbankan ukuran, bukan
   janji satu barisnya. */
.jn-h {
  margin: 0;
  font-size: min(36px, 5.4vw);
  font-weight: 600; line-height: var(--lh-h2); letter-spacing: var(--tr-h2);
  color: var(--paper-2); /* 3.44:1 di atas #E16602 — lihat catatan .jn-d */
  white-space: nowrap;   /* <br> tetap memutus; yang dicegah hanya wrap */
}
/* Seluruh heading kini satu warna, jadi <em> tidak lagi membawa aksen —
   markup-nya sengaja ditinggal utuh supaya highlight "AeroLogic" bisa
   dihidupkan lagi lewat satu baris di sini kalau nanti dibutuhkan. */
.jn-h em { font-style: normal; color: inherit; }

.jn-d {
  margin: clamp(14px, 1.8vh, 22px) 0 0; max-width: 46ch;
  font-size: var(--fs-deck);
  font-weight: 400; line-height: var(--lh-deck); letter-spacing: var(--tr-deck);
  /* Putih di atas #E16602 adalah 3.44:1. Pada heading 36px/600 angka itu
     lolos AA sebagai large text; pada baris ini, yang 14.5–17px, ia di
     bawah ambang 4,5:1 dan tidak lolos. Warnanya diminta putih, jadi yang
     bisa menutup selisihnya hanya ground-nya: menurunkan --jn-ground ke
     sekitar #B85100 membuat putih mencapai 4,98:1 di kedua tingkat. */
  color: var(--paper-2);
  text-wrap: pretty;
}

/* ── kolom kanan: tombolnya ───────────────────────────────────────── */
.jn-cta {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 16px 30px 17px; border-radius: 99px;
  background: var(--ink); color: var(--paper-2);
  font-size: var(--fs-lead); font-weight: 500; letter-spacing: var(--tr-body);
  white-space: nowrap;
  box-shadow: 0 12px 30px -16px rgba(10, 10, 11, .55);
  transition: background .2s ease, color .2s ease, box-shadow .25s ease,
              transform .25s cubic-bezier(.2,.8,.2,1);
}
.jn-cta svg { flex: none; transition: transform .25s cubic-bezier(.2,.8,.2,1); }
/* hover membalik pil-nya sekali lagi — putih di atas oranye, dengan teks
   --signal-d yang 6.65:1 terhadap putih. --signal-d sebagai ground tombol
   tidak lagi mungkin: ia hampir menyatu dengan kartunya. */
.jn-cta:hover {
  background: var(--paper-2);
  color: var(--signal-d);
  box-shadow: 0 18px 40px -18px rgba(74, 31, 0, .6);
  transform: translateY(-1px);
}
.jn-cta:hover svg { transform: translateX(3px); }

@media (prefers-reduced-motion: reduce) {
  .jn-cta, .jn-cta svg { transition-duration: .01ms; }
}

/* Satu kolom di bawah 1000px. Ambangnya naik dari 960px bersama turunnya
   heading ke 36px: dua kolom baru sehat kalau kolom kiri menyisakan lebih
   dari ~479px yang dituntut baris pertama, dan pada 1000px ia menyisakan
   ~528px. Di bawah itu tombol menjadi baris penuh. */
@media (max-width: 999px) {
  .jn-card { grid-template-columns: 1fr; gap: clamp(26px, 4vh, 38px); }
  .jn-cta { width: 100%; justify-content: center; padding: 15px 22px 16px; }
}

/* Blok 380px yang dulu menukar warna aksen dihapus bersama aksennya:
   dengan heading satu warna tidak ada lagi yang bisa ditukar di sini. */

/* ══════════════════════════════════════════════════════════════════════
   SECTION 7 · FAQ
   An accordion: twelve questions at reading weight on one full-width
   list, one answer open at a time. Closed rows keep the section short
   enough to scan before you commit to reading any single answer.
   ═══════════════════════════════════════════════════════════════════ */
.fq {
  display: flex; flex-direction: column; align-items: center;
  padding: var(--sec-pt) var(--gut) var(--sec-pb);
}
.fq-top { width: 100%; max-width: 1120px; margin-bottom: var(--sec-gap); }
.fq-h {
  margin: 0; max-width: 20ch;
  font-size: var(--fs-h2); font-weight: 500;
  line-height: var(--lh-h2); letter-spacing: var(--tr-h2); color: var(--ink);
}
.fq-list { width: 100%; max-width: 1120px; margin: 0; }
/* the accordion row itself is built further down; the type scale for
   the question stays here */
.fq-q {
  margin: 0; font-size: var(--fs-lead); font-weight: 500;
  line-height: var(--lh-tight); letter-spacing: var(--tr-body); color: var(--ink);
}
.fq-a { margin: 0; font-size: var(--fs-read); line-height: var(--lh-deck); color: var(--ink-2); }
.fq-a + .fq-a { margin-top: 10px; }
.fq-a b { font-weight: 600; color: var(--ink); }

/* ══════════════════════════════════════════════════════════════════════
   SECTION 9 · THE FOOTER
   The risk disclaimer that used to live here is answered in the FAQ and
   summarised under the final CTA, so the footer is now brand, contact
   and a sitemap — three even columns, nothing set at 9px.
   ═══════════════════════════════════════════════════════════════════ */
.ft {
  padding: clamp(32px, 4.5vh, 48px) var(--gut) clamp(24px, 3vw, 32px);
  border-top: 1px solid var(--hair);
  background: #FAFAFB;
}
.ft-in { width: 100%; max-width: 1120px; margin: 0 auto; text-align: center; }
.ft-brand { display: flex; align-items: center; justify-content: center; gap: 9px; margin-bottom: 14px; }
.ft-brand img { display: block; opacity: .85; }
.ft-line { margin: 0 auto; max-width: 40ch; font-size: var(--fs-xs); line-height: var(--lh-deck); color: var(--ink-3); }
.ft-social { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 18px; }
.ft-soc {
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: 99px;
  color: var(--ink-3); transition: color .18s ease, background-color .18s ease;
}
.ft-soc:hover { color: var(--ink); background: var(--hair-2); }
.ft-soc svg { width: 17px; height: 17px; display: block; }
.ft-base {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px 26px;
  margin-top: clamp(32px, 5vh, 52px); padding-top: var(--sp-2);
  border-top: 1px solid var(--hair);
  font-size: 10px; color: var(--ink-3);
}

@media (max-width: 760px) {
  .fq-q { gap: var(--sp-1); }
  .fq-body-in { padding-right: 0; }
}


/* ══════════════════════════════════════════════════════════════════════
   SECTION 3 · the lead card's footing
   The lead card is the only two-row one, so its plots take the slack
   rather than leaving a hole above the chip. The SVGs already scale
   non-uniformly, and the markers are absolute, so growing the box grows
   the chart without moving anything off its point.
   ═══════════════════════════════════════════════════════════════════ */
.bx-lead-foot { margin-top: clamp(14px, 1.8vh, 20px); }


/* ══════════════════════════════════════════════════════════════════════
   CORPORATE ACTION — ported from assets/company-events-ambient.html
   The source is a 1254-square spot illustration and this card is a
   landscape band, so the port is a change of frame rather than a change
   of drawing: the cluster — document, buildings, calendar — is moved
   across by one uniform transform (x' = .28x + 64.64, y' = .28y − 45.6)
   and everything the source put in a circle around it now sits on an
   ellipse instead, which is the only concession the wider frame asks
   for. The peach ground, the buildings behind the filing, the two
   hairline orbits, the three dotted connections that light one after
   another from the centre outward, the dot that runs each of them and
   the date that keeps a slow heartbeat all come across intact.
   ═══════════════════════════════════════════════════════════════════ */
/* one step between --hair and --ink-4, for the structural hairlines the
   buildings are made of — the source's #D5D5D3, which neither reaches */
.ce { --ce-line: #D8D8DE; }
/* the copy here is four short lines at the shared 34ch measure, and the
   four lines are all height the illustration below it does not get. On
   this card alone the measure comes off: the description settles into
   two, and the drawing takes the rest of the card. */
.bx-corp .bx-desc { max-width: none; }
.bx-vis.ce { padding-top: 10px; }
.ce-svg { display: block; flex: 1; min-height: 0; width: 100%; height: 100%; }

/* the warm ground the composition sits on */
.ce-glow { transform-box: fill-box; transform-origin: center; }

/* the orbit the four events sit on */
.ce-arc { fill: none; stroke: var(--hair); stroke-width: 1.1; }
.ce-plus line { stroke: var(--hair); stroke-width: 1.4; stroke-linecap: round; }

/* the company that files it */
.ce-b-back  { fill: var(--hair-2); stroke: var(--hair); stroke-width: 1; }
.ce-b-roof  { fill: var(--paper-2); stroke: var(--ink); stroke-width: 1.3; stroke-linejoin: round; }
.ce-b-side  { fill: var(--hair-2); stroke: var(--ce-line); stroke-width: 1; }
.ce-b-face  { fill: var(--paper-2); stroke: var(--ink); stroke-width: 1.3; stroke-linejoin: round; }
.ce-b-slab  { fill: #F6F6F8; stroke: var(--hair); stroke-width: 1; }
.ce-b-main  { fill: var(--paper-2); stroke: var(--ink); stroke-width: 1.4; stroke-linejoin: round; }
.ce-b-flue  { fill: var(--hair); }
.ce-b-win line, .ce-b-stripe line {
  stroke: var(--ce-line); stroke-width: 1.2; stroke-linecap: round;
}
.ce-b-door  { fill: var(--ink); }
.ce-b-jamb  { stroke: var(--paper-2); stroke-width: 1; }

/* the filing */
.ce-sheet { fill: var(--paper-2); stroke: var(--ink); stroke-width: 1.5; stroke-linejoin: round; }
.ce-fold  { fill: var(--hair-2); stroke: var(--ce-line); stroke-width: 1.2; stroke-linejoin: round; }
.ce-sq    { fill: var(--hair); }
.ce-bar   { fill: var(--hair); }

/* the date it takes effect */
.ce-calback  { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1; stroke-linejoin: round; }
.ce-calbody  { fill: var(--paper-2); stroke: var(--ink); stroke-width: 1.5; }
.ce-calhead  { fill: var(--ink); }
.ce-rings rect { fill: none; stroke: var(--ink-3); stroke-width: 1.5; }
.ce-cell rect { fill: var(--hair); }
.ce-date     { fill: var(--signal); }
.ce-datedot  { fill: var(--paper-2); }
.ce-datering { fill: none; stroke: var(--signal); stroke-width: 1.2; }
.ce-dateglow, .ce-datering, .ce-date {
  transform-box: fill-box; transform-origin: center;
}
.ce-dateglow { opacity: 0; }

/* what connects them: three dotted runs, each masked by a stroke that
   is drawn on so the line arrives along its own path rather than
   through a straight wipe */
.ce-line path {
  fill: none; stroke: var(--signal); stroke-width: 2;
  stroke-linecap: round; stroke-dasharray: .1 7;
}
.ce-rv {
  fill: none; stroke: #fff; stroke-width: 9; stroke-linecap: round;
  stroke-dasharray: 1; stroke-dashoffset: 0;
}

/* the node each line lands on */
.ce-node  { fill: var(--signal); transform-box: fill-box; transform-origin: center; }
.ce-nglow { opacity: 0; transition: opacity 1.2s ease 2.6s; }
.ce-nring {
  fill: none; stroke: var(--signal); stroke-width: 1.2; opacity: 0;
  transform-box: fill-box; transform-origin: center;
}

/* the dot that makes the trip, centre to node, on a slow loop */
.ce-run { opacity: 0; offset-rotate: 0deg; offset-distance: 100%; }
.ce-runcore { fill: var(--signal); }
.ce-run1 { offset-path: path("M194,39 C206,52 218,68 227,84 C231,92 234,98 236,103"); }
.ce-run2 { offset-path: path("M392,116 C376,114 358,110 340,104 C326,99 314,95 308,93"); }
.ce-run3 { offset-path: path("M89,209 C114,232 158,240 196,232 C214,228 226,219 232,211"); }

/* the four kinds of event */
.ce-chip { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.3; }
.ce-g  { fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.ce-ga { fill: none; stroke: var(--signal); stroke-width: 1.5; stroke-linecap: round; }
.ce-gf { fill: var(--signal); stroke: none; }

/* ── arrival ───────────────────────────────────────────────────────── */
/* the source's order, kept: the ground, then the company, then the
   filing, then the date, then the lines out to each kind of event */
.bx.js-r .ce-glow  { opacity: 0; transform: scale(.94); }
.bx.js-r .ce-arc   { stroke-dasharray: 1; stroke-dashoffset: 1; }
.bx.js-r .ce-bldg  { opacity: 0; transform: translateY(7px); }
.bx.js-r .ce-doc   { opacity: 0; transform: translateY(9px); }
.bx.js-r .ce-cal   { opacity: 0; transform: translateY(7px); }
.bx.js-r .ce-plus  { opacity: 0; }
.bx.js-r .ce-rv    { stroke-dashoffset: -1; }
.bx.js-r .ce-ic    { opacity: 0; transform: scale(.82); transform-box: fill-box; transform-origin: center; }
.bx.js-r .ce-node  { opacity: 0; transform: scale(.4); }
.bx.js-r .ce-date  { opacity: 0; }

.bx.js-r .is-live .ce-glow {
  opacity: 1; transform: none;
  transition: opacity .9s ease .1s, transform 1.1s cubic-bezier(.2,.8,.3,1) .1s;
}
.bx.js-r .is-live .ce-arc {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.15s cubic-bezier(.38,.05,.28,1) .16s;
}
.bx.js-r .is-live .ce-bldg {
  opacity: 1; transform: none;
  transition: opacity .5s ease .26s, transform .56s cubic-bezier(.18,.85,.3,1) .26s;
}
.bx.js-r .is-live .ce-doc {
  opacity: 1; transform: none;
  transition: opacity .5s ease .1s, transform .58s cubic-bezier(.16,.86,.28,1) .1s;
}
.bx.js-r .is-live .ce-cal {
  opacity: 1; transform: none;
  transition: opacity .48s ease .64s, transform .54s cubic-bezier(.16,.86,.28,1) .64s;
}
.bx.js-r .is-live .ce-plus { opacity: 1; transition: opacity .8s ease .9s; }
.bx.js-r .is-live .ce-date { opacity: 1; transition: opacity .3s ease .96s; }

/* the three lines draw one after another, and each event turns up as
   its own line lands */
.bx.js-r .is-live .ce-rv { stroke-dashoffset: 0; transition: stroke-dashoffset .62s cubic-bezier(.38,.05,.28,1); }
.bx.js-r .is-live #ceMk1 .ce-rv { transition-delay: .95s; }
.bx.js-r .is-live #ceMk2 .ce-rv { transition-delay: 1.3s; }
.bx.js-r .is-live #ceMk3 .ce-rv { transition-delay: 1.65s; }

.bx.js-r .is-live .ce-ic {
  opacity: 1; transform: none;
  transition: opacity .34s ease calc(1.4s + var(--n) * .24s),
              transform .42s cubic-bezier(.16,1,.3,1) calc(1.4s + var(--n) * .24s);
}
.bx.js-r .is-live .ce-node {
  opacity: 1; transform: none;
  transition: opacity .3s ease calc(1.34s + var(--n) * .24s),
              transform .42s cubic-bezier(.2,1.4,.4,1) calc(1.34s + var(--n) * .24s);
}
.bx.js-r .is-live .ce-nglow { opacity: 1; }

/* ── ambient: once it has settled, three dots on a loop and a date that
   keeps a slow heartbeat ──────────────────────────────────────────── */
.bx.js-r .is-live .ce-run1 { animation: ce-run 5.4s cubic-bezier(.45,0,.55,1) 2.9s infinite; }
.bx.js-r .is-live .ce-run2 { animation: ce-run 5.4s cubic-bezier(.45,0,.55,1) 4.2s infinite; }
.bx.js-r .is-live .ce-run3 { animation: ce-run 5.4s cubic-bezier(.45,0,.55,1) 5.5s infinite; }
.bx.js-r .is-live .ce-nring1 { animation: ce-ring 5.4s ease-out 2.9s infinite; }
.bx.js-r .is-live .ce-nring2 { animation: ce-ring 5.4s ease-out 4.2s infinite; }
.bx.js-r .is-live .ce-nring3 { animation: ce-ring 5.4s ease-out 5.5s infinite; }

@keyframes ce-run {
  0%        { offset-distance: 100%; opacity: 0; }
  7%        { opacity: .95; }
  50%       { opacity: .95; }
  57%, 100% { offset-distance: 0%;   opacity: 0; }
}
@keyframes ce-ring {
  0%, 51%   { transform: scale(1);   opacity: 0; }
  55%       { opacity: .5; }
  78%, 100% { transform: scale(3.4); opacity: 0; }
}

.bx.js-r .is-live .ce-datering { animation: ce-datering 4.2s ease-out 2.2s infinite both; }
.bx.js-r .is-live .ce-date     { animation: ce-datelift 4.2s ease-out 2.2s infinite both; }
.bx.js-r .is-live .ce-dateglow { animation: ce-dateglow 4.2s ease-in-out 2.2s infinite both; }

@keyframes ce-datering {
  0%       { transform: scale(.88); opacity: 0; }
  4%       { opacity: .42; }
  34%,100% { transform: scale(1.9); opacity: 0; }
}
@keyframes ce-datelift {
  0%       { transform: scale(1); }
  8%       { transform: scale(1.08); }
  34%,100% { transform: scale(1); }
}
@keyframes ce-dateglow {
  0%,100%  { opacity: .1;  transform: scale(.92); }
  12%      { opacity: .5;  transform: scale(1.12); }
  48%      { opacity: .14; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .ce-glow, .bx.js-r .ce-bldg, .bx.js-r .ce-doc, .bx.js-r .ce-cal,
  .bx.js-r .ce-plus, .bx.js-r .ce-ic, .bx.js-r .ce-node, .bx.js-r .ce-date {
    opacity: 1; transform: none;
  }
  .bx.js-r .ce-arc, .bx.js-r .ce-rv { stroke-dashoffset: 0; }
  .ce-nglow { opacity: 1; transition: none; }
  .bx.js-r .is-live .ce-run, .bx.js-r .is-live .ce-nring,
  .bx.js-r .is-live .ce-datering, .bx.js-r .is-live .ce-date,
  .bx.js-r .is-live .ce-dateglow { animation: none; }
  .bx.js-r .is-live .ce-dateglow { opacity: .3; }
}

/* the motion path is the only thing here that cannot degrade quietly —
   without it the dot would sit at the origin, so it stays hidden */
@supports not (offset-path: path("M0,0 L1,1")) {
  .ce-run { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════
   THE INSIGHT NETWORK — adapted from assets/insight-network.html
   The source is a 2160×728 stage: five chat widgets around a Market
   Insight card, each with a blurred halo and a drop shadow, plus a crowd
   of people, a matrix of dots, a set of concentric rings, and a
   requestAnimationFrame loop that walks one dot through five legs while
   booking a glow value for every node on every frame. This card is a
   1240×230 band at the foot of a full-width tile and the page's world is
   flat line art, so the halos, the shadows, the crowd and the furniture
   are gone, and the loop is two motion paths.

   What is left is the sentence the picture was making, arranged to match
   the card's own title: what the owner writes sits on the left, the room
   sits on the right, and both run into the same insight in the middle.
   It is the whole card now — the sample quote and the sample chat room
   that used to sit above it are gone, so the band carries the value on
   its own, the way the other three cards' visuals do.
   ═══════════════════════════════════════════════════════════════════ */
.in-net {
  /* one step between --hair and --ink-4: the source's #D6D6D6 placeholder
     bars, which neither token reaches on a white card */
  --in-bar: #DEDEE2;
}
.in-svg { display: block; width: 100%; height: auto; max-height: 214px; margin: 0 auto; }

/* the four messages around it */
.in-body { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.2; stroke-linejoin: round; }
.in-av   { fill: var(--hair-2); }
.in-face { fill: var(--ink-4); }
.in-h    { fill: var(--ink-2); }
.in-b    { fill: var(--in-bar); }
.in-quote, .in-qmark {
  font-family: Georgia, "Times New Roman", serif;
  fill: var(--ink-4); text-anchor: middle;
}
.in-quote { font-size: 34px; }
.in-qmark { font-size: 30px; }
.in-ask   { fill: var(--signal-w); }
.in-askt  { font: 700 15px/1 Onest, system-ui, sans-serif; fill: var(--signal-d); text-anchor: middle; }
.in-mini rect { fill: var(--ink-4); }
.in-trend polyline {
  fill: none; stroke: var(--signal); stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}

/* the insight itself — the only object in the band drawn in the accent,
   and its label is --signal-d, since --signal cannot carry text */
.in-cardbody { fill: var(--paper-2); stroke: var(--signal); stroke-width: 1.8; }
.in-badge    { fill: var(--signal); }
.in-bulb     { fill: none; stroke: var(--paper-2); stroke-width: 1.8; stroke-linecap: round; }
.in-label    { font: 700 13px/1 Onest, system-ui, sans-serif; letter-spacing: 1.4px; fill: var(--signal-d); }
.in-key      { fill: var(--signal); }
.in-starchip { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.2; }
.in-starpath { fill: var(--signal); }

/* what connects them: solid out of a message, dotted into the card */
.in-leg    { fill: none; stroke: var(--hair); stroke-width: 1.6; stroke-linecap: round; }
.in-leg--d { stroke: var(--in-bar); stroke-dasharray: .1 7; }
.in-chip   { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.2; }
.in-people { fill: var(--ink-4); }

/* one from each side, making the trip — the source's traveller, on two
   motion paths instead of its five-leg frame loop */
.in-run { fill: var(--signal); opacity: 0; offset-rotate: 0deg; offset-distance: 0%; }
.in-run--a { offset-path: path("M300,62 C328,72 342,88 350,102 L366,115 L452,115"); }
.in-run--b { offset-path: path("M958,170 C926,162 904,148 892,128 L874,115 L788,115"); }

/* ── arrival: the two sides first, then what joins them, then the
   insight they arrive at — the same order the card is read in ────── */
.bx.js-r .in-w, .bx.js-r .in-ic { opacity: 0; transform: translateY(8px); }
.bx.js-r .in-legs { opacity: 0; }
.bx.js-r .in-card { opacity: 0; transform: scale(.96); transform-box: fill-box; transform-origin: center; }
.bx.js-r .in-star { opacity: 0; }

.bx.js-r .is-live .in-w {
  opacity: 1; transform: none;
  transition: opacity .4s ease calc(.18s + var(--n) * .08s),
              transform .46s cubic-bezier(.16,.9,.3,1) calc(.18s + var(--n) * .08s);
}
.bx.js-r .is-live .in-ic {
  opacity: 1; transform: none;
  transition: opacity .34s ease calc(.18s + var(--n) * .08s),
              transform .4s cubic-bezier(.16,1,.3,1) calc(.18s + var(--n) * .08s);
}
.bx.js-r .is-live .in-legs { opacity: 1; transition: opacity .5s ease .52s; }
.bx.js-r .is-live .in-card {
  opacity: 1; transform: none;
  transition: opacity .45s ease .66s, transform .5s cubic-bezier(.16,.9,.3,1) .66s;
}
.bx.js-r .is-live .in-star { opacity: 1; transition: opacity .3s ease 1.06s; }
.bx.js-r .is-live .in-run--a { animation: in-run 5.6s cubic-bezier(.5,0,.5,1) 1.35s infinite; }
.bx.js-r .is-live .in-run--b { animation: in-run 5.6s cubic-bezier(.5,0,.5,1) 2.65s infinite; }

@keyframes in-run {
  0%        { offset-distance: 0%;   opacity: 0; }
  8%        { opacity: 1; }
  52%, 100% { offset-distance: 100%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .in-w, .bx.js-r .in-ic, .bx.js-r .in-legs,
  .bx.js-r .in-card, .bx.js-r .in-star { opacity: 1; transform: none; }
  .bx.js-r .is-live .in-run { animation: none; }
}

@supports not (offset-path: path("M0,0 L1,1")) {
  .in-run { display: none; }
}

/* The band is now the whole card, so it cannot step aside on a phone the
   way it could when a written sample and a chat room sat above it. Below
   this width the full 1240 would scale down to a grey texture, so it is
   cropped rather than scaled: the insight and the two chips feeding it
   hold a legible size and the messages on either side run off the card —
   which is what the picture is saying in any case. */
@media (max-width: 760px) {
  .in-net { overflow: hidden; }
  .in-svg {
    width: 860px; max-width: none;
    margin-left: 50%; transform: translateX(-50%);
  }
}


/* ══════════════════════════════════════════════════════════════════════
   THE BREAKOUT — adapted from assets/bullish-breakout.html
   The source is a 1600×900 stage with a heading, a price axis, level
   labels, a last-price tag and a signal card, all driven by a 13-second
   script. This card is a fifth of that width, so text at that size would
   be texture; the copy lives in .bx-head/.bx-desc instead. What is kept
   is the picture: a range that keeps testing its ceiling, one candle with
   the volume to clear it, and the ceiling turning into a floor.
   ═══════════════════════════════════════════════════════════════════ */
.bb-svg { display: block; width: 100%; height: auto; max-height: 150px; margin: 0 auto; }

.bb-lvl {
  stroke: var(--hair); stroke-width: 1.2; stroke-dasharray: 5 6;
  vector-effect: non-scaling-stroke;
}
.bb-base-axis { stroke: var(--hair); stroke-width: 1; }

/* candles: neutral, and the one that breaks out is the accent */
.bb-c line { stroke: var(--ink-4); stroke-width: 1.3; stroke-linecap: round; }
.bb-c rect { fill: var(--ink-4); }
.bb-c--up rect:not(.bb-v) { fill: var(--paper-2); stroke: var(--ink-4); stroke-width: 1.2; }
.bb-c .bb-v { fill: var(--ink-4); opacity: .32; stroke: none; }
.bb-c--hit line { stroke: var(--signal); stroke-width: 1.6; }
.bb-c--hit rect:not(.bb-v) { fill: var(--signal); stroke: none; }
.bb-c--hit .bb-v { fill: var(--signal); opacity: .9; }

.bb-ext {
  stroke: var(--signal); stroke-width: 1.8; stroke-linecap: round; opacity: .85;
  stroke-dasharray: 1; vector-effect: non-scaling-stroke;
}
.bb-ring {
  fill: none; stroke: var(--signal); stroke-width: 1.5; opacity: 0;
  transform-box: fill-box; transform-origin: center;
}
.bb-dot { fill: var(--signal); }

/* ── arrival: the range, the candles that build on it one by one, the
   breakout, and the ceiling drawn out as the new floor ─────────────── */
.bx.js-r .bb-base, .bx.js-r .bb-live .bb-c { opacity: 0; }
.bx.js-r .bb-live .bb-c { transform: translateY(6px); }
.bx.js-r .bb-ext { stroke-dashoffset: 1; }
.bx.js-r .bb-dot { opacity: 0; }

.bx.js-r .is-live .bb-base { opacity: 1; transition: opacity .5s ease .05s; }
.bx.js-r .is-live .bb-live .bb-c {
  opacity: 1; transform: none;
  transition: opacity .3s ease var(--bd, 0s),
              transform .4s cubic-bezier(.16,.9,.3,1) var(--bd, 0s);
}
.bx.js-r .is-live .bb-dot { opacity: 1; transition: opacity .3s ease 1.3s; }
.bx.js-r .is-live .bb-ext { stroke-dashoffset: 0; transition: stroke-dashoffset .7s cubic-bezier(.22,.85,.3,1) 1.4s; }
.bx.js-r .is-live .bb-ring { animation: bb-ring 3.2s ease-out 1.3s infinite; }

@keyframes bb-ring {
  0%       { opacity: .55; transform: scale(.6); }
  45%, 100% { opacity: 0;  transform: scale(3.4); }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .bb-base, .bx.js-r .bb-live .bb-c, .bx.js-r .bb-dot { opacity: 1; }
  .bx.js-r .bb-live .bb-c { transform: none; }
  .bx.js-r .bb-ext { stroke-dashoffset: 0; }
  .bx.js-r .is-live .bb-ring { animation: none; }
}



/* ══════════════════════════════════════════════════════════════════════
   THE SIGNAL PLOT — adapted from assets/signal-chart.html
   The source is a 1440×1080 stage that scales to the viewport; here the
   same idea has to live inside a bento tile. What carried over: one
   continuous price line, two moments marked on it, a dashed connector
   down to the alert that fired there. What did not: the fixed stage, the
   replay control, and the two-frame lift — at tile scale they were
   machinery around an idea that reads on its own.

   Markers and connectors are CSS overlay, not SVG children, because the
   plot stretches (preserveAspectRatio="none") and a stretched <circle>
   is an ellipse. The line keeps a non-scaling stroke, so only its shape
   stretches, never its weight.
   ═══════════════════════════════════════════════════════════════════ */
.sg { display: flex; flex-direction: column; flex: 1; margin-top: var(--sp-1); }

/* the tile is grid-row: span 2 and its height is set by the two cards
   beside it, so the slack goes into the plot rather than into dead space
   under the alerts */
.sg-plot { position: relative; flex: 1; min-height: clamp(132px, 15.5vw, 208px); }
.sg-svg { display: block; width: 100%; height: 100%; overflow: visible; }

.sg-grid line { stroke: var(--hair); stroke-width: 1; stroke-dasharray: 5 6; vector-effect: non-scaling-stroke; }
.sg-line {
  fill: none; stroke: var(--ink); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* ── the two marked moments ────────────────────────────────────────── */
.sg-mark {
  position: absolute; left: var(--x); top: var(--y);
  width: 11px; height: 11px; margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  background: var(--signal);
  box-shadow: 0 0 0 3px var(--paper-2), 0 0 0 9px var(--signal-w);
}
/* the ring that keeps breathing after it lands */
.sg-mark::after {
  content: ""; position: absolute; inset: -1px;
  border-radius: 50%; border: 1.4px solid var(--signal);
  opacity: 0;
}

.sg-conn {
  position: absolute; left: var(--x); top: var(--y);
  width: 0; height: calc(100% - var(--y));
  margin-left: -.5px;
  border-left: 1px dashed var(--signal);
  transform-origin: top;
}

/* ── the alert that fired at each marker ───────────────────────────── */
.sg-cards {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.4vw, 22px);
  margin-top: 14px;
}
.sg-card {
  padding: var(--sp-1);
  border: 1px solid var(--hair);
  border-radius: 12px;
  background: var(--paper-2);
}
.sg-kind {
  display: flex; align-items: center; gap: 7px; margin: 0 0 9px;
  font-size: var(--fs-3xs); font-weight: 600;
  letter-spacing: var(--tr-tag); text-transform: uppercase;
  color: var(--signal-d);
}
.sg-kind i { width: 5px; height: 5px; border-radius: 50%; background: var(--signal); flex: none; }

.sg-when {
  margin: 0 0 10px; padding-bottom: 10px;
  border-bottom: 1px solid var(--hair-2);
  font-size: var(--fs-3xs); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.sg-tick {
  margin: 0 0 11px;
  font-size: var(--fs-xs); color: var(--ink-3);
  letter-spacing: var(--tr-body);
}
.sg-tick b { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.sg-up { color: var(--up); font-weight: 500; font-variant-numeric: tabular-nums; }
.sg-dn { color: var(--down); font-weight: 500; font-variant-numeric: tabular-nums; }

.sg-rows { display: grid; gap: 5px; margin: 0; }
.sg-rows > div { display: flex; align-items: baseline; gap: 8px; }
.sg-rows dt {
  flex: none; width: 46px;
  font-size: var(--fs-3xs); color: var(--ink-3);
  letter-spacing: var(--tr-tag); text-transform: uppercase;
}
.sg-rows dd {
  margin: 0; font-size: var(--fs-xs); color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* the calculated levels are the part a member acts on, so they sit at
   full ink and are ruled off from the classification above them */
.sg-rows .sg-lv:first-of-type { margin-top: 7px; padding-top: 9px; border-top: 1px solid var(--hair-2); }
.sg-rows .sg-lv dd { color: var(--ink); font-weight: 500; }
.sg-rows .sg-lv dd span { color: var(--ink-3); font-weight: 400; }
.sg-rows .sg-lv dd em {
  font-style: normal; font-size: var(--fs-3xs);
  letter-spacing: var(--tr-tag); color: var(--ink-4);
}

/* ── the one authored moment: the plot builds itself on arrival ─────
   Base state is the finished chart, so with no JS — and for anyone who
   asked for less motion — the card simply is what it ends as. */
.bx.js-r .sg-line { stroke-dasharray: 1; stroke-dashoffset: 1; }
.bx.js-r .sg-grid line { opacity: 0; }
.bx.js-r .sg-conn { transform: scaleY(0); }
.bx.js-r .sg-mark { transform: scale(0); }
.bx.js-r .sg-card { opacity: 0; transform: translateY(10px); }

.bx.js-r .is-live .sg-line { stroke-dashoffset: 0; transition: stroke-dashoffset 1.05s cubic-bezier(.35,.05,.2,1) .1s; }
.bx.js-r .is-live .sg-grid line { opacity: 1; transition: opacity .4s ease; }
.bx.js-r .is-live .sg-conn { transform: none; transition: transform .38s cubic-bezier(.3,.9,.3,1) calc(1.0s + var(--md, 0s)); }
.bx.js-r .is-live .sg-mark { transform: none; transition: transform .4s cubic-bezier(.16,1,.3,1) calc(.92s + var(--md, 0s)); }
.bx.js-r .is-live .sg-card { opacity: 1; transform: none; transition: opacity .42s ease calc(1.16s + var(--md, 0s)), transform .42s cubic-bezier(.16,.9,.3,1) calc(1.16s + var(--md, 0s)); }
.bx.js-r .is-live .sg-mark::after { animation: sg-pulse 2.6s ease-out 1.5s infinite; }

@keyframes sg-pulse {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.4); opacity: 0; }
  100% { transform: scale(2.4); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .sg-line { stroke-dasharray: none; stroke-dashoffset: 0; }
  .bx.js-r .sg-grid line, .bx.js-r .sg-card { opacity: 1; }
  .bx.js-r .sg-conn, .bx.js-r .sg-mark, .bx.js-r .sg-card { transform: none; }
  .bx.js-r .is-live .sg-mark::after { animation: none; }
}

@media (max-width: 620px) {
  /* two level tables side by side stop being readable well before they
     stop fitting; the second signal reads under the first instead */
  .sg-cards { grid-template-columns: minmax(0, 1fr); }
  .sg-plot { min-height: clamp(120px, 30vw, 150px); }
}
/* ── mobile navigation ────────────────────────────────────────────────
   The link row used to be display:none below 900px with nothing in its
   place, which left the page with no navigation at all on its primary
   device. */
.fn-menu { display: none; position: relative; }
.fn-menu > summary {
  width: 44px; height: 44px; display: flex; align-items: center;
  justify-content: center; cursor: pointer; list-style: none;
  border-radius: 10px;
}
.fn-menu > summary::-webkit-details-marker { display: none; }
.fn-menu > summary:hover { background: var(--hair-2); }
.fn-menu-bars { display: block; width: 18px; }
.fn-menu-bars i {
  display: block; height: 1.5px; border-radius: 2px; background: var(--ink);
  transition: transform .28s cubic-bezier(.16,.84,.28,1);
}
.fn-menu-bars i + i { margin-top: 5px; }
.fn-menu[open] .fn-menu-bars i:first-child { transform: translateY(3.25px) rotate(45deg); }
.fn-menu[open] .fn-menu-bars i:last-child  { transform: translateY(-3.25px) rotate(-45deg); }
.fn-menu-panel {
  position: absolute; right: 0; top: calc(100% + 10px); z-index: 40;
  display: flex; flex-direction: column; min-width: 208px;
  padding: 8px; border: 1px solid var(--hair); border-radius: 14px;
  background: var(--paper-2);
  box-shadow: 0 1px 2px rgba(10,10,11,.05), 0 18px 40px -20px rgba(10,10,11,.32);
}
.fn-menu-panel a {
  padding: 12px 12px; border-radius: 9px;
  font-size: var(--fs-body); color: var(--ink-2);
}
.fn-menu-panel a:hover { background: var(--hair-2); color: var(--ink); }

/* ── the sticky conversion bar ────────────────────────────────────────
   Below 900px the price sits ~11,000px down the page and the header is
   not sticky, so after the hero there was no control on screen at all. */
.sk {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 10px var(--gut) calc(10px + env(safe-area-inset-bottom, 0px));
  background: rgba(254,254,254,.92);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-top: 1px solid var(--hair);
  transform: translateY(102%);
  transition: transform .42s cubic-bezier(.16,.84,.28,1);
}
.sk.is-up { transform: none; }
.sk-in {
  display: flex; align-items: center; gap: 14px;
  max-width: 640px; margin: 0 auto;
}
.sk-price { margin: 0; font-size: var(--fs-body); color: var(--ink-3); white-space: nowrap; }
.sk-price b {
  font-size: 19px; font-weight: 600; letter-spacing: var(--tr-lead); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.sk-cta {
  margin-left: auto; padding: 13px 26px 14px; border-radius: 99px;
  background: var(--ink); color: var(--paper-2);
  font-size: var(--fs-body); font-weight: 500; white-space: nowrap;
  min-height: 46px; display: inline-flex; align-items: center;
}
.sk-cta:hover { background: var(--signal-d); }

/* ── reassurance at the point of payment ──────────────────────────────
   The four things a buyer needs in the second before they commit. */

/* ── FAQ as an accordion ──────────────────────────────────────────────
   One question open at a time, closed by default. The markup stays
   <details>/<summary>, so with script off every answer is still
   reachable and indexable; script only adds the single-open rule and
   the height transition. The row is full width — the old two-column
   definition grid read as a reference sheet, not as something to open.
   ═══════════════════════════════════════════════════════════════════ */
.fq-list { border-top: 1px solid var(--hair); }
.fq-item { border-bottom: 1px solid var(--hair); }

.fq-q {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3) 0;
  list-style: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .2s ease;
}
.fq-q::-webkit-details-marker { display: none; }
.fq-q::marker { content: ""; }
.fq-q:hover { color: var(--signal-d); }

/* the mark: a plus that becomes a minus. Non-text, so --ink-4 is the
   right tier for it and the hover state is the only colour it takes. */
.fq-chev {
  position: relative; flex: none;
  width: 15px; height: 15px; margin: 3px 0 0 auto;
}
.fq-chev::before, .fq-chev::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 13px; height: 1.5px; border-radius: 1px; background: var(--ink-4);
  transform: translate(-50%, -50%);
  transition: transform .34s cubic-bezier(.2,.8,.2,1), background-color .2s ease;
}
.fq-chev::after { transform: translate(-50%, -50%) rotate(90deg); }
.fq-d[open] > .fq-q .fq-chev::before,
.fq-d[open] > .fq-q .fq-chev::after { background: var(--signal-d); }
.fq-d[open] > .fq-q .fq-chev::after { transform: translate(-50%, -50%) rotate(180deg); }
.fq-q:hover .fq-chev::before, .fq-q:hover .fq-chev::after { background: var(--signal-d); }

/* the clip box script animates; padding lives on the inner box so a
   height of 0 really is zero. */
.fq-body { overflow: hidden; transition: height .38s cubic-bezier(.2,.8,.2,1); }
.fq-body-in {
  padding: 0 0 var(--sp-3);
  max-width: 68ch; padding-right: calc(15px + var(--sp-2));
}

@media (prefers-reduced-motion: reduce) {
  .fq-body { transition: none; }
  .fq-chev::before, .fq-chev::after { transition: none; }
}

@media (max-width: 900px) {
  /* the link row gives way to the menu, not to nothing. The menu is last
     in the row here: the CTA is the one thing in the bar worth reaching
     for, so it takes the thumb's corner and the menu sits outside it —
     and because it follows the CTA in the markup too, tab order and
     reading order agree. */
  .fn-menu { display: block; }
  /* 22px was the desktop bar's padding worn on a phone: with a 44px
     touch target inside it the header stood 89px tall and took an
     eighth of the screen before the page began. */
  /* the row's gap is only ever between these two here — .fn-links is out
     of flow and the CTA's auto margin swallows the rest — so it is set
     to what the pair should be, not what brand-to-links wanted */
  .fn-nav { padding-block: 10px; gap: 12px; }
  /* the sticky bar owns the bottom of the screen here */
  .ft { padding-bottom: calc(var(--sp-5) + 76px); }
}

@media (max-width: 760px) {
  /* the row is a touch target before it is a line of type */
  .fq-q { min-height: 44px; padding-block: var(--sp-2); }
}

@media (max-width: 620px) {
  .sk-cta { padding: 13px 20px 14px; }
  .sk-price b { font-size: 17px; }
}

@media (prefers-reduced-motion: reduce) {
  /* the universal element selector does not reach pseudo-elements, so
     the dot pill and the menu bars need naming */
  .fn-dots button::before, .fn-menu-bars i,
  .fq-chev::before, .fq-chev::after, .sk {
    transition-duration: .01ms !important;
  }
}


/* ══════════════════════════════════════════════════════════════════════
   THE MORNING BRIEFING — adapted from assets/daily-market-outlook.html
   The source is a 1536×1024 stage: six cards around one analytics card,
   every one of them on a drop shadow, plus a dot-matrix world map, a row
   of tag pills, a column of stars, a donut that drifts, a live point
   that never settles, and a requestAnimationFrame loop walking three
   dots through six connector legs. This card is a fifth of that width
   and the page's world is flat line art, so the shadows, the map, the
   pills and the stars are gone.

   What is left is the sentence the picture was making: three things get
   read overnight and at the open, they are read together in one place,
   and three things come out of that reading. The connectors keep the
   source's rounded elbows, because the elbow is what says the legs are
   routed rather than drawn.
   ═══════════════════════════════════════════════════════════════════ */

.dm-svg { display: block; width: 100%; height: auto; max-height: 200px; margin: 0 auto; }

.dm-leg { fill: none; stroke: var(--hair); stroke-width: 1.4; stroke-linecap: round; }

.dm-src rect, .dm-out rect { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.2; }
.dm-tag rect { fill: var(--signal); }
.dm-bar rect { fill: var(--hair); }

.dm-hub { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.4; }
.dm-track { fill: none; stroke: var(--hair-2); stroke-width: 9; }
.dm-arc   { fill: none; stroke: var(--signal); stroke-width: 9; stroke-linecap: butt; }
.dm-tick {
  fill: none; stroke: var(--ink); stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
.dm-hubline--1 { fill: var(--ink-2); }
.dm-hubline--2 { fill: var(--ink-4); }
.dm-hubline--3 { fill: var(--hair); }

/* the dots that make the trip, two in and two out, on one slow loop */
.dm-run { fill: var(--signal); opacity: 0; offset-rotate: 0deg; offset-distance: 0%; }
.dm-run--a { offset-path: path("M68,84 H126"); }
.dm-run--b { offset-path: path("M68,28 H88 A12,12 0 0 1 100,40 V72 A12,12 0 0 0 112,84 H126"); }
.dm-run--c { offset-path: path("M234,84 H244 A12,12 0 0 0 256,72 V40 A12,12 0 0 1 268,28 H292"); }
.dm-run--d { offset-path: path("M234,84 H244 A12,12 0 0 1 256,96 V128 A12,12 0 0 0 268,140 H292"); }

/* ── arrival: what is read, then where it is read, then what comes out
   of it — the same left-to-right order the card is scanned in ────── */
.bx.js-r .dm-leg { opacity: 0; }
.bx.js-r .dm-src rect, .bx.js-r .dm-out rect { opacity: 0; transform: translateX(-6px); }
.bx.js-r .dm-out rect { transform: translateX(6px); }
.bx.js-r .dm-tag rect, .bx.js-r .dm-bar rect { opacity: 0; }
.bx.js-r .dm-hub { opacity: 0; transform: scale(.94); transform-box: fill-box; transform-origin: center; }
.bx.js-r .dm-ring, .bx.js-r .dm-tick { opacity: 0; }
.bx.js-r .dm-hubline { opacity: 0; }

.bx.js-r .is-live .dm-src rect, .bx.js-r .is-live .dm-out rect {
  opacity: 1; transform: none;
  transition: opacity .42s ease var(--dmd, .2s),
              transform .46s cubic-bezier(.16,.9,.3,1) var(--dmd, .2s);
}
.bx.js-r .is-live .dm-src rect:nth-child(1) { --dmd: .18s; }
.bx.js-r .is-live .dm-src rect:nth-child(2) { --dmd: .26s; }
.bx.js-r .is-live .dm-src rect:nth-child(3) { --dmd: .34s; }
.bx.js-r .is-live .dm-out rect:nth-child(1) { --dmd: .92s; }
.bx.js-r .is-live .dm-out rect:nth-child(2) { --dmd: 1s; }
.bx.js-r .is-live .dm-out rect:nth-child(3) { --dmd: 1.08s; }
.bx.js-r .is-live .dm-tag rect, .bx.js-r .is-live .dm-bar rect { opacity: 1; transition: opacity .4s ease .5s; }
.bx.js-r .is-live .dm-leg { opacity: 1; transition: opacity .5s ease .46s; }
.bx.js-r .is-live .dm-hub {
  opacity: 1; transform: none;
  transition: opacity .45s ease .52s, transform .5s cubic-bezier(.16,.9,.3,1) .52s;
}
.bx.js-r .is-live .dm-ring { opacity: 1; transition: opacity .4s ease .72s; }
.bx.js-r .is-live .dm-tick { opacity: 1; transition: opacity .3s ease .86s; }
.bx.js-r .is-live .dm-hubline { opacity: 1; transition: opacity .4s ease .8s; }
.bx.js-r .is-live .dm-run--a { animation: dm-run 6.4s cubic-bezier(.5,0,.5,1) 1.3s infinite; }
.bx.js-r .is-live .dm-run--b { animation: dm-run 6.4s cubic-bezier(.5,0,.5,1) 2.9s infinite; }
.bx.js-r .is-live .dm-run--c { animation: dm-run 6.4s cubic-bezier(.5,0,.5,1) 2.1s infinite; }
.bx.js-r .is-live .dm-run--d { animation: dm-run 6.4s cubic-bezier(.5,0,.5,1) 3.7s infinite; }

@keyframes dm-run {
  0%        { offset-distance: 0%;   opacity: 0; }
  9%        { opacity: 1; }
  46%, 100% { offset-distance: 100%; opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .dm-leg, .bx.js-r .dm-tag rect, .bx.js-r .dm-bar rect,
  .bx.js-r .dm-ring, .bx.js-r .dm-tick, .bx.js-r .dm-hubline,
  .bx.js-r .dm-src rect, .bx.js-r .dm-out rect, .bx.js-r .dm-hub { opacity: 1; }
  .bx.js-r .dm-src rect, .bx.js-r .dm-out rect, .bx.js-r .dm-hub { transform: none; }
  .bx.js-r .is-live .dm-run { animation: none; }
  .dm-run { display: none; }
}

/* the motion path is the only thing here that cannot degrade quietly —
   without it the dot would sit at the origin, so it stays hidden */
@supports not (offset-path: path("M0,0 L1,1")) {
  .dm-run { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════
   THE SWEEP — adapted from assets/aerologic-bot-swing.html
   The source is a 1536×1024 stage: a chart card, a radar card, an alert
   card, a target card, an AeroLogic device with the mark embedded as a
   quarter-megabyte data URI, three beam gradients, and a frame loop
   animating the sweep, the radar blips, the bell rays and the target
   arcs at once. This card is a fifth of that width, so all of that
   furniture would arrive as texture rather than as meaning.

   What is left is the claim: a run of candles, a beam that crosses
   them, and the one place the beam stops. Everything the bot does
   between those two facts is the product, not the picture.
   ═══════════════════════════════════════════════════════════════════ */

.bsw-svg { display: block; width: 100%; height: auto; max-height: 176px; margin: 0 auto; }

.bsw-grid line {
  stroke: var(--hair); stroke-width: 1;
  stroke-dasharray: 5 6; vector-effect: non-scaling-stroke;
}

/* the candles: neutral until one of them is the answer */
.bsw-c line { stroke: var(--ink-4); stroke-width: 1.4; stroke-linecap: round; }
.bsw-c rect { fill: var(--ink-4); }
.bsw-c--up rect { fill: var(--paper-2); stroke: var(--ink-4); stroke-width: 1.3; }
.bsw-c--hit line { stroke: var(--signal); stroke-width: 1.8; }
.bsw-c--hit rect { fill: var(--signal); }

/* the sweep. The gradient stops carry the accent so the beam moves with
   the token rather than with a hard-coded orange. */
.bsw-beam-a { stop-color: var(--signal); stop-opacity: .26; }
.bsw-beam-b { stop-color: var(--signal); stop-opacity: 0; }
.bsw-edge { stroke: var(--signal); stroke-width: 1.4; opacity: .5; }
.bsw-beam { transform: translateX(252px); }

/* where it stops */
.bsw-spot { fill: var(--signal-w); }
.bsw-cross circle { fill: none; stroke: var(--signal); stroke-width: 1.8; }
.bsw-crosst line { stroke: var(--signal); stroke-width: 1.6; stroke-linecap: round; }

/* what comes out of it */
.bsw-link {
  fill: none; stroke: var(--signal); stroke-width: 1.3;
  stroke-dasharray: 4 5; stroke-linecap: round;
}
.bsw-chip { fill: var(--paper-2); stroke: var(--hair); stroke-width: 1.2; }
.bsw-chipdot { fill: var(--signal); }
.bsw-chipbar { fill: var(--ink-4); }

/* ── arrival: the field, then the sweep across it, then the one thing
   the sweep found, then the alert that leaves ─────────────────────── */
.bx.js-r .bsw-c { opacity: 0; transform: translateY(8px); }
.bx.js-r .bsw-grid line { opacity: 0; }
.bx.js-r .bsw-spot, .bx.js-r .bsw-cross { opacity: 0; }
.bx.js-r .bsw-cross { transform: scale(.6); transform-box: fill-box; transform-origin: center; }
.bx.js-r .bsw-link { stroke-dashoffset: 30; opacity: 0; }
.bx.js-r .bsw-chip, .bx.js-r .bsw-chipdot, .bx.js-r .bsw-chipbar {
  opacity: 0; transform: translateY(-6px);
}

.bx.js-r .is-live .bsw-grid line { opacity: 1; transition: opacity .4s ease .2s; }
.bx.js-r .is-live .bsw-c {
  opacity: 1; transform: none;
  transition: opacity .34s ease var(--bd, 0s),
              transform .4s cubic-bezier(.16,.9,.3,1) var(--bd, 0s);
}
/* the candles are the third through eighteenth <g> in the svg — the grid
   and the beam are the first two, and nth-of-type counts every sibling of
   that element type, matched or not */
.bx.js-r .is-live .bsw-c:nth-of-type(3) { --bd: .26s; }
.bx.js-r .is-live .bsw-c:nth-of-type(4) { --bd: .30s; }
.bx.js-r .is-live .bsw-c:nth-of-type(5) { --bd: .34s; }
.bx.js-r .is-live .bsw-c:nth-of-type(6) { --bd: .38s; }
.bx.js-r .is-live .bsw-c:nth-of-type(7) { --bd: .42s; }
.bx.js-r .is-live .bsw-c:nth-of-type(8) { --bd: .46s; }
.bx.js-r .is-live .bsw-c:nth-of-type(9) { --bd: .50s; }
.bx.js-r .is-live .bsw-c:nth-of-type(10) { --bd: .54s; }
.bx.js-r .is-live .bsw-c:nth-of-type(11) { --bd: .58s; }
.bx.js-r .is-live .bsw-c:nth-of-type(12) { --bd: .62s; }
.bx.js-r .is-live .bsw-c:nth-of-type(13) { --bd: .66s; }
.bx.js-r .is-live .bsw-c:nth-of-type(14) { --bd: .70s; }
.bx.js-r .is-live .bsw-c:nth-of-type(15) { --bd: .74s; }
.bx.js-r .is-live .bsw-c:nth-of-type(16) { --bd: .78s; }
.bx.js-r .is-live .bsw-c:nth-of-type(17) { --bd: .82s; }
.bx.js-r .is-live .bsw-c:nth-of-type(18) { --bd: .86s; }
.bx.js-r .is-live .bsw-spot { opacity: 1; transition: opacity .5s ease 1.5s; }
.bx.js-r .is-live .bsw-cross {
  opacity: 1; transform: none;
  transition: opacity .3s ease 1.42s, transform .44s cubic-bezier(.16,1,.3,1) 1.42s;
}
.bx.js-r .is-live .bsw-link {
  opacity: 1; stroke-dashoffset: 0;
  transition: opacity .3s ease 1.72s, stroke-dashoffset .5s ease 1.72s;
}
.bx.js-r .is-live .bsw-chip, .bx.js-r .is-live .bsw-chipdot, .bx.js-r .is-live .bsw-chipbar {
  opacity: 1; transform: none;
  transition: opacity .36s ease 1.92s, transform .42s cubic-bezier(.16,.9,.3,1) 1.92s;
}
.bx.js-r .is-live .bsw-beam { animation: bsw-sweep 7s cubic-bezier(.42,0,.58,1) 1s infinite; }

/* the beam crosses the field, fades where it found something, and comes
   back from the left rather than sliding back through the candles */
@keyframes bsw-sweep {
  0%       { transform: translateX(10px);  opacity: 0; }
  6%       { opacity: 1; }
  46%      { transform: translateX(252px); opacity: 1; }
  58%      { transform: translateX(252px); opacity: 0; }
  100%     { transform: translateX(252px); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .bx.js-r .bsw-c, .bx.js-r .bsw-grid line, .bx.js-r .bsw-spot,
  .bx.js-r .bsw-cross, .bx.js-r .bsw-link, .bx.js-r .bsw-chip,
  .bx.js-r .bsw-chipdot, .bx.js-r .bsw-chipbar { opacity: 1; }
  .bx.js-r .bsw-c, .bx.js-r .bsw-cross, .bx.js-r .bsw-chip,
  .bx.js-r .bsw-chipdot, .bx.js-r .bsw-chipbar { transform: none; }
  .bx.js-r .bsw-link { stroke-dashoffset: 0; }
  .bx.js-r .is-live .bsw-beam { animation: none; }
}


/* ══════════════════════════════════════════════════════════════════════
   LOOPING MOTION STARTS WHERE IT IS READ
   Section arrival was already gating most of this page's motion, but a
   section is not the unit a reader arrives at. The bento is six cards
   tall and the flow section is a header plus a wide diagram: gated on
   the section, the last card's loop has been running for several
   seconds by the time anyone reaches it, and two of the loops — the
   condition band in section 2 and the ripple under the mark in section
   3 — were not gated at all and started at page load, below the fold,
   for nobody.

   So the block that holds a loop carries [data-anim] and its animations
   are paused until that block itself has been scrolled to. Pausing,
   rather than withholding the declaration, is what keeps this to one
   rule: animation-play-state suspends the delay as well as the run, so
   every carefully staggered delay in this file still counts from the
   moment its own block is on screen. Once started, a loop is left
   running — a block re-pausing behind the reader is motion nobody sees
   and jank they might.

   The hero is deliberately not marked: it is on screen at load, and a
   loop that waits for a scroll that may never come is a loop that never
   runs. Transitions are untouched — [data-r] arrival still belongs to
   the section, because that is genuinely what arrives.

   .js-anim is set by script, so with no script every loop runs as it
   always did rather than sitting paused forever.
   ═══════════════════════════════════════════════════════════════════ */
/* !important, and only here: `animation:` is a shorthand, so every one
   of the per-element rules above resets play-state to running as a side
   effect of naming a keyframe — and at .bx.js-r .is-live .dm-run--a they
   outrank any selector this rule could reasonably be given. Restoring
   the run needs no second rule: once .is-live lands, :not() stops
   matching and each element's own play-state applies again. */
.js-anim [data-anim]:not(.is-live),
.js-anim [data-anim]:not(.is-live) * { animation-play-state: paused !important; }

/* a pseudo-element is not matched by the * above, and three of this
   page's loops live on one — .sg-mark::after, .fw-plate::after and the
   chevrons — so they are named separately rather than left running */
.js-anim [data-anim]:not(.is-live)::before,
.js-anim [data-anim]:not(.is-live)::after,
.js-anim [data-anim]:not(.is-live) *::before,
.js-anim [data-anim]:not(.is-live) *::after { animation-play-state: paused !important; }
