/* Seri Consulting Engineers — site stylesheet
   Tokens and per-ground contrast per spec §3.1; type scale §3.3; motion §3.5.
   Two Latin families only: Acumin Pro (Typekit kit xmf8ozp) + Source Serif 4 Italic. */

/* ---------- tokens ---------- */
:root {
  --ink: #14232b;
  --balm: #2e6b60;
  --balm-light: #6fa79b;
  --paper: #faf8f4;
  --muted: #5c6b72;        /* on paper  5.1:1 */
  --muted-onink: #8fa0a8;  /* on ink    6.0:1 */
  --hairline: #cdc6ba;
  --slate: #4d6975;

  --sans: "acumin-pro", "Acumin Fallback", "Helvetica Neue", Arial, sans-serif;
  --serif: "source-serif-4", Georgia, serif;
  --heb: "adobe-hebrew", "Frank Ruhl Libre", serif;

  --container: 1280px;
  --gutter: clamp(20px, 4vw, 56px);
  --ease: cubic-bezier(0, 0, .4, 1);
}

/* Metric-compatible fallback so Acumin's arrival barely reflows.
   Typekit serves font-display:auto; the kit setting is changed to swap when the domain is added. */
@font-face {
  font-family: "Acumin Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  size-adjust: 94%;
  ascent-override: 92%;
  descent-override: 24%;
}
/* The kit's adobe-hebrew face is served with Latin glyphs only until its character set is widened,
   so the Hebrew falls through to Frank Ruhl Libre (OFL), a revival of the same Frank-Ruehl genre,
   self-hosted and subset to the Hebrew block. Adobe Hebrew takes over the moment the kit serves it. */
@font-face {
  font-family: "Frank Ruhl Libre";
  src: url("assets/fonts/FrankRuhlLibre-hebrew.woff2") format("woff2"),
       url("assets/fonts/FrankRuhlLibre-hebrew.woff") format("woff");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0590-05FF, U+FB1D-FB4F;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--sans);
  font-weight: 400;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}
img, svg, canvas { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ol, ul { list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

:focus-visible { outline: 2px solid var(--balm-light); outline-offset: 3px; }
.chapter--paper :focus-visible { outline-color: var(--balm); }

.skip {
  position: absolute; left: 16px; top: -48px; z-index: 100;
  padding: 10px 14px; background: var(--ink); color: var(--paper); border-radius: 4px;
}
.skip:focus { top: 16px; }

.container { width: min(var(--container), 100% - 2 * var(--gutter)); margin-inline: auto; }

/* ---------- type scale (§3.3) ---------- */
.eyebrow {
  font-size: 13px; letter-spacing: .28em; text-transform: uppercase;
  font-weight: 400; color: var(--balm); line-height: 1.2;
}
.eyebrow--onink { color: var(--balm-light); }

.counter {
  font-size: 13px; letter-spacing: .28em; font-weight: 500; color: var(--muted);
  margin-bottom: 18px;
}
.counter--onink { color: var(--muted-onink); }

.h1 {
  font-size: clamp(44px, 7vw, 104px);
  line-height: .98; font-weight: 600; letter-spacing: -.025em;
  color: var(--ink);
  max-width: 14ch;
  margin: 18px 0 26px;
}
.h2 {
  font-size: clamp(32px, 4.2vw, 56px);
  line-height: 1.05; font-weight: 600; letter-spacing: -.015em;
  margin-bottom: 22px;
}
.h2--onink { color: var(--paper); }
.h3 { font-size: 28px; line-height: 1.2; font-weight: 500; margin-top: 22px; }

.lead { font-size: 22px; line-height: 1.45; font-weight: 300; max-width: 42ch; }
.lead--onink { color: var(--paper); }
.body { font-size: 18px; line-height: 1.55; max-width: 60ch; }
.body--onink { color: var(--paper); }
.caption { font-size: 14px; line-height: 1.4; color: var(--muted); margin-top: 12px; max-width: 60ch; }
.caption--onink { color: var(--muted-onink); }
.narrow { max-width: 46ch; }

/* ---------- fixed chrome ---------- */
.progress { position: fixed; inset: 0 0 auto 0; height: 2px; z-index: 60; background: transparent; pointer-events: none; }
.progress span { display: block; height: 100%; width: 0; background: var(--balm-light); }

.chrome {
  position: fixed; inset: 0 0 auto 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding: 18px var(--gutter);
  color: var(--ink);
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: blur(8px);
  transition: background-color 300ms var(--ease), color 300ms var(--ease);
}

.wordmark { display: inline-flex; align-items: center; gap: 10px; }
.wordmark-drop { width: 14px; height: 25px; fill: var(--balm); }
.wordmark-text { font-weight: 600; letter-spacing: .145em; font-size: 18px; }

.nav { display: flex; gap: 28px; font-size: 14px; letter-spacing: .04em; }
.nav a { opacity: .78; transition: opacity 140ms var(--ease), transform 140ms var(--ease); }
.nav a:hover { opacity: 1; transform: translateY(-1px); }

.cta {
  font-size: 14px; letter-spacing: .04em; font-weight: 500;
  padding: 10px 16px; border: 1px solid currentColor; border-radius: 999px;
  transition: transform 140ms var(--ease), background-color 140ms var(--ease), color 140ms var(--ease);
}
.cta:hover { transform: translateY(-2px); background: var(--balm); color: var(--paper); border-color: var(--balm); }
.cta.copied::after { content: " · copied"; font-weight: 300; }

.rail {
  position: fixed; right: 22px; top: 50%; transform: translateY(-50%); z-index: 50;
  display: flex; flex-direction: column; gap: 14px;
}
.rail a { width: 18px; height: 2px; background: var(--hairline); transition: width 300ms var(--ease), background-color 300ms var(--ease); }
.rail a.is-active { width: 34px; background: var(--balm); }

/* ---------- hero ---------- */
.hero {
  position: relative; min-height: clamp(560px, 100svh, 960px); display: grid; align-items: end;
  background: var(--paper); color: var(--ink); overflow: hidden;
  padding: 140px 0 56px;
}
.hero-media { position: absolute; inset: 0; }
.hero-img { width: 100%; height: 100%; object-fit: cover; transform-origin: 60% 50%; will-change: transform; }
/* Daylight photograph under a paper scrim: ink type on the left, image breathing on the right. */
.hero-scrim {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(250,248,244,.90) 0%, rgba(250,248,244,.74) 42%, rgba(250,248,244,.12) 100%),
              linear-gradient(0deg, rgba(250,248,244,.82) 0%, rgba(250,248,244,0) 42%);
}
.hero-copy { position: relative; z-index: 1; }
.facts { display: flex; flex-wrap: wrap; gap: 8px 0; font-size: 14px; letter-spacing: .06em; color: var(--ink); margin-top: 8px; }
.facts span + span::before { content: "·"; margin: 0 12px; color: var(--balm); }
/* The caption sits where the scrim is thinnest, so it carries its own paper plate and is set in ink. */
.hero-caption { position: absolute; right: var(--gutter); bottom: 20px; z-index: 1; font-size: 12px; letter-spacing: .04em; color: var(--ink); max-width: 46ch; text-align: right;
                padding: 6px 10px; background: rgba(250,248,244,.88); border-radius: 3px; }

/* ---------- chapters ---------- */
.chapter { position: relative; padding: clamp(88px, 11vw, 160px) 0; }
.chapter--paper { background: var(--paper); color: var(--ink); }
.chapter--ink { background: var(--ink); color: var(--paper); }

.two-col { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(32px, 5vw, 96px); align-items: start; }
.two-col--tight { gap: clamp(28px, 4vw, 72px); margin-top: 56px; }
.two-col--media-first .col-media { order: 0; }
.two-col--media-first .col-copy { order: 1; }
.col-media img { width: 100%; height: auto; border-radius: 4px; }
.col-media--narrow { max-width: 60%; }
/* A screenshot of a working interface is evidence, so it is shown whole and at full width:
   no push-in scale to crop it and no soften of its text. */
.col-media--screen { max-width: 100%; }
.col-media--screen img { width: 100%; border: 1px solid var(--hairline); border-radius: 4px; }
.col-media figure, figure.col-media { margin: 0; }
.narrow-col { max-width: 760px; }

.steps { margin: 40px 0 8px; display: grid; gap: 18px; }
.steps li { display: grid; grid-template-columns: 34px 1fr; column-gap: 14px; align-items: baseline; }
.steps-n { font-size: 13px; letter-spacing: .28em; font-weight: 500; color: var(--balm); }
.steps-t { font-size: 18px; font-weight: 500; }
.steps-d { grid-column: 2; font-size: 16px; color: var(--muted); line-height: 1.5; }
.steps--compact { grid-template-columns: repeat(4, auto); gap: 24px; margin: 28px 0; }
.steps--compact li { grid-template-columns: auto auto; }

.track { margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--hairline); }
.track--onink { border-top-color: var(--slate); }
.track .eyebrow { margin-bottom: 12px; }

.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 40px; }
.stats dt { font-size: clamp(40px, 4.6vw, 72px); font-weight: 300; line-height: 1; letter-spacing: -.02em; }
.stats .unit { font-size: .42em; margin-left: .12em; color: var(--balm); }
.stats dd { font-size: 14px; color: var(--muted); margin-top: 8px; }

.status {
  margin-top: 28px; padding: 14px 16px; border-left: 2px solid var(--balm);
  font-size: 15px; color: var(--ink); max-width: 60ch;
}

.standards { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0 8px; }
.standards span { font-size: 13px; letter-spacing: .08em; padding: 6px 10px; border: 1px solid var(--hairline); border-radius: 999px; color: var(--muted); }

/* ---------- 01 cloud: no frame, floats over the paper and bleeds to the right edge ---------- */
.cloud { position: relative; margin-right: calc(-1 * var(--gutter)); }
.cloud-stage { position: relative; aspect-ratio: 5 / 4; min-height: 420px; background: transparent; overflow: visible; }
.cloud-poster { position: absolute; inset: 6% 0; width: 100%; height: 88%; object-fit: cover; transition: opacity 800ms var(--ease);
                -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, #000 55%, transparent 100%);
                        mask-image: radial-gradient(ellipse 70% 60% at 50% 50%, #000 55%, transparent 100%); }
/* The cloud is a 600 x 200 m rectangle of samples, so without a mask it ends in a hard edge.
   Feathering the canvas lets the points dissolve into the paper instead of sitting in a box. */
.cloud-canvas { position: absolute; inset: -10% -6%; width: 112%; height: 120%; cursor: grab; touch-action: pan-y;
                -webkit-mask-image: radial-gradient(ellipse 76% 66% at 50% 50%, #000 42%, rgba(0,0,0,.72) 70%, transparent 100%);
                        mask-image: radial-gradient(ellipse 76% 66% at 50% 50%, #000 42%, rgba(0,0,0,.72) 70%, transparent 100%); }
.cloud-canvas:active { cursor: grabbing; }
.cloud-stage.is-live .cloud-poster { opacity: 0; }
.cloud .caption { margin-left: 4px; }
.caption-hint { font-size: 12px; line-height: 1.4; color: var(--muted); margin: 4px 0 0 4px; }
.cloud .caption, .cloud .caption-hint { position: relative; z-index: 1; background: var(--paper); }

/* ---------- 02 architecture ---------- */
.arch { width: 100%; height: auto; margin: 56px 0 8px; }
.arch-lines path { stroke: var(--balm-light); }
.arch-node circle { fill: var(--paper); stroke: var(--balm); stroke-width: 2; }
.arch-node--accent circle { fill: var(--balm); stroke: var(--balm); }
.arch-node text { font-family: var(--sans); text-anchor: middle; }
.arch-node .arch-label { fill: var(--ink); font-size: 15px; font-weight: 500; dominant-baseline: central; }
.arch-node .arch-sub { fill: var(--muted); font-size: 11px; font-weight: 400; letter-spacing: .06em; dominant-baseline: central; }
.arch-node--accent .arch-label { fill: var(--paper); }
.arch-line { stroke-dasharray: 120; stroke-dashoffset: 120; }

/* ---------- 04 mesh ground (plate is tinted balm-on-paper) ---------- */
.chapter--mesh { overflow: hidden; }
.mesh-bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .30; pointer-events: none; }
.chapter--mesh .container { position: relative; z-index: 1; }
.lines { display: grid; gap: 26px; margin: 40px 0 28px; }
.lines li { display: grid; grid-template-columns: 140px 1fr; gap: 20px; align-items: baseline; }
.lines-k { font-size: 13px; letter-spacing: .28em; text-transform: uppercase; color: var(--balm); }
.lines-v { font-size: 17px; line-height: 1.55; color: var(--ink); max-width: 62ch; }

/* ---------- 05 experience ---------- */
.table-wrap { overflow-x: auto; margin-top: 40px; -webkit-overflow-scrolling: touch; }
.xp { width: 100%; min-width: 980px; border-collapse: collapse; font-size: 15px; line-height: 1.45; }
.xp th { text-align: left; font-size: 12px; letter-spacing: .22em; text-transform: uppercase; font-weight: 500; color: var(--muted); padding: 0 18px 14px 0; border-bottom: 1px solid var(--ink); }
.xp td { vertical-align: top; padding: 18px 18px 18px 0; border-bottom: 1px solid var(--hairline); }
.xp td:first-child { font-weight: 500; }
.xp td:nth-child(4) { white-space: nowrap; color: var(--muted); }

/* ---------- 06 people ---------- */
.people { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(32px, 6vw, 96px); margin-top: 40px; }
.person-img { width: 100%; max-width: 420px; height: auto; }
.person-img--small { max-width: 240px; }
.person-title { font-size: 15px; letter-spacing: .04em; color: var(--muted); margin-top: 6px; }
.person-creds { font-size: 14px; letter-spacing: .11em; color: var(--balm); margin: 10px 0 18px; }
.person .caption .eyebrow { display: inline-block; margin-bottom: 6px; }
.link { display: inline-block; margin-top: 18px; font-size: 14px; letter-spacing: .06em; border-bottom: 1px solid var(--balm); padding-bottom: 2px; }

/* ---------- contact ---------- */
.contact .two-col { align-items: start; margin-top: 0; }
.addr { font-style: normal; display: grid; gap: 22px; padding-top: 12px; }
.addr p { display: grid; grid-template-columns: 150px 1fr; gap: 16px; align-items: baseline; }
.addr-k { font-size: 13px; letter-spacing: .28em; text-transform: uppercase; color: var(--balm); }
.addr-v { font-size: 17px; line-height: 1.55; color: var(--ink); }
.addr-v a { border-bottom: 1px solid var(--hairline); }
.addr-v a:hover { border-bottom-color: var(--balm); }

/* ---------- footer: the one saturated ground ---------- */
.footer { background: var(--balm); color: var(--paper); padding: 72px 0 40px; }
.footer .container { display: grid; gap: 22px; justify-items: start; }
.footer-mark { width: 120px; height: auto; }
.footer-sentence { font-family: var(--serif); font-style: italic; font-size: 20px; line-height: 1.45; max-width: 46ch; color: var(--paper); }
.footer-heb { display: flex; align-items: baseline; gap: 16px; }
.heb { font-family: var(--heb); font-size: 30px; color: var(--paper); }
.gloss { font-family: var(--serif); font-style: italic; font-size: 15px; color: #d7e5e1; }
.footer-legal { font-size: 13px; letter-spacing: .04em; color: #d7e5e1; }

/* ---------- reveal pre-state ----------
   Hidden only when JS is running (html.js is set by an inline script in <head>); GSAP then
   animates to the final state, and main.js restores visibility if GSAP fails to load.
   Without JS the page is fully visible. */
html.js [data-reveal], html.js [data-reveal-group] > * { opacity: 0; transform: translateY(12px); }

/* ---------- responsive ---------- */
@media (max-width: 1100px) {
  .col-media--narrow { max-width: 100%; }
  .lines li { grid-template-columns: 1fr; gap: 8px; }
}
@media (max-width: 860px) {
  .nav { display: none; }
  .rail { display: none; }
  .two-col { grid-template-columns: minmax(0, 1fr); }
  .cloud-stage { min-height: 0; overflow: hidden; }
  .two-col--media-first .col-media { order: 1; }
  .two-col--media-first .col-copy { order: 0; }
  .people { grid-template-columns: 1fr; }
  .steps--compact { grid-template-columns: 1fr 1fr; }
  .hero { padding-top: 120px; }
  .hero-scrim {
    background: linear-gradient(90deg, rgba(250,248,244,.94) 0%, rgba(250,248,244,.86) 55%, rgba(250,248,244,.5) 100%),
                linear-gradient(0deg, rgba(250,248,244,.9) 0%, rgba(250,248,244,0) 50%);
  }
  .hero-caption { position: static; text-align: left; margin: 28px var(--gutter) 0; }
  .facts { font-size: 13px; }
  .addr p { grid-template-columns: 1fr; gap: 6px; }
  .arch { margin: 36px 0 0; }
  .arch-node text { font-size: 30px; }
  .arch-node .arch-sub { display: none; }
}
@media (max-width: 480px) {
  .h1 { font-size: 44px; }
  .stats { grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
  .stats dt { font-size: 34px; }
  .cta { padding: 8px 12px; font-size: 13px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal], [data-reveal-group] > * { opacity: 1 !important; transform: none !important; }
  .hero-img, [data-pushin] { transform: none !important; }
  .cloud-canvas { display: none !important; }
  .cloud-stage.is-live .cloud-poster { opacity: 1; }
  .arch-line { stroke-dashoffset: 0; }
  * { transition-duration: 0ms !important; }
}


/* Works: photographs of the founders' built work under the civil chapter. */
.works-eyebrow { margin-top: clamp(40px, 6vw, 88px); }
.works { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(20px, 3vw, 40px); margin-top: 20px; }
.work { margin: 0; }
.work img { display: block; width: 100%; height: auto; border: 1px solid var(--hairline); }
.work .caption { margin-top: 10px; }
@media (max-width: 720px) { .works { grid-template-columns: 1fr; } }


/* ==========================================================================
   motion-text.css — merged into style.css; the four motion sheets were four
   render-blocking requests for one stylesheet's worth of rules.
   ========================================================================== */
/* Seri Consulting Engineers — motion-text.css
   WHY. The type on this page reports measurements, so the type is set out rather than faded in.
   Three verbs only: a heading is DRAWN by a wipe travelling at constant rate, a station counter
   SETTLES onto a ruled datum, a statistic STEPS through discrete detents because a measurement
   has a resolution. Nothing here interpolates a number and nothing here writes to a text node.

   WHY THE PRE-STATES ARE DOUBLE-SCOPED. Every hidden state below requires BOTH html.js (set by
   the inline script in <head>, absent under ?static=1) AND html.m-text (added by motion-text.js
   only once every gate has passed). If this stylesheet ships without its script, if GSAP fails,
   if the reader asks for reduced motion, or if the page is rendered for headless review, none of
   these rules match and the page rests in its correct static state. Nothing can be stranded.

   The permanent additions this module leaves behind are the counter's column rule and datum tick
   and the key-column rule in chapter 04 — hairline weight, paper-led, no new hue. */

/* ---------- 1. ink wipe (DRAW) ----------
   m-text-mask is added only when the browser reports mask-image support; without it the headings
   keep their data-reveal attribute and main.js reveals them as it does today. */
@supports (mask-image: linear-gradient(#000, #000)) or (-webkit-mask-image: linear-gradient(#000, #000)) {
  html.js.m-text.m-text-mask .h1,
  html.js.m-text.m-text-mask .h2 {
    /* The 2.2em feather trails the front rather than leading it. Leading it left the first 2.2em
       of every heading opaque at --ink-x:0 — up to 229px of ghosted h1 sitting on screen before
       the wipe had run. Trailing it, the pre-state is fully clear and nothing is shown early. */
    -webkit-mask-image: linear-gradient(90deg, #000 0 calc(var(--ink-x, 0%) - 2.2em), transparent var(--ink-x, 0%));
            mask-image: linear-gradient(90deg, #000 0 calc(var(--ink-x, 0%) - 2.2em), transparent var(--ink-x, 0%));
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
  }
  /* Once written, the mask is dropped outright rather than parked at its last value, so the
     resting heading is exactly what style.css paints and no compositing survives the entrance. */
  html.js.m-text.m-text-mask .h1.m-text-inked,
  html.js.m-text.m-text-mask .h2.m-text-inked {
    -webkit-mask-image: none; mask-image: none;
  }
}

/* ---------- 2. station block (SETTLE + DRAW) ----------
   Both marks sit inside .counter's existing 18px margin-bottom (rule 8-9px below the box, tick
   3-10px), so the block carries no layout cost and nothing on the page reflows. */
html.js.m-text .counter { opacity: 0; transform: translateY(6px); }

html.m-text .counter { position: relative; }

html.m-text .counter::after {
  content: ""; position: absolute; left: 0; top: calc(100% + 8px);
  width: 100%; height: 1px; background: var(--hairline);
  transform: scaleX(var(--ct-rule, 0)); transform-origin: 0 50%;
  pointer-events: none;
}
html.m-text .counter::before {
  content: ""; position: absolute; left: 0; top: calc(100% + 3px);
  width: 1px; height: 7px; background: var(--balm);
  transform: scaleY(var(--ct-tick, 0)); transform-origin: 50% 100%;
  pointer-events: none;
}

/* ---------- 3. statistics (STEP) ----------
   Three detents, hard class swaps, no tween between them: the figure fills in at increasing
   density like a point cloud rather than counting up to itself. */
html.js.m-text .stats dt { clip-path: inset(100% 0 0 0); }
html.js.m-text .stats dt.m-text-lvl1 { clip-path: inset(64% 0 0 0); }
html.js.m-text .stats dt.m-text-lvl2 { clip-path: inset(38% 0 0 0); }
html.js.m-text .stats dt.m-text-lvl3 { clip-path: none; }
html.js.m-text .stats dd { opacity: 0; }

html.m-text .stats > div { position: relative; }

/* The one line that rides the fill front to each detent, then leaves. A pseudo-element rather
   than an injected span: nothing is added to the document, nothing has to be removed again on
   three separate paths, and the <dl><div> content model stays what the markup says it is. */
html.m-text .stats > div::before {
  content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 1px;
  background: var(--balm-light); opacity: var(--edge-o, 0);
  transform: translate3d(0, var(--edge, 0px), 0);
  pointer-events: none;
}

/* ---------- 4. chapter 04 key column (DRAW) ----------
   The rule is exactly the width of the .lines-k grid track, so the key column reads as ruled. */
html.js.m-text .lines-k { opacity: 0; transform: translateY(6px); }
html.js.m-text .lines-v { opacity: 0; transform: translateY(10px); }

html.m-text .lines li { position: relative; }
html.m-text .lines li::after {
  content: ""; position: absolute; left: 0; top: 1.45em;
  width: 140px; height: 1px; background: var(--balm); opacity: .45;
  transform: scaleX(var(--ln-key, 0)); transform-origin: 0 50%;
  pointer-events: none;
}

/* Mirrors style.css's single-column collapse of .lines li at the same breakpoint. Collapsed, the
   key line box ends at 20.2px and the value's first line box starts at 28.2px (8px row gap), so
   the rule is set at 24.3px — inside that gap. At 1.9em (34.2px) it struck through the ascenders
   of the value's first line on every viewport at or below 1100px. */
@media (max-width: 1100px) {
  html.m-text .lines li::after { width: 100%; top: 1.35em; }
}

/* ---------- 5. hero facts ----------
   Higher specificity than style.css's .facts span + span::before, so the separator can take a
   per-span custom property and be ticked from hairline to balm ahead of the span it marks.
   Resting value is var(--balm) — byte-identical to the page as it stands, and so is the fallback,
   so a separator whose property is never written keeps the shipped colour rather than a pale one. */
html.js.m-text .facts span { opacity: 0; transform: translateY(4px); }
html.m-text .facts span + span::before { color: var(--sep-col, var(--balm)); }

/* ---------- 6. reduced motion ----------
   Belt and braces: motion-text.js never adds .m-text under reduced motion, so these rules should
   be unreachable. They exist so a preference changed after load still cannot hide anything. */
@media (prefers-reduced-motion: reduce) {
  html.js.m-text .counter,
  html.js.m-text .lines-k,
  html.js.m-text .lines-v,
  html.js.m-text .facts span,
  html.js.m-text .stats dd { opacity: 1 !important; transform: none !important; }
  html.js.m-text .stats dt { clip-path: none !important; }
  html.js.m-text.m-text-mask .h1,
  html.js.m-text.m-text-mask .h2 { -webkit-mask-image: none !important; mask-image: none !important; }
  html.m-text .counter::after,
  html.m-text .lines li::after { transform: scaleX(1) !important; }
  html.m-text .counter::before { transform: scaleY(1) !important; }
  html.m-text .stats > div::before { display: none !important; }
}


/* ==========================================================================
   motion-marks.css — merged into style.css; the four motion sheets were four
   render-blocking requests for one stylesheet's worth of rules.
   ========================================================================== */
/* Seri Consulting Engineers — motion-marks.css
   WHY. This file carries only the resting geometry the marks module needs: the places a
   drawn mark has to sit, and the one pre-state that style.css can no longer supply.

   WHY .arch-leg exists. style.css declares .arch-line { stroke-dasharray:120;
   stroke-dashoffset:120 } UNSCOPED, so the legs are invisible whether or not JS runs, and
   main.js animates the same class. motion-marks.js renames the four legs to .arch-leg in
   its init, which hands the element over cleanly — main.js's NodeList comes back empty and
   the unscoped pre-state stops applying. The pre-state is re-declared here scoped to
   html.js, so ?static=1 and JS-off render the legs finished.

   Everything else a mark needs (opacity, dash offsets, arrow scale) is written as an inline
   style by the JS after its gates, never here, so a page without the .js class is complete
   on first paint. British English throughout. */

/* The arch legs: pre-state only while JS is actually driving them. The dash is 1000 rather
   than style.css's 120 because the legs are now 132 units long, and a 120 dash leaves a
   12-unit sliver showing before the JS writes the real length off the path data. */
html.js .arch-leg { stroke-dasharray: 1000; stroke-dashoffset: 1000; }
@media (prefers-reduced-motion: reduce) {
  html.js .arch-leg { stroke-dashoffset: 0; }
}

/* containing blocks for the overlays. Only under html.js, so the static page is untouched. */
html.js figure.col-media { position: relative; }
html.js .steps { position: relative; }

/* The caption becomes the dimension note, so it needs the clearance a note has under a
   dimension line rather than the 12px a label has under a photograph, and it has to paint
   above the overlay whatever the drawn line does. Scoped to .m-marks-set, a class the JS adds
   only once it is actually going to draw, so a reduced-motion or no-GSAP page is not left
   with the clearance and nothing in it. */
html.m-marks-set figure.col-media > .caption { margin-top: 26px; position: relative; z-index: 1; }

/* the dimension overlay: never hit-tests, never clipped, never scaled by the reset */
.m-marks-plate {
  position: absolute; left: 0; top: 0;
  overflow: visible; max-width: none;
  pointer-events: none;
}

/* the levelling line down (or under) a list of steps — it remains once drawn */
.m-marks-rule {
  position: absolute; pointer-events: none;
  background: var(--hairline); opacity: .45;
}
.m-marks-rule--v { top: 0; bottom: 0; left: 17px; width: 1px; transform-origin: 50% 0%; }
.m-marks-rule--h { left: 0; right: 0; bottom: -9px; height: 1px; transform-origin: 0% 50%; }

/* the travelling observation on the arch: balm, and gone once the leg is recorded */
.m-marks-dot { fill: var(--balm); }

/* a scribed circle starts at twelve o'clock, the way a compass does */
.m-marks-scribe { transform: rotate(-90deg); transform-box: fill-box; transform-origin: center; }


/* ==========================================================================
   motion-pointer.css — merged into style.css; the four motion sheets were four
   render-blocking requests for one stylesheet's worth of rules.
   ========================================================================== */
/* Seri — motion-pointer.css
   WHY: the pointer layer is a viewfinder, not a cursor toy. Everything here is a
   response to where the reader is pointing, and every response is one of the three
   verbs: a rule that DRAWS at a constant rate, a bracket that SETTLES onto a target,
   a state that STEPS. Nothing here loops, nothing follows the cursor across the
   viewport, nothing persists once the pointer has moved on, and nothing hides
   content — every element this file touches is already legible before it runs.

   WHY the media gate: a hover state that a touch reader cannot reach is not on the
   page. On a coarse pointer this file contributes nothing at all, and no information
   is lost, because none of it carries information. Under prefers-reduced-motion the
   companion script never adds a class, and style.css already zeroes every transition
   duration, so the resting state is simply the page as authored.

   WHY the class prefixes: four motion modules load side by side; "m-pointer-" is
   this module's namespace so none of them can collide. */

@media (hover: hover) and (pointer: fine) {

  /* ---------- 1. acquisition brackets ------------------------------------
     One shared overlay for the whole document. Fixed, so it is written in the
     same coordinate space that getBoundingClientRect reports and never affects
     layout; pointer-events:none so it can never take a hit from the element
     beneath it. The z-index sits below the fixed chrome (50) and the progress
     line (60): a viewfinder does not cover the instrument panel. */
  .m-pointer-acq {
    position: fixed;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    z-index: 40;
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms var(--ease);
    will-change: transform, opacity;
  }
  .m-pointer-acq[data-on="1"] {
    opacity: .85;
    transition-duration: 140ms;
  }

  /* Each bracket is an L: two 1 px segments with 12 px arms, drawn as two borders
     of a 12x12 box so there is one node per corner rather than two. */
  .m-pointer-acq span {
    position: absolute;
    width: 12px;
    height: 12px;
    border: 0 solid var(--balm-light);
    transform: translate(0, 0);
    transition: transform 160ms var(--ease);
  }
  /* Scoped to the parent on purpose: the reset above is ".m-pointer-acq span"
     (0-1-1), which would otherwise out-rank a bare ".m-pointer-acq-tl" (0-1-0)
     and hold every arm at border-width 0 — invisible brackets. */
  .m-pointer-acq .m-pointer-acq-tl { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; }
  .m-pointer-acq .m-pointer-acq-tr { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; }
  .m-pointer-acq .m-pointer-acq-bl { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; }
  .m-pointer-acq .m-pointer-acq-br { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }

  /* The overlay box is set 6 px outside the target; the brackets then travel 6 px
     inward and come to rest exactly on the target's true corners. SETTLE. */
  .m-pointer-acq[data-on="1"] .m-pointer-acq-tl { transform: translate(6px, 6px); }
  .m-pointer-acq[data-on="1"] .m-pointer-acq-tr { transform: translate(-6px, 6px); }
  .m-pointer-acq[data-on="1"] .m-pointer-acq-bl { transform: translate(6px, -6px); }
  .m-pointer-acq[data-on="1"] .m-pointer-acq-br { transform: translate(-6px, -6px); }

  /* ---------- 2. the pen finishes its stroke -----------------------------
     A reversible tween is a decoration. A stroke that always completes in the
     direction of travel is a pen: it arrives left to right and it leaves left to
     right, because the nib does not reverse. The origin swap on exit is the whole
     idea. DRAW in, DRAW out. */
  .m-pointer-pen { position: relative; }
  .m-pointer-pen::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: var(--mp-rule-b, -3px);
    width: var(--mp-rule-w, 100%);
    height: 1px;
    background: var(--balm);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 180ms var(--ease);
    pointer-events: none;
  }
  .m-pointer-ruled::after { transform: scaleX(1); }
  .m-pointer-unruling::after {
    transform-origin: 100% 50%;
    transform: scaleX(0);
    transition-duration: 140ms;
  }

  /* Per-target offsets. .link and .addr-v a already carry a border-bottom, so the
     rule sits 3 px under it as a second, shorter line rather than doubling it —
     the way a drawing carries an extension tick under a leader. Both containing
     blocks are the padding box, so -4px on a 1 px border reads as a 3 px gap. */
  .nav a.m-pointer-pen    { --mp-rule-b: -2px; }
  .wordmark.m-pointer-pen { --mp-rule-b: -4px; }
  .link.m-pointer-pen     { --mp-rule-b: -4px; --mp-rule-w: 32px; }
  .addr-v a.m-pointer-pen { --mp-rule-b: -4px; --mp-rule-w: 32px; }

  /* ---------- 3. the calliper on the CTA ---------------------------------
     Two jaws closing on the pill. style.css owns .cta:hover (translateY, balm
     fill) and is not touched here; the only addition to the button itself is a
     containing block for the two jaws and for the confirmation sweep. */
  .m-pointer-cta { position: relative; }
  .m-pointer-cal {
    position: absolute;
    top: 50%;
    width: 1px;
    height: 14px;
    margin-top: -7px;
    background: var(--balm);
    opacity: 0;
    pointer-events: none;
    transition: opacity 120ms var(--ease), transform 200ms var(--ease);
  }
  .m-pointer-cal--l { left: -10px; }
  .m-pointer-cal--r { right: -10px; }
  .m-pointer-cta:hover .m-pointer-cal { opacity: .9; }
  .m-pointer-cta:hover .m-pointer-cal--l { transform: translateX(6px); }
  .m-pointer-cta:hover .m-pointer-cal--r { transform: translateX(-6px); }

  /* The confirmation sweep runs off the .copied class, not off a click handler,
     so keyboard activation by Enter or Space is confirmed exactly as a mouse
     click is. .copied::after (" · copied") is untouched. */
  .m-pointer-sweep {
    position: absolute;
    inset: 0;
    overflow: visible;
    opacity: 0;
    pointer-events: none;
  }
  .m-pointer-sweep rect {
    fill: none;
    stroke: var(--balm-light);
    stroke-width: 1;
  }

  /* ---------- 4. table rows ----------------------------------------------
     The marginal mark an engineer puts against the line being checked. Balm at
     4% keeps every cell far above 4.5:1, and the rule is an inset box-shadow
     rather than a positioned pseudo-element because position:relative on a <tr>
     is unreliable. No row is dimmed, no column is hidden: the whole table stays
     readable while one line is marked. This is the row's only treatment — the
     script does not also frame it, both because a row would then carry two marks
     for one hover and because .table-wrap clips horizontally while the brackets
     do not. */
  .xp tbody td {
    transition: background-color 140ms var(--ease), box-shadow 140ms var(--ease);
  }
  .xp tbody td:first-child { box-shadow: inset 2px 0 0 transparent; }
  .xp tbody tr:hover td { background: color-mix(in srgb, var(--balm) 4%, transparent); }
  .xp tbody tr:hover td:first-child { box-shadow: inset 2px 0 0 var(--balm); }

}

/* ---------- 5. figures -------------------------------------------------------
   Deliberately absent. Survey products are georeferenced and true; tilting one
   into false perspective on hover would contradict the only claim the page makes.
   The acquisition brackets are the entire hover treatment for a plate. */

/* ---------- reduced motion ---------------------------------------------------
   The script bails before adding a single class, so this is belt and braces: if
   the preference is changed after load, the overlay leaves the page rather than
   sitting frozen mid-travel. Nothing here has a resting visible state to lose. */
@media (prefers-reduced-motion: reduce) {
  .m-pointer-acq { display: none !important; }
  .m-pointer-cal, .m-pointer-sweep { opacity: 0 !important; }
  .m-pointer-pen::after { display: none !important; }
}


/* ==========================================================================
   motion-scroll.css — merged into style.css; the four motion sheets were four
   render-blocking requests for one stylesheet's worth of rules.
   ========================================================================== */
/* Seri — motion-scroll.css : the traverse.
   ------------------------------------------------------------------------
   WHY this file exists, not what it does.

   A survey traverse is a sequence of stations booked in order, each one ruled
   onto the sheet before anything is written on it. This module supplies the
   metronome of that traverse: the chapter datum rule that opens every station,
   and the resting graduations (progress ticks, rail staff, table extent) that
   let a reader see how far the survey has been carried. Everything here is
   either DRAW (constant rate, ease:'none' — a pen or a tape moves at one
   speed) or SETTLE (--ease, zero overshoot — a needle coming to rest). There
   is no third motion in this file.

   Every pre-state that HIDES something is scoped `html.js`, because the page is
   also served with ?static=1, which withholds that class. Without it nothing
   below is hidden, so a headless review and a no-JS reader both see the
   finished page. The reduced-motion block at the foot restores the same end
   state at first paint, before the JS bail can run, so there is no flash.

   Every class invented here is prefixed m-scroll- so no sibling module can
   collide with it. Colours come only from the existing tokens.
   ------------------------------------------------------------------------ */


/* --- 1. CHAPTER DATUM RULE — beat one of the three-beat routine -----------
   The sheet is ruled before the station is booked. Seven of these rest
   permanently, one at the head of every chapter, at 55% of a hairline.
   z-index 2 lifts it over #software's .mesh-bg, which is positioned at auto.

   The inset is .container's own resolved margin, not the raw gutter: above
   1392 px the container stops growing and the two part company, so a rule set
   at the gutter would end up overhanging the type it is meant to datum by up
   to 264 px a side. A datum that lines up with nothing is not a datum. */
section.chapter::before {
  content: "";
  position: absolute;
  top: 0;
  left: max(var(--gutter), (100% - var(--container)) / 2);
  right: max(var(--gutter), (100% - var(--container)) / 2);
  height: 1px;
  background: var(--hairline);
  opacity: .55;
  z-index: 2;
  pointer-events: none;
  transform: scaleX(1);
  transform-origin: 0 50%;
}
html.js section.chapter::before { transform: scaleX(var(--ms-sheet, 0)); }


/* --- 2. SCAN-LINE CAPTURE ------------------------------------------------
   clip-path only. main.js scrubs `scale` on any of these carrying
   [data-pushin], so touching transform here would make a second owner of one
   property; an inset composes with the scale instead of fighting it. */
html.js img.m-scroll-scan {
  clip-path: inset(0 0 calc(var(--ms-scan, 1) * 100%) 0);
}
.m-scroll-scanhost { position: relative; }
.m-scroll-scanline {
  position: absolute;
  height: 2px;
  background: var(--balm-light);
  opacity: .8;
  pointer-events: none;
  z-index: 3;
  transform: translateY(0);
}


/* --- 3. PROGRESS AS COVERAGE --------------------------------------------
   Graduations hung under the progress rule at each chapter's true document
   offset. A tick flips to balm as the bar passes it and never flips back:
   coverage acquired does not un-acquire. */
.m-scroll-tick {
  position: absolute;
  top: 2px;
  width: 1px;
  height: 4px;
  background: var(--hairline);
  pointer-events: none;
  transition: background-color 120ms var(--ease);
}
.m-scroll-tick.is-acquired { background: var(--balm); }


/* --- 4. THE RAIL AS A GRADUATED STAFF ------------------------------------
   Five subdivisions hung in the gaps between the six chapter marks, shorter
   and fainter than a mark, so the rail reads as a graduated staff rather than
   six loose dashes. They rest; nothing here moves.

   There is deliberately NO second active indicator. style.css already states
   the active chapter twice over — .rail a.is-active goes 18 px to 34 px AND
   from hairline to balm — so a travelling bracket beside it would be a third
   reading of one fact. Dies with .rail below 860 px. */
.m-scroll-grad {
  position: absolute;
  left: 3px;
  width: 12px;
  height: 1px;
  background: var(--hairline);
  opacity: .45;
  pointer-events: none;
}


/* --- 5. HERO SCROLL CUE --------------------------------------------------
   A tape dropped once and withdrawn the moment it has been read. */
.m-scroll-cue {
  position: absolute;
  left: var(--gutter);
  bottom: 20px;
  width: 1px;
  height: 28px;
  background: var(--hairline);
  transform: scaleY(0);
  transform-origin: 50% 0;
  pointer-events: none;
  z-index: 1;
}


/* --- 6. MESH DRIFT -------------------------------------------------------
   Oversize and offset so that ±24 px of drift can never uncover the ground. */
html.js #software img.mesh-bg { height: 112%; top: -6%; }


/* --- 7. TABLE ------------------------------------------------------------
   The head rule is drawn rather than declared: the real border is held
   transparent, a line is drawn across it at constant rate, then the border is
   restored and the line removed in the same frame. Rows land as a steady log.
   .xp is min-width:980px inside an overflow-x scroller that offers no
   wayfinding of its own; the graduation beneath it is the only indication a
   touch reader gets that the columns continue. */
.m-scroll-tablewrap { position: relative; }
html.js .xp thead th { border-bottom-color: transparent; }
html.js .xp.m-scroll-ruled thead th { border-bottom-color: var(--ink); }
html.js .xp tbody tr { opacity: 0; transform: translateY(6px); }

.m-scroll-headrule {
  position: absolute;
  left: 0;
  height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}
.m-scroll-xscroll {
  position: relative;
  height: 1px;
  background: var(--hairline);
  margin-top: 14px;
  overflow: hidden;
  opacity: 0;
}
.m-scroll-xscroll > span {
  display: block;
  height: 1px;
  width: 100%;
  background: var(--balm);
  transform: scaleX(0);
  transform-origin: 0 50%;
}
.m-scroll-xscroll[hidden] { display: none; }


/* --- 8. FOOTER TEXT ------------------------------------------------------
   The balm ground. motion-marks owns svg.footer-mark; only the type is set
   here, in paper and the existing #d7e5e1. The Hebrew settles on x, never on
   letter-spacing, which would reflow the glyph run mid-animation. */
html.js .footer-sentence,
html.js .footer-legal,
html.js .footer-heb .gloss { opacity: 0; transform: translateY(8px); }
html.js .footer-heb .heb { opacity: 0; transform: translateX(4px); }


/* --- REDUCED MOTION ------------------------------------------------------
   Written in CSS as well as in the JS bail, because the JS bail cannot run
   before first paint and a pre-state must never be visible even for a frame.
   The end state below is the correct finished page, not a degraded one. */
@media (prefers-reduced-motion: reduce) {
  section.chapter::before { transform: scaleX(1) !important; }
  html.js img.m-scroll-scan { clip-path: none !important; }
  html.js #software img.mesh-bg { height: 100%; top: 0; }
  html.js .xp thead th { border-bottom-color: var(--ink) !important; }
  html.js .xp tbody tr,
  html.js .footer-sentence,
  html.js .footer-legal,
  html.js .footer-heb .heb,
  html.js .footer-heb .gloss {
    opacity: 1 !important;
    transform: none !important;
  }
}
