/* ============================================================================
   exotic — landing rebuild · css/landing/landing.css
   ----------------------------------------------------------------------------
   Glass on a high-contrast ground. The page is one dark room: a violet-cast
   near-black (never pure black — Mercury's hue-cast-neutrals trick, our hue),
   film grain, and light that exists only where it means something — a meridian
   seam, tight spotlights behind the product, the lit edges of smoked glass.
   No photography. No page-wide color weather (the liquid-glass law).

   The product is the artwork: every interface on this page is live HTML —
   the real trade widget, real chart generators, real company marks. No
   screenshots, no browser chrome (the cofounder/Mercury law).

   Voice: Schibsted at micro-graded light weights carries headlines, two-tone
   (bright clause / dim clause). One family only — the serif seasoning retired
   emphasized words, never a full headline. Geist Mono owns every number and
   micro-label (house law).

   Structure: .nv glass nav pill · .hero (type + email capture + .desk, the
   frameless glass dashboard cropped by the fold) · .proof investor strip ·
   #interface self-driving trade demo on a dot-grid stage · #app phone section
   · #markets marquee tape · .stats numerals band · #why manifesto · .close
   warm CTA band · .ft footer.
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root{
  --edge:clamp(22px,5vw,64px);
  --maxw:1280px;
  --font-disp:var(--font-sans);
  --font-season:"Instrument Serif", Georgia, serif;

  /* the room — violet-cast near-blacks, one family */
  --lx-bg:oklch(0.131 0.013 268);
  --lx-bg-2:oklch(0.152 0.014 268);      /* lifted panels of the room itself */
  --lx-warm:oklch(0.148 0.012 310);      /* the closing act warms, barely */

  /* on-dark ink — tinted near-whites, never #fff */
  --lx-ink:oklch(0.962 0.005 262);
  --lx-ink-2:oklch(0.962 0.005 262 / .74);
  --lx-mut:oklch(0.962 0.005 262 / .52);
  --lx-faint:oklch(0.962 0.005 262 / .34);

  /* direction on glass — bright pair, readable over smoked glass */
  --lx-up:oklch(0.82 0.14 158);
  --lx-down:oklch(0.80 0.15 22);

  /* the glass material (the round the user kept) */
  --lx-line:oklch(1 0 0 / .13);
  --glass:linear-gradient(150deg, oklch(0.30 0.016 270 / .46), oklch(0.23 0.015 272 / .30));
  --glass-2:linear-gradient(150deg, oklch(0.32 0.016 270 / .64), oklch(0.24 0.015 272 / .48));
  --glass-in:inset 0 1px 0 oklch(1 0 0 / .13);
  --glass-shadow:0 30px 80px -32px oklch(0 0 0 / .8);

  /* CHIP GLASS — the nav pill's recipe, promoted to a token (user: one surface
     system, no ad-hoc options). Every small thing that floats over BUSY ground
     wears this: the nav, the tape chips, the globe's cards and tick tips, the
     capture pill, glass buttons, the corner pills, the fund cards, the
     backed-by card. -firm is the scrolled-nav variant, for chips that must
     read instantly (the fill toast, the staged fund card). */
  --pod:oklch(0.83 0.14 155);          /* pod.network's green, on-dark */
  --glass-chip:linear-gradient(150deg, oklch(0.24 0.015 270/.5), oklch(0.20 0.015 272/.34));
  --glass-chip-firm:linear-gradient(150deg, oklch(0.21 0.014 270/.82), oklch(0.18 0.013 272/.7));
  --blur-chip:blur(16px) saturate(1.3);
  --chip-shadow:0 14px 40px -18px oklch(0 0 0/.7);
  --chip-in:inset 0 1px 0 oklch(1 0 0/.12);
  --blur:blur(22px) saturate(1.35);
  --blur-lite:blur(12px) saturate(1.25);

  --spring:cubic-bezier(.34,1.56,.64,1);
  --enter:cubic-bezier(.23,1,.32,1);
}

/* SOLAR + TIDE ONLY (decree 2026-07-24): this page's brand light draws on no
   other colorway. One PURE ramp at a time — the corner toggle (and ?ramp=)
   picks solar or tide; js/landing/landing.js writes --g1..6/--grad inline from the
   canonical EXOTIC.COLORWAYS. The static default below is SOLAR (user), matching
   the JS fallback so the page does not change temperature once JS runs — and
   matching body[data-ramp] being absent before then, which is what the grove and
   the canvases read as solar. Direction colors (--up/--down) are data. */
:root{
  --g1:oklch(0.79 0.13 8);    /* Solar · Coral */
  --g2:oklch(0.805 0.13 25);  /* Solar · Salmon */
  --g3:oklch(0.82 0.13 40);   /* Solar · Peach */
  --g4:oklch(0.85 0.12 55);   /* Solar · Apricot */
  --g5:oklch(0.87 0.12 72);   /* Solar · Amber */
  --g6:oklch(0.89 0.11 90);   /* Solar · Gold */
  --grad:linear-gradient(100deg,var(--g1),var(--g2),var(--g3),var(--g4),var(--g5),var(--g6));
  --grad-accent:linear-gradient(100deg,var(--g2),var(--g5));
}

/* ------------------------------------------------------- the light room
   Same architecture, lights on: hue-cast near-white, ink type, white glass
   with ink edges. Everything routed through the lx and glass tokens flips
   here; component exceptions follow their own blocks. */
body:not(.theme-dark){
  --lx-bg:oklch(0.955 0.007 255);
  --lx-bg-2:oklch(0.932 0.008 255);
  --lx-warm:oklch(0.962 0.008 310);
  --lx-ink:oklch(0.185 0.014 260);
  --lx-ink-2:oklch(0.185 0.014 260 / .76);
  --lx-mut:oklch(0.185 0.014 260 / .55);
  --lx-faint:oklch(0.185 0.014 260 / .34);
  --lx-up:oklch(0.55 0.15 158);
  --lx-down:oklch(0.58 0.17 22);
  --lx-line:oklch(0.2 0.014 260 / .16);
  --glass:linear-gradient(150deg, oklch(1 0 0 / .74), oklch(1 0 0 / .46));
  --glass-2:linear-gradient(150deg, oklch(1 0 0 / .86), oklch(1 0 0 / .62));
  --glass-in:inset 0 1px 0 oklch(1 0 0 / .9);
  --glass-shadow:0 24px 60px -28px oklch(0.35 0.03 260 / .38);
  --pod:oklch(0.55 0.14 155);
  --glass-chip:linear-gradient(150deg, oklch(1 0 0/.72), oklch(1 0 0/.5));
  --glass-chip-firm:linear-gradient(150deg, oklch(1 0 0/.92), oklch(0.98 0.004 250/.84));
  --chip-shadow:0 14px 40px -20px oklch(0.3 0.03 260/.4);
  --chip-in:inset 0 1px 0 #fff;
}
body:not(.theme-dark)::after{ mix-blend-mode:multiply; opacity:.05; }

/* ------------------------------------------------------------------- base */
*{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }
body{ margin:0; background:var(--lx-bg); color:var(--lx-ink);
  font-family:var(--font-sans); -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility; overflow-x:hidden;
  /* the room turns mid-scroll, so the ground and its ink ease rather than snap */
  /* 1.15s, not .7s (user: the handover should be smoother). The whole room
     changes at one scroll position, so the crossfade is the only thing making
     it a transition rather than a cut — and at .7s a page-wide swap still read
     as a snap. js/landing/landing.js gives the trigger hysteresis so this
     always gets to run start to finish. */
  transition:background-color 1.15s var(--enter), color 1.15s var(--enter); }
/* the surfaces that read as material follow the room across the same beat */
.gl, .nv, .ctog, .lb-frame, .dk-card, .tk, .cap, .fund-card, .ft-grid, .faq-list,
.lb-chrome, .iph, .ip-card, .glass-widget .tw{
  transition:background 1.15s var(--enter), border-color 1.15s var(--enter),
    box-shadow 1.15s var(--enter), color 1.15s var(--enter); }
/* the grove's own night dim is part of the same handover, so it travels with it
   rather than snapping while everything else eases */
.ft-scene, .ft-scene::before{ transition:filter 1.15s var(--enter); }
a{ color:inherit; text-decoration:none; }
img{ max-width:100%; }
.wrap{ max-width:var(--maxw); margin:0 auto; padding-left:var(--edge); padding-right:var(--edge); }
::selection{ background:color-mix(in oklab, var(--g3) 32%, transparent); }
:where(button,a,input,select,[tabindex]):focus-visible{ outline:none; box-shadow:var(--focus-ring); }

/* film grain — the room's only full-bleed texture, almost subliminal */
body::after{ content:""; position:fixed; inset:-50%; z-index:90; pointer-events:none; opacity:.05;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size:240px 240px; mix-blend-mode:screen; }

/* mono micro-label — the eyebrow voice everywhere */
.eyebrow{ font-family:var(--font-num); font-size:11px; font-weight:600; letter-spacing:.22em;
  text-transform:uppercase; color:var(--lx-mut); margin:0; }
.eyebrow b{ color:var(--lx-ink-2); font-weight:600; }

/* the generative mark (js/brand/exotic-brand.js mounts into .lt-mark hosts) */
.lt-mark{ display:block; flex:0 0 auto; }
.lt-mark svg{ width:100%; height:100%; display:block; }
.mk-grad{ opacity:0; } .mk-lines{ opacity:1; }

/* the house logo chip — EXOTIC_MARKS.imgHTML emits these (white field, real
   mark; gradient monogram when no mark exists; dark chip for white-on-clear) */
.logo{ position:relative; isolation:isolate; display:inline-flex; align-items:center;
  justify-content:center; background:#fff; border-radius:8px; overflow:hidden; flex:0 0 auto;
  box-shadow:inset 0 0 0 1px oklch(1 0 0/.08); }
.logo img{ width:100%; height:100%; object-fit:contain; padding:14%; }
.logo.mono{ background:oklch(1 0 0/.1); }
.logo.mono::after{ content:attr(data-mono); position:absolute; inset:0; display:flex;
  align-items:center; justify-content:center; font-family:var(--font-num); font-weight:600;
  font-size:10px; letter-spacing:-.02em;
  background:var(--grad); -webkit-background-clip:text; background-clip:text; color:transparent; }
.logo.inv{ background:oklch(0.26 0.015 268); }

/* section reveal — js/landing/landing.js flips .in when a [data-io] enters the viewport */
[data-io]{ opacity:0; transform:translateY(18px);
  transition:opacity .8s var(--enter), transform .8s var(--enter);
  transition-delay:var(--d, 0s); }
[data-io].in{ opacity:1; transform:none; }

/* ------------------------------------------------------------- stagger
   Every group on this page arrived as one block, because the [data-io] hook
   sits on the wrapper. A 40–70ms cascade reads as things settling into place
   rather than a slab appearing. It is decorative, so it never gates
   interaction — the children are already hit-testable, only their paint is
   delayed — and reduced motion drops it entirely. */
.stg > *{ opacity:0; transform:translateY(12px);
  transition:opacity .62s var(--enter), transform .62s var(--enter); }
.stg.in > *{ opacity:1; transform:none; }
.stg.in > :nth-child(1){ transition-delay:.02s; }
.stg.in > :nth-child(2){ transition-delay:.08s; }
.stg.in > :nth-child(3){ transition-delay:.14s; }
.stg.in > :nth-child(4){ transition-delay:.2s; }
.stg.in > :nth-child(5){ transition-delay:.26s; }
.stg.in > :nth-child(6){ transition-delay:.32s; }
.stg.in > :nth-child(7){ transition-delay:.38s; }
.stg.in > :nth-child(8){ transition-delay:.44s; }
.stg.in > :nth-child(9){ transition-delay:.5s; }
/* the wrapper itself stops moving once its children do the arriving */
[data-io].stg{ transform:none; transition:opacity .4s var(--enter); }
@media (prefers-reduced-motion:reduce){
  .stg > *{ opacity:1; transform:none; transition:none; }
  .stg.in > *{ transition-delay:0s !important; }
}

/* ------------------------------------------------------------ glass grammar */
.gl{ background:var(--glass); -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
  border:1px solid var(--lx-line); border-radius:14px;
  box-shadow:var(--glass-shadow), var(--glass-in); color:var(--lx-ink); }

/* buttons — the widget's grammar, page-wide: flat solid fills, sharp
   corners, brightness+lift on hover. No gradient tricks (user decree). */
.pbtn{ display:inline-flex; align-items:center; justify-content:center; gap:8px;
  font-family:var(--font-sans); font-size:13.5px; font-weight:600; letter-spacing:-.005em;
  padding:13px 22px; border-radius:10px; border:0; white-space:nowrap; cursor:pointer;
  transition:transform .16s var(--enter), filter .16s, background .2s, border-color .2s; }
.pbtn:active{ transform:scale(.98); }
.pbtn.light{ color:oklch(0.16 0.014 260); background:oklch(0.985 0.003 250);
  box-shadow:0 1px 2px -1px oklch(0 0 0/.4), 0 12px 30px -16px oklch(0 0 0/.7); }
@media (hover:hover) and (pointer:fine){
.pbtn.light:hover{ filter:brightness(1.04); transform:translateY(-1px); }
}
.pbtn.light .arr{ transition:transform .18s; }
.pbtn.light:hover .arr{ transform:translateX(3px); }
body:not(.theme-dark) .pbtn.light{ color:oklch(0.985 0.003 250); background:var(--lx-ink);
  box-shadow:0 1px 2px -1px oklch(0.3 0.03 260/.3), 0 12px 26px -16px oklch(0.3 0.03 260/.5); }
.pbtn.glass{ color:var(--lx-ink); border:1px solid var(--lx-line); background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  box-shadow:var(--chip-shadow), var(--chip-in); }
.pbtn.glass:hover{ border-color:color-mix(in oklab, var(--lx-ink) 38%, transparent); }

/* ------------------------------------------------------------------- nav */
/* At the top the bar is just the mark and the name — the hero has its own
   headline and its own capture form under it, and a full menu over them is
   noise. Passing that CTA expands the pill into the whole bar (user, round
   10), on the same beat and with the same growth idiom as the CTA itself. */
/* Its vertical position is SCROLL-DRIVEN, not transitioned: js/landing/landing.js writes
   --nv-top as max(12, rest - scrollY) so the pill travels up with the headline
   1:1, exactly as if it were in the flow, and then locks at 12px. A transition
   on top would make it lag behind the text it is travelling with. The expand is
   the part that animates, once it has locked. */
.nv{ position:fixed;
  top:calc(var(--nv-top, clamp(34px,6.5vh,68px)) + env(safe-area-inset-top,0px));
  left:50%; transform:translateX(-50%);
  /* gap:0 while collapsed. The hidden menu and the hidden CTA are still flex
     items, so their two 6px gaps were living on inside the pill — 30px of inset
     to the right of the "c" against 18px to the left of the mark (user spotted
     it). The gap comes back with the bar. */
  z-index:80; display:flex; align-items:center; gap:0; padding:7px 15px;
  border-radius:13px; white-space:nowrap;
  /* the GIC material (user, from generalintelligencecompany.com): the fill is
     almost nothing — the page itself, blurred, IS the bar. A whisper of tint
     keeps the pill legible at rest; the border does the drawing. The chips
     keep the chip recipe — this transparency is the nav's own. */
  background:oklch(0.22 0.015 270 / 0.10);
  -webkit-backdrop-filter:blur(10px) saturate(1.25); backdrop-filter:blur(10px) saturate(1.25);
  border:1px solid oklch(1 0 0 / 0.17);
  box-shadow:var(--chip-in);
  transition:background .4s, box-shadow .4s, padding .48s var(--enter); }
body:not(.theme-dark) .nv{ background:oklch(1 0 0 / 0.16);
  border-color:oklch(0.2 0.014 260 / 0.15); }
/* A lone brand mark belongs with the headline it introduces; a BAR belongs at
   the top of the screen. It travels up with the text and LOCKS, and only then
   does it expand — the CTA is a separate, later beat that waits for the hero's
   own capture form to go by. */
.nv.up, .nv.full{ padding:7px 15px; gap:6px; }
.nv.full{ padding:6px 6px 6px 14px; }
/* floating over content the bar takes a touch more body — still far from
   the old chip fill */
.nv.scrolled{ background:oklch(0.19 0.014 270 / 0.38); }
body:not(.theme-dark) .nv.scrolled{ background:oklch(0.99 0.002 250 / 0.45); }
.nv-brand{ display:inline-flex; align-items:center; gap:9px; margin-right:0;
  transition:margin-right .48s var(--enter); }
.nv.up .nv-brand, .nv.full .nv-brand{ margin-right:6px; }
.nv-brand .lt-mark{ width:22px; height:22px; }
.nv-brand b{ font-family:var(--font-num); font-size:14.5px; font-weight:600; letter-spacing:-.01em; }
/* collapsed it is not just invisible, it is out of the tab order too */
.nv nav{ display:flex; gap:2px; max-width:0; overflow:hidden; opacity:0; visibility:hidden;
  transition:max-width .5s var(--enter), opacity .28s var(--enter), visibility 0s linear .5s; }
.nv.up nav, .nv.full nav{ max-width:600px; opacity:1; visibility:visible;
  transition:max-width .5s var(--enter), opacity .34s var(--enter) .1s, visibility 0s; }
.nv nav a{ font-family:var(--font-num); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--lx-mut); padding:8px 11px; border-radius:8px; transition:color .18s, background .18s; }
.nv nav a:hover{ color:var(--lx-ink); background:oklch(1 0 0/.09); }
/* the nav's CTA waits its turn: the hero has its own capture form, so this
   one only arrives once that has scrolled away. It GROWS out of the pill
   rather than fading in beside it, so the nav makes room for it and
   re-centres instead of jumping. That means animating max-width and the
   inline padding — layout, deliberately: position:fixed makes the nav its
   own containing block, so the reflow is confined to this one flex line and
   happens once per visit. A transform-only version would have to reserve
   the space up front, and the empty gap reads as a bug. */
.nv-cta{ font-size:12.5px; font-weight:600; border-radius:10px; overflow:hidden;
  color:oklch(0.16 0.014 260); background:oklch(0.985 0.003 250);
  box-shadow:inset 0 1px 0 oklch(1 0 0/.7);
  max-width:0; padding:9px 0; opacity:0; pointer-events:none; white-space:nowrap;
  transition:max-width .46s var(--enter), padding .46s var(--enter),
    opacity .28s var(--enter) .07s, transform .3s var(--enter); }
.nv-cta.on{ max-width:200px; padding:9px 15px; opacity:1; pointer-events:auto; }
/* a tap fires a false hover on touch and leaves the pill stuck lifted */
@media (hover:hover) and (pointer:fine){ .nv-cta.on:hover{ transform:translateY(-1px); } }
.nv-cta.on:active{ transform:scale(.96); }
/* ---- the burger and its sheet: phones only ----
   On phones the bar spans the screen, and with only the mark inside it read
   as an empty box (user: "the top exotic pill breaks"). The burger gives the
   width a reason and carries the sections the bar cannot show. */
/* ---- navigation on phones: its own island, on the left ----
   Not a button inside the brand pill (user) — a separate chip-glass island at
   the same height as the pill, absent at the top of the page and arriving on
   the same beat the desktop bar's CTA does. It ships with a `hidden`
   attribute that landing.js lifts, so a stale stylesheet can never strand it
   on the desktop. */
.nv-side[hidden]{ display:none !important; }
.nv-side{ display:none; }
@media (max-width:760px){
  /* the bar stays a COMPACT pill here too (user: "behave similar to how we see
     on the desktop view") — it was stretched to the screen's edges, which is
     what made it read as an empty box in the first place */
  .nv nav{ display:none; }
  /* ---- the brand pill holds ONE size on phones ----
     Its width was drifting as you scrolled (user): .up adds a 6px brand
     margin and .full cuts the right padding 15px -> 6px, both transitioned —
     states that exist so a DESKTOP bar can expand around its menu and CTA,
     neither of which is here. Frozen, and 44px tall so it matches the
     navigation island exactly. */
  .nv, .nv.up, .nv.full{ padding:0 15px; gap:0; min-height:44px;
    box-sizing:border-box; }
  .nv .nv-brand, .nv.up .nv-brand, .nv.full .nv-brand{ margin-right:0; }
  .nv-side{ position:fixed; z-index:81;
    top:calc(var(--nv-top, clamp(34px,6.5vh,68px)) + env(safe-area-inset-top,0px));
    left:var(--edge); display:block;
    opacity:0; transform:translateX(-10px); pointer-events:none;
    transition:opacity .3s var(--enter), transform .45s var(--enter); }
  .nv-side.on{ opacity:1; transform:none; pointer-events:auto; }
  .nv-burger{ display:flex; flex-direction:column; justify-content:center;
    gap:5px; width:44px; height:44px; padding:0 11px;
    -webkit-appearance:none; appearance:none; cursor:pointer;
    /* THE NAV'S OWN MATERIAL, not the chip recipe (user: same glass as the
       cookie bar and the logo pill). Those two are actually different materials
       — the bar wears the chip recipe, the pill wears the near-transparent GIC
       fill — and this island sits directly BESIDE the pill, so it takes the
       pill's: an almost-nothing fill, the page blurred through it, and the
       border doing the drawing. At --glass-chip-firm it was the most opaque
       surface on the page sitting next to the most transparent one. */
    border-radius:12px;
    background:oklch(0.22 0.015 270 / 0.10);
    -webkit-backdrop-filter:blur(10px) saturate(1.25); backdrop-filter:blur(10px) saturate(1.25);
    border:1px solid oklch(1 0 0 / 0.17); box-shadow:var(--chip-in); }
  body:not(.theme-dark) .nv-burger{ background:oklch(1 0 0 / 0.16);
    border-color:oklch(0.2 0.014 260 / 0.15); }
  /* and it firms up when the nav does. The pill thickens once content is passing
     under it; an island beside it that stayed transparent would break the pair
     at exactly the moment both are hardest to read. */
  .nv-burger{ transition:background .4s; }
  body:has(.nv.scrolled) .nv-burger{ background:oklch(0.19 0.014 270 / 0.38); }
  body:not(.theme-dark):has(.nv.scrolled) .nv-burger{ background:oklch(0.99 0.002 250 / 0.45); }
  /* 2px, NOT 1.5px (user: the two lines looked different weights — they were
     not meant to). 1.5px is 4.5 device pixels at DPR 3, and the two bars sit at
     different fractional offsets, so one rounded to 4 and the other to 5. A whole
     even number lands identically at every ratio.
     The X's travel is half the centre-to-centre distance, which is height + gap:
     (2 + 5) / 2 = 3.5, so the two ends meet exactly. */
  .nv-burger i{ display:block; height:2px; border-radius:1px; background:var(--lx-ink);
    transition:transform .3s var(--enter), opacity .2s; }
  .nv-burger[aria-expanded="true"] i:first-child{ transform:translateY(3.5px) rotate(45deg); }
  .nv-burger[aria-expanded="true"] i:last-child{ transform:translateY(-3.5px) rotate(-45deg); }
  /* the CTA is the sheet's job on phones — two calls to action in one bar is
     one too many */
  .nv-cta{ display:none !important; }
}
.nv-sheet[hidden]{ display:none; }
.nv-sheet{ position:absolute; top:calc(100% + 8px); left:0;
  width:min(272px, calc(100vw - 2*var(--edge)));
  display:flex; flex-direction:column; padding:8px;
  border-radius:13px;
  /* the chip recipe OVER the page's own ground — a menu must be readable, and
     at chip alphas alone the headline read straight through the links */
  background:var(--glass-chip-firm), var(--lx-bg);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line); box-shadow:var(--elev-3, var(--chip-shadow)), var(--chip-in);
  opacity:0; transform-origin:0 0;
  transition:opacity .22s var(--enter), transform .3s var(--enter);
  transform:translateY(-8px) scale(.985); }
.nv-sheet.on{ opacity:1; transform:none; }
.nv-sheet a{ display:flex; align-items:center; min-height:46px; padding:0 12px;
  border-radius:9px; font-size:15px; font-weight:500; color:var(--lx-ink);
  transition:background .15s; }
.nv-sheet a:active{ background:color-mix(in oklab, var(--lx-ink) 8%, transparent); }
.nv-sheet .go{ margin-top:4px; justify-content:center; font-weight:600;
  color:oklch(0.16 0.014 260); background:oklch(0.985 0.003 250); }
body:not(.theme-dark) .nv-sheet .go{ color:oklch(0.985 0.003 250); background:var(--lx-ink); }
.nv-sheet .go .arr{ margin-left:8px; }
@media (prefers-reduced-motion:reduce){
  .nv-sheet, .nv-sheet.on{ transition:opacity .15s linear; transform:none; }
  .nv-side, .nv-side.on{ transition:opacity .2s linear; transform:none; }
  .nv-burger i{ transition:none; }
}
@media (prefers-reduced-motion:reduce){
  .nv-cta{ transition:opacity .2s linear; transform:none; }
  .nv-cta.on{ transform:none; }
}

/* ------------------------------------------------------- THE WORLD, PINNED
   The globe opens the page and stays put while you read past it: one fixed
   layer at the bottom of the viewport, under everything (main is z-index 1),
   so the hero's type stands on it like a horizon and the tape of names
   glides across it. globe.js puts the sphere's centre below the stage box,
   so what shows is the top of a planet, not a ball on a shelf.
   It fades once the desk takes the stage — js/landing/landing.js toggles .on. */
.gstage{ position:absolute; left:0; right:0; overflow-x:clip;
  /* in the FLOW now (user): anchored under the hero's copy, it scrolls away
     with the page instead of riding the viewport. The old fixed layer put it
     at 348px from the top of a 966 screen; this reproduces that opening frame
     and then lets it leave like everything else. */
  top:clamp(240px,36vh,420px); z-index:0;
  height:clamp(400px,46vw,560px); cursor:grab; touch-action:pan-y; --acc:var(--g4);
  opacity:0; pointer-events:none; transition:opacity .8s var(--enter);
  /* the crown dissolves into the room like haze, so the hero's own copy sits
     on clear ground instead of competing with the top rows of dots */
  /* crown 46%, skirt from 80%: the crown keeps the hero's copy on clear
     ground, and the skirt melts the dome out before the backed-by row — in
     flow, the box's bottom edge would otherwise cut the dots in a hard line */
  -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 46%, #000 80%, transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0, #000 46%, #000 80%, transparent 100%);
}
.gstage.on{ opacity:1; pointer-events:auto; }
/* Reduced motion gets a SHORTER fade, not none. This rule used to kill the
   transition outright — correct when the globe rose once at the close, wrong
   now that it comes and goes as you read past it, because none means a hard
   pop every time. The fade is opacity only; there is no movement to remove.
   (globe.js's own auto-spin is already off under reduced motion.) */
@media (prefers-reduced-motion:reduce){ .gstage{ transition:opacity .3s linear; } }
@media (max-width:720px){
  .gstage .mk .mk-more{ display:none; }
}
/* ---- THE PHONE'S OPENING FRAME -------------------------------------------
   Three things the desktop gets for free and the phone did not (user: move the
   globe down, show more of it to the end of the page, and bring the ribbons up
   so both are visible).
   The size was the root of it: height is 46vw, which is generous on a desktop
   and only 181px on a 393px phone — so it sat on its 400px floor, a small dome
   with a lot of dark room under it. Driving it from vh instead makes the planet
   scale with the screen it is actually on.
   And the crown's 46% fade was tuned to keep the hero's type clear on a wide
   screen; on a phone that fade ate most of the dome, leaving only the part the
   ribbons cover. At 26% the dome reads above the tape as well as behind it, and
   the hero's copy still sits on clear ground because the box now starts lower. */
@media (max-width:760px){
  /* .act1 .hero, not .hero: the base rule lives LATER in this file and a media
     query adds no specificity, so a bare .hero here would simply lose on order.
     The globe override above does not need it — .gstage's base rule is earlier.

     THE RIBBONS SIT AS LOW AS THEY CAN, not at a fixed inset (user: still a lot of
     space at the bottom). 20vh left 110px of dead room on a 700px screen and only
     46px on a 620px one, so no single value can both fill the tall case and fit the
     short one. Solving it as a budget instead: everything above the second ribbon
     measures ~450px, so the room left over is (viewport - 474). Expressed as
     100svh - 480px it consumes the slack at every height and still clears the
     bottom edge — the same 100vh-minus-a-constant idiom .mkts already uses.
     svh, not vh: vh is the LARGE viewport on a phone and ignores the browser's
     own toolbars, which is exactly the space we must not spend. */
  .act1 .hero{ padding-bottom:clamp(118px, calc(100svh - 480px), 300px); }
  /* and the planet grows into the room the ribbons just gave back */
  .gstage{ top:clamp(258px,38svh,360px); height:clamp(460px,78svh,760px);
    -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 26%, #000 90%, transparent 100%);
    mask-image:linear-gradient(180deg, transparent 0, #000 26%, #000 90%, transparent 100%); }
}
/* SHORT SCREENS NEED A BIGGER SUBTRAHEND. The 480px budget above is measured
   against a 393px-wide headline; narrower than that it wraps one line taller, so
   the same arithmetic overshot and the second ribbon fell 29px off a 360x585
   screen. Under 600px tall the constant grows and the floor drops, which trades
   the globe's lower reach for keeping both ribbons on the page — the right way
   round, since a ribbon that is cut off is a bug and a smaller planet is not. */
@media (max-width:760px) and (max-height:600px){
  .act1 .hero{ padding-bottom:clamp(44px, calc(100svh - 540px), 130px); }
  .gstage{ top:clamp(230px,34svh,300px); height:clamp(420px,72svh,600px); }
}
/* the planet arrives as texture and only NAMES itself once it has the screen
   to itself — the flag cards would otherwise sit on top of the hero's own
   headline and capture form */
.gstage .lt-poles{ opacity:0; transition:opacity .55s var(--enter); }
.gstage.labels .lt-poles{ opacity:1; }
/* The globe is DRAGGABLE again (user, round 10). It is a fixed layer at
   z-index 0, under main — so main used to swallow every pointer event before
   it could reach the planet, and pointer-events:auto on the stage did nothing.
   The page's empty regions now let the pointer through and everything
   interactive re-arms itself, which means the drag lands exactly where the
   planet is actually visible and unobstructed. */
main, .act1, .hero{ pointer-events:none; }
main > *:not(.act1), .act1 > *:not(.hero), .hero-c{ pointer-events:auto; }
/* the globe lives inside .act1 now: its siblings stack ABOVE it (z1 over z0)
   but stay pointer-transparent in their empty regions, so the tape's chips
   stay clickable while a drag between them still reaches the planet */
.hero, .mkts{ z-index:1; }
.act1 > .mkts{ pointer-events:none; }
.mkts .tk, .mkts-foot a, .proof-row{ pointer-events:auto; }

/* ------------------------------------------------------------------- hero */
/* The hero's bottom padding is the planet's room. It replaced a separate
   spacer section AND min-height:100svh — both pushed the markets tape off the
   first screen, and the user wants the strip visible on landing. */
.hero{ position:relative; display:flex; flex-direction:column; justify-content:flex-start;
  padding-top:clamp(116px,14.6vh,180px); padding-bottom:clamp(205px,32vh,320px); }
/* the meridian seam — one hairline of brand light high in the room */
/* The hero's spotlight pair is gone, and so is the .hero-light element that
   carried it: the user's decree is no gradients in the page background, either
   room. The ground is one flat colour and the glass carries the depth. */
/* act 1 — hero, the world, its names, and the unfolding desk */
.act1{ position:relative; }
/* The page's content rides above the pinned globe (.gstage, z-index 0) and
   below the fixed furniture. This rule used to name .nv and .ctog too, which
   silently overrode their own z-index (80/85) with 1 — same specificity,
   later in the file — so main, being later in the DOM, painted OVER the nav
   wherever the two met. That was the "nav is sometimes behind" bug: only
   `main` belongs here. Nothing else may set z-index on .nv or .ctog. */
main{ position:relative; z-index:1; }
/* anchor jumps land clear of the fixed nav pill */
:target{ scroll-margin-top:96px; }
main > section[id], main > .act1 > section[id]{ scroll-margin-top:96px; }
.hero-c{ position:relative; z-index:2; text-align:center; }
.hero-c .eyebrow{ margin-bottom:22px; }
.hero-h{ margin:0; font-family:var(--font-disp); font-weight:420; letter-spacing:-.022em;
  font-size:clamp(46px,7.2vw,104px); line-height:1.01; text-wrap:balance; }
.hero-h em{ font-family:var(--font-season); font-style:italic; font-weight:400;
  letter-spacing:-.004em; font-size:1.02em; }
.hero-h .dim{ color:var(--lx-mut); }
.hero-sub{ margin:22px auto 0; max-width:58ch; font-size:clamp(14.5px,1.5vw,17px); line-height:1.62;
  color:var(--lx-ink-2); text-wrap:balance; }
.hero-sub .dim{ color:var(--lx-mut); }

/* email capture — one fused glass pill (the Mercury move) */
.cap{ display:flex; align-items:center; gap:6px; margin:30px auto 0; padding:6px 6px 6px 16px;
  width:min(480px, calc(100vw - 2*var(--edge))); border-radius:12px;
  background:var(--glass-chip); border:1px solid var(--lx-line);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  box-shadow:var(--chip-shadow), var(--chip-in);
  transition:border-color .2s, background .2s, box-shadow .2s; }
.cap:focus-within{ border-color:oklch(1 0 0/.34); box-shadow:var(--focus-ring); }
.cap input{ flex:1; min-width:0; border:0; background:transparent; outline:none; box-shadow:none;
  font-family:var(--font-sans); font-size:14px; color:var(--lx-ink); padding:10px 0; }
.cap input::placeholder{ color:var(--lx-faint); }
.cap .pbtn{ padding:11px 18px; font-size:13px; border-radius:10px; }
.cap.done{ border-color:color-mix(in oklab, var(--lx-up) 45%, transparent); }
.cap.done .pbtn{ pointer-events:none; }
.cap.done input{ opacity:.45; transition:opacity .3s var(--enter); }
/* the acknowledgement — the page's one tick glyph, drawn rather than popped */
.cap .pbtn .ok{ width:15px; height:15px; flex:0 0 auto; border-radius:50%; display:grid;
  place-items:center; background:color-mix(in oklab, var(--lx-up) 26%, transparent);
  color:color-mix(in oklab, var(--lx-up) 70%, black);
  transform:scale(.7); opacity:0;
  animation:cap-ok .34s var(--enter) both .04s; }
.cap .pbtn .ok svg{ width:9px; height:9px; stroke:currentColor; fill:none; stroke-width:2.6;
  stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:13; stroke-dashoffset:13;
  animation:cap-draw .3s var(--enter) both .18s; }
@keyframes cap-ok{ from{ transform:scale(.7); opacity:0; } to{ transform:none; opacity:1; } }
@keyframes cap-draw{ to{ stroke-dashoffset:0; } }
/* a rejected address shakes the pill — small, once, and never on touch-typing
   corrections (the .nope class is cleared on the next keystroke) */
.cap.nope{ animation:cap-nope .32s var(--enter) both;
  border-color:color-mix(in oklab, var(--lx-down) 50%, transparent); }
@keyframes cap-nope{
  0%{ transform:translateX(0); } 22%{ transform:translateX(-5px); }
  48%{ transform:translateX(4px); } 74%{ transform:translateX(-2px); }
  100%{ transform:translateX(0); } }
@media (prefers-reduced-motion:reduce){
  .cap.nope{ animation:none; }
  .cap .pbtn .ok{ animation:none; transform:none; opacity:1; }
  .cap .pbtn .ok svg{ animation:none; stroke-dashoffset:0; }
}
/* --mut, not --faint: at 2.61:1 over the dot field this line was decoration,
   and it carries the page's only link to the live demo (QA) */

/* hero entrance — content cascades up into the light */
@keyframes hero-rise{ from{ opacity:0; transform:translateY(18px); } to{ opacity:1; transform:none; } }
.nv{ animation:nv-drop .7s var(--enter) both .12s; }
@keyframes nv-drop{ from{ opacity:0; transform:translateX(-50%) translateY(-10px); }
  to{ opacity:1; transform:translateX(-50%) translateY(0); } }
/* the phone override existed because the bar USED to stretch edge to edge with
   transform:none. It is a centred pill again, so the keyframes must carry the
   centring translate — an animation's `to` frame wins over the base rule, and
   `transform:none` was parking the pill half a screen to the right. */
.hero-c > *{ animation:hero-rise .8s var(--enter) both; }
.hero-c > :nth-child(1){ animation-delay:.25s; }
.hero-c > :nth-child(2){ animation-delay:.33s; }
.hero-c > :nth-child(3){ animation-delay:.45s; }
.hero-c > :nth-child(4){ animation-delay:.55s; }
.hero-c > :nth-child(5){ animation-delay:.65s; }

/* ---- the desk — frameless glass dashboard, cropped by the fold ---- */
@keyframes pulse{ 0%{ box-shadow:0 0 0 0 color-mix(in oklab,var(--lx-up) 45%,transparent); }
  70%{ box-shadow:0 0 0 8px transparent; } 100%{ box-shadow:0 0 0 0 transparent; } }

/* --------------------------------------------- the desk window
   (.lobby and the sticky camera stage are history — the window stands whole
   in a plain section now; .lb-sticky survives as a simple centering row) */
.lb-sticky{ display:grid; place-items:center; padding:14px; }

/* THE WINDOW (user). The preview used to be a bare slab whose contents were
   clipped along with its own edges, which is why the first frame read as broken:
   you could not tell a cropped app from a broken panel. The FRAME is whole and
   always visible — glass boundary, its own title bar with the three dots — and
   only the surface INSIDE it un-crops. A window showing part of an app is a
   thing everyone recognises. */
/* THE RIM IS THE CHROME (user, to the reference). The dots used to sit in their
   own bar inside the window, divided from the app by a hairline — two nested
   boxes. In the reference the glass shell itself is deep enough at the top to
   hold them, and the app is an inset SCREEN floating inside that shell with its
   own rounding. So: the frame gains padding, the dots live in that top band,
   and .lb-screen is the surface. */
.lb-chrome{ position:absolute; top:0; left:0; right:0; height:44px; z-index:5;
  display:flex; align-items:center; padding:0 20px; pointer-events:none; }
.lb-screen{ position:relative; flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column; border-radius:12px; overflow:hidden;
  background:oklch(0.152 0.014 268); }
/* the meridian — the app's own top hairline, which the preview was missing
   (user): the real UI draws the brand ramp across its very top edge */
.lb-screen::before{ content:""; position:absolute; top:0; left:0; right:0; height:2px;
  background:var(--grad); z-index:6;
  /* the ends dissolve before the corner radius — a straight bar meeting the
     curve read as a notch (user) */
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
  mask-image:linear-gradient(90deg, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%); }
body:not(.theme-dark) .lb-screen{ background:oklch(0.988 0.003 255); }
.lb-chrome .dots{ display:inline-flex; gap:8px; flex:0 0 auto; }
.lb-chrome .dots i{ width:11px; height:11px; border-radius:50%; display:block;
  box-shadow:inset 0 0 0 1px oklch(0 0 0/.07); }
.lb-chrome .dots .r{ background:#ff5f57; }
.lb-chrome .dots .y{ background:#febc2e; }
.lb-chrome .dots .g{ background:#28c840; }
.lb-frame{ display:flex; flex-direction:column;
  --f-card:linear-gradient(150deg, oklch(1 0 0/.075), oklch(1 0 0/.032));
  --f-fill:oklch(1 0 0/.08);
  --f-line:oklch(1 0 0/.13);
  --f-line2:oklch(1 0 0/.08);
  position:relative; width:min(1180px, calc(100vw - 2*var(--edge) - 24px));
  /* the 740px ceiling left a tall window half empty below the frame (user);
     it scales with the viewport now and only stops at a genuinely big screen */
  height:min(86svh, 1000px);
  /* Its OWN glass, not the generic --glass-2. The band could not read as glass
     because the page ground is flat and a translucent white over near-white is
     indistinguishable from an opaque one — there is nothing behind it to bend.
     What makes the reference read as frosted is the RELATIONSHIP: a cool grey
     band against a white app panel. So the shell is tuned to contrast with the
     screen it holds — cooler and deeper than it in the light room, lighter than
     it in the dark one, where glass catches the light instead. */
  background:linear-gradient(158deg, oklch(0.40 0.018 268/.62), oklch(0.30 0.016 272/.44));
  -webkit-backdrop-filter:blur(26px) saturate(1.5); backdrop-filter:blur(26px) saturate(1.5);
  /* NO border of its own. The two spread rings below ARE the shell's edge — with
     a border as well there were THREE nested outlines at the corner (ring,
     border, screen), which is what made the dots read as sitting inside a second
     box instead of in the shell's band. The reference has exactly two. */
  border:0; border-radius:20px; overflow:hidden;
  padding:44px 14px 14px;
  color:var(--lx-ink); text-align:left;
  /* page | frosted glass | app — and NOTHING between (user, to the reference).
     The two spread rings that used to sit here were strokes; so were the screen's
     inset outline and the top-lip hairline. Three nested edges where the
     reference has none. The shell is a frosted fill plus a drop shadow, and the
     BAND around the app is what reads as the rim — not an outline drawn on it. */
  box-shadow:var(--glass-shadow); }
.lb-frame{ animation:lb-in 1s var(--enter) both .7s; }
body:not(.theme-dark) .lb-frame{
  background:linear-gradient(158deg, oklch(0.905 0.011 255/.78), oklch(0.865 0.013 258/.62));
  box-shadow:var(--glass-shadow);
  --f-card:linear-gradient(150deg, oklch(1 0 0/.92), oklch(1 0 0/.7));
  --f-fill:oklch(0.2 0.014 260/.055);
  --f-line:oklch(0.2 0.014 260/.16);
  --f-line2:oklch(0.2 0.014 260/.11);
  border-color:oklch(0.2 0.014 260/.2); }
@keyframes lb-in{ from{ opacity:0; } to{ opacity:1; } }
/* ---- the app's own window (the hand-built replica retired) ----
   The wrap clips and owns the real size; the iframe is laid out at 1280 and
   scaled into it by landing.js (--lbf-s), exactly like the phone's copy.
   Everything the replica needed — its rail, cards, chart, holdings table and
   the boot choreography cast onto them — is gone with it: the app brings its
   own, and could never drift from the product again (user). */
.lbf-appwrap{ position:absolute; inset:0; overflow:hidden; border-radius:inherit; }
.lbf-app{ border:0; display:block; width:1280px; background:transparent;
  transform-origin:0 0; transform:scale(var(--lbf-s, 0.55));
  /* a live picture, not a usable app: no clicks, no scroll capture */
  pointer-events:none; }


/* ------------------------------------------------------------------ proof */
/* THE PROOF ROW. It was its own section, a 19px logo line floating in ~180px of
   void with no material on a page where everything else sits on glass (QA). It
   is a row under the closing panel now — attached to the ask it supports, and
   the last thing read before the footer. Logos up from 19px so they are legible
   rather than suggested. */
/* Bare on the page (user — the card came and went): a full-ink label and
   full-strength logos carry the trust line without a pane, and the planet now
   stands down BEFORE this row arrives (landing.js), so the ground under it is
   quiet rather than the dot field that made the card necessary. */
.proof-row{ display:flex; align-items:center; justify-content:center;
  gap:clamp(20px,3.2vw,44px); flex-wrap:wrap;
  margin-top:clamp(30px,4vw,52px); }
.proof-row .eyebrow{ width:auto; color:var(--lx-ink); }
.proof-row img{ height:23px; width:auto; opacity:.92; filter:brightness(0) invert(1);
  transition:opacity .2s; }
@media (hover:hover) and (pointer:fine){ .proof-row img:hover{ opacity:1; } }
.proof-row img.sq{ height:28px; } .proof-row img.tall{ height:38px; }
body:not(.theme-dark) .proof-row img{ filter:brightness(0); opacity:.92; }
.proof-row .sep{ width:1px; height:22px; background:var(--lx-line); }
/* the pod credit wears the same clothes as BACKED BY: mono eyebrow, then the
   name at logo weight — one grammar for both provenance lines (user) */
/* THE MARK, NOT THE NAME (user). It was set in the page's own typeface, which
   made the one piece of borrowed identity on the row look like our own copy.
   It rides the same recipe as the investor logos beside it — the .proof-row img
   rule normalizes any source to pure black, then inverts it for the dark room —
   so nothing here has to know that the supplied file is white-on-transparent.
   Slightly shorter than they are: the wordmark's letterforms are blocky and
   solid, so matched heights give it more weight than its neighbours. */
.proof-row .pod-mark{ display:inline-flex; align-items:center; transition:opacity .2s; }
.proof-row .pod-mark img{ height:19px; }
.proof-row .pod-mark:hover{ opacity:.8; }
@media (max-width:700px){
  .proof-row{ gap:14px 22px; }
  .proof-row .sep{ display:none; }
  /* each provenance label takes its own line; logos and the pod mark
     become centered rows under them */
  .proof-row .eyebrow{ flex-basis:100%; text-align:center; }
}

/* ------------------------------------------------- section head grammar */
.sec-head{ text-align:center; margin-bottom:clamp(30px,4.5vw,54px); }
.sec-head .eyebrow{ margin-bottom:16px; }
.sec-head h2{ margin:0; font-family:var(--font-disp); font-weight:430; letter-spacing:-.02em;
  font-size:clamp(30px,4.2vw,56px); line-height:1.06; text-wrap:balance; }
.sec-head h2 .dim{ color:var(--lx-ink-2); }
.sec-head h2 em{ font-family:var(--font-season); font-style:italic; font-weight:400; font-size:1.02em; }
.sec-head .sub{ margin:16px auto 0; max-width:56ch; font-size:15px; line-height:1.6; color:var(--lx-mut); }

/* ------------------------------------------------- interface deep-dive */
.fx{ position:relative; padding:clamp(60px,9vh,130px) 0; }
.fx-stage{ position:relative; padding:clamp(24px,3.4vw,48px) var(--edge) clamp(24px,3.4vw,44px); }

/* ---------------------------------------------- the arc, as a storyboard
   ONE left-to-right row — the idea, the ticket, the position — with a lit line
   drawn between each pair, so you can read cause and effect across the stage.
   Five beats, cumulative .s1–.s5 on #fxStage; the CSS owns every pose and the
   JS only decides when.
     s1  the idea arrives on the left, its 90-day return beside its name
     s2  the ticket arrives in the centre and the first line is drawn to it
     s3  the order fills — the confirmation pops FIRST
     s4  then the position appears on the right, and the second line is drawn
     s5  then its chart wipes in and the P&L starts running (user's order:
         confirmation, then the position, then watching it work)
     s6  news gaps the tape up mid-chart — the wipe continues through the
         jump, a pole rises from the gap to a glass island with the headline
         (the globe's own grammar; hover unfolds the story), P&L steps with it
     s7  the desk offers protections: two one-tap CTAs, recommended levels —
         each click (the storyboard's or YOURS) draws its line on the chart
   Transforms, opacity and clip-paths. Nothing here animates width. */
.dk{ position:relative; z-index:1; }
.dk-row{ position:relative; z-index:1; display:flex; align-items:center; justify-content:center;
  gap:0; max-width:100%; }
.dk-card{ padding:16px 18px; border-radius:12px; text-align:left; flex:0 0 auto;
  opacity:0; transform:translateY(20px) scale(.97);
  transition:opacity .6s var(--enter), transform .7s var(--enter); }
.dk.s1 .dk-idea, .dk.s4 .dk-pos{ opacity:1; transform:none; }
.dk-idea{ width:clamp(268px,25vw,330px); }
.dk-pos{ width:clamp(230px,21vw,278px); }
.dk-card .t{ font-family:var(--font-num); font-size:9.5px; letter-spacing:.18em;
  text-transform:uppercase; color:var(--lx-mut); display:flex; align-items:center; gap:8px; }
.dk-card .t .pdot{ width:6px; height:6px; border-radius:50%; background:var(--lx-up);
  animation:pulse 2.6s ease-in-out infinite; }
.dk-idea h3{ font-size:19px; font-weight:600; letter-spacing:-.015em; }
.dk-idea .dek{ margin:6px 0 0; font-size:12.5px; line-height:1.5; color:var(--lx-mut); }
.dk-card .klabel{ display:block; margin-top:14px; font-family:var(--font-num); font-size:8.5px;
  letter-spacing:.18em; text-transform:uppercase; color:var(--lx-faint); }
/* the weights: one segment per holding, widths ARE the allocations, growing
   left to right as the card lands */
.dk-bar{ display:flex; gap:2px; margin-top:8px; height:5px; }
.dk-bar i{ display:block; width:var(--w); border-radius:2px; background:var(--c); opacity:.88;
  transform:scaleX(0); transform-origin:0 50%;
  transition:transform .6s var(--enter); }
.dk.s1 .dk-bar i{ transform:scaleX(1); transition-delay:calc(.24s + var(--i) * .05s); }
body:not(.theme-dark) .dk-bar i{ opacity:1; filter:saturate(1.5) brightness(.82); }
.dk-hold{ margin-top:10px; display:flex; flex-direction:column; }
.dk-hold .r{ display:flex; align-items:center; gap:9px; padding:6px 0; min-width:0; }
.dk-hold .r + .r{ border-top:1px solid var(--lx-line); }
.dk-hold .r .logo{ border-radius:6px; }
.dk-hold .r b{ flex:1; min-width:0; font-size:12px; font-weight:500; color:var(--lx-ink-2);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.dk-hold .r .w{ font-family:var(--font-num); font-size:10.5px; color:var(--lx-mut);
  font-variant-numeric:tabular-nums; }
/* the 90-day return reads beside the name it belongs to — it was a fold-out
   section of its own, which was a whole beat spent on one number (user) */
.dk-hd{ display:flex; align-items:baseline; justify-content:space-between; gap:12px; margin-top:9px; }
.dk-hd h3{ margin:0; }
.dk-ret{ display:inline-flex; align-items:baseline; gap:5px; flex:0 0 auto; }
.dk-ret b{ font-family:var(--font-num); font-size:15px; font-weight:600; letter-spacing:-.01em;
  color:var(--lx-up); font-variant-numeric:tabular-nums; }
.dk-ret i{ font-style:normal; font-family:var(--font-num); font-size:9px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--lx-faint); }
/* the position's own line — small, beside the numbers it belongs to, and it
   WIPES IN on the last beat so you watch the position start working rather than
   finding a finished chart already sitting there (user) */
.dk-chart{ position:relative; margin:9px -4px 2px; height:clamp(74px,7.4vw,96px);
  opacity:0; transition:opacity .3s var(--enter); }
.dk.s5 .dk-chart{ opacity:1; }
.dk-chart svg{ width:100%; height:100%; display:block;
  clip-path:inset(0 100% 0 0); transition:clip-path 1.1s var(--enter); }
.dk.s5 .dk-chart svg{ clip-path:inset(0 52% 0 0); }
.dk.s6 .dk-chart svg{ clip-path:inset(0 0 0 0); }
.dk-chart .ln{ stroke:var(--lx-up); stroke-width:1.6; vector-effect:non-scaling-stroke; }
.dk-chart .ar{ fill:color-mix(in oklab, var(--lx-up) 10%, transparent); }
/* the levels: each hairline is ATTACHED by its own click (tp-on / sl-on on
   the card — the storyboard and a real tap share the same door); the band
   between them appears only once both stand */
.dk-chart .rng{ fill:var(--lx-up); opacity:0; transition:opacity .6s var(--enter); }
.dk-chart .lv{ stroke-width:1; stroke-dasharray:3 3; vector-effect:non-scaling-stroke;
  opacity:0; transition:opacity .5s var(--enter) .1s; }
.dk-chart .lv.tp{ stroke:var(--lx-up); }
.dk-chart .lv.sl{ stroke:var(--lx-down); }
.dk-pos.tp-on.sl-on .dk-chart .rng{ opacity:.06; }
.dk-pos.tp-on .dk-chart .lv.tp{ opacity:.75; }
.dk-pos.sl-on .dk-chart .lv.sl{ opacity:.75; }
.dk-lv{ position:absolute; right:2px; font-family:var(--font-num); font-size:8px;
  letter-spacing:.06em; font-variant-numeric:tabular-nums;
  opacity:0; transition:opacity .5s var(--enter) .25s; }
.dk-lv.tp{ color:var(--lx-up); left:2px; right:auto; transform:translateY(calc(-100% - 2px)); }
.dk-lv.sl{ color:var(--lx-down); transform:translateY(3px); }
.dk-pos.tp-on .dk-lv.tp{ opacity:1; }
.dk-pos.sl-on .dk-lv.sl{ opacity:1; }
/* the news stands on a pole at the gap it caused — the globe's grammar:
   a base dot, a line drawn UP, a glass island on top, hover unfolds the
   story (same .mk-more 0fr→1fr move as the dome's flags) */
.dk-pole{ position:absolute; z-index:3; opacity:0; transition:opacity .2s var(--enter) .12s; }
.dk.s6 .dk-pole{ opacity:1; }
.dk-pole .base{ position:absolute; left:-2.5px; top:-2.5px; width:5px; height:5px;
  border-radius:50%; background:var(--g5); }
.dk-pole .line{ position:absolute; left:-0.75px; bottom:2px; width:1.5px; height:30px;
  background:color-mix(in oklab, var(--lx-ink) 34%, transparent);
  transform:scaleY(0); transform-origin:50% 100%;
  transition:transform .4s var(--enter) .2s; }
.dk.s6 .dk-pole .line{ transform:scaleY(1); }
/* TOP-anchored: hover growth unfolds DOWN over the chart — bottom-anchored
   it climbed up and covered the P&L (the globe has sky above; this card
   does not). -57px = pole rise 32 + the collapsed row's ~25. */
.dk-pole .mk{ position:absolute; top:-57px; left:0; transform:translate(-50%,4px) scale(.95);
  display:inline-flex; flex-direction:column; align-items:flex-start; padding:5px 9px;
  border-radius:8px; background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line); box-shadow:var(--chip-shadow), var(--chip-in);
  font-family:var(--font-num); cursor:default;
  opacity:0; transition:opacity .35s var(--enter) .5s, transform .45s var(--enter) .5s; }
.dk.s6 .dk-pole .mk{ opacity:1; transform:translate(-50%,0) scale(1); }
.dk-pole .mk .mk-row{ display:inline-flex; align-items:center; gap:6px; white-space:nowrap; }
.dk-pole .mk .pin{ width:5px; height:5px; border-radius:50%; background:var(--g5); flex:0 0 auto; }
.dk-pole .mk .nm{ font-size:9px; font-weight:600; letter-spacing:.03em; color:var(--lx-ink); }
.dk-pole .mk .val{ font-size:9px; font-weight:600; }
.dk-pole .mk .val.up{ color:var(--lx-up); }
.dk-pole .mk .mk-more{ display:grid; grid-template-rows:0fr; opacity:0;
  transition:grid-template-rows .28s cubic-bezier(.16,1,.3,1), opacity .2s; }
.dk-pole .mk:hover .mk-more{ grid-template-rows:1fr; opacity:1; }
.dk-pole .mk .mk-more .in{ overflow:hidden; min-height:0; }
.dk-pole .mk .mk-more .dek{ display:block; font-family:var(--font-sans); font-size:10px;
  line-height:1.4; color:var(--lx-ink-2); width:172px; white-space:normal;
  padding:6px 0 1px; border-top:1px solid var(--lx-line); margin-top:5px; }
/* the offers: dashed while they wait, their own color the moment they land —
   the storyboard presses them, but they are REAL buttons and yours to press */
.dk-protect{ margin-top:10px; display:flex; gap:8px;
  opacity:0; transform:translateY(6px);
  transition:opacity .45s var(--enter), transform .5s var(--enter); }
.dk.s7 .dk-protect{ opacity:1; transform:none; }
.dk-set{ flex:1; display:inline-flex; align-items:center; justify-content:center;
  padding:7px 8px; border-radius:8px; cursor:pointer;
  font-family:var(--font-num); font-size:9.5px; letter-spacing:.04em; color:var(--lx-ink-2);
  background:none; border:1px dashed color-mix(in oklab, var(--lx-ink) 28%, transparent);
  transition:border-color .25s, color .25s; }
.dk-set i{ font-style:normal; margin-right:5px; }
.dk-set .a, .dk-set .b{ display:inline-flex; align-items:center; }
.dk-set .b{ display:none; }
.dk-set.on{ cursor:default; animation:dk-press .32s var(--enter); }
.dk-set.on .a{ display:none; }
.dk-set.on .b{ display:inline-flex; }
.dk-set.tp.on{ border:1px solid color-mix(in oklab, var(--lx-up) 45%, transparent); color:var(--lx-up); }
.dk-set.sl.on{ border:1px solid color-mix(in oklab, var(--lx-down) 45%, transparent); color:var(--lx-down); }
@media (hover:hover) and (pointer:fine){
  .dk-set:not(.on):hover{ border-color:color-mix(in oklab, var(--lx-ink) 48%, transparent); color:var(--lx-ink); }
}
@keyframes dk-press{ 0%{ transform:scale(1); } 45%{ transform:scale(.96); } 100%{ transform:scale(1); } }

/* the ticket in the middle of the row */
.dk-row .glass-widget{ opacity:0; transform:translateY(24px) scale(.97);
  transition:opacity .6s var(--enter), transform .75s var(--enter); }
.dk.s2 .dk-row .glass-widget{ opacity:1; transform:none; }
/* The lit links. An SVG path was the obvious tool and the wrong one: a 100x10
   viewBox with preserveAspectRatio:none scales the stroke non-uniformly and it
   rendered as a hairline nobody could see. A scaleX'd rule draws the same way,
   on the compositor, and always shows. */
.dk-link{ position:relative; flex:0 0 auto; width:clamp(26px,3.6vw,56px); height:10px;
  opacity:0; transition:opacity .3s var(--enter); }
.dk.s2 .dk-link.l1, .dk.s4 .dk-link.l2{ opacity:1; }
.dk-link .rule{ position:absolute; left:0; right:0; top:50%; height:2px; margin-top:-1px;
  border-radius:2px; background:var(--grad-accent);
  transform:scaleX(0); transform-origin:0 50%;
  transition:transform .7s var(--enter) .1s; }
.dk.s2 .dk-link.l1 .rule, .dk.s4 .dk-link.l2 .rule{ transform:scaleX(1); }
body:not(.theme-dark) .dk-link .rule{ filter:saturate(1.5) brightness(.85); }
.dk-link .pip{ position:absolute; top:50%; width:5px; height:5px; margin-top:-2.5px;
  border-radius:50%; opacity:0; transition:opacity .25s var(--enter); }
.dk-link .pip.a{ left:-2px; background:var(--g2); }
.dk-link .pip.b{ right:-2px; background:var(--g5); transition-delay:.5s; }
.dk.s2 .dk-link.l1 .pip, .dk.s4 .dk-link.l2 .pip{ opacity:1; }
@media (max-width:1180px){
  /* three panes and two links stop fitting — the row becomes a column and the
     links run downward instead of across */
  .dk-row{ flex-direction:column; gap:0; }
  /* stacked, the stage is tall and its foot is nowhere near the ticket — the
     confirmation was landing a whole pane away from the order it confirmed
     (user). The wrapper becomes the anchor; the toast's own rules live after
     its base block below, or the base `bottom` wins and stretches the box. */
  .dk-ticket{ position:relative; }
  .dk-idea, .dk-pos{ width:min(392px,100%); }
  .dk-link{ width:2px; height:clamp(24px,4vh,42px); }
  .dk-link .rule{ left:50%; right:auto; top:0; bottom:0; width:2px; height:auto;
    margin:0 0 0 -1px; transform:scaleY(0); transform-origin:50% 0; }
  .dk.s2 .dk-link.l1 .rule, .dk.s4 .dk-link.l2 .rule{ transform:scaleY(1); }
  .dk-link .pip.a{ left:50%; top:0; margin-left:-2.5px; }
  .dk-link .pip.b{ left:50%; right:auto; top:auto; bottom:0; margin-left:-2.5px; }
}
@media (prefers-reduced-motion:reduce){
  .dk-card, .dk-row .glass-widget{ opacity:1; transform:none; transition:none; }
  .dk-bar i{ transform:none; transition:none; }
  .dk-chart{ opacity:1; transition:none; }
  .dk-chart svg{ clip-path:none; transition:none; }
  .dk-pole, .dk-pole .mk, .dk-protect, .dk-lv{ opacity:1; transition:none; animation:none; }
  .dk-pole .line{ transform:none; transition:none; }
  .dk-pole .mk{ transform:translate(-50%,0); }
  .dk-protect{ transform:none; }
  .dk-link{ opacity:1; }
  .dk-link .rule{ transform:none; transition:none; }
  .dk-link .pip{ opacity:1; }
}

/* side cards — the venue heartbeat and the position that the demo opens */
.fxc{ padding:16px 18px; border-radius:14px; }
.fxc .t{ font-family:var(--font-num); font-size:9.5px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--lx-mut); display:flex; align-items:center; gap:8px; margin-bottom:10px; }
.fxc .t .pdot{ width:6px; height:6px; border-radius:50%; background:var(--lx-up);
  animation:pulse 2.6s ease-in-out infinite; }
.fxc-open .big{ font-family:var(--font-num); font-size:21px; font-weight:600;
  font-variant-numeric:tabular-nums; letter-spacing:-.01em; }
.fxc-open .sub{ margin-top:4px; font-family:var(--font-num); font-size:10.5px; color:var(--lx-mut);
  font-variant-numeric:tabular-nums; }
.fxc-open .sub b{ color:var(--lx-ink-2); font-weight:500; }
.fxc-note{ font-size:12.5px; line-height:1.55; color:var(--lx-ink-2); }
.fxc-note b{ font-weight:600; color:var(--lx-ink); }

/* The position card at rest used to advertise '+$0.00 · size $2,500' — a
   placeholder wearing the clothes of real data. It now reads as an empty slot
   until the demo actually fills, and the numbers arrive with the fill. */
.fxc-pos{ opacity:.72; transition:opacity .5s var(--enter); }
.fxc-pos.on{ opacity:1; }
.fxc-pos:not(.on) .pnl b, .fxc-pos:not(.on) .pnl span,
.fxc-pos:not(.on) .head .tag, .fxc-pos:not(.on) .meta{ color:var(--lx-faint); }
.fxc-pos:not(.on) .pnl b{ font-variant-numeric:tabular-nums; }
.fxc-pos .head, .dk-pos .head{ display:flex; align-items:center; gap:10px; margin-top:11px; }
.fxc-pos .head .nm, .dk-pos .head .nm{ font-size:12.5px; font-weight:600; }
.fxc-pos .head .tag, .dk-pos .head .tag{ margin-left:auto; font-family:var(--font-num); font-size:9px; letter-spacing:.1em;
  text-transform:uppercase; padding:3px 8px; border-radius:999px;
  color:var(--lx-up); border:1px solid color-mix(in oklab, var(--lx-up) 40%, transparent); }
.fxc-pos .pnl, .dk-pos .pnl{ margin-top:10px; display:flex; align-items:baseline; gap:10px; }
.fxc-pos .pnl b, .dk-pos .pnl b{ font-family:var(--font-num); font-size:22px; font-weight:600; color:var(--lx-up);
  font-variant-numeric:tabular-nums; }
.fxc-pos .pnl span, .dk-pos .pnl span{ font-family:var(--font-num); font-size:11px; color:var(--lx-mut);
  font-variant-numeric:tabular-nums; }
.fxc-pos .meta, .dk-pos .meta{ margin-top:8px; display:flex; gap:14px; font-family:var(--font-num); font-size:10.5px;
  color:var(--lx-mut); font-variant-numeric:tabular-nums; }
.fxc-pos .meta b, .dk-pos .meta b{ color:var(--lx-ink-2); font-weight:500; }
.fxc-pos.down .head .tag, .dk-pos.down .head .tag{ color:var(--lx-down); border-color:color-mix(in oklab, var(--lx-down) 40%, transparent); }
.fxc-pos.down .pnl b, .dk-pos.down .pnl b{ color:var(--lx-down); }

/* fill toast — pops with spring overshoot when the demo executes */
/* the toast lives inside the ticket now, but STATIC here — so on the desk its
   absolute box still resolves against the stage and sits at its foot, exactly
   as before. Only the phone layout makes the wrapper the anchor (below). */
.dk-ticket{ position:static; }
.fxt{ position:absolute; left:50%; bottom:clamp(10px,2vw,26px); transform:translateX(-50%) translateY(8px) scale(.92);
  z-index:5; display:flex; align-items:center; gap:10px; padding:12px 18px; border-radius:12px;
  opacity:0; pointer-events:none;
  background:var(--glass-chip-firm); -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line); box-shadow:var(--chip-shadow), var(--chip-in); }
.fxt.on{ opacity:1; transform:translateX(-50%) translateY(0) scale(1);
  transition:opacity .18s ease-out, transform .5s var(--spring); }
.fxt .ok{ width:18px; height:18px; border-radius:50%; display:grid; place-items:center;
  background:color-mix(in oklab, var(--lx-up) 22%, transparent); color:var(--lx-up); }
.fxt .ok svg{ width:10px; height:10px; stroke:currentColor; fill:none; stroke-width:2.4;
  stroke-linecap:round; stroke-linejoin:round; }
.fxt .msg{ font-size:12.5px; font-weight:600; letter-spacing:-.005em; }
.fxt .sub{ font-family:var(--font-num); font-size:10px; color:var(--lx-mut); font-variant-numeric:tabular-nums; }
/* stacked layout: the confirmation lands ON the ticket (see .dk-ticket above).
   This must come after the base rule — `top` and `bottom` both set would
   stretch the toast into a column. */
@media (max-width:1180px){
  .fxt{ top:50%; bottom:auto; width:max-content; max-width:calc(100% - 28px);
    transform:translate(-50%,-50%) scale(.92); }
  .fxt.on{ transform:translate(-50%,-50%) scale(1); }
}

/* three facts under the stage (Mercury's icon row, our mono voice) */
/* the three facts sit close under the stage and name its three beats — the
   idea, the ticket, the position — so they read as a caption on what you just
   watched rather than a generic feature row (user) */
.fx-cols{ display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(18px,3vw,44px);
  margin-top:clamp(14px,2vw,30px); text-align:center; }
.fx-cols .c .k{ font-family:var(--font-num); font-size:10px; letter-spacing:.2em; text-transform:uppercase;
  color:var(--lx-mut); margin-bottom:10px; display:flex; align-items:center; justify-content:center; gap:8px; }
.fx-cols .c .k::before, .fx-cols .c .k::after{ content:""; width:22px; height:1px;
  background:linear-gradient(90deg, transparent, oklch(1 0 0/.2)); }
.fx-cols .c .k::after{ transform:scaleX(-1); }
.fx-cols .c h3{ margin:0 0 8px; font-size:16.5px; font-weight:600; letter-spacing:-.01em; }
.fx-cols .c p{ margin:0 auto; max-width:34ch; font-size:13.5px; line-height:1.6; color:var(--lx-mut); }
@media (max-width:980px){
  .fx-cols{ grid-template-columns:1fr; gap:26px; }
  .fx-cols .c p{ max-width:44ch; }
}

/* --------------------------------------------------------- app section */
.app{ position:relative; padding:clamp(60px,9vh,130px) 0; }
/* the copy column was 1fr and the prose only fills ~500px of it, so the
   leftover pooled into one dead band beside the phone. Bounding the column
   and centring the phone in what is left splits that space around the two
   masses instead of parking it between them. */
.app .wrap{ display:grid; grid-template-columns:minmax(0,500px) minmax(0,1fr);
  align-items:center; gap:clamp(30px,4vw,64px); }
.app .iph-wrap{ justify-self:center; }
.app-copy .eyebrow{ margin-bottom:16px; }
.app-copy h2{ margin:0; font-family:var(--font-disp); font-weight:430; letter-spacing:-.02em;
  font-size:clamp(30px,3.8vw,52px); line-height:1.06; text-wrap:balance; }
.app-copy h2 .dim{ color:var(--lx-ink-2); }
.app-copy h2 em{ font-style:italic; }
.app-copy .lede{ margin:18px 0 0; max-width:46ch; font-size:15.5px; line-height:1.62; color:var(--lx-ink-2); }
.app-feats{ margin:28px 0 0; padding:0; list-style:none; display:flex; flex-direction:column; gap:18px; }
.app-feats li{ display:flex; gap:14px; align-items:flex-start; }
.app-feats .ic{ width:34px; height:34px; flex:0 0 auto; border-radius:10px; display:grid; place-items:center;
  background:oklch(1 0 0/.07); border:1px solid oklch(1 0 0/.13);
  box-shadow:inset 0 1px 0 oklch(1 0 0/.1); }
.app-feats .ic svg{ width:16px; height:16px; stroke:var(--lx-ink-2); fill:none; stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round; }
.app-feats b{ display:block; font-size:14.5px; font-weight:600; letter-spacing:-.01em; margin-bottom:3px; }
.app-feats p{ margin:0; font-size:13px; line-height:1.55; color:var(--lx-mut); max-width:40ch; }
.app-cta{ margin-top:30px; display:flex; gap:10px; align-items:center; }

/* ---- the phone — premium hardware, live glass interior ---- */
.iph-wrap{ position:relative; }

/* ONE thin, light bezel (user) — the four-layer titanium-and-chassis stack read
   as a product photo of a phone. What is wanted is a quiet frame that lets the
   SCREEN be the subject, so: a single soft rim, a hairline, and no side keys. */
/* One thin bezel, and around it the same glass rim the desk window wears (user:
   follow that style here too). Two spread rings do it — a translucent band and
   its hairline — so the hardware reads as a pane of glass holding a screen
   rather than a photograph of a phone. No extra element, no second blur. */
/* page | glass | app — the same shell the window wears (user), so the two
   surfaces are one material rather than a bezel and a pane that merely look
   alike. No rings, no inset hairline: the BAND is the bezel. */
.iph{ position:relative; width:min(318px, 84vw); aspect-ratio:318/652; border-radius:50px;
  background:linear-gradient(158deg, oklch(0.40 0.018 268/.62), oklch(0.30 0.016 272/.44));
  -webkit-backdrop-filter:blur(26px) saturate(1.5); backdrop-filter:blur(26px) saturate(1.5);
  box-shadow:0 44px 104px -34px oklch(0 0 0/.82);
  padding:9px; }
body:not(.theme-dark) .iph{
  background:linear-gradient(158deg, oklch(0.905 0.011 255/.78), oklch(0.865 0.013 258/.62));
  box-shadow:0 40px 92px -34px oklch(0.3 0.03 260/.4); }
@keyframes iph-bob{ from{ transform:translateY(0); } to{ transform:translateY(-10px); } }
.iph-screen{ position:relative; width:100%; height:100%; border-radius:46px; overflow:hidden;
  /* the app's OWN ground, sampled from it (oklch .176/.012/262 dark,
     .987/.0035/250 light) so the status strip above the iframe is seamless */
  background:oklch(0.176 0.012 262);
  /* No inset. The app fills the glass edge to edge and its own header wash runs
     all the way to the top of the screen — starting it below a status row left
     a hard horizontal line where the gradient began (user: "cuts off
     abruptly"). The row floats over it instead. */
  padding:0; }
/* THE DYNAMIC ISLAND — a floating overlay, never a layout box. Sized off the
   screen's WIDTH so it stays right across every phone size on the page (the
   .sfc-stage one is 232–306px, the standalone is up to 318px): a real island is
   125x37 in a 393pt viewport, so 32% wide at 125/37 and ~2.8% of width down
   from the top edge. z-index clears the app iframe; nothing here affects flow,
   which is the whole point — see index.html for why that matters. */
.iph-island{ position:absolute; top:8px; left:50%; transform:translateX(-50%); z-index:4;
  width:32%; aspect-ratio:125/37; border-radius:999px;
  background:oklch(0.045 0.005 268); pointer-events:none; }
.iph-island::after{ content:""; position:absolute; right:7px; top:50%; transform:translateY(-50%);
  width:11px; height:11px; border-radius:50%;
  background:radial-gradient(circle at 35% 35%, oklch(0.28 0.03 280/.9), oklch(0.08 0.01 268) 60%); }
.iph-status{ position:absolute; top:0; left:0; right:0; z-index:3;
  display:flex; align-items:center; justify-content:center;
  padding:9px 0 0; pointer-events:none; }
.iph-status .l{ flex:1 1 0; padding-left:8px; }
.iph-status .r{ flex:1 1 0; display:inline-flex; gap:5px; align-items:center; justify-content:flex-end; }
body:not(.theme-dark) .iph-island{ background:oklch(0.05 0.005 268); }
.iph-status svg{ width:15px; height:11px; display:block; }
/* interior glass cards */
.ip-card{ border-radius:14px; padding:13px 14px; background:linear-gradient(150deg, oklch(1 0 0/.09), oklch(1 0 0/.04));
  border:1px solid oklch(1 0 0/.11); box-shadow:inset 0 1px 0 oklch(1 0 0/.09); }
.ip-card + .ip-card{ margin-top:9px; }
.ip-bal .lbl{ font-family:var(--font-num); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--lx-mut); }
.ip-bal .v{ margin-top:4px; font-family:var(--font-num); font-size:26px; font-weight:600;
  letter-spacing:-.015em; font-variant-numeric:tabular-nums; }
.ip-bal .d{ margin-top:2px; font-family:var(--font-num); font-size:10.5px; font-weight:600;
  color:var(--lx-up); font-variant-numeric:tabular-nums; }
.ip-chart{ margin:8px -4px -4px; height:56px; }
.ip-chart svg{ width:100%; height:100%; display:block; }
.ip-rows{ padding:6px 8px; }
.ip-rows .hd{ font-family:var(--font-num); font-size:8.5px; letter-spacing:.18em; text-transform:uppercase;
  color:var(--lx-mut); padding:6px 6px 7px; }
.ip-row{ display:grid; grid-template-columns:24px minmax(0,1fr) auto; gap:9px; align-items:center;
  padding:7px 6px; border-radius:11px; }
.ip-row .logo{ border-radius:7px; }
.ip-row .nm{ min-width:0; display:flex; flex-direction:column; gap:0; }
.ip-row .nm b{ font-size:11px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ip-row .nm span{ font-family:var(--font-num); font-size:8.5px; color:var(--lx-mut); }
.ip-row .px{ text-align:right; }
.ip-row .px b{ display:block; font-family:var(--font-num); font-size:11px; font-weight:600;
  font-variant-numeric:tabular-nums; }
.ip-row .px span{ font-family:var(--font-num); font-size:9.5px; font-weight:600;
  font-variant-numeric:tabular-nums; }
.ip-row .px .up{ color:var(--lx-up); } .ip-row .px .down{ color:var(--lx-down); }
.ip-trade{ margin-top:9px; text-align:center; font-size:13px; font-weight:600; border-radius:10px;
  padding:12px; color:oklch(0.16 0.014 260); background:oklch(0.985 0.003 250);
  box-shadow:0 8px 26px -10px oklch(0 0 0/.7); }
/* the alert — the app's voice arriving like a notification */
.ip-alert{ display:flex; align-items:center; gap:10px; margin-top:9px; }
.ip-alert .ic{ width:28px; height:28px; flex:0 0 auto; border-radius:9px; display:grid; place-items:center;
  background:oklch(1 0 0/.09); border:1px solid oklch(1 0 0/.1); }
.ip-alert .ic svg{ width:14px; height:14px; stroke:var(--lx-ink-2); fill:none; stroke-width:1.6;
  stroke-linecap:round; stroke-linejoin:round; }
.ip-alert .tx{ min-width:0; flex:1; }
.ip-alert .tx b{ display:block; font-size:10.5px; font-weight:600; }
.ip-alert .tx p{ margin:1px 0 0; font-family:var(--font-num); font-size:8.5px; color:var(--lx-mut);
  font-variant-numeric:tabular-nums; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ip-alert .when{ font-family:var(--font-num); font-size:8.5px; color:var(--lx-faint); align-self:flex-start; }
.ip-dock{ margin-top:auto; display:flex; justify-content:space-around; align-items:center;
  padding:10px 18px calc(8px + 6px); margin-inline:6px; border-radius:14px;
  background:oklch(1 0 0/.07); border:1px solid oklch(1 0 0/.1);
  -webkit-backdrop-filter:var(--blur-lite); backdrop-filter:var(--blur-lite); }
.ip-dock svg{ width:19px; height:19px; stroke:var(--lx-mut); fill:none; stroke-width:1.7;
  stroke-linecap:round; stroke-linejoin:round; }
.ip-dock .on svg{ stroke:var(--lx-ink); }
@media (max-width:900px){
  .app .wrap{ grid-template-columns:1fr; justify-items:center; }
  .app-copy{ text-align:center; }
  .app-copy .lede, .app-feats p{ margin-inline:auto; }
  .app-feats li{ text-align:left; }
  .app-cta{ justify-content:center; }
}

/* ------------------------------------------------------------- markets */
/* the tape starts BELOW the dome's midsection now (user: more globe), but
   its stripes must still be INSIDE the first viewport on a MacBook Pro 14
   (user) — so the drop is keyed to the viewport's height, pinning the
   strip near the fold: tall rooms push it lower, short rooms barely at all */
.mkts{ position:relative; padding:clamp(24px, 100vh - 940px, 210px) 0 clamp(22px,4vh,52px); }
/* the headline above the tape is gone (user, round 10) — only the label is
   left, so the strip rides right up under it */
.mkts-head{ text-align:center; }
.tape{ position:relative; margin-top:clamp(16px,2.4vw,28px);
  /* the rows are width:max-content — without a clip they widen the DOCUMENT
     to the full strip (11,000px of sideways scroll on a phone) */
  overflow-x:clip;
  -webkit-mask-image:linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  mask-image:linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent); }
.tape-row{ display:flex; gap:12px; width:max-content; padding:6px 0; }
/* slower (user) — the names were streaming past faster than they read */
.tape-row.a{ animation:tape-l 84s linear infinite; }
.tape-row.b{ animation:tape-r 104s linear infinite; }
@keyframes tape-l{ from{ transform:translateX(0); } to{ transform:translateX(-50%); } }
@keyframes tape-r{ from{ transform:translateX(-50%); } to{ transform:translateX(0); } }
.tape:hover .tape-row{ animation-play-state:paused; }
/* chip glass — the nav's own surface (user: the moving cards lacked the feel) */
.tk{ display:inline-flex; align-items:center; gap:10px; padding:9px 14px 9px 10px; border-radius:8px;
  max-width:calc(100vw - 2*var(--edge));
  /* -firm, the scrolled-nav variant: the tape carries DATA over the busiest
     ground on the page, and at the base alphas the dots read through the
     prices (user, twice). Same system, firmer tier. */
  background:var(--glass-chip-firm);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line);
  box-shadow:var(--chip-shadow), var(--chip-in);
  transition:background .18s, border-color .18s, transform .18s var(--enter); }
@media (hover:hover) and (pointer:fine){
  a.tk:hover{ border-color:color-mix(in oklab, var(--lx-ink) 32%, transparent); transform:translateY(-1px); }
}
.tk .logo{ border-radius:7px; }
.tk .nm{ font-size:12px; font-weight:600; letter-spacing:-.01em; white-space:nowrap; }
.tk .sc{ font-family:var(--font-num); font-size:9px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--lx-mut); white-space:nowrap; }
.tk .px{ font-family:var(--font-num); font-size:11px; font-weight:600; font-variant-numeric:tabular-nums; }
.tk .ch{ font-family:var(--font-num); font-size:10.5px; font-weight:600; font-variant-numeric:tabular-nums; }
.tk .ch.up{ color:var(--lx-up); } .tk .ch.down{ color:var(--lx-down); }
.mkts-foot{ margin-top:clamp(26px,4vw,40px); display:flex; justify-content:center;
  opacity:0; transform:translateY(8px);
  transition:opacity .5s var(--enter,ease), transform .5s var(--enter,ease); }
.mkts-foot.on{ opacity:1; transform:none; }
@media (prefers-reduced-motion:reduce){ .mkts-foot{ transition:none; transform:none; } }
/* a chip like its neighbours, not a bare outline over the dot field */
.mkts-foot a{ font-family:var(--font-num); font-size:12px; letter-spacing:.06em; color:var(--lx-ink-2);
  display:inline-flex; align-items:center; gap:8px; padding:10px 18px; border-radius:10px;
  background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line);
  box-shadow:var(--chip-shadow), var(--chip-in);
  transition:color .18s, border-color .18s, background .18s; }
.mkts-foot a:hover{ color:var(--lx-ink);
  border-color:color-mix(in oklab, var(--lx-ink) 32%, transparent); }

/* ------------------------------------------------------------- baskets
   The card hand, the staged fund and its living book are the shipped ones
   (css/landing/funds.css + funds.js, lifted whole). Only the room is ours: the
   fund cards read on the app tokens, which this page's deeper violet-cast
   near-black does not match, so the pane material is re-dressed here in the
   page's own glass. Nothing about the choreography is touched. */
.fnd{ position:relative; padding:clamp(60px,9vh,130px) 0 clamp(50px,7vh,100px);
  --acc:var(--g4); }

.fnd .fd-rail{ position:relative; z-index:1; }
.fnd .sec-head{ margin-bottom:0; }
#funds .fd-fan{ margin-top:clamp(22px,3vw,36px); }
/* the page's sharp-radius law reaches the fan too (user: sharper edges) */
.fnd .fund-card{ background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border-color:var(--lx-line); border-radius:12px;
  box-shadow:var(--chip-shadow), var(--chip-in); }
/* the author's mark was a 100px circle — the loudest round thing on a page
   whose law is that only true dots stay round. It is a squared chip now, and
   the ramp ring squares with it. */
/* the author's mark stays a CIRCLE (user: it looked better) — the page's
   sharp-radius law bends for a portrait, the same way it does for true dots */
.fnd .fc-ava{ border-radius:50%; }
.fnd .fc-ava img, .fnd .fc-ava b{ border-radius:50%; }
.fnd .fc-ava.add{ border-radius:50%; }
.fnd .fc-invest{ border-radius:10px; }
.fnd .fund-card.focused{ background:var(--glass-chip-firm);
  /* funds.css gives .focused var(--elev-3), but this file loads later and the
     base .fnd .fund-card rule ties it on specificity — without restating the
     shadow here, the card on the stage gains no depth at all */
  box-shadow:0 50px 110px -34px oklch(0 0 0/.92), var(--glass-in); }
body:not(.theme-dark) .fnd .fund-card.focused{
  box-shadow:0 40px 84px -30px oklch(0.3 0.03 260/.5), var(--glass-in); }
.fnd .fund-card h3{ color:var(--lx-ink); }
.fnd .fund-card p{ color:var(--lx-mut); }
.fnd .fund-card .fc-idx{ color:var(--lx-faint); }
.fnd .fund-card .fc-ret.up{ color:var(--lx-up); }
.fnd .fc-ava img, .fnd .fc-ava b{ border-color:transparent; }
/* The invest button follows the page's button law — flat solid, radius 10 —
   but the PRIMARY white is reserved for the card on the stage. Five white
   pills in a fan is five things shouting; unstaged cards wear the glass
   secondary and earn the white when you pick them up. */
.fnd .fc-invest{ border-radius:10px; box-shadow:none;
  color:var(--lx-ink); background:oklch(1 0 0/.07); border:1px solid oklch(1 0 0/.2); }
.fnd .fc-invest:hover{ border-color:oklch(1 0 0/.4); background:oklch(1 0 0/.12); }
.fnd .fund-card.focused .fc-invest{ color:oklch(0.16 0.014 260);
  background:oklch(0.985 0.003 250); border-color:transparent;
  box-shadow:inset 0 1px 0 oklch(1 0 0/.7); }
.fnd .fund-card.focused .fc-invest:hover{ filter:brightness(1.04); }
.fnd .fund-card.focused .fc-invest.ghost{ color:var(--lx-ink); background:oklch(1 0 0/.07);
  border:1px solid oklch(1 0 0/.24); box-shadow:none; }
.fnd .fund-card.live .fc-invest{ background:color-mix(in oklab, var(--lx-up) 72%, black);
  color:oklch(0.985 0.01 160); border-color:transparent; }
body:not(.theme-dark) .fnd .fc-invest{ color:var(--lx-ink); background:oklch(1 0 0/.55);
  border-color:oklch(0.2 0.014 260/.18); }
body:not(.theme-dark) .fnd .fc-invest:hover{ background:#fff; border-color:oklch(0.2 0.014 260/.4); }
body:not(.theme-dark) .fnd .fund-card.focused .fc-invest{ color:oklch(0.985 0.003 250);
  background:var(--lx-ink); box-shadow:none; }
body:not(.theme-dark) .fnd .fund-card.focused .fc-invest.ghost{ background:oklch(1 0 0/.5);
  color:var(--lx-ink); border-color:oklch(0.2 0.014 260/.2); }
body:not(.theme-dark) .fnd .fund-card.live .fc-invest{
  background:color-mix(in oklab, var(--lx-up) 88%, black); color:#fff; }
/* the book's rows and its timeline speak the page's ink */
.fnd .fd-dist-head, .fnd .fd-tl .xd{ color:var(--lx-mut); }
.fnd .ths-rows .rw{ background:var(--glass); border-color:var(--lx-line); }
.fnd .ths-rows .rw .l{ color:var(--lx-ink); }
.fnd .ths-rows .rw .l small{ color:var(--lx-mut); }
.fnd .ths-rows .rw .v{ color:var(--lx-ink); }
.fnd .ths-rows .rw .v.mut{ color:var(--lx-mut); }
.fnd .ths-rows .rw .v.up{ color:var(--lx-up); }
.fnd .ths-rows .rw .v.down{ color:var(--lx-down); }
.fnd .fd-tl .dim{ stroke:var(--lx-line); }
.fnd .fd-tl .ph{ background:color-mix(in oklab, var(--lx-ink), transparent 60%); }
.fnd .fd-tl .ph i{ background:var(--lx-ink); border-color:transparent; }

/* --------------------------------------------------------------- stats */
.stats{ position:relative; padding:clamp(50px,8vh,110px) 0; }
.stats::before{ content:""; position:absolute; left:18%; right:18%; top:0; height:1px;
  background:linear-gradient(90deg, transparent, oklch(1 0 0/.14), transparent); }
.stats .wrap{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:clamp(20px,3vw,40px);
  text-align:center; }
.st .v{ font-family:var(--font-num); font-size:clamp(30px,3.6vw,46px); font-weight:600;
  letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
.st .v .u{ font-size:.52em; color:var(--lx-mut); font-weight:500; margin-left:2px; }
.st .k{ margin-top:8px; font-size:12.5px; line-height:1.5; color:var(--lx-mut); }
@media (max-width:860px){ .stats .wrap{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:34px 20px; } }

/* --------------------------------------------------------------- close */
/* ----------------------------------------------------------------- faq
   Back to the shipped shape (user, round 10): no glass panel — the rows sit
   directly on the page, divided by hairlines, with the mono number, the "+"
   that turns into a "×", and main's INTERRUPTIBLE unfold. That unfold is a
   grid-template-rows transition rather than native <details> toggling, so
   clicking twice quickly retargets instead of restarting; js/landing/landing.js keeps
   every details element open in the DOM and drives .is-open. Without JS the
   native behaviour is untouched. */
.faq{ position:relative; padding:clamp(50px,8vh,110px) 0 clamp(60px,9vh,120px); }
.faq-list{ max-width:820px; margin:clamp(18px,3vw,30px) auto 0; }
.faq-item{ border-top:1px solid var(--lx-line); }
.faq-item:last-child{ border-bottom:1px solid var(--lx-line); }
.faq-item summary{ display:flex; align-items:baseline; gap:18px; padding:20px 2px;
  cursor:pointer; list-style:none; -webkit-user-select:none; user-select:none; }
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item .qn{ font-family:var(--font-num); font-size:11px; letter-spacing:.14em;
  color:var(--lx-faint); flex:0 0 auto; }
.faq-item .q{ flex:1; min-width:0; font-size:clamp(16px,1.7vw,19px); font-weight:600;
  letter-spacing:-.015em; color:var(--lx-ink-2); line-height:1.35; text-wrap:pretty;
  transition:color .15s; }
.faq-item summary:hover .q, .faq-item.is-open .q{ color:var(--lx-ink); }
.faq-item summary:active{ background:oklch(1 0 0/.04); }
body:not(.theme-dark) .faq-item summary:active{ background:oklch(0.2 0.014 260/.04); }
.faq-item[open]:not(.faqjs *) .q{ color:var(--lx-ink); }
.faq-item .tick{ font-family:var(--font-num); font-size:16px; color:var(--lx-mut); flex:0 0 auto;
  transition:transform .2s ease, color .15s; }
.faq-item .a{ padding:0 2px 22px 40px; }
.faq-item .a p{ margin:0; max-width:62ch; font-size:clamp(14.5px,1.5vw,16.5px); line-height:1.6;
  color:var(--lx-mut); text-wrap:pretty; }
.faq-item .a p b{ color:var(--lx-ink-2); font-weight:600; }
.faq-item .a p a{ color:var(--lx-ink-2); text-decoration:underline;
  text-decoration-color:var(--lx-faint); text-underline-offset:3px; transition:color .15s; }
.faq-item .a p a:hover{ color:var(--lx-ink); text-decoration-color:currentColor; }
/* the unfold, gated on html.faqjs so a JS-less page keeps native <details> */
.faqjs .faq-item .a{ display:grid; grid-template-rows:0fr; padding:0;
  transition:grid-template-rows .42s var(--enter); }
.faqjs .faq-item .a > div{ min-height:0; overflow:hidden; visibility:hidden; opacity:0;
  transform:translateY(-4px);
  transition:opacity .24s, transform .24s, visibility 0s linear .42s; }
.faqjs .faq-item .a > div > div{ padding:0 2px 22px 40px; }
.faqjs .faq-item.is-open .a{ grid-template-rows:1fr; }
.faqjs .faq-item.is-open .a > div{ visibility:visible; opacity:1; transform:none;
  transition:opacity .24s .05s, transform .42s var(--enter) .05s, visibility 0s; }
/* no-JS: the native [open] state turns the plus. With the driver on, .faqjs
   resets it and .is-open owns the turn — the order here is load-bearing. */
.faq-item[open] .tick{ transform:rotate(45deg); color:var(--lx-ink); }
.faqjs .faq-item .tick{ transform:none; color:var(--lx-mut); }
.faqjs .faq-item.is-open .tick{ transform:rotate(45deg); color:var(--lx-ink); }
@media (max-width:680px){
  .faq-item summary{ gap:14px; padding:17px 2px; }
  .faq-item .a, .faqjs .faq-item .a > div > div{ padding-left:2px; }
}
@media (prefers-reduced-motion:reduce){
  .faq-item .tick{ transition:none; }
  .faqjs .faq-item .a{ transition:none; }
  .faqjs .faq-item .a > div{ transform:none; transition:opacity .15s, visibility 0s; }
  .faqjs .faq-item:not(.is-open) .a > div{ transition:opacity .15s, visibility 0s linear .15s; }
}

/* ---------------------------------------------------- the ending, one panel
   The letter and the door, together, ON GLASS.
   ----------------------------------------------------------------------------
   The critique of the flat version, and what each line here answers:
     · No MATERIAL. Every other section sits on or inside glass — the desk in a
       window, the strategies on panes, the footer on a panel — so bare type on
       empty ground read as unfinished rather than as deliberate quiet. The pair
       is one glass panel now, the footer's own recipe.
     · No RELATIONSHIP between the halves: one left-aligned, one centred, 27px
       prose against 66px display, nothing binding them. They now share a left
       axis, a hairline between them, and a closer type scale.
     · No FOCUSING MECHANISM: two blocks of equal weight and no entry point. The
       ask keeps the display face and the accent italic, so the eye lands there
       first and the letter reads as the reason underneath it.
     · The section rhythm broke: every other block opens on a mono eyebrow and
       this one had none on the right. It does now.
   ========================================================================== */
/* ONE PANEL for the whole ending (user). Two stacked cards said goodbye twice;
   the closing act and the site's nav now share a single surface, divided by a
   rule rather than by a gap between two glass boxes. */
.ft-panel{ padding:clamp(26px,3.4vw,44px);
  border-radius:14px;
  background:var(--glass); -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
  border:1px solid var(--lx-line);
  box-shadow:var(--glass-shadow), var(--glass-in); }
.ft-rule{ height:1px; background:var(--lx-line);
  margin:clamp(26px,3.4vw,44px) 0 clamp(22px,2.8vw,34px); }
/* ------------------------------------------------------------- the shore
   On the page itself (user), no panel: the letter left, the grove as a
   PICTURE right — oversized past its grid cell (negative margins) so the
   sky's fade carries onto the paper instead of stopping at the column. */
.shore{ position:relative; padding:clamp(56px,8vh,110px) 0 clamp(24px,3vh,44px);
  overflow:hidden; }
.ft-top{ display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
  gap:clamp(24px,3.4vw,56px); align-items:center; }
.ft-top > *{ min-width:0; }
.ft-why{ position:relative; z-index:1; }
.ft-why .big{ margin:14px 0 0; font-family:var(--font-disp); font-weight:430;
  letter-spacing:-.015em; font-size:clamp(15.5px,1.5vw,19px); line-height:1.52;
  color:var(--lx-ink-2); text-wrap:pretty; }
.ft-why .big + .big{ margin-top:12px; }
/* main's letter grammar: the key phrase wears a quiet ramp-tinted mark */
.ft-why .big b{ font-weight:460; color:var(--lx-ink);
  background:color-mix(in oklab, var(--g4) 16%, transparent);
  border-radius:4px; padding:0 3px; margin:0 -3px;
  box-decoration-break:clone; -webkit-box-decoration-break:clone; }
.ft-why .big a{ color:var(--pod); font-weight:600; transition:opacity .15s; }
.ft-why .big a:hover{ opacity:.8; }
.ft-why .pod-cta{ margin-top:18px; color:var(--pod);
  border-color:color-mix(in oklab, var(--pod) 45%, transparent); }
.ft-why .pod-cta:hover{ border-color:color-mix(in oklab, var(--pod) 75%, transparent); }
.ft-why .sign{ margin:16px 0 0; font-family:var(--font-num); font-size:11.5px;
  color:var(--lx-mut); }
.ft-brand .ft-ask{ margin:clamp(18px,2.2vw,28px) 0 0; font-family:var(--font-disp);
  font-weight:420; letter-spacing:-.02em; font-size:clamp(21px,2.1vw,27px);
  line-height:1.14; text-wrap:balance; }
.ft-brand .ft-ask em{ font-family:var(--font-season); font-style:italic; font-weight:400; }
.ft-brand .cap{ margin:14px 0 0; width:min(420px,100%); }
/* the picture: sky in the ramp, masked so it melts into the paper on every
   edge — a painting hung INTO the surface, not a card on it. Negative
   margins push the box past its grid cell so the fade lands on the page. */
.ft-scene{ position:relative; height:clamp(360px,30vw,470px); cursor:crosshair;
  margin:clamp(-80px,-4vw,-36px) clamp(-120px,-6vw,-48px) clamp(-32px,-1.8vw,-14px) clamp(-72px,-4vw,-32px);
  /* THE GROVE IS A PAINTING (user 2026-07-28: "we would like to use water
     painted image also next to the why we built this"). Four earlier rounds
     drew the palms in canvas — brush strokes, cached crowns, painted crowns on
     drawn trunks — and none stopped reading as vector art; the round that
     generated its own picture missed too. This is the user's own canvas, so the
     artwork is settled and the code only has to carry the birds. */
  /* the sheet the painting is on — light in BOTH rooms (see the --paper note
     below); multiply has nothing to bite on otherwise */
  background:var(--paper, var(--lx-bg));
  /* two linear fades, INTERSECTED — one ellipse cannot kill the corners
     without eating the palms: bottom-centre and the corners sit at almost the
     same radius. This keeps a big opaque middle and melts every edge. The mask
     stays on the WRAPPER so a bird flying out of frame dissolves with it. */
  -webkit-mask-image:linear-gradient(90deg, transparent 0, #000 26%, #000 82%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 20%, #000 84%, transparent 100%);
  -webkit-mask-composite:source-in;
  mask-image:linear-gradient(90deg, transparent 0, #000 26%, #000 82%, transparent 100%),
    linear-gradient(180deg, transparent 0, #000 20%, #000 84%, transparent 100%);
  mask-composite:intersect; }
/* THE PAINTING IS ITS OWN LAYER so the night room can dim it WITHOUT dimming
   the birds. With the filter on .ft-scene it inherited down to the canvas: the
   birds still drew (the pixels were there) but at 36% brightness they were
   invisible against the sky they had just been lit to contrast with. */
.ft-scene::before{ content:""; position:absolute; inset:0; z-index:0;
  background:url('../../marks/grove/shore.jpg') center 58% / cover no-repeat; }
/* the grove is painted in both palettes now, and follows the ramp like the
   section canvases do. Same composition, so grove.js ANCHORS need no change. */
body[data-ramp="tide"] .ft-scene::before{
  background-image:url('../../marks/grove/shore-tide.jpg'); }
/* PIGMENT ON THIS PAGE'S PAPER, not a photo of a painting (user: "how can we
   make it such that it feels like its painted on the paper background").
   The tell was that the JPEG brought its OWN paper — a warm sheet measured at
   R255 G233 B210 — so wherever the paint was thin you saw cream where the page
   is cool grey, and a rectangle of foreign paper is exactly what reads as
   pasted. Two halves to the fix:
     1. the asset is white-balanced, each channel divided by that white point,
        so blank paper is neutral and carries no colour of its own;
     2. multiply, which is how watercolour actually works — pigment can only
        darken the sheet under it, never lighten it. Paper at white becomes a
        no-op and the page's own ground shows through untouched, so the edges
        have nothing to give them away and the paper's tooth reads as the
        page's tooth.
   Multiply blends against .ft-scene's own --lx-bg fill on purpose: main
   {z-index:1} is an isolated group, so blending cannot reach the body ground —
   verified, the no-fill version was pixel-identical to no blending at all. */
.ft-scene::before{ mix-blend-mode:multiply; }
/* THE NIGHT ROOM DIMS THE LIGHT, NOT THE PAINTING (user: "it looks awkward in
   the night mode"). Dimming the art alone turned a warm sunset into brown mud —
   an underexposed photograph, not a night — and because a subtractive model
   needs paper lighter than its pigment, the paper had gone dark underneath it,
   so the birds had to be flipped to near-white to survive and read as glowing
   marks pasted on top.
   So the painting keeps its own paper at night: .ft-scene's sheet stays light in
   both rooms, and the filter moves to the WHOLE scene — paper, paint and birds
   dimmed by one amount, the way a painting looks in a dim room. Every
   relationship inside the picture is preserved, which is why nothing needs to be
   special-cased any more: multiply is unconditional and the pigments never
   change. The edge masks keep it from reading as a lit panel. */
.ft-scene{ --paper:var(--lx-bg); }
/* THE NIGHT SHEET FOLLOWS THE RAMP. One warm sheet served both paintings and
   turned the tide grove to olive mud (user: "the dark mode looks a bit weird
   especially for the tide version") — a hue-60 paper under blue pigment is
   subtractively a green filter, which is exactly what it looked like. The sheet
   is the paper the painting was made on, so it belongs to the palette, not to
   the room. */
/* .44, from a second bracket after the sheet was fixed: .50 still read as a lit
   panel hung on the page, .34 took the palms with it */
body.theme-dark .ft-scene{ --paper:oklch(0.90 0.014 62);
  filter:brightness(.44) saturate(.94); }
body.theme-dark[data-ramp="tide"] .ft-scene{ --paper:oklch(0.90 0.012 232); }
.ft-scene canvas{ position:absolute; left:0; bottom:0; width:100%; display:block;
  z-index:1; pointer-events:none;
  /* the last of the sharpness: a sub-pixel blur takes the vector edge off marks
     that are already drawn wet (js/landing/grove.js WET) without smudging them */
  filter:blur(0.4px); }
/* the birds are pigment too — they multiply INTO the painting instead of sitting
   on a pane of glass above it. Unconditional now: the sheet is light in both
   rooms, so a subtractive mark works in both. */
.ft-scene canvas{ mix-blend-mode:multiply; }
@media (max-width:980px){
  .ft-top{ grid-template-columns:1fr; }
  .ft-scene{ height:clamp(240px,40vw,320px); margin:10px 0 0; }
}
@media (prefers-reduced-motion:reduce){ .ft-scene{ cursor:default; } }

.gstage.grabbing{ cursor:grabbing; }
.gstage canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.gstage .lt-poles{ position:absolute; inset:0; pointer-events:none; }
.gstage .lt-poles .tick-tip{ position:absolute; left:0; top:0; display:inline-flex; align-items:center;
  gap:7px; padding:6px 11px; border-radius:8px; background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line);
  font-family:var(--font-num); box-shadow:var(--chip-shadow), var(--chip-in);
  transform:translate(-50%,-135%); opacity:0; transition:opacity .12s; white-space:nowrap; z-index:6; }
.gstage .lt-poles .tick-tip.on{ opacity:1; }
.gstage .lt-poles .tick-tip .pin{ width:6px; height:6px; border-radius:50%; background:var(--acc); }
.gstage .lt-poles .tick-tip b{ font-size:11.5px; font-weight:600; color:var(--ink); letter-spacing:-.01em; }
.gstage .lt-poles .tick-tip .val{ font-size:10.5px; font-weight:600; }
.gstage .lt-poles .tick-tip .val.up{ color:var(--up); } .gstage .lt-poles .tick-tip .val.down{ color:var(--down); }
.gstage .lt-poles .tick-tip .tt-hint{ font-size:8.5px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); padding-left:8px; border-left:1px solid var(--line); }
/* -------------------------------------------------------------- footer */
.ft{ position:relative; padding:clamp(26px,3.5vh,48px) 0 calc(28px + env(safe-area-inset-bottom,0px)); }
/* the brand column carries the ask now — give it the room */
.ft-grid{ grid-template-columns:minmax(0,1.35fr) minmax(0,1.65fr); }
/* footer content above the panel's own stacking; the grove lives in the
   shore section now */
.ft > .wrap{ position:relative; z-index:1; }
.ft-grid{ display:grid; grid-template-columns:minmax(0,1.3fr) minmax(0,2fr);
  gap:clamp(24px,4vw,56px); }
.ft-grid > *{ min-width:0; }
.ft-brand .lt-brand{ display:inline-flex; align-items:center; gap:10px; }
.ft-brand .lt-mark{ width:24px; height:24px; }
.ft-brand .wm{ font-family:var(--font-num); font-size:15px; }
.ft-brand .wm b{ font-weight:600; } .ft-brand .wm span{ color:var(--lx-mut); }
.ft-auct{ display:inline-flex; align-items:center; gap:9px; font-family:var(--font-num); font-size:10.5px;
  color:var(--lx-mut); font-variant-numeric:tabular-nums; }
.ft-auct b{ color:var(--lx-ink-2); font-weight:500; min-width:74px; display:inline-block; }
.ft-auct .pdot{ width:7px; height:7px; border-radius:50%; background:var(--lx-up);
  animation:pulse 2.6s ease-in-out infinite; }
.ft-cols{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:22px 18px; }
.ft-col{ display:flex; flex-direction:column; gap:9px; }
.ft-col .h{ font-family:var(--font-num); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--lx-mut); margin-bottom:4px; }
.ft-col a{ font-family:var(--font-num); font-size:12px; color:var(--lx-ink-2); width:fit-content;
  transition:color .15s; }
.ft-col a:hover{ color:var(--lx-ink); }
.ft-col a .ext{ opacity:.5; font-size:10px; margin-left:3px; }
/* dev-only exploration links inside the Design column — index.html reveals
   them on local origins only. A shade quieter than the product links, so the
   column still reads as three shipped pages plus a workbench. */
.ft-col a.ft-x{ color:var(--lx-mut); }
.ft-col a.ft-x:hover{ color:var(--lx-ink); }
.ft-legal{ display:flex; align-items:center; gap:18px; flex-wrap:wrap; margin-top:clamp(20px,2.6vw,30px);
  font-family:var(--font-num); font-size:10.5px; color:var(--lx-mut); }
.ft-legal .links{ margin-left:auto; display:flex; gap:16px; }
.ft-legal a{ color:var(--lx-mut); transition:color .15s; }
.ft-legal a:hover{ color:var(--lx-ink); }
@media (max-width:900px){
  .ft-grid{ grid-template-columns:1fr; }
  .ft-cols{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .ft-legal .links{ margin-left:0; }
}
/* the explorations fold behind their own summary at EVERY width (user) — six
   of them ran the Design column twice as long as its neighbours. Missing out
   folds with them: it is an explorations page too. */
.ft-col .ft-x{ display:none; }
.ft-more{ display:none; }
.ft-col.has-x .ft-more{ display:flex; align-items:center; gap:6px;
  margin-top:2px; padding:0; border:0; background:none; cursor:pointer;
  font-family:var(--font-num); font-size:12.5px; letter-spacing:.04em;
  color:var(--lx-mut); transition:color .15s; }
.ft-col.has-x .ft-more:hover{ color:var(--lx-ink); }
.ft-col.has-x .ft-more .pl{ font-size:14px; line-height:1; }
.ft-col.has-x.open .ft-x{ display:block; }
.ft-col.has-x.open .ft-more{ color:var(--lx-ink); }

/* ------------------------------------------- corner toggles: the old pills
   Two round pills (user's pick over the single square dock): colorway bottom
   left, theme bottom right. Quiet at rest, full strength when you go near
   them. These are the one place the sharp-radius law yields — a pill of round
   swatches is what the user asked for by name. */
.ctog{ position:fixed; bottom:calc(clamp(14px,2vw,22px) + env(safe-area-inset-bottom,0px));
  z-index:85; display:flex; align-items:center; gap:3px; padding:4px; border-radius:999px;
  background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line);
  box-shadow:var(--chip-shadow), var(--chip-in);
  opacity:.5; transition:opacity .18s var(--enter); }
.ctog:hover, .ctog:focus-within{ opacity:1; }
.ctog.ct-left{ left:calc(clamp(16px,2.4vw,26px) + env(safe-area-inset-left,0px)); }
.ctog.ct-right{ right:calc(clamp(16px,2.4vw,26px) + env(safe-area-inset-right,0px)); }
.ctog button{ width:30px; height:30px; border-radius:999px; border:0; background:none; padding:0;
  display:grid; place-items:center; cursor:pointer;
  transition:background .18s, transform .15s var(--enter); }
.ctog button:hover{ transform:translateY(-1px); }
.ctog button:active{ transform:scale(.92); }
.ctog button.on{ background:oklch(1 0 0/.14); box-shadow:inset 0 1px 0 oklch(1 0 0/.14); }
body:not(.theme-dark) .ctog button.on{ background:#fff;
  box-shadow:0 1px 3px -1px oklch(0.3 0.03 260/.3); }
.ctog .sw{ width:16px; height:16px; border-radius:50%;
  box-shadow:inset 0 0 0 1px oklch(0 0 0/.1); }
.ctog .ti{ font-size:14px; line-height:1; color:var(--lx-mut); }
.ctog button.on .ti{ color:var(--lx-ink); }
@media (pointer:coarse){ .ctog{ opacity:1; } .ctog button{ width:36px; height:36px; } }

/* --------------------------------------------- light-room exceptions
   Surfaces whose dark literals don't route through the --lx/glass tokens. */
body:not(.theme-dark) .nv nav a:hover{ background:oklch(0.2 0.014 260/.06); }
body:not(.theme-dark) .nv-cta{ color:oklch(0.985 0.003 250); background:var(--lx-ink); box-shadow:none; }
/* On near-white, a tinted radial that terminates before 100% shows its own
   edge as a band — the warm one across the middle of the hero was the ramp's
   gold stop ending at 62%. Both now fall all the way off, through a mid stop
   so the ramp is halved before it fades rather than cut. */
body:not(.theme-dark) .app-feats .ic{ background:oklch(1 0 0/.6);
  border-color:oklch(0.2 0.014 260/.12); box-shadow:inset 0 1px 0 #fff; }
body:not(.theme-dark) .stats::before{
  background:linear-gradient(90deg, transparent, oklch(0.2 0.014 260/.18), transparent); }
body:not(.theme-dark) .fx-cols .c .k::before, body:not(.theme-dark) .fx-cols .c .k::after{
  background:linear-gradient(90deg, transparent, oklch(0.2 0.014 260/.25)); }
body:not(.theme-dark) .iph-screen{ background:oklch(0.987 0.0035 250); }
body:not(.theme-dark) .ip-card{ background:linear-gradient(150deg, oklch(1 0 0/.78), oklch(1 0 0/.52));
  border-color:oklch(0.2 0.014 260/.1); box-shadow:inset 0 1px 0 #fff; }
body:not(.theme-dark) .ip-trade{ color:oklch(0.985 0.003 250); background:var(--lx-ink); }
body:not(.theme-dark) .ip-dock{ background:oklch(1 0 0/.62); border-color:oklch(0.2 0.014 260/.1); }
body:not(.theme-dark) .ip-alert .ic{ background:oklch(1 0 0/.75); border-color:oklch(0.2 0.014 260/.1); }

/* the real app, filling the screen under the hardware's status row. It is art
   here, not a control: pointer-events off so it never traps the page's scroll,
   and out of the tab order. */
/* It renders at a REAL phone's width (390px) and is scaled down to fit the
   bezel, rather than being handed a 283px viewport — at that width the app's
   own breakpoints crush the greeting onto two lines and every proportion is a
   little wrong. landing.js writes --iph-s from the screen's measured width. */
/* The fallback matters: the wrap is always (phone - 12 - 24)px wide and the
   phone is at most 318, so the largest honest ratio is ~0.72. A fallback ABOVE
   that overflows and the app gets sliced down its right edge before JS has
   measured anything — which is exactly what "the view is broken" was. 0.64 can
   never overflow at any width; JS refines it the moment the wrap has a size. */
.iph-app{ border:0; display:block;
  width:390px; transform-origin:0 0; transform:scale(var(--iph-s, 0.64));
  background:transparent; pointer-events:none; }
/* the host owns the real height and clips; the iframe's own height is set in JS
   to the PRE-scale height so the app is handed the viewport it actually fills */
/* inset from the top by the island's height: the app keeps its own natural
   layout and simply starts below the notch, the way it would on hardware. The
   strip above it is the screen's own ground, matched to the app's, so there is
   no seam where one ends and the other begins. */
/* inset 0 — every non-zero inset made a band and cut the app's own header
   wash (user). The corner-radius clearance lives INSIDE the app instead:
   landing.js pads the app's fixed header by 10px, so the wash still reaches
   the top of the glass and only the mark moves down. */
.iph-appwrap{ position:absolute; inset:0; overflow:hidden; border-radius:inherit; }

/* ---- (the hand-built phone interior lived here — replaced by the real app) ---- */
.ip-body{ position:relative; flex:1; min-height:0; margin-top:0; }
.ip-scr{ position:absolute; inset:0; display:flex; flex-direction:column;
  transform:translateX(103%); opacity:0; pointer-events:none;
  transition:transform .55s var(--enter), opacity .45s var(--enter); }
.ip-scr.on{ transform:none; opacity:1; pointer-events:auto; }
.ip-scr.out{ transform:translateX(-22%); opacity:0; }
.ip-nav{ display:flex; align-items:center; gap:8px; padding:2px 2px 10px; }
.ip-nav .bk{ font-size:16px; line-height:1; color:var(--lx-mut); }
.ip-nav b{ font-size:12px; font-weight:600; }
.ip-nav .mut{ margin-left:auto; font-family:var(--font-num); font-size:8.5px; letter-spacing:.1em;
  color:var(--lx-mut); border:1px solid oklch(1 0 0/.14); border-radius:5px; padding:2px 6px; }
body:not(.theme-dark) .ip-nav .mut{ border-color:oklch(0.2 0.014 260/.16); }
.ip-px .v{ margin-top:4px; font-family:var(--font-num); font-size:24px; font-weight:600;
  letter-spacing:-.015em; font-variant-numeric:tabular-nums; }
.ip-px .d{ margin-top:2px; font-family:var(--font-num); font-size:10.5px; font-weight:600;
  color:var(--lx-up); font-variant-numeric:tabular-nums; }
.ip-amt{ margin-top:9px; }
.ip-amt .v2{ margin-top:4px; font-family:var(--font-num); font-size:20px; font-weight:600;
  font-variant-numeric:tabular-nums; }
.ip-amt .v2 .lv{ font-size:11px; color:var(--lx-mut); font-weight:500; }
.ip-amt .chips{ display:flex; gap:6px; margin-top:9px; }
.ip-amt .chips span{ font-family:var(--font-num); font-size:9.5px; padding:5px 10px; border-radius:6px;
  color:var(--lx-mut); border:1px solid oklch(1 0 0/.13); }
.ip-amt .chips span.on{ color:var(--lx-ink); background:oklch(1 0 0/.1); border-color:oklch(1 0 0/.22); }
body:not(.theme-dark) .ip-amt .chips span{ border-color:oklch(0.2 0.014 260/.14); }
body:not(.theme-dark) .ip-amt .chips span.on{ background:oklch(0.2 0.014 260/.06); border-color:oklch(0.2 0.014 260/.3); }
.ip-prot{ margin-top:9px; }
.ip-prot .pr{ display:flex; gap:6px; margin-top:8px; flex-wrap:wrap; }
.ip-prot .pr span{ font-family:var(--font-num); font-size:9px; padding:4px 8px; border-radius:6px;
  font-variant-numeric:tabular-nums; }
.ip-prot .tp{ color:var(--lx-up); background:color-mix(in oklab, var(--lx-up) 14%, transparent); }
.ip-prot .sl{ color:var(--lx-down); background:color-mix(in oklab, var(--lx-down) 14%, transparent); }
.ip-prot .lq{ color:var(--lx-mut); border:1px solid oklch(1 0 0/.12); }
body:not(.theme-dark) .ip-prot .lq{ border-color:oklch(0.2 0.014 260/.14); }
.ip-long{ margin-top:auto; border:0; cursor:pointer; text-align:center; font-family:var(--font-sans);
  font-size:13px; font-weight:600; border-radius:10px; padding:13px;
  color:oklch(0.985 0.01 160); background:color-mix(in oklab, var(--lx-up) 72%, black);
  transition:transform .18s var(--enter), filter .18s; }
.ip-long.press{ transform:scale(.96); filter:brightness(1.15); }
body:not(.theme-dark) .ip-long{ background:color-mix(in oklab, var(--lx-up) 88%, black); color:#fff; }
.ip-done{ margin:auto; text-align:center; display:flex; flex-direction:column; align-items:center; gap:8px; }
.ip-done .ok{ width:46px; height:46px; border-radius:50%; display:grid; place-items:center;
  background:color-mix(in oklab, var(--lx-up) 20%, transparent); color:var(--lx-up); }
.ip-done .ok svg{ width:20px; height:20px; stroke:currentColor; fill:none; stroke-width:2.4;
  stroke-linecap:round; stroke-linejoin:round; }
.ip-done b{ font-size:16px; font-weight:600; letter-spacing:-.01em; }
.ip-done p{ margin:0; font-family:var(--font-num); font-size:10px; line-height:1.6; color:var(--lx-mut);
  font-variant-numeric:tabular-nums; }
.ip-done .pl{ font-family:var(--font-num); font-size:19px; font-weight:600; color:var(--lx-up);
  font-variant-numeric:tabular-nums; }
.ip-done .pl span{ font-size:11px; color:var(--lx-mut); font-weight:500; }

/* ---- globe flag-poles + marker cards (the letter's, on this page's vars) */
.gstage .pole{ position:absolute; left:0; top:0; will-change:transform, opacity; transform-origin:left bottom; }
.gstage .pole .line{ position:absolute; left:-0.5px; bottom:0; width:1.5px; background:var(--muted);
  transform-origin:50% 100%; }
.gstage .pole .head{ position:absolute; transform:translate(-50%,-100%); white-space:nowrap;
  pointer-events:auto; transition:opacity .2s ease; -webkit-user-select:none; user-select:none; }
.gstage .pole .base{ position:absolute; left:-3px; bottom:-3px; width:6px; height:6px; border-radius:50%;
  background:var(--ink); opacity:.6; }
.gstage .pole.thesis .line{ background:var(--acc); }
.gstage .pole.thesis .base{ background:var(--acc); opacity:1; }
.gstage .pole.invest .line{ background:var(--ink); }
.gstage .pole.news .line{ background:repeating-linear-gradient(var(--muted) 0 3px, transparent 3px 6px); }
.gstage .mk{ display:inline-flex; flex-direction:column; align-items:flex-start; gap:0; padding:7px 11px;
  border-radius:10px; background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line); font-family:var(--font-num);
  box-shadow:var(--chip-shadow), var(--chip-in);
  transition:transform .15s, box-shadow .2s; cursor:pointer; }
@media (hover:hover) and (pointer:fine){
.gstage .mk:hover{ transform:translateY(-2px); box-shadow:var(--elev-3); }
}
.gstage .mk .mk-row{ display:inline-flex; align-items:center; gap:8px; }
.gstage .mk .pin{ width:8px; height:8px; border-radius:50%; flex:0 0 auto; }
.gstage .mk .nm{ font-size:12px; font-weight:600; color:var(--lx-ink); letter-spacing:-.01em; }
.gstage .mk .sub{ font-size:10px; color:var(--muted); }
.gstage .mk .val{ font-size:11.5px; font-weight:600; }
.gstage .mk .val.up{ color:var(--up); } .gstage .mk .val.down{ color:var(--down); }
.gstage .mk .mk-more{ display:grid; grid-template-rows:0fr; opacity:0;
  transition:grid-template-rows .28s cubic-bezier(.16,1,.3,1), opacity .2s; }
.gstage .mk:hover .mk-more, .gstage .mk.open .mk-more{ grid-template-rows:1fr; opacity:1; }
.gstage .mk .mk-more .in{ overflow:hidden; min-height:0; }
.gstage .mk .mk-more .dek{ font-family:var(--font-sans); font-size:11px; line-height:1.4;
  color:var(--lx-ink-2); max-width:230px; white-space:normal; padding:8px 0 1px;
  border-top:1px solid var(--line); margin-top:7px; }
.gstage .mk .mk-more .meta{ display:flex; gap:14px; padding:8px 0 1px; border-top:1px solid var(--line); margin-top:7px; }
.gstage .mk .mk-more .meta span{ display:flex; flex-direction:column; }
.gstage .mk .mk-more .meta i{ font-style:normal; font-size:8px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--muted); }
.gstage .mk .mk-more .meta b{ font-size:11px; color:var(--ink); }
.gstage .mk.thesis{ border-color:color-mix(in oklab, var(--acc), transparent 50%); }
.gstage .mk.thesis .pin{ background:var(--acc); }
.gstage .mk.invest .pin{ background:var(--ink); border-radius:2px; }
.gstage .mk.news{ border-style:dashed; }
.gstage .mk.news .pin{ background:var(--g3); border-radius:1px; width:9px; height:6px; }

/* -------------------------------------------------------------- comfort */
@media (pointer:coarse){
  .pbtn, .nv-cta, .mkts-foot a{ min-height:44px; }
  .ft-col a, .ft-legal a{ padding-block:5px; }
}
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  .nv, .hero-c > *, .lb-frame{ animation-duration:.01s !important; animation-delay:0s !important; }
  .iph{ animation:none; }
  [data-io]{ transition:none; opacity:1; transform:none; }
  .tape-row.a, .tape-row.b{ animation:none; }
  .tape{ overflow-x:auto; -webkit-mask-image:none; mask-image:none; }
  .fxc .pdot, .ft-auct .pdot{ animation:none; }
  .ip-scr{ transition:none; }
  .fxt{ transform:translateX(-50%); transition:opacity .2s ease; }
  .fxt.on{ transform:translateX(-50%); }
  @media (max-width:1180px){
    .fxt, .fxt.on{ transform:translate(-50%,-50%); }
  }
}
/* ------------------------------------------------------- the cookie bar
   A design element for now (user): chip glass, the mark, one joyful line.
   No storage anywhere — it greets every hard refresh; either door slides
   it away for this visit only. */
.ckbar[hidden]{ display:none; }
/* z-index 88, above the corner furniture's 85 (user: the bar belongs on top of
   the theme, colourway and backdrop pickers). It asks a question and the reader
   should be able to answer it without a picker sitting on the Accept button. */
.ckbar{ position:fixed; left:50%; bottom:calc(18px + env(safe-area-inset-bottom,0px));
  z-index:88; transform:translate(-50%, calc(100% + 30px));
  display:flex; align-items:center; gap:11px; padding:11px 16px 11px 14px;
  max-width:min(600px, calc(100vw - 2*var(--edge)));
  border-radius:13px; background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  border:1px solid var(--lx-line); box-shadow:var(--chip-shadow), var(--chip-in);
  opacity:0; transition:transform .65s var(--enter), opacity .45s; }
.ckbar.on{ transform:translate(-50%, 0); opacity:1; }
.ckbar .ck-bite{ flex:0 0 auto; transform:rotate(-10deg); }
.ckbar p{ margin:0; font-size:13px; line-height:1.4; color:var(--lx-ink-2); text-wrap:pretty; }
.ckbar p a{ color:var(--lx-ink); text-decoration:underline; text-underline-offset:3px;
  text-decoration-color:color-mix(in oklab, var(--lx-ink) 32%, transparent);
  transition:text-decoration-color .15s; }
.ckbar p a:hover{ text-decoration-color:var(--lx-ink); }
.ck-acts{ display:inline-flex; align-items:center; gap:7px; margin-left:8px; flex:0 0 auto; }
.ck-acts i{ font-style:normal; color:var(--lx-faint); font-size:12px; }
.ck-acts button{ border:0; background:none; padding:6px 2px; cursor:pointer;
  font-family:var(--font-sans); font-size:13px; font-weight:600;
  color:var(--lx-mut); transition:color .15s; }
.ck-acts button:hover{ color:var(--lx-ink); }
.ck-acts button:active{ transform:scale(.92); }
.ck-acts button{ transition:color .15s, transform .12s var(--enter); }
.ck-acts #ckYes{ color:var(--lx-ink); }
/* Accept: the cookie is EATEN — two more bites (the mask circles grow, r is
   a CSS geometry property) while crumbs pop off; Decline: a polite no.
   Either way the bar takes its bow only after the little show. */
/* .crumble replaced .shake when Decline stopped being a head-shake; the stale
   name here would have left the buttons live through the whole performance */
.ckbar.eat, .ckbar.crumble{ pointer-events:none; }
.ck-bite .b2, .ck-bite .b3, .ck-bite .b4{ transition:r .15s var(--enter); }
.ckbar.eat .ck-bite{ animation:ck-hop .72s var(--enter); }
.ckbar.eat .ck-bite .b2{ r:5.6px; transition-delay:.1s; }
.ckbar.eat .ck-bite .b3{ r:7px; transition-delay:.26s; }
/* THE LAST BITE FINISHES IT (user). Three bites left a crescent, which read as
   a cookie put down rather than eaten. r:15 at 24 units clears the whole disc
   from wherever it is centred, and it is slower than the others because the
   final mouthful is the big one. */
.ckbar.eat .ck-bite .b4{ r:15px; transition:r .3s var(--enter) .44s; }
.ck-bite .cr{ opacity:0; }
.ckbar.eat .ck-bite .cr{ animation:ck-crumb .5s var(--enter) forwards; }
.ckbar.eat .ck-bite .cr.c1{ animation-delay:.1s; }
.ckbar.eat .ck-bite .cr.c2{ animation-delay:.16s; }
.ckbar.eat .ck-bite .cr.c3{ animation-delay:.26s; }
/* the last two fly on the final bite, so the cookie goes out in a puff */
.ckbar.eat .ck-bite .cr.c4{ animation-delay:.48s; }
.ckbar.eat .ck-bite .cr.c5{ animation-delay:.56s; }

/* ---- CRUMBLE: the Decline ------------------------------------------------
   The cookie fractures, the pieces fall turning, and they PILE rather than
   fade — the crumbs are still sitting there as the bar bows out. The shards
   must escape the 24-unit box to fall convincingly, so the svg stops clipping;
   nothing else in the bar paints outside it. */
.ckbar .ck-bite{ overflow:visible; }
.ck-bite .sh{ opacity:0; fill:url(#ckDough);
  /* rotate each piece about ITS OWN centre, not the viewBox origin */
  transform-box:fill-box; transform-origin:50% 50%; }
/* the whole body goes at once — the break is the moment, not a dissolve */
.ckbar.crumble .ck-body{ opacity:0; transition:opacity .01s linear .13s; }
.ckbar.crumble .ck-bite{ animation:ck-crack .13s var(--enter) both; }
.ckbar.crumble .ck-bite .sh{
  /* ease-IN, because falling accelerates — the pieces are slowest at the top */
  animation:ck-tumble .66s cubic-bezier(.38,0,.82,.46) both .13s; }
/* ---- the pile catches fire -----------------------------------------------
   Flames rise out of the landed shards, and the shards CHAR under them: the
   darkening is a filter on the whole group, which is one declaration instead of
   restaging every piece's fill, and it reads as soot rather than as a recolour.
   The flames scale from their own base, so they lick upward instead of
   inflating from the middle. */
.ck-fire{ opacity:0; }
.ck-fire .fl{ fill:url(#ckFlame); transform-box:fill-box; transform-origin:50% 100%;
  /* soft, like everything else in this bar — hard vector edges were the
     other half of why the licks looked stuck on */
  filter:blur(.34px); }
.ck-fire .glow{ fill:url(#ckGlow); opacity:0; filter:blur(1.1px); }
.ckbar.crumble .ck-fire{ opacity:1; }
.ckbar.crumble .ck-fire .glow{ animation:ck-glow .72s ease-out both .84s; }
.ckbar.crumble .ck-fire .fl{ animation:ck-flame .58s ease-out both .9s; }
.ckbar.crumble .ck-fire .fl.f2{ animation-duration:.66s; animation-delay:.94s; }
.ckbar.crumble .ck-fire .fl.f3{ animation-duration:.6s;  animation-delay:.91s; }
@keyframes ck-glow{
  0%{ opacity:0; transform:scale(.6); }
  30%{ opacity:1; transform:scale(1); }
  62%{ opacity:.86; transform:scale(1.05); }
  100%{ opacity:0; transform:scale(.9); }
}
@keyframes ck-flame{
  0%{ opacity:0; transform:scaleY(.18) scaleX(.7); }
  20%{ opacity:1; transform:scaleY(1.02) scaleX(1); }
  44%{ transform:scaleY(.82) scaleX(1.1); }
  66%{ opacity:1; transform:scaleY(1.1) scaleX(.9); }
  100%{ opacity:0; transform:scaleY(.28) scaleX(.8); }
}

/* ---- and then it un-bakes ------------------------------------------------
   The heap softens into raw dough before the bar takes it away. The shards fade
   as the mound arrives, at the same place, so it reads as the crumbs relaxing
   rather than one thing being swapped for another. The mound rises from its own
   base (transform-origin at the bottom) so it SPREADS as it settles, which is
   what soft dough does; a centred origin made it inflate like a balloon. */
.ck-dough{ opacity:0; }
.ck-dough .dg{
  /* pale and desaturated — dough has not caramelised. Mixed off the ramp so it
     still belongs to whichever palette the page is wearing. */
  fill:color-mix(in oklab, var(--g4) 46%, oklch(0.9 0.022 82));
  /* the softness is literal: without this it is five hard ellipses. .28, not
     .42 — at the larger blur the lobes melted into one flat shape and the heap
     lost its form; this softens the edges while the dome survives. */
  filter:blur(.28px); }
/* the base carries the shadow side, which is what gives the heap weight */
.ck-dough .dg.base{ fill:color-mix(in oklab, var(--g5) 42%, oklch(0.82 0.026 74)); }
/* the dough now waits for the fire to burn out */
.ckbar.crumble .ck-dough{
  transform-box:fill-box; transform-origin:50% 100%;
  animation:ck-dough .36s var(--enter) both 1.42s; }
/* the pieces char while they burn, then hand over to the dough — two animations
   on one group, because the charring has to persist through the fire rather than
   fading with it */
.ckbar.crumble .shards{
  animation:ck-char .55s ease-out both .88s,
            ck-shards-out .28s linear both 1.44s; }
/* TOASTED, NOT OBLITERATED. brightness(.3) took the pile to near-black and it
   read as a dead bug rather than a burnt biscuit; sepia keeps it brown while it
   darkens, which is what over-baking actually looks like. */
@keyframes ck-char{ to{ filter:brightness(.52) saturate(.6) sepia(.4); } }
@keyframes ck-shards-out{ to{ opacity:0; } }
@keyframes ck-dough{
  0%{ opacity:0; transform:scaleY(.62) scaleX(.9); }
  55%{ opacity:1; }
  100%{ opacity:1; transform:none; }
}

/* the pieces with furthest to fall leave first, so the heap lands together
   instead of the low chunks arriving and the top one catching up late */
.ckbar.crumble .ck-bite .sh.s2{ animation-delay:.17s; }
.ckbar.crumble .ck-bite .sh.s3{ animation-delay:.25s; }
.ckbar.crumble .ck-bite .sh.s4{ animation-delay:.24s; }
.ckbar.crumble .ck-bite .sh.s5{ animation-delay:.16s; }
.ckbar.crumble .ck-bite .sh.s6{ animation-delay:.14s; }
.ckbar.crumble .ck-bite .sh.s7{ animation-delay:.15s; }
.ckbar.crumble .ck-bite .sh.s8{ animation-delay:.2s; }
@keyframes ck-hop{
  0%{ transform:rotate(-10deg); }
  30%{ transform:translateY(-3.5px) rotate(2deg); }
  62%{ transform:translateY(.5px) rotate(-16deg); }
  100%{ transform:rotate(-10deg); }
}
@keyframes ck-crumb{
  0%{ opacity:0; transform:translate(0,0); }
  22%{ opacity:1; }
  100%{ opacity:0; transform:translate(var(--dx,4px), var(--dy,-4px)); }
}
/* the flinch that breaks it — one sharp jolt, not a wobble */
@keyframes ck-crack{
  0%{ transform:rotate(-10deg) scale(1); }
  55%{ transform:rotate(-11.5deg) scale(1.05); }
  100%{ transform:rotate(-10deg) scale(1); }
}
/* the fall. The last 12% is the settle: each piece drops a hair past its
   resting place and comes back, which is what landing on a pile looks like —
   it is a stop, not a bounce. */
@keyframes ck-tumble{
  0%{ opacity:0; transform:translate(0,0) rotate(0deg); }
  9%{ opacity:1; }
  88%{ opacity:1;
    transform:translate(var(--fx,0), calc(var(--fy,8px) + .6px)) rotate(var(--fr,0deg)); }
  100%{ opacity:1;
    transform:translate(var(--fx,0), var(--fy,8px)) rotate(var(--fr,0deg)); }
}
@keyframes ck-no{
  0%,100%{ transform:rotate(-10deg) translateX(0); }
  25%{ transform:rotate(-10deg) translateX(-3px); }
  55%{ transform:rotate(-10deg) translateX(3px); }
  80%{ transform:rotate(-10deg) translateX(-1.5px); }
}
/* The bar used to be held 72px up so it could step OVER the corner toggles.
   It does not need to any more (user: it can sit much lower and overlap them) —
   it paints at z-index 88 above their 85, and they take themselves away on scroll
   regardless. Dropping the offset puts it at the bottom edge where a consent bar
   belongs, and buys back most of a screen on a phone. */
@media (max-width:560px){
  /* it was a cramped little card in the middle of the screen (user): it takes
     the full gutter width now, with real padding and readable type */
  /* TWO CLEAN ROWS (user: the sizing and the button position looked off). The
     actions were floating on a second line with margin-left:auto, so Accept sat
     hard against the bar's right edge — and the label ran under the backdrop
     chip. They get a row of their OWN now (flex-basis:100%), which also lets the
     text use the full width above them instead of being squeezed beside them. */
  .ckbar{ left:12px; right:12px; transform:translateY(calc(100% + 30px));
    max-width:none; padding:12px 14px 2px; gap:11px; align-items:flex-start;
    flex-wrap:wrap; row-gap:0; }
  .ckbar.on{ transform:none; }
  /* basis 0, not auto: with an auto basis the sentence's intrinsic width pushed
     itself onto its own line and the 20px glyph sat alone above it — three rows
     for two things. At 0 it shares row one with the glyph and the bar is a row
     shorter. */
  .ckbar p{ flex:1 1 0; min-width:0; font-size:13.5px; line-height:1.45; }
  /* THE 44px TARGET IS THE SPACING (user: too much air between the text and the
     buttons, making the bar taller than it needs to be). A 44px button holding
     14px type carries ~13px of empty space above its label, and that was sitting
     on top of the bar's own row-gap and bottom padding — three paddings stacked.
     The row is pulled up into that space and the bar's bottom padding shrinks to
     almost nothing: the tap target is untouched, only the emptiness goes. */
  .ck-acts{ flex:0 0 100%; display:flex; align-items:center; justify-content:flex-end;
    gap:2px; margin:-6px 0 0; }
  /* 44px of touch target, but the ink stays where the eye expects it: the
     padding does the reaching, not the text */
  .ck-acts button{ min-height:44px; padding:6px 10px; font-size:14px; }
  .ck-acts button:last-child{ padding-right:2px; }
  .ckbar .ck-bite{ margin-top:3px; }
}
@media (prefers-reduced-motion:reduce){
  .ckbar{ transition:opacity .3s linear; transform:translate(-50%, 0); }
  .ckbar .ck-bite, .ckbar .ck-bite .cr, .ckbar .ck-bite .sh,
  .ckbar .ck-dough, .ckbar .shards, .ckbar .ck-fire, .ckbar .ck-fire .fl{ animation:none; }
  /* no pyrotechnics for anyone who asked for stillness */
  .ckbar .ck-fire{ display:none; }
  .ck-bite .b2, .ck-bite .b3, .ck-bite .b4{ transition:none; }
  /* both doors still resolve, they just do not perform: the cookie is simply
     gone rather than eaten or dropped */
  .ckbar.eat .ck-body, .ckbar.crumble .ck-body{ opacity:0; transition:none; }
}

@media (prefers-reduced-transparency:reduce){
  .gl, .lb-frame, .ft-panel, .fxt{ background:oklch(0.22 0.015 270/.96);
    -webkit-backdrop-filter:none; backdrop-filter:none; }
  /* the whole chip family drops to one opaque ground */
  .nv, .ctog, .tk, .gstage .mk, .gstage .lt-poles .tick-tip, .mkts-foot a,
  .cap, .pbtn.glass, .fnd .fund-card, .ckbar, .dk-pole .mk{
    background:oklch(0.21 0.014 270/.97);
    -webkit-backdrop-filter:none; backdrop-filter:none; }
  .glass-widget .tw{ background:oklch(0.22 0.015 270/.96);
    -webkit-backdrop-filter:none; backdrop-filter:none; }
}
@media print{
  body{ background:#fff; color:#000; }
  body::after, .nv, .iph-wrap, .tape, .fxt, .gstage, .ctog{ display:none !important; }
  .lb-frame{ transform:none; }
}

/* ============================================================================
   THE TRADE WIDGET, DRESSED
   ----------------------------------------------------------------------------
   Kept at the END of the file on purpose: this block sat between the desk
   section's rules and got spliced away TWICE while that section was rebuilt,
   each time silently reverting the ticket's slim LONG/SHORT tabs to slabs and
   losing its glass and its sharp radii. Nothing here is scoped to a section,
   so nothing needs it to live next to one.
   ========================================================================== */
.glass-widget{ width:min(392px, 100%); flex:0 0 auto; }

/* the real widget, dressed in smoked glass */
.glass-widget .tw{
  background:var(--glass-2);
  -webkit-backdrop-filter:var(--blur); backdrop-filter:var(--blur);
  border-color:oklch(1 0 0/.16);
  box-shadow:var(--glass-shadow), var(--glass-in);
  --w-card:oklch(1 0 0/.05);
  --w-sunken:oklch(1 0 0/.09);
  --w-fill:oklch(1 0 0/.07);
  --w-line:oklch(1 0 0/.14);
  --w-line-2:oklch(1 0 0/.1);
  --w-ink:var(--lx-ink);
  --w-ink-2:var(--lx-ink-2);
  --w-muted:var(--lx-mut);
  --w-up:var(--lx-up);
  --w-down:var(--lx-down);
  --w-up-bg:color-mix(in oklab, var(--lx-up) 16%, transparent);
  --w-down-bg:color-mix(in oklab, var(--lx-down) 16%, transparent);
  --w-crisp:inset 0 1px 0 oklch(1 0 0/.08);
}
.glass-widget .tw .tw-logo{ background:oklch(0.99 0.002 250); border-color:transparent; }
.glass-widget .tw .tw-logo.mono{ background:oklch(1 0 0/.1); }
body:not(.theme-dark) .glass-widget .tw{
  border-color:oklch(0.2 0.014 260/.14);
  --w-card:oklch(1 0 0/.5);
  --w-sunken:oklch(0.2 0.014 260/.06);
  --w-fill:oklch(0.2 0.014 260/.04);
  --w-line:oklch(0.2 0.014 260/.15);
  --w-line-2:oklch(0.2 0.014 260/.09);
  --w-up-bg:color-mix(in oklab, var(--lx-up) 12%, transparent);
  --w-down-bg:color-mix(in oklab, var(--lx-down) 12%, transparent);
  --w-crisp:inset 0 1px 0 oklch(1 0 0/.85);
}
body:not(.theme-dark) .glass-widget .tw .tw-logo{ box-shadow:inset 0 0 0 1px oklch(0.2 0.014 260/.1); }
body:not(.theme-dark) .glass-widget .tw .tw-logo.mono{ background:oklch(0.2 0.014 260/.05); }

/* sharper corners + slim direction tabs (user decree: no Long/Short slabs) */
.glass-widget .tw{ border-radius:12px; }
.glass-widget .tw-logo{ border-radius:7px; }
.glass-widget .tw-field, .glass-widget .tw-fieldstack{ border-radius:8px; }
.glass-widget .tw-cur{ border-radius:6px; }
.glass-widget .tw-cur button{ border-radius:4px; }
.glass-widget .tw-pill, .glass-widget .px-est, .glass-widget .tw-badge{ border-radius:5px; }
.glass-widget .tw-chips button{ border-radius:6px; }
.glass-widget .tw-eff, .glass-widget .tw-impact, .glass-widget .tw-mode{ border-radius:8px; }
.glass-widget .tw-cta{ border-radius:10px; }
.glass-widget .tw-sides{ display:flex; gap:22px; padding:6px 20px 0; margin:0;
  border-bottom:1px solid var(--w-line-2); }
.glass-widget .tw-sides button{ flex:0 0 auto; background:none; border:0; border-radius:0;
  padding:10px 1px 12px; font-family:var(--w-num); font-size:11px; font-weight:600;
  letter-spacing:.16em; text-transform:uppercase; color:var(--w-muted); position:relative;
  box-shadow:none; }
.glass-widget .tw-sides button:hover{ color:var(--w-ink); background:none; }
.glass-widget .tw-sides button.on{ background:none; box-shadow:none; }
.glass-widget .tw-sides button.on[data-side="buy"]{ color:var(--w-up); background:none; }
.glass-widget .tw-sides button.on[data-side="sell"]{ color:var(--w-down); background:none; }
.glass-widget .tw-sides button.on::after{ content:""; position:absolute; left:0; right:0;
  bottom:-1px; height:2px; background:currentColor; }

/* ---- the TAB STRIP answers the tab (user) ---------------------------------
   Only the band the tabs live in, not the whole pane: the widget keeps its side
   state on the buttons alone, so :has() reaches up from the chosen tab and
   tints the strip it sits in. Doing this in CSS keeps js/kit/trade-widget.js — shared
   with the Trade page and the terminal — untouched. */
.glass-widget .tw-sides{ transition:background .45s var(--enter), border-color .45s var(--enter); }
.glass-widget .tw:has(.tw-sides button.on[data-side="buy"]) .tw-sides{
  background:color-mix(in oklab, var(--lx-up) 9%, transparent);
  border-bottom-color:color-mix(in oklab, var(--lx-up) 26%, transparent); }
.glass-widget .tw:has(.tw-sides button.on[data-side="sell"]) .tw-sides{
  background:color-mix(in oklab, var(--lx-down) 9%, transparent);
  border-bottom-color:color-mix(in oklab, var(--lx-down) 26%, transparent); }
body:not(.theme-dark) .glass-widget .tw:has(.tw-sides button.on[data-side="buy"]) .tw-sides{
  background:color-mix(in oklab, var(--lx-up) 7%, transparent); }
body:not(.theme-dark) .glass-widget .tw:has(.tw-sides button.on[data-side="sell"]) .tw-sides{
  background:color-mix(in oklab, var(--lx-down) 7%, transparent); }
@media (prefers-reduced-motion:reduce){ .glass-widget .tw-sides{ transition:none; } }

/* ============================================================================
   THE SURFACES — a plain composed section
   ----------------------------------------------------------------------------
   The 200vh pinned stage and the interior camera are both gone (user: no
   zoom-out motion — start fully zoomed out). The window and the phone stand
   whole and simply scroll like everything else.
   ========================================================================== */
.sfc{ position:relative; padding:clamp(30px,4vh,54px) 0 0; }
.sfc .sec-head{ margin-bottom:clamp(16px,2vw,28px); }
.sfc .lb-sticky{ display:grid; place-items:center; padding:14px; }
.sfc-stage{ position:relative; display:flex; align-items:flex-end; justify-content:center;
  width:100%; padding:0 var(--edge); }
.sfc .lb-frame{ width:min(1000px, calc(100vw - 2*var(--edge) - 260px));
  height:min(86svh, 1000px); animation:none; opacity:1; flex:0 0 auto; }
.sfc-stage .iph-wrap{ position:static; flex:0 0 auto; z-index:3;
  margin:0 0 -30px -56px; }
.sfc-stage .iph{ width:clamp(232px,20vw,306px); }
.sfc-after{ position:relative; padding:clamp(14px,1.8vw,26px) 0 clamp(46px,6vh,80px); }
/* No [data-io] on the CTA. A call to action that waits for an observer is
   fragile, and a section's last row sits closest to the fold by definition. */
.sfc-cta{ display:flex; gap:10px; justify-content:center; }
@media (max-width:1100px){
  .sfc-stage{ flex-direction:column; align-items:center; gap:22px; }
  .sfc-stage .iph-wrap{ margin:0; }
  .sfc-stage .iph{ width:min(306px,76vw); }
  .sfc .lb-frame{ width:min(1000px, calc(100vw - 2*var(--edge) - 24px));
    height:min(70svh, 620px); }
}
@media (max-width:760px){
  /* a whole desktop app scaled into 350px is unreadable, and it is not what a
     phone visitor came for — the phone preview carries the section alone
     (user). display:none also keeps the lazy iframe from ever fetching. */
  .sfc .lb-frame{ display:none; }
  .sfc-stage .iph{ width:min(306px,82vw); }
}

/* --------------------------------------------- the postcard ground (experiment)
   2026-07-28: a watercolor canvas behind the product (#app) and the desk
   (#desk), so the glass has something to bend — the frosted shell finally
   reads as glass because a real image sits behind it (the reference effect).
   Off by default. The bottom-right #ctBg pill tries each canvas.

   EACH SECTION CARRIES ITS OWN (user 2026-07-28: "allow me to set different
   backgrounds on the different sections"). data-bg lives on the SECTION, not on
   body, so #app and #desk are independent — scroll to one, toggle, and only
   that room changes. The pill follows the section you are reading and shows its
   current pick. Persists as {app,desk} in exotic.landing.tweaks.bg; ?bg= sets
   both at once and ?bgApp=/?bgDesk= set one, for review links.
   The layer is z:-1 inside main's stacking context: under every section's
   content, over the page ground. */
section[data-bg="beach"]{ --bg-art:url("../../marks/backdrops/beach.jpg"); }
section[data-bg="lake"]{  --bg-art:url("../../marks/backdrops/lake.jpg"); }
section[data-bg="solar"]{ --bg-art:url("../../marks/backdrops/solar.jpg"); }
section[data-bg="falls"]{ --bg-art:url("../../marks/backdrops/falls.jpg"); }
section[data-bg="palms"]{ --bg-art:url("../../marks/backdrops/palms.jpg"); }
/* THE CANVAS FOLLOWS THE RAMP where a second painting of the same place exists
   (user supplied the tide versions). This is the answer to the pairing problem
   the warm canvases raised: a swatch names a PLACE, and the place wears whatever
   palette the page is wearing, so a visitor can no longer land on a coral sky
   behind a cyan mark. Only beach and lake are painted twice so far — the rest
   keep their one canvas in both ramps. body[data-ramp] comes from applyRamp.

   The tide dusk is pinned to the SOLAR swatch, not the lake one (user). All
   three of lake.jpg, solar.jpg and solar-tide.jpg are the same composition, so
   the palette-matched pair has to live on ONE swatch or the set is impossible to
   reason about: solar.jpg and solar-tide.jpg are now that pair, and the `lake`
   swatch keeps the untouched pastel original as its own thing. */
body[data-ramp="tide"] section[data-bg="beach"]{ --bg-art:url("../../marks/backdrops/beach-tide.jpg"); }
body[data-ramp="tide"] section[data-bg="solar"]{ --bg-art:url("../../marks/backdrops/solar-tide.jpg"); }
.sec-bg{ position:absolute; inset:0; z-index:-1; pointer-events:none;
  background-image:var(--bg-art, none); background-size:cover; background-position:center 32%;
  opacity:0; transition:opacity .9s var(--enter);
  /* THE DISSOLVE IS IN PIXELS, NOT PERCENT. At 12%/84% of the section the fade
     scaled with the section's height, and #desk is tall enough that the ramp
     started below the fold — so scrolling out of #funds hit the canvas as a hard
     horizontal band, which reads as a broken image rather than a room. A fixed
     220px in and 190px out dissolves over the same distance in both sections. */
  -webkit-mask-image:linear-gradient(180deg, transparent 0, #000 220px, #000 calc(100% - 190px), transparent 100%);
  mask-image:linear-gradient(180deg, transparent 0, #000 220px, #000 calc(100% - 190px), transparent 100%); }
section[data-bg] .sec-bg{ opacity:1; }
/* a veil keeps the type readable over the art — milk in the light room,
   smoke in the dark one (white ink over a pastel sky fails without it) */
.sec-bg::after{ content:""; position:absolute; inset:0;
  background:oklch(0.97 0.005 255/.16); }
body.theme-dark .sec-bg::after{ background:oklch(0.16 0.014 268/.45); }

/* the desk's canvas spans the head and the stage only — see index.html. The
   room takes over the section's TOP padding so the art still starts at the
   section's edge (moving it below the padding made the canvas appear to begin
   halfway down); the section keeps the bottom padding for the copy row. The
   fade-out then lands inside the stage's lower third, so the art is strongest
   behind the head and the ticket and has dissolved to plain ground well before
   the three explanatory columns begin. */
.fx{ padding-top:0; }
.fx-room{ position:relative; padding-top:clamp(60px,9vh,130px); }

/* THE BEZELS THIN OUT OVER ART (user: "need to be more see through to get the
   glass effect"). Both shells wear one recipe, and it was tuned for a FLAT
   ground, where the note further up this file applies — a translucent white
   over near-white is indistinguishable from an opaque one, so the alpha had to
   stay high to read as a band at all. With a canvas behind them that constraint
   inverts: the art is what proves the glass, so the fill steps back and the
   blur does the work. Scoped to #app's OWN data-bg — the flat-ground values are
   still correct when that section has no canvas, even if the desk has one. */
#app[data-bg] .iph,
#app[data-bg] .lb-frame{
  background:linear-gradient(158deg, oklch(0.42 0.018 268/.34), oklch(0.32 0.016 272/.20));
  -webkit-backdrop-filter:blur(30px) saturate(1.7); backdrop-filter:blur(30px) saturate(1.7); }
body:not(.theme-dark) #app[data-bg] .iph,
body:not(.theme-dark) #app[data-bg] .lb-frame{
  background:linear-gradient(158deg, oklch(0.94 0.008 255/.46), oklch(0.90 0.010 258/.30)); }

/* THE DESK FIRMS UP, THE PRODUCT DOES NOT — and the asymmetry is the whole
   lesson of the reference: there, the sky sits behind an OPAQUE screenshot and
   the glass is only the chrome around it. #app already works that way (.lb-screen
   is an opaque panel, so only the window's frosted band meets the art). #desk is
   the opposite: .dk-card and the trade widget are translucent panels carrying
   label/value TEXT, and over foliage "buying power 0% · 0×" stopped being
   readable. So the panels that hold text wear the firm chip glass while a
   backdrop is on, and the art keeps its full strength everywhere else. */
#desk[data-bg] .dk-card.gl,
#desk[data-bg] .glass-widget .tw{ background:var(--glass-chip-firm); }
/* the 2px brand hairlines between the panes are the first thing a busy canvas
   eats — they get their own contrast back rather than a heavier veil */
#desk[data-bg] .dk-link .rule{ box-shadow:0 0 0 1px oklch(0 0 0/.14); }
#desk[data-bg] .dk-link .pip{ box-shadow:0 0 0 1.5px oklch(0 0 0/.16); }
body:not(.theme-dark) #desk[data-bg] .dk-link .rule{ box-shadow:0 0 0 1px oklch(1 0 0/.5); }
body:not(.theme-dark) #desk[data-bg] .dk-link .pip{ box-shadow:0 0 0 1.5px oklch(1 0 0/.55); }

/* the backdrop pill stacks above the theme pill, same corner, same voice.
   The swatches wear 96px thumbnails, NOT the full canvases: pointing them at
   the real JPEGs downloaded 2.4MB to paint four 16px circles, on every load,
   with no backdrop even chosen. */
/* the wrapper is only an anchor — it carries no chrome of its own, so the
   collapsed chip is the whole footprint until you go near it */
.ct-bg-wrap{ position:fixed; z-index:85;
  bottom:calc(clamp(14px,2vw,22px) + env(safe-area-inset-bottom,0px) + 46px); }
.ct-bg-wrap.ct-right{ right:calc(clamp(16px,2.4vw,26px) + env(safe-area-inset-right,0px)); }
/* the chip: one 38px pill matching a ctog's outer size, wearing the canvas
   that is currently on (or ∅ when none is) */
/* GONE AT REST (user: "hide this completely and i can just hover or click around
   there to bring it back"). Even a dimmed chip was one more thing in the corner,
   so the picker shows nothing until a pointer comes near — the ::before below is
   an invisible hit area larger than the chip, so "near" is generous rather than
   pixel-hunting. Hovering a pseudo-element counts as hovering its parent, which
   is what makes this work without disturbing the layout. */
.ct-bg-wrap::before{ content:""; position:absolute; inset:-22px -16px -12px -22px; }
/* 40, matching a .ctog island's outer height — at 38 it curved 1px tighter than
   its neighbours, which is the same size-not-radius mismatch as the worktree pill */
.ct-bg-dot{ display:grid; place-items:center; width:40px; height:40px; padding:0;
  border-radius:999px; cursor:pointer; border:1px solid var(--lx-line);
  background:var(--glass-chip);
  -webkit-backdrop-filter:var(--blur-chip); backdrop-filter:var(--blur-chip);
  box-shadow:var(--chip-shadow), var(--chip-in);
  opacity:0; transition:opacity .2s var(--enter), transform .15s var(--enter); }
.ct-bg-wrap:hover .ct-bg-dot, .ct-bg-wrap:focus-within .ct-bg-dot,
.ct-bg-wrap.open .ct-bg-dot{ opacity:1; }
/* keyboard users need it back on focus, which :focus-within covers, but the
   button must still be reachable while invisible — so no display:none here */
.ct-bg-dot:active{ transform:scale(.94); }
.ct-bg-dot .sw{ width:18px; height:18px; border-radius:50%;
  box-shadow:inset 0 0 0 1px oklch(0 0 0/.1); background-size:cover; background-position:center; }
/* only one of the two shows: the swatch when a canvas is on, ∅ when not */
.ct-bg-dot .ti{ font-size:15px; line-height:1; color:var(--lx-mut); }
.ct-bg-wrap:not([data-has]) .ct-bg-dot .sw{ display:none; }
.ct-bg-wrap[data-has] .ct-bg-dot .ti{ display:none; }

/* The row keeps the .ctog class so its buttons and swatches inherit the pill's
   own recipe, which means it also inherits .ctog's fixed positioning — hence
   two-class selectors here, so this wins over the base rule regardless of where
   either sits in the file. */
.ct-bg-wrap .ct-bg-row{ position:absolute; right:0; bottom:calc(100% + 8px);
  opacity:0; transform:translateY(6px) scale(.97); transform-origin:100% 100%;
  pointer-events:none; visibility:hidden;
  transition:opacity .2s var(--enter), transform .24s var(--enter),
    visibility 0s linear .24s; }
.ct-bg-wrap:hover .ct-bg-row, .ct-bg-wrap:focus-within .ct-bg-row,
.ct-bg-wrap.open .ct-bg-row{
  opacity:1; transform:none; pointer-events:auto; visibility:visible;
  transition-delay:0s; }
@media (prefers-reduced-motion:reduce){
  .ct-bg-wrap .ct-bg-row{ transition:opacity .15s linear, visibility 0s; transform:none; } }
/* the target's name, in the eyebrow voice, divided from the swatches by the
   same hairline the rest of the page uses */
.ct-for{ font-family:var(--font-num); font-size:9px; font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--lx-mut); white-space:nowrap;
  padding:0 9px 0 7px; margin-right:1px; border-right:1px solid var(--lx-line);
  align-self:stretch; display:flex; align-items:center; }
/* it changes as you scroll between the two rooms — a cross-fade, so the pill
   never flickers mid-scroll */
.ct-for{ transition:opacity .18s var(--enter); }
.ct-for.swap{ opacity:0; }
.ctog .sw.art{ background-size:cover; background-position:center; }
#ctBg [data-bg="beach"] .sw{ background-image:url("../../marks/backdrops/beach-sw.jpg"); }
#ctBg [data-bg="lake"] .sw{  background-image:url("../../marks/backdrops/lake-sw.jpg"); }
#ctBg [data-bg="solar"] .sw{ background-image:url("../../marks/backdrops/solar-sw.jpg"); }
#ctBg [data-bg="falls"] .sw{ background-image:url("../../marks/backdrops/falls-sw.jpg"); }
#ctBg [data-bg="palms"] .sw{ background-image:url("../../marks/backdrops/palms-sw.jpg"); }
/* the swatch previews what you will actually get, so it follows the ramp too */
body[data-ramp="tide"] #ctBg [data-bg="beach"] .sw{ background-image:url("../../marks/backdrops/beach-tide-sw.jpg"); }
body[data-ramp="tide"] #ctBg [data-bg="solar"] .sw{ background-image:url("../../marks/backdrops/solar-tide-sw.jpg"); }
/* touch: clear the taller theme pill and give the chip a 44px target. It stays
   HIDDEN here too (user: "we don't need to see the background picker, it can come
   into existence if i click somewhere around there") — landing.js opens it from a
   tap anywhere in the wrap's invisible zone, so touch does not need it visible to
   be reachable. */
@media (pointer:coarse){
  .ct-bg-wrap{ bottom:calc(14px + env(safe-area-inset-bottom,0px) + 54px); }
  .ct-bg-dot{ width:44px; height:44px; }
}

/* ---- THE FURNITURE GETS OUT OF THE WAY ------------------------------------
   All the corner selectors leave while you are reading and come back when you
   scroll up (user). landing.js sets body.furn-away on downward scroll and after
   a pause, and clears it on upward scroll or near the top.
   They slide out along their OWN edge rather than just fading, so the motion
   reads as "put away" rather than "broken". Pointer events go with them, or an
   invisible pill would still be swallowing taps. */
.ctog, .ct-bg-wrap, #xws{
  transition:opacity .28s var(--enter), transform .34s var(--enter); }
body.furn-away .ctog.ct-left{ transform:translateX(calc(-100% - 18px)); }
body.furn-away .ctog.ct-right, body.furn-away .ct-bg-wrap{ transform:translateX(calc(100% + 18px)); }
body.furn-away #xws{ transform:translateX(calc(-100% - 18px)); }
body.furn-away .ctog, body.furn-away .ct-bg-wrap, body.furn-away #xws{
  opacity:0; pointer-events:none; }
/* the backdrop row must not be caught mid-open when the set leaves */
body.furn-away .ct-bg-wrap .ct-bg-row{ opacity:0; visibility:hidden; }
@media (prefers-reduced-motion:reduce){
  .ctog, .ct-bg-wrap, #xws{ transition:opacity .2s linear; }
  body.furn-away .ctog, body.furn-away .ct-bg-wrap, body.furn-away #xws{ transform:none; }
}
