/* Rivermind Labs landing page.
   Layout: a hero, then mist and river bands that alternate.
   Two modes: white, the default, where the hero and the river bands are paper; and dark, where they are night graphite.
   Amber is reserved for AI: it marks the place where AI acts, nothing else. */

/* Fonts: self-hosted variable fonts (SIL OFL 1.1, licences in fonts/), latin, latin-ext and Vietnamese subsets
   from Google Fonts. No third-party request, so visitors' IP addresses are not sent anywhere.
   The latin-ext ranges leave out what the Vietnamese subset already covers (ă, đ, ơ, ư...), so Vietnamese text
   loads latin and vietnamese only. */

@font-face {
  font-family: "Rivermind Mono Web";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("fonts/rivermind-mono-vietnamese-v1.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Rivermind Mono Web";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("fonts/rivermind-mono-latin-ext-v1.woff2") format("woff2");
  unicode-range: U+0100-0101, U+0104-010F, U+0112-0127, U+012A-0167, U+016A-019F, U+01A2-01AE, U+01B1-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EFA-1EFF, U+2020, U+20A0-20AA, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Rivermind Mono Web";
  font-style: normal;
  font-weight: 400 500;
  font-display: swap;
  src: url("fonts/rivermind-mono-latin-v1.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Rivermind Display Web";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 75% 125%;
  font-display: swap;
  src: url("fonts/rivermind-display-vietnamese-v1.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Rivermind Display Web";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 75% 125%;
  font-display: swap;
  src: url("fonts/rivermind-display-latin-ext-v1.woff2") format("woff2");
  unicode-range: U+0100-0101, U+0104-010F, U+0112-0127, U+012A-0167, U+016A-019F, U+01A2-01AE, U+01B1-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EFA-1EFF, U+2020, U+20A0-20AA, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Rivermind Display Web";
  font-style: normal;
  font-weight: 400 800;
  font-stretch: 75% 125%;
  font-display: swap;
  src: url("fonts/rivermind-display-latin-v1.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Rivermind Text Web";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100% 112%;
  font-display: swap;
  src: url("fonts/rivermind-text-vietnamese-v1.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Rivermind Text Web";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100% 112%;
  font-display: swap;
  src: url("fonts/rivermind-text-latin-ext-v1.woff2") format("woff2");
  unicode-range: U+0100-0101, U+0104-010F, U+0112-0127, U+012A-0167, U+016A-019F, U+01A2-01AE, U+01B1-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+1D00-1DBF, U+1E00-1E9F, U+1EFA-1EFF, U+2020, U+20A0-20AA, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Rivermind Text Web";
  font-style: normal;
  font-weight: 400 700;
  font-stretch: 100% 112%;
  font-display: swap;
  src: url("fonts/rivermind-text-latin-v1.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Color: the river palette. Dark mode, below, repaints it in graphite */
  --river-950: #061719; /* night river: dark bands, ink on light bands */
  --river-900: #0b2327; /* deep water: surfaces on dark */
  --river-700: #1b3d42; /* hairlines on dark */
  --mist-50: #eef2f0;   /* morning mist: light bands, text on dark */
  --mist-200: #d0dad7;  /* hairlines on light */
  --paper: #f8fbf9;     /* the hero and the river bands in white mode */
  --white: #ffffff;     /* cards on the mist bands */
  --reed-400: #8ea6a1;  /* secondary text on dark */
  --reed-600: #4e6662;  /* secondary text on light */
  --lamp-400: #ffb547;  /* wharf lamp: AI at work, on dark */
  --lamp-700: #9a5b00;  /* the same signal on light */

  /* Type */
  --font-display: "Rivermind Display Web", "Rivermind Text Web", system-ui, sans-serif;
  --font-body: "Rivermind Text Web", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Rivermind Mono Web", ui-monospace, SFMono-Regular, Menlo, monospace;

  --step--2: 0.75rem;
  --step--1: 0.875rem;
  --step-0: 1.0625rem;
  --step-1: 1.25rem;
  --step-2: clamp(1.25rem, 1.05rem + 0.6vw, 1.5rem);
  --step-3: clamp(1.5rem, 1.1rem + 1.4vw, 2.375rem);
  --step-4: clamp(2rem, 1.2rem + 3vw, 3.75rem);
  --step-5: clamp(2.5rem, 1.2rem + 5vw, 5.75rem);

  /* Space */
  --gutter: clamp(16px, 4vw, 48px);
  --max: 1280px;
  --section: clamp(88px, 11vw, 168px);
  --nav-h: 64px;
  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1); /* entrances: fast start, long settle */

  /* Band tokens of the page, which the hero and the river bands use; the mist bands set their own. White mode: paper */
  --bg: var(--paper);
  --fg: var(--river-950);
  --fg-muted: var(--reed-600);
  --line: var(--mist-200);
  --surface: var(--mist-50);
  --signal: var(--lamp-700);
  --glow: transparent; /* the lamp's halo, which only shows on dark */

  color-scheme: light;
}

/* Dark mode, which the visitor picks with the switch in the menu (site.js, boot.js): the hero and the river bands turn
   to night, and the palette turns from river teal to graphite (the Than chì palette). Amber stays. */
:root[data-theme="dark"] {
  --river-950: #121417;
  --river-900: #1c1f24;
  --river-700: #33383e;
  --mist-50: #f0f1f2;
  --mist-200: #d5d8db;
  --reed-400: #9ba1a7;
  --reed-600: #5c6166;

  --bg: var(--river-950);
  --fg: var(--mist-50);
  --fg-muted: var(--reed-400);
  --line: var(--river-700);
  --surface: var(--river-900);
  --signal: var(--lamp-400);
  --glow: var(--lamp-400);

  color-scheme: dark;
}

/* The home hero is the river at night in white mode too, as the link preview draws it (brand/og/build.mjs): [data-night]
   marks it, and the opening's curtain on the same page. The values are the river palette's, written out because dark mode
   repaints --river-950 and the others in graphite and the night must not follow. Dark mode keeps its graphite hero, so this
   is white mode only. The nav takes the same tokens while the hero is behind it: it starts that way, so it reads before
   home.js runs and without it, and home.js adds .is-past-night once the hero has scrolled away. */
:root:not([data-theme="dark"]) [data-night],
:root:not([data-theme="dark"]):has([data-night]) .nav:not(.is-past-night) {
  --bg: #061719;
  --fg: #eef2f0;
  --fg-muted: #8ea6a1;
  --line: #1b3d42;
  --surface: #0b2327;
  --signal: var(--lamp-400);
  --glow: var(--lamp-400);

  color-scheme: dark;
}
/* Like a band, the marked box paints its ground and sets its ink: on the home page it holds the hero and the band under it */
:root:not([data-theme="dark"]) [data-night] {
  background: var(--bg);
  color: var(--fg);
}

/* Base */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 16px);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; }
img { max-width: 100%; height: auto; }
button { font: inherit; color: inherit; }
fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
legend { padding: 0; }
/* The hidden attribute wins over any display a rule sets: a script shows a part by clearing it, and without a script it stays hidden */
[hidden] { display: none !important; }

/* Ink, not amber: amber marks only the places where AI works */
:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
  border-radius: 4px;
}

/* Hidden to the eye, read aloud */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 8px);
  left: max(16px, env(safe-area-inset-left, 0px));
  z-index: 100;
  padding: 10px 16px;
  background: var(--fg);
  color: var(--bg);
  border-radius: 6px;
  transform: translateY(-200%);
}
.skip:focus { transform: none; }

.wrap {
  width: 100%;
  max-width: calc(var(--max) + 2 * var(--gutter));
  margin-inline: auto;
  padding-left: max(var(--gutter), env(safe-area-inset-left, 0px));
  padding-right: max(var(--gutter), env(safe-area-inset-right, 0px));
}

/* Bands */

.band {
  padding-block: var(--section);
  background: var(--bg);
  color: var(--fg);
}

/* .band--river has no tokens of its own: it takes the page's, paper in white mode and night in dark mode */
.band--mist {
  --bg: var(--mist-50);
  --fg: var(--river-950);
  --fg-muted: var(--reed-600);
  --line: var(--mist-200);
  --surface: var(--white);
  --signal: var(--lamp-700);
  --glow: transparent;
}
/* Shared type */

/* The small label: eyebrows, fact names, the names of form fields. It is written in sentence case in the body face, a
   step smaller than the text it labels, the way the capability blocks name theirs. Capitals and wide tracking made
   Vietnamese diacritics crowd at this size. Mono is kept for data: the figures' readouts, the durations and step numbers,
   the address, the copyright line. Each component's own rule adds only
   what differs */
.eyebrow,
.contact__label,
.scope__title,
.person__role,
.facts__list dt,
.course__facts dt,
.course__label,
.estimate__label {
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-weight: 500;
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--fg-muted);
}

h2 {
  font-family: var(--font-display);
  font-size: var(--step-4);
  font-stretch: 110%;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.25;
  text-wrap: balance;
}

.section-head {
  display: grid;
  gap: 20px;
  max-width: 52rem;
  margin-bottom: clamp(48px, 6vw, 88px);
}

.section-head__lead {
  max-width: 38rem;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.tag {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding-inline: 10px;
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px var(--line);
  font-family: var(--font-body);
  font-size: var(--step--1);
  font-stretch: 100%; /* it sits in a heading, which widens its face */
  font-weight: 500;
  color: var(--fg-muted);
  vertical-align: middle;
  white-space: nowrap;
}

.link-arrow {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 2px;
}
.link-arrow::after {
  content: "→";
  transition: transform 0.25s var(--ease-out);
}
.link-arrow:hover::after { transform: translateX(4px); }

/* Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 48px;
  padding-inline: 22px;
  border-radius: 6px;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  max-width: 100%;
  padding-block: 12px;
  text-align: center;
  white-space: normal;
  border: 0;
  background: none;
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, box-shadow 0.2s, transform 0.2s var(--ease-out);
}
.btn:active { transform: translateY(1px); }
.btn[aria-disabled="true"] { opacity: 0.45; cursor: default; transform: none; }

.btn--primary {
  background: var(--fg);
  color: var(--bg);
}
.btn--primary:hover { background: color-mix(in srgb, var(--fg) 86%, var(--bg)); }

.btn--ghost {
  color: var(--fg);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fg) 28%, transparent);
}
.btn--ghost:hover { box-shadow: inset 0 0 0 1px var(--fg); }

.btn--small {
  min-height: 40px;
  padding-inline: 16px;
  font-size: var(--step--1);
}

/* Navigation */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 50;
  padding-top: env(safe-area-inset-top, 0px);
  transition: background-color 0.3s, box-shadow 0.3s;
}
.nav.is-scrolled,
.nav.is-open {
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  backdrop-filter: blur(16px) saturate(140%);
  box-shadow: 0 1px 0 var(--line);
}

/* Without a script nothing shades the nav on scroll, so on the home page it is solid night and reads over every band */
:root:not(.js):has([data-night]) .nav {
  background: var(--bg);
  box-shadow: 0 1px 0 var(--line);
}

.nav__inner {
  display: flex;
  align-items: center;
  gap: 32px;
  height: var(--nav-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--fg);
  text-decoration: none;
}
.nav .brand { min-height: 44px; }
/* Same proportions as brand/logo/kit/svg/rivermind-horizontal-reversed.svg */
.brand__mark { width: auto; height: 28px; flex: none; }
.brand__name {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-stretch: 118%;
  font-weight: 700;
  letter-spacing: 0.01em;
}
/* A real space sits between the two words, so a screen reader hears two words. It is set in this size wherever the brand
   is (the footer is smaller than the header); with the margin below the gap is the 8px it was drawn with. */
.brand__word { font-size: var(--step-0); }
.brand__labs {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-left: 4px;
}

.nav__menu { margin-left: auto; }
.nav__links {
  display: flex;
  gap: 4px;
}
.nav__links a {
  display: block;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: var(--step--1);
  color: var(--fg-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.2s;
}
@media (max-width: 1100px) {
  .nav__links a { padding-inline: 10px; }
}
.nav__links a:hover,
.nav__links a[aria-current] { color: var(--fg); }
.nav__links a[aria-current] {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 6px;
}

/* Light or dark (site.js): a half-lit disc after the links, a row with its name in the phone menu. It needs a script. */
.theme-switch {
  display: none;
  align-items: center;
  gap: 12px;
  padding: 8px;
  min-width: 44px;
  min-height: 44px;
  justify-content: center;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--fg-muted);
  cursor: pointer;
  transition: color 0.2s;
}
.js .theme-switch { display: inline-flex; }
.theme-switch:hover { color: var(--fg); }
.theme-switch__icon {
  width: 16px;
  height: 16px;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  background: linear-gradient(90deg, currentColor 50%, transparent 50%);
  transition: transform 0.4s var(--ease-out);
}
.theme-switch[aria-pressed="true"] .theme-switch__icon { transform: rotate(180deg); }
@media (min-width: 961px) {
  .nav__menu { display: flex; align-items: center; gap: 4px; }
  .theme-switch__label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
@media (max-width: 960px) {
  .js .theme-switch {
    display: flex;
    width: 100%;
    padding: 14px 0;
    font-size: var(--step-1);
    color: var(--fg);
    justify-content: flex-start;
  }
}

/* The pages show the logo for light backgrounds (brand/logo/kit/svg/rivermind-horizontal.svg); dark mode, and the nav over the
   home hero, the reversed one */
:root[data-theme="dark"] .brand__mark,
:root:has([data-night]) .nav:not(.is-past-night) .brand__mark { content: url("/brand-mark.svg"); }

.nav__toggle {
  display: none;
  width: 44px;
  height: 44px;
  margin-right: -10px;
  border: 0;
  background: none;
  cursor: pointer;
}
.nav__toggle-bars,
.nav__toggle-bars::before {
  display: block;
  width: 20px;
  height: 1.5px;
  margin-inline: auto;
  background: var(--fg);
  transition: transform 0.25s var(--ease-out);
}
.nav__toggle-bars { position: relative; transform: translateY(-3px); }
.nav__toggle-bars::before {
  content: "";
  position: absolute;
  top: 6px;
}
.nav.is-open .nav__toggle-bars { transform: rotate(45deg); }
.nav.is-open .nav__toggle-bars::before { transform: translateY(-6px) rotate(-90deg); }

@media (max-width: 960px) {
  .nav__inner { gap: 16px; }
  .nav__cta { margin-left: auto; }
  /* The menu is opened by site.js; without it the button would do nothing */
  .js .nav__toggle { display: block; }
  .nav__menu {
    position: absolute;
    inset: 100% 0 auto;
    margin: 0;
    /* dvh: with svh the menu stops short of the bottom edge once the mobile address bar has slid away */
    height: calc(100dvh - var(--nav-h) - env(safe-area-inset-top, 0px));
    padding: 8px max(var(--gutter), env(safe-area-inset-right, 0px)) 24px max(var(--gutter), env(safe-area-inset-left, 0px));
    overflow-y: auto;
    background: var(--bg);
    display: none;
  }
  .menu-open { overflow: hidden; }
  .nav.is-open .nav__menu { display: block; }
  .nav__links { flex-direction: column; }
  .nav__links a {
    padding: 14px 0;
    font-size: var(--step-1);
    color: var(--fg);
    border-bottom: 1px solid var(--line);
    border-radius: 0;
  }
}

@media (max-width: 400px) {
  .nav .brand__labs { display: none; }
}

@media (max-width: 359px) {
  .nav__cta { display: none; }
  .nav__toggle { margin-left: auto; }
}

/* Hero */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* The whole first screen, however tall: a cap would let the light band below show on load */
  min-height: 100svh;
  padding-top: calc(var(--nav-h) + env(safe-area-inset-top, 0px) + clamp(32px, 5vh, 88px));
  padding-bottom: clamp(32px, 5vh, 56px);
  overflow: hidden;
  isolation: isolate;
  /* The glow fades back to the ground at the foot, where the band under the hero takes over */
  background:
    linear-gradient(transparent 80%, var(--bg)),
    radial-gradient(70% 55% at 50% 100%, color-mix(in srgb, var(--line) 60%, transparent), transparent 70%),
    radial-gradient(28% 42% at 50% 76%, color-mix(in srgb, var(--glow) 8%, transparent), transparent 70%),
    var(--bg);
}

.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
  opacity: 0.06;
  mix-blend-mode: overlay;
  pointer-events: none;
}

/* On a screen taller than the hero's content, the copy sits midway between the nav and the foot
   of the hero */
.hero__copy {
  display: grid;
  gap: 24px;
  margin-block: auto;
}

.hero__stage { margin-block: auto; }
/* From 1080 px the copy keeps the left column and the river runs behind it and to its right */
@media (min-width: 1080px) {
  .hero__copy { max-width: clamp(22rem, 30vw, 28rem); }
  .hero__stage .hero__title { font-size: clamp(2.75rem, 0.5rem + 4.2vw, 4.75rem); }
}

/* The hero's river of papers (river.js): from 1080 px it fills the first screen behind the copy, darkest on the copy's
   side; below that it follows the copy, full-bleed. */
.hero__river {
  position: absolute;
  inset: 0 0 auto;
  height: 100svh;
  z-index: -2;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 14%, #000 82%, transparent);
  mask-image: linear-gradient(180deg, transparent, #000 14%, #000 82%, transparent);
}
.hero__river canvas {
  display: block;
  width: 100%;
  height: 100%;
}
/* The copy reads over still water: the night is solid behind it and lifts towards the gate */
.hero__river::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--bg) 0, color-mix(in srgb, var(--bg) 90%, transparent) 34%, transparent 56%);
  pointer-events: none;
}
/* In from the dark under the opening's curtain, like the photo before it: opacity only, no rise */
@media (prefers-reduced-motion: no-preference) {
  .hero__river { animation: river-in 1.6s ease-out var(--intro-photo, 0s) both; }
}
@keyframes river-in {
  from { opacity: 0; }
}

/* Below 1080 px the river follows the copy, as wide as the screen, and fades in from the top */
@media (max-width: 1079px) {
  .hero { padding-bottom: 12px; }
  .hero__river {
    position: relative;
    inset: auto;
    height: clamp(360px, 70vw, 520px);
    margin-left: calc(-1 * max(var(--gutter), env(safe-area-inset-left, 0px)));
    margin-right: calc(-1 * max(var(--gutter), env(safe-area-inset-right, 0px)));
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 22%, #000 86%, transparent);
    mask-image: linear-gradient(180deg, transparent, #000 22%, #000 86%, transparent);
  }
  .hero__river::after { display: none; }
}

/* The hero parts (home.js sets .is-parting on the night wrapper unless motion is reduced, and --part from 0 to 1 as the
   visitor scrolls on). The hero holds still while the band under it comes up over it like a sheet laid on the water, and
   its two halves part like the leaves of a gate: the copy turns away to the left on its outer edge, the river to the
   right, and both sink into the night. Transforms and opacity only, so the browser moves them without painting. */
.is-parting .hero {
  position: sticky; /* home.js sets top: 0, or less on a hero taller than the window */
}
.is-parting .hero__copy,
.is-parting .hero__river { will-change: transform, opacity; }
.is-parting .hero__copy {
  transform-origin: 0 50%;
  transform:
    perspective(1400px)
    translateX(calc(var(--part, 0) * -12vw))
    rotateY(calc(var(--part, 0) * 24deg))
    scale(calc(1 - var(--part, 0) * 0.08));
  opacity: calc(1 - var(--part, 0) * 1.6);
}
.is-parting .hero__river {
  transform-origin: 100% 50%;
  transform:
    perspective(1400px)
    translateX(calc(var(--part, 0) * 12vw))
    rotateY(calc(var(--part, 0) * -24deg))
    scale(calc(1 - var(--part, 0) * 0.08));
}
.is-parting .hero__river canvas { opacity: calc(1 - var(--part, 0) * 1.25); }
/* The sheet: a hair narrower than the window while it comes up, full width once it is up. Its ground is the night's, so
   once up it is one night with what is left of the hero above it, as before the scroll: its edge, a hairline and a
   shadow cast up on the hero, fades out on the last stretch. Pseudo-elements, so the scale never touches the band's text */
.is-parting .demo-band {
  position: relative;
  z-index: 1;
  isolation: isolate;
}
.is-parting .demo-band::before,
.is-parting .demo-band::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 28px 28px 0 0;
  transform: scaleX(calc(0.95 + var(--part, 0) * 0.05));
  will-change: transform, opacity;
  pointer-events: none;
}
.is-parting .demo-band::before { background: var(--bg); }
.is-parting .demo-band::after {
  border-top: 1px solid var(--line);
  box-shadow: 0 -32px 80px -16px rgb(0 0 0 / 0.55);
  /* the shadow falls on the hero only, not on the section under the band */
  clip-path: inset(-140px -140px 0 -140px);
  opacity: calc((1 - var(--part, 0)) * 3);
}

.hero__title {
  font-family: var(--font-display);
  font-size: var(--step-5);
  font-stretch: 108%;
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.025em;
}
.hero__line { display: block; }
.hero__nowrap { white-space: nowrap; }
.hero__ai { color: var(--signal); }

.hero__lead {
  max-width: 38rem;
  margin-top: 8px;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

/* Under the buttons: what the first step costs, and what follows it */
.hero__note {
  max-width: 30rem;
  margin-top: -8px;
  font-size: var(--step-0);
  line-height: 1.45;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* 404 page: the hero band on its own */
.notfound { justify-content: center; }

/* Hero load sequence: the title rises word by word, then the lead, the buttons and the note.
   --intro holds it back while the opening plays; it is the moment the curtain starts to uncover the title. */

@media (prefers-reduced-motion: no-preference) {
  .hero__eyebrow,
  .hero__lead,
  .hero__actions,
  .hero__note {
    animation: rise-in 1s var(--ease-expo) both;
  }
  .hero__word {
    display: inline-block;
    animation: word-in 1.1s var(--ease-expo) both;
    animation-delay: calc(var(--intro, 0s) + var(--i) * 45ms);
  }
  .hero__eyebrow { animation-delay: var(--intro, 0s); }
  .hero__lead { animation-delay: calc(var(--intro, 0s) + 0.42s); }
  .hero__actions { animation-delay: calc(var(--intro, 0s) + 0.5s); }
  .hero__note { animation-delay: calc(var(--intro, 0s) + 0.56s); }
}

@keyframes word-in {
  from { opacity: 0; transform: translateY(0.45em); }
}

/* Once per visit the AI crew builds the hero instead (boot.js adds .has-crew before the first paint; swarm.js takes the
   hero over with .sw-on and leaves .sw-done). Until it does, the copy is held back; if it never does, the copy shows by
   itself three seconds after the opening. The load sequence above does not play on that visit. */
@media screen and (prefers-reduced-motion: no-preference) {
  .has-crew .hero__copy > *,
  .has-crew .hero__word { animation: none; }
  .has-crew .hero:not(.sw-on, .sw-done) .hero__copy > * {
    animation: crew-late 0.3s calc(var(--intro, 0s) + 3s) both;
  }
}
@keyframes crew-late {
  from { opacity: 0; }
}

/* Opening, once per visit (boot.js adds .has-intro to the page): the lamp in the R fills, then the night lifts off the
   page and the title arrives as it does. CSS only, so it ends on time even if a script fails. Amber is the lamp of the
   logo, where AI works. The opening stands between the visitor and the title, so it is short: the lamp is full at
   0.62s, the curtain lifts from 0.65s to 1.25s, and --intro is when the curtain has just uncovered the title.
   The hero's backdrop does not wait for that: it starts fading in under the curtain at --intro-photo. */

.intro { display: none; }
.has-intro { --intro: 0.72s; --intro-photo: 0.3s; }
.has-intro .intro {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  background: var(--bg);
  animation: intro-lift 0.6s 0.65s var(--ease-expo) forwards;
}
.intro__mark {
  grid-area: 1 / 1;
  width: clamp(64px, 8vw, 84px);
  height: auto;
  animation: intro-mark 0.45s var(--ease-expo) both;
}
.intro__ink { fill: var(--fg); }
.intro__vessel { fill: var(--line); }
.intro__lamp { fill: var(--signal); }
/* The lamp's own copy of the mark, cut away from the top until the liquid has risen to the brim */
.intro__mark--lamp {
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--glow) 55%, transparent));
  animation:
    intro-mark 0.45s var(--ease-expo) both,
    intro-fill 0.5s 0.12s cubic-bezier(0.65, 0, 0.35, 1) both;
}
@keyframes intro-mark {
  from { opacity: 0; transform: translateY(10px) scale(0.96); }
}
@keyframes intro-fill {
  from { clip-path: inset(91% 0 0 0); }
  to { clip-path: inset(36% 0 0 0); }
}
@keyframes intro-lift {
  to { transform: translateY(-100%); visibility: hidden; }
}

@keyframes rise-in {
  from { opacity: 0; transform: translateY(24px); filter: blur(6px); }
  to { opacity: 1; transform: none; filter: none; }
}

/* The band under the hero, inside the same night: one job before and after */
.demo-band { padding-block: clamp(48px, 6vw, 88px); }

/* One job, before and after (demo.js): the band under the hero. A size container, so it lays itself out from its own
   width: two columns with the amber links when wide, one column below 760px, a 2x2 tab grid on a phone. The tokens are the
   band's (night in white mode, graphite in dark mode); the paper invoice and the spreadsheet are objects and stay light
   paper in both modes. */

.demo {
  container: demo / inline-size;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 28px 24px;

  --sheet-bg: #e9efec;
  --sheet-head: #dde5e2;
  --sheet-ink: #1d2b2c;
  --sheet-line: #b9c6c2;
}
.demo__tabs,
.demo__stage { grid-column: 1 / -1; }

/* The tabs. The line under the chosen one fills over the time it stays, so the visitor sees when the next job comes */
.demo__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 28px;
  border-bottom: 1px solid var(--line);
}
.demo__tab {
  position: relative;
  padding: 0 0 14px;
  border: 0;
  background: none;
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--fg-muted);
  white-space: nowrap;
  cursor: pointer;
}
.demo__tab:hover,
.demo__tab[aria-selected="true"] { color: var(--fg); }
.demo__tab i {
  position: absolute;
  inset: auto 0 -1px;
  height: 2px;
  background: var(--fg);
  transform: scaleX(0);
  transform-origin: left;
}
.demo__tab[aria-selected="true"] i { animation: demo-run var(--hold, 7s) linear forwards; }
.demo.is-idle .demo__tab i { animation-play-state: paused; }
.demo.is-still .demo__tab[aria-selected="true"] i { animation: none; transform: none; }
@keyframes demo-run { to { transform: scaleX(1); } }

/* The panels share one cell, so the stage is as tall as the tallest job and the page below does not move when the job
   changes. Only the current one is seen. */
.demo__stage { display: grid; }
.demo__panel {
  position: relative;
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 40px clamp(40px, 9cqi, 88px);
  align-items: start;
  align-content: start; /* a shorter job keeps its rows together at the top of the shared cell */
}
.demo__panel:not(.is-current) { visibility: hidden; }

.demo__side { display: grid; gap: 14px; align-content: start; min-width: 0; }
.demo__label { font-size: var(--step--1); font-weight: 500; color: var(--fg-muted); }
.demo__label--ai { color: var(--signal); }
.demo__in,
.demo__out { position: relative; }

/* The amber links from each result to the words it took them from, drawn by demo.js when the two sit side by side */
.demo__links {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.demo__links path { fill: none; stroke: var(--signal); stroke-width: 1.25; stroke-linecap: round; opacity: 0.85; }
.demo__links circle { fill: var(--signal); }

.demo__foot { font-size: var(--step--1); color: var(--fg-muted); }
.demo__pause {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 4px;
  border: 0;
  background: none;
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--fg-muted);
  cursor: pointer;
}
.demo__pause:hover { color: var(--fg); }
.demo__pause::before { content: ""; width: 8px; height: 10px; border-inline: 2.5px solid currentColor; }
.demo__pause.is-paused::before {
  width: 0;
  height: 0;
  border: 0;
  border-left: 8px solid currentColor;
  border-block: 5px solid transparent;
}

/* What arrives, in the form it arrives in */
.demo mark { color: inherit; background: none; border-radius: 3px; transition: background-color 0.4s, box-shadow 0.4s; }
.demo mark.is-on { background: color-mix(in srgb, var(--signal) 20%, transparent); box-shadow: inset 0 -1.5px 0 var(--signal); }
.demo__paper mark.is-on,
.demo__sheet mark.is-on { background: color-mix(in srgb, var(--lamp-400) 34%, transparent); box-shadow: inset 0 -1.5px 0 var(--lamp-700); }
.demo__meta { display: block; margin-bottom: 8px; font-size: 0.8125rem; color: var(--fg-muted); }
.demo__bubble {
  max-width: 24rem;
  padding: 14px 18px;
  border-radius: 18px 18px 18px 4px;
  background: color-mix(in srgb, var(--fg) 9%, var(--surface));
  font-size: var(--step-0);
  line-height: 1.5;
}

.demo__paper {
  max-width: 25rem;
  padding: 18px 20px 20px;
  border-radius: 3px;
  background: var(--sheet-bg);
  color: var(--sheet-ink);
  font-size: 0.8125rem;
  line-height: 1.55;
  box-shadow: 0 18px 40px -18px rgb(0 0 0 / 0.7);
  transform: rotate(-1.2deg);
}
.demo__paper-head { text-align: center; font-size: var(--step--1); font-weight: 700; letter-spacing: 0.02em; }
.demo__paper-sub { display: flex; justify-content: space-between; margin: 6px 0 12px; font-family: var(--font-mono); font-size: var(--step--2); }
.demo__paper table { width: 100%; border-collapse: collapse; font-size: var(--step--2); }
.demo__paper th,
.demo__paper td { padding: 4px 6px; border: 1px solid var(--sheet-line); text-align: left; font-weight: 400; }
.demo__paper th { background: var(--sheet-head); font-weight: 600; }
.demo__paper-sum { display: grid; grid-template-columns: 1fr auto; gap: 2px 16px; margin: 10px 0 0; }
.demo__paper-sum dd { font-family: var(--font-mono); text-align: right; }
.demo__total { font-weight: 700; }
.demo__num { text-align: right; font-family: var(--font-mono); }

.demo__files { display: grid; gap: 8px; margin-top: 14px; }
.demo__file {
  display: flex;
  align-items: center;
  gap: 10px;
  width: fit-content;
  max-width: 100%;
  padding: 8px 12px 8px 10px;
  border-radius: 6px;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: var(--step--1);
}
.demo__file svg { flex: none; width: 14px; height: 18px; }
.demo__file path { fill: none; stroke: currentColor; stroke-width: 1.2; }
.demo__sheet {
  max-width: 24rem;
  margin-top: 14px;
  border-collapse: collapse;
  background: var(--sheet-bg);
  color: var(--sheet-ink);
  font-size: 0.8125rem;
  box-shadow: 0 18px 40px -18px rgb(0 0 0 / 0.7);
}
.demo__sheet td { padding: 6px 12px; border: 1px solid var(--sheet-line); }
.demo__sheet tr:first-child td { background: var(--sheet-head); font-weight: 600; }

/* The scan: AI reads what came in, once, top to bottom */
.demo__scan {
  position: absolute;
  inset: 0 -12px auto;
  height: 72px;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--signal) 14%, transparent) 85%, var(--signal) 85% calc(85% + 1px), transparent calc(85% + 1px));
}
.demo__scan.is-on { animation: demo-scan 1s cubic-bezier(0.45, 0, 0.25, 1) forwards; }
@keyframes demo-scan {
  0% { opacity: 0; transform: translateY(-40px); }
  15%, 85% { opacity: 1; }
  100% { opacity: 0; transform: translateY(var(--scan-to, 200px)); }
}

/* What AI made of it */
.demo__out {
  padding: 18px 20px 20px;
  border-radius: 8px;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line), inset 2px 0 0 var(--signal);
}
.demo__kind {
  font-family: var(--font-display);
  font-stretch: 108%;
  font-size: 1.1875rem;
  font-weight: 600;
  transition: opacity 0.4s;
}
.demo__out dl { display: grid; gap: 12px; margin: 14px 0 0; }
.demo__row {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  gap: 12px;
  transition: opacity 0.45s var(--ease-expo), transform 0.45s var(--ease-expo);
}
.demo__row dt { padding-top: 2px; font-size: 0.8125rem; color: var(--fg-muted); }
.demo__row dd { font-size: 0.9375rem; line-height: 1.45; }
.demo__data { font-family: var(--font-mono); font-size: var(--step--1); white-space: nowrap; }
.demo__bars { display: flex; align-items: flex-end; gap: 6px; height: 40px; margin-top: 8px; }
.demo__bars i { width: 22px; border-radius: 2px 2px 0 0; background: color-mix(in srgb, var(--fg-muted) 40%, var(--surface)); }
.demo__bars i:last-child { background: var(--signal); }

/* Once the script runs, a result is built up in front of the visitor: it waits, then comes in. Without the script (or with
   reduced motion) the job stands complete. */
.demo.is-live .demo__kind:not(.is-on) { opacity: 0; }
.demo.is-live .demo__row:not(.is-on) { opacity: 0; transform: translateX(-6px); }

@container demo (max-width: 759px) {
  .demo__panel { grid-template-columns: minmax(0, 1fr); }
}
@container demo (max-width: 520px) {
  .demo__tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; border-bottom: 0; }
  .demo__tab { padding: 12px 0; text-align: left; box-shadow: inset 0 -1px 0 var(--line); }
  .demo__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .demo mark,
  .demo__kind,
  .demo__row { transition: none; }
  .demo__scan.is-on { animation: none; }
}

/* Ticked lists: a short rule for each bullet */

.ticks {
  display: grid;
  gap: 10px;
  color: var(--fg-muted);
}
.ticks li {
  position: relative;
  padding-left: 24px;
}
.ticks li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 0.8em;
  width: 10px;
  height: 1px;
  background: currentColor;
}

/* Controls, shared by Hình 2 and the estimate card */

/* Chips: one radio each, the checked one filled */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: 8px;
  padding-inline: 16px;
  border-radius: 999px;
  box-shadow: inset 0 0 0 1px var(--line);
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--fg-muted);
  cursor: pointer;
  transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
}
.chip input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.chip:hover { color: var(--fg); }
.chip:has(:checked) {
  background: var(--fg);
  color: var(--bg);
  box-shadow: none;
}
.chip:has(:focus-visible) {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

/* Range slider: --fill is the share of the track to ink; home.js sets it on the estimate's sliders */

.range {
  --fill: 0%;
  --track: linear-gradient(90deg, var(--fg) var(--fill), color-mix(in srgb, var(--fg) 18%, transparent) var(--fill));
  width: 100%;
  height: 44px;
  margin: 0;
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--track);
}
.range::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--track);
}
.range::-webkit-slider-thumb {
  width: 22px;
  height: 22px;
  margin-top: -9px;
  border-radius: 50%;
  background: var(--fg);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--fg) 12%, transparent);
  -webkit-appearance: none;
  appearance: none;
}
.range::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: var(--fg);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--fg) 12%, transparent);
}

/* Process */

.steps {
  position: relative;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 32px;
  counter-reset: step;
  view-timeline: --steps block;
}
.steps::before,
.steps::after {
  content: "";
  position: absolute;
  top: 6px;
  left: 0;
  right: 0;
  height: 1px;
}
.steps::before { background: var(--line); }
.steps::after {
  background: linear-gradient(90deg, transparent, var(--signal), transparent) no-repeat -30% 0;
  background-size: 22% 100%;
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .steps::after {
      animation: current linear both;
      animation-timeline: --steps;
      animation-range: cover 25% cover 75%;
    }
  }
}
@keyframes current {
  from { background-position: -30% 0; }
  to { background-position: 130% 0; }
}

.step {
  position: relative;
  display: grid;
  align-content: start;
  gap: 12px;
  padding-top: 40px;
}
.step::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--bg);
  box-shadow: inset 0 0 0 1px var(--fg);
}
.step__no {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--fg-muted);
}
.step__name {
  font-size: var(--step-2);
  font-stretch: 105%;
}
.step p {
  color: var(--fg-muted);
  font-size: 0.9875rem;
  text-wrap: pretty;
}
.step .step__time {
  margin-top: -4px;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--fg);
}

@media (max-width: 960px) {
  .steps {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding-left: 40px;
  }
  .steps::before,
  .steps::after {
    top: 6px;
    bottom: 6px;
    left: 6px;
    right: auto;
    width: 1px;
    height: auto;
  }
  .steps::after {
    background: linear-gradient(180deg, transparent, var(--signal), transparent) no-repeat 0 -30%;
    background-size: 100% 22%;
    animation-name: current-down;
  }
  .step {
    padding-top: 0;
    padding-bottom: 40px;
    grid-template-columns: minmax(0, 1fr);
  }
  .step:last-child { padding-bottom: 0; }
  .step::before { left: -40px; }
}
@keyframes current-down {
  from { background-position: 0 -30%; }
  to { background-position: 0 130%; }
}

/* The hours estimate card, and the headings it shares with the self-check in its second half */

.estimate {
  justify-self: stretch;
  display: grid;
  gap: 28px;
  padding: clamp(24px, 3vw, 40px);
  border-radius: 16px;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--line);
}
.check__title,
.check__level,
.estimate__title {
  font-size: var(--step-2);
  font-stretch: 105%;
}

/* Pilot self-check: the second half of the estimate card, about the same task, the result under the questions.
   No amber: nothing here is AI at work. */

.check {
  padding-top: 28px;
  border-top: 1px solid var(--line);
}

/* A <details> that starts closed, folded like the FAQ (the plus is shared, see .qa below): the heading and its lead are the
   summary, and the plus sits at the right of the heading */
.check > summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 8px 24px;
}
.check > summary::after { grid-area: 1 / 2; }
.check__lead {
  max-width: 38rem;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.check__questions {
  display: grid;
  gap: 24px;
  margin-top: 28px;
}
/* The rule between questions sits under the chips: a fieldset border or padding would be placed around its legend */
.check__q:not(:last-child) .chips {
  padding-bottom: 24px;
  border-bottom: 1px solid var(--line);
}
.check__q legend {
  font-weight: 500;
  text-wrap: pretty;
}

/* A dashed empty slot until every question is answered, then the result on a plain panel */
.check__result {
  display: grid;
  gap: 16px;
  margin-top: 28px;
  padding: clamp(20px, 2.4vw, 32px);
  border: 1px solid transparent;
  border-radius: 12px;
  background: var(--bg);
}
.check[data-state="idle"] .check__result {
  place-content: center;
  min-height: 8rem;
  border: 1px dashed var(--line);
  background: none;
}
.check[data-state="idle"] .check__outcome,
.check[data-state="done"] .check__pending { display: none; }

.check__pending {
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.check__outcome {
  display: grid;
  justify-items: start;
  gap: 16px;
  text-wrap: pretty;
}
.check__level {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.25;
  text-wrap: balance;
}
.check__outcome .ticks:empty { display: none; }
.check__outcome .ticks strong { color: var(--fg); }

/* Principles */

.principles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.principle {
  display: grid;
  align-content: start;
  gap: 14px;
  padding: clamp(28px, 3.6vw, 48px) clamp(24px, 3.6vw, 48px) clamp(28px, 3.6vw, 48px) 0;
  border-bottom: 1px solid var(--line);
}
.principle:nth-child(even) {
  padding-left: clamp(24px, 3.6vw, 48px);
  padding-right: 0;
  border-left: 1px solid var(--line);
}
.principle h3 { font-size: var(--step-2); max-width: 22ch; }
.principle p { color: var(--fg-muted); max-width: 34rem; text-wrap: pretty; }

@media (max-width: 760px) {
  .principles { grid-template-columns: minmax(0, 1fr); }
  .principle,
  .principle:nth-child(even) {
    padding-inline: 0;
    border-left: 0;
  }
}

/* Figures: machines drawn by brand/figures, styled by figures/iso.css. Each starts as a still drawing;
   figures.js makes it move. */

.fig-slot { margin-block: clamp(48px, 6vw, 88px); }
.section-head + .fig-slot { margin-top: 0; }

/* Photos: every one is toned to the river ramp by brand/photos/build.py, so none carries amber */

.photo {
  position: relative;
  overflow: hidden;
  border-radius: 2px;
  background: var(--surface);
}
.photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* In dark mode the photos lose most of the river teal, to sit on graphite */
:root[data-theme="dark"] :is(.photo img, .person__photo) { filter: saturate(0.3); }
/* Loading state: a slow band of light crosses the empty frame until boot.js marks the frame is-loaded, then it fades in */
.js .photo img {
  opacity: 0;
  transition: opacity 0.8s ease-out;
}
.js .photo.is-loaded img { opacity: 1; }
.js .photo:not(.is-loaded)::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--fg) 7%, transparent) 50%, transparent 70%);
  animation: shimmer 1.6s ease-in-out infinite;
}
@keyframes shimmer {
  from { transform: translateX(-100%); }
  to { transform: translateX(100%); }
}

/* Capabilities: the three main jobs are cards with the river palette's tones; the other
   five are a ruled list under them, like a specification sheet. Each has a sketch of what goes in and what comes out, its
   name, then the two facts. In each sketch the input is dim and the part AI makes is amber. Prices are agreed per project,
   so the section lists none. */

.caps-head { margin-bottom: clamp(28px, 3.5vw, 44px); }
/* The heading and the AI core that runs the jobs below it: side by side from 960 px, the core under the heading below that */
.caps-top {
  display: grid;
  gap: 28px clamp(32px, 6vw, 96px);
  margin-bottom: clamp(40px, 5vw, 72px);
}
.caps-top .caps-head { margin-bottom: 0; }
@media (min-width: 960px) {
  .caps-top {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    align-items: end;
  }
}

/* The AI core (core.js), in the "Năng lực" section: a plate with its own night tone, as .cap--night does it, so it reads the
   same on the light band in white mode and on the graphite band in dark mode (--river-* turn graphite there). The hairline
   keeps its edge on graphite. The drawing's colours are mixed from the river palette and the tones below are the same
   four every box takes: the steel dock, the dark tiles, the core when dim, and the core lit. Amber (--signal) appears only
   while the core is plugged in: the core, the beads on the cables, the light it throws and the tiles' "AI đang làm". It
   is a size container, so it needs a width of its own (a grid column or a block), not a shrink-to-fit one. */
.core {
  --bg: var(--river-950);
  --fg: var(--mist-50);
  --fg-muted: var(--reed-400);
  --signal: var(--lamp-400);
  --core-line: color-mix(in srgb, var(--river-700) 70%, var(--reed-400));

  container-type: inline-size;
  display: grid;
  gap: 12px;
  padding: clamp(16px, 2.4vw, 28px);
  border: 1px solid var(--river-700);
  border-radius: 2px;
  /* The floor grid runs past the drawing's box; the plate is its edge */
  overflow: hidden;
  background: var(--bg);
  color: var(--fg);
}
.core__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

/* Boxes: three faces and an edge, from the tone's four values */
.core__box {
  stroke: var(--core-edge);
  stroke-width: 0.8;
  stroke-linejoin: round;
}
.core__fy { fill: var(--core-y); }
.core__fx { fill: var(--core-x); }
.core__ft { fill: var(--core-top); }

.core__tone--steel {
  --core-top: color-mix(in srgb, var(--river-700) 92%, var(--reed-400));
  --core-y: color-mix(in srgb, var(--river-700) 70%, var(--river-900));
  --core-x: color-mix(in srgb, var(--river-900) 80%, var(--river-700));
  --core-edge: var(--core-line);
}
.core__tone--tile {
  --core-top: color-mix(in srgb, var(--river-700) 80%, var(--river-900));
  --core-y: color-mix(in srgb, var(--river-900) 70%, var(--river-700));
  --core-x: color-mix(in srgb, var(--river-900) 85%, var(--river-950));
  --core-edge: color-mix(in srgb, var(--river-700) 80%, var(--reed-400));
}
.core__tone--dim {
  --core-top: color-mix(in srgb, var(--river-700) 68%, var(--reed-400));
  --core-y: color-mix(in srgb, var(--river-700) 83%, var(--reed-400));
  --core-x: color-mix(in srgb, var(--river-700) 95%, var(--reed-400));
  --core-edge: color-mix(in srgb, var(--river-700) 45%, var(--reed-400));
  --core-pane: color-mix(in srgb, var(--river-700) 50%, var(--reed-400));
  --core-pane-opacity: 0.6;
  --core-vein: color-mix(in srgb, var(--river-700) 83%, var(--reed-400));
}
.core__tone--lit {
  --core-top: color-mix(in srgb, var(--signal) 78%, var(--white));
  --core-y: color-mix(in srgb, var(--signal) 75%, var(--lamp-700));
  --core-x: color-mix(in srgb, var(--signal) 35%, var(--lamp-700));
  --core-edge: color-mix(in srgb, var(--signal) 45%, var(--white));
  --core-pane: color-mix(in srgb, var(--signal) 18%, var(--white));
  --core-pane-opacity: 0.85;
  --core-vein: var(--core-y);
}
.core__pane {
  fill: var(--core-pane);
  opacity: var(--core-pane-opacity);
}
.core__vein {
  fill: none;
  stroke: var(--core-vein);
  stroke-width: 1.2;
}

/* The floor and the dock's details */
.core__grid {
  stroke: var(--reed-400);
  stroke-opacity: 0.1;
  stroke-width: 0.8;
}
.core__etch {
  fill: none;
  stroke: color-mix(in srgb, var(--river-700) 60%, var(--reed-400));
  stroke-width: 1.1;
}
.core__socket {
  fill: color-mix(in srgb, var(--river-950) 70%, var(--river-900));
  stroke: var(--core-line);
  stroke-width: 0.8;
}
.core__slot {
  fill: var(--river-950);
  stroke: var(--core-line);
  stroke-width: 0.8;
}
.core__cables {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.core__cable-sheath {
  stroke: color-mix(in srgb, var(--river-950) 55%, var(--river-900));
  stroke-width: 5.5;
}
.core__cable-wire {
  stroke: color-mix(in srgb, var(--river-700) 80%, var(--reed-400));
  stroke-width: 3;
}
.core__icon {
  fill: none;
  stroke: var(--mist-200);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Amber: the light, the beads and the lit core, which all go out when the core is pulled */
.core__stop { stop-color: var(--signal); }
.core__halo, .core__bead { fill: var(--signal); }

.core__label {
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  fill: var(--fg);
}
.core__state {
  font-family: var(--font-body);
  font-size: 13px;
  fill: var(--fg-muted);
}
.core__state--ai { fill: var(--signal); }
.core.is-off .core__state--ai,
.core:not(.is-off) .core__state--off { display: none; }
/* Below 560px the names would be under 10px on screen; the icons and the line below carry it */
@container (width < 560px) {
  .core__label, .core__state { display: none; }
}

/* The core you take hold of; the ring shows only on keyboard focus */
.core__part {
  cursor: grab;
  touch-action: none; /* a finger on the core drags it; anywhere else on the plate still scrolls the page */
}
.core__part:active { cursor: grabbing; }
.core__part:focus-visible { outline: none; }
.core__ring {
  fill: none;
  stroke: transparent;
  stroke-width: 1.5;
  stroke-dasharray: 4 4;
}
.core__part:focus-visible .core__ring { stroke: var(--fg); }

.core__slide { transition: transform 0.9s var(--ease-expo); }
.core.is-dragging .core__slide { transition: none; }
.core__lit, .core__beads, .core__glow, .core__icon { transition: opacity 0.6s; }
.core.is-off .core__lit,
.core.is-off .core__beads,
.core.is-off .core__glow { opacity: 0; }
.core.is-off .core__icon { opacity: 0.35; }
@media (prefers-reduced-motion: reduce) {
  .core__slide, .core__lit, .core__beads, .core__glow, .core__icon { transition: none; }
}

.core__foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 4px 16px;
  font-size: var(--step--1);
  line-height: 1.5;
}
.core__hint { color: var(--fg); }
.core__status { color: var(--fg-muted); }
.caps__key {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--step--1);
  color: var(--fg-muted);
}
.caps__swatch {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: var(--signal);
}

.caps {
  display: grid;
  gap: clamp(12px, 1.6vw, 20px);
}
.cap {
  display: grid;
  align-content: start;
  gap: clamp(16px, 1.8vw, 22px);
  padding: clamp(20px, 2.2vw, 32px);
  border-radius: 2px;
  background: var(--bg);
  color: var(--fg);
}

/* Each tone sets the tokens the card's own text and sketch read */
.cap--night {
  --bg: var(--river-950);
  --fg: var(--mist-50);
  --fg-muted: var(--reed-400);
  --signal: var(--lamp-400);
}
.cap--deep {
  --bg: var(--river-700);
  --fg: var(--mist-50);
  --fg-muted: var(--mist-200);
  --signal: var(--lamp-400);
}
.cap--light {
  --bg: var(--mist-200);
  --fg: var(--river-950);
  --fg-muted: color-mix(in srgb, var(--reed-600) 70%, var(--river-950));
  --signal: var(--lamp-700);
}

/* A third of the row is narrow, so the name is a step smaller than its width would allow elsewhere */
.cap__name {
  font-size: clamp(1.375rem, 1rem + 0.9vw, 1.75rem);
  font-stretch: 108%;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.cap__facts {
  display: grid;
  align-content: start;
  gap: 14px;
}
.cap__facts div {
  display: grid;
  align-content: start;
  gap: 2px;
}
.cap__facts dt {
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--fg-muted);
}
.cap__facts dd { text-wrap: pretty; }
.cap__in dd { color: var(--fg-muted); }

.caps__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px 48px;
  margin-top: clamp(28px, 3vw, 40px);
}
.caps__note {
  max-width: 34rem;
  color: var(--fg-muted);
  text-wrap: pretty;
}

/* The sketches: flat drawings in the block's own tones. Surfaces are a tint of the text colour, text is bars, and the one
   amber part is what AI made. They hold no words, so a reader only needs the text beside them. */
.sketch {
  display: block;
  width: 100%;
  height: auto;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.sketch .sk-sheet {
  fill: color-mix(in srgb, currentColor 9%, transparent);
  stroke-opacity: 0.5;
}
.sketch .sk-edge { stroke-opacity: 0.9; }
.sketch .sk-dim { stroke-opacity: 0.5; }
.sketch .sk-grid { stroke-opacity: 0.25; stroke-width: 1.5; }
.sketch .sk-text,
.sketch .sk-ink {
  fill: currentColor;
  stroke: none;
}
.sketch .sk-text { fill-opacity: 0.3; }
.sketch .sk-ink { fill-opacity: 0.6; }
.sketch .sk-wave { stroke-opacity: 0.5; stroke-width: 5; }
.sketch .sk-ai {
  fill: var(--signal);
  stroke: none;
}
.sketch .sk-ai-edge { stroke: var(--signal); stroke-width: 2.5; }
.sketch .sk-knock {
  fill: var(--bg);
  fill-opacity: 0.78;
  stroke: none;
}
.sketch .sk-tick {
  stroke: var(--bg);
  stroke-width: 2.5;
}
.sketch .sk-stamp {
  fill: var(--bg);
  stroke-width: 3;
  stroke-opacity: 0.9;
}
.sketch .sk-check { stroke-width: 3.5; }

/* The other five jobs sit on the band itself, which already sets the tokens the sketches read, so a row needs none of
   its own. The sketch and name share the first line on a phone, the facts run under them */
.jobs {
  margin-top: clamp(28px, 3vw, 44px);
  border-top: 1px solid var(--line);
}
.job {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
  gap: 14px 20px;
  padding-block: clamp(14px, 1.4vw, 20px);
  border-bottom: 1px solid var(--line);
}
/* At about 100px wide the 2-unit hairlines of the sketches draw thinner than a pixel */
.job .sketch { stroke-width: 3; }
.job__name {
  font-size: var(--step-1);
  font-stretch: 108%;
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.job .cap__facts {
  grid-column: 1 / -1;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 12px 24px;
}
/* A list is read across, so its sentences are set a step smaller than the cards' */
.job .cap__facts dd {
  font-size: var(--step--1);
  line-height: 1.55;
}

/* One card per row on a phone, with the sketch on top. From 640px the sketch moves beside the name and facts; from 1080px
   the three cards share a row. The list rows spread into four columns from 960px */
@media (min-width: 640px) and (max-width: 1079px) {
  .cap {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    grid-template-areas:
      "sketch name"
      "sketch facts";
    grid-template-rows: auto 1fr;
    column-gap: clamp(24px, 3vw, 40px);
  }
  .cap .sketch {
    grid-area: sketch;
    align-self: center;
  }
  .cap__name { grid-area: name; }
  .cap__facts { grid-area: facts; }
}
@media (min-width: 1080px) {
  .caps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* The cards share the row's sketch, name and facts lines, so the facts start level where one name wraps to two lines */
  .cap {
    grid-row: span 3;
    grid-template-rows: subgrid;
  }
}
@media (min-width: 960px) {
  .job {
    grid-template-columns: 104px minmax(0, 0.9fr) minmax(0, 1.2fr) minmax(0, 1.5fr);
    align-items: start;
    column-gap: 28px;
  }
  /* The facts share the row's last two columns, with the row's own gap between them */
  .job .cap__facts {
    grid-column: 3 / -1;
    grid-template-columns: subgrid;
    column-gap: normal;
  }
}

/* The AI crew (crew.js, told what to build by swarm.js): amber cursors build the hero once per visit, out of the lamp in
   the logo, and the content of each part after it as it comes up the screen, each part with a move of its own (spec 14.9
   to 14.11). Each part stays whole
   without it; under .sw-on, data-sw hides a part only until the crew gets to it, by clip, opacity or a clear hairline,
   never by layout, so nothing on the page moves. Everything here is amber because it is AI at work, and all of it leaves
   with the crew. The marks (boxes, guides, sizes) take the band's tokens; the cursors carry the core's own light, so like
   .core they take the lamp and the night ink straight from the palette. Screen only: a printout shows the page as
   written. */
@media screen {
  [data-swarm] { position: relative; }
  .sw-on [data-sw="clip"] { clip-path: inset(0 100% 100% 0); }
  .sw-on [data-sw="hide"] { opacity: 0; }
  .sw-on [data-sw="top"] { border-top-color: transparent; }
  .sw-on [data-sw="bottom"] { border-bottom-color: transparent; }
  .sw-on [data-sw="fold"] { clip-path: inset(0 0 100% 0); }
  /* A question waiting to be clicked open shows its plus whole, like a closed one */
  .sw-on .qa > summary[data-sw-shut]::after { background-size: 100% 1.5px, 1.5px 100%; }
  .sw-on [data-sw-pop] > * {
    transform-box: fill-box;
    transform-origin: center;
    transition: opacity 0.18s, transform 0.4s var(--ease-expo);
  }
  .sw-on [data-sw-pop] > [data-sw="hide"] { transform: scale(0.4); }
  /* Typing: the rest of the text is laid out but clear, so the lines break where they will in the end */
  .sw-rest { color: transparent; }
  .sw-caret {
    border-left: 2px solid var(--signal);
    margin-right: -2px;
  }

  .sw-layer {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    contain: strict;
    transition: opacity 0.4s;
  }
  .sw-layer > * {
    position: absolute;
    top: 0;
    left: 0;
  }
  .sw-fade { opacity: 0 !important; transition: opacity 0.35s; }

  /* A cursor's hot spot is at 0,0 of its drawing, 12px into the box */
  .sw-agent {
    --sw-lamp: var(--lamp-400);
    --sw-edge: var(--river-950);
    width: 36px;
    height: 36px;
    margin: -12px 0 0 -12px;
    will-change: transform;
    transition: opacity 0.5s, filter 0.5s;
  }
  .sw-agent svg {
    display: block;
    overflow: visible;
    transform: scale(0);
    transform-origin: 12px 12px;
    transition: transform 0.3s var(--ease-expo);
  }
  .sw-agent.is-in svg { transform: none; }
  /* Waiting for the visitor to scroll: quieter */
  .sw-agent.is-idle { opacity: 0.55; }
  .sw-agent.is-in.is-down svg { transform: scale(0.86); }
  .sw-agent path { display: none; }
  .sw-agent[data-tool="arrow"] .sw-arrow,
  .sw-agent[data-tool="cross"] .sw-cross,
  .sw-agent[data-tool="beam"] .sw-beam,
  .sw-agent[data-tool="pen"] .sw-pen { display: inline; }
  .sw-arrow,
  .sw-pen {
    fill: var(--sw-lamp);
    stroke: var(--sw-edge);
    stroke-width: 1.3;
    stroke-linejoin: round;
  }
  .sw-nib { fill: none; }
  .sw-cross,
  .sw-beam {
    fill: none;
    stroke: var(--sw-lamp);
    stroke-width: 1.8;
    stroke-linecap: round;
  }
  .sw-cross.sw-under,
  .sw-beam.sw-under {
    stroke: var(--sw-edge);
    stroke-width: 4.2;
  }
  /* AI off: the crew greys where it stands, then goes */
  .sw-layer.is-off .sw-agent {
    filter: grayscale(1);
    opacity: 0.45;
  }

  .sw-frame { border: 1.5px solid var(--signal); }
  .sw-frame i,
  .sw-line i {
    position: absolute;
    width: 7px;
    height: 7px;
    background: var(--surface);
    border: 1.5px solid var(--signal);
  }
  .sw-frame i:nth-child(1) { left: -4px; top: -4px; }
  .sw-frame i:nth-child(2) { right: -4px; top: -4px; }
  .sw-frame i:nth-child(3) { left: -4px; bottom: -4px; }
  .sw-frame i:nth-child(4) { right: -4px; bottom: -4px; }
  .sw-size {
    position: absolute;
    left: 50%;
    top: calc(100% + 8px);
    transform: translateX(-50%);
    padding: 3px 6px;
    border-radius: 3px;
    background: var(--signal);
    color: var(--surface);
    font: 500 11px/1 var(--font-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }
  .sw-guide {
    background: var(--signal);
    opacity: 0.6;
  }
  .sw-guide--h { height: 1px; }
  .sw-guide--v { width: 1px; }
  .sw-line { background: var(--signal); }
  .sw-line i { top: -3px; }
  .sw-line i:nth-child(1) { left: -4px; }
  .sw-line i:nth-child(2) { right: -4px; }
  /* A row being copied down, and how far it has gone */
  .sw-ghost {
    margin: 0;
    background: var(--bg);
    box-shadow: 0 10px 28px -14px color-mix(in srgb, var(--fg) 45%, transparent);
  }
  .sw-measure {
    width: 0;
    border-left: 1px dashed var(--signal);
  }
  .sw-measure .sw-size {
    left: auto;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
  }
  /* A press: a ring that opens and goes */
  .sw-click::after {
    content: "";
    position: absolute;
    left: -13px;
    top: -13px;
    width: 26px;
    height: 26px;
    border: 1.5px solid var(--signal);
    border-radius: 50%;
    animation: sw-click 0.5s var(--ease-out) both;
  }
  /* A layout grid: a band for each cell, its size at its top while it drops */
  .sw-band {
    position: absolute;
    top: 0;
    left: 0;
    background: color-mix(in srgb, var(--signal) 8%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--signal) 40%, transparent);
  }
  .sw-band .sw-size { top: 8px; }
  /* A link from one part to the next: a dot where it leaves, a head where it arrives */
  .sw-link svg { display: block; overflow: visible; }
  .sw-link path {
    fill: none;
    stroke: var(--signal);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .sw-link circle { fill: var(--signal); }
  .sw-link .sw-head { opacity: 0; transition: opacity 0.15s; }
}
@keyframes sw-click {
  from { transform: scale(0.2); opacity: 1; }
  to { opacity: 0; }
}

/* Training: the head beside a meeting room set for a workshop, then three sample courses side by side, ruled like the principles */

.split-head {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  align-items: end;
  gap: clamp(32px, 6vw, 96px);
  margin-bottom: clamp(48px, 6vw, 88px);
}
.split-head .section-head { margin-bottom: 0; }
.split-head__photo { aspect-ratio: 3 / 2; }
@media (max-width: 760px) {
  .split-head { grid-template-columns: minmax(0, 1fr); }
  .split-head__photo { aspect-ratio: 3 / 2; }
}

.courses {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.course {
  display: grid;
  grid-template-rows: auto auto auto 1fr auto;
  gap: 18px;
  padding: clamp(28px, 3vw, 40px) clamp(20px, 2.4vw, 36px);
  border-left: 1px solid var(--line);
}
.course:first-child { padding-left: 0; border-left: 0; }
.course:last-child { padding-right: 0; }
.course__name {
  font-size: var(--step-3);
  font-stretch: 108%;
  letter-spacing: -0.01em;
}
.course__facts {
  display: grid;
  gap: 8px;
}
.course__facts div {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}
.course__facts dt,
.course__label { color: var(--fg-muted); }
.course__takeaway {
  display: grid;
  gap: 6px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  text-wrap: pretty;
}

.courses__note {
  max-width: 48rem;
  margin-top: clamp(28px, 3vw, 40px);
  color: var(--fg-muted);
  text-wrap: pretty;
}
.courses__note .link-arrow { margin-left: 8px; color: var(--fg); }

@media (max-width: 960px) {
  .courses { grid-template-columns: minmax(0, 1fr); }
  .course,
  .course:first-child,
  .course:last-child {
    padding-inline: 0;
    border-left: 0;
  }
  .course + .course { border-top: 1px solid var(--line); }
}

/* Questions: one short list, answers open. <details> needs no script and still prints. */

/* The questions are a reference, not a pitch: their band takes less room than the others */
.band--tight { padding-block: calc(var(--section) * 0.6); }

.faq {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  align-items: start;
  gap: clamp(40px, 6vw, 112px);
}
.faq__head {
  position: sticky;
  top: calc(var(--nav-h) + 32px);
  margin-bottom: 0;
}
.faq__head h2 { font-size: var(--step-3); }
.faq__head .section-head__lead { font-size: var(--step-0); }
.faq__head a { color: var(--fg); overflow-wrap: anywhere; }
.faq__list { border-top: 1px solid var(--fg); }

.qa { border-bottom: 1px solid var(--line); }
.qa summary {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding-block: 14px;
  font-family: var(--font-display);
  font-size: var(--step-0);
  font-stretch: 104%;
  font-weight: 600;
  line-height: 1.35;
}
/* The self-check folds the same way as an answer: no native marker, a hand cursor, a plus that loses its upright stroke when it opens */
:is(.qa, .check) > summary {
  list-style: none;
  cursor: pointer;
}
:is(.qa, .check) > summary::-webkit-details-marker { display: none; }
:is(.qa, .check) > summary::after {
  content: "";
  flex: none;
  width: 14px;
  height: 14px;
  margin-top: 0.3em;
  background:
    linear-gradient(currentColor, currentColor) center / 100% 1.5px no-repeat,
    linear-gradient(currentColor, currentColor) center / 1.5px 100% no-repeat;
  transition: background-size 0.25s var(--ease-out);
}
.check > summary::after { margin-top: 0.5em; } /* level with the first line of the larger heading */
.qa[open] summary::after,
.check[open] > summary::after { background-size: 100% 1.5px, 1.5px 0; }
:is(.qa, .check) > summary:hover { color: color-mix(in srgb, var(--fg) 75%, var(--bg)); }
.qa p {
  max-width: 42rem;
  padding-bottom: 16px;
  font-size: var(--step--1);
  color: var(--fg-muted);
  text-wrap: pretty;
}
.qa a { color: var(--fg); }

@media (max-width: 960px) {
  .faq { grid-template-columns: minmax(0, 1fr); }
  .faq__head { position: static; }
}

/* Call to action */

.cta {
  background:
    radial-gradient(50% 70% at 50% 100%, color-mix(in srgb, var(--line) 70%, transparent), transparent 70%),
    var(--bg);
}

.cta__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: start;
  gap: 28px;
}

/* The pitch: title, lead and email in one block, so that it can follow the reader past the estimate */
.cta__copy {
  display: grid;
  justify-items: start;
  gap: 28px;
}

.cta__title {
  font-size: var(--step-5);
  font-stretch: 108%;
  line-height: 1.04;
  letter-spacing: -0.025em;
  max-width: 14ch;
}

.cta__lead {
  max-width: 36rem;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin-top: 16px;
}

/* Wide screens, once home.js has shown the estimate: the pitch on the left, the estimate beside it */
@media (min-width: 961px) {
  .cta__inner:has(> .estimate:not([hidden])) {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    align-items: start;
    column-gap: clamp(48px, 6vw, 112px);
  }
  .estimate {
    grid-column: 2;
    grid-row: 1;
  }
  /* Small enough that "một quy trình." fits the narrower column on one line */
  .cta__inner:has(> .estimate:not([hidden])) .cta__title { font-size: min(5.6vw, 5rem); }
  .cta__inner:has(> .estimate:not([hidden])) .cta__actions { margin-top: 0; }
}
/* The estimate is much longer than the pitch, so the pitch stays beside the reader all the way down it. A window too short
   to hold it under the menu lets it scroll away instead, because a pitch taller than the window would hide its end. */
@media (min-width: 961px) and (min-height: 640px) {
  .cta__copy {
    position: sticky;
    top: calc(var(--nav-h) + 32px);
  }
}

.cta__reply {
  flex-basis: 100%;
  font-size: var(--step--1);
  color: var(--fg-muted);
}

.contact {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 16px;
  min-height: 48px;
  padding: 6px 6px 6px 18px;
  border-radius: 6px;
  box-shadow: inset 0 0 0 1px var(--line);
}
/* Without a script the copy button stays hidden, so the address gets the same room on the right as the label has on the left */
.contact:has(> .contact__copy[hidden]) { padding-right: 18px; }
.contact__label { color: var(--fg-muted); }
.contact__value {
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  user-select: all;
  overflow-wrap: anywhere;
}
.contact__copy {
  min-height: 36px;
  padding-inline: 14px;
  border: 0;
  border-radius: 4px;
  background: var(--surface);
  font-size: var(--step--1);
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}
.contact__copy:hover { background: var(--line); }

@media (max-width: 480px) {
  .cta__actions,
  .contact { width: 100%; }
  .contact {
    flex-wrap: nowrap;
    justify-content: space-between;
  }
  .contact__label { display: none; }
  .contact__value { min-width: 0; font-size: 0.875rem; }
  .contact__copy { flex: none; }
}

/* Hours estimate: the visitor's current hours left of the seam, the measurement after a pilot right of it */

.estimate__label { color: var(--fg-muted); }

.estimate__field {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 8px 16px;
}
.estimate__field .range { grid-column: 1 / -1; }
.estimate__value { font-weight: 600; }

.estimate__result {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: 48px;
  padding-top: 28px;
  border-top: 1px solid var(--line);
}
.estimate__result::before {
  content: "";
  position: absolute;
  top: 28px;
  bottom: 0;
  left: 50%;
  width: 1px;
  background: var(--signal);
  box-shadow: 0 0 14px color-mix(in srgb, var(--glow) 45%, transparent);
}
.estimate__result > div {
  display: grid;
  align-content: start;
  gap: 6px;
}
.estimate__total {
  font-family: var(--font-body); /* Mona Sans: Hubot Sans draws its zero with a bar, so 9.000.000 reads as 9.θθθ.θθθ */
  font-size: clamp(2rem, 1.4rem + 1.6vw, 3rem);
  font-stretch: 112%;
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
}
.estimate__sub {
  font-size: var(--step--1);
  color: var(--fg-muted);
}
.estimate__sub span { white-space: nowrap; } /* keeps "3.800.000 ₫" on one line */
.estimate__after .estimate__label,
.estimate__after .estimate__total { color: var(--signal); }

.estimate__send {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
}
.estimate__note {
  font-size: var(--step--1);
  color: var(--fg-muted);
}

@media (max-width: 480px) {
  .estimate__result { column-gap: 32px; }
}

/* About page (gioi-thieu/) */

/* At least the first screen, like the home hero: on a phone or tablet the photo grows to the bottom edge */
.page-hero {
  display: flex;
  flex-direction: column;
  min-height: 100svh;
  padding-top: calc(var(--nav-h) + env(safe-area-inset-top, 0px) + clamp(56px, 9vh, 120px));
  background: var(--bg);
}
.page-hero__copy {
  display: grid;
  gap: 24px;
}
.page-hero__title {
  max-width: 16ch;
  font-family: var(--font-display);
  font-size: var(--step-5);
  font-stretch: 108%;
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.025em;
  text-wrap: balance;
}
.page-hero__lead {
  max-width: 38rem;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--fg-muted);
  text-wrap: pretty;
}
/* A hero that is only its text keeps the top and ends with its text */
.page-hero--text {
  min-height: 0;
  padding-bottom: clamp(56px, 8vw, 104px);
}
@media (prefers-reduced-motion: no-preference) {
  .page-hero__copy > * {
    animation: rise-in 1s var(--ease-expo) both;
    animation-delay: calc(var(--intro, 0s) + var(--d, 0s));
  }
  .page-hero__copy > :nth-child(2) { --d: 0.08s; }
  .page-hero__copy > :nth-child(3) { --d: 0.2s; }
}

.why {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  align-items: start;
  gap: 20px clamp(40px, 6vw, 112px);
  margin-bottom: clamp(48px, 6vw, 88px);
}
.why h2 { grid-row: 1 / span 2; }
.why p {
  grid-column: 2;
  max-width: 38rem;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--fg-muted);
  text-wrap: pretty;
}
.why p:last-child { color: var(--fg); }
@media (max-width: 760px) {
  .why { grid-template-columns: minmax(0, 1fr); }
  .why h2 { grid-row: auto; }
  .why p { grid-column: 1; }
}

.scope {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(40px, 6vw, 112px);
}
.scope__title {
  padding-bottom: 14px;
  border-bottom: 1px solid var(--fg);
}
.scope__list li {
  display: grid;
  justify-items: start;
  gap: 10px;
  padding-block: 24px;
  border-bottom: 1px solid var(--line);
}
.scope__list h4 {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-stretch: 105%;
  font-weight: 600;
  line-height: 1.25;
}
.scope__list p {
  max-width: 34rem;
  color: var(--fg-muted);
  text-wrap: pretty;
}
.scope__list--no p {
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--fg);
}
@media (max-width: 760px) {
  .scope { grid-template-columns: minmax(0, 1fr); }
}

/* Portraits stay small because one original is only 181 px wide */
.founders {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(40px, 6vw, 112px);
}
.person {
  display: grid;
  grid-template-columns: 9.5rem minmax(0, 1fr);
  align-items: start;
  gap: 28px;
  padding-top: 28px;
  border-top: 1px solid var(--fg);
}
.person__photo {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 2px;
  background: var(--surface);
}
.person__copy {
  display: grid;
  justify-items: start;
  gap: 12px;
}
.person__name {
  font-size: var(--step-2);
  font-stretch: 105%;
}
.person__role { color: var(--fg-muted); }
.person__copy p:not(.person__role) {
  color: var(--fg-muted);
  text-wrap: pretty;
}
.person__copy .link-arrow { margin-top: 8px; }
@media (max-width: 960px) {
  .founders { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 480px) {
  .person { grid-template-columns: minmax(0, 1fr); }
  .person__photo { width: 9.5rem; }
}

.facts {
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  align-items: start;
  gap: clamp(40px, 6vw, 112px);
}
.facts__head { margin-bottom: 0; }
.facts__list { border-top: 1px solid var(--fg); }
.facts__list div {
  display: grid;
  grid-template-columns: 8rem minmax(0, 1fr);
  gap: 16px;
  padding-block: 18px;
  border-bottom: 1px solid var(--line);
}
.facts__list dt {
  padding-top: 0.3em;
  color: var(--fg-muted);
  text-wrap: balance; /* a two-line name does not leave one word alone on the second line */
}
.facts__list dd { text-wrap: pretty; }
.facts__list dd > p + p { margin-top: 0.75em; }
.facts__list a { overflow-wrap: anywhere; }
@media (max-width: 960px) {
  .facts { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 480px) {
  .facts__list div { grid-template-columns: minmax(0, 1fr); gap: 4px; }
}

/* Footer */

.footer {
  padding-block: 56px calc(40px + env(safe-area-inset-bottom, 0px));
  background: var(--bg);
  border-top: 1px solid var(--line);
  color: var(--fg-muted);
  font-size: var(--step--1);
}
.footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 40px;
}
.footer__brand {
  display: grid;
  gap: 20px;
  align-content: start;
}
.footer__legal {
  font-style: normal;
  line-height: 1.7;
}
.footer__legal a { text-decoration: none; }
.footer__legal a:hover { color: var(--fg); }
.footer__nav {
  display: grid;
  grid-template-columns: repeat(2, auto);
  column-gap: 40px;
  align-content: start;
}
/* Padding instead of a row gap: same rhythm, a taller target for a thumb */
.footer__nav a { padding-block: 5px; text-decoration: none; }
.footer__nav a:hover { color: var(--fg); }
.footer__copy {
  grid-column: 1 / -1;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--step--2);
}

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

/* Scenes: what the scroll does past the hero (spec 14.8). The hero parts like a gate (home.js, above); after it each part
   of the page has one scene of its own, and no two move the same way. The morning comes up over the night with a bowed
   edge, and the night sinks under it; the meeting room's blinds open; the process inks its line one station after
   another; the questions swing up on a hinge; the last heading is set from narrow type to its full width. The scroll
   plays each scene, forwards and backwards, and nothing runs by itself; each is over by the time its part reaches the
   middle of the window, so it is read standing still. No fade-and-rise. CSS scroll timelines and no script: a browser
   without them (Firefox in 2026), and a visitor who asks for less motion, get the page standing. Under each scene, the
   reason it costs little: transforms on the compositor, or paint on a small box. */

@property --slat { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --ink { syntax: "<percentage>"; inherits: false; initial-value: 0%; }

/* The lines of a heading the type scene sets: fixed, so the narrow type cannot pull a word up a line on the way */
.typeset > span { display: block; }

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Daybreak, from the night band to the first light one. The light comes up with a bowed edge, throwing a little of
       its light on the night. While the whole demo is in the window the bow stays at half height, under the demo's foot
       (85 px over the band's edge); once the demo starts to leave at the top, the bow swells while the night's content
       sinks under it and dims, then lies flat a little under the nav. Both run on the demo band's timeline, which main
       lends to the section after it; a browser without timeline-scope leaves the edge straight. Transforms and opacity
       only. */
    main { timeline-scope: --night; }
    .demo-band { view-timeline: --night block; }
    .demo-band > .wrap {
      animation: night-sinks linear both;
      animation-timeline: --night;
      animation-range: exit 0% exit 100%;
    }
    .dawn {
      position: relative;
      z-index: 2; /* over the band, which the hero's parting lifts to 1 */
    }
    .dawn::before,
    .dawn::after {
      content: "";
      position: absolute;
      left: 0;
      right: 0;
      bottom: calc(100% - 1px); /* a pixel into the section, so no seam of night shows between them */
      transform: scaleY(0);
      transform-origin: 50% 100%;
      pointer-events: none;
      animation: linear both;
      animation-timeline: --night;
      animation-range: exit 0% exit 75%;
    }
    .dawn::before {
      height: clamp(56px, 11vw, 168px);
      background: var(--bg);
      clip-path: ellipse(72% 100% at 50% 100%); /* wider than the window, so the edge is a long bow, not a dome */
      animation-name: dawn-bow;
    }
    .dawn::after {
      height: clamp(160px, 28vw, 400px);
      background: radial-gradient(60% 100% at 50% 100%, color-mix(in srgb, var(--bg) 22%, transparent), transparent 72%);
      animation-name: dawn-light;
    }

    /* Blinds over the meeting room: shaded slats, shut while the photo comes up from the bottom of the window, open by
       the time it is in the middle, while the room settles from close up. The slats are a gradient on a box the size of
       the photo, so only that box paints. */
    .blinds { view-timeline: --blinds block; }
    .blinds::after {
      content: "";
      position: absolute;
      inset: 0;
      background: repeating-linear-gradient(var(--line) 0, var(--surface) var(--slat), transparent var(--slat) 26px);
      pointer-events: none;
      animation: blinds-open linear both;
      animation-timeline: --blinds;
      animation-range: entry 10% cover 50%;
    }
    .blinds img {
      animation: room-settles linear both;
      animation-timeline: --blinds;
      animation-range: entry 0% cover 50%;
    }

    /* The process: the current that crosses the line (.steps::after) leaves it inked behind it, and each station fills
       as the current reaches it. --ink and the stations' ranges follow the current's keyframes (current, current-down):
       its middle is at -12.4% of the line at the start and 112.4% at the end, and the stations stand at fifths. A 1 px
       line and five dots paint. */
    .steps::before {
      background: linear-gradient(var(--fg), var(--fg)) no-repeat 0 0 / max(0%, var(--ink)) 100%, var(--line);
      animation: relay-ink linear both;
      animation-timeline: --steps;
      animation-range: cover 25% cover 75%;
    }
    .step::before {
      animation: relay-lit linear both;
      animation-timeline: --steps;
      animation-range: cover calc(29% + var(--n, 0) * 8.2%) cover calc(31% + var(--n, 0) * 8.2%);
    }
    .step:nth-child(2) { --n: 1; }
    .step:nth-child(3) { --n: 2; }
    .step:nth-child(4) { --n: 3; }
    .step:nth-child(5) { --n: 4; }
    @media (max-width: 960px) {
      .steps::before { background-size: 100% max(0%, var(--ink)), auto; }
    }

    /* The questions swing up on their top edge, each as it comes in, and stand flat from just past the middle. Transform
       only; the timeline reads each answer's place in the page, not its turned box, so the turn cannot feed back. */
    .unfold > * {
      transform-origin: 50% 0;
      animation: unfold linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }

    /* The last heading is set: narrow and light as it comes in, at its full width and weight by the middle of the
       window. One heading lays itself out again on each frame, nothing else moves. */
    .typeset {
      animation: typeset linear both;
      animation-timeline: view();
      animation-range: entry 25% cover 45%;
    }
  }
}
@keyframes night-sinks {
  to { transform: translateY(22vh) scale(0.97); opacity: 0.3; }
}
@keyframes dawn-bow {
  from { transform: scaleY(0.5); }
  40% { transform: scaleY(1); }
  to { transform: scaleY(0); }
}
@keyframes dawn-light {
  from { transform: none; opacity: 0.5; }
  40% { transform: none; opacity: 1; }
  to { transform: none; opacity: 0; }
}
@keyframes blinds-open {
  from { --slat: 26px; }
  to { --slat: 0px; }
}
@keyframes room-settles {
  from { transform: scale(1.16); }
}
@keyframes relay-ink {
  from { --ink: -12.4%; }
  to { --ink: 112.4%; }
}
@keyframes relay-lit {
  to { background: var(--fg); }
}
@keyframes unfold {
  from { transform: perspective(1200px) rotateX(-72deg); }
}
@keyframes typeset {
  from { font-stretch: 75%; font-weight: 400; letter-spacing: 0.02em; }
}

/* Moving between the home page and the About page: the nav stays, the old page fades and the new one rises. */

@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  .nav { view-transition-name: nav; }
  ::view-transition-old(root) { animation: 0.25s ease-in both page-out; }
  ::view-transition-new(root) { animation: 0.6s var(--ease-expo) both page-in; }
}
@keyframes page-out {
  to { opacity: 0; }
}
@keyframes page-in {
  from { opacity: 0; transform: translateY(24px); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .steps::after,
  .photo::before { animation: none; }
  .js .photo img { transition: none; }
}

/* The estimate remains local until the visitor copies and sends its text. */
.estimate__brief summary { cursor: pointer; padding-block: 12px; text-underline-offset: 5px; }
.estimate__brief textarea { width: 100%; margin-top: 12px; padding: 16px; border: 1px solid var(--line); border-radius: 6px; color: var(--fg); background: var(--bg); font: inherit; line-height: 1.6; resize: vertical; }
.estimate__send > [role="status"] { flex-basis: 100%; }

/* A short path into the guides, without another animated scene on the home page. */
.reading-strip { padding-block: 48px; border-top: 1px solid var(--line); }
.reading-strip__head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 20px; margin-bottom: 24px; }
.reading-strip__head h2 { font-size: var(--step-2); }
.reading-strip__links { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.reading-strip__links a { display: grid; grid-template-columns: 1fr auto; gap: 12px 24px; padding-block: 24px; border-top: 1px solid var(--line); text-decoration: none; }
.reading-strip__links a > span:first-child { grid-column: 1 / -1; color: var(--fg-muted); font-size: var(--step--1); }
.reading-strip__links strong { font-size: var(--step-1); font-weight: 550; }
.reading-strip__links a:hover strong { text-decoration: underline; text-underline-offset: 5px; }
@media (max-width: 640px) { .reading-strip__links { grid-template-columns: minmax(0, 1fr); gap: 0; } }
.nav__mobile-contact { display: none; }
@media (max-width: 960px) { .nav__mobile-contact { display: list-item; } }
