/* ============================================================
   SANIX — Editorial Technology
   Warm paper is the dominant plane; graphite carries the hero,
   the engineer section and the contact close.
   ============================================================ */

@font-face { font-family:"GeneralSans"; src:url("../fonts/general-sans-400.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"GeneralSans"; src:url("../fonts/general-sans-600.woff2") format("woff2"); font-weight:600; font-display:swap; }
@font-face { font-family:"Inter"; src:url("../fonts/inter-400.woff2") format("woff2"); font-weight:400; font-display:swap; }
@font-face { font-family:"Inter"; src:url("../fonts/inter-500.woff2") format("woff2"); font-weight:500; font-display:swap; }
@font-face { font-family:"PlexMono"; src:url("../fonts/plex-mono-400.woff2") format("woff2"); font-weight:400; font-display:swap; }

:root {
  /* ---------- the obsidian identity ----------
     Warm near-black surfaces, copper light, teal glints; the paper tier
     warms to bone so the light pages belong to the same material world.
     The variable NAMES kept their old life: --mint is the primary accent
     (now copper) and --cyan the secondary (now teal) — renaming them would
     touch every selector for no reader-visible gain. */
  --paper: #efe7da;
  --paper-2: #e8dcc9;
  --ink: #191512;
  --ink-2: #564a3f;
  /* #8a867d before: 2.99:1 on paper, which is not a quiet tier, it is text
     nobody can read. This clears 4.5:1 — the small mono labels, figure captions
     and datelines all sit at this weight, and they are the majority of the type
     on the site by count. */
  --ink-3: #6f6155;
  --graphite: #14110f;
  --graphite-2: #1b1714;
  --ivory: #f1eae0;
  --ivory-2: #bcb1a3;
  --ivory-3: #948779;

  --mint: #d98e4f;
  --cyan: #7fc0ab;
  --amber: #e0a468;

  --rule: rgba(25, 21, 18, .22);
  --rule-l: rgba(240, 233, 223, .18);

  --font-display: "GeneralSans", system-ui, sans-serif;
  --font-body: "Inter", system-ui, sans-serif;
  --font-mono: "PlexMono", ui-monospace, Menlo, monospace;

  /* ---------- motion vocabulary ----------
     One set of curves and durations for the whole site. Before this, micro
     interactions were all `.18s ease` — the browser default curve at a
     duration short enough to read as a jump rather than a movement, which is
     the single most common reason an otherwise careful page feels cheap.

     entrance  settles long and late — for things arriving on scroll
     mask      slower still, for the heading reveal, which travels further
     micro     hover and focus: quick, but eased, never linear */
  --ease-entrance: cubic-bezier(.215, .61, .355, 1);
  --ease-mask: cubic-bezier(.16, 1, .3, 1);
  --ease-micro: cubic-bezier(.32, .72, 0, 1);
  --dur-micro: .28s;
  --dur-2: .45s;
  --dur-3: .9s;

  --maxw: 1520px;
  --pad: 4vw;
  /* One horizontal inset for every container. `100%` inside a custom property
     is substituted as a token and resolves against each element, so full-bleed
     sections and wrapped ones share the same left edge at any viewport width.
     Before this, wide screens had three different left edges. */
  --inset: max(var(--pad), calc((100% - var(--maxw)) / 2));
}

*, *::before, *::after { box-sizing: border-box; }
/* the sticky masthead would otherwise cover whatever an anchor jumps to */
section[id], [id] { scroll-margin-top: 76px; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ---------- moving between pages ----------
   A multi-page site flashes white between documents and drops the reader at
   the top of a fresh layout. Cross-document view transitions remove the flash
   without a router, a framework or a single line of JavaScript: the browser
   holds the outgoing frame, paints the incoming one underneath, and animates
   between them.

   Kept to 220ms on purpose. The point is to cover the seam, not to perform a
   transition — anything longer and the navigation stops feeling immediate,
   which is the opposite of what this is for. */
@view-transition { navigation: auto; }

/* The masthead is the same object on every page, so it is named and therefore
   carried across rather than cross-faded with the body underneath it. */
.masthead { view-transition-name: masthead; }

::view-transition-old(root) {
  animation: vt-out 160ms cubic-bezier(.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: vt-in 220ms cubic-bezier(.22, 1, .3, 1) both;
}
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px); } }

@media (prefers-reduced-motion: reduce) {
  /* still no flash, just no movement */
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 1ms; }
  ::view-transition-new(root) { transform: none; }
}

/* ---------- animation driven by the scroll itself ----------
   These run on `animation-timeline`, which means the browser advances them from
   the scroll position on the compositor rather than from a script on the main
   thread. No listener, no rAF, no work in the frame budget — which is the whole
   reason to use them here rather than an animation library. The measured page
   already holds 60fps with zero long tasks, and nothing added below can change
   that, because none of it runs where long tasks happen.

   The one-shot entrance reveals elsewhere in this file stay time-based on
   purpose. Tying an entrance to scroll position means it plays backwards when
   the reader scrolls up, and a headline that un-writes itself is a gimmick.
   Scroll drives the things that are genuinely continuous: progress, and depth. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* how far through the document the reader is, as a hairline under the
       masthead — the only always-on indicator on the page */
    .masthead::after {
      content: "";
      position: absolute;
      left: 0; right: 0; bottom: -1px;
      height: 2px;
      background: var(--mint);
      transform: scaleX(0);
      transform-origin: 0 50%;
      animation: scroll-progress linear both;
      animation-timeline: scroll(root block);
    }

  }
}
@keyframes scroll-progress { to { transform: scaleX(1); } }

/* Parallax on the cinematic backdrops was written here and then deleted. Two
   reasons, both already recorded further down this file: those layers sit
   behind a heavy veil, so the movement is not actually visible, and animating a
   full-section image layer was once measured as the biggest scroll cost on the
   page. A cheaper way to move something invisible is still not worth doing.

   The page's scroll-linked motion is already carried by the things that earn
   it: the hero scrubs its video, and the stage crosses the frame. */

body {
  margin: 0;
  position: relative;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  /* `overflow-x: hidden` turns the body into a scroll container, which silently
     kills `position: sticky` on the masthead. `clip` trims the same overflow
     without creating one. */
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}
/* film grain — the one texture the whole system shares */
body::after {
  content: "";
  /* Absolute, not fixed. A fixed full-viewport texture has to be re-composited
     over the whole screen on every scroll frame; as part of the document it is
     painted into the normal scrolling layer and costs nothing. */
  position: absolute;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23n)'/%3E%3C/svg%3E");
}

h1, h2, h3 { margin: 0; font-family: var(--font-display); font-weight: 600; letter-spacing: -.025em; line-height: 1.05; }
p { margin: 0; }
img, video { max-width: 100%; display: block; }
a { color: inherit; }

.mono {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .24em;
  text-transform: uppercase;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: var(--paper); padding: 12px 18px;
  font-family: var(--font-mono); font-size: .7rem; letter-spacing: .18em; text-transform: uppercase;
}
.skip:focus { left: 12px; top: 12px; }

/* ---------- masthead ---------- */
.masthead {
  position: sticky;
  top: 0;
  z-index: 80;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 20px;
  padding: 18px var(--inset);
  border-bottom: 1px solid var(--rule);
  /* A solid bar rather than a blurred one. backdrop-filter has to re-read and
     blur the backdrop on every scroll frame; measured at 28% janky frames
     against 5% without it. An opaque background reads just as cleanly. */
  background: var(--paper);
}
/* the bar sits over dark sections too, so it inverts once past the first one */
.masthead.on-dark {
  background: var(--graphite);
  border-bottom-color: var(--rule-l);
  color: var(--ivory);
}
.masthead.on-dark .logo { color: var(--ivory); }
.masthead.on-dark .issue { color: var(--ivory-3); }
.masthead.on-dark nav a { color: var(--ivory-2); }
.masthead.on-dark nav a:hover { color: var(--ivory); }
.masthead .logo {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  text-decoration: none;
  letter-spacing: -.01em;
}
.masthead .issue { color: var(--ink-3); }
.masthead nav { display: flex; gap: 34px; }
.masthead nav a {
  font-size: .85rem; color: var(--ink-2); text-decoration: none;
  position: relative; padding-bottom: 3px;
  transition: color var(--dur-micro) var(--ease-micro);
}
/* a rule that draws in from the left and retracts the same way. scaleX on a
   pseudo-element is a compositor job; animating border or width is not. */
.masthead nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--dur-micro) var(--ease-micro);
}
.masthead nav a:hover { color: var(--ink); }
.masthead nav a:hover::after, .masthead nav a:focus-visible::after { transform: scaleX(1); }
.nav-trigger { display: none; }
@media (prefers-reduced-motion: reduce) {
  .masthead nav a::after { transition: none; }
}

/* ---------- primary navigation ----------
   Material 3's navigation model, in this site's palette: an inactive
    destination is a word, hover and focus raise a state layer behind it — a
    veil of ink at a few per cent — and the destination you are on carries a
    tinted container, the same mint the rest of the site uses for the live
    thing. Nothing here transforms, lifts or follows the pointer: the row is an
    index, and an index that reacts to a cursor passing over it on the way down
    the page is a distraction, not a feature. State-layer percentages follow
    Material 3 (8 % hover, 12 % pressed). */
.masthead nav {
  position: relative;
  align-items: center;
  gap: 2px;
}
.masthead nav a {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 7px 13px;
  border-radius: 999px;
  color: var(--ink-2);
  background-color: transparent;
  transition: background-color .16s var(--ease-micro),
              color .16s var(--ease-micro);
}
/* The underline belonged to an earlier vocabulary; the container replaces it. */
.masthead nav a::after { display: none; }

.masthead nav a:hover { color: var(--ink); background-color: rgba(20, 20, 19, .08); }
.masthead nav a:active { background-color: rgba(20, 20, 19, .12); }
.masthead nav a:focus-visible {
  color: var(--ink);
  background-color: rgba(20, 20, 19, .08);
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

.masthead nav a.is-current {
  color: var(--ink);
  font-weight: 500;
  background-color: rgba(217, 142, 79, .45);
}
.masthead nav a.is-current:hover,
.masthead nav a.is-current:focus-visible { background-color: rgba(217, 142, 79, .62); }

.masthead.on-dark nav a { color: var(--ivory-2); }
.masthead.on-dark nav a:hover { color: var(--ivory); background-color: rgba(241, 239, 233, .10); }
.masthead.on-dark nav a:active { background-color: rgba(241, 239, 233, .16); }
.masthead.on-dark nav a:focus-visible {
  color: var(--ivory);
  background-color: rgba(241, 239, 233, .10);
  outline-color: var(--ivory);
}
.masthead.on-dark nav a.is-current { color: var(--ivory); background-color: rgba(217, 142, 79, .18); }
.masthead.on-dark nav a.is-current:hover,
.masthead.on-dark nav a.is-current:focus-visible { background-color: rgba(217, 142, 79, .26); }

/* Between 901px and 1200px the row shares the strip with the language
   control, so the containers tighten rather than wrap. */
@media (min-width: 901px) and (max-width: 1200px) {
  .masthead nav a { padding: 6px 10px; font-size: .8rem; }
}

/* ---------- language switcher ----------
   Flags are added by i18n.js only on platforms whose font actually fuses
   the regional-indicator pair into one glyph. Where it does not, the
   markup falls back to the mono labels alone rather than printing a bare
   letter pair next to the label it duplicates. */
.lang { position: relative; margin-left: 4px; }
.lang-trigger {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 32px; padding: 6px 9px;
  border: 1px solid var(--rule); border-radius: 999px;
  background: transparent; color: var(--ink-2); cursor: pointer;
  font-family: var(--font-mono); font-size: .64rem; letter-spacing: .12em;
}
.lang-trigger:hover, .lang.open .lang-trigger { color: var(--ink); border-color: var(--ink-3); }
.lang-menu {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 2;
  display: none; min-width: 112px; padding: 6px;
  background: var(--paper); border: 1px solid var(--rule); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0,0,0,.12);
}
.lang.open .lang-menu { display: grid; gap: 2px; }
.lang .flag {
  margin-right: 6px; font-size: .95rem; line-height: 1; letter-spacing: 0;
  /* full-saturation emoji fights a paper-and-graphite palette; the inactive
     one steps back so the pair reads as one control, not two stickers */
  filter: saturate(.5) contrast(.9);
  opacity: .6;
  transition: filter var(--dur-micro) var(--ease-micro), opacity var(--dur-micro) var(--ease-micro);
}
.lang-btn.on .flag, .lang-btn:hover .flag { filter: none; opacity: 1; }
.lang.has-flags .lang-btn { display: inline-flex; align-items: center; }
.lang-btn {
  appearance: none;
  background: none;
  border: 0;
  width: 100%; padding: 8px 9px; text-align: left; border-radius: 6px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .12em;
  color: var(--ink-3);
  border-bottom: 1px solid transparent;
  transition: color var(--dur-micro) var(--ease-micro), border-color var(--dur-micro) var(--ease-micro);
}
.lang-btn:hover { color: var(--ink); }
.lang-btn.on { color: var(--ink); background: var(--paper-2); border-bottom-color: transparent; }
.lang-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.masthead.on-dark .lang-btn { color: var(--ivory-3); }
.masthead.on-dark .lang-btn:hover { color: var(--ivory); }
.masthead.on-dark .lang-btn.on { color: var(--ivory); border-bottom-color: var(--ivory); }
.masthead.on-dark .lang-btn:focus-visible { outline-color: var(--ivory); }
.masthead.on-dark .lang-trigger { color: var(--ivory-2); border-color: var(--rule-l); }
.masthead.on-dark .lang-menu { background: var(--graphite); border-color: var(--rule-l); }
.masthead.on-dark .lang-btn.on { background: rgba(255,255,255,.1); border-bottom-color: transparent; }

/* ============================================================
   01 — HERO
   ============================================================ */
/* The hero is a composition, not a size contest.

   It used to run the headline at 10vw over three lines, which filled about
   seventy per cent of the viewport and left nothing else room to exist — and
   it split the sentence into an ivory half and a grey half, which read as if
   the more important half had been disabled. Scale alone is the most common
   way a page tries to look expensive and the most reliable way it fails.

   Now: one tone, a measure that holds four or five words a line, display
   tracking tightened as caps at this size require, and enough left over for
   the kicker, the lede, the actions and the index rule to be part of the
   picture. */
.hero {
  position: relative;
  background: var(--graphite);
  color: var(--ivory);
  overflow: hidden; overflow: clip;
  display: flex;
  align-items: center;
  min-height: min(780px, 92vh);
}
.hero-copy {
  position: relative; z-index: 3; width: 100%;
  padding: 104px var(--inset) 100px;
  max-width: 74ch;
}
.hero .kicker {
  color: var(--ivory-3); margin-bottom: 30px;
  display: flex; gap: 30px; align-items: baseline;
  /* a two-word label row that wraps reads as a mistake, and Spanish is longer
     than English, so it must be allowed to run past the copy measure */
  flex-wrap: nowrap;
}
/* the label itself is what wraps, not the row */
.hero .kicker span { white-space: nowrap; }
.hero h1 {
  font-weight: 600;
  text-transform: uppercase;
  font-size: clamp(2.5rem, 6.2vw, 6.2rem);
  line-height: .96;
  /* caps at display size are loose by default; this is not optional */
  letter-spacing: -.028em;
  max-width: 13ch;
}
/* one sentence, one colour. The break is a line break, not a demotion. */
.hero h1 .l2 { display: block; color: inherit; }
.hero .lede {
  max-width: 46ch; margin-top: 36px;
  font-size: clamp(1rem, 1.32vw, 1.1rem); line-height: 1.6; color: #d8d6cf;
}
.hero .lede-2 { max-width: 50ch; margin-top: 16px; font-size: .9rem; line-height: 1.65; color: var(--ivory-3); }
.hero-ctas { display: flex; gap: 40px; margin-top: 40px; flex-wrap: wrap; }
.hero-ctas a {
  color: var(--ivory);
  text-decoration: none;
  font-size: .9rem;
  border-bottom: 1px solid rgba(241, 239, 233, .4);
  padding-bottom: 7px;
  transition: border-color var(--dur-micro) var(--ease-micro), color var(--dur-micro) var(--ease-micro);
}
.hero-ctas a:hover { border-color: var(--mint); color: var(--mint); }
.hero-ctas a.ghost { color: var(--ivory-3); border-color: transparent; }
.hero-ctas a.ghost:hover { color: var(--ivory); border-color: rgba(241, 239, 233, .4); }
.hero .baseline {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 4;
  border-top: 1px solid var(--rule-l);
  display: flex; justify-content: space-between; gap: 12px;
  padding: 16px var(--inset);
  color: var(--ivory-3);
}

/* ============================================================
   Section scaffolding
   ============================================================ */
.wrap { padding-left: var(--inset); padding-right: var(--inset); }
.sec-head {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--pad);
  align-items: baseline;
  margin-bottom: 34px;
}
.sec-head .mono { color: var(--ink-3); }
.sec-head h2 { font-size: clamp(2.2rem, 5.2vw, 4.8rem); }
.sec-head h2 em { font-style: normal; font-weight: 400; color: inherit; }
.sec-intro {
  max-width: 52ch;
  color: var(--ink-2);
  margin: 0 0 52px calc(33.333% + 1.33vw);
  font-size: 1.02rem;
}

/* ============================================================
   02 — SELECTED INDEPENDENT SYSTEMS
   ============================================================ */
.systems { padding-top: 88px; padding-bottom: 64px; }
.project-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(18px, 2.2vw, 34px); margin-top: 10px;
}
.project-card {
  /* Two tokens, because the accent does two jobs. The 4px spine is a graphic
     and answers to nobody; the ordinal is text at 11px on ivory, where the
     same hue lands at 1.9:1. --accent-ink is that hue darkened to clear AA. */
  --accent: #89b9a4; --accent-ink: #467560;
  position: relative; display: flex; flex-direction: column; min-width: 0;
  color: var(--ink); text-decoration: none; overflow: hidden;
  border: 1px solid rgba(20,20,19,.2); border-radius: 12px; background: #f3f0e8;
  box-shadow: 0 1px 0 rgba(255,255,255,.7) inset;
  transition: transform .28s var(--ease-micro), border-color .28s ease, box-shadow .28s ease;
}
.project-card.grants { --accent: #68a7ae; --accent-ink: #417479; }
.project-card.table { --accent: #9bb968; --accent-ink: #5b7235; }
.project-card.pliegos { --accent: #5584c9; --accent-ink: #3a6cb6; }
.project-card.pliegos .stagevis { background:#f2f5f9; }
.project-card.pliegos .stagevis img.poster { object-fit:contain; object-position:center; }
.project-card::before { content:""; position:absolute; inset:0 auto 0 0; width:4px; z-index:3; background:var(--accent); }
.project-card:hover, .project-card:focus-visible {
  transform: translateY(-5px); border-color: rgba(20,20,19,.38);
  box-shadow: 0 20px 50px rgba(20,20,19,.11);
}
.project-card:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* the loop surface */
.stagevis {
  position: relative;
  aspect-ratio: 1200 / 750;
  overflow: hidden; overflow: clip;
  border-radius: 3px;
  background: #0d0f10;
}
.project-card .stagevis { aspect-ratio: 16 / 9; border-radius: 0; border-bottom: 1px solid rgba(20,20,19,.16); }
.project-card .stagevis::after {
  content:""; position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(180deg, transparent 68%, rgba(5,8,9,.24));
}
.stagevis video,
.stagevis img.poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.stagevis video { opacity: 0; transition: opacity .5s ease; }
.stagevis.playing video { opacity: 1; }

.project-card__body { display:grid; grid-template-columns:1fr auto; gap:12px 18px; padding:clamp(22px,3vw,36px); flex:1; }
.project-card__body .idx { font-family:var(--font-mono); font-size:.68rem; letter-spacing:.12em; color:var(--accent-ink); }
.project-card__body h3 { grid-column:1/-1; font-size:clamp(1.8rem,3.2vw,3.1rem); margin:4px 0 0; }
.project-card__body .desc { grid-column:1/-1; color:var(--ink-2); max-width:46ch; margin:0 0 16px; }
/* the description, the state and the action are one block: on a phone it is
   simply the rest of the card; in the desktop row it is what opens and
   closes, as one measured height rather than three things toggling */
.project-card__more { grid-column:1/-1; display:grid; grid-template-columns:1fr auto; gap:12px 18px; align-content:end; }
.project-card__state { align-self:end; color:var(--ink-3); }
.project-card__cta { align-self:end; justify-self:end; font-size:.86rem; border-bottom:1px solid currentColor; padding-bottom:4px; }
.project-card__cta::after { content:" ↗"; display:inline-block; transition:transform .2s ease; }
.project-card:hover .project-card__cta::after { transform:translate(3px,-3px); }

/* ============================================================
   03 — ABOUT THE ENGINEER
   ============================================================ */
.engineer { background: var(--graphite); color: var(--ivory);
  padding: 96px var(--inset) 88px; }
.engineer .kicker { color: var(--ivory-3); margin-bottom: 40px; display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.engineer .lead { font-size: clamp(2.2rem, 5.6vw, 5.4rem); max-width: 20ch; }
/* ---------- emphasis, not demotion ----------

   Every display headline on this site used to set half its sentence in a grey
   two steps down from the rest. "One person. [The full lifecycle of complex
   systems.]" — with the bracketed half, the substantial half, greyed out. It
   reads as if that part had been disabled, and it was the single most
   damaging thing on the page: it happened in the hero, the practice lead, the
   thesis, the About hero and every cinematic band.

   Emphasis in editorial typography is carried by weight, not by turning the
   lights down. `em` now drops from 600 to 400 at the same colour: the tonal
   shift survives, the sentence stays one sentence, and nothing looks broken.
   The contact close keeps the mint, where a single accent on a single line is
   a deliberate highlight rather than a habit. */
.engineer .lead em { font-style: normal; font-weight: 400; color: inherit; }
/* Copy on the left, a figure of the work on the right. The section argues that
   one person carries every layer, so the image is the person doing it rather
   than a diagram claiming it. */
.engineer .cols { margin-top: 52px; }
.engineer .practice-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: 5vw;
  align-items: start;
}
.engineer .practice-cols .body { max-width: 62ch; }
/* Beside the argument, on the right.

   No frame: a bordered rectangle on a dark page announces itself as a picture
   placed there. The plate is masked on all four sides so it dissolves into the
   same graphite it sits on, and it is graded down to that graphite rather than
   lit brighter than it. What is left is the hands and the signal traces
   emerging from the page. */
.engineer .practice-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .82fr);
  gap: 4vw;
  align-items: center;
}
.engineer .practice-cols .body { max-width: 60ch; }
.practice-figure {
  position: relative;
  margin: 0;
  aspect-ratio: 4 / 3;
  border: 0;
  overflow: visible;
}
.practice-figure img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: 44% 48%;
  filter: brightness(.98) contrast(1.03) saturate(.95);
  -webkit-mask-image:
    linear-gradient(90deg, transparent 0%, #000 14%, #000 90%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 12%, #000 88%, transparent 100%);
  mask-image:
    linear-gradient(90deg, transparent 0%, #000 14%, #000 90%, transparent 100%),
    linear-gradient(180deg, transparent 0%, #000 12%, #000 88%, transparent 100%);
  -webkit-mask-composite: source-in;
  mask-composite: intersect;
}
@media (max-width: 900px) {
  .engineer .practice-cols { grid-template-columns: 1fr; gap: 30px; }
  .practice-figure { aspect-ratio: 3 / 2; }
}
.engineer .body p { color: var(--ivory-2); max-width: 58ch; margin-bottom: 1.3em; }
.engineer .body p b { color: var(--ivory); font-weight: 400; }
.engineer .confidential {
  font-family: var(--font-mono);
  font-size: .62rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #6d6b64;
  max-width: 56ch;
  line-height: 2.1;
  margin-top: 40px;
  padding-top: 26px;
  border-top: 1px solid rgba(241, 239, 233, .12);
}
.pull { margin-top: 72px; border-top: 1px solid var(--rule-l); padding-top: 48px; }
.pull blockquote { margin: 0; font-family: var(--font-display); font-weight: 600; font-size: clamp(1.7rem, 4.2vw, 4rem); letter-spacing: -.02em; line-height: 1.1; max-width: 24ch; }
.pull blockquote b { color: var(--mint); font-weight: 600; }

/* experience index */
.exp { background: var(--graphite-2); color: var(--ivory);
  padding: 80px var(--inset) 88px; border-top: 1px solid var(--rule-l); }
.exp .kicker { color: var(--ivory-3); margin-bottom: 44px; }
.exp-cols { columns: 2; column-gap: 6vw; }
.exp-item { break-inside: avoid; display: flex; align-items: baseline; gap: 22px; padding: 11px 0; border-bottom: 1px solid rgba(241, 239, 233, .1); }
.exp-item .n { font-family: var(--font-mono); font-size: .58rem; letter-spacing: .2em; color: #6d6b64; }
.exp-item span:last-child { font-family: var(--font-display); font-weight: 400; font-size: clamp(1rem, 1.5vw, 1.35rem); letter-spacing: -.01em; color: #d8d6cf; }
.exp-item.hi span:last-child { color: var(--ivory); font-weight: 600; }

/* ============================================================
   04 — TECHNICAL RESPONSIBILITY
   ============================================================ */
.resp { padding-top: 88px; padding-bottom: 80px; }
.resp .sec-head { margin-bottom: 48px; }
.r-rows { columns: 2; column-gap: 5vw; }
.r-row { break-inside: avoid; display: grid; grid-template-columns: 64px 1fr; gap: 20px; padding: 22px 0; border-top: 1px solid var(--rule); align-items: baseline; }
.r-row .mono { color: var(--ink-3); }
.r-row h3 { font-size: clamp(1.25rem, 2vw, 1.7rem); }
.r-row p { color: var(--ink-2); margin-top: 10px; font-size: .92rem; }
.r-row:nth-of-type(even) h3 { color: #3c3a36; }

/* ============================================================
   05 — THESIS
   ============================================================ */
.thesis { background: var(--paper-2); padding: 170px var(--pad); border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }
.thesis .inner { display: grid; grid-template-columns: 1fr 5fr; gap: var(--pad); }
.thesis .mono { color: var(--ink-3); padding-top: 1.4rem; }
.thesis .pre { color: var(--ink-3); font-size: .85rem; margin-bottom: 26px; max-width: 44ch; }
.thesis h2 { font-size: clamp(2.4rem, 6.4vw, 6.2rem); line-height: 1.02; max-width: 18ch; }
.thesis h2 em { font-style: normal; font-weight: 400; color: inherit; }
.thesis .reveal { margin-top: 56px; font-family: var(--font-display); font-weight: 400; font-size: clamp(1.3rem, 2.8vw, 2.7rem); line-height: 1.3; letter-spacing: -.01em; color: var(--ink-2); max-width: 30ch; }
.thesis .reveal u { text-decoration: none; border-bottom: 2px solid #7a5230; }

/* ============================================================
   06 — CAPABILITIES
   ============================================================ */
.caps { padding-top: 88px; padding-bottom: 80px; }
.caps .sec-head { margin-bottom: 50px; }
.acc { border-top: 1px solid var(--rule); }
.acc-row { border-bottom: 1px solid var(--rule); padding: 0; }
.acc-row summary {
  list-style: none;
  cursor: pointer;
  padding: 24px 0;
  display: grid;
  grid-template-columns: 70px 1fr 30px;
  align-items: baseline;
  gap: 3vw;
}
.acc-row summary::-webkit-details-marker { display: none; }
.acc-row summary .mono { color: var(--ink-3); }
.acc-row summary h3 { font-size: clamp(1.6rem, 3.4vw, 3.2rem); transition: color var(--dur-micro) var(--ease-micro); }
.acc-row summary:hover h3 { color: var(--ink-2); }
.acc-row .sign { font-family: var(--font-display); font-size: 1.6rem; color: var(--ink-3); text-align: right; }
/* the sign is generated, so it tracks [open] instead of being static text —
   and it stays out of the translation surface, which is right for punctuation */
.acc-row .sign::after { content: "+"; }
.acc-row[open] .sign::after { content: "\2212"; }
.acc-row[open] .sign { color: var(--ink); }
.acc-row .detail { color: var(--ink-2); max-width: 56ch; margin: 0 0 26px calc(70px + 3vw); font-size: .95rem; }
/* the concrete responsibilities under each capability — one level of hierarchy,
   so the abstraction is what you read and the specifics are what you open */
.acc-items {
  list-style: none;
  margin: 0 0 30px calc(70px + 3vw);
  padding: 0;
  columns: 2;
  column-gap: 4vw;
}
.acc-items li { break-inside: avoid; padding: 0 0 20px; max-width: 46ch; }
.acc-items h4 {
  margin: 0 0 4px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: .98rem;
  letter-spacing: -.01em;
}
.acc-items p { color: var(--ink-2); font-size: .88rem; line-height: 1.55; }

/* ============================================================
   07 — NOTES
   ============================================================ */
.notes { padding-bottom: 88px; }
.note {
  display: grid;
  grid-template-columns: 70px 1fr auto;
  gap: 3vw;
  align-items: baseline;
  padding: 26px 0;
  border-top: 1px solid var(--rule);
  text-decoration: none;
  color: var(--ink);
}
.note:last-child { border-bottom: 1px solid var(--rule); }
.note .mono { color: var(--ink-3); }
.note h3 { font-size: clamp(1.25rem, 2.5vw, 2.3rem); max-width: 32ch; transition: color var(--dur-micro) var(--ease-micro); }
.note:hover h3 { color: var(--ink-2); }
.note time { font-family: var(--font-mono); font-size: .62rem; letter-spacing: .2em; color: var(--ink-3); }

/* ============================================================
   08 — CONTACT
   ============================================================ */
.contact { background: var(--graphite); color: var(--ivory);
  padding: 104px var(--inset) 88px; }
.contact h2 { font-size: clamp(1.9rem, 3.7vw, 3.7rem); line-height: 1.08; max-width: 24ch; text-wrap: balance; }
.contact h2 em { font-style: normal; color: var(--mint); }
.contact .sub { margin-top: 34px; color: var(--ivory-3); max-width: 52ch; font-size: .98rem; }
.contact .row { display: flex; justify-content: space-between; align-items: flex-end; margin-top: 60px; flex-wrap: wrap; gap: var(--pad); }
.contact a { color: var(--ivory); text-decoration: none; font-size: 1.1rem; border-bottom: 1px solid rgba(241, 239, 233, .4); padding-bottom: 8px; }
.contact a { transition: color var(--dur-micro) var(--ease-micro), border-color var(--dur-micro) var(--ease-micro); }
.contact a:hover { border-color: var(--mint); color: var(--mint); }
.contact .mono { color: var(--ivory-3); }

/* ============================================================
   CASE STUDY PAGES
   ============================================================ */
.chero { position: relative; color: var(--ivory); min-height: min(460px, 62vh); display: flex; align-items: flex-end; overflow: hidden; overflow: clip; }
.chero.markets { background: radial-gradient(150% 120% at 70% 0%, #152018 0%, #0e120e 55%, #0a0b0a 100%); }
.chero.grants { background: radial-gradient(140% 120% at 28% 0%, #241a10 0%, #170f09 55%, #0e0a07 100%); }
.chero.table { background: radial-gradient(140% 120% at 66% 6%, #2a1e13 0%, #1a130d 55%, #100c08 100%); }
.chero .paths { position: absolute; inset: 0; width: 100%; height: 100%; }
.chero .inner { position: relative; z-index: 2; width: 100%; padding: 0 var(--inset) 56px; }
.crumbs { display: flex; gap: 30px; color: var(--ivory-3); margin-bottom: 30px; flex-wrap: wrap; }
.crumbs a { text-decoration: none; }
.crumbs a:hover { color: var(--ivory); }
.chero h1 { font-size: clamp(2.4rem, 7vw, 7rem); line-height: .94; max-width: 10ch; }
.chero .lede { margin-top: 30px; max-width: 44ch; font-size: clamp(1rem, 1.5vw, 1.12rem); color: #c9cec9; }
.metarow { display: flex; gap: 40px; margin-top: 30px; flex-wrap: wrap; }
/* #7e897f measured 4.37:1 against the lightest part of these hero grounds,
   which is a label nobody with ordinary eyesight reads comfortably and a fail
   under AA. Same green cast, two steps up: 5.45:1 on the same pixels. */
.metarow .m b { display: block; font-family: var(--font-mono); font-size: .58rem; letter-spacing: .22em; color: #8f9a90; text-transform: uppercase; margin-bottom: 6px; font-weight: 400; }
.metarow .m span { font-size: .9rem; color: var(--ivory); }

.brief { padding: 56px var(--inset) 48px; display: grid; grid-template-columns: 1fr 2fr; gap: var(--pad); }
.brief .mono { color: var(--ink-3); padding-top: 1rem; }
.brief p { font-family: var(--font-display); font-weight: 400; font-size: clamp(1.2rem, 2.2vw, 2rem); line-height: 1.35; letter-spacing: -.015em; max-width: 42ch; }
.brief p em { font-style: normal; color: var(--ink); }

.figure { position: relative; width: 100%; height: clamp(260px, 30vw, 420px); overflow: hidden; background: #0d0f10; }
.figure video, .figure img.poster { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.figure video { opacity: 0; transition: opacity .5s ease; }
.figure.playing video { opacity: 1; }
.figcap { display: flex; justify-content: space-between; gap: 20px; padding: 16px var(--inset) 22px; color: var(--ink-3); font-size: .8rem; border-bottom: 1px solid var(--rule); }

.body2col { padding: 56px var(--inset); display: grid; grid-template-columns: 1fr 1.15fr; gap: 5vw; align-items: start; }
.body2col h2 { font-size: clamp(1.5rem, 2.8vw, 2.5rem); max-width: 16ch; }
.body2col .txt p { color: var(--ink-2); max-width: 54ch; margin-bottom: 1.3em; }
.body2col .txt p b { color: var(--ink); font-weight: 400; }

.stat-spread { background: var(--graphite); color: var(--ivory);
  padding: 64px var(--inset); position: relative; overflow: hidden; }
.stat-spread > * { position: relative; }
.stat-spread .big { font-family: var(--font-display); font-weight: 600; font-size: clamp(3.4rem, 11vw, 10rem); letter-spacing: -.04em; line-height: .9; }
.stat-spread .big small { font-size: .32em; color: var(--ivory-3); letter-spacing: -.02em; }
.stat-spread .sub { margin-top: 20px; color: var(--ivory-2); max-width: 52ch; }
.stat-spread .foot { margin-top: 34px; color: #6d6b64; font-family: var(--font-mono); font-size: .6rem; letter-spacing: .18em; text-transform: uppercase; line-height: 2.1; max-width: 70ch; }
.stat-spread .glowline { position: absolute; right: -10%; top: 0; bottom: 0; width: 50%; max-width: none; background: radial-gradient(60% 50% at 50% 50%, rgba(120, 201, 165, .12), transparent 70%); }

.duo { padding: 56px var(--inset); display: grid; grid-template-columns: 1fr 1fr; gap: var(--pad); align-items: start; }
.duo h2 { grid-column: 1 / -1; font-size: clamp(1.6rem, 3vw, 2.6rem); margin-bottom: 22px; }
.duo .panelv { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: #0d1a14; }
.duo .panelv svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.duo .cap { padding-top: 16px; color: var(--ink-3); font-size: .82rem; max-width: 44ch; }

.bounds { background: var(--paper-2); border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule);
  padding: 56px var(--inset); }
.bounds .sec-head { margin-bottom: 40px; }
.bounds .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 6vw; }
.bounds h3 { font-size: 1.05rem; margin-bottom: 22px; color: var(--ink-2); letter-spacing: 0; }
.bounds ul { list-style: none; margin: 0; padding: 0; }
.bounds li { padding: 10px 0; border-bottom: 1px solid var(--rule); color: var(--ink-2); font-size: .95rem; }
.bounds .foot { margin-top: 36px; color: var(--ink-3); font-size: .82rem; max-width: 72ch; }

.nextsys { background: var(--graphite); color: var(--ivory); padding: 56px var(--inset); display: flex; justify-content: space-between; align-items: flex-end; gap: var(--pad); flex-wrap: wrap; }
.nextsys .mono { color: var(--ivory-3); }
.nextsys h2 { font-size: clamp(2rem, 6vw, 5.6rem); margin-top: 14px; }
.nextsys a { color: var(--ivory); text-decoration: none; font-size: 1rem; border-bottom: 1px solid rgba(241, 239, 233, .4); padding-bottom: 8px; white-space: nowrap; }
.nextsys a:hover { border-color: var(--mint); color: var(--mint); }

/* ---------- generic page footer ---------- */
.pagefoot { padding: 40px var(--inset); display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; color: var(--ink-3); border-top: 1px solid var(--rule); }
.pagefoot a { text-decoration: none; color: var(--ink-2); }
/* one tier darker than the caption text beside them: these links are 10px
   mono, and the about page lays a warm tint over its foot that took the
   inherited ink-3 under AA */
.pagefoot a:hover { color: var(--ink); }

/* ---------- about page ---------- */
.about-hero { background: var(--graphite); color: var(--ivory);
  padding: 64px var(--inset) 56px; min-height: 400px; }
.about-hero h1 { font-size: clamp(2rem, 3.3vw, 3.2rem); line-height: 1.0;
  letter-spacing: -.025em; max-width: 20ch; margin-top: 18px; }
.about-hero h1 em { font-style: normal; font-weight: 400; color: inherit; }

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 900px) {
  :root { --pad: 5vw; }
  .masthead { min-height: 58px; gap: 10px; padding-top: 10px; padding-bottom: 10px; }
  .masthead .logo { font-size: .98rem; }
  .masthead .issue { display: none; }
  .nav-trigger {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 42px;
    margin-left: auto;
    padding: 8px 10px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    background: transparent;
    color: var(--ink);
    cursor: pointer;
    font: 600 .68rem/1 var(--font-body);
    letter-spacing: .02em;
  }

  .nav-trigger i,
  .nav-trigger i::before,
  .nav-trigger i::after {
    display: block;
    width: 13px;
    height: 1px;
    background: currentColor;
  }

  .nav-trigger i {
    position: relative;
    transition: background var(--dur-micro) var(--ease-micro);
  }

  .nav-trigger i::before,
  .nav-trigger i::after {
    content: "";
    position: absolute;
    left: 0;
    transition: transform var(--dur-micro) var(--ease-micro),
                top var(--dur-micro) var(--ease-micro);
  }

  .nav-trigger i::before { top: -4px; }
  .nav-trigger i::after { top: 4px; }

  .nav-trigger[aria-expanded="true"] i {
    background: transparent;
  }

  .nav-trigger[aria-expanded="true"] i::before {
    top: 0;
    transform: rotate(45deg);
  }

  .nav-trigger[aria-expanded="true"] i::after {
    top: 0;
    transform: rotate(-45deg);
  }

  .masthead nav {
    position: absolute;
    top: calc(100% + 1px);
    left: var(--pad);
    right: var(--pad);
    display: block;
    max-width: none;
    padding: 6px;
    background: var(--paper);
    border: 1px solid var(--rule);
    border-radius: 18px;
    box-shadow: 0 22px 48px -20px rgba(20, 20, 19, .5);
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
    transform: translate3d(0, -8px, 0);
    transition: opacity .28s var(--ease-micro),
                transform .28s var(--ease-micro),
                visibility 0s linear .42s;
  }

  .masthead.nav-open nav {
    visibility: visible;
    opacity: 1;
    pointer-events: auto;
    transform: translate3d(0, 0, 0);
    transition-delay: 0s;
    will-change: transform, opacity;
  }

  .masthead nav a {
    display: flex;
    align-items: center;
    min-height: 52px;
    padding: 0 16px;
    border: 0;
    border-radius: 13px;
    font-size: .95rem;
    line-height: 1.25;
    color: var(--ink-2);
    background-color: transparent;
    opacity: 0;
    transform: translate3d(0, -4px, 0);
    transition: opacity .2s var(--ease-micro),
                transform .2s var(--ease-micro),
                background-color .16s var(--ease-micro),
                color .16s var(--ease-micro);
  }

  .masthead.nav-open nav a {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }

  .masthead.nav-open nav a:nth-child(1) { transition-delay: .02s; }
  .masthead.nav-open nav a:nth-child(2) { transition-delay: .04s; }
  .masthead.nav-open nav a:nth-child(3) { transition-delay: .06s; }
  .masthead.nav-open nav a:nth-child(4) { transition-delay: .08s; }
  .masthead.nav-open nav a:nth-child(5) { transition-delay: .10s; }

  .masthead nav a::after { display: none; }

  .masthead nav a.is-current {
    color: var(--ink);
    font-weight: 500;
    background-color: rgba(217, 142, 79, .45);
  }
  .masthead nav a:active { background-color: rgba(20, 20, 19, .12); }
  .nav-trigger:active { background-color: rgba(20, 20, 19, .08); }

  .masthead.on-dark nav a.is-current {
    color: var(--ivory);
    border-color: rgba(241, 239, 233, .34);
    background-color: rgba(217, 142, 79, .18);
  }
  .lang { margin-left: 0; }
  .lang-trigger { min-height: 30px; padding: 5px 7px; }
  .hero { min-height: 0; }
  .hero-copy { padding: 90px var(--pad); }
  /* on a phone the second line sits flush left: an indent that reads as a
     gesture on a wide screen reads as a misalignment on a narrow one */
  .hero h1 .l2 { margin-left: 0; }
  .hero .baseline { display: none; }

  .sec-head { grid-template-columns: 1fr; gap: 12px; }
  .sec-intro { margin-left: 0; margin-bottom: 56px; }

  .systems { padding-top: 80px; padding-bottom: 40px; }
  .project-grid { grid-template-columns:1fr; gap:18px; }
  .project-card:hover { transform:none; }
  .project-card__body { padding:22px 20px 24px; }
  .project-card__body h3 { font-size:2rem; }
  .project-card__state { font-size:.56rem; }

  .engineer { padding: 90px var(--pad) 70px; }
  .engineer .cols { grid-template-columns: 1fr; gap: 40px; }
  .layer { grid-template-columns: 1fr 120px; gap: 16px; }
  .pull { margin-top: 70px; padding-top: 50px; }
  .exp { padding: 70px var(--pad); }
  .exp-cols { columns: 1; }

  .resp { padding-top: 80px; padding-bottom: 70px; }
  .r-rows { columns: 1; }
  .r-row { grid-template-columns: 44px 1fr; padding: 22px 0; }
  .r-row:nth-of-type(even) { padding-left: 0; }

  .thesis { padding: 100px var(--pad); }
  .thesis .inner { grid-template-columns: 1fr; }

  .caps { padding-top: 80px; padding-bottom: 70px; }
  .acc-row summary { grid-template-columns: 44px 1fr 24px; }
  .acc-row .detail { margin-left: calc(44px + 3vw); }
  .acc-items { margin-left: calc(44px + 3vw); columns: 1; }

  .note { grid-template-columns: 44px 1fr; }
  .note time { display: none; }

  .contact { padding: 100px var(--pad) 80px; }
  .contact .row { margin-top: 60px; }

  .chero { min-height: 0; max-height: none; }
  .chero .inner { padding: 112px var(--pad) 34px; }
  .chero .crumbs { gap: 8px; margin-bottom: 14px; }
  .chero .crumbs span:nth-of-type(n+2) { display: none; }
  .chero h1 { font-size: clamp(2.25rem, 11vw, 3.25rem); max-width: 13ch; }
  .chero .lede { margin-top: 14px; font-size: .96rem; line-height: 1.5; }
  .chero .metarow { margin-top: 18px; gap: 12px 22px; }
  .chero .metarow .m:not(:first-child) { display: none; }
  .figure { height: 68vw; }
  .brief, .body2col, .duo { grid-template-columns: 1fr; padding: 80px var(--pad); gap: 34px; }
  .stat-spread { padding: 90px var(--pad); }
  .bounds { padding: 80px var(--pad); }
  .bounds .cols { grid-template-columns: 1fr; gap: 40px; }
  .nextsys { padding: 80px var(--pad); }
  .about-hero { padding: 90px var(--pad) 70px; }
}

@media (max-width: 900px) {
  /* A scrim marks the menu as a layer over the page. It hangs off the root
     element so it sits under the masthead's own z-index rather than over its
     background, and it never takes the pointer: taps go through to the
     document, where the masthead's handler closes the menu. */
  html.nav-open::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 40;
    background: rgba(20, 20, 19, .34);
    pointer-events: none;
    animation: nav-scrim .22s var(--ease-micro) both;
  }
  html.nav-open.on-dark-bar::before { background: rgba(0, 0, 0, .46); }
}
@keyframes nav-scrim { from { opacity: 0 } to { opacity: 1 } }

@media (prefers-reduced-motion: reduce) {
  .masthead nav,
  .masthead nav a,
  .nav-trigger i,
  .nav-trigger i::before,
  .nav-trigger i::after {
    transition: none !important;
  }
}

.save-data .masthead nav,
.save-data .masthead nav a,
.save-data .nav-trigger i,
.save-data .nav-trigger i::before,
.save-data .nav-trigger i::after {
  transition: none !important;
}

.masthead.on-dark .nav-trigger { color: var(--ivory); border-color: var(--rule-l); }
@media (max-width: 900px) {
  .masthead.on-dark nav { background: var(--graphite); border-color: var(--rule-l); }
  .masthead.on-dark nav a { color: var(--ivory-2); background-color: transparent; }
  .masthead.on-dark nav a:active { background-color: rgba(241, 239, 233, .16); }
  .masthead.on-dark nav a.is-current { color: var(--ivory); background-color: rgba(217, 142, 79, .18); }
}

.product-still img { width: 100%; height: 100%; object-fit: cover; }

/* ============================================================
   MOTION LAYER
   Scramble reveals, scroll reveals and full-bleed cinematic
   statements. All of it collapses to static under reduced motion.
   ============================================================ */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity var(--dur-3) var(--ease-entrance), transform var(--dur-3) var(--ease-entrance); }
.reveal.in { opacity: 1; transform: none; }
.reveal.d1 { transition-delay: .10s; }
.reveal.d2 { transition-delay: .20s; }
.reveal.d3 { transition-delay: .30s; }

/* ---------- masked heading reveal ----------
   The section headings rise out from behind their own edge instead of just
   fading up. It is the one gesture on the page that reads as deliberate
   rather than generic, and it costs nothing in markup: the selector picks up
   the headings that already carry .reveal, so the DOM is untouched and the
   language switch — which replaces innerHTML — cannot break it.

   The bottom inset resolves to -12% rather than 0 so descenders and the
   mint em are never shaved by a rounding error at the end of the travel. */
.sec-head h2.reveal,
.cine .inner h2.reveal,
.contact h2.reveal,
.engineer h2.reveal {
  clip-path: inset(0 -6% 105% -6%);
  transform: translateY(4%);
  opacity: 1;
  transition: clip-path 1.15s var(--ease-mask), transform 1.15s var(--ease-mask);
}
.sec-head h2.reveal.in,
.cine .inner h2.reveal.in,
.contact h2.reveal.in,
.engineer h2.reveal.in {
  clip-path: inset(0 -6% -12% -6%);
  transform: none;
}

/* ---------- automatic stagger ----------
   A cascade with no delay classes to maintain. The work index and the metric
   bands read as one movement arriving in order rather than as N elements
   that happen to animate at once. */
.pw-grid .pwc.reveal:nth-child(1) { transition-delay: .00s; }
.pw-grid .pwc.reveal:nth-child(2) { transition-delay: .07s; }
.pw-grid .pwc.reveal:nth-child(3) { transition-delay: .14s; }
.pw-grid .pwc.reveal:nth-child(4) { transition-delay: .21s; }
.pw-grid .pwc.reveal:nth-child(5) { transition-delay: .28s; }
.pw-grid .pwc.reveal:nth-child(6) { transition-delay: .35s; }
.pw-grid .pwc.reveal:nth-child(7) { transition-delay: .42s; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .sec-head h2.reveal, .cine .inner h2.reveal,
  .contact h2.reveal, .engineer h2.reveal {
    clip-path: none; transform: none; transition: none;
  }
}
.scramble { white-space: pre-wrap; }

/* ---------- cinematic full-bleed statement over a loop ---------- */
.cine {
  position: relative;
  min-height: 0;
  display: flex;
  align-items: center;
  overflow: hidden; overflow: clip;
  background: #0a0c0e;
  color: var(--ivory);
  padding-block: 88px;
}
.cine .bg { position: absolute; inset: 0; overflow: hidden; overflow: clip; }
/* zoom into the abstract part of the loop: the source's own captions and
   figures must never read through and compete with the statement */
.cine .bg video, .cine .bg img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.85); transform-origin: 26% 58%;
}
.cine .bg video { opacity: 0; transition: opacity .8s ease; }
.cine .bg.playing video { opacity: 1; }
.cine::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background:
    radial-gradient(130% 100% at 10% 50%, rgba(10,12,14,.97), rgba(10,12,14,.78) 48%, rgba(10,12,14,.5) 100%),
    linear-gradient(180deg, rgba(10,12,14,.92), rgba(10,12,14,.35) 26%, rgba(10,12,14,.35) 74%, rgba(10,12,14,.92));
}
.cine .dots {
  position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: .05;
  background-image: radial-gradient(#fff 1px, transparent 1px);
  background-size: 26px 26px;
  transform: translateZ(0);          /* paint the tile once, then composite */
}
.cine .inner { position: relative; z-index: 3; width: 100%; padding: 0 var(--inset); }
.cine .kicker { color: var(--ivory-3); margin-bottom: 28px; display: block; }
.cine h2 { font-size: clamp(2rem, 5.2vw, 5rem); max-width: 20ch; line-height: 1.04; }
.cine h2 em { font-style: normal; font-weight: 400; color: inherit; }
.cine p { margin-top: 24px; max-width: 50ch; color: var(--ivory-2); font-size: clamp(1rem, 1.4vw, 1.1rem); }

/* Two adjacent dark cinematic sections read as one continuous passage rather
   than two images butting against each other: the seam is dissolved and the
   pair share a single top and bottom edge. */
.cine + .cine { padding-top: 56px; }
.cine + .cine::before {
  content: "";
  position: absolute; left: 0; right: 0; top: 0; height: 72px; z-index: 4;
  /* short and soft: it dissolves the seam between two dark sections without
     blanketing the top of the frame, which was slicing the figure's head */
  background: linear-gradient(180deg, rgba(10, 12, 14, .92) 0%, rgba(10, 12, 14, .45) 40%, transparent 100%);
  pointer-events: none;
}

/* metric band inside a cinematic section */
.metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4vw; margin-top: 52px; margin-bottom: 8px; }
.metrics .m .v { font-family: var(--font-display); font-weight: 600; font-size: clamp(2.4rem, 6vw, 5.2rem); letter-spacing: -.04em; line-height: 1; }
.metrics .m .l { color: var(--ivory-3); font-family: var(--font-mono); font-size: .6rem; letter-spacing: .22em; text-transform: uppercase; margin-top: 18px; }

/* ---------- professional work spreads ---------- */
.prowork { padding-top: 88px; padding-bottom: 32px; }
.pw { display: grid; grid-template-columns: 5.5fr 4.5fr; margin-bottom: 48px; align-items: stretch; }
.pw.right { grid-template-columns: 4.5fr 5.5fr; }
.pw.right .stagevis { order: 2; }
.pw .caption { display:flex; flex-direction:column; padding: 34px 0 0 var(--pad); }
.pw.right .caption { padding: 34px var(--pad) 0 0; text-align: right; align-items: flex-end; }
.pw h3 { font-size: clamp(1.6rem, 3.2vw, 2.8rem); margin-bottom: 18px; }
.pw .desc { color: var(--ink-2); max-width: 40ch; margin-bottom: 22px; }
.pw .tags { display: flex; flex-wrap: wrap; gap: 10px 18px; }
.pw.right .tags { justify-content: flex-end; }
.pw .tags span { font-family: var(--font-mono); font-size: .58rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3); }

/* sector strip */
.sectors { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--pad); padding-top: 16px; padding-bottom: 72px; }
.sector { border-top: 1px solid var(--rule); padding-top: 28px; }
.sector .mono { color: var(--ink-3); }
.sector h3 { font-size: clamp(1.3rem, 2.4vw, 2rem); margin: 16px 0 12px; }
.sector p { color: var(--ink-2); max-width: 46ch; font-size: .95rem; }

@media (max-width: 900px) {
  .cine { min-height: 0; padding-block: 80px; }
  .cine h2 { max-width: none; }
  .metrics { grid-template-columns: 1fr; gap: 40px; margin-top: 50px; }
  .prowork { padding-top: 80px; }
  .pw, .pw.right { grid-template-columns: 1fr; margin-bottom: 70px; }
  .pw.right .stagevis { order: 0; }
  .pw .caption, .pw.right .caption { padding: 24px 0 0; text-align: left; align-items: flex-start; }
  .pw.right .tags { justify-content: flex-start; }
  .sectors { grid-template-columns: 1fr; gap: 40px; padding-bottom: 80px; }
}

/* ---------- standards band ---------- */
.standards { border-top: 1px solid var(--rule); padding: 36px 0 44px; }
.standards .mono { color: var(--ink-3); display: block; margin-bottom: 30px; }
.standards ul { list-style: none; margin: 0 0 26px; padding: 0; display: flex; flex-wrap: wrap; gap: 10px 12px; }
.standards li {
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-2); border: 1px solid var(--rule); border-radius: 3px; padding: 12px 18px;
}
.standards p { color: var(--ink-3); max-width: 62ch; font-size: .92rem; }

/* ---------- business & funding block ---------- */
.business { border-top: 1px solid var(--rule); padding: 48px 0 24px; }
.business .mono { color: var(--ink-3); display: block; margin-bottom: 26px; }
.business h3 { font-size: clamp(1.6rem, 3.6vw, 3rem); max-width: 22ch; margin-bottom: 32px; }
.business .bcols { display: grid; grid-template-columns: 1fr 1fr; gap: 5vw; }
.business .bcols p { color: var(--ink-2); max-width: 46ch; }
.business .bcols p b { color: var(--ink); font-weight: 400; }
.business .bnote {
  margin-top: 32px; padding-top: 22px; border-top: 1px solid var(--rule);
  color: var(--ink-3); max-width: 68ch; font-size: .92rem;
}
.business .bnote a { color: var(--ink); border-bottom: 1px solid rgba(20,20,19,.4); text-decoration: none; padding-bottom: 2px; }

@media (max-width: 900px) {
  .business .bcols { grid-template-columns: 1fr; gap: 26px; }
  .standards { padding: 34px 0 44px; }
}

/* ============================================================
   EDITORIAL MEDIA — Cortex Operator stills

   Motion runs as compositor-driven CSS animations, never as per-frame
   JavaScript transforms. Writing style.transform each frame on an image
   this large forces a style recalc and a re-raster on the main thread —
   measured at 88% janky frames. As keyframes on a promoted layer the
   same movement costs effectively nothing.
   ============================================================ */
.media { position: relative; overflow: hidden; overflow: clip; }
.media picture, .media img { display: block; width: 100%; height: 100%; }
.media img {
  object-fit: cover;
  transform-origin: center;
  backface-visibility: hidden;
  animation-play-state: paused;          /* the controller starts it */
  animation-fill-mode: both;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
.media.is-animating img { animation-play-state: running; will-change: transform; }

/* one movement language per level — different distance, speed and axis */
@keyframes media-hero      { from { transform: translate3d(-14px, -7px, 0) scale(1.06); }
                             to   { transform: translate3d( 14px,  7px, 0) scale(1.06); } }
@keyframes media-project   { from { transform: translate3d( 10px,  5px, 0) scale(1.05); }
                             to   { transform: translate3d(-10px, -5px, 0) scale(1.05); } }
@keyframes media-editorial { from { transform: translate3d( -7px,  0,   0) scale(1.04); }
                             to   { transform: translate3d(  7px,  0,   0) scale(1.04); } }

.media[data-media="hero"] img      { animation-name: media-hero;      animation-duration: 14s; }
.media[data-media="project"] img   { animation-name: media-project;   animation-duration: 11s; }
.media[data-media="editorial"] img { animation-name: media-editorial; animation-duration: 18s; }
/* Backdrops behind a heavy veil never animate: the movement is not visible and
   moving a full-section image layer was the biggest scroll cost on the page. */
.media[data-media="static"] img,
.media[data-media="static"] .media-sweep { animation: none !important; will-change: auto !important; }

/* a slow band of light crossing the frame — also a keyframe */
.media-sweep {
  position: absolute; inset: -10% -40%;
  z-index: 2; pointer-events: none; opacity: 0;
  background: linear-gradient(100deg, transparent 38%, rgba(226, 236, 240, .10) 50%, transparent 62%);
  animation: media-sweep 17s ease-in-out infinite;
  animation-play-state: paused;
}
.media.is-animating .media-sweep { animation-play-state: running; }
@keyframes media-sweep {
  0%   { transform: translate3d(-30%, 0, 0); opacity: 0; }
  50%  { opacity: .5; }
  100% { transform: translate3d(130%, 0, 0); opacity: 0; }
}

/* editorial veil so HTML type always wins over the image */
.media-veil {
  position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background:
    radial-gradient(120% 100% at 8% 50%, rgba(10, 12, 14, .95), rgba(10, 12, 14, .55) 52%, rgba(10, 12, 14, .18) 100%),
    linear-gradient(180deg, rgba(10, 12, 14, .8), transparent 24%, transparent 76%, rgba(10, 12, 14, .85));
}
.media-veil.paper {
  background:
    radial-gradient(120% 100% at 10% 50%, rgba(236, 233, 225, .96), rgba(236, 233, 225, .6) 52%, rgba(236, 233, 225, .2) 100%);
}
@media (prefers-reduced-motion: reduce) {
  .media img, .media-sweep { animation: none !important; }
  .media-sweep { display: none; }
}

/* ---------- case-study hero still ---------- */
.chero-media { position: absolute; inset: 0; z-index: 1; }
.chero-media picture { display:block; width:100%; height:100%; }
.chero-media img { width: 100%; height: 100%; object-fit: cover; }
.pliegos-hero img { object-position:center 48%; }
.chero::after {
  content: "";
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    radial-gradient(120% 100% at 12% 60%, rgba(9, 12, 11, .94), rgba(9, 12, 11, .6) 54%, rgba(9, 12, 11, .28) 100%),
    linear-gradient(180deg, rgba(9, 12, 11, .85), transparent 26%, transparent 62%, rgba(9, 12, 11, .95));
}
.chero .inner { z-index: 3; }

/* ---------- about hero still ---------- */
.about-hero.has-media { position: relative; overflow: hidden; overflow: clip; }
.about-media {
  position: absolute; inset: 0 0 0 auto; width: 54%; z-index: 1; overflow: hidden;
}
.about-media::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(90deg, var(--graphite) 0%, rgba(19, 20, 22, .8) 30%, transparent 62%);
  transform: translateZ(0);
}
.about-media img { width: 100%; height: 100%; object-fit: cover; opacity: .82; mix-blend-mode: screen; }
.about-hero.has-media > *:not(.about-media) { position: relative; z-index: 2; }
@media (max-width: 900px) {
  .about-media { width: 100%; opacity: .35; }
}

/* ---------- hero still behind the sculpture ---------- */
/* One image, not two.

   The homepage hero used to layer the portrait and the scroll-scrub video on
   top of each other. Translucent architectural planes crossing a face is not
   depth, it is noise, and it was the single most amateur thing on the page.

   The portrait belongs on About — that is a page about a person. Here the
   architectural loop carries the frame alone, which is also the asset that
   belongs to this brand rather than to a stock library. */
.hero-still {
  position: absolute; inset: 0 0 0 auto; width: 58%; z-index: 1;
  overflow: hidden; overflow: clip;
}
.hero-still img {
  width: 100%; height: 100%; object-fit: cover; opacity: .95;
  filter: contrast(1.12) brightness(1.06) saturate(.92);
}
/* A short feathered edge rather than a gradient across half the frame, so the
   image reads as a plate set into the layout and the type never sits on it. */
.hero-still::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background:
    linear-gradient(90deg, var(--graphite) 0%, rgba(19, 20, 22, .78) 10%, transparent 30%),
    linear-gradient(180deg, rgba(19, 20, 22, .45), transparent 20%, transparent 76%, rgba(19, 20, 22, .65));
  transform: translateZ(0);
}
@media (max-width: 900px) {
  .hero-still { width: 100%; opacity: .5; }
}

/* ---------- hero scrub video: scroll is the timeline ---------- */
.hero-motion {
  position: absolute; inset: 0; z-index: 1;
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(1.045);
  /* No transition here on purpose. The pointer rewrites --mx/--my many times a
     second, and a transition would restart its interpolation on every one of
     them: the layer would chase the cursor on a 550 ms ease and never arrive,
     which is what read as rubbery. The damping is done once, in scrub.js, by a
     single rAF that lerps toward the target and stops when it gets there. */
  will-change: transform;
}
.hero-still video.hero-scrub {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity 1.1s ease;
}
.hero-still.scrub-live video.hero-scrub { opacity: 1; }
/* the About hero carries the same scrub behind a lighter veil, so the
   photographic subject underneath is still legible */
.about-media video.hero-scrub {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; transition: opacity 1.1s ease;
  mix-blend-mode: screen;
}
.about-media.scrub-live video.hero-scrub { opacity: .5; }
.about-media.scrub-live img { animation: none !important; will-change: auto !important; }
/* while the scrub video carries the frame, the still underneath stops
   animating — one moving layer in the hero, never two */
.hero-still.scrub-live img { animation: none !important; will-change: auto !important; }
@media (prefers-reduced-motion: reduce) {
  .hero-scrub { display: none; }
  .hero-motion { transform: none; transition: none; }
}

/* ---------- forward-looking note inside the business block ---------- */
.thesis-note { border-top: 1px solid var(--rule); padding: 44px 0 16px; }
.thesis-note .mono { color: var(--ink-3); display: block; margin-bottom: 24px; }
.thesis-note h3 { font-size: clamp(1.5rem, 3.2vw, 2.6rem); max-width: 26ch; margin-bottom: 30px; }
.thesis-note .bcols { display: grid; grid-template-columns: 1fr 1fr; gap: 5vw; }
.thesis-note .bcols p { color: var(--ink-2); max-width: 46ch; }
.thesis-note .bcols p b { color: var(--ink); font-weight: 400; }
@media (max-width: 900px) { .thesis-note .bcols { grid-template-columns: 1fr; gap: 26px; } }

/* ============================================================
   COMPACT CASE STUDY
   The pages were eleven full-width bands stacked in series. Putting
   the text and its figure side by side makes each block as tall as
   its tallest column instead of the sum of both, which is most of
   the scrolling gone.
   ============================================================ */
.cs { padding: 56px var(--inset); }
.cs + .cs { padding-top: 0; }

/* text on one side, its figure on the other */
.cs-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 4vw;
  align-items: start;
  padding-top: 40px;
  border-top: 1px solid var(--rule);
}
.cs-split.flip > .cs-body { order: 2; }
.cs-body h2 { font-size: clamp(1.4rem, 2.4vw, 2.1rem); margin-bottom: 18px; max-width: 20ch; }
.cs-body p { color: var(--ink-2); font-size: .95rem; margin-bottom: .95em; }
.cs-body p:last-child { margin-bottom: 0; }
.cs-body p b { color: var(--ink); font-weight: 400; }
.cs-body .mono { color: var(--ink-3); display: block; margin-bottom: 14px; }

.cs-fig { margin: 0; }
.cs-fig .panelv { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: #0d1a14; border-radius: 3px; }
.cs-fig .panelv svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.cs-fig .panelv img,
.cs-fig .panelv video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cs-fig .panelv video { opacity: 0; transition: opacity var(--dur-2) var(--ease-micro); }
.cs-fig .panelv.playing video { opacity: 1; }
.cs-fig .panelv.playing img { opacity: 0; transition: opacity var(--dur-2) var(--ease-micro); }
/* --ink-3 clears AA on the page ground but lands at 4.32:1 on the darker
   band these captions sit against, so the caption carries its own ink. */
.cs-fig figcaption { margin-top: 12px; color: #656159; font-size: .8rem; max-width: 52ch; }

/* the headline number lives inside the text column, not in its own band */
.cs-stat { display: block; margin: 4px 0 20px; }
.cs-stat .v {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(2.4rem, 5vw, 4rem); letter-spacing: -.04em; line-height: 1;
}
.cs-stat .v small { font-size: .34em; color: var(--ink-3); letter-spacing: -.01em; }
.cs-stat .l { display: block; margin-top: 10px; color: var(--ink-3); font-family: var(--font-mono);
  font-size: .6rem; letter-spacing: .2em; text-transform: uppercase; max-width: 40ch; line-height: 1.9; }
.cs-note { margin-top: 14px; color: var(--ink-3); font-size: .78rem; line-height: 1.6; }

/* boundaries: two lists beside each other, not a section of their own */
.cs-bounds { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4vw; }
.cs-bounds h3 { font-size: .95rem; color: var(--ink-2); letter-spacing: 0; margin-bottom: 14px; }
.cs-bounds ul { list-style: none; margin: 0; padding: 0; }
.cs-bounds li { padding: 8px 0; border-bottom: 1px solid var(--rule); color: var(--ink-2); font-size: .88rem; }

@media (max-width: 900px) {
  .cs { padding: 40px var(--inset); }
  .cs-split { grid-template-columns: 1fr; gap: 24px; padding-top: 28px; }
  .cs-split.flip > .cs-body { order: 0; }
  .cs-bounds { grid-template-columns: 1fr; gap: 26px; }
}

/* ============================================================
   TRANSFER STRIP
   Closes a case-study block by showing that the method is not
   domain-bound: a compact diagram, one sentence, and the stack.
   ============================================================ */
.cs-transfer {
  margin-top: 28px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
}
.cs-transfer > .mono { color: var(--ink-3); display: block; margin-bottom: 16px; }
.tr-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  gap: 3vw;
  align-items: center;
}
.tr-viz {
  display: block; width: 100%; height: auto;
  background: #0d1a14; border-radius: 3px;
}
.cs-split.flip .tr-viz { background: #0c1116; }
.tr-line {
  font-family: var(--font-display);
  font-size: clamp(.98rem, 1.5vw, 1.22rem);
  line-height: 1.4;
  color: var(--ink);
  max-width: 34ch;
}
.tr-line b { font-weight: 600; }
.tr-chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 16px; }
.tr-chips span {
  font-family: var(--font-mono);
  font-size: .58rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 6px 10px;
}
@media (max-width: 900px) {
  .tr-grid { grid-template-columns: 1fr; gap: 20px; }
}

/* ---------- verified figures table inside a case study ---------- */
.cs-figures { width: 100%; border-collapse: collapse; margin-top: 4px; }
.cs-figures tr { border-bottom: 1px solid var(--rule); }
.cs-figures tr:last-child { border-bottom: 0; }
.cs-figures td { padding: 9px 0; font-size: .88rem; color: var(--ink-2); vertical-align: baseline; }
.cs-figures td.n {
  text-align: right; white-space: nowrap; padding-left: 16px;
  font-family: var(--font-mono); font-size: .82rem; font-variant-numeric: tabular-nums; color: var(--ink);
}
.cs-figures td.sub { padding-left: 18px; color: var(--ink-3); font-size: .82rem; }
.cs-stack { margin-top: 18px; color: var(--ink-3); font-size: .8rem; line-height: 1.75; }
.cs-stack b { color: var(--ink-2); font-weight: 400; }

/* Menucesta: the economic decision is shown as a comparison, not another paragraph. */
.basket-compare {
  align-self:stretch; margin:0; padding:clamp(22px,3vw,36px);
  border-radius:12px; color:#edf3ed; background:#101713;
  box-shadow:0 18px 55px rgba(16,23,19,.14);
}
.basket-compare__head { display:flex; justify-content:space-between; gap:18px; padding-bottom:22px; border-bottom:1px solid rgba(255,255,255,.14); font-family:var(--font-mono); font-size:.66rem; letter-spacing:.08em; text-transform:uppercase; }
.basket-compare__head b { color:#b8d578; font-weight:500; }
.basket-row { display:grid; grid-template-columns:minmax(125px,1.2fr) minmax(80px,1fr) auto; gap:16px; align-items:center; padding:18px 0; border-bottom:1px solid rgba(255,255,255,.1); }
.basket-row strong, .basket-row small { display:block; }
.basket-row strong { font-size:.92rem; }
.basket-row small { color:rgba(237,243,237,.55); margin-top:4px; font-size:.7rem; }
.basket-row > b { font-family:var(--font-mono); color:rgba(237,243,237,.62); font-size:.7rem; }
.basket-bar { height:7px; border-radius:99px; background:rgba(255,255,255,.1); overflow:hidden; }
.basket-bar::after { content:""; display:block; height:100%; width:var(--w); border-radius:inherit; background:#8fa59b; }
.basket-row.best .basket-bar::after { background:#b8d578; }
.basket-row.best > b { color:#b8d578; }
.basket-saving { display:flex; justify-content:space-between; gap:18px; padding-top:24px; }
.basket-saving span { color:rgba(237,243,237,.55); font-size:.78rem; }
.basket-saving strong { text-align:right; color:#b8d578; font-size:.9rem; }
/* .42 of the ivory over this panel composites to rgb(110,117,112), which is
   3.76:1 — a caption that says the prices are illustrative, set too faintly for
   anyone to read the disclaimer. .52 keeps it the quietest line in the figure
   and clears AA at 5.05:1. */
.basket-compare figcaption { margin-top:28px; color:rgba(237,243,237,.52); }
.recipe-photo picture { display:block; width:100%; height:100%; }
.recipe-photo img { width:100%; height:100%; object-fit:cover; object-position:center; }
.recipe-photo + figcaption b { color:var(--ink); font-weight:600; }

/* A product screenshot is evidence. Preserve the complete interface instead
   of forcing a tall document into the generic 16:10 media crop. */
.pliegos-capture { margin:0; }
.pliegos-capture { overflow:hidden; border:1px solid var(--rule); border-radius:8px; background:#f3f6fa; }
.pliegos-capture img { display:block; width:100%; height:auto; }
.pliegos-capture figcaption { padding:12px 14px 14px; margin:0; background:var(--paper); }

/* Grants gets a product surface here, not a second frame from the same film. */
.grants-ui { overflow:hidden; border:1px solid #23312d; border-radius:10px; background:#111816; color:#eef3ef; box-shadow:0 20px 55px rgba(12,18,16,.14); }
.grants-ui__top, .grants-ui__profile, .grants-ui__foot { display:flex; align-items:center; justify-content:space-between; gap:16px; }
.grants-ui__top { padding:25px 26px 20px; }
.grants-ui__top span:first-child { display:block; margin-bottom:7px; color:#81928b; font:500 .6rem/1 var(--font-mono); letter-spacing:.14em; text-transform:uppercase; }
.grants-ui__top h3 { margin:0; color:#fff; font-size:clamp(1.35rem,2.4vw,2rem); }
.grants-ui__fresh { padding:7px 10px; border:1px solid rgba(150,215,189,.25); border-radius:999px; color:#9bd4bb !important; background:rgba(75,147,117,.1); white-space:nowrap; }
.grants-ui__profile { padding:14px 26px; border-block:1px solid rgba(255,255,255,.1); color:#aebbb5; font-size:.72rem; }
.grants-ui__profile b { color:#d8e3de; font-weight:500; }
.grants-ui__list { padding:0 14px; }
.grant-row { display:grid; grid-template-columns:64px minmax(0,1fr) auto; gap:14px; align-items:center; padding:20px 12px; border-bottom:1px solid rgba(255,255,255,.09); }
.grant-row:last-child { border-bottom:0; }
.grant-row.is-best { background:linear-gradient(90deg,rgba(73,150,117,.12),transparent); }
.grant-row__score { color:#a7dbc4; font:600 1.08rem/1 var(--font-mono); }
.grant-row__score small { display:block; margin-top:5px; color:#71827a; font-size:.52rem; font-weight:400; letter-spacing:.08em; text-transform:uppercase; }
.grant-row h4 { margin:0 0 5px; color:#f4f7f5; font:600 .88rem/1.25 var(--font-body); }
.grant-row p { margin:0 0 7px; color:#84948c; font-size:.68rem; }
.grant-row div > span { color:#9bbdad; font:400 .56rem/1.3 var(--font-mono); letter-spacing:.03em; }
.grant-row:nth-child(2) div > span { color:#d3b777; }
.grant-row:nth-child(3) div > span { color:#c58d84; }
.grant-row time { color:#b7c3bd; font:500 .62rem/1 var(--font-mono); white-space:nowrap; }
.grants-ui__foot { padding:15px 26px; border-top:1px solid rgba(255,255,255,.1); color:#81928b; font-size:.62rem; }
.grants-ui__foot b { color:#a7dbc4; font-size:1rem; font-weight:400; }
@media (max-width:520px) {
  .grants-ui__top, .grants-ui__profile, .grants-ui__foot { padding-inline:18px; }
  .grants-ui__profile { align-items:flex-start; flex-direction:column; gap:5px; }
  .grant-row { grid-template-columns:52px minmax(0,1fr); }
  .grant-row time { grid-column:2; }
}
@media (max-width:520px) {
  .basket-row { grid-template-columns:1fr auto; gap:10px; }
  .basket-bar { grid-column:1/-1; grid-row:2; }
  .basket-row > b { grid-column:2; grid-row:1; }
  /* Side by side, the two halves of the footer each break mid-phrase —
     "Ahorro / detectado" beside "Comparación por cesta / completa". Stacked,
     each one gets the full column and reads as one line of thought. */
  .basket-saving { flex-direction:column; gap:4px; }
  .basket-saving strong { text-align:left; }
  .basket-compare__head { gap:12px; }
}

/* ============================================================
   02 — PROFESSIONAL WORK, compact index
   ------------------------------------------------------------
   Seven full-width alternating blocks read as seven separate
   arguments and cost 3,266 px. The same seven as a card index
   cost about a third of that and can be scanned in one pass;
   the argument moves to a page per item for anyone who wants it.

   Every card is the same size. A lead card made the first item
   look like the important one, which is not what the section is
   saying, and left the other six at a size their frames could
   not be read at.
   ============================================================ */
/* Two columns, and the grid does not run the full 1520px measure.

   470px cards showed the frames at a quarter of their authored size and the
   content inside them turned to mush. 732px cards fixed the legibility and
   overshot the other way — seven figures that size read as a gallery rather
   than as an index. Constraining the grid puts them near 600px: the in-frame
   floor still clears 8.5 CSS px, and the section reads as a list of work
   again.

   Two columns, not three.

   The loops are authored at 1920 with a 28px floor on any text inside the
   frame, because below roughly 12 CSS px on screen that text stops being
   readable. Three columns put them in a 470px box — a 4x reduction, which
   lands that 28px floor at under 7 CSS px. The frames were not short of
   pixels; they were being shown at a quarter of the size they were drawn for,
   and illegible content inside a frame is what "pixelated" actually looks
   like.

   Two columns give roughly 720px, and the section is taller for it. A legible
   figure in a taller section beats seven unreadable ones in a shorter. */
.pw-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 46px 2.4vw;
  padding-bottom: 8px;
}
.pwc { display: flex; flex-direction: column; text-decoration: none; color: inherit; }
.pwc .shot {
  position: relative; overflow: hidden; overflow: clip; aspect-ratio: 16 / 10;
  background: #0d0f10; border: 1px solid var(--rule);
}
.pwc .shot img, .pwc .shot video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  /* the loop frames were composed for a 1920 px figure; at a third of that the
     mid-tones collapse, so the thumbnail gets a little more separation */
  filter: contrast(1.12) brightness(1.06);
  transition: filter var(--dur-2) var(--ease-micro), transform var(--dur-2) var(--ease-micro);
}
.pwc .shot video { opacity: 0; transition: opacity .4s ease; }
.pwc .shot.playing video { opacity: 1; }
.pwc .shot.playing img { opacity: 0; transition: opacity .4s ease; }
.pwc:hover .shot img, .pwc:hover .shot video { filter: contrast(1.18) brightness(1.14); }
.pwc .body { padding-top: 16px; flex: 1; display: flex; flex-direction: column; }
.pwc h3 {
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.2; margin-bottom: 10px;
  transition: color var(--dur-micro) var(--ease-micro);
}
.pwc .desc { color: var(--ink-2); font-size: .87rem; line-height: 1.55; margin-bottom: 14px; }
.pwc .tags { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-bottom: 14px; }
.pwc .tags span {
  font-family: var(--font-mono); font-size: .56rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.pwc .more {
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
  margin-top: auto; padding-top: 4px;
  transition: color var(--dur-micro) var(--ease-micro);
}
.pwc:hover h3, .pwc:hover .more { color: var(--ink); }
.pwc:hover .shot { border-color: var(--ink-3); }
/* the card lifts a little and its frame tightens: enough to confirm the
   pointer landed, small enough that a grid of seven does not jitter */
.pwc { transition: transform var(--dur-2) var(--ease-micro); will-change: auto; }
.pwc:hover, .pwc:focus-within { transform: translateY(-4px); }
.pwc .more { transition: color var(--dur-micro) var(--ease-micro), letter-spacing var(--dur-2) var(--ease-micro); }
.pwc:hover .more { letter-spacing: .02em; }
@media (prefers-reduced-motion: reduce) {
  .pwc, .pwc .more { transition: none; }
  .pwc:hover, .pwc:focus-within { transform: none; }
}
.pwc:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; }

@media (max-width: 1100px) {
  .pw-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 44px 3vw; }
}
@media (max-width: 900px) {
  .pw-grid { grid-template-columns: 1fr; gap: 44px; }
}

/* the professional-work pages reuse the case-study chrome; only the ground
   colour is theirs, keyed to the graphite the practice sections already use */
.chero.work { background: radial-gradient(140% 120% at 46% 0%, #201a15 0%, #151110 55%, #0e0c0a 100%); }
/* these titles are sentences, not two-word product names, so the 10ch measure
   the case studies use would push them to four lines and off the top */
/* a sentence title plus a two-line lede does not fit the case-study hero, which
   was sized for two-word product names and clips what overflows */
.chero.work { min-height: min(540px, 70vh); }
.chero.work .inner { padding-top: 52px; }
.chero.work h1 { max-width: 20ch; font-size: clamp(2rem, 4.8vw, 4.2rem); }
.chero.work .lede { max-width: 52ch; }

/* ============================================================
   APPLIED AI — the four complaints
   ------------------------------------------------------------
   The claim ("I keep up with AI") sits in the dark band above.
   This is the proof, on paper: what actually causes each
   complaint and what removes it, each one pointing at a system
   where it is already running.
   ============================================================ */
.aifix { padding: 84px var(--inset) 76px; }
.aifix .sec-intro { margin-bottom: 44px; }
.fx {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1.45fr);
  gap: 3vw;
  padding: 30px 0;
  border-top: 1px solid var(--rule);
  align-items: start;
}
.fx:last-child { border-bottom: 1px solid var(--rule); }
.fx .q {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.15rem, 2.1vw, 1.75rem);
  line-height: 1.15;
  letter-spacing: -.02em;
  max-width: 16ch;
}
.fx .a p { color: var(--ink-2); max-width: 62ch; margin-bottom: .9em; }
.fx .a p b { color: var(--ink); font-weight: 400; }
.fx .a p:last-of-type { margin-bottom: 0; }
.fx .where {
  display: block; margin-top: 16px; color: var(--ink-3);
  font-family: var(--font-mono); font-size: .58rem; letter-spacing: .18em; text-transform: uppercase;
}
.fx .where a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 2px; }
.fx .where a:hover { color: var(--ink); border-color: var(--ink); }
.aifix .close {
  margin-top: 44px; max-width: 66ch;
  font-family: var(--font-display); font-weight: 400;
  font-size: clamp(1.05rem, 1.9vw, 1.5rem); line-height: 1.4; letter-spacing: -.015em;
}
.aifix .close b { font-weight: 600; }
@media (max-width: 900px) {
  .aifix { padding-block: 64px 56px; }
  .fx { grid-template-columns: 1fr; gap: 14px; padding: 26px 0; }
  .fx .q { max-width: none; }
}

/* ============================================================
   APPLIED AI — services
   Built from the vocabulary the rest of the site already uses: the
   numbered section rhythm, the loop frames from the work index, and the
   cinematic band. Nothing here is a new visual language, which is the
   point — a services page that looked like a different site would read
   as a landing page bolted on.
   ============================================================ */
/* There is no photograph behind this hero, so the scrub is the whole image and
   the veil only has to protect the first two thirds where the type sits. */
.ai-hero .hero-still { width: 74%; }
.ai-hero .hero-still::after {
  background:
    linear-gradient(90deg, var(--graphite) 0%, rgba(19, 20, 22, .9) 22%, rgba(19, 20, 22, .35) 52%, transparent 82%),
    linear-gradient(180deg, rgba(19, 20, 22, .55), transparent 26%, transparent 78%, rgba(19, 20, 22, .7));
}
.ai-hero .hero-still video.hero-scrub { opacity: 0; }
.ai-hero .hero-still.scrub-live video.hero-scrub { opacity: 1; }
/* the section labels must clear the sticky masthead when a link jumps to one */
.svc, .eng-row { scroll-margin-top: 96px; }

.ai-position { padding-top: 84px; padding-bottom: 20px; }

.services { padding-top: 84px; padding-bottom: 92px; }
.svc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: 4vw;
  align-items: start;
  padding: 54px 0;
  border-top: 1px solid var(--rule);
}
.svc:nth-child(even) .svc-fig { order: 2; }
.svc-fig {
  position: relative; aspect-ratio: 16 / 10;
  overflow: hidden; overflow: clip;
  border: 1px solid var(--rule); background: #0d0f10;
  position: sticky; top: 96px;
}
.svc-fig img, .svc-fig video {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
.svc-fig video { opacity: 0; transition: opacity var(--dur-2) var(--ease-micro); }
.svc-fig.playing video { opacity: 1; }
.svc-fig.playing img { opacity: 0; transition: opacity var(--dur-2) var(--ease-micro); }

.svc-body .n { display: block; color: var(--ink-3); margin-bottom: 14px; }
.svc-body h3 { font-size: clamp(1.6rem, 3.2vw, 2.6rem); margin-bottom: 16px; }
.svc-body .desc { color: var(--ink-2); max-width: 52ch; margin-bottom: 22px; font-size: 1.01rem; }
.svc-body .desc b { color: var(--ink); font-weight: 400; }
.svc-list { list-style: none; margin: 0 0 22px; padding: 0; }
.svc-list li {
  position: relative; padding: 11px 0 11px 26px; max-width: 54ch;
  border-top: 1px solid var(--rule); color: var(--ink-2); font-size: .92rem; line-height: 1.55;
}
.svc-list li::before {
  content: ""; position: absolute; left: 2px; top: 1.35em;
  width: 9px; height: 1px; background: var(--ink-3);
}
.svc-out { color: var(--ink-2); font-size: .92rem; max-width: 54ch; margin-bottom: 18px; }
.svc-out b { color: var(--ink); font-weight: 600; }
.svc-body .more {
  display: inline-block; font-family: var(--font-mono); font-size: .68rem;
  letter-spacing: .18em; text-transform: uppercase; text-decoration: none;
  color: var(--ink-3); border-bottom: 1px solid var(--rule); padding-bottom: 6px;
  transition: color var(--dur-micro) var(--ease-micro), border-color var(--dur-micro) var(--ease-micro);
}
.svc-body .more:hover { color: var(--ink); border-color: var(--ink); }

.engagement { padding-top: 78px; padding-bottom: 88px; border-top: 1px solid var(--rule); }
.eng-rows { display: grid; grid-template-columns: repeat(3, 1fr); gap: 3vw; }
.eng-row { display: grid; grid-template-columns: 48px 1fr; gap: 16px; align-items: baseline; }
.eng-row .n { color: var(--ink-3); }
.eng-row h3 { font-size: clamp(1.15rem, 1.9vw, 1.5rem); margin-bottom: 12px; }
.eng-row p { color: var(--ink-2); font-size: .92rem; line-height: 1.62; }

.ai-limits { min-height: 0; padding: 104px 0; }
.ai-limits .inner { padding: 0 var(--inset); }
.ai-limits h2 { font-size: clamp(2rem, 4.6vw, 4rem); max-width: 20ch; }
.ai-limits p { max-width: 62ch; margin-top: 26px; color: var(--ivory-2); font-size: 1.02rem; }

@media (max-width: 900px) {
  .ai-hero .hero-still { width: 100%; }
  .svc { grid-template-columns: 1fr; gap: 26px; padding: 40px 0; }
  .svc:nth-child(even) .svc-fig { order: 0; }
  .svc-fig { position: relative; top: auto; }
  .eng-rows { grid-template-columns: 1fr; gap: 30px; }
  .ai-limits { padding: 76px 0; }
  .ai-limits .inner { padding: 0 var(--pad); }
}

/* ============================================================
   PHASE 1 — the label voice, put back in its place

   Measured before this block: 105 elements set in IBM Plex Mono against 60
   body paragraphs. The mono label — 9-10px, uppercase, wide tracking — was
   the most frequent voice on the site, applied to skip links, tag rows, read
   -more links, metric captions and, worst of all, to a whole paragraph of
   confidentiality copy.

   A mono label is an accent. At 105 repetitions it stops reading as technical
   precision and becomes decoration, which is the single biggest reason the
   page read as a junior technical aesthetic rather than as a practice.

   It now survives in exactly three places, all of which mean something:

     · section numbers and kickers   — an index mark
     · the metric caption            — a unit, under a figure
     · tabular figures in tables     — where monospacing is the point

   Everything else returns to Inter at a readable size. No copy changes, no
   structural changes: this block only stops shouting.
   ============================================================ */

/* --- the skip link is a control, not a specimen --- */
.skip {
  font-family: var(--font-body);
  font-size: .82rem;
  letter-spacing: 0;
  text-transform: none;
}

/* --- the language switch is a control too --- */
.lang-btn, .lang-sep {
  font-family: var(--font-body);
  font-size: .8rem;
  letter-spacing: .01em;
}

/* --- a paragraph of legal copy is a paragraph.
       The original rule is `.engineer .confidential`, so this has to match it
       to win rather than rely on order. --- */
.engineer .confidential,
.confidential {
  font-family: var(--font-body);
  font-size: .82rem;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.7;
  color: var(--ivory-3);
}

/* --- tag rows: 35 of these on the homepage alone --- */
.pwc .tags span,
.pw .tags span,
.standards ul li,
.tr-chips span {
  font-family: var(--font-body);
  font-size: .78rem;
  letter-spacing: 0;
  text-transform: none;
}

/* --- "read the detail" is a link, and a link reads as text --- */
.pwc .more,
.svc-body .more,
.fx .where {
  font-family: var(--font-body);
  font-size: .84rem;
  letter-spacing: 0;
  text-transform: none;
}

/* --- captions and footnotes --- */
.note time,
.metarow .m b,
.stat-spread .foot,
.cs-stat .l {
  font-family: var(--font-body);
  font-size: .8rem;
  letter-spacing: 0;
  text-transform: none;
}

/* --- the hero baseline carries `.mono` on the row, so the children inherit
       it. These are five capability words, not five index marks. --- */
.hero .baseline,
.hero .baseline span {
  font-family: var(--font-body);
  font-size: .8rem;
  letter-spacing: .01em;
  text-transform: none;
}

/* --- case-study meta and the contact strapline are sentences --- */
.case .meta .mono,
.contact .row .mono {
  font-family: var(--font-body);
  font-size: .84rem;
  letter-spacing: 0;
  text-transform: none;
}

/* --- what the mono is FOR: the caption under a figure, as a unit --- */
.metrics .m .l {
  font-size: .64rem;
  letter-spacing: .18em;
}

/* ============================================================
   PHASE 1 — one scale instead of twenty-six

   Twenty-six distinct size/weight/case combinations were in use on the
   homepage. A designed system has six or eight. These are the roles; the
   strays below are folded into them.
   ============================================================ */
.hero .lede,
.sec-intro,
.svc-body .desc { font-size: 1rem; line-height: 1.62; }

.engineer .body p,
.pwc .desc,
.svc-list li,
.svc-out,
.acc-row .detail,
.acc-items p,
.eng-row p,
.hero .lede-2 { font-size: .92rem; line-height: 1.62; }

/* one dim tone for supporting copy on paper, one on graphite */
.hero .lede { color: var(--ivory-2); }

/* ============================================================
   PHASE 2 — one gesture, and the decoration retired

   Nine motion devices were running on one page: scramble, scroll reveal,
   masked heading reveal, the scrubbed hero, nine project loops, the
   companion, the sculpture, a CSS light sweep and pointer drift. Nothing was
   the signature because everything was trying to be.

   The sculpture is the signature — it is the only thing here that could not
   be built by anyone in an afternoon. These two are decoration and they go:
   the scramble was a terminal cliché on labels that are already index marks,
   and the sweep was a highlight sliding across images nobody was looking at.
   ============================================================ */
.media-sweep { display: none !important; }
.scramble { white-space: normal; }

/* ============================================================
   PHASE 3 — the figure stops pretending to move

   It was a still image that teleported between four stations as you scrolled.
   An interpolated jump of a static object is not motion, it is an element
   changing position, and at 38vw it also turned the homepage into a large
   photograph with a website underneath.

   Producing real motion needs a video the account cannot currently generate,
   so the honest resolution is the other one: the figure becomes a composed
   still, in the single place where a person is genuinely the subject — the
   invitation to work together, which is also where the page lost its image
   when the sculpture moved up to the thesis.
   ============================================================ */
.contact.has-figure { position: relative; overflow: hidden; overflow: clip; }
.contact.has-figure > *:not(.contact-figure) { position: relative; z-index: 3; }
.contact-figure {
  position: absolute; inset: 0 0 0 auto;
  width: min(38vw, 520px); z-index: 1;
  pointer-events: none;
}
.contact-figure picture, .contact-figure img {
  display: block; width: 100%; height: 100%;
  object-fit: cover; object-position: 52% 34%;
}
.contact-figure img {
  filter: brightness(.86) contrast(1.05) saturate(.95);
  opacity: .9;
}
/* a feathered left edge so the plate dissolves into the page's own graphite
   and the type never sits on a photograph */
.contact-figure::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(90deg, var(--graphite) 0%, rgba(19, 20, 22, .84) 22%, rgba(19, 20, 22, .2) 58%, transparent 86%),
    linear-gradient(180deg, rgba(19, 20, 22, .55), transparent 26%, transparent 74%, rgba(19, 20, 22, .6));
}
@media (max-width: 900px) {
  .contact-figure { display: none; }
}

/* ============================================================
   PHASE 4 — figures that are figures

   Three metric bands carried nine "numbers". Only one of them was a
   measurement. "MDR" is a regulation, "Hosted" and "Open" are deployment
   choices, "EU AI Act" is a statute, and "26" was an age presented as a
   headline KPI on a site selling regulated engineering — the one number a
   conservative buyer would rather not be led with, printed largest.

   A metric band with no metrics in it is the shape of evidence without the
   substance, and on a page whose whole argument is accountability that costs
   more credibility than it buys.

   What is left: two measurements, each linking to the page that shows the
   method behind it. The rest became prose or was removed, since the trajectory
   heading already says the age in words.
   ============================================================ */
.metrics-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 62ch; }
.metrics-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); max-width: 78ch; gap: 3vw; }
/* Three figures share the width, and one of them is "1,842 → 312" — "1.842 →
   312" in Spanish, wider still. nowrap keeps it on one line, so the size is
   what has to give: at the previous 3.1rem it ran past its own rule and into
   the figure beside it. Sized so the longest of the two languages fits. */
.metrics-3 .m .v { font-size: clamp(1.75rem, 2.9vw, 2.7rem); letter-spacing: -.03em; }
.metrics a.m {
  text-decoration: none; color: inherit; display: block;
  border-top: 1px solid var(--rule-l); padding-top: 18px;
  transition: border-color var(--dur-micro) var(--ease-micro);
}
.metrics a.m:hover { border-color: var(--mint); }
/* a caption is a label, not a sentence: the link carries the detail */
.metrics a.m .l { max-width: 24ch; }
.metrics-2 .m .v, .metrics-3 .m .v { white-space: nowrap; }

.statement-line {
  margin-top: 34px; max-width: 62ch;
  color: var(--ivory-2); font-size: .95rem; line-height: 1.65;
}
@media (max-width: 900px) {
  .metrics-2, .metrics-3 { grid-template-columns: 1fr; }
}

/* ============================================================
   ABOUT — v2. Four numbered positions, built for a desk first.
   The previous page was one narrow reading column with the label in a
   gutter: fine on a phone, half-empty at 1440. Now the label sits in a
   200px rail and the copy, the three habits and the stack table take the
   remaining width. Same rhythm as the notes and the homepage sections.
   ============================================================ */
.about-page .about-hero {
  min-height: 400px;
  padding: clamp(82px, 7.5vw, 112px) var(--inset) 40px;
}
.about-page .about-media { width: 46%; }
.about-page .about-hero h1 {
  max-width: 22ch;
  font-size: clamp(2.5rem, 3.2vw, 3.2rem);
  line-height: 1.04;
}
.about-page .reveal { opacity: 1; transform: none; clip-path: none; transition: none; }

.ab {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  column-gap: clamp(36px, 4vw, 68px);
  row-gap: 30px;
  padding-top: 68px; padding-bottom: 68px;
  border-top: 1px solid var(--rule);
  align-items: start;
}
.ab:first-of-type { border-top: 0; padding-top: 80px; }
.ab-head { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: start; }
.ab-head > .mono { grid-column: 1; color: var(--ink-3); padding-top: 10px; }
.ab-head > h2 {
  grid-column: 2; margin: 0; max-width: 24ch;
  font-size: clamp(2.1rem, 3.2vw, 3.2rem); line-height: 1.04; letter-spacing: -.02em;
}
.ab-head > .ab-note {
  grid-column: 2; margin: 18px 0 0; max-width: 62ch;
  color: var(--ink-3); font-size: .95rem; line-height: 1.6;
}
.ab-body { grid-column: 2; max-width: 780px; }
.ab-body p { color: var(--ink-2); max-width: 66ch; margin-bottom: 1.2em; font-size: 1.06rem; line-height: 1.7; }
.ab-body p:last-child { margin-bottom: 0; }
.ab-body p b { color: var(--ink); font-weight: 400; }
.ab-body a, .ab-cell a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--rule);
  transition: border-color var(--dur-micro) var(--ease-micro); }
.ab-body a:hover, .ab-cell a:hover { border-color: var(--ink); }

/* facts strip: four short pairs under the practice copy */
.ab-facts {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0 26px;
  margin-top: 34px; padding-top: 22px; border-top: 1px solid var(--rule);
}
.ab-facts > div { display: grid; gap: 8px; align-content: start; }
.ab-facts b {
  font: 500 .72rem/1 var(--font-mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3);
}
.ab-facts span { color: var(--ink-2); font-size: .95rem; line-height: 1.5; }

/* three habits, three columns, hairline between */
.ab-three {
  grid-column: 2;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 clamp(26px, 3vw, 44px);
}
.ab-cell { position: relative; padding-right: clamp(4px, 1vw, 12px); }
.ab-cell + .ab-cell { padding-left: clamp(26px, 3vw, 44px); margin-left: calc(-1 * clamp(26px, 3vw, 44px)); border-left: 1px solid var(--rule); }
.ab-cell h3 { font-size: 1.22rem; line-height: 1.25; letter-spacing: -.01em; margin: 0 0 14px; max-width: 22ch; }
.ab-cell p { color: var(--ink-2); font-size: .98rem; line-height: 1.65; margin: 0; }
.ab-cell p em { font-style: normal; color: var(--ink); border-bottom: 1px solid var(--rule); }

/* stack: hairline rows, label column then the tools */
.ab-stack { grid-column: 2; border-top: 1px solid var(--rule); }
.ab-row {
  display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: 0 clamp(24px, 3vw, 48px);
  padding: 16px 0; border-bottom: 1px solid var(--rule); align-items: baseline;
}
.ab-row b {
  font: 500 .74rem/1.4 var(--font-mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink); padding-top: 3px;
}
.ab-row span { color: var(--ink-2); font-size: .98rem; line-height: 1.6; }
/* the stack is an inventory to look at, not paragraphs to read: every tool
   is a chip, in the same mono vocabulary the project pages already use */
.ab-row .chips { display: flex; flex-wrap: wrap; gap: 7px; }
.ab-row .chips i {
  font: 400 .58rem/1 var(--font-mono); font-style: normal;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
  border: 1px solid var(--rule); border-radius: 3px; padding: 6px 10px;
  white-space: nowrap;
}

.ab-close { border-bottom: 1px solid var(--rule); }
.about-mail { margin-top: 2em; }
.about-mail a { font-size: 1.2rem; border-bottom: 1px solid var(--rule); padding-bottom: 6px; }
.about-page .about-foot { padding-block: 30px; align-items: center; background: var(--paper); }
.about-page .about-foot span { max-width: 58ch; text-align: center; }

@media (max-width: 1100px) {
  .ab { grid-template-columns: 150px minmax(0, 1fr); column-gap: 30px; }
  .ab-row { grid-template-columns: 150px minmax(0, 1fr); }
  .ab-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 20px; }
}
@media (max-width: 900px) {
  .about-page .about-hero { min-height: 330px; padding: 86px var(--pad) 42px; }
  .about-page .about-media { width: 100%; opacity: .42; }
  .about-page .about-media img { object-position: 66% center; }
  .about-page .about-hero h1 {
    max-width: 15ch; margin-top: 22px;
    font-size: clamp(2rem, 8.2vw, 2.45rem); line-height: 1.03;
  }
  .ab { display: block; padding-top: 48px; padding-bottom: 48px; }
  .ab:first-of-type { padding-top: 52px; }
  .ab-head { display: block; }
  .ab-head > .mono { display: block; padding: 0; margin-bottom: 14px; }
  .ab-head > h2 { max-width: 18ch; margin: 0 0 22px; font-size: clamp(1.8rem, 7.6vw, 2.3rem); }
  .ab-head > .ab-note { margin: -6px 0 22px; }
  .ab-body { max-width: none; }
  .ab-body p { font-size: 1.02rem; line-height: 1.66; }
  .ab-facts { grid-template-columns: 1fr 1fr; gap: 18px 18px; margin-top: 26px; }
  .ab-three { display: block; }
  .ab-cell + .ab-cell { padding-left: 0; margin-left: 0; border-left: 0; margin-top: 26px; padding-top: 24px; border-top: 1px solid var(--rule); }
  .ab-cell h3 { max-width: none; font-size: 1.12rem; }
  .ab-row { grid-template-columns: 1fr; gap: 6px; padding: 14px 0; }
  .ab-row span { font-size: .96rem; }
  .about-page .about-foot { align-items: flex-start; padding-block: 28px; }
  .about-page .about-foot span { text-align: left; }
}

.site-footer {
  position: relative;
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 28px var(--pad);
  border-top: 1px solid var(--rule);
  color: var(--ink-3);
  font-size: .68rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.site-footer a { color: inherit; text-decoration: none; }
.site-footer a:hover { color: var(--mint); }
@media (max-width: 600px) {
  .site-footer { align-items: flex-start; flex-direction: column; gap: 12px; }
}
/* Every section is positioned, which puts it in the same paint layer as the
   stage and — being later in the document — over it. No z-index: a section that
   made its own stacking context would trap the backdrop below.

   `#smooth-content` is the second address the same sections live at: the smooth
   scroll layer needs one element it can transform, so everything below the
   masthead is inside it. The stage stays outside, because a fixed backdrop
   inside a transformed box is no longer fixed to anything. */
body > section,
#smooth-content > section { position: relative; }

@media (max-width: 900px) {
  body .reveal,
  body .sec-head h2.reveal,
  body .cine .inner h2.reveal,
  body .contact h2.reveal,
  body .engineer h2.reveal {
    opacity: 1;
    transform: none;
    clip-path: none;
    transition: none;
  }
}

.medical-shot img,
.medical-workstation img { object-position: center center; }

/* ============================================================
   THE MOTION LAYER — GSAP
   ------------------------------------------------------------
   Everything under `.js-gsap` is what the page looks like once
   assets/js/motion.js has taken ownership of the entrances. The
   rules above stay exactly as they were: they are what a reader
   sees with Save-Data, with reduced motion, or if the library
   never arrives, and that page has to remain a finished page
   rather than a degraded one.
   ============================================================ */

/* The CSS reveal and the mask wipe both stand down. GSAP sets its own hidden
   state inline in the same task that adds this class, so there is no frame in
   which an element is neither hidden by one system nor the other. */
.js-gsap .reveal,
.js-gsap .sec-head h2.reveal,
.js-gsap .cine .inner h2.reveal,
.js-gsap .contact h2.reveal,
.js-gsap .engineer h2.reveal {
  opacity: 1;
  transform: none;
  clip-path: none;
  transition: none;
  transition-delay: 0s;
}

/* A split line travels its own height behind a mask; telling the compositor
   in advance is the difference between a heading that rises and one that
   rises after a repaint. */
.m-line { will-change: transform; }

/* ---------- the independent systems, as a row that opens ----------
   Four products in one row, all on screen at once; the one the reader points
   at opens wider with its description and action, the others narrow to their
   picture and their name. The grid is still the markup, so a phone, a reader
   with reduced motion and a browser without the library keep the stack. */
.js-gsap .systems.is-row .project-grid.is-row-track {
  display: flex;
  gap: clamp(14px, 1.4vw, 22px);
  align-items: stretch;
}
.js-gsap .project-grid.is-row-track > .project-card {
  flex: 1 1 0;
  min-width: 0;
  /* the row owns the width; the hover lift would fight the growth */
  transition: border-color .28s ease, box-shadow .28s ease;
}
.js-gsap .project-grid.is-row-track > .project-card:hover { transform: none; }
.js-gsap .project-grid.is-row-track > .project-card.is-open { border-color: rgba(20,20,19,.38); box-shadow: 0 20px 50px rgba(20,20,19,.11); }
/* the picture takes whatever height the text does not: a closed card is a
   tall render with its name under it, an open one gives the room to its
   description and its action, and the row stays one height */
.js-gsap .project-grid.is-row-track > .project-card { display: flex; flex-direction: column; }
/* The row is one fixed height, set by the script from the tallest open card,
   so opening a card never changes the height of the page: that height change
   was what made the row jump, the smoother remeasure and the masthead hide
   while the reader was only pointing. The picture takes whatever height the
   text does not. */
.js-gsap .project-grid.is-row-track .stagevis { aspect-ratio: auto; flex: 1 1 auto; min-height: 0; height: auto; }
/* the picture's height in the tallest open card — what the script measures
   the row against; afterwards the picture just fills what the text leaves */
.js-gsap .project-grid.is-row-track.is-measuring .stagevis { flex: 0 0 clamp(220px, 16.5vw, 280px); }
.js-gsap .project-grid.is-row-track .project-card__body { flex: 0 0 auto; padding: 22px 24px 24px; gap: 8px 16px; }
.js-gsap .project-grid.is-row-track .project-card__more { overflow: hidden; }
/* while the widths move, the shadow would be repainted at full blur every
   frame; the open card is marked by its border alone until it settles */
.js-gsap .project-grid.is-row-track > .project-card.is-open { box-shadow: none; }
.js-gsap .project-grid.is-row-track > .project-card.is-open.is-settled { box-shadow: 0 20px 50px rgba(20,20,19,.11); }
.js-gsap .project-grid.is-row-track h3 { font-size: clamp(1.25rem, 1.8vw, 1.7rem); line-height: 1.1; }
.js-gsap .project-grid.is-row-track .desc { font-size: .92rem; margin-bottom: 10px; }

/* how far through the rail the reader is — the same hairline vocabulary as the
   progress line under the masthead, because it is the same idea */
.rail-progress {
  position: relative;
  height: 2px;
  margin-top: 34px;
  background: var(--rule);
}
.rail-progress span {
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* ---------- figures that hold ----------
   `position: sticky` measures itself against a scrolling ancestor. Under the
   smooth-scroll layer the content is transformed rather than scrolled, so
   there is no such ancestor and ScrollTrigger pins these instead. */
.js-pinned-figures .svc-fig { position: relative; top: auto; }

/* An entrance that has not been scrolled into yet is an element at opacity 0,
   and a printer never scrolls. Everything the motion layer is holding back is
   released for print, inline styles included — which is why this is the one
   place in the file that needs !important. */
@media print {
  body * {
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
    clip-path: none !important;
  }
}

/* ============================================================
   THE 2026-08-16 HOME
   ------------------------------------------------------------
   The page opens on applied AI, says everything once, and moves
   where movement carries meaning: a stack of layers for the
   hero, a row of work that travels, a rail of systems.
   ============================================================ */

/* ---------- hero: copy on the left, the system as an object on the right ---------- */
.hero-ai {
  display: grid;
  grid-template-columns: minmax(0, 1.16fr) minmax(0, .84fr);
  align-items: center;
  gap: 3vw;
  padding-right: var(--inset);
}
/* The copy takes the width its column has. A 62ch cap here left a dead
   band between the sentence and the visual and pushed the Spanish h1 into
   four lines with "UNA" alone on one of them. */
.hero-ai .hero-copy { max-width: none; }
/* three lines, not six: the display size comes down a step so the sentence
   holds "AI systems / that survive / an audit." in either language */
.hero-ai h1 { max-width: 15ch; font-size: clamp(2.2rem, 4.8vw, 4.8rem); }
.hero-ai .lede { max-width: 58ch; }
.hero-ai .lede-2 { max-width: 58ch; }

/* The object. Four planes, stacked in real CSS 3D and viewed from above and
   to the side, the way an exploded drawing shows an assembly. The model is
   one plane inside it, bounded above by rules and below by evidence — which is
   the whole argument of the page as a shape.

   Nothing here needs a GPU: it is four rounded rectangles with a transform,
   composited like any other transform. The `.tilt` wrapper is what GSAP
   turns with the pointer, the `.stack` carries the fixed viewing angle, and
   each plane's depth is its own transform, so no two systems ever write the
   same property. */
.hero-stack {
  position: relative;
  z-index: 3;
  height: min(520px, 60vh);
  perspective: 1500px;
  perspective-origin: 50% 40%;
}
.hero-stack .tilt {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
}
.hero-stack .stack {
  position: absolute;
  left: 50%; top: 50%;
  width: min(360px, 34vw); height: min(240px, 22.6vw);
  margin: calc(min(240px, 22.6vw) / -2) 0 0 calc(min(360px, 34vw) / -2);
  transform-style: preserve-3d;
  transform: rotateX(56deg) rotateZ(-34deg);
}
.hero-stack .plane {
  position: absolute; inset: 0;
  border-radius: 14px;
  border: 1px solid rgba(241, 239, 233, .22);
  background:
    linear-gradient(135deg, rgba(241, 239, 233, .10), rgba(241, 239, 233, .03) 55%, rgba(241, 239, 233, .06));
  /* No drop shadow. A 60px blur on four planes inside a 3D context was, on
     its own, the difference between one and twelve per cent of janky frames
     while the page scrolled past — measured by hiding nothing else. The
     bottom edge does the same job of separating the planes and costs a line. */
  border-bottom-color: rgba(241, 239, 233, .40);
  backface-visibility: hidden;
  /* the no-JS and reduced-motion state is the assembled one; the motion
     layer overwrites this transform when it animates */
  transform: translateZ(calc(var(--i) * 74px));
}
.hero-stack .p4 { --i: 0; }
.hero-stack .p3 { --i: 1; }
.hero-stack .p2 { --i: 2; }
.hero-stack .p1 { --i: 3; }
/* the model plane is the one that is not like the others */
.hero-stack .p3 {
  border-color: rgba(217, 142, 79, .55);
  background:
    linear-gradient(135deg, rgba(217, 142, 79, .18), rgba(217, 142, 79, .05) 60%, rgba(217, 142, 79, .12));
}
.hero-stack .plane span {
  position: absolute; left: 18px; bottom: 14px;
  color: var(--ivory-2);
  font-size: .58rem;
  letter-spacing: .22em;
  /* printed on the plane, foreshortened with it — a label that stood upright
     to the viewer would read as a caption floating in front of the drawing */
  white-space: nowrap;
}
.hero-stack .p3 span { color: var(--mint); }
.hero-stack .plane .tag {
  /* beside its own label, not in the corner: the far corner of this plane
     projects on top of the label of the plane above it */
  position: absolute; left: 18px; bottom: 36px;
  font-style: normal;
  font-size: .5rem;
  letter-spacing: .18em;
  padding: 4px 8px;
  border: 1px solid rgba(217, 142, 79, .55);
  border-radius: 999px;
  color: var(--mint);
}
/* the four ports where the layers connect: a thread of dots down the spine */
.hero-stack .stack::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 2px; height: calc(74px * 3);
  margin-left: -1px;
  background: repeating-linear-gradient(180deg, rgba(217, 142, 79, .8) 0 3px, transparent 3px 11px);
  transform-origin: 50% 0;
  transform: rotateX(-90deg);
  opacity: .8;
}

@media (max-width: 1100px) {
  /* minmax(0, …), not 1fr: a bare 1fr track is minmax(auto, 1fr), and its
     minimum is the widest thing that refuses to wrap — the kicker did, so on a
     phone the whole column was laid out wider than the screen */
  .hero-ai { grid-template-columns: minmax(0, 1fr); gap: 0; padding-right: 0; }
  .hero-ai .kicker span { white-space: normal; }
  .hero-ai .hero-copy { padding-bottom: 12px; }
  .hero-stack { height: 300px; margin: 26px 0 70px; perspective: 1100px; }
  .hero-stack .stack { width: 260px; height: 172px; margin: -86px 0 0 -130px; }
  .hero-stack .plane { transform: translateZ(calc(var(--i) * 52px)); }
  .hero-stack .stack::after { height: calc(52px * 3); }
}
@media (max-width: 900px) {
  .hero-stack { height: 240px; margin-bottom: 20px; }
}
@media (max-width: 520px) {
  .hero-stack .stack { width: 210px; height: 140px; margin: -70px 0 0 -105px; }
  .hero-stack .plane { transform: translateZ(calc(var(--i) * 44px)); }
  .hero-stack .stack::after { height: calc(44px * 3); }
  .hero-stack .plane span { font-size: .5rem; left: 12px; bottom: 10px; }
}

/* ---------- applied AI: four cards, one answer each ---------- */
.fx-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 2vw, 28px);
  margin-top: 8px;
}
.fx-grid .fx {
  display: block;
  position: relative;
  padding: 28px 28px 26px 30px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: #f3f0e8;
  box-shadow: 0 1px 0 rgba(255,255,255,.7) inset;
}
.fx-grid .fx:last-child { border-bottom: 1px solid var(--rule); }
.fx-grid .fx::before {
  content: ""; position: absolute; inset: 0 auto 0 0; width: 4px;
  border-radius: 12px 0 0 12px; background: var(--mint);
}
.fx-grid .fx-n { display: block; color: var(--ink-3); margin-bottom: 18px; }
.fx-grid .q {
  max-width: none;
  font-size: clamp(1.25rem, 2.1vw, 1.7rem);
  margin-bottom: 14px;
}
.fx-grid .a { color: var(--ink-2); font-size: .95rem; line-height: 1.55; max-width: 44ch; }
.fx-grid .where { margin-top: 18px; }
.ai-foot {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(20px, 3vw, 48px);
  align-items: end;
  margin-top: 40px;
}
.ai-foot .close { margin-top: 0; }
.ai-foot .statement-line { color: var(--ink-2); font-size: .92rem; line-height: 1.55; }
.ai-foot .more {
  grid-column: 1 / -1;
  justify-self: start;
  color: var(--ink);
  text-decoration: none;
  font-size: .95rem;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 5px;
  transition: border-color var(--dur-micro) var(--ease-micro), letter-spacing var(--dur-2) var(--ease-micro);
}
.ai-foot .more:hover { border-color: var(--ink); letter-spacing: .01em; }
@media (max-width: 900px) {
  .fx-grid { grid-template-columns: 1fr; }
  .fx-grid .fx { padding: 22px 20px 22px 24px; }
  .ai-foot { grid-template-columns: 1fr; margin-top: 30px; }
}

/* ---------- professional work: a ring the reader turns ----------
   The section is full-bleed and its head is wrapped inside it, so the ring can
   use the whole width while the type keeps the page's inset. On a desktop the
   motion layer stands the seven cards on a circle in CSS 3D and pins the
   section while the scroll turns it; on a phone it is a strip that snaps; with
   neither, the plain grid stands. */
.prowork { padding-top: 88px; padding-bottom: 32px; }
.prowork .standards { margin-top: 44px; }

.js-gsap .prowork.is-ring {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(280px, 30%) minmax(0, 1fr);
  align-items: center;
  gap: 0 clamp(20px, 3vw, 56px);
  padding: 0 0 0 var(--inset);
  overflow: clip;
}
.js-gsap .prowork.is-ring .ring-aside { display: flex; flex-direction: column; justify-content: center; }
.js-gsap .prowork.is-ring .ring-aside > .wrap { padding: 0; }
.js-gsap .prowork.is-ring .sec-head { display: block; margin-bottom: 20px; }
.js-gsap .prowork.is-ring .sec-head h2 { font-size: clamp(1.9rem, 2.9vw, 3.1rem); margin-top: 16px; }
.js-gsap .prowork.is-ring .sec-intro { margin: 0; max-width: none; font-size: .95rem; }

/* the ring's stage: the perspective lives here, the ring turns inside it, and
   the standards list waits below it, inside the pinned frame */
.js-gsap .prowork.is-ring .ring-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 30px;
  min-width: 0;
  /* room at the top for the masthead, which comes back over the section
     when the reader scrolls up */
  padding: 76px 0 24px;
  perspective: 1500px;
  perspective-origin: 50% 40%;
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
}
.js-gsap .prowork.is-ring .ring-stage:active { cursor: grabbing; }
.js-gsap .prowork.is-ring .ring-stage .standards { margin: 0; padding: 0; align-self: stretch; }

/* the ring itself is one card wide and one card tall; the cards stand on the
   circle around its centre and the whole thing is pushed back by the radius,
   so the front card sits at the screen plane at true size */
.js-gsap .pw-grid.ring {
  display: block;
  position: relative;
  width: clamp(300px, 24vw, 400px);
  height: var(--ring-h, 560px);
  margin: 0 auto;
  padding: 0;
  transform-style: preserve-3d;
  will-change: transform;
}
.js-gsap .pw-grid.ring > .pwc {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  /* the ring writes the transform and the opacity; nothing else may */
  transition: none;
  /* a card that stands in space needs a body: the same card the systems index
     uses, so the site has one kind of card */
  background: #f3f0e8;
  border: 1px solid rgba(20,20,19,.18);
  border-radius: 12px;
  overflow: hidden;
}
.js-gsap .pw-grid.ring > .pwc:hover,
.js-gsap .pw-grid.ring > .pwc:focus-within { transform: none; }
.js-gsap .pw-grid.ring > .pwc .shot { border: 0; border-bottom: 1px solid rgba(20,20,19,.16); }
.js-gsap .pw-grid.ring > .pwc .body { padding: 18px 22px 24px; }
.js-gsap .pw-grid.ring > .pwc.is-front { border-color: rgba(20,20,19,.34); }
.js-gsap .pw-grid.ring > .pwc:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.ring-nav {
  display: flex;
  align-items: center;
  gap: 18px;
  margin-top: 30px;
}
.js-gsap .prowork.is-ring .rail-progress { margin-top: 26px; max-width: 220px; }
.ring-nav button {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  font-size: .95rem;
  cursor: pointer;
  transition: border-color var(--dur-micro) var(--ease-micro),
              background var(--dur-micro) var(--ease-micro),
              color var(--dur-micro) var(--ease-micro);
}
.ring-nav button:hover:not(:disabled) { border-color: var(--ink); background: var(--ink); color: var(--paper); }
.ring-nav button:disabled { opacity: .3; cursor: default; }
.ring-nav button:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.ring-count { color: var(--ink-3); }

@media (max-width: 900px) {
  .pw-grid {
    display: flex;
    grid-template-columns: none;
    gap: 16px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--pad);
    padding: 0 var(--pad) 6px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .pw-grid::-webkit-scrollbar { display: none; }
  .pw-grid > .pwc {
    flex: 0 0 min(78%, 340px);
    scroll-snap-align: start;
  }
  .prowork .standards { margin-top: 32px; }
}

/* ---------- thesis without the object ----------
   The section used to be composed around the sculpture, with half its frame
   left empty for it. Without it the copy takes the width it needs and the
   band is as tall as its content, which on this page is a heading, one line
   and three figures. */
.cine.credo { min-height: 0; padding-block: 96px; }
.cine.credo .inner h2 { max-width: 20ch; }
.cine.credo .inner > p { max-width: 58ch; }
@media (max-width: 900px) {
  .cine.credo { padding-block: 72px; }
}

/* ---------- capabilities: the list beside what it looks like ----------
   Six rows and, beside them, the piece of work that carries whichever one is
   open. The rows come down a size: the section used to spend a screen and a
   half on six lines of display type, and the viewer is what now says "this is
   real" instead of the size of the letters. */
.caps { padding-top: 80px; padding-bottom: 72px; }
.caps .sec-head { margin-bottom: 40px; }
.acc-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: clamp(28px, 4vw, 72px);
  align-items: start;
}
.acc-row summary { padding: 18px 0; grid-template-columns: 52px 1fr 26px; gap: 2vw; }
.acc-row summary h3 { font-size: clamp(1.25rem, 2.1vw, 1.9rem); }
.acc-row .sign { font-size: 1.3rem; }
.acc-row .detail { margin: 0 0 20px calc(52px + 2vw); font-size: .92rem; max-width: 52ch; }
.acc-items { margin: 0 0 22px calc(52px + 2vw); columns: 1; }
.acc-items li { padding-bottom: 14px; }
.acc-items h4 { font-size: .92rem; }
.acc-items p { font-size: .85rem; }

.acc-visual {
  position: sticky;
  top: 96px;
  margin: 0;
  aspect-ratio: 16 / 10;
  border-radius: 12px;
  overflow: hidden;
  background: #0d0f10;
  border: 1px solid var(--rule);
}
.acc-visual img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  filter: contrast(1.1) brightness(1.04);
  transition: opacity .55s var(--ease-micro);
}
/* which plate shows is decided by the open row, in CSS, so it works with no
   script at all; the motion layer takes over the same property with a
   crossfade and a settle, and clears it when it is done */
.acc-visual img:first-child { opacity: 1; }
.caps:has(.acc-row[data-visual="signals"][open])    .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="signals"][open])    .acc-visual img[data-visual="signals"]    { opacity: 1; }
.caps:has(.acc-row[data-visual="agents"][open])     .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="agents"][open])     .acc-visual img[data-visual="agents"]     { opacity: 1; }
.caps:has(.acc-row[data-visual="markets"][open])    .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="markets"][open])    .acc-visual img[data-visual="markets"]    { opacity: 1; }
.caps:has(.acc-row[data-visual="migration"][open])  .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="migration"][open])  .acc-visual img[data-visual="migration"]  { opacity: 1; }
.caps:has(.acc-row[data-visual="industrial"][open]) .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="industrial"][open]) .acc-visual img[data-visual="industrial"] { opacity: 1; }
.caps:has(.acc-row[data-visual="grants"][open])     .acc-visual img { opacity: 0; }
.caps:has(.acc-row[data-visual="grants"][open])     .acc-visual img[data-visual="grants"]     { opacity: 1; }
/* once the motion layer drives the viewer, the CSS rule steps aside: GSAP
   writes the opacity inline and the transition would drag on its crossfade */
.js-gsap .acc-visual img { transition: none; }
.js-gsap .acc-visual[data-showing] img { opacity: 0; }

@media (max-width: 900px) {
  .caps { padding-top: 64px; padding-bottom: 56px; }
  .acc-grid { grid-template-columns: 1fr; gap: 20px; }
  .acc-visual { position: relative; top: auto; order: -1; aspect-ratio: 16 / 9; }
  .acc-row summary { grid-template-columns: 40px 1fr 22px; }
  .acc-row .detail, .acc-items { margin-left: calc(40px + 2vw); }
}

/* ---------- thesis and practice: the same words in half the height ----------
   Both sections were composed at display size for a page that had far more
   room to spend; on a home that opens on the work they are two short bands. */
.cine.credo { padding-block: 72px; }
.cine.credo .inner {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
  gap: 0 clamp(24px, 4vw, 64px);
  align-items: end;
}
.cine.credo .inner .kicker { grid-column: 1 / -1; margin-bottom: 20px; }
.cine.credo .inner h2 { font-size: clamp(1.8rem, 3.6vw, 3.4rem); max-width: 18ch; }
.cine.credo .inner > p { margin-top: 0; padding-bottom: 6px; max-width: 48ch; font-size: .95rem; }
/* the three figures take the whole width under the statement, one row */
.cine.credo .metrics { grid-column: 1 / -1; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 3vw; margin: 40px 0 0; }
.cine.credo .metrics .m .v { font-size: clamp(1.7rem, 3.2vw, 2.9rem); }
.cine.credo .metrics .m .l { margin-top: 10px; }

.engineer { padding: 72px var(--inset) 64px; }
.engineer .kicker { margin-bottom: 24px; }
.engineer .lead { font-size: clamp(1.8rem, 3.6vw, 3.4rem); max-width: 22ch; }
.engineer .cols { margin-top: 32px; }
.engineer .practice-cols { grid-template-columns: minmax(0, 1fr) minmax(0, .62fr); align-items: center; }
.practice-figure { aspect-ratio: 16 / 10; max-height: 300px; }
.engineer .body p { font-size: .95rem; margin-bottom: 1em; }
.engineer .confidential { font-size: .82rem; }
.pull { margin-top: 36px; padding-top: 28px; }
.pull blockquote { font-size: clamp(1.15rem, 2.1vw, 1.75rem); max-width: 40ch; }

@media (max-width: 900px) {
  .cine.credo { padding-block: 56px; }
  .cine.credo .inner { grid-template-columns: 1fr; gap: 28px; }
  .cine.credo .metrics { grid-template-columns: 1fr; gap: 22px; }
  .engineer { padding: 56px var(--pad) 48px; }
  /* the compact desktop rule for the two columns is written after the phone
     rule that stacked them, so it won on a phone too and left the photograph
     in a 119px column beside the paragraph. On a phone the photograph takes
     the whole width, under the text, at the proportion it was composed in. */
  .engineer .practice-cols { grid-template-columns: 1fr; gap: 26px; align-items: start; }
  .practice-figure { aspect-ratio: 3 / 2; max-height: none; width: 100%; }
  .practice-figure img { object-position: 50% 45%; }
}

/* ---------- applied AI on the home: six things, for the person who decides ---------- */
.fx-grid a.fx { text-decoration: none; color: inherit; transition: border-color var(--dur-micro) var(--ease-micro), transform var(--dur-2) var(--ease-micro); }
.fx-grid a.fx:hover, .fx-grid a.fx:focus-visible { border-color: rgba(20,20,19,.4); transform: translateY(-3px); }
.fx-grid a.fx:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.fx-grid .q { font-family: var(--font-display); font-weight: 600; }
/* the line that says how, for the reader who wants to know — quiet, mono,
   under the plain sentence rather than in it */
.fx .how { display: block; margin-top: 16px; color: var(--ink-3); font-size: .58rem; letter-spacing: .14em; line-height: 1.7; }
.fx-grid a.fx .where { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 2px; display: inline-block; text-transform: none; letter-spacing: 0; font-family: var(--font-body); font-size: .86rem; }
.fx-grid a.fx:hover .where { color: var(--ink); border-color: var(--ink); }
.aifix .sec-intro { margin-bottom: 40px; }

/* ---------- applied AI page: the technical notes ---------- */
.ai-notes { padding-top: 84px; padding-bottom: 72px; }
.note-list { display: grid; gap: 0; border-top: 1px solid var(--rule); }
.note { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 2vw; padding: 34px 0; border-bottom: 1px solid var(--rule); scroll-margin-top: 90px; }
.note .n { color: var(--ink-3); padding-top: .4rem; }
.note h3 { font-size: clamp(1.3rem, 2.2vw, 1.9rem); margin-bottom: 14px; }
.note p { color: var(--ink-2); max-width: 74ch; font-size: .96rem; line-height: 1.62; margin-bottom: 12px; }
.note p.stack { color: var(--ink-3); font-size: .86rem; line-height: 1.6; }
.note p.stack b { color: var(--ink); font-weight: 500; }
.note .where { display: block; margin-top: 4px; color: var(--ink-3); font-family: var(--font-mono); font-size: .58rem; letter-spacing: .18em; text-transform: uppercase; }
.note .where a { color: var(--ink-2); text-decoration: none; border-bottom: 1px solid var(--rule); padding-bottom: 2px; }
.note .where a:hover { color: var(--ink); border-color: var(--ink); }
/* every card ends where the engineering continues: its long note */
.note .full { display: inline-block; margin-top: 16px; color: var(--ink); text-decoration: none; font-size: .9rem; border-bottom: 1px solid var(--rule); padding-bottom: 3px; transition: border-color var(--dur-micro) var(--ease-micro); }
.note .full:hover { border-color: var(--ink); }
.note .where + .full { margin-top: 14px; }
/* a note arrived at from the home page is briefly named, so the reader knows
   the jump landed where the link said */
.note:target h3 { text-decoration: underline; text-decoration-color: var(--mint); text-decoration-thickness: 3px; text-underline-offset: 6px; }
@media (max-width: 900px) {
  .note { grid-template-columns: 40px 1fr; padding: 26px 0; }
}

/* ---------- applied AI: the ledger ----------
   Six things a company can ask for, set as a table of hairlines rather than as
   six boxes: a number, a small line drawing of the idea, the sentence, the
   quiet line that says how, and the way in. The drawing is the only ornament
   and it draws itself; nothing here has a colour bar, a shadow or a card. */
.ai-ledger {
  --fig-acc: #3f7f66;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  margin-top: 8px;
}
.ai-item {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 30px 30px 30px;
  min-height: 100%;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  color: inherit;
  text-decoration: none;
  background: transparent;
  transition: background var(--dur-2) var(--ease-micro);
}
.ai-item:hover, .ai-item:focus-visible { background: rgba(20, 20, 19, .035); }
.ai-item:focus-visible { outline: 2px solid var(--ink); outline-offset: -2px; }
.ai-n { color: var(--ink-3); }
.ai-fig {
  display: block;
  width: 100%;
  max-width: 236px;
  height: auto;
  margin: 26px 0 26px -6px;
  color: var(--ink);
}
.ai-fig .s { opacity: .55; }
.ai-item:hover .ai-fig .s { opacity: .8; }
.ai-t {
  font-size: clamp(1.15rem, 1.6vw, 1.38rem);
  line-height: 1.15;
  letter-spacing: -.02em;
  margin-bottom: 12px;
  max-width: 22ch;
}
.ai-p { color: var(--ink-2); font-size: .93rem; line-height: 1.58; max-width: 40ch; }
.ai-how {
  display: block;
  margin-top: 18px;
  color: var(--ink-3);
  font-size: .58rem;
  letter-spacing: .14em;
  line-height: 1.7;
  text-transform: uppercase;
}
.ai-go {
  margin-top: auto;
  padding-top: 22px;
  color: var(--ink-2);
  font-size: .88rem;
  transition: color var(--dur-micro) var(--ease-micro), letter-spacing var(--dur-2) var(--ease-micro);
}
.ai-item:hover .ai-go, .ai-item:focus-visible .ai-go { color: var(--ink); letter-spacing: .01em; }
/* the drawings arrive drawn by the motion layer; without it, they are simply
   there — nothing on this page depends on a script to be readable */
.js-gsap .ai-fig .draw { stroke-dasharray: 1; stroke-dashoffset: 1; }
.js-gsap .ai-fig .fill, .js-gsap .ai-fig .dot { opacity: 0; }
@media (max-width: 1100px) { .ai-ledger { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px) {
  .ai-ledger { grid-template-columns: 1fr; border-left: 0; }
  .ai-item { border-right: 0; padding: 26px 0; }
  .ai-fig { max-width: 200px; margin: 20px 0 20px -4px; }
}

/* ---------- applied AI: the dive ----------
   On a desktop the six ideas are flown through: the section holds, one idea
   at a time fills the stage — its drawing large, its copy beside it — and the
   scroll carries the reader to the next through a short dissolve. The reading
   column on the left keeps the head and names the six. */
.js-gsap .aifix.is-dive {
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(260px, 27%) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 clamp(28px, 4vw, 72px);
  padding-top: 0;
  padding-bottom: 0;
  overflow: clip;
}
.js-gsap .aifix.is-dive .dive-aside { align-self: center; padding: 40px 0 24px; }
.js-gsap .aifix.is-dive .sec-head { display: block; margin-bottom: 18px; }
.js-gsap .aifix.is-dive .sec-head h2 { font-size: clamp(1.6rem, 2.3vw, 2.3rem); margin-top: 14px; }
.js-gsap .aifix.is-dive .sec-intro { margin: 0 0 30px; max-width: none; font-size: .9rem; }
.dive-rail {
  position: relative;
  list-style: none;
  margin: 0; padding: 14px 0 0;
  border-top: 1px solid var(--rule);
}
.dive-rail li { margin: 0; }
.dive-rail button {
  display: grid;
  grid-template-columns: 34px 1fr;
  gap: 10px;
  width: 100%;
  padding: 9px 0;
  border: 0;
  background: transparent;
  text-align: left;
  color: var(--ink-3);
  font: inherit;
  font-size: .9rem;
  cursor: pointer;
  transition: color var(--dur-micro) var(--ease-micro);
}
.dive-rail button .mono { padding-top: .2em; }
.dive-rail button:hover, .dive-rail button.is-active { color: var(--ink); }
.dive-rail button.is-active .dive-name { font-weight: 500; }
.dive-rail button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* the hairline that says how far through the six the reader is */
.dive-bar {
  position: absolute; left: 0; right: 0; top: -1px; height: 2px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* the stage: every scene in the same place, one shown at a time */
.js-gsap .aifix.is-dive .ai-ledger {
  position: relative;
  display: block;
  height: min(560px, 60vh);
  margin: 0;
  border: 0;
  --fig-acc: #3f7f66;
}
.js-gsap .aifix.is-dive .ai-item {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, .9fr);
  grid-template-rows: auto auto auto auto auto;
  gap: 0 clamp(24px, 4vw, 64px);
  align-items: start;
  align-content: center;
  padding: 0;
  border: 0;
  background: transparent;
  will-change: opacity;
}
.js-gsap .aifix.is-dive .ai-item:hover { background: transparent; }
.js-gsap .aifix.is-dive .ai-fig {
  grid-column: 1; grid-row: 1 / -1;
  align-self: center;
  width: 100%; max-width: 560px;
  margin: 0;
  transform-origin: 50% 50%;
  will-change: transform;
}
.js-gsap .aifix.is-dive .ai-n { grid-column: 2; align-self: end; margin-bottom: 14px; }
.js-gsap .aifix.is-dive .ai-t { grid-column: 2; font-size: clamp(1.6rem, 2.5vw, 2.4rem); max-width: 16ch; margin-bottom: 16px; }
.js-gsap .aifix.is-dive .ai-p { grid-column: 2; font-size: 1.02rem; max-width: 42ch; }
.js-gsap .aifix.is-dive .ai-how { grid-column: 2; margin-top: 18px; }
.js-gsap .aifix.is-dive .ai-go { grid-column: 2; margin-top: 22px; padding-top: 0; font-size: .92rem; border-bottom: 1px solid var(--rule); padding-bottom: 5px; justify-self: start; }
.js-gsap .aifix.is-dive .ai-item:hover .ai-go { border-color: var(--ink); }

/* the closing lines sit at the foot of the held frame, across both columns */
.js-gsap .aifix.is-dive .ai-foot { grid-column: 1 / -1; align-self: end; margin: 0; padding: 20px 0 22px; border-top: 1px solid var(--rule); }
.js-gsap .aifix.is-dive .ai-foot .close { font-size: clamp(1rem, 1.5vw, 1.2rem); }

/* ============================================================
   MOBILE PASS — 2026-08-16
   From an audit of every page at 360, 390 and 414 wide, in two
   engines. Each rule below answers a finding; the finding is named.
   ============================================================ */

/* The tag rows on the seven work pages had no rule at all: .pw .tags and
   .pwc .tags were styled, .cs-body .tags was not, and seven pages showed
   "MDRValidationTraceability" as one word. */
.cs-body .tags { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 6px 0 4px; }
.cs-body .tags span { font-family: var(--font-body); font-size: .78rem; color: var(--ink-3); letter-spacing: 0; text-transform: none; }

/* A 6px sideways scroll at exactly 360 wide: the case-study hero image is
   scaled by the editorial media animation and its box clips it at every other
   width. `overflow-x: clip` on body was already there; the root needs it too,
   because the root is what the scrollbar belongs to. */
html { overflow-x: clip; }

/* A page hero states the page in one line and gets out of the way: shorter
   than the about hero, which carries a photograph. */
.about-hero.page-hero { min-height: 400px; padding-bottom: 70px; }
.about-hero .lede {
  margin-top: 20px;
  max-width: 62ch;
  color: var(--ivory-2);
  font-size: 1.02rem;
  line-height: 1.55;
}
@media (max-width: 900px) {
  .about-hero .lede { font-size: .98rem; margin-top: 16px; }
}

 /* ---------- home: the short form of a section that has its own page ----------
    Six names and a link read in seconds; the page behind them carries the
    detail. The rule under each row is the same hairline the rest of the site
    uses for an index. */
 .cap-brief {
   list-style: none;
   display: grid;
   grid-template-columns: repeat(3, minmax(0, 1fr));
   column-gap: clamp(20px, 3vw, 44px);
   margin-top: 30px;
   border-top: 1px solid var(--rule);
 }
 .cap-brief li {
   display: flex;
   align-items: baseline;
   gap: 12px;
   padding: 15px 0;
   border-bottom: 1px solid var(--rule);
 }
 .cap-brief li span { color: var(--ink-3); font-size: .72rem; }
 .cap-brief li b { font-weight: 500; font-size: 1.02rem; letter-spacing: -.01em; }
 .cap-more { margin-top: 26px; }
 .cap-more a {
   color: var(--ink-2);
   text-decoration: none;
   border-bottom: 1px solid var(--rule);
   padding-bottom: 4px;
   transition: color var(--dur-micro) var(--ease-micro),
               border-color var(--dur-micro) var(--ease-micro);
 }
 .cap-more a:hover { color: var(--ink); border-color: var(--mint); }
 @media (max-width: 900px) {
   .cap-brief { grid-template-columns: 1fr; margin-top: 22px; }
   .cap-brief li { padding: 13px 0; }
   .cap-more { margin-top: 20px; }
 }

 @media (max-width: 900px) {
   /* Tap targets. The menu and language triggers rendered 32px and 30px
      tall, the hero actions 32px, the footer links 16px — under the 44px a
      thumb needs and under WCAG's own floor. Padding does it; nothing moves. */
   .nav-trigger { min-height: 42px; padding-block: 8px; }
  .lang-trigger { min-height: 42px; padding-block: 8px; }
  .hero-ctas a { padding-block: 10px; }
  .hero-ctas { gap: 24px; }
  .pagefoot a, .site-footer a { display: inline-block; padding-block: 12px; }
  .pagefoot { gap: 4px 20px; padding-block: 24px; }
  .nextsys a { display: inline-block; padding-block: 12px; }
  .contact .row a { display: inline-block; padding-block: 8px; }

  /* The hero object's labels were 8px on a phone, in a 3D skew, on glass:
     the site's own metaphor set too small to read. Up a step, tighter
     tracking, ivory rather than the second tier. */
  .hero-stack .plane span { font-size: .64rem; letter-spacing: .12em; color: var(--ivory); }
  .hero-stack .plane .tag { font-size: .56rem; }

  /* The case-study crumbs read "Practice / 02" and then "Practice · Applied
     AI" — the same word twice in a row. The second span is the one that
     adds something, so that is the one a phone keeps. */
  .chero .crumbs span:nth-of-type(1) { display: none; }
  .chero .crumbs span:nth-of-type(2) { display: inline; }
  .chero .crumbs span:nth-of-type(n+3) { display: none; }

  /* The Pliegos mobile capture — a phone screen inside a 16:10 plate — was
     cropped at its foot on a phone. On a phone the plate takes the capture's
     own proportion, so the whole screen shows. */
  .cs-fig .panelv.product-still { aspect-ratio: 9 / 16; max-height: 78vh; }
  .cs-fig .panelv.product-still img { position: absolute; object-fit: contain; }
}

/* The work strip on a phone showed one card and a sliver of the next, and
   nothing said there were seven. A hairline under the strip fills as it is
   swiped — driven by the strip's own scroll position, on the compositor,
   with no script; browsers without scroll-driven animations show nothing,
   which is what they showed before. */
@media (max-width: 900px) {
  .prowork { timeline-scope: --strip; }
  .pw-grid { scroll-timeline: --strip x; }
  .strip-bar { display: block; height: 2px; margin: 14px var(--pad) 0; background: var(--rule); position: relative; overflow: hidden; }
  .strip-bar span { display: block; height: 100%; width: 100%; background: var(--ink); transform-origin: 0 50%; transform: scaleX(.14); }
  @supports (animation-timeline: scroll()) {
    .strip-bar span { animation: strip-fill linear both; animation-timeline: --strip; }
  }
}
@media (min-width: 901px) { .strip-bar { display: none; } }
@keyframes strip-fill { from { transform: scaleX(.14); } to { transform: scaleX(1); } }

/* the line under each system's name: what it is, in five words, visible in
   the closed card as much as in the open one — the reader asked to know what
   each is at a glance, without opening it */
.project-card__tag {
  grid-column: 1 / -1;
  display: block;
  margin-top: 4px;
  color: var(--ink-3);
  font-size: .82rem;
  line-height: 1.4;
}
.js-gsap .project-grid.is-row-track .project-card__tag { font-size: .8rem; margin-top: 2px; }
.js-gsap .project-grid.is-row-track .project-card__body { min-height: 0; }

/* ============================================================
   NOTES — the technical blog
   ------------------------------------------------------------
   Long-form pages under /notes/. The header reuses the case hero; the body
   is a single measure of prose with a sticky contents rail beside it on a
   desktop. Code is set on graphite because it is the one place on the site
   where a dark panel earns its keep: a program is not prose, and the eye
   should be told so before it reads a word.
   ============================================================ */
.chero.notes-ai { background: radial-gradient(150% 120% at 74% 0%, #12201b 0%, #0d1411 55%, #0a0c0b 100%); }
.chero.notes-sys { background: radial-gradient(150% 120% at 26% 0%, #1e1813 0%, #14100d 55%, #0c0a09 100%); }
.chero.notes h1 { font-size: clamp(2.1rem, 4.4vw, 4.4rem); line-height: 1; max-width: 17ch; letter-spacing: -.03em; }
.chero.notes .lede { max-width: 52ch; }
.chero.notes .metarow .m span { font-family: var(--font-body); }

.post { padding: 56px var(--inset) 96px; display: grid; grid-template-columns: 200px minmax(0, 66ch); gap: 5vw; align-items: start; }
.post-toc { position: relative; } /* pinned by the motion layer on a desktop; sticky has nothing to stick to inside the smoother */
.post-toc .mono { color: var(--ink-3); display: block; margin-bottom: 14px; }
.post-toc ol { list-style: none; margin: 0; padding: 0; border-left: 1px solid var(--rule); }
.post-toc li a {
  display: block; padding: 7px 0 7px 16px; margin-left: -1px;
  border-left: 1px solid transparent;
  color: var(--ink-3); text-decoration: none; font-size: .82rem; line-height: 1.35;
  transition: color var(--dur-micro) var(--ease-micro), border-color var(--dur-micro) var(--ease-micro);
}
.post-toc li a:hover { color: var(--ink); }
.post-toc li a.is-here { color: var(--ink); border-left-color: var(--ink); }
.post-toc .back { display: block; margin-top: 26px; color: var(--ink-3); text-decoration: none; font-size: .82rem; }
.post-toc .back:hover { color: var(--ink); }

.post-body { font-size: 1.02rem; line-height: 1.7; color: var(--ink-2); }
.post-body > * + * { margin-top: 1.15em; }
.post-body h2 {
  font-size: clamp(1.4rem, 2.2vw, 1.9rem); line-height: 1.12; color: var(--ink);
  margin-top: 2.2em; padding-top: 1.1em; border-top: 1px solid var(--rule);
}
.post-body h2:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.post-body h2 .n { display: block; font-family: var(--font-mono); font-weight: 400; font-size: .62rem; letter-spacing: .22em; color: var(--ink-3); margin-bottom: 12px; }
.post-body h3 { font-size: 1.08rem; color: var(--ink); margin-top: 1.9em; letter-spacing: -.015em; }
.post-body p b, .post-body li b { color: var(--ink); font-weight: 500; }
.post-body a { color: var(--ink); text-decoration-color: var(--rule); text-underline-offset: 3px; }
.post-body a:hover { text-decoration-color: currentColor; }
.post-body ul, .post-body ol { padding-left: 1.3em; }
.post-body li + li { margin-top: .45em; }
.post-body code {
  font-family: var(--font-mono); font-size: .84em;
  background: var(--paper-2); padding: .12em .38em; border-radius: 3px; color: var(--ink);
}
.post-body pre {
  background: var(--graphite); color: var(--ivory);
  padding: 20px 22px; border-radius: 3px; overflow-x: auto;
  font-family: var(--font-mono); font-size: .8rem; line-height: 1.62; tab-size: 4;
  margin-top: 1.4em; margin-bottom: 1.4em;
}
.post-body pre code { background: none; padding: 0; color: inherit; font-size: inherit; }
/* code is wider than prose: on a wide screen the block runs past the text
   column into the empty right margin, so an 100-character line is read
   rather than scrolled */
@media (min-width: 1200px) { .post-body pre { margin-right: -180px; } }
.post-body pre .c { color: var(--ivory-3); }        /* a comment */
.post-body pre .k { color: var(--mint); }           /* a keyword or a name that matters */
.post-body pre .s { color: var(--amber); }          /* a string literal */
.post-body table { border-collapse: collapse; width: 100%; font-size: .88rem; margin-top: 1.4em; margin-bottom: 1.4em; }
.post-body th, .post-body td { text-align: left; padding: 10px 12px 10px 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
.post-body th { font-family: var(--font-mono); font-weight: 400; font-size: .62rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3); }
.post-body td.n, .post-body th.n { text-align: right; font-variant-numeric: tabular-nums; padding-right: 0; padding-left: 12px; }
.post-body .table-wrap { overflow-x: auto; }
.post-body .callout {
  border-left: 2px solid var(--ink); padding: 4px 0 4px 20px; margin-top: 1.6em; margin-bottom: 1.6em;
  font-size: .95rem;
}
.post-body .callout .mono { display: block; color: var(--ink-3); margin-bottom: 8px; }
.post-body .callout p { margin: 0; }
.post-body .callout p + p { margin-top: .7em; }
.post-body figure { margin: 2em 0; }
.post-body figure svg { display: block; width: 100%; height: auto; color: var(--ink); }
.post-body figure svg .draw { fill: none; stroke: currentColor; stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.post-body figure svg .thin { stroke-width: .8; opacity: .55; }
.post-body figure svg .fill { fill: currentColor; opacity: .12; stroke: none; }
.post-body figure svg text { font-family: var(--font-mono); font-size: 10px; fill: var(--ink-2); }
.post-body figcaption { margin-top: 12px; color: #656159; font-size: .8rem; line-height: 1.55; max-width: 60ch; }
.post-body .footnotes { margin-top: 3em; padding-top: 1.4em; border-top: 1px solid var(--rule); font-size: .84rem; color: var(--ink-3); }
.post-body .footnotes ol { padding-left: 1.2em; }
.post-body .footnotes li + li { margin-top: .5em; }
.post-body sup a { text-decoration: none; font-family: var(--font-mono); font-size: .68em; }
.post-body .kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px 22px; font-size: .92rem; margin-top: 1.4em; margin-bottom: 1.4em; }
.post-body .kv b { font-family: var(--font-mono); font-weight: 400; font-size: .64rem; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-3); padding-top: 5px; }

/* index: two series, then the rows */
.notes .series-head { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; margin: 64px 0 8px; }
.notes .series-head:first-child { margin-top: 0; }
.notes .series-head h2 { font-size: clamp(1.4rem, 2.4vw, 2.2rem); }
.notes .series-head .mono { color: var(--ink-3); }
.notes .series-lede { color: var(--ink-2); max-width: 60ch; margin-bottom: 26px; font-size: .95rem; }
.note .tags { display: block; margin-top: 12px; font-family: var(--font-mono); font-size: .6rem; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-3); }
.note h3 { max-width: 38ch; }
.notes .note { grid-template-columns: 56px minmax(0, 1fr) auto; align-items: start; color: inherit; text-decoration: none; }
.notes .note h3 { transition: color var(--dur-micro) var(--ease-micro); }
.notes .note:hover h3 { color: var(--ink-2); }
.note .read { font-family: var(--font-mono); font-size: .62rem; letter-spacing: .2em; color: var(--ink-3); white-space: nowrap; padding-top: .55rem; }

/* ---------- applied AI: six things as a grid ----------
   One screen, six cards, each a title and one sentence: the page has to be
   understood by someone who will not scroll. Depth lives behind the link. */
.note-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 clamp(24px, 3vw, 48px); border-top: 0; }
.note-grid .note { grid-template-columns: 1fr; gap: 10px; padding: 28px 0 26px; border-top: 1px solid var(--rule); border-bottom: 0; align-content: start; }
.note-grid .note .n { padding-top: 0; font-size: .78rem; color: var(--ink-3); }
.note-grid .note h3 { font-size: 1.3rem; line-height: 1.25; letter-spacing: -.01em; max-width: none; }
.note-grid .note p { margin-top: 8px; font-size: .98rem; line-height: 1.55; color: var(--ink-2); }
.note-grid .note p b { color: var(--ink); font-weight: 500; }
.note-grid .note .full { margin-top: 12px; display: inline-block; }
@media (max-width: 1100px) { .note-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .note-grid { grid-template-columns: 1fr; } }

@media (max-width: 1024px) {
  .post { grid-template-columns: minmax(0, 1fr); gap: 34px; padding-top: 40px; }
  .post-toc { position: static; }
  .post-toc ol { border-left: 0; display: flex; flex-wrap: wrap; gap: 6px 18px; }
  .post-toc li a { padding: 4px 0; border-left: 0; margin: 0; }
  .post-toc li a.is-here { color: var(--ink-3); }
  .post-toc .back { display: none; }
}
@media (max-width: 900px) {
  .post { padding: 34px var(--pad) 64px; }
  .post-body { font-size: .98rem; }
  .post-body pre { padding: 16px 16px; font-size: .74rem; margin-left: calc(-1 * var(--pad)); margin-right: calc(-1 * var(--pad)); border-radius: 0; }
  .post-body h2 { max-width: none; }
  .note { grid-template-columns: 1fr; gap: 6px; }
  .note .read { display: none; }
  .note time { display: none; }
  .notes .series-head { margin-top: 44px; flex-direction: column; gap: 4px; }
}

/* ---------- applied AI hero ----------
   The copy leads and the picture accompanies: a 62/38 split, the figure
   held to a modest width and vertically centred against the copy. The
   headline drops a size step so the column reads as a page, not a poster,
   and the proof list is the point: four things that already ran. */
/* The masthead of this page used to stand 720px tall while every other
   section head in the site sits between 480 and 585: it read as a different
   template rather than the same one. It now keeps the family's proportions,
   and the figure is sized to fit them instead of setting them. */
.ai-hero { display: grid; grid-template-columns: minmax(0, 62fr) minmax(0, 38fr); align-items: center;
  gap: clamp(32px, 5vw, 96px); padding-right: var(--inset); min-height: 400px; }
.ai-hero .hero-copy { max-width: none; padding: 48px 0 40px var(--inset); }
.ai-hero .kicker { margin-bottom: 20px; }
.ai-hero h1 { font-size: clamp(2rem, 3.3vw, 3.2rem); max-width: 20ch; }
.ai-hero .lede { max-width: 54ch; margin-top: 20px; }
.ai-figure { position: relative; display: flex; align-items: center; justify-content: center; }
.ai-figure img { width: min(100%, 320px); height: auto; display: block;
  /* the render carries its own haze; a soft radial mask lets its edges dissolve
     into the section so the object floats on the page rather than in a card */
  -webkit-mask-image: radial-gradient(72% 68% at 50% 50%, #000 58%, transparent 100%);
          mask-image: radial-gradient(72% 68% at 50% 50%, #000 58%, transparent 100%); }
/* Four proofs stacked as four rows added a hundred and thirty pixels this
   masthead did not have to spend: they read just as well as one strip, which
   is also closer to the kicker line every other section head carries. */
.hero-proof { list-style: none; margin: 24px 0 0; padding: 0; display: flex; flex-wrap: wrap;
  gap: 8px 26px; color: var(--ivory-2); font-size: .72rem; letter-spacing: .06em; max-width: 62ch; }
.hero-proof li { position: relative; padding-left: 18px; }
.hero-proof li::before { content: ""; position: absolute; left: 0; top: .55em; width: 9px; height: 1px; background: var(--mint); }
.ai-hero .hero-ctas { margin-top: 28px; }
@media (max-width: 1100px) { .ai-hero { grid-template-columns: minmax(0, 58fr) minmax(0, 42fr); } }
@media (max-width: 900px) {
  /* minmax(0,…) like the desktop tracks: a plain 1fr takes the content's
     min width as its floor, and the figure's intrinsic size was pushing the
     whole column wider than the phone */
  .ai-hero { grid-template-columns: minmax(0, 1fr); padding-right: 0; min-height: 0; }
  .ai-hero .hero-copy { padding: 72px var(--pad) 28px; }
  /* same exception the home hero already makes: on a phone the kicker wraps,
     and the second label drops below the first instead of sharing the row */
  .ai-hero .kicker { flex-wrap: wrap; gap: 6px 30px; }
  .ai-hero .kicker span { white-space: normal; }
  .ai-figure { padding: 0 var(--pad) 56px; }
  .ai-figure img { width: min(100%, 340px); }
  .hero-proof { font-size: .7rem; gap: 8px; }
}

/* On the applied AI page the section heads sit above their grid, full width:
   a title floating in the right column over a three-column grid reads as a
   misalignment, not as a choice. */
.ai-page .sec-head { grid-template-columns: 1fr; gap: 14px; margin-bottom: 34px; max-width: none; }
.ai-page .sec-head h2 { max-width: 22ch; }
/* the intro sits under its title, in the same column, not off to the right */
.ai-page .sec-intro { margin: -10px 0 38px 0; max-width: 58ch; }
.ai-page .sec-head h2 { font-size: clamp(1.9rem, 3.6vw, 3.2rem); line-height: 1.05; }
.ai-page .sec-intro { max-width: 58ch; }
.ai-page .ai-notes, .ai-page .engagement { padding-top: 84px; padding-bottom: 84px; }


/* ============================================================
   THE WORLD — the home as one assembling system (index.html)
   Obsidian identity: near-black ground, copper light, teal glints.
   All geometry and type; world.js scrubs the scene by scroll.
   ============================================================ */
.world-page { background: #0b0a0b; }
.world-page .masthead { position: fixed; top: 0; left: 0; right: 0; }

.world-scene {
  --w-bg: #0b0a0b;
  --w-panel: #161311;
  --w-panel-2: #1d1815;
  --w-ink: #efe9e1;
  --w-ink-2: #b9b0a4;
  --w-copper: #d98e4f;
  --w-copper-soft: rgba(217,142,79,.38);
  --w-copper-line: rgba(217,142,79,.55);
  --w-teal: #7fc0ab;
  /* One screen. The journey advances by gesture, not by position, so there
     is no scrollbar to drag and nothing below the fold to hunt for. */
  height: 100vh; height: 100dvh; position: relative; overflow: hidden; background: var(--w-bg); color: var(--w-ink);
}
.w-stage {
  position: sticky; top: 0; height: 100vh; height: 100dvh; overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 30%, rgba(217,142,79,.05), transparent 55%),
    radial-gradient(150% 110% at 50% 115%, rgba(127,192,171,.04), transparent 60%),
    var(--w-bg);
}
.w-stage::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 7;
  background: radial-gradient(130% 100% at 50% 42%, transparent 55%, rgba(0,0,0,.5) 100%);
}
.w-grain { position: absolute; inset: -50%; width: 200%; height: 200%; pointer-events: none; opacity: .05; z-index: 8; }

/* ---------- HUD ---------- */
.w-hud { position: absolute; inset: 0; pointer-events: none; z-index: 9; font-family: var(--font-mono); }
.w-hud .tr { position: absolute; top: 74px; right: 30px; text-align: right; font-size: 11px; line-height: 1.7; letter-spacing: .22em; color: var(--w-ink-2); }
.w-hud .tr .phase { color: var(--w-teal); }
.w-hud .rail { position: absolute; left: 30px; bottom: 26px; right: 30px; height: 1px; background: rgba(239,233,225,.12); }
.w-hud .rail i { position: absolute; left: 0; top: 0; height: 1px; width: 0; background: var(--w-copper); box-shadow: 0 0 8px var(--w-copper-soft); }
.w-corner { position: absolute; width: 14px; height: 14px; border: 1px solid rgba(217,142,79,.3); }
.w-corner.c1 { top: 66px; left: 20px; border-right: 0; border-bottom: 0; }
.w-corner.c2 { top: 66px; right: 20px; border-left: 0; border-bottom: 0; }
.w-corner.c3 { bottom: 20px; left: 20px; border-right: 0; border-top: 0; }
.w-corner.c4 { bottom: 20px; right: 20px; border-left: 0; border-top: 0; }

/* ---------- stage copy: centered, large ---------- */
.w-copy { position: absolute; z-index: 6; left: 50%; top: 12vh; transform: translateX(-50%); width: min(94vw, 72rem); text-align: center; pointer-events: none; }
.w-block { position: absolute; left: 0; right: 0; top: 0; opacity: 0; visibility: hidden; pointer-events: auto; }
/* a soft dark halo behind the copy so the titles hold against any film
   frame; edges dissolve, so it reads as light falloff, not as a card */
.w-block::before {
  content: ""; position: absolute; inset: -34px -6vw; z-index: -1;
  background: radial-gradient(62% 105% at 50% 42%, rgba(11, 10, 9, .64), rgba(11, 10, 9, .3) 58%, transparent 84%);
}
.w-block h1, .w-block h2 { text-shadow: 0 2px 26px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .55); }
.w-block p { text-shadow: 0 1px 14px rgba(0, 0, 0, .65); color: #cfc4b6; }
.w-kicker { display: block; font-family: var(--font-mono); font-size: 12px; letter-spacing: .28em; color: #f0b879; text-transform: uppercase; margin-bottom: 20px; }
.w-block h1, .w-block h2 {
  font-family: var(--font-display); font-weight: 600; text-transform: uppercase;
  font-size: clamp(2.2rem, 4.8vw, 4.6rem); line-height: 1.0; letter-spacing: -.02em; margin: 0;
}
.w-block h1 em, .w-block h2 em { font-style: normal; color: var(--w-teal); }
.w-block p { margin: 22px auto 0; color: var(--w-ink-2); font-size: clamp(1rem, 1.25vw, 1.15rem); line-height: 1.6; max-width: 56ch; }
.w-link {
  display: inline-block; margin-top: 22px; font-family: var(--font-mono); font-size: 12px;
  letter-spacing: .22em; text-transform: uppercase; color: var(--w-ink); text-decoration: none;
  border-bottom: 1px solid var(--w-copper-line); padding-bottom: 6px;
}
.w-link:hover { color: var(--w-copper); }
.w-ctas { margin-top: 28px; display: flex; gap: 26px; justify-content: center; flex-wrap: wrap; }
.w-cta {
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .14em; color: var(--w-bg);
  background: var(--w-copper); text-decoration: none; padding: 14px 22px; border-radius: 4px;
}
.w-cta:hover { background: #e9a366; }
.w-cta.ghost { background: transparent; color: var(--w-ink); border: 1px solid rgba(239,233,225,.3); }
.w-cta.ghost:hover { border-color: var(--w-copper); color: var(--w-copper); }

/* ---------- legend ---------- */
.w-legend { position: absolute; z-index: 10; left: 30px; bottom: 52px; display: grid; gap: 3px; font-family: var(--font-mono); font-size: 11px; letter-spacing: .18em; }
.w-legend button {
  display: flex; align-items: center; gap: 12px; min-height: 28px; padding: 3px 5px;
  border: 0; border-radius: 3px; background: transparent; color: rgba(239,233,225,.68);
  font: inherit; letter-spacing: inherit; text-align: left; cursor: pointer;
  transition: color .25s var(--ease-micro), background .25s var(--ease-micro);
}
.w-legend button::before { content: ""; flex: 0 0 auto; width: 18px; height: 1px; background: currentColor; transition: transform .25s var(--ease-micro); }
.w-legend button:hover { color: var(--w-ink); background: rgba(239,233,225,.055); }
.w-legend button:focus-visible { color: var(--w-ink); outline: 1px solid var(--w-copper); outline-offset: 2px; }
.w-legend button.on { color: var(--w-copper); }
.w-legend button.on::before { transform: scaleX(1.35); transform-origin: left; }

/* ---------- 3D world ---------- */
.w-persp { position: absolute; z-index: 4; left: 50%; top: 56%; width: min(96vw, 1060px); height: 76vh; transform: translate(-50%, -44%); perspective: 1500px; }
.w-world { position: absolute; inset: 0; transform-style: preserve-3d; }
.w-inner { position: absolute; left: 50%; top: 58%; width: 0; height: 0; transform-style: preserve-3d; }
.w-floor {
  position: absolute; left: -330px; top: -220px; width: 660px; height: 440px;
  transform: translateZ(-40px); opacity: .5;
  background:
    repeating-linear-gradient(0deg, rgba(217,142,79,.09) 0 1px, transparent 1px 44px),
    repeating-linear-gradient(90deg, rgba(217,142,79,.09) 0 1px, transparent 1px 44px);
  -webkit-mask-image: radial-gradient(60% 60% at 50% 50%, #000 30%, transparent 78%);
          mask-image: radial-gradient(60% 60% at 50% 50%, #000 30%, transparent 78%);
}

.w-wrap { position: absolute; left: -190px; top: -125px; width: 380px; height: 250px; transform-style: preserve-3d; }
.w-plate {
  position: absolute; inset: 0; border-radius: 10px; transform-style: preserve-3d;
  background: linear-gradient(135deg, var(--w-panel-2), var(--w-panel) 55%, #120f0d);
  border: 1px solid rgba(217,142,79,.5);
  box-shadow: 0 0 0 1px rgba(0,0,0,.6), 0 22px 60px rgba(0,0,0,.55), inset 0 1px 0 rgba(239,233,225,.05);
  animation: wBob 5.2s ease-in-out infinite;
}
.w-wrap[data-plate="1"] .w-plate { animation-delay: -1.1s; }
.w-wrap[data-plate="2"] .w-plate { animation-delay: -2.3s; }
.w-wrap[data-plate="3"] .w-plate { animation-delay: -3.4s; }
.w-wrap[data-plate="4"] .w-plate { animation-delay: -4.2s; }
@keyframes wBob { 0%, 100% { transform: translateZ(0); } 50% { transform: translateZ(16px); } }
.w-stage.is-live .w-plate { animation: none; }
.w-plate::before {
  content: ""; position: absolute; inset: 10px; border-radius: 6px;
  border: 1px solid rgba(217,142,79,.12);
  background:
    repeating-linear-gradient(0deg, rgba(239,233,225,.025) 0 1px, transparent 1px 26px),
    repeating-linear-gradient(90deg, rgba(239,233,225,.025) 0 1px, transparent 1px 26px);
}
.w-plate .tag, .w-modplate .tag {
  position: absolute; left: 18px; top: 14px; font-family: var(--font-mono); font-size: 13px;
  letter-spacing: .3em; color: var(--w-copper); text-shadow: 0 0 12px var(--w-copper-soft);
}
.w-plate .idx { position: absolute; right: 18px; top: 14px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .2em; color: rgba(239,233,225,.35); }
.w-anchor { position: absolute; left: 12px; bottom: 12px; width: 0; height: 0; }

/* plate details */
.d-db { position: absolute; display: flex; gap: 14px; left: 26px; bottom: 26px; }
.d-db i { width: 34px; height: 46px; border-radius: 8px/6px; border: 1px solid rgba(217,142,79,.4); background: linear-gradient(180deg, #1d1815, #14100e); position: relative; }
.d-db i::before { content: ""; position: absolute; left: -1px; right: -1px; top: -1px; height: 10px; border-radius: 50%; border: 1px solid rgba(217,142,79,.45); background: #211a15; }
.d-rows { position: absolute; right: 26px; bottom: 30px; display: grid; gap: 9px; }
.d-rows i { width: 120px; height: 3px; background: rgba(239,233,225,.14); border-radius: 2px; }
.d-rows i:nth-child(2) { width: 88px; } .d-rows i:nth-child(3) { width: 104px; }
.d-lock { position: absolute; left: 30px; bottom: 28px; width: 34px; height: 26px; border-radius: 4px; border: 1px solid var(--w-copper-line); background: #191411; }
.d-lock::before { content: ""; position: absolute; left: 7px; top: -12px; width: 16px; height: 14px; border: 1px solid var(--w-copper-line); border-bottom: 0; border-radius: 8px 8px 0 0; }
.d-clauses { position: absolute; left: 84px; bottom: 28px; display: grid; gap: 8px; }
.d-clauses i { height: 3px; width: 150px; background: rgba(239,233,225,.13); border-radius: 2px; position: relative; }
.d-clauses i::before { content: ""; position: absolute; left: -14px; top: -2px; width: 7px; height: 7px; border: 1px solid rgba(127,192,171,.6); border-radius: 2px; }
.d-net { position: absolute; right: 20px; bottom: 16px; }
.d-chat { position: absolute; left: 26px; bottom: 24px; display: grid; gap: 10px; }
.d-chat i { height: 22px; border-radius: 11px; border: 1px solid rgba(239,233,225,.16); width: 160px; }
.d-chat i:nth-child(2) { width: 120px; margin-left: 46px; border-color: rgba(127,192,171,.45); }
.d-checks { position: absolute; left: 26px; bottom: 24px; display: flex; gap: 16px; font-family: var(--font-mono); font-size: 12px; color: var(--w-teal); letter-spacing: .12em; }
.d-checks b { font-weight: 400; opacity: .55; color: var(--w-ink-2); }

/* ---------- stage 2 modules ---------- */
.w-mod { position: absolute; left: -110px; top: -70px; width: 220px; height: 140px; transform-style: preserve-3d; }
.w-modplate {
  position: absolute; inset: 0; border-radius: 8px;
  background: linear-gradient(135deg, var(--w-panel-2), var(--w-panel) 60%, #120f0d);
  border: 1px solid rgba(217,142,79,.42);
  box-shadow: 0 0 0 1px rgba(0,0,0,.6), 0 16px 40px rgba(0,0,0,.5);
}
.w-modplate .leds { position: absolute; left: 18px; bottom: 16px; display: flex; gap: 10px; }
.w-modplate .leds i { width: 8px; height: 8px; border-radius: 2px; background: rgba(127,192,171,.55); box-shadow: 0 0 8px rgba(127,192,171,.4); }
.w-modplate .leds i:nth-child(2) { background: rgba(217,142,79,.6); box-shadow: 0 0 8px var(--w-copper-soft); }

/* ---------- stage 3 chips ---------- */
.w-chip {
  position: absolute; left: -90px; top: -27px; width: 180px; height: 54px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 27px; border: 1px solid rgba(127,192,171,.4);
  background: rgba(18, 20, 19, .88);
  font-family: var(--font-mono); font-size: 11px; letter-spacing: .2em; color: var(--w-teal);
}


/* ---------- wires ---------- */
.w-wires { position: absolute; inset: 0; z-index: 5; pointer-events: none; }
.w-wires path.spine, .w-wires path.bus { fill: none; stroke: var(--w-copper); stroke-width: 1.6; filter: drop-shadow(0 0 6px rgba(217,142,79,.5)); }
.w-wires circle.pulse { fill: #ffd9ae; filter: drop-shadow(0 0 8px rgba(255,190,120,.9)); }
.w-wires circle.node { fill: #0b0a0b; stroke: var(--w-copper); stroke-width: 1.4; }
.w-wires g.lock rect { fill: #0f0d0c; stroke: var(--w-copper); stroke-width: 1.2; }
.w-wires g.lock path { fill: none; stroke: var(--w-copper); stroke-width: 1.2; }

/* ---------- terminal ---------- */
.w-terminal {
  position: absolute; z-index: 6; left: 50%; bottom: 11vh; transform: translate(-50%, 30px);
  width: min(92vw, 560px); border-radius: 10px; opacity: 0; visibility: hidden;
  background: rgba(16, 13, 11, .92); border: 1px solid rgba(217,142,79,.4);
  box-shadow: 0 30px 80px rgba(0,0,0,.6);
}
.w-terminal .bar { display: flex; align-items: center; gap: 7px; padding: 12px 16px; border-bottom: 1px solid rgba(239,233,225,.08); }
.w-terminal .bar i { width: 9px; height: 9px; border-radius: 50%; background: rgba(239,233,225,.16); }
.w-terminal .bar i:first-child { background: rgba(217,142,79,.6); }
.w-terminal .bar span { margin-left: 8px; font-family: var(--font-mono); font-size: 10px; letter-spacing: .18em; color: var(--w-ink-2); }
.w-terminal .line { margin: 0; padding: 18px 18px 20px; font-family: var(--font-mono); font-size: 13px; color: var(--w-teal); letter-spacing: .04em; }
.w-terminal .caret { display: inline-block; width: 8px; height: 15px; margin-left: 6px; vertical-align: -2px; background: var(--w-teal); animation: wCaret 1s steps(1) infinite; }
@keyframes wCaret { 50% { opacity: 0; } }

/* ---------- scroll cue ---------- */
.w-cue {
  position: absolute; z-index: 9; left: 50%; bottom: 44px; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 12px; pointer-events: none;
}
.w-cue .mouse { width: 26px; height: 42px; border: 1.5px solid rgba(239,233,225,.5); border-radius: 14px; position: relative; }
.w-cue .mouse i { position: absolute; left: 50%; top: 7px; width: 3px; height: 8px; margin-left: -1.5px; border-radius: 2px; background: var(--w-copper); animation: wWheel 1.6s ease-in-out infinite; }
@keyframes wWheel { 0% { transform: translateY(0); opacity: 1; } 70% { transform: translateY(14px); opacity: 0; } 100% { opacity: 0; } }
.w-cue .chev { display: grid; gap: 4px; }
.w-cue .chev i { width: 12px; height: 12px; border-right: 1.5px solid var(--w-copper); border-bottom: 1.5px solid var(--w-copper); transform: rotate(45deg); animation: wChev 1.6s ease-in-out infinite; }
.w-cue .chev i:nth-child(2) { animation-delay: .18s; }
@keyframes wChev { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
.w-cue .lbl { font-family: var(--font-mono); font-size: 11px; letter-spacing: .3em; color: var(--w-ink-2); }

/* ---------- responsive ---------- */
@media (max-width: 900px) {
  .w-copy { top: 10vh; }
  .w-block p { max-width: 34ch; }
  .w-persp { top: 62%; width: 150vw; height: 62vh; }
  .w-legend { left: 50%; bottom: calc(18px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%); grid-auto-flow: column; gap: 3px; font-size: 9px; }
  .w-legend button { gap: 6px; min-height: 38px; padding: 5px 8px; } .w-legend button::before { width: 10px; }
  .w-hud .tr { top: 66px; font-size: 9px; }
  .w-hud .tr { right: 20px; }
  .w-terminal { bottom: 15vh; }
}

/* phones: the HUD keeps only the phase readout and the rail; the legend
   duplicates the plate tags at this size and fought the scroll cue */
@media (max-width: 600px) {
  /* the phase name repeats the kicker right under it at this width */
  .w-hud .tr .phase { display: none; }
  .w-hud .tr { top: 62px; font-size: 9px; }
  .w-legend { display: flex; }
  .w-legend button { width: 42px; height: 42px; min-height: 42px; padding: 0; justify-content: center; font-size: 0; }
  .w-legend button::before { width: 7px; height: 7px; border-radius: 50%; background: rgba(239,233,225,.42); }
  .w-legend button.on::before { background: var(--w-copper); transform: scale(1.55); transform-origin: center; }
  .w-copy { top: 12vh; }
  .w-cue { bottom: calc(72px + env(safe-area-inset-bottom, 0px)); gap: 9px; }
  .w-cue .lbl { font-size: 9px; letter-spacing: .24em; }
}


/* ============================================================
   Obsidian touches for every subpage: the world's survey corners,
   quoted quietly on each dark page hero. Notes keep them too — a
   pair of thin brackets is technical, not theatrical.
   ============================================================ */
.chero::before, .about-hero::before, .ai-hero::before {
  content: ""; position: absolute; z-index: 3; top: 86px; left: 22px;
  width: 14px; height: 14px;
  border-left: 1px solid rgba(217, 142, 79, .45);
  border-top: 1px solid rgba(217, 142, 79, .45);
}
/* .chero::after is the photo scrim — the case heroes keep the single
   top-left bracket and their scrim stays untouched */
.about-hero::after, .ai-hero::after {
  content: ""; position: absolute; z-index: 3; bottom: 20px; right: 22px;
  width: 14px; height: 14px;
  border-right: 1px solid rgba(217, 142, 79, .45);
  border-bottom: 1px solid rgba(217, 142, 79, .45);
}

/* ============================================================
   Ambient texture behind dark heroes: an 8-second cinematic loop
   at low opacity under the section's own scrim. Pure background —
   it never carries information, so reduced-motion simply drops it
   and the hero stands on its gradient.
   ============================================================ */
.has-ambient { position: relative; overflow: hidden; overflow: clip; }
.has-ambient > *:not(.ambient-media) { position: relative; z-index: 2; }
.ambient-media { position: absolute; inset: 0; z-index: 1; overflow: hidden; pointer-events: none; }
.ambient-media video { width: 100%; height: 100%; object-fit: cover; opacity: .44; }
/* keep the reading half calm: a soft floor for the copy to sit on */
.ambient-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    radial-gradient(110% 120% at 18% 62%, rgba(11, 10, 9, .78), rgba(11, 10, 9, .3) 52%, transparent 78%),
    linear-gradient(180deg, rgba(11, 10, 9, .5), transparent 30%, transparent 70%, rgba(11, 10, 9, .6));
}
@media (prefers-reduced-motion: reduce) { .ambient-media { display: none; } }

/* the film's own footer: it belongs to the last frame, not to the page
   below it — the piece ends where the film ends */
.w-foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 8;
  opacity: 0; visibility: hidden; transform: translateY(16px);
  background: linear-gradient(to top, rgba(11, 10, 9, .92), transparent);
  border-top: 0; color: var(--w-ink-2);
}
.w-foot a { color: var(--w-ink-2); }
.w-foot a:hover { color: var(--w-copper); }

/* Scroll-scrubbed film. No image/poster layer is used: at every stop the
   visible background is a frame from the same video. */
.w-film {
  position: absolute; inset: 0; z-index: 5; width: 100%; height: 100%;
  object-fit: cover; object-position: center; background: var(--w-bg);
  opacity: 0;
  transition: opacity .18s linear, transform 1.5s var(--ease-mask);
}
/* At a stop the film leans back a breath — a slow push of scale under the
   veil; the moment the journey moves it comes forward again. Transform and
   opacity only: both run on the compositor, so the move never stutters. */
.w-stage.at-rest .w-film { transform: scale(1.035); }

/* The veil that carries the soft focus: a canvas the script paints ONCE per
   landing, at a twelfth of the pixels, stretched back up by the browser —
   the upscale is the blur. Only its opacity animates, and a fading cached
   texture is pure compositor work: nothing is recomputed mid-dissolve.
   Settling in is slow and soft; leaving is quicker, because the film is
   answering a gesture. */
.w-veil {
  position: absolute; inset: 0; z-index: 6; pointer-events: none;
  width: 100%; height: 100%;
  opacity: 0;
  transition: opacity .5s cubic-bezier(.4, 0, .6, 1);
  will-change: opacity;
}
.w-stage.at-rest .w-veil { opacity: 1; transition: opacity 1.15s cubic-bezier(.22, .61, .36, 1); }
@media (prefers-reduced-motion: reduce) {
  .w-film, .w-veil { transition: opacity .18s linear; }
}
.w-film.is-ready { opacity: 1; }
.w-stage.has-media .w-persp, .w-stage.has-media .w-wires { display: none; }
/* The CSS assembly is the no-JS fallback, not a loading screen. Undelayed it
   flashed for the beat between first paint and the script taking over — read
   as "old cards" before the film. It now waits out that beat: a normal load
   goes dark straight into the film and never sees it; without JS it still
   fades in after a breath. */
.w-persp, .w-wires { opacity: 0; animation: wFallback .6s ease 1.4s forwards; }
.w-stage.has-media .w-persp, .w-stage.has-media .w-wires { animation: none; opacity: 0; }
html.w-render .w-persp, html.w-render .w-wires { animation: none; opacity: 1; }
@keyframes wFallback { to { opacity: 1; } }

@media (max-width: 700px) {
  .w-block::before { inset: -24px -4vw -10px; }
}
/* render mode: the build photographs the scene alone */
html.w-render .masthead, html.w-render .w-copy, html.w-render .w-hud,
html.w-render .w-legend, html.w-render .w-cue, html.w-render .w-terminal,
html.w-render .site-footer, html.w-render .w-grain { display: none !important; }
html.w-render .world-scene { height: 100vh; }
html.w-render .w-stage::after { display: none; }
/* the frame is photographed at 1920 wide and cover-fitted onto the viewer's
   stage: a vw-proportional scene keeps its on-screen size identical to the
   live CSS version after that mapping */
html.w-render .w-persp { width: 74vw; }


/* ============================================================
   Quality control: the shiba who comes along (about page).

   Cut out of his studio backdrop and pinned to the viewport, so he sits
   in the corner of the page and follows the cursor while you read. He
   changes side twice on the way down; the move is a fade, never a slide.
   Decorative and inert: pointer-events are off, so he can never take a
   click meant for the page.
   ============================================================ */
.shiba-pet {
  position: fixed; z-index: 30; bottom: 0; left: clamp(10px, 2.5vw, 40px);
  height: clamp(150px, 26vh, 300px);
  pointer-events: none; opacity: 0; visibility: hidden;
  transform: translateY(24px);
  transition: opacity .42s var(--ease-micro), transform .5s var(--ease-entrance), visibility .42s;
  filter: drop-shadow(0 20px 24px rgba(20, 17, 15, .3));
}
.shiba-pet.at-right { left: auto; right: clamp(10px, 2.5vw, 40px); }
.shiba-pet.is-in { opacity: 1; visibility: visible; transform: translateY(0); }
.shiba-pet canvas {
  display: block; height: 100%; width: auto; aspect-ratio: 499 / 938;
}
/* No breathing loop: a body that rises and falls forever is movement nobody
   asked for, and next to a head that holds still it reads as a glitch. He
   moves when there is a reason to. */
@media (prefers-reduced-motion: reduce) {
  .shiba-pet { transition: none; }
}
/* on a phone he is smaller and sits on the floor of the window. He used to
   be lifted by env(safe-area-inset-bottom) to clear the home bar, which on a
   phone that reports an inset reads as him hovering a centimetre above the
   edge; he is bottom-anchored art, so he goes all the way down. */
/* on a phone he is not an overlay at all: he lives inside his section, in
   the flow, so the only thing ever under him is the page itself */
@media (max-width: 700px) {
  .shiba-pet { height: clamp(120px, 20vh, 190px); bottom: 0; }
  .shiba-pet.is-inline {
    position: static; height: 230px;
    opacity: 1; visibility: visible; transform: none; transition: none;
    display: flex; justify-content: center; margin: 26px 0 0;
  }
  .shiba-pet.is-inline canvas { height: 100%; }
}

/* the companion's body: the entrance and the side change live on the outer
   element, the lean on the middle one, the breath on the canvas — three
   transforms that never fight over the same property */
.shiba-hold {
  /* the pivot is his seat, so the lean turns him without ever lifting him
     off the bottom of the window */
  transform: rotate(var(--pet-rot, 0deg));
  transition: transform .38s var(--ease-micro);
  transform-origin: 50% 100%;
  height: 100%;
}
.shiba-pet canvas { transform-origin: 50% 100%; }
@media (prefers-reduced-motion: reduce) {
  .shiba-hold { transform: none; transition: none; }
}

/* the chapter rail is navigation, so it has to look pressable */
.w-legend button {
  background: none; border: 0; padding: 2px 0; font: inherit; color: inherit;
  text-align: left; cursor: pointer; letter-spacing: inherit;
}
.w-legend button:hover { color: var(--w-copper); }
/* the reset above sits after the responsive block and silently undid the
   phone legend's label-hiding — five full labels printed over five dots */
@media (max-width: 600px) {
  .w-legend button { font-size: 0; }
}
/* the cue line is one sentence: wrapped it collided with the rail below */
@media (max-width: 600px) {
  .w-cue .lbl { white-space: nowrap; }
}


/* the scroll cue keeps two keyframe animations running for as long as the
   page is open; once the reader has obliged, they have nothing left to say */
.w-stage.is-live .w-cue { display: none; }


/* the world page never scrolls: the film answers gestures instead, and a
   rubber-band bounce on a page with nowhere to go reads as a fault */
/* the html element is what rubber-bands; body alone was not enough */
html:has(body.world-page) { overscroll-behavior: none; }
.world-page { overscroll-behavior: none; }

/* the closing invitation is a footer, not a second hero: it was taking half a
   screen for four lines of copy */
.ai-page .contact { padding-top: 72px; padding-bottom: 64px; min-height: 0; }
.ai-page .contact h2 { font-size: clamp(1.5rem, 2.4vw, 2.2rem); max-width: 26ch; }
.ai-page .contact .sub { margin-top: 14px; font-size: .95rem; max-width: 52ch; }
.ai-page .contact .row { margin-top: 22px; }

/* One masthead across five pages: they share a floor of 400px and the copy
   sits centred on it, so a page with a short headline no longer reads as a
   different template from one with a long one. The applied-AI head keeps its
   two-column grid; only the single-column heads become flex. */
.about-hero {
  display: flex; flex-direction: column;
  justify-content: center; align-items: flex-start;
}
