/* The marketing pages' stylesheet. Generated from nothing: this is the source. Linked by renderMarketingShell with a content hash, cached immutable. */
* { box-sizing: border-box; }
:root { --paper:#faf7f0; --card:#fffdf8; --border:#e7e0d3; --ink:#1c1917; --soft:#44403c; --muted:#78716c; --faint:#a8a29e; --flame:#f0472c; --flame-dark:#d13a22; }
body { font-family: 'Iowan Old Style','Palatino','Charter','Georgia',serif; background: var(--paper); color: var(--ink); margin: 0; }
.sans { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
.wrap { max-width: 720px; margin: 0 auto; padding: 0 24px; }
body.home .wrap { max-width: 760px; }
header { display: flex; align-items: center; gap: 14px; padding: 22px 0; }
header .brandline { display: inline-flex; align-items: center; text-decoration: none; color: inherit; }
body.home header {
  position: relative;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 0 18px;
  gap: 0;
}
body.home header .brandline { margin: 0; }
body.home header .wordmark { gap: 2px; }
body.home header .wordmark .wm-z { width: 88px; height: 88px; }
body.home header .wordmark .wm-rest {
  font-size: 72px;
  transform: translateY(-4px);
}
.hero { padding: 28px 0 8px; }
body.home .hero { padding: 8px 0 8px; }
.kicker { font-family: -apple-system, sans-serif; font-size: 12px; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--flame-dark); margin: 0 0 12px; }
h1 { font-size: clamp(32px, 6vw, 46px); line-height: 1.1; letter-spacing: -0.02em; margin: 0 0 16px; font-weight: 700; }
h1 .line { display: block; }
h1 .accent, .hero-tag .accent { color: var(--flame); }
body.home h1 { text-align: center; }
body.home .hero-detail {
  max-width: 42rem;
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 8px;
  font-size: 15px;
  line-height: 1.55;
}
.hero-tag {
  text-align: center;
  font-size: clamp(18px, 3.4vw, 22px);
  line-height: 1.2;
  letter-spacing: -0.015em;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 18px;
  white-space: nowrap;
}
.lede { font-size: 18px; line-height: 1.6; color: var(--soft); margin: 0 0 22px; text-wrap: pretty; }
/* Home hero stays one centered column on laptop too (logo, headline, lede).
   Wider measure + pretty wrap so the long lede and Who/What/Why lines
   break cleanly at common laptop widths (~1100–1440). */
body.home .hero-copy {
  text-align: center;
  max-width: 46rem;
  margin: 0 auto;
}
body.home .hero-copy p {
  text-align: center;
  text-wrap: pretty;
  max-width: 42rem;
  margin-left: auto;
  margin-right: auto;
}
body.home .hero-copy .lede {
  max-width: 40rem;
}
.lede-mobile { display: none; }
body.home .hero-copy .cta {
  justify-content: center;
  max-width: none;
}
body.home .hero-copy .cta-jump { text-align: center; }
body.home .hero-copy .price-line,
body.home .hero-copy .trust { text-align: center; max-width: none; }
/* Button width tracks the price line ("From $4.99… Cancel anytime."). */
.cta-stack {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: fit-content;
  max-width: 100%;
  margin: 28px auto 0;
}
.cta-stack .cta-jump { margin: 0; }
.cta-stack .price-line { margin-top: 14px; white-space: nowrap; }
.cta-stack .trust { margin-top: 8px; }
h2 { font-size: 24px; margin: 36px 0 12px; letter-spacing: -0.01em; }
p { font-size: 16px; line-height: 1.65; color: var(--soft); margin: 0 0 14px; }
ul { margin: 0 0 18px; padding-left: 1.2em; color: var(--soft); }
li { margin: 0 0 8px; line-height: 1.55; }
.cta { margin: 28px 0 0; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.cta input[type=email] { flex: 1; min-width: 220px; font-family: -apple-system, sans-serif; padding: 16px 18px; font-size: 15px; background: var(--card); color: var(--ink); border: 1.5px solid var(--ink); border-radius: 12px; }
.cta-jump { margin: 28px 0 0; }
.cta-jump a {
  display: block; width: 100%; box-sizing: border-box;
  font-family: -apple-system, sans-serif; font-size: 18px; font-weight: 800;
  letter-spacing: -0.01em; line-height: 1.2;
  color: var(--card); background: var(--flame); text-decoration: none;
  padding: 18px 28px; text-align: center;
  border-radius: 12px; box-shadow: 0 2px 0 var(--flame-dark);
}
.cta-jump a:hover { background: var(--flame-dark); }
.cta button { font-family: -apple-system, sans-serif; padding: 16px 22px; font-size: 15px; font-weight: 700; background: var(--flame); color: var(--card); border: none; border-radius: 12px; cursor: pointer; box-shadow: 0 2px 0 var(--flame-dark); white-space: nowrap; }
.cta button:hover { background: var(--flame-dark); }
.price-line { font-family: -apple-system, sans-serif; font-size: 15px; line-height: 1.5; font-weight: 600; color: var(--ink); margin: 14px 0 0; }
.price-line b { color: var(--flame-dark); }
.price-line a.price-link,
.zm-end .zm-body a.price-link { color: inherit; text-decoration: none; border-bottom: 1.5px solid color-mix(in srgb, var(--flame-dark) 45%, transparent); }
.price-line a.price-link:hover,
.zm-end .zm-body a.price-link:hover { border-bottom-color: var(--flame-dark); }
.price-line .muted { font-weight: 400; color: var(--muted); }
/* Comparison pages: the dated price disclosure and the row of other pages. */
p.muted { color: var(--muted); font-size: 14px; }
.compare-links { margin-top: 28px; font-size: 14px; color: var(--muted); }
.compare-links a { color: var(--flame-dark); }
.trust { font-family: -apple-system, sans-serif; font-size: 13px; color: var(--muted); margin-top: 8px; }
.trust a { color: var(--flame-dark); }
.faq { margin: 40px 0 20px; }
.faq details { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 14px 18px; margin: 0 0 10px; }
.faq summary { font-family: -apple-system, sans-serif; font-weight: 700; cursor: pointer; }
.faq p { margin: 10px 0 0; font-size: 15px; }
.plan-toggle { display: inline-flex; font-family: -apple-system, sans-serif; background: var(--card); border: 1.5px solid var(--ink); border-radius: 999px; padding: 3px; gap: 2px; margin: 8px 0 22px; }
.plan-toggle button { appearance: none; border: none; background: transparent; color: var(--soft); font: inherit; font-size: 14px; font-weight: 700; padding: 10px 18px; border-radius: 999px; cursor: pointer; }
.plan-toggle button[aria-pressed="true"] { background: var(--ink); color: var(--card); }
.price-card { background: var(--card); border: 1px solid var(--border); border-radius: 16px; padding: 28px 26px; margin: 0 0 18px; }
.price-card .amount { font-family: -apple-system, sans-serif; font-size: clamp(36px, 7vw, 48px); font-weight: 800; letter-spacing: -0.03em; color: var(--ink); margin: 0 0 4px; line-height: 1; }
.price-card .amount .period { font-size: 18px; font-weight: 600; color: var(--muted); letter-spacing: 0; }
.price-card .was { font-family: -apple-system, sans-serif; font-size: 15px; color: var(--muted); text-decoration: line-through; margin: 0 0 10px; }
.price-card .save { display: inline-block; font-family: -apple-system, sans-serif; font-size: 12px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; color: var(--flame-dark); background: #fde8e3; border-radius: 999px; padding: 5px 10px; margin: 0 0 14px; }
.price-card .equiv { font-family: -apple-system, sans-serif; font-size: 15px; color: var(--soft); margin: 0 0 18px; }
.price-card ul { margin: 0 0 22px; }
.price-card .cta { margin: 0; }
.price-card .cta button, .price-card .cta a.btn {
  display: inline-block; font-family: -apple-system, sans-serif; padding: 16px 22px; font-size: 15px; font-weight: 700;
  background: var(--flame); color: var(--card); border: none; border-radius: 12px; cursor: pointer; text-decoration: none;
  box-shadow: 0 2px 0 var(--flame-dark); width: 100%; text-align: center;
}
.price-card .cta button:hover, .price-card .cta a.btn:hover { background: var(--flame-dark); }
.price-card .cta button:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
.plan-panel[hidden] { display: none !important; }
footer { border-top: 1px dashed var(--border); margin-top: 40px; padding: 24px 0 42px; font-family: -apple-system, sans-serif; font-size: 13px; color: var(--muted); display: flex; flex-direction: column; gap: 12px; }
footer .footer-links { display: flex; gap: 18px; flex-wrap: wrap; }
footer .footer-compare { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: baseline; }
footer .footer-compare-label { color: var(--muted); }
footer a { color: var(--flame-dark); }
footer .legal { line-height: 1.55; margin: 0; display: flex; flex-wrap: wrap; gap: 0 0.35em; align-items: baseline; }
footer .legal-entity { white-space: nowrap; flex-shrink: 0; }
.faq-page h2 { margin-top: 42px; font-size: clamp(22px, 4vw, 28px); }
.faq-page .faq-lead { font-size: 17px; color: var(--soft); margin: 0 0 22px; max-width: 40em; }
.faq-page .faq { margin-top: 12px; }
.faq-page .faq-toc { font-family: -apple-system, sans-serif; font-size: 14px; line-height: 1.7; color: var(--soft); margin: 0 0 28px; padding: 16px 18px; background: var(--card); border: 1px solid var(--border); border-radius: 12px; }
.faq-page .faq-toc a { color: var(--flame-dark); }
.faq-page .faq-toc strong { display: block; color: var(--ink); margin-bottom: 6px; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; }
@media (max-width: 520px) {
  .wrap { padding: 0 18px; }
  header { padding: 18px 0 14px; }
  header .wordmark .wm-z { width: 34px; height: 34px; }
  header .wordmark .wm-rest { font-size: 26px; }
  body.home header .owner-key { top: 12px; font-size: 13px; padding: 7px 8px; }
  .hero { padding: 20px 0 6px; }
  body.home .hero { padding: 4px 0 6px; }
  .kicker { font-size: 10.5px; line-height: 1.4; }
  h1 { font-size: clamp(28px, 8.2vw, 34px); line-height: 1.12; }
  body.home .hero-tag,
  body.home h1,
  body.home .hero-copy,
  body.home .hero-copy p { text-align: center; }
  body.home .hero-copy .cta,
  body.home .hero-copy .cta-jump,
  body.home .hero-copy .price-line,
  body.home .hero-copy .trust { text-align: center; }
  .hero-tag { font-size: clamp(16px, 4.6vw, 20px); margin-bottom: 14px; }
  .lede { font-size: 17px; line-height: 1.55; text-wrap: pretty; }
  .lede-full { display: none; }
  .lede-mobile { display: block; }
  body.home header { padding: 28px 0 10px; }
  body.home header .wordmark .wm-z { width: 56px; height: 56px; }
  body.home header .wordmark .wm-rest { font-size: 46px; transform: translateY(-2px); }
  body.home .hero-tag { margin-bottom: 10px; }
  body.home h1 { margin-bottom: 10px; font-size: clamp(26px, 7.4vw, 32px); }
  body.home .hero-copy .lede {
    margin-bottom: 12px;
    font-size: clamp(14px, 3.7vw, 16px);
    line-height: 1.4;
    text-wrap: pretty;
  }
  body.home .hero-detail { display: none; }
  body.home .hero-copy .cta,
  body.home .hero-copy .cta-stack { margin-top: 16px; }
  body.home .hero-copy .cta-stack { width: 100%; }
  body.home .hero-copy .cta-stack .price-line { white-space: normal; }
  .cta { gap: 8px; margin-top: 24px; }
  .cta input[type=email], .cta button { width: 100%; flex: 1 0 100%; }
  .cta button { min-height: 52px; }
  .cta-jump a {
    display: block; width: 100%; box-sizing: border-box; text-align: center; min-height: 52px;
    padding: 16px 22px; font-size: 17px;
  }
  .faq details { padding: 14px 16px; }
  footer .footer-links { gap: 12px 16px; }
  footer .footer-compare { gap: 6px 12px; }
  footer .legal { font-size: 12px; gap: 0 0.3em; }
  footer .legal-entity { white-space: nowrap; }
}

/* ---- the home page, as the approved motion study ----------------------
 *
 * Everything from here down is gated on body.home and deliberately sits
 * LAST in this stylesheet: it restates hero rules the shell sets earlier for
 * every page, and equal-specificity selectors are resolved by order. Move
 * this block up and the centred hero wins again.
 *
 * The other marketing pages are documents and stay documents. This is the
 * one page whose job is the argument, and it makes the argument by doing
 * what the product does.
 */
@font-face {
  font-family: "Source Serif 4";
  src: url("/fonts/source-serif-4-latin.woff2") format("woff2");
  font-weight: 200 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/fonts/space-grotesk-latin.woff2") format("woff2");
  font-weight: 300 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("/fonts/ibm-plex-mono-latin-400.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
body.home {
  --sans: "Space Grotesk", "Segoe UI", sans-serif;
  /*
   * Every headline on this page is Akmorn Grotesque Bold (founder, 2026-09-14:
   * "You saved it and you never read it needs to be in akmorn grotesque bold
   * on the landing page including other headers"). The face is named first
   * so it renders wherever it is installed; the file is not in the repo yet,
   * so Space Grotesk 700, which this page already ships, stands in. When the
   * woff2 lands: one @font-face above and nothing else changes.
   */
  --display: "Akmorn Grotesque", "Akmorn Grotesque Bold", var(--sans);
  --serif: "Source Serif 4", Georgia, serif;
  --mono: "IBM Plex Mono", ui-monospace, Menlo, monospace;
  /* Two tokens the rest of the site sets lighter. At --faint #a8a29e the
     counter's "unsorted" and the readout's caption sat on the edge of
     legible over a moving field; the study runs them four steps darker. */
  --muted: #6a645c; --faint: #706a65; --moss: #2f6f4f; --letgo: #3e5a74;
  font-family: var(--sans);
}

#zm-field {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 0; pointer-events: none; display: block;
}
#zm-field[hidden] { display: none; }
/* The document sits above the field and creates its own stacking context,
   so nothing inside it can slip underneath. */
body.home .wrap { position: relative; z-index: 1; max-width: 860px; padding: 0 34px; }
/*
 * Copy always wins over the field.
 *
 * Text straight onto 1,600 coloured boxes is something you have to work to
 * read, which is the wrong kind of impressive. Each band carries a soft pool
 * of paper that fades out before its edges, so the words sit on their own
 * ground while the field still runs behind and around them.
 *
 * Two wrong turns before this one. The study inset the pool's box to -14%,
 * which inside its clipped browser frame was fine; on a real page a box 28%
 * wider than its parent put 32px past the right edge of a 390px phone and
 * gave the document a horizontal scrollbar. Pulling the box back to the
 * column and widening the gradient instead fixed the scrollbar and drew a
 * hard vertical line down the page: at the column's left edge the gradient
 * was still most of the way opaque, so the field simply stopped there. That
 * is the faint box you could see around the copy.
 *
 * The pool is 100vw now, so it fades to nothing well outside the text and
 * its own edges are off screen. overflow-x: clip on the body absorbs the vw
 * against scrollbar difference; clip rather than hidden because hidden makes
 * a scroll container, and neither clips the canvas, which is positioned
 * against the viewport rather than against the body.
 */
body.home { overflow-x: clip; }
body.home .hero::before,
body.home .zm-band::before,
body.home footer::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  /* Full bleed, so the pool's own edges are off screen. */
  left: calc(50% - 50vw); width: 100vw;
}
body.home .zm-band::before {
  top: -10%; bottom: -10%;
  background: radial-gradient(ellipse 46% 40% at 50% 50%, var(--paper) 0%,
    var(--paper) 32%, color-mix(in srgb, var(--paper) 88%, transparent) 58%, transparent 88%);
}
/*
 * The hero's pool sits high, because its copy does.
 *
 * The study centres four short lines in its band, so its pool centres too.
 * Here the header is a real element above the hero rather than a line inside
 * it, so the tag, headline, lede and pill stack from the top and the bottom
 * half of the band is field. A pool centred at 50% cleared a screen-wide
 * bubble under the CTA with nothing in it.
 */
/*
 * Centred on the copy, measured rather than guessed.
 *
 * The wordmark through the pill occupies the top 34-37% of this box on every
 * size, so its centre sits at 29% down, and left-aligned copy in a centred
 * column has its own centre at 50% across. The pool was at 44% 40% on a
 * laptop and 46% 33% on a phone: low and left of what it was meant to be
 * covering on both, which is why it looked misplaced rather than merely big.
 */
/*
 * Re-fitted after the CTA stack left and the scroll cue took its place.
 *
 * The block the pool exists to sit under changed shape, so the pool had to
 * follow it: the copy now runs 21% to 51% of the painted box on a laptop
 * and 19% to 47% on a desk, centred at 36% and 33%. At 34% radius from 29%
 * the solid paper stopped around 42% and the cue landed in the falloff,
 * grey mono over a dense field. 38% from 34% puts solid paper from 20% to
 * 48% and the whole block, cue included, on ground it can be read against.
 */
body.home .hero::before {
  top: -170px; bottom: -10%;
  background: radial-gradient(ellipse 46% 44% at 50% 29%, var(--paper) 0%,
    var(--paper) 32%, color-mix(in srgb, var(--paper) 88%, transparent) 58%, transparent 88%);
}
/*
 * Smaller again on a phone.
 *
 * The pool is sized as a share of the frame, and a 390px frame is a quarter
 * the width of a laptop's: the same 52% that clears a comfortable margin
 * around a 792px column swallows almost the entire screen, so the field only
 * showed at the very corners. The copy is narrower here too, so it needs
 * proportionally less ground to sit on.
 */
@media (max-width: 640px) {
  /*
   * Narrower than the laptop's pool but taller, and centred higher.
   *
   * A phone hero is a tall thin box with the copy stacked in its top half,
   * where a laptop's is wide with the copy in the middle. Shrinking the
   * laptop ellipse uniformly took the pool off the wordmark and the tag
   * before it took it off anything else, and those are the two lines a
   * first-time visitor reads first.
   */
  /*
   * Wide, not tall. Percentages mislead here.
   *
   * A phone's pool box is about 390 across and 950 down, so "54% x 32%" is
   * 211px of horizontal radius against 304px of vertical: a portrait ellipse
   * standing on its end, which is why it read as the wrong shape rather than
   * the wrong size. The copy runs to 94% of a phone's width where a laptop's
   * stops at 78%, so the radii have to grow sideways and shrink vertically
   * to make the same landscape pool the laptop gets.
   */
  /* Same re-fit as the laptop, on a phone's block: wordmark at 2% to 10%,
     copy to 46%, so the pool centres at 24% and reaches 40%. */
  body.home .hero::before {
    background: radial-gradient(ellipse 78% 40% at 50% 24%, var(--paper) 0%,
      var(--paper) 32%, color-mix(in srgb, var(--paper) 86%, transparent) 58%, transparent 88%);
  }
  body.home .zm-band::before {
    background: radial-gradient(ellipse 78% 34% at 50% 50%, var(--paper) 0%,
      var(--paper) 32%, color-mix(in srgb, var(--paper) 86%, transparent) 58%, transparent 88%);
  }
}

/* ---- hero, as the study's first band ---------------------------------- */
/*
 * Centred and twice the size.
 *
 * The mark sat left at 40px, which is a site header's wordmark: something
 * you navigate by, parked out of the way. This page has no navigation and
 * the header is the hero's first line, so it should read as a title over
 * centred copy rather than as chrome beside it.
 *
 * These sizes are set HERE rather than in the earlier body.home block,
 * because these selectors are equally specific and sit later in the sheet:
 * the 56px/46px rule inside the phone breakpoint above never applied, media
 * query or not, which is why the phone's mark was the same 40px as a desk's.
 */
body.home header {
  position: relative; flex-direction: row; align-items: center; justify-content: center;
  padding: 26px 0 0; gap: 14px;
}
body.home header .wordmark { gap: 2px; }
body.home header .wordmark .wm-z { width: 76px; height: 76px; }
body.home header .wordmark .wm-rest { font-size: 52px; transform: translateY(-3px); }
body.home header .owner-key { top: 26px; }
/*
 * Hero and header together are 92vh, which is what the study's first band
 * measures. The 66px is that header: 26px of padding plus a 40px mark, both
 * set eight lines above, so the two cannot drift apart independently.
 */
body.home .hero {
  position: relative; min-height: calc(92vh - 66px); display: flex; flex-direction: column;
  justify-content: flex-start; padding: 22px 0 40px; text-align: left;
}
body.home .hero-tag {
  text-align: left; font-family: var(--sans); font-size: 13px; font-weight: 700;
  letter-spacing: 0; line-height: normal; color: var(--muted); margin: 0 0 10px;
  white-space: normal;
}
body.home h1 {
  font-family: var(--serif); font-size: clamp(32px, 5.4vw, 58px); line-height: 1.06;
  font-weight: 600; letter-spacing: -0.02em; margin: 0 0 16px; text-align: left;
}
body.home h1 .line { display: block; }
body.home .hero-copy { text-align: left; max-width: none; margin: 0; }
body.home .hero-copy p { text-align: left; }
body.home .hero-copy .lede {
  font-size: 17px; line-height: 1.65; color: var(--soft); max-width: 54ch; margin: 0 0 18px;
}
body.home .hero-copy .hero-detail {
  max-width: 54ch; margin: 0 0 8px; font-size: 14.5px; line-height: 1.6; color: var(--muted);
}
body.home .hero-copy .hero-detail strong { color: var(--soft); }
/*
 * The study's CTA is a pill sized to its own words, not a bar.
 *
 * .cta-jump a is display:block/width:100% because on the other pages it
 * sits in a narrow centred stack where filling the column is right. Left
 * aligned at 792px it drew a flame band across the page.
 *
 * Centred, because the closing band is now the only one of these on the
 * page and that band is centred at every width. The rule said left, from
 * when it also styled a hero pill under a left-aligned headline: with the
 * hero's pill gone that inherited alignment left the one remaining way into
 * the trial sitting 324px off the axis on a laptop and 102px on a phone,
 * under copy that was centred over it.
 */
body.home .cta-jump { margin: 0; text-align: center; }
body.home .cta-jump a {
  display: inline-flex; align-items: center; gap: 10px; width: auto; min-height: 0;
  font-family: var(--sans); font-size: 15px; font-weight: 700; letter-spacing: 0;
  line-height: normal; color: #fff;
  padding: 14px 26px; border-radius: 999px; box-shadow: none; text-align: left;
}
body.home .price-line { font-size: 14px; color: var(--muted); margin-top: 14px; }
body.home .trust { font-size: 13px; color: var(--faint); }

/*
 * The way down.
 *
 * The lede used to end "Keep scrolling." and it was not landing: a
 * first-time visitor scans a hero, does not reach the end of the third
 * line, and a page whose whole argument is below the fold gets about a
 * second to say so. A sentence is the wrong instrument. Motion is caught
 * before it is read.
 *
 * width: fit-content with auto margins rather than text-align, because this
 * is a block that has to sit on the column's axis and text-align only moves
 * the lines inside it. That distinction has now cost this page two bugs.
 */
body.home .zm-scroll {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: fit-content; margin: 34px auto 0; text-decoration: none;
  font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--soft);
}
/*
 * Its own pool, because the hero's does not reach this far down.
 *
 * Founder report from a phone, with the screenshot: "Scroll button needs to
 * be bigger and more obvious. It's blending it." Measured at 393px, the cue
 * sits at 46% of the hero while the phone pool's solid paper stops at about
 * 37% and has washed out entirely by 47% -- so the word was mono grey
 * straight onto 1,600 coloured boxes. The laptop is the same fault with
 * less of it: the cue is at 50% against solid paper to 43%.
 *
 * The comment on .hero::before says a re-fit put "the whole block, cue
 * included, on ground it can be read against". That was true of the block
 * it was fitted to; the cue has since moved down past the falloff on both
 * sizes. Widening the hero pool again is the wrong lever -- one ellipse
 * cannot cover a wordmark at 2% and a cue at 50% without swallowing the
 * whole hero, which is the failure the phone pool's own comment records.
 * A second, small pool costs nothing and moves with the cue.
 *
 * Same gradient language as the bands so it reads as the page's own device
 * rather than a box: paper in the middle, gone before its edges.
 */
body.home .zm-scroll::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  width: 240px; height: 132px;
  background: radial-gradient(ellipse 50% 50% at 50% 50%, var(--paper) 0%,
    var(--paper) 42%, color-mix(in srgb, var(--paper) 86%, transparent) 64%, transparent 88%);
}
body.home .zm-scroll svg { color: var(--flame); width: 34px; height: 20px; }
body.home .zm-scroll:hover { color: var(--flame); }
body.home .zm-scroll:focus-visible { outline: 2px solid var(--flame); outline-offset: 6px; border-radius: 4px; }
/*
 * The whole cue travels, and the chevron fades at the bottom of the fall.
 *
 * A 7px bob on a 12px chevron was a twitch: at a laptop's viewing distance
 * it read as a static mark, which is the one thing a scroll cue cannot be.
 * Moving the word with it makes the gesture legible from the corner of the
 * eye, and the fade gives it a direction rather than a wobble.
 *
 * On the CHILDREN, not on the anchor. The hero reveal sets animation-name
 * on .zm-scroll itself and sits further down this sheet, so a beckon
 * declared there would be overwritten by zm-pop and the cue would arrive
 * once and then never move again.
 */
body.home .zm-scroll-word { animation: zm-beckon 2.2s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
body.home .zm-scroll svg {
  animation: zm-beckon 2.2s cubic-bezier(0.45, 0, 0.55, 1) infinite,
             zm-chev 2.2s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes zm-beckon {
  0%, 100% { transform: translateY(0); }
  45% { transform: translateY(12px); }
}
@keyframes zm-chev {
  0% { opacity: 0.45; }
  30% { opacity: 1; }
  70% { opacity: 1; }
  100% { opacity: 0.45; }
}
/*
 * Bigger on the phone, not smaller.
 *
 * It was 12px type and a 30x18 chevron in a 58x42 box -- the smallest thing
 * on the screen, on the busiest ground, and the only control in the hero.
 * The phone is where this cue does its whole job, because the laptop shows
 * enough of the next band to imply the scroll on its own.
 */
@media (max-width: 640px) {
  body.home .zm-scroll {
    font-size: 15px; margin-top: 30px; gap: 12px;
    /* A real target, not just bigger text. */
    padding: 10px 20px; min-height: 44px; justify-content: center;
  }
  body.home .zm-scroll svg { width: 40px; height: 24px; }
  body.home .zm-scroll::before { width: 210px; height: 140px; }
}

/*
 * Centred from the laptop up, left-aligned on a phone.
 *
 * The bands sit in a centred 860px column, but their copy has a 52ch measure
 * inside it, so left-aligned prose occupied the left 480px of a 792px column
 * and the page read as weighted to one side even though the column itself
 * was centred. On a phone the copy already fills the width, so centring
 * there would only cost the ragged-right edge that makes it easy to read.
 */
@media (min-width: 641px) {
  body.home .hero,
  body.home .zm-band { text-align: center; }
  body.home .hero-tag,
  body.home h1,
  body.home .hero-copy,
  body.home .hero-copy p,
  body.home .hero-copy .cta-jump,
  body.home .hero-copy .price-line,
  body.home .hero-copy .trust,
  .zm-band h2,
  .zm-band .zm-cap,
  .zm-band .zm-count { text-align: center; }
  /* Anything with its own measure has to be re-centred as a block, or it
     stays pinned left and only its lines move. .zm-band .zm-body is not
     here: it carries its own auto margins, for the reason written above it. */
  body.home .hero-copy .lede { margin-left: auto; margin-right: auto; }
  body.home .hero-copy .cta-stack { margin-left: auto; margin-right: auto; align-items: center; }
  body.home .hero-copy .cta-jump,
  body.home .hero-copy .cta-stack .price-line,
  body.home .hero-copy .cta-stack .trust { margin-left: auto; margin-right: auto; }
  /*
   * The speed band keeps its left-aligned diagram.
   *
   * Its queue and readout are a two-column grid that has to be read left to
   * right, and centred list items with a bullet on the left are a different
   * component. Only its heading and lede centre.
   */
  .zm-band[data-phase="speed"] .zm-speed { text-align: left; }
  /* The pool follows the copy: centred text centres its own ground. */
  body.home .hero::before,
  body.home .zm-band::before { background-position-x: center; }
}

/* ---- the four steps --------------------------------------------------- */
.zm-arc { position: relative; margin: 0; }
.zm-band {
  position: relative; min-height: 92vh; display: flex; flex-direction: column;
  justify-content: center; padding: 40px 0;
}
.zm-band.tall { min-height: 120vh; }
.zm-cap {
  font-family: var(--mono); font-size: 11px; font-weight: 400; letter-spacing: 0.12em;
  line-height: normal; text-transform: uppercase; color: var(--flame); margin: 0 0 12px;
}
/* The serif was the band voice until 2026-09-14; the founder moved every
   headline to the display grotesque. --display, never a face name here. */
body.home .hero h1,
.zm-band h2 {
  font-family: var(--display); font-size: clamp(32px, 5.4vw, 58px); line-height: 1.04;
  font-weight: 700; letter-spacing: -0.03em; margin: 0 0 16px; text-wrap: balance;
}
body.home .hero h1 { font-size: clamp(38px, 7vw, 62px); margin-bottom: 20px; }

/* ---- Map and Podcast bands ------------------------------------------- */
/*
 * No card, no border, no background: the stage is a full-bleed transparent
 * strip, like the pool behind the copy, so the field's columns show through
 * it and the boxes landing.js throws start in those columns and end on the
 * map. The first version drew each band in a bordered box with its own
 * background, and the founder read them as "solid, different elements than
 * the rest of the animation" -- the box was the disjoint. The height is
 * the stage's; the canvas takes the viewport's width so a box can leave a
 * column at the edge of the screen.
 */
.zm-stage { position: relative; height: 440px; margin: 26px 0 0; }
.zm-stage canvas {
  position: absolute; top: 0; height: 100%; display: block; pointer-events: none;
  left: calc(50% - 50vw); width: 100vw;
}
.zm-stage-wave { height: 300px; margin-top: 18px; }
/*
 * The wave and the player sit on a pool of their own. By this band the
 * field is one stream down the middle of the page, exactly where the wave's
 * centre and the episode title are, and both were unreadable on it. The
 * pool hangs below the stage to cover the player row too, and it starts
 * faint at the top so the stream is still seen feeding the throat.
 */
.zm-stage-wave::before {
  content: ""; position: absolute; pointer-events: none;
  /* Down past the live line, which is two rows on a phone. */
  left: calc(50% - 50vw); width: 100vw; top: 8%; height: 200%;
  background: radial-gradient(ellipse 34% 46% at 50% 50%, var(--paper) 0%,
    var(--paper) 44%, color-mix(in srgb, var(--paper) 88%, transparent) 66%, transparent 92%);
}
.zm-stage canvas { z-index: 1; }
/*
 * The pool follows the copy on the two bands that carry a stage. The
 * band's pool is centred on the band, and a band that is half stage puts
 * its copy in the top third: on a phone the columns ran straight through
 * "Then they get mapped." The stage wants the field showing through it
 * anyway, so the pool sits where the words are.
 */
body.home .zm-band[data-phase="map"]::before,
body.home .zm-band[data-phase="podcast"]::before {
  background: radial-gradient(ellipse 50% 30% at 50% 30%, var(--paper) 0%,
    var(--paper) 32%, color-mix(in srgb, var(--paper) 88%, transparent) 58%, transparent 88%);
}
/* The player is a row on the paper, not a card: play and the time. */
.zm-pod {
  /* Above the stage's pool: a positioned ::before paints over any later
     sibling that is not positioned, and the row was fading under it. */
  position: relative; z-index: 2;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 10px 14px; max-width: 640px; margin: 10px auto 0; text-align: left;
}
/*
 * Play is glass, not a solid disc. Founder: "so it looks cooler and not so
 * solid and archaic." The same recipe as the app's glass buttons: a tint
 * of the flame over the paper with a lit top edge, a thin flame ring, the
 * triangle in flame rather than white, and a halo that breathes out of it
 * while the band is on screen (still under reduced motion).
 */
.zm-pod-btn {
  position: relative; width: 46px; height: 46px; border-radius: 50%; flex: 0 0 46px;
  display: grid; place-items: center; color: var(--flame);
  /* A real button, so it resets what a button brings. */
  border: 0; padding: 0; margin: 0; font: inherit; cursor: pointer;
  background:
    radial-gradient(circle at 35% 28%, color-mix(in srgb, var(--flame) 34%, transparent),
      color-mix(in srgb, var(--flame) 10%, transparent) 72%);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
    0 0 0 1px color-mix(in srgb, var(--flame) 48%, transparent),
    0 10px 22px -12px color-mix(in srgb, var(--flame) 70%, transparent);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.zm-pod-btn:focus-visible { outline: 2px solid var(--flame); outline-offset: 3px; }
.zm-pod-btn svg { width: 17px; height: 17px; display: block; grid-area: 1 / 1; }
/* Two glyphs in the markup; the state picks one. The play triangle sits
   2px right of centre because a centred triangle reads as left-heavy. */
.zm-pod-btn .zm-pod-play { margin-left: 2px; }
.zm-pod-btn .zm-pod-pause { display: none; }
.zm-pod-btn.is-playing .zm-pod-play { display: none; }
.zm-pod-btn.is-playing .zm-pod-pause { display: block; }
/* The halo breathes only while the wave plays. */
.zm-pod-btn::after {
  content: ""; position: absolute; inset: -1px; border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--flame) 55%, transparent);
  opacity: .5;
}
.zm-pod-btn.is-playing::after { animation: zm-halo 2.6s cubic-bezier(0.32, 0.72, 0, 1) infinite; }
@keyframes zm-halo { from { transform: scale(1); opacity: .9; } to { transform: scale(1.7); opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .zm-pod-btn::after { animation: none; opacity: .5; } }
/* The hosts' pills and the live transcript line sat here until
   2026-09-16; the founder: "we don't really need the transcript below, it
   looks weird, remove the names Ada and Wes as well." The script still
   drives the wave and the conductor; it is just not printed. */
.zm-pod-time { margin-left: auto; }
.zm-pod-time { font-family: var(--mono); font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
@media (max-width: 640px) {
  /* The copy fills the width on a phone, so its pool has to as well. */
  body.home .zm-band[data-phase="map"]::before,
  body.home .zm-band[data-phase="podcast"]::before {
    background: radial-gradient(ellipse 72% 30% at 50% 30%, var(--paper) 0%,
      var(--paper) 40%, color-mix(in srgb, var(--paper) 88%, transparent) 64%, transparent 90%);
  }
  .zm-stage { height: 360px; }
  .zm-stage-wave { height: 240px; }
  .zm-pod { justify-content: flex-start; }
}
/*
 * Auto on the sides at every width, not only inside the centring
 * breakpoint.
 *
 * It was in the breakpoint, and this shorthand sat further down the sheet on
 * an equally specific selector, so "margin: 0 0 22px" reset it and the block
 * stayed pinned to the column's left edge with only its lines centred:
 * measured 136px off the headline's axis at 1180 and at 1440. Auto costs
 * nothing on a phone, where 52ch is wider than the column and the free space
 * the margins would split is zero.
 */
.zm-band .zm-body {
  font-family: var(--sans); font-size: 16px; line-height: 1.7; color: var(--soft);
  max-width: 52ch; margin: 0 auto 22px; text-wrap: pretty;
}
/* Nothing follows it on the three bands that are only copy, so the trailing
   margin would just push a vertically centred block off centre. */
.zm-band .zm-body:last-child { margin-bottom: 0; }
/*
 * line-height: normal on every one of these, because the shell styles bare
 * <p> at 16px/1.65 in --soft for its document pages and each of these is a
 * <p>. That rule is why the counter came out four steps lighter than the
 * study's and 38px taller in its line box: a class with no line-height
 * declaration does not beat an element selector that has one.
 */
.zm-count {
  font-family: var(--mono); font-size: clamp(46px, 9vw, 110px); font-weight: 400;
  color: var(--ink); letter-spacing: -0.03em; line-height: normal; margin: 0 0 6px;
}
/* No letter-spacing reset: "unsorted" inherits the counter's -0.03em in the
   study, which at 35px is the -1px that keeps it tucked under the number. */
.zm-count .zm-of { color: var(--faint); font-size: 0.32em; }

/* ---- the destinations band --------------------------------------------
 *
 * The saves leave, and you watch them go.
 *
 * The first cut of this band drew two lists of arriving rows, which said
 * "things show up in Notion" without ever saying what a thing is. What
 * crosses the gap now is the same card the speed band triages - avatar,
 * handle, field, the post itself - so what lands in Notion is visibly the
 * tweet you just marked done rather than an abstraction of one.
 *
 * A keep goes to both, so every card splits the moment it is marked and
 * flies both ways at once. The distinction the copy draws survives the
 * landing: Notion's rows arrive and stay, because a live push takes them
 * one at a time; Obsidian's gather and dim as zeromark-keeps.md takes
 * over, because a download leaves once, as a file.
 *
 * Every animation here runs on --cyc and is offset by whole fifths of it,
 * so nothing can drift out of phase with anything else. Four saves take
 * four fifths of a pass; the last fifth is the file being written.
 *
 * The marks are the app's own: the Notion tile and the crystal already
 * shipping in Memory. app.css and app.html both record why - shipping
 * Notion's logo would bundle someone else's trademark, and Obsidian's is
 * not licensed for commercial use without permission. Swapping in official
 * artwork later is a change to .zm-dest-mark and nothing else.
 */
.zm-ship { --cyc: 8s; position: relative; margin: 30px auto 0; }

/* The queue holds its own height, so the destinations below it cannot
   shift when a card leaves: every flying card is out of flow. */
.zm-queue { position: relative; min-height: 122px; }
/*
 * Full band width, not card width, and that is the whole trick.
 *
 * The flight is translateX(+/-25%), and a percentage inside a transform
 * resolves against the element's own border box. A wrapper that spans the
 * band resolves 25% to a quarter of the band, which is exactly the
 * distance from the band's centre to a destination card's centre - at
 * every viewport width, with nothing measured and no script to measure it.
 */
.zm-dest {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  margin: 26px 0 0;
}
.zm-dest-card {
  /* Positioning context for the flyers below, and the clip that keeps a
     save's approach from painting outside the card it is arriving at. */
  position: relative; overflow: hidden;
  flex: 1 1 240px;
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 20px 20px 22px;
  background: var(--card);
  text-align: left;
}
.zm-dest-mark {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  margin-bottom: 12px;
}
.zm-dest-mark[data-dest="notion"] {
  background: var(--ink);
  color: var(--paper);
  font-family: var(--serif);
  font-size: 19px;
  font-weight: 700;
  line-height: 1;
}
/* Tinted tile, so the crystal reads as the Notion square's pair rather
   than as a bare glyph sitting next to a filled one. */
.zm-dest-mark[data-dest="obsidian"] {
  color: #7c5cff;
  background: color-mix(in srgb, #7c5cff 12%, var(--paper));
}
.zm-dest-name {
  font-size: 19px;
  font-weight: 700;
  margin: 0 0 6px;
  letter-spacing: -0.01em;
}
/*
 * Miniatures of where a keep lands.
 *
 * The handle feed says saves arrive; it does not say what they arrive AS.
 * A Notion keep is a row in a database with real columns, and an Obsidian
 * keep is a node that pulls on the ones near it — those two shapes are the
 * product claim, and describing them in a sentence asks the reader to
 * picture something they already know by sight.
 *
 * Deliberately small and static. The feed above them is the animated
 * element in this band, and a second moving thing beside it would compete
 * for the same glance. These are recognisable at a glance and then stop
 * asking for attention.
 */
.zm-mini {
  margin: 14px 0 0; border: 1px solid var(--border); border-radius: 8px;
  background: var(--card); overflow: hidden;
}
/* Notion: a database, which means columns with names and typed cells. */
/*
 * Sized up because the feed above them is gone.
 *
 * The sheet used to call these "deliberately small and static", and the
 * stated reason was that the handle feed was the band's one animated
 * element and a second moving thing beside it would compete for the same
 * glance. Removing the feed removes the competition -- so this is not a
 * reversal of that decision, it is the condition the decision rested on.
 */
.zm-db-head, .zm-db-row {
  display: grid; grid-template-columns: 1fr 74px 52px; align-items: center;
  gap: 8px; padding: 9px 12px; font-family: var(--sans); font-size: 12.5px;
}
.zm-db-head {
  color: var(--faint); border-bottom: 1px solid var(--border);
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 10px;
}
.zm-db-row { color: var(--soft); border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.zm-db-row:first-of-type { border-top: 0; }
.zm-db-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
/* The pill is what makes a cell read as a Notion select rather than text. */
.zm-db-tag {
  justify-self: start; padding: 1px 7px; border-radius: 999px; font-size: 9.5px;
  background: color-mix(in srgb, var(--flame) 12%, transparent);
  color: var(--flame-dark); white-space: nowrap;
}
.zm-db-when { color: var(--faint); font-variant-numeric: tabular-nums; text-align: right; }
/* Obsidian: the graph. Nodes sized by how much links to them, which is the
   one thing the picture is actually saying. */
.zm-graph { display: block; width: 100%; height: 168px; margin-top: 14px; }
.zm-graph line { stroke: color-mix(in srgb, #7c5cff 38%, transparent); stroke-width: 1; }
.zm-graph circle { fill: #7c5cff; }
.zm-graph circle.hub { fill: #7c5cff; }
.zm-graph text {
  font-family: var(--sans); font-size: 7px; fill: var(--muted);
}
/*
 * The stream lands, and the landing is what fills the destination.
 *
 * This band used to run two loops side by side: a handle feed saying saves
 * arrive, and under it miniatures on their own 3.6s cycle saying what they
 * arrive AS. The feed is gone -- it said the weaker of the two things, and
 * it was taking the room the miniatures needed to say the stronger one.
 *
 * What replaces it is not a third loop. The hero field's own boxes fall
 * through a funnel drawn on the canvas above, and a row or a node appears
 * because a box reached it. That is why there are no infinite keyframes
 * here any more: landing.js adds .lit at the frame something arrives, and
 * the element holds that state until the destination has filled and resets.
 * Timing lives with the thing doing the throwing.
 *
 * The .zm-ship.on prefix on the .lit rules is load-bearing. Without it,
 * .zm-db-row[data-i].lit is (0,3,0) against a (0,4,0) rule, and a row told
 * to land would keep the other rule's opacity: 0 -- lit and invisible at
 * the same time. That exact collision cost a debugging pass already.
 */
#zm-funnel {
  position: absolute; inset: 0; width: 100%; height: 100%;
  /* Above the cards: the last stretch of a throw is the stretch where the
     box turns into the row, and behind the card that is invisible. */
  z-index: 4; pointer-events: none;
}
.zm-ship .zm-dest { position: relative; z-index: 3; }

.zm-db-row[data-i] { opacity: 0; transform-origin: left center; }
.zm-graph circle[data-i] { opacity: 0; transform-box: fill-box; transform-origin: center; }
.zm-graph line[data-i] { stroke-dasharray: 70; stroke-dashoffset: 70; }

@media (prefers-reduced-motion: no-preference) {
  .zm-ship.on .zm-mini { animation: zm-mini-in 420ms ease-out both; }
  .zm-ship.on .zm-graph { animation: zm-mini-in 420ms ease-out both; }
  .zm-ship.on .zm-db-row[data-i].lit {
    animation: zm-row-form 380ms cubic-bezier(0.2, 0.9, 0.2, 1) forwards;
  }
  .zm-ship.on .zm-graph circle[data-i].lit {
    animation: zm-node-form 440ms cubic-bezier(0.2, 1.7, 0.4, 1) forwards;
  }
  .zm-ship.on .zm-graph line[data-i].lit {
    animation: zm-edge-draw 440ms ease-out forwards;
  }
}
@keyframes zm-mini-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
/* Arrives from the left, carrying the flame of the thing that threw it. */
@keyframes zm-row-form {
  from { opacity: 0; transform: scaleX(0.9) translateX(-10px);
         background: color-mix(in srgb, var(--flame) 16%, transparent); }
  to   { opacity: 1; transform: none; background: transparent; }
}
@keyframes zm-node-form {
  0%   { opacity: 0; transform: scale(0); }
  62%  { opacity: 1; transform: scale(1.4); }
  100% { opacity: 1; transform: scale(1); }
}
/* The edge reaches for the hub only once the node exists. */
@keyframes zm-edge-draw { to { stroke-dashoffset: 0; } }
/*
 * Reduced motion gets the arrived state, not the pre-arrival one: every
 * keyframe above sits in a no-preference query, so these would otherwise
 * hold their defaults, which for a node is scale(0) -- an empty graph.
 */
@media (prefers-reduced-motion: reduce) {
  #zm-funnel { display: none; }
  .zm-db-row[data-i] { opacity: 1; transform: none; }
  .zm-graph circle[data-i] { opacity: 1; transform: none; }
  .zm-graph line[data-i] { stroke-dashoffset: 0; }
}
.zm-dest-how {
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  color: var(--soft);
  margin: 0;
}
/* ---- what a landing leaves behind --------------------------------------
 *
 * A row is the receipt for a flight, so it appears at the frame the card
 * reaches it and not before. Its arrival is baked into the percentages
 * rather than set with animation-delay: delayed rows loop on their own
 * clocks, so they would clear one at a time, in arrival order, while the
 * file was already on screen. On one clock the card empties at once, the
 * way a pass that has been written out should.
 */
.zm-file {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 12.5px;
  color: #4c2fbf;
  margin: 10px 0 0;
  opacity: 0;
  animation: zm-file var(--cyc) linear infinite;
  animation-play-state: paused;
}
.zm-file-glyph {
  flex: none;
  width: 9px;
  height: 12px;
  border: 1.5px solid currentColor;
  border-radius: 2px;
}
@keyframes zm-file {
  0%, 88% { opacity: 0; transform: translateY(6px); }
  92%     { opacity: 1; transform: translateY(0); }
  97%     { opacity: 1; transform: translateY(0); }
  100%    { opacity: 0; transform: translateY(0); }
}
/* Paused until the band is on screen, the way the speed band's rail is. A
   dozen looping elements running behind four screens of scroll is battery
   nobody asked for. */
  .zm-ship.on .zm-file { animation-play-state: running; }

@media (prefers-reduced-motion: reduce) {
  /* The end of the pass, held still: both destinations full, the file
     written, nothing in the air. The claim survives without the motion. */
  .zm-queue { display: none; }
  .zm-file { animation: none; opacity: 1; transform: none; }
}

@media (max-width: 700px) {
  /* Stacked, so neither card is the narrow one. */
  .zm-dest { gap: 14px; margin-top: 22px; }
  .zm-dest-card { flex: 1 1 100%; padding: 18px; }
  .zm-dest-how { font-size: 14px; }
  /* Stacked, the split is vertical rather than sideways: both cards are
     dead ahead, one behind the other, so the two flights differ in how far
     they go instead of which way they went. */
}

/* ---- the speed band --------------------------------------------------- */
.zm-speed { display: grid; gap: 22px; }
.zm-left { display: grid; gap: 18px; align-content: start; }
/*
 * The clock sits BESIDE the queue, not under it. Stacked, the measured
 * number landed a screen below the rail: the band that exists to show speed
 * showed a queue with no clock and a clock with no queue.
 */
/*
 * 320px, not 290.
 *
 * "measured in your browser, just now" is 34 monospaced characters, and at
 * the caption's new 12.5px with its 0.1em tracking that measures 297px: it
 * wrapped "now" onto a line of its own inside a 290px column. The column is
 * the thing that should give, not the caption, because the rail beside it
 * clamps its text at three lines and simply absorbs the 20px.
 */
@media (min-width: 900px) {
  .zm-speed { grid-template-columns: minmax(0, 1fr) minmax(0, 320px); column-gap: 40px; align-items: start; }
  .zm-keys { grid-column: 1 / -1; }
}
/*
 * min-width: 0 at every level between the band and the tweet text. Grid and
 * flex items floor at min-content, and a nowrap line of tweet text has a
 * min-content width of the whole line: the ellipsis never fires, the rail
 * sets the column to ~660px, and on a phone the cards, the key row and the
 * attribute list all run off the right edge together.
 */
.zm-speed > *, .zm-left > *, .zm-rail, .zm-row { min-width: 0; }
.zm-keys { display: flex; gap: 8px; flex-wrap: wrap; }
.zm-key {
  font-family: var(--mono); font-size: 12px; color: var(--soft);
  border: 1px solid var(--border); border-bottom-width: 2px; border-radius: 7px;
  padding: 6px 10px; background: var(--card);
}
.zm-key b { color: var(--ink); font-weight: 400; }
/*
 * The three keys that decide a save wear that decision's colour.
 *
 * The cards beside them already leave in flame, blue and moss, but nothing
 * connected a colour to a verb: you saw a card go green without being told
 * green means snooze. These are the same three tokens the triage deck
 * reveals behind a card in the app, so the mapping a visitor learns here is
 * the one they meet after signing up.
 *
 * The letter and the weighted bottom edge carry it, with a 7% wash of the
 * same ink behind. Undo and the palette stay in ink, which also says which
 * three of the five are outcomes and which two are machinery.
 */
.zm-key[data-verb="done"] { --k: var(--flame); }
.zm-key[data-verb="letgo"] { --k: var(--letgo); }
.zm-key[data-verb="snooze"] { --k: var(--moss); }
.zm-key[data-verb] {
  background: color-mix(in srgb, var(--k) 7%, var(--card));
  border-color: color-mix(in srgb, var(--k) 34%, var(--border));
  border-bottom-color: var(--k);
}
.zm-key[data-verb] b { color: var(--k); }
/*
 * The rail clips its own cards.
 *
 * A card leaves by sliding 46px in its outcome's direction, and on a 390px
 * phone the rail is nearly the full width: the card ended 24px past the
 * document's right edge. overflow-x: clip on the body hid that from the
 * reader, but hiding an overflow is not the same as not having one. Clipping
 * at the rail also reads better, because the card leaves the queue rather
 * than floating away over the page.
 */
.zm-rail { display: grid; gap: 8px; max-width: 520px; overflow: hidden; }
/*
 * A card in the queue is a tweet, so it has to look like one.
 *
 * It used to be a handle, one clipped line and an ellipsis, which could
 * equally have been a to-do, an email subject or a browser tab: the band
 * argued about the speed of triaging saves without ever showing what a save
 * is. It now carries the avatar, the handle, the field the agents filed it
 * under, its age, the X glyph and the post itself.
 *
 * --f is the field's ink, set per card. It is one of the five the pile
 * behind the copy is painted in, so a card's avatar is the same colour as
 * the boxes it came out of.
 */
.zm-row {
  position: relative; display: grid; grid-template-columns: 34px minmax(0, 1fr);
  gap: 11px; padding: 13px 15px 14px;
  border: 1px solid var(--border); border-radius: 14px; background: var(--card);
  font-family: var(--sans); color: var(--soft); will-change: transform, opacity;
}
.zm-av {
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  font-size: 14px; font-weight: 700; line-height: normal; color: #fff; background: var(--f, var(--flame));
}
.zm-who { display: flex; align-items: center; gap: 7px; min-width: 0; margin: 0 0 3px; line-height: normal; }
.zm-row b {
  color: var(--ink); font-size: 13.5px; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
.zm-field {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--f, var(--flame)); white-space: nowrap; flex: none;
}
.zm-when { margin-left: auto; font-size: 12px; color: var(--faint); white-space: nowrap; flex: none; }
.zm-x { flex: none; color: var(--faint); display: block; }
/*
 * Three lines, floored as well as capped.
 *
 * The saves are 48 characters and 270 characters and everything between, so
 * an unclamped rail changed height under itself twice a second while two
 * cards swapped: the thing meant to read as a queue being worked read as a
 * page reflowing. min-height holds the short ones open to the same box.
 */
.zm-row .zm-txt {
  font-size: 14px; line-height: 1.45; color: var(--soft); margin: 0; min-width: 0;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden; min-height: calc(3 * 1.45em);
}
/*
 * A white pill, not coloured text.
 *
 * The verdict used to be drawn in its own outcome's colour, and the card
 * arrives at that same colour a fifth of a second later: for most of the
 * swipe the word was moss on moss and flame on flame, which is to say
 * invisible in exactly the frames it exists to label.
 */
.zm-row .zm-verdict {
  position: absolute; top: 11px; right: 13px;
  font-family: var(--mono); font-size: 11px; line-height: normal; color: #fff;
  background: rgba(0, 0, 0, 0.2); border-radius: 999px; padding: 2px 8px;
  opacity: 0; white-space: nowrap;
}
/* The age and the glyph give the corner up to the verdict as it lands. */
.zm-row[data-leaving] .zm-when,
.zm-row[data-leaving] .zm-x { opacity: 0; }
.zm-row[data-leaving] .zm-av { background: rgba(255, 255, 255, 0.24); }
/*
 * While a card is leaving it wears its outcome's colour, so the handle and
 * the tweet have to stop being ink and follow the card instead. They set
 * their own colours, so inheriting has to be asked for rather than assumed.
 */
.zm-row[data-leaving] b,
.zm-row[data-leaving] .zm-txt,
.zm-row[data-leaving] .zm-field { color: inherit; }
/* Only shown once the field behind the copy has actually reached zero. */
.zm-rail-empty {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.09em; line-height: normal;
  text-transform: uppercase; color: var(--moss); max-width: 520px; margin: -2px 0 0;
}
.zm-rail-empty[hidden] { display: none; }
/*
 * The readout owns its gaps. The margin reset is the whole fix: a <p> at
 * 54px carries a 54px default margin, so a grid gap was adding three pixels
 * to a hundred-and-eight the browser had already inserted and the clock
 * drifted a third of a screen away from its own caption.
 */
.zm-readout { display: grid; gap: 3px; align-content: start; }
.zm-readout p { margin: 0; }
.zm-readout .zm-tally { margin-top: 22px; }
.zm-readout .zm-behind-line { margin-top: 5px; }
.zm-readout .zm-tally[hidden], .zm-readout .zm-behind-line[hidden] { display: none; }
.zm-clock {
  font-family: var(--mono); font-size: clamp(30px, 5vw, 54px); color: var(--ink);
  letter-spacing: -0.02em; line-height: normal; font-variant-numeric: tabular-nums;
}
/* The tally is the supporting number; the per-decision clock is the claim. */
.zm-clock.zm-small { font-size: clamp(24px, 3.4vw, 36px); }
.zm-clock .zm-unit { color: var(--faint); font-size: 0.4em; display: block; margin-top: 4px; line-height: 1.35; }
.zm-note {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; line-height: normal;
  text-transform: uppercase; color: var(--flame);
}
.zm-note.zm-quiet { color: var(--faint); }
.zm-end .zm-body { margin-bottom: 22px; }

/*
 * Caption type, laptop and up.
 *
 * 16px of body copy under a 58px headline and a 110px counter is a 3.6x
 * jump with nothing in between, so at laptop distance the one line carrying
 * the argument reads as a footnote to the number rather than as the
 * sentence you are meant to take away. On a phone the headline is 29.6px
 * and the ratio is already 1.9, so nothing below 641px moves.
 *
 * This block sits AFTER the components it overrides. Every selector here is
 * as specific as the one it beats, so position in the file is the only
 * thing deciding the winner, and the earlier centring block already lost
 * that argument once.
 */
@media (min-width: 641px) {
  .zm-band .zm-body { font-size: 19px; }
  .zm-cap { font-size: 14px; letter-spacing: 0.14em; }
  .zm-key { font-size: 14.5px; }
  /*
   * Every caption on the page, not only the band's.
   *
   * Read off the rendered page rather than chosen: at 1440 the small type
   * ran 9.5, 11, 11, 12, 12.5, 13, 13, 13, 13.5, 14 against a 58px headline
   * and a 110px counter. Nine sizes inside four points is not a scale, it
   * is a floor, and everything on it reads as a footnote. The phone keeps
   * its own sizes, where the headline is 29.6px and the ratios already work.
   */
  body.home .hero-tag { font-size: 16px; }
  body.home .hero-copy .lede { font-size: 20px; }
  body.home .zm-scroll { font-size: 14px; }
  .zm-field { font-size: 11px; }
  .zm-when { font-size: 13px; }
  .zm-row b { font-size: 15px; }
  .zm-row .zm-txt { font-size: 15.5px; }
  .zm-row .zm-verdict { font-size: 12px; }
  .zm-note { font-size: 12.5px; }
  .zm-rail-empty { font-size: 12.5px; }
  /* The unit is a share of its clock, so it scales with the number it
     labels rather than needing a size of its own. */
  .zm-clock .zm-unit { font-size: 0.44em; }
}

/*
 * The footer, and only the footer, is not in the study.
 *
 * The study framed a browser window and stopped at "That is the whole
 * product." A real page billed through Stripe still has to name its legal
 * entity and reach Privacy, Terms and Refunds, so the footer stays and is
 * made to recede instead: the study's own muted ink rather than flame, and
 * the same pool of paper the bands use so the last of the draining field
 * does not run through the legal line.
 */
body.home footer { position: relative; font-family: var(--sans); }
/*
 * The pool stops at the footer's bottom edge, and the ellipse is restated so
 * it paints exactly where it did.
 *
 * ~~bottom: -120%~~ hung this decoration 120% of the footer's height BELOW
 * the page. It is position:absolute inside a position:relative footer, so
 * that overflow still counts toward the document's scrollable height --
 * body.home sets overflow-x: clip, which does nothing on the y axis. So
 * the page ended in dead space: measured 206px at 390x844 (footer 173px) and
 * 171px at 430x932 (footer 144px), each exactly 120% of its footer. Founder,
 * on a phone screenshot of the bottom of the page: "why is there extra space
 * here?"
 *
 * None of it was ever visible. The gradient is --paper fading to transparent
 * over a --paper body, so everything below the footer was paper on paper.
 *
 * Shortening the box would move the ellipse with it, so the geometry is
 * re-derived rather than trimmed. The box ran -40%..+120% (260% of the
 * footer) with its centre at 50% and a y-radius of 62% -- which puts the
 * centre 0.90x the footer's height below its top, and the radius at 1.612x.
 * Against the new 140% box those are 92.86% and 115.14%.
 *
 * Checked by pixel diff rather than by eye, with the footer's bottom pinned
 * to the viewport in both states so the two shots line up at the subpixel
 * level: max channel delta 1/255 and ZERO pixels differing by more than 2,
 * at 390 and at 430.
 */
body.home footer::before {
  top: -40%; bottom: 0;
  background: radial-gradient(ellipse 57% 115.14% at 44% 92.86%, var(--paper) 0%,
    var(--paper) 38%, color-mix(in srgb, var(--paper) 88%, transparent) 62%, transparent 88%);
}
body.home footer .legal { color: var(--faint); }
body.home footer .footer-links a { color: var(--muted); text-decoration-color: var(--border); }
body.home footer .footer-links a:hover { color: var(--flame); }

/*
 * The bands are tall on purpose: each one is a screen the field moves
 * through. On a phone that is a lot of scrolling before the FAQ, so they
 * tighten rather than disappear. The argument is the same either way.
 */
@media (max-width: 640px) {
  body.home .wrap { padding: 0 22px; }
  /*
   * The mark and the tagline were touching: measured, a 0px gap between the
   * wordmark's bottom edge and the top of "Inbox zero for bookmarks". The
   * header carried no bottom padding and the hero no top padding, so at
   * 58px the mark sat straight on the line under it and the two read as one
   * smudged block rather than a title and its subtitle.
   */
  body.home header { padding: 18px 0 22px; }
  body.home header .owner-key { top: 18px; }
  /*
   * Under 520px the wordmark is 209px wide and centred, so a right-aligned
   * "Sign in" runs into it: measured 11px of overlap at 390 and 9px of air
   * at 430. The word takes its own line above the mark instead, which
   * costs the first screen 32px and keeps the wordmark centred.
   */
  @media (max-width: 520px) {
    body.home header { padding: 50px 0 22px; }
    body.home header .owner-key { top: 12px; }
  }
  body.home .hero { min-height: 84vh; padding: 0 0 28px; }
  /*
   * Centred on a phone too, wordmark included.
   *
   * The study is left-aligned and the phone inherited that, so on a 390px
   * screen the wordmark and the pill both sat 102px left of the column's
   * centre while every line of copy hugged the left edge. On a wide screen
   * that reads as a deliberate axis; in a 346px column with nothing to the
   * right of it, it reads as a layout that failed to load.
   */
  body.home header { justify-content: center; }
  /* Bigger here too, but the mark and the name have to clear the owner key
     in the corner and still leave the headline room on an 84vh hero. */
  body.home header .wordmark .wm-z { width: 58px; height: 58px; }
  body.home header .wordmark .wm-rest { font-size: 40px; transform: translateY(-2px); }
  body.home .hero,
  body.home .zm-band,
  body.home .hero-tag,
  body.home h1,
  body.home .hero-copy,
  body.home .hero-copy p,
  body.home .hero-copy .cta,
  body.home .hero-copy .cta-jump,
  body.home .hero-copy .price-line,
  body.home .hero-copy .trust,
  .zm-band h2,
  .zm-band .zm-cap,
  .zm-band .zm-count { text-align: center; }
  /* Sized to its own words, so it needs both margins to find the middle. */
  body.home .hero-copy .cta-stack {
    width: fit-content; margin-left: auto; margin-right: auto; align-items: center;
  }
  body.home .hero-copy .cta-jump,
  body.home .hero-copy .cta-stack .price-line,
  body.home .hero-copy .cta-stack .trust { margin-left: auto; margin-right: auto; }
  body.home .cta-jump a { display: inline-flex; width: auto; padding: 14px 24px; font-size: 16px; }
  /* The speed band keeps its left-aligned diagram, as it does on the desk:
     a queue and a readout have to be read left to right. */
  .zm-band[data-phase="speed"] .zm-speed { text-align: left; }
  body.home .hero::before,
  body.home .zm-band::before { background-position-x: center; }
  .zm-band { min-height: 76vh; padding: 28px 0; }
  .zm-band.tall { min-height: 88vh; }
  .zm-band h2 { font-size: clamp(26px, 7.6vw, 34px); }
  .zm-count { font-size: clamp(40px, 15vw, 64px); }
}
/*
 * Motion off: the field paints one still frame and the bands stop being a
 * screen tall each, because the height only existed to give the animation
 * room to run.
 */
@media (prefers-reduced-motion: reduce) {
  body.home .hero { min-height: 0; }
  .zm-band, .zm-band.tall { min-height: 0; padding: 26px 0; }
  /* The cue stops moving and stays: still, it is a labelled link to the
     arc, which is more use than nothing. Both children, because the travel
     and the fade are declared separately. */
  body.home .zm-scroll-word,
  body.home .zm-scroll svg { animation: none; }
}
/*
 * The hero arrives in sequence, in CSS.
 *
 * This is the one piece an animation library would have owned, and it is a
 * staggered fade with six delays: keyframes plus animation-delay say it in
 * eight lines and run on the compositor rather than from a script writing
 * styles every frame. The .zm-motion gate is set by a one-line script in
 * <head>, so a visitor with JavaScript off never sees a page whose hero
 * starts invisible and has nothing to reveal it, and a deferred file cannot
 * paint the hero once before snapping it back to run the reveal.
 */
@keyframes zm-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes zm-pop { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }
.zm-motion body.home header .brandline,
.zm-motion body.home .hero-tag,
.zm-motion body.home h1 .line,
.zm-motion body.home .hero-copy .lede,
.zm-motion body.home .hero-copy .hero-detail,
.zm-motion body.home .zm-scroll {
  animation: zm-rise 620ms cubic-bezier(0.32, 0.72, 0, 1) both;
}
.zm-motion body.home .hero-tag { animation-delay: 120ms; }
.zm-motion body.home h1 .line:first-child { animation-delay: 240ms; }
.zm-motion body.home h1 .line:last-child { animation-delay: 360ms; }
.zm-motion body.home .hero-copy .lede { animation-delay: 480ms; }
.zm-motion body.home .hero-copy .hero-detail { animation-delay: 560ms; }
/*
 * The reveal's last beat, inherited from the CTA stack that used to hold it.
 * The cue is the hero's one gesture now, so it is the thing the sequence
 * should arrive on; without this it was simply there from the first frame
 * while everything above it faded in around it.
 */
.zm-motion body.home .zm-scroll {
  animation-name: zm-pop; animation-delay: 700ms; animation-duration: 560ms;
}
@media (prefers-reduced-motion: reduce) {
  .zm-motion body.home header .brandline,
  .zm-motion body.home .hero-tag,
  .zm-motion body.home h1 .line,
  .zm-motion body.home .hero-copy .lede,
  .zm-motion body.home .hero-copy .hero-detail,
  .zm-motion body.home .zm-scroll { animation: none; }
}
