/* ============================================================================
   Vik Al — vik.al
   A specification, set in two voices.

   The type system carries the argument. Everything the machine says — labels,
   numbers, statuses, captions — is set in mono. Everything a person claims or
   judges is set in the serif. Compute, then human.
   ========================================================================= */

/* --- 1. Fallback faces --------------------------------------------------- */

/* The webfonts arrive after the first paint. If the stand-in has different
   advance widths, every line re-wraps when the real font lands and the page
   jumps: measured at 244px on a 375px viewport, because IBM Plex Mono is 26%
   wider than the system mono it was falling back to.

   Each face below scales a local font so its metrics match the webfont's, so
   the page is laid out once and the swap is invisible. size-adjust matches
   the advance width; the ascent and descent overrides are expressed against
   that adjusted em, which is why they are not the raw ratios. A face whose
   local() font is missing simply fails, and the stack moves on.

   Measured, not guessed: per em, Newsreader runs 21.01 wide to Georgia's
   20.10 and Times' 18.58; Plex Mono runs 23.40 to Menlo's 23.48. */

@font-face {
  font-family: "Newsreader Fb Georgia";
  src: local("Georgia");
  size-adjust: 104.49%;
  ascent-override: 70.82%;
  descent-override: 24.88%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Newsreader Fb Times";
  src: local("Times New Roman");
  size-adjust: 113.06%;
  ascent-override: 65.45%;
  descent-override: 23%;
  line-gap-override: 0%;
}

/* local() matches the font's own name, not the family you would write in a
   stack: local("Menlo") finds nothing, local("Menlo-Regular") finds it. */
@font-face {
  font-family: "Plex Fb Menlo";
  src: local("Menlo-Regular"), local("Menlo Regular");
  size-adjust: 99.66%;
  ascent-override: 102.35%;
  descent-override: 27.09%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Plex Fb Consolas";
  src: local("Consolas");
  size-adjust: 109.09%;
  ascent-override: 93.5%;
  descent-override: 24.75%;
  line-gap-override: 0%;
}

/* Courier New is the one that is almost always there, and at 23.40 per em it
   matches Plex Mono's 23.40 outright. It is the backstop, not the first
   choice, because Menlo and Consolas are the better-looking match. */
@font-face {
  font-family: "Plex Fb Courier";
  src: local("Courier New");
  size-adjust: 99.98%;
  ascent-override: 102.02%;
  descent-override: 27.01%;
  line-gap-override: 0%;
}

/* --- 2. Tokens ----------------------------------------------------------- */

/* Every colour is declared once, as light-dark(light, dark). The theme is
   chosen by `color-scheme` alone, so the two palettes cannot drift apart. */

:root {
  color-scheme: light dark;

  --serif: "Newsreader", "Newsreader Fb Georgia", "Newsreader Fb Times", Georgia, serif;
  --mono: "IBM Plex Mono", "Plex Fb Menlo", "Plex Fb Consolas", "Plex Fb Courier", ui-monospace, monospace;

  /* Type scale, small end. Sized for legibility first: the mono labels are
     uppercase and tracked, which reads smaller than its size, and Newsreader's
     short x-height reads smaller than most text faces. In rem, so they follow
     the reader's own browser text size. */
  --fs-micro: .75rem;     /* 12px  chips, measure labels, tertiary meta */
  --fs-label: .8125rem;   /* 13px  section labels, captions, metadata   */
  --fs-small: .875rem;    /* 14px  nav, buttons, wordmark, standfirst   */
  --fs-body:  1.1875rem;  /* 19px  running text                         */

  /* Warm paper, warm ink, one signal. */
  --paper:       light-dark(#f4f2ec, #131311);
  --surface:     light-dark(#eae7dd, #232219);
  --ink:         light-dark(#191813, #edeae2);
  --ink-2:       light-dark(#4c483f, #a8a298);
  --ink-3:       light-dark(#736d61, #918b7c);
  --line:        light-dark(#87806f, #6b6559);
  --rule:        light-dark(rgba(25, 24, 19, 0.14),   rgba(237, 234, 226, 0.16));
  --rule-faint:  light-dark(rgba(25, 24, 19, 0.055),  rgba(237, 234, 226, 0.06));
  --signal:      light-dark(#bf3f18, #ff6a3d);

  /* Drawing palette, two slots, so a different scheme is a two-line change:
       --mat   the objects the system handles
       --warn  the state it is fixing, before
     Restraint by default: both are ink, and the "before" reads through form
     (squiggle, queue, drift, dashes) rather than through a warning colour.
     People are never coloured -- they are always drawn in ink. */
  --mat:         light-dark(#6d4526, #c9a184);
  --warn:        light-dark(#736d61, #918b7c);
  --signal-soft: light-dark(rgba(191, 63, 24, 0.10), rgba(255, 106, 61, 0.12));

  --rail-w: 15rem;
  --pad: clamp(1.25rem, 4vw, 4rem);
  --maxw: 68rem;

  --ease: cubic-bezier(0.22, 0.75, 0.25, 1);
}

/* The toggle pins one scheme; with no attribute the system preference wins. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/* --- 2. Reset ------------------------------------------------------------ */

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

/* scrollbar-gutter keeps the space reserved whether or not a scrollbar is
   showing, so content is never nudged sideways when one appears. */
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-optical-sizing: auto;
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color .3s var(--ease), color .3s var(--ease);
}

h1, h2, h3, p, ol, ul, figure { margin: 0; }
ol, ul { padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }

::selection { background: var(--signal); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 3px;
  border-radius: 2px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: .75rem 1.25rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-size: var(--fs-small);
}
.skip-link:focus { left: 0; }

/* --- 3. Shared type ------------------------------------------------------ */

.label {
  font-family: var(--mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

em {
  font-style: normal;
  font-weight: 500;
  color: var(--signal);
}

/* The page has exactly two emphases, and they are shaped very differently:
   "$1.16M ARR" is ten glyphs of caps, figures and a currency sign with no
   lowercase to break it up, so it sets as a solid block of signal. Measured,
   it carries 4.74 units of ink per pixel of width against 4.06 for
   "zero-error tactical UI" — 17% denser — which is why the lowercase one
   reads as the smaller of the two even though both are 19px.

   Weight alone got it to 4.31 and was still visibly the lighter of the two,
   so it also takes 12% more size. The size needs the line-height with it.
   line-height here is unitless, so a bigger font-size would otherwise give
   this inline box its own taller line box — 31.3px inside a 30.4px paragraph,
   growing the paragraph from 91.2px to 94.8px and drifting its lines 0.9px
   out of rhythm. Pinning line-height to 1 keeps the box shorter than the
   paragraph's strut, which governs the line instead: measured, the paragraph
   stays at exactly 91.2px, the same as with no emphasis at all.

   Not uppercase, which was the other candidate. It does not actually read
   denser (4.29, no better than weight alone), it stretches the phrase from
   185px to 251px so it dominates by length rather than presence, and on this
   page uppercase is the mono voice's signature — a shouted serif run mid
   sentence reads as a third voice.

   It needs a class because CSS cannot see letter case, and a blanket rule
   would thicken the figures too, which are already the heavier of the two. */
em.em-lc {
  font-weight: 600;
  font-size: 1.12em;
  line-height: 1;
}

/* --- 4. Layout guides ---------------------------------------------------- */
/* Four hairlines marking the column the page is built on. */

.guides { display: none; }

@media (min-width: 1080px) {
  .guides {
    display: flex;
    justify-content: space-between;
    position: fixed;
    top: 0;
    bottom: 0;
    left: var(--rail-w);
    right: 0;
    max-width: calc(var(--maxw) + var(--pad) * 2);
    margin-inline: auto;
    padding-inline: var(--pad);
    pointer-events: none;
    z-index: 0;
  }
  .guides > i { width: 1px; background: var(--rule-faint); }
}

/* --- 5. Rail ------------------------------------------------------------- */
/* A fixed index on desktop; a compact bar on small screens. */

.rail {
  position: fixed;
  z-index: 20;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .45rem 1rem;
  padding: .75rem var(--pad);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--rule);
}

/* On small screens the bar carries the name and the toggle on one row and
   gives the availability line a full row of its own, so it is never clipped.
   `display: contents` lets the two nested children take part in that wrap. */
.rail-top { display: contents; }
.wordmark { order: 1; margin-right: auto; }
.rail-bottom { order: 2; }
.availability { order: 3; flex: 1 0 100%; }
.rail-nav { display: none; }

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--mono);
  font-size: var(--fs-small);
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
}
.mark { width: 1.125rem; height: 1.125rem; flex: none; }
.mark rect { fill: var(--ink); }
.mark .mark-top { fill: var(--signal); }

.availability {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .05em;
  color: var(--ink-2);
  line-height: 1.45;
  min-width: 0;
}

.beacon {
  position: relative;
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--signal);
}
.beacon::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--signal);
  animation: beacon 2.4s var(--ease) infinite;
}
@keyframes beacon {
  0%        { transform: scale(.6); opacity: .9; }
  70%, 100% { transform: scale(1.8); opacity: 0; }
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .4rem .7rem;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color .2s, color .2s;
}
.theme-toggle:hover { border-color: var(--line); color: var(--ink); }
.theme-toggle svg { width: 14px; height: 14px; }
.theme-toggle .sun-core { fill: none; stroke: currentColor; stroke-width: 1.4; }
.theme-toggle .sun-rays path { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
.theme-toggle .moon { fill: currentColor; display: none; }
:root[data-theme="dark"] .theme-toggle .sun-core,
:root[data-theme="dark"] .theme-toggle .sun-rays { display: none; }
:root[data-theme="dark"] .theme-toggle .moon { display: block; }
.theme-toggle-label { display: none; }

@media (min-width: 1080px) {
  .rail {
    flex-wrap: nowrap;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    top: 0;
    right: auto;
    width: var(--rail-w);
    height: 100vh;
    height: 100dvh;
    padding: 2.5rem 1.75rem;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: none;
    border-right: 1px solid var(--rule);
  }
  .rail-top {
    display: flex;
    flex: none;
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }
  .wordmark { margin-right: 0; }
  .availability { flex: none; align-items: flex-start; }
  .beacon { margin-top: .42rem; }

  .rail-nav { display: block; margin-top: 2.75rem; }
  .rail-nav ol { display: flex; flex-direction: column; gap: .1rem; }
  .rail-nav a {
    display: flex;
    gap: .75rem;
    padding: .3rem 0;
    font-family: var(--mono);
    font-size: var(--fs-small);
    letter-spacing: .04em;
    color: var(--ink-3);
    text-decoration: none;
    transition: color .2s;
  }
  .rail-nav a:hover { color: var(--ink); }
  .rail-nav .n { color: var(--line); transition: color .2s; }
  .rail-nav a[aria-current="true"] { color: var(--ink); }
  .rail-nav a[aria-current="true"] .n { color: var(--signal); }

  .rail-bottom { display: flex; margin-top: auto; }
  /* main.js rewrites this label to match the theme after the first paint.
     Pin the width so "Dark" and "Light" occupy the same space and that
     rewrite cannot nudge the button's contents. */
  .theme-toggle-label { display: inline-block; min-width: 3.25em; }
}

/* --- 6. Sections --------------------------------------------------------- */

.main { position: relative; z-index: 1; padding-top: 6.25rem; }

@media (min-width: 1080px) {
  .main { margin-left: var(--rail-w); padding-top: 0; }
}

.section {
  max-width: calc(var(--maxw) + var(--pad) * 2);
  margin-inline: auto;
  padding-inline: var(--pad);
  padding-block: clamp(3.5rem, 8vw, 7rem);
}

.section-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: clamp(1.75rem, 4vw, 3rem);
}
.section-head .rule { flex: 1; height: 1px; background: var(--rule); }
.label-count { color: var(--ink-3); }

/* Reveal on scroll — only when JS is available, so content is never trapped.
   The exhibits are skipped here; each one reveals on its own instead. */
.has-js .section > *:not(.exhibits) { opacity: 0; transform: translateY(12px); }
.has-js .section.in-view > *:not(.exhibits) {
  opacity: 1;
  transform: none;
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.has-js .section.in-view > *:nth-child(2) { transition-delay: .07s; }
.has-js .section.in-view > *:nth-child(3) { transition-delay: .14s; }
.has-js .section.in-view > *:nth-child(4) { transition-delay: .21s; }

/* Exempt the Hero section from the initial slide-up animation to guarantee 0 CLS on load */
.has-js .section-opening > * {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}


@media (prefers-reduced-motion: reduce) {
  .has-js .section > * { opacity: 1; transform: none; transition: none; }
}

/* --- 7. Opening ---------------------------------------------------------- */

/* .section-opening { padding-block: clamp(4rem, 12vw, 9rem) clamp(3rem, 7vw, 6rem); } */
.section-opening { 
  min-height: 85vh; /* Creates the "Peek Effect" to force scrolling */
  display: flex;
  flex-direction: column;
  justify-content: center;
  contain: layout; /* Locks the bounding box to prevent CLS */
  padding-block: clamp(4rem, 12vw, 9rem) clamp(3rem, 7vw, 6rem); 
}

.statement {
  font-size: clamp(2.5rem, 6.4vw, 4.75rem);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -0.025em;
  text-wrap: balance;
  max-width: 15em;
  margin-bottom: clamp(1.75rem, 4vw, 2.75rem);
}

.lede {
  font-size: clamp(1.1875rem, 1.6vw, 1.4375rem);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 32em;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}

.standfirst {
  font-family: var(--mono);
  font-size: var(--fs-small);
  line-height: 1.75;
  letter-spacing: .02em;
  color: var(--ink-3);
  max-width: 40em;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}

/* --- 8. Track record ----------------------------------------------------- */
/* Fourteen years, drawn to scale. */

.measure { display: none; }

@media (min-width: 720px) {
  .measure { display: block; margin-bottom: 3.5rem; }

  .measure-axis { position: relative; height: 4.5rem; }
  .measure-axis::after {
    content: "";
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 1px;
    background: var(--line);
  }

  .tick {
    position: absolute;
    bottom: 0;
    left: var(--at);
    width: 1px;
    height: 6px;
    background: var(--line);
  }
  .tick.major { height: 12px; background: var(--ink-3); }

  .span {
    position: absolute;
    left: var(--from);
    width: calc(var(--to) - var(--from));
    height: 5px;
    border-radius: 3px;
  }
  .span::after {
    position: absolute;
    bottom: calc(100% + .45rem);
    font-family: var(--mono);
    font-size: var(--fs-micro);
    letter-spacing: .12em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .span-cypherock { bottom: 18px; background: var(--ink); }
  .span-cypherock::after { content: "Cypherock"; left: 0; color: var(--ink-2); }

  .span-orbit { bottom: 40px; background: var(--signal); }
  .span-orbit::after { content: "OrbitWatch"; right: 0; color: var(--signal); }

  .measure-years { position: relative; height: 1.5rem; margin-top: .6rem; }
  .year {
    position: absolute;
    left: var(--at);
    font-family: var(--mono);
    font-size: var(--fs-label);
    letter-spacing: .08em;
    color: var(--ink-3);
  }
  .year-end { left: auto; right: 0; }

  .measure-note {
    font-family: var(--mono);
    font-size: var(--fs-micro);
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--line);
  }
}

.roles { display: grid; gap: 3rem; }
@media (min-width: 820px) { .roles { grid-template-columns: 1fr 1fr; gap: 4rem; } }

.role { border-top: 1px solid var(--rule); padding-top: 1.5rem; }

.role-meta {
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .875rem;
}
.role-meta .role-title { color: var(--ink-2); }

.role-org {
  font-size: clamp(1.875rem, 3.2vw, 2.5rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.role-sector {
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .08em;
  color: var(--ink-3);
  margin: .5rem 0 1.125rem;
}

.role-body { color: var(--ink-2); max-width: 32em; }

/* --- 9. Point of view ---------------------------------------------------- */

.section-pov {
  max-width: none;
  padding-inline: 0;
  background: var(--surface);
  border-block: 1px solid var(--rule);
}
.pov-inner {
  max-width: calc(var(--maxw) + var(--pad) * 2);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.has-js .section-pov > * { opacity: 1; transform: none; }
.has-js .section-pov .pov-inner > * { opacity: 0; transform: translateY(12px); }
.has-js .section-pov.in-view .pov-inner > * {
  opacity: 1;
  transform: none;
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.has-js .section-pov.in-view .pov-inner > *:nth-child(2) { transition-delay: .07s; }
@media (prefers-reduced-motion: reduce) {
  .has-js .section-pov .pov-inner > * { opacity: 1; transform: none; transition: none; }
}

.pov {
  font-size: clamp(1.375rem, 2.6vw, 2.0625rem);
  line-height: 1.5;
  letter-spacing: -0.012em;
  color: var(--ink-2);
  max-width: 26em;
}
/* The middle sentence carries the weight, so it gets full ink. */
.pov-key { color: var(--ink); }

/* --- 10. Selected work --------------------------------------------------- */
/* A register to scan, then five exhibits to look at. */

.register {
  border-top: 1px solid var(--ink);
  margin-bottom: clamp(4rem, 9vw, 7rem);
}

.register a {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr) 1.25rem;
  grid-template-areas:
    "num name go"
    "num meta go";
  align-items: baseline;
  gap: .3rem 1rem;
  padding: 1rem .5rem;
  border-bottom: 1px solid var(--rule);
  color: inherit;
  text-decoration: none;
  transition: background-color .2s var(--ease);
}
.register a:hover { background: var(--surface); }

.reg-num {
  grid-area: num;
  font-family: var(--mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: .1em;
  color: var(--signal);
}
.reg-name {
  grid-area: name;
  font-size: clamp(1.25rem, 2.2vw, 1.625rem);
  line-height: 1.2;
  letter-spacing: -0.015em;
}
.reg-meta {
  grid-area: meta;
  display: flex;
  flex-wrap: wrap;
  gap: .25rem 1rem;
}
.reg-cat,
.reg-status {
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.reg-status.is-wip { color: var(--signal); }
.reg-go {
  grid-area: go;
  justify-self: end;
  font-family: var(--mono);
  font-size: var(--fs-small);
  color: var(--ink-3);
  transition: transform .25s var(--ease), color .2s;
}
.register a:hover .reg-go { transform: translateY(3px); color: var(--signal); }

@media (min-width: 760px) {
  .register a {
    grid-template-columns: 3rem minmax(0, 1fr) 11rem 7rem 1.25rem;
    grid-template-areas: "num name cat status go";
    padding: 1.15rem .75rem;
  }
  .reg-meta { display: contents; }
  .reg-cat { grid-area: cat; }
  .reg-status { grid-area: status; }
}

/* Exhibits: a large panel and a wall label, bottom-aligned, alternating. */

/* The gap has to be read against the height of what it separates, not on its
   own. Tied to 10vw it bottomed out at 72px on a 375px screen — where the
   drawing stacks above the label and each exhibit is TALLER (552px) than on a
   laptop (498px). So the weakest separator sat under the longest items: 13% of
   an exhibit against 26% on desktop, and six of them read as one wall. The
   floor is what matters at the narrow end, and it holds to 1024px.

   The ceiling is set against how far apart two SECTIONS actually sit, which is
   measured at 208-237px — both sides' padding, plus the fact that it is
   asymmetric (144px top, 96px bottom). An earlier version of this rule capped
   the gap at one side's padding, 112px, which is half the real figure: that
   left 104px on a 1920px screen, only 20% of a 506px exhibit standing against
   a 475px drawing, and it read as tight. Two works still must not sit as far
   apart as two sections do — 160px against 208px keeps that order intact. */
.exhibits { display: grid; gap: clamp(6.5rem, 6vw + 2.75rem, 10rem); }

.exhibit { display: grid; gap: 1.5rem; scroll-margin-top: 7rem; }
@media (min-width: 1080px) { .exhibit { scroll-margin-top: 3rem; } }

.exhibit-figure {
  order: -1;
  margin: 0;
}

/* Names the transformation, so the drawing does not have to be decoded. */
.ex-caption {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem .55rem;
  margin-top: .7rem;
  font-family: var(--mono);
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.ex-arrow { color: var(--signal); }

.exhibit-text {
  border-top: 1px solid var(--rule);
  padding-top: 1.25rem;
}

@media (min-width: 900px) {
  .exhibit {
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: clamp(1.5rem, 3vw, 2.5rem);
    align-items: end;
  }
  .exhibit-figure { order: 0; grid-row: 1; grid-column: 1 / span 8; }
  .exhibit-text   { grid-row: 1; grid-column: 9 / span 4; }
  .exhibit:nth-child(even) .exhibit-figure { grid-column: 5 / span 8; }
  .exhibit:nth-child(even) .exhibit-text   { grid-column: 1 / span 4; }
}

/* From 1080 to 1239px the rail takes its share and the column is at its
   narrowest. Split 7/5 instead of 8/4 and tighten the label's rhythm, so a
   label always sits clearly shorter than the drawing beside it rather than
   nearly flush with it. The text size itself does not change. */
@media (min-width: 1080px) and (max-width: 1239px) {
  .exhibit-figure { grid-column: 1 / span 7; }
  .exhibit-text   { grid-column: 8 / span 5; padding-top: .875rem; }
  .exhibit:nth-child(even) .exhibit-figure { grid-column: 6 / span 7; }
  .exhibit:nth-child(even) .exhibit-text   { grid-column: 1 / span 5; }
  /* Scoped under .exhibit: the base .ex-meta and .ex-name rules come later
     in the file and would otherwise win at equal specificity. */
  .exhibit .ex-meta { margin-bottom: .625rem; }
  .exhibit .ex-name { margin-bottom: .625rem; }
  .exhibit .ex-desc { line-height: 1.5; }
}

.ex-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .9rem;
  margin-bottom: 1rem;
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .13em;
  text-transform: uppercase;
}
.ex-num { color: var(--signal); font-weight: 500; }
.ex-cat { color: var(--ink-3); }
.ex-status {
  padding: .2rem .5rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  color: var(--ink-3);
}
.ex-status.is-wip { color: var(--signal); border-color: var(--signal); background: var(--signal-soft); }

.ex-name {
  font-size: clamp(1.75rem, 2.8vw, 2.3125rem);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin-bottom: .875rem;
}

.ex-desc { color: var(--ink-2); max-width: 32em; }

.has-js .exhibit-text { opacity: 0; transform: translateY(12px); }
.has-js .exhibit.in-view .exhibit-text {
  opacity: 1;
  transform: none;
  transition: opacity .7s var(--ease) .1s, transform .7s var(--ease) .1s;
}

/* --- 11. Drawings -------------------------------------------------------- */
/* Each drawing starts in disorder and settles into structure as it scrolls
   into view. `--s` is how much structure the system has imposed (0 → 1);
   main.js drives it, and it defaults to 1 so the settled state is the
   fallback. Ink is the material. Signal is only ever the structure. */

.exhibit-figure svg {
  --s: 1;
  --m: 1;
  /* What the drawing settles into. Overridden per family below. */
  --settled: var(--mat);
  display: block;
  width: 100%;
  height: auto;
  background: var(--surface);
  border: 1px solid var(--rule);
}

/* Colour follows the settling: ochre while the drawing is in disorder, its
   own hue once the system has hold of it. --s already drives the motion, so
   the mix needs no JavaScript of its own. */
.exhibit-figure :where(rect, circle, path, line, polyline) {
  stroke: color-mix(in srgb, var(--settled) calc(var(--m) * 100%), var(--warn));
}

.exhibit-figure :where(rect, circle, path, line, polyline) {
  fill: none;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.exhibit-figure pattern path { stroke: var(--rule); stroke-width: 1; vector-effect: none; opacity: .55; }
.exhibit-figure .x-grid { stroke: none; }
.exhibit-figure .x-reg path { stroke: var(--ink-3); opacity: .55; }

.exhibit-figure .solid,
.exhibit-figure .dot {
  fill: color-mix(in srgb, var(--settled) calc(var(--m) * 100%), var(--warn));
  stroke: none;
}
.exhibit-figure .k { opacity: .7; }

/* The component and the work under review are the source of truth, so they
   never change: everything else settles around them. */
.exhibit-figure .src :where(rect, path, circle) { stroke: var(--mat); }

/* Things that exist in only one of the two states, so a drawing can show two
   different scenes rather than the same shapes rearranged. */
.exhibit-figure .only-before { opacity: calc(1 - var(--m)); }
.exhibit-figure .only-after  { opacity: var(--m); }

/* People are always ink: never the material, never the structure. */
.exhibit-figure :is(.person, .human, .bot) :where(circle, path, rect) { stroke: var(--ink); }
.exhibit-figure :is(.person, .human, .bot) .solid { fill: var(--ink); }
.exhibit-figure .tick { stroke: var(--ink); stroke-width: 2; }
.exhibit-figure .page rect { stroke: var(--mat); }
.exhibit-figure .hair { opacity: .4; }
.exhibit-figure .scale { stroke: var(--signal); opacity: var(--s); }

/* structure */
.exhibit-figure .sx       { stroke: var(--signal); stroke-width: 1.5; opacity: var(--s); }
.exhibit-figure .sx-fill  { fill: var(--signal); stroke: none; opacity: var(--s); }
.exhibit-figure .sx-faint { stroke: var(--signal); stroke-dasharray: 2 5; opacity: calc(var(--s) * .45); }
.exhibit-figure .sx-band  { fill: var(--signal); stroke: none; opacity: calc(var(--s) * .07); }
.exhibit-figure .leader   {
  vector-effect: none;
  stroke-width: 1.25;
  stroke-dasharray: 1 1;
  stroke-dashoffset: calc(1 - var(--s));
}

/* 02 — the thread, and the gate that is left for a person */
.exhibit-figure .thread     { stroke: var(--warn); stroke-width: 1.5; }
.exhibit-figure .sx-thread  { stroke: var(--signal); stroke-width: 2; opacity: var(--s); }
.exhibit-figure .node       { fill: color-mix(in srgb, var(--mat) calc(var(--m) * 100%), var(--warn)); stroke: none; }
.exhibit-figure .gate-ring  { stroke: var(--ink); stroke-width: 2.25; fill: var(--surface); }
.exhibit-figure .gate-core  { fill: var(--ink); stroke: none; }

/* 11.1 Motion opt-out: the drawings simply stay settled (main.js does not
   run them), and the availability beacon stops pulsing. */
@media (prefers-reduced-motion: reduce) {
  .beacon::after { animation: none; }
  .has-js .exhibit-text { opacity: 1; transform: none; transition: none; }
}

/* --- 12. Footer ---------------------------------------------------------- */

.section-footer { padding-bottom: clamp(3rem, 6vw, 5rem); }

.closing {
  font-size: clamp(1.875rem, 4.6vw, 3.5rem);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.025em;
  max-width: 14em;
  margin-bottom: clamp(2rem, 4vw, 3rem);
}

.links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 2rem;
  margin-bottom: clamp(3rem, 7vw, 5.5rem);
}

/* The one action on the page. It speaks in the human voice rather than the
   mono of the labels and tags, which are not clickable, and it is shaped like
   something to press: a comfortable target, rounded corners, and an arrow
   that says it opens elsewhere. */
.link-primary {
  /* Elevation: the one raised object on an otherwise flat page. On paper the
     shadow has to reach past the button to be seen at all, since the button
     itself is near-black. In the dark theme a dark shadow cannot read on a
     near-black page, so the outer layer becomes a faint light halo instead —
     the button is the brightest thing on screen, and this lifts it. */
  --lift-near: light-dark(rgba(25, 24, 19, .22), rgba(0, 0, 0, .60));
  --lift-far:  light-dark(rgba(25, 24, 19, .16), rgba(237, 234, 226, .10));
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  max-width: 100%;
  min-height: 3.75rem;
  padding: .85rem 1.25rem .85rem 1.35rem;
  border: 1px solid var(--ink);
  border-radius: 6px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--serif);
  font-size: var(--fs-body);
  font-weight: 500;
  line-height: 1.25;
  text-decoration: none;
  box-shadow:
    0 2px 3px -1px var(--lift-near),
    0 8px 18px -6px var(--lift-near),
    0 22px 34px -18px var(--lift-far);
  transition: background-color .2s, border-color .2s,
    transform .15s var(--ease), box-shadow .2s var(--ease);
}
.link-primary:hover {
  background: var(--signal);
  border-color: var(--signal);
  transform: translateY(-1px);
  box-shadow:
    0 3px 4px -1px var(--lift-near),
    0 12px 24px -6px var(--lift-near),
    0 30px 44px -20px var(--lift-far);
}
.link-primary:active {
  transform: translateY(0);
  box-shadow:
    0 1px 2px var(--lift-near),
    0 3px 6px -2px var(--lift-far);
}

/* Centre the icons on the capitals, not on the line box. The label is set in
   title case, so its visual middle is halfway up the capitals; flexbox would
   otherwise centre the full line box, which puts Newsreader's capitals
   1.87px high at this size. text-box trims the line box to cap height and
   baseline. Where it is unsupported, nudge by the same measured amount
   (1.87px at 19px, 0.1em), which holds for any number of lines. */
.link-text { text-box: trim-both cap alphabetic; }
@supports not (text-box: trim-both cap alphabetic) {
  .link-text { position: relative; top: .1em; }
}

/* Both icons are drawn centred in their own boxes, at the same height and the
   same rendered stroke, so flexbox can line them up exactly. */
.link-go {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  transition: transform .2s var(--ease);
}
.link-primary:hover .link-go { transform: translate(2px, -2px); }

/* On a phone the button spans the column, so the label can wrap without
   looking cramped and the arrow keeps to the far edge. */
@media (max-width: 599px) {
  .link-primary { display: flex; width: 100%; }
  .link-text { flex: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .link-primary, .link-go { transition: none; }
  .link-primary:hover, .link-primary:active, .link-primary:hover .link-go { transform: none; }
}
.doc-glyph { width: 17px; height: 20px; flex: none; }
.doc-glyph path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.doc-glyph .doc-rule { stroke-linecap: round; opacity: .7; }

.link-secondary {
  font-family: var(--mono);
  font-size: var(--fs-small);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: .2rem;
  transition: color .2s, border-color .2s;
}
.link-secondary:hover { color: var(--signal); border-color: var(--signal); }

.colophon {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem 1.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--fs-label);
  letter-spacing: .06em;
  color: var(--ink-3);
}
