/* =========================================================
   FOUNDATION — Simple-Friendly tokens for aesv.io
   Cream paper · ink borders · cartoon shadows · halftone
   =========================================================

   How sizing works on this site (read once, then never wonder
   again):

   1. Type and most layout sizes are in rem so browser zoom
      rides them coherently. Otherwise everything is fixed —
      type doesn't grow at wider viewports.
   2. The shell caps at --shell-max (wide enough for ~4 card
      columns) and centers via margin-inline. At narrower
      widths it fills naturally; at wider widths the empty
      space sits as paper on either side, but the cap is loose
      enough that a 4K monitor still actually uses the screen.
   3. Reading prose narrows further to --reading-default (66ch)
      within the centered shell. Card grids use auto-fit with a
      capped column width so they tile MORE cards at wider
      widths instead of stretching each card.

       wrist      ≤ 280px   html @ 13px   riso off, footer gone
       watch    281–359px   html @ 14px   ≥44px taps, footer gone
       default  ≥ 360px     html @ 16px   the canonical site

   The dashboard at / deliberately opts out of --shell-max so
   the bento spreads to the viewport edges; that's intentional,
   not a bug — see dashboard.css.

   Container queries: tile labels in the bento scale with cqi
   against their cell (which is itself capped). See the .tile
   / .widget-cell declarations in dashboard.css.
   ========================================================= */

/* Responsive utilities — must precede any other rule per CSS spec. */
@import url('/css/_responsive-utilities.css');

@font-face {
  font-family: 'Birdie';
  src:
    url('/fonts/birdie-regular.woff2') format('woff2'),
    url('/fonts/birdie-regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* The html element anchors the entire scale. Every rem value below
   tracks this one number; tier media queries at the bottom of the file
   change ONLY this. Defaults to 16px (browser default) for the canonical
   360–2559px range. */
html {
  font-size: 100%;
}

:root {
  /* Palette. Every color used on the site is in this list — no
     in-line hex values, no per-component palettes. Add a token
     here before you use a new color. */
  --ink: #1a1a1a;
  --ink-mute: #5c5650;
  --cream: #f4ecd8;
  --cream-deep: #e8dfc8;
  --offwhite: #faf6f2;
  --lavender: #6e6291;
  --brick: #a84a3c;
  --ochre: #c8924a;
  --sage: #6b8576;

  /* Semantic aliases — let component CSS speak in roles
     (background, foreground, accent) instead of concrete colors
     so a future theme is one set of overrides. */
  --bg: var(--cream);
  --fg: var(--ink);
  --fg-mute: var(--ink-mute);
  --accent: var(--lavender);
  --accent-fg: var(--offwhite);
  --border: var(--ink);

  /* Type families. Birdie is the display + body face; the alias
     pair stays so a future swap to a separate body face is one
     token change. */
  --font-display: 'Birdie', system-ui, sans-serif;
  --font-body: 'Birdie', system-ui, sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'Menlo', 'Consolas', monospace;

  /* Type scale — rem, fixed. At the default tier (16px html)
     these resolve to the numbers in the comments; smaller at
     watch / wrist via the html font-size knob. They do NOT
     grow at wide viewports — that's the point. */
  --fs-marquee: 6rem; /* 96px */
  --fs-hero: 3.5rem; /* 56px */
  --fs-h1: 2.5rem; /* 40px */
  --fs-h2: 1.875rem; /* 30px */
  --fs-h3: 1.375rem; /* 22px */
  --fs-body: 1rem; /* 16px */
  --fs-lede: 1.125rem; /* 18px — page-head ledes */
  --fs-small: 0.875rem; /* 14px */
  --fs-eyebrow: 0.75rem; /* 12px */
  --fs-tile: 1.5rem; /* 24px */

  --lh-display: 1;
  --lh-tight: 1.15;
  --lh-body: 1.55;

  --tracking-eyebrow: 0.12em;
  --tracking-display: -0.01em;

  --border-thin: 3px solid var(--ink);
  --border-thick: 4px solid var(--ink);

  --radius-pill: 9999px;

  --shadow-sm: 3px 3px 0 0 var(--ink);
  --shadow-md: 6px 6px 0 0 var(--ink);
  --shadow-lg: 14px 14px 0 0 var(--ink);
  /* The "pressed" shadow is the absence of one — used during the
     :active state on tile-family controls so the card lands flush
     against the page after the press travel. */
  --shadow-pressed: 0 0 0 0 var(--ink);

  /* Spacing scale in rem so the whole rhythm rides the html
     font-size knob in lockstep with type. */
  --s-1: 0.5rem; /*  8px */
  --s-2: 1rem; /* 16px */
  --s-3: 1.5rem; /* 24px */
  --s-4: 2rem; /* 32px */
  --s-6: 3rem; /* 48px */
  --s-8: 4rem; /* 64px */
  --s-12: 6rem; /* 96px */

  --riso: 0.7;
  --riso-misreg: 2px;

  --ease-press: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-press: 120ms;
  --dur-wiggle: 200ms;

  /* Reading-width token. ch is font-relative so prose stays
     a comfortable measure at every tier. */
  --reading-default: 66ch;

  /* The shell. Caps wide enough for ~4 card columns to tile and
     centers via margin-inline. A 4K monitor uses most of the
     screen; a 5K ultrawide gets a sensible paper gutter rather
     than a 1200px island. Prose narrows further inside via
     --reading-default; card grids auto-fit so they tile MORE
     cards at wider widths instead of stretching each card. */
  --shell-max: 1600px;
  --shell-pad-x: 1.5rem;
  --shell-pad-y: 1.5rem;
}

/* ---------- Reset / base ---------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  padding: 0;
  overscroll-behavior: none;
}
html {
  background-color: var(--cream);
}

/* Cross-document view transitions: modern browsers (Chrome 126+) crossfade
   between same-origin documents on navigation, masking the white flash that
   appears between unload and the next page's first paint. Older browsers
   ignore the at-rule. The crossfade is short and runs after our outro
   animations have completed. */
@view-transition {
  navigation: auto;
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 180ms;
  animation-timing-function: ease-in-out;
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}
body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
  background-color: var(--cream);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  position: relative;
  isolation: isolate;
}

/* =========================================================
   RISO — paper grain + ink dots + warm color speckle
   Three layers stacked on body via ::before (fixed position so it
   covers the entire scroll, not just the initial viewport).
   Tunable via --riso (0..1) and --riso-misreg (px).
   ========================================================= */

body::before,
body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

/* Layer 1: fine fractal ink grain */
body::before {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='400' height='400'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch' seed='3'/><feColorMatrix values='0 0 0 0 0.10  0 0 0 0 0.10  0 0 0 0 0.10  0 0 0 0.65 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity: calc(var(--riso) * 0.35);
  mix-blend-mode: multiply;
}

/* Layer 2: coarse paper texture, slightly off-register */
body::after {
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='600' height='600'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.35' numOctaves='2' stitchTiles='stitch' seed='11'/><feColorMatrix values='0 0 0 0 0.522  0 0 0 0 0.475  0 0 0 0 0.627  0 0 0 1 -0.6'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='800' height='800'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.18' numOctaves='2' stitchTiles='stitch' seed='17'/><feColorMatrix values='0 0 0 0 0.784  0 0 0 0 0.572  0 0 0 0 0.290  0 0 0 1 -0.7'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size:
    600px 600px,
    800px 800px;
  background-position:
    var(--riso-misreg) var(--riso-misreg),
    0 0;
  opacity: calc(var(--riso) * 0.55);
  mix-blend-mode: multiply;
}

main,
footer,
header {
  position: relative;
  z-index: 1;
}

/* Push the footer below the fold on every page. main fills at least the
   initial viewport plus a buffer, so the big-footer never peeks on first
   paint — readers only meet it after they choose to scroll. The buffer
   covers subpixel rounding plus the iOS dvh-at-load edge case where dvh
   exactly equals the visible area and the footer's first row would
   otherwise touch the bottom edge. .dashboard already pins itself to
   100svh; this rule covers every cream-paper .subpage. */
main {
  min-height: calc(100vh + var(--shell-pad-y));
  min-height: calc(100dvh + var(--shell-pad-y));
}

/* Per-surface riso classes (cards, buttons can opt-in to extra grain) */
.riso {
  position: relative;
  isolation: isolate;
}
.riso > * {
  position: relative;
  z-index: 1;
}
.riso::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' seed='5'/><feColorMatrix values='0 0 0 0 0.10  0 0 0 0 0.10  0 0 0 0 0.10  0 0 0 0.6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity: calc(var(--riso) * 0.45);
  mix-blend-mode: multiply;
}

img {
  max-width: 100%;
  display: block;
}
a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 2px;
}
a:hover {
  color: var(--lavender);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
}
.skip-link:focus {
  left: 8px;
  top: 8px;
  padding: 8px 16px;
  background: var(--ink);
  color: var(--cream);
  z-index: 999;
}

/* Screen-reader only. Used for headings that need to exist for the
   document outline (e.g. the home-page h1) but aren't part of the
   visual design. */
.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;
}

/* ---------- Halftone overlay ---------- */
.halftone {
  position: relative;
}
.halftone::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at 1px 1px, var(--ink) 0.6px, transparent 0.6px);
  background-size: 4px 4px;
  opacity: 0.05;
  mix-blend-mode: multiply;
  z-index: 1;
}

/* ---------- Type ---------- */
.h-hero,
.h-h1,
.h-h2,
.h-h3 {
  font-family: var(--font-display);
  color: var(--ink);
  line-height: var(--lh-display);
  letter-spacing: var(--tracking-display);
  margin: 0;
}
.h-hero {
  font-size: var(--fs-hero);
}
.h-h1 {
  font-size: var(--fs-h1);
}
.h-h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
}
.h-h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-tight);
}

.t-body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.t-mute {
  color: var(--ink-mute);
}
.t-small {
  font-size: var(--fs-small);
}

.t-mono {
  font-family: var(--font-mono);
}

/* Inline code — text with inverted background. Used as a primitive
   so any element (lede, button, prose paragraph, etc.) renders inline
   <code> consistently. Block code (<pre>) is a tile, defined below. */
code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--ink);
  color: var(--cream);
  padding: 2px 6px;
  border-radius: 0;
}
.t-code {
  /* Legacy alias for inline code — same as the bare `code` rule above. */
  font-family: var(--font-mono);
  font-size: 14px;
  background: var(--ink);
  padding: 2px 8px;
  color: var(--cream);
  border-radius: 0;
}

/* ---------- Primitives ----------
   .btn, .btn-sm, .btn-primary, .btn-pill all share THE TILE chrome
   defined further down in this file. They only override sizing,
   typography, and a small set of CSS custom properties consumed by
   the tile primitive (--tile-bg, --tile-radius). */
.btn {
  /* Pin tile chrome to button defaults so a parent container that
     customises --tile-border / --tile-shadow for itself (e.g. .lv-grid
     using thick border + lg shadow) doesn't leak into nested buttons. */
  --tile-border: var(--border-thin);
  --tile-shadow: var(--shadow-md);
  --tile-shadow-hover: var(--shadow-lg);
  /* Strip native <button> chrome (the translucent grey border + grey
     button-face background browsers paint on bare <button> elements).
     All visible chrome comes from the ::before tile layer. Same reset
     .field uses above for the same reason. */
  background: transparent;
  border: 0;
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 18px;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  padding: 12px 24px;
  color: var(--ink);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
.btn:hover {
  color: var(--ink);
}
.btn-primary {
  --tile-bg: var(--lavender);
  color: var(--offwhite);
}
.btn-primary:hover {
  color: var(--offwhite);
}
.btn-pill {
  --tile-radius: var(--radius-pill);
  padding: 12px 24px;
}
.btn-sm {
  font-size: 15px;
  padding: 6px 14px;
  gap: 8px;
  letter-spacing: 0.02em;
}
/* Icon-only button — hard 36×36 square at every breakpoint. Selector
   doubles up (.btn.btn-icon) so the phone/watch tier .btn min-height
   override doesn't outrank this. Pair with aria-label for the word. */
.btn.btn-icon {
  padding: 0;
  gap: 0;
  width: 36px;
  height: 36px;
  min-width: 36px;
  min-height: 36px;
  max-height: 36px;
  flex: none;
  align-self: center;
  justify-content: center;
  font-size: 16px;
  line-height: 1;
}

/* Unified subpage shell — every cream-paper subpage uses this so
   the brand/back-link line up across pages. Caps at --shell-max
   and centers via margin-inline.

   Inside the subpage, every direct child of the page-content sits
   inside a 64rem-wide centered band (--content-max). Title, prose,
   case facts, card grids, ledgers — all anchored to the same left
   edge. The subpage is the *outer* container that centers; the
   inner band is the *content* container that everything aligns to.
   The body of this page reads at the LEFT of that band. */
.subpage {
  --subpage-gap: var(--s-8);
  --content-max: 64rem;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: var(--shell-pad-y) var(--shell-pad-x);
  display: flex;
  flex-direction: column;
  gap: var(--subpage-gap);
}

/* Every direct child of a subpage is constrained to the inner
   content band and centered horizontally. Page-specific CSS can
   widen by setting `max-width: none` or override --content-max
   per section if the content genuinely needs the full subpage
   (e.g. a full-bleed marquee).

   The site-header opts out so the brand sits at the subpage's
   leftmost edge and the back-link at its rightmost, matching the
   visual anchoring that's been there since the redesign began. */
.subpage > *:not(.site-header) {
  max-width: var(--content-max);
  margin-inline: auto;
  width: 100%;
}

/* Page hero block — title + small CTA card under it. Used on every
   subpage for visual consistency. */
.page-head {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.page-head h1 {
  font-family: var(--font-display);
  font-size: var(--fs-h1);
  line-height: 1;
  letter-spacing: -0.02em;
  margin: 0;
}
.page-head .lede {
  font-family: var(--font-body);
  font-size: var(--fs-lede);
  line-height: 1.55;
  color: var(--ink-mute);
  margin: 0;
  max-width: 60ch;
}
/* Used in .page-head AND inside content blocks (e.g. .lv-meta on
   /activities), so the rule is unscoped. Gap clears --shadow-lg
   (14px on hover) so adjacent tile shadows breathe. */
.page-cta-row {
  display: flex;
  gap: var(--s-3);
  flex-wrap: wrap;
}
.page-head .page-cta-row {
  margin-top: 4px;
}

/* /404 + /500 hero — oversized status code above the headline so the page
   reads at a glance even before the prose lands. */
.error-page .page-head {
  align-items: flex-start;
}
.error-page .error-code {
  font-family: var(--font-display);
  font-size: 8rem; /* 128px → 176px @ billboard */
  line-height: 0.85;
  letter-spacing: -0.04em;
  margin: 0;
  color: var(--ink);
}
.error-page .page-head h1 {
  margin-top: 8px;
}
.error-page .page-head .lede code {
  font-family: var(--font-mono);
  background: var(--ink);
  color: var(--cream);
  padding: 1px 6px;
}

.card {
  background: var(--cream);
  border: var(--border-thin);
  box-shadow: var(--shadow-md);
  padding: var(--s-4);
  position: relative;
}
.card-hero {
  border: var(--border-thick);
  box-shadow: var(--shadow-lg);
  padding: var(--s-6);
}

/* Tile styles live in dashboard.css — single rule, single label. */

/* ---------- Widget sticker (no card chrome — just shadow on the visual) ---------- */
.widget {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.widget-art {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  box-shadow: var(--shadow-md);
  border: var(--border-thin);
  background: var(--cream);
  overflow: hidden;
  position: relative;
}
.widget-art img,
.widget-art svg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.widget-caption {
  font-family: var(--font-display);
  font-size: 14px;
  color: var(--ink);
  line-height: 1.3;
  word-wrap: break-word;
}
.widget-caption strong {
  font-weight: 700;
}
.widget-caption .meta {
  color: var(--ink-mute);
  display: block;
  margin-top: 2px;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------- Field — form input
   Reads its chrome from THE TILE primitive (foundation), so it
   matches every other cream-paper card-shape. Focus state lifts
   the wrapper and grows ::before's shadow, same magnitudes as
   tile hover. */
.field {
  font-family: var(--font-display);
  font-size: 17px;
  padding: 12px 16px;
  color: var(--ink);
  outline: none;
  background: transparent;
  border: none;
}
.field:focus {
  transform: translate(-2px, -2px);
}
.field:focus::before {
  box-shadow: var(--shadow-lg);
}

/* ---------- Pill — universal small tag/label
   Used for entry tags, category labels, anywhere a short word
   needs a bordered pill chrome. Title case (capitalize), not
   all-caps; the tiny-uppercase eyebrow look is forbidden site-wide. */
.pill {
  display: inline-block;
  font-family: var(--font-display);
  font-size: 13px;
  line-height: 1.2;
  text-transform: capitalize;
  letter-spacing: 0;
  padding: 3px 10px;
  border: 2px solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--cream);
  color: var(--ink);
}

/* Section heading — shared by every subpage's h2 (work / activities /
   music / etc.). Defined once here so titles render identically across
   pages; per-page CSS only handles layout (gap, columns). */
:where(.work-section, .act-section, .music-section) {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
:where(.work-section, .act-section, .music-section) > h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  line-height: var(--lh-tight);
  margin: 0;
  letter-spacing: var(--tracking-display);
  color: var(--ink);
}

/* ------------------------------------------------------------
   THE TILE — every cream-paper card-shape on the site.

   CHROME LIST (gets ::before chrome layer): every element below.
   PRESS LIST (lifts on hover): the clickable subset only.

   The pattern: chrome (cream paper, ink border, cartoon shadow)
   lives on a ::before pseudo-element. Clickable tiles also
   translate -2px on hover and +6px on active, while ::before's
   shadow grows --shadow-md → --shadow-lg.

   Per-component CSS files are NOT allowed to set background,
   border, or box-shadow on these elements. Variant tweaks go
   through these custom properties:

     --tile-bg       background color of the chrome (default cream)
     --tile-border   border style (default --border-thin)
     --tile-radius   border-radius (default 0; pill variants set it)
     --tile-shadow   resting shadow (default --shadow-md)
     --tile-shadow-hover  hover shadow (default --shadow-lg)

   .album-link / .album-art on /music keeps a real-element variant
   of this pattern (the album art is meaningful content, not chrome)
   but matches the same magnitudes. */

/* CHROME LIST — every element with a ::before chrome layer. Static
   surfaces (figures, audiences, info cards, etc.) join the same list
   so every cream-paper card on the site shares one source of truth. */
.tile,
.widget-cell.spotify,
.widget-cell.hobbies,
.case-card,
.note-card,
.about-section.is-url,
.btn,
.back-link,
.view-chip,
.ledger-link,
.track-link,
.field,
.prose pre,
.surface-press,
.figure,
.sweep,
.aphorism,
.pictogram-card,
.audience,
.case-fact,
.service,
.contact-block,
.lv-grid,
.lv-board,
.empty-state,
.feed-controls {
  position: relative;
  isolation: isolate;
}

.tile::before,
.widget-cell.spotify::before,
.widget-cell.hobbies::before,
.case-card::before,
.note-card::before,
.about-section.is-url::before,
.btn::before,
.back-link::before,
.view-chip::before,
.ledger-link::before,
.track-link::before,
.field::before,
.prose pre::before,
.surface-press::before,
.figure::before,
.sweep::before,
.aphorism::before,
.pictogram-card::before,
.audience::before,
.case-fact::before,
.service::before,
.contact-block::before,
.lv-grid::before,
.lv-board::before,
.empty-state::before,
.feed-controls::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--tile-bg, var(--cream));
  border: var(--tile-border, var(--border-thin));
  border-radius: var(--tile-radius, 0);
  box-shadow: var(--tile-shadow, var(--shadow-md));
  transition: box-shadow var(--dur-press) var(--ease-press);
}

/* PRESS LIST — clickable subset that lifts on hover and presses
   on active. .field has its own focus state (defined by the field
   primitive above); .prose pre is static (block of code, no press). */
.tile,
.widget-cell.spotify,
.widget-cell.hobbies,
.case-card,
.note-card,
.about-section.is-url,
.btn,
.back-link,
.view-chip,
.ledger-link,
.track-link,
.surface-press {
  transition: transform var(--dur-press) var(--ease-press);
}

.tile:hover,
.widget-cell.spotify:hover,
.widget-cell.hobbies:hover,
.case-card:hover,
.note-card:hover,
.about-section.is-url:hover,
.btn:hover,
.back-link:hover,
.view-chip:hover,
.ledger-link:hover,
.track-link:hover,
.surface-press:hover {
  transform: translate(-2px, -2px);
}
.tile:hover::before,
.widget-cell.spotify:hover::before,
.widget-cell.hobbies:hover::before,
.case-card:hover::before,
.note-card:hover::before,
.about-section.is-url:hover::before,
.btn:hover::before,
.back-link:hover::before,
.view-chip:hover::before,
.ledger-link:hover::before,
.track-link:hover::before,
.surface-press:hover::before {
  box-shadow: var(--tile-shadow-hover, var(--shadow-lg));
}

.tile:active,
.widget-cell.spotify:active,
.widget-cell.hobbies:active,
.case-card:active,
.note-card:active,
.about-section.is-url:active,
.btn:active,
.back-link:active,
.view-chip:active,
.ledger-link:active,
.track-link:active,
.surface-press:active {
  transform: translate(6px, 6px);
}
.tile:active::before,
.widget-cell.spotify:active::before,
.widget-cell.hobbies:active::before,
.case-card:active::before,
.note-card:active::before,
.about-section.is-url:active::before,
.btn:active::before,
.back-link:active::before,
.view-chip:active::before,
.ledger-link:active::before,
.track-link:active::before,
.surface-press:active::before {
  box-shadow: var(--shadow-pressed);
}

/* ------------------------------------------------------------
   PROSE — long-form markdown body (essays, case studies, about
   page). Promoted from feed.css so any future content surface
   gets the same typography for free. Sizes are fixed rem; the
   whole scale rides the html font-size knob at the tier
   boundaries (watch / wrist / billboard). */

.prose {
  font-family: var(--font-body);
  font-size: 1.0625rem; /* 17px → 23px @ billboard */
  line-height: 1.55;
  color: var(--ink);
}
.prose > * {
  margin: 0;
}
.prose > * + * {
  margin-top: 1.6em;
}
.prose h1,
.prose h2,
.prose h3 {
  font-family: var(--font-display);
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin-top: 2em;
  margin-bottom: 0.5em;
}
.prose h2 {
  font-size: 1.875rem; /* 30px → 41px @ billboard */
}
.prose h3 {
  font-size: 1.375rem; /* 22px → 30px @ billboard */
}
.prose a {
  color: var(--ink);
  text-decoration-color: var(--ink-mute);
}
.prose a:hover {
  color: var(--lavender);
}
.prose blockquote {
  border-left: 4px solid var(--ink);
  padding: 0 0 0 24px;
  margin: 1.6em 0;
  color: var(--ink-mute);
  font-style: italic;
}
.prose ul,
.prose ol {
  padding-left: 28px;
}
.prose li + li {
  margin-top: 0.4em;
}
.prose hr {
  border: 0;
  border-top: 2px dashed var(--ink-mute);
  margin: 2em 0;
}

/* In-page link arrows. Set by entry.js based on document position.
   gwern-style: ↑ for links to content above the reader's position,
   ↓ for content below. Footnote refs and backrefs are excluded —
   they have their own superscript / ↩ markers. External links keep
   the manually-typed ↗ convention used throughout the site. */
.prose a.link-up::after,
.prose a.link-down::after {
  font-family: var(--font-mono);
  font-size: 0.85em;
  color: var(--ink-mute);
  padding-left: 0.15em;
  text-decoration: none;
  display: inline-block;
  vertical-align: 0.05em;
}
.prose a.link-up::after {
  content: '↑';
}
.prose a.link-down::after {
  content: '↓';
}

/* Footnote hover preview (desktop only — entry.js feature-detects
   hover-capable pointers before wiring it up). A floating tile with
   foundation chrome that shows the footnote body without the reader
   scrolling. */
.fn-preview {
  position: absolute;
  z-index: 50;
  max-width: 28rem;
  padding: 14px 18px;
  background: var(--cream);
  border: var(--border-thin);
  box-shadow: var(--shadow-md);
  font-family: var(--font-body);
  font-size: 0.94rem;
  line-height: 1.55;
  color: var(--ink);
  opacity: 0;
  transform: translateY(-4px);
  transition:
    opacity 140ms ease,
    transform 140ms ease;
  pointer-events: auto;
}
.fn-preview.is-visible {
  opacity: 1;
  transform: translateY(0);
}
.fn-preview p {
  margin: 0;
}
.fn-preview p + p {
  margin-top: 0.7em;
}

/* Block code — a tile, with the dark-on-cream variant for syntax
   readability. The chrome comes from the tile primitive above
   (--tile-bg: var(--ink) inverts cream → ink). The <pre> element
   itself holds typography + scroll. */
.prose pre {
  --tile-bg: var(--ink);
  --tile-shadow: var(--shadow-md);
  color: var(--cream);
  padding: 1rem 1.25rem; /* 16/20 → 22/27.5 @ billboard */
  margin: 1.6em 0;
  font-family: var(--font-mono);
  font-size: 0.875rem; /* 14px → 19px @ billboard */
  line-height: 1.55;
  overflow-x: auto;
}
.prose pre code {
  /* Inline-code chrome would double up inside the <pre> tile. */
  background: transparent;
  color: inherit;
  padding: 0;
  font-size: inherit;
}

/* ---------- Layout ---------- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.5rem; /* 24px → 33px @ billboard */
}
.brand img {
  width: 2rem; /* 32px → 44px @ billboard */
  height: auto;
}
.brand:hover img {
  animation: wiggle var(--dur-wiggle) linear 1;
}

/* ---------- Site header (shared by every cream-paper page) ----------
   Bento page is the visual anchor: brand left, nav center, name
   right. Each page passes `pageEyebrow` to optionally swap the
   right-side greeting for a page-specific label. */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  margin: 0;
}
.site-nav {
  display: flex;
  gap: 1.25rem; /* 20px → 27.5px @ billboard */
  align-items: center;
  font-family: var(--font-display);
  font-size: 1rem;
  letter-spacing: 0.02em;
}
.site-nav a {
  text-decoration: none;
  color: var(--ink);
  border-bottom: 2px solid transparent;
  padding-bottom: 1px;
  text-transform: lowercase;
}
.site-nav a:hover {
  border-bottom-color: var(--lavender);
  color: var(--lavender);
}
.site-nav a[aria-current='page'] {
  border-bottom-color: var(--ink);
}
.dashboard-greeting {
  font-family: var(--font-display);
  font-size: 1.5rem; /* 24px → 33px @ billboard */
  line-height: 1.1;
  color: var(--ink);
  margin: 0;
  text-align: right;
  max-width: 60ch;
}
/* Back-link is a button: same family as .btn-sm so the header CTA on
   subpages reads as a real control, not a tertiary text link. Sized as
   a real hit target at every tier — minimum 36px tall at default, 44px
   at watch (the watch-tier rule lives in the responsive block below). */
.back-link {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  color: var(--ink);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  min-height: 36px;
  cursor: pointer;
}
.back-link:hover,
.back-link:active {
  color: var(--ink);
}
.dashboard-greeting strong {
  font-weight: 400;
}
.dashboard-greeting .meta {
  display: block;
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--ink-mute);
  margin-top: 6px;
}
@media (max-width: 640px) {
  .site-header {
    gap: 8px;
  }
  .site-nav {
    order: 3;
    flex-basis: 100%;
    flex-wrap: wrap;
    gap: 12px;
  }
}

@keyframes wiggle {
  0% {
    transform: rotate(0deg);
  }
  25% {
    transform: rotate(-3deg);
  }
  75% {
    transform: rotate(3deg);
  }
  100% {
    transform: rotate(0deg);
  }
}

/* =========================================================
   BIG FOOTER (shared by every cream-paper page)
   ========================================================= */

.big-footer {
  margin-top: var(--s-8);
  padding: var(--s-6) var(--shell-pad-x) var(--s-4);
  background: var(--ink);
  color: var(--cream);
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.big-footer::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='600' height='600'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.4' numOctaves='2' stitchTiles='stitch' seed='13'/><feColorMatrix values='0 0 0 0 0.957  0 0 0 0 0.925  0 0 0 0 0.847  0 0 0 1 -0.7'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity: 0.5;
  mix-blend-mode: screen;
}
.big-footer > * {
  position: relative;
  z-index: 1;
}
.big-footer-inner {
  width: 100%;
  max-width: var(--shell-max);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
}
@media (max-width: 640px) {
  .big-footer-inner {
    gap: var(--s-4);
  }
}
.footer-marquee {
  font-family: var(--font-display);
  font-size: var(--fs-marquee);
  line-height: 0.85;
  letter-spacing: -0.04em;
  color: var(--cream);
  margin: 0;
  overflow-wrap: anywhere;
  text-align: center;
}
.footer-marquee .accent {
  color: var(--lavender);
}
.footer-tagline {
  font-family: var(--font-display);
  font-size: 1.5rem; /* 24px → 33px @ billboard */
  line-height: 1.25;
  margin: 0;
  max-width: 36ch;
  color: var(--cream);
}
/* Footer rows — Pages and Reach me each become a full-width row with
   their items spread across via justify-content: space-between. The
   eyebrow ("Pages" / "Reach me") sits on its own line above the row
   so the items use the entire width. */
.footer-row {
  display: flex;
  flex-direction: column;
}
.footer-row-reach + .footer-row-profiles {
  margin-top: calc(var(--s-8) * -1 + 10px);
}
@media (max-width: 640px) {
  .footer-row-reach + .footer-row-profiles {
    margin-top: calc(var(--s-4) * -1 + 10px);
  }
}
.footer-row > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: var(--s-3);
}
.footer-icon-list a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.footer-icon {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  color: currentColor;
}
@media (max-width: 640px) {
  .footer-row > ul {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}
.footer-row a {
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--cream);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  padding-bottom: 1px;
}
.footer-row a:hover {
  border-bottom-color: var(--lavender);
  color: var(--lavender);
}
.footer-row li > [aria-current='page'] {
  color: rgb(from var(--cream) r g b / 0.55);
  font-family: var(--font-display);
  font-size: 1rem;
}
.footer-bottom {
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
  padding-top: var(--s-2);
  font-family: var(--font-display);
  font-size: var(--fs-small);
  letter-spacing: 0.02em;
  color: rgb(from var(--cream) r g b / 0.55);
  text-align: center;
}

/* =========================================================
   RESPONSIVE — Tier-wide defaults (cream-paper system only)
   The terminal stack (style.css, canvas.css) opts out by
   not loading foundation.css.

   The four tiers each twist exactly one knob — html font-size —
   so the whole rem scale (type, spacing, shell padding) moves in
   lockstep. Per-element overrides below are reserved for things
   that need a hard pixel floor (touch targets) or where the
   visual breaks at the extreme (footer too heavy on a watch).
   ========================================================= */

/* ---------- Container-query opt-in.
   Cards/tiles can react to their cell width independent of
   viewport. Mostly used by the bento on /. */
.tile,
.widget-cell,
.service,
.case-study {
  container-type: inline-size;
}

/* ---------- Phone tier (360–639px) — touch-target floor only. */
@media (min-width: 360px) and (max-width: 639px) {
  .btn {
    min-height: 44px;
  }
}

/* ---------- Watch tier (281–359px) ----------
   One thing per page. ≥44px taps. No heavy footer. */
@media (max-width: 359px) {
  html {
    font-size: 87.5%; /* 14px */
  }
  :root {
    --riso: 0.4;
  }
  .btn {
    min-height: 44px;
  }
  .field {
    min-height: 44px;
  }
  .back-link,
  .view-chip,
  .ledger-link {
    min-height: 44px;
    min-width: 44px;
  }
  /* Skip the heavy footer at watch — it's not load-bearing,
     it's an end-of-page marquee. Saves a screen of scroll. */
  .big-footer {
    display: none;
  }
}

/* ---------- Wrist tier (≤280px) ----------
   Goes further than watch. Smaller knob, no riso burden,
   shadows shrink so chrome doesn't overwhelm the screen,
   and overflow-x is clamped as a safety net. */
@media (max-width: 280px) {
  html {
    font-size: 81.25%; /* 13px */
  }
  :root {
    --riso: 0;
    --shadow-sm: 2px 2px 0 0 var(--ink);
    --shadow-md: 3px 3px 0 0 var(--ink);
    --shadow-lg: 4px 4px 0 0 var(--ink);
  }
  html,
  body {
    overflow-x: hidden;
  }
}

/* No billboard tier. The site is designed at the 1200px-shell
   scale and locks there: type is the same size at a 13" laptop,
   a 4K monitor, and a 5K ultrawide. Wide screens get more paper
   on the sides, not bigger letters. Reach for browser zoom if
   the room genuinely calls for it — the rem scale rides zoom
   correctly. */

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .brand:hover img {
    animation: none;
  }
}

/* ---------- Print ----------
   Strip riso (ink-saver), drop big footer, force light bg. */
@media print {
  html,
  body {
    background: #fff !important;
    color: #000 !important;
  }
  body::before,
  body::after {
    display: none !important;
  }
  .big-footer {
    display: none !important;
  }
  .btn,
  .tile,
  .case-study,
  .card {
    box-shadow: none !important;
    border: 1px solid #000 !important;
  }
  a {
    color: #000 !important;
    text-decoration: underline;
  }
  .skip-link {
    display: none;
  }
}
