/* ===========================================================================
   House of Planets — landing page.
   One persistent composition moving through nine narrative states.

   Architecture notes that matter when editing:
   - .lp-stage is position:sticky with margin-bottom:-100svh. The negative
     margin removes its flow contribution so scene 1 starts at the top of the
     document while the stage stays pinned for the whole of <main>. Do not
     switch it to position:fixed — sticky is what keeps iOS URL-bar collapse
     from drifting the scene maths.
   - Everything the director animates is transform / opacity / a registered
     custom property feeding one of those. Nothing else. No filter, no
     backdrop-filter, no width/height on anything that moves.
   - Default state is VISIBLE. An early head script marks JavaScript support;
     only the animated stage is hidden until landing.js applies its first pose.
     With JavaScript off, the complete fallback remains visible.
   - Design tokens come from chart-app-theme.css. hop-main.css is deliberately
     NOT loaded (it sets body{overflow:hidden} for the app shell).
   =========================================================================== */

@property --lp-ws    { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --lp-tilt  { syntax: "<angle>";  inherits: true; initial-value: 0deg; }
@property --lp-spin  { syntax: "<angle>";  inherits: true; initial-value: 0deg; }
@property --lp-wx    { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }
@property --lp-wy    { syntax: "<length-percentage>"; inherits: true; initial-value: 0%; }

:root {
    --lp-ink:        rgba(241, 245, 249, 0.94);
    --lp-ink-soft:   rgba(203, 213, 225, 0.78);
    --lp-ink-faint:  rgba(148, 163, 184, 0.62);
    --lp-bg:         #020204;
    --lp-display:    'Marcellus SC', Georgia, serif;
    --lp-gutter:     clamp(20px, 6vw, 96px);
}

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    background: var(--lp-bg);
    color: var(--lp-ink);
    font-family: 'Inter', system-ui, sans-serif;
    font-weight: 300;
    line-height: 1.6;
    overflow-x: hidden;
}

::selection { background: rgba(241, 245, 249, 0.18); }

/* --------------------------------------------------------------------- skip */
.lp-skip {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: 12px 18px;
    background: rgba(8, 13, 22, 0.96);
    border: 1px solid rgba(241, 245, 249, 0.22);
    border-radius: 10px;
    color: var(--lp-ink);
    font-size: 14px;
    text-decoration: none;
}
.lp-skip:focus { left: 12px; top: 12px; }

/* ------------------------------------------------------------------- header */
.lp-head {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: max(14px, env(safe-area-inset-top)) var(--lp-gutter) 14px;
    background: linear-gradient(to bottom,
        rgba(2,4,8,0.97) 0%, rgba(2,4,8,0.95) 62%, rgba(2,4,8,0.72) 82%, rgba(2,4,8,0) 100%);
    /* The gradient runs past the controls so a line arriving from below is
       already covered by the time it reaches them. Nothing here is clickable
       except the CTA, which sits above its own box. */
    padding-bottom: 58px;
    margin-bottom: -44px;
    pointer-events: none;
}
.lp-brand, .lp-head-actions { pointer-events: auto; }
.lp-brand {
    font-family: var(--lp-display);
    font-size: 13px;
    letter-spacing: 0.20em;
    text-transform: uppercase;
    color: var(--lp-ink-soft);
}
.lp-head-actions {
    display: flex;
    align-items: center;
    gap: 14px;
}
.lp-head-signin {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0.06em;
    text-decoration: none;
    color: var(--lp-ink-soft);
    padding: 9px 4px;
    transition: var(--hop-control-transition, all 180ms ease);
}
.lp-head-signin:hover { color: var(--lp-ink); }
.lp-head-cta {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-decoration: none;
    color: var(--lp-ink);
    padding: 9px 16px;
    border-radius: 9999px;
    border: 1px solid rgba(241, 245, 249, 0.22);
    background: rgba(241, 245, 249, 0.06);
    transition: var(--hop-control-transition, all 180ms ease);
}
.lp-head-cta:hover {
    background: rgba(241, 245, 249, 0.11);
    border-color: rgba(241, 245, 249, 0.38);
}

/* ============================== PERSISTENT STAGE ========================== */

.lp { position: relative; }

html.js:not(.landing-ready) .lp-stage { visibility: hidden; }

.lp-stage {
    position: sticky;
    top: 0;
    height: 100svh;
    margin-bottom: -100svh;   /* remove flow contribution — see header note */
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
    contain: layout paint;
}

.lp-stars {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: var(--lp-stars-o, 0.5);
    transition: opacity 400ms linear;
}

/* ------------------------------------------------------------------- wheel */
.lp-wheel-wrap {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    perspective: 1400px;
}
.lp-wheel-3d {
    position: relative;
    transform:
        translate3d(var(--lp-wx, 0%), var(--lp-wy, 0%), 0)
        rotateX(var(--lp-tilt, 0deg))
        rotateZ(var(--lp-spin, 0deg))
        scale(var(--lp-ws, 1));
    opacity: var(--lp-wheel-o, 1);
    transform-style: preserve-3d;
    will-change: transform;
}
/* Atmosphere, not a brand gradient. It lives INSIDE .lp-wheel-3d so it scales
   and tilts with the wheel — the chart carries its own ground rather than
   sitting on a painted patch of page. DOM order puts it behind the <svg>, so
   no z-index is needed (which preserve-3d would have made unreliable anyway). */
.lp-wheel-3d::before {
    content: '';
    position: absolute;
    inset: -16%;
    border-radius: 50%;
    background: radial-gradient(closest-side,
        rgba(52, 60, 88, 0.30) 0%,
        rgba(28, 33, 52, 0.15) 52%,
        rgba(10, 12, 20, 0) 80%);
    pointer-events: none;
}
.lp-wheel {
    display: block;
    width: min(76vmin, 620px);
    height: auto;
    overflow: visible;
}

.lw-signs circle { fill: none; stroke-width: 8; }
.lw-signs         { opacity: var(--lp-ring-o, 1); }

/* The spokes used to carry an opacity FLOOR of their own (--lp-spoke-o), for
   a scene whose pose pinned --lp-ring-o at 0 while the spokes were drawing.
   That scene is gone: the fan is now drawn inside scene 2, whose pose has
   ringO 1 from its first frame, so the floor could never win and the ring
   opacity is the whole story again. */
.lw-houses        { opacity: var(--lp-ring-o, 1); }
.lw-houses line   { stroke: #ffffff; stroke-opacity: 0.15; stroke-width: 1; }

.lw-numerals      { opacity: calc(var(--lp-ring-o, 1) * var(--lp-detail-o, 1)); }
.lw-numerals text {
    fill: rgba(226, 232, 240, 0.34);
    font-family: var(--lp-display);
    font-size: 13px;
    letter-spacing: 0.08em;
    text-anchor: middle;
    dominant-baseline: middle;
}

.lw-signlabels      { opacity: calc(var(--lp-ring-o, 1) * var(--lp-detail-o, 1)); }
.lw-signlabels text {
    fill: rgba(203, 213, 225, 0.44);
    font-family: 'Inter', sans-serif;
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0.02em;
    text-transform: none;
    font-variant: normal;
    text-anchor: middle;
    dominant-baseline: middle;
}

/* Planet colours are the product's own accents (planet-chat.js PLANETS).
   Structure stays monochrome; these are the only saturated colour on screen. */
.lw-pl[data-planet="Sun"]     { color: #E8B84B; }
.lw-pl[data-planet="Moon"]    { color: #B8BDCC; }
.lw-pl[data-planet="Mars"]    { color: #E06040; }
.lw-pl[data-planet="Mercury"] { color: #7ADAC8; }
.lw-pl[data-planet="Jupiter"] { color: #F0C07A; }
.lw-pl[data-planet="Venus"]   { color: #C47EA8; }
.lw-pl[data-planet="Saturn"]  { color: #8A9BC4; }
.lw-pl[data-planet="Rahu"]    { color: #8F7BC0; }
.lw-pl[data-planet="Ketu"]    { color: #B39B72; }

.lw-pl { opacity: var(--o, 1); }
/* The per-planet radial gradients the flat halo was always a placeholder for
   (landing.html <defs>). Still no SVG filter: filters re-rasterise on every
   scale step and this wheel scales continuously — a gradient does not. */
.lw-aura { fill: currentColor; opacity: 0.16; }
.lw-pl[data-planet="Sun"]     .lw-aura { fill: url(#lw-glow-sun);     opacity: 1; }
.lw-pl[data-planet="Moon"]    .lw-aura { fill: url(#lw-glow-moon);    opacity: 1; }
.lw-pl[data-planet="Mars"]    .lw-aura { fill: url(#lw-glow-mars);    opacity: 1; }
.lw-pl[data-planet="Mercury"] .lw-aura { fill: url(#lw-glow-mercury); opacity: 1; }
.lw-pl[data-planet="Jupiter"] .lw-aura { fill: url(#lw-glow-jupiter); opacity: 1; }
.lw-pl[data-planet="Venus"]   .lw-aura { fill: url(#lw-glow-venus);   opacity: 1; }
.lw-pl[data-planet="Saturn"]  .lw-aura { fill: url(#lw-glow-saturn);  opacity: 1; }
.lw-pl[data-planet="Rahu"]    .lw-aura { fill: url(#lw-glow-rahu);    opacity: 1; }
.lw-pl[data-planet="Ketu"]    .lw-aura { fill: url(#lw-glow-ketu);    opacity: 1; }
.lw-body { fill: currentColor; }
/* Once a hopicon face is injected the flat body disc is redundant. */
.lw-pl.has-face .lw-body { display: none; }
.lw-glyph {
    fill: currentColor;
    font-size: 15px;
    text-anchor: middle;
    dominant-baseline: middle;
    opacity: 0.9;
}
/* Rahu and Ketu stay subordinate to the seven bodies, as they were at the old
   flat 0.10 vs 0.16. Written .lw-pl.lw-node so it outranks nothing and ties
   with the per-planet gradient rules above on specificity -- source order then
   decides, and this comes after them. */
.lw-pl.lw-node .lw-aura { opacity: 0.58; }

.lw-rel      { opacity: var(--lp-rel-o, 0); }
/* Scene 7's whole argument is these lines, and at 1px / 0.30 they were sitting
   at the noise floor. Colour and dash come from landing.js buildRelations:
   an A-to-B gradient of the two planets' own accents, dashed where the pair
   pulls against each other and solid where it does not. */
.lw-rel line { stroke-width: 1.4; fill: none; stroke-linecap: round; }

.lw-centre       { opacity: var(--lp-centre-o, 1); }
.lw-centre-halo  { fill: rgba(251, 191, 36, 0.10); }
.lw-centre-dot   { fill: #ffe6b0; }

/* ------------------------------------------------------------------- noise */
.lp-noise {
    position: absolute;
    inset: 0;
    opacity: var(--lp-noise-o, 0);
}
.lp-noise span {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate3d(-50%, -50%, 0) translate3d(var(--nx, 0), var(--ny, 0), 0);
    white-space: nowrap;
    font-size: clamp(11px, 1.5vw, 15px);
    font-weight: 300;
    letter-spacing: 0.02em;
    color: rgba(203, 213, 225, 0.52);
    opacity: var(--no, 0);
}

/* ------------------------------------------------------------------- scrim
   Mobile only. It exists so copy stays legible over the wheel and the
   starfield. It used to be .lp-copy::before, which put it in the scene layer
   -- above the whole stage, product surfaces included, so it dimmed the very
   card and conversations each scene was there to show. .lp-stage is
   position:sticky and therefore always a stacking context, so no z-index on a
   surface could climb out of it. Moving the scrim in here instead gives the
   paint order the design wanted all along:
       stars / noise / wheel -> scrim -> surfaces -> scene copy.
   Opacity tracks the active scene's copy (--lp-scrim-o, written by the
   director), so it still arrives and leaves exactly when the copy does. */
.lp-scrim {
    display: none;
    position: absolute;
    inset: 30% 0 0;
    background: linear-gradient(to bottom,
        rgba(2,4,8,0) 0%, rgba(2,4,8,0.82) 34%, rgba(2,4,8,0.94) 100%);
    opacity: var(--lp-scrim-o, 0);
    pointer-events: none;
}

/* ---------------------------------------------------------------- surfaces */
.lp-surfaces { position: absolute; inset: 0; }
.lp-surface {
    position: absolute;
    top: 50%;
    left: 50%;
    width: min(440px, calc(100vw - 40px));
    transform: translate3d(-50%, -50%, 0) translate3d(var(--sx, 0%), var(--sy, 0%), 0) scale(var(--ss, 1));
    opacity: var(--so, 0);
    visibility: hidden;
}
.lp-surface.is-live { visibility: visible; }

/* Scene 6 — the planet card. Same shape and the same class names as the
   product's wheel modal (hop-main.js:5740, hop-main.css:542+), rebuilt as
   static markup so it can never drift into production logic. */
.lp-card {
    --planet-color: #8A9BC4;
    /* Two layers: the planet's own light falling on the top of the card, over
       an opaque-enough ground that the wheel behind it stops bleeding through
       the type. The accent never leaves the card -- the scene around it stays
       black, ivory and grey. */
    background:
        radial-gradient(130% 88% at 50% 0%, rgba(138, 155, 196, 0.11), rgba(138, 155, 196, 0) 62%),
        rgba(8, 13, 22, 0.90);
    border: 1px solid rgba(138, 155, 196, 0.18);
    border-radius: var(--hop-radius-modal, 20px);
    padding: 24px 24px 20px;
    box-shadow:
        var(--hop-shadow-surface-modal, 0 30px 100px rgba(0,0,0,0.62)),
        inset 0 1px 0 rgba(169, 183, 216, 0.22);
}
.lp-card .modal-title {
    text-align: center;
    font-family: var(--lp-display);
    font-size: 12px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgba(148, 163, 184, 0.70);
}
.lp-card .modal-force {
    text-align: center;
    margin-top: 8px;
    font-family: var(--lp-display);
    font-size: 32px;
    line-height: 1.1;
    color: var(--planet-color);
}
.lp-card .modal-line {
    text-align: center;
    margin: 16px 0 0;
    font-size: 15px;
    line-height: 1.55;
    color: rgba(203, 213, 225, 0.68);
}
.lp-card .modal-arena {
    color: rgba(233, 240, 252, 0.94);
    border-bottom: 1.5px solid rgba(58, 71, 99, 0.9);
    padding-bottom: 1px;
}
.lp-card .modal-verb { color: rgba(233, 240, 252, 0.94); font-style: italic; }
/* The product's door into a planet is the composer itself, not a button
   (hop-main.js openModal -> .modal-dialogue > .planet-chat-composer-shell).
   Restated here from planet-chat.css:325-383 -- shape, radius, the 34px send
   circle and its arrow -- with --planet-color standing in for --pc-accent.
   Illustrative and inert: no input, no handler, nothing to submit. */
.lp-card-door {
    margin-top: 28px;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: 18px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(8, 13, 22, 0.62);
}
.lp-card-door-text {
    flex: 1;
    min-height: 34px;
    display: flex;
    align-items: center;
    font-size: 14px;
    line-height: 1.6;
    color: rgba(148, 163, 184, 0.62);
}
.lp-card-door-send {
    flex: none;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid rgba(138, 155, 196, 0.45);
    color: var(--planet-color);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    line-height: 1;
}
.lp-card-door-send::before { content: '↑'; }

/* Scenes 7 & 8 — conversation surfaces. chart-chat.css is not loaded (it is
   built for the app's full-viewport view), so the message shapes are restated
   here from chart-chat.css:630-695 verbatim in value. */
.lp-chat, .lp-planet { width: min(560px, calc(100vw - 32px)); }
.lp-example {
    font-size: 10px;
    font-weight: 400;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--lp-ink-faint);
    margin-bottom: 14px;
    text-align: center;
}
.chart-chat-thread { display: flex; flex-direction: column; gap: 14px; }
.chart-chat-message { display: flex; flex-direction: column; opacity: var(--bo, 1); }
.chart-chat-message.user { align-items: flex-end; }
.chart-chat-message.assistant, .chart-chat-message.thinking { align-items: flex-start; }
.chart-chat-message-card {
    max-width: min(100%, 460px);
    border-radius: 24px;
    padding: 14px 18px;
    border: 1px solid rgba(255, 255, 255, 0.09);
    /* 0.62 let the wheel's planets render straight through the words behind
       scenes 7 and 8. 0.88 stops that and still passes the starfield, so the
       cards keep sitting in the scene rather than on top of it. */
    background: rgba(8, 13, 22, 0.88);
    box-shadow: 0 2px 14px rgba(0, 0, 0, 0.38), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.chart-chat-message.user .chart-chat-message-card {
    background: rgba(241, 245, 249, 0.06);
    border-color: rgba(241, 245, 249, 0.14);
}
.chart-chat-message-body {
    font-size: 14px;
    line-height: 1.75;
    color: rgba(241, 245, 249, 0.94);
}
.chart-chat-thinking-copy {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    color: var(--lp-ink-soft);
    font-size: 13px;
}
.chart-chat-thinking-dots { display: inline-flex; gap: 4px; }
.chart-chat-thinking-dots span {
    width: 7px; height: 7px; border-radius: 9999px;
    background: rgba(241, 245, 249, 0.64);
    animation: lpChatPulse 1.4s ease-in-out infinite;
}
.chart-chat-thinking-dots span:nth-child(2) { animation-delay: 0.16s; }
.chart-chat-thinking-dots span:nth-child(3) { animation-delay: 0.32s; }
@keyframes lpChatPulse {
    0%, 100% { transform: scale(0.78); opacity: 0.45; }
    50%      { transform: scale(1);    opacity: 1; }
}

.lp-planet-face {
    width: 108px;
    height: 108px;
    margin: 0 auto 18px;
    overflow: visible;
}
/* Planet Dialogue is Saturn's room, so Saturn is in it -- as a tint on the
   fill of the user's side and a whisper of the same accent around the
   perimeter of his, and nothing else.

   What is deliberately NOT here: the leading 2px accent rail and the gradient
   that bled off it. That device is the house style of every assistant UI on
   the internet, which makes it the one thing this surface must not look like,
   and the product's own bubble does not have it either (chart-chat.css: a
   plain full-perimeter hairline on a dark fill). Speaker is carried by the
   things that already carry it -- which side the card sits on, and which of
   the two fills it has. Do not replace the rail with another ornament. */
.lp-planet .chart-chat-message.user .chart-chat-message-card {
    background: rgba(138, 155, 196, 0.14);
    border-color: rgba(138, 155, 196, 0.34);
}
/* Perimeter only, and well under the user side's 0.34 so the two never read
   as the same speaker. The fill is the base dark, inherited untouched. */
.lp-planet .chart-chat-message.assistant .chart-chat-message-card {
    border-color: rgba(138, 155, 196, 0.22);
}

/* ================================ SCENES ================================= */

.lp-scene {
    position: relative;
    z-index: 1;
    min-height: calc(var(--w, 1) * 100svh);
}
.lp-copy {
    position: sticky;
    top: 0;
    height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 18px;
    max-width: 46ch;
    padding: 0 var(--lp-gutter);
    opacity: var(--lp-copy-o, 1);
}

/* Scene 1's promise -- the page's single <h1>. These base values are the ones
   this line rendered as when it was .lp-lede; the two responsive compositions
   establish its actual hero scale below. */
.lp-promise {
    margin: 0;
    font-family: var(--lp-display);
    font-weight: 400;
    font-size: clamp(15px, 1.7vw, 20px);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--lp-ink);
}

/* The explanation beneath the promise. Carries what .lp-h1 used to be; the
   responsive scene-1 rules below make it subordinate at every width. */
.lp-deck {
    font-family: var(--lp-display);
    font-weight: 400;
    font-size: clamp(28px, 4.4vw, 52px);
    line-height: 1.22;
    letter-spacing: 0.005em;
    margin: 0;
    color: var(--lp-ink);
}
.lp-deck em { font-style: normal; color: var(--lp-ink-soft); }

.lp-h2 {
    font-family: var(--lp-display);
    font-weight: 400;
    font-size: clamp(24px, 3.2vw, 40px);
    line-height: 1.25;
    margin: 0;
    color: var(--lp-ink);
}
.lp-h2--hero { font-size: clamp(28px, 4.2vw, 50px); }

.lp-lede {
    margin: 0;
    font-family: var(--lp-display);
    font-size: clamp(15px, 1.7vw, 20px);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--lp-ink);
}
.lp-body  { margin: 0; font-size: clamp(15px, 1.35vw, 18px); color: var(--lp-ink-soft); }
.lp-punch { margin: 0; font-size: clamp(16px, 1.6vw, 21px); line-height: 1.5; color: var(--lp-ink); }
.lp-punch em { font-style: italic; color: var(--lp-ink); }
.lp-quote {
    margin: 0;
    padding-left: 18px;
    border-left: 1px solid rgba(241, 245, 249, 0.22);
    font-size: clamp(16px, 1.6vw, 21px);
    line-height: 1.5;
    color: var(--lp-ink);
}
.lp-quote b { font-family: var(--lp-display); font-weight: 400; color: var(--lp-ink); }
.lp-fine  { margin: 0; font-size: 12px; font-weight: 400; color: var(--lp-ink-faint); }
/* Scene 1's scroll cue. It used to read "SCROLL"; the arrow says the same
   thing without spending a word on it. The drift IS the message -- the stroke
   falls and fades on a long ease, so it reads as an invitation rather than a
   blinking prompt. Hairline weight to match the rest of the page's rules. */
.lp-cue {
    margin: 0;
    align-self: flex-start;
    width: 22px;
    height: 26px;
    color: var(--lp-ink-faint);
}
.lp-cue svg {
    display: block;
    width: 100%;
    height: 100%;
    animation: lp-cue-fall 2.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes lp-cue-fall {
    0%   { transform: translateY(-4px); opacity: 0; }
    28%  { opacity: 0.9; }
    64%  { opacity: 0.9; }
    100% { transform: translateY(8px); opacity: 0; }
}

.lp-parts { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.lp-parts li { display: flex; gap: 12px; align-items: baseline; font-size: 15px; }
.lp-parts b {
    font-family: var(--lp-display);
    font-weight: 400;
    color: var(--lp-ink);
    min-width: 9.5em;
}
.lp-parts span { color: var(--lp-ink-faint); font-size: 14px; }

.lp-cta {
    align-self: flex-start;
    margin-top: 10px;
    padding: 15px 30px;
    border-radius: 9999px;
    text-decoration: none;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: #14171d;
    background: rgba(241, 245, 249, 0.94);
    border: 1px solid rgba(255, 255, 255, 0.42);
    box-shadow: 0 14px 40px rgba(0, 0, 0, 0.38);
    transition: transform 160ms ease, box-shadow 160ms ease;
}
.lp-cta:hover { transform: translateY(-1px); box-shadow: 0 18px 52px rgba(0, 0, 0, 0.48); }

/* ================================ FOOTER ================================= */
.lp-foot {
    position: relative;
    z-index: 1;
    background: var(--lp-bg);
    padding: 72px var(--lp-gutter) calc(40px + env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    gap: 14px;
    align-items: center;
    text-align: center;
}
/* The wheel is at its largest in the last scene and runs right down to here.
   A 1px rule sliced straight through it — Capricorn and Sagittarius came off
   mid-glyph. The chart should dissolve into the footer instead, so the rule is
   a fade that starts above the footer box and lands on its own background. */
.lp-foot::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: 150px;
    background: linear-gradient(to bottom, rgba(2, 4, 8, 0), var(--lp-bg));
    pointer-events: none;
}
.lp-foot-note  { margin: 0; font-size: 12px; font-weight: 400; color: var(--lp-ink-faint); max-width: 60ch; }
.lp-foot-links { display: flex; flex-wrap: wrap; gap: 20px; justify-content: center; }
.lp-foot-links a {
    font-size: 13px;
    font-weight: 400;
    color: var(--lp-ink-soft);
    text-decoration: none;
}
.lp-foot-links a:hover { color: var(--lp-ink); }

/* ============================ NO-JAVASCRIPT ==============================
   Default (no html.js) leaves every scene's copy at opacity 1 and the wheel
   fully assembled. The stage surfaces stay hidden — each duplicates copy that
   already exists in the sections, so nothing is lost. The ready gate lets the
   hero remain readable while JavaScript prepares the animated stage. */
html.landing-ready .lp-copy { opacity: var(--lp-copy-o, 0); }

/* ============================= DESKTOP HERO ==============================
   Scene 1 only, and the only min-width query in this file. Everything else
   here is written base-first with a max-width:640px override -- but a base
   fluid width resolves too narrowly on a 390px phone, so following
   that convention would need a second rule purely to undo it. A min-width
   query keeps the approved desktop/tablet composition isolated instead.

   What it fixes, measured: .lp-copy's max-width is 46ch, and ch resolves
   against its own font (Inter 300 @16px = 9.88px), so that is a *fixed*
   454.6px box at every desktop width. box-sizing:border-box then subtracts
   the fluid gutter (up to 96px a side) from the inside, so the text column
   got NARROWER as the viewport got WIDER -- 282px at 1440, 263px at 1920 and
   beyond. Meanwhile the explanation was pinned at its 52px clamp ceiling from
   1182px up, which put it at 11 lines / 698px -- taller than a 900px viewport
   can centre, so it started under the fixed header and pushed the promise
   below it, at 20px, reading as a caption to its own explanation.

   Four things follow, and each is here for a reason:
   - width replaces max-width, so the column scales with the viewport rather
     than against it; the promise and deck retain their own readable measures.
   - fluid gap and bottom padding add breathing room and lift the composition
     slightly above mathematical centre, including on short desktop screens.
   - the promise drops text-transform. Marcellus SC is already a small-caps
     face, so sentence case renders as full cap + small caps -- the letterform
     contrast reads as composed at hero scale, where forced uppercase flattens
     every glyph to one height and reads as volume rather than confidence.
   - the explanation drops to the body face (font-family: inherit -> Inter,
     matching .lp-body). Marcellus SC at 24px is a wall of small caps and
     reads as a second headline, which is exactly what it must stop being.
   ========================================================================= */
@media (min-width: 641px) {
    /* R1. The two compositions below fixed this for their own scenes; the other
       eight still ran the 46ch max-width, which is a FIXED 454.6px box (ch
       resolves against Inter 300/16px = 9.88px). border-box then subtracts the
       fluid gutter from the inside, so the text column got narrower as the
       viewport got wider -- 282px at 1440, 263px at 1920 and beyond, with
       headings breaking to three lines beside 1000px of unused page.

       The clamp is the measure; the single gutter term cancels the padding the
       border-box would otherwise eat out of it. That holds the text column at
       ~460px from 641px up -- close to the 467px #scene-wheel was approved at,
       and it never shrinks again as the viewport grows. */
    .lp-copy {
        max-width: none;
        width: calc(clamp(46ch, 38vw, 560px) + var(--lp-gutter));
        padding-top: clamp(76px, 10svh, 116px);
    }
    #scene-wheel .lp-copy {
        max-width: none;
        width: clamp(46ch, 47vw, 640px);
    }
    /* The three scenes that share the stage with a product surface. Their copy
       is two or three short lines, so it does not need the full rail — and the
       full rail ran it straight into the card and the conversations, which sit
       centred on the viewport. A narrower measure plus a small push on the
       surfaces keeps the two columns apart from 1280px up; below that they
       still meet, and landing.js fades the copy against the surface instead
       (see the ceiling note in measure()). */
    #scene-product .lp-copy,
    #scene-saturn .lp-copy,
    #scene-dialogue .lp-copy {
        width: calc(clamp(40ch, 30vw, 400px) + var(--lp-gutter));
    }
    .lp-card, .lp-planet, .lp-chat { --sx: 4vw; }
    .lp-chat { --sy: 2.5%; }
    .lp-chat .chart-chat-thread { gap: 12px; }
    #scene-wheel .lp-parts b { min-width: 6em; }
    #scene-you .lp-copy {
        max-width: none;
        width: min(76vw, 1200px);
        gap: clamp(24px, 2.4vw, 40px);
        padding-bottom: 6svh;
    }
    #scene-you .lp-promise {
        max-width: 7em;
        font-size: clamp(64px, 7.2vw, 120px);
        line-height: 0.98;
        letter-spacing: 0;
        text-transform: none;
    }
    #scene-you .lp-deck {
        width: fit-content;
        font-family: inherit;
        font-weight: 300;
        font-size: clamp(16px, 1.45vw, 24px);
        line-height: 1.55;
        letter-spacing: 0;
        max-width: 58ch;
        color: var(--lp-ink-soft);
    }
    #scene-you .lp-deck em { color: var(--lp-ink-faint); }
}

/* ================================ MOBILE =================================
   A separate composition, not a squeezed desktop one. The wheel moves to the
   upper third, the copy anchors to the lower two-thirds behind a scrim, and
   the director switches to the mobile pose table (landing.js POSE_M). */
@media (max-width: 640px) {
    .lp-wheel { width: 88vmin; }
    .lp-copy {
        max-width: none;
        justify-content: flex-end;
        padding-bottom: max(12svh, 96px);
        gap: 14px;
    }
    .lp-scrim { display: block; }

    /* Scene 1 only. Preserve the bottom-weighted mobile composition, but give
       its DOM hierarchy the same visual hierarchy as desktop: ivory Marcellus
       promise first, quiet Inter explanation second. The 7em measure keeps
       the promise at two lines through the full mobile range. */
    #scene-you .lp-copy { gap: 18px; }
    #scene-you .lp-promise {
        max-width: 7em;
        font-size: clamp(46px, 12vw, 52px);
        line-height: 0.98;
        letter-spacing: 0;
        text-transform: none;
    }
    #scene-you .lp-deck {
        width: fit-content;
        font-family: inherit;
        font-weight: 300;
        font-size: clamp(14.5px, 3.55vw, 16.5px);
        line-height: 1.5;
        letter-spacing: 0;
        max-width: 58ch;
        color: var(--lp-ink-soft);
    }
    #scene-you .lp-deck em { color: var(--lp-ink-faint); }

    /* The hero and the noise scene only -- the two whose composition lives in
       the upper band. 56% keeps the persistent origin above the scrim while
       every line stays covered: the promise begins 83-99px inside it and the
       centre stays 111-123px clear, at both measured mobile sizes. On the
       noise scene the same inset is what keeps the lower fragments off the
       scrim's opaque foot. Keyed off data-act now that the scrim is a stage
       layer -- act N is scene N. */
    .lp-stage[data-act="1"] .lp-scrim,
    .lp-stage[data-act="5"] .lp-scrim { inset-block-start: 56%; }

    /* M2. The parts list is the tallest copy block on the page, and three of
       its seven rows were wrapping, which pushed the block up into the wheel.
       A shorter label column unwraps them; the pose change (landing.js POSE_M
       act 6) does the rest. */
    .lp-parts b { min-width: 7.4em; }
    .lp-parts li { font-size: 13.5px; }
    .lp-parts span { font-size: 13.5px; }
    .lp-parts { gap: 5px; }

    .lp-surface { width: calc(100vw - 32px); }

    /* M3/M4. The surfaces are centred on the viewport and the copy is anchored
       to its foot, so at 390x844 they met in the middle: the card's own title
       sat under the copy, and the chat's label clipped under the header while
       ~180px of dead black sat between the two. Lifting each surface by its own
       amount seats it in the free upper band and closes the gap. */
    .lp-card   { --sy: -13%; }
    .lp-planet { --sy: -16%; }
    .lp-chat   { --sy: -12%; }

    /* Four beats, not six. 1-2-3-6 reads straight through -- "...or whether all
       this effort is going anywhere?" / "The last one." / "Then maybe you're not
       lost." -- and is the only cut that leaves the thread intact. Desktop is
       untouched; landing.js reveals whichever beats are actually showing. */
    .lp-chat [data-beat-mobile="off"] { display: none; }
    .lp-chat .chart-chat-thread { gap: 11px; }
    .lp-chat .chart-chat-message-card { padding: 12px 15px; }
    .lp-chat .chart-chat-message-body { line-height: 1.66; }
    .lp-cta { align-self: stretch; text-align: center; }
    .lp-head { padding-left: 20px; padding-right: 20px; }
    .lp-brand { flex-shrink: 0; white-space: nowrap; }
    .lp-head-signin { display: none; }
    .lp-head-cta {
        white-space: nowrap;
        padding: 9px 12px;
        font-size: 11px;
        line-height: 1;
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }
}

@media (max-width: 380px) {
    .lp-parts b { min-width: 6.9em; }
    .lp-planet-face { width: 84px; height: 84px; }
}

/* ============================= REDUCED MOTION ============================
   Not a duration hack. The director stops scrubbing entirely (landing.js
   never starts its rAF loop), scenes snap to their end pose on entry, and the
   only transition left is a short opacity fade. The whole argument survives. */
@media (prefers-reduced-motion: reduce) {
    .lp-wheel-3d {
        will-change: auto;
        transition: opacity 150ms linear;
    }
    .lp-copy    { transition: opacity 150ms linear; }
    .lp-surface { transition: opacity 150ms linear; }
    .lp-cue svg { animation: none; opacity: 0.9; transform: none; }
    .lp-noise span { transition: none; }
    .chart-chat-thinking-dots span { animation: none; opacity: 0.7; }
    .lp-cta { transition: none; }
    .lp-stars { display: none; }
}
