/* ==========================================================================
   rvmishra.in — design system
   Editorial / broadsheet. Hairline rules, asymmetric measures, real type.
   No framework, no build step, no gradients, no glow, no rounded cards.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* Paper & ink — warm, printed, never #fff on #000 */
  --paper:        #f4f1ea;
  --paper-sunk:   #eae5da;
  --paper-raised: #fbf9f4;
  --ink:          #15120e;
  --ink-2:        #4b443a;
  --ink-3:        #776e60;
  --rule:         rgba(21, 18, 14, 0.16);
  --rule-strong:  rgba(21, 18, 14, 0.42);
  --rule-hair:    rgba(21, 18, 14, 0.08);

  /* One accent. Used for marks, not for decoration. */
  --flag:         #a5301a;
  --flag-ink:     #fdf6f3;

  /* Monochrome scale for the gallery — deliberately separate from paper/ink */
  --mono-000: #ffffff;
  --mono-800: #262626;
  --mono-900: #171717;
  --mono-950: #0a0a0a;

  /* Type */
  --font-display: "Fraunces", "Times New Roman", serif;
  --font-body:    "Newsreader", Georgia, "Times New Roman", serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --step--2: clamp(0.68rem, 0.66rem + 0.10vw, 0.74rem);
  --step--1: clamp(0.80rem, 0.77rem + 0.15vw, 0.88rem);
  --step-0:  clamp(1.00rem, 0.96rem + 0.20vw, 1.13rem);
  --step-1:  clamp(1.25rem, 1.17rem + 0.40vw, 1.50rem);
  --step-2:  clamp(1.60rem, 1.44rem + 0.80vw, 2.20rem);
  --step-3:  clamp(2.10rem, 1.75rem + 1.75vw, 3.40rem);
  --step-4:  clamp(2.80rem, 2.05rem + 3.75vw, 5.60rem);
  --step-5:  clamp(3.40rem, 1.90rem + 7.50vw, 9.00rem);

  /* Rhythm */
  --gutter: clamp(1.15rem, 4vw, 3.5rem);
  --measure: 66ch;
  --band: clamp(4.5rem, 9vw, 9.5rem);
  --band-tight: clamp(2.75rem, 5vw, 5rem);
  --max: 1560px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 480ms;

  /* Chip/frame borders. The gallery overrides these with the strict
     monochrome scale; everywhere else they follow the paper palette. */
  --g-line: var(--rule-strong);
  --g-line-soft: var(--rule);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:        #0c0b0a;
    --paper-sunk:   #080807;
    --paper-raised: #151311;
    --ink:          #efeae0;
    --ink-2:        #b0a89a;
    --ink-3:        #7d7466;
    --rule:         rgba(239, 234, 224, 0.18);
    --rule-strong:  rgba(239, 234, 224, 0.45);
    --rule-hair:    rgba(239, 234, 224, 0.09);
    --flag:         #e0644a;
    --flag-ink:     #17100d;
  }
}

:root[data-theme="dark"] {
  --paper:        #0c0b0a;
  --paper-sunk:   #080807;
  --paper-raised: #151311;
  --ink:          #efeae0;
  --ink-2:        #b0a89a;
  --ink-3:        #7d7466;
  --rule:         rgba(239, 234, 224, 0.18);
  --rule-strong:  rgba(239, 234, 224, 0.45);
  --rule-hair:    rgba(239, 234, 224, 0.09);
  --flag:         #e0644a;
  --flag-ink:     #17100d;
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  font-optical-sizing: auto;
  line-height: 1.62;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

img, svg, video { display: block; max-width: 100%; height: auto; }
img { background: var(--paper-sunk); }

a { color: inherit; }

h1, h2, h3, h4, h5 {
  font-family: var(--font-display);
  font-weight: 460;
  font-variation-settings: "SOFT" 0, "WONK" 1;
  line-height: 0.98;
  letter-spacing: -0.022em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0 0 1.15em; text-wrap: pretty; }

hr {
  border: 0;
  border-top: 1px solid var(--rule);
  margin: var(--band-tight) 0;
}

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

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

button { font: inherit; color: inherit; }

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.shell--narrow { max-width: 1080px; }
.shell--text   { max-width: 760px; }

.band { padding-block: var(--band); }
.band--tight { padding-block: var(--band-tight); }
.band--ruled { border-top: 1px solid var(--rule); }
.band--sunk { background: var(--paper-sunk); }

.stack > * + * { margin-top: var(--stack, 1.5rem); }
.prose { max-width: var(--measure); }

.grid { display: grid; gap: var(--gutter); }
.grid--12 { grid-template-columns: repeat(12, 1fr); }

@media (max-width: 860px) {
  .grid--12 { grid-template-columns: repeat(6, 1fr); }
}

/* --------------------------------------------------------------------------
   4. Type utilities
   -------------------------------------------------------------------------- */

.display {
  font-family: var(--font-display);
  font-variation-settings: "SOFT" 0, "WONK" 1;
  line-height: 0.92;
  letter-spacing: -0.03em;
}

.d1 { font-size: var(--step-5); font-weight: 500; }
.d2 { font-size: var(--step-4); font-weight: 500; }
.d3 { font-size: var(--step-3); font-weight: 480; }
.d4 { font-size: var(--step-2); font-weight: 500; }

.lede {
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--ink-2);
  font-weight: 300;
}

.mono {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  letter-spacing: 0.04em;
  font-weight: 500;
}

.mono--caps { text-transform: uppercase; letter-spacing: 0.12em; font-size: var(--step--2); }
.muted { color: var(--ink-3); }
.ink-2 { color: var(--ink-2); }

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-3);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.9rem;
}

.eyebrow::after {
  content: "";
  flex: 1 1 2rem;
  height: 1px;
  background: var(--rule);
  transform: translateY(-0.25em);
}

.dropcap::first-letter {
  font-family: var(--font-display);
  font-size: 3.6em;
  font-weight: 500;
  line-height: 0.78;
  float: left;
  padding: 0.06em 0.12em 0 0;
  color: var(--ink);
}

.underline-link {
  text-decoration: none;
  background-image: linear-gradient(var(--flag), var(--flag));
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size var(--dur) var(--ease);
  padding-bottom: 0.08em;
}

.underline-link:hover,
.underline-link:focus-visible { background-size: 100% 1px; }

.prose-body {
  font-size: clamp(1.05rem, 1rem + 0.35vw, 1.28rem);
  line-height: 1.72;
}

.prose-body h2 { font-size: var(--step-2); margin: 2.4em 0 0.6em; }
.prose-body h3 { font-size: var(--step-1); margin: 2em 0 0.5em; }
.prose-body h4 { font-size: var(--step-0); margin: 1.8em 0 0.4em; font-family: var(--font-mono);
  text-transform: uppercase; letter-spacing: 0.1em; }
.prose-body a { color: var(--flag); text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
.prose-body img { margin: 2.2rem 0; border: 1px solid var(--rule-strong); }
.prose-body ul, .prose-body ol { padding-left: 1.3em; margin: 0 0 1.3em; }
.prose-body li { margin-bottom: 0.4em; }
.prose-body code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  background: var(--paper-sunk);
  padding: 0.14em 0.36em;
  border: 1px solid var(--rule-hair);
}
.prose-body pre {
  font-family: var(--font-mono);
  font-size: 0.82em;
  line-height: 1.6;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  padding: 1.15rem 1.25rem;
  overflow-x: auto;
  margin: 1.8rem 0;
}
.prose-body pre code { background: none; border: 0; padding: 0; }
.prose-body blockquote {
  margin: 2rem 0;
  padding-left: 1.4rem;
  border-left: 2px solid var(--flag);
  font-style: italic;
  color: var(--ink-2);
}
.prose-body table { width: 100%; border-collapse: collapse; margin: 1.8rem 0; font-size: 0.92em; }
.prose-body th, .prose-body td { border-bottom: 1px solid var(--rule); padding: 0.6rem 0.8rem;
  text-align: left; }
.prose-body th { font-family: var(--font-mono); font-size: 0.8em; text-transform: uppercase;
  letter-spacing: 0.08em; }

/* --------------------------------------------------------------------------
   5. Masthead / navigation
   -------------------------------------------------------------------------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--rule);
  transition: transform 340ms var(--ease), border-color 200ms linear;
}

.masthead[data-hidden="true"] { transform: translateY(-102%); }

.masthead__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  min-height: 62px;
}

.wordmark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  text-decoration: none;
  font-family: var(--font-display);
  font-size: 1.42rem;
  font-weight: 600;
  letter-spacing: -0.03em;
  white-space: nowrap;
}

.wordmark__dot {
  width: 7px; height: 7px;
  background: var(--flag);
  display: inline-block;
  transform: translateY(-0.1em);
}

.wordmark__meta {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-3);
}

.nav { display: flex; align-items: center; gap: clamp(0.9rem, 1.7vw, 2rem); }

.nav__link {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  text-decoration: none;
  color: var(--ink-2);
  padding: 0.55rem 0;
  position: relative;
  transition: color 200ms var(--ease);
}

.nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0.25rem;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 320ms var(--ease);
}

.nav__link:hover, .nav__link:focus-visible { color: var(--ink); }
.nav__link:hover::after, .nav__link:focus-visible::after { transform: scaleX(1); }
.nav__link[aria-current="page"] { color: var(--ink); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); background: var(--flag); }

.masthead__tools { display: flex; align-items: center; gap: 0.4rem; }

.icon-btn {
  width: 40px; height: 40px;
  display: grid; place-items: center;
  background: none;
  border: 1px solid transparent;
  cursor: pointer;
  color: var(--ink-2);
  transition: color 200ms, border-color 200ms;
}

.icon-btn:hover { color: var(--ink); border-color: var(--rule); }
.icon-btn svg { width: 17px; height: 17px; stroke: currentColor; fill: none; stroke-width: 1.5; }

.nav-toggle { display: none; }

/* Mobile drawer.
   NOT position:fixed. The masthead uses backdrop-filter and a translateY
   hide-on-scroll transform, and either of those makes it the containing block
   for fixed descendants -- which pinned the old drawer inside the 62px bar and
   turned it into a thin scrolling strip. The drawer is now an in-flow row of
   the masthead's flex container, so the bar simply grows to hold it. */
@media (max-width: 940px) {
  .nav-toggle { display: grid; }

  .masthead__inner { flex-wrap: wrap; }

  .nav {
    order: 3;
    flex-basis: 100%;
    width: 100%;
    display: none;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    max-height: min(70vh, calc(100dvh - 62px));
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    padding-bottom: max(1rem, env(safe-area-inset-bottom));
    border-top: 1px solid var(--rule);
    animation: drawer-in 260ms var(--ease-out);
  }

  .nav[data-open="true"] { display: flex; }

  @keyframes drawer-in {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }

  .nav__link {
    font-family: var(--font-display);
    font-size: clamp(1.5rem, 6.5vw, 2.2rem);
    text-transform: none;
    letter-spacing: -0.025em;
    line-height: 1.1;
    padding: 0.55rem 0;
    min-height: 52px;
    display: flex;
    align-items: center;
    width: 100%;
    border-bottom: 1px solid var(--rule-hair);
    color: var(--ink);
  }
  .nav__link:last-child { border-bottom: 0; }
  .nav__link::after { display: none; }
  .nav__link[aria-current="page"] { color: var(--flag); }
  .nav__link[aria-current="page"]::before {
    content: "";
    width: 6px; height: 6px;
    background: var(--flag);
    margin-right: 0.7rem;
    flex: 0 0 auto;
  }

  /* While the drawer is open the bar must not slide away underneath it. */
  .masthead:has(.nav[data-open="true"]) { transform: none !important; }
}

/* --------------------------------------------------------------------------
   6. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.65rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  text-decoration: none;
  padding: 0.95rem 1.5rem;
  border: 1px solid var(--ink);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  min-height: 46px;
  transition: color 260ms var(--ease), border-color 260ms var(--ease);
}

.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform 340ms var(--ease-out);
  z-index: -1;
}

.btn > * { position: relative; z-index: 1; }
.btn:hover, .btn:focus-visible { color: var(--paper); }
.btn:hover::before, .btn:focus-visible::before { transform: scaleY(1); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:disabled:hover { color: var(--ink); }
.btn:disabled:hover::before { transform: scaleY(0); }

.btn--flag { border-color: var(--flag); color: var(--flag); }
.btn--flag::before { background: var(--flag); }
.btn--flag:hover, .btn--flag:focus-visible { color: var(--flag-ink); }

.btn--ghost { border-color: var(--rule); color: var(--ink-2); }
.btn--sm { padding: 0.6rem 1rem; font-size: 0.68rem; min-height: 38px; }

.btn__arrow { transition: transform 340ms var(--ease-out); }
.btn:hover .btn__arrow { transform: translateX(4px); }

/* --------------------------------------------------------------------------
   7. Reveal + parallax
   -------------------------------------------------------------------------- */

/* Everything below is scoped to [data-js], set by theme-init.js before first
   paint. Without it these rules do not match and nothing is ever hidden --
   a broken script must degrade to a plain page, not a blank one. */

[data-js] [data-reveal] {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity 760ms var(--ease-out), transform 900ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

[data-js] [data-reveal].is-in { opacity: 1; transform: none; }
[data-js] [data-reveal="left"]  { transform: translate3d(-26px, 0, 0); }
[data-js] [data-reveal="right"] { transform: translate3d(26px, 0, 0); }

/* Both ends of the wipe are percentages on purpose. Going from
   `inset(0 0 100% 0)` to `inset(0 0 0 0)` mixes a percentage with a length,
   and browsers that will not interpolate the two treat the transition as
   discrete or drop it outright -- leaving the figure clipped to nothing with
   no way back. `0%` interpolates everywhere. */
[data-js] [data-reveal="mask"] {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
}
[data-js] [data-reveal="mask"].is-in {
  clip-path: inset(0 0 0% 0);
  transition: clip-path 1100ms var(--ease-out);
}

/* No `will-change` here. It used to sit on [data-reveal], which promoted every
   revealed block on the page to its own compositing layer -- dozens of them on
   the home page. Past a driver's layer budget the surplus layers render blank,
   and a blank layer under a clip-path is indistinguishable from missing
   content. The transitions composite fine without the hint. */

[data-parallax] { transform: translateY(var(--parallax-y, 0px)); }

/* overflow-wrap: a heading that is one long unbroken word (Instrumentation,
   Paperwork, Commonplace) can be wider than its column at in-between viewport
   widths. Ordinary text wraps around that; a single word does not, so it
   overflows the box -- and this box's overflow is hidden on purpose, for the
   slide-up reveal. Unbreakable overflow here does not show as a scrollbar, it
   shows as silently missing letters. Letting the word break is the smaller
   defect. */
.rise-line { display: block; overflow: hidden; overflow-wrap: anywhere; }
[data-js] .rise-line > span {
  display: block;
  transform: translateY(105%);
  transition: transform 1000ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-js] .is-in .rise-line > span,
[data-js] .rise-line.is-in > span { transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
  .rise-line > span { transform: none !important; }
  [data-parallax] { transform: none !important; }
}

/* --------------------------------------------------------------------------
   8. Home — nameplate
   -------------------------------------------------------------------------- */

.plate { position: relative; padding-block: clamp(2.5rem, 7vw, 6rem) var(--band-tight); }

.plate__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr);
  gap: var(--gutter);
  align-items: end;
}

.plate__name {
  font-size: var(--step-5);
  font-weight: 500;
  font-variation-settings: "SOFT" 0, "WONK" 1;
  line-height: 0.86;
  letter-spacing: -0.045em;
  margin: 0;
}

.plate__name em {
  font-style: italic;
  font-family: var(--font-body);
  font-weight: 300;
  letter-spacing: -0.02em;
}

.plate__rule {
  height: 1px;
  background: var(--ink);
  margin-block: clamp(1.2rem, 3vw, 2.2rem);
  transform-origin: left;
  transform: scaleX(0);
  transition: transform 1200ms var(--ease-out) 240ms;
}
.plate__rule.is-in { transform: scaleX(1); }

.plate__strap {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.6rem;
  align-items: baseline;
}

.plate__portrait {
  position: relative;
  border: 1px solid var(--ink);
  overflow: hidden;
  aspect-ratio: 4 / 5;
}

/* 2D transform, no `will-change`: this image is a promoted layer's worth of
   trouble inside a clip-path parent (Fig. 01), which is precisely the pairing
   that renders blank on some mobile GPUs. */
.plate__portrait img {
  width: 100%; height: 100%;
  object-fit: cover;
  transform: scale(1.14) translateY(var(--parallax-y, 0px));
}

/* Opaque, never translucent: a caption over a photograph has to be readable on
   every photograph, not just the dark ones. */
.plate__caption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 0.7rem 0.9rem;
  background: var(--paper);
  color: var(--ink-2);
  border-top: 1px solid var(--ink);
}

.ticker {
  display: flex;
  flex-wrap: wrap;
  gap: 0 clamp(1.2rem, 3vw, 3rem);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding-block: 0.85rem;
  margin-top: clamp(2rem, 5vw, 4rem);
}

.ticker__cell {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  padding-block: 0.2rem;
}

.ticker__value {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

@media (max-width: 900px) {
  .plate__grid { grid-template-columns: 1fr; }
  .plate__portrait { max-width: 340px; aspect-ratio: 1 / 1; }
}

/* Below ~560px the ticker's five cells wrap into five ragged lines. Two tidy
   columns read better and take less height. */
@media (max-width: 560px) {
  .ticker {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.55rem 1rem;
  }
  .ticker__cell { flex-wrap: wrap; gap: 0.3rem; }
  .ticker__value { font-size: 1.15rem; }
}

/* --------------------------------------------------------------------------
   9. Index list (work, writing) — no cards
   -------------------------------------------------------------------------- */

.index-list { border-top: 1px solid var(--ink); }

.index-row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) minmax(0, 15rem) 3rem;
  gap: clamp(1rem, 2.5vw, 2.5rem);
  align-items: baseline;
  padding-block: clamp(1.6rem, 3vw, 2.6rem);
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  position: relative;
  transition: background-color 400ms var(--ease);
}

.index-row:hover { background: var(--paper-raised); }
.index-row:hover .index-row__title { color: var(--flag); }

.index-row__num {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--ink-3);
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
}

.index-row__title {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.2vw, 2.75rem);
  font-weight: 460;
  line-height: 1.02;
  letter-spacing: -0.028em;
  transition: color 300ms var(--ease);
  margin: 0;
}

.index-row__sub { color: var(--ink-3); margin: 0.55rem 0 0; font-size: var(--step--1); }

.index-row__meta {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--ink-3);
  letter-spacing: 0.07em;
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.75rem;
}

.index-row__arrow {
  justify-self: end;
  color: var(--ink-3);
  transition: transform 400ms var(--ease-out), color 300ms;
}

.index-row:hover .index-row__arrow { transform: translate(5px, -5px); color: var(--flag); }

/* Hover preview — the image follows the pointer beside the link */
.hover-preview {
  position: fixed;
  top: 0; left: 0;
  width: clamp(180px, 20vw, 300px);
  aspect-ratio: 4 / 3;
  pointer-events: none;
  z-index: 40;
  opacity: 0;
  border: 1px solid var(--ink);
  overflow: hidden;
  transform: translate3d(var(--px, 0), var(--py, 0), 0) scale(0.92);
  transition: opacity 260ms var(--ease), transform 420ms var(--ease-out);
}

.hover-preview[data-visible="true"] { opacity: 1; transform: translate3d(var(--px, 0), var(--py, 0), 0) scale(1); }
.hover-preview img { width: 100%; height: 100%; object-fit: cover; }

@media (hover: none), (max-width: 940px) {
  .hover-preview { display: none; }
}

@media (max-width: 860px) {
  .index-row {
    grid-template-columns: 2.6rem minmax(0, 1fr) 1.5rem;
  }
  .index-row__meta { grid-column: 2 / -1; margin-top: 0.7rem; }
}

/* --------------------------------------------------------------------------
   10. Gallery — strict monochrome, hard borders, zero radius, no shadow
   -------------------------------------------------------------------------- */

/* Strict monochrome: neutral-800 hairlines, no radius, no shadow, in both
   themes. The photographs themselves stay in full colour -- the chrome is what
   goes black and white. */
body.gallery-page {
  --g-line: #262626;
  --g-line-soft: rgba(38, 38, 38, 0.42);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body.gallery-page {
    --g-line: #3f3f3f;
    --g-line-soft: rgba(212, 212, 212, 0.24);
  }
}

:root[data-theme="dark"] body.gallery-page {
  --g-line: #3f3f3f;
  --g-line-soft: rgba(212, 212, 212, 0.24);
}

.filter-rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding-block: 1rem;
  border-top: 1px solid var(--g-line);
  border-bottom: 1px solid var(--g-line);
}

.chip {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 0.5rem 0.85rem;
  border: 1px solid var(--g-line-soft);
  text-decoration: none;
  color: var(--ink-2);
  background: none;
  cursor: pointer;
  min-height: 38px;
  display: inline-flex;
  align-items: center;
  transition: background-color 220ms, color 220ms, border-color 220ms;
}

.chip:hover { border-color: var(--g-line); color: var(--ink); }
.chip[aria-pressed="true"], .chip.is-on {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
}

.chip__count { opacity: 0.55; margin-left: 0.45em; }

/* Justified rows — the contact-sheet layout.

   A fixed column grid cannot hold portrait and landscape frames together: one
   orientation always ends up stunted or towering. Here each frame's flex-grow
   AND flex-basis are proportional to its own aspect ratio, so within any row
   the widths land in proportion to the ratios and every frame in that row
   resolves to the *same height*. Portraits come out narrow, landscapes wide,
   the row edges stay flush, and nothing is cropped to fit a cell. */

.plates {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--plate-gap, clamp(0.85rem, 1.6vw, 1.5rem));
  --plate-row: clamp(13rem, 21vw, 20rem);
}

.plate-item {
  --ar: 1.5;
  flex: var(--ar) 1 calc(var(--ar) * var(--plate-row));
  min-width: 0;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  border: 1px solid var(--g-line);
  border-radius: 0;
  box-shadow: none;
  background: var(--paper);
  position: relative;
}

/* A periodic full-width plate, for rhythm. Landscape only — a full-width
   portrait would be four screens tall. */
.plate-item--wide { flex: 1 1 100%; }
.plate-item--wide .plate-item__frame { max-height: 72vh; }

.plate-item__frame {
  display: block;          /* a <span> is inline, and aspect-ratio needs a box */
  position: relative;
  overflow: hidden;
  aspect-ratio: var(--ar);
  background: var(--paper-sunk);
  border-bottom: 1px solid var(--g-line);
}

.plate-item__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease-out);
}

.plate-item:hover .plate-item__frame img { transform: scale(1.03); }

/* Fillers absorb the leftover space on the final row so a lone photograph is
   not stretched across the whole viewport. Zero height, no border, invisible. */
.plate-filler {
  flex: 1.5 1 calc(1.5 * var(--plate-row));
  height: 0;
  border: 0;
  padding: 0;
  margin: 0;
}

.plate-item__meta {
  padding: 0.7rem 0.85rem 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
  min-width: 0;
}

.plate-item__caption {
  font-family: var(--font-body);
  font-size: 0.94rem;
  letter-spacing: 0;
  color: var(--ink);
  line-height: 1.35;
  overflow-wrap: anywhere;
}

/* The plate number lives in the caption block, not on top of the photograph.
   Nothing is printed over an image anywhere in the gallery. */
.plate-item__index {
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
  letter-spacing: 0.12em;
}

@media (max-width: 900px) {
  .plates { --plate-row: clamp(11rem, 26vw, 15rem); }
}

@media (max-width: 640px) {
  /* One frame per row below 640: at phone widths a justified pair leaves each
     photograph too small to read. */
  .plates { --plate-gap: 0.85rem; }
  .plate-item, .plate-item--wide { flex: 1 1 100%; }
  .plate-item--wide .plate-item__frame { max-height: none; }
  .plate-item__meta { padding: 0.6rem 0.7rem 0.7rem; }
}

/* Horizontal photo band on the home page */
.strip {
  display: flex;
  gap: clamp(0.75rem, 1.6vw, 1.6rem);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: 1rem;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.strip::-webkit-scrollbar { height: 3px; }
.strip::-webkit-scrollbar-thumb { background: var(--rule-strong); }

.strip__item {
  flex: 0 0 auto;
  width: clamp(210px, 26vw, 380px);
  scroll-snap-align: start;
  border: 1px solid var(--rule-strong);
  text-decoration: none;
}

.strip__item img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.strip__item figcaption {
  padding: 0.55rem 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.63rem;
  letter-spacing: 0.08em;
  color: var(--ink-3);
  border-top: 1px solid var(--rule);
}

/* --------------------------------------------------------------------------
   11. Lightbox
   -------------------------------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: #08080a;
  display: grid;
  /* minmax(0, 1fr), not 1fr. A bare `1fr` track keeps an automatic minimum of
     its content, so a tall photograph grew the row past the viewport and shoved
     the caption bar off the bottom — which is why the text appeared to sit on
     top of the image. */
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100dvh;
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms var(--ease);
}

.lightbox[data-open="true"] { opacity: 1; pointer-events: auto; }

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem var(--gutter);
  padding-top: max(0.7rem, env(safe-area-inset-top));
  background: #08080a;
  border-bottom: 1px solid var(--mono-800);
  color: #e5e5e5;
  z-index: 2;
}

.lightbox__stage {
  position: relative;
  display: grid;
  /* The explicit tracks are the whole fix, and they are not decoration: a
     plain `display:grid` gets an *implicit* track, which is content-sized
     ("auto"), not a share of the container. The image's `max-height: 100%`
     then had no definite height to resolve against, so it was ignored, the
     photo laid out at its intrinsic height, and the overflow:hidden below
     silently ate whatever did not fit. Measured on a 810x1080 portrait in a
     1440x900 window: the stage is 693px tall and the photo rendered 1082px --
     nearly 400px of it simply cropped away. Removing the explicit tracks
     reproduces it instantly (1790px tall). `minmax(0, 1fr)` rather than a
     bare `1fr` for the same reason as .lightbox above: a bare `1fr` keeps an
     automatic minimum equal to its content. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  padding: clamp(0.5rem, 2vw, 2rem);
  min-height: 0;
  overflow: hidden;
  touch-action: none; /* gestures here are ours: pinch, pan, double-tap */
}

.lightbox__stage img {
  /* 100% + object-fit rather than max-width/max-height: it makes the <img>
     box exactly the stage's content box, and the pan clamp in initLightbox
     measures the stage to decide how far a zoomed photo may travel. With
     max-* the box shrink-wraps the photo (478px wide for the portrait above)
     while the clamp still reasons about the 1343px stage, so panning would
     let a zoomed photo slide clean off the screen. object-fit letterboxes the
     pixels inside the full-size box, so the two always agree. */
  width: 100%;
  height: 100%;
  object-fit: contain;
  border: 1px solid var(--mono-800);
  cursor: zoom-in;
  touch-action: none;
  /* Dragging this image is our pan gesture, never the browser's drag-and-drop
     or a text selection. -webkit-user-drag covers Safari, which honours
     neither draggable="false" nor a cancelled dragstart reliably; user-select
     stops a fast drag from painting a selection highlight over the photo. */
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}

.lightbox__stage[data-zoomed="true"] img { cursor: grab; }
.lightbox__stage[data-panning="true"] img { cursor: grabbing; }

/* Transform only while a gesture or the zoom toggle is actually changing it --
   not while a fresh photo is loading, or the very first frame would visibly
   tween in from whatever the previous photo's transform happened to be. */
.lightbox__stage img.is-transiting { transition: transform 220ms var(--ease-out); }

/* Solid, opaque, and its own row — never a translucent panel over a photo.
   The old failure mode was light-grey text landing on a bright image. */
.lightbox__foot {
  padding: 0.8rem var(--gutter);
  padding-bottom: max(0.9rem, env(safe-area-inset-bottom));
  background: #08080a;
  border-top: 1px solid var(--mono-800);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.4rem 1.5rem;
  flex-wrap: wrap;
  z-index: 2;
}

.lightbox__caption {
  color: #fafafa;
  font-size: 1rem;
  line-height: 1.4;
  max-width: 62ch;
}

.lightbox__meta {
  color: #a1a1aa;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.07em;
}

.lightbox__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px; height: 52px;
  display: grid; place-items: center;
  background: rgba(8, 8, 10, 0.82);
  border: 1px solid var(--mono-800);
  color: #f5f5f5;
  cursor: pointer;
  z-index: 3;
}

.lightbox__nav--prev { left: max(0.4rem, 1.5vw); }
.lightbox__nav--next { right: max(0.4rem, 1.5vw); }
.lightbox__nav:hover { background: #262626; }

@media (max-width: 640px) {
  .lightbox__nav { width: 44px; height: 44px; }
  .lightbox__caption { font-size: 0.92rem; }
  .lightbox__foot { padding-inline: 1rem; }
}

/* --------------------------------------------------------------------------
   12. Editorial blog index
   -------------------------------------------------------------------------- */

.post-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 22ch);
  gap: clamp(1rem, 3vw, 3rem);
  padding-block: clamp(2rem, 4vw, 3.4rem);
  border-bottom: 1px solid var(--rule);
  align-items: start;
}

.post-row__date {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.post-row__title {
  font-size: clamp(1.9rem, 4.4vw, 3.6rem);
  font-weight: 440;
  line-height: 0.98;
  letter-spacing: -0.032em;
  margin: 0.5rem 0 0;
}

.post-row__title a { text-decoration: none; transition: color 300ms var(--ease); }
.post-row:hover .post-row__title a { color: var(--flag); }

.post-row__excerpt {
  margin: 0.9rem 0 0;
  max-width: 58ch;
  color: var(--ink-2);
  font-size: var(--step-0);
}

.post-row__aside {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  color: var(--ink-3);
  letter-spacing: 0.07em;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding-top: 0.4rem;
}

@media (max-width: 800px) {
  .post-row { grid-template-columns: 1fr; }
  .post-row__aside { flex-direction: row; flex-wrap: wrap; gap: 0.4rem 1rem; }
}

.tag-list { display: flex; flex-wrap: wrap; gap: 0.4rem; }

.tag {
  font-family: var(--font-mono);
  font-size: 0.63rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  border: 1px solid var(--rule);
  padding: 0.28rem 0.55rem;
  text-decoration: none;
  color: var(--ink-3);
  transition: border-color 220ms, color 220ms;
}

.tag:hover { border-color: var(--ink); color: var(--ink); }

/* Article */
.article__header { padding-block: clamp(2.5rem, 6vw, 5.5rem) clamp(1.5rem, 3vw, 3rem); }
.article__title { font-size: var(--step-4); font-weight: 470; letter-spacing: -0.035em; }
.article__sub { font-size: var(--step-1); color: var(--ink-2); font-weight: 300; margin-top: 1rem;
  max-width: 46ch; }
.article__cover { border: 1px solid var(--rule-strong); margin-block: clamp(1.5rem, 4vw, 3rem);
  overflow: hidden; }
.article__cover img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }

.article__grid {
  display: grid;
  grid-template-columns: minmax(0, 15rem) minmax(0, 68ch);
  gap: clamp(1.5rem, 5vw, 5rem);
  justify-content: center;
  align-items: start;
}

.toc { position: sticky; top: 90px; font-family: var(--font-mono); font-size: 0.72rem;
  line-height: 1.7; letter-spacing: 0.04em; }
.toc a { display: block; text-decoration: none; color: var(--ink-3); padding: 0.22rem 0;
  border-left: 1px solid var(--rule); padding-left: 0.8rem; transition: color 200ms, border-color 200ms; }
.toc a:hover, .toc a.is-active { color: var(--ink); border-color: var(--flag); }
.toc a[data-level="3"] { padding-left: 1.6rem; }

@media (max-width: 1000px) {
  .article__grid { grid-template-columns: minmax(0, 1fr); }
  .toc { display: none; }
}

.reading-bar {
  position: fixed;
  top: 0; left: 0;
  height: 2px;
  background: var(--flag);
  width: var(--read, 0%);
  z-index: 100;
  transition: width 120ms linear;
}

/* --------------------------------------------------------------------------
   13. Library — quotes and books
   -------------------------------------------------------------------------- */

.quote-wall {
  columns: 3 22rem;
  column-gap: clamp(1.2rem, 3vw, 3rem);
}

.quote-card {
  break-inside: avoid;
  margin-bottom: clamp(1.2rem, 3vw, 2.4rem);
  padding: clamp(1.2rem, 2.5vw, 2rem);
  border: 1px solid var(--rule);
  background: var(--paper-raised);
}

.quote-card--mine { border-color: var(--flag); }
.quote-card--mine .quote-card__mark { color: var(--flag); }

.quote-card__mark {
  font-family: var(--font-display);
  font-size: 2.6rem;
  line-height: 0.6;
  display: block;
  margin-bottom: 0.5rem;
  color: var(--ink-3);
}

.quote-card__text {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 1.7vw, 1.55rem);
  line-height: 1.28;
  font-weight: 400;
  letter-spacing: -0.015em;
  margin: 0 0 1rem;
}

.quote-card__by {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.11em;
  color: var(--ink-3);
}

.quote-card__note { font-size: 0.9rem; color: var(--ink-2); margin-top: 0.8rem; font-style: italic; }

@media (max-width: 700px) { .quote-wall { columns: 1; } }

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

.shelf-row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) auto;
  gap: 1.2rem;
  align-items: baseline;
  padding-block: 1.35rem;
  border-bottom: 1px solid var(--rule);
}

.shelf-row__title { font-family: var(--font-display); font-size: var(--step-1); font-weight: 460;
  letter-spacing: -0.02em; }
.shelf-row__author { color: var(--ink-3); font-size: var(--step--1); }

.stars { font-family: var(--font-mono); letter-spacing: 0.15em; color: var(--flag); font-size: 0.8rem; }

/* --------------------------------------------------------------------------
   14. Lab — stats, charts, IoT
   -------------------------------------------------------------------------- */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
}

.stat {
  padding: clamp(1.2rem, 2.4vw, 2rem);
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-height: 160px;
}

.stat__value {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4vw, 3.2rem);
  font-weight: 560;
  line-height: 0.95;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
}

.stat--coffee .stat__value { color: var(--flag); }
.stat__label { font-family: var(--font-mono); font-size: 0.68rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--ink-3); }
.stat__caption { font-size: 0.9rem; color: var(--ink-2); margin-top: auto; line-height: 1.4; }

.panel { border: 1px solid var(--rule); padding: clamp(1.1rem, 2.2vw, 1.8rem); background: var(--paper-raised); }
.panel__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  flex-wrap: wrap; margin-bottom: 1.2rem; }

.chart { width: 100%; height: auto; overflow: visible; }
.chart__grid { stroke: var(--rule-hair); stroke-width: 1; }
.chart__tick { fill: var(--ink-3); font-family: var(--font-mono); font-size: 9px;
  letter-spacing: 0.06em; }
.chart__empty { fill: var(--ink-3); font-family: var(--font-mono); font-size: 11px; }
.chart__line { stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.chart__line--primary { stroke: var(--ink); }
.chart__line--accent  { stroke: var(--flag); }
.chart__dot--primary  { fill: var(--ink); }
.chart__dot--accent   { fill: var(--flag); }
.chart__bar--primary  { fill: var(--ink); }
.chart__bar--accent   { fill: var(--flag); }
.spark path { stroke: var(--flag); stroke-width: 1.4; }

.legend { display: flex; gap: 1.2rem; flex-wrap: wrap; font-family: var(--font-mono);
  font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-3); }
.legend__key { display: inline-flex; align-items: center; gap: 0.45rem; }
.legend__swatch { width: 14px; height: 2px; background: var(--ink); }
.legend__swatch--accent { background: var(--flag); }

.select {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.55rem 0.8rem;
  border: 1px solid var(--rule-strong);
  background: var(--paper);
  color: var(--ink);
  min-height: 40px;
}

/* IoT demo */
.rig {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
}

.device {
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  padding: clamp(1.2rem, 2.4vw, 1.8rem);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  align-items: flex-start;
  transition: background-color 400ms var(--ease);
}

.device[data-on="true"] { background: var(--paper-raised); }

.device__glyph {
  width: 44px; height: 44px;
  border: 1px solid var(--rule-strong);
  display: grid; place-items: center;
  transition: background-color 300ms, color 300ms, border-color 300ms;
}

.device[data-on="true"] .device__glyph {
  background: var(--flag); color: var(--flag-ink); border-color: var(--flag);
}

.device__glyph svg { width: 20px; height: 20px; stroke: currentColor; fill: none; stroke-width: 1.5; }
.device__state { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-3); }
.device[data-on="true"] .device__state { color: var(--flag); }

.device--fan[data-on="true"] .device__glyph svg { animation: spin 1.4s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.reveal-note {
  margin-top: 1.6rem;
  padding: 1.1rem 1.3rem;
  border: 1px dashed var(--rule-strong);
  font-size: 0.95rem;
  color: var(--ink-2);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 600ms var(--ease), transform 600ms var(--ease-out);
}

.reveal-note.is-shown { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   15. Forms
   -------------------------------------------------------------------------- */

.field { display: block; margin-bottom: 1.4rem; }

.field__label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  color: var(--ink-3);
  margin-bottom: 0.5rem;
}

.field__input,
.field__textarea,
.field__select {
  width: 100%;
  font-family: var(--font-body);
  font-size: 1.02rem;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  padding: 0.7rem 0.1rem;
  min-height: 46px;
  transition: border-color 260ms var(--ease);
}

.field__select { font-family: var(--font-mono); font-size: 0.85rem; }
.field__textarea { resize: vertical; min-height: 9rem; line-height: 1.6; }

.field__input:focus,
.field__textarea:focus,
.field__select:focus { outline: none; border-color: var(--flag); }

.field__input::placeholder, .field__textarea::placeholder { color: var(--ink-3); opacity: 0.6; }
.field__hint { font-size: 0.8rem; color: var(--ink-3); margin-top: 0.4rem; }
.field__error { font-family: var(--font-mono); font-size: 0.7rem; color: var(--flag);
  margin-top: 0.4rem; letter-spacing: 0.06em; }

.honeypot {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.form-note {
  padding: 1rem 1.2rem;
  border: 1px solid var(--rule-strong);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  margin-bottom: 1.5rem;
}

.form-note--ok { border-color: var(--flag); color: var(--ink); }
.form-note--bad { border-color: var(--flag); background: color-mix(in srgb, var(--flag) 8%, transparent); }

/* --------------------------------------------------------------------------
   16. Pagination
   -------------------------------------------------------------------------- */

.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  flex-wrap: wrap;
  padding-block: clamp(2rem, 4vw, 3.5rem);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.08em;
}

.pager__link {
  min-width: 42px;
  min-height: 42px;
  display: inline-grid;
  place-items: center;
  padding: 0 0.7rem;
  text-decoration: none;
  border: 1px solid transparent;
  color: var(--ink-2);
  transition: border-color 200ms, color 200ms, background-color 200ms;
}

.pager__link:hover { border-color: var(--rule-strong); color: var(--ink); }
.pager__link[aria-current="page"] { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.pager__gap { color: var(--ink-3); padding: 0 0.35rem; }
.pager__link[aria-disabled="true"] { opacity: 0.3; pointer-events: none; }

/* --------------------------------------------------------------------------
   17. Footer
   -------------------------------------------------------------------------- */

.colophon { border-top: 1px solid var(--ink); padding-block: var(--band-tight) 2rem; margin-top: var(--band); }

.colophon__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--gutter);
  padding-bottom: clamp(2rem, 4vw, 3.5rem);
}

.colophon__col h3 {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-3);
  margin-bottom: 1rem;
  font-weight: 500;
}

.colophon__col a { display: block; text-decoration: none; color: var(--ink-2); padding: 0.28rem 0;
  transition: color 200ms; font-size: 0.95rem; }
.colophon__col a:hover { color: var(--flag); }

.colophon__base {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  border-top: 1px solid var(--rule);
  padding-top: 1.4rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  color: var(--ink-3);
}

#egg-trigger { cursor: pointer; user-select: none; -webkit-user-select: none; }

/* --------------------------------------------------------------------------
   18. Toast / dialog (replaces SweetAlert)
   -------------------------------------------------------------------------- */

.toast-stack {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  max-width: min(380px, calc(100vw - 2 * var(--gutter)));
}

.toast {
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  padding: 0.9rem 1.1rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  line-height: 1.5;
  animation: toast-in 420ms var(--ease-out);
}

.toast--flag { background: var(--flag); border-color: var(--flag); color: var(--flag-ink); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   18b. Egg dialog — the guestbook, reachable by thumb
   -------------------------------------------------------------------------- */

.egg-dialog {
  border: 1px solid var(--ink);
  background: var(--paper);
  color: var(--ink);
  padding: 0;
  max-width: min(30rem, calc(100vw - 2rem));
  width: 100%;
}

.egg-dialog::backdrop { background: rgba(8, 8, 10, 0.72); }
.egg-dialog__inner { padding: clamp(1.4rem, 4vw, 2.2rem); }
.egg-dialog .field { margin-bottom: 1rem; }

/* --------------------------------------------------------------------------
   19. Misc
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  background: var(--ink);
  color: var(--paper);
  padding: 0.8rem 1.2rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.skip-link:focus { left: 0; }

.paste-body {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.7;
  background: var(--paper-sunk);
  border: 1px solid var(--rule-strong);
  padding: 1.2rem 1.3rem;
  overflow-x: auto;
  white-space: pre;
  tab-size: 2;
  max-height: 78vh;
}

.marginalia {
  border-left: 2px solid var(--flag);
  padding-left: 1.2rem;
  font-style: italic;
  color: var(--ink-2);
  max-width: 46ch;
}

.empty-state {
  padding: clamp(3rem, 8vw, 6rem) 0;
  text-align: center;
  color: var(--ink-3);
  border: 1px dashed var(--rule-strong);
}

.empty-state__mark { font-family: var(--font-display); font-size: 3rem; display: block;
  margin-bottom: 0.6rem; opacity: 0.5; }

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 5vw, 5rem);
  align-items: start;
}

@media (max-width: 860px) { .split { grid-template-columns: 1fr; } }

.timeline { border-left: 1px solid var(--rule); padding-left: clamp(1.2rem, 3vw, 2.5rem); }

.timeline__item { position: relative; padding-bottom: clamp(2rem, 4vw, 3.2rem); }

.timeline__item::before {
  content: "";
  position: absolute;
  left: calc(-1 * clamp(1.2rem, 3vw, 2.5rem) - 4px);
  top: 0.55em;
  width: 7px; height: 7px;
  background: var(--flag);
}

.badge {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  border: 1px solid var(--rule-strong);
  padding: 0.22rem 0.5rem;
  color: var(--ink-3);
  display: inline-block;
}

.badge--live { border-color: var(--flag); color: var(--flag); }

.cluster { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }
.spread { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
  flex-wrap: wrap; }
.text-right { text-align: right; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

@media print {
  .masthead, .colophon, .pager, .toast-stack, .lightbox { display: none !important; }
  body { background: #fff; color: #000; }
}
