/* ==========================================================================
   raheelyawar.com
   Hand-written. No build step, no framework, no external requests.
   Visual direction: "Framed Gallery" — light editorial, warm paper,
   images contained in uniform mats so heterogeneous artwork can coexist.
   ========================================================================== */

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

:root {
  --paper:       #F8F5EF;
  --panel:       #FFFDFA;
  --ink:         #1A1714;
  --body:        #4A443B;
  --muted:       #6B6459;
  --rule:        #DFD7C8;
  --rule-soft:   #EBE4D8;

  /* Measured 4.52:1 on --paper, 4.84:1 on --panel. Clears WCAG AA.
     Use this for anything text-sized. */
  --accent:      #8A6B45;
  /* Measured 2.93:1 on --paper — FAILS AA, so DECORATIVE ONLY on light
     backgrounds: rules, hover fills, marks. Never body text. (It is fine as
     text on the dark consent banner, where the polarity flips.) */
  --accent-soft: #A98B69;

  --serif: Georgia, 'Iowan Old Style', 'Palatino Linotype', Palatino, 'Times New Roman', serif;
  --sans:  -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  --mono:  ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', monospace;

  --measure: 65ch;
  --gap:     40px;
}

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

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; }

a { color: var(--accent); }

/* Visible focus everywhere. Never remove this. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 12px 20px;
  z-index: 100;
  text-decoration: none;
}
.skip-link:focus { left: 0; }

.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;
}

.wrap { max-width: 1240px; margin: 0 auto; padding: 0 44px; }

/* --- Masthead & nav ------------------------------------------------------- */

.masthead { padding: 72px 0 0; }

.sitename {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 40px;
}
.sitename a { color: inherit; text-decoration: none; }
.sitename a:hover { color: var(--ink); }

.nav {
  margin-top: 42px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--rule);
  display: flex;
  gap: 32px;
  flex-wrap: wrap;
}
.nav a {
  font-size: 15px;
  color: var(--muted);
  text-decoration: none;
  padding-bottom: 4px;
  border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--ink); }
/* Two tokens, one highlight. "page" is the link to the document you are on;
   "true" is the section you are inside — a project page marks Portfolio, which
   it belongs to but is not. Both look the same; only the announcement differs. */
.nav a[aria-current="page"],
.nav a[aria-current="true"] { color: var(--ink); border-bottom-color: var(--accent); }

/* --- Book promo (home masthead) -------------------------------------------
   Fills the dead column beside the hero. The cover runs bare here rather than
   in the shared .cover mat.
   -------------------------------------------------------------------------- */

.bookad {
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: 26px 28px;
  display: grid;
  grid-template-columns: 190px 1fr;
  gap: 28px;
  align-items: center;
}
.bookad p { margin: 0; font-size: 14.5px; line-height: 1.62; color: var(--body); }
.bookad .more { margin-top: 16px; }

/* No mat, so nothing frames the artwork — and the cover's top two-thirds is
   pure white against a near-white panel. The shadow is what holds the edge. */
.bookad-cover {
  display: block;
  width: 100%;
  height: auto;
  box-shadow: 0 5px 16px rgba(60,45,25,.18);
}

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

.pagehead { padding: 72px 0 0; max-width: 820px; }
.pagehead h1 {
  font-family: var(--serif);
  font-size: clamp(34px, 5vw, 54px);
  line-height: 1.1;
  font-weight: 400;
  margin: 0 0 22px;
  letter-spacing: -.018em;
}
.lead {
  font-size: 19px;
  color: #3D3830;
  margin: 0;
  max-width: 620px;
  line-height: 1.65;
}
.lead a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.lead a:hover { border-bottom-color: var(--accent); }

/* Hero (home only). The top padding lives on .masthead-top so the hero and
   the book promo beside it share one baseline. */
.masthead-top {
  padding-top: 88px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 510px;
  gap: 56px;
  /* The promo runs taller than the hero text; centring splits the difference
     instead of dumping it all under the lead. */
  align-items: center;
}
.hero { max-width: 820px; }
.hero h1 {
  font-family: var(--serif);
  font-size: clamp(36px, 5.2vw, 58px);
  line-height: 1.1;
  font-weight: 400;
  margin: 0 0 24px;
  letter-spacing: -.018em;
}

/* --- Sections ------------------------------------------------------------- */

section { padding: 76px 0 0; }

.sechead { display: flex; align-items: baseline; gap: 16px; margin-bottom: 34px; }
.sechead h2 {
  font-family: var(--serif);
  font-size: 30px;
  font-weight: 400;
  margin: 0;
  letter-spacing: -.01em;
}
.sechead .line { flex: 1; height: 1px; background: var(--rule); }
.sechead .n {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  letter-spacing: .08em;
  white-space: nowrap;
}

.grouplabel {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 24px;
}
.grouplabel.second { margin-top: 66px; }

/* --- Section jump nav ------------------------------------------------------
   In-page links to the sections below. The headings carry tabindex="-1" so
   focus actually lands on them, not just the scroll position.
   -------------------------------------------------------------------------- */

.sectionnav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 44px;
}
.sectionnav a {
  display: inline-block;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: 11px 18px;
  transition: background .18s, border-color .18s, color .18s;
}
.sectionnav a:hover {
  background: #fff;
  border-color: var(--accent-soft);
  color: var(--ink);
}
/* Jumping to a heading otherwise pins it flush to the viewport top. */
.sechead h2 { scroll-margin-top: 28px; }

/* --- Cards ---------------------------------------------------------------- */

.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 44px var(--gap); }

.card {
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: 16px;
  transition: box-shadow .2s, transform .2s;
}
.card:hover { box-shadow: 0 8px 24px rgba(60,45,25,.10); transform: translateY(-2px); }

/* The mat: every image sits in the same frame, whatever it depicts.
 * The frame owns the aspect ratio and the image fills it. Note the explicit
 * height:100% — img width/height HTML attributes are presentational hints,
 * and without an author height they win over aspect-ratio, which makes
 * images render at intrinsic size and blows the grid apart. */
.shot {
  background: #EFEAE0;
  border: 1px solid rgba(0,0,0,.08);
  overflow: hidden;
  aspect-ratio: 16/10;
}
.shot img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The mat doubles as the card's link to its project page. An anchor is inline by
   default, which would drop the aspect-ratio, so it has to be a block. It also
   carries tabindex="-1": the card heading already links to the same page, and a
   second tab stop per card would double the keyboard cost of the grid for no
   new destination. The alt text stays readable to assistive tech. */
a.shot { display: block; text-decoration: none; }

/* Code-panel card (Twinelike — a library has nothing to screenshot) */
.shot.code {
  background: #1E1B17;
  border-color: #1E1B17;
  display: flex;
  align-items: center;
}
.shot.code pre {
  margin: 0;
  padding: 0 20px;
  font: 12px/1.75 var(--mono);
  color: #D6C9B2;
  white-space: pre;
  overflow-x: auto;
}
.shot.code .k { color: #BFA07A; }

.card h3 {
  font-family: var(--serif);
  font-size: 22px;
  font-weight: 400;
  margin: 18px 0 8px;
  letter-spacing: -.005em;
}
.card h3 a { color: var(--ink); text-decoration: none; }
.card:hover h3 a { color: var(--accent); }
.card p { margin: 0 0 12px; color: var(--body); font-size: 15.5px; }

.meta {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .06em;
  color: var(--muted);
  border-top: 1px solid var(--rule);
  padding-top: 10px;
  line-height: 1.75;
}
.meta b { font-weight: 400; color: var(--accent); }

.credit {
  display: block;
  font-family: var(--sans);
  font-size: 11.5px;
  letter-spacing: 0;
  color: var(--muted);
  margin-top: 6px;
  opacity: .85;
}

.card.wide {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 26px;
  align-items: stretch;
}
.card.wide .shot.code { aspect-ratio: auto; min-height: 100%; padding: 30px 0; }
.card.wide .body { display: flex; flex-direction: column; justify-content: center; }
.card.wide h3 { margin-top: 0; }

.projectlinks { margin: 0 0 12px; font-size: 14px; }
.projectlinks a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.projectlinks a:hover { border-bottom-color: var(--accent); }
.projectlinks span { color: var(--rule); margin: 0 8px; }

/* --- Portfolio tile (home) ------------------------------------------------ */

a.tile {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
  align-items: center;
  color: inherit;
  text-decoration: none;
  padding: 18px;
}
.rotator {
  position: relative;
  aspect-ratio: 16/10;
  overflow: hidden;
  background: #EFEAE0;
  border: 1px solid rgba(0,0,0,.08);
}
.rotator img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .9s ease;
}
.rotator img.on { opacity: 1; }
.rotator .cap {
  position: absolute;
  left: 0; bottom: 0;
  z-index: 2;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .08em;
  color: #F4EEE2;
  background: rgba(26,23,20,.72);
  padding: 6px 12px;
}
a.tile h3 {
  font-family: var(--serif);
  font-size: 30px;
  font-weight: 400;
  margin: 0 0 12px;
  letter-spacing: -.01em;
}
a.tile .arrow { color: var(--accent); transition: margin-left .2s; display: inline-block; }
a.tile:hover .arrow { margin-left: 6px; }
a.tile p { margin: 0 0 14px; color: var(--body); font-size: 16px; }

/* --- Inline link ---------------------------------------------------------- */

.more {
  display: inline-block;
  margin-top: 36px;
  font-size: 15px;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 3px;
}
.more:hover { border-bottom-color: var(--accent); }

/* --- Writing -------------------------------------------------------------- */

.two { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); }
.pub { background: var(--panel); border: 1px solid var(--rule); padding: 26px 28px; }
.pub .tag {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--accent);
}
.pub h3 { font-family: var(--serif); font-size: 21px; font-weight: 400; margin: 10px 0 8px; }
.pub p { margin: 0 0 14px; color: var(--body); font-size: 15.5px; }
.pub a.out { font-size: 15px; text-decoration: none; border-bottom: 1px solid var(--rule); }
.pub a.out:hover { border-bottom-color: var(--accent); }

.picks { margin-top: 44px; }
.picks ul { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--rule); }
.picks li { border-bottom: 1px solid var(--rule); padding: 16px 0; }
.picks a { font-size: 17px; text-decoration: none; color: var(--ink); }
.picks a:hover { color: var(--accent); }
.picks .where {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  display: block;
  margin-top: 4px;
}

/* --- Book ----------------------------------------------------------------- */

.book {
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: 34px;
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 52px;
  align-items: center;
}
.cover {
  background: #fff;
  padding: 9px;
  border: 1px solid rgba(0,0,0,.10);
  box-shadow: 0 3px 10px rgba(60,45,25,.10);
}
.cover img { display: block; width: 100%; height: auto; }
.book h3 { font-family: var(--serif); font-size: 28px; font-weight: 400; margin: 0 0 14px; line-height: 1.24; }
.book p { margin: 0 0 16px; color: var(--body); max-width: 640px; }

.bookpage { display: grid; grid-template-columns: 300px 1fr; gap: 56px; align-items: start; margin-top: 8px; }
.bookpage .cover { position: sticky; top: 40px; }
.bookpage h2 { font-family: var(--serif); font-size: 26px; font-weight: 400; margin: 40px 0 14px; }
.bookpage p { color: var(--body); max-width: var(--measure); margin: 0 0 18px; }
.bookpage .first { font-size: 18px; }

.cta {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  padding: 15px 28px;
  font-size: 16px;
  margin: 8px 0 0;
  transition: background .18s;
}
.cta:hover { background: var(--accent); color: #fff; }
.cta-secondary {
  display: inline-block;
  margin: 8px 0 0 20px;
  font-size: 15px;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 3px;
}
.cta-secondary:hover { border-bottom-color: var(--accent); }

.bookshot { margin: 44px 0 0; }
.bookshot .shot { aspect-ratio: 3/2; }
.bookshot figcaption {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: .05em;
  margin-top: 10px;
}

/* --- Project pages ---------------------------------------------------------
   One page per featured project, linked from the portfolio card headings.
   Reuses the card mat (.shot) for the hero so artwork is framed exactly as it
   is in the grid, and .prose for the body copy.
   -------------------------------------------------------------------------- */

.crumb {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  margin: 0 0 24px;
}
.crumb a { color: var(--muted); text-decoration: none; }
.crumb a:hover { color: var(--accent); }

.projecthero { margin: 0; }
/* Landscape sources (most key art) fill the same 16/10 mat as the cards. */
.projecthero .shot { aspect-ratio: 16/10; }
/* Near-square sources — Trollhunters is 640×520 — lose their subject to
   side-cropping in a 16/10 slot. */
.projecthero .shot.tall { aspect-ratio: 4/3; }
/* Square art (Sunrise Roots is 900×900) surrenders a quarter of its height even
   to a 4/3 mat, which is where the characters are. */
.projecthero.square { max-width: 620px; }
.projecthero.square .shot { aspect-ratio: 1/1; }
/* Portrait art can't fill a wide mat without surrendering most of its height,
   so cap the frame and let it keep its own proportions instead. Deliberately
   not called .portrait — that name already belongs to the About headshot. */
.projecthero.upright { max-width: 420px; }
.projecthero.upright .shot { aspect-ratio: 2/3; }
/* Small sources. Some titles left behind nothing but a 640px promo composite,
   and the default hero is as wide as the column — which would scale that up
   nearly twice its size and show every pixel of it. Cap the frame instead. */
.projecthero.compact { max-width: 480px; }
.projecthero.compact .shot { aspect-ratio: 4/3; }
/* Title cards and wordmarks. Several of the smaller Toggo titles left behind
   nothing but their lettering — a logo on a banner, often on transparency.
   Those can't be cropped to fill a mat, because the subject IS the lettering
   and a filling crop eats it. Shallow mat, art contained inside it with room
   to breathe, leftover space reading as mount board. */
.projecthero.wordmark { max-width: 520px; }
.projecthero.wordmark .shot {
  aspect-ratio: 2/1;
  display: flex;
  align-items: center;
  justify-content: center;
}
.projecthero.wordmark .shot img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 26px;
}
/* The hero runs far wider than a card, so the code panel can afford to breathe.
   It must also drop the 16/10 mat — at full hero width that is a ~720px tall
   panel holding five lines of code, i.e. mostly a black rectangle. Size it to
   the snippet instead, the same way the wide card does. */
.projecthero .shot.code { aspect-ratio: auto; padding: 44px 0; }
.projecthero .shot.code pre { font-size: 13.5px; padding: 0 32px; }
.projecthero figcaption {
  font-family: var(--sans);
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 10px;
  opacity: .85;
}

/* The artwork shares the grid with the copy rather than sitting above it, so
   the factsheet starts level with the image. Most media here is narrower than
   the column — a 400px carousel, a wordmark plate — which used to leave the
   whole right-hand side empty until the prose began, pushing Role and Studio
   the better part of a screen down the page. The factsheet spans every row so
   it can still stick as the copy scrolls past it. */
.projectbody {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 290px;
  gap: 56px;
  align-items: start;
}
.projectbody > .carousel,
.projectbody > .projecthero,
.projectbody > .prose { grid-column: 1; }
.projectbody > .factsheet { grid-column: 2; grid-row: 1 / -1; }
.projectbody .prose h2 {
  font-family: var(--serif);
  font-size: 25px;
  font-weight: 400;
  margin: 38px 0 12px;
}
.projectbody .prose h2:first-child { margin-top: 0; }
.projectbody .prose .first { font-size: 18px; }

/* Inline identifiers in the body copy — API names, macros. The tint keeps them
   distinct without the boxed-in look of a border. */
.prose code {
  font-family: var(--mono);
  font-size: .88em;
  background: rgba(169,139,105,.12);
  /* Kept tight horizontally: these sit mid-sentence, and wider padding opens a
     visible gap before a following comma. */
  padding: 1px 3px;
}

/* --- Media carousel --------------------------------------------------------
   Screens and the gameplay clip in one frame, for a project with more than a
   single image worth showing. Takes the hero's place at the top of the page.

   Built on scroll-snap, so the component works with no JavaScript at all: the
   track is a horizontally scrollable strip that swipes on touch and scrolls
   from the keyboard once focused. Script only adds the arrows and the dots,
   which is why those ship `hidden` in the markup — controls that move nothing
   are worse than no controls.

   The mat holds the game's own aspect ratio, and the media is contained inside
   it rather than cropped to fill it as every other .shot is. A carousel is the
   one place a visitor has gone looking for the whole frame, and these sources
   disagree about shape anyway — 9:16 for the clip and the phone captures, 2:3
   for the two screenshots — so a filling crop would take the logo off the top
   of the title screen. What's left over reads as mat, in the mat's own colour.

   No autoplay on the clip: the site makes no external requests and stays light,
   so `preload="metadata"` means the file costs a few hundred bytes until
   somebody actually presses play. It also sidesteps prefers-reduced-motion,
   which an autoplaying loop would override with no way to opt out in CSS.
   -------------------------------------------------------------------------- */

.carousel { max-width: 400px; }

.carousel-track {
  display: flex;
  gap: var(--gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Programmatic scrolls read this, so the reduced-motion block below turns the
     arrows into hard cuts without the script knowing anything about it. */
  scroll-behavior: smooth;
}
/* The scrollbar is the only affordance a script-less visitor gets, so it stays
   put until the dots and arrows are actually there to replace it. */
.carousel.enhanced .carousel-track { scrollbar-width: none; }
.carousel.enhanced .carousel-track::-webkit-scrollbar { display: none; }

.carousel-slide {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: center;
  margin: 0;
}
.carousel-slide .shot {
  aspect-ratio: 9/16;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Desktop and 16:9 captures. Same component, mat turned on its side, and wide
   enough that a landscape frame isn't smaller than the portrait one. */
.carousel.wide { max-width: 640px; }
.carousel.wide .carousel-slide .shot { aspect-ratio: 16/9; }
.carousel-slide .shot img,
.carousel-slide .shot video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.carousel-slide figcaption {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: .05em;
  margin-top: 10px;
}

.carousel-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 16px;
}
.carousel-controls button {
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  padding: 0;
}
.carousel-arrow {
  width: 34px;
  height: 34px;
  font-size: 15px;
  line-height: 1;
  background: var(--panel);
  border: 1px solid var(--rule);
  transition: background .18s, border-color .18s, color .18s;
}
.carousel-arrow:hover:not(:disabled) {
  background: #fff;
  border-color: var(--accent-soft);
  color: var(--ink);
}
.carousel-arrow:disabled { opacity: .3; cursor: default; }

/* A 9px dot is a 9px target. The button is 24px square and transparent; the
   mark is drawn inside it, so the dot stays small and the hit area doesn't. */
.carousel-dots { display: flex; }
.carousel-dots button {
  width: 24px;
  height: 24px;
  padding: 7px;
  background: none;
  border: 0;
}
.carousel-dots button::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  border: 1px solid var(--accent-soft);
  border-radius: 50%;
  transition: background .18s;
}
.carousel-dots button[aria-current="true"]::before { background: var(--accent-soft); }

.carousel-credit {
  font-family: var(--sans);
  font-size: 11.5px;
  color: var(--muted);
  margin: 14px 0 0;
  opacity: .85;
}

.factsheet {
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: 24px 26px;
  position: sticky;
  top: 40px;
}
.factsheet dl { margin: 0; }
.factsheet dt {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
}
.factsheet dd { margin: 4px 0 18px; font-size: 15px; color: var(--ink); }
.factsheet dd:last-child { margin-bottom: 0; }
.factsheet a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.factsheet a:hover { border-bottom-color: var(--accent); }
.factsheet .stack a { display: block; margin-bottom: 7px; }
.factsheet .stack a:last-child { margin-bottom: 0; }

/* --- Archive -------------------------------------------------------------- */

.archive-group { margin-bottom: 60px; }
.archive-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 10px;
  margin-bottom: 4px;
  flex-wrap: wrap;
}
.archive-head h2 { font-family: var(--serif); font-size: 22px; font-weight: 400; margin: 0; }
.archive-head .period,
.archive-head .role {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .07em;
  color: var(--muted);
}
.archive-head .period { color: var(--accent); }
.archive-note { color: var(--muted); font-size: 14.5px; margin: 14px 0 6px; max-width: var(--measure); }

.archive-row {
  display: grid;
  grid-template-columns: 72px 1fr 190px 84px;
  gap: 20px;
  padding: 13px 0;
  border-bottom: 1px solid var(--rule-soft);
  align-items: baseline;
}

/* Title card / logo, where one exists. Only about half the archive has artwork
   — the JS utility repos never will — so the slot is always reserved and simply
   sits empty otherwise, which keeps the four columns aligned down the page.
   `contain`, not `cover`: these are logos, and cropping one cuts its lettering. */
.archive-row .logo {
  align-self: center;
  display: block;
  width: 72px;
  height: 44px;
}
.archive-row .logo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.archive-row:hover { background: rgba(169,139,105,.06); }
.archive-row .t { font-size: 16px; }

/* Only 9 of the 50 rows have a project page behind them. The row-level hover
   fires on all of them, so the title tinting on hover is what actually tells
   you this one is a link — same signal the portfolio cards use. */
.archive-row .t a { color: inherit; text-decoration: none; }
.archive-row:hover .t a { color: var(--accent); }
.archive-row .logo a { display: block; height: 100%; }
.archive-row .t small { display: block; color: var(--muted); font-size: 13px; margin-top: 2px; }
.archive-row .tech,
.archive-row .yr {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .05em;
  color: var(--muted);
}
.archive-row .yr { text-align: right; }

/* --- About ---------------------------------------------------------------- */

.about-top { display: grid; grid-template-columns: 260px 1fr; gap: 48px; align-items: start; }
.portrait { background: #fff; padding: 10px; border: 1px solid rgba(0,0,0,.10); box-shadow: 0 3px 10px rgba(60,45,25,.08); }
.portrait img { display: block; width: 100%; height: auto; }
.prose { max-width: var(--measure); }
.prose p { color: var(--body); margin: 0 0 18px; }
.prose a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.prose a:hover { border-bottom-color: var(--accent); }

/* --- Dated entry rows (about, writing) ------------------------------------ */

.entry { border-top: 1px solid var(--rule); padding: 20px 0; display: grid; grid-template-columns: 172px 1fr; gap: 26px; }
.entry .when { font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em; color: var(--accent); padding-top: 3px; }
.entry h3 { font-size: 17px; font-weight: 600; margin: 0 0 5px; }
.entry p { margin: 0; color: var(--body); font-size: 15.5px; max-width: 620px; }
.entry .where { color: var(--muted); font-weight: 400; }
.entry a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.entry a:hover { border-bottom-color: var(--accent); }

/* --- About, continued ----------------------------------------------------- */

.contact { margin-top: 20px; }
.contact .row { display: flex; gap: 12px; flex-wrap: wrap; align-items: baseline; margin-bottom: 12px; }
.contact .k {
  font-family: var(--mono);
  font-size: 11.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 88px;
}

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

.foot {
  margin-top: 92px;
  border-top: 1px solid var(--rule);
  padding: 30px 0 68px;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 26px;
  align-items: center;
  font-size: 14px;
}
.foot a { color: var(--muted); text-decoration: none; }
.foot a:hover { color: var(--accent); }

/* Two groups, not one long row of links. Wide, they sit at opposite ends; when
   the footer runs out of room each group wraps whole instead of fragmenting
   across the break. That keeps the split working at any label widths, so there
   is no spacer element to place and no breakpoint to keep in step with them —
   an earlier version pinned it at 700px and was wrong on both sides of it. */
.foot-social,
.foot-site {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 26px;
}

/* Consent withdrawal. A real button, not a link — it performs an action rather
   than navigating — styled to sit in the footer row. Hidden unless consent.js
   runs, since without JS there is nothing for it to reset. */
.foot .linklike {
  font: inherit;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.foot .linklike:hover { color: var(--accent); }
.foot .linklike[hidden] { display: none; }

/* --- Consent banner ------------------------------------------------------- */

.consent {
  position: fixed;
  left: 20px; right: 20px; bottom: 20px;
  z-index: 90;
  max-width: 620px;
  background: var(--ink);
  color: #F1EBDF;
  padding: 20px 22px;
  display: none;
  box-shadow: 0 8px 30px rgba(0,0,0,.25);
  font-size: 14.5px;
  line-height: 1.55;
}
.consent[data-open="true"] { display: block; }
.consent p { margin: 0 0 14px; }
.consent a { color: var(--accent-soft); }
.consent .actions { display: flex; gap: 12px; flex-wrap: wrap; }
.consent button {
  font: inherit;
  font-size: 14px;
  padding: 9px 18px;
  border: 1px solid rgba(255,255,255,.35);
  background: transparent;
  color: #F1EBDF;
  cursor: pointer;
}
.consent button.primary { background: #F1EBDF; color: var(--ink); border-color: #F1EBDF; }
.consent button:hover { border-color: var(--accent-soft); }
.consent button.primary:hover { background: var(--accent-soft); border-color: var(--accent-soft); color: var(--ink); }
.consent :focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 2px; }

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

@media (max-width: 1080px) {
  /* Shrink the cover before the panel stacks, so the blurb keeps a usable
     column instead of being squeezed to ~48ch. */
  .book { grid-template-columns: 220px 1fr; gap: 40px; }
  .masthead-top { grid-template-columns: minmax(0, 1fr) 430px; gap: 40px; }
  .bookad { padding: 22px 24px; grid-template-columns: 142px 1fr; gap: 24px; }
}

@media (max-width: 900px) {
  .bookpage { grid-template-columns: 1fr; gap: 32px; }
  .bookpage .cover { position: static; max-width: 240px; }
  .about-top { grid-template-columns: 1fr; gap: 28px; }
  .portrait { max-width: 220px; }
  /* The factsheet drops below the copy, so pinning it would strand it. */
  .projectbody { grid-template-columns: 1fr; gap: 36px; }
  .factsheet { position: static; }
  /* Single column: let everything fall back into source order. */
  .projectbody > .carousel,
  .projectbody > .projecthero,
  .projectbody > .prose,
  .projectbody > .factsheet { grid-column: auto; grid-row: auto; }
}

@media (max-width: 860px) {
  .wrap { padding: 0 22px; }
  .grid, .two { grid-template-columns: 1fr; }
  .card.wide { grid-template-columns: 1fr; }
  a.tile { grid-template-columns: 1fr; gap: 22px; }
  .book { grid-template-columns: 1fr; gap: 24px; padding: 24px; }
  /* Stacked, the cover would otherwise stretch to the full panel width and
     stand ~1000px tall. Matches the cap on .bookpage .cover. */
  .book .cover { max-width: 240px; }
  .pagehead { padding-top: 52px; }
  /* Stacked: the promo drops below the hero and goes side-by-side internally. */
  .masthead-top { grid-template-columns: 1fr; gap: 30px; padding-top: 52px; }
  .bookad { grid-template-columns: 156px 1fr; gap: 26px; }
  .masthead { padding-top: 48px; }
  .entry { grid-template-columns: 1fr; gap: 8px; }
  .archive-row { grid-template-columns: 56px 1fr 68px; gap: 12px; }
  .archive-row .logo { width: 56px; height: 34px; }
  /* Drops to its own line under the logo and title. */
  .archive-row .tech { grid-column: 1 / -1; }
}

@media (max-width: 480px) {
  /* Below this the side-by-side split starves the blurb — a fixed book column
     plus gap leaves it under 20 characters wide. Stack instead, and cap the
     book so it doesn't grow to the full panel width. */
  .bookad { grid-template-columns: 1fr; gap: 20px; }
  .bookad-cover { width: 150px; }
}

@media (max-width: 420px) {
  .sechead h2 { font-size: 25px; }
  .nav { gap: 20px; }
  .card { padding: 12px; }
  .consent { left: 12px; right: 12px; bottom: 12px; padding: 18px; }
}

/* --- Reduced motion -------------------------------------------------------
   The rotator keeps cycling; only the crossfade collapses to a hard cut.
   Gating the feature off entirely would hide it from anyone browsing with
   reduced motion on — including the site's owner.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  .rotator img { transition: none; }
  .card, a.tile .arrow, .cta, .sectionnav a { transition: none; }
  .card:hover { transform: none; }
  /* The `*` rule above already resets scroll-behavior, so the carousel arrows
     jump straight to the next slide instead of gliding to it. */
  .carousel-arrow, .carousel-dots button::before { transition: none; }
}

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

@media print {
  .nav, .sectionnav, .consent, .foot, .carousel-controls { display: none; }
  body { background: #fff; color: #000; }
  .card, .pub, .book, .bookad { border-color: #ccc; box-shadow: none; }
  /* Paper doesn't scroll sideways: unstack the strip so every slide prints. */
  .carousel { max-width: 300px; }
  .carousel-track { display: block; overflow: visible; }
  .carousel-slide + .carousel-slide { margin-top: 24px; }
}
