/* Foretaste — landing.css
   Loaded only by the four landing pages (written into the template by
   scripts/build-landing.py), layered on top of styles.css. It is the launch
   page's alone: a glassmorphism pass over its surfaces, a handful of
   typographic fixes, and the resting styles for the pointer motion that
   landing.js drives.

   Nothing here is load-bearing. A browser without backdrop-filter gets an
   opaque fill it can still read; a reader with prefers-reduced-motion gets the
   panels standing still; a device with no fine pointer never runs landing.js
   at all. In every one of those cases the page is the one styles.css already
   draws, intact.

   Everything is scoped under .product. On a page that loads this file that is
   always the landing page — the shared stylesheet keeps that class on the
   features page too, but the features page does not load landing.css. */

/* ---- Tokens ---------------------------------------------------------- */

.product {
  /* The frosted material, in two weights: surfaces take the full blur, the
     controls sitting on them take a lighter one so they stay distinct. */
  --glass-bg: color-mix(in srgb, var(--surface) 58%, transparent);
  --glass-line: color-mix(in srgb, var(--primary-text) 10%, transparent);
  --glass-hi: color-mix(in srgb, #ffffff 55%, transparent);  /* top-edge catch */
  --glass-sheen: color-mix(in srgb, #ffffff 62%, transparent);  /* movable highlight */
  --glass-filter: blur(20px) saturate(1.6);
  --glass-filter-sm: blur(11px) saturate(1.35);
  --glass-radius: 26px;
  --glass-shadow:
    0 26px 70px -24px rgba(27, 29, 34, 0.30),
    0 10px 26px -14px rgba(27, 29, 34, 0.18),
    inset 0 1px 0 var(--glass-hi);

  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-color-scheme: dark) {
  .product {
    --glass-bg: color-mix(in srgb, var(--surface) 44%, transparent);
    --glass-line: color-mix(in srgb, #ffffff 12%, transparent);
    --glass-hi: color-mix(in srgb, #ffffff 16%, transparent);
    --glass-sheen: color-mix(in srgb, #ffffff 20%, transparent);
    --glass-shadow:
      0 34px 90px -28px rgba(0, 0, 0, 0.66),
      0 12px 30px -14px rgba(0, 0, 0, 0.52),
      inset 0 1px 0 var(--glass-hi);
  }
}

/* No backdrop-filter: the frost cannot happen, so the fill has to carry the
   panel. Without this the translucent tint over the aurora is unreadable. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .product {
    --glass-bg: color-mix(in srgb, var(--surface) 93%, transparent);
    --glass-sheen: transparent;
  }
}

/* ---- The ground ---------------------------------------------------- */
/* A warm wash pinned behind everything, under the aurora, so the page opens on
   depth rather than a flat field. */

body.product {
  background:
    radial-gradient(135% 100% at 50% -8%,
      color-mix(in srgb, var(--action-gold) 10%, transparent) 0%,
      transparent 52%),
    var(--background);
  background-attachment: fixed;
}

/* The aurora leans toward the pointer. landing.js sets --pointer-x / --pointer-y
   (roughly -0.5..0.5); both default to 0, so this is inert without the script.
   The drift keyframes in styles.css own `transform` on the spans, so the lean
   rides the container instead. */
.product .aurora {
  transform: translate3d(
    calc(var(--pointer-x, 0) * 26px),
    calc(var(--pointer-y, 0) * 26px), 0);
  transition: transform 0.7s var(--ease);
}

/* The cursor spotlight. landing.js builds the element and appends it; it sits
   with the aurora, behind every line of content, and fades in once the pointer
   has actually moved. */
.product .spotlight {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease;
  background: radial-gradient(
    520px 520px at var(--spot-x, 50%) var(--spot-y, 32%),
    color-mix(in srgb, var(--action-gold) 13%, transparent),
    transparent 68%);
}

.product .spotlight.is-live { opacity: 1; }

/* ---- The glass surfaces ------------------------------------------- */
/* One recipe: the tint the aurora shows through, a hairline that catches light
   along the top edge (folded into --glass-shadow), and the shadow that lifts
   the panel clear of the background. */

.product .hero-card,
.product .stage .pane,
.product .card.glass,
.product .topbar::before,
.product .waitlist-toast {
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-filter);
  backdrop-filter: var(--glass-filter);
  border: 1px solid var(--glass-line);
  box-shadow: var(--glass-shadow);
}

/* The full-size panels also share a corner radius; the bar keeps its pill and
   the toast sets its own, just below. */
.product .hero-card,
.product .stage .pane,
.product .card.glass {
  border-radius: var(--glass-radius);
}

/* The movable highlight, behind the panel's own text (z-index: -1 in an
   isolated stacking context) so it lifts the glass without washing the words.
   landing.js steers --mx / --my toward the pointer on the hero; the closing
   card keeps the top-centre default. */
.product .hero-card,
.product .card.glass.cta {
  position: relative;
  isolation: isolate;
}

.product .hero-card::after,
.product .card.glass.cta::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0.6;
  background: radial-gradient(
    280px 220px at var(--mx, 50%) var(--my, -8%),
    var(--glass-sheen), transparent 62%);
}

/* The controls that sit on the panels: a lighter frost, the same hairline. */
.product .lang,
.product .waitlist input[type="email"],
.product .button,
.product .badge,
.product .chips span,
.product .provenance {
  -webkit-backdrop-filter: var(--glass-filter-sm);
  backdrop-filter: var(--glass-filter-sm);
}

.product .lang,
.product .waitlist input[type="email"],
.product .button {
  background: color-mix(in srgb, var(--surface) 54%, transparent);
  border-color: var(--glass-line);
}

.product .button {
  transition: box-shadow 0.25s ease, border-color 0.2s ease;
}

/* ---- Hero --------------------------------------------------------- */
/* styles.css centres .hero-copy-exit and hands it the scroll-out animation.
   The .hero-card inside it is the glass panel — kept flat, no 3D transform:
   a perspective()/rotateX ancestor stops the browser painting
   background-clip: text, and the whole h1 is clipped text. The motion is a
   2D drift and the travelling highlight, both of which clipped text survives. */

.product .hero { min-height: 90svh; }

/* `.hero` is a centred flex column, so its items shrink to fit their content —
   which made the glass panel's width track the longest line of the headline and
   gave every language a differently sized card. Both the item and the card take
   the full column width (capped at 44rem) so the box is identical in all four. */
.product .hero-copy-exit { width: 100%; }

.product .hero-card {
  width: 100%;
  max-width: 44rem;
  margin-inline: auto;
  padding: clamp(1.9rem, 1.4rem + 3.5vw, 3.6rem) clamp(1.4rem, 1rem + 4vw, 3.4rem)
           clamp(2.1rem, 1.6rem + 3.5vw, 3.4rem);
}

.product .hero h1 { margin-top: 1rem; }

/* The icon lifts off the card, keeps a slow float, and drifts a little further
   than the card it sits on (landing.js sets --px / --py, default 0). */
.product .hero-card .hero-icon {
  box-shadow:
    var(--card-shadow),
    0 30px 60px -22px color-mix(in srgb, var(--action-gold) 40%, transparent);
  translate: calc(var(--px, 0) * 9px) calc(var(--py, 0) * 7px);
  transition: translate 0.5s var(--ease);
}

@media (prefers-reduced-motion: no-preference) {
  .product .hero-icon {
    animation:
      icon-in 1s var(--ease) both,
      hero-icon-float 7s ease-in-out 1.2s infinite;
  }
}

@keyframes hero-icon-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-10px); }
}

/* ---- Hanging punctuation ---------------------------------------- */
/* build-site.py wraps a line-final full-width comma, full stop or closing
   bracket in .hang on the centred CJK copy. The mark then hangs entirely
   outside the measure: it is given no width at all, and paints past the end of
   the line it belongs to. `hanging-punctuation: allow-end` says exactly this
   in one declaration, but only Safari implements it.

   The whole mark, not half of it. Discounting half its box does centre the
   ink, which reads correctly on a line by itself — but these headlines are two
   written lines stacked, and a reader lines up the CHARACTERS. Half-hanging a
   comma on the first line and nothing on a second line that ends in a dash
   left the two runs of glyphs ten to thirty pixels apart on their own axis,
   which looks like a mistake rather than typography. At zero width every line
   centres on its own glyphs and the stack is true, whatever each line happens
   to end with.

   Zero width rather than a negative margin, because a negative margin is also
   subtracted from the width the browser measures the line against. On the
   headline spans, which shrink to fit their own text, that shrank the box
   below the text that had just sized it and the browser re-broke the line the
   copy writes with <br>. A zero-width box contributes nothing to measure and
   cannot start that argument. It also never gets pushed to the next line on
   its own: nothing that occupies no width can overflow. */
.product .hang {
  display: inline-block;
  width: 0;
  overflow: visible;
}

/* A mark hanging off a line that already fills its column paints past the
   column, and that would widen the page by the few pixels it sticks out --
   a horizontal scrollbar on a phone, caused by a full stop. Clipping these
   blocks keeps that out of the page's scroll width; the clip margin is wider
   than any mark, so the mark itself still paints in full. */
.product :is(.hero h1, .stage-title, .statement h2, .hero .lede, .statement > p) {
  overflow-x: clip;
  overflow-clip-margin: 1.2em;
}

/* ---- CJK display leading -------------------------------------- */
/* The big two-line headings run a Latin-tight line-height (1.02 on the hero,
   ~1.05–1.08 on the statements and the stage). CJK glyphs are tall and dense
   and have no real descenders, so those lines nearly touch — Japanese and
   Chinese want noticeably more leading. `:lang(zh)` covers both scripts. */
.product:is(:lang(ja), :lang(zh)) :is(.hero h1, .statement h2, .stage-title),
.product:is(:lang(ja), :lang(zh)) .statement h2 :is(.t-accent, .accent-text) {
  line-height: 1.34;
}

/* ---- Stage ------------------------------------------------------ */

.product .stage-frame {
  translate: calc(var(--px, 0) * 7px) calc(var(--py, 0) * 6px);
  transition: translate 0.5s var(--ease);
}

/* A hair of frost on the picked line, so it reads as lifted off the card. */
.product .menu-line.is-picked {
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

/* ---- Statements ------------------------------------------------- */
/* No panel behind the two big claims — they read better free on the ground,
   the way styles.css sets them. Two fixes only:

   The gradient-filled second line is an inline-block, and background-clip: text
   crops the paint to that box; at line-height 1.05 the box is shorter than the
   glyphs and the g / y / p descenders get sliced ("language.", "yours."). A
   little headroom and bottom padding hand the descender back. */
.product .statement h2 .t-accent,
.product .statement h2 .accent-text {
  line-height: 1.14;
  padding-bottom: 0.14em;
}

/* The reveal.
   styles.css ranges each claim's arrival inside `entry`, which — for a block
   only about a third of the viewport tall — is over before the heading clears
   the bottom third, and it only touches the headline. Here the whole claim
   arrives on the section's own timeline (`--claim`), every line blurring into
   focus as it rises: headline first, the sentence and the language row a beat
   behind. Ranges are measured against `cover` (the element's full pass through
   the viewport) and sit low enough that each line is sharp before it reaches
   mid-screen. Safe against `cover` here because a statement is never the last
   thing on the page (the entry/cover rule in the README). */

@keyframes lift-in {
  from { opacity: 0; filter: blur(9px); transform: translateY(14px); }
  to   { opacity: 1; filter: none; transform: none; }
}

@supports (animation-timeline: view()) and (timeline-scope: --stage) {
  @media (prefers-reduced-motion: no-preference) {
    .product .statement h2 .line        { animation-range: cover 4% cover 22%; }
    .product .statement h2 .t-accent    { animation-range: cover 6% cover 26%, cover 9% cover 34%; }
    .product .statement h2 .accent-text { animation-range: normal, cover 6% cover 32%; }

    .product .statement > p,
    .product .statement > .marquee {
      animation: lift-in linear both;
      animation-timeline: --claim;
    }
    .product .statement > p:not(.native-langs) { animation-range: cover 10% cover 40%; }
    .product .statement > .marquee             { animation-range: cover 12% cover 42%; }
    .product .statement > .native-langs        { animation-range: cover 14% cover 46%; }
    /* the whole language row lifts as one line; styles.css staggers the words
       on their own timeline, which would double up */
    .product .statement .native-langs span     { animation: none; }
  }
}

/* ---- The definition ------------------------------------------- */
/* No box either: styles.css gives it a gold left rule, which reads as a
   dictionary indent rather than a card stranded against the left of a wide
   column.

   "fore·taste" is Latin on every page, but its syllable dot (·) turns into a
   wide, centred glyph in a CJK Mincho — styles.css hands the whole headword a
   Mincho on the localized pages because its selector only checks an ancestor's
   lang, not the word's own lang="en". Take the headword back to the Latin
   serif; keep the part-of-speech label in the page's serif so it still matches
   the sentence below it. */
.product .definition .word          { font-family: var(--serif); }
.product :lang(ja) .definition .pos      { font-family: "Hiragino Mincho ProN", "Yu Mincho", var(--serif); }
.product :lang(zh-Hant) .definition .pos { font-family: "Songti TC", "LiSong Pro", var(--serif); }
.product :lang(zh-Hans) .definition .pos { font-family: "Songti SC", var(--serif); }

/* ---- The closing card ---------------------------------------------- */

.product .card.glass.cta {
  padding: clamp(1.75rem, 1.4rem + 2vw, 2.6rem);
}

/* ---- The launch-notification result ------------------------------- */
/* waitlist.js writes the outcome to .waitlist-status AND floats it up as a
   card. The status line stays the screen-reader live region only — this is a
   deliberate copy of styles.css `.sr-only`, kept here (not in the markup) so a
   page that loses this stylesheet falls back to the visible line. The card is
   the thing sighted readers see. */
.product .waitlist-status {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.product .waitlist-toast {
  position: fixed;
  z-index: 12;
  left: 50%;
  bottom: max(1.75rem, env(safe-area-inset-bottom));
  width: min(27rem, calc(100vw - 1.75rem));
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.85rem 0.95rem 0.85rem 1rem;
  border-radius: 18px;
  color: var(--primary-text);
  font-size: 0.92rem;
  line-height: 1.45;
  cursor: pointer;
  opacity: 0;
  translate: -50% 1.25rem;
  pointer-events: none;
  transition:
    opacity 0.35s ease,
    translate 0.4s var(--ease);
}

.product .waitlist-toast.is-open {
  opacity: 1;
  translate: -50% 0;
  pointer-events: auto;
}

.product .waitlist-toast-icon {
  flex: none;
  width: 1.4rem;
  height: 1.4rem;
  margin-top: 0.05rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--on-accent);
}

.product .waitlist-toast.is-good .waitlist-toast-icon { background: var(--green); }
.product .waitlist-toast.is-bad .waitlist-toast-icon  { background: var(--secondary-text); }

.product .waitlist-toast-msg {
  margin: 0;
  flex: 1;
}

.product .waitlist-toast-close {
  flex: none;
  margin: -0.15rem -0.1rem 0 0.2rem;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--muted-text);
}

/* ---- The primary button ------------------------------------------- */
/* Frost is shared above; the gold gets a top-lit gradient and a warm shadow. */

.product .button.primary {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--action-gold) 88%, #ffffff) 0%,
      var(--action-gold) 52%,
      color-mix(in srgb, var(--action-gold) 90%, #000000) 100%);
  box-shadow:
    0 12px 26px -12px color-mix(in srgb, var(--action-gold) 65%, transparent),
    inset 0 1px 0 color-mix(in srgb, #ffffff 45%, transparent);
}

.product .button.primary:hover {
  box-shadow:
    0 16px 32px -12px color-mix(in srgb, var(--action-gold) 75%, transparent),
    inset 0 1px 0 color-mix(in srgb, #ffffff 55%, transparent);
}

.product .badge {
  background:
    linear-gradient(color-mix(in srgb, var(--surface) 30%, transparent),
                    color-mix(in srgb, var(--surface) 30%, transparent)),
    color-mix(in srgb, var(--action-gold) 16%, transparent);
}

/* ---- Motion off --------------------------------------------------- */
/* landing.js bows out entirely here, so the pointer never moves anything; this
   block only has to settle the resting states. The scroll-reveal re-range and
   the hero-icon float sit in `no-preference` blocks above and never apply. */

@media (prefers-reduced-motion: reduce) {
  .product .hero-card .hero-icon,
  .product .stage-frame,
  .product .aurora {
    translate: none;
    transform: none;
    transition: none;
  }
  .product .spotlight { display: none; }
  .product .waitlist-toast {
    translate: -50% 0;
    transition: opacity 0.2s ease;
  }
}
