/* rodrueysimbul.com
   One story, one thread. The system is described in DESIGN.md; read it before changing this file. */

:root {
  --bg: #000;
  --fg: #f5f5f3;
  --fg-2: #a3a3a3;   /* secondary text, 8.3:1 on black */
  --fg-3: #7c7c7c;   /* meta text, 4.9:1 on black */
  --fg-dim: #5e5e5e; /* large text not yet reached by the thread, 3.2:1 */
  --line: rgba(255, 255, 255, 0.14);
  --line-soft: rgba(255, 255, 255, 0.08);
  --azure: #02c9f1;

  --sans: 'Geist', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --mono: 'Geist Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

  --t-hero: clamp(2.7rem, 1.1rem + 6.2vw, 7.5rem);
  --t-xl: clamp(2.2rem, 1rem + 4.2vw, 5.25rem);
  --t-l: clamp(1.75rem, 1.1rem + 2.2vw, 3.25rem);
  --t-m: clamp(1.25rem, 1.02rem + 0.8vw, 1.75rem);
  --t-body: 1.125rem;
  --t-s: 0.9375rem;
  --t-xs: 0.78rem;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* The lane: [label gutter][spine][content]. The thread runs down the middle of the spine. */
  --pad: 16px;
  --label-w: 0px;
  --spine-w: 28px;
  --pen: 0.5; /* where the thread's pen sits, as a fraction of the viewport height (mirrored in site.js) */
  --chapter-gap: clamp(8rem, 24vh, 15rem);

  /* Long-form (writing pages only) */
  --fg-read: #d6d6d3; /* body text of posts, 14.4:1: brighter than --fg-2 for ten minutes of reading */
  --measure: 40rem;   /* the reading column, about 70 characters */
  --part-gap: clamp(4.5rem, 12vh, 7.5rem);
}
@media (min-width: 640px) {
  :root { --pad: 32px; --spine-w: 44px; }
}
@media (min-width: 1024px) {
  :root { --pad: 56px; --label-w: clamp(170px, 16vw, 240px); --spine-w: 72px; }
}

/* ---------- Base ---------- */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html {
  background: var(--bg); color: var(--fg); color-scheme: dark;
  -webkit-text-size-adjust: 100%; scroll-padding-top: 96px;
}
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
  @view-transition { navigation: auto; }
}
body {
  font-family: var(--sans); font-size: var(--t-body); line-height: 1.6; font-weight: 400;
  background: var(--bg); overflow-x: clip;
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; text-align: inherit; }
ul, ol { list-style: none; }
h1, h2, h3, h4 { font-weight: inherit; font-size: inherit; }
[hidden] { display: none !important; } /* component display rules must never un-hide */
::selection { background: var(--azure); color: #000; }
:focus-visible { outline: 2px solid var(--azure); outline-offset: 4px; border-radius: 2px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
.skip {
  position: fixed; left: var(--pad); top: -80px; z-index: 100;
  padding: 10px 16px; background: var(--azure); color: #000; font-size: var(--t-s); font-weight: 500;
  border-radius: 0 0 6px 6px; transition: top 0.2s;
}
.skip:focus { top: 0; }

/* ---------- Links ----------
   A hairline sits under every prose link; on hover an azure line draws in from the left
   and leaves to the right. Feedback without moving anything. */

.link, .post-body a:not([class]) {
  background-image: linear-gradient(var(--azure), var(--azure)), linear-gradient(var(--line), var(--line));
  background-size: 0% 1px, 100% 1px;
  background-position: 100% 100%, 0 100%;
  background-repeat: no-repeat;
  padding-bottom: 0.08em;
  transition: background-size 0.55s var(--ease), color 0.3s;
}
.link:hover, .link:focus-visible, .post-body a:not([class]):hover, .post-body a:not([class]):focus-visible {
  background-size: 100% 1px, 100% 1px;
  background-position: 0 100%, 0 100%;
  color: var(--fg);
}
.arrow { display: inline-block; transition: transform 0.45s var(--ease); }
a:hover .arrow, a:focus-visible .arrow { transform: translate(2px, -2px); }
a:hover .arrow-down, a:focus-visible .arrow-down { transform: translateY(3px); }

/* ---------- Header ----------
   Out of the way while reading (hides on scroll down), back the moment you reach for it. */

.site-header {
  position: fixed; inset: 0 0 auto; z-index: 50; height: 64px;
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  padding-inline: var(--pad);
  transition: transform 0.55s var(--ease), background-color 0.4s, -webkit-backdrop-filter 0.4s, backdrop-filter 0.4s;
}
html:not(.js) .site-header, .site-header.is-solid {
  background: rgba(0, 0, 0, 0.78);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
.site-header.is-hidden { transform: translateY(-100%); }
.site-header:has(:focus-visible) { transform: none; }
.site-name { font-size: var(--t-s); font-weight: 500; letter-spacing: -0.01em; view-transition-name: site-name; }
.site-nav { display: flex; gap: clamp(14px, 2.6vw, 34px); font-size: var(--t-s); color: var(--fg-2); }
.site-nav a { transition: color 0.3s; }
.site-nav a:hover, .site-nav a[aria-current] { color: var(--fg); }
@media (max-width: 400px) { .site-nav .optional { display: none; } }

/* ---------- The lane and the thread ---------- */

.story { position: relative; padding-inline: var(--pad); }
.lane { display: grid; grid-template-columns: var(--label-w) var(--spine-w) minmax(0, 1fr); }
.lane > * { grid-column: 3; min-width: 0; }

/* The line begins at chapter 01's node, below the hero, so the first screen stays black and white,
   and it ends at the node beside the closing note. No fade: it goes somewhere. */
.thread {
  position: absolute; top: 0; bottom: 0; width: 1px; pointer-events: none;
  left: calc(var(--pad) + var(--label-w) + var(--spine-w) / 2 - 0.5px);
  background: var(--line);
}
.thread-fill { position: absolute; inset: 0; background: var(--azure); transform-origin: 50% 0; transform: scaleY(0); }
/* The pen lives in its own layer, above the nodes and outside the fade mask: it never slips under
   a node it's about to light, and it never dims. It shows up once the line starts drawing. */
.thread-pen {
  position: absolute; top: 0; z-index: 2; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px;
  left: calc(var(--pad) + var(--label-w) + var(--spine-w) / 2);
  border-radius: 50%; background: var(--azure); pointer-events: none;
  opacity: 0; transition: opacity 0.3s var(--ease);
}
.thread-pen.is-live { opacity: 1; }

.node {
  width: 9px; height: 9px; border-radius: 50%; position: relative; z-index: 1;
  background: var(--bg); box-shadow: inset 0 0 0 1px var(--fg-3);
  transform: scale(0.78);
  transition: background-color 0.5s var(--ease), box-shadow 0.5s var(--ease), transform 0.5s var(--ease);
}
.node.is-lit { background: var(--azure); box-shadow: inset 0 0 0 1px var(--azure); transform: scale(1); }

/* ---------- Chapters ---------- */

.chapter { padding-top: var(--chapter-gap); }
.chapter > .node { grid-column: 2; grid-row: 1; justify-self: center; margin-top: calc(var(--t-xs) * 0.75 - 4.5px); }
.chapter > .label { grid-row: 1; }
.chapter > .body { grid-row: 2; }

.label {
  font-family: var(--mono); font-size: var(--t-xs); line-height: 1.5; letter-spacing: 0.01em;
  color: var(--fg-3); margin-bottom: 2.25rem; transition: color 0.5s var(--ease);
}
.label .n { margin-right: 0.9em; transition: color 0.5s var(--ease); }
.js .chapter.is-lit > .label { color: var(--fg); }
.js .chapter.is-lit > .label .n { color: var(--azure); }

@media (min-width: 1024px) {
  /* The label hangs in the gutter. When the thread reaches a chapter, the label is picked up
     by the pen and rides beside it until the chapter ends. Pure CSS: sticky at the pen's height. */
  .chapter > .label {
    grid-column: 1; grid-row: 1; align-self: start; margin: 0;
    position: sticky; top: calc(var(--pen) * 100vh - var(--t-xs) * 0.75);
    text-align: right; padding-right: 4px;
  }
  .chapter > .body { grid-row: 1; }
}

/* ---------- Type roles ---------- */

.statement {
  font-size: var(--t-xl); font-weight: 500; line-height: 1.02; letter-spacing: -0.045em;
  text-wrap: balance; max-width: 15em;
}
.lead {
  font-size: var(--t-m); line-height: 1.4; letter-spacing: -0.015em; color: var(--fg);
  text-wrap: pretty; max-width: 30em;
}
.prose { color: var(--fg-2); max-width: 36em; text-wrap: pretty; }
.prose p + p { margin-top: 1em; }
.prose strong { color: var(--fg); font-weight: 500; }
.meta { font-family: var(--mono); font-size: var(--t-xs); line-height: 1.6; color: var(--fg-3); }
.muted { color: var(--fg-2); }
.accent { color: var(--azure); }
.gap-s { margin-top: 1.25rem; }
.gap-m { margin-top: 2.5rem; }
.gap-l { margin-top: clamp(3.5rem, 9vh, 6rem); }

/* ---------- Hero ---------- */

.hero {
  min-height: 100svh; padding-top: clamp(88px, 14vh, 120px); padding-bottom: clamp(2.5rem, 8vh, 5rem);
  align-content: end; row-gap: 0;
}
/* Sized by width, and also by height: on a short laptop screen the headline shrinks just enough
   that the whole introduction (name, headline, the line under it) is on the first screen. */
.hero-title {
  font-size: max(2.2rem, min(var(--t-hero), calc((100svh - 270px) / 5)));
  font-weight: 500; line-height: 1; letter-spacing: -0.05em;
  max-width: 13.5em; text-wrap: balance;
}
.hero-title .soft { color: var(--fg-2); }
/* How to say it, set above the name like a gloss. It takes no room in the line, so the wave
   follows the name at a normal word space instead of across a hole. */
.hero-title .named { position: relative; display: inline-block; vertical-align: top; }
.hero-title .say {
  position: absolute; left: 0.04em; bottom: 100%;
  font-family: var(--mono); font-weight: 400; letter-spacing: 0.02em; color: var(--fg-3);
  font-size: clamp(0.78rem, 0.6rem + 0.45vw, 1.05rem); line-height: 1.2; margin-bottom: 0.1em;
}
/* The wave sits a little smaller than the words, riding the cap height, so it greets rather than shouts. */
.hero-title .wave { font-size: 0.7em; letter-spacing: 0; margin-top: 0.2em; }
.hero-titles { margin-top: clamp(1.5rem, 3.5vh, 2.5rem); letter-spacing: 0.02em; }
.hero-titles + .hero-sub { margin-top: 0.6rem; }
.hero-sub { margin-top: clamp(1.75rem, 4vh, 2.75rem); max-width: 30em; color: var(--fg-2); font-size: var(--t-m); line-height: 1.4; letter-spacing: -0.01em; }
/* Words rise out of their own line once, on arrival. Plain CSS, so it never waits on JS. */
/* The clip box gets breathing room on every side (padding, cancelled by negative margin) so
   tight tracking never shaves a letter's edge: the last "w" of a word used to lose its arm. */
.w {
  display: inline-block; overflow: hidden; vertical-align: top;
  padding: 0 0.08em 0.14em; margin: 0 -0.08em -0.14em;
}
.w > span { display: inline-block; }

/* The signature: once the words settle, an azure underline draws under the name, holds for a
   beat, and leaves the way a link hover does. The first azure a visitor sees. It's a real text
   underline on an invisible copy of the name, so it breaks around the tail of the "y" the way
   good typography does; a wipe (clip-path) draws it in and out. */
.hero-title .name > span { position: relative; }
.hero-title .sig {
  position: absolute; left: 0; top: 0; white-space: nowrap; pointer-events: none;
  color: transparent;
  text-decoration: underline var(--azure);
  text-decoration-thickness: max(2px, 0.028em);
  text-underline-offset: 0.1em;
  text-decoration-skip-ink: auto;
  clip-path: inset(0 100% 0 0);
}

@media (prefers-reduced-motion: no-preference) {
  .w > span { animation: rise 1.15s var(--ease) both; animation-delay: calc(120ms + var(--i) * 42ms); }
  .hero-sub, .hero-titles { animation: fade 1.2s var(--ease) both 0.8s; }
  .hero-title .sig { animation: sign 2.4s var(--ease) both 1.25s; }
  /* Held at the start until Geist is in (the head script sets .fonts-ready, 1.2s at most). */
  .js:not(.fonts-ready) .w > span, .js:not(.fonts-ready) .hero-sub, .js:not(.fonts-ready) .hero-titles,
  .js:not(.fonts-ready) .hero-title .sig { animation-play-state: paused; }
  /* Once per visit: back on the home page later in the same visit, the hero is simply there. */
  .introduced .w > span, .introduced .hero-sub, .introduced .hero-titles, .introduced .hero-title .sig { animation: none; }
}
@keyframes rise { from { transform: translateY(108%); } }
@keyframes fade { from { opacity: 0; transform: translateY(8px); } }
/* Draw in from the left, hold, leave to the right: the same path as a link hover. */
@keyframes sign {
  0% { clip-path: inset(0 100% 0 0); }
  34%, 58% { clip-path: inset(0 0 0 0); }
  100% { clip-path: inset(0 0 0 100%); }
}

/* ---------- 01 Who I am ---------- */

.intro { display: grid; gap: 2.5rem; align-items: start; }
.portrait { width: min(260px, 62%); }
.portrait img { aspect-ratio: 5 / 6; object-fit: cover; object-position: 50% 30%; border-radius: 3px; }
.portrait figcaption { margin-top: 0.75rem; }
@media (min-width: 900px) {
  .intro { grid-template-columns: minmax(0, 1fr) clamp(220px, 22vw, 300px); gap: clamp(3rem, 6vw, 6rem); }
  .portrait { width: 100%; margin-top: 0.4rem; }
}

/* ---------- 02 What I build ---------- */

.kinds { margin-top: clamp(2.5rem, 6vh, 4rem); display: grid; gap: 1.75rem; max-width: 34em; }
.kinds a, .kinds .kind { display: block; font-size: var(--t-m); line-height: 1.4; letter-spacing: -0.015em; color: var(--fg-3); transition: color 0.35s; }
.kinds strong { color: var(--fg); font-weight: 500; }
.kinds a:hover, .kinds a:focus-visible { color: var(--fg-2); }
.kinds .arrow { color: var(--azure); margin-left: 0.2em; opacity: 0; transition: opacity 0.35s, transform 0.45s var(--ease); }
.kinds a:hover .arrow, .kinds a:focus-visible .arrow { opacity: 1; }

/* ---------- 03 What I've built ---------- */

.group + .group { margin-top: clamp(4.5rem, 12vh, 7rem); }
.group-title { display: flex; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.index { border-top: 1px solid var(--line); }
.entry { border-bottom: 1px solid var(--line); }
.entry-toggle {
  width: 100%; display: grid; grid-template-columns: minmax(0, 1fr) 14px;
  align-items: center; column-gap: 1.5rem; row-gap: 0.35rem; padding: 1.35rem 0;
}
/* Explicit placement throughout: an item with only a row set would be placed before the name. */
.entry-name {
  grid-column: 1; grid-row: 1;
  font-size: var(--t-l); font-weight: 500; line-height: 1.08; letter-spacing: -0.04em;
  transition: color 0.35s, transform 0.55s var(--ease);
}
.entry-line { grid-column: 1; grid-row: 2; font-size: var(--t-s); line-height: 1.45; color: var(--fg-3); transition: color 0.35s; }
.entry-icon { grid-column: 2; grid-row: 1; position: relative; width: 14px; height: 14px; color: var(--fg-3); transition: color 0.35s; }
.entry-icon::before, .entry-icon::after {
  content: ''; position: absolute; left: 0; top: 50%; width: 100%; height: 1px; margin-top: -0.5px;
  background: currentColor; transition: transform 0.55s var(--ease);
}
.entry-icon::after { transform: rotate(90deg); }
.entry.is-open .entry-icon::after { transform: rotate(0deg); }
.entry.is-open .entry-icon { color: var(--azure); }
@media (min-width: 768px) {
  .entry-toggle { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) 14px; }
  .entry-line { grid-column: 2; grid-row: 1; }
  .entry-icon { grid-column: 3; }
}
/* Pointing at one entry quiets the rest, so the one you're considering is the only thing lit.
   Keyboard focus gets exactly the same treatment. */
@media (hover: hover) {
  .index:has(.entry-toggle:hover) .entry-toggle:not(:hover) .entry-name { color: var(--fg-dim); }
  .entry-toggle:hover .entry-name { transform: translateX(0.35rem); }
  .entry-toggle:hover .entry-line { color: var(--fg-2); }
  .entry-toggle:hover .entry-icon { color: var(--azure); }
}
.index:has(.entry-toggle:focus-visible) .entry-toggle:not(:focus-visible) .entry-name { color: var(--fg-dim); }
.entry-toggle:focus-visible .entry-name { transform: translateX(0.35rem); }
.entry-toggle:focus-visible .entry-line { color: var(--fg-2); }
.entry-toggle:focus-visible .entry-icon { color: var(--azure); }

/* The entry opens in place: the list parts, the story slides in, nothing leaves the page. */
.entry-panel { display: grid; grid-template-rows: 1fr; }
.entry-inner { min-height: 0; overflow: hidden; }
.js .entry-panel { grid-template-rows: 0fr; visibility: hidden; transition: grid-template-rows 0.7s var(--ease), visibility 0s linear 0.7s; }
.js .entry.is-open .entry-panel { grid-template-rows: 1fr; visibility: visible; transition: grid-template-rows 0.7s var(--ease), visibility 0s; }
.entry-content { display: grid; gap: 2rem; padding: 0.5rem 0 2.75rem; }
.js .entry-content { opacity: 0; transform: translateY(12px); transition: opacity 0.35s, transform 0.6s var(--ease); }
.js .entry.is-open .entry-content { opacity: 1; transform: none; transition-delay: 0.14s; }
@media (min-width: 1100px) {
  .entry-content.has-media { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: clamp(2.5rem, 4vw, 4rem); align-items: start; }
}
.entry-text { display: grid; gap: 1.5rem; max-width: 38em; }
.entry-text p { color: var(--fg-2); text-wrap: pretty; }
.entry-text .entry-result { font-size: var(--t-m); line-height: 1.35; letter-spacing: -0.015em; color: var(--fg); }
.entry-text code { font-family: var(--mono); font-size: 0.86em; color: var(--fg); }
.facts { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 0.35rem 1.5rem; }
.facts dd { color: var(--fg-2); }
.entry-links { display: flex; flex-wrap: wrap; gap: 0.5rem 1.75rem; font-size: var(--t-s); }
.entry-media img { width: 100%; border-radius: 4px; border: 1px solid var(--line-soft); }
.index-foot { margin-top: 2.5rem; }

/* ---------- 04 How I got here ---------- */

.journey { display: grid; gap: clamp(4.5rem, 14vh, 8rem); margin-top: clamp(4rem, 12vh, 7rem); }
.stage { position: relative; max-width: 40em; }
.stage > .node { position: absolute; left: calc(var(--spine-w) / -2 - 4.5px); top: calc(var(--t-l) * 0.56 - 4.5px); }
.stage-name { font-size: var(--t-l); font-weight: 500; line-height: 1.12; letter-spacing: -0.04em; }
.stage p { color: var(--fg-2); margin-top: 1rem; text-wrap: pretty; }
.stage .meta { margin-top: 1.25rem; }
.js .stage-name { color: var(--fg-dim); transition: color 0.7s var(--ease); }
.js .stage.is-lit .stage-name { color: var(--fg); }
.caret {
  display: inline-block; width: 0.07em; height: 0.9em; margin-left: 0.1em; vertical-align: -0.08em;
  background: var(--azure);
}
@media (prefers-reduced-motion: no-preference) {
  .caret { animation: blink 1.1s steps(1) infinite; }
  /* Pointing at it, or typing, holds the caret solid, the way a real one stops blinking. */
  .next:hover .caret, .next.is-typing .caret { animation: none; }
}
@keyframes blink { 50% { opacity: 0; } }

/* Whatever comes next: the easter egg. It looks like a heading until you touch it. */
.next { cursor: pointer; text-align: left; letter-spacing: inherit; line-height: inherit; }
.typed { color: var(--fg-2); white-space: pre-wrap; }
.typed.is-struck { text-decoration: line-through; text-decoration-thickness: 2px; text-decoration-color: var(--azure); }
.next-note { margin-top: 1rem; min-height: 1.6em; opacity: 0; transform: translateY(4px); transition: opacity 0.6s var(--ease), transform 0.6s var(--ease); }
.next-note.is-shown { opacity: 1; transform: none; }

/* ---------- 05 Now / 06 Exploring ---------- */

.pairs { display: grid; gap: 1.75rem; margin-top: clamp(2.5rem, 6vh, 3.5rem); max-width: 46em; }
.pairs > div { display: grid; gap: 0.35rem; }
.pairs dt { font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); padding-top: 0.2em; }
.pairs dd { font-size: var(--t-m); line-height: 1.4; letter-spacing: -0.015em; text-wrap: pretty; }
.pairs dd .muted { font-size: var(--t-body); letter-spacing: 0; display: block; margin-top: 0.35rem; line-height: 1.55; }
@media (min-width: 700px) {
  .pairs > div { grid-template-columns: 8.5rem minmax(0, 1fr); gap: 2rem; }
  .pairs dt { padding-top: 0.55em; }
}


/* Off the keyboard: a few real photos, the same restraint as the portrait. */
.photos {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: clamp(0.75rem, 1.6vw, 1.25rem);
  margin-top: clamp(2.5rem, 6vh, 3.5rem); max-width: 60rem;
}
.photo img, .photo .frame { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; border-radius: 3px; }
.photo figcaption { margin-top: 0.65rem; }
/* They develop as they arrive: dim and grey, then warm into full color, one after another. */
.js .photo img { opacity: 0.35; filter: grayscale(1) brightness(0.7); transition: opacity 1.4s var(--ease), filter 2.2s var(--ease); }
.js .photo.is-developed img { opacity: 1; filter: none; }
.photos > li:nth-child(2) img { transition-delay: 0.18s; }
.photos > li:nth-child(3) img { transition-delay: 0.36s; }
@media (max-width: 699px) {
  /* A strip you swipe, bleeding off the right edge so the next photo peeks in. */
  .photos {
    grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 72%;
    overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain; scrollbar-width: none;
    margin-right: calc(-1 * var(--pad)); padding-right: var(--pad);
  }
  .photos::-webkit-scrollbar { display: none; }
  .photos > li { scroll-snap-align: start; }
}

/* Fill-ins: things only Rodruey can write. Loud on purpose, so they can't ship by accident. */
.todo { color: var(--azure); border-bottom: 1px dashed var(--azure); }

/* ---------- 07 Where I'm going ---------- */

/* One line per verb, so the pen lights them one at a time: the page's last bit of movement. */
.mantra { margin-top: clamp(3.5rem, 10vh, 6rem); font-size: var(--t-xl); font-weight: 500; line-height: 1.06; letter-spacing: -0.045em; }
.mantra span { display: block; transition: color 0.7s var(--ease); }
.js .mantra [data-light] { color: var(--fg-dim); }
.js .mantra [data-light].is-lit { color: var(--fg); }
.js .mantra [data-light].is-lit.accent { color: var(--azure); }

/* ---------- 08 Connect ---------- */

.email { display: inline-flex; flex-wrap: wrap; align-items: baseline; gap: 0.75rem 1.5rem; margin-top: clamp(2rem, 5vh, 3rem); }
.email-address { font-size: clamp(1.35rem, 0.55rem + 3.6vw, 3.25rem); font-weight: 500; letter-spacing: -0.04em; line-height: 1.15; }
.copy { font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); padding: 0.5rem 0; min-height: 44px; transition: color 0.3s; }
.copy:hover { color: var(--fg); }
.copy.is-done { color: var(--azure); }
.clock { margin-top: 1rem; }

.channels { margin-top: clamp(4rem, 11vh, 6.5rem); border-top: 1px solid var(--line); max-width: 52rem; }
.channels a {
  display: grid; grid-template-columns: 6.5rem minmax(0, 1fr) auto; align-items: baseline; gap: 1rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--line);
}
.channels .role { font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); }
.channels .where { font-size: var(--t-m); font-weight: 500; letter-spacing: -0.02em; transition: transform 0.55s var(--ease); }
/* The description sits on its own line under the platform: some run a sentence or two. */
.channels .what {
  display: block; margin-top: 0.35rem; max-width: 36em;
  color: var(--fg-3); font-size: var(--t-s); line-height: 1.5; font-weight: 400; letter-spacing: 0; transition: color 0.35s;
}
.channels .arrow { color: var(--fg-3); transition: color 0.35s, transform 0.45s var(--ease); }
.channels a:hover .where, .channels a:focus-visible .where { transform: translateX(0.35rem); }
.channels a:hover .what, .channels a:focus-visible .what { color: var(--fg-2); }
.channels a:hover .arrow, .channels a:focus-visible .arrow { color: var(--azure); }
@media (max-width: 599px) {
  .channels a { grid-template-columns: minmax(0, 1fr) auto; }
  .channels .role { grid-column: 1 / -1; }
}

/* ---------- The ending: a note in handwriting ----------
   The one deliberate break from the type system. Everything else is Geist; this is a card tucked
   into the back of the book. When it arrives, the pen finishes the line and the note writes in. */

.note { position: relative; margin-top: clamp(6rem, 18vh, 10rem); max-width: 34rem; }
.note > .node { position: absolute; left: calc(var(--spine-w) / -2 - 4.5px); top: calc(0.82em - 4.5px); font-size: var(--t-note); }
.note-text, .note-sign {
  font-family: 'La Belle Aurore', 'Segoe Script', 'Bradley Hand', cursive;
  font-size: var(--t-note); line-height: 1.6; letter-spacing: 0.01em; color: var(--fg);
}
.note { --t-note: clamp(1.3rem, 1rem + 1vw, 1.75rem); }
.note-sign { margin-top: 0.9em; }
.note .link { padding-bottom: 0.02em; }
/* Word by word, at a writing pace. site.js wraps the words; without it the note is simply there. */
.note .nw { opacity: 0; transition: opacity 0.6s ease; transition-delay: calc(var(--k) * 42ms); }
.note.is-written .nw { opacity: 1; }
/* The inbox link's hairline waits for its own words, then draws in under them (site.js sets --k
   to the link's first word). Hover still answers instantly. */
.js .note .link { background-size: 0% 1px, 0% 1px; }
.js .note.is-written .link { background-size: 0% 1px, 100% 1px; transition-delay: calc(var(--k, 0) * 42ms); }
.js .note.is-written .link:hover, .js .note.is-written .link:focus-visible { background-size: 100% 1px, 100% 1px; transition-delay: 0s; }

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

.site-footer {
  margin-top: var(--chapter-gap); padding: 1.75rem var(--pad) 2rem;
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem 2rem;
  border-top: 1px solid var(--line-soft);
  font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3);
}
.site-footer nav { display: flex; flex-wrap: wrap; gap: 0.75rem 1.5rem; }
.site-footer a { transition: color 0.3s; }
.site-footer a:hover { color: var(--fg); }

/* ---------- Document pages (resume, 404) ---------- */

.doc-hero { min-height: 62svh; padding-top: 140px; align-content: end; }
.doc-hero .statement { max-width: 11em; }
.rows { border-top: 1px solid var(--line); margin-top: 0.25rem; max-width: 56rem; }
.rows > li { border-bottom: 1px solid var(--line); }
.rows a, .rows .row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.25rem 1.5rem; align-items: baseline;
  padding: 1.1rem 0;
}
.rows .title { font-size: var(--t-m); font-weight: 500; letter-spacing: -0.02em; line-height: 1.3; transition: transform 0.55s var(--ease); }
.rows .by { grid-column: 1; font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); }
.rows .arrow { grid-column: 2; grid-row: 1; color: var(--fg-3); transition: color 0.35s, transform 0.45s var(--ease); }
.rows a:hover .title, .rows a:focus-visible .title { transform: translateX(0.35rem); }
.rows a:hover .arrow, .rows a:focus-visible .arrow { color: var(--azure); }
.skills { display: grid; gap: 2rem; max-width: 52rem; }
.skills > div { display: grid; gap: 0.5rem; }
.skills dd { color: var(--fg-3); text-wrap: pretty; }
.skills dd b { color: var(--fg); font-weight: 400; }
@media (min-width: 700px) {
  .skills > div { grid-template-columns: 11rem minmax(0, 1fr); gap: 2rem; }
  .skills dt { padding-top: 0.3em; }
}

/* ---------- 404: the thread, broken ----------
   The line comes down from the top of the page and snaps just above the message; the pen stops
   at the break, and what's left of the line picks up again a few pixels off, going nowhere. */

.break { grid-column: 2; grid-row: 1 / span 5; justify-self: center; position: relative; width: 1px; }
.break-top {
  position: absolute; left: 0; bottom: calc(100% + 28px); width: 1px; height: min(30vh, 260px);
  background: var(--azure); transform-origin: 50% 0;
  -webkit-mask-image: linear-gradient(to top, #000 50%, transparent);
  mask-image: linear-gradient(to top, #000 50%, transparent);
}
.break-pen {
  position: absolute; left: -4px; bottom: calc(100% + 23.5px); width: 9px; height: 9px;
  border-radius: 50%; background: var(--azure);
}
.break-bottom {
  position: absolute; left: 7px; top: 0; width: 1px; height: calc(100% + 5rem); background: var(--line);
  -webkit-mask-image: linear-gradient(to bottom, #000 40%, transparent);
  mask-image: linear-gradient(to bottom, #000 40%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .break-top { animation: draw 1.3s var(--ease) both 0.2s; }
  .break-pen { animation: settle 0.5s var(--ease) both 1.35s; }
  .break-bottom { animation: fade 1.2s var(--ease) both 0.9s; }
}
@keyframes draw { from { transform: scaleY(0); } }
@keyframes settle { from { opacity: 0; transform: translateY(-10px); } }

/* ---------- Writing: posts and the writing index ----------
   Long-form reuses the lane. Each section of a post is a lane of its own with a node on the
   spine, so the thread is the reading progress, and on desktop each section's label rides with
   the pen in the gutter, the way chapters do on the home page. See "Writing" in DESIGN.md. */

/* The head of a post: kicker, title, dek, then facts (case studies) and a cover (optional). */
.post-head { padding-top: clamp(112px, 20vh, 210px); }
.post-title {
  margin-top: 1.25rem; font-size: var(--t-xl); font-weight: 500; line-height: 1.04; letter-spacing: -0.045em;
  text-wrap: balance; max-width: 16em;
}
.post-dek {
  margin-top: 1.5rem; max-width: 30em; font-size: var(--t-m); line-height: 1.4; letter-spacing: -0.015em;
  color: var(--fg-2); text-wrap: pretty;
}
.post-kicker a { transition: color 0.3s; }
.post-kicker a:hover, .post-kicker a:focus-visible { color: var(--fg); }
.post-facts { margin-top: 2.75rem; max-width: var(--measure); border-top: 1px solid var(--line); }
.post-facts > div { display: grid; gap: 0.2rem; padding: 0.85rem 0; border-bottom: 1px solid var(--line-soft); }
.post-facts dt { font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); padding-top: 0.2em; }
.post-facts dd { font-size: var(--t-s); color: var(--fg-2); }
@media (min-width: 640px) {
  .post-facts > div { grid-template-columns: 8.5rem minmax(0, 1fr); gap: 1.5rem; }
}
.post-cover { margin-top: clamp(3rem, 8vh, 4.5rem); }
.post-cover img, .post-body figure img { width: 100%; border-radius: 4px; border: 1px solid var(--line-soft); }
.post-cover figcaption, .post-body figcaption { margin-top: 0.75rem; font-family: var(--mono); font-size: var(--t-xs); line-height: 1.6; color: var(--fg-3); }

/* A section of a post. Below 1024px the h2 is the only heading; the gutter label appears on
   desktop, where it can ride with the pen. It copies the h2, so screen readers skip it. */
.part { padding-top: var(--part-gap); }
.part > .node { grid-column: 2; grid-row: 1; justify-self: center; margin-top: calc(var(--t-l) * 0.55 - 4.5px); }
.part > .label { display: none; }
.part > .post-body { grid-row: 1; }
.js .part.is-lit > .label { color: var(--fg); }
.js .part.is-lit > .label .n { color: var(--azure); }
@media (min-width: 1024px) {
  .part > .label {
    display: block; grid-column: 1; grid-row: 1; align-self: start; margin: 0;
    position: sticky; top: calc(var(--pen) * 100vh - var(--t-xs) * 0.75);
    text-align: right; padding-right: 4px;
    /* The first line lines up with the h2 beside it. */
    margin-top: calc(var(--t-l) * 0.55 - var(--t-xs) * 0.75);
  }
}
/* A post without sections (a note) still gets a node, beside its first line. */
.part.is-plain > .node { margin-top: calc(var(--t-body) * 0.85 - 4.5px); }

/* The reading column. Every block is held to the measure except the ones marked wide. */
.post-body { color: var(--fg-read); line-height: 1.7; }
.post-body > * { max-width: var(--measure); }
.post-body > * + * { margin-top: 1.25em; }
.post-body > .wide { max-width: none; }
.post-body > pre { max-width: 52rem; } /* code lines run longer than prose */
.post-body h2 {
  font-size: var(--t-l); font-weight: 500; line-height: 1.1; letter-spacing: -0.04em; color: var(--fg);
  text-wrap: balance; max-width: 16em;
}
.post-body h2 + * { margin-top: 1.5rem; }
.post-body h3 { margin-top: 2.5em; font-size: var(--t-m); font-weight: 500; line-height: 1.3; letter-spacing: -0.02em; color: var(--fg); text-wrap: balance; }
.post-body h3 + * { margin-top: 0.75em; }
.post-body strong { color: var(--fg); font-weight: 500; }
.post-body ul:not([class]) { padding-left: 1.3em; }
.post-body ol:not([class]) { padding-left: 1.7em; }
.post-body ul:not([class]) { list-style: disc; }
.post-body ol:not([class]) { list-style: decimal; }
.post-body :is(ul, ol):not([class]) > li + li { margin-top: 0.5em; }
.post-body :is(ul, ol):not([class]) > li::marker { color: var(--fg-3); }
.post-body ol:not([class]) > li::marker { font-family: var(--mono); font-size: 0.85em; }
.post-body :not(pre) > code { font-family: var(--mono); font-size: 0.86em; color: var(--fg); }
/* A quote from someone else: a hairline and their words in white. */
.post-body blockquote { padding-left: 1.25em; border-left: 1px solid var(--line); color: var(--fg); }
.post-body blockquote p + p { margin-top: 0.75em; }
.post-body blockquote footer { margin-top: 0.75em; font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); }
/* A pull quote: one line of the post said large. Rodruey's own words, once per post at most.
   It waits dim and lights when the pen reaches it (data-light), so it never reads as a heading. */
.post-body > .pull, .post-body > figure, .post-body > pre, .post-body > .table, .post-body > .aside { margin-top: 2.5rem; }
.post-body > .pull + *, .post-body > figure + *, .post-body > pre + *, .post-body > .table + *, .post-body > .aside + * { margin-top: 2.5rem; }
.post-body .pull {
  font-size: var(--t-l); font-weight: 500; line-height: 1.12; letter-spacing: -0.04em; color: var(--fg);
  text-wrap: balance; max-width: 18em;
}
.js .post-body .pull[data-light] { color: var(--fg-dim); transition: color 0.7s var(--ease); }
.js .post-body .pull[data-light].is-lit { color: var(--fg); }
/* An aside: a mono label and a few lines, set off by a hairline. Not a box. */
.post-body .aside { padding-left: 1.25em; border-left: 1px solid var(--line); color: var(--fg-2); font-size: var(--t-s); line-height: 1.65; }
.post-body .aside > .meta { display: block; margin-bottom: 0.35rem; }
/* Code: mono between two hairlines. No panel, no syntax colors; comments may be dimmed with .c. */
.post-body pre {
  padding: 1.25rem 0; border-block: 1px solid var(--line); overflow-x: auto;
  font-family: var(--mono); font-size: 0.84rem; line-height: 1.75; color: var(--fg-2); tab-size: 2;
}
.post-body pre code { font: inherit; color: inherit; }
.post-body pre .c { color: var(--fg-3); }
/* Tables: hairline rows, mono headings, figures that line up. The wrapper scrolls on a phone. */
.post-body .table { overflow-x: auto; }
.post-body table { width: 100%; border-collapse: collapse; font-size: var(--t-s); line-height: 1.5; font-variant-numeric: tabular-nums; }
.post-body th {
  padding: 0 1.25rem 0.7rem 0; border-bottom: 1px solid var(--line); text-align: left; vertical-align: bottom; white-space: nowrap;
  font-family: var(--mono); font-size: var(--t-xs); font-weight: 400; color: var(--fg-3);
}
.post-body td { padding: 0.8rem 1.25rem 0.8rem 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; color: var(--fg-2); }
.post-body td:first-child { color: var(--fg); }
.post-body th:last-child, .post-body td:last-child { padding-right: 0; }
.post-body .num { text-align: right; }
/* Footnotes: a small mono number in the text, the notes at the end of the post. */
.post-body sup { font-size: 0.7em; line-height: 0; }
.post-body .fn { font-family: var(--mono); color: var(--fg-3); padding: 0 0.15em; transition: color 0.3s; }
.post-body .fn:hover, .post-body .fn:focus-visible { color: var(--azure); }
.footnotes { padding-top: 1.5rem; border-top: 1px solid var(--line-soft); font-size: var(--t-s); line-height: 1.6; color: var(--fg-2); }
.post-body > .footnotes { margin-top: 3rem; }
.footnotes ol { list-style: decimal; padding-left: 1.75em; }
.footnotes li::marker { font-family: var(--mono); font-size: 0.85em; color: var(--fg-3); }
.footnotes .fn-back { margin-left: 0.35em; font-family: var(--mono); color: var(--fg-3); transition: color 0.3s; }
.footnotes .fn-back:hover, .footnotes .fn-back:focus-visible { color: var(--azure); }

/* The end of a post. The pen finishes the line when this comes into view (data-finish). */
.post-end > .node { margin-top: calc(var(--t-xs) * 0.8 - 4.5px); }
.post-end .post-body > .lead { margin-top: 1.5rem; text-wrap: balance; }
.post-end .email { margin-top: 1.25rem; }
.post-end .email-address { font-size: var(--t-m); letter-spacing: -0.02em; }
.post-end .rows { margin-top: clamp(3rem, 8vh, 4.5rem); }

/* The writing index: a year per chapter. The year rides with the pen; posts are a quiet list. */
.posts { border-top: 1px solid var(--line); max-width: 56rem; }
.posts > li { border-bottom: 1px solid var(--line); }
.posts a { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 1.5rem; row-gap: 0.35rem; padding: 1.35rem 0; }
.posts .post-meta { grid-column: 1 / -1; grid-row: 1; font-family: var(--mono); font-size: var(--t-xs); color: var(--fg-3); }
.posts .post-name { grid-column: 1; grid-row: 2; font-size: var(--t-m); font-weight: 500; line-height: 1.25; letter-spacing: -0.02em; text-wrap: balance; transition: color 0.35s, transform 0.55s var(--ease); }
.posts .post-line { grid-column: 1; grid-row: 3; font-size: var(--t-s); line-height: 1.5; color: var(--fg-3); transition: color 0.35s; }
.posts .arrow { grid-column: 2; grid-row: 2; align-self: baseline; color: var(--fg-3); transition: color 0.35s, transform 0.45s var(--ease); }
.posts .post-type::before { content: '· '; content: '· ' / ''; }
@media (min-width: 768px) {
  .posts a { grid-template-columns: 8.5rem minmax(0, 1fr) auto; }
  .posts .post-meta { grid-column: 1; grid-row: 1 / span 2; padding-top: 0.5em; }
  .posts .post-meta > * { display: block; }
  .posts .post-type::before { content: none; }
  .posts .post-name { grid-column: 2; grid-row: 1; }
  .posts .post-line { grid-column: 2; grid-row: 2; }
  .posts .arrow { grid-column: 3; grid-row: 1; }
}
@media (hover: hover) {
  .posts:has(a:hover) a:not(:hover) .post-name { color: var(--fg-dim); }
  .posts a:hover .post-name { transform: translateX(0.35rem); }
  .posts a:hover .post-line { color: var(--fg-2); }
  .posts a:hover .arrow { color: var(--azure); }
}
.posts:has(a:focus-visible) a:not(:focus-visible) .post-name { color: var(--fg-dim); }
.posts a:focus-visible .post-name { transform: translateX(0.35rem); }
.posts a:focus-visible .arrow { color: var(--azure); }
.chapter.year { padding-top: clamp(4.5rem, 12vh, 7rem); }
.writing-end { margin-top: clamp(4rem, 10vh, 6rem); position: relative; }
.writing-end > .node { position: absolute; left: calc(var(--spine-w) / -2 - 4.5px); top: calc(var(--t-xs) * 0.8 - 4.5px); }

/* ---------- Reduced motion ----------
   The thread still follows the scroll (that is direct manipulation, not animation);
   everything that would move on its own is still. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 0.01ms !important; animation: none !important; }
}
