/* ============================================================================
   desert-v2: the STRUCTURE layer of the scroll-craft redesign.
   Grammar: chaptered editorial. Brief: scrollcraft/builds/coreshift-desert/BRIEF.md

   desert-v1.css is the design floor (ground, type scale, plate, spread, folio
   tokens) and is owned by someone else; nothing here edits it. This file styles
   only what source/desert-v1.py adds: the masthead that replaces the pill, the
   folio in the margin, the title page, the chapter grounds, the lateral
   statement spread, the parallax strata and the colophon.

   Loaded LAST in document order (linked at the head of NAV, since build.py's
   <head> list is not ours to extend), so at equal specificity it wins.

   THE CONTRAST RULE THIS FILE EXISTS TO HONOUR
   -------------------------------------------------------------------------
   Six grounds, five of them not the body's. `color` is inherited as a COMPUTED
   value, so redefining --sc-ink on a chapter does NOT re-ink text that already
   resolved on <body>: every ground block below therefore restates `color`, and
   the accent is a TWO-STOP brass (one hue, three lightnesses keyed to the
   ground) because no single stop clears 4.5:1 on bone paper AND on espresso.
   Measured, not eyeballed:
     ink  #1A1714 on paper 14.7:1 · on plate 13.1:1 · on terrain 8.7:1
     bone #EFE8DB on dusk  12.7:1 · on close 14.7:1
     muted           paper 6.0:1 · plate 5.4:1 · terrain 4.8:1 · dark 7.8-9.0:1
     brass #8A5116   paper 5.3:1 · plate 4.7:1
     brass #6B3C0C   terrain 4.5:1
     brass #D99A4E   dusk 6.4:1  · close 7.4:1
   ============================================================================ */

/* --- 0. tokens per ground ------------------------------------------------- */
/* The light set is declared on <body> as well, not only on chapters: 36 of the
   42 routes have no chapters, and three values reaching them from the espresso
   theme measured BELOW 4.5:1 on paper (--faint 3.45, --text-dim 3.45, the
   accent used as running label text 3.61). Found by measuring the render on
   /contact, which has no chapter wrapper to catch them. */
body,
.chapter--paper, .chapter--plate, .title-page,
.chapter--paper .chapter__body, .chapter--plate .chapter__body {
  --text: #1A1714; --muted: #5E554B; --faint: #655C51;
  --line: rgba(26, 23, 20, .16);
  --surface: #E7DECE; --surface-2: #E0D4C0;
  --accent: #8A5116;
  --sc-ink: #1A1714; --sc-ink-soft: #6E6459; --sc-accent: #8A5116;
  --lede-ink: #4A4239; --cap-ink: #655C51;
  --text-dim: #655C51; --line-control: rgba(26, 23, 20, .34);
  --panel: #F4EEE3;
}
.chapter--terrain {
  --text: #1A1714; --muted: #4A4239; --faint: #3E382F;
  --line: rgba(26, 23, 20, .26);
  --surface: #D8C4A6; --surface-2: #D0B99A;
  --accent: #6B3C0C;
  --sc-ink: #1A1714; --sc-ink-soft: #3E382F; --sc-accent: #6B3C0C;
  --lede-ink: #332E27; --cap-ink: #3E382F;
  --text-dim: #3E382F; --line-control: rgba(26, 23, 20, .4);
  --panel: #F1EADE;
}
.chapter--dusk, .chapter--close, .colophon {
  --text: #EFE8DB; --muted: #C4B6A4; --faint: #B3A491;
  --line: rgba(239, 232, 219, .2);
  --surface: #352C24; --surface-2: #3E342A;
  --accent: #D99A4E;
  --sc-ink: #EFE8DB; --sc-ink-soft: #C4B6A4; --sc-accent: #D99A4E;
  --lede-ink: #CFC2B1; --cap-ink: #B3A491;
  --text-dim: #B3A491; --line-control: rgba(239, 232, 219, .4);
  --panel: #352C24;
}

/* --- 1. the grounds: hard cuts, never an interpolation -------------------- */
.chapter { position: relative; isolation: isolate; color: var(--text); }
.chapter--paper   { background: var(--ch-paper); }
.chapter--plate   { background: var(--ch-plate); }
.chapter--terrain { background: var(--ch-terrain); }
.chapter--dusk    { background: var(--ch-dusk); }
.chapter--close   { background: var(--ch-close); }
.chapter__body { position: relative; z-index: 1; }

/* The cut is a change of ground, so the chapter's own first rule would be a
   second line drawn on top of it. */
.chapter__body > .section:first-child,
.chapter__body > *:first-child > .section:first-child { border-top: 0; }

:is(.chapter, .title-page, .colophon) :is(.lede, .section-intro) { color: var(--lede-ink); }
:is(.chapter, .title-page, .colophon) :is(figcaption, .note, .price-caption, .price-unit) { color: var(--cap-ink); }
:is(.chapter, .colophon) figcaption { border-left-color: var(--sc-accent); }

/* Two controls that break the moment the ink inverts, because desert-v1 paints
   them FROM the ink token: a bone button on a bone label, and bone text typed
   into a near-white field. */
:is(.chapter--dusk, .chapter--close) .button { background: #EFE8DB; color: #1A1714; }
:is(.chapter--dusk, .chapter--close) .button:hover { background: var(--accent); color: #1A1714; }
:is(.chapter--dusk, .chapter--close) :is(input, textarea, select, .money-field) { color: #1A1714; }
:is(.chapter--dusk, .chapter--close) .money-affix { color: #5E554B; }

/* A light island on a dark ground: the instruments keep their paper plate, so
   they keep paper ink. Without this the panel inherits bone type on #E6DCCB. */
:is(.chapter--dusk, .chapter--close)
  :is(.calculator, .service-sample, .audit-form, .score-grid,
      .wt-stage, .wt-panel, .sample-payload, .empty-explorer, .bot-context) {
  color: #1A1714;
  --text: #1A1714; --muted: #5E554B; --faint: #655C51;
  --line: rgba(26, 23, 20, .16);
  --surface: #E7DECE; --surface-2: #E0D4C0;
  --accent: #8A5116; --sc-ink: #1A1714; --sc-accent: #8A5116;
  --lede-ink: #4A4239; --cap-ink: #655C51;
  --text-dim: #655C51; --line-control: rgba(26, 23, 20, .34);
  --panel: #F4EEE3;
  background: var(--ch-plate);
}
:is(.chapter--terrain)
  :is(.calculator, .service-sample, .audit-form, .wt-stage, .wt-panel) {
  background: #F1EADE;
}

/* --- 2. the masthead (the pill is gone) ----------------------------------- */
/* The grammar forbids a fixed bar, so this sits in the flow and scrolls away
   with the title page. Navigation stays reachable: it is the first thing in the
   document after the skip link, it is keyboard-operable (the Automation menu is
   a native <details>), and the footer repeats every destination. */
html { scroll-padding-top: clamp(1rem, 4vh, 2.25rem); }
.site-masthead {
  position: relative; z-index: 20;
  background: var(--ch-paper);
  border-bottom: 1px solid rgba(26, 23, 20, .18);
}
.masthead-bar {
  display: flex; align-items: center;
  gap: clamp(.75rem, 2.5vw, 2.25rem);
  padding-block: clamp(.7rem, 1.3vw, 1.05rem);
}
.site-masthead .brand {
  font-size: clamp(.98rem, 1.15vw, 1.12rem);
  letter-spacing: -.03em; color: #1A1714;
}
.site-masthead .brand-period { color: #8A5116; }
.site-masthead #navigation { margin-left: auto; align-items: center; }
.site-masthead #navigation > a:not(.button),
.site-masthead .services-menu > summary {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  color: #5E554B; min-height: 40px;
  border-bottom: 1px solid transparent;
}
.site-masthead #navigation > a:not(.button):hover,
.site-masthead .services-menu > summary:hover,
.site-masthead #navigation > a[aria-current="page"] {
  color: #1A1714; border-bottom-color: #8A5116;
}
.site-masthead .services-menu > summary::after { color: #8A5116; font-size: 1rem; }
.site-masthead .services-dropdown {
  background: #F4EEE3; border: 1px solid rgba(26, 23, 20, .22);
  border-radius: 2px; box-shadow: 0 22px 44px rgba(42, 35, 28, .18);
}
.site-masthead .services-dropdown a { border-radius: 0; color: #1A1714; }
.site-masthead .services-dropdown a:hover { background: #E7DECE; }
.site-masthead .services-dropdown span { color: #5E554B; }
.site-masthead .nav-button {
  background: #8A5116; color: #FFF9F0; border-radius: 2px;
  min-height: 40px; padding: .5rem 1.05rem;
  font-size: .7rem; letter-spacing: .12em;
}
.site-masthead .nav-button:hover { background: #1A1714; color: var(--ch-paper); }
.site-masthead .menu-toggle {
  border-radius: 2px; border-color: rgba(26, 23, 20, .3); color: #1A1714;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .72rem; letter-spacing: .1em; text-transform: uppercase;
}
@media (max-width: 1099px) {
  .masthead-bar { flex-wrap: wrap; }
  .site-masthead .menu-toggle:not([hidden]) { display: block; margin-left: auto; }
  .site-masthead #navigation {
    width: 100%; margin-left: 0; flex-direction: column; align-items: stretch;
    gap: .15rem; padding-block: .9rem .4rem;
    max-height: 72svh; overflow: auto;
    border-top: 1px solid rgba(26, 23, 20, .18); margin-top: .8rem;
  }
  .site-masthead #navigation:not(.is-open) { display: none; }
  .site-masthead #navigation > a:not(.button),
  .site-masthead .services-menu > summary {
    min-height: 46px; font-size: .78rem; border-bottom: 0;
    border-top: 1px solid rgba(26, 23, 20, .1);
  }
  .site-masthead .services-menu > summary { justify-content: space-between; }
  .site-masthead .services-dropdown {
    position: static; min-width: 0; margin: .3rem 0 .6rem .9rem;
    border: 0; border-left: 2px solid #8A5116; box-shadow: none; background: none;
  }
  .site-masthead .services-dropdown a { min-height: 56px; }
  .site-masthead .nav-button { margin-top: .8rem; align-self: flex-start; }
}
@media (min-width: 1100px) {
  .site-masthead .menu-toggle { display: none; }
}

/* --- 3. the folio in the margin ------------------------------------------- */
/* Chapter number and title, updating as chapters pass. It repaints itself from
   the current chapter's ground (data-ground), because a fixed element over five
   different grounds cannot have one colour. Decorative: the headings are the
   real structure, so it is aria-hidden and never announced. */
/* 🔴 A fixed element in "the margin" has to be measured against where the text
   column actually starts, and the number has to be MEASURED. Two passes got it
   wrong: first a 140px rail at x=30 sat on the first 22px of every paragraph;
   then the correction was computed from .container's declared max-width of
   1240px, which is not what this site serves. The rendered value is 1280px
   (tighten-v1 raises it), so the copy begins at x=128 at 1440, not 148, and a
   124px rail still overlapped by 8px. Read off the render:
     content left = max(0, (100vw - 1280) / 2) + 48
   The rail is 100px wide, pinned to that gutter, and shown only from 1440px up,
   which is the width at which the gutter can hold it with 20px to spare. Below
   that the chapter head's own folio column does the job in flow, where it
   cannot collide with anything. */
.folio-rail { display: none; }
@media (min-width: 1440px) {
  .folio-rail {
    display: flex; flex-direction: column; gap: .5rem;
    position: fixed; left: max(.5rem, calc(50vw - 640px - 7rem)); top: 42vh;
    width: 6.25rem; z-index: 15; pointer-events: none;
    font-family: 'IBM Plex Mono', ui-monospace, monospace;
    opacity: 0; transition: opacity 420ms var(--sc-ease-out, ease-out);
    --folio-num: #8A5116; --folio-txt: #5E554B;
  }
  .folio-rail.is-live { opacity: 1; }
  .folio-rail[data-ground="terrain"] { --folio-num: #6B3C0C; --folio-txt: #3E382F; }
  .folio-rail[data-ground="dusk"],
  .folio-rail[data-ground="close"] { --folio-num: #D99A4E; --folio-txt: #C4B6A4; }
  .folio-rail__num {
    font-size: 1.05rem; letter-spacing: .08em; color: var(--folio-num);
    line-height: 1;
  }
  .folio-rail__line { width: 1.8rem; height: 1px; background: var(--folio-num); opacity: .6; }
  .folio-rail__title {
    font-size: .665rem; line-height: 1.5; letter-spacing: .01em;
    color: var(--folio-txt);
  }
}

/* --- 4. the title page --------------------------------------------------- */
/* Type on the paper ground. No media above the fold; the depth is the static
   composition: a sand plate behind an offset paper sheet with a real shadow,
   and a brass rule that starts on the plate, disappears behind the sheet and
   comes out the other side. That occlusion is the layering the brief asks for,
   without the pinned scrub hero this grammar bans. */
.title-page {
  position: relative; isolation: isolate; overflow: clip;
  background: var(--ch-paper); color: var(--text);
  padding-block: clamp(2rem, 3.4vw, 3.4rem) clamp(2.25rem, 3.8vw, 4rem);
}
.title-page__plate {
  position: absolute; z-index: 0; inset: 0 0 auto auto;
  width: 46%; min-width: 320px; height: 72%;
  background: var(--ch-terrain);
  border-left: 1px solid rgba(26, 23, 20, .22);
  box-shadow: inset 0 1px 0 rgba(255, 249, 240, .35);
}
.title-page__crossrule {
  position: absolute; z-index: 1; left: 0; right: 0; height: 1px;
  top: clamp(4.5rem, 9vw, 7.5rem);
  background: #8A5116; opacity: .8;
}
.title-page__grid {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: 8.75rem minmax(0, 1fr);
  gap: clamp(1rem, 2vw, 2.25rem); align-items: start;
}
.title-page__folio {
  display: flex; flex-direction: column; gap: .5rem;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  padding-top: .55rem; border-top: 2px solid #8A5116; margin: 0;
}
.title-page__folio .chapter-folio__n { font-size: 1.15rem; letter-spacing: .08em; color: #8A5116; }
.title-page__folio .chapter-folio__t { font-size: .705rem; line-height: 1.5; color: #5E554B; }
.title-page__sheet {
  max-width: 47rem;
  background: #F4EEE3;
  border: 1px solid rgba(26, 23, 20, .16);
  box-shadow: 0 28px 60px rgba(42, 35, 28, .17), inset 0 1px 0 rgba(255, 249, 240, .8);
  padding: clamp(1.4rem, 2.2vw, 2.3rem) clamp(1.3rem, 2.4vw, 2.6rem) clamp(1.5rem, 2.4vw, 2.4rem);
}
.title-page__sheet > *:last-child { margin-bottom: 0; }
.title-page__sheet .eyebrow {
  font-size: .715rem; letter-spacing: .1em; text-transform: none;
  color: #5E554B; margin-bottom: 1.1rem;
}
.title-page__sheet .eyebrow a { color: #8A5116; text-decoration: none; border-bottom: 1px solid rgba(138, 81, 22, .4); }
.title-page__sheet .eyebrow a:hover { color: #1A1714; border-bottom-color: #1A1714; }
/* Two lines, and the CTA above the fold at 1440x900. The floor's h1 clamp is
   written for a page whose hero is a pinned stage; a title page is a printed
   sheet and its headline is set to the sheet, not to the viewport. */
.title-page__sheet h1 { font-size: clamp(2.05rem, 3.5vw, 3.35rem); max-width: 20ch; margin-bottom: 1.1rem; }
.title-page__sheet h1 em { font-style: normal; color: #8A5116; }
.title-page__sheet .lede { max-width: 54ch; color: #4A4239; font-size: clamp(1rem, 1.1vw, 1.12rem); line-height: 1.62; margin-bottom: 1.4rem; }
.title-page__sheet .actions { gap: .7rem 1.1rem; }
@media (max-width: 1099px) {
  .title-page__grid { grid-template-columns: 1fr; gap: 1rem; }
  .title-page__folio { flex-direction: row; align-items: baseline; gap: .8rem; max-width: 22rem; }
  .title-page__plate { inset: 0 0 auto 0; width: 100%; min-width: 0; height: clamp(90px, 22vw, 190px);
                       border-left: 0; border-bottom: 1px solid rgba(26, 23, 20, .22); }
  .title-page__crossrule { top: clamp(4rem, 14vw, 8rem); }
}
@media (max-width: 700px) {
  /* The portrait crop of the TYPE, not of an image: desert-v1's h1 floors at
     2.8rem, which wraps this headline to five lines on a 390px screen. */
  .title-page__sheet h1 { font-size: clamp(2.05rem, 8.6vw, 2.7rem); max-width: 18ch; }
  .title-page__sheet .lede { font-size: 1.02rem; }
  .title-page { padding-block: 1.75rem 2.25rem; }
  .title-page__sheet { padding: 1.35rem 1.15rem 1.5rem; }
}

/* --- 5. the chapter head ------------------------------------------------- */
.chapter-head { padding-top: clamp(2.25rem, 4.5vw, 4.5rem); align-items: center; }
.chapter-folio { display: flex; flex-direction: column; gap: .5rem; margin: 0; }
.chapter-folio__n { font-size: 1.15rem; letter-spacing: .08em; line-height: 1; }
.chapter-folio__t { font-size: .705rem; line-height: 1.5; letter-spacing: .015em; color: var(--muted); text-transform: none; }
.chapter-rule { height: 1px; background: var(--sc-accent); opacity: .7; align-self: center; }
/* The boundary wipe: full bleed, on the cut itself, 3px of brass drawn across
   the page as the chapter arrives. A wipe on a small element is a fidget. */
.chapter-cut {
  position: absolute; z-index: 2; top: 0; left: 0; right: 0; height: 3px;
  pointer-events: none;
}
.chapter-cut__bar { display: block; height: 100%; background: var(--sc-accent); }
@media (max-width: 720px) {
  .chapter-folio { flex-direction: row; align-items: baseline; gap: .7rem; }
  .chapter-rule { display: none; }
}

/* --- 6. the lateral spread (chapter V of /) ------------------------------- */
/* Lateral travel reads as "options"; vertical reads as "argument", and the
   argument is already made by then. Three statements the eye crosses. These are
   the three scenes that used to crossfade inside the pinned hero: same words,
   no longer hidden behind a scroll position. */
.statement-spread { border-top: 1px solid var(--line); }
.statement-spread__grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0;
}
.statement {
  padding: clamp(1.25rem, 2vw, 2rem) clamp(1rem, 1.8vw, 1.9rem) 1.5rem;
  border-left: 1px solid var(--line);
  display: flex; flex-direction: column;
}
.statement:first-child { border-left: 0; padding-left: 0; }
.statement:last-child { padding-right: 0; }
.statement-folio {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .7rem; letter-spacing: .1em; color: var(--sc-accent);
  margin: 0 0 1.1rem; padding-top: .5rem; border-top: 2px solid var(--sc-accent);
  max-width: none;
}
.statement h2 { font-size: clamp(1.2rem, 1.55vw, 1.62rem); line-height: 1.16; max-width: 20ch; margin-bottom: .8rem; }
.statement p { font-size: .96rem; max-width: 40ch; }
.statement .button { margin-top: auto; align-self: flex-start; }
@media (max-width: 900px) {
  .statement-spread__grid { grid-template-columns: 1fr; }
  .statement { border-left: 0; border-top: 1px solid var(--line); padding: 1.4rem 0 1.5rem; }
  .statement:first-child { border-top: 0; }
}

/* --- 7. parallax: depth in the media column, never under type ------------- */
.chapter-strata { position: absolute; inset: 0; z-index: 0; overflow: clip; pointer-events: none; }
.chapter-strata > span { position: absolute; left: 0; right: 0; display: block; }
.chapter-strata__far {
  top: -12%; height: 46%;
  background: color-mix(in oklab, var(--ch-terrain) 42%, transparent);
  border-bottom: 1px solid rgba(26, 23, 20, .12);
}
.chapter-strata__near {
  bottom: -8%; height: 26%;
  background: color-mix(in oklab, var(--ch-dusk) 6%, transparent);
}
.chapter--dusk .chapter-strata__far,
.chapter--close .chapter-strata__far { background: color-mix(in oklab, #EFE8DB 7%, transparent); border-bottom-color: rgba(239, 232, 219, .1); }
.chapter--dusk .chapter-strata__near,
.chapter--close .chapter-strata__near { background: color-mix(in oklab, #EFE8DB 3%, transparent); }

.card-thumb--depth, .portfolio-frame--depth { overflow: hidden; }
.card-thumb--depth > picture, .portfolio-frame--depth > picture { display: block; }
.card-thumb--depth img, .portfolio-frame--depth img { transform: scale(1.06); transform-origin: 50% 40%; }

/* --- 8. the reveal, and the states it must fail open from ----------------- */
/* The engine writes clip-path only while an act is live, so these are the
   pre-live states. Both are cleared by .sc-fallback, which the page's own mount
   block sets if scrollcraft never mounts: an entrance effect that can hide a
   chapter permanently is not an effect, it is an outage. */
html.js:not(.sc-fallback) .chapter-cut[data-sc-in] { opacity: 1; transform: none; }
html.js:not(.sc-fallback) .chapter-cut[data-sc-in] .chapter-cut__bar {
  clip-path: inset(0 0 0 100%);
  transition: clip-path 720ms var(--sc-ease-out, ease-out);
}
html.js .chapter-cut[data-sc-in].sc-in .chapter-cut__bar { clip-path: inset(0 0 0 0); }
html.sc-fallback :is([data-sc-reveal], .chapter-cut__bar) { clip-path: none !important; }
html:not(.js) [data-sc-in], html:not(.js) [data-sc-stagger] > * { opacity: 1 !important; transform: none !important; }

/* --- 9. the colophon ----------------------------------------------------- */
/* Not a button island. Small type, the ask set as a line of running text, and
   it resolves and HOLDS: no drift down, no fade into the footer. */
.colophon {
  position: relative; background: var(--ch-close); color: var(--text);
  border-top: 1px solid rgba(239, 232, 219, .18);
  padding-block: clamp(2.75rem, 5vw, 5rem) clamp(3rem, 5.5vw, 5.5rem);
}
.colophon__grid {
  display: grid; grid-template-columns: 8.75rem minmax(0, 1fr);
  gap: clamp(1rem, 2vw, 2.25rem); align-items: start;
}
.colophon__folio {
  display: flex; flex-direction: column; gap: .5rem; margin: 0;
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  padding-top: .55rem; border-top: 2px solid var(--accent);
}
.colophon__folio .chapter-folio__n { font-size: 1.15rem; letter-spacing: .08em; color: var(--accent); }
.colophon__folio .chapter-folio__t { font-size: .705rem; line-height: 1.5; color: var(--muted); }
.colophon__body { max-width: 52rem; }
.colophon__lead {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .705rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 1.1rem; max-width: none;
}
.colophon__title {
  font-size: clamp(1.55rem, 2.15vw, 2.15rem); line-height: 1.12;
  max-width: 24ch; margin: 0 0 1.1rem; color: var(--text);
}
.colophon__note {
  font-size: 1rem; line-height: 1.7; max-width: 62ch; color: var(--muted); margin: 0;
}
.colophon__ask {
  color: var(--text); text-decoration: underline;
  text-decoration-color: var(--accent); text-decoration-thickness: 2px;
  text-underline-offset: .28em; white-space: nowrap;
}
.colophon__ask:hover { color: var(--accent); }
@media (max-width: 1099px) {
  .colophon__grid { grid-template-columns: 1fr; gap: 1rem; }
  .colophon__folio { flex-direction: row; align-items: baseline; gap: .8rem; max-width: 22rem; }
}

/* --- 10. footer, on the colophon's ground -------------------------------- */
/* The colophon must hold, not dissolve into the footer, so the footer takes a
   visible rule and a step of ground rather than being the same black. */
.site-footer { background: #14110E; border-top: 1px solid rgba(239, 232, 219, .14); }

/* --- 11. reduced motion -------------------------------------------------- */
/* Fewer and gentler, not zero: the chapter grounds, the folio, the plate and
   the sheet are the design and they are all static anyway. */
@media (prefers-reduced-motion: reduce) {
  .chapter-cut__bar { clip-path: none !important; transition: none !important; }
  .folio-rail { transition: none; }
  .card-thumb--depth img, .portfolio-frame--depth img { transform: none; }
}

/* ============================================================================
   12. Components the chapters inherited, brought onto the paper
   ----------------------------------------------------------------------------
   These were drawn for the espresso theme and for a centred marketing page.
   Nothing here changes a word; it changes a surface, a radius and an alignment,
   and every one of them was found by RENDERING the page, not by reading CSS.
   ========================================================================== */

/* The footer sits on the colophon's ground, so it needs the colophon's ink.
   It was inheriting the paper token set and rendering its headings at about
   1.3:1 on near-black. */
.site-footer {
  color: #EFE8DB;
  --text: #EFE8DB; --muted: #C4B6A4; --faint: #B3A491;
  --line: rgba(239, 232, 219, .16);
  --surface: #241E19; --accent: #D99A4E;
}
.site-footer h2 { color: #EFE8DB; font-size: .705rem; letter-spacing: .1em;
                  text-transform: uppercase; font-family: 'IBM Plex Mono', ui-monospace, monospace; }
.site-footer a { color: #C4B6A4; }
.site-footer a:hover { color: #EFE8DB; }
.site-footer .brand { color: #EFE8DB; }
.site-footer .brand-period { color: #D99A4E; }
.site-footer .footer-bottom { color: #B3A491; border-top: 1px solid rgba(239, 232, 219, .12); padding-top: 1.4rem; }

/* The three service cards were glass slabs (34% black, a 22px blur, a 20px
   radius) designed to float on an espresso ground. On paper they read as dirt.
   Same composition, including the dropped middle card; paper surfaces. */
.page-home .service-card {
  background: #F4EEE3; border: 1px solid rgba(26, 23, 20, .16); border-radius: 2px;
  backdrop-filter: none; box-shadow: 0 18px 40px rgba(42, 35, 28, .13);
  color: #1A1714;
}
.page-home .service-card p { color: #5E554B; }
.page-home .service-card-photo { border-radius: 1px; }
.page-home .service-card-arrow {
  border-radius: 2px; background: #8A5116; color: #FFF9F0;
  width: 44px; height: 44px; font-size: 1.2rem;
}
.page-home .service-card:hover { border-color: #8A5116; }

/* An editorial spread is not symmetrical, and the chapter folio is on the left
   margin: a centred heading under it reads as two different pages. */
.portfolio-head { text-align: left; max-width: 64ch; margin: 0 0 2rem; }
.portfolio-head .section-intro { margin: 0; }
.portfolio-tabs {
  justify-content: flex-start; max-width: none; margin: 0 0 2.25rem;
  padding: 0; border: 0; border-top: 1px solid var(--line); border-radius: 0;
  background: none; gap: 0;
}
.portfolio-tabs button {
  font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
  border-radius: 0; padding: .85rem 1.1rem .8rem 0; margin-right: 1.4rem;
  border-top: 2px solid transparent; margin-top: -1px; color: var(--muted);
}
.portfolio-tabs button:hover { background: none; color: var(--text); }
.portfolio-tabs button[aria-pressed="true"] {
  background: none; color: var(--text); border-top-color: var(--sc-accent);
}
/* One radius scale across the page: paper has 2px corners, not 12 and 20. */
.portfolio-frame, .portfolio-link:focus-visible { border-radius: 2px; }
.portfolio-frame { background: var(--surface); box-shadow: 0 14px 30px rgba(42, 35, 28, .14); }
.portfolio-caption { color: var(--cap-ink); }
.actions--center { justify-content: flex-start; }
.portfolio-empty { text-align: left; }

/* The strata are atmosphere, not a seam. A hairline edge on a band that moves
   reads as a rendering artefact crossing the tiles. */
.chapter-strata__far { border-bottom: 0; -webkit-mask-image: linear-gradient(#000 62%, transparent); mask-image: linear-gradient(#000 62%, transparent); }
.chapter-strata__near { -webkit-mask-image: linear-gradient(transparent, #000 45%); mask-image: linear-gradient(transparent, #000 45%); }

/* Filter chips, FAQ rows and details markers all carried the old radius. */
:is(.chapter, .colophon) :is(.faq details, .filter-bar, .status-label, .wt-msg,
  .wt-stage, .wt-panel, .wt-mail, .connection-flow, .service-sample, .app-shot,
  .clip-frame, .editorial-photo, .card-thumb, .calculator) { border-radius: 2px; }

/* --- 13. four contrast failures the render found, and nothing else would ---- */
/* Every one of these was measured on the served page (computed colour against
   its real painted background), not judged by eye. */

/* 1. The skip link was brass on #2A2419 at 3.5:1. digital-theme paints it from
   --accent, which is now a text-weight brass rather than a surface. */
.skip-link { background: #1A1714; color: #EFE8DB; border-radius: 2px; }

/* 2. A pre-ticked service box on a dark chapter: bone label text on the
   #F6F1E7 checked plate, 1.08:1. */
:is(.chapter--dusk, .chapter--close) .service-choices label {
  background: #F4EEE3; color: #1A1714; border-color: rgba(26, 23, 20, .26);
}
:is(.chapter--dusk, .chapter--close) .service-choices label:has(:checked) {
  border-color: #8A5116; background: #FFF9F0;
}
/* 🔴 :is() takes the specificity of its MOST specific argument, so listing
   `.form-pair label span` in here lifted the whole selector to (0,2,2) and it
   silently beat `.chapter--close .service-choices label` at (0,2,1), putting
   bone type back on the checked plate one fix after removing it. Same trap the
   header button hit on 2026-09-26. Keep the two apart. */
:is(.chapter--dusk, .chapter--close) :is(label, legend) { color: var(--muted); }
:is(.chapter--dusk, .chapter--close) .form-pair label span { color: var(--faint); }
:is(.chapter--dusk, .chapter--close) .service-choices label,
:is(.chapter--dusk, .chapter--close) .service-choices label:has(:checked) { color: #1A1714; }
:is(.chapter--dusk, .chapter--close) fieldset { border-color: var(--line); }

/* 3. Anything that paints itself brass took its label colour from the old
   espresso ground (#2A2419 on --accent, 2.4:1). The accent is now a text-weight
   brass, so its own labels have to be bone. */
.ad-tabs button[aria-selected="true"],
.wt-channel[aria-pressed="true"] { color: #FFF9F0; border-color: #8A5116; }
::selection { background: #8A5116; color: #FFF9F0; }
/* ...but the portfolio tabs are ruled, not filled, so they keep page ink. */
.portfolio-tabs button[aria-pressed="true"] { color: var(--text); background: none; }

/* 4. Focus must be visible on every ground, and the floor's ring is --text. */
:is(.chapter--dusk, .chapter--close) :is(a, button, summary, input, select, textarea):focus-visible {
  outline-color: #D99A4E;
}

/* The secondary button was a 22% black wash designed to sit on espresso; on
   paper it renders as a grey slab next to a crisp ink primary. A ghost button
   on paper is the border, not a fill. */
.quiet-button { background: transparent; }
.quiet-button:hover { background: var(--surface); }

/* 🔴 A grid with a missing child needs a different grid, not a missing child.
   The title page and the colophon both run `8.75rem minmax(0,1fr)` with the
   folio in the first column. On the 36 routes that carry no chapters there is
   no folio, so the BODY took the 140px folio column and every headline on those
   pages broke one word per line. Nothing automated flagged it: no overflow, no
   broken image, no console error. A screenshot did, immediately. */
.title-page--plain .title-page__grid,
.colophon--plain .colophon__grid { grid-template-columns: minmax(0, 1fr); }

/* --- 14. the grain, on every ground --------------------------------------- */
/* desert-v1 paints its paper grain on a FIXED body::before at z-index 0, under
   <main>. That works while every section is transparent, which is how the floor
   was written. A chapter paints an opaque ground, so it covers the grain, and
   the six chaptered routes lost the one thing that makes the ground read as
   paper rather than as a flat fill. Measured, not guessed: #EFE8DB exactly
   inside a chapter, #ECE5DA on an unchaptered section directly below it.
   So each ground carries its own grain, under the content and over the fill. */
.chapter::after, .title-page::after, .colophon::after {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .3;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.86' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.32'/%3E%3C/svg%3E");
}
/* A flat dark ground bands on a real display; grain is what stops it, but it
   needs less of it than paper does. */
.chapter--dusk::after, .chapter--close::after, .colophon::after { opacity: .16; }
.chapter-head, .title-page__grid, .colophon__grid { position: relative; z-index: 1; }

/* --- 15. the glass panels ------------------------------------------------- */
/* 🔴 digital-theme builds every panel on this site out of a 34% black wash
   (`rgba(24,18,8,.34)`) with a 20px radius and a 22px backdrop blur. That is
   correct on espresso and it is a grey slab on paper: measured on /pricing,
   the Custom Application panel painted #9E9588 on a #E6DCCB ground while its
   own --surface token read #E7DECE, because the wash never consults the token.
   One list, one paper surface per ground, one radius. The wash is not tuned,
   it is replaced. */
:is(.service-portrait, .service-sample, .pricing-columns, .price-grid,
    .score-grid article, .two-column article, .connection-flow, .wt-stage,
    .wt-panel, .contact-aside, .calculator, .audit-form,
    .ad-stage, .ad-side, .ad-tabs button, .pc-stats > div, .pc-status,
    .audit-sample, .sample-tiles div, .wt-msg, .wt-msg.wt-bot, .ad-msg,
    .home-audit .container, .empty-explorer, .sample-payload, .bot-context) {
  background: var(--panel, #F4EEE3);
  border-radius: 2px;
  backdrop-filter: none;
}
/* 🔴 #audit-findings is listed on its OWN, never inside an :is(). :is() takes
   the specificity of its most specific argument, so one ID in that list lifted
   the whole 26-selector rule to (1,0,0) and it silently beat
   `.ad-tabs button[aria-selected="true"]` (0,2,1) at (1,0,0): the selected demo
   tab lost its brass fill and rendered bone-on-paper at 1.1:1. Exactly the trap
   this repo recorded on 2026-09-26 for the header button, walked into again by
   writing a convenience list. */
#audit-findings { background: var(--panel, #F4EEE3); border-radius: 2px; backdrop-filter: none; }
:is(.chapter--dusk, .chapter--close) #audit-findings { background: var(--ch-plate); color: #1A1714; }
:is(.chapter--dusk, .chapter--close)
  :is(.calculator, .service-sample, .audit-form, .score-grid,
      .wt-stage, .wt-panel, .sample-payload, .empty-explorer, .bot-context) {
  background: var(--ch-plate);   /* the light islands stay light: see section 0 */
}

/* --- 16. the one word that would not fit --------------------------------- */
/* 🔴 5px of real horizontal document scroll at 390px on /work and /terms, and
   nothing whose bounding box exceeded the viewport: the overflow was INSIDE an
   h2, "Automation demonstrations." at 30.4px, 375px of unbreakable word in a
   350px column. A single long word cannot wrap, so the fix is hyphenation,
   which is what a printed page does anyway. Measured before and after:
   documentElement.scrollWidth 395 -> 390 on both routes. */
@media (max-width: 600px) {
  h1, h2, h3, .colophon__title { overflow-wrap: break-word; hyphens: auto; }
}

/* ============================================================================
   13. The three page-scroll devices                        (added 2026-09-26)
   ----------------------------------------------------------------------------
   Darrin, on the preview: "rather than the scrolling cards at the begininning
   can we do some creating scroll effect at the top and throuhough page? how
   about the portfolio or showcase, there has to be a way to use cool scrolling
   technicques".

   Three devices, all driven from properties published by
   source/desert-scroll-v1.js, all legal in a chaptered-editorial page (its
   device bans are `scrub` beyond one chapter, `spotlight` and `magnet`, and none
   of these is any of those). The choreography is written here; the JS measures
   and publishes and does nothing per frame beyond one property write.

   --dp  0 → 1 across the title page's own height      (the opening plate)
   --tp  0 → 1 across the whole document               (the page trace)
   --bx  the bead's x on the profile, as a percentage  (the page trace)
   ========================================================================== */
@property --dp { syntax: "<number>"; inherits: true; initial-value: 0; }
@property --tp { syntax: "<number>"; inherits: true; initial-value: 0; }

/* --- 13a. The opening plate ---------------------------------------------- */
/* 🔴 NOTHING HERE ASSEMBLES THE COMPOSITION. devices.md's "ground or greet"
   rule: a reader who never turns the wheel must be looking at a finished page.
   At --dp:0 every rule below is a no-op or a hold, and scrolling DEEPENS the
   plate rather than building it. The one thing that arrives from nothing is the
   seed trace, which is decorative.

   🔴 And the lift is on `.title-page__grid`, NOT on `.title-page__sheet`. The
   sheet carries `data-sc-in data-sc-stagger="80"`, and scrollcraft.css writes
   `transform: translate3d(0,14px,0)` on `[data-sc-in]` and on every child of a
   `[data-sc-stagger]` — devices.md 9 spells the consequence out: two devices
   writing `transform` on one node means one of them is silently discarded. The
   grid is the sheet's parent and no engine device touches it. */
html.desert-open .title-page__plate {
  transform: translate3d(calc(var(--dp) * 30px), 0, 0);
  will-change: transform;
}
/* The light going long. The plate is the desert ground of the whole build, so
   the opening is late afternoon deepening rather than an element sliding in. */
html.desert-open .title-page__plate::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(205deg, rgba(122, 74, 26, 0) 34%, rgba(66, 40, 16, .30));
  opacity: var(--dp);
  pointer-events: none;
}
html.desert-open .title-page__grid {
  transform: translate3d(0, calc(var(--dp) * -15px), 0);
}
html.desert-open .title-page__sheet {
  box-shadow: 0 calc(28px + var(--dp) * 26px) calc(60px + var(--dp) * 40px) rgba(42, 35, 28, .17),
              inset 0 1px 0 rgba(255, 249, 240, .8);
}
/* The folio rule draws itself out of the plate and across the sheet. It holds a
   real composition at rest — a rule that crosses two thirds of the page and
   stops is a printed page, not a broken one — and completes on scroll. */
html.desert-open .title-page__crossrule {
  transform: scaleX(calc(.62 + var(--dp) * .38));
  transform-origin: 0 50%;
}
/* The seed: the same measured dune ridge the peak traces two chapters later,
   surfacing on the plate as the reader scrolls. Built by desert-scroll-v1.js
   from window.ScrollCraftRidge — one array, borrowed, never copied. */
.title-page__seed { position: absolute; inset: 14% 0 10% 0; pointer-events: none; }
.seed-svg { display: block; width: 100%; height: 100%; overflow: visible; }
.seed-line {
  fill: none;
  stroke: #5A3410;
  stroke-width: 1.7px;
  stroke-linecap: round;
  stroke-dasharray: var(--seed-len, 260);
  stroke-dashoffset: calc(var(--seed-len, 260) * (1 - clamp(0, calc((var(--dp) - .02) / .48), 1)));
  opacity: calc(clamp(0, calc((var(--dp) - .02) / .22), 1) * .62);
}

/* --- 13b. The page trace -------------------------------------------------- */
/* The "throughout" device, and the only one that is present on every screen of
   the page. Persistent chrome driven by PAGE scroll, not by any act, which is
   uniqueness.md 2.8's own escape: "the bans are on what the acts do, not on
   what the page can do."

   Decorative by decision: aria-hidden in the markup, pointer-events none here,
   no tab stops. The folio rail on the opposite margin already names the chapter
   and the chapter heads carry it in flow; a second clickable chapter list would
   be a duplicate navigation for a keyboard to walk for nothing.

   Shown from 1000px up — wider than the folio rail's 1440px, because the rail
   needs 100px of gutter for its title and this needs 22. Below that a fixed
   element at the edge of a phone is in the way of the thing it decorates. */
.page-trace { display: none; }
@media (min-width: 1000px) {
  html.desert-trace .page-trace {
    display: block;
    position: fixed;
    right: max(.75rem, calc(50vw - 640px - 2.6rem));
    top: 24vh; height: 52vh; width: 22px;
    z-index: 14;
    pointer-events: none;
    opacity: 0;
    animation: page-trace-in 700ms 320ms var(--sc-ease-out, ease-out) forwards;
  }
}
@keyframes page-trace-in { to { opacity: 1; } }
.page-trace__svg { display: block; width: 100%; height: 100%; overflow: visible; }
/* The whole profile, faint: the reader can see how much page is left, which is
   what a folio is for. The bright stroke on top is how far they have come. */
.page-trace__ghost { fill: none; stroke: rgba(169, 106, 34, .22); stroke-width: 1px; }
.page-trace__line {
  fill: none;
  stroke: #A96A22;
  stroke-width: 1.6px;
  stroke-linecap: round;
  stroke-dasharray: var(--trace-len, 1000);
  stroke-dashoffset: calc(var(--trace-len, 1000) * (1 - var(--tp)));
}
.page-trace__mark line { stroke: rgba(169, 106, 34, .30); stroke-width: 1px; }
.page-trace__mark.is-past line { stroke: #A96A22; stroke-width: 1.8px; }
.page-trace__bead {
  position: absolute;
  top: calc(var(--tp) * 100%);
  left: var(--bx, 30%);
  width: 6px; height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: #A96A22;
  box-shadow: 0 0 0 1.5px rgba(255, 249, 240, .9), 0 0 0 2.5px rgba(169, 106, 34, .28);
}

/* --- 13c. The showcase deal ---------------------------------------------- */
/* 🔴 Gated behind `html.desert-deal`, which desert-scroll-v1.js adds only after
   it has wired every tile. That gate is the whole safety story: no JS, a thrown
   exception, a blocked script or reduced motion all leave eleven pieces of real
   work fully visible, because without the class these rules do not exist. A
   bare `opacity: 0` here would be one bad deploy away from an empty showcase.

   The transform is on the inner `<a>`, not on the `<li>`: the `<li>` is what the
   filter sets `hidden` on, and `.portfolio-shot` inside already carries
   `data-sc-parallax`. Three nodes, three transforms, no collision. */
html.desert-deal .portfolio-link[data-deal] {
  opacity: 0;
  transform: translate3d(0, 34px, 0);
  transition: opacity 560ms var(--sc-ease-out, cubic-bezier(.22, .61, .36, 1)),
              transform 700ms var(--sc-ease-out, cubic-bezier(.22, .61, .36, 1));
  will-change: opacity, transform;
}
html.desert-deal .portfolio-link[data-deal].is-dealt {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* --- 13d. Reduced motion -------------------------------------------------- */
/* desert-scroll-v1.js already refuses to add `desert-open` or `desert-deal`
   under reduced motion, so 13a and 13c cannot apply at all. The page trace is
   kept, because it is a folio rather than an animation: the whole profile is
   drawn, every chapter is stamped, and the bead is removed by the script (a dot
   that never moves is a smudge, not a position). */
@media (prefers-reduced-motion: reduce) {
  html.desert-trace .page-trace { animation: none; opacity: 1; }
  .page-trace__line { stroke-dashoffset: 0; }
}

/* ---------------------------------------------------------------------------
   §14  THE SHOWCASE, UNCROPPED.
   2026-09-26 — Darrin: "assemble a better showcase for see what was built,
   the images are cropped, we can do better."

   Measured before changing anything. The tiles were a fixed 16:9 slot with
   `object-fit: cover` and `object-position: top`, holding eleven screenshots
   whose real ratios run from 0.78 to 2.07:

     screen-diegotech-assistant   1400x1803   56% of it never rendered
     screen-rfsrx                 1400x997    21%
     site-digital/diegotech/pc    1400x933    15% each
     screen-bmg-monitor           1400x676    14%

   ...and this layer then scaled every one a further 6% to buy parallax slack
   inside an overflow:hidden frame. None of it was art direction: `top` is a
   blind alignment, so what got cut was whatever happened to sit at the bottom
   of the capture.

   The uniform row is what forced the crop, so the row goes. Each tile now
   carries its OWN ratio and nothing is cropped at all. Columns pack the mixed
   heights — the portrait assistant reads tall, the wide monitor reads short —
   with no letterbox bands anywhere. The obvious alternative, `contain` inside
   the fixed slot, only trades a crop for dead space, which this project
   already rejected once for the BMG clip on /work.
   --------------------------------------------------------------------------- */
.portfolio-grid {
  display: block;
  columns: 3;
  column-gap: 1.75rem;
}
.portfolio-tile {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  display: inline-block;   /* engines honour break-inside far more reliably here */
  width: 100%;
  margin: 0 0 1.9rem;
}
/* The frame's box IS the image's ratio now, so there is nothing left to fit
   and nothing to align. `--shot-ar` is the true ratio read out of the WebP
   header at build time (build.py::_webp_size), and the width/height pair on
   the <img> reserves the right box before the bytes land. */
.portfolio-frame img {
  aspect-ratio: var(--shot-ar, auto);
  height: auto;
  object-fit: fill;
  object-position: center;
}
.portfolio-frame--depth img { transform: none; }

@media (max-width: 1000px) { .portfolio-grid { columns: 2; } }
@media (max-width: 600px)  { .portfolio-grid { columns: 1; column-gap: 0; } }

/* =============================================================================
   §20 · 2026-09-28 clean-up pass (Darrin's preview review)
   One block, in the order he listed the changes, so each one can be found and
   reverted on its own. Loads after everything above, so equal specificity wins.
   ========================================================================== */

/* --- 20.1 the masthead is PINNED --------------------------------------------
   "pin the menu so it floats down as you scroll". §2 kept it in flow because
   the chaptered grammar forbids a fixed bar; the owner has overruled that, so
   it is sticky, with a soft shadow once the page has moved under it. Anchors
   are pushed below it via scroll-padding, or every #jump would land under it. */
html { scroll-padding-top: 6.25rem; }
.site-masthead {
  position: sticky; top: 0; z-index: 40;
  background: rgba(239, 232, 219, .94);
  -webkit-backdrop-filter: saturate(1.2) blur(10px);
  backdrop-filter: saturate(1.2) blur(10px);
  transition: box-shadow 320ms ease, background-color 320ms ease;
}
html.is-scrolled .site-masthead {
  box-shadow: 0 10px 28px rgba(42, 35, 28, .12);
  background: rgba(244, 238, 227, .97);
}
@media (prefers-reduced-transparency: reduce) {
  .site-masthead { background: var(--ch-paper); backdrop-filter: none; }
}

/* --- 20.2 header type and logo +30% ----------------------------------------- */
.site-masthead .brand { font-size: clamp(1.27rem, 1.5vw, 1.46rem); gap: .15rem; }
.site-masthead .brand-mark { grid-template-columns: repeat(2, 10.4px); gap: 4px; margin-right: 7px; }
.site-masthead .brand-mark i { width: 10.4px; height: 10.4px; border-width: 1.7px; }
.site-masthead #navigation > a:not(.button),
.site-masthead .services-menu > summary { font-size: .91rem; letter-spacing: .07em; }
.site-masthead .services-menu > summary::after { font-size: 1.3rem; }
.site-masthead .nav-button { font-size: .91rem; letter-spacing: .1em; min-height: 46px; padding: .55rem 1.35rem; }
.site-masthead .menu-toggle { font-size: .94rem; }
.site-masthead .services-dropdown a { font-size: 1.04rem; }
.site-masthead .services-dropdown span { font-size: .86rem; }
@media (max-width: 1099px) {
  .site-masthead #navigation > a:not(.button),
  .site-masthead .services-menu > summary { font-size: 1.01rem; }
}

/* --- 20.3 the title page: no stray rule, and the words fit the sheet --------
   The crossrule was scaled to 62% of the viewport by the opening move, so on a
   wide screen it ended half-way across the sheet, straight through the eyebrow.
   The sheet is widened so the three service links sit on one line and each
   sentence of the lede on its own. */
.title-page__crossrule { display: none; }
.title-page__sheet { max-width: 56rem; }
.title-page__sheet .eyebrow { white-space: nowrap; overflow-wrap: normal; }
.title-page__sheet .lede { max-width: 64ch; }
@media (max-width: 760px) {
  .title-page__sheet .eyebrow { white-space: normal; }
}

/* --- 20.4 tighter rhythm between sections ----------------------------------- */
.section, .closing { padding-block: clamp(2.25rem, 4vw, 4rem); }
.chapter-head { padding-top: clamp(1.5rem, 3vw, 3rem); }
.chapter__body > .section:first-child { padding-top: clamp(1.5rem, 2.5vw, 2.5rem); }
.colophon { padding-block: clamp(2.5rem, 4.5vw, 4.5rem); }

/* --- 20.5 the showcase: a bigger selector, an even grid, no crop, no zoom ----
   The column-major masonry (§14) is replaced by an ordinary row grid. Every
   tile gets the same matted frame and the screenshot is centred in it at its
   own ratio (contain), so nothing is cropped and nothing is enlarged: a picture
   is shown at the size the frame allows, never scaled up past it. */
.portfolio-tabs {
  display: flex; flex-wrap: wrap; gap: .75rem;
  border-top: 0; margin: 0 0 2.5rem;
}
.portfolio-tabs button {
  font-size: 1rem; letter-spacing: .08em;
  padding: 1rem 1.6rem; margin: 0;
  border: 1px solid rgba(26, 23, 20, .28); border-radius: 2px;
  background: rgba(244, 238, 227, .55); color: #3E382F;
  transition: background-color 240ms ease, color 240ms ease, border-color 240ms ease, transform 240ms ease;
}
.portfolio-tabs button:hover { background: #F4EEE3; color: #1A1714; border-color: #8A5116; }
.portfolio-tabs button[aria-pressed="true"] {
  background: #1A1714; color: #F4EEE3; border-color: #1A1714;
}
.portfolio-grid {
  display: grid; columns: auto;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.75rem, 3vw, 2.75rem) clamp(1.25rem, 2.4vw, 2.25rem);
  align-items: start;
}
.portfolio-tile { display: block; margin: 0; }
.portfolio-tile[hidden] { display: none; }
.portfolio-link { display: flex; flex-direction: column; height: 100%; }
.portfolio-frame {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 16 / 10; overflow: hidden;
  padding: clamp(.6rem, 1.1vw, 1rem);
  background: #EDE4D4;
  border: 1px solid rgba(26, 23, 20, .14);
  transition: box-shadow 420ms cubic-bezier(.2,.7,.2,1), transform 420ms cubic-bezier(.2,.7,.2,1);
}
.portfolio-frame > picture { display: contents; }
.portfolio-frame img,
.portfolio-frame--depth img {
  width: auto; height: auto; max-width: 100%; max-height: 100%;
  aspect-ratio: auto; object-fit: contain; transform: none;
  box-shadow: 0 6px 18px rgba(42, 35, 28, .14);
}
.portfolio-link:hover .portfolio-frame { transform: translateY(-4px); box-shadow: 0 24px 44px rgba(42, 35, 28, .2); }
.portfolio-copy { padding-top: 1.1rem; }
@media (max-width: 1000px) { .portfolio-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px)  { .portfolio-grid { grid-template-columns: 1fr; } }

/* The filter re-flows the grid; tiles coming back in ease up rather than snap. */
@keyframes cs-tile-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
.portfolio-grid.is-filtering .portfolio-tile:not([hidden]) {
  animation: cs-tile-in 520ms cubic-bezier(.2,.7,.2,1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}

/* --- 20.6 smoother flow through the page ------------------------------------ */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  .chapter__body > *, .colophon__body { transition: opacity 600ms ease, transform 600ms cubic-bezier(.2,.7,.2,1); }
}
@media (prefers-reduced-motion: reduce) {
  .portfolio-grid.is-filtering .portfolio-tile { animation: none; }
  .portfolio-frame, .portfolio-tabs button { transition: none; }
}
/* The signature move reads over a shorter pinned span, so the showcase grid
   arrives sooner and the page keeps moving. */
#ch-terrain { --tts-span: 210svh; }

/* --- 20.7 audit results get room -------------------------------------------- */
#audit-result { padding: clamp(1.6rem, 3vw, 2.8rem) clamp(1.5rem, 3.2vw, 3rem); }
#audit-result h3 { margin-bottom: 1.1rem; }
#audit-findings { padding: .35rem clamp(1rem, 2vw, 1.6rem); }
#audit-findings article { padding: 1.15rem .35rem; }
#audit-findings article p + p { margin-top: .4rem; }
.score-grid { gap: clamp(1rem, 2vw, 1.6rem); }
.score-grid article { padding: clamp(1.5rem, 2.6vw, 2.4rem) clamp(1.4rem, 2.8vw, 2.6rem); }

/* --- 20.8 /applications "What we build": wide and short ----------------------
   Each sub-service stacks copy over its record card, and the card's nine rows
   run across in three columns instead of down one, so the table is a third of
   the height. */
:is(.page-applications, .page-web) #what-we-build .work-feature.sub-service { grid-template-columns: 1fr; gap: 1.25rem; }
:is(.page-applications, .page-web) #what-we-build .work-feature.sub-service > div:first-child { max-width: 72ch; }
:is(.page-applications, .page-web) #what-we-build .service-sample dl {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(1rem, 2.4vw, 2.2rem);
}
:is(.page-applications, .page-web) #what-we-build .service-sample dl > div { min-width: 0; }
@media (max-width: 900px) { :is(.page-applications, .page-web) #what-we-build .service-sample dl { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { :is(.page-applications, .page-web) #what-we-build .service-sample dl { grid-template-columns: 1fr; } }

/* --- 20.9 Auto Solar Service screens on /applications ----------------------- */
.ssa-gallery {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.2vw, 2rem); margin-top: clamp(1.5rem, 3vw, 2.5rem);
}
.ssa-gallery .app-shot { margin: 0; display: flex; flex-direction: column; }
.ssa-gallery .app-shot picture { display: block; }
.ssa-gallery .app-shot img { width: 100%; height: auto; display: block; border: 1px solid rgba(26, 23, 20, .16); box-shadow: 0 12px 26px rgba(42, 35, 28, .14); }
.ssa-gallery figcaption { font-size: .9rem; margin-top: .6rem; color: var(--muted); }
@media (max-width: 900px) { .ssa-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .ssa-gallery { grid-template-columns: 1fr; } }

/* --- 20.10 /automation/technology: a light diagram --------------------------
   The plate was a 34% espresso wash with 45% espresso boxes on top: mud on
   mud. Paper plate, near-white boxes, and the same ink and brass lines. */
.stack-diagram {
  background: #FAF6EE;
  border: 1px solid rgba(26, 23, 20, .16); border-radius: 4px;
  box-shadow: 0 16px 36px rgba(42, 35, 28, .1);
}
.stack-diagram svg rect:not([fill="none"]) { fill: #FFFDF8; stroke: rgba(26, 23, 20, .28); }
.stack-diagram svg rect[fill="none"]:not([stroke-dasharray]) { stroke: rgba(26, 23, 20, .32); }
.stack-diagram .note { color: #4A4239; }

/* --- 20.11 the audit form box gets an inset too (/web/seo-aeo, /web, /audit) ---
   The form plate and the two score cards had no padding in the preview theme,
   so "Your website", the input and "SEO Health" sat on the box edge. */
.audit-form { padding: clamp(1.25rem, 2.6vw, 2.25rem) clamp(1.25rem, 2.8vw, 2.5rem); }
.audit-form .note { margin-top: 1rem; }
.score-grid article h3 { margin-bottom: .6rem; }

/* --- 20.12 JobTrace on /web/seo-aeo, organised ------------------------------- */
.jobtrace-steps {
  list-style: none; padding: 0; margin: clamp(1.5rem, 3vw, 2.5rem) 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(1rem, 2vw, 1.75rem);
  counter-reset: none;
}
.jobtrace-steps li {
  background: #F4EEE3; border: 1px solid rgba(26, 23, 20, .16); border-top: 3px solid #8A5116;
  padding: clamp(1.3rem, 2.4vw, 2rem); border-radius: 2px;
  box-shadow: 0 14px 30px rgba(42, 35, 28, .08);
}
.jobtrace-steps__n {
  display: block; font-family: 'IBM Plex Mono', ui-monospace, monospace;
  font-size: .9rem; letter-spacing: .12em; color: #8A5116; margin-bottom: .7rem;
}
.jobtrace-steps h3 { margin-bottom: .5rem; }
.jobtrace-steps p { margin: 0; }
.jobtrace-section .actions { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
@media (max-width: 800px) { .jobtrace-steps { grid-template-columns: 1fr; } }
.title-page__sheet h1 { text-wrap: balance; }

/* --- 20.13 every audit looks like the one on /web/seo-aeo -------------------
   2026-09-28 (second pass). On /web the widget sat in the 479px right column of
   a .work-feature on the dark dusk ground, so the score cards were 227px wide
   and "AI Readiness" at 33.6px ran out of its box. The measure block now stacks,
   and the widget sits on the same paper panel, at the same widths and type
   sizes, as /web/seo-aeo and /audit. */
.page-web #measure .work-feature { grid-template-columns: 1fr; gap: clamp(1.5rem, 3vw, 2.5rem); }
.page-web #measure #audit {
  background: #EFE8DB; color: #1A1714;
  border: 1px solid rgba(26, 23, 20, .16); border-radius: 2px;
  padding: clamp(1.5rem, 3vw, 2.75rem);
  --text: #1A1714; --muted: #5E554B;
}
.page-web #measure #audit > h3, .page-web #measure #audit > p { color: #1A1714; max-width: 60ch; }
#audit-form, .score-grid { max-width: 820px; }
.score-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.score-grid article h3 { font-size: 1.28rem; line-height: 1.25; overflow-wrap: anywhere; }
@media (max-width: 520px) { .score-grid { grid-template-columns: 1fr; } }

/* --- 20.14 /work: every thumbnail centred, whole, and not zoomed --------------
   2026-09-28, Darrin: "fix all the images and thumbnails on showcase page, center
   them, zoom correctly". The cards used a 16:9 cover crop plus the parallax
   layer's scale(1.06), so every screenshot was cropped and then enlarged (the
   portrait DiegoTech assistant lost most of itself). Same treatment as the
   homepage grid (§20.5): one matted 16:10 frame, the capture centred at its own
   ratio, never scaled past the frame, and no parallax transform. */
.page-work .card-thumb {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 16 / 10; height: auto; overflow: hidden;
  padding: clamp(.6rem, 1.1vw, 1rem); margin-bottom: 1.1rem;
  background: #EDE4D4; border: 1px solid rgba(26, 23, 20, .14); border-radius: 2px;
}
.page-work .card-thumb > picture { display: contents; transform: none !important; }
.page-work .card-thumb img,
.page-work .card-thumb video {
  width: auto; height: auto; max-width: 100%; max-height: 100%;
  aspect-ratio: auto; object-fit: contain; object-position: center;
  transform: none !important;
  box-shadow: 0 6px 18px rgba(42, 35, 28, .14);
}
.page-work .card-thumb--clip video { height: 100%; width: auto; }

/* --- 20.15 /automation #auto-solar: image across, the five steps in a row ---- */
#auto-solar .work-feature { grid-template-columns: 1fr; gap: clamp(1.5rem, 3vw, 2.5rem); }
#auto-solar .app-shot { max-width: 60rem; margin: 0 auto; }
#auto-solar .app-shot img { width: 100%; height: auto; }
#auto-solar .process {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(.9rem, 1.8vw, 1.5rem); margin-bottom: 1.5rem;
}
#auto-solar .process li { padding-top: .9rem; border-top: 2px solid #8A5116; }
#auto-solar .process h3 { font-size: clamp(1.05rem, 1.3vw, 1.3rem); margin-bottom: .45rem; }
#auto-solar .process p { font-size: .98rem; line-height: 1.55; }
#auto-solar .work-feature > div:last-child > p { max-width: 70ch; }
@media (max-width: 1000px) { #auto-solar .process { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { #auto-solar .process { grid-template-columns: 1fr; } }

/* =============================================================================
   §21 · 2026-09-28, third pass (Darrin's review)
   ========================================================================== */

/* --- 21.1 footer: column labels +50%, logo +30% ------------------------------ */
.site-footer h2 { font-size: 16.92px; letter-spacing: .08em; }
.site-footer .brand { font-size: 30.16px; }

/* --- 21.2 /web: room above the competitor table ------------------------------ */
.page-web #measure #audit .score-grid { margin-bottom: clamp(2.75rem, 5.5vw, 4.5rem); }

/* --- 21.3 /applications: the three built applications side by side ---------- */
.built-apps {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1rem, 2.2vw, 1.75rem); margin-top: 1.25rem;
}
.built-apps article {
  background: #F4EEE3; border: 1px solid rgba(26, 23, 20, .16); border-top: 3px solid #8A5116;
  padding: clamp(1.3rem, 2.4vw, 2rem); border-radius: 2px; color: #1A1714;
}
.built-apps h4 { font-size: 1.3rem; margin: .2rem 0 .8rem; }
.built-apps li { font-size: .98rem; line-height: 1.5; margin-bottom: .45rem; }
@media (max-width: 960px) { .built-apps { grid-template-columns: 1fr; } }

/* --- 21.4 fewer "made by a machine" tells: labels in sentence case ----------
   The monospace UPPERCASE eyebrow on every block was the loudest one. Content
   labels now set in the body face, sentence case, brass; the header and
   buttons keep their own type. */
main :is(.eyebrow, .portfolio-category, .service-card-eyebrow, .status-label,
         .statement-folio, .jobtrace-steps__n, .service-sample > span,
         .app-shot figcaption, .card-thumb + .eyebrow) {
  font-family: inherit; text-transform: none; letter-spacing: .005em;
}
main .eyebrow, main .portfolio-category, main .service-card-eyebrow {
  font-size: .95rem; font-weight: 600; color: #8A5116;
}
:is(.chapter--dusk, .chapter--close) main .eyebrow { color: #D99A4E; }
.portfolio-tabs button { font-family: inherit; text-transform: none; letter-spacing: .01em; font-weight: 600; }

/* --- 21.5 scroll: a reading-progress rule, and content that rises as it arrives
   Pure CSS scroll-driven animation: no script, nothing runs where it is not
   supported, and nothing moves under prefers-reduced-motion. Entry-triggered
   ranges only, so a block never re-hides once it is fully on screen. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes cs-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
    @keyframes cs-rise { from { opacity: 0; transform: translateY(42px); } to { opacity: 1; transform: none; } }
    @keyframes cs-unveil { from { clip-path: inset(0 0 100% 0); opacity: .2; } to { clip-path: inset(0 0 0 0); opacity: 1; } }
    @keyframes cs-settle { from { transform: scale(1.035); filter: saturate(.8); } to { transform: none; filter: none; } }
    body::after {
      content: ""; position: fixed; z-index: 60; top: 0; left: 0; right: 0; height: 3px;
      background: linear-gradient(90deg, #8A5116, #D99A4E); transform-origin: 0 50%;
      animation: cs-progress linear both; animation-timeline: scroll(root);
      pointer-events: none;
    }
    /* No ID inside this :is(): one ID would lift the whole list to (1,x,x) and beat the
       filter re-entry animation on .portfolio-tile (the 2026-09-26 header-button trap). */
    :is(.portfolio-tile, .catalog-grid article, .work-grid article, .service-card,
        .built-apps article, .jobtrace-steps li, .ssa-gallery .app-shot, .process li,
        .feature-rows > article, .price-columns > *, .pricing-columns > *, .addition-columns > article) {
      animation: cs-rise linear both; animation-timeline: view();
      animation-range: entry 0% entry 90%;
    }
    .chapter__body .section > .container > h2,
    .section > .container > h2 {
      animation: cs-unveil linear both; animation-timeline: view();
      animation-range: entry 10% entry 100%;
    }
    .app-shot img, .photo-spread img, .editorial-photo img {
      animation: cs-settle linear both; animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}
main .colophon__lead { font-family: inherit; text-transform: none; letter-spacing: .005em; font-size: .95rem; font-weight: 600; }

/* --- 21.6 /work filter + pricing spacing ------------------------------------ */
.work-tabs { margin-bottom: 2.25rem; }
.work-grid article[hidden] { display: none; }
.work-grid.is-filtering article:not([hidden]) {
  animation: cs-tile-in 520ms cubic-bezier(.2,.7,.2,1) both;
  animation-delay: calc(var(--i, 0) * 45ms);
}
:is(.price-columns, .pricing-columns) + .feature-rows { margin-top: clamp(2.75rem, 5.5vw, 4.5rem); }

/* --- 21.7 more of the page answering to scroll ------------------------------
   Individual transform properties (scale/translate) only, so none of these
   fights the engine's own transforms or the entrance opacity. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    @keyframes cs-lift-away { to { translate: 0 -46px; scale: .975; } }
    @keyframes cs-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
    @keyframes cs-float-in { from { scale: .94; translate: 0 26px; } to { scale: 1; translate: 0 0; } }
    @keyframes cs-footer { from { translate: 0 60px; } to { translate: 0 0; } }
    /* the opening sheet lifts off the page as you leave it */
    .title-page__sheet { animation: cs-lift-away linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
    /* every section heading draws a brass rule under itself as the section passes */
    .section > .container > h2 { position: relative; }
    .section > .container > h2::after {
      content: ""; position: absolute; left: 0; bottom: -.45em; width: min(9rem, 40%); height: 2px;
      background: #8A5116; transform-origin: 0 50%;
      animation: cs-draw linear both; animation-timeline: view(); animation-range: entry 40% cover 45%;
    }
    /* framed screenshots float up into their mats (never scaled past 1) */
    .portfolio-frame, .page-work .card-thumb, .ssa-gallery .app-shot picture, .app-shot > picture {
      animation: cs-float-in linear both; animation-timeline: view(); animation-range: entry 0% cover 35%;
    }
    .site-footer .footer-grid { animation: cs-footer linear both; animation-timeline: view(); animation-range: entry 0% entry 100%; }
  }
}
