/* ÆON v3 — monochrome shell over a coloured field.
 * The canvas carries every colour on this page. The interface stays bone on
 * near-black so the landscape is the only thing that ever changes hue. */

@import url('https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500&family=Geist+Mono:wght@300;400&display=swap');

:root {
  color-scheme: dark;
  /* 0 at night, 1 at noon. scroll.js writes this from the field's measured sky
   * luminance every frame. It defaults to 0, so a page with no JS — or one whose
   * module failed — gets the night values, which is the safe direction. */
  --day: 0;

  --ink:  #05060a;
  --bone: #e9e7e2;
  /* Copy opens up as the sun comes up. A 0.70-alpha grey reads fine on a
   * near-black night sky and drops to 3.6:1 on a noon sky — measured, not
   * assumed. Rather than darken the daylight back out of existence to protect a
   * translucent grey, spend the alpha: at noon these are effectively solid bone.
   * The tone difference between body and headline still survives at night, which
   * is the only place it was ever doing typographic work. */
  --dim:  rgba(233, 231, 226, calc(0.70 + var(--day) * 0.30));
  --faint:rgba(233, 231, 226, calc(0.64 + var(--day) * 0.36));
  --hair: rgba(233, 231, 226, calc(0.14 + var(--day) * 0.20));
  --sans: "Geist", -apple-system, "Helvetica Neue", Arial, sans-serif;
  --mono: "Geist Mono", "SF Mono", Menlo, Consolas, monospace;
  --gut: clamp(22px, 5vw, 84px);
}

* { box-sizing: border-box; }

/* Text carries its own bed.
 *
 * Measured: copy passes through 0-91% of the frame vertically and out to 86%
 * horizontally as you scroll, so there is NO band of the viewport that copy
 * reliably stays out of — which means no positional scrim can protect it
 * without darkening the entire picture. Four rounds of scrim geometry proved
 * that the hard way: every version that made noon legible also made noon look
 * like dusk.
 *
 * A halo on the glyphs themselves darkens only the pixels touching a letterform.
 * It costs the picture nothing and it moves with the words. Scaled by --day so
 * it is effectively absent at night, where the type is already crisp on black
 * and a shadow would only soften it. */
main, .masthead, footer, .cue {
  text-shadow:
    /* zero-blur underlay: paints the glyph's own footprint dark, so
     * semi-transparent copy (--dim at .85) stops letting a white photo
     * cloud bleed through its strokes. Invisible under opaque text and
     * on a night sky; blur 0 costs the repaint nothing. */
    0 0 0 rgba(5, 6, 10, 0.82),
    /* tight ring next: this is the layer that actually sets the ratio at the
     * letterform edge, where a reader's eye resolves the stroke */
    0 0 3px  rgba(5, 6, 10, calc(0.12 + var(--day, 0) * 0.74)),
    0 1px 2px rgba(5, 6, 10, calc(0.10 + var(--day, 0) * 0.68)),
    0 0 12px rgba(5, 6, 10, calc(0.14 + var(--day, 0) * 0.74));
}

html { background: var(--ink); -webkit-font-smoothing: antialiased; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--sans);
  font-weight: 300;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }
a:focus-visible { outline: 1px solid var(--bone); outline-offset: 6px; }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--bone); color: var(--ink);
  padding: 12px 18px; font-family: var(--mono); font-size: 12px; z-index: 100;
}
.skip:focus { left: 12px; top: 12px; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- the field ---------------------------------------------------- */

#field {
  position: fixed; inset: 0;
  width: 100vw; height: 100vh;
  display: block; z-index: 0;
  pointer-events: none;
}

/* Directional scrim. Copy is left-aligned throughout, so the bed is weighted
 * left and the landscape stays open on the right. Without it the lede sits on
 * the busiest part of the terrain and stops being readable. */
.scrim {
  position: fixed; inset: 0; z-index: 5; pointer-events: none;
  /* Recedes as the day pool takes over. The two beds were stacking at full
   * strength through the middle of the page — 0.90 night scrim UNDER a 0.92 day
   * pool composites to 0.99, which is why noon rendered as a black void with a
   * strip of sky. One bed at a time; they crossfade. */
  opacity: calc(1 - var(--day, 0) * 0.42);
  background:
    linear-gradient(100deg,
      rgba(5,6,10,0.90) 0%,
      rgba(5,6,10,0.78) 30%,
      rgba(5,6,10,0.38) 54%,
      rgba(5,6,10,0.00) 72%),
    /* This used to fall to zero straight through the middle of the frame, back
     * when the horizon sat at 57% and the copy band was over dark near-ground.
     * Raising the horizon to ~22% put lit far terrain behind the copy instead,
     * and measurement showed the worst samples needing +0.23 of overlay. So the
     * band now carries weight where the words are, at every time of day, and
     * still opens up at the top (sky, clouds) and at the very bottom. */
    linear-gradient(to bottom,
      rgba(5,6,10,0.52) 0%,
      rgba(5,6,10,0.22) 10%,
      rgba(5,6,10,0.26) 30%,
      rgba(5,6,10,0.30) 52%,
      rgba(5,6,10,0.26) 74%,
      rgba(5,6,10,0.22) 90%,
      rgba(5,6,10,0.52) 100%);
}

/* Daylight bed. A separate layer rather than more alpha on .scrim, because the
 * night scrim is a full-height wash and scaling THAT with the sun darkens the
 * sky and the ground along with the copy — which is exactly how the first
 * daylight pass turned noon into mud. This one is an ellipse sized to the copy
 * column: hard over the words, nothing over the open right of the frame, so the
 * sky stays as blue as the canvas painted it.
 *
 * Driven by plain `opacity` on an element, not calc() inside rgba(), so it
 * degrades to invisible if --day is never written (no JS, failed module). */
.scrim--day {
  position: fixed; inset: 0; z-index: 6; pointer-events: none;
  opacity: var(--day, 0);
  background:
    /* Extra weight under the headline column, where the type is largest. */
    radial-gradient(54% 60% at 26% 50%,
      rgba(5,6,10,0.74) 0%,
      rgba(5,6,10,0.58) 44%,
      rgba(5,6,10,0.22) 72%,
      rgba(5,6,10,0.00) 90%),
    /* A full-width band, not an ellipse. Two chapters run copy out to 91% of the
     * frame (.row's third column, .split's .defs column) and no ellipse reaches
     * that without swallowing the whole picture. A band covers every x at the
     * copy's height and leaves the top of the frame — where the sky and the
     * clouds are — completely untouched. */
    linear-gradient(to bottom,
      rgba(5,6,10,0.30) 0%,
      rgba(5,6,10,0.00) 13%,
      rgba(5,6,10,0.34) 30%,
      rgba(5,6,10,0.42) 52%,
      rgba(5,6,10,0.36) 76%,
      rgba(5,6,10,0.06) 93%,
      rgba(5,6,10,0.00) 100%);
}

@media (max-width: 860px) {
  .scrim {
    background:
      linear-gradient(to bottom,
        rgba(5,6,10,0.80) 0%,
        rgba(5,6,10,0.55) 30%,
        rgba(5,6,10,0.55) 70%,
        rgba(5,6,10,0.86) 100%);
  }
  /* Copy spans the full width on phones, so the pool has to as well — but not
   * the full HEIGHT. The first cut was a flat 0.86 wash over the whole frame,
   * and at noon the phone rendered as night: the entire diurnal arc, the point
   * of the page, was invisible on the device most visitors use. The top fifth
   * is the one strip mobile copy only passes through in transit (the fixed
   * masthead lives there, but it carries the dense small-type halo), so the sky
   * and the cloud deck stay open there and the bed does its work where
   * paragraphs actually sit. */
  .scrim--day {
    background:
      linear-gradient(to bottom,
        rgba(5,6,10,0.30) 0%,
        rgba(5,6,10,0.06) 9%,
        rgba(5,6,10,0.10) 20%,
        rgba(5,6,10,0.74) 34%,
        rgba(5,6,10,0.80) 72%,
        rgba(5,6,10,0.84) 100%);
  }
}

/* Small type needs a denser ring than a 62px headline does: the same halo that
 * carries a headline stroke is spread too thin around a 10px letter. Measurement
 * put the remaining gap at +0.14 of overlay, and all seven samples that needed it
 * were 10-14px. A near-opaque 2px ring on small text reads as the type sitting on
 * a soft plate; on a headline it would look like a drop shadow, which is why this
 * is scoped rather than global. */
.label, .hud, .defs .k, .row .idx, .row .out, .row p, .more, .quote figcaption,
footer, .wordmark span, .cue {
  /* Base alphas raised for the photo pivot: a photograph's brightness is
   * LOCAL (the dusk scene is near-night by --day yet carries a blazing sun
   * path), so the ring can't lean on the daylight scale alone. At night the
   * background is near-black and the stronger ring costs nothing visible. */
  text-shadow:
    0 0 0 rgba(5, 6, 10, 0.85),
    0 0 2px rgba(5, 6, 10, calc(0.55 + var(--day, 0) * 0.45)),
    0 0 5px rgba(5, 6, 10, calc(0.45 + var(--day, 0) * 0.50)),
    0 0 14px rgba(5, 6, 10, calc(0.32 + var(--day, 0) * 0.55));
}

/* ---- chrome -------------------------------------------------------- */

.masthead {
  position: fixed; top: 0; left: 0; right: 0; z-index: 20;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 20px;
  padding: 22px var(--gut);
}

.wordmark {
  font-family: var(--mono);
  font-size: 13px; letter-spacing: 0.42em; text-transform: uppercase;
}
.wordmark span { color: var(--faint); letter-spacing: 0.2em; margin-left: 14px; }

/* Ambient toggle. Revealed by JS (no JS, no audio, no dead button). Dim when
 * off; bone with a live underline when running. */
.soundtoggle {
  background: none; border: 0; padding: 6px 2px; cursor: pointer;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.30em; text-transform: uppercase;
  color: var(--faint);
  border-bottom: 1px solid transparent;
  transition: color 0.4s ease, border-color 0.4s ease;
}
.soundtoggle:hover { color: var(--bone); }
.soundtoggle:focus-visible { outline: 1px solid var(--bone); outline-offset: 5px; }
.soundtoggle[aria-pressed="true"] { color: var(--bone); border-bottom-color: var(--bone); }


.rail {
  position: fixed; right: var(--gut); top: 50%; transform: translateY(-50%);
  z-index: 20; display: flex; flex-direction: column; gap: 16px;
}
.rail a { display: block; width: 26px; height: 10px; position: relative; }
/* difference-blend collapses to invisible whenever the backdrop is ~half the
 * foreground per channel, which is a common mid-tone terrain value — so the
 * focus ring cannot rely on it. Solid halo, blend isolated. */
.rail a:focus-visible {
  outline: 2px solid #e9e7e2;
  outline-offset: 4px;
  box-shadow: 0 0 0 6px #05060a;
}
.rail a::after {
  content: ''; position: absolute; right: 0; top: 4px;
  width: 18px; height: 1px; background: var(--faint);
  transition: width 0.5s cubic-bezier(.16,1,.3,1), background 0.5s ease;
}
.rail a[aria-current]::after { width: 26px; background: var(--bone); }

/* ---- chapters ------------------------------------------------------ */

main { position: relative; z-index: 10; }

.chapter {
  /* Whitespace audit 2026-08-17: only the hero keeps a full-viewport claim.
   * Every other chapter sizes to its content — a one-sentence section no
   * longer costs a whole empty screen. Generous padding carries the rhythm.
   * vh alone is not enough clearance on short viewports (1280x600, landscape
   * phones): 17vh = 102px there, and the fixed masthead sits at 22px + its own
   * line-height, so the chapter label collides with the wordmark. Floor it. */
  padding: max(13vh, 132px) var(--gut) max(11vh, 88px);
  display: flex; align-items: center;
}
.chapter--hero {
  min-height: 100vh;
  min-height: 100dvh;
  padding-top: max(17vh, 132px);
  padding-bottom: max(14vh, 88px);
}
.chapter > .inner { width: 100%; max-width: 1180px; }

.label {
  font-family: var(--mono);
  font-size: 10px; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 26px;
}
/* The live altimeter line under each chapter label. Dimmer than the label
 * itself: instrument, not headline. */
.hud {
  display: block;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--faint); opacity: 0.58;
  margin: -20px 0 26px;
}

h1, h2, h3 { font-weight: 300; margin: 0; letter-spacing: -0.025em; }

h1 {
  font-size: clamp(30px, 4.6vw, 64px);
  line-height: 1.04;
  max-width: 24ch;
  text-wrap: balance;
}
/* Rotator reintroduced (demo hero, 2026-08-08): still no reserved min-height —
 * the words share the h1's own max-width and reflow, same as the prototype.
 * The old 3.05em reservation was what pushed the tile below the fold the
 * first time this existed. Timing matches the JS swap delay (480ms) exactly,
 * ported from _proto/demo-hero.html. */
#heroword { display: inline-block; transition: opacity 0.48s ease, transform 0.48s ease; }
#heroword.swap { opacity: 0; transform: translateY(10px); }
h2 {
  font-size: clamp(30px, 4.4vw, 62px);
  line-height: 1.04;
  max-width: 17ch;
}
h3 { font-size: clamp(18px, 1.7vw, 23px); line-height: 1.2; }

.lede {
  font-size: clamp(16px, 1.5vw, 20px);
  line-height: 1.62;
  color: var(--dim);
  max-width: 39ch;
  margin: 32px 0 0;
  font-weight: 300;
}

.cta {
  display: inline-block; margin-top: 44px;
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.2em; text-transform: uppercase;
  padding-bottom: 7px; border-bottom: 1px solid var(--hair);
  transition: border-color 0.4s ease, letter-spacing 0.4s ease;
}
.cta:hover { border-color: var(--bone); letter-spacing: 0.26em; }

.cue {
  position: absolute; bottom: 7vh; left: var(--gut);
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--faint);
}
.chapter--hero { position: relative; }

/* SCN-02 demo: the support phone, mirrored — phone left, copy right, so the
 * page changes shape instead of repeating the hero's arrangement. */
.chapter--demo .inner {
  display: grid;
  grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: center;
}
.chapter--demo .demo-phone { display: flex; justify-content: center; }

@media (max-width: 860px) {
  /* Mobile order matches the hero: heading, phone, then the lede tile. */
  .chapter--demo .inner { display: flex; flex-direction: column; gap: 30px; }
  .chapter--demo .demo-copy { display: contents; }
  .chapter--demo .demo-copy .label { order: 0; }
  .chapter--demo .demo-copy h2 { order: 1; }
  .chapter--demo .demo-phone { order: 2; }
  .chapter--demo .demo-copy .tile { order: 3; }
  .chapter--demo .phone { min-height: 360px; width: min(300px, 82vw); }
}

/* Demo hero: two columns, copy left / phone right. Ported layout from
 * _proto/demo-hero.html — see the glass-recipe note near .phone below for
 * why the phone's background differs from the prototype. */
.chapter--hero .inner {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(32px, 6vw, 96px);
  align-items: center;
}
.hero-phone { display: flex; justify-content: center; }

@media (max-width: 860px) {
  /* Mobile order: headline, then the demo phone above the fold, then the
   * lede tile. display:contents lifts hero-copy's children into the flex
   * flow so the phone can slot between h1 and tile (Sensei, 2026-08-08). */
  .chapter--hero .inner { display: flex; flex-direction: column; gap: 30px; }
  .chapter--hero .hero-copy { display: contents; }
  .chapter--hero .hero-copy .label { order: 0; }
  .chapter--hero .hero-copy h1 { order: 1; }
  .chapter--hero .hero-phone { order: 2; }
  .chapter--hero .hero-copy .tile { order: 3; }
  .chapter--hero .phone { min-height: 360px; width: min(300px, 82vw); }
}

/* ---- lists (hairlines, not cards) ---------------------------------- */

.rows { margin-top: 64px; border-top: 1px solid var(--hair); }

.row {
  display: grid;
  grid-template-columns: 9.5rem minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(14px, 3vw, 48px);
  align-items: start;
  padding: 30px 0;
  border-bottom: 1px solid var(--hair);
}
.row .idx {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint);
  padding-top: 5px;
}
.row p { margin: 8px 0 0; color: var(--dim); font-size: 15px; line-height: 1.6; }
.row .out { color: var(--dim); font-size: 13.5px; line-height: 1.55; margin-top: 0; }
.row .more {
  display: inline-block; margin-top: 14px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--faint);
  border-bottom: 1px solid transparent; transition: color .35s, border-color .35s;
}
.row a:hover .more { color: var(--bone); border-color: var(--hair); }

/* SCN-03 is a SEQUENCE, not a third table. Using .rows for work, approach and
 * services in a row meant three consecutive chapters were one template wearing
 * different headlines — the terrain varied, the type never did. Steps get big
 * numerals, air, and no grid, so the page changes shape as you move through it. */
.steps {
  list-style: none;
  margin: 44px 0 0;
  padding: 0;
  max-width: 60ch;
}
.steps li {
  position: relative;
  padding-left: clamp(58px, 8vw, 104px);
  padding-bottom: clamp(38px, 5vw, 62px);
}
.steps li:last-child { padding-bottom: 0; }
.steps .n {
  position: absolute; left: 0; top: -0.14em;
  font-family: var(--mono); font-weight: 300;
  font-size: clamp(30px, 4vw, 52px);
  line-height: 1;
  color: var(--hair);
  letter-spacing: -0.03em;
}
.steps h3 { margin-bottom: 12px; }
.steps p {
  margin: 0; color: var(--dim);
  font-size: 15.5px; line-height: 1.62; max-width: 52ch;
}

/* SCN-03 proof band: a document peek under the three steps, not a fourth
 * card. .opsmap-doc joins the shared glass recipe below (background/border/
 * radius come from there); this block only lays out its shape and contents. */
.opsmap {
  display: flex;
  align-items: center;
  gap: clamp(20px, 3vw, 32px);
  margin-top: clamp(20px, 2.4vw, 30px);
  max-width: 60ch;
}
.opsmap-doc {
  flex: none;
  width: clamp(96px, 10vw, 140px);
  aspect-ratio: 3 / 4;
  padding: 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.opsmap-label {
  font-family: var(--mono); font-size: 8px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint);
  margin-bottom: 4px;
}
.opsmap-bar { display: block; height: 5px; border-radius: 2px; background: var(--hair); }
.opsmap-caption {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em;
  color: var(--faint); margin: 0;
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
}
.opsmap-tag { font-size: 8.5px; letter-spacing: 0.18em; text-transform: uppercase; opacity: 0.6; }

@media (max-width: 860px) {
  .opsmap { flex-direction: column; align-items: flex-start; gap: 14px; }
  .opsmap-doc { width: 96px; }
}

/* SCN-04 splits: heading and CTA hold the left, the list runs down the right.
 * Third distinct composition, so no two adjacent chapters share a shape. */
.split { display: grid; grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: clamp(28px, 6vw, 92px); align-items: start; }
.split > .side { position: sticky; top: 22vh; }
/* The sticky side gained the guarantee + Monday note (2026-08-08); the display
 * h2 size made the stack exceed short viewports. Scoped down, not global. */
.split .side h2 { font-size: clamp(26px, 3.4vw, 46px); max-width: 20ch; }
.defs { margin: 0; }
.defs > div { padding: 0 0 clamp(30px, 3.6vw, 44px); }
.defs > div:last-child { padding-bottom: 0; }
.defs .k {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--faint);
  margin-bottom: 10px;
}
.defs h3 { margin-bottom: 10px; }
.defs p { margin: 0; color: var(--dim); font-size: 15.5px; line-height: 1.62; }

/* SCN-04 ledger mock, sticky column. Tightened lede margin above it to keep
 * the sticky .side from outgrowing short viewports — .mondaynote joins the
 * shared glass recipe below for its background/border/radius. */
.split .side .lede { margin-top: 20px; }
.mondaynote {
  margin-top: clamp(18px, 2.2vw, 26px);
  padding: 16px clamp(16px, 2vw, 20px);
}
.mondaynote-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin: 0 0 12px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--faint);
  border-bottom: 1px solid var(--hair);
  padding-bottom: 10px;
}
.mondaynote-tag { font-size: 8.5px; letter-spacing: 0.18em; opacity: 0.6; }
.mondaynote-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.mondaynote-list li {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: 13px; color: var(--dim);
}
.mondaynote-val { font-family: var(--mono); color: var(--bone); }
.mondaynote-redaction { display: inline-block; width: 52px; height: 8px; border-radius: 2px; background: var(--hair); }

@media (max-width: 860px) {
  .split { grid-template-columns: 1fr; gap: 40px; }
  .split > .side { position: static; }
  .steps li { padding-left: 52px; }
}

/* One quote, no card. A hairline and room around it does the work. */
.quote { margin: 56px 0 0; max-width: 62ch; }
.quote blockquote {
  margin: 0;
  font-size: clamp(19px, 2.1vw, 28px);
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--bone);
  text-wrap: pretty;
}
.quote figcaption {
  margin-top: 16px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--faint);
}

.mail {
  display: inline-block; margin-top: 34px;
  font-size: clamp(26px, 4.6vw, 58px);
  letter-spacing: -0.025em;
  border-bottom: 1px solid var(--hair); padding-bottom: 10px;
  transition: border-color 0.4s ease;
}
.mail:hover { border-color: var(--bone); }

/* ---- entrance ------------------------------------------------------ */

/* Gated on .js (set by an inline script in <head>). No JS means no hiding, so a
 * failed module leaves a readable page rather than invisible copy. */
.js .rise {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 1.05s cubic-bezier(.16,1,.3,1),
              transform 1.05s cubic-bezier(.16,1,.3,1);
}
.js .rise.in { opacity: 1; transform: none; }
.rise:nth-child(2) { transition-delay: 0.07s; }
.rise:nth-child(3) { transition-delay: 0.14s; }
.rise:nth-child(4) { transition-delay: 0.21s; }

footer {
  position: relative; z-index: 10;
  display: flex; flex-wrap: wrap; gap: 14px 30px;
  justify-content: space-between;
  padding: 40px var(--gut) 44px;
  border-top: 1px solid var(--hair);
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint);
}

/* ---- glass tiles (the bento pass) ----------------------------------
 * Round 3 put one plate under each whole chapter and Sensei called it:
 * slabs, and the hero spilled past the fold. The glass now attaches to
 * CONTENT UNITS — a work row, a step, a service, the quote — and the
 * headlines float back on the picture, where the halo system already
 * holds them. Same material as round 3: day-scaled hairline, bone-to-ink
 * tint, one top light catch. Frost stays 3px — at 13px the blur
 * RECONSTRUCTS the photo from the glyphs and un-ASCIIs the field. */
:root { --tilepad: clamp(22px, 2.4vw, 30px); }

.tile, .row, .quote, .steps li, .defs > div, .split > .side,
.phone, .opsmap-doc, .mondaynote {
  /* Smoked, not clear: a tile can land on a white cloud or blown canyon
   * rock, and 0.3 ink read as bare glass there (step 03 was illegible).
   * The ink floor is now ~0.55 — dark tiles just get deeper, bright ones
   * finally read. */
  background: linear-gradient(
    165deg,
    rgba(233, 231, 226, 0.030),
    rgba(5, 6, 10, 0.52) 32%,
    rgba(5, 6, 10, 0.62)
  );
  -webkit-backdrop-filter: blur(3px) brightness(0.78);
  backdrop-filter: blur(3px) brightness(0.78);
  border: 1px solid var(--hair);
  border-radius: 14px;
  box-shadow:
    inset 0 1px 0 rgba(233, 231, 226, 0.10),
    0 14px 40px rgba(0, 0, 0, 0.26);
}
@supports not (backdrop-filter: blur(1px)) {
  .tile, .row, .quote, .steps li, .defs > div, .split > .side,
  .phone, .opsmap-doc, .mondaynote {
    background: rgba(5, 6, 10, 0.62);
  }
}

/* hero + contact: the card holds only the reading copy; h1/h2 float */
.tile { padding: var(--tilepad); width: fit-content; }
.tile--hero { margin-top: 36px; }
h2 + .tile { margin-top: 36px; } /* SCN-02 + SCN-05: tile breathes below its heading */
.tile--hero .lede { margin-top: 0; max-width: 48ch; }
.tile--hero .cta { margin-top: 24px; }
.tile .lede { margin-top: 0; }

/* Hero phone: structural shape only (background/border/shadow come from the
 * shared glass recipe above via .phone's membership there). Ported from
 * _proto/demo-hero.html, but the prototype's own 10px decorative blur is
 * dropped — over the busy terrain canvas the site's tuned 3px frost is what
 * keeps the picture from reconstructing through the glass. */
.phone {
  position: relative;
  width: min(348px, 86vw);
  min-height: 430px;
  border-radius: 40px;
  padding: 22px 20px 18px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.phone-header {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 14px; margin-bottom: 16px;
  border-bottom: 1px solid var(--hair);
}
.phone-header .name {
  font-family: var(--mono); font-size: 11px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--bone);
}
.phone-header .tag {
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint);
  white-space: nowrap;
}
.phone-messages {
  flex: 1; min-height: 230px;
  /* Top-anchored: the thread fills from under the header as it plays, so the
   * frame never shows a hollow above the first bubble (Sensei, 2026-08-08). */
  display: flex; flex-direction: column; justify-content: flex-start; gap: 10px;
  transition: opacity 0.42s ease;
}
.phone-messages.fadeout { opacity: 0; }

.bubble {
  max-width: 84%; padding: 10px 14px; border-radius: 16px;
  font-size: 13.5px; line-height: 1.48;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.45s cubic-bezier(.16,1,.3,1), transform 0.45s cubic-bezier(.16,1,.3,1);
}
.bubble.in { opacity: 1; transform: none; }
.bubble--aeon {
  align-self: flex-start; color: var(--bone); border-bottom-left-radius: 4px;
  background: linear-gradient(165deg, rgba(233,231,226,0.035), rgba(5,6,10,0.55) 34%, rgba(5,6,10,0.68));
  border: 1px solid var(--hair);
}
.bubble--owner {
  align-self: flex-end; color: var(--ink); border-bottom-right-radius: 4px;
  background: rgba(233, 231, 226, 0.92);
  border: 1px solid rgba(233, 231, 226, 0.92);
}
.typing {
  align-self: flex-start; display: inline-flex; align-items: center; gap: 4px;
  padding: 13px 14px; border-radius: 16px; border-bottom-left-radius: 4px;
  background: linear-gradient(165deg, rgba(233,231,226,0.035), rgba(5,6,10,0.55) 34%, rgba(5,6,10,0.68));
  border: 1px solid var(--hair);
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.4s ease, transform 0.4s ease;
}
.typing.in { opacity: 1; transform: none; }
.typing span {
  width: 5px; height: 5px; border-radius: 50%; background: var(--faint);
  animation: typingPulse 1.1s ease-in-out infinite;
}
.typing span:nth-child(2) { animation-delay: 0.15s; }
.typing span:nth-child(3) { animation-delay: 0.30s; }
@keyframes typingPulse {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-2px); }
}
.phone-caption {
  position: absolute; right: 18px; bottom: 14px;
  font-family: var(--mono); font-size: 8.5px;
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--faint);
  opacity: 0.6; pointer-events: none;
}

/* work: the list becomes stacked tiles; hairlines retire, gaps carry rhythm */
.rows { border-top: 0; margin-top: 44px; display: flex; flex-direction: column; gap: 14px; }
.row { border-bottom: 0; padding: 24px var(--tilepad); }

/* approach: three steps become a bento row; the numeral moves inline */
.steps { max-width: none; margin-top: 44px; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; }
.steps li { padding: var(--tilepad); }
.steps .n { position: static; display: block; margin-bottom: 14px; }

/* build: the sticky side and each service are their own tiles */
.split > .side { padding: var(--tilepad); }
.defs { display: flex; flex-direction: column; gap: 14px; }
.defs > div { padding: var(--tilepad); }
.defs > div:last-child { padding-bottom: var(--tilepad); }

.quote { padding: var(--tilepad); }

/* ---- responsive ---------------------------------------------------- */

@media (max-width: 860px) {
  .rail { display: none; }
  .row { grid-template-columns: 1fr; gap: 6px; padding: 22px var(--tilepad); }
  .row .idx { padding-top: 0; }
  .chapter { padding: max(16vh, 116px) var(--gut) max(12vh, 72px); }
  h1 { max-width: 100%; }
  .steps { grid-template-columns: 1fr; }
  .tile { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  /* must out-specify `.js .rise` (0,2,0) or reduced-motion users get hidden copy */
  .js .rise, .rise { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  .cta, .mail, .rail a::after { transition: none; }
  #heroword, .bubble, .typing, .phone-messages { transition: none !important; }
  .typing span { animation: none !important; }
}
