/* ---------------------------------------------------------------------------
   jj26 — portfolio front door
   Direction: gallery-forward editorial. Light ground, hairline rules, no
   decoration. The work carries the colour; the chrome stays out of the way.

   Everything visual is a token in :root. Swapping the direction (dark studio,
   heavier editorial, etc.) is a matter of changing this block, not the markup.
--------------------------------------------------------------------------- */

:root {
  /* Ground + ink — DAY EDITION. The complete palette lives here; the night
     blocks below redefine these tokens and nothing else. */
  --paper:      #F2F3F1;
  --paper-sunk: #FBFBFA;
  --ink:        #16171A;
  --ink-soft:   #53565B;
  --ink-faint:  #8A8D91;
  --rule:       rgba(22, 23, 26, 0.14);
  --rule-soft:  rgba(22, 23, 26, 0.07);
  --signal:     #2C6B70;
  --curve:      #16171A;
  --plate-glow: rgba(22, 23, 26, 0.06);   /* pool of ground behind device plates; set to transparent to switch off */

  /* Type — the display face is the edition's voice and changes with it.
     Body and mono stay constant so it reads as one site changing clothes. */
  --display:      "Fraunces", Georgia, "Times New Roman", serif;
  --display-wght: 400;
  --display-case: none;
  --display-vf:   "SOFT" 18, "WONK" 1, "opsz" 100;
  --display-trk:  -0.018em;
  --text: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  --step--1: clamp(0.78rem, 0.76rem + 0.10vw, 0.84rem);
  --step-0:  clamp(0.98rem, 0.95rem + 0.14vw, 1.06rem);
  --step-1:  clamp(1.18rem, 1.10rem + 0.38vw, 1.42rem);
  --step-2:  clamp(1.55rem, 1.35rem + 1.00vw, 2.20rem);
  --step-3:  clamp(2.10rem, 1.60rem + 2.50vw, 3.60rem);
  --step-4:  clamp(2.80rem, 1.70rem + 5.20vw, 6.40rem);

  /* Measure + rhythm */
  --gutter:  clamp(1.25rem, 0.9rem + 1.8vw, 2.75rem);
  --shelf:   clamp(4.5rem, 3rem + 6vw, 9rem);
  --maxw:    78rem;

  /* Motion — considered, never default.
     --ease is an expo-out: fast departure, long settle. Everything that
     enters the page uses it, so arrivals feel like one hand moved them. */
  --ease:      cubic-bezier(0.22, 1.00, 0.36, 1.00);
  --ease-soft: cubic-bezier(0.16, 1.00, 0.30, 1.00);
  --t-quick:   240ms;
  --t-base:    620ms;
  --t-slow:    900ms;
}


/* --- NIGHT STUDIO -------------------------------------------------------
   Two blocks on purpose: the media query covers viewers on system-dark who
   have never touched the switch; the stamp lets the switch win either way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:      #0E0E10;
    --paper-sunk: #151518;
    --ink:        #EDECE8;
    --ink-soft:   #97958F;
    --ink-faint:  #65635F;
    --rule:       rgba(237, 236, 232, 0.16);
    --rule-soft:  rgba(237, 236, 232, 0.07);
    --signal:     #79A9AC;
    --curve:      #D8D6D0;
    --plate-glow: rgba(237, 236, 232, 0.055);

    --display:      "Archivo", "Helvetica Neue", Arial, sans-serif;
    --display-wght: 800;
    --display-case: uppercase;
    --display-vf:   "wdth" 64;
    --display-trk:  -0.02em;
  }
}
:root[data-theme="dark"] {
    --paper:      #0E0E10;
    --paper-sunk: #151518;
    --ink:        #EDECE8;
    --ink-soft:   #97958F;
    --ink-faint:  #65635F;
    --rule:       rgba(237, 236, 232, 0.16);
    --rule-soft:  rgba(237, 236, 232, 0.07);
    --signal:     #79A9AC;
    --curve:      #D8D6D0;
    --plate-glow: rgba(237, 236, 232, 0.055);

    --display:      "Archivo", "Helvetica Neue", Arial, sans-serif;
    --display-wght: 800;
    --display-case: uppercase;
    --display-vf:   "wdth" 64;
    --display-trk:  -0.02em;
}

/* --------------------------------------------------------------- reset-ish */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  transition: background-color var(--t-base) var(--ease), color var(--t-base) var(--ease);
  color: var(--ink);
  font-family: var(--text);
  font-size: var(--step-0);
  line-height: 1.6;
  font-feature-settings: "kern" 1, "liga" 1;
  -webkit-font-smoothing: antialiased;
}

img, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; }

h1, h2, h3 {
  font-family: var(--display);
  font-weight: var(--display-wght);
  font-variation-settings: var(--display-vf);
  text-transform: var(--display-case);
  letter-spacing: var(--display-trk);
  line-height: 1.04;
  margin: 0;
  text-wrap: balance;
}

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

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

.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--paper);
  padding: 0.7rem 1.1rem; z-index: 100;
}
.skip:focus { left: 1rem; top: 1rem; }

/* ------------------------------------------------------------------ layout */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.shelf { padding-block: var(--shelf); }

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

/* Small caps label used to head every section. Hairline, quiet, consistent. */
.label {
  font-size: var(--step--1);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 2.25rem;
  font-weight: 500;
}

/* --------------------------------------------------------------- masthead */
.masthead {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-base) var(--ease),
              background-color var(--t-base) var(--ease);
}
.masthead[data-lifted="true"] { border-bottom-color: var(--rule); }

.masthead__inner {
  display: flex; align-items: baseline; gap: 1.5rem;
  justify-content: space-between;
  padding-block: 1.15rem;
}

.wordmark {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: var(--text);
  font-weight: 600;
  font-size: var(--step-0);
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
}
/* The name only (not the mark): JetBrains Mono, all caps. CSS caps keep the DOM text
   "Jamie Jones" for screen readers; name-scramble.js measures it as rendered. */
.wordmark > span {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.nav { display: flex; gap: clamp(1rem, 0.4rem + 1.8vw, 2.25rem); }

.nav a {
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-soft);
  position: relative;
  padding-block: 0.2rem;
  transition: color var(--t-quick) var(--ease);
}
/* Hairline wipes in from the left rather than fading — reads as drawn. */
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--ink);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-base) var(--ease);
}
.nav a:hover { color: var(--ink); }
.nav a:hover::after { transform: scaleX(1); }

/* Phones: two rows. Name and edition switch share the top; the nav gets a
   full-width row of its own instead of pushing the page sideways. */
@media (max-width: 34rem) {
  .masthead__inner { flex-wrap: wrap; row-gap: 0.55rem; padding-block: 0.85rem 0.6rem; }
  .wordmark { order: 1; }
  .masthead__inner { column-gap: 0.75rem; }
  .masthead__end { order: 2; margin-left: auto; gap: 0.35rem; }
  .mw-slot { width: 36px; }
  .nav { order: 3; width: 100%; justify-content: space-between; gap: 0; }
  .nav a { letter-spacing: 0.04em; }
}

/* ------------------------------------------------------------------- hero */
main { position: relative; overflow-x: clip; }
/* The pixel field sits fixed behind everything (z-index -1, above only the
   page ground) so the pointer trail follows down the whole page. The hero's
   old bottom fade now happens in hero-field.js, row by row. */
.hero-field {
  position: fixed; top: 0; left: 0; width: 100%; height: 0;
  z-index: -1; pointer-events: none;
}
/* The hero keeps the height it had with the big one-line statement (top pad
   + 0.98 x step-4 line + statement margin + bottom pad), so the field keeps
   its room. The statement is now small and sits bottom-left, its bottom
   100px above the Selected work rule. Same height in Day and Night. */
.hero {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; justify-content: flex-end;
  min-height: calc(clamp(3.5rem, 1.75rem + 8vw, 8rem) + 0.98 * var(--step-4)
                 + clamp(1.75rem, 1rem + 2vw, 3rem) + clamp(3.5rem, 2rem + 5vw, 6.5rem));
  padding-block: 2rem 3.125rem; /* 50px */
}

.hero__statement {
  /* small: 26px at 390, ~35px at 1440, 36px max */
  font-size: clamp(1.625rem, 1.2rem + 1.1vw, 2.25rem);
  line-height: 1;
  letter-spacing: -0.02em;
  max-width: 22ch;
  margin: 0;
}
.hero__statement em {
  font-style: italic;
  /* Optical: the italic sits a touch heavy next to the roman at display size */
  letter-spacing: -0.015em;
  /* the italic's overhang eats the word space; give it back */
  margin-inline-end: 0.14em;
}

/* Night has no true italic in a compressed grotesk, and a faux oblique on
   that face looks like a mistake. The emphasis moves to width + weight
   instead — same job, done in the voice the edition actually speaks. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hero__statement em {
    font-style: normal;
    font-variation-settings: "wdth" 88;
    font-weight: 500;
    letter-spacing: -0.01em;
  }
}
:root[data-theme="dark"] .hero__statement em {
  font-style: normal;
  font-variation-settings: "wdth" 88;
  font-weight: 500;
  letter-spacing: -0.01em;
}

.hero__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: clamp(1.5rem, 1rem + 2vw, 3.5rem);
  max-width: 52rem;
}
.hero__meta p { margin: 0; color: var(--ink-soft); max-width: 34ch; }

.availability {
  display: inline-flex; align-items: flex-start; gap: 0.6rem;
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
}
.availability::before {
  content: ""; flex: 0 0 auto;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ink);
  /* sit on the baseline of the first line, not the middle of the block */
  margin-top: 0.52em;
  animation: pulse 3.2s var(--ease-soft) infinite;
}
@keyframes pulse {
  0%, 62%, 100% { opacity: 1;   transform: scale(1); }
  78%           { opacity: 0.28; transform: scale(0.82); }
}

/* ------------------------------------------------------------------- work */
/* Asymmetric on purpose: the archive is websites, an app, and a film, so a
   rigid uniform grid would flatten exactly the differences worth showing. */
.gallery {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(2.5rem, 1.5rem + 4vw, 6rem) clamp(1.25rem, 0.8rem + 1.6vw, 2.5rem);
}

.piece { grid-column: span 12; }
@media (min-width: 52rem) {
  .piece--wide  { grid-column: span 12; }
  .piece--half  { grid-column: span 6; }
  .piece--offset { grid-column: 4 / span 9; }
  .piece--tall  { grid-column: span 5; }
  .piece--seven { grid-column: span 7; }
  .piece--five  { grid-column: span 5; }
}

.piece a {
  text-decoration: none;
  display: block;
  color: inherit;
}

.piece__frame {
  position: relative;
  overflow: hidden;
  background: var(--paper-sunk);
  border: 1px solid var(--rule-soft);
  margin-bottom: 1.25rem;
}
.piece__frame img,
.piece__frame video {
  display: block;
  width: 100%;
  transition: transform var(--t-slow) var(--ease-soft),
              filter var(--t-slow) var(--ease-soft);
  will-change: transform;
}
.piece a:hover .piece__frame img,
.piece a:focus-visible .piece__frame img,
.piece a:hover .piece__frame video,
.piece a:focus-visible .piece__frame video { transform: scale(1.028); }

/* Aspect discipline so mixed source ratios still line up on a shelf. */
.ratio-16x9 { aspect-ratio: 16 / 9; }
.ratio-4x3  { aspect-ratio: 4 / 3; }
.ratio-3x4  { aspect-ratio: 3 / 4; }
.piece__frame--device picture { display: block; height: 100%; }
.piece__frame--device img { height: 100%; object-fit: cover; object-position: center top; }
.case__tags { margin-top: 1.25rem; }
.piece__frame .cover { height: 100%; object-fit: cover; object-position: top center; }

.piece__title {
  font-size: var(--step-2);
  display: inline;
  background-image: linear-gradient(var(--ink), var(--ink));
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--t-base) var(--ease);
}
.piece a:hover .piece__title,
.piece a:focus-visible .piece__title { background-size: 100% 1px; }

.piece__meta {
  margin: 0.7rem 0 0;
  font-size: var(--step--1);
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.piece__note {
  margin: 0.85rem 0 0;
  color: var(--ink-soft);
  max-width: 46ch;
}

/* Honest placeholder tile — no invented imagery for work not yet uploaded. */
.piece__frame--empty {
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(
      -45deg,
      transparent 0 11px,
      var(--rule-soft) 11px 12px
    ),
    var(--paper-sunk);
}
.piece__frame--empty span {
  font-size: var(--step--1);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  background: var(--paper);
  padding: 0.6rem 1rem;
  border: 1px solid var(--rule);
}

/* ------------------------------------------------------------ prose blocks */
.duo {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(1.5rem, 1rem + 3vw, 4rem);
}
@media (min-width: 52rem) {
  .duo { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
}

.lede {
  font-family: var(--display);
  font-weight: var(--display-wght);
  font-variation-settings: var(--display-vf);
  text-transform: var(--display-case);
  font-size: var(--step-2);
  line-height: 1.22;
  letter-spacing: -0.015em;
  margin: 0 0 1.5rem;
  max-width: 26ch;
}

.prose p { margin: 0 0 1.15rem; color: var(--ink-soft); max-width: 62ch; }
.prose p:last-child { margin-bottom: 0; }


/* ------------------------------------------------------------------- links */
.linklist { list-style: none; margin: 0; padding: 0; }
.linklist li { border-top: 1px solid var(--rule); }
.linklist li:last-child { border-bottom: 1px solid var(--rule); }

.linklist a {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1.5rem;
  padding: 1.15rem 0;
  text-decoration: none;
  transition: padding-inline-start var(--t-base) var(--ease),
              color var(--t-quick) var(--ease);
}
.linklist a:hover { padding-inline-start: 0.7rem; }
.linklist .k { font-size: var(--step-1); font-family: var(--display); }
.linklist .v {
  font-size: var(--step--1);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  white-space: nowrap;
}

/* ------------------------------------------------------------------ footer */
.footer {
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem 3.5rem;
  color: var(--ink-faint);
  font-size: var(--step--1);
}
.footer__inner { display: flex; flex-wrap: wrap; gap: 1rem 2rem; justify-content: space-between; }
.footer a { text-decoration: none; }
.footer a:hover { color: var(--ink); }

/* ------------------------------------------------------- case study pages */
.case__header { padding-block: clamp(3rem, 1.5rem + 6vw, 6.5rem) 0; }
.case__title { font-size: var(--step-3); max-width: 18ch; margin-bottom: 1.5rem; }

.factbar {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1.5rem 2rem;
  border-top: 1px solid var(--rule);
  padding-top: 1.5rem;
  margin-top: clamp(2rem, 1rem + 3vw, 3.5rem);
}
.fact dt {
  font-size: var(--step--1);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.4rem;
}
.fact dd { margin: 0; }
.fact a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.fact a:hover { border-bottom-color: var(--ink); }

.plate { margin-block: clamp(2rem, 1rem + 3vw, 4rem); }
.plate img { width: 100%; border: 1px solid var(--rule-soft); }
.plate figcaption {
  margin-top: 0.85rem;
  font-size: var(--step--1);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* Device plates: the renders are transparent (their shadows live in the
   alpha), so they sit on the edition's own ground. Behind them, a faint pool
   of --plate-glow fades out to nothing well inside the frame: depth without a
   rectangle. To switch it off, set --plate-glow: transparent (one token, in
   each edition block at the top of this file). Device videos
   can't be transparent, so they bake the same pool into a day and a night cut
   (assets/js/plate-video.js picks one) and feather their outer edge. */
.plate--device picture {
  display: block;
  /* closest-side: the pool reaches zero exactly at the frame's edges, so
     there is no seam anywhere along the box. */
  background: radial-gradient(closest-side, var(--plate-glow), transparent);
}
/* Optional: add .plate--bleed to a figure whose device runs off the bottom
   edge to dissolve it instead of cropping it hard. Not applied anywhere yet. */
.plate--bleed picture {
  -webkit-mask-image: linear-gradient(to bottom, #000 80%, transparent 99%);
          mask-image: linear-gradient(to bottom, #000 80%, transparent 99%);
}
.plate--device img,
.plate--cut img { border: 0; }
.plate--device video {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 3%, #000 97%, transparent),
                      linear-gradient(to bottom, transparent, #000 3%, #000 97%, transparent);
  -webkit-mask-composite: source-in;
          mask-image: linear-gradient(to right, transparent, #000 3%, #000 97%, transparent),
                      linear-gradient(to bottom, transparent, #000 3%, #000 97%, transparent);
          mask-composite: intersect;
}

.plate--phone img {
  width: auto;
  max-height: 74vh;
  margin-inline: auto;
  border: 1px solid var(--rule-soft);
}
.plate--phone figcaption { text-align: center; }

.plates-duo {
  display: grid; gap: clamp(1.25rem, 0.8rem + 1.6vw, 2.5rem);
  grid-template-columns: 1fr;
}
@media (min-width: 46rem) { .plate--phone img {
  width: auto;
  max-height: 74vh;
  margin-inline: auto;
  border: 1px solid var(--rule-soft);
}
.plate--phone figcaption { text-align: center; }

.plates-duo { grid-template-columns: 1fr 1fr; } }

/* Long full-page captures: scroll inside their own frame, never the page. */
.scrollplate {
  max-height: 78vh;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
}
.scrollplate img { border: 0; }

.backlink {
  display: inline-block;
  margin-top: 0.5rem;
  font-size: var(--step--1);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-decoration: none;
  transition: color var(--t-quick) var(--ease);
}
.backlink:hover { color: var(--ink); }

/* ------------------------------------------------------------------ motion */
/* Entrances: 14px rise + fade on the same expo-out curve, staggered by index
   via --i. Held to one gesture — nothing slides in from the side, nothing
   scales. Disabled wholesale under reduced-motion. */
.rise {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
}
.rise.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--t-base) var(--ease) calc(var(--i, 0) * 70ms),
    transform var(--t-base) var(--ease) calc(var(--i, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  .rise, .rise.is-in { opacity: 1; transform: none; transition: none; }
  .availability::before { animation: none; }
  .piece__frame img, .piece__frame video { transition: none; }
  *, *::before, *::after { transition-duration: 0.01ms !important; }
}

/* No-JS: never leave content invisible. */
.no-js .rise { opacity: 1; transform: none; }


/* --------------------------------------------------------------- the mark
   Machine pixels spelling human gestures: a square of dots that assembles
   into small interface shapes and comes home again. Shapes live in
   assets/js/mark-shapes.js; the motion is at the bottom of site.js. */
.mark { width: 30px; height: 30px; flex: none; overflow: visible; fill: var(--ink); }

/* ----------------------------------------------------------- edition switch
   Styles live with the Workin’ Day & Night switch (.wdn) further down. */
.masthead__inner { align-items: center; }

/* ---------------------------------------------------------------- the lab
   Entries are dated, numbered and typed. A note is writing; an artifact is
   a thing that runs. The feed shows both the same way — what differs is
   what the entry page holds. */

.feed { list-style: none; margin: 0; padding: 0; }

.feed li { border-top: 1px solid var(--rule-soft); }
.feed li:first-child { border-top: 1px solid var(--rule); }
.feed li:last-child { border-bottom: 1px solid var(--rule); }

.feed a {
  display: grid;
  grid-template-columns: 3.25rem 1fr;
  gap: 0 clamp(1rem, 3vw, 2rem);
  padding-block: clamp(1.3rem, 2.5vw, 2rem);
  text-decoration: none;
  transition: padding-inline-start var(--t-quick) var(--ease);
}
.feed a:hover { padding-inline-start: 0.7rem; }

@media (max-width: 34rem) {
  .feed a { grid-template-columns: 1fr; gap: 0.4rem; }
}

.feed__no {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  padding-top: 0.3em;
}

.feed__kind {
  display: inline-flex; align-items: center; gap: 0.5rem;
  font-family: var(--mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.55rem;
}
/* A filled tick means it runs; an outline means it's writing. */
.feed__kind::before {
  content: ""; width: 6px; height: 6px; flex: none;
  background: var(--signal);
}
.feed__kind[data-kind="note"]::before {
  background: none; border: 1px solid var(--ink-faint);
}

.feed__title {
  font-family: var(--display);
  font-weight: var(--display-wght);
  font-variation-settings: var(--display-vf);
  text-transform: var(--display-case);
  letter-spacing: var(--display-trk);
  font-size: var(--step-1);
  line-height: 1.16;
  margin: 0;
  transition: color var(--t-quick) var(--ease);
}
.feed a:hover .feed__title,
.feed a:focus-visible .feed__title { color: var(--signal); }

.feed__note {
  margin: 0.5rem 0 0;
  color: var(--ink-soft);
  font-size: var(--step-0);
  max-width: 58ch;
}

.feed__date { margin-top: 0.6rem; }

/* --------------------------------------------------------------- an entry */

.entry__head { border-bottom: 1px solid var(--rule); padding-bottom: clamp(1.4rem, 3vw, 2.2rem); }
.entry__head h1 { margin: 0.7rem 0 0; max-width: 20ch; }
.entry__standfirst {
  margin: 1.1rem 0 0;
  font-size: var(--step-1);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 54ch;
}
.entry__body { padding-block: clamp(2rem, 5vw, 3.2rem); }
.entry__body h2 {
  font-size: var(--step-1);
  margin: 2.4rem 0 0.9rem;
  max-width: 34ch;
}
.entry__body h2:first-child { margin-top: 0; }
.entry__body p { margin: 0 0 1.15rem; color: var(--ink-soft); max-width: 66ch; }
.entry__body strong { color: var(--ink); font-weight: 500; }
.entry__body code {
  font-family: var(--mono);
  font-size: 0.9em;
  color: var(--ink);
  background: var(--paper-sunk);
  padding: 0.12em 0.36em;
}

/* The living artifact: a thing that actually runs, framed as an exhibit
   rather than an illustration. */
.artifact {
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
  padding: clamp(1rem, 3vw, 1.75rem);
  margin-block: clamp(1.75rem, 4vw, 2.75rem);
}
.artifact__label {
  font-family: var(--mono);
  font-size: var(--step--1);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  padding-bottom: 0.9rem;
  margin-bottom: 1.1rem;
  border-bottom: 1px solid var(--rule-soft);
}
.artifact__caption {
  margin: 1.1rem 0 0;
  font-size: var(--step--1);
  color: var(--ink-faint);
  max-width: 62ch;
}

/* commit list — used by entry 001, reusable for anything git-shaped */
.commits { list-style: none; margin: 0; padding: 0; font-family: var(--mono); font-size: var(--step--1); }
.commits li {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: 0 1rem;
  padding-block: 0.5rem;
  border-top: 1px solid var(--rule-soft);
}
.commits li:first-child { border-top: 0; }
@media (max-width: 34rem) { .commits li { grid-template-columns: 1fr; gap: 0.2rem; } }
.commits .sha { color: var(--signal); }
.commits .msg { color: var(--ink-soft); }
.commits .when { color: var(--ink-faint); }
.commits__status { grid-column: 1 / -1; color: var(--ink-faint); }

/* back link shared by work + lab pages */
.backlink { display: inline-block; margin-top: 1rem; }

/* --------------------------------------------------------------- lab: buzz hive widgets */
.hive {
  display: grid;
  gap: 0.75rem;
}
@media (min-width: 40rem) {
  .hive { grid-template-columns: repeat(2, 1fr); }
}
.hive__node {
  text-align: left;
  border: 1px solid var(--rule);
  background: var(--paper);
  color: var(--ink);
  padding: 0.9rem 1rem;
  cursor: pointer;
  font: inherit;
  transition: border-color var(--t-quick) var(--ease), background var(--t-quick) var(--ease);
}
.hive__node:hover,
.hive__node:focus-visible { border-color: var(--signal); outline: none; }
.hive__node[aria-pressed="true"] {
  border-color: var(--signal);
  background: var(--paper-sunk);
}
.hive__name {
  display: block;
  font-family: var(--display);
  font-weight: var(--display-wght);
  font-size: var(--step-0);
  margin-bottom: 0.25rem;
}
.hive__role {
  display: block;
  font-family: var(--mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.hive__detail {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule-soft);
  color: var(--ink-soft);
  font-size: var(--step-0);
  max-width: 62ch;
  min-height: 4.5rem;
}
.hive__detail strong { color: var(--ink); font-weight: 500; }

.traps { list-style: none; margin: 0; padding: 0; }
.traps li { border-top: 1px solid var(--rule-soft); }
.traps li:first-child { border-top: 0; }
.traps__btn {
  width: 100%;
  text-align: left;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  padding: 0.75rem 0;
  cursor: pointer;
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: 0.75rem;
}
.traps__btn:hover .traps__title,
.traps__btn:focus-visible .traps__title { color: var(--signal); outline: none; }
.traps__n {
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--ink-faint);
  padding-top: 0.25em;
}
.traps__title { font-size: var(--step-0); margin: 0; }
.traps__body {
  display: none;
  grid-column: 2;
  color: var(--ink-soft);
  font-size: var(--step-0);
  max-width: 58ch;
  padding: 0 0 0.9rem;
}
.traps li[data-open="true"] .traps__body { display: block; }

.wall {
  display: grid;
  gap: 1rem;
}
@media (min-width: 40rem) {
  .wall { grid-template-columns: 1fr 1fr; align-items: start; }
}
.wall__panel {
  border: 1px solid var(--rule);
  padding: 1rem;
  min-height: 8rem;
}
.wall__panel[data-state="bad"] { border-color: #c44; }
.wall__panel[data-state="good"] { border-color: var(--signal); }
.wall__channels { list-style: none; margin: 0.75rem 0 0; padding: 0; font-family: var(--mono); font-size: var(--step--1); }
.wall__channels li {
  display: flex; justify-content: space-between; gap: 1rem;
  padding-block: 0.35rem;
  border-top: 1px solid var(--rule-soft);
}
.wall__channels li:first-child { border-top: 0; }
.wall__toggle {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: var(--mono);
  font-size: var(--step--1);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  user-select: none;
}
.wall__toggle input { accent-color: var(--signal); }
.wall__verdict { margin: 0.75rem 0 0; color: var(--ink-soft); max-width: 48ch; font-size: var(--step-0); }

/* --------------------------------------------------- "All work →" links
   Same hairline wipe as the nav, same gap below the word. The line draws
   under the word only; the arrow starts tucked in and steps out just as the
   line arrives, so the two read as one motion. Leaving reverses with no
   delay — the arrow shouldn't hang around after you've moved on. */
.bl {
  display: inline-flex; align-items: baseline; gap: 0.3em;
  text-decoration: none;
  color: var(--ink-faint);
  transition: color var(--t-quick) var(--ease);
}
.bl__t { position: relative; padding-block: 0.2rem; }
.bl__t::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: var(--ink);
  transform: scaleX(0); transform-origin: left;
  transition: transform var(--t-base) var(--ease);
}
.bl__a {
  display: inline-block;
  transition: transform 420ms var(--ease);
}
.bl:hover, .bl:focus-visible { color: var(--ink); }
.bl:hover .bl__t::after, .bl:focus-visible .bl__t::after { transform: scaleX(1); }
.bl:hover .bl__a, .bl:focus-visible .bl__a {
  transform: translateX(0.32em);
  transition-delay: 160ms;
}
/* Back links: the arrow leads, so the line draws from the right toward it
   and the arrow steps out to the left. */
.bl--back .bl__t::after { transform-origin: right; }
.bl--back:hover .bl__a, .bl--back:focus-visible .bl__a { transform: translateX(-0.32em); }
.backlink .bl, a.backlink.bl { text-decoration: none; }

/* ------------------------------------------------------------------ tags
   Tags are links, never decoration: each one opens tags.html filtered to it
   (Work + Lab together). Quiet mono so they sit under the meta line without
   competing; same left-to-right wipe as everything else on hover. */
.tags {
  list-style: none; margin: 0.8rem 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 0.4rem;
}
.tags a {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: none;
  line-height: 1;
  padding: 0.18rem 0.32rem 0.16rem;
  border: 1px solid var(--rule);
  border-radius: 2px;
  transition: color var(--t-quick) var(--ease), border-color var(--t-quick) var(--ease);
}
.tags a:hover, .tags a:focus-visible, .tags a[aria-current] { color: var(--ink); border-color: var(--ink); }
.tags__n { color: var(--ink-faint); margin-left: 0.25em; }

/* In the Lab feed the tag row lines up with the title column. */
.feed li:has(> .tags) > a { padding-bottom: 0.7rem; }
.feed li > .tags {
  margin: 0 0 clamp(1.3rem, 2.5vw, 2rem) calc(3.25rem + clamp(1rem, 3vw, 2rem));
}
@media (max-width: 34rem) { .feed li > .tags { margin-left: 0; } }

/* tags.html */
.shelf--tight { padding-top: clamp(2rem, 1.5rem + 2vw, 3rem); }
.tags-all__label { margin: clamp(2.5rem, 2rem + 2vw, 4rem) 0 1rem; }
.feed__kind[data-kind="work"]::before { background: var(--ink-faint); }

/* ------------------------------------------------------- micro details
   Small mono notes that report something true: section number, a live
   count, a place, the time there. Same faint ink as the labels, so they
   read as the label's second half rather than new information. */
.label--row { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.label__no {
  font-family: var(--mono);
  letter-spacing: 0.04em;
  margin-right: 0.9em;
  color: var(--ink-faint);
  opacity: 0.7;
}
.label__aside {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: none;
  font-weight: 400;
  color: var(--ink-faint);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 34rem) { .label__aside { display: none; } }

/* Registration marks where each section rule meets the content column —
   they mark the grid the page is actually set on. */
.shelf.rule-top { position: relative; }
.shelf.rule-top::before,
.shelf.rule-top::after {
  content: ""; position: absolute; top: -5px;
  width: 9px; height: 9px;
  background:
    linear-gradient(var(--ink-faint), var(--ink-faint)) center / 1px 100% no-repeat,
    linear-gradient(var(--ink-faint), var(--ink-faint)) center / 100% 1px no-repeat;
  opacity: 0.55;
  pointer-events: none;
}
.shelf.rule-top::before { left: calc(var(--gutter) - 4px); }
.shelf.rule-top::after  { right: calc(var(--gutter) - 4px); }

/* -------------------------------------- edition switch: Workin’ Day & Night
   The label is the switch: "Workin’ Day & Night", where Day and Night are
   the two buttons. One hairline frame sits on the pressed edition and slides
   to the other on a flick. Fallback variant (.wdn--icons): thin sun / moon.
   Logic in edition.js; the moonwalker (moonwalker.js) dances in the slot
   to its right, on the far right edge of the masthead. */
.wdn {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.3em;
  font-family: var(--mono); font-size: 10px; line-height: 1;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-faint); white-space: nowrap;
}
.wdn__w { padding-block: 0.5em; }
.wdn { gap: 0.95em; }
.wdn__b {
  position: relative; z-index: 1;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  color: var(--ink-faint); background: none; border: 0; margin: 0;
  padding: 0.5em 0; cursor: pointer;
  transition: color var(--t-quick) var(--ease);
}
.wdn__b:hover { color: var(--ink-soft); }
.wdn__b[aria-pressed="true"] { color: var(--ink); cursor: default; }
.wdn__frame {
  position: absolute; left: 0; top: 0; width: 0; height: 0;
  border: 1px solid var(--rule); pointer-events: none;
  transition: border-color var(--t-quick) var(--ease);
}
.wdn:hover .wdn__frame { border-color: var(--ink-faint); }
/* The pressed word carries its own hairline box; the boxes crossfade on a
   flick. (Replaces the JS-measured sliding frame, which could land off.) */
.wdn__frame { display: none; }
/* Words keep one even gap whatever is pressed: the box hangs outside the
   word into that gap instead of padding it, and the hit area does the same. */
.wdn__b::before { content: ""; position: absolute; inset: 0 -0.45em; }
.wdn__b::after {
  content: ""; position: absolute; inset: 0 -0.45em;
  border: 1px solid var(--rule); opacity: 0;
  transition: opacity var(--t-base) var(--ease), border-color var(--t-quick) var(--ease);
}
.wdn__b[aria-pressed="true"]::after { opacity: 1; }
.wdn:hover .wdn__b::after { border-color: var(--ink-faint); }
/* Night: faint ink is too dim to read at 10px — step everything up one. */
:root[data-theme="dark"] .wdn, :root[data-theme="dark"] .wdn__b { color: var(--ink-soft); }
:root[data-theme="dark"] .wdn__b[aria-pressed="true"] { color: var(--ink); }
:root[data-theme="dark"] .wdn__b::after { border-color: var(--ink-faint); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wdn, :root:not([data-theme="light"]) .wdn__b { color: var(--ink-soft); }
  :root:not([data-theme="light"]) .wdn__b[aria-pressed="true"] { color: var(--ink); }
  :root:not([data-theme="light"]) .wdn__b::after { border-color: var(--ink-faint); }
}
.wdn.is-ready .wdn__frame {
  transition: transform var(--t-base) var(--ease), width var(--t-base) var(--ease),
              border-color var(--t-quick) var(--ease);
}

/* Fallback: thin sun / moon. Same frame, same behaviour. */
.wdn--icons { gap: 0.15rem; }
.wdn--icons .wdn__b { display: inline-flex; padding: 0.3rem; }
.wdn--icons svg {
  width: 14px; height: 14px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1; stroke-linecap: square;
}

/* Right end of the masthead: the switch, then the moonwalker's slot on the
   far right edge. The slot mirrors the mark on the far left (same 30px box,
   same 0.6rem gap the mark keeps from the name) so the ends pair up. */
.masthead__end { display: flex; align-items: center; gap: 0.6rem; }
.mw-slot { position: relative; flex: none; width: 36px; height: 34px; }

/* The stage hangs off the slot's right edge by a few px (room for the
   sparkle) and centres his 36px height on the slot's 30px. */
.mw {
  position: absolute; right: 0; top: 0;
  pointer-events: none; image-rendering: pixelated;
  visibility: hidden;
}
.mw.is-on { visibility: visible; }

/* His palette, as real colour properties so the tokens resolve (read by
   moonwalker.js; no border is drawn). Day: ink body, pale socks, white
   glove walled in by body pixels. Night: the body steps down to ink-soft so
   the white glove is still the one bright pixel. */
.mw, .mw-frames {
  border: 0 solid;
  color: var(--ink);                                                          /* body  */
  border-top-color: color-mix(in srgb, var(--ink-faint) 45%, var(--paper));  /* socks */
  border-bottom-color: #fff;                                                  /* glove */
}
:root[data-theme="dark"] .mw, :root[data-theme="dark"] .mw-frames {
  color: var(--ink-soft);
  border-top-color: color-mix(in srgb, var(--ink) 55%, var(--ink-soft));
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mw, :root:not([data-theme="light"]) .mw-frames {
    color: var(--ink-soft);
    border-top-color: color-mix(in srgb, var(--ink) 55%, var(--ink-soft));
  }
}
@media (prefers-reduced-motion: reduce) { .mw, .mw-slot { display: none; } }

/* ------------------------------------------------------------ bottom blur
   Content goes slightly out of focus as it nears the bottom edge. Two
   stacked layers make it progressive: a soft one over the whole band, a
   stronger one only at the very edge, each faded in by a mask. It steps
   aside at the end of the page (site.js) so the footer reads sharp. */
body::before,
body::after {
  content: ""; position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to top, #000 20%, transparent);
          mask-image: linear-gradient(to top, #000 20%, transparent);
  transition: opacity var(--t-base) var(--ease);
}
body::before {
  height: 260px; /* tuned by Jamie 2026-10-06 */
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
}
body::after {
  height: 125px;
  -webkit-backdrop-filter: blur(3px);
          backdrop-filter: blur(3px);
}
.at-end body::before,
.at-end body::after { opacity: 0; }
@media print { body::before, body::after { display: none; } }


/* Case pages, this release: pieces run full width in one column (the
   two-up layout returns with the long-form copy in the next version). */
.case__header ~ section .plates-duo { grid-template-columns: 1fr !important; }
