/* ==========================================================================
   endersari.com
   Plain HTML + one stylesheet. No build step, no dependencies.
   ========================================================================== */

:root {
  --bg:      #ffffff;
  --text:    #111111;
  --muted:   #5c5c5c;
  --faint:   #757575;   /* 4.6:1 on white; #8a8a8a was 3.4:1 and failed AA */
  --line:    #e8e8e8;
  --sunk:    #fafafa;

  /* One family for text and one for data, drawn by the same hand: IBM Plex Sans
     and IBM Plex Mono were designed together and share proportions and details,
     so labels and code sit with the prose instead of beside it. */
  --sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* Used only for the wordmark. */
  --display: "VT323", ui-monospace, "Cascadia Code", Menlo, Consolas, monospace;
  /* Everything set in the display face: the wordmark and the section headings.
     9.1:1 on white, so it stays dark enough to read as a heading. */
  --display-ink: #8a1f2d;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.7;
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;

  /* Hold the footer at the bottom of the window when the page is shorter than
     the screen, which it is once the collapsible sections are closed.
     Otherwise the footer stops partway up with blank space beneath it. */
  min-height: 100vh;
  min-height: 100dvh;   /* excludes mobile browser chrome where supported */
  display: flex;
  flex-direction: column;
}

/* The content column absorbs the slack, so the footer is pushed down rather
   than floating. Its own top margin still applies on a long page. */
.site-header + .container { flex: 1 0 auto; }

/* Horizontal only. The header and footer bands own their vertical padding so
   their rules can run the full width of the viewport. */
/* width:100% matters: body is a flex column and the auto margins here stop
   the item stretching, so without it the container sizes to its content and
   the column silently narrows whenever no long line happens to force it. */
.container { width: 100%; max-width: 900px; margin: 0 auto; padding: 0 24px; }

a { color: var(--text); text-decoration: underline; text-underline-offset: 3px;
    text-decoration-color: #c9c9c9; transition: text-decoration-color .15s ease; }
a:hover { text-decoration-color: var(--text); }
a:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

p  { margin: 0 0 14px; }
ul { margin: 0 0 14px; padding-left: 20px; }
li { margin-bottom: 4px; }
strong { font-weight: 600; }
img, svg, canvas { max-width: 100%; height: auto; }

/* --- Header ------------------------------------------------------------- */

/* Full-bleed band: the rule spans the whole viewport, the content does not. */
.site-header { padding: 20px 0; border-bottom: 1px solid var(--line); }
.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

/* Breathing room under the rule, set once so every page matches. */
.site-header + .container { padding-top: 46px; }
.site-title {
  font-family: var(--display);
  font-size: 28px;
  font-weight: 400;
  letter-spacing: 3px;
  line-height: 1.2;
  margin: 0;
  white-space: nowrap;
}
/* Set on the anchor, not the heading: the generic `a` rule would otherwise
   repaint it back to --text. */
.site-title a { color: var(--display-ink); text-decoration: none; }
.site-title a:hover {
  text-decoration: underline;
  text-underline-offset: 5px;
  text-decoration-color: currentColor;
}

.social { display: flex; flex: 0 0 auto; gap: 14px; margin-top: 0; }
.social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--muted);
  text-decoration: none;
}
.social a:hover { color: var(--display-ink); }
.social svg { width: 18px; height: 18px; fill: currentColor; }

/* --- Intro two-column --------------------------------------------------- */

.columns { display: flex; gap: 28px; align-items: flex-start; }
/* Share the project thumbnails' column while keeping the portrait compact. */
.column-left  { flex: 0 0 200px; width: min(200px, 100%); max-width: 200px; }
.column-right { flex: 1; min-width: 0; }
/* A compact visual gap of about 18px between the opening paragraphs, without
   changing the typography or paragraph spacing used elsewhere on the site. */
.column-right > p { margin-bottom: 8px; }
.portrait { display: block; width: 152px; height: auto; margin-inline: auto; border-radius: 3px; }

@media (max-width: 700px) {
  .columns { flex-direction: column; gap: 20px; }
  .column-left { flex: none; }
}

@media (max-width: 360px) {
  .site-header { padding: 16px 0; }
  .site-title { font-size: 25px; letter-spacing: 2px; }
  .social { gap: 9px; }
  .social svg { width: 16px; height: 16px; }
}

/* --- Section headings --------------------------------------------------- */

/* Same pixel face as the wordmark, as on annahedstroem.com. */
h2.section {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 400;
  letter-spacing: 3px;
  margin: 44px 0 16px;
  line-height: 1.8;
  color: var(--display-ink);
}

/* Collapsible sections, in the manner of the disclosure headings on
   annahedstroem.com. */
details.section { margin: 40px 0 0; }
details.section#projects { margin-top: 20px; }
/* Closed, a section is only its heading, so they sit closer together: at the
   open spacing, a page with everything collapsed reads as four headings
   adrift rather than a short list. */
details.section:not([open]) { margin-top: 22px; }
details.section#projects:not([open]) { margin-top: 20px; }
details.section > summary { list-style: none; cursor: pointer; }
details.section > summary::-webkit-details-marker { display: none; }
details.section > summary h2.section { display: inline; margin: 0; }
/* The triangle is set in the body face: VT323 has no glyph for it. */
details.section > summary h2.section::before {
  content: '▾ ';
  font-family: var(--sans);
  font-size: 18px;
  /* Takes the heading's color and recedes with opacity, so the marker stays
     part of the heading instead of sitting next to it in grey. */
  color: inherit;
  opacity: 0.45;
  vertical-align: 2px;
  transition: opacity .15s ease;
}
details.section:not([open]) > summary h2.section::before { content: '▸ '; }
details.section > summary:hover h2.section::before { opacity: 1; }
details.section > summary:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }
details.section > .section-body { margin-top: 18px; }

/* --- Experience / education rows ---------------------------------------- */

.rows { display: grid; gap: 20px; }
/* Education keeps a date column; 148px is what the widest spaced date,
   "Sep 2020 – Sep 2022", needs to stay on one line. Experience overrides
   these columns further down and puts its dates on the right instead. */
.row { display: grid; grid-template-columns: 148px 1fr; gap: 0 20px; }
.row-when {
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 300;
  color: var(--muted);
  letter-spacing: 0.03em;
  line-height: 1.85;
  padding-top: 2px;
}
.row-where { display: block; color: var(--faint); }
.row h3 { margin: 0; font-size: 14px; font-weight: 600; line-height: 1.6; }
.row .role { margin: 0 0 6px; color: var(--muted); }
.row p:last-child, .row ul:last-child { margin-bottom: 0; }
.row ul { padding-left: 17px; }
.row li::marker { color: var(--faint); }
.row .thesis-line { font-size: 13px; }

/* Experience puts the dates on the right of the organisation instead of in a
   column of their own, so the role and the bullets start at the same left edge
   as the name. Placement is by grid area, not source order: the date follows
   the role in the markup, which is where it belongs once the grid collapses on
   a narrow screen. */
#experience .row,
#education .row {
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 20px;
  align-items: baseline;
}
#experience .row > h3,
#education .row > h3 { grid-area: 1 / 1; }
#experience .row > .role,
#education .row > .role { grid-area: 2 / 1; }
#experience .row > .row-when,
#education .row > .row-when {
  /* Spans the name's row and the role's, so the dates sit beside the one and
     the work arrangement beside the other. */
  grid-area: 1 / 2 / 3 / 3;
  justify-self: end;
  text-align: right;
  padding-top: 0;
  line-height: 1.95;
  color: var(--faint);
}
/* Auto-placed rather than pinned to row 3: an entry can carry more than one
   paragraph of detail, and pinning them to the same row would stack them on
   top of each other. */
#experience .row > ul,
#education .row > p:not(.role):not(.row-when) { grid-column: 1 / -1; }
/* A narrower measure, centred in the page but with everything inside it still
   set flush left. The toggle for the earlier roles lives in the same wrapper,
   so it stays aligned with the entries above it. */
#experience .section-body,
#education .section-body { max-width: 720px; margin-inline: auto; }
/* More air between jobs than between the lines inside one. */
#experience .rows,
#education .rows { gap: 30px; }
#experience .experience-more > .rows { gap: 30px; }
/* The bullets are the detail, so they sit a shade smaller and closer together
   than the name and role above them. */
#experience .row ul { margin-top: 2px; }
/* The degree line and the detail under it are one thought, so they sit
   close: no top margin on the detail, and a smaller gap under the role. */
#education .row > p:not(.role):not(.row-when) { margin-top: 0; }
#education .row > .role { margin-bottom: 2px; }
/* What the role is attached to, set off from the title by more than a word
   space now that there is no dash between them. */
.role-detail { margin-left: 8px; }
#experience .row li,
#education .row > p:not(.role):not(.row-when) { font-size: 13.5px; line-height: 1.5; margin-bottom: 5px; }
/* Keep the thesis detail quiet, while allowing it to wrap at any viewport.
   The :not() matches the specificity of the paragraph rule above it. */
#education .row > p.thesis-line:not(.role) {
  font-size: 11.5px;
  color: var(--muted);
}
#experience .row li:last-child { margin-bottom: 0; }
/* The work arrangement sits under the dates, a shade smaller and with no
   separator: the line break does that job. */
#experience .row-where,
#education .row-where { display: block; font-size: 10.5px; }
#experience .row-where::before,
#education .row-where::before { content: none; }

@media (max-width: 620px) {
  /* Block flow puts the date back under the role, where the markup has it. */
  #experience .row,
  #education .row { display: block; }
  /* The grid placement above has to be undone by hand, or the date keeps the
     end-aligned, shrink-to-fit box it had in the second column. */
  #experience .row > .row-when,
  #education .row > .row-when {
    grid-area: auto; justify-self: start;
    text-align: left; margin-top: 2px; line-height: 1.6;
  }
  #experience .row-where,
  #education .row-where { display: block; }
}


/* Earlier roles stay available without making the landing page compete with
   the two most recent positions. */
.experience-more { margin-top: 18px; }
.experience-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  color: var(--muted);
  font: 400 11px/1.3 var(--mono);
  letter-spacing: .03em;
  list-style: none;
}
.experience-more > summary::-webkit-details-marker { display: none; }
.experience-more > summary::after { content: "↓"; color: var(--display-ink); }
.experience-more[open] > summary::after { content: "↑"; }
.experience-more > summary span {
  text-decoration: underline;
  text-decoration-color: #c9c9c9;
  text-underline-offset: 3px;
}
.experience-more > summary:hover { color: var(--display-ink); }
.experience-more > summary:hover span { text-decoration-color: currentColor; }
.experience-more > summary:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }
.experience-more__hide { display: none; }
.experience-more[open] .experience-more__show { display: none; }
.experience-more[open] .experience-more__hide { display: inline; }
.experience-more > .rows { margin-top: 20px; }
/* A summary has to be the first child, so open it and the toggle would sit
   above the roles it just revealed — you would scroll back up to close them.
   Flex order puts it underneath instead, where a 'show less' belongs. */
.experience-more[open] { display: flex; flex-direction: column; }
.experience-more[open] > .rows { order: 0; margin-top: 0; }
.experience-more[open] > summary { order: 1; margin-top: 20px; }

@media (max-width: 620px) {
  .row { grid-template-columns: 1fr; gap: 2px; }
  .row-where { display: inline; }
  .row-where::before { content: ' · '; }
}

/* --- Project cards ------------------------------------------------------ */

/* Image first, after denizcemonduygu.com: every project gets the same canvas,
   its own drawing fills it, and the name sits directly underneath. No frame
   around the pair and no rule between them — the canvases are the grid, and
   the words hang off them. Tiles keep their own --tint (the canvas ground) and
   --ink (the hover colour), set inline on each link. */
.cards {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: 30px 22px;
  margin: 0;
}

@media (min-width: 701px) {
  .cards {
    /* Expand only into the container's 24px side padding. */
    width: calc(100% + 48px);
    margin-inline: -24px;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    column-gap: 52px;
  }
  /* Two columns per card keep all five equally wide and center the last pair. */
  .cards > .card { grid-column: span 2; }
  .cards > .card:nth-child(4) { grid-column: 2 / span 2; }
  .cards > .card:nth-child(5) { grid-column: 4 / span 2; }
}
.card {
  display: flex;
  flex-direction: column;
  gap: 13px;
  color: var(--text);
  text-decoration: none;
  min-width: 0;
}
.card-canvas {
  width: 94%;
  margin-inline: auto;
  aspect-ratio: 4 / 3;
  background: transparent;
  border-radius: 0;
  overflow: visible;
  /* The art is taken out of flow just below, so nothing inside can
     stretch the tile: a portrait photo used to push the box past its
     aspect ratio and leave the row of tiles ragged. */
  position: relative;
}
@media (min-width: 701px) {
  .card-canvas { width: 73%; }
}
/* What grows on hover is the drawing inside the canvas, never the canvas: the
   grid holds still and only the picture moves. The reduced-motion rule at the
   top of the file drops the transition for anyone who asks for stillness. */
/* A grid item's automatic minimum size is its content, so a portrait image
   stretches the tile past its aspect ratio and the row of tiles goes ragged.
   Zeroing that minimum lets the ratio hold and object-fit do the cropping. */
.card-canvas > * { position: absolute; inset: 0; margin: auto;
                    transition: transform .45s ease, opacity .45s ease; }
:is(.card, .project-row):hover .card-canvas > * { transform: scale(1.035); opacity: .93; }
/* A line drawing needs air around it; a drawing that is already a picture,
   paper and all, fills the canvas to its edges. */
.card-canvas svg { width: 76%; height: 76%; display: block; overflow: visible; }
.card-canvas img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-canvas > * {
  filter: saturate(.84) contrast(.95) brightness(.97);
  transition: filter .3s ease;
}
:is(.card, .project-row):hover .card-canvas > * { filter: none; }
/* These SVGs stay transparent and float directly in the reserved thumbnail
   area, with a little more scale than a photographic crop. */
.card-canvas .thumb-art--diagram,
.more-thumb .thumb-art--diagram {
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  object-fit: contain;
}
.card-canvas .thumb-art--diagram { width: 105%; height: 105%; }
.card-canvas .thumb-art--apollo,
.more-thumb .thumb-art--apollo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
/* These images are already mapped to the exact green and pink palette, so
   they must bypass the generic photograph softening above. */
.card-canvas .thumb-art--apollo { filter: none; }
.apollo-thumb-panel {
  position: relative;
  overflow: hidden;
}
.apollo-thumb-panel--a,
.apollo-thumb-panel--b { background: transparent; }
.apollo-thumb-panel img {
  width: 100%;
  height: 100%;
  max-width: none;
  /* The comparison panels are narrow. Keeping the whole cutout visible is
     more useful than filling their width and slicing through Apollo's head. */
  object-fit: contain;
  object-position: center;
  /* Keep the left edge fixed and trim only from Apollo's hand-side. */
  transform-origin: left center;
  transform: scale(1.65);
  mix-blend-mode: normal;
}
/* The related-project strip is much smaller than the homepage cards. Keep the
   compact Apollo portraits whole rather than carrying over the homepage crop. */
.more-thumb .apollo-thumb-panel img {
  object-fit: contain;
  transform: none;
}
/* The complete A/B visual is already almost the card's aspect ratio. Centre
   its minimal edge crop and retain the supplied green and pink tones. */
.card-canvas .thumb-art--ab-alternative,
.more-thumb .thumb-art--ab-alternative {
  object-fit: cover;
  object-position: center;
  filter: none;
}
/* The YouTube card uses a dedicated 4:3 crop, so it can sit complete inside
   the shared thumbnail canvas rather than relying on a stronger crop. */
.card-canvas .thumb-art--youtube,
.more-thumb .thumb-art--youtube {
  object-fit: cover;
  object-position: center;
  transform: scale(1);
  transform-origin: center;
  filter: none;
}
.card-canvas--youtube,
.more-thumb--youtube { overflow: hidden; }
:is(.card, .project-row):hover .card-canvas > .thumb-art--youtube,
.more-card:hover .more-thumb > .thumb-art--youtube {
  transform: scale(1.035);
  opacity: .93;
}
/* Preserve the transparent metro cutout and keep it still on both surfaces. */
.card-canvas .thumb-art--delay,
.more-thumb .thumb-art--delay {
  object-fit: contain;
  filter: none;
  transition: none;
  will-change: auto;
}
:is(.card, .project-row):hover .card-canvas > .thumb-art--delay,
.more-card:hover .more-thumb > .thumb-art--delay {
  transform: none;
  opacity: 1;
}
/* The project thumbnails stay still as cards; only their subject gets a small,
   meaningful response on hover. The wheel layer reuses the exact photograph,
   clipped to the tire, so the car body does not rotate with it. */
.card-canvas--tire {
  position: relative;
  overflow: visible;
  background: transparent;
}
.tire-art {
  /* Keep the wheel out of flow so it cannot stretch the card and push its
     title below the neighbouring cards. */
  position: absolute;
  /* The source has transparent headroom above the wheel; offset it so the
     visible tire aligns with the other card artwork. */
  top: -22%;
  bottom: auto;
  width: 75%;
  aspect-ratio: 400 / 592;
  transform-origin: 43% 48%;
  animation: tire-spin 2.6s linear infinite paused;
  will-change: transform;
}
.tire-art > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
@keyframes tire-spin { to { transform: rotate(360deg); } }
:is(.card, .project-row):hover .card-canvas--tire > * { transform: none; opacity: 1; }
:is(.card, .project-row):hover .tire-art { animation-play-state: running; }

.card-canvas--train { position: relative; overflow: hidden; }
.card-text {
  width: 94%;
  min-width: 0;
  margin-inline: auto;
  text-align: center;
}
@media (min-width: 701px) {
  .card-text { width: 73%; }
}
.card-title {
  display: block;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  margin: 0 0 3px;
}
.card-arrow { font-family: var(--mono); font-size: 11px; color: var(--faint);
              transition: color .22s ease; }
/* Each tile hovers in its own drawing's colour, set as --ink beside --tint on
   the link itself; the dark red is the fallback for a tile without one. --ink
   is not always the exact artwork hex: the orange is darkened from #cf6224,
   which is 3.87:1 on white and fails as 14px text. */
.card:hover .card-title, .card:hover .card-arrow { color: var(--ink, var(--display-ink)); }
.card-description {
  display: block;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
  margin: 0 0 4px;
}
.card-meta {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 300;
  letter-spacing: 0.03em;
  color: var(--faint);
  margin: 0;
  line-height: 1.6;
}

/* Three tiles across is too narrow to read below roughly the tablet width. */
@media (max-width: 700px) {
  .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px 18px; }
}
@media (max-width: 430px) {
  .cards { grid-template-columns: 1fr; gap: 24px; }
  .card-title { font-size: 13px; }
  .card-description { font-size: 13px; }
  .card-meta { font-size: 10px; }
}

/* --- Homepage project rows --------------------------------------------- */

#projects .project-list {
  --project-row-gap: 24px;
  display: grid;
  gap: var(--project-row-gap);
}
#projects .projects-more { margin-top: 0; }
#projects .project-row {
  position: relative;
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  align-items: start;
  column-gap: 28px;
  min-width: 0;
}
/* Place each separator in the existing gap without changing row spacing. */
#projects .project-row + .project-row::before,
#projects .projects-more .project-row:first-child::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: calc(var(--project-row-gap) / -2);
  border-top: 1px solid var(--line);
  pointer-events: none;
}
#projects .project-row__thumbnail {
  display: block;
  width: 200px;
  aspect-ratio: 4 / 3;
  margin: 0;
}
#projects .project-row__thumbnail:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
}
#projects .project-row__thumbnail *,
#projects .project-row__thumbnail::before,
#projects .project-row__thumbnail::after {
  will-change: auto;
}
@media (prefers-reduced-motion: reduce) {
  #projects .project-row__thumbnail::before,
  #projects .project-row__thumbnail::after {
    animation: none !important;
    transition: none !important;
  }
}
#projects .project-row__content {
  min-width: 0;
  text-align: left;
}
#projects .project-row h3 {
  margin: 0 0 8px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.4;
}
#projects .project-row h3 a {
  color: var(--text);
  text-decoration: none;
}
#projects .project-row h3 a:hover {
  text-decoration: underline;
}
#projects .project-row__description {
  margin: 0 0 10px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.65;
}
#projects .project-row__links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
  font: 400 11px/1.6 var(--mono);
}
#projects .project-row__links a {
  color: var(--display-ink);
  text-decoration-color: #c9c9c9;
}
#projects .project-row__links a:hover {
  text-decoration-color: currentColor;
}
#projects .project-row__links span {
  font-size: 10px;
}
@media (max-width: 600px) {
  #projects .project-list { --project-row-gap: 32px; }
  #projects .project-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
  #projects .project-row__thumbnail {
    width: min(200px, 100%);
  }
}

/* --- Where next, at the foot of a project page -------------------------- */

/* Set apart from the case study above by a rule and real space, so it reads as
   a way onward rather than a final section of the argument. Built by
   more-projects.js, which leaves out whichever project you are reading. */
.more { margin: 44px 0 0; padding-top: 24px; border-top: 1px solid var(--line); }

/* One row of the remaining projects. Art and title only: at this size a
   description would be unreadable, and the case study just made the argument
   for reading on. */
/* The columns are 1fr, so widening the gap is what shrinks the tiles: at 46px
   they come out around 178px instead of 194px, with more air between them. */
.more-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 32px 46px;
}
.more-card {
  display: flex; flex-direction: column; gap: 15px;
  color: var(--text); text-decoration: none; min-width: 0;
}
/* Only 7% padding: at this tile size the drawing has to carry the tile, and
   the old 15% left it as a small icon adrift in a coloured box. */
/* The same canvas as the homepage tiles, at strip size: one shape for a
   project's picture wherever it appears. */
.more-thumb {
  aspect-ratio: 4 / 3;
  background: transparent;
  border-radius: 0;
  /* The art is taken out of flow just below, so nothing inside can
     stretch the tile: a portrait photo used to push the box past its
     aspect ratio and leave the row of tiles ragged. */
  position: relative;
  overflow: visible;
}
.more-thumb > * { position: absolute; inset: 0; margin: auto;
                    transition: transform .45s ease, opacity .45s ease; }
.more-card:hover .more-thumb > * { transform: scale(1.035); opacity: .93; }
.more-thumb svg { width: 82%; height: 82%; display: block; overflow: visible; }
.more-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.more-thumb .thumb-art--diagram { width: 105%; height: 105%; }
/* Keep the portrait tire whole in the compact strip instead of cover-cropping
   it into the landscape thumbnail. */
.more-thumb .thumb-art--tire {
  /* The portrait stays height-limited; a narrower image box also keeps its
     scaled transparent edges inside the thumbnail on small screens. */
  width: 80%; object-fit: contain; transform: scale(1.2);
}
.more-card:hover .more-thumb > .thumb-art--tire { transform: scale(1.24); opacity: .93; }
/* Centred under its tile, quiet, with room above: the drawing is the thing
   being scanned, the name is confirmation. */
.more-title {
  font-size: 12px; font-weight: 400; line-height: 1.45;
  text-align: center; color: var(--muted);
  transition: color .22s ease;
}
/* Same per-card colour the homepage uses, carried on the link itself. */
.more-card:hover .more-title { color: var(--ink, var(--display-ink)); }
.more-card:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }

/* Four across is too narrow to read below roughly the tablet width. */
@media (max-width: 700px) {
  .more-row { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 34px; }
}
@media (max-width: 380px) {
  .more-row { grid-template-columns: 1fr; }
}

@media (max-width: 360px) {
  .card { grid-template-columns: 76px minmax(0, 1fr); gap: 12px; }
}

/* --- Pills -------------------------------------------------------------- */

/* Plain text links, not pills. The page head was six bubbles deep before the
   article started. With one link left it rides the end of the tag line rather
   than taking a row of its own. */
.btn-group { display: inline; margin: 0; }
/* Margins rather than spaces inside the string: a leading space in generated
   content collapses at the start of the inline box. */
.btn-group::before { content: "·"; color: var(--faint); margin: 0 7px 0 5px; }
.btn {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--muted);
  white-space: nowrap;
  text-decoration: underline;
  /* Matched to the base `a` rule. These sit on the same line as an ordinary
     inline link now, and a 4px offset with a currentColor rule read as a
     different kind of link beside it. Not --line: at 11.5px #e8e8e8 is
     invisible and the link stops looking like one. */
  text-underline-offset: 3px;
  text-decoration-color: #c9c9c9;
  transition: color .15s ease;
}
/* The decoration colour has to be restated here. Without it the generic
   `a:hover` rule, which is more specific than `.btn`, repaints the underline
   near-black while the word itself goes red. */
.btn:hover { color: var(--display-ink); text-decoration-color: currentColor; }
/* Marks the link as leaving the site, as the project cards already do. Not
   underlined with the word, so the rule stops at the text. */
.btn-arrow {
  font-size: 10.5px;
  color: var(--faint);
  text-decoration: none;
  display: inline-block;
  transition: color .15s ease;
}
.btn:hover .btn-arrow { color: var(--display-ink); }

/* Between two head links. A real element rather than a ::before on .btn: a
   pseudo inside the <a> would join the link text and take its underline. */
.btn-sep { color: var(--faint); margin: 0 7px 0 5px; }

/* A single link sitting inside a dot-separated head line rather than in a
   row of its own: a touch smaller, with the rule closer to the word, so it
   reads as part of the sentence instead of a button parked beside it. */
.btn--inline { font-size: 11px; text-underline-offset: 2px; }

/* Keeps a link and the middot after it on one line, so the run can only break
   after a separator — never onto a line that starts with one. */
.nb { white-space: nowrap; }

/* A gap between the sentence and the run of resource links after it. A word
   space alone is ~3px at this size and reads as part of the sentence. */
.page-credit > .nb:first-of-type { margin-left: 10px; }

/* Links on their own row, under the tags. A modifier and not a change to
   .btn-group: the bootstrapping page has one link and still rides it on the end
   of its tag line, which is where a single link belongs. */
/* font-size and line-height are set on the row itself, not left to the .btn
   children: as a block its strut comes from the inherited 16px body size, and
   that leading — not a margin — is what held the links 23px off the subtitle. */
.btn-group--block {
  display: block; font-size: 11.5px; line-height: 1.3; margin-top: -4px;
}
.btn-group--block::before { content: none; }

/* --- Project page ------------------------------------------------------- */

.backlink { font-family: var(--mono); font-size: 11px; color: var(--muted);
            text-decoration: none; letter-spacing: 0.04em; }
.backlink:hover { color: var(--text); text-decoration: underline; text-underline-offset: 3px; }

/* The gap to the first heading lives here: .prose zeroes its first child's top
   margin, so this padding is the only thing separating them. */
.page-head { padding-top: 0; padding-bottom: 20px; }
/* Stops a leading heading's top margin collapsing through and adding space
   below the rule that other pages do not have. */
.page-head > :first-child { margin-top: 0; }
.project-teaser { margin: 0 0 26px; }
.project-teaser__preview {
  display: block;
  width: min(100%, 728px);
  overflow: hidden;
  border: 1px solid var(--line);
  background: #0e0d20;
  text-decoration: none;
  transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.project-teaser__video {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.project-teaser__preview:hover {
  border-color: var(--display-ink);
  box-shadow: 0 8px 24px rgba(17, 17, 17, .10);
  transform: translateY(-1px);
}
.project-teaser__preview:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }
.project-teaser__link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: min(100%, 728px);
  margin: 10px 0 0;
  text-align: center;
}
.project-teaser__link .btn { font-size: 12.5px; }
.project-teaser__link .btn-arrow { font-size: 11.5px; }
.project-teaser__link .btn--framed {
  display: inline-flex;
  align-items: center;
  padding: 8px 11px;
  border: 1px solid var(--display-ink);
  background: var(--bg);
  box-shadow: 0 3px 10px rgba(138, 31, 45, .08);
  color: var(--display-ink);
  text-decoration: none;
}
.project-teaser__link .btn--framed:hover {
  background: #fff7f8;
  box-shadow: 0 5px 14px rgba(138, 31, 45, .14);
}
.project-teaser__link .btn--framed .btn-arrow { color: currentColor; }

.page-head h1 {
  font-size: 24px; font-weight: 600; letter-spacing: -0.015em;
  line-height: 1.25; margin: 14px 0 8px;
  /* 40ch is 576px in this face, and the longest title on the site sets at 548px,
     so every title holds one line on a full-width column. 30ch capped at 432px
     and broke "Data Analytics for Inventory & Production Planning" in two. Still
     a cap, not a licence to run the full 852px column. */
  max-width: 40ch;
}
.standfirst { color: var(--muted); margin: 0 0 14px; max-width: 62ch; }

/* One line rather than a measure. 62ch is 521px at 14px and this standfirst
   sets at ~580px, so the shared cap was the only thing wrapping it. A modifier,
   because the other pages using .standfirst run longer and want the measure. */
.standfirst--wide { max-width: none; }

@media (max-width: 620px) {
  .project-teaser { margin-bottom: 24px; }
}

/* Text is held to a readable measure, but figures are free to use the full
   column width, which is what they need to be worth interacting with. */
.prose { max-width: none; }
.prose > p,
.prose > h2,
.prose > h3,
.prose > ul,
.prose > ol,
.prose > .note { max-width: 66ch; }
/* A rule under each section heading, as on the slides these pages come from. */
/* Section titles carry the same red bar as .bridge, so a reader can find where
   a section starts by the mark alone. Replaces the hairline that used to run
   under the heading: a bar and a rule together is two markers for one job. */
.prose h2 {
  font-size: 15px; font-weight: 600; margin: 30px 0 14px;
  border-left: 2px solid var(--display-ink); padding-left: 14px;
  /* the case index jumps to three of these, so leave air above on arrival */
  scroll-margin-top: 24px;
}

/* Whatever follows a section title gives up its own top margin. Adjacent
   margins collapse to the larger of the two, so without this the gap under a
   title is set by whatever happens to sit beneath it — 14px before a
   paragraph, 26px before a .setup-block, 24px before a .loop. */
.prose > h2 + * { margin-top: 0; }
.prose h3 { font-size: 14px; font-weight: 600; margin: 24px 0 8px; }
.prose ol { margin: 0 0 14px; padding-left: 20px; }
.prose > :first-child { margin-top: 0; }

/* One quiet line of methods, separated by middots, rather than a row of pills.
   Inline so the code link can sit on the same line. */
.tags {
  display: inline; margin: 0; padding: 0; list-style: none;
  font-family: var(--mono); font-size: 11px; font-weight: 300;
  letter-spacing: 0.04em; line-height: 1.7; color: var(--faint);
}
.tags li { display: inline; margin: 0; }
.tags li + li::before { content: " · "; }
/* Two meta rows in one head: .tags is inline so a single row can carry a link
   at its end, but stacked rows each need a line of their own. */
.tags--row { display: block; }
/* One item picked out in the site's red. The separator after it belongs to
   the next item, so the dots stay grey. */
.tag--accent { color: var(--display-ink); }

/* --- The setup, and the compact fact blocks it is built from ------------ */

.setup-block { margin: 26px 0; }

/* Section and figure labels share one rule, so the label over a fact block and
   the label over a plot cannot drift apart. Darker and heavier than the stat
   labels, which are the same size and would otherwise read as the same tier,
   and underscored so each reads as a title rather than a caption. */
.setup-head,
.viz-step {
  position: relative;
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text); margin: 0 0 12px; padding-bottom: 7px;
}

.setup-head::after,
.viz-step::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 22px; height: 1px; background: var(--display-ink);
}

/* A section label stands off the block above it; a figure label sits under a
   lede that already provides the gap. */
.setup-head { margin-top: 26px; }

/* A semantic subsection that uses the same small underlined label treatment
   as the project setup headings. */
.prose h3.setup-head {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text);
  margin: 26px 0 12px;
  padding-bottom: 7px;
}
/* A first subsection belongs directly to its section title; later ones use
   the shared 26px setup rhythm above. */
.prose > h2 + h3.setup-head { margin-top: 0; }

/* The head of one stock case: a numbered label in the same voice as .setup-head,
   with its purpose on the same baseline. The purpose is a sibling of the h3 and
   not inside it, so the heading text stays the name of the case. */


/* An index of the three cases, each row jumping to its section. Indented behind
   a hairline so it reads as belonging to the sentence above it, and a lighter
   rule than .bridge's so the two do not merge into one bar. */
/* Set in from the article so the three rows read as a group under the sentence
   that introduces them. */
.case-nav { list-style: none; margin: 12px 0 26px; padding: 0 0 0 16px; }

/* Outranks `.prose > ul`, whose 66ch is ~554px and broke the longest row
   (407px of note behind 188px of bar, number and name) onto a second line. */
.prose > .case-nav { max-width: 676px; }

.case-nav li { margin-bottom: 11px; }

.case-nav li:last-child { margin-bottom: 0; }

/* A grid, not a flex row: fixed columns line the three names up under each
   other, and a note too long for its column wraps inside it instead of dropping
   to the row's left edge. 109px is the widest name ("Prebuild stock") measured
   at this size, plus air. */
/* No bar: on this site the accent bar marks a section, and these are links to
   sections, not sections themselves. The red numeral carries the series. */
.case-nav a {
  display: grid; grid-template-columns: 17px 122px 1fr;
  gap: 0 9px; align-items: baseline; text-decoration: none;
}

@media (max-width: 620px) {
  /* Too narrow for three columns; the note takes its own line under the name. */
  .case-nav a { grid-template-columns: 17px 1fr; gap: 3px 9px; }
  .case-nav .case-note { grid-column: 2; }
}

/* The numeral appears only in the index now; the section titles are plain
   names. It is a grid cell, so the column gap sets its spacing. */
/* The same numeral in a section title. Mono and smaller than the 15px name it
   precedes, so it reads as a marker rather than as part of the words. */
.prose h2 .case-num {
  font-family: var(--mono); font-size: 14px; font-weight: 500;
  color: var(--display-ink); margin-right: 10px;
}

.case-nav .case-num {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.14em; color: var(--display-ink);
}

.case-nav .case-name {
  font-family: var(--mono); font-size: 10.5px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--text);
  transition: color .15s ease;
}

.case-nav .case-note { font-size: 13.5px; line-height: 1.45; color: var(--muted); }

.case-nav a:hover .case-name,
.case-nav a:hover .case-note { color: var(--display-ink); }

.case-nav a:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }

.setup-block > .setup-head:first-child { margin-top: 0; }

/* One list shape for every factual group in the section, so the groups line up
   with one another instead of each finding its own. */
/* Every list in the article, at both levels. .setup-list and .constraints share
   one rule: they used to differ, so lists in The setup carried a dot and a 15px
   indent while every list after it sat flush with no marker, and the left edge
   visibly changed partway down the page. */
.setup-list,
.constraints,
.prose > ul:not([class]) { list-style: none; margin: 0 0 20px; padding: 0; }

.setup-list li,
.constraints li,
.prose > ul:not([class]) > li {
  position: relative; padding-left: 15px; margin-bottom: 8px;
  font-size: 13.5px; line-height: 1.45;
}

.setup-list li:last-child,
.constraints li:last-child,
.prose > ul:not([class]) > li:last-child { margin-bottom: 0; }

.setup-list li::before,
.constraints li::before,
.prose > ul:not([class]) > li::before {
  content: ""; position: absolute; left: 0; top: 8px;
  width: 3px; height: 3px; border-radius: 50%; background: var(--faint);
}

/* A second level, for items that belong under one of the facts above them. */
.setup-list ul { list-style: none; margin: 8px 0 0; padding: 0 0 0 15px; }

.setup-list ul li { margin-bottom: 8px; }

.setup-list ul li:last-child { margin-bottom: 0; }

/* Same dot as the level above, smaller: depth by size, not by a new shape. */
.setup-list ul li::before { width: 2px; height: 2px; top: 9px; }

/* A group inside a group. Indent alone carries the nesting; a rule here read as
   a second kind of accent beside the red bars in the case index. */
.setup-sub { margin-top: 22px; padding-left: 19px; }

.setup-sub .setup-head { margin-top: 0; }

/* Inside a .setup-block the block carries the spacing; standing on its own in
   the article it has to carry its own, or the next paragraph runs into it. */
.prose > .constraints { margin-bottom: 20px; }

/* The sentence that turns three objectives into the three sections that follow.
   Given weight because the whole page hangs off it. */
/* An ordinary sentence that hands off to the index below it. No bar: on this
   site the accent bar marks a section, and this is not one. The class exists
   only to give the hand-off room to breathe. */
.bridge { margin: -10px 0 12px; }

/* Prebuild makes the score the output; the AHP study only determines the
   weights. The four dimensions and their weights therefore belong in one
   compact analytical table, rather than in two repeated lists. */
.priority-score-lede { margin: 0 0 12px; font-size: 13.5px; }
.prose > .prebuild-priority-score-lede { max-width: none; }
.prose > .cycle-priority-score-lede { max-width: none; }
.prebuild-decision-bridge {
  margin: 24px 0 0;
  font-size: 13.5px;
}
.prose > .prebuild-decision-bridge { max-width: none; }
.dimensions-table {
  width: min(100%, 780px); margin: 0 0 12px; border-collapse: collapse;
  font-size: 12.5px; line-height: 1.45;
}
.dimensions-table thead th {
  padding: 0 14px 7px 0; border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: 0.06em; text-align: left; text-transform: uppercase; color: var(--muted);
}
.dimensions-table tbody th,
.dimensions-table tbody td {
  padding: 8px 14px 8px 0; border-bottom: 1px solid var(--line);
  vertical-align: top; text-align: left;
}
.dimensions-table tbody th {
  width: 25%; font-weight: 600; color: var(--text);
}
.dimensions-table tbody td:nth-child(2) { width: 60%; color: var(--muted); }
.dimensions-table thead th:last-child,
.dimensions-table tbody td:last-child {
  width: 15%; padding-right: 0; text-align: right;
}
.dimensions-table tbody td:last-child {
  font-family: var(--mono); font-weight: 500; font-variant-numeric: tabular-nums; color: var(--text);
}
.priority-method-note {
  margin: 12px 0 20px; font-size: 12.5px; line-height: 1.55; color: var(--muted);
}
.prose > .priority-method-note { max-width: none; }
.prebuild-profiles-title { margin-bottom: 8px; }
.prebuild-sku-profiles {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 8px;
  width: 100%; margin: 0 0 26px;
}
.prebuild-sku-profile { min-width: 0; margin: 0; padding: 0; }
.prebuild-profile-meta {
  margin: 0 0 6px; font-size: 11.5px; line-height: 1.45; color: var(--muted); text-align: center;
}
.prebuild-profile-meta span { padding: 0 3px; color: var(--faint); }
.prebuild-profile-meta .prebuild-profile-score {
  margin-left: 7px; padding: 0; color: var(--text); white-space: nowrap;
}
.prebuild-profile-score:only-child { margin-left: 0; }
.prebuild-sku-profile svg { display: block; width: 100%; height: auto; }
.prebuild-sku-profile svg text { font-size: 9.5px; }

/* Reads the three profiles above it, so it sits with them rather than with the
   section that follows. */
.prebuild-profiles-note {
  font-size: 13px; line-height: 1.6; color: var(--muted); margin: 14px 0 0;
}

/* Where the diagnostics live. Muted and small: it points off the page rather
   than competing with the method notes above it. */
.method-pointer {
  font-size: 13px; line-height: 1.6; color: var(--muted); margin: 16px 0 0;
}
.score-formula {
  margin: 8px 0 2px; font: 400 13px/1.55 var(--mono); color: var(--text);
}
.score-weight { text-decoration: underline; text-underline-offset: 2px; }
.score-inputs {
  margin: 0 0 26px; font-size: 12.5px; line-height: 1.5; color: var(--muted);
}
@media (max-width: 760px) {
  .prebuild-sku-profiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px 18px; max-width: 580px; }
}
@media (max-width: 520px) {
  .prebuild-sku-profiles {
    grid-template-columns: 1fr; gap: 22px 0; max-width: 300px;
    margin: 0 auto 26px;
  }
}



/* Where the diagnostics went. Small and muted: it points away from the page
   rather than competing with the methods above it. */


/* Three quantities the cycle-stock decision has to balance against each other.
   The arrows sit in the gaps, so the row reads as pulling against itself. */
.tension {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 32px;
  list-style: none; margin: 22px 0 26px; padding: 0;
}

.tension li {
  position: relative; border-top: 1px solid var(--line); padding-top: 12px;
  font-size: 13.5px; font-weight: 500;
}

.tension li + li::before {
  content: "↔"; position: absolute; left: -22px; top: 9px;
  color: var(--display-ink); font-size: 12px; line-height: 1;
}

/* Cycle stock is a shorter prioritization study. Its three trade-offs are a
   triangle, and the score weights are the only other visualization. */
/* The three objectives that pull against each other, one per corner, with the
   balance point marked. 480px wide so the box renders 1:1 and the labels keep
   the size they were laid out at. */
.cycle-tradeoff-figure { margin: 0 0 14px; padding: 0; max-width: 480px; }

.cycle-tradeoff-figure svg { display: block; width: 100%; height: auto; }

/* Body text, and past the 66ch measure: the sentence sets at 686px and the
   column is 852px, so it holds one line only if the cap is lifted. */
.tradeoff-note { margin: 0 0 24px; }

.prose > .tradeoff-note { max-width: none; }

/* The planning chain, rebuilt from the slide's own icons. Full column width so
   the five nodes and their labels have room; the icons carry transparency, so
   they sit on the page rather than in boxes. */
.process-figure { margin: 4px 0 24px; padding: 0; }

.process-figure svg { display: block; width: 100%; height: auto; }

.process-caption { margin: 8px 0 0; font-size: 11px; color: var(--muted); }

/* Ties the word to the box around the same thing in the diagram above. */
.output-mark {
  text-decoration: underline; text-decoration-color: #8a1f2d;
  text-decoration-thickness: 2px; text-underline-offset: 3px;
}
.cycle-operating-contrast { margin: 0 0 22px; font-size: 13.5px; line-height: 1.6; }





/* Safety Stock uses the same compact fact blocks as The setup, then lets two
   static comparisons carry the policy change and its operating effect. */
.safety-context { margin-bottom: 22px; }
.safety-figure { margin: 24px 0 28px; padding: 0; }
.safety-figure svg { display: block; width: 100%; height: auto; }
.safety-figure figcaption {
  font-size: 12.5px; line-height: 1.6; color: var(--muted); margin-top: 12px;
}
.safety-twcv-title { margin: 24px 0 6px; }
.prose > .safety-twcv-definition { max-width: none; }
.safety-twcv-definition { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; }
.safety-twcv-list { margin: 0 0 16px; }
.cu-definition { margin: 24px 0 0; }
.cu-definition > .setup-head { margin-top: 0; }
.cu-definition-lede { font-size: 12.5px; margin: 0 0 10px; }
.cu-formula {
  margin: 12px 0 18px; text-align: center;
  font: 400 12px/1.4 var(--mono); color: var(--text);
}
.cu-rule-table {
  border-collapse: collapse; margin: 0 auto 22px; width: min(100%, 680px);
  font-size: 12px; line-height: 1.35;
}
.cu-rule-table caption {
  caption-side: top; margin-bottom: 10px;
  font-family: var(--mono); font-size: 9px; letter-spacing: 0.06em;
  color: var(--muted); text-align: center;
}
.cu-rule-table th {
  font-family: var(--mono); font-size: 9px; font-weight: 500;
  letter-spacing: 0.06em; color: var(--muted); text-align: left;
  padding: 0 14px 7px 0; border-bottom: 1px solid var(--line);
}
.cu-rule-table td {
  padding: 7px 14px 7px 0; border-bottom: 1px solid var(--line);
  font-variant-numeric: tabular-nums;
}
.cu-rule-table th:last-child,
.cu-rule-table td:last-child { padding-right: 0; text-align: right; }
.cu-rule-table td:first-child,
.cu-rule-table td:last-child { font-family: var(--mono); }
@media (max-width: 520px) {
  .cu-rule-table { table-layout: fixed; width: 100%; font-size: 10.5px; }
  .cu-rule-table caption { font-size: 8px; letter-spacing: 0.04em; }
  .cu-rule-table th { font-size: 8.5px; letter-spacing: 0.04em; padding-right: 8px; }
  .cu-rule-table td { padding: 7px 8px 7px 0; }
  .cu-rule-table th:nth-child(1) { width: 31%; }
  .cu-rule-table th:nth-child(2) { width: 47%; }
  .cu-rule-table th:nth-child(3) { width: 22%; }
}
.safety-algorithm { max-width: 720px; margin: 0 0 22px; }
.safety-algorithm > summary { display: flex; width: 100%; }
.safety-algorithm .code-block {
  box-sizing: border-box; width: 100%; max-width: none;
  font-size: 11.5px; line-height: 1.65;
}
.safety-algorithm .code-block .line::before { content: none; display: none; }
.safety-algorithm .code-block .k { color: var(--text); font-weight: 600; }
.safety-algorithm .code-block .n { color: var(--display-ink); }
.safety-algorithm .safety-algorithm-note {
  margin: -7px 0 10px; font-size: 11.5px; font-style: italic; color: var(--muted);
}
.safety-figure-title,
.safety-inventory-title { margin: 26px 0 12px; }
.prose > .safety-figure-lede,
.prose > .safety-figure-conclusion,
.prose > .safety-bridge { max-width: none; }
.safety-figure-lede { font-size: 13.5px; color: var(--text); margin: 0 0 16px; }
.safety-twcv-figure { margin: 0 0 8px; }
.safety-figure-conclusion { font-size: 13.5px; color: var(--text); margin: 0 0 4px; }
.safety-bridge { font-size: 13.5px; line-height: 1.55; color: var(--text); margin: 0 0 18px; }
.safety-impact-summary {
  margin: 0 0 12px; font-family: var(--sans); font-size: 13.5px;
  color: var(--text);
}
.safety-impact-summary strong {
  font-size: inherit;
  font-weight: 600;
  color: var(--text);
}
.prose > .safety-impact-summary { max-width: none; }
.safety-inventory-takeaway {
  margin: 0 0 3px;
  font-size: 13px;
  color: var(--text);
}
.prose > .safety-inventory-takeaway { max-width: none; }
.safety-inventory-note {
  margin: 0 0 28px;
  font-size: 11.5px;
  color: var(--muted);
}
.safety-inventory-figure { margin: 0 0 8px; }

/* The supervisor line under the standfirst, and the acknowledgement at the end. */
/* Sits directly under the standfirst, with no gap: title, then one tight block
   of three lines. The pull-up lives here and not on .standfirst, which is shared
   with pages that have nothing under it and want their 14px. */
.page-credit {
  font-size: 13px; line-height: 1.4; color: var(--muted);
  margin: -11px 0 4px;
}
.page-credit__line { display: block; }

.credit {
  font-size: 12.5px; color: var(--faint); margin: 34px 0 0;
  padding-top: 15px; border-top: 1px solid var(--line);
}

@media (max-width: 700px) {
  /* Stacked, the arrows would point down a column and say the wrong thing. */
  .tension { grid-template-columns: 1fr; gap: 0; }
  .tension li { margin-top: 14px; }
  .tension li + li::before { content: none; }
}

@media (max-width: 520px) {
}

/* One quiet line of methods, sitting under the decision it served. Same voice as
   .tags, but a block: .tags is display:inline for the page head, and a <ul>
   cannot be nested in the <p> flow of the article. */
.methods {
  font-family: var(--mono); font-size: 11px; font-weight: 300;
  letter-spacing: 0.04em; line-height: 1.7; color: var(--faint);
  margin: 14px 0 26px;
}

.note { background: var(--sunk); border-left: 2px solid var(--line);
        padding: 12px 14px; margin: 20px 0; color: var(--muted); }
.note p:last-child { margin-bottom: 0; }

/* The decision loop the case study closes on. Set in text, not SVG, so it
   reflows on narrow screens and the steps stay selectable and focusable.
   Selectors are doubled up (.loop .loop-steps) to outrank `.prose ol`, which
   would otherwise reimpose its list padding. */
.loop { margin: 24px 0 26px; }
/* One line, wrapping in order. The numbered list above already explains the
   steps, so this only has to be memorable. */
.loop .loop-steps {
  display: flex; flex-wrap: wrap; align-items: baseline;
  list-style: none; margin: 0; padding: 0 0 10px; gap: 6px 0;
}
.loop-steps li { display: flex; align-items: baseline; }
/* The thin arrows between steps, lighter than the labels they join. */
.loop-steps li + li::before {
  content: "\2192"; color: #a8a8a8; margin: 0 11px; flex: 0 0 auto;
}
.loop-name { font-size: 13px; font-weight: 500; color: var(--text); }
/* The return to the start. The one accent mark, because iteration is the point
   the loop is making. */
.loop-return { color: var(--display-ink); font-size: 15px; line-height: 1; }
.loop-caption { font-size: 12px; font-style: italic; color: var(--faint); margin: 0; }

.refs { padding-left: 18px; font-size: 12.5px; line-height: 1.65; color: var(--muted); }
.refs li { margin-bottom: 7px; }

/* Secondary material inside an article: implementation notes, references.
   Present but not competing with the argument. */
.section-aside { margin: 22px 0; }
.section-aside > summary {
  cursor: pointer; list-style: none;
  display: inline-flex; align-items: center; min-height: 38px;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.section-aside > summary::-webkit-details-marker { display: none; }
.section-aside > summary::before {
  content: "›"; margin-right: 7px; font-size: 15px; line-height: 1;
  transition: transform .15s ease;
}
.section-aside[open] > summary::before { transform: rotate(90deg); }
.section-aside > summary:hover { color: var(--text); }
.section-aside > summary:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }
.section-aside > div { padding-bottom: 6px; }
.section-aside p { font-size: 13px; color: var(--muted); max-width: 66ch; }
/* Short cautions, set like the aside's own prose. Own class rather than a bare
   `ul` selector so the Further reading list keeps its .refs sizing. */
/* The two models, one image tinted two ways. Multiply over a solid colour turns
   a greyscale photo into a duotone, so one file serves both. */
/* Held to the same measure as the prose, then centred inside it, so the pair
   sits under the middle of the paragraph rather than the middle of the column. */
.models {
  display: flex; justify-content: center; gap: 16px;
  margin: 18px 0 22px; padding: 0; max-width: 66ch;
}
.model { flex: 0 1 128px; min-width: 0; margin: 0; text-align: center; }
/* The tint sits behind the image only, so the caption stays on white. */
.model-tint { display: block; border-radius: 3px; overflow: hidden; }
.model-tint img { display: block; width: 100%; height: auto; mix-blend-mode: multiply; }
.model-tint--a { background: #2f8f6f; }
.model-tint--b { background: #f4808c; }
.model figcaption {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.04em;
  color: var(--muted); margin-top: 7px;
}
@media (max-width: 520px) { .models { gap: 12px; } }

/* A short code listing, where showing the loop says it better than a paragraph. */
.code-block {
  margin: 16px 0; padding: 12px 14px; max-width: 66ch;
  background: var(--sunk); border-left: 2px solid var(--line);
  font: 400 12.5px/1.75 var(--mono); color: var(--text);
  overflow-x: auto;
}
.code-block code { font: inherit; }

/* Line numbers from a counter, so they are not part of the copied text. */
.code-block { counter-reset: codeline; }
/* inline, not block: the newline between spans is what makes the snippet
   copy as real Python instead of one run-on line */
.code-block .line { counter-increment: codeline; }
.code-block .line::before {
  content: counter(codeline);
  display: inline-block;
  width: 2ch;
  margin-right: 16px;
  text-align: right;
  color: #6f6f6f;
  -webkit-user-select: none;
  user-select: none;
}
/* Python tokens, in the manner of a light editor theme. */
.code-block .k { color: #a626a4; }   /* import, as, for, in */
.code-block .f { color: #3268e0; }   /* calls */
.code-block .n { color: #986801; }   /* numbers */
.code-block .c { color: #986801; }   /* True */

/* --- Interactive visualization shell ------------------------------------ */

/* Figures sit directly on the page, with no frame around them. */
.viz { margin: 26px 0 28px; padding: 0; }

/* Only revealed by the <noscript> block in each project page's head. */
.viz-noscript {
  display: none;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.7;
  color: var(--muted);
  margin: 0;
}
.viz-head {
  font-family: var(--mono); font-size: 10.5px; font-weight: 400;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 14px;
}
.viz-stage { width: 100%; }
.viz-stage svg { display: block; width: 100%; height: auto; overflow: visible; }

.viz-stage + .viz-step { margin-top: 24px; }

/* The control sits under the sentence describing the plot it drives, flush
   with that paragraph's left edge rather than off in a corner. Its nudge sits
   beside it, where the reader is already looking for something to do. */
.viz-control {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 22px; margin: 0 0 28px;
}
.viz-control .ctrl { flex: 0 1 340px; }
.viz-control .viz-cue { margin: 0; }

/* One line of setup before a plot, where a list would be too much furniture.
   Set as body copy — same size, leading and colour as the prose it sits among
   — but on the figure's measure, not the prose one: it introduces a full-width
   plot, so holding it to 66ch broke it early against empty space. */
.viz-lede { margin: 0 0 13px; }
/* A second line belonging to the first: the negative top margin collapses the
   paragraph gap so the pair reads as one thought, not two. */
/* Slightly smaller than the question above it: this line states how the figure
   was computed, which is supporting detail rather than the framing. */
.viz-lede + .viz-lede { font-size: 13px; margin-top: -6px; margin-bottom: 18px; }
/* but give the control back its breathing room when it follows that pair */
.viz-lede + .viz-control { margin-top: 12px; }


/* The descriptive half of the story. Held back so the uncertainty plot
   below it carries the argument. */
.viz-stage--minor { opacity: 0.88; }

/* Borrowed artwork. Held to the text measure so it reads as an aside rather
   than competing with the figures, and always credited in the caption. */
.cartoon { margin: 22px 0 30px; padding: 0; max-width: 66ch; }
.cartoon img { display: block; width: 100%; height: auto; }
/* The credit sits off the artwork rather than tucked under its edge. */
.cartoon figcaption {
  font-size: 11.5px; line-height: 1.6; color: var(--faint); margin-top: 13px;
}

/* A static diagram that explains a method, in place of a paragraph doing it.
   Its labels render near 1:1 rather than being shrunk to the text measure. */
.viz-steps { margin: 26px 0 8px; padding: 0; }
.viz-steps svg { display: block; width: 100%; height: auto; }
.seasonal-demand-figure { max-width: 620px; margin: 26px 0 8px; }

/* Preserve the bootstrap drawings' label size on narrow screens. The named,
   focusable containers support both touch swipes and keyboard scrolling. */
.bootstrap-scroll {
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.bootstrap-scroll:focus-visible {
  outline: 2px solid var(--text); outline-offset: 3px;
}
.bootstrap-scroll-hint { display: none; }
@media screen and (max-width: 900px) {
  .bootstrap-scroll { padding-bottom: 6px; }
  .bootstrap-scroll svg { width: 860px; max-width: none; }
  .bootstrap-scroll-hint {
    display: block;
    margin: 8px 0 14px;
    font-size: 12px;
    color: var(--muted);
  }
}

/* The source figures are deliberately wide so their labels remain legible.
   On a phone, preserve that drawing scale inside a touch-scrollable figure
   instead of shrinking 9–12px chart labels into unreadable text. */
@media (max-width: 620px) {
  .process-figure,
  .safety-twcv-figure,
  .safety-inventory-figure,
  .seasonal-demand-figure {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    padding-bottom: 6px;
  }

  .process-figure svg,
  .safety-inventory-figure svg { width: 760px; max-width: none; }
  .safety-twcv-figure svg { width: 800px; max-width: none; }
  .seasonal-demand-figure svg { width: 720px; max-width: none; }
}

/* The demand chart and response options are deliberately separate: the
   equal-width alternatives do not imply fixed calendar periods. */
.prebuild-alternatives { max-width: 760px; margin: 0 auto; }
.prebuild-alternatives-title { margin: 18px 0 12px; }
.prebuild-alternatives-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 26px;
}
.prebuild-alternative-name {
  margin: 0 0 4px; font-size: 12px; font-weight: 600; line-height: 1.4;
}
.prebuild-alternative-name span {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  letter-spacing: 0.04em; color: var(--display-ink); margin-right: 6px;
}
.prebuild-alternative-action {
  margin: 0 0 3px; font-size: 11.5px; line-height: 1.45; color: var(--text);
}
.prebuild-alternative-risk {
  margin: 0; font-size: 11px; line-height: 1.45; color: var(--muted);
}

@media (max-width: 620px) {
  /* Below this the three alternatives need their own full line. */
  .prebuild-alternatives-grid { grid-template-columns: 1fr; gap: 14px 0; }
  .dimensions-table { table-layout: fixed; font-size: 11.5px; }
  .dimensions-table thead th { font-size: 8px; letter-spacing: 0.04em; }
  .dimensions-table tbody th,
  .dimensions-table tbody td { padding: 7px 8px 7px 0; }
  .dimensions-table tbody th { width: 29%; }
  .dimensions-table tbody td:nth-child(2) { width: 55%; }
  .dimensions-table thead th:last-child,
  .dimensions-table tbody td:last-child { width: 16%; }
  .score-formula { font-size: 11.5px; letter-spacing: -0.02em; }
}

/* An aside in the author's voice, not a callout box. */
.viz-cue {
  font-size: 12.5px; line-height: 1.6; color: var(--faint);
  margin: 10px 0 0; max-width: 66ch;
}

/* Bottom-aligned so every slider track lines up, even when a label wraps. */
.viz-controls { display: flex; flex-wrap: wrap; gap: 13px 22px; margin-top: 15px;
                padding-top: 13px; border-top: 1px solid var(--line); align-items: flex-end; }
.ctrl { display: flex; flex-direction: column; gap: 4px; min-width: 150px; flex: 1 1 150px; }
.ctrl label { font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.05em;
              color: var(--muted); display: flex; justify-content: space-between; gap: 10px; }
.ctrl label b { font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 2px; background: var(--line); border-radius: 2px; outline: none;
  margin: 6px 0; cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--bg); border: 1.5px solid var(--text); cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--bg); border: 1.5px solid var(--text); cursor: pointer;
}
input[type="range"]:focus-visible { outline: 2px solid var(--text); outline-offset: 4px; }

/* Squared off and hairline rather than a heavy pill, so the control reads as
   part of the figure instead of UI dropped on top of it. */
.viz-btn {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.04em;
  padding: 4px 11px; border: 1px solid var(--line); border-radius: 3px;
  background: transparent; color: var(--text); cursor: pointer; white-space: nowrap;
}
.viz-btn:hover { border-color: var(--display-ink); color: var(--display-ink); }
.viz-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 3px; }

/* The secondary action, with no box at all. Must follow .viz-btn: the base
   rule's `border` shorthand has equal specificity and would otherwise win,
   which is what made both buttons render as identical pills. */
.viz-btn--quiet {
  border-color: transparent; color: var(--faint); padding: 4px 2px;
}
.viz-btn--quiet:hover {
  border-color: transparent; color: var(--display-ink);
  text-decoration: underline; text-underline-offset: 3px;
}

.viz-note {
  font-family: var(--mono); font-size: 10.5px; line-height: 1.75;
  color: var(--muted); margin: 12px 0 0;
}

/* Panel that describes whatever the reader has hovered or selected. */
.viz-detail { margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--line); }
.viz-detail-title {
  font-family: var(--mono); font-size: 11px; font-weight: 500;
  letter-spacing: 0.06em; margin: 0 0 5px;
}
.viz-detail-body { font-size: 13px; line-height: 1.65; color: var(--muted);
                   margin: 0; max-width: 64ch; }

/* Rule toggles */
.checks { display: flex; flex-wrap: wrap; gap: 9px 18px; }
.checks label {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.04em; color: var(--muted);
}
.checks input { accent-color: #111; width: 13px; height: 13px; cursor: pointer; }
.checks input:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.viz-note b { font-weight: 500; color: var(--text); font-variant-numeric: tabular-nums; }

.viz figcaption { font-size: 12.5px; line-height: 1.6; color: var(--muted); margin-top: 12px; }

/* Cluster markers are focusable, so they need a visible ring. */
.fcm-centroid:focus { outline: none; }
.fcm-centroid:focus-visible circle:first-child {
  stroke: var(--text);
  stroke-width: 3;
}

/* Shared plot primitives */
.axis      { stroke: var(--line); stroke-width: 1; }
.tick-text { font-family: var(--mono); font-size: 10px; fill: var(--faint); }
.axis-text { font-family: var(--mono); font-size: 10px; fill: var(--muted);
             letter-spacing: 0.04em; }

/* Legend */
.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 12px;
          font-family: var(--mono); font-size: 10px; color: var(--muted); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 14px; height: 2px; border-radius: 1px; display: inline-block; }

/* --- Reading clock ------------------------------------------------------ */

/* Sits in the corner of a project page; the minute hand turns once over the
   length of the article. Never intercepts clicks on the figures beneath it. */
.reading-clock {
  position: fixed;
  right: 16px;
  bottom: 14px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  pointer-events: none;
}
.reading-clock svg { display: block; width: 38px; height: 38px; }
/* White dial, gray wedge, then a heavy black bezel drawn over the top so the
   fill never eats into the border. */
.rc-dial  { fill: var(--bg); }
.rc-fill  { fill: #d5d5d5; }
.rc-bezel { fill: none; stroke: var(--text); stroke-width: 2.6; }
.rc-hand  { stroke: var(--text); stroke-width: 1.8; stroke-linecap: round; }
.rc-pin   { fill: var(--text); }
.rc-label {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  /* Keep the watch in place when the countdown becomes a checkmark. */
  height: 15px;
  line-height: 15px;
}
/* At the end the wedge completes the circle and a checkmark replaces the time. */
.reading-clock.is-done .rc-label {
  font-size: 16px;
}

/* The 900px column plus its margins needs about 1040px of window before there
   is clear space to the right of the text. Below that the clock would sit on
   top of the paragraphs rather than beside them, so it is hidden. */
@media (max-width: 1040px) { .reading-clock { display: none; } }

/* --- Footer ------------------------------------------------------------- */

/* Full-bleed band, like the header. */
.site-footer { border-top: 1px solid var(--line); margin-top: 44px; padding: 18px 0 40px;
               color: var(--faint); font-size: 14px; }
.site-footer .container { display: flex; flex-wrap: wrap; gap: 6px 20px;
                          justify-content: space-between; }
.site-footer a { color: var(--faint); }
.site-footer a:hover { color: var(--text); }

/* --- Delay prediction: from a point model to a probability --------------- */

/* Notation set in the mono face, inline in a sentence that stays prose. */
.mono-inline { font-family: var(--mono); font-size: 0.94em; }
/* This places the delay pipeline precisely in the wider team system. */
.routing-fit-diagram {
  display: grid;
  grid-template-columns: 1.55fr auto 1.15fr auto 1.35fr auto .9fr auto .65fr;
  align-items: center;
  gap: 8px;
  max-width: 860px;
  margin: 4px 0 0;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.45;
  letter-spacing: .01em;
  text-align: center;
}
.routing-fit-context { color: var(--muted); }
.routing-fit-contribution, .probability-accent { color: var(--display-ink); }
.routing-fit-arrow { color: var(--faint); font-size: 14px; line-height: 1; }
.routing-fit-note {
  max-width: 860px;
  margin: 16px 0 0;
  color: var(--muted);
  font: 400 10.5px/1.45 var(--mono);
  text-align: left;
}

@media (max-width: 620px) {
  .routing-fit-diagram { grid-template-columns: 1fr; gap: 6px; max-width: 360px; }
  .routing-fit-arrow { height: 12px; font-size: 0; }
  .routing-fit-arrow::after { content: "↓"; font-size: 14px; }
}

/* A sentence short enough to hold one line, but only if it is allowed past
   the 66ch measure the prose column sets. */
.prose > .wide-line { max-width: none; }

/* A sentence that introduces the list right under it, so the two read as one
   thought rather than two blocks. */
.lead-in { margin-bottom: 6px; }

.validation-underlined { text-decoration: underline; text-underline-offset: 3px; text-decoration-color: #c9c9c9; }

.pipeline-figure, .coverage-figure { margin: 0 0 22px; padding: 0; }
.pipeline-figure svg, .coverage-figure svg {
  display: block; width: 100%; height: auto;
}

/* Three equal stages, with a shared set of vertical slots so the comparison
   reads as one pipeline rather than three independently spaced lists. */
.pipeline-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: 100%;
  max-width: 780px;
  overflow: visible;
  padding-bottom: 0;
}
.pipeline-step {
  min-width: 0;
  padding: 0 16px;
  display: grid;
  grid-template-rows: 32px auto;
}
.pipeline-step + .pipeline-step { border-left: 1px solid var(--line); }
.pipeline-step-head {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 11px;
  margin-inline: -16px;
  padding-top: 1px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
  line-height: 1.35;
}
.pipeline-step-head span {
  flex: 0 0 auto;
  padding-top: 2px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 400;
  letter-spacing: .08em;
  color: var(--display-ink);
}
.pipeline-step-head strong {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.pipeline-step-flow {
  display: grid;
  grid-template-rows: 38px 16px 34px 16px 38px;
  align-items: center;
  justify-items: center;
  padding-top: 14px;
}
.pipeline-step-flow p {
  display: grid;
  place-items: center;
  width: 100%;
  margin: 0;
  text-align: center;
  font-size: 12px;
  line-height: 1.35;
}
.pipeline-input { color: var(--muted); white-space: nowrap; }
.pipeline-operation { color: var(--text); }
.pipeline-arrow {
  display: grid;
  place-items: center;
  width: 100%;
  height: 16px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  color: var(--faint);
}
.pipeline-output { color: var(--text); font-weight: 600; }
.pipeline-step--final .pipeline-output { color: var(--display-ink); }
.pipeline-details-link:hover { color: var(--display-ink); text-decoration-color: currentColor; }
.pipeline-details-arrow { display: inline-block; font-size: .78em; }

@media (max-width: 760px) {
  .pipeline-figure:not(.pipeline-grid) {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    padding-bottom: 6px;
  }
  .pipeline-figure:not(.pipeline-grid) svg { width: 852px; max-width: none; }

  .pipeline-grid { grid-template-columns: 1fr; }
  .pipeline-step { padding: 0; }
  .pipeline-step-head { margin-inline: 0; }
  .pipeline-step + .pipeline-step {
    margin-top: 20px;
    padding-top: 20px;
    border-top: 1px solid var(--line);
    border-left: 0;
  }
  .pipeline-input { white-space: normal; }
}
.validation-result { margin: 4px 0 2px; }
.coverage-figure { max-width: 400px; margin: 6px 0 8px; }
.coverage-figure svg { max-width: 400px; margin-inline: 0; }
.validation-footer {
  margin: 0 0 26px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--muted);
}
.validation-footer a { color: var(--muted); }
.validation-footer a:hover { color: var(--display-ink); text-decoration-color: currentColor; }
.validation-footer span { display: inline-block; font-size: .78em; }

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

@media print {
  /* The watch is fixed, so it would otherwise stamp itself on every sheet. */
  .reading-clock, .skip-link { display: none; }
  .site-header { border-bottom: 1px solid #ccc; }
  a { text-decoration: none; }
  .viz-controls { display: none; }   /* dead controls on paper */
}

/* --- Accessibility ------------------------------------------------------ */

.skip-link { position: absolute; left: -9999px; top: 0; padding: 8px 14px;
             background: var(--bg); border: 1px solid var(--text); z-index: 10; }
.skip-link:focus { left: 0; }
