/* home.css — home's own stylesheet (rewritten from the stale app.css fork,
 * restructure Phase 6, 2026-07-10).
 *
 * The old home/static/css/app.css was a 3,950-line snapshot of the dashboard
 * stylesheet; ~90% of its selectors matched nothing in home's templates.
 * This file keeps exactly the rules home's pages use (home.html + legal.html
 * load it; login.html is fully self-styled inline), preserved verbatim from
 * the fork — including the 'Google Sans Flex' font stack, which home really
 * uses. Grep inventory of the used class/id tokens lives with the restructure
 * plan; regenerate by re-extracting class= / classList tokens from
 * templates/*.html + static/js/home.js.
 */
    *,*::before,*::after{margin:0;padding:0;box-sizing:border-box;}

    :root{
      /* Surfaces, borders, accent, semantic — original palette */
      --bg-primary:    #09090b;
      --bg-secondary:  #0f0f12;
      --bg-tertiary:   #16161a;
      --bg-elevated:   #1c1c21;
      --bg-hover:      #22222a;
      --border-subtle: #1e1e26;
      --border-default:#2a2a35;

      --text-primary:  #f0f0f3;
      --text-secondary:#a1a1aa;
      --text-muted:    #71717a;
      --text-xmuted:   #565549;
      --menu-invert-desc:#56688a;
      /* Floating-chrome drop shadow. Mirrors the dashboard token of the same
         name (tokens.css) because css/lib/menu.css — the account dropdown,
         shared byte-for-byte with the dashboard — resolves its elevation
         through it. Deeper than the dashboard's value on purpose: home's menu
         floats over a bright autumn hero, not over dark app chrome. */
      --shadow-pop:    0 8px 24px rgba(0,0,0,.5), 0 24px 64px rgba(0,0,0,.45);

      --accent:        #6366f1;
      --accent-dim:    rgba(99,102,241,.12);
      --green:         #22c55e;
      --green-dim:     rgba(34,197,94,.10);
      --red:           #ef4444;
      --red-dim:       rgba(239,68,68,.10);
      --amber:         #f59e0b;

      /* Warm secondary accent — muted gold/tan. Used for "active but not
         primary" highlights (chart-type toggles, watchlist row in focus).
         Calm, distinct from the cream inversion used by 1D/5D/1W. */
      --accent-warm:     #d4b18a;
      --accent-warm-dim: rgba(212,177,138,0.10);

      /* Type — Space Grotesk (display/headings) + Inter (body/UI) + JetBrains Mono (data).
         IBM Plex stays as vendored offline fallback for all three. */
      --font-display:  'Space Grotesk', 'IBM Plex Sans', system-ui, sans-serif;
      --font-sans:     'Google Sans Flex', 'IBM Plex Sans', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
      --font-mono:     'JetBrains Mono', 'IBM Plex Mono', 'SF Mono', ui-monospace, monospace;

      /* Tracking — premium financial platform values.
         Tight negative tracking on headings = authority + trust (Bloomberg, Citadel-level). */
      --tr-display:    -0.04em;
      --tr-heading:    -0.03em;
      --tr-body:       -0.01em;
      --tr-caps:        0.10em;
      --tr-mono:        0.01em;

      /* Geometry — flat language: zero radius on chrome surfaces.
         Round corners now only appear where they're semantically required
         (true circles like the LIVE dot, scrollbar thumbs, brand video). */
      /* Sidebar — cap stays at 390px (27" full-screen). Steeper midband
         so smaller screens shrink properly:
           • 1280  → 212.5px (floor)
           • 1366 laptop  → 222.5px
           • 1500  → 244px
           • 1700  → 276px
           • 1920  → 312.5px
           • 2200  → 357.5px
           • 2400+ → 390px (cap)
         Laptops sit visibly smaller than the desk-monitor cap. */
      --sidebar-w:         clamp(212.5px, 13vw, 390px);
      --header-h:          70px;
      --radius:            0;
      --radius-sm:         0;

      /* Motion */
      --ease-out:      cubic-bezier(0.22, 0.8, 0.18, 1);
      --ease-in-out:   cubic-bezier(0.4, 0, 0.2, 1);
      --dur-fast:      140ms;
      --dur:           220ms;
      --dur-slow:      420ms;
      --dur-reveal:    560ms;
    }

    /* ─── Global typography rules ─── */
    h1,h2,h3,h4,h5,h6{font-family:var(--font-display);letter-spacing:var(--tr-heading);}
    h1{letter-spacing:var(--tr-display);}
    .font-mono,code,pre{font-variant-numeric:tabular-nums;}

    html,body{height:100%;overflow:hidden;}
    /* home's 1.25x root. The FLOOR is load-bearing for legal.html, which is a
       scrolling document authored entirely in rem — lowering it would shrink
       the policy pages on every laptop. Areas that must keep scaling below the
       floor opt out with their own viewport-tracking unit instead (--ha-u on
       the top-right cluster, --hf-u on the legal footer, zoom:var(--ap-z) on
       the auth sheet); all three are px/vw based, so none compound with this. */
    html{font-size:clamp(20px, calc(12.5px + 0.4vw), 30px);}
    body{
      font-family:var(--font-sans);
      letter-spacing:var(--tr-body);
      background:var(--bg-primary);
      color:var(--text-primary);
      display:flex;
      -webkit-font-smoothing:antialiased;
      text-rendering:optimizeLegibility;
    }
    .sr-item{
      display:flex;align-items:center;gap:15px;
      padding:12.5px 25px;cursor:pointer;transition:background .1s;
    }
    .sr-item:hover,.sr-item.active{background:var(--bg-hover);}
    .sr-sym{font-family:var(--font-mono);font-size:0.875rem;font-weight:700;color:var(--text-primary);min-width:87.5px;}
    .sr-name{font-size:0.8125rem;color:var(--text-secondary);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

    /* ── Home top-right account / auth pills ────────────────────────────
       SIZING: this cluster does NOT ride the root font (`html` clamps at a
       20px FLOOR, which every viewport from ~1875px down shares — so a 1440px
       laptop was drawing these controls at the exact size a 3072px 27" draws
       them, and they swallowed the corner). Instead the whole cluster is
       measured in its own fluid unit, --ha-u, and every size below is
       `calc(<the dashboard's rem number> * var(--ha-u))` — the ported rem
       values stay readable and 1:1 with the chart page (see the notes on
       .flow-btn and .home-account-chip), they just scale off a unit that
       tracks the viewport instead of one that flatlines.

         3072px (the 27" reference) → 25px    ← matches the old 24.79px root,
         2560px                     → 22.1px     i.e. the big screen is UNCHANGED
         1920px                     → 18.6px
         ≤1450px (laptops)          → 16px    ← floor: parity with the dashboard's
                                                own .view-trigger at its 16px root
       Only the controls scale; the corner inset (25/30px) is deliberately
       fixed so the cluster doesn't drift on the reference monitor. */
    .home-account{
      --ha-u:clamp(16px, calc(8px + 0.55vw), 25px);
      position:fixed;top:25px;right:30px;z-index:50;
      display:flex;align-items:center;gap:10px;
    }
    /* ── Logged-out auth buttons — "Flow Button" ──────────────────────────
       Port of 21st.dev/@xubohuah/components/flow-button (React + Tailwind) to
       vanilla CSS on home's dark palette. The mechanic is the original's,
       kept intact: a pill whose radius morphs to 12px while a small dot
       scales up from dead centre and floods the surface, the label slides
       right, the resting arrow flies out past the right edge and a second
       arrow flies in from off-canvas left. Durations and easings are the
       component's own — 600ms cubic-bezier(.23,1,.32,1) on the border and
       radius, 800ms elsewhere, with its overshoot cubic on the arrows.

       Two weights share one mechanic, set by the four --fb-* vars below:
       "Join Cavalry" floods WARM (the brand accent — this is the commit),
       "Log In" floods IVORY, the same invert the account chip and the chart
       page's view trigger use.

       Sizes were rem (home's 1.25x root); they are now the same numbers
       against the cluster's fluid --ha-u — see the sizing note above. */
    .flow-btn{
      position:relative;
      display:inline-flex;align-items:center;gap:calc(0.25 * var(--ha-u));
      overflow:hidden;
      padding:calc(0.5 * var(--ha-u)) calc(1.375 * var(--ha-u));
      border:1.5px solid var(--fb-ring);
      border-radius:100px;
      background:transparent;
      color:var(--fb-ink);
      font-family:var(--font-sans);
      font-size:calc(0.75 * var(--ha-u));font-weight:600;letter-spacing:0.01em;
      text-decoration:none;white-space:nowrap;
      cursor:pointer;
      transition:border-color .6s cubic-bezier(0.23,1,0.32,1),
                 border-radius .6s cubic-bezier(0.23,1,0.32,1),
                 color .6s cubic-bezier(0.23,1,0.32,1),
                 transform .15s var(--ease-out);
    }
    .flow-btn:hover{
      border-color:transparent;
      border-radius:12px;
      color:var(--fb-ink-on);
    }
    .flow-btn:active{transform:scale(0.95);}
    .flow-btn:focus{outline:none;}
    .flow-btn:focus-visible{
      border-color:var(--fb-fill);
      box-shadow:0 0 0 4px color-mix(in srgb, var(--fb-fill) 20%, transparent);
    }
    /* The flood — a 1-unit dot pinned dead centre that grows past the corners. */
    .fb-circle{
      position:absolute;top:50%;left:50%;
      transform:translate(-50%,-50%);
      width:var(--ha-u);height:var(--ha-u);border-radius:50%;
      background:var(--fb-fill);
      opacity:0;pointer-events:none;
      transition:width .8s cubic-bezier(0.19,1,0.22,1),
                 height .8s cubic-bezier(0.19,1,0.22,1),
                 opacity .8s cubic-bezier(0.19,1,0.22,1);
    }
    .flow-btn:hover .fb-circle{
      width:calc(13.75 * var(--ha-u));height:calc(13.75 * var(--ha-u));opacity:1;
    }
    .fb-label{
      position:relative;z-index:1;
      transform:translateX(calc(-0.625 * var(--ha-u)));
      transition:transform .8s var(--ease-out);
    }
    .flow-btn:hover .fb-label{transform:translateX(calc(0.625 * var(--ha-u)));}
    /* Arrows ride left/right (not transform) exactly like the original, so
       the -25% off-canvas rest position scales with the button's width. */
    .fb-arrow{
      position:absolute;top:50%;
      width:calc(0.875 * var(--ha-u));height:calc(0.875 * var(--ha-u));z-index:9;
      transform:translateY(-50%);
      fill:none;stroke:var(--fb-ink);
      transition:left .8s cubic-bezier(0.34,1.56,0.64,1),
                 right .8s cubic-bezier(0.34,1.56,0.64,1),
                 stroke .8s cubic-bezier(0.34,1.56,0.64,1);
    }
    .fb-arrow-in{left:-25%;}
    .fb-arrow-out{right:calc(0.75 * var(--ha-u));}
    .flow-btn:hover .fb-arrow-in{left:calc(0.75 * var(--ha-u));}
    .flow-btn:hover .fb-arrow-out{right:-25%;}
    .flow-btn:hover .fb-arrow{stroke:var(--fb-ink-on);}
    @media (prefers-reduced-motion: reduce){
      .flow-btn,.fb-circle,.fb-label,.fb-arrow{transition-duration:.01ms;}
    }
    /* Weight: neutral. Ivory flood — the account chip's invert. */
    .home-auth-link{
      --fb-ring:rgba(240,240,243,0.22);
      --fb-ink:rgba(240,240,243,0.72);
      --fb-fill:var(--text-primary);
      --fb-ink-on:var(--bg-primary);
    }
    /* Weight: primary. Warm flood — the brand accent. */
    .home-auth-cta{
      --fb-ring:rgba(212,177,138,0.50);
      --fb-ink:#e7c9a3;
      --fb-fill:var(--accent-warm);
      --fb-ink-on:var(--bg-primary);
      /* No ambient glow — owner requirement: these buttons cast nothing onto
         the backdrop. Same rule kills the lancer/ENTER hover halo below. */
    }

    /* ── Account chip + dropdown ──────────────────────────────────────
       Speaks the dashboard chart page's .view-trigger / .view-menu language
       verbatim (dashboard/static/css/app/05-chart-panels.css) so the top-right
       control is the SAME component on both services: flat ink surface, crisp
       1px ivory ring as an INSET box-shadow (1px rounded borders bowtie in
       headless captures — never a border here), 0.5rem radius, ivory invert on
       hover, rich rows with title over description.

       Sizes are the dashboard's rem NUMBERS copied 1:1 on purpose, so the chip
       reproduces the chart page's PROPORTIONS exactly. They multiply the
       cluster's --ha-u rather than a rem (see the sizing note at the top of
       this section) — keep the numbers, don't "fix" them into flat px. */
    .home-account-wrap{position:relative;}
    .home-account-chip{
      position:relative;
      display:flex;align-items:center;gap:calc(0.4375 * var(--ha-u));
      height:calc(1.8125 * var(--ha-u));
      padding:0 calc(0.6875 * var(--ha-u)) 0 calc(0.25 * var(--ha-u));
      border:none;
      border-radius:calc(0.4375 * var(--ha-u));
      background:var(--bg-primary);
      color:var(--text-primary);
      font-family:var(--font-sans);
      font-size:calc(0.75 * var(--ha-u));font-weight:600;letter-spacing:.01em;
      text-decoration:none;
      box-shadow:
        inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 35%, transparent),
        0 8px 24px rgba(0,0,0,0.45);
      cursor:pointer;
      transition:background .2s var(--ease-out),color .2s var(--ease-out),
                 box-shadow .2s var(--ease-out),transform .15s var(--ease-out);
    }
    /* The invert: fills with the ring colour, every glyph flips to ink. */
    .home-account-chip:hover{background:var(--text-primary);color:var(--bg-primary);}
    .home-account-chip:active{transform:scale(0.99);}
    .home-account-chip:focus{outline:none;}
    .home-account-chip:focus-visible{
      box-shadow:
        inset 0 0 0 1px color-mix(in srgb, var(--text-primary) 80%, transparent),
        0 0 0 4px color-mix(in srgb, var(--text-primary) 15%, transparent);
    }
    .home-account-caret{
      /* overrides the markup's width/height="13" so the caret scales too */
      width:calc(0.65 * var(--ha-u));height:calc(0.65 * var(--ha-u));
      color:currentColor;margin-left:2px;flex-shrink:0;opacity:.7;
      transition:transform .3s var(--ease-out),opacity .18s ease;
    }
    .home-account-chip[aria-expanded="true"] .home-account-caret{
      transform:rotate(180deg);opacity:1;
    }
    .home-account-chip img{
      width:calc(1.3125 * var(--ha-u));height:calc(1.3125 * var(--ha-u));
      border-radius:50%;object-fit:cover;flex-shrink:0;
    }
    .home-account-fallback{
      width:calc(1.3125 * var(--ha-u));height:calc(1.3125 * var(--ha-u));
      border-radius:50%;flex-shrink:0;
      background:var(--bg-elevated);
      display:inline-flex;align-items:center;justify-content:center;
      color:var(--text-secondary);font-weight:700;font-size:calc(0.625 * var(--ha-u));
      transition:background .2s var(--ease-out),color .2s var(--ease-out);
    }
    .home-account-chip:hover .home-account-fallback{
      background:color-mix(in srgb, var(--bg-primary) 12%, transparent);
      color:var(--bg-primary);
    }
    .home-account-name{
      max-width:calc(10 * var(--ha-u));
      white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
    }

    /* Menu — MPMenu (static/css/lib/menu.css), the SAME dropdown-menu
       primitive the dashboard's sidebar account panel uses: our vanilla port
       of shadcn/ui's Radix dropdown-menu. Everything the panel looks like and
       does now lives in that shared file; all home owns is the scale binding
       below and the placement props (side:'bottom', align:'end'), which
       home.js passes to MPMenu.attach.

       SCALE: the shared file measures itself in --mpm-u, which falls back to
       1rem. home's root font clamp FLOORS at 20px, so a bare rem would draw a
       laptop's menu at the 27" size — the same trap the rest of this cluster
       sidesteps. Binding --mpm-unit to --ha-u puts the menu on the cluster's
       fluid unit instead. It rides the inherited --mpm-unit hook rather than
       overriding --mpm-u so this stays order- and specificity-independent. */
    .home-account{--mpm-unit:var(--ha-u);}

    /* ═══════════════════════════════════════════════════════════
       MAIN AREA
    ═══════════════════════════════════════════════════════════ */
    .main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0;position:relative;}

    /* ═══════════════════════════════════════════════════════════
       WELCOME STATE — editorial first impression.
       Thin hairline eyebrow, large IBM Plex Sans wordmark, muted tagline.
       Each line masks-in with a 80ms stagger.
    ═══════════════════════════════════════════════════════════ */
    .welcome{
      position:relative;
      flex:1;display:flex;align-items:flex-start;justify-content:flex-end;
      flex-direction:row;gap:0;
      color:var(--text-muted);
      /* Smaller right padding pushes the hero further into the
         right edge while keeping a comfortable gutter. */
      padding:0 clamp(40px, 5vw, 120px) 0 30px;
      overflow:hidden;
      /* 60px square grid behind the hero. Two 1px lines per axis,
         subtle ivory tint so it reads atmospheric, not decorative.
         Welcome state doesn't pan, so a fixed background-image is fine. */
      background-image:
        linear-gradient(rgba(255,255,255,0.035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.035) 1px, transparent 1px);
      background-size:60px 60px;
    }
    .welcome > *{position:relative;z-index:1;}

    /* ─────────────── Welcome content stack ───────────────
       Wraps eyebrow + wordmark + tagline + search and pins
       them to the right half of the welcome area so they
       don't collide with the autumn-tree backdrop on the
       left. Stays centered within its own column.
       ──────────────────────────────────────────────────── */
    .welcome-content{
      display:flex;flex-direction:column;
      align-items:center;justify-content:center;
      gap:0;
      max-width:min(850px, 48vw);
      width:100%;
      /* Pushed higher so the stack sits in the upper-right of the
         hero. Smaller wordmark below means we don't need as much
         top space to clear the autumn canopy. */
      margin-top:clamp(215px, 26vh, 375px);
      padding:10px 0;
    }

    /* ── Frame Button: futuristic corner-bracket CTA (Components 2) ──
       Four corner chevrons float outside the edges and spring outward
       on hover. Pure CSS, no JS. Base .fbtn + variants .fbtn--research
       (rose → Eva) and .fbtn--lancer (gold → tracking). */
    .fbtn-wrap{
      position:relative;
      display:inline-block;
      /* room for the corners to travel */
      padding:17.5px;
    }
    .fbtn{
      position:relative;
      display:inline-flex;
      align-items:center;
      justify-content:center;
      /* Fluid: full size only at ≥2400px effective viewport. Laptops with
         OS display scaling report 1280–1920px, so they land mid-curve and
         get a genuinely smaller button instead of the desktop cap.

         The FLOORS were 40/55/250px, which the vw terms reach at 1127/1467/
         1453px — so from a 1453px viewport down the button stopped shrinking
         while the window kept going. Under zoom that is the whole story: at
         200% on a 1366px laptop (683 CSS px) a 250px-wide button is 37% of
         the window, the label wrapped to two lines, and the box pushed left
         into the canopy. The floors below keep each vw term live to ~800px
         and only bite on a genuinely phone-sized window. Every CAP is
         untouched, so ≥2400px — the 27" reference included — is unchanged,
         and so is a 1536px laptop at 100% (all three vw terms already win
         there). New px values are authored at home's 1.25x root. */
      padding:0 clamp(27.5px, 3.55vw, 85px);
      height:clamp(30px, 3.75vw, 90px);
      min-width:clamp(137.5px, 17.2vw, 412.5px);
      box-sizing:border-box;
      font-family:"Manrope", ui-sans-serif, system-ui, sans-serif;
      font-size:clamp(14px, 0.84vw, 20px);
      font-weight:600;
      letter-spacing:0.2em;
      text-transform:uppercase;
      text-decoration:none;
      /* 1px ring via inset box-shadow, NOT border — 1px rounded borders
         render as a bowtie X in headless screenshots */
      border:none;
      border-radius:2.5px;
      background:transparent;
      cursor:pointer;
      -webkit-tap-highlight-color:transparent;
      transition:background 0.45s ease, color 0.45s ease,
        box-shadow 0.55s ease, transform 0.3s cubic-bezier(.22,1,.36,1);
    }
    .fbtn:active{ transform:scale(0.965); }

    /* corner brackets: flush against the rectangle at rest, spring out on hover */
    .fbtn-corner{
      position:absolute;
      width:clamp(16px, 1.05vw, 25px);
      height:clamp(16px, 1.05vw, 25px);
      border:2px solid;
      pointer-events:none;
      transition:transform 0.45s cubic-bezier(.22,1,.36,1), border-color 0.45s ease,
        opacity 0.45s ease;
      opacity:0.85;
    }
    .fbtn-corner--tl{ top:14px;    left:14px;   border-right:none; border-bottom:none; }
    .fbtn-corner--tr{ top:14px;    right:14px;  border-left:none;  border-bottom:none; }
    .fbtn-corner--bl{ bottom:14px; left:14px;   border-right:none; border-top:none; }
    .fbtn-corner--br{ bottom:14px; right:14px;  border-left:none;  border-top:none; }

    .fbtn-wrap:hover .fbtn-corner{ opacity:1; }
    .fbtn-wrap:hover .fbtn-corner--tl{ transform:translate(-11px, -11px); }
    .fbtn-wrap:hover .fbtn-corner--tr{ transform:translate(11px, -11px); }
    .fbtn-wrap:hover .fbtn-corner--bl{ transform:translate(-11px, 11px); }
    .fbtn-wrap:hover .fbtn-corner--br{ transform:translate(11px, 11px); }
    /* corners settle back near the rectangle when pressed */
    .fbtn-wrap:active .fbtn-corner--tl{ transform:translate(-4px, -4px); }
    .fbtn-wrap:active .fbtn-corner--tr{ transform:translate(4px, -4px); }
    .fbtn-wrap:active .fbtn-corner--bl{ transform:translate(-4px, 4px); }
    .fbtn-wrap:active .fbtn-corner--br{ transform:translate(4px, 4px); }

    /* Research variant — rose on dark plum (Eva slide) */
    .fbtn--research{
      color:#f0a8c4;
      box-shadow:inset 0 0 0 1px rgba(240,168,196,0.38);
      background:rgba(38,15,27,0.35);
    }
    .fbtn--research ~ .fbtn-corner{ border-color:rgba(240,168,196,0.55); }
    .fbtn-wrap:hover .fbtn--research{
      color:#f7bcd3;
      background:rgba(56,20,36,0.4);
      box-shadow:inset 0 0 0 1px rgba(244,184,208,0.5), 0 0 15px rgba(231,124,165,0.1);
    }
    .fbtn-wrap:hover .fbtn--research ~ .fbtn-corner{ border-color:rgba(240,168,196,0.75); }

    /* Lancer variant — gold on dark amber (autumn-tree slide) */
    .fbtn--lancer{
      color:#d9b178;
      box-shadow:inset 0 0 0 1px rgba(217,177,120,0.36);
      background:rgba(36,24,12,0.35);
    }
    .fbtn--lancer ~ .fbtn-corner{ border-color:rgba(217,177,120,0.5); }
    .fbtn-wrap:hover .fbtn--lancer{
      color:#e6c490;
      background:rgba(46,30,15,0.4);
      /* Ring only — the outer 15px amber halo was removed on purpose (no
         glow cast onto the backdrop). Don't re-add it. */
      box-shadow:inset 0 0 0 1px rgba(230,190,130,0.48);
    }
    .fbtn-wrap:hover .fbtn--lancer ~ .fbtn-corner{ border-color:rgba(217,177,120,0.7); }

    /* ═══════════════════════════════════════════════════════════
       Autumn Tree backdrop — merged from /demo/index.html.
       Sits inside .welcome at z-index 0 so it paints behind the
       welcome content (eyebrow, wordmark, tagline, search) and
       above the welcome's 60px grid. All transitions preserved
       from the source demo: warm glow breathing in, tree rising
       from below with blur, falling leaves with sway+spin, and
       a soft vignette + lifting veil.
       ═══════════════════════════════════════════════════════════ */
    .welcome .tree-glow,
    .welcome .tree-stage,
    .welcome .tree-vignette,
    .welcome .tree-veil{
      position:absolute;
      z-index:0;
      pointer-events:none;
    }

    /* Warm radial glow that breathes in behind the canopy */
    .welcome .tree-glow{
      inset:0;
      background:
        radial-gradient(ellipse 60% 50% at 38% 42%,
          rgba(255, 138, 60, 0.22) 0%,
          rgba(220, 90, 40, 0.10) 30%,
          rgba(0, 0, 0, 0) 65%);
      opacity:0;
    }
    .welcome .tree-glow{ transition:opacity 1200ms ease; }
    body.tree-ready #welcome .tree-glow{ opacity:1; }
    body.auth-open #welcome .tree-glow{ opacity:0; }
    /* only the RETURN from the auth scene waits for the roll — page load
       glows in right away (body.auth-return is set transiently by home.js) */
    body.auth-return:not(.auth-open) #welcome .tree-glow{ transition:opacity 900ms ease 1100ms; }
    #welcome-eva .tree-glow{ transition-delay:250ms; }
    body.auth-open #welcome-eva .tree-glow{ opacity:1; }

    /* Soft dark veil that lifts after the tree has settled */
    .welcome .tree-veil{
      inset:0;
      background:var(--bg-primary);
      z-index:3;
      opacity:1;
    }
    body.tree-ready .welcome .tree-veil{
      animation:tree-veil-lift 0.7s cubic-bezier(0.22, 1, 0.36, 1) 0s forwards;
    }

    /* Subtle radial vignette that eases in last, for depth */
    .welcome .tree-vignette{
      inset:0;
      background:radial-gradient(ellipse at center, rgba(0,0,0,0) 50%, rgba(0,0,0,0.55) 100%);
      opacity:0;
      z-index:2;
    }
    body.tree-ready .welcome .tree-vignette{
      animation:tree-vignette-in 0.9s ease-out 0.7s forwards;
    }

    .welcome .tree-stage{
      inset:0;
      background-image:url("/static/autumn/tree.png");
      background-repeat:no-repeat;
      background-position:center bottom;
      /* auto 95%, not contain: tree.png is 1.5:1 so contain ≈ full stage
         height on widescreen — 95% shrinks the tree 5% on every screen.
         The vw term caps height so width (h × 1.5) never exceeds the
         stage box — otherwise tall (F11) viewports hard-clip the image
         at the element edge instead of showing its own soft edge. */
      background-size:auto min(95%, 66.6vw);
      transform:translate(-13.5vw, 5.15vh);
      opacity:0;
    }
    body.tree-ready .welcome .tree-stage{
      animation:tree-in 1.2s cubic-bezier(0.22, 1, 0.36, 1) 0.2s both;
    }

    .welcome .tree-leaf{
      position:absolute;
      left:var(--x);
      top:var(--y);
      /* --size is in vh (calibrated: 1vh = 16px on a ~1625px-tall viewport,
         the 27" reference) so leaves scale with the tree, which is
         height-fit. The 25px floor stops dust-speck leaves in short windows. */
      width:calc(max(var(--size, 3.2vh), 25px) * 0.68);
      will-change:transform, opacity;
      opacity:0;
      pointer-events:none;
    }
    body.tree-ready .welcome .tree-leaf{
      animation:tree-leaf-fade-in 0.6s ease-out 1.2s both,
                tree-fall var(--dur, 14s) linear calc(var(--delay, 0s) + 1.2s) infinite;
    }
    .welcome .tree-leaf .tree-sway{
      animation:tree-sway var(--sway-dur, 5s) ease-in-out var(--sway-delay, 0s) infinite alternate;
    }
    .welcome .tree-leaf img{
      display:block;
      width:100%;
      height:auto;
      animation:tree-spin var(--spin-dur, 9s) linear var(--spin-delay, 0s) infinite;
      transform-origin:50% 50%;
      filter:drop-shadow(0 5px 7.5px rgba(0,0,0,0.4));
    }

    /* ─────────────── Warm light wash ───────────────
       Big soft warm light originating from the bottom-right
       corner, fanning across the welcome area to illuminate
       the autumn tree on the left. Uses `mix-blend-mode:
       screen` so the light reads as additive — the orange
       canopy glows where the wash reaches it, the dark
       background stays dark in the cold corners.
       Color matches --accent-warm and the foliage tones.
       ───────────────────────────────────────────────── */
    .welcome .welcome-light{
      position:absolute;
      inset:0;
      z-index:1;
      background:radial-gradient(ellipse 140% 130% at 96% 102%,
        rgba(255, 210, 140, 0.527) 0%,
        rgba(255, 180, 100, 0.408) 12%,
        rgba(255, 150, 70,  0.272) 26%,
        rgba(255, 120, 50,  0.170) 42%,
        rgba(255,  95, 35,  0.085) 60%,
        rgba(255,  80, 25,  0.034) 76%,
        rgba(0, 0, 0, 0)            90%);
      mix-blend-mode:screen;
      pointer-events:none;
      opacity:0;
    }
    .welcome .welcome-light{ transition:opacity 1200ms ease; }
    body.tree-ready #welcome .welcome-light{
      opacity:1;
      animation:welcome-light-breathe 7.4s ease-in-out 1.5s infinite;
    }
    body.auth-open #welcome .welcome-light{ opacity:0; }
    body.auth-return:not(.auth-open) #welcome .welcome-light{ transition:opacity 900ms ease 1100ms; }
    #welcome-eva .welcome-light{ transition-delay:250ms; }
    body.auth-open #welcome-eva .welcome-light{ opacity:1; }
    .welcome.hidden{display:none;}
    /* ═══════════════════════════════════════════════════════════
       Welcome wordmark — "CAVALRY[knight]"
         • Each letter rises through a mask reveal, staggered.
         • The knight is aligned with the cap height of the text and
           fades + rises in after the last letter — same rhythm as the
           tagline, no rotation gimmicks.
         • Hover the logo → plays on loop. Mouse out → current loop
           finishes, then the video pauses on its first frame again.
       ═══════════════════════════════════════════════════════════ */
    .welcome-wordmark{
      display:flex;align-items:center;justify-content:center;
      gap:0;
      margin-bottom:27.5px;
      max-width:100%;
    }
    .ch-text{
      display:inline-flex;
      flex-shrink:0;
      font-family:var(--font-display);
      /* Trimmed ~20% from the previous clamp(50px, 5.8vw, 110px).
         Smaller wordmark sits more comfortably in the upper-right
         pocket above the autumn canopy. */
      font-size:clamp(42.5px, 4.8vw, 90px);
      font-weight:700;
      letter-spacing:-0.045em;
      line-height:0.9;
      color:var(--text-primary);
      text-transform:uppercase;
      overflow:hidden;           /* clips each letter while it slides up */
      padding-bottom:0.08em;     /* descenders get a sliver of room */
      /* The negative tracking (-0.045em) is applied INSIDE each .ch-l span,
         so every span box is ~0.045em narrower than its glyph's ink. Mid-word
         that ink just overlaps the next span, but the last letter's overhang
         (Y's right arm) falls outside the box and gets clipped by the
         overflow mask — this padding gives it back. */
      padding-right:0.05em;
      /* Lift above the knight in the stacking order so the letters
         always paint on top if the video's negative margin overlaps. */
      position:relative;
      z-index:2;
    }
    .ch-l{
      display:inline-block;
      transform:translateY(110%);
      opacity:0;
      animation:ch-letter-in 720ms cubic-bezier(0.22, 0.8, 0.18, 1) forwards;
      animation-delay:calc(var(--i) * 55ms + 140ms);
    }
    .welcome-wordmark video{
      /* Sized ~1.6× the cap height of CAVALRY so the knight feels
         "tall" next to the letters. Floor and cap stepped down to
         match the trimmed wordmark size. */
      height:clamp(75px, 7.8vw, 155px);
      width:auto;max-width:30vw;
      flex-shrink:0;
      cursor:pointer;
      display:block;
      /* Knight sits tight against CAVALRY. The WebM has ~25.6%
         transparent left padding; the negative margin shaves part
         of that away so the visible knight body stays close to the
         text. Plain fade-in after the Y settles — no transform, so
         the later hover scale doesn't fight an animated transform. */
      margin-left:clamp(-40px, -1.4vw, -10px);
      position:relative;
      z-index:1;
      opacity:0;
      animation:knight-fade-in 600ms ease-out 580ms forwards;
    }
    .welcome-wordmark em{display:none;}
    /* The tagline ("Live market tracking and AI partner research…") was
       removed on owner request 2026-07-25. It used to carry the gap between
       the wordmark and the CTA row; .welcome-wordmark owns that gap now. */

    /* Welcome hero "Track Stock" — inline search + "Go to Tracking"
       CTA, in the same row. Both controls share the same height and
       a polished aesthetic: gradient surfaces, soft drop shadow,
       refined borders, smooth focus/hover.  Results dropdown still
       anchors directly below the input. */
    .track-stock-row{
      display:flex;
      gap:15px;
      width:min(675px, 88vw);
      align-items:stretch;
      opacity:0;
      animation:welcome-piece-in 720ms cubic-bezier(0.22, 1, 0.36, 1) 460ms forwards;
    }

    /* ═══════════════════════════════════════════════════════════
       MOBILE
    ═══════════════════════════════════════════════════════════ */
    @media(max-width:800px){
      :root{--sidebar-w:250px;}
      body{height:auto;min-height:100vh;overflow:auto;}
      .main{height:auto;min-height:100vh;overflow:auto;}}

    /* ═══════════════════════════════════════════════════════════
       Home hero — Tracking ⇄ Research, in place (no page travel).
       Both heroes occupy the SAME cell, stacked. Switching crossfades
       them; the incoming background pops up from below. Nothing slides
       sideways, so the layout never "moves".
       ═══════════════════════════════════════════════════════════ */
    .hero-track{
      flex:1;
      position:relative;
      min-height:0;
    }
    .hero-track{ overflow:hidden; }
    .hero-track > .welcome{ position:absolute; inset:0; width:100%; }
    /* The auth backdrop is a pure art layer under the tree scene. The square
       grid comes from the tree scene above it — painting it here too would
       double the line contrast (both scenes are always stacked now). */
    #welcome-eva{ pointer-events:none; background-image:none; }


    /* ───────────────────────────────────────────────────────────
       Consistent pop-up transition for BOTH slides.
       Whichever slide is .is-active plays the SAME entrance: its
       background(s), CAVALRY and logo rise up from below and fade in;
       the other slide fades out. JS sets .is-active on the INCOMING
       slide on every toggle (and on tracking at load), so the motion
       is identical in both directions.
       ─────────────────────────────────────────────────────────── */

    /* Backgrounds pop up via keyframe ANIMATIONS (not transitions) so the
       restart trick replays them on EVERY activation — identical both ways.
       animation:none !important on the base opts out of the old one-shot
       tree-in; the active rules re-assert with !important. */
    .welcome .tree-stage{ animation:none !important; opacity:0; }

    #welcome .tree-stage{
      transform:translate(-13.5vw, 120vh);
      transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
      will-change:transform;
    }
    /* Load entrance plays once; the settled pose is DECLARED (no fill) so the
       auth-open transition below can take the transform over afterwards. */
    #welcome.is-active .tree-stage{
      opacity:1;
      transform:translate(-13.5vw, 5.15vh);
      animation:tree-pop-track .9s cubic-bezier(0.22, 1, 0.36, 1) !important;
    }
    /* Auth open: the tree art rolls off to the RIGHT. */
    body.auth-open #welcome.is-active .tree-stage{
      transform:translate(100vw, 5.15vh);
    }
    /* The leaves ride with it on their own layer ABOVE everything — the auth
       sheet included (sheet z-index 301; leaves are the top layer by design). */
    .leaf-field{
      position:absolute; inset:0;
      z-index:400;
      pointer-events:none;
      /* SAME rest transform as #welcome .tree-stage — the leaves were born
         inside the stage and their --x/--y coords assume its offset */
      transform:translate(-13.5vw, 5.15vh);
      transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
      will-change:transform;
    }
    body.auth-open .leaf-field{ transform:translate(100vw, 5.15vh); }

    /* Eva: rose-bridge in front. */
    .welcome-eva .tree-stage{
      background-image:url("/static/rose/bg_bridge.png");
      /* bg_bridge.png is 2.01:1 — same width cap as the tree (h × 2.01
         ≤ stage width) so fullscreen never hard-clips the hill's right side */
      background-size:auto min(95%, 49.7vw);
      opacity:1;
      /* rest 20vw deeper off-screen than strictly needed — keeps daylight
         between the exiting tree and the incoming bridge mid-transition */
      transform:translate(-140vw, 25.15vh) scale(0.95);
      transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
      will-change:transform;
    }
    body.auth-open .welcome-eva .tree-stage{
      transform:translate(-20.5vw, 25.15vh) scale(0.95);
    }

    /* Eva: archer-tower behind the bridge, rises a beat later (+0.22s). */
    .welcome-eva .tree-back{
      position:absolute; inset:0; z-index:0;
      background-image:url("/static/rose/archer_tower_v2.png");
      background-repeat:no-repeat;
      /* Height-limited to ~70% of the panel and anchored higher (33%
         from the top, not bottom) so the tower rises clear of the
         bridge instead of hiding behind it. */
      background-position:center 33%;
      background-size:auto 70%;
      opacity:1;
      /* rest = target −119.5vw: the SAME travel as the bridge, so the two
         move as one rigid scene instead of feeling detached */
      transform:translate(-149.5vw, -2vh);
      transition:transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
      will-change:transform;
      pointer-events:none;
    }
    body.auth-open .welcome-eva .tree-back{
      transform:translate(-30vw, -2vh);
    }

    /* Rose petals on the air — each blows IN from off-screen left and drifts
       diagonally down toward the bottom. The field is a plain full-panel
       overlay (not the transformed bridge stage) so the path stays clean.
       Per-petal CSS vars (--x0/--y0 start, --drift/--fall end) shape each arc. */
    .welcome-eva .petal-field{
      /* top layer like the tree leaves (above the auth sheet, z 301) — the
         petals drift ACROSS the form panel, not behind it */
      position:absolute; inset:0; z-index:400;
      overflow:hidden; pointer-events:none;
      opacity:0;
      /* the petal field RIDES IN with the bridge (same travel/curve) instead
         of fading in place — anchored petals read as out of sync with the bg */
      transform:translateX(-119.5vw);
      transition:opacity 800ms ease 300ms,
                 transform 1400ms cubic-bezier(0.45, 0, 0.15, 1);
      will-change:transform;
    }
    body.auth-open .welcome-eva .petal-field{ opacity:1; transform:none; }
    /* petals are invisible while closed — don't burn frames animating them */
    body:not(.auth-open) .welcome-eva .petal{ animation-play-state:paused; }
    .welcome-eva .petal{
      position:absolute; top:var(--top, 0%); left:0;
      width:var(--size, 40px); height:var(--size, 40px);
      background:var(--img) center / contain no-repeat;
      opacity:0;
      will-change:transform, opacity;
      /* no drop-shadow: petals drift across the white auth panel, where a
         cast shadow reads as dirt */
      animation:petal-drift var(--dur, 15s) linear var(--delay, 0s) infinite;
    }

    /* Eva ambient light — soft spring rose-pink (no icy blue), dialed down so
       it doesn't wash out the centre of the hero. The amber autumn glow is
       untouched on the tracking slide. */
    .welcome-eva .welcome-light{
      background:radial-gradient(ellipse 135% 125% at 98% 104%,
        rgba(255, 176, 206, 0.34) 0%,
        rgba(255, 150, 190, 0.23) 16%,
        rgba(255, 135, 178, 0.13) 36%,
        rgba(250, 165, 200, 0.06) 58%,
        rgba(0, 0, 0, 0)          82%);
    }
    /* Central glow pulled WAY down (was the main source of mid-page wash). */
    .welcome-eva .tree-glow{
      background:radial-gradient(ellipse 52% 44% at 30% 48%,
        rgba(255, 178, 208, 0.07) 0%,
        rgba(255, 150, 190, 0.03) 38%,
        rgba(0, 0, 0, 0)          68%);
    }

    /* The research logo occupies the EXACT same box as the knight video so the
       lockup width — and therefore the CAVALRY position and the logo's size +
       position — is pixel-identical on both slides (seamless crossfade). The
       knight is height:clamp(60,7.8vw,124) width:auto on a 1.5:1 frame, i.e.
       width:clamp(90,11.7vw,186), with the same negative tuck. The square logo
       is centred inside that box via object-fit. */
    .welcome-wordmark .brand-logo{
      height:clamp(75px, 7.8vw, 155px);
      width:clamp(112.5px, 11.7vw, 232.5px);
      object-fit:contain;
      object-position:center;
      margin-left:clamp(-40px, -1.4vw, -10px);
      flex-shrink:0; display:block;
      position:relative; z-index:1;
      opacity:0;
    }

    /* Content hidden by default; the active slide rises + fades it in. These
       selectors are more specific than the original load-time rules, so they
       win for BOTH slides. */
    .welcome .welcome-eyebrow,
    .welcome .track-stock-row,
    .welcome .welcome-wordmark video,
    .welcome .welcome-wordmark .brand-logo{ animation:none; opacity:0; }
    .welcome .ch-l{ animation:none; opacity:0; transform:translateY(110%); }
    .welcome.is-active .ch-l{
      animation:ch-letter-in 720ms cubic-bezier(0.22, 0.8, 0.18, 1) forwards;
      animation-delay:calc(var(--i) * 55ms + 140ms);
    }
    /* The knight AND the research logo pop up from below (was a plain fade) so
       the logo swap matches CAVALRY + the background motion. */
    .welcome.is-active .welcome-wordmark video,
    .welcome.is-active .welcome-wordmark .brand-logo{
      animation:ch-logo-in 620ms cubic-bezier(0.22, 1, 0.36, 1) 540ms forwards;
    }
    .welcome.is-active .track-stock-row{
      animation:welcome-piece-in 720ms cubic-bezier(0.22, 1, 0.36, 1) 460ms forwards;
    }


/* ═══════════════════════════════════════════════════════════
   WELCOME HERO — RESPONSIVE
   Goal: every hero piece (name, logo, tagline, search row, CTA) must
   shrink FASTER than the viewport so on a small window they read
   small — proportional, not desktop-sized.

   Each tier uses a LOW vw coefficient + a low cap, so the value tracks
   the window tightly and never balloons. The 1366 tier catches laptops
   (which sit above the old 1000 cut and were getting full desktop size).
   Selectors are double-class (.welcome .x) so they beat the single-class
   base rules. Sizes step DOWN monotonically across the tiers. */

/* KEY TRICK: every tier uses the SAME vw coefficient (ch 4.0vw, logo 6.5vw,
   logo-width 9.75vw). Only the cap (upper clamp) and floor (lower clamp)
   step down per tier. That makes each property a SINGLE continuous line as
   the window shrinks — it can only get smaller, never bumps back up at a
   breakpoint (the bug before, where a steeper per-tier coefficient made the
   text grow again after crossing a breakpoint). */

/* Laptops / small desktops — start shrinking before the layout flips.
   MUST shrink the CONTROLS too (eyebrow, search field, input, button),
   not just the name/logo — laptops sit in this 1000–1366 band and were
   keeping full-desktop controls while only the wordmark scaled. */
@media screen and (max-width:1708px){
    .welcome .ch-text{ font-size:clamp(37.5px, 4.0vw, 70px); }
    .welcome .welcome-wordmark video,
    .welcome .welcome-wordmark .brand-logo{ height:clamp(60px, 6.5vw, 120px); }
    .welcome .welcome-wordmark .brand-logo{ width:clamp(90px, 9.75vw, 180px); }
    .welcome .track-stock-row{ width:min(600px, 42vw); }}

/* ── WHY THE LAYOUT TIERS BELOW SPLIT ON *SHAPE*, NOT ON WIDTH ──────────
   Browser zoom does not resize the window; it shrinks the CSS viewport by
   the zoom factor. 125% on a 1536px laptop reports 1229 CSS px, 200%
   reports 768 — so a plain `max-width` breakpoint fires on ZOOM and hands
   a desk-monitor user the tablet layout. That is the "everything smooshes
   into the middle" bug: crossing 1250px flipped the hero to the centred
   column, which parks the wordmark and the CTA on top of the canopy while
   the right half of the window sits empty.

   Zoom scales width and height by the SAME factor, so the window's aspect
   ratio is the one dimension zoom cannot move — which makes it the correct
   trigger for anything that is really a question of window SHAPE.

   The threshold is not a magic number: it is the artwork's own crossover.
   `.tree-stage` is sized `auto min(95%, 66.6vw)`, so the tree is height-fit
   (and therefore leaves a pocket to its right that grows with the aspect
   ratio) while 0.95H < 0.666W, i.e. while W/H > 1.426. Below that the tree
   is width-fit, spans two thirds of the window whatever the height, and no
   side pocket exists — which is exactly when the centred column is right.

   So: the LAYOUT flips (justify-content, column width, gutters, margins)
   are gated on `max-aspect-ratio:1426/1000`; the TYPE-SIZE steps stay on
   plain width, because those are about absolute legibility in a small
   window and step down monotonically as before. */

/* Laptops (effective 1280–1680 with OS display scaling) — the artwork
   is height-fit and reaches further right relative to the narrower
   window, so the content column hugs the right edge harder: tighter
   right gutter + narrower column = less overlap with the tree/bridge.
   No lower bound: a landscape window narrower than this (a zoomed laptop)
   keeps the same right-hand composition, just smaller. */
@media screen and (max-width:2100px){
    .hero-track .welcome{ padding-right:clamp(20px, 1.8vw, 40px); }
    .welcome .welcome-content{ max-width:min(750px, 41vw); }
    }

/* Landscape window under the tablet cut = a ZOOMED laptop, not a tablet.
   The composition above carries it; only the vertical rhythm needs help,
   because the base 215px top-margin floor is a fixed pixel value the zoom
   keeps shrinking the window away from — at 768x432 it parks the stack in
   the bottom half. The artwork is height-fit here, so plain vh (the middle
   term of that same clamp) already tracks it. */
@media screen and (max-width:1250px) and (min-aspect-ratio:1426/1000){
    .welcome .welcome-content{ margin-top:26vh; }}

/* Tablet / narrow window — flip to a CENTRED column so nothing spills
   over the artwork and the controls fit their parent. */
@media screen and (max-width:1250px) and (max-aspect-ratio:1426/1000){
    .hero-track .welcome{ justify-content:center; padding:0 30px; }
    .welcome .welcome-content{
      max-width:min(650px, 88vw);
      width:100%;
      margin-top:clamp(110px, 15vh, 187.5px);
    }
    .welcome .track-stock-row{ width:100%; max-width:575px; }}

/* Type steps for the same band. Caps drop; the 4.0vw line continues
   unbroken from the tier above. Width-only on purpose — a small window is
   a small window whether it got there by size or by zoom. */
@media screen and (max-width:1250px){
    .welcome .ch-text{ font-size:clamp(30px, 4.0vw, 50px); }
    .welcome .welcome-wordmark video,
    .welcome .welcome-wordmark .brand-logo{
      height:clamp(50px, 6.5vw, 81px);
      margin-left:clamp(-22.5px, -1vw, -7.5px);
    }
    .welcome .welcome-wordmark .brand-logo{ width:clamp(75px, 9.75vw, 122.5px); }}

/* Phones — stack the search field above the CTA. Layout half: shape-gated,
   same reasoning as the tier above. */
@media screen and (max-width:775px) and (max-aspect-ratio:1426/1000){
    .welcome .welcome-content{
      max-width:none;
      margin-top:clamp(70px, 11vh, 130px);
      padding:10px 5px;
    }
    .welcome .track-stock-row{ width:min(425px, 90vw); flex-direction:column; gap:12.5px; align-items:center; }
    /* Shrink + lift the tower a touch more on phones. */
    .welcome-eva .tree-back{ background-size:auto 60%; background-position:center 30%; }}

/* Phones — smaller still; type half. */
@media screen and (max-width:775px){
    .welcome .ch-text{ font-size:clamp(25px, 4.0vw, 32.5px); }
    .welcome .welcome-wordmark video,
    .welcome .welcome-wordmark .brand-logo{ height:clamp(37.5px, 6.5vw, 50px); }
    .welcome .welcome-wordmark .brand-logo{ width:clamp(56px, 9.75vw, 75px); }}

/* Tiny phones. */
@media screen and (max-width:500px){
    /* Header pills don't scale down with the hero on phones — compact them
       so they never collide. */
    .home-account{ top:16px; right:16px; gap:7px; }
    /* Flow buttons keep their mechanic, just tighter: the arrows need less
       runway, so the label's slide comes down with the padding. (Same --ha-u
       multiples as the base rules — at this width the unit is already at its
       16px floor, so these only trim the horizontal runway.) */
    .flow-btn{ padding:calc(0.5 * var(--ha-u)) calc(1.25 * var(--ha-u)); }
    .fb-label{ transform:translateX(calc(-0.5 * var(--ha-u))); }
    .flow-btn:hover .fb-label{ transform:translateX(calc(0.5 * var(--ha-u))); }
    .fb-arrow-out{ right:calc(0.625 * var(--ha-u)); }
    .flow-btn:hover .fb-arrow-in{ left:calc(0.625 * var(--ha-u)); }
    .welcome .ch-text{ font-size:clamp(22.5px, 4.5vw, 25px); }
    .welcome .welcome-wordmark video,
    .welcome .welcome-wordmark .brand-logo{ height:clamp(35px, 7vw, 37.5px); }
    .welcome .welcome-wordmark .brand-logo{ width:clamp(52.5px, 10.5vw, 56px); }
    }

/* ── @keyframes used by the rules above ── */


    /* New unified slide-fade transition for "the rest" (eyebrow,
       knight video, tagline, search). The CAVALRY letter cascade
       (.ch-text .ch-l → @keyframes ch-letter-in) is untouched. */
    @keyframes welcome-piece-in{
      0%   {opacity:0; transform:translateX(70px);}
      100% {opacity:1; transform:translateX(0);}
    }
    @keyframes tree-veil-lift{
      0%   {opacity:1;}
      100% {opacity:0;}
    }
    @keyframes tree-vignette-in{
      to{opacity:1;}
    }
    @keyframes tree-in{
      0%{
        opacity:0;
        filter:blur(12.5px) brightness(0.5);
        transform:translate(-13.5vw, 20vh) scale(1);
      }
      25%{
        opacity:1;
        filter:blur(2.5px) brightness(0.9);
      }
      100%{
        opacity:1;
        filter:blur(0) brightness(1);
        transform:translate(-13.5vw, 5.15vh) scale(1);
      }
    }
    @keyframes tree-leaf-fade-in{
      from{opacity:0;}
      to  {opacity:1;}
    }
    @keyframes tree-fall{
      0%   {transform:translate3d(0, 0, 0);                                                opacity:0;}
      6%   {opacity:var(--peak, 0.95);}
      85%  {opacity:var(--peak, 0.95);}
      100% {transform:translate3d(calc(var(--wind, 9vw) * 1.3), var(--fall, 70vh), 0);     opacity:0;}
    }
    @keyframes tree-sway{
      from{transform:translateX(calc(var(--sway-amp, 35px) * -1));}
      to  {transform:translateX(var(--sway-amp, 35px));}
    }
    @keyframes tree-spin{
      from{transform:rotate(var(--rot-from, -12deg));}
      to  {transform:rotate(var(--rot-to, 348deg));}
    }
    /* scale-only: opacity belongs to the crossfade transition now */
    @keyframes welcome-light-breathe{
      0%,100%{transform:scale(1);   }
      50%    {transform:scale(1.02);}
    }
    @keyframes ch-letter-in{
      0%  {transform:translateY(110%);opacity:0;}
      60% {opacity:1;}
      100%{transform:translateY(0);    opacity:1;}
    }
    @keyframes knight-fade-in{
      0%  {opacity:0;}
      100%{opacity:1;}
    }
    @keyframes ch-logo-in{
      0%  {transform:translateY(30px);opacity:0;}
      100%{transform:translateY(0);    opacity:1;}
    }
    @keyframes tree-pop-track{
      0%  { opacity:0; transform:translate(-13.5vw, 120vh); }
      100%{ opacity:1; transform:translate(-13.5vw, 5.15vh); }
    }
    @keyframes petal-drift{
      0%   { transform:translate3d(var(--x0, -14vw), var(--y0, -6vh), 0) rotate(0deg); opacity:0; }
      8%   { opacity:var(--peak, 0.92); }
      88%  { opacity:var(--peak, 0.92); }
      100% { transform:translate3d(var(--drift, 45vw), var(--fall, 104vh), 0) rotate(var(--spin, 210deg)); opacity:0; }
    }



/* ── Auth sheet (Log In / Join Cavalry) ──────────────────────────────────
   The 21st.dev animated-characters-login-page FORM PANEL as a right-side
   sheet (~40vw). The page never moves: body.auth-open rolls the tree art
   out right, rolls the rose bridge/tower art in from the left (rules live
   with the scene styles above), crossfades the amber glow to rose, and
   slides this sheet in. px values follow this file's ×1.25 scale. */
body{ --ap-w:max(525px, 40vw); }
/* Invisible click-catcher: outside-click closes WITHOUT dimming the hero. */
.auth-scrim{
  position:fixed; inset:0; z-index:300;
  background:transparent;
  pointer-events:none;
}
.auth-scrim.open{ pointer-events:auto; }
/* The hero content (wordmark/tagline/ENTER) steps aside while the sheet is
   out and returns on close. */
.welcome-content{
  /* return leg: wait out the art roll (1400ms x ~90%), then ease back in */
  transition:opacity 500ms ease 1250ms,
             transform 600ms cubic-bezier(0.22, 1, 0.36, 1) 1250ms;
}
body.auth-open .welcome-content{
  opacity:0;
  transform:translateX(-75px);
  pointer-events:none;
  /* exit leg: step aside immediately */
  transition:opacity 260ms ease, transform 320ms cubic-bezier(0.33, 0.66, 0.08, 1);
}
/* ── Instant open (EVERY panel route: /login, /signup, /plans) ──────────
   body.ap-instant is stamped SERVER-SIDE alongside auth-open (home.html's
   body_class block), so the rose scene and the sheet are already in their
   settled pose at first paint. This kills every transition and entrance
   animation that would otherwise play them into place: reload /login and you
   land ON the login scene, never on the autumn tree rolling away.

   Extended from /plans to all three on 2026-07-25. The old comment here said
   /login and /signup "are reached from the hero, where the roll is the whole
   point" — true of the CLICK from the hero, which is a JS openPanel() on /home
   and still animates because /home is not a panel route. It was never true of
   a reload or a deep link, which had no hero to roll away from and played the
   tree's 0.9s tree-pop-track entrance over the scene the sheet had already
   replaced, then snapped it off-screen when the animation ended.

   NOTE the specificity contract: the dashboard→/plans arrival deliberately
   OUTRANKS these rules (html.plans-arrive.plans-arrive-go …, at the foot of
   this file) so that one journey still rolls in. Keep any new ap-instant rule
   at this weight or lighter, or that arrival goes instant too. */
body.ap-instant #welcome .tree-stage,
body.ap-instant .leaf-field,
body.ap-instant .welcome-eva .tree-stage,
body.ap-instant .welcome-eva .tree-back,
body.ap-instant .welcome-eva .petal-field,
body.ap-instant #welcome .tree-glow,
body.ap-instant #welcome-eva .tree-glow,
body.ap-instant #welcome .welcome-light,
body.ap-instant #welcome-eva .welcome-light,
body.ap-instant .welcome-content,
body.ap-instant .home-account,
body.ap-instant .home-footer,
body.ap-instant .auth-scrim,
body.ap-instant .auth-panel{ transition:none !important; }
/* The tree's load entrance would animate transform and fight the rolled-away
   pose for its 0.9s, then snap when it ended. It has nothing to enter for here.
   Outranks `#welcome.is-active .tree-stage{animation:… !important}` on
   specificity (id+2 classes+1 type beats id+2 classes); home.js pins the same
   thing inline as well, for the frames after ap-instant is released. */
body.ap-instant #welcome .tree-stage,
body.ap-instant .leaf-field{ animation:none !important; }
/* Belt and braces on top of the server-stamped `open`: even if the class were
   missing, a panel route must never paint the sheet off-screen. */
body.ap-instant .auth-panel{ transform:none; }

/* Top-right pills bow out while the sheet is up. */
.home-account{ transition:opacity 260ms ease; }
body.auth-open .home-account{ opacity:0; pointer-events:none; }
/* The page's Privacy · Terms footer goes with it — the panel carries its own
   pair under the sign-in buttons (.ap-legal), so they'd otherwise show twice. */
.home-footer{ transition:opacity 260ms ease; }
body.auth-open .home-footer{ opacity:0; pointer-events:none; }

.auth-panel{
  position:fixed; top:0; right:0; bottom:0; z-index:301;
  width:var(--ap-w);
  background:#ffffff;
  /* flex-start + auto margins on .ap-col, NOT align-items:center: centering a
     scroll container clips the TOP of any child taller than the panel, which
     put the plans step's heading permanently out of reach. Auto margins centre
     when there's room and top-align when there isn't. */
  display:flex; align-items:flex-start; justify-content:center;
  transform:translateX(103%);
  /* closing: prompt, no delay */
  transition:transform 420ms cubic-bezier(0.33, 0.66, 0.08, 1);
  overflow-y:auto;
}
.auth-panel.open{
  transform:none;
  /* opening: waits until the art roll is almost settled, then eases in */
  transition:transform 750ms cubic-bezier(0.22, 1, 0.36, 1) 1000ms;
}
@media (max-width:800px){ .auth-panel{ width:100%; } }
@media (prefers-reduced-motion: reduce){
  .auth-panel{ transition-duration:0.01ms; }
  .auth-scrim{ transition-duration:0.01ms; }
  #welcome .tree-stage, .welcome-eva .tree-stage, .welcome-eva .tree-back,
  .leaf-field{ transition-duration:0.01ms; }
}

/* ── Panel scale (login / signup / username / plans / payment) ──────────
   Everything inside the sheet is authored in flat px at home's 1.25x scale.
   Fixed px looks right on the 27" reference (~3072px CSS wide) and oversized
   on a 1400px laptop, where the same 60px inputs and 37px headings swallow
   the column. So the CONTENT is scaled by a factor that tracks the viewport
   and every hand-tuned px value below stays exactly as authored:

     ≥3072px (the 27")  → 1.00   ← reference renders unchanged
     2560px             → 0.91
     1920px             → 0.80
     ≤1440px (laptops)  → 0.72

   `zoom`, not `transform:scale`, so the column genuinely re-lays-out: text
   rasterises crisply, hit targets stay honest, and the sheet's scroll height
   follows. The factor must be a unitless NUMBER, which calc() can't distil
   from a vw length — `tan(atan2(<length>, 16px))` performs exactly that
   division (baseline CSS since 2023). A browser that can't parse it drops
   `zoom` and renders at 1: today's behaviour, so the failure mode is benign.
   The scrim/sheet geometry (--ap-w) is deliberately NOT scaled — the white
   sheet keeps its 40vw relationship with the artwork. */
.auth-panel{ --ap-z:tan(atan2(clamp(11.5px, calc(7.5px + 0.276vw), 16px), 16px)); }
.ap-col,.ap-close{ zoom:var(--ap-z); }

.ap-close{
  position:absolute; top:20px; right:24px;
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px;
  background:transparent; border:none; border-radius:10px;
  color:#9ca3af; cursor:pointer;
  transition:color 120ms ease, background 120ms ease;
}
.ap-close:hover{ color:#111827; background:#f3f4f6; }

.ap-col{ width:100%; max-width:525px; padding:50px 45px; margin:auto 0; }

.ap-title{
  text-align:center;
  font-family:'Space Grotesk','Inter',sans-serif;
  font-size:37px; font-weight:700; letter-spacing:-0.025em;
  color:#111827; margin:0 0 10px;
}
.ap-sub{
  text-align:center;
  font-size:17px; color:#6b7280;
  margin:0 0 40px;
}

.ap-form{ display:flex; flex-direction:column; gap:25px; }
.ap-field{ display:flex; flex-direction:column; gap:10px; }
.ap-field label{ font-size:17px; font-weight:500; color:#374151; }
.ap-field input{
  height:60px; padding:0 18px; width:100%;
  background:#fff; color:#111827;
  border:1px solid #d1d5db; border-radius:10px;
  font-family:inherit; font-size:19px;
  transition:border-color 120ms ease;
}
.ap-field input::placeholder{ color:#9ca3af; }
.ap-field input:focus{ outline:none; border-color:#000; }

/* Password reveal — a ghost icon button parked inside the field's right edge.
   The input reserves the room with padding-right so a long password never
   runs under the icon. Plain eye = hidden ("show me"); slashed eye = the
   password is on screen. */
.ap-input-wrap{ position:relative; display:flex; }
.ap-input-wrap input{ padding-right:56px; }
.ap-eye{
  position:absolute; top:50%; right:8px; transform:translateY(-50%);
  display:inline-flex; align-items:center; justify-content:center;
  width:44px; height:44px; padding:0;
  background:transparent; border:none; border-radius:8px;
  color:#9ca3af; cursor:pointer;
  transition:color 120ms ease, background 120ms ease;
}
.ap-eye svg{ width:25px; height:25px; }
.ap-eye:hover{ color:#111827; background:#f3f4f6; }
.ap-eye:focus-visible{ outline:2px solid #000; outline-offset:2px; }
.ap-eye.on{ color:#111827; }
.ap-eye .ap-eye-slash{ display:none; }
.ap-eye.on .ap-eye-slash{ display:block; }

.ap-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:12px;
  width:100%; height:60px; border-radius:10px;
  font-family:inherit; font-size:20px; font-weight:500;
  cursor:pointer; text-decoration:none;
  transition:background 120ms ease, border-color 120ms ease, transform 80ms ease;
}
.ap-btn:active{ transform:translateY(1px); }
.ap-btn-primary{ background:#000; color:#fff; border:1px solid #000; }
.ap-btn-primary:hover{ background:#1f2937; border-color:#1f2937; }
.ap-btn-outline{
  margin-top:30px;
  background:#fff; color:#111827; border:1px solid #d1d5db;
}
.ap-btn-outline:hover{ background:#f3f4f6; }
.ap-btn-outline svg{ width:25px; height:25px; flex-shrink:0; }

.ap-foot{
  margin-top:40px; text-align:center;
  font-size:17px; color:#4b5563;
}
.ap-switch{
  background:none; border:none; padding:0;
  font-family:inherit; font-size:17px; font-weight:500;
  color:#000; cursor:pointer;
}
.ap-switch:hover{ text-decoration:underline; }

/* Legal line — the page footer's Privacy · Terms, relocated into the panel
   under the sign-in buttons (the page copy fades out on body.auth-open).
   New tab on purpose: following them in place would throw away whatever the
   user has already typed into the form. */
.ap-legal{
  margin-top:14px; text-align:center;
  font-size:15px; color:#6b7280;
}
.ap-legal a{ color:inherit; text-decoration:none; }
.ap-legal a:hover{ color:#111827; text-decoration:underline; }
.ap-legal-sep{ margin:0 6px; opacity:.6; }

/* ── Auth panel: multi-step flow (check / plans / payment) ──────────────── */
.ap-label-row{
  display:flex; align-items:baseline; justify-content:space-between;
}
.ap-mini-link{
  background:none; border:none; padding:0;
  font-family:inherit; font-size:15px; font-weight:500;
  color:#6b7280; cursor:pointer;
}
.ap-mini-link:hover{ color:#111827; text-decoration:underline; }

.ap-error{
  padding:12px 15px;
  background:#fef2f2; border:1px solid #fecaca; border-radius:10px;
  color:#dc2626; font-size:16px; line-height:1.45;
}

.ap-center{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center;
}

/* Green check: ring sweeps, then the mark draws — pure CSS, replays each
   time the step is shown because the [hidden] toggle re-triggers it. */
.ap-check{ width:90px; height:90px; }
.ap-check-ring{
  stroke:#22c55e; stroke-width:2.5;
  stroke-dasharray:151; stroke-dashoffset:151;
  animation:ap-ring 0.55s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
.ap-check-mark{
  stroke:#22c55e; stroke-width:3.5; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:36; stroke-dashoffset:36;
  animation:ap-mark 0.35s cubic-bezier(0.65, 0, 0.45, 1) 0.5s forwards;
}
@keyframes ap-ring{ to{ stroke-dashoffset:0; } }
@keyframes ap-mark{ to{ stroke-dashoffset:0; } }
.ap-check-title{
  font-family:'Space Grotesk','Inter',sans-serif;
  font-size:30px; font-weight:700; letter-spacing:-0.02em;
  color:#111827; margin:22px 0 8px;
}
.ap-check-sub{ font-size:17px; color:#6b7280; margin:0; }

/* ── Plans step (/plans) ────────────────────────────────────────────────
   Cards from 21st.dev @uilayout.contact/growth-plans, switch + comparison
   table from @uniquesonu/interactive-pricing-component, both rendered in the
   sheet's LIGHT palette — which is the components' native palette, so this is
   the straight port: grey card for Squire, black featured card for Knight.

   LAYOUT (owner call 2026-07-25): the two cards sit SIDE BY SIDE — the
   component's own lg layout, not the stacked below-lg one this started as.
   The sheet is a narrow column (--ap-w = max(525px, 40vw)), so the step has
   to earn the room: it widens to 690px and trades 15px of its side padding
   for card width. 690px is the ceiling the TIGHTEST sheet allows, not a taste
   call — below ~1312px viewport --ap-w bottoms out at 525px and the panel's
   zoom:var(--ap-z) bottoms out at 0.71875, so the column can be at most
   525/0.71875 ≈ 730 authored px, less a scrollbar's worth. The featured
   card's scale-up stays dropped: inside a column this narrow it would collide
   with its neighbour.

   px values are authored at home's ×1.25 scale like the rest of the panel. */
.ap-col-plans{ max-width:690px; padding:56px 30px 50px; }
.ap-col-plans .ap-title{ font-size:31px; }
.ap-col-plans .ap-sub{ margin-bottom:24px; }

/* Segmented switch (inline-flex bg-muted p-1 rounded-lg) */
.pl-switch{
  display:flex; align-items:center; gap:2px;
  width:fit-content; margin:0 auto 28px;
  padding:5px;
  background:#f3f4f6;
  border:1px solid #e5e7eb;
  border-radius:12px;
}
.pl-switch-btn{
  padding:9px 18px;
  border:none; border-radius:8px;
  background:transparent;
  color:#6b7280;
  font-family:inherit; font-size:16px; font-weight:500;
  cursor:pointer;
  transition:background 160ms ease, color 160ms ease, box-shadow 160ms ease;
}
.pl-switch-btn:hover{ color:#111827; }
.pl-switch-btn:focus{ outline:none; }
.pl-switch-btn:focus-visible{ box-shadow:0 0 0 2px #111827; }
.pl-switch-btn.is-on{
  background:#ffffff; color:#111827;
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

/* ── The two views share ONE grid cell ──────────────────────────────────
   Bug fix 2026-07-25: switching Pricing ⇄ Compare Features must not move the
   heading, the subtitle or the switch by a single pixel. Both panels are
   placed in the SAME 1×1 grid cell, so this wrapper is permanently as tall as
   the TALLER of the two and the swap changes no geometry whatsoever: nothing
   above it can reflow, and .ap-col's `margin:auto 0` centring has nothing to
   re-resolve. (A wrapper that ANIMATED its height would keep re-centring the
   column for the length of the animation and drag the heading along with it —
   which is why the reserved-height version is the one that ships.)

   Reserving the taller height costs dead space under the shorter panel, and
   that is only affordable now: side by side the cards and the table land
   within ~60px of each other, where stacked the cards were twice the table.

   The off panel must keep its box — `hidden` / `display:none` would collapse
   it out of the cell and hand the height back to whichever panel is on — so
   it is put out of reach the other way: visibility:hidden (out of the a11y
   tree AND out of the tab order) plus pointer-events:none, with `inert`
   stamped by home.js for the length of the fade. visibility itself waits out
   the transition, otherwise there would be nothing left to fade. */
.pl-views{ display:grid; align-items:start; }
.pl-view{
  grid-area:1 / 1; min-width:0;
  transition:opacity 220ms ease, transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
}
.pl-view.is-off{
  opacity:0; visibility:hidden; pointer-events:none;
  /* fades out in place, then silently drops to the incoming start pose */
  transform:translateY(10px);
  transition:opacity 160ms ease,
             transform 0ms linear 160ms,
             visibility 0ms linear 160ms;
}

/* Cards row: two columns over ONE shared set of row tracks ──────────────
   The cards carry unequal feature counts (Squire 3, Knight 7), so side by
   side their landmarks would otherwise float at different heights. Each card
   spans every row of the parent and re-uses those tracks with
   `grid-template-rows:subgrid`, which puts BOTH cards' four children — head,
   price, CTA, features — on the SAME four tracks: every track is as tall as
   the taller card needs, so the price rows and the buttons line up across
   both cards by construction and both cards are the same height, with no
   per-card measurement or magic number. row-gap is 0 deliberately: the rhythm
   inside a card stays the margins below, which is also what the stacked
   fallback keeps using. A browser without subgrid drops that one declaration
   and gets plain auto rows — landmarks stop aligning, nothing breaks. */
.pl-cards{
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  grid-template-rows:auto auto auto 1fr;
  column-gap:20px; row-gap:0;
}

.pl-compare{ display:flex; flex-direction:column; gap:20px; }

/* Card (rounded-lg p-8 flex flex-col border) */
.pl-card{
  grid-row:1 / -1;
  display:grid; grid-template-rows:subgrid;
  padding:26px;
  border:1px solid #e5e7eb; border-radius:14px;
  background:#f9fafb;
  text-align:left;
}
/* featured: bg-foreground text-background — the component's inversion */
.pl-card-featured{
  background:#111827;
  border-color:transparent;
  box-shadow:0 25px 50px -12px rgba(0,0,0,0.25);
}

.pl-card-head{ margin-bottom:24px; }
.pl-name{
  font-family:'Space Grotesk','Inter',sans-serif;
  font-size:22px; font-weight:700; color:#111827;
  letter-spacing:-0.01em; margin:0;
}
.pl-desc{ font-size:16px; color:#6b7280; line-height:1.4; margin:4px 0 0; }
.pl-card-featured .pl-name{ color:#ffffff; }
.pl-card-featured .pl-desc{ color:rgba(255,255,255,0.6); }

/* Price (flex items-baseline gap-1) */
.pl-price-row{ display:flex; align-items:baseline; gap:4px; margin-bottom:24px; }
.pl-cur{ font-size:28px; font-weight:500; color:#6b7280; }
.pl-amount{
  font-family:'Space Grotesk','Inter',sans-serif;
  font-size:56px; font-weight:700; line-height:1;
  color:#111827; letter-spacing:-0.03em;
  font-variant-numeric:tabular-nums;
}
.pl-per{ font-size:16px; color:#6b7280; }
.pl-card-featured .pl-cur,
.pl-card-featured .pl-per{ color:rgba(255,255,255,0.6); }
.pl-card-featured .pl-amount{ color:#ffffff; }

/* CTA (w-full h-14 rounded-lg mb-10) */
.pl-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:100%; height:64px;
  margin-bottom:30px;
  border:1px solid #e5e7eb; border-radius:10px;
  background:#ffffff; color:#111827;
  font-family:inherit; font-size:17px; font-weight:600;
  cursor:pointer;
  transition:background 180ms ease, box-shadow 180ms ease;
}
.pl-btn:hover{ background:#f3f4f6; box-shadow:0 10px 24px -8px rgba(0,0,0,0.18); }
.pl-btn:focus{ outline:none; }
.pl-btn:focus-visible{ box-shadow:0 0 0 3px rgba(17,24,39,0.25); }
/* featured CTA: a translucent well inside the inverted card */
.pl-card-featured .pl-btn{
  background:rgba(255,255,255,0.10);
  border-color:rgba(255,255,255,0.20);
  color:#ffffff;
}
.pl-card-featured .pl-btn:hover{ background:rgba(255,255,255,0.20); box-shadow:none; }

/* Features (space-y-4 pt-8 border-t) */
.pl-features{
  list-style:none; margin:0; padding:24px 0 0;
  border-top:1px solid #e5e7eb;
  display:flex; flex-direction:column; gap:14px;
}
.pl-card-featured .pl-features{ border-top-color:rgba(255,255,255,0.20); }
.pl-feat{
  display:flex; align-items:center; gap:12px;
  font-size:16px; color:#4b5563; line-height:1.4;
}
.pl-card-featured .pl-feat{ color:rgba(255,255,255,0.8); }
.pl-check{ flex-shrink:0; color:currentColor; }

/* Comparison table */
.pl-compare-scroll{ overflow-x:auto; }
.pl-table{ width:100%; border-collapse:collapse; }
.pl-table th, .pl-table td{ padding:14px 10px; }
.pl-table thead tr{ border-bottom:1px solid #e5e7eb; }
.pl-th-label{ text-align:left; font-size:16px; font-weight:600; color:#111827; }
.pl-table thead th:not(.pl-th-label){ min-width:96px; text-align:center; }
.pl-th-name{ display:block; font-size:15px; font-weight:600; color:#111827; }
.pl-th-price{
  display:block;
  font-family:'Space Grotesk','Inter',sans-serif;
  font-size:24px; font-weight:700; color:#111827;
  letter-spacing:-0.02em; font-variant-numeric:tabular-nums;
}
.pl-th-per{ display:block; font-size:13px; color:#6b7280; }
.pl-table tbody tr{
  border-bottom:1px solid #f3f4f6;
  transition:background 160ms ease;
}
.pl-table tbody tr:hover{ background:#f9fafb; }
.pl-td-label{ font-size:15px; font-weight:500; color:#111827; }
.pl-td-mark{ text-align:center; }
.pl-td-mark svg{ display:inline-block; vertical-align:middle; }
.pl-yes{ color:#16a34a; }
.pl-no{ color:#9ca3af; }

.pl-btn-featured{
  background:#111827; border-color:#111827; color:#ffffff;
}
.pl-btn-featured:hover{ background:#1f2937; box-shadow:none; }
.pl-compare-cta{ margin-top:28px; margin-bottom:0; }

/* ── CTA states (2026-07-26) ───────────────────────────────────────────────
   The card that matches the viewer's own tier is a STATEMENT, not an offer, so
   it stops looking like a button you press: flat, dashed, no hover lift, no
   pointer. Same box and same height either way, so switching accounts never
   moves the column.

   These rules come AFTER .pl-btn-featured and its :hover on purpose — the
   compare-view CTA carries that class, and an attribute selector only outranks
   it here by ordering, not by specificity, for the :hover pair.

   `disabled` is what makes them unpressable; NOT pointer-events:none, which
   would take them out of hit testing and make a reachable control
   indistinguishable from an unreachable one. */
.pl-btn[data-plan-state="current"],
.pl-btn[data-plan-state="included"]{
  cursor:default;
  background:transparent;
  border-style:dashed;
  border-color:#d1d5db;
  color:#6b7280;
  box-shadow:none;
}
.pl-btn[data-plan-state="current"]:hover,
.pl-btn[data-plan-state="included"]:hover{
  background:transparent; box-shadow:none;
}
/* the inverted (featured) card needs the light-on-dark version */
.pl-card-featured .pl-btn[data-plan-state="current"],
.pl-card-featured .pl-btn[data-plan-state="included"]{
  background:transparent;
  border-color:rgba(255,255,255,0.35);
  color:rgba(255,255,255,0.82);
}
.pl-card-featured .pl-btn[data-plan-state="current"]:hover,
.pl-card-featured .pl-btn[data-plan-state="included"]:hover{
  background:transparent; box-shadow:none;
}

/* A Knight looking at the free card. It IS a live action (it navigates to the
   surface that owns cancelling), so it stays a real, hit-testable button — but
   a pricing page must not LEAD with a downgrade, and .pl-btn's default white
   fill becomes the loudest thing on the sheet the moment the Knight CTA has
   gone quiet. So: understated at rest, firm on hover and focus. */
.pl-btn[data-plan-state="downgrade"]{
  background:transparent;
  border-color:#e5e7eb;
  color:#6b7280;
  font-weight:500;
}
.pl-btn[data-plan-state="downgrade"]:hover{
  background:#f3f4f6; color:#111827; box-shadow:none;
}

/* Explains the two disabled cards to a Banneret or Warden, who is above both.
   Empty (and hidden) for every other tier, so it costs no height there. */
.pl-note{
  margin:26px 0 0;
  font-size:15px; line-height:1.45; color:#6b7280; text-align:center;
}
.pl-note[hidden]{ display:none; }

.pl-foot{
  margin:32px 0 0;
  padding-top:22px;
  border-top:1px solid #e5e7eb;
  font-size:14px; color:#6b7280; text-align:center;
}

/* Stacked fallback. 800px is where the sheet stops being a column beside the
   artwork and becomes the whole screen (.auth-panel{width:100%} further up):
   a phone-shaped surface, where two cards abreast read as a cramped table
   instead of two offers. Reusing the sheet's own breakpoint keeps this to one
   number instead of two that can drift. Above it the row always fits, because
   the column is sized to the tightest sheet (see .ap-col-plans). */
@media (max-width:800px){
  .pl-cards{ display:flex; flex-direction:column; gap:20px; }
  .pl-card{ display:flex; flex-direction:column; grid-row:auto; }
}

@media (prefers-reduced-motion: reduce){
  .pl-switch-btn, .pl-btn, .pl-table tbody tr{ transition:none; }
  /* the swap becomes instant: no fade, no travel, still no jump */
  .pl-view{ transition:none; }
  .pl-view.is-off{ transition:none; transform:none; }
}

/* Payment step */
/* The card form and its loading cover share one positioned stack (see
   .ap-pay-stack below), so the margin that used to sit on each of them belongs
   to the stack now — otherwise the absolutely positioned cover would drop its
   own margin and the sheet would move on handover. */
.ap-payment-element{ min-height:220px; }
#ap-pay-form .ap-error{ margin-bottom:16px; }
.ap-pay-back{ display:block; margin:18px auto 0; }

/* An inert Subscribe button is a STATUS, not a broken call to action: it drops
   off the black accent entirely instead of sitting there as a faded black bar,
   which was the whole complaint. The live step dot below carries the motion. */
#ap-pay-btn[disabled]{
  background:#f3f4f6; color:#6b7280; border-color:#e5e7eb;
  cursor:default;
}
#ap-pay-btn[disabled]:hover{ background:#f3f4f6; border-color:#e5e7eb; }
#ap-pay-btn[disabled]:active{ transform:none; }

/* ── Payment step: loading tracker (.ap-load) ────────────────────────────
   Measured 2026-07-25 on this machine, cold, against live Stripe:
     Stripe.js load  172ms · GET billing/config  31ms · POST billing/subscribe
     ~25ms of app+proxy plus one Stripe write (Stripe API RTT floor 65ms) ·
     Payment Element mount to its `ready` event  918ms cold / 452ms warm.
   So ~1.4s on a fast desktop and several seconds on a slow link, with a real
   tail whenever js.stripe.com is slow or blocked. NN/g puts a lone spinner at
   2 to 10s and a STEP INDICATOR past that, for phased waits — this one is
   short but genuinely two phased and does have the tail, so it borrows the
   IBKR connect surface's language (dashboard js/wallet/portfolio.js +
   .ibx-* in app/12-trade-wallet.css) at a smaller size: two steps, status
   text that changes as time passes, an elapsed clock only once the wait stops
   being routine, motion ONLY on the running step, and deliberately NO
   progress bar — nothing in this chain reports progress and drawing one
   would be a lie.

   PALETTE: the sheet is a white, single-theme surface with its own literal
   values (home's dark :root is for the page behind it, and the dashboard's
   tokens.css does not exist here), so every colour below is a literal hex
   from the same family the rest of .ap-* already uses. Only --font-mono, a
   home :root font variable, is referenced.
   SCALE: authored at home's 1.25x root like the rest of the sheet, and the
   whole block rides zoom:var(--ap-z) from .ap-col. */
/* ── The stack: card form underneath, tracker over the top ───────────────
   Bug fix 2026-07-25. The tracker used to REPLACE the card form
   (#ap-pay-form.is-loading .ap-payment-element{display:none}), so Stripe
   mounted its Payment Element into a box with no layout: a display:none
   subtree is not laid out and never paints, so the iframe only got a size when
   the tracker was removed, and the fields appeared 1 to 2 seconds LATER with
   no loading UI left on screen — measured, see the JS.

   Now the element keeps its box from the moment it mounts (it lays out at its
   real width, so Stripe never has to re-layout either) and the tracker sits
   over it, opaque, until home.js has seen the iframe painted. inset:0 on the
   stack rather than a fixed height so the cover still covers a form that
   renders taller than the reserved 220px. */
.ap-pay-stack{ position:relative; margin-bottom:20px; }
/* Belt and braces on the cover, which is opaque and takes the pointer events
   itself. The TAB ORDER is not a CSS job — pointer-events leaves a covered
   iframe focusable — so home.js stamps `inert` on the element for the length of
   the wait, the same way the plans views do it. */
#ap-pay-form.is-loading .ap-payment-element{ pointer-events:none; }

.ap-load{
  position:absolute; inset:0; z-index:2;
  /* opaque: a half-built Stripe iframe must never show through */
  background:#fff;
  /* matches .ap-payment-element's reserved height, so the sheet does not
     jump when the real card form takes this space */
  min-height:220px;
  padding-top:22px;
  box-shadow:inset 0 1px 0 #e5e7eb;
}
.ap-load-steps{
  list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:2px;
}
.ap-load-step{
  position:relative;
  display:grid; grid-template-columns:auto minmax(0,1fr);
  gap:14px; padding:10px 0; align-items:start;
}
/* Rail between the two numbers, so they read as one sequence and not as two
   unrelated lines. Anchored to the ROW, whose height moves with the live
   detail text, never to the fixed-height dot. */
.ap-load-step:not(:last-child)::before{
  content:''; position:absolute; left:13px; margin-left:-0.5px;
  top:38px; bottom:-4px; width:1px; background:#e5e7eb;
}
.ap-load-dot{
  position:relative; flex:none;
  width:27px; height:27px; border-radius:50%;
  display:grid; place-items:center;
  font-family:var(--font-mono); font-size:14px; font-weight:700; line-height:1;
  font-variant-numeric:tabular-nums;
  color:#9ca3af; background:#fff;
  box-shadow:inset 0 0 0 1px #d1d5db;
}
.ap-load-txt{ display:flex; flex-direction:column; gap:4px; min-width:0; padding-top:2px; }
.ap-load-label{ font-size:16px; font-weight:600; line-height:1.35; color:#9ca3af; }
.ap-load-detail{ font-size:15px; line-height:1.5; color:#9ca3af; }
/* Honest and unglamorous: mono, tabular, quiet, and empty until the wait has
   run long enough that a clock is reassurance rather than clutter. */
.ap-load-elapsed{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:14px; color:#9ca3af;
}
.ap-load-elapsed:not(:empty){ margin-left:6px; }

/* Ramp: todo is silent, live is the sheet's black, done is its green, fail is
   its red — the same three colours the check screen and the error box already
   use, so the tracker introduces no new palette. */
.ap-load-step[data-st="live"] .ap-load-label{ color:#111827; }
.ap-load-step[data-st="live"] .ap-load-detail{ color:#6b7280; }
.ap-load-step[data-st="live"] .ap-load-dot{
  color:#fff; background:#111827; box-shadow:inset 0 0 0 1px #111827;
}
/* Motion means RUNNING and only running: every terminal state below is
   static, so no second spinner is ever needed on this screen. */
.ap-load-step[data-st="live"] .ap-load-dot::before{
  content:''; position:absolute; inset:-5px; border-radius:50%;
  box-shadow:0 0 0 0 rgba(17,24,39,.26);
  animation:ap-load-pulse 1.9s ease-out infinite;
}
@keyframes ap-load-pulse{
  0%{   box-shadow:0 0 0 0   rgba(17,24,39,.26); }
  70%{  box-shadow:0 0 0 9px rgba(17,24,39,0); }
  100%{ box-shadow:0 0 0 0   rgba(17,24,39,0); }
}
.ap-load-step[data-st="done"] .ap-load-label{ color:#6b7280; }
.ap-load-step[data-st="done"] .ap-load-dot{
  color:#22c55e; background:rgba(34,197,94,.10);
  box-shadow:inset 0 0 0 1px rgba(34,197,94,.34);
}
.ap-load-step[data-st="fail"] .ap-load-label{ color:#111827; }
.ap-load-step[data-st="fail"] .ap-load-detail{ color:#dc2626; }
.ap-load-step[data-st="fail"] .ap-load-dot{
  color:#dc2626; background:#fef2f2;
  box-shadow:inset 0 0 0 1px #fecaca;
}

@media (prefers-reduced-motion: reduce){
  .ap-load-step[data-st="live"] .ap-load-dot::before{ animation:none; }
}

/* [hidden] must beat the steps' own display values (.ap-center is flex) */
.auth-panel [hidden]{ display:none !important; }


/* ── Exit send-off: logged-in ENTER → dashboard (body.enter-exit) ────────
   The MIRROR of the auth-scene roll: the camera pans RIGHT — tree + leaf
   field roll off to the LEFT together (same 99.5vw-family travel, same
   curve), the hero copy steps aside, and the veil closes to full dark in
   the final stretch. home.js navigates at ~950ms into the (usually
   prerendered) dashboard, whose sidebar+cascade arrival picks the motion
   up on the other side. */
/* NO veil: home and the dashboard share the same square-grid dark bg, so
   the page must END as the bare grid — the constant the eye holds onto
   across the swap. Only the art and chrome leave. */
body.enter-exit .home-account,
body.enter-exit .home-footer{
  opacity:0;
  pointer-events:none;   /* invisible Sign-out must not race the navigation */
  transition:opacity 300ms ease;
}
/* The warm glow leaves with the art so the final frame is the same flat
   dark grid the dashboard's transition frame shows — no color-temperature
   jump at the swap. */
body.enter-exit #welcome .tree-glow,
body.enter-exit #welcome .welcome-light{
  opacity:0;
  transition:opacity 400ms ease;
}
body.enter-exit .welcome-content{
  opacity:0;
  transform:translateX(-75px);
  pointer-events:none;
  /* immediate — beats the base rule's 1250ms return-leg delay */
  transition:opacity 280ms ease, transform 320ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.enter-exit #welcome.is-active .tree-stage{
  transform:translate(-130vw, 5.15vh);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.enter-exit .leaf-field{
  transform:translate(-130vw, 5.15vh);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
@media (prefers-reduced-motion: reduce){
  body.enter-exit #welcome.is-active .tree-stage,
  body.enter-exit .leaf-field,
  body.enter-exit .welcome-content{ transition-duration:0.01ms; }
}


/* ── Reverse arrival: dashboard brand → /home?return=1 ──────────────────
   The mirror of the ENTER exit: the page opens as the bare grid, then the
   tree + leaf field roll IN from the left (from where they left), glow
   breathes in, and the content cascade fires as the art settles (home.js
   adds is-active ~500ms in, then strips these classes with the pop
   animation suppressed inline so it can't replay). */
html.return-enter #welcome .tree-stage,
html.return-enter #welcome.is-active .tree-stage{
  animation:none !important;
  opacity:1;
  transform:translate(-130vw, 5.15vh);
}
html.return-enter .leaf-field{ transform:translate(-130vw, 5.15vh); }
html.return-enter.return-go #welcome .tree-stage,
html.return-enter.return-go #welcome.is-active .tree-stage,
html.return-enter.return-go .leaf-field{
  transform:translate(-13.5vw, 5.15vh);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
@media (prefers-reduced-motion: reduce){
  html.return-enter #welcome .tree-stage,
  html.return-enter .leaf-field{ transform:translate(-13.5vw, 5.15vh); }
  html.return-enter.return-go #welcome .tree-stage,
  html.return-enter.return-go #welcome.is-active .tree-stage,
  html.return-enter.return-go .leaf-field{ transition-duration:0.01ms; }
}

/* Username step (panel onboarding) */
.ap-hint{
  min-height:22px;
  font-size:15px; color:#9ca3af;
}
.ap-hint.ok{ color:#16a34a; }
.ap-hint.bad{ color:#dc2626; }
.ap-consent{
  margin:22px 0 0; text-align:center;
  font-size:14px; color:#6b7280; line-height:1.5;
}
.ap-consent a{ color:#111827; font-weight:500; text-decoration:underline; }


/* ── Return send-off: /plans (arrived from the dashboard) → dashboard ─────
   body.plans-exit, added by home.js when the sheet is closed on a /plans view
   that came from the dashboard (#hero-main[data-from="watchlist"]).

   DIRECTION (owner call 2026-07-25, mirrored): on the whole dashboard ↔ plans
   axis the departing page leaves toward the RIGHT and the incoming one enters
   from the LEFT. Coming here, the plans scene rolled IN from the left; leaving,
   it rolls back OUT to the left. The camera pans LEFT throughout, i.e. the
   world moves right on the way in and the frame overtakes it on the way out.

   This is a CONTINUATION, not a second animation. The rose bridge, the archer
   tower and the petal field each take ANOTHER −119.5vw, on the same 1000ms
   cubic-bezier(0.45, 0, 0.15, 1) the ENTER send-off uses, so from the user's
   eye the roll that brought the scene in simply reverses and keeps going. The
   three exit poses below are literally the scene's own REST poses (see the
   .welcome-eva block above) — the art goes back where it sleeps.

   THE SHEET IS THE ONE EXCEPTION, and it is deliberate. .auth-panel is
   position:fixed to the RIGHT edge: a viewport-attached surface, not a thing
   in the world (the hero has always dismissed it with translateX(103%)).
   Dragging it leftward with the art would mean a 40vw white slab wiping across
   the entire scene — 100vw of travel, on screen for essentially the whole
   1000ms, covering the very art the send-off exists to reveal. So it leaves by
   its NEAREST edge: same 119.5vw magnitude, same duration, same curve, opposite
   sign. Same speed as the art, mirrored on the vertical axis. It only needs
   ~41vw to clear (100vw on a phone), so it is gone in the first third and the
   scene rolls away in the open. The autumn tree is NOT brought back — auth-open
   stays on throughout, so nothing sweeps through frame.

   The rose glow and warm light leave with the art, so the last frame is the
   bare 60px grid — the same constant surface the ENTER send-off ends on and
   the dashboard's arrival opens on. home.js navigates at 950ms to
   {DASHBOARD_URL}/watchlist?enter=plans (NOT ?enter=1 — that value is home's
   own ENTER, whose direction the owner kept), where the dashboard slides in
   from the right: still the same leftward camera pan, picked up on the other
   side. px/vw values follow this file's ×1.25 scale (vw units are scale-free). */
body.plans-exit .welcome-eva .tree-stage{
  transform:translate(-140vw, 25.15vh) scale(0.95);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.plans-exit .welcome-eva .tree-back{
  transform:translate(-149.5vw, -2vh);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
body.plans-exit .welcome-eva .petal-field{
  transform:translateX(-119.5vw);
  opacity:0;
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1),
             opacity 700ms ease 300ms;
}
/* Camera-attached, not world-attached: same speed and travel as the art but
   toward its own near edge (see the DIRECTION note above). */
body.plans-exit .auth-panel{
  transform:translateX(119.5vw);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1);
}
/* Nothing behind the sheet is clickable once the trip has started. */
body.plans-exit .auth-scrim{ pointer-events:none; }
/* Land on the bare dark grid: no colour-temperature jump at the swap. */
body.plans-exit #welcome-eva .tree-glow,
body.plans-exit #welcome-eva .welcome-light{
  opacity:0;
  transition:opacity 400ms ease;
}
@media (prefers-reduced-motion: reduce){
  /* home.js navigates immediately under reduced motion, so these only exist
     for the case where the preference flips mid-flight. */
  body.plans-exit .welcome-eva .tree-stage,
  body.plans-exit .welcome-eva .tree-back,
  body.plans-exit .welcome-eva .petal-field,
  body.plans-exit .auth-panel{ transition-duration:0.01ms; }
}


/* ── Forward arrival: dashboard → /plans?from=watchlist ──────────────────
   The exact time-reverse of body.plans-exit above, and the far half of the
   dashboard's html.leave-home.leave-plans send-off.

   Leaving the dashboard, the whole app column slides OUT to the RIGHT and
   dissolves, so the camera is panning LEFT and the last frame is the bare 60px
   grid. This picks the same pan up on the other side of the origin hop: the
   rose bridge, the archer tower and the petal field all start ONE full travel
   (−119.5vw) to the LEFT of their settled pose — that is, exactly where
   plans-exit parks them, which is also the scene's own rest pose and the same
   side the hero's /login roll brings them in from — and roll back in over the
   same 1000ms cubic-bezier(0.45, 0, 0.15, 1). Nothing else moves: the autumn
   tree stays parked off-screen right at 100vw (auth-open is stamped
   server-side and never comes off during the arrival), so it can't sweep
   through frame.

   The white sheet is the deliberate exception (full reasoning in the
   plans-exit note above): it is fixed to the RIGHT edge and enters from there,
   so the art fills in from the left while the sheet closes in from the right
   and the two meet. Same magnitude, duration and curve as the art, opposite
   sign.

   html.plans-arrive is stamped by home.html PRE-PAINT, only for a signed-in
   visitor who really came from the dashboard, and only there — the flag is on
   <html> so the inline failsafe in <head> can strip it before <body> exists.
   home.js adds .plans-arrive-go on ACTIVATION (whenActivated), because the
   dashboard prerenders this URL and an ungated arrival would play itself out
   invisibly during the prerender.

   Specificity is deliberate: body.ap-instant (also stamped server-side) kills
   every transition on these layers with !important so /plans can paint
   settled. These rules out-specify it and re-declare the one transition the
   arrival needs. vw units are scale-free, so home's ×1.25 root doesn't apply. */
html.plans-arrive body.auth-open .welcome-eva .tree-stage{
  transform:translate(-140vw, 25.15vh) scale(0.95);
  transition:none;
}
html.plans-arrive body.auth-open .welcome-eva .tree-back{
  transform:translate(-149.5vw, -2vh);
  transition:none;
}
html.plans-arrive body.auth-open .welcome-eva .petal-field{
  transform:translateX(-119.5vw);
  opacity:0;
  transition:none;
}
/* Camera-attached: the sheet comes in off its own near (right) edge at the
   art's speed, so it enters frame in the last third (last ~16% on a phone,
   where it is full-width) — the mirror of how it clears frame early on the
   way out. */
html.plans-arrive body .auth-panel{
  transform:translateX(119.5vw);
  transition:none;
}
/* Open on the same bare dark grid the send-off ended on, then bloom. */
html.plans-arrive body.auth-open #welcome-eva .tree-glow,
html.plans-arrive body.auth-open #welcome-eva .welcome-light{
  opacity:0;
  transition:none;
}

html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .tree-stage{
  transform:translate(-20.5vw, 25.15vh) scale(0.95);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1) !important;
}
html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .tree-back{
  transform:translate(-30vw, -2vh);
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1) !important;
}
html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .petal-field{
  transform:none;
  opacity:1;
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1),
             opacity 700ms ease 300ms !important;
}
html.plans-arrive.plans-arrive-go body .auth-panel{
  transform:none;
  transition:transform 1000ms cubic-bezier(0.45, 0, 0.15, 1) !important;
}
html.plans-arrive.plans-arrive-go body.auth-open #welcome-eva .tree-glow,
html.plans-arrive.plans-arrive-go body.auth-open #welcome-eva .welcome-light{
  opacity:1;
  transition:opacity 600ms ease 300ms !important;
}
@media (prefers-reduced-motion: reduce){
  /* home.js drops the flag on activation, so the roll never starts. These
     neutralise the pre-paint pose for the frames before the deferred script
     runs (and for a preference that flips mid-flight): land settled. */
  html.plans-arrive body.auth-open .welcome-eva .tree-stage{
    transform:translate(-20.5vw, 25.15vh) scale(0.95);
  }
  html.plans-arrive body.auth-open .welcome-eva .tree-back{
    transform:translate(-30vw, -2vh);
  }
  html.plans-arrive body.auth-open .welcome-eva .petal-field{
    transform:none; opacity:1;
  }
  html.plans-arrive body .auth-panel{ transform:none; }
  html.plans-arrive body.auth-open #welcome-eva .tree-glow,
  html.plans-arrive body.auth-open #welcome-eva .welcome-light{ opacity:1; }
  html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .tree-stage,
  html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .tree-back,
  html.plans-arrive.plans-arrive-go body.auth-open .welcome-eva .petal-field,
  html.plans-arrive.plans-arrive-go body .auth-panel{
    transition-duration:0.01ms !important;
  }
}
