/* ===== page motion (2026-11-05): on-appear reveals per section + extra controlled parallax.
   One easing family (expo-out), short staggers, nothing bounces: calm, like the app. ===== */
:root { --mo: cubic-bezier(.16, 1, .3, 1); }

/* section headlines: each word rises out of its own line mask; the eyebrow pill unblurs in first */
.js .mo-h .mw { display: inline-block; overflow: hidden; vertical-align: top; padding-bottom: .12em; margin-bottom: -.12em; }
.js .mo-h .mw__i { display: inline-block; transform: translate3d(0, 105%, 0) rotate(4deg); transform-origin: 0 100%; transition: transform 1.1s var(--mo); transition-delay: calc(var(--wi) * 45ms + .12s); }
.mo-h.mo-in .mw__i { transform: none; }
.js .mo-eb { opacity: 0; scale: .9; filter: blur(6px); transition: opacity .7s var(--mo), scale .9s var(--mo), filter .7s var(--mo); }
.mo-eb.mo-in { opacity: 1; scale: 1; filter: none; }

/* pinned features: copy, phone and stats arrive from three directions the first time the section shows */
.js .fs__l, .js .fs__c, .js .fs__r { transition: opacity .9s var(--mo), translate 1.2s var(--mo), scale 1.2s var(--mo); }
.js .fs:not(.mo-in) .fs__l { opacity: 0; translate: -32px 0; }
.js .fs:not(.mo-in) .fs__c { opacity: 0; translate: 0 70px; scale: .94; }
.js .fs:not(.mo-in) .fs__r { opacity: 0; translate: 32px 0; }
.js .fs.mo-in .fs__c { transition-delay: .1s; } .js .fs.mo-in .fs__r { transition-delay: .2s; }

/* benefit cards: rise with a slight tilt back, like cards set down on a table */
.kb__card { transform: perspective(1400px) translateY(64px) rotateX(9deg) scale(.97); transform-origin: 50% 100%; transition: opacity .9s var(--mo), transform 1.3s var(--mo); }
.kb.in .kb__card { transform: none; }
.kb__phone { transform: translateX(-50%) translateY(var(--py, 0px)); }

/* bento tiles: unmask upward from the bottom edge as they rise */
.js .bx__t { clip-path: inset(18% 0 0 0 round var(--r-frame)); transform: translateY(44px) scale(.97); }
.bx.is-in .bx__t { clip-path: inset(0 0 0 0 round var(--r-frame)); transform: none; transition: opacity .8s var(--mo), transform 1.2s var(--mo), clip-path 1.1s var(--mo); }

/* closing block */
.js .next .next__t { opacity: 0; translate: 0 24px; transition: opacity .9s var(--mo) .15s, translate 1.1s var(--mo) .15s; }
.next.mo-in .next__t { opacity: 1; translate: 0 0; }

@media (prefers-reduced-motion: reduce) {
  .js .mo-h .mw__i, .js .mo-eb, .js .fs__l, .js .fs__c, .js .fs__r, .kb__card, .js .bx__t, .js .next .next__t { transition: none !important; transform: none !important; opacity: 1 !important; translate: none !important; scale: none !important; filter: none !important; clip-path: none !important; }
}
.next__t { transform: translateY(var(--py, 0px)); }
/* (2026-11-05) the alert's blur breaks inside a 3D-tilted card while both animate, so the cards rise flat (2D),
   and the alert opens with an ease that doesn't overshoot (overshoot pushed the clip past its rounded shape) */
.kb__card { transform: translateY(56px) scale(.97); }
.kb.in .kb__card { transform: none; }
.kb__alert { transition: clip-path .8s cubic-bezier(.22, 1, .36, 1) 1.15s; }
.kb__card:nth-child(3) .kb__alert { transition-delay: 1.35s; }
/* (2026-11-05) alert open, smoother: grows out of the island on an iOS-style curve over 1s, and the text fades in
   only once the shape has mostly opened (it used to show while the shape was still snapping open) */
.kb__alert { transition: clip-path 1s cubic-bezier(.32, .72, 0, 1) 1.1s; }
.kb__alert > * { transition: opacity .45s var(--e) 1.55s; }
.kb__card:nth-child(3) .kb__alert { transition-delay: 1.3s; } .kb__card:nth-child(3) .kb__alert > * { transition-delay: 1.75s; }

/* cost section (2026-11-05): same language as the rest. Heading words rise and the pill unblurs (shared rules above),
   the intro fades up, the car rows and charger cards rise in a quick stagger, and the battery panel rises with a
   slight scale a beat later. Uses opacity + translate/scale only, so the section's own hover and tick transforms keep working. */
.js .cost__sub { opacity: 0; translate: 0 18px; transition: opacity .9s var(--mo) .2s, translate 1.1s var(--mo) .2s; }
.cost__sub.mo-in { opacity: 1; translate: 0 0; }
.js .cost__grid .calc-field legend, .js .cost__grid .calc-car, .js .cost__grid .calc-ch { opacity: 0; translate: 0 22px; }
.js .cost__grid .bc { opacity: 0; translate: 0 48px; scale: .97; }
.cost__grid.mo-in .calc-field legend, .cost__grid.mo-in .calc-car, .cost__grid.mo-in .calc-ch { opacity: 1; translate: 0 0;
  transition: opacity .8s var(--mo), translate 1s var(--mo); transition-delay: calc(var(--ci, 0) * 55ms + .05s); }
.cost__grid.mo-in .bc { opacity: 1; translate: 0 0; scale: 1; transition: opacity .9s var(--mo) .15s, translate 1.2s var(--mo) .15s, scale 1.2s var(--mo) .15s; }
@media (prefers-reduced-motion: reduce) { .js .cost__sub, .js .cost__grid .calc-field legend, .js .cost__grid .calc-car, .js .cost__grid .calc-ch, .js .cost__grid .bc { opacity: 1 !important; translate: none !important; scale: none !important; transition: none !important; } }

/* (2026-11-05) alert open, v3: a two-step morph like iOS. The island pill first widens (stays short), then grows
   down to the full card, on an even ease-in-out, so nothing snaps. Text eases in and lifts as the shape settles. */
@keyframes kbIsland {
  0%   { clip-path: inset(0 31% calc(100% - 9.6cqw) 31% round 5cqw); animation-timing-function: cubic-bezier(.45, 0, .55, 1); }
  42%  { clip-path: inset(0 0 calc(100% - 13cqw) 0 round 6.5cqw); animation-timing-function: cubic-bezier(.45, 0, .2, 1); }
  100% { clip-path: inset(0 0 0 0 round 11cqw); }
}
.kb__alert { transition: none; }
.kb.in .kb__alert { animation: kbIsland 1.25s 1.1s both; }
.kb.in .kb__card:nth-child(3) .kb__alert { animation-delay: 1.3s; }
.kb__alert > * { transform: translateY(4px); transition: opacity .5s var(--e) 1.85s, transform .6s var(--e) 1.85s; }
.kb.in .kb__alert > * { transform: none; }
.kb__card:nth-child(3) .kb__alert > * { transition-delay: 2.05s; }
.no-io .kb__alert { animation: none; clip-path: none; }
@media (prefers-reduced-motion: reduce) { .kb.in .kb__alert { animation: none; clip-path: none; } .kb__alert > * { transform: none; transition: none; } }

/* (2026-11-05) alert open, v4. Animating clip-path on the glass (backdrop-filter) layer, inside a card that clips its
   rounded corners, made Chrome skip the in-between frames (island, then the card popped in). Now nothing is
   clipped: the glass lives on ::before and its box itself grows (inset + radius) from the island to the full alert.
   The card keeps its rounded clip, so no corner clipping either. */
.kb__alert, .kb.in .kb__alert { clip-path: none !important; animation: none !important; transition: none !important;
  background: none; -webkit-backdrop-filter: none; backdrop-filter: none; box-shadow: none; isolation: isolate; }
.kb__alert::before { content: ""; position: absolute; z-index: -1; inset: 0 31% calc(100% - 9.6cqw) 31%; border-radius: 5cqw;
  background: linear-gradient(180deg, rgba(18,20,19,.62), rgba(8,9,9,.7)); -webkit-backdrop-filter: blur(22px) saturate(1.7); backdrop-filter: blur(22px) saturate(1.7);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.14), inset 0 0 0 1px rgba(255,255,255,.08), 0 2cqw 6cqw -2cqw rgba(0,0,0,.35);
  transition: inset 1.1s cubic-bezier(.32, .72, 0, 1) 1.1s, border-radius 1.1s cubic-bezier(.32, .72, 0, 1) 1.1s; }
.kb.in .kb__alert::before { inset: 0; border-radius: 11cqw; }
.kb__card:nth-child(3) .kb__alert::before { transition-delay: 1.3s; }
.kb__alert > * { transition: opacity .5s var(--e) 1.7s, transform .6s var(--e) 1.7s; }
.kb__card:nth-child(3) .kb__alert > * { transition-delay: 1.9s; }
.no-io .kb__alert::before { inset: 0; border-radius: 11cqw; }
@media (prefers-reduced-motion: reduce) { .kb__alert::before { inset: 0; border-radius: 11cqw; transition: none; } }
