/* ==========================================================================
   原子画布 — landing page.

   The geometry, colour and type on this page are copied from flora.ai, measured
   from its live document rather than remembered. Only the words and the
   pictures are ours. `docs/design-rules.md` (Ink & Amber) deliberately does NOT
   govern this file: it is a different visual system on purpose, and the two
   should not be reconciled.

   Two rules this file keeps, and the verifier enforces:

   1. EVERY colour literal lives in `:root`. Nothing below the token block may
      contain a hex, `rgb(`, `rgba(` or a named colour. `landing/.verify.mjs`
      greps for exactly that, so a stray literal fails the build rather than
      quietly forking the palette.

   2. NO EXTERNAL REQUESTS. No @import, no web font, no CDN. Mainland networks
      block the font CDNs flora uses (Geist and a variable grotesk), and a face
      that silently falls back is worse than one chosen on purpose — so the
      stack below is the system UI face plus the system CJK face, and flora's
      look is carried by weight, size, tracking and line-height instead.
      That contrast — 12–14px UI text under 52–60px display type — is most of it.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* ---- ground and raised steps (flora's measured values) ---------------- */
  --ground:        #000000;
  --ground-2:      #1a1a1a;
  --raise-1:       #2a2a2a;
  --raise-2:       #313131;
  --raise-3:       #3a3a3a;
  --raise-4:       #4d4d4d;

  /* Card fill. Flora's cards are all but black — a hairline and a breath of
     top light is the whole card. #0a0a0a keeps #7b7b7b above 4.5:1 on it. */
  --card:          #0a0a0a;
  --card-lit:      rgba(255, 255, 255, 0.035);

  /* ---- text ------------------------------------------------------------ */
  --ink:           #eeeeee;   /* by far the most-used value on flora's page */
  --ink-2:         #b4b4b4;
  /* #7b7b7b is 4.96:1 on #000 and 4.11:1 on #1a1a1a, so it is a page-ground
     colour only; on a card or a raised mock the dim tone steps up to --ink-2.
     flora's fourth step, #606060, is 3.34:1 on black — it is a real value on
     their page but it cannot carry text here, so it is not defined at all
     rather than defined and misused. */
  --ink-3:         #7b7b7b;
  --ink-soft:      rgba(255, 255, 255, 0.7);

  /* ---- edges. Every edge on flora's page is one of these three. --------- */
  --edge:          rgba(255, 255, 255, 0.11);
  --edge-10:       rgba(255, 255, 255, 0.1);
  --edge-15:       rgba(255, 255, 255, 0.15);
  --edge-06:       rgba(255, 255, 255, 0.06);

  /* ---- the one saturated colour ---------------------------------------- */
  --accent:        rgb(0, 153, 255);
  --accent-dim:    rgba(0, 153, 255, 0.16);

  /* ---- inverted CTA (flora's filled button) ----------------------------- */
  --invert:        #f4f4f2;
  --invert-hi:     #ffffff;
  --on-invert:     #000000;

  /* ---- decoration ------------------------------------------------------- */
  --dot:           rgba(255, 255, 255, 0.07);
  --wire:          rgba(255, 255, 255, 0.1);
  --shadow:        rgba(0, 0, 0, 0.55);
  --plate-warm-a:  #3a2f26;
  --plate-warm-b:  #14100d;
  --plate-cool-a:  #232a31;
  --plate-cool-b:  #0e1113;
  --plate-dim-a:   #24262a;
  --plate-dim-b:   #0d0e10;
  --scrim-94:      rgba(0, 0, 0, 0.94);
  --scrim-92:      rgba(0, 0, 0, 0.92);
  --scrim-62:      rgba(0, 0, 0, 0.62);
  --scrim-40:      rgba(0, 0, 0, 0.4);
  --scrim-28:      rgba(0, 0, 0, 0.28);
  --scrim-16:      rgba(0, 0, 0, 0.16);
  --scrim-78:      rgba(0, 0, 0, 0.78);
  --scrim-nav:     rgba(0, 0, 0, 0.72);
  --transparent:   rgba(0, 0, 0, 0);

  /* ---- geometry -------------------------------------------------------- */
  --container: 1280px;
  --gutter:    48px;
  --r-lg:      24px;
  --r-md:      12px;
  --r-sm:      8px;

  /* ---- type ------------------------------------------------------------ */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue",
          "Arial", "PingFang SC", "HarmonyOS Sans SC", "Hiragino Sans GB",
          "Source Han Sans SC", "Noto Sans CJK SC", "Microsoft YaHei",
          sans-serif;
  /* Line height. flora's body computes to 137.5%, and that is right for
   * Latin — but this page is Chinese. A CJK glyph fills its em box, so the
   * space between lines IS the leading; at 1.375 the lines touch and the
   * block reads as a grey slab. Chinese body sets at 1.75–1.9. The display
   * sizes stay tight because large glyphs need less. */
  --lh-body:    1.8;
  --lh-display: 1.15;
  --lh-tight:   1.3;
  --lh-mid:     1.5;

  /* Tracking. flora runs -0.01em/-0.03em, which is Latin kerning: it closes
   * the gaps that letterforms leave. Chinese glyphs are already on an em
   * grid with no such gaps, so negative tracking just jams them together —
   * it is the single biggest reason CJK type looks cheap. Headings and body
   * therefore sit at 0, and the negative values survive only for the Latin
   * and numeral runs (--ls-latin), which is where they were earned. */
  --ls-tight:   0;
  --ls-display: 0;
  --ls-latin:   -0.02em;
}

/* ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  /* 15px, not flora's 14: that is a Latin size. A Chinese glyph at 14px on a
   * near-black ground loses its interior strokes, which is most of what makes
   * CJK type read as cheap. Small screens step to 16 (see the media query). */
  font-size: 15px;
  font-weight: 500;
  line-height: var(--lh-body);
  letter-spacing: var(--ls-tight);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { display: block; max-width: 100%; }
h1, h2, h3, h4, p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }

a { color: var(--accent); text-decoration: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 60;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  background: var(--invert);
  color: var(--on-invert);
  font-size: 13px;
}
.skip:focus { left: 16px; }

.wrap {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* ---------------------------------------------------------------- header -- */

.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--scrim-nav);
  backdrop-filter: saturate(140%) blur(14px);
  -webkit-backdrop-filter: saturate(140%) blur(14px);
  border-bottom: 1px solid var(--edge-06);
}
.nav-in {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 10px var(--gutter);
  display: flex;
  align-items: center;
  gap: 24px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink);
}
.brand-mark { color: var(--ink); flex: none; }
.brand-word {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: var(--ls-display);
  color: var(--ink);
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-left: 16px;
}
.nav-links a {
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-2);
  transition: color 140ms ease;
}
.nav-links a:hover { color: var(--ink); }

.nav-cta {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* --------------------------------------------------------------- buttons -- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 15px;
  border-radius: 999px;
  border: 1px solid var(--transparent);
  font-size: 14px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--ls-tight);
  white-space: nowrap;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
.btn-ghost {
  border-color: var(--edge-15);
  color: var(--ink);
  background: var(--transparent);
}
.btn-ghost:hover { background: var(--ground-2); border-color: var(--edge); }
.btn-solid {
  background: var(--invert);
  color: var(--on-invert);
}
.btn-solid:hover { background: var(--invert-hi); }
.btn-lg { padding: 12px 20px; font-size: 15px; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.pill {
  display: inline-flex;
  align-items: center;
  padding: 9px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--edge);
  background: var(--ground-2);
  color: var(--ink);
  font-size: 13px;
  font-weight: 500;
  transition: background-color 150ms ease;
}
.pill:hover { background: var(--raise-1); }

.link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--accent);
  font-size: 14px;
  font-weight: 500;
}
.link:hover { text-decoration: underline; }
.link-r { white-space: nowrap; }

/* ----------------------------------------------------------- type scale -- */

.display {
  font-size: 60px;
  font-weight: 500;
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.h-lg {
  font-size: 40px;
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.h-md {
  font-size: 36px;
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.lede {
  margin-top: 16px;
  font-size: 16px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.sub {
  margin-top: 8px;
  max-width: 56ch;
  font-size: 15px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* -------------------------------------------------------- canvas ground -- */

.grid-ground {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--dot) 1px, var(--transparent) 1px);
  background-size: 24px 24px;
  background-position: 0 0;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 42%, var(--ground) 40%, var(--transparent) 100%);
  mask-image: radial-gradient(120% 90% at 50% 42%, var(--ground) 40%, var(--transparent) 100%);
  pointer-events: none;
}

.wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.wires path {
  stroke: var(--wire);
  stroke-width: 1;
  fill: none;
  vector-effect: non-scaling-stroke;
}

/* ------------------------------------------------------------ node card -- */

.node { position: absolute; }
/* One line, always. In a narrow node these two spans used to wrap into four
   ragged lines ("角色参 / 考"), which reads as breakage rather than as density. */
.node-cap {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  gap: 16px;
  padding: 0 2px 4px;
  font-size: 13px;
  font-weight: 500;
  line-height: var(--lh-body);
  white-space: nowrap;
}
.node-cap span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.node-cap span:last-child { flex: none; }
.node-cap span:first-child { color: var(--ink-2); }
.node-cap span:last-child { color: var(--ink-3); }

.node-body {
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: var(--card);
  overflow: hidden;
  box-shadow: 0 18px 40px -24px var(--shadow);
}
/*
 * A node holds a frame, so the plates are frames. Each one is a separate
 * `nd-*.webp` cut to 16:9 by `landing/build-images.mjs`, not a
 * `background-position` into a shared picture: the crop is a decision about a
 * photograph and it belongs with the photograph, so a stylesheet edit can never
 * reframe a shot. An earlier pass used flat gradients here and every node read
 * as an empty loading box, which is the one thing flora's canvas never looks
 * like; the pass after that used eleven crops of a single church interior,
 * which read as a page with one photograph.
 *
 * They are CSS backgrounds rather than <img> on purpose: each plate lives
 * inside an `aria-hidden` mock, so it is decoration with nothing to announce
 * and nothing for a screen reader to wade through.
 *
 * The flow card's four plates are one forest — a scene board and three seasons
 * — because that is what its copy claims (「接法不动，把场景板换掉，出来的就是
 * 另一条片子」) and a shot list that does not match its captions is a decorative
 * lie. It used to read 全景 / 中景 / 特写 over footage of one family in a park;
 * that footage was withdrawn (a colleague's face, see `build-images.mjs`), and
 * the captions were rewritten to the new pictures rather than left describing
 * pictures that are no longer there.
 *
 * The leading gradient in each stack is a scrim: it seats a bright plate into
 * a near-black page instead of letting it punch a hole in it.
 */
.node-plate {
  aspect-ratio: 16 / 9;
  background-color: var(--plate-dim-b);
  background-repeat: no-repeat;
}
.p-sea    { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-sea.webp"); }
.p-lens   { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-lens.webp"); }
.p-tea    { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-tea.webp"); }
.p-board  { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-board.webp"); }
.p-spring { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-spring.webp"); }
.p-autumn { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-autumn.webp"); }
.p-winter { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/nd-winter.webp"); }
.p-cast   { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/cast-steven.webp"); }
.p-cast-b { background-image: linear-gradient(var(--scrim-28), var(--scrim-28)), url("img/cast-monica.webp"); }

.p-sea, .p-lens, .p-tea, .p-board, .p-spring, .p-autumn, .p-winter {
  background-size: cover;
  background-position: 50% 50%;
}
/* The two character plates are full-length studio sheets in a 16:9 window, so
   they are framed on the head rather than on the middle of the figure. */
.p-cast, .p-cast-b {
  background-size: cover;
  background-position: 50% 4%;
}

/* The 首帧 / 尾帧 node. The hairline down the middle is drawn here, but the two
   halves are a real pair — the same tree bare and in leaf, composited into
   `nd-pair.webp` by the image build. Drawing a divider over ONE photograph, as
   this rule used to, illustrates the feature; two frames show it. */
.p-pair {
  background-image:
    linear-gradient(90deg,
      var(--transparent) calc(50% - 2px), var(--ground) calc(50% - 2px),
      var(--ground) calc(50% + 2px), var(--transparent) calc(50% + 2px)),
    linear-gradient(var(--scrim-28), var(--scrim-28)),
    url("img/nd-pair.webp");
  background-size: 100% 100%, 100% 100%, cover;
  background-position: 0 0, 0 0, 50% 50%;
}

.node-foot {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 11px;
  border-top: 1px solid var(--edge-06);
  white-space: nowrap;
}
.node-foot .tag { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.dots { display: inline-flex; gap: 4px; }
.dots i {
  width: 5px; height: 5px;
  border-radius: 999px;
  background: var(--raise-3);
}
.dots i.on { background: var(--ink-2); }
.tag {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0;
}

/* ------------------------------------------------------------------ hero -- */

.hero {
  position: relative;
  overflow: hidden;
  padding: 128px var(--gutter);
  min-height: 500px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero-scatter {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.hero-mid {
  position: relative;
  z-index: 2;
  max-width: 620px;
  text-align: center;
}
.hero-cta { margin-top: 24px; }

/* Flora lets its hero cards bleed off the edge. Ours sit just inside it: a
   node's caption is two words of 12px text, and a caption sliced mid-glyph
   reads as a rendering bug rather than as a composition. The card bodies still
   run to the margins, which is where the bleed actually shows.

   The vertical offsets are PIXELS, not percentages. A node's height comes from
   its own width (a 16:9 plate plus a 20px caption and a 37px footer), so a
   percentage top makes the gap between two nodes depend on the section height —
   and in a 650px-tall window the hero shrank and the four nodes drove straight
   through each other. Pixels make the composition independent of the window. */
.hero .n1 { left: 2%; top: 40px;  width: 290px; }
.hero .n2 { left: 6%; top: 280px; width: 240px; }
.hero .n3 { right: 2%; top: 28px;  width: 300px; }
.hero .n4 { right: 7%; top: 292px; width: 240px; }

/* ------------------------------------------------------------------ band -- */

.band {
  position: relative;
  padding: 128px 0;
  overflow: hidden;
}
.band-head { max-width: 62ch; }

/* three steps */
.steps {
  margin-top: 48px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

.card {
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  background:
    linear-gradient(180deg, var(--card-lit), var(--transparent) 55%),
    linear-gradient(var(--card), var(--card));
}

.step {
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.step-title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 40px;
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.step-num { color: var(--ink-2); }
.step-art {
  flex: 1;
  min-height: 200px;
  display: flex;
  align-items: flex-end;
}
.step-text h4 {
  font-size: 18px;
  font-weight: 600;
  line-height: var(--lh-mid);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.step-text p {
  margin-top: 4px;
  /* 15px, not flora's 14: that is a Latin size. A Chinese glyph at 14px on a
   * near-black ground loses its interior strokes, which is most of what makes
   * CJK type read as cheap. Small screens step to 16 (see the media query). */
  font-size: 15px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* the little in-card UI mocks */
.mock {
  width: 100%;
  border: 1px solid var(--edge);
  border-radius: var(--r-md);
  background: var(--ground-2);
  overflow: hidden;
}
.mock-head, .mock-foot {
  padding: 9px 11px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
}
.mock-head { border-bottom: 1px solid var(--edge-06); }
.mock-foot {
  display: flex;
  gap: 16px;
  border-top: 1px solid var(--edge-06);
  color: var(--ink-2);
}
.mock-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 11px;
  font-size: 14px;
  font-weight: 500;
  color: var(--ink-2);
}
.mock-row.on { background: var(--raise-1); color: var(--ink); }
.mock-ic {
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: 6px;
  background: var(--raise-1);
  color: var(--ink-2);
  font-size: 13px;
}
.mock-row.on .mock-ic { background: var(--raise-3); color: var(--ink); }
.mock-row kbd {
  margin-left: auto;
  font: inherit;
  font-size: 13px;
  color: var(--ink-2);
}
.mock-cap {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 11px;
  border-bottom: 1px solid var(--edge-06);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
}
.mock-cap span:last-child { color: var(--ink-2); }
.mock-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 11px;
  border-top: 1px solid var(--edge-06);
}
.mock-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2px;
  padding: 2px;
}
/* Four takes of one setup. They are four quarters of a single 1440x480 file
   (`nd-var.webp`) rather than four requests, so the strip costs one image; the
   quarter is chosen with background-position on a 400%-wide background. Flat
   fills here made the card read as a loading skeleton. */
.v {
  aspect-ratio: 3 / 4;
  border-radius: 6px;
  background-color: var(--plate-dim-b);
  background-image: linear-gradient(var(--scrim-40), var(--scrim-40)), url("img/nd-var.webp");
  background-size: 100% 100%, 400% 100%;
  background-repeat: no-repeat;
}
.v:nth-child(1) { background-position: 0 0, 0% 50%; }
.v:nth-child(2) { background-position: 0 0, 33.3333% 50%; }
.v:nth-child(3) { background-position: 0 0, 66.6667% 50%; }
.v:nth-child(4) { background-position: 0 0, 100% 50%; }
/* The selected take is the one that is not held back by the scrim. */
.v.on {
  background-image: linear-gradient(var(--scrim-16), var(--scrim-16)), url("img/nd-var.webp");
  outline: 1px solid var(--edge-15);
  outline-offset: -1px;
}
.tl {
  display: flex;
  gap: 2px;
  padding: 8px 8px 0;
}
.tl-sub { padding-bottom: 8px; }
.clip {
  height: 26px;
  border-radius: 6px;
  background: linear-gradient(155deg, var(--plate-cool-a), var(--plate-cool-b));
  border: 1px solid var(--edge-06);
}
.clip.w1 { flex: 3; }
.clip.w2 { flex: 2; }
.clip.w3 { flex: 4; }
.cap-clip {
  height: 14px;
  flex: 5;
  background: var(--raise-1);
}
.cap-clip.w-s { flex: 3; }

/* --------------------------------------------- 所有模型，一块画布 (split) -- */

.band-split { padding: 128px 0; }
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: center;
}
.split-text .btn-row { margin-top: 24px; }

.chips {
  margin-top: 40px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.chip {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 11px;
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  background: var(--card);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: 0;
}
.chip .dim { color: var(--ink-2); }

/* Left / right / left, so the only pair that can ever collide is m1 and m3 —
   and they are pinned to opposite ends of the frame rather than to percentages,
   which is what stops the bottom node from hanging out of it at any width. */
.split-art {
  position: relative;
  min-height: 560px;
}
/* Widths are a share of the column, not pixels: at 1200px the fixed 240 + 290
   pair left the two edges 2px inside each other. 45% + 52% keeps a real gap at
   every column width the two-up layout can take. */
.split-art .m1 { left: 0;  top: 0;    width: 45%; }
.split-art .m2 { right: 0; top: 50%;  width: 52%; transform: translateY(-50%); }
.split-art .m3 { left: 0;  bottom: 0; width: 45%; }

/* ------------------------------------------- 能复用的工作流 (full-bleed) -- */

.flows {
  position: relative;
  isolation: isolate;
  padding: 128px 0;
  overflow: hidden;
}
.flows-plate {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 46%;
}
.flows-scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, var(--ground) 0%, var(--transparent) 24%,
                    var(--transparent) 76%, var(--ground) 100%),
    linear-gradient(90deg, var(--scrim-94) 0%, var(--scrim-92) 44%,
                    var(--scrim-62) 74%, var(--scrim-40) 100%);
}
.flows-in {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 48px;
  align-items: center;
}
.stack {
  margin-top: 48px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.stack li {
  font-size: 52px;
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.stack li:nth-child(2) { color: var(--ink-2); }
.stack li:nth-child(3) { color: var(--ink-2); }
.stack li:nth-child(4) { color: var(--ink-3); }
.stack li:nth-child(5) { color: var(--ink-3); }
.stack li:nth-child(6) { color: var(--ink-3); }

.flow-card {
  padding: 24px;
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: 0 40px 90px -50px var(--shadow);
}
.flow-canvas {
  position: relative;
  aspect-ratio: 1.4;
  border: 1px solid var(--edge-06);
  border-radius: var(--r-md);
  background-color: var(--ground);
  background-image: radial-gradient(var(--dot) 1px, var(--transparent) 1px);
  background-size: 20px 20px;
  overflow: hidden;
}
/* Three nodes stacked in the right half: first pinned to the top, last pinned
   to the bottom, middle centred. A node is 0.169×W + 20px tall, so with the
   canvas height tied to its width by `aspect-ratio` the three always clear each
   other; a fixed 340px height did not, and they overlapped by 20px at 768 and
   hung 22px out of the frame. Verified at every breakpoint by `.verify.mjs`. */
.flow-canvas .f0 { left: 5%;  top: 50%; width: 30%; transform: translateY(-50%); }
.flow-canvas .f1 { right: 5%; top: 0;   width: 30%; }
.flow-canvas .f2 { right: 5%; top: 50%; width: 30%; transform: translateY(-50%); }
.flow-canvas .f3 { right: 5%; bottom: 0; width: 30%; }
.flow-canvas .node-body { box-shadow: none; }

.flow-title {
  margin-top: 24px;
  font-size: 18px;
  font-weight: 600;
  line-height: var(--lh-mid);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.flow-desc {
  margin-top: 4px;
  margin-bottom: 16px;
  /* 15px, not flora's 14: that is a Latin size. A Chinese glyph at 14px on a
   * near-black ground loses its interior strokes, which is most of what makes
   * CJK type read as cheap. Small screens step to 16 (see the media query). */
  font-size: 15px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* ------------------------------------------------------------------ 技法 -- */

.band-tech { padding: 128px 0 96px; }

.tech-hero {
  margin-top: 36px;
  padding: 24px;
}
.tech-hero-title {
  font-size: 18px;
  font-weight: 600;
  line-height: var(--lh-mid);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.tech-hero-desc {
  margin-top: 4px;
  margin-bottom: 8px;
  max-width: 62ch;
  /* 15px, not flora's 14: that is a Latin size. A Chinese glyph at 14px on a
   * near-black ground loses its interior strokes, which is most of what makes
   * CJK type read as cheap. Small screens step to 16 (see the media query). */
  font-size: 15px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.tech-hero-art {
  position: relative;
  margin-top: 24px;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--edge-06);
}
.tech-hero-art img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.art-cap {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 20px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  background: linear-gradient(180deg, var(--transparent), var(--scrim-92));
}
.art-cap span:last-child { color: var(--ink-2); }

.tech-head {
  margin-top: 80px;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
}

/* Six columns for five cards, because one of them (`.tech-wide`) spans two:
   1 wide + 4 portraits is exactly six units and the row closes. At five columns
   the sixth unit wrapped and the row ended with a single orphaned card under an
   otherwise full row, which reads as a card that failed to load. The 3- and
   2-column steps below divide six evenly for the same reason. */
.tech-grid {
  margin-top: 24px;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 16px;
}
.tech {
  padding: 8px 8px 16px;
  border-radius: var(--r-md);
  display: flex;
  flex-direction: column;
}
/* The five character sheets are shot on a light studio cyc, and five light
   rectangles in a row punch holes in a near-black page. The scrim below is a
   seat, not a retouch: it leaves the figure and the face alone and darkens the
   empty floor the card sits on, which is what flora's own cards do to their
   photographs. Grading the WebP itself would be the wrong place — these files
   are reference INPUTS and must stay usable as inputs. */
.tech-art {
  position: relative;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--ground-2);
}
.tech-art::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(180deg, var(--scrim-16) 0%, var(--transparent) 30%,
                    var(--transparent) 58%, var(--scrim-62) 100%);
}
/* A three-panel sheet is one landscape frame; cropping it to 3/4 keeps the
 * middle panel's backdrop and throws the character away. The wide modifier
 * lets a card carry a landscape plate, and the grid gives it two columns so
 * the row is not five identical portraits. */
.tech.tech-wide { grid-column: span 2; }
.tech.tech-wide .tech-art img { aspect-ratio: 16 / 9; object-position: 50% 42%; }

.tech-art img {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  object-position: 50% 16%;
  transition: transform 400ms ease;
}
.tech:hover .tech-art img { transform: scale(1.02); }
.tech h3 {
  margin: 16px 8px 0;
  font-size: 14px;
  font-weight: 600;
  line-height: var(--lh-mid);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.tech p {
  margin: 4px 8px 0;
  font-size: 13px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.tech-meta {
  margin: 16px 8px 0;
  display: flex;
  align-items: center;
  gap: 8px;
}
.sq {
  flex: none;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border-radius: 6px;
  background: var(--raise-1);
  color: var(--ink-2);
  font-size: 13px;
  font-weight: 500;
}
.tech-meta span:last-child { display: grid; gap: 2px; min-width: 0; }
.tech-meta b {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: 0;
}
.tech-meta .dim {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0;
}

.notes {
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--edge-06);
  display: grid;
  gap: 16px;
}
.notes > p {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
}
.notes ul {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 40px;
}
.notes li {
  font-size: 13px;
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.notes b { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------- closing -- */

.closing { padding: 96px 0 128px; }
.closing-in {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.closing-line {
  font-size: 52px;
  font-weight: 500;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}
.closing-line .dim { color: var(--ink-3); }

/* ---------------------------------------------------------------- footer -- */

.foot {
  border-top: 1px solid var(--edge-06);
  padding: 48px 0 36px;
}
.foot-in {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}
.foot-brand p {
  margin-top: 8px;
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
}
.foot-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}
.foot-nav a {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-2);
  letter-spacing: 0;
}
.foot-nav a:hover { color: var(--ink); }
.foot-legal {
  margin-top: 40px;
  display: flex;
  gap: 16px;
}
.foot-legal p {
  font-size: 13px;
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
}

/* ================================================================ motion == */
/* Opt-in only. `html.motion` is set by the inline head script, and only when
   the reader has not asked for reduced motion. Without it every `.reveal`
   below is simply a plain element in its finished state. */

.motion .reveal {
  opacity: 0;
  transform: translateY(14px);
}
.motion .reveal.in {
  opacity: 1;
  transform: none;
  transition: opacity 620ms cubic-bezier(0.22, 0.61, 0.36, 1),
              transform 620ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.motion .steps .reveal:nth-child(2).in,
.motion .tech-grid .reveal:nth-child(2).in { transition-delay: 70ms; }
.motion .steps .reveal:nth-child(3).in,
.motion .tech-grid .reveal:nth-child(3).in { transition-delay: 140ms; }
.motion .tech-grid .reveal:nth-child(4).in { transition-delay: 210ms; }
.motion .tech-grid .reveal:nth-child(5).in { transition-delay: 280ms; }

/* ============================================================ responsive == */

/* 1181–1340px: still four nodes, but narrower, and the headline column gives
   up 60px so nothing on either side can reach it. */
@media (max-width: 1340px) {
  .hero-mid { max-width: 560px; }
  .hero .n1 { width: 250px; }
  .hero .n2 { left: 4%; width: 210px; }
  .hero .n3 { width: 260px; }
  .hero .n4 { right: 4%; width: 210px; }
}

@media (max-width: 1180px) {
  .hero .n2, .hero .n4 { display: none; }
  .tech-grid { grid-template-columns: repeat(3, 1fr); }
}

/*
 * Below 1000px a scattered canvas is not a composition, it is a pile. Every
 * absolutely-positioned mock becomes an ordinary stacked grid — which is also
 * the only arrangement that cannot overlap, at any width, ever.
 */
@media (max-width: 1000px) {
  :root { --gutter: 32px; }
  .split-art {
    min-height: 0;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    align-items: start;
  }
  .split-art .wires { display: none; }
  .split-art .node,
  .split-art .m1, .split-art .m2, .split-art .m3 {
    position: static;
    width: auto;
    transform: none;
  }
  .split-art .node { grid-column: auto; }
  .display { font-size: 44px; }
  .h-lg { font-size: 32px; }
  .h-md { font-size: 30px; }
  .stack li { font-size: 40px; }
  .closing-line { font-size: 40px; }
  .steps { grid-template-columns: 1fr; }
  .step-art { min-height: 0; }
  .split, .flows-in { grid-template-columns: 1fr; gap: 40px; }
  /* The scatter cannot survive a narrow column, but dropping it left the
     mobile hero as a headline over 250px of nothing. One node, in normal flow,
     under the CTA: the same card, doing the same job, without the collisions. */
  .hero { flex-direction: column; }
  .hero-mid { order: 1; }
  .hero-scatter {
    order: 2;
    position: static;
    width: 100%;
    max-width: 420px;
    margin-top: 48px;
  }
  .hero-scatter .wires,
  .hero .n2, .hero .n3, .hero .n4 { display: none; }
  .hero .n1 { position: static; width: auto; }
  .notes ul { grid-template-columns: 1fr; }
}

@media (max-width: 860px) {
  .nav-links { display: none; }
  /* The band's scrim is a HORIZONTAL ramp because the band is two columns:
     dark under the copy on the left, clearing to the right where only the
     photograph shows. Below 1000px it is one column, so that ramp puts the
     bottom of the 用途 list on the clear end — `--ink-3` measured 4.36:1 there
     against a 4.5 floor, and the 78% stop was the reason. One flat level
     instead: the same 92% the copy already sits on above this breakpoint,
     across the whole width, which measures 4.79.

     Two things worth writing down. The failure did not appear until the band's
     photograph changed, because the previous one happened to be darker in that
     corner — so this rule was always wrong and was passing by luck, which is
     the argument for `landing/.verify.mjs` sampling RENDERED pixels at every
     breakpoint instead of reading the cascade. And the first fix attempted was
     an override in the 1000px query, which did nothing at all: this 860px rule
     is later in the file and was quietly winning. Verified by reading the
     computed style in the browser, not by assuming the cascade. */
  .flows-scrim {
    background:
      linear-gradient(180deg, var(--ground) 0%, var(--transparent) 20%,
                      var(--transparent) 80%, var(--ground) 100%),
      linear-gradient(180deg, var(--scrim-92) 0%, var(--scrim-92) 100%);
  }
}

@media (max-width: 760px) {
  :root { --gutter: 24px; }
  .flow-canvas {
    aspect-ratio: auto;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    padding: 10px;
  }
  .flow-canvas .wires { display: none; }
  .flow-canvas .node,
  .flow-canvas .f0, .flow-canvas .f1,
  .flow-canvas .f2, .flow-canvas .f3 {
    position: static;
    width: auto;
    transform: none;
  }
  .band, .band-split, .band-tech, .flows, .closing { padding: 80px 0; }
  .hero { padding: 80px var(--gutter); min-height: 0; }
  body { font-size: 16px; }   /* the guideline's mobile floor */
  .display { font-size: 36px; }
  .lede { font-size: 15px; }
  .stack li { font-size: 32px; }
  .closing-line { font-size: 32px; }
  .tech-grid { grid-template-columns: repeat(2, 1fr); }
  .tech-head { flex-direction: column; align-items: flex-start; gap: 16px; }
  .foot-legal { margin-top: 24px; }
}

@media (max-width: 560px) {
  .split-art { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 480px) {
  :root { --gutter: 20px; }
  .brand-word { font-size: 14px; }
  .btn { padding: 8px 12px; font-size: 13px; }
  .display { font-size: 32px; }
  .h-lg { font-size: 26px; }
  .h-md { font-size: 24px; }
  .step-title { font-size: 32px; }
  .stack li { font-size: 26px; }
  .closing-line { font-size: 26px; }
  .flow-canvas { height: 240px; }
  .tech-hero, .step, .flow-card { padding: 16px; }
}

/* Short windows: the hero must not need a full screen to be readable. The
   padding gives way; `min-height` does NOT, because the scattered nodes are
   laid out against it — dropping it to 0 collapsed the frame around them. */
@media (max-height: 700px) and (min-width: 1000px) {
  .hero { padding-top: 72px; padding-bottom: 72px; }
}
