/* ==========================================================================
   Linus Kemper - personal site
   A browsable workspace. Structure is the argument.
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */

@font-face {
  font-family: "Geist";
  src: url("./fonts/geist-var.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Source Serif 4";
  src: url("./fonts/source-serif-4-var.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("./fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Mono";
  src: url("./fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Surfaces */
  --bg: #ffffff;
  --bg-2: #e9e4dc;
  --paper: #faf9f6;

  /* Ink. --steel is non-text ornament only (joint squares, list rules): it reads
     2.8:1 on --bg, below the 3:1 floor for meaningful graphics. */
  --ink: #141413;
  --graphite: #262624;
  --muted: #63635d;
  --steel: #92928d;

  /* Rules */
  --line: #d1ccc3;
  --line-soft: #e2ded6;

  /* One accent, everywhere. */
  --accent: #a84f2a;
  --accent-tint: rgba(168, 79, 42, 0.07);
  --accent-select: rgba(168, 79, 42, 0.2);

  /* Type */
  --sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: "Source Serif 4", ui-serif, Georgia, "Times New Roman", serif;
  --mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Module. Everything sits on an 8px grid. */
  --s1: 0.25rem;
  --s2: 0.5rem;
  --s3: 0.75rem;
  --s4: 1rem;
  --s5: 1.5rem;
  --s6: 2rem;
  --s7: 3rem;
  --s8: 4rem;
  --s9: 6rem;
  --s10: 8rem;

  /* Structure */
  /* Concrete texture laid over the page ground. */
  --texture-opacity: 0.4;

  /* --- bg.webp window ------------------------------------------------------
     Measured from the alpha channel of assets/bg.webp (3072x2048, 3:2).
     The transparent hole is a rounded rect; 97% of its bounding box is clear.
     --win-fill is the share of the viewport the window is allowed to take.
     ---------------------------------------------------------------------- */
  --bg-aspect: 1.5;
  --win-x: 30.73%;
  --win-y: 45.7%;
  --win-w: 33.59%;
  --win-h: 38.67%;
  --win-cx: 47.52%;
  --win-cy: 65.03%;
  --win-fill: 0.66;
  /* Unitless twins of the unrotated window fractions, for use inside calc(). */
  --win-w-n: 0.3359;
  --win-h-n: 0.3867;
  --win-fill-wide: 0.96;

  /* The same window after rotating bg.webp 90deg counter-clockwise, which puts
     the knob at the top. A point (x,y) maps to (y, 1-x). Aspect flips to 2:3. */
  --bg-aspect-rot: 0.66667;
  --win-x-rot: 45.7%;
  --win-y-rot: 35.68%;
  --win-w-rot: 38.67%;
  --win-h-rot: 33.59%;
  --win-cx-rot: 65.035%;
  --win-cy-rot: 52.475%;
  --win-fill-rot: 0.96;
  --win-w-rot-n: 0.3867;
  --win-h-rot-n: 0.3359;
  --win-shift: 6%;

  --r: 2px;
  --page-max: 85rem;
  --gutter: clamp(1.25rem, 4vw, 4rem);
  --measure: 33rem;
  --pane-max: 57.5rem;

  /* Motion. Mechanical: decisive ease-out, no overshoot, nothing over 300ms. */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --ease-soft: cubic-bezier(0.32, 0.72, 0, 1);
  --t-fast: 130ms;
  --t: 190ms;
  --t-slow: 260ms;
}



/* --- Reset ---------------------------------------------------------------- */

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

* {
  margin: 0;
}

html {
  /* Simulates a browser zoom of 90%: the type scale and the spacing scale are
     both rem-based, so both shrink together. Percent rather than px keeps a
     user's own larger default font working. */
  font-size: 85%;
  color-scheme: light;
  height: 100%;
  overflow: hidden;
  -webkit-text-size-adjust: 100%;
}

body {
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--graphite);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.62;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("./texture.webp") center / cover no-repeat;
  opacity: var(--texture-opacity);
  pointer-events: none;
}

img,
svg {
  display: block;
  max-width: 100%;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
  padding: 0;
}

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

::selection {
  background: var(--accent-select);
  color: var(--ink);
}

.skip {
  position: absolute;
  left: var(--s4);
  top: var(--s4);
  z-index: 100;
  padding: var(--s3) var(--s4);
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--r);
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transform: translateY(-200%);
  transition: transform var(--t) var(--ease);
}

.skip:focus {
  transform: translateY(0);
}

.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Typography ----------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-weight: 500;
  color: var(--ink);
  text-wrap: balance;
  line-height: 1.16;
}

.t-name {
  font-size: clamp(2rem, 1.42rem + 2.4vw, 2.875rem);
  letter-spacing: -0.028em;
  line-height: 1.04;
}

.t-h1 {
  font-size: clamp(1.75rem, 1.42rem + 1.4vw, 2.375rem);
  letter-spacing: -0.026em;
  line-height: 1.08;
}

.t-h2 {
  font-size: clamp(1.3rem, 1.16rem + 0.62vw, 1.625rem);
  letter-spacing: -0.021em;
  line-height: 1.18;
}

.t-h3 {
  font-size: 1.0625rem;
  letter-spacing: -0.012em;
  line-height: 1.35;
}

/* Editorial serif. Reserved for statements, project titles, quotations. */
.t-statement {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.375rem, 1.06rem + 1.35vw, 2rem);
  line-height: 1.34;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: pretty;
}

.statement--lines>p+p {
  margin-top: 0.42em;
}

.t-serif {
  font-family: var(--serif);
  font-weight: 400;
  letter-spacing: -0.008em;
}

.t-lead {
  font-size: 1.0625rem;
  line-height: 1.68;
  color: var(--graphite);
  text-wrap: pretty;
}

.t-small {
  font-size: 0.875rem;
  line-height: 1.58;
  color: var(--muted);
  text-wrap: pretty;
}

/* Mono is for metadata only: labels, dates, categories, states, paths. */
.label {
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.label--accent {
  color: var(--accent);
}

.label--ink {
  color: var(--ink);
}

.prose {
  max-width: var(--measure);
}

.prose p {
  text-wrap: pretty;
}

.prose p+p {
  margin-top: var(--s4);
}

.prose>*+h2,
.prose>*+h3 {
  margin-top: var(--s7);
}

.prose h2+p,
.prose h3+p {
  margin-top: var(--s3);
}

.prose a:not(.btn) {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--accent) 38%, transparent);
  transition: text-decoration-color var(--t-fast) ease;
}

.prose a:not(.btn):hover {
  text-decoration-color: currentColor;
}

.prose strong {
  font-weight: 550;
  color: var(--ink);
}

.prose ul {
  margin-top: var(--s4);
  display: grid;
  gap: var(--s3);
}

.prose ul li {
  position: relative;
  padding-left: var(--s5);
  text-wrap: pretty;
}

.prose ul li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.66em;
  width: 0.4375rem;
  height: 1px;
  background: var(--steel);
}

/* --- Grid joints ---------------------------------------------------------
   USM Haller's ball joint, translated: a 3px steel square wherever the frame
   meets. Used on bordered panels only.
   ------------------------------------------------------------------------- */

.joints {
  background-image: linear-gradient(var(--steel), var(--steel)), linear-gradient(var(--steel), var(--steel)),
    linear-gradient(var(--steel), var(--steel)), linear-gradient(var(--steel), var(--steel));
  background-size: 3px 3px;
  background-position: 0 0, 100% 0, 0 100%, 100% 100%;
  background-repeat: no-repeat;
  background-origin: border-box;
  background-clip: border-box;
}

/* --- Stage -----------------------------------------------------------------
   Default: no frame, the screen fills the viewport.

   Above 1100x720 the photograph is scaled so its transparent window is
   --win-fill of the tighter viewport axis, then offset so that window sits
   centred. The frame paints on top: its opaque pixels crop the screen and
   its soft alpha edge feathers the content into the hole for free.

   Below that threshold the window would be under ~880px wide, which cannot
   hold a readable measure, so the frame is not used at all.
   -------------------------------------------------------------------------- */

.stage {
  position: fixed;
  inset: 0;
}

.frame {
  display: none;
}

@media (min-width: 1100px) and (min-height: 720px) {
  .stage {
    inset: auto;
    left: 50%;
    top: 50%;
    height: min(calc(100dvh * var(--win-fill) / 0.3867),
        calc(100vw * var(--win-fill) / 0.5039));
    width: calc(min(calc(100dvh * var(--win-fill) / 0.3867),
          calc(100vw * var(--win-fill) / 0.5039)) * var(--bg-aspect));
    /* Shift the window right of the tree rather than centring it. */
    transform: translate(calc(var(--win-cx) * -1 + var(--win-shift)), calc(var(--win-cy) * -1));
  }

  .frame {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 5;
    background: url("./bg.webp") center / 100% 100% no-repeat;
    pointer-events: none;
  }


  :root {
    --pane-max: 40rem;
  }
}

/* --- Screen ----------------------------------------------------------------
   The window in the frame. It holds the pane and nothing else: navigation now
   lives outside it, on the black surround.
   -------------------------------------------------------------------------- */

.screen {
  position: absolute;
  inset: 0;
}

/* Bound to the window in the frame. Media queries add no specificity, so this
   has to sit after the base rule to win. */
@media (min-width: 1100px) and (min-height: 720px) {
  .screen {
    left: var(--win-x);
    top: var(--win-y);
    width: var(--win-w);
    height: var(--win-h);
    right: auto;
    bottom: auto;
    z-index: 1;
    overflow: hidden;
  }
}

.pane {
  min-width: 0;
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  scroll-padding-top: var(--s6);
  padding: var(--s8) var(--gutter) var(--s10);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  /* Reserve the gutter always, so centred content does not shift by half a
     scrollbar between a page that scrolls and one that does not. */
  scrollbar-gutter: stable;
}

.pane__inner {
  max-width: var(--pane-max);
  margin-inline: auto;
}

/* --- Plant -----------------------------------------------------------------
   Decorative. Sized and placed by assets/site.js from measured geometry; it
   stays hidden unless its left edge clears the tree.
   -------------------------------------------------------------------------- */

.plant {
  display: none;
  position: fixed;
  z-index: 15;
  bottom: 0;
  left: var(--plant-left);
  width: var(--plant-w);
  height: var(--plant-h);
  pointer-events: none;
}

.plant[data-show="true"] {
  display: block;
}

.plant img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: bottom center;
}

/* --- Directory tree --------------------------------------------------------
   Sits on the black surround, left of the set. Box-drawing characters rather
   than CSS borders, so the stems keep the monospace rhythm of the labels.
   -------------------------------------------------------------------------- */

.tree {
  position: fixed;
  z-index: 20;
  /* Overridden by site.js when the plant is placed, so the tree sits beside it. */
  left: var(--tree-left, clamp(1.25rem, 3.4vw, 4.25rem));
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--mono);
  font-size: 0.875rem;
  line-height: 1;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.tree__list {
  display: grid;
  gap: 2px;
}

.tree__toggle {
  display: flex;
  align-items: center;
  min-height: 2.375rem;
  padding-right: var(--s3);
  padding-left: 0.5em;
  color: #9a9a93;
  white-space: pre;
  cursor: pointer;
  list-style: none;
  transition: color var(--t-fast) ease;
}

.tree__toggle::-webkit-details-marker {
  display: none;
}

.tree__toggle::after {
  content: "+";
  padding-left: 0.7em;
  color: var(--accent);
}

.tree__disclosure[open]>.tree__toggle::after {
  content: "\2212";
}

@media (hover: hover) and (pointer: fine) {
  .tree__toggle:hover {
    color: #ffffff;
  }
}

.tree__row {
  display: flex;
  align-items: center;
  min-height: 2.375rem;
  padding-right: var(--s3);
  color: #9a9a93;
  white-space: pre;
  transition: color var(--t-fast) ease;
}

.tree__stem {
  color: #5c5c56;
  transition: color var(--t-fast) ease;
}

.tree__name {
  padding-left: 0.5em;
}

/* Present on every row so the tree's box width does not change with the page,
   which would otherwise move the tree and the plant on every navigation. */
.tree__mark {
  padding-left: 0.6em;
  color: var(--accent);
  visibility: hidden;
}

.tree__row.is-current .tree__mark {
  visibility: visible;
}

@media (hover: hover) and (pointer: fine) {
  .tree__row:hover {
    color: #ffffff;
  }

  .tree__row:hover .tree__stem {
    color: #9a9a93;
  }
}

.tree__row.is-current {
  color: #ffffff;
}

.tree__row:focus-visible {
  outline-offset: 4px;
}

/* --- Metadata plate --------------------------------------------------------
   The three-cell identity strip under the name. An index-card header.
   -------------------------------------------------------------------------- */

.plate {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-top: var(--s5);
}

.plate__cell {
  padding: var(--s3) var(--s5) var(--s3) 0;
  border-right: 1px solid var(--line-soft);
}

.plate__cell:last-child {
  border-right: none;
  padding-right: 0;
}

.plate__cell:not(:first-child) {
  padding-left: var(--s5);
}

.plate__val {
  display: block;
  margin-top: 3px;
  font-size: 0.875rem;
  font-weight: 450;
  letter-spacing: -0.008em;
  color: var(--ink);
  white-space: nowrap;
}

/* --- The shelf: project rows ---------------------------------------------- */

.shelf {
  border-top: 1px solid var(--line);
}

.shelf__item {
  position: relative;
  display: grid;
  grid-template-columns: 3.75rem minmax(0, 1fr) 8.25rem 1.5rem;
  gap: var(--s5);
  align-items: start;
  padding: var(--s6) var(--s4);
  border-bottom: 1px solid var(--line);
  isolation: isolate;
}

.shelf__item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--accent-tint);
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--t-slow) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .shelf__item:hover::before {
    clip-path: inset(0 0 0 0);
  }
}

.shelf__item:focus-visible::before {
  clip-path: inset(0 0 0 0);
}

.shelf__item:last-child {
  border-bottom: none;
}

.shelf__item--lead {
  padding-block: var(--s7);
}

.shelf__name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 1.24rem + 1.1vw, 2.125rem);
  line-height: 1.1;
  letter-spacing: -0.014em;
  color: var(--ink);
  transition: transform var(--t-slow) var(--ease);
}

.shelf__item--lead .shelf__name {
  font-size: clamp(1.875rem, 1.5rem + 1.6vw, 2.625rem);
}

.shelf__chev {
  color: var(--muted);
  line-height: 1.2;
  transition: transform var(--t-slow) var(--ease), color var(--t-fast) ease;
  justify-self: end;
}

@media (hover: hover) and (pointer: fine) {
  .shelf__item:hover .shelf__name {
    transform: translateX(6px);
  }

  .shelf__item:hover .shelf__chev {
    transform: translateX(4px);
    color: var(--accent);
  }
}

.shelf__meta {
  display: grid;
  gap: var(--s2);
  align-content: start;
}

/* --- Callouts and quotes -------------------------------------------------- */

.pull {
  margin-block: var(--s7);
  padding-left: var(--s5);
  border-left: 1px solid var(--accent);
  max-width: 36ch;
}

/* A pull quote used as an epigraph sits tighter to the page head. */
.pull--lede {
  margin-top: 0;
}

.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.figure {
  padding: var(--s5) var(--s5) var(--s5) 0;
  border-right: 1px solid var(--line-soft);
}

.figure:last-child {
  border-right: none;
}

.figure:not(:first-child) {
  padding-left: var(--s5);
}

.figure__val {
  display: block;
  font-family: var(--serif);
  font-size: 1.75rem;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.015em;
  color: var(--accent);
  margin-bottom: var(--s2);
}

.figure__note {
  font-size: 0.875rem;
  color: var(--muted);
  line-height: 1.5;
  text-wrap: pretty;
}

/* --- The model grid ------------------------------------------------------
   An orthographic key plan of what Tarsis maps. Real content, laid out as a
   drawing rather than an illustration.
   ------------------------------------------------------------------------- */

.plan {
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.plan__cell {
  padding: var(--s5) var(--s4);
  border-right: 1px solid var(--line-soft);
  display: grid;
  gap: var(--s3);
  align-content: start;
}

.plan__cell:last-child {
  border-right: none;
}

.plan__rule {
  height: 1px;
  background: var(--accent);
  width: 1.375rem;
}

.plan__name {
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--ink);
}

.plan__note {
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--muted);
  text-wrap: pretty;
}

/* --- Contact links ---------------------------------------------------------
   Two icons under the name. The row is pulled left by the icon's own padding so
   its optical edge lines up with the text above it.
   -------------------------------------------------------------------------- */

.social {
  display: flex;
  gap: var(--s1);
  margin-top: var(--s5);
  margin-left: -0.75rem;
}

.social__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--r);
  color: var(--graphite);
  transition: color var(--t-fast) ease, transform var(--t-fast) var(--ease);
}

@media (hover: hover) and (pointer: fine) {
  .social__link:hover {
    color: var(--accent);
  }
}

.social__link:active {
  transform: scale(0.96);
}

/* A plain accent text link, using the same underline treatment as prose links
   so it reads as a link rather than as coloured text. */
.textlink {
  display: inline-block;
  margin-top: var(--s5);
  padding-block: var(--s2);
  font-size: 0.875rem;
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--accent) 38%, transparent);
  transition: text-decoration-color var(--t-fast) ease;
}

.textlink:hover {
  text-decoration-color: currentColor;
}

/* --- Buttons and links ---------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  min-height: 2.75rem;
  padding: var(--s3) var(--s5);
  border-radius: var(--r);
  background: var(--ink);
  color: var(--paper);
  font-size: 0.9375rem;
  font-weight: 450;
  letter-spacing: -0.008em;
  white-space: nowrap;
  transition: background-color var(--t-fast) ease, transform var(--t-fast) var(--ease);
}

.btn:hover {
  background: var(--accent);
}

.btn:active {
  transform: scale(0.97);
}



.btn__chev {
  color: currentColor;
  opacity: 0.62;
  transition: transform var(--t) var(--ease);
}

.btn:hover .btn__chev {
  transform: translateX(3px);
}




/* --- Entrance ------------------------------------------------------------
   One authored moment, on the root page header only. Content is visible by
   default; the animation is additive.
   ------------------------------------------------------------------------- */

@media (prefers-reduced-motion: no-preference) {
  .stagger>* {
    animation: rise 460ms var(--ease) both;
    animation-delay: calc(var(--i, 0) * 42ms);
  }

  @keyframes rise {
    from {
      opacity: 0;
      transform: translateY(7px);
    }
  }
}

/* --- Page transitions ----------------------------------------------------- */

@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: no-preference) {

  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }

  ::view-transition-old(pane) {
    animation: pane-out 120ms var(--ease) both;
  }

  ::view-transition-new(pane) {
    animation: pane-in 230ms var(--ease) 40ms both;
  }



  @keyframes pane-out {
    to {
      opacity: 0;
      transform: translateY(-5px);
    }
  }

  @keyframes pane-in {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
  }

}

.vt-pane {
  view-transition-name: pane;
}

/* The transition layer paints above the whole page, so an unnamed element ends
   up beneath the pane's snapshot for the duration of the swap. On phones the
   pane is full width and the tree's card floats over it, so the card would sink
   behind the text mid-transition. Naming the tree puts it in the same layer,
   where its group can be ordered above the pane. */
.tree {
  view-transition-name: tree;
}

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(tree) {
    z-index: 20;
  }

  ::view-transition-group(pane) {
    z-index: 1;
  }

  ::view-transition-old(tree),
  ::view-transition-new(tree) {
    animation-duration: 120ms;
  }
}


/* --- Page head ------------------------------------------------------------ */

.pagehead {
  margin-bottom: var(--s8);
}

.pagehead .label {
  display: block;
  margin-bottom: var(--s4);
}

.pagehead--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(18.75rem, 24rem);
  gap: var(--s7);
  align-items: start;
}

.pagehead h1.t-serif {
  font-size: clamp(2.125rem, 1.6rem + 2.3vw, 3rem);
  line-height: 1.06;
  letter-spacing: -0.016em;
}

/* Without an aside the head would run the full pane width, which is too long a
   measure for the display line and leaves the plate rule stranded. */
.pagehead:not(.pagehead--split)>div:first-child {
  max-width: 46rem;
}

.pagehead__stamp {
  margin-top: var(--s4);
}

.pagehead__lead {
  margin-top: var(--s5);
  max-width: 58ch;
}

/* --- Layout utilities ----------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  gap: clamp(var(--s6), 5vw, var(--s9));
  align-items: start;
}

.split--even {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}





.stack-4>*+* {
  margin-top: var(--s4);
}

.stack-5>*+* {
  margin-top: var(--s5);
}

.stack-6>*+* {
  margin-top: var(--s6);
}

.stack-7>*+* {
  margin-top: var(--s7);
}

.pane__section {
  margin-top: var(--s9);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
}

.pane__section>.label:first-child {
  display: block;
  margin-bottom: var(--s3);
}

.pane__section h2 {
  margin-bottom: var(--s5);
}

/* Keep the pagehead-to-first-rule gap on the same rhythm as section-to-section. */
.pagehead+.pane__section {
  margin-top: var(--s6);
}

/* --- Shelf description ---------------------------------------------------- */

.shelf__desc {
  margin-top: var(--s3);
  max-width: 56ch;
  color: var(--muted);
  font-size: 0.9375rem;
}

.shelf__item--lead .shelf__desc {
  font-size: 1.0625rem;
  color: var(--graphite);
}

/* --- Manifesto -------------------------------------------------------------
   A single editorial artifact. The one place the serif carries a whole passage
   rather than a line.
   -------------------------------------------------------------------------- */

.manifesto {
  max-width: 40rem;
}

.manifesto>.label {
  display: block;
  margin-bottom: var(--s4);
}

.manifesto h1 {
  margin-bottom: var(--s7);
}

.manifesto__opening {
  padding-bottom: var(--s6);
  border-bottom: 1px solid var(--line);
}

.manifesto__body {
  margin-top: var(--s6);
  max-width: none;
  font-size: 1.0625rem;
  line-height: 1.7;
}



/* Framed sizes: there is room for the whole tree, so the disclosure is forced
   open and its toggle hidden. The legacy display rule covers engines without
   ::details-content; the @supports block hides the toggle only where the
   modern mechanism can actually keep the content visible. */
@media (min-width: 1100px) and (min-height: 720px) {
  .tree__disclosure>.tree__list {
    display: grid;
  }

  @supports selector(::details-content) {
    .tree__toggle {
      display: none;
    }

    .tree__disclosure::details-content {
      content-visibility: visible;
      block-size: auto;
    }
  }
}

/* Unframed sizes: the tree keeps its black card and collapses out of the way,
   so the content gets the full width. */
@media (max-width: 1099px),
(max-height: 719px) {

  /* The texture exists to make the screen read as a screen. With no set around
     it, it only dims the page, and its scanlines moire on a high-density
     display, so it is dropped entirely here. */
  body::before {
    display: none;
  }

  /* Top inset plus the height of the collapsed card, so the pane can clear it. */
  :root {
    --tree-offset: 4.625rem;
  }

  .tree {
    left: var(--s4);
    top: var(--s4);
    transform: none;
    max-height: calc(100dvh - var(--s8));
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--s2) var(--s4) var(--s3);
    background: #000000;
    border-radius: var(--r);
    font-size: 0.8125rem;
  }

  .tree__list {
    padding-top: var(--s2);
  }

  .screen {
    left: 0;
  }

  /* The card floats over the pane, so content has to begin below it. */
  .pane {
    padding-top: calc(var(--tree-offset) + var(--s5));
  }
}

/* --- Portrait phones: the set on its side ----------------------------------
   Rotating the photograph 90deg counter-clockwise puts the knob at the top and
   turns the window portrait. The window is 0.767 wide-to-tall against a phone's
   ~0.46, so width is always the binding constraint: it fills 96% of the width
   and lands at roughly 58% of the height on every phone size.

   Only the frame is rotated, never the stage, so the content inside the window
   stays upright.
   -------------------------------------------------------------------------- */

@media (max-width: 1099px) and (max-aspect-ratio: 1/1) {
  :root {
    /* Width and height are solved independently, so the photograph is stretched
       rather than scaled and the set ends up taller than life.

       The window is anchored below the knob band rather than centred: --rot-top
       is where its top edge sits in the viewport, and --rot-win-h how much of
       the height it takes. The knob sits about 4.8% of the stage above the
       window, so --rot-top has to leave at least that much room. */
    /* Everything below is a fraction of the stage height, measured off the
       render, counted upward from the window's top edge:

         0.0762  bottom of the set's bright chrome trim
         0.0611  top of the knob
         0.0475  centre of the knob

       Stretching the stage pulls these apart in pixels, so parking the window's
       top edge between the first two puts the trim off screen while the knob
       stays fully in frame. No mask needed. */
    --rot-trim: 0.0762;
    --rot-knob-top: 0.0611;
    --rot-knob: 0.0475;
    --rot-top-n: 0.0687;

    /* Where the window's bottom edge lands in the viewport. */
    --rot-bottom: 0.97;

    --stage-w: calc(100vw * var(--win-fill-rot) / var(--win-w-rot-n));
    /* 0.4046 = --rot-top-n + --win-h-rot-n, the stage above and inside the
       window as a share of its own height. */
    --stage-h: calc(100dvh * var(--rot-bottom) / 0.4046);
    --rot-top: calc(var(--rot-top-n) * var(--stage-h));
  }

  /* The set is back, so the texture has a screen to sit behind again. */
  body::before {
    display: block;
  }

  .stage {
    inset: auto;
    left: 50%;
    top: var(--rot-top);
    width: var(--stage-w);
    height: var(--stage-h);
    /* Y offset is the window's top, not its centre, so the band above it stays
       on screen no matter how far the stage is stretched. */
    transform: translate(calc(var(--win-cx-rot) * -1), calc(var(--win-y-rot) * -1));
  }

  /* Sized to the unrotated landscape image, then turned a quarter turn left so
     it exactly fills the portrait stage. */
  .frame {
    display: block;
    position: absolute;
    inset: auto;
    left: 50%;
    top: 50%;
    width: var(--stage-h);
    height: var(--stage-w);
    transform: translate(-50%, -50%) rotate(-90deg);
    background: url("./bg.webp") center / 100% 100% no-repeat;
    z-index: 5;
    pointer-events: none;
  }

  .screen {
    left: var(--win-x-rot);
    top: var(--win-y-rot);
    width: var(--win-w-rot);
    height: var(--win-h-rot);
    right: auto;
    bottom: auto;
    z-index: 1;
    overflow: hidden;
  }

  /* The toggle row is centred on the knob, not the card as a whole, so opening
     the index grows it downward instead of pushing its first rows off screen. */
  :root {
    --tree-toggle-h: 2.375rem;
  }

  .tree {
    top: calc(
      var(--rot-top) - var(--rot-knob) * var(--stage-h) - var(--s2) - var(--tree-toggle-h) / 2
    );
    transform: none;
  }

  /* The card now floats over the photograph above the window, not over the
     content, so the pane does not need to clear it. */
  .pane {
    padding-top: var(--s6);
    padding-inline: var(--s5);
  }
}

/* --- Responsive ----------------------------------------------------------- */

@media (max-width: 1240px) {
  :root {
    --col-1: 232px;
    --col-2: 212px;
  }
}

/* Fold the section column into the pane. */
@media (max-width: 1080px) {
  .col--section {
    display: none;
  }

  .plan {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .plan__cell {
    border-bottom: 1px solid var(--line-soft);
  }

  .plan__cell:nth-child(2n) {
    border-right: none;
  }

  .plan__cell:last-child {
    border-bottom: none;
    grid-column: 1 / -1;
  }

  .pagehead--split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s6);
  }

  .pagehead__aside {
    max-width: 26.25rem;
  }
}

@media (max-width: 900px) {
  .pane {
    padding-inline: var(--s5);
  }

  .shelf__item {
    grid-template-columns: 2.75rem minmax(0, 1fr) 1.25rem;
    gap: var(--s4);
    padding-block: var(--s5);
  }

  .shelf__meta {
    grid-column: 2;
    grid-row: 2;
    margin-top: var(--s3);
  }

  .shelf__chev {
    grid-column: 3;
    grid-row: 1;
  }

  .figures {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .figure {
    border-bottom: 1px solid var(--line-soft);
  }

  .figure:nth-child(2n) {
    border-right: none;
  }

  .figure:nth-last-child(-n + 2) {
    border-bottom: none;
  }

  .split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s6);
  }

  .pane__section {
    margin-top: var(--s7);
  }

  .pagehead+.pane__section {
    margin-top: var(--s5);
  }

  .pagehead {
    margin-bottom: var(--s7);
  }
}

@media (max-width: 560px) {
  :root {
    --tree-offset: 4.375rem;
  }

  .tree {
    font-size: 0.75rem;
  }

  :root {
    --tree-toggle-h: 2.125rem;
  }

  .tree__row,
  .tree__toggle {
    min-height: 2.125rem;
  }

  /* Below this the two-column plate cell cannot hold a label and a value. */
  .plate__cell {
    display: block;
  }

  .plate__val {
    text-align: left;
    margin-top: 3px;
  }
}

@media (max-width: 620px) {
  .plate {
    grid-auto-flow: row;
    grid-auto-columns: auto;
  }

  .plate__cell {
    padding: var(--s3) 0;
    border-right: none;
    border-bottom: 1px solid var(--line-soft);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s4);
  }

  .plate__cell:last-child {
    border-bottom: none;
  }

  .plate__cell:not(:first-child) {
    padding-left: 0;
  }

  .plate__val {
    margin-top: 0;
    text-align: right;
    white-space: normal;
  }

  .plan {
    grid-template-columns: minmax(0, 1fr);
  }

  .plan__cell {
    border-right: none;
  }

  .plan__cell:last-child {
    grid-column: auto;
  }

  .figures {
    grid-template-columns: minmax(0, 1fr);
  }

  .figure {
    border-right: none;
    padding-left: 0;
  }

  .figure:not(:first-child) {
    padding-left: 0;
  }


  .shelf__item {
    grid-template-columns: minmax(0, 1fr) 1.25rem;
    gap: var(--s3) var(--s4);
    padding-inline: 0;
  }

  .shelf__item>.label:first-child {
    grid-column: 1;
    grid-row: 1;
  }

  .shelf__item>div:nth-child(2) {
    grid-column: 1 / -1;
    grid-row: 2;
  }

  .shelf__meta {
    grid-column: 1 / -1;
    grid-row: 3;
    grid-auto-flow: column;
    justify-content: start;
    gap: var(--s4);
    margin-top: var(--s3);
  }

  .shelf__chev {
    grid-column: 2;
    grid-row: 1;
  }

  .pull {
    margin-block: var(--s6);
  }
}

/* --- Narrow landscape: the set, unrotated and stretched --------------------
   Between 933px and the desktop breakpoint there is not enough width to put the
   tree beside the set, but plenty for the set itself. Same treatment as the
   phone, minus the rotation: both axes are solved independently so the window
   fills the viewport, and the index card moves to the upper right, where the
   content's centred measure leaves it room.
   -------------------------------------------------------------------------- */

@media (min-width: 933px) and (max-width: 1099px) and (min-aspect-ratio: 1/1) {
  :root {
    /* Mirror of the phone rule. The same three fractions, measured off the same
       photograph, now read rightward from the window's right edge as shares of
       the stage width:

         0.0762  the set's bright chrome trim
         0.0611  outer edge of the knob
         0.0475  centre of the knob

       Parking the window's right edge 0.0687 in leaves the knob on screen and
       pushes the trim off it. --wide-left is where the window's left edge sits,
       so 0.4046 = that gap plus the window's own width. */
    --wide-left: 2%;
    --stage-w: calc(100vw * 0.98 / 0.4046);
    --stage-h: calc(100dvh * var(--win-fill-wide) / var(--win-h-n));
    /* Keeps the centred measure clear of the card in the corner. */
    --pane-max: 40rem;
  }

  body::before {
    display: block;
  }

  .stage {
    inset: auto;
    left: var(--wide-left);
    top: 50%;
    width: var(--stage-w);
    height: var(--stage-h);
    /* X offset is the window's left edge, not its centre, so the band on the
       right stays on screen however far the stage is stretched. */
    transform: translate(calc(var(--win-x) * -1), calc(var(--win-cy) * -1));
  }

  .frame {
    display: block;
    position: absolute;
    inset: 0;
    background: url("./bg.webp") center / 100% 100% no-repeat;
    z-index: 5;
    pointer-events: none;
  }

  .screen {
    left: var(--win-x);
    top: var(--win-y);
    width: var(--win-w);
    height: var(--win-h);
    right: auto;
    bottom: auto;
    z-index: 1;
    overflow: hidden;
  }

  .tree {
    left: auto;
    right: var(--s5);
    top: var(--s5);
    transform: none;
  }

  .pane {
    padding-top: var(--s7);
    padding-inline: var(--s5);
  }
}

/* --- Reduced motion ------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* --- Print ---------------------------------------------------------------- */

@media print {

  .topbar,
  .col,
  .foot,
  .mindex,
  .mindex__scrim {
    display: none !important;
  }

  .workspace {
    display: block;
  }

  body {
    background: #fff;
  }
}