/* ============================================================================
   habeat — THE JOURNEY (v2)
   A pinned scrollytelling page. One painterly orb is the protagonist: born as
   the app's onboarding pearl (with the painted h as its face), woken by a hold,
   then it sheds the pearl shell and travels the page as the original glowing
   paint-nebula — crowning each section's painted illustration and fanning into
   three orbs over the three prices. Content fades over scroll; the orb glows
   BEHIND it. Reduced-motion / no-JS fall back to a plain stacked page.

   Layers in the stage:  bg-layer (z1)  <  orb thread (z5)  <  content (z8)
   ============================================================================ */

* { box-sizing: border-box; }
body {
  margin: 0; background: #08080A; color: #F4F2EE; font-family: var(--font-rounded);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; overflow-x: hidden;
}
a { color: inherit; }

/* ─────────────── nav ─────────────── */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: 20px clamp(20px, 4vw, 46px);
  transition: opacity .7s ease, transform .7s var(--motion-glide);
}
/* The nav is NOT gated on the hold. A first-time visitor has to be able to see
   whose site this is — and reach pricing/support — before performing a ritual.
   Only the section links are held back until the journey has begun. */
.nav-links a[data-go] { opacity: 0; pointer-events: none; transition: opacity .7s ease, color .3s ease; }
html.begun .nav-links a[data-go] { opacity: 1; pointer-events: auto; }
.wordmark { display: inline-flex; align-items: center; gap: 10px; font-weight: 800; font-size: 21px; letter-spacing: -.02em; text-decoration: none; color: #F4F2EE; }
.wordmark img { width: 26px; height: 26px; object-fit: contain; }
.nav-links { display: flex; align-items: center; gap: clamp(14px, 2.4vw, 30px); position: relative; }
.nav-glow {
  position: absolute; top: 50%; left: 0; width: 96px; height: 38px; border-radius: 999px;
  transform: translate(calc(var(--ng-x, 0px) - 50%), -50%); filter: blur(17px); opacity: var(--ng-o, 0);
  pointer-events: none; z-index: -1; background: radial-gradient(closest-side, var(--ng-c, #6FC4BB), transparent 74%);
  transition: transform .55s var(--motion-glide), opacity .45s ease, background .5s ease;
}
.nav-links a { text-decoration: none; font-size: 15px; font-weight: 600; color: rgba(244,242,238,.62); transition: color .3s ease; }
.nav-links a:hover, .nav-links a.is-active { color: #F4F2EE; }
.nav-cta { padding: 9px 18px; border-radius: 999px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14); color: #F4F2EE !important; }
@media (max-width: 640px) { .nav-links a:not(.nav-cta) { display: none; } }

/* ─────────────── journey scaffold ─────────────── */
.journey { position: relative; }
/* Free, continuous scroll — no scroll-snap. The sticky stage holds the scene in
   place while the orb + crossfade advance gradually with the scroll, so a small
   scroll moves a small amount (never flips a whole page). */
.snaps { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.snaps i { display: block; height: 100vh; }   /* one viewport per scene (JS sets the real height) */
/* one frame where scenes sit at rest + visible-to-layout, so the orb targets can
   be measured against their real positions, then restored. */
html.measuring .scene { opacity: 1 !important; transform: none !important; visibility: hidden; }
.stage {
  position: sticky; top: 0; height: 100vh; height: 100svh; overflow: hidden; background: #08080A;
  --card-w: min(560px, 90vw); --card-h: clamp(168px, 22vh, 200px); --card-shift: 5vh;
}

/* the hero nebula — a full-bleed WebGL canvas behind every scene background.
   JS fades it in only for the hero (opacity ← heroVis); it stretches the
   (down-sampled, capped) drawing buffer to fill, which is invisible on a soft
   volumetric field and keeps the GPU cost bounded on hi-dpi displays. */
.bg-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0;
  display: block; pointer-events: none; opacity: 0;
}

.bg-layer, .content-layer { position: absolute; inset: 0; }
.bg-layer { z-index: 1; }
.content-layer { z-index: 8; }

/* scene backgrounds (behind the orb) */
.bg { position: absolute; inset: 0; opacity: 0; will-change: opacity; }
.bg.gallery {
  background:
    radial-gradient(58% 58% at var(--wash-x, 50%) var(--wash-y, 50%), var(--wash, rgba(59,139,133,.34)), transparent 64%),
    radial-gradient(120% 100% at 50% 120%, #121013, transparent 60%), #08080A;
}
/* the ten-themes ground — a quiet spectrum of all the brand hues at once */
.bg.bg-spectrum {
  background:
    radial-gradient(38% 48% at 16% 28%, rgba(255,158,99,.16), transparent 60%),
    radial-gradient(40% 50% at 84% 30%, rgba(132,56,199,.20), transparent 60%),
    radial-gradient(46% 52% at 50% 92%, rgba(26,148,144,.16), transparent 62%),
    radial-gradient(40% 46% at 78% 84%, rgba(242,56,141,.13), transparent 60%),
    radial-gradient(120% 100% at 50% 120%, #121013, transparent 60%), #08080A;
}

/* hero ground — the live nebula canvas (LAYER 0) paints the room. This sits
   over it as a framing vignette only (transparent centre lets the shader read
   through), plus a faint teal centre glow that doubles as the no-WebGL fallback
   so the hero is never a flat black void. The orb is the light; the canvas is
   its air. */
.bg.bg-hero {
  background:
    radial-gradient(60% 50% at 50% 46%, rgba(26,148,144,.05), transparent 70%),
    radial-gradient(120% 120% at 50% 46%, transparent 46%, rgba(0,0,0,.5) 100%);
}

/* the paywall re-light — a soft wash that takes the selected plan's hue. The
   registered --pw lets the colour crossfade (1.2s) when you switch plans; the
   element only shows on the pricing scene (opacity ← split, set in JS). */
@property --pw { syntax: '<color>'; inherits: false; initial-value: transparent; }
.pricing-wash {
  position: absolute; inset: 0; z-index: 1; pointer-events: none; opacity: 0;
  mix-blend-mode: screen; will-change: opacity;
  background: radial-gradient(72% 60% at 50% 42%, var(--pw, transparent), transparent 70%);
  transition: --pw 1.2s ease;
}

/* hero ground = the app's painterly "held" card, nearly full-bleed */
.herocard {
  position: absolute; inset: clamp(14px, 2.4vw, 34px); border-radius: clamp(22px, 2.4vw, 34px);
  overflow: hidden; border: 1px solid rgba(255,255,255,.08); box-shadow: 0 30px 90px rgba(0,0,0,.55);
  background: linear-gradient(135deg, var(--card-a, #3B8B85), var(--card-b, #266D65));
}
.herocard .swirl {
  position: absolute; inset: 0; opacity: .3; mix-blend-mode: plus-lighter;
  background:
    radial-gradient(60% 70% at 16% 28%, var(--accent-teal), transparent 70%),
    radial-gradient(55% 60% at 86% 74%, var(--accent-violet), transparent 70%),
    radial-gradient(50% 55% at 72% 16%, var(--accent-pink), transparent 70%);
  animation: paintDrift 16s ease-in-out infinite;
}
.herocard .bloom {
  position: absolute; inset: 0; pointer-events: none; background:
    radial-gradient(120% 90% at 50% 124%, var(--card-bloom, #134D48), transparent 60%),
    linear-gradient(180deg, rgba(255,255,255,.06), transparent 30%),
    radial-gradient(135% 130% at 50% 44%, transparent 46%, rgba(8,12,12,.5) 100%);
}
@keyframes paintDrift { 0%,100% { transform: translate(0,0) scale(1); } 50% { transform: translate(2%,-1.4%) scale(1.05); } }

/* scenes (content, in front of the orb) */
.scene { position: absolute; inset: 0; display: grid; place-items: center; padding: clamp(84px, 13vh, 150px) clamp(22px, 6vw, 84px); opacity: 0; will-change: opacity; }
.scene > * { width: min(1120px, 100%); }

/* ─────────────── typography ─────────────── */
.eyebrow { margin: 0 0 18px; font-size: clamp(12px, 1vw, 15px); font-weight: 700; letter-spacing: .26em; text-transform: uppercase; color: rgba(244,242,238,.58); }
.display { margin: 0; font-weight: 800; letter-spacing: -.034em; line-height: 1.0; font-size: clamp(40px, 6.4vw, 86px); text-wrap: balance; text-shadow: 0 4px 40px rgba(0,0,0,.5); }
.title { margin: 0; font-weight: 800; letter-spacing: -.03em; line-height: 1.04; font-size: clamp(30px, 3.8vw, 50px); text-wrap: balance; text-shadow: 0 2px 28px rgba(0,0,0,.5); }
.lead { margin: 20px 0 0; font-size: clamp(16.5px, 1.7vw, 20px); line-height: 1.55; color: rgba(244,242,238,.84); max-width: 42ch; text-wrap: pretty; text-shadow: 0 1px 14px rgba(0,0,0,.4); }
.lead em { font-style: italic; color: #FAF8F5; }
.paint-word { background: linear-gradient(92deg, #6FC4BB, #8538C7 46%, #F23890 80%); -webkit-background-clip: text; background-clip: text; color: transparent; }

.cta-row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-top: 32px; }
.btn { display: inline-flex; align-items: center; gap: 10px; height: 56px; padding: 0 28px; border-radius: 999px; font-size: 17px; font-weight: 700; text-decoration: none; background: #FAF8F5; color: #15241B; border: 1px solid transparent; transition: transform .2s var(--motion-tap), background .2s ease; }
.btn:hover { transform: translateY(-1px); background: #fff; }
.btn-quiet { font-size: 17px; font-weight: 700; text-decoration: none; color: rgba(244,242,238,.84); transition: color .2s ease; }
.btn-quiet:hover { color: #FAF8F5; }

/* hero — dormant: just the (greyed) h + the orb, centred. On hold the orb
   slides to the LEFT and the app's main card blooms in with its text, while
   the headline rises above and the CTA settles below. */
.scene--hero { overflow: visible; }
.scene--hero .display { font-size: clamp(30px, 4.4vw, 56px); }
.hero-head {
  position: absolute; left: 50%; top: clamp(11%, 15vh, 19%);
  transform: translateX(-50%) translateY(-12px);
  width: min(660px, 92vw); text-align: center;
  opacity: var(--hero-show, 0); transition: opacity .7s ease, transform .9s var(--motion-glide);
}
html.begun .hero-head { transform: translateX(-50%) translateY(0); }
.hero-head .eyebrow { margin-bottom: 14px; }
/* the app card's text — sits over the card's right, to the right of the orb */
.hero-card-copy {
  position: absolute; left: 50%; top: 50%; width: var(--card-w);
  height: var(--card-h);
  transform: translate(-50%, -50%) translateY(var(--card-shift, 0));
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding-left: calc(var(--card-w) * 0.44); padding-right: clamp(16px, 3vw, 30px);
  text-align: left; pointer-events: none;
  opacity: var(--card-show, 0); transition: opacity .5s ease;
}
/* the card's text rows stagger in after the card blooms (0.1s apart) */
.hero-card-copy > * { transition: opacity .5s ease, transform .6s var(--motion-glide); }
html:not(.begun) .hero-card-copy > * { opacity: 0; transform: translateY(8px); }
html.begun .hc-name { transition-delay: .18s; }
html.begun .hc-identity { transition-delay: .28s; }
html.begun .hc-state { transition-delay: .38s; }
html.begun .hc-hint { transition-delay: .48s; }
.hc-name { margin: 0; font-size: clamp(19px, 2.1vw, 24px); font-weight: 800; letter-spacing: -.01em; color: #FCFAF7; display: flex; align-items: center; gap: .35em; }
.hc-chev { font-weight: 600; opacity: .5; }
.hc-identity { margin: 3px 0 0; font-size: clamp(13.5px, 1.5vw, 16px); font-style: italic; line-height: 1.32; color: rgba(255,255,255,.88); }
.hc-state { margin: 9px 0 0; font-size: 13px; font-weight: 700; color: rgba(255,255,255,.82); }
.hc-hint { margin: 5px 0 0; font-size: 12.5px; font-weight: 600; color: rgba(255,255,255,.55); }
.hero-foot {
  position: absolute; left: 50%; bottom: clamp(9%, 12vh, 15%);
  transform: translateX(-50%) translateY(12px);
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap; justify-content: center;
  opacity: var(--hero-show, 0); transition: opacity .7s ease .1s, transform .9s var(--motion-glide) .1s;
}
html.begun .hero-foot { transform: translateX(-50%) translateY(0); }

/* feature scenes: copy one side, the painted illustration (orb behind) the other */
.feature { display: grid; grid-template-columns: 1.05fr .95fr; align-items: center; gap: clamp(32px, 5vw, 76px); }
.feature.flip .copy { order: 2; }
.feature.flip .art { order: 1; }
.feature .copy { max-width: 46ch; }
.feature .art { display: grid; place-items: center; }
/* the orb is the only subject here — this cell is the room it lands in, so it
   has to hold its own height for computeTargets() to measure a stable centre. */
.art-orbspace { min-height: clamp(160px, 16vw, 240px); }
.feature .art-heatmap { width: 100%; position: relative; }
/* the orb floats small ABOVE the grid (clear of the card, so it never hides
   behind the tiles) and casts its light down onto them — the measured target the
   orb lands on for this scene. */
/* the orb presides ABOVE the heatmap, centred over it with a clear gap — the
   light the grid earns its colour from, its halo washing warm down onto the top
   tiles. (Centred over the card, never in the dead gutter and never over the
   tiles — a deliberate sun over the field.) */
.grid-orb-anchor { position: absolute; left: 50%; top: -38%; width: 1px; height: 1px; pointer-events: none; }
@media (max-width: 820px) {
  .feature { grid-template-columns: 1fr; gap: 28px; text-align: center; }
  .feature.flip .copy { order: 1; } .feature.flip .art { order: 2; }
  .feature .copy { max-width: 100%; margin: 0 auto; }
  .art-orbspace { min-height: clamp(150px, 40vw, 240px); }
  .lead { margin-left: auto; margin-right: auto; }
  .vibes { justify-content: center; }
}

/* vibe deepening row — one theme ripening early → bloom (reads the .vibe-* class) */
.vibes { display: flex; gap: clamp(16px, 2.6vw, 32px); margin-top: 34px; flex-wrap: wrap; }
.vibe { display: grid; gap: 7px; justify-items: center; }
.vibe .dot { width: clamp(40px, 4vw, 52px); aspect-ratio: 1; border-radius: 50%; box-shadow: inset 0 1px 0 rgba(255,255,255,.22), inset 0 -6px 12px rgba(0,0,0,.35), 0 10px 24px rgba(0,0,0,.4); }
.vibe .v1 { background: var(--vibe-early, linear-gradient(135deg,#A3DDD8,#6FC4BB)); }
.vibe .v2 { background: var(--vibe-steady, linear-gradient(135deg,#3B8B85,#266D65)); }
.vibe .v3 { background: var(--vibe-deep, linear-gradient(135deg,#134D48,#051D1A)); }
.vibe .v4 { background: var(--vibe-deepbloom, linear-gradient(135deg,#0C3835,#031412)); }
.vibe .lbl { font-size: 13px; font-weight: 700; color: rgba(244,242,238,.78); }
.vibe .span { font-size: 11px; font-weight: 600; letter-spacing: .02em; color: rgba(244,242,238,.5); }

.vow { display: inline-block; margin: 18px 0 0; font-size: clamp(17px,1.9vw,22px); line-height: 1.5; color: rgba(244,242,238,.92); max-width: 30ch; }
.vow em { font-style: italic; color: rgba(244,242,238,.6); }

/* ─────────────── the heatmap — the app's calendar grid, exact effect ───────────────
   7 rows × 14 weeks of tiles. Held days carry a breathing gradient that deepens
   left→right (early→bloom of the tide theme); rest days show a centre dot; empty
   days a faint tint. Tiles ignite staggered by column as the scene arrives. */
.heatmap {
  position: relative; z-index: 1;
  display: grid; grid-template-rows: repeat(7, 1fr); grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: clamp(4px, .5vw, 6px);
  width: min(100%, 460px); margin: 0 auto; padding: clamp(14px, 1.6vw, 20px);
  border-radius: 20px;
  /* opaque panel — the orb glows BEHIND it (halo frames the card) without
     bleeding through the grid and muddying the tiles */
  background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.012)), radial-gradient(135% 135% at 50% 0%, #161b22, #0a0d12);
  border: 1px solid rgba(255,255,255,.07);
  box-shadow: 0 24px 60px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.06);
}
.heatmap::before {            /* top shine, like the app's grid card */
  content: ''; position: absolute; inset: 0; border-radius: 20px; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.05), transparent 30%);
}
.tile {
  position: relative; aspect-ratio: 1; border-radius: 4px; background: rgba(255,255,255,.05);
  opacity: var(--idea-show, 1);
  transform: translateY(calc((1 - var(--idea-show, 1)) * 10px)) scale(calc(.72 + .28 * var(--idea-show, 1)));
  /* --hot (0→1) is the TileRipple amplitude, driven per-frame in JS. The app
     lift/pop/brightness all derive from one amp: the tile pops on its own
     `scale` axis, lifts on its own `translate` axis (both compose with the
     entrance `transform`), and brightens — instant, the wave itself is smooth. */
  scale: calc(1 + var(--hot, 0) * 0.2);          /* pop  — 1 + amp·0.20 */
  translate: 0 calc(var(--hot, 0) * -13px);      /* lift — amp·13px up   */
  filter: brightness(calc(1 + var(--hot, 0) * 0.14));   /* brighten — amp·0.14 */
  box-shadow: inset 0 0 calc(var(--hot, 0) * 11px) rgba(255,255,255, calc(var(--hot, 0) * .2));
  transition:
    opacity .45s ease calc(var(--td, 0) * 1ms),
    transform .55s var(--motion-glide) calc(var(--td, 0) * 1ms);
}
.tile.empty { background: rgba(255,255,255,.05); box-shadow: inset 0 0 0 1px rgba(255,255,255,.015); }
.tile.future { background: rgba(255,255,255,.03); }
.tile.rest { background: rgba(255,255,255,.045); display: grid; place-items: center; }
.tile.rest::after { content: ''; width: 32%; aspect-ratio: 1; border-radius: 50%; background: rgba(111,196,187,.7); }
.tile.filled {
  background: linear-gradient(135deg, var(--c1, #6FC4BB), var(--c2, #3B8B85));
  background-size: 180% 180%;
  box-shadow:
    0 3px 8px rgba(5,29,26,.4),
    inset 0 1px 0 rgba(255,255,255,.14),
    inset 0 0 calc(var(--hot, 0) * 15px) rgba(255,255,255, calc(var(--hot, 0) * .38)),
    0 0 calc(var(--hot, 0) * 20px) rgba(126,210,201, calc(var(--hot, 0) * .6));
}
@media (prefers-reduced-motion: no-preference) {
  .tile.filled { animation: tileBreathe var(--bp, 5.2s) ease-in-out infinite; animation-delay: calc(var(--td, 0) * -1ms); }
}
/* the app's BreathingGradient — the held-tile gradient endpoints orbit slowly
   (5.2s steady). We approximate the orbit by drifting background-position; the
   brightness is left to the ripple (`--hot`) so the two never fight. */
@keyframes tileBreathe { 0%,100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
/* tide theme, deepening early → bloom (the colour you earn over a streak) */
.tile.lvl1 { --c1: #A3DDD8; --c2: #6FC4BB; }
.tile.lvl2 { --c1: #6FC4BB; --c2: #3B8B85; }
.tile.lvl3 { --c1: #3B8B85; --c2: #1E605A; }
.tile.lvl4 { --c1: #2C7A72; --c2: #134D48; }
@media (max-width: 820px) { .heatmap { width: min(100%, 360px); } }

/* pricing — the app's paywall. Three living plan-orbs you choose between; the
   chosen orb (a real WebGL sphere, positioned by JS over the .plan-orb slot)
   grows and the room re-lights to its hue. Selection is the reward — no badge. */
.scene--pricing .pricing-inner { text-align: center; }
.scene--pricing .title { margin-inline: auto; max-width: 18ch; }

/* perks — rise in, staggered, as the scene arrives (--perks-show ← split) */
.perks {
  list-style: none; margin: clamp(20px,3.2vh,38px) auto 0; padding: 0; max-width: 760px;
  display: flex; gap: clamp(16px,3vw,46px); justify-content: center; flex-wrap: wrap;
}
.perks li {
  --pk: clamp(0, calc((var(--perks-show, 0) - var(--pi, 0) * 0.14) * 2.4), 1);
  display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto;
  column-gap: 11px; align-items: center; text-align: left;
  opacity: var(--pk); transform: translateY(calc((1 - var(--pk)) * 9px));
}
.pk-i { grid-column: 1; grid-row: 1 / 3; align-self: center; font-size: 20px; color: #6FC4BB; }
.pk-t { grid-column: 2; grid-row: 1; font-size: 15px; font-weight: 800; color: #F4F2EE; }
.pk-s { grid-column: 2; grid-row: 2; font-size: 12.5px; color: rgba(244,242,238,.6); }

/* plans — each a button; the WebGL orb floats above the price over .plan-orb */
.plans {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(14px,4vw,56px);
  max-width: 860px; margin: clamp(150px,21vh,236px) auto 0;   /* headroom for the bigger orbs */
}
/* no card chrome — like the app, the orb's size + the price's weight ARE the
   selection. No rectangle highlight behind the orb or price on hover/select. */
.plan {
  position: relative; display: grid; justify-items: center; gap: clamp(20px,3vh,34px);
  padding: 0; border: 0; background: none; cursor: pointer;
  font: inherit; color: inherit; -webkit-tap-highlight-color: transparent;
}
.plan:focus-visible { outline: 2px solid rgba(255,255,255,.5); outline-offset: 6px; border-radius: 18px; }
.plan-orb { width: clamp(120px, 14vw, 188px); aspect-ratio: 1; display: block; }   /* footprint the orb floats over */
.plan-price { font-size: clamp(26px,3vw,40px); font-weight: 800; letter-spacing: -.02em; color: rgba(244,242,238,.5); transition: color .45s ease, transform .45s var(--motion-glide); }
.plan-price .per { font-size: .5em; font-weight: 600; color: rgba(244,242,238,.5); }
.plan-cad { font-size: 12.5px; font-weight: 700; letter-spacing: .03em; text-transform: lowercase; color: rgba(244,242,238,.5); transition: color .45s ease; }
.plan.is-selected .plan-price { color: #FAF8F5; transform: scale(1.05); }
.plan.is-selected .plan-cad { color: rgba(244,242,238,.82); }
.fine { margin-top: clamp(26px,4vh,40px); font-size: 13px; color: rgba(244,242,238,.56); }
@media (max-width: 720px) {
  .plans { grid-template-columns: 1fr; gap: 20px; margin-top: 60px; }
  .perks { gap: 16px 22px; }
}

/* close */
.scene--close .close-inner { text-align: center; }
.scene--close .art-center { min-height: clamp(110px, 12vw, 160px); }
.scene--close .display { margin-top: 26px; }
.scene--close .close-sub { margin: 18px auto 0; max-width: 42ch; font-size: clamp(15px, 1.5vw, 18px); line-height: 1.55; color: rgba(244,242,238,.72); text-wrap: pretty; }
.scene--close .close-sub em { font-style: italic; color: #FAF8F5; }
.scene--close .cta-row { justify-content: center; margin-top: 30px; }

/* ─────────────── the ten themes — the chromatic identity, in full ─────────────── */
.scene--themes .preside { position: absolute; left: 50%; top: 14%; width: 1px; height: 1px; transform: translateX(-50%); pointer-events: none; }
.scene--themes .themes-inner { text-align: center; }
.scene--themes .title { margin-inline: auto; max-width: 20ch; }
.scene--themes .lead { margin-left: auto; margin-right: auto; max-width: 52ch; }
.theme-grid {
  position: relative;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(18px, 2.6vh, 34px) clamp(10px, 1.6vw, 26px);
  margin: clamp(40px, 6vh, 70px) auto 0; max-width: 880px;
}
/* the ten real WebGL orbs render into this one canvas, overlaying the grid and
   positioned per-cell by JS; the labels sit above it. Fades with the scene. */
.theme-orbs { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; opacity: 0; }
/* when the canvas is live, drop the CSS fallback jewels (keep the .torb box for
   measurement) — the labels rise above the canvas. */
.theme-grid.gl .torb { background: none; box-shadow: none; animation: none; }
.theme-grid.gl .torb::before, .theme-grid.gl .torb::after { content: none; }
.theme-grid.gl .tlabel { position: relative; z-index: 2; }
/* under GL the cell must not translate (the orb canvas is measured at the torb's
   resting rect; the scene fade is carried by the canvas opacity + the label). */
.theme-grid.gl .theme { transform: none; }
.theme {
  display: grid; justify-items: center; gap: 11px;
  opacity: var(--themes-show, 1);
  transform: translateY(calc((1 - var(--themes-show, 1)) * 20px));
  transition: opacity .5s ease, transform .6s var(--motion-glide);
  transition-delay: calc(var(--ti, 0) * 45ms);
}
/* each theme-orb = a jewel sphere in its own palette (mirrors the app orb) */
.torb {
  position: relative; z-index: 1; width: clamp(48px, 6.4vw, 90px); aspect-ratio: 1; border-radius: 50%;
  background:
    radial-gradient(56% 56% at 36% 30%, rgba(255,255,255,.5), transparent 44%),
    radial-gradient(closest-side at 44% 38%, var(--vibe-steady-start, #3B8B85) 0%, var(--vibe-deep-start, #134D48) 60%, #05070c 100%);
  box-shadow:
    inset 0 -8px 16px rgba(0,0,0,.55), inset 0 4px 10px rgba(255,255,255,.14),
    0 14px 30px rgba(0,0,0,.45);
}
.torb::before {                                  /* outer halo — the orb's light in the night */
  content: ''; position: absolute; inset: -34%; border-radius: 50%; z-index: -1;
  background: radial-gradient(closest-side, var(--vibe-early-start, #A3DDD8), transparent 70%);
  opacity: .42; filter: blur(13px);
}
.torb::after {                                   /* fresnel rim — seats the volume */
  content: ''; position: absolute; inset: 0; border-radius: 50%; mix-blend-mode: screen; opacity: .55;
  background: radial-gradient(closest-side, transparent 62%, var(--vibe-early-start, #A3DDD8) 94%, transparent 100%);
}
@media (prefers-reduced-motion: no-preference) {
  .torb { animation: orbBreathe 4.8s ease-in-out infinite; animation-delay: calc(var(--ti, 0) * -0.42s); }
}
.tlabel { font-size: clamp(11px, 1.05vw, 13px); font-weight: 700; letter-spacing: .02em; color: rgba(244,242,238,.74); }
.themes-fine { margin-top: clamp(24px, 4vh, 40px); font-size: 13px; letter-spacing: .04em; color: rgba(244,242,238,.5); }
@media (max-width: 760px) {
  .theme-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 16px 8px; max-width: 460px; }
  .torb { width: clamp(42px, 13vw, 62px); }
  .tlabel { font-size: 10.5px; }
}

/* ─────────────── the app card (hero) — PaintedSurface(.study) ─────────────── */
/* the card BLOOMS in once the habit is held (html.begun) — rises + scales up
   from 0.94 with a soft over-settle (the app's .bloom spring, response 1.5). */
/* the colour the glass refracts — a soft brand bloom behind the pane so the
   frosted glass always has vivid light to read through (backdrop-filter only
   samples what's BEHIND the element, and the hero nebula alone is too dim on the
   card's wings). Fades and clears with the card, so it never ghosts forward. */
.app-card-glow {
  position: absolute; left: 50%; top: 50%; z-index: 3;
  width: calc(var(--card-w) + 150px); height: calc(var(--card-h) + 150px);
  transform: translate(-50%, -50%) translateY(var(--card-shift, 0));
  border-radius: 48% / 56%;
  opacity: var(--card-show, 0); transition: opacity .28s ease;
  background:
    radial-gradient(46% 60% at 20% 36%, rgba(26,148,144,.62), transparent 70%),
    radial-gradient(48% 62% at 82% 32%, rgba(242,56,144,.50), transparent 72%),
    radial-gradient(60% 72% at 58% 96%, rgba(133,56,199,.60), transparent 72%),
    radial-gradient(40% 50% at 50% 50%, rgba(122,236,226,.30), transparent 74%);
  filter: blur(40px); pointer-events: none; mix-blend-mode: screen;
}
@media (prefers-reduced-motion: no-preference) { .app-card-glow { animation: glowDrift 13s ease-in-out infinite; } }
@keyframes glowDrift {
  0%,100% { transform: translate(-50%, -50%) translateY(var(--card-shift, 0)) scale(1); filter: blur(40px) saturate(1); }
  50%     { transform: translate(-50%, -50%) translateY(var(--card-shift, 0)) scale(1.06) translateX(2%); filter: blur(44px) saturate(1.15); }
}

/* liquid glass — a frosted pane the orb's air shows through, tinted by faint
   colour pockets and edged with a bright spec rim + a diagonal sheen. Light
   mass, so it can never "burn in" as a dark ghost when it clears into the next
   scene. Opacity is driven per-frame by --card-show; the short transition only
   softens the bloom on the earning tap and never lags behind the scroll. */
.app-card {
  position: absolute; left: 50%; top: 50%; z-index: 4;
  width: var(--card-w); height: var(--card-h);
  transform: translate(-50%, -50%) translateY(var(--card-shift, 0)) translateY(18px) scale(.94);
  border-radius: 26px; overflow: hidden;
  opacity: var(--card-show, 0); transition: opacity .28s ease, transform 1.3s cubic-bezier(.2,.85,.25,1.03);
  background: linear-gradient(135deg, rgba(255,255,255,.10), rgba(255,255,255,.025));
  -webkit-backdrop-filter: blur(18px) saturate(1.8) brightness(1.08);
  backdrop-filter: blur(18px) saturate(1.8) brightness(1.08);
  box-shadow: 0 24px 64px rgba(0,0,0,.30),
              inset 0 1.5px 1px rgba(255,255,255,.55),
              inset 0 0 0 1px rgba(255,255,255,.05),
              inset 0 -18px 44px rgba(255,255,255,.05);
  border: 1px solid rgba(255,255,255,.22);
}
html.begun .app-card { transform: translate(-50%, -50%) translateY(var(--card-shift, 0)) scale(1); }
/* the colour the glass holds — light translucent pools that let the blurred
   bloom behind read through clearly (the see-through is the point) */
.ac-surface {
  position: absolute; inset: 0;
  background:
    radial-gradient(58% 88% at 14% 18%, rgba(122,236,226,.16), transparent 64%),
    radial-gradient(56% 82% at 94% 16%, rgba(242,56,144,.14), transparent 66%),
    radial-gradient(72% 98% at 80% 104%, rgba(133,56,199,.18), transparent 68%);
}
@media (prefers-reduced-motion: no-preference) { .ac-surface { animation: cardBreathe 7.2s ease-in-out infinite; } }
@keyframes cardBreathe { 0%,100% { transform: scale(1); filter: saturate(1); } 50% { transform: scale(1.03); filter: saturate(1.12); } }
/* glass optics — a bright sweep across the top edge, a faint lower lift, and a
   crisp diagonal specular streak that gives the pane its liquid sheen */
.ac-sheen {
  position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(255,255,255,.34), transparent 20%),
    linear-gradient(0deg, rgba(255,255,255,.07), transparent 26%),
    linear-gradient(118deg, transparent 36%, rgba(255,255,255,.16) 49%, transparent 60%);
}

/* the orb's ambient light spilling into the room — large, soft, follows the orb
   and takes its warm hue, so the whole scene reads as lit BY the orb. */
.orb-aura {
  position: absolute; left: 50%; top: 50%; z-index: 2; width: 860px; height: 860px; border-radius: 50%;
  transform: translate(-50%, -50%) translate(var(--orb-x, 0px), var(--orb-y, 0px)) scale(var(--orb-scale, 1));
  background: radial-gradient(closest-side, var(--o-warm, #A3DDD8), transparent 70%);
  filter: blur(64px); opacity: calc(.08 + .24 * var(--orb-wake, 1));
  mix-blend-mode: screen; pointer-events: none; will-change: transform, opacity;
}

/* ─────────────── the orb thread — the app's living plasma sphere ─────────────── */
.orb {
  position: absolute; left: 50%; top: 50%; z-index: 5; width: 360px; height: 360px; pointer-events: none;
  transform: translate(-50%, -50%) translate(var(--orb-x, 0px), var(--orb-y, 0px)) scale(var(--orb-scale, 1));
  will-change: transform;
}
.orb-mini { width: 300px; height: 300px; opacity: 0; }
/* The living orb is rendered by a WebGL fragment shader — a faithful port of the
   app's PowerOrb.metal (geometry-lit sphere · aurora curtains · subsurface ember ·
   fresnel atmosphere · thin-film iridescence · grain). The canvas fades in from a
   dark pearl base as you hold (ignite). See makeOrbGL() in journey.js. */
.orb-stage {
  position: absolute; inset: 0; border-radius: 50%; overflow: hidden; isolation: isolate;
  /* dormant pearl — what shows before the shader ignites (canvas opacity → wake) */
  background: radial-gradient(128% 128% at 38% 32%, #20262f 0%, #12161f 38%, #080c13 72%, #04060a 100%);
  box-shadow: inset 0 -14px 32px rgba(0,0,0,.7), inset 0 8px 18px rgba(255,255,255,.045);
}
@media (prefers-reduced-motion: no-preference) { .orb-stage { animation: orbBreathe 4.8s ease-in-out infinite; } }
@keyframes orbBreathe { 0%,100% { transform: scale(1); } 50% { transform: scale(1.035); } }
/* always visible — the shader itself renders the dormant orb as a desaturated
   silver marble (uSat→wake), so colour floods in on tap instead of the canvas
   fading up from a flat pearl. */
.orb-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; border-radius: 50%; opacity: 1; }

/* outer halo — the orb's own light blooming into the night (not clipped) */
.orb-halo {
  position: absolute; inset: -42%; border-radius: 50%; filter: blur(40px); pointer-events: none;
  background: radial-gradient(closest-side, var(--o-warm, #A3DDD8), transparent 66%);
  opacity: calc((.06 + .42 * var(--orb-wake, 1)) * .52);
}
@media (prefers-reduced-motion: no-preference) { .orb-halo { animation: haloBreathe 4.8s ease-in-out infinite; } }
@keyframes haloBreathe { 0%,100% { transform: scale(1); } 50% { transform: scale(1.06); } }
/* the mini-orb halo (pricing trio) stays a soft glow, not a pale disc behind a
   small orb — keep it subtle so the orb reads as the light, never a white patch */
.orb-mini .orb-halo { opacity: .3; }

/* the painted h — the brand mark, IN FRONT of the orb; greyed dormant, in
   colour once ignited, and present only in the hero (fades as you scroll past). */
.orb-mark {
  position: absolute; left: 50%; top: 50%; width: 46%; aspect-ratio: 1; transform: translate(-50%, -50%);
  display: grid; place-items: center; pointer-events: none; z-index: 6;
  opacity: var(--mark-show, 1);
  filter: grayscale(calc(1 - var(--orb-wake, 0))) brightness(calc(.72 + .3 * var(--orb-wake, 0)));
  transition: filter .2s linear;
}
.orb-mark img { width: 100%; display: block; filter: drop-shadow(0 8px 22px rgba(0,0,0,.55)); }

/* No ring on the hero. The app's birth orb (ComposeOrbHero) wears no progress
   ring — it's a silver-grey marble where COLOUR itself is the feedback: each tap
   floods more hue in (uSat→wake). The only affordance is the quiet "tap to hold"
   pill below (.invite). */

/* the invitation under the orb */
.invite {
  position: absolute; left: 50%; top: calc(50% + var(--orb-y, 0px)); transform: translate(-50%, calc(var(--orb-scale, 1) * 200px + 30px));
  z-index: 10; margin: 0; display: flex; align-items: center; gap: 10px; font-size: 13px; font-weight: 700; letter-spacing: .2em; text-transform: lowercase; color: rgba(244,242,238,.62); white-space: nowrap;
  opacity: var(--invite-show, 0); transition: opacity .5s ease, color .4s ease;
}
.invite .ring { width: 9px; height: 9px; border-radius: 50%; border: 2px solid currentColor; opacity: .7; animation: invitePulse 2.6s ease-in-out infinite; }
@keyframes invitePulse { 0%,100% { transform: scale(1); opacity: .4; } 50% { transform: scale(1.3); opacity: .95; } }
.invite.held { color: rgba(244,242,238,.72); }
.invite.held .ring { border: 0; background: var(--status-success); box-shadow: 0 0 12px var(--status-success); animation: none; opacity: 1; }

/* the hold hit-target over the orb */
.hold-target { position: absolute; left: 50%; top: 50%; z-index: 9; width: 300px; height: 300px; border-radius: 50%; transform: translate(-50%, -50%) translate(var(--orb-x,0px), var(--orb-y,0px)) scale(var(--orb-scale,1)); cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; outline: none; -webkit-appearance: none; appearance: none; background: transparent; border: 0; padding: 0; }
.hold-target:focus-visible { box-shadow: 0 0 0 2px rgba(255,255,255,.5); }
html.begun .hold-target { pointer-events: none; }

/* dots + scroll cue */
.dots { position: fixed; right: clamp(16px, 2.4vw, 34px); top: 50%; transform: translateY(-50%); z-index: 60; display: flex; flex-direction: column; gap: 12px; opacity: 0; transition: opacity .6s ease; pointer-events: none; }
html.begun .dots { opacity: 1; }
.dots .d { width: 8px; height: 8px; border-radius: 50%; background: rgba(255,255,255,.22); transition: background .35s ease, transform .35s ease; }
.dots .d.on { background: #F4F2EE; transform: scale(1.25); }
.scroll-cue { position: fixed; left: 50%; bottom: 30px; transform: translateX(-50%); z-index: 60; display: grid; justify-items: center; gap: 8px; font-size: 12px; letter-spacing: .2em; text-transform: lowercase; color: rgba(244,242,238,.5); opacity: var(--cue-show, 0); transition: opacity .6s ease; pointer-events: none; }
.scroll-cue .chev { width: 18px; height: 18px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(45deg); animation: cueBob 1.8s ease-in-out infinite; }
@keyframes cueBob { 0%,100% { transform: rotate(45deg) translate(-2px,-2px); opacity: .5; } 50% { transform: rotate(45deg) translate(2px,2px); opacity: 1; } }

/* footer */
.foot { position: relative; z-index: 2; background: #08080A; padding: clamp(48px,8vh,90px) clamp(22px,6vw,80px) 60px; }
.foot-inner { max-width: 1080px; margin: 0 auto; }
.foot-grid { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; padding-bottom: 28px; border-bottom: 1px solid rgba(255,255,255,.08); }
.foot-links { display: flex; gap: clamp(14px,2.4vw,28px); flex-wrap: wrap; }
.foot-links a { font-size: 15px; color: rgba(244,242,238,.72); text-decoration: none; }
.foot-links a:hover { color: #F4F2EE; }
.foot-fine { margin-top: 26px; font-size: 13px; line-height: 1.7; color: rgba(244,242,238,.55); }
.foot-fine em { font-style: italic; }

html.scroll-lock, html.scroll-lock body { overflow: hidden; height: 100%; }

/* reduced motion / no-JS → plain stacked page */
@media (prefers-reduced-motion: reduce) {
  .stage { position: static; height: auto; }
  .bg-layer { display: none; } .content-layer { position: static; }
  .scene { position: static; opacity: 1 !important; min-height: 90vh; }
  .orb, .invite, .scroll-cue, .dots { display: none; }
  .nav { opacity: 1; transform: none; pointer-events: auto; position: static; }
}
html.no-js .stage { position: static; height: auto; }
html.no-js .bg-layer { display: none; } html.no-js .content-layer { position: static; }
html.no-js .scene { position: static; opacity: 1; min-height: 90vh; }
html.no-js .orb, html.no-js .invite, html.no-js .scroll-cue, html.no-js .dots { display: none; }
