/* ============================================================================
   Landing page (the public "/" route).
   ============================================================================

   SCOPE CONTRACT
   --------------
   Every rule in this file is scoped under `.tz-home`. Nothing here leaks into
   the console, the docs pages, or the public shell (nav / footer), all of
   which are already built and must not shift.

   COLOUR CONTRACT
   ---------------
   This file defines NO colours of its own. Every colour is `rgb(var(--color-*))`
   or a `--lum-*` material from app.css. That is deliberate: the console already
   ships a settled near-monochrome system, and the landing page is the last
   surface to be built — it adapts to the system, not the other way around.
   In particular the primary action stays `.tz-satin--primary` (brushed light
   satin), NOT a black slab; see the note at app.css:134.

   What this file DOES own: layout, the type scale, rhythm, depth composition
   and motion. That is where the landing page earns its presence.

   The `--lz-*` prefix marks landing-only values so they are easy to tell apart
   from the global `--lum-*` design tokens. */

.tz-home {
  /* Landing-local rhythm. Not global tokens — these only describe how this
     one page breathes. */
  /* 152px at the top end left every section opening with a conspicuous empty
     band before the kicker. Generous is the goal; vacant is not. */
  --lz-section-y: clamp(64px, 8.5vw, 116px);
  --lz-gap: clamp(20px, 2.5vw, 32px);

  /* Landing-local motion. Durations follow the brief's hierarchy: fast
     interaction, UI transition, cinematic. The easing is app.css's. */
  --lz-dur-interaction: 180ms;
  --lz-dur-ui: 380ms;
  --lz-dur-cinematic: 900ms;

  max-width: 1240px;
  margin: 0 auto;
  padding: 0 var(--lum-gutter, 24px) 0;

  /* Defeat the flex-item automatic minimum size.
     `body` is `display:flex; flex-direction:column` and `#public-main` inherits
     that, so `.tz-home` is a flex item whose default `min-width:auto` refuses
     to shrink below its min-content width. The widest line in the curl sample
     is ~530px, so on a 390px phone the whole page was pinned to 531px and
     scrolled sideways — the <pre>'s own `overflow-x:auto` never got a chance
     to engage, because nothing was ever narrower than the content.
     `min-width: 0` lets the column shrink to the viewport and hands the
     horizontal scrolling back to the code block, where it belongs. */
  min-width: 0;
  /* `min-width: 0` alone is not enough. As a flex item this box still
     shrink-to-fits, and shrink-to-fit resolves against the CONTENT's
     min-content size regardless of the box's own min-width. Pinning the
     width to the container is what actually stops the page widening. */
  width: 100%;
  align-self: stretch;
}

/* Same reasoning one level down: the panel is the scroll boundary (it already
   carries `overflow: hidden` from app.css), so it must be allowed to be
   narrower than the code inside it. */
.tz-home__code-panel { min-width: 0; }
.tz-home__code { max-width: 100%; }

/* ============================================================================
   Type scale.
   The brief asks for a much larger headline than the console uses. That is
   correct for a landing page and wrong for a console, which is exactly why
   it is scoped here. Every size is fluid via clamp() so there are no
   breakpoint cliffs between 360px and 1440px.
   ========================================================================== */

.tz-home__title {
  /* 100px/15ch pushed the headline to ~940px on a 1440 viewport, which left
     the particle field nowhere to live except behind the type. Trading a few
     points of size for a narrower measure buys a genuinely empty right third
     — the composition reads as more confident, not less. */
  /* Four lines rather than three now that the headline ends "...infrastructure
     overhead." A little less size buys the extra line without pushing the
     pipeline strip off the fold, and 20ch keeps the longest line unbroken. */
  font-size: clamp(34px, 5.2vw, 78px);
  line-height: 0.98;
  font-weight: 600;              /* restraint: not 700 at this size */
  letter-spacing: -0.038em;
  margin: 0;
  max-width: 20ch;
  text-wrap: balance;
  color: rgb(var(--color-on-surface));
}
.tz-home__title-accent {
  display: block;
  color: rgb(var(--color-on-surface-variant));
}

.tz-home__lede {
  font-size: clamp(16px, 1.35vw, 20px);
  line-height: 1.6;
  color: rgb(var(--color-on-surface-variant));
  max-width: 52ch;
  margin: 0;
  text-wrap: pretty;
}

.tz-home__h2 {
  font-size: clamp(28px, 4.1vw, 56px);
  line-height: 1.06;
  font-weight: 600;
  letter-spacing: -0.032em;
  margin: 0;
  /* Wide enough that each authored line in `lines()` occupies exactly one
     visual line. At 18ch the spans re-wrapped internally, so the line-by-line
     reveal broke at different words than the copy was written to break at —
     which reads as a layout bug rather than as choreography. */
  max-width: 24ch;
  text-wrap: balance;
  color: rgb(var(--color-on-surface));
}
.tz-home__h2 em {
  font-style: normal;
  color: rgb(var(--color-on-surface-variant));
}

.tz-home__body {
  font-size: clamp(15px, 1.1vw, 17px);
  line-height: 1.66;
  color: rgb(var(--color-on-surface-variant));
  margin: 0;
  max-width: 58ch;
  text-wrap: pretty;
}

/* Metadata / eyebrow. Mono, because on this page the small type is always
   a machine-ish label (a stage number, a count, an endpoint). */
.tz-home__meta {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgb(var(--color-on-surface-ghost));
  font-weight: 500;
}

/* ============================================================================
   Section frame.
   Each section carries a number in a left rail on wide viewports. The rail
   is what makes the page read as one continuous document rather than eight
   stacked marketing blocks.
   ========================================================================== */

.tz-home__section {
  position: relative;
  padding: var(--lz-section-y) 0;
  border-top: 1px solid rgb(var(--color-border-subtle) / 0.08);
}
.tz-home__section:first-child { border-top: 0; }

.tz-home__section-head {
  display: flex;
  flex-direction: column;
  gap: clamp(14px, 1.6vw, 22px);
  margin-bottom: clamp(36px, 5vw, 64px);
}

/* The vertical section rail ("02 — WHY TEZZINC" down the left gutter) was
   removed rather than repositioned. Two reasons, and the first is enough:
   it repeated the kicker pill's text verbatim, so every section announced
   itself twice. It also never had a real gutter to live in — at common
   widths it crowded the pill instead of sitting clear of the column, which
   read as a collision rather than as structure. The section numbering it
   carried was decorative; the headings already do the navigating. */

/* Kicker — reuses the satin material already defined for .tz-home__kicker in
   app.css; we only adjust the rhythm here. */
.tz-home__kicker { align-self: flex-start; margin: 0; }

/* ============================================================================
   01 — Hero.
   The hero is a stage: a full-bleed WebGL canvas sits behind the copy at
   z-index 0, the copy rides at z-index 1. The canvas is *decorative* — the
   hero is fully legible with it absent, which is the fallback path.
   ========================================================================== */

.tz-home__hero {
  position: relative;
  min-height: min(92vh, 900px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(80px, 12vh, 150px) 0 clamp(48px, 7vh, 90px);
  text-align: left;
  isolation: isolate;
}
.tz-home__hero-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(20px, 2.6vw, 34px);
  width: 100%;
}

/* The canvas is deliberately wider than the content column and bleeds into
   the page gutters, so the particle field reads as an environment the copy
   is standing in rather than a boxed illustration. */
.tz-home__hero-stage {
  position: absolute;
  z-index: 0;
  inset: 0 calc(var(--lum-gutter, 24px) * -1);
  overflow: hidden;
  pointer-events: none;
  /* The mask is weighted hard to the RIGHT, and this is the most important
     line in the file.

     Centred at 62% the field ran straight through the headline and the lede,
     and the copy lost. "The user should notice the content first" is not a
     preference here — a hero whose H1 competes with its own background is
     simply broken. Anchoring the field at 84% keeps it in the empty right
     third of the composition, where it reads as an environment the type is
     standing in rather than a texture printed over it. */
  -webkit-mask-image: radial-gradient(44% 88% at 87% 48%, #000 8%, transparent 74%);
          mask-image: radial-gradient(44% 88% at 87% 48%, #000 8%, transparent 74%);
}
/* Below the two-column-ish breakpoint the copy spans the full width, so there
   is no empty right third to hide the field in. It drops to a whisper. */
@media (max-width: 900px) {
  .tz-home__hero-stage {
    opacity: 0.4;
    -webkit-mask-image: radial-gradient(90% 45% at 50% 88%, #000 0%, transparent 74%);
            mask-image: radial-gradient(90% 45% at 50% 88%, #000 0%, transparent 74%);
  }
}
.tz-home__hero-stage canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Parallax layers. `--lz-px` / `--lz-py` are written by landing_motion.js as
   unitless -1..1 pointer offsets; each layer multiplies them by its own depth
   so the composition separates when the cursor moves. Depth is the only
   difference between layers — no layer animates on its own. */
.tz-home__hero [data-lz-depth] {
  transform: translate3d(
    calc(var(--lz-px, 0) * var(--lz-depth, 0) * 1px),
    calc(var(--lz-py, 0) * var(--lz-depth, 0) * 1px),
    0
  );
  transition: transform 620ms var(--lum-ease, cubic-bezier(0.22, 1, 0.36, 1));
  will-change: transform;
}

.tz-home__pill { margin: 0; }

.tz-home__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: flex-start;
  margin: clamp(4px, 1vw, 12px) 0 0;
}
.tz-home__cta {
  height: 46px;
  padding: 0 22px;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 14.5px;
}
/* The arrow advances on hover. This is the one hover flourish on the page's
   primary action, and it is 4px — the brief's "tiny translation", not a
   lunge. */
.tz-home__cta svg {
  transition: transform var(--lz-dur-interaction) var(--lum-ease, ease);
}
.tz-home__cta:hover svg { transform: translateX(4px); }

/* ----------------------------------------------------------------------------
   Hero pipeline — the five stages.
   Rendered as a row of cells divided by hairlines rather than as five separate
   cards, because the stages are one pipeline, not five products. The active
   cell is driven by `data-active` from landing_motion.js so the pipeline
   advances in time with the particles crossing the canvas behind it.
   -------------------------------------------------------------------------- */

.tz-home__pipeline {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  width: 100%;
  margin-top: clamp(20px, 3.5vw, 48px);
  border-radius: var(--lum-radius, 1rem);
  overflow: hidden;
  background: rgb(var(--color-surface-lowest) / 0.55);
  border: 1px solid rgb(var(--color-border-subtle) / 0.10);
  box-shadow: var(--lum-elev-2);
  backdrop-filter: blur(14px) saturate(1.1);
  -webkit-backdrop-filter: blur(14px) saturate(1.1);
}
@media (max-width: 860px) {
  .tz-home__pipeline { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 460px) {
  .tz-home__pipeline { grid-template-columns: 1fr; }
}

.tz-home__stage {
  position: relative;
  padding: clamp(16px, 1.8vw, 24px) clamp(14px, 1.5vw, 20px);
  display: flex;
  flex-direction: column;
  gap: 5px;
  text-align: left;
  border-left: 1px solid rgb(var(--color-border-subtle) / 0.09);
  transition: background var(--lz-dur-ui) var(--lum-ease, ease);
}
.tz-home__stage:first-child { border-left: 0; }
@media (max-width: 860px) {
  .tz-home__stage:nth-child(odd) { border-left: 0; }
  .tz-home__stage:nth-child(n + 3) { border-top: 1px solid rgb(var(--color-border-subtle) / 0.09); }
}
@media (max-width: 460px) {
  .tz-home__stage { border-left: 0; }
  .tz-home__stage + .tz-home__stage { border-top: 1px solid rgb(var(--color-border-subtle) / 0.09); }
}

/* The travelling highlight. A 1px line crossing the top edge of the active
   cell — this is the only element on the page that moves continuously, and it
   is what makes the pipeline read as running rather than drawn. */
.tz-home__stage::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: rgb(var(--color-on-surface));
  transform: scaleX(0);
  transform-origin: left;
  opacity: 0;
  transition:
    transform var(--lz-dur-cinematic) var(--lum-ease, ease),
    opacity var(--lz-dur-ui) ease;
}
.tz-home__stage[data-active="true"]::before { transform: scaleX(1); opacity: 0.55; }
.tz-home__stage[data-active="true"] { background: rgb(var(--color-on-surface) / 0.03); }
.tz-home__stage[data-active="true"] .tz-home__stage-n { color: rgb(var(--color-on-surface)); }

.tz-home__stage-n {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: rgb(var(--color-on-surface-ghost));
  transition: color var(--lz-dur-ui) ease;
}
.tz-home__stage-label {
  font-size: clamp(14px, 1.1vw, 16px);
  font-weight: 600;
  letter-spacing: -0.012em;
  color: rgb(var(--color-on-surface));
}
.tz-home__stage-note {
  font-size: 12.5px;
  line-height: 1.45;
  color: rgb(var(--color-on-surface-variant));
}

/* ============================================================================
   Reveal choreography.
   motion.js already owns `.tz-reveal` / `.tz-in` and the IntersectionObserver
   plus its 1.2s force-reveal failsafe. We only add landing-specific variants
   on top of that same vocabulary, so there is one animation architecture on
   the page and one failsafe path.
   ========================================================================== */

/* Line-by-line headline reveal. Each line is a span with its own clip
   window; `--lz-i` staggers them. Because the gate is `.tz-motion` (added by
   motion.js on load) the text is fully visible if that module ever fails. */
.tz-motion .tz-home__line {
  display: block;
  clip-path: inset(0 0 108% 0);
  transform: translate3d(0, 0.28em, 0);
  transition:
    clip-path var(--lz-dur-cinematic) var(--lum-ease, ease),
    transform var(--lz-dur-cinematic) var(--lum-ease, ease);
  transition-delay: calc(var(--lz-i, 0) * 90ms);
}
.tz-motion .tz-in .tz-home__line,
.tz-motion .tz-home__line.tz-in {
  clip-path: inset(-0.22em 0 -0.22em 0);
  transform: translate3d(0, 0, 0);
}

/* ============================================================================
   03 — Reliability.
   A two-column composition: the numbered principle list on the left, the
   topology canvas on the right. The topology is the section's argument made
   visible — nodes that keep re-forming their links as one drops out.
   ========================================================================== */

.tz-home__split {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(32px, 5vw, 72px);
  align-items: start;
}
@media (min-width: 1024px) {
  .tz-home__split { grid-template-columns: 1.02fr 1fr; }
}

.tz-home__topology {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 100%;
  max-width: 520px;
  margin-inline: auto;
  border-radius: var(--lum-radius-md, 1.5rem);
  overflow: hidden;
}
.tz-home__topology canvas,
.tz-home__topology svg { display: block; width: 100%; height: 100%; }
@media (max-width: 1023px) {
  /* Below the two-column breakpoint the topology is decoration competing for
     a small viewport. It goes. */
  .tz-home__topology { display: none; }
}

/* The reliability list. Numbered, hairline-separated, no card chrome — the
   restraint is the point in an "engineering manifesto" section. */
.tz-home__principles { display: flex; flex-direction: column; margin-top: 8px; }
.tz-home__principle {
  display: grid;
  grid-template-columns: 42px 1fr;
  gap: clamp(12px, 1.4vw, 20px);
  padding: clamp(20px, 2.4vw, 30px) 0;
  border-top: 1px solid rgb(var(--color-border-subtle) / 0.10);
}
.tz-home__principle:first-child { border-top: 0; padding-top: 0; }
.tz-home__principle-n {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11.5px;
  letter-spacing: 0.08em;
  color: rgb(var(--color-on-surface-ghost));
  padding-top: 4px;
}
.tz-home__principle-title {
  font-size: clamp(16px, 1.4vw, 19px);
  font-weight: 600;
  letter-spacing: -0.016em;
  margin: 0 0 7px;
  color: rgb(var(--color-on-surface));
}
.tz-home__principle-body {
  font-size: 14.5px;
  line-height: 1.62;
  color: rgb(var(--color-on-surface-variant));
  margin: 0;
  max-width: 52ch;
}

/* ============================================================================
   Cards — landing variants.
   `.tz-card` from app.css already supplies the surface, border and shadow.
   These rules add only the hover behaviour the brief asks for: a 3px lift and
   a border that illuminates. No rotation, no scale.
   ========================================================================== */

.tz-home .tz-card,
.tz-home .tz-tile {
  transition:
    transform var(--lz-dur-ui) var(--lum-ease, ease),
    box-shadow var(--lz-dur-ui) var(--lum-ease, ease),
    border-color var(--lz-dur-ui) ease;
}
@media (hover: hover) {
  .tz-home .tz-card:hover,
  .tz-home .tz-tile:hover {
    transform: translateY(-3px);
    box-shadow: var(--lum-elev-hover-lift);
    border-color: rgb(var(--color-on-surface) / 0.16);
  }
}

/* Grids used by the why / docs / contact sections. Replaces the inline
   Tailwind grid classes those sections used, so the page has one spacing
   rhythm instead of Tailwind's. */
.tz-home__grid { display: grid; gap: var(--lz-gap); grid-template-columns: 1fr; }
@media (min-width: 700px)  { .tz-home__grid--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 700px)  { .tz-home__grid--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .tz-home__grid--3 { grid-template-columns: repeat(3, 1fr); } }

.tz-home__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: clamp(28px, 3.5vw, 44px);
}

/* ============================================================================
   07 — Live API.
   A terminal-flavoured panel with three tabs. The point of the design is to
   make "this number came from the API just now" legible at a glance, so the
   endpoint and the live dot are part of the chrome, not a footnote.
   ========================================================================== */

.tz-home__live {
  border-radius: var(--lum-radius, 1rem);
  overflow: hidden;
  background: rgb(var(--color-surface-lowest));
  border: 1px solid rgb(var(--color-border-subtle) / 0.10);
  box-shadow: var(--lum-elev-3, var(--lum-elev-2));
}
.tz-home__live-head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 12px 16px;
  border-bottom: 1px solid rgb(var(--color-border-subtle) / 0.10);
}
.tz-home__live-endpoint {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  color: rgb(var(--color-on-surface-variant));
  margin-right: auto;
}
.tz-home__tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.tz-home__tab {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 14px;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}
/* The tab count reuses the mono face so it reads as data, not as a badge. */
.tz-home__tab-count {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0;
  opacity: 0.6;
}

/* Selection state.
   Selected and unselected both wearing a satin was not legible — the primary
   and default satins differ by a few percent of lightness, which is a fine
   distinction for a button pair and a useless one for "which tab am I on".
   So the UNSELECTED state gives up the material entirely and goes flat. The
   selected tab is then the only raised object in the row, which is
   unmistakable and still uses nothing but existing tokens. */
.tz-home__tab[aria-selected="false"],
.tz-home__interval .tz-mode[aria-pressed="false"] {
  background: none;
  box-shadow: none;
  border-color: transparent;
  color: rgb(var(--color-on-surface-variant));
}
@media (hover: hover) {
  .tz-home__tab[aria-selected="false"]:hover,
  .tz-home__interval .tz-mode[aria-pressed="false"]:hover {
    background: rgb(var(--color-on-surface) / 0.05);
    color: rgb(var(--color-on-surface));
  }
}
.tz-home__live-body { padding: clamp(18px, 2.2vw, 28px); }
.tz-home__live-panel[hidden] { display: none; }
.tz-home__live-hint {
  font-size: 13px;
  color: rgb(var(--color-on-surface-variant));
  margin: 0 0 16px;
}
.tz-home__chips { display: flex; flex-wrap: wrap; gap: 8px; }

/* Chips stagger in when their panel becomes active. 18ms apart and capped, so
   a 26-chip panel resolves in well under half a second. */
.tz-motion .tz-home__live-panel:not([hidden]) .tz-fmt-pill {
  animation: lz-chip-in 320ms var(--lum-ease, ease) both;
  animation-delay: calc(var(--lz-i, 0) * 18ms);
}
@keyframes lz-chip-in {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}

/* ----------------------------------------------------------------------------
   Savings-calculator output.
   These three classes are emitted by renderComparison() but had NO rules
   anywhere in app.css — the results block has been rendering as unstyled
   inline spans. app.css still carries `.tz-home__calc-table` and
   `.tz-home__verdict` rules for an earlier table-based version of this
   output, which is where the styling went. Defining them here rather than
   reviving the table keeps the fix next to the markup that needs it.
   -------------------------------------------------------------------------- */

.tz-home__calc-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}
@media (min-width: 640px) {
  .tz-home__calc-grid { grid-template-columns: repeat(3, 1fr); }
}

.tz-home__calc-cell {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: clamp(18px, 2vw, 26px);
  border-radius: var(--lum-radius, 1rem);
  background: rgb(var(--color-surface-lowest));
  border: 1px solid rgb(var(--color-border-subtle) / 0.10);
  box-shadow: var(--lum-elev-2);
}
/* The TezzInc column is the answer, so it is the one with weight. It earns
   that from elevation and a firmer border — not from a fill colour, which
   would be the only saturated element on the page. */
.tz-home__calc-cell--ours {
  border-color: rgb(var(--color-on-surface) / 0.20);
  box-shadow: var(--lum-elev-3, var(--lum-elev-2));
}
.tz-home__calc-cell-label {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: rgb(var(--color-on-surface-ghost));
}
.tz-home__calc-cell-value {
  font-size: clamp(26px, 3vw, 38px);
  font-weight: 600;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  color: rgb(var(--color-on-surface));
}
.tz-home__calc-cell--save .tz-home__calc-cell-value { color: rgb(var(--color-on-surface)); }
.tz-home__calc-cell-sub {
  font-size: 12.5px;
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  color: rgb(var(--color-on-surface-variant));
}

/* ============================================================================
   Ambient background.
   A hairline grid plus a very faint radial lift, fixed behind the whole page.
   Both are drawn from the foreground token at very low alpha, so they invert
   correctly with the theme instead of being a hardcoded grey.
   ========================================================================== */

.tz-home__backdrop {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgb(var(--color-on-surface) / 0.030) 1px, transparent 1px),
    linear-gradient(90deg, rgb(var(--color-on-surface) / 0.030) 1px, transparent 1px);
  background-size: 76px 76px, 76px 76px;
  -webkit-mask-image: radial-gradient(120% 80% at 50% 0%, #000 0%, transparent 72%);
          mask-image: radial-gradient(120% 80% at 50% 0%, #000 0%, transparent 72%);
}
.tz-home > * { position: relative; z-index: 1; }

/* ============================================================================
   Custom cursor.
   Desktop pointers only, and only when the device actually has a fine pointer
   and the user has not asked for reduced motion. It is a hairline ring that
   scales on interactive targets — no dot, no trail, no lag gimmick.
   ========================================================================== */

.tz-cursor {
  position: fixed;
  top: 0; left: 0;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgb(var(--color-on-surface) / 0.42);
  border-radius: 50%;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  transition:
    opacity var(--lz-dur-ui) ease,
    width var(--lz-dur-interaction) var(--lum-ease, ease),
    height var(--lz-dur-interaction) var(--lum-ease, ease),
    margin var(--lz-dur-interaction) var(--lum-ease, ease),
    background-color var(--lz-dur-interaction) ease;
}
.tz-cursor[data-visible="true"] { opacity: 1; }
.tz-cursor[data-hot="true"] {
  width: 44px; height: 44px; margin: -22px 0 0 -22px;
  background: rgb(var(--color-on-surface) / 0.05);
}
@media (hover: none), (pointer: coarse) {
  .tz-cursor { display: none; }
}

/* ============================================================================
   Reduced motion.
   Every landing animation collapses to a static end state. The page must be
   complete and premium with all motion removed — that is the fallback the
   brief asks for, and it is also what a screen reader user gets.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .tz-home *,
  .tz-cursor {
    animation-duration: 0.001ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }
  .tz-motion .tz-home__line { clip-path: none; transform: none; }
  .tz-home__hero-stage { display: none; }
  .tz-cursor { display: none; }
}
/* The in-app toggle (`.tz-reduced-motion` on <html>) must behave identically
   to the OS preference. app.css sets the class; we mirror the rules. */
.tz-reduced-motion .tz-home *,
.tz-reduced-motion .tz-cursor {
  animation-duration: 0.001ms !important;
  animation-delay: 0ms !important;
  transition-duration: 0.001ms !important;
  transition-delay: 0ms !important;
}
.tz-reduced-motion .tz-motion .tz-home__line { clip-path: none; transform: none; }
.tz-reduced-motion .tz-home__hero-stage { display: none; }
.tz-reduced-motion .tz-cursor { display: none; }

/* Focus states are never sacrificed for aesthetics. app.css defines the ring;
   we only make sure the landing's own interactive elements opt into it. */
.tz-home__tab:focus-visible,
.tz-home__cta:focus-visible {
  outline: 2px solid rgb(var(--color-on-surface));
  outline-offset: 2px;
}
