
/* ============ Design tokens (copied 1:1 from treasureokure.com) ============ */
:root{
  --paper:#151513; --paper-sunk:#212120; --hover:#212120; --ink:#e9e6df; --ink-muted:#9a978f;
  --rule:#333331; --rule-strong:#e9e6df;
  --accent:#7cb873; --accent-alt:#e0718c; --accent-third:#d4ad33; --blue:#6fa8dc;
  --grid-line: color-mix(in srgb, var(--ink) 2%, transparent);
  --grain:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='340' height='1200'><filter id='s'><feTurbulence type='fractalNoise' baseFrequency='0.96 0.004' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.10  0 0 0 0 0.09  0 0 0 0 0.07  0 0 0 0.058 0'/></filter><rect width='100%25' height='100%25' filter='url(%23s)'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='f'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.09  0 0 0 0 0.08  0 0 0 0 0.06  0 0 0 0.116 0'/></filter><rect width='100%25' height='100%25' filter='url(%23f)'/></svg>");
  --grain-size: 340px 1200px, 140px 140px;

  --font-serif:"Newsreader","Times New Roman",Times,serif;
  --font-grotesk:"Sora","Helvetica Neue",Helvetica,Arial,sans-serif;

  --u:.5rem; --u2:1rem; --u3:1.5rem; --u4:2rem; --u6:3rem; --u8:4rem; --u12:6rem; --u16:8rem;

  --t--2:.688rem; --t--1:.75rem; --t-0:.9375rem; --t-1:1.0625rem; --t-2:1.4375rem;
  --t-3:1.9375rem; --t-4:2.5625rem; --t-5:3.4375rem; --t-6:4.5625rem; --t-7:6.0625rem;

  --lh-tight:.9; --lh-snug:1.15; --lh-ui:1.4; --lh-body:1.55;
  --tr-tight:-.015em; --tr-label:.12em; --tr-wide:.18em; --tr-display:-.035em;

  --page-max:1760px; --margin:1in; --gutter:1.5rem; --cols:12; --measure:68ch;
}
:root[data-theme="light"]{
  --paper:#f0f0f0; --paper-sunk:#e3e3e3; --hover:#dcdcdc; --ink:#14130f; --ink-muted:#6c6c6c;
  --rule:#d2d2d2; --rule-strong:#14130f;
  --accent:#43713d; --accent-alt:#8f0a30; --accent-third:#7a5e02; --blue:#1a5fb4;
}
@media (max-width: 900px){ :root{ --margin: 1.25rem; --cols: 6; } }

/* ============ Reset ============ */
*,::after,::before{ box-sizing:border-box; margin:0; padding:0; }
html{ background:var(--paper); font-size:16px; scroll-padding-top:var(--u12); }
body{
  color:var(--ink); font-family:var(--font-serif); font-size:var(--t-1); line-height:var(--lh-body);
  -webkit-font-smoothing:antialiased; background-color:var(--paper);
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    var(--grain);
  background-position:0 0; background-repeat:repeat; background-size:50px 50px,50px 50px,var(--grain-size);
  text-shadow: color-mix(in srgb, currentcolor 34%, transparent) 0 0 .63px;
}
/* Short pages still fill the screen: main takes the spare height,
   so the footer sits at the bottom instead of halfway up. svh, not vh, so
   mobile browser toolbars don't push the footer off-screen. */
body{ display:flex; flex-direction:column; min-height:100vh; min-height:100svh; }
main{ flex:1 0 auto; }
a{ color:inherit; }
::selection{ background:var(--blue); color:var(--paper); }
img{ max-width:100%; display:block; }

.page{ margin-inline:auto; max-width:var(--page-max); padding-inline:var(--margin); width:100%; }
.grid{ column-gap:var(--gutter); display:grid; grid-template-columns:repeat(var(--cols),minmax(0,1fr)); row-gap:0; }
.grid-rows{ row-gap:var(--u6); }
.c-3{ grid-column-end:span 3; } .c-4{ grid-column-end:span 4; }
.c-5{ grid-column-end:span 5; } .c-6{ grid-column-end:span 6; } .c-7{ grid-column-end:span 7; } .c-9{ grid-column-end:span 9; }
.start-1{ grid-column-start:1; } .start-5{ grid-column-start:5; } .start-6{ grid-column-start:6; } .start-7{ grid-column-start:7; } .start-8{ grid-column-start:8; } .start-9{ grid-column-start:9; } .start-10{ grid-column-start:10; }
@media (max-width:900px){
  .c-3,.c-4,.c-5,.c-7,.c-9{ grid-column-end:span 6; }
  .start-5,.start-6,.start-7,.start-8,.start-9,.start-10{ grid-column-start:auto; }
}
@media (max-width:600px){
  .c-3,.c-4{ grid-column-end:span 6; }
}

.label{ color:var(--ink-muted); font-family:var(--font-grotesk); font-size:var(--t--1); font-weight:500; letter-spacing:var(--tr-label); line-height:var(--lh-ui); text-transform:uppercase; }
.link{ background-image:linear-gradient(var(--blue),var(--blue)); background-position:0 100%; background-repeat:no-repeat; background-size:100% 1px; color:inherit; padding-bottom:1px; text-decoration:none; transition:background-size .15s,color .15s; }
.link:hover{ background-size:100% 2px; color:var(--blue); }
.prose{ max-width:var(--measure); }
.prose > * + *{ margin-top:var(--u3); }
.prose p{ font-family:var(--font-serif); font-size:var(--t-1); line-height:var(--lh-body); text-wrap:pretty; }

/* ============ Header ============ */
.masthead-bar{ background:var(--paper); border-bottom:1px solid var(--rule-strong); position:sticky; top:0; z-index:100; }
/* 24 + the 22px icons and switch + 24 + the 1px rule = 71px. */
.masthead-bar .bar{ align-items:center; display:flex; gap:var(--u4); padding-block:var(--u3); }
.wordmark{ color:var(--ink); font-family:var(--font-grotesk); font-size:var(--t--1); font-weight:600; letter-spacing:var(--tr-wide); text-decoration:none; text-transform:uppercase; white-space:nowrap; }
.name-faint{ font-weight:400; opacity:.6; }
.nav{ align-items:baseline; display:flex; gap:var(--u4); margin-left:auto; }
.nav-item{ align-items:baseline; border-bottom:1px solid transparent; color:var(--ink); display:flex; font-family:var(--font-grotesk); font-size:var(--t--1); font-weight:500; letter-spacing:var(--tr-label); padding-bottom:2px; text-decoration:none; text-transform:uppercase; transition:border-color .15s,color .15s; }
.nav-item:hover{ border-bottom-color:var(--ink); }
.nav-item.is-active{ border-bottom-color:var(--blue); color:var(--blue); }
/* Icon nav. 22px is exactly the height a text .nav-item occupied, so the swap
   leaves the masthead bar at its original 71px — the detailed 28px set was
   what pushed it taller. The caption is absolutely positioned and only fades
   in on hover, so it never adds height either. It stays in the DOM rather than
   being display:none, which is what gives each link its accessible name.
   Scoped with :has() so the text nav above still works if it is ever restored. */
.nav:has(.nav-icon){ align-items:center; gap:var(--u8); }
.nav-icon{ align-items:center; color:var(--ink); display:flex; flex-direction:column;
  position:relative; text-decoration:none; transition:color .15s; }
.nav-icon svg{ display:block; height:22px; width:22px; }
.nav-cap{ font-family:var(--font-grotesk); font-size:var(--t--2); font-weight:500;
  left:50%; letter-spacing:var(--tr-label); line-height:1; opacity:0;
  pointer-events:none; position:absolute; text-transform:uppercase;
  top:calc(100% + 6px); transform:translateX(-50%); transition:opacity .15s;
  white-space:nowrap; }
.nav-icon:hover,.nav-icon.is-active{ color:var(--blue); }
.nav-icon:hover .nav-cap,.nav-icon:focus-visible .nav-cap{ opacity:1; }
@media (prefers-reduced-motion:reduce){ .nav-cap{ transition:none; } }
.bar-end{ align-items:center; display:flex; gap:var(--u4); }
/* Theme switch, drawn as a line icon to match the nav: same 1.5 stroke at the
   same 1.1x scale (20 units -> 22px), round ends, no fill, the text colour,
   blue on hover. A plate with two screws, a slot, and a knob: left = light,
   right = dark, and the knob fills in when dark is on. Its position is keyed
   off data-theme on <html>, not the button's state, so it's right from the
   first paint instead of jumping once site.js runs. */
.sw{ background:none; border:0; color:var(--ink); cursor:pointer; display:inline-flex; flex:0 0 auto;
  line-height:0; padding:0; position:relative; transition:color .15s; -webkit-tap-highlight-color:transparent; }
.sw:hover{ color:var(--blue); }
.sw:focus-visible{ outline:2px solid var(--blue); outline-offset:4px; }
.sw svg{ display:block; height:22px; overflow:visible; width:auto; }
/* An invisible margin around it gives a thumb-sized tap area (42px tall)
   without drawing the switch any bigger. */
.sw::before{ content:""; inset:-10px -6px; position:absolute; }
.sw-knob{ fill:var(--paper); transform-box:fill-box; transform-origin:center;
  transition:translate .3s cubic-bezier(.34,1.56,.64,1),scale .1s,fill .15s; }
/* Dark is the default theme, so "not light" rather than "is dark": with no
   script at all the knob still agrees with the page. 12 units is the slot. */
:root:not([data-theme="light"]) .sw-knob{ fill:currentColor; translate:12px 0; }
.sw:active .sw-knob{ scale:.88; }
@media (prefers-reduced-motion:reduce){ .sw-knob{ transition:none; } }
/* Keep the switch clearly apart from the icon nav: the icons sit 64px apart,
   so 32px of bar gap + 64px here puts it 96px off the envelope. Desktop only;
   on phones the nav is hidden and the switch sits beside the burger. */
@media (min-width:901px){ .bar-end{ margin-left:var(--u8); } }
.burger{ background:none; border:0; cursor:pointer; display:none; flex-direction:column; gap:6px; height:28px; justify-content:center; width:28px; -webkit-tap-highlight-color:transparent; }
.bar-line{ background:var(--ink); display:block; height:1px; transition:transform .22s,opacity .2s; width:28px; }
.burger.open .bar-line:nth-child(1){ transform:translateY(3.5px) rotate(45deg); }
.burger.open .bar-line:nth-child(2){ transform:translateY(-3.5px) rotate(-45deg); }
.drawer{ background:var(--paper); inset:0; opacity:0; padding:var(--u16) var(--margin) var(--u6); pointer-events:none; position:fixed; transition:opacity .2s; z-index:90; }
.drawer.open{ opacity:1; pointer-events:auto; }
.drawer-link{ align-items:baseline; border-bottom:1px solid var(--rule); color:var(--ink); display:flex; font-family:var(--font-grotesk); font-size:var(--t-3); font-weight:500; gap:var(--u3); letter-spacing:var(--tr-display); padding-block:var(--u3); text-decoration:none; text-transform:uppercase; }
.drawer-link.is-active{ color:var(--blue); }
/* Phones and tablets: the switch stays in the header. Name, switch and burger
   need ~360px side by side, so from ~383px wide the name is untouched. Below
   that it scales down just enough to fit: 12px at 383, about 10.8px at 360.
   The 28px burger is the tallest thing in this bar, so 21px of padding holds
   it at 71px. */
@media (max-width:900px){
  .masthead-bar .bar{ gap:var(--u2); padding-block:21px; }
  .bar-end{ gap:var(--u2); }
  .wordmark{ font-size:min(var(--t--1), calc((100vw - 143px) / 20.2)); }
}
@media (max-width:900px){
  .nav{ display:none; }
  .bar-end{ margin-left:auto; }
  .burger{ display:flex; }
}

/* ============ Hero ============ */
.masthead{ display:flex; flex-direction:column; justify-content:flex-end; padding-block:var(--u12) var(--u3); }
@media (min-width:901px){ .masthead{ min-height:24rem; } }
@media (max-width:900px){ .masthead{ padding-block:var(--u6) var(--u8); } }
.headnote{ color:var(--blue); display:block; margin-bottom:var(--u6); }
.masthead-grid{ align-items:start; }
/* The fluid step was 6vw until the rotating word joined line 4. "authority and
   gets cited." is the widest line at 11.99px of width per 1px of type, and the
   nine-column cell is tightest at 901px, where it allows 43.9px — so 4.85vw is
   what holds that line unbroken all the way down. The 4.5625rem cap still takes
   over at ~1505px, so anything wider than that is unchanged at 73px.
   Re-breaking the four lines was measured first and makes this worse: every
   other split lengthens a different line and forces a smaller size still. */
.statement{ font-family:var(--font-grotesk); font-size:clamp(2.5625rem,4.85vw,4.5625rem); font-weight:400; letter-spacing:var(--tr-tight); line-height:1.12; }
/* Phones keep the larger step. Holding line 4 together here would cost ~16% of
   the type, and a fifth line costs a phone reader nothing. */
@media (max-width:600px){ .statement{ font-size:clamp(1.75rem,8.8vw,2.5625rem); } }
/* Rotating last word. Both words share one grid cell, so the box is as wide as
   the longer of them and nothing reflows on the swap. The full stop lives
   inside each word, and the rotator ends the line, so the slack after the
   shorter word falls at the end of a left-aligned line and never shows.
   The visible words are aria-hidden; an .sr-only copy carries one clean
   sentence, since a screen reader reading both in sequence is nonsense. */
/* The incoming word rises from below the line while the outgoing one lifts
   out above it. clip-path, not overflow, hides them outside the line: overflow
   would move the inline-grid's baseline to its bottom edge and drop the word
   off the sentence's baseline. The clip reaches a little below the box so
   descenders (the g in "gets") aren't cut, which is why the waiting word sits
   a full 110% down. */
.rot{ clip-path:inset(-.1em -.3em -.3em -.3em); display:inline-grid; vertical-align:baseline; }
.rot-word{ grid-area:1/1; opacity:0; transform:translateY(110%); transition:transform .6s cubic-bezier(.2,.7,.2,1),opacity .45s ease; }
.rot-word.is-on{ opacity:1; transform:none; }
.rot-word.is-out{ opacity:0; transform:translateY(-110%); }
/* Reduced motion: no movement, and the second word never enters the DOM flow,
   so the hero simply reads "authority and ranks." and stays there. */
@media (prefers-reduced-motion:reduce){
  .rot-word{ transform:none; transition:none; }
  .rot-word:not(.is-on){ display:none; }
}
.hero-cta{ margin-top:var(--u6); }
.hero-cta .link{ font-family:var(--font-grotesk); font-size:var(--t--1); font-weight:600; letter-spacing:var(--tr-label); text-transform:uppercase; }
.figure{ display:block; }
.figure img{ border:1px solid var(--rule); height:auto; mix-blend-mode:multiply; width:100%; }
:root[data-theme="dark"] .figure img{ mix-blend-mode:normal; }
.portrait img{ aspect-ratio:3/4; filter:grayscale(1) contrast(1.05); object-fit:cover; }
@media (max-width:900px){ .portrait{ margin-top:var(--u2); } .masthead-grid .portrait{ margin-top:var(--u6); } }
.scroll-cue{ margin-top:auto; padding-top:10rem; display:block; }
.scroll-cue svg{ display:block; color:var(--ink-muted); animation:scroll-pulse 2s ease-in-out infinite; }
@keyframes scroll-pulse{ 0%,100%{ opacity:1; transform:scale(1); } 50%{ opacity:.35; transform:scale(.82); } }
@media (prefers-reduced-motion: reduce){ .scroll-cue svg{ animation:none; } }
@media (max-width:900px){ .scroll-cue{ display:none; } }

/* ============ Sections ============ */
/* Sections are separated by space, not a rule. The masthead and colophon rules
   stay — they bracket the page rather than divide it. */
/* With no rule, space alone has to mark a new section, so the gap between
   sections must clearly beat the biggest gap inside one — the 96px above
   Receipts. 96 + 128 = 224px between sections (it was 160), a 2.3x margin. */
.section{ border-top:0; padding-block:var(--u12) var(--u16); }
/* Exception: on a case study the rule below SCOPE closes the credits block, so
   it stays — dimmed to --rule to match the row lines above it instead of
   outshining them. */
.case-head + .section{ border-top:1px solid var(--rule); }
/* A page title (ABOUT, PORTFOLIO, FINANCE…) isn't a section break; it heads the
   content right under it. So the first section after it skips the inter-section
   gap, leaving the title's own 48px margin: the same title-to-content distance
   as WHAT I DO and every other section title on the site. */
.masthead-title + .section{ padding-top:0; }
@media (max-width:900px){ .section{ padding-block:var(--u8) var(--u12); } }
.section-head{ column-gap:var(--gutter); display:grid; grid-template-columns:repeat(var(--cols),minmax(0,1fr)); padding-bottom:var(--u6); }
@media (max-width:900px){ .section-head{ padding-bottom:var(--u4); } }
/* Gochi Hand sets lighter and smaller than a brush script at the same nominal
   size, so the kickers run at --t-1 rather than the --t-0 the other labels use. */
.section-head .label{ background:none; color:var(--ink); font-family:'Gochi Hand',cursive; font-size:var(--t-1); font-weight:300; letter-spacing:normal; text-transform:none; grid-column:span 5; justify-self:start; padding:0; }
@media (max-width:900px){ .section-head .label{ grid-column:span 4; } }
@media (max-width:600px){ .section-head .label{ grid-column:1/-1; } }
:root[data-theme="light"] .section:nth-of-type(3n+1) .section-head .label{ color:var(--accent-third); }
:root[data-theme="light"] .section:nth-of-type(3n+2) .section-head .label{ color:var(--blue); }
:root[data-theme="light"] .section:nth-of-type(3n+3) .section-head .label{ color:var(--accent-alt); }
.section-title{ font-family:var(--font-grotesk); font-size:clamp(2.25rem,5.5vw,4rem); font-weight:600; letter-spacing:var(--tr-tight); line-height:var(--lh-tight); margin-bottom:var(--u6); text-transform:uppercase; }

/* ============ Receipts (record/data grid) ============ */
.record{ margin-top:var(--u12); }
.record-label{ margin-bottom:var(--u3); }
.data{ background-color:var(--paper); background-image:var(--grain); background-position:0 0; background-repeat:repeat; background-size:var(--grain-size); border:1px solid var(--rule); display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); }
.data-row{ border-left:1px solid var(--rule); border-top:1px solid var(--rule); padding:var(--u3); }
.data-row:nth-child(-n+4){ border-top:0; }
.data-row:nth-child(4n+1){ border-left:0; }
.data-row:last-child:nth-child(4n+2){ grid-column:span 3; }
.data-row:last-child:nth-child(4n+3){ grid-column:span 2; }
.data-figure{ font-family:var(--font-grotesk); font-size:var(--t-3); font-variant-numeric:tabular-nums; font-weight:600; letter-spacing:var(--tr-display); line-height:1; }
.data-label{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0); line-height:var(--lh-body); margin-top:var(--u2); max-width:34ch; }
@media (max-width:1200px){
  .data{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  .data-row:nth-child(-n+4){ border-top:1px solid var(--rule); }
  .data-row:nth-child(4n+1){ border-left:1px solid var(--rule); }
  .data-row:nth-child(-n+2){ border-top:0; }
  .data-row:nth-child(2n+1){ border-left:0; }
  .data-row:last-child:nth-child(4n+2),.data-row:last-child:nth-child(4n+3){ grid-column:auto; }
  .data-row:last-child:nth-child(2n+1){ grid-column:span 2; }
}
@media (max-width:560px){
  .data{ grid-template-columns:1fr; }
  .data-row:last-child:nth-child(n){ grid-column:auto; }
  .data-row:nth-child(n){ border-left:0; border-top:1px solid var(--rule); }
  .data-row:first-child{ border-top:0; }
}

/* ============ Index rows (services + selected work) ============ */
.index{ border-top:1px solid var(--rule); }
.index-row{ align-items:baseline; border-bottom:1px solid var(--rule); color:inherit; column-gap:var(--gutter); display:grid; grid-template-columns:repeat(var(--cols),minmax(0,1fr)); padding-block:var(--u4); text-decoration:none; transition:background-color .18s,padding-inline .18s; }
/* Rows only react when they are links; plain-text rows shouldn't look
   clickable. --hover is its own token because light mode
   needs a deeper step than --paper-sunk: dark mode's fill roughly doubles the
   background's brightness, light mode's only dims it ~12%, and that small grey
   step gets lost under the grain. #dcdcdc matches dark mode's perceived step. */
a.index-row:hover{ background:var(--hover); padding-inline:var(--u2); }
.index-num{ color:var(--ink-muted); font-family:var(--font-grotesk); font-size:var(--t--1); font-variant-numeric:tabular-nums; font-weight:500; grid-column:1/2; letter-spacing:var(--tr-label); }
.index-name{ font-family:var(--font-grotesk); font-size:var(--t-2); font-weight:500; grid-column:2/6; letter-spacing:var(--tr-tight); line-height:var(--lh-snug); }
.index-desc{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0); grid-column:6/10; line-height:var(--lh-body); }
/* Offsetting margins nudge the blurb left of its grid area without resizing it, opening a gap before the thumbnail. */
@media (min-width:901px){ .index-row:has(.case-thumb) .index-desc{ grid-column:6/9; margin-left:calc(var(--gutter) * -2); margin-right:calc(var(--gutter) * 2); } }
/* Sits in the last column, past the thumbnail, so every row ends on the same
   mark. Always visible -- it signals the row is a link, not a hover reward. */
.index-arrow{ align-self:center; color:var(--ink-muted); grid-column:12/-1; justify-self:end; transition:color .15s,transform .18s; }
a.index-row:hover .index-arrow{ color:var(--blue); transform:translateX(3px); }
.case-thumb{ align-self:center; aspect-ratio:2/1; display:block; grid-column:9/12; overflow:hidden; }
.case-thumb img{ filter:grayscale(1) contrast(1.04); height:100%; mix-blend-mode:multiply; object-fit:cover; transition:filter .3s; width:100%; }
:root[data-theme="dark"] .case-thumb img{ mix-blend-mode:normal; }
a.index-row:hover .case-thumb img{ filter:grayscale(0) contrast(1.04); }
@media (hover:none){ .case-thumb img{ filter:grayscale(0) contrast(1.04); } }
@media (max-width:900px){
  .index-row{ padding-block:var(--u3); row-gap:var(--u); }
  .index-num{ grid-column:1/2; }
  .index-desc,.index-name{ grid-column:2/-1; }
  .index-arrow{ display:none; }
  .case-thumb{ grid-column:2/-1; margin-top:var(--u2); max-width:220px; }
}
.more{ margin-top:var(--u4); }

/* ============ Services ============ */
/* Same card language as the testimonials: a faint hairline on top, the name
   in the grotesk, the body in the serif. Three across and two down on
   desktop, two across on tablets, one on phones. */
.svc-grid{ column-gap:var(--gutter); display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); row-gap:var(--u8); }
@media (max-width:1100px){ .svc-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:640px){ .svc-grid{ grid-template-columns:1fr; row-gap:var(--u6); } }
.svc-card{ border-top:1px solid var(--rule); padding-top:var(--u4); }
.svc-num{ color:var(--ink-muted); display:block; font-family:var(--font-grotesk); font-size:var(--t--1); font-variant-numeric:tabular-nums; font-weight:500; letter-spacing:var(--tr-label); margin-bottom:var(--u2); }
.svc-name{ font-family:var(--font-grotesk); font-size:var(--t-2); font-weight:500; letter-spacing:var(--tr-tight); line-height:var(--lh-snug); margin-bottom:var(--u3); }
.svc-desc{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0); line-height:var(--lh-body); }

/* ============ Testimonials ============ */
/* A row of cards that scrolls sideways: three across on desktop, two on
   tablets, one and a peek on phones (swipe). Arrows step one card. */
.rail{ --across:3; display:grid; gap:var(--gutter); grid-auto-columns:calc((100% - (var(--across) - 1) * var(--gutter)) / var(--across)); grid-auto-flow:column; overflow-x:auto; overscroll-behavior-x:contain; padding-bottom:var(--u3); scroll-snap-type:x mandatory; scrollbar-width:thin; }
@media (max-width:1100px){ .rail{ --across:2; } }
@media (max-width:640px){ .rail{ grid-auto-columns:85%; } }
.rail:focus-visible{ outline:2px solid var(--blue); outline-offset:4px; }
.rail-card{ border-top:1px solid var(--rule); padding-top:var(--u4); scroll-snap-align:start; }
/* Room for name and role even when a client has no role, so every quote
   starts at the same height. */
.rail-card figcaption{ margin-bottom:var(--u4); min-height:calc(2 * var(--t-0) * var(--lh-body)); }
.t-name{ display:block; font-family:var(--font-grotesk); font-size:var(--t-0); font-weight:500; }
.t-role{ color:var(--ink-muted); display:block; font-family:var(--font-serif); font-size:var(--t-0); }
.rail-card blockquote{ font-family:var(--font-serif); font-size:var(--t-1); line-height:var(--lh-body); }
.rail-nav{ display:flex; gap:var(--u2); justify-content:flex-end; margin-top:var(--u3); }
@media (hover:none){ .rail-nav{ display:none; } }
.rail-btn{ background:none; border:1px solid var(--rule-strong); border-radius:999px; color:var(--ink); cursor:pointer; font:inherit; height:40px; transition:color .15s,border-color .15s; width:40px; }
.rail-btn:hover{ border-color:var(--blue); color:var(--blue); }
.rail-btn:focus-visible{ outline:2px solid var(--blue); outline-offset:3px; }

/* ============ Footer ============ */
.colophon{ border-top:1px solid var(--rule-strong); margin-top:var(--u8); padding-block:var(--u4); }
@media (max-width:900px){ .colophon{ margin-top:var(--u6); } }
.colophon .label{ margin-bottom:var(--u2); }
.colophon-note{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0); line-height:var(--lh-body); max-width:34ch; }
.colophon-note + .colophon-note{ margin-top:var(--u2); }
/* "Let's work together": the invitation reads in full ink, the availability
   line stays muted. Both run the column's full width instead of the 34ch cap,
   so on desktop line one ends on "Let's talk." (the <br> is desktop-only). */
.colophon-pitch,.colophon-pitch + .colophon-note{ max-width:none; }
.colophon-pitch{ color:var(--ink); }
@media (max-width:900px){ .br-wide{ display:none; } }
.colophon-list{ display:grid; font-family:var(--font-serif); font-size:var(--t-0); line-height:var(--lh-ui); row-gap:var(--u); list-style:none; }
.colophon-list .link{ display:inline-block; }
.colophon-list.inline{ display:flex; flex-wrap:wrap; column-gap:var(--u3); row-gap:var(--u); }
.ext-arrow{ display:inline-flex; align-items:center; color:var(--ink-muted); margin-left:.2em; }
.colophon-list a{ color:inherit; text-decoration:none; transition:color .15s; }
.colophon-list a:hover,.colophon-list a:hover .ext-arrow{ color:var(--blue); }
[data-copy-email].is-copied,[data-copy-email].is-copied .ext-arrow{ color:var(--blue); }
/* Footer links carry the nav's hand-drawn icons. The arrow keeps its own small
   gap to the word, so it doesn't drift off on the flex gap. */
.icon-link{ align-items:center; display:inline-flex; gap:7px; }
.icon-link .ext-arrow{ margin-left:-3px; }
.link-icon{ flex:0 0 auto; height:18px; width:18px; }
/* Icons hang from the top of the word, not its middle. */
.icon-link .link-icon{ align-self:flex-start; }
/* Three columns: pitch, links, credit. The credit comes last in the markup so
   it lands at the very bottom once the columns stack on phones. Between 901
   and 1200px the pitch needs the full width to keep "Let's talk." on line one,
   so links and credit move to a second row. */
.colophon-credit{ align-self:start; text-align:right; }
@media (min-width:1201px){
  .colophon-say{ grid-column:1/7; }
  .colophon-links{ grid-column:7/11; }
  .colophon-credit{ grid-column:11/-1; }
}
@media (min-width:901px) and (max-width:1200px){
  .colophon-say{ grid-column:1/-1; }
  .colophon .grid > .colophon-links{ grid-column:1/9; margin-top:var(--u6); }
  .colophon .grid > .colophon-credit{ grid-column:9/-1; margin-top:var(--u6); }
}
/* The other columns open with a small-caps heading; the credit has none, so it
   drops by one heading (line + gap) to sit on their first line of text. */
@media (min-width:901px){ .colophon .grid > .colophon-credit{ padding-top:calc(var(--t--1) * var(--lh-ui) + var(--u2)); } }
@media (max-width:900px){ .colophon-say,.colophon-links,.colophon-credit{ grid-column:1/-1; } .colophon-credit{ text-align:left; } }
.colophon .grid > * + *{ margin-top:0; }
@media (max-width:900px){ .colophon .grid > * + *{ margin-top:var(--u6); } }

/* ============ About page ============ */
/* The word reuses .section-title so it matches SELECTED WORK exactly; its own
   margin-bottom sets the gap to the rule, so the masthead adds none below. */
.masthead-title{ padding-block:var(--u12) 0; }
@media (min-width:901px){ .masthead-title{ min-height:0; } }
@media (max-width:900px){ .masthead-title{ padding-block:var(--u6) 0; } }


/* Headings inside the About flow: same face and size as the body text, caps
   only. No rules, no kickers -- the page stays one continuous scroll. */
.prose-head{ font-family:var(--font-serif); font-size:var(--t-1); font-weight:400; line-height:var(--lh-body); text-transform:uppercase; }
.prose > .prose-head{ margin-top:var(--u6); }

/* Hover asides. A dotted rule says "something's here" without borrowing the
   blue underline, which everywhere else on this site means "this goes
   somewhere". Desktop-only by nature: there's no hover on a phone, and the
   text has to read fine for anyone who never sees the aside. Nothing in the
   About prose sets overflow:hidden, so the pseudo-element isn't clipped. */
.aside{ border-bottom:1px dotted var(--ink-muted); cursor:help; position:relative; }
.aside::after{
  background:var(--paper-sunk); border:1px solid var(--rule); bottom:100%; left:0;
  color:var(--ink); content:attr(data-aside); font-family:var(--font-serif);
  font-size:var(--t-0); font-style:italic; line-height:var(--lh-body);
  margin-bottom:var(--u); opacity:0; padding:var(--u) var(--u2);
  pointer-events:none; position:absolute; transition:opacity .15s;
  white-space:nowrap; z-index:10; }
.aside:hover::after,.aside:focus-visible::after{ opacity:1; }
@media (prefers-reduced-motion:reduce){ .aside::after{ transition:none; } }

/* Source is 336x420, so 4:5 — don't inherit the homepage portrait's 3:4 crop. */
.about-portrait img{ aspect-ratio:4/5; }
/* Portrait is first in the DOM so it leads on phones; on desktop both are
   pinned to row 1, since sparse auto-placement won't backfill columns 1-7
   once an explicitly-placed item has moved the cursor past them. */
@media (min-width:901px){ .about-intro,.about-portrait{ grid-row:1; } }
/* Stacked, a full-column 4:5 portrait runs ~440px tall on a phone and pushes
   the opening text past the fold. Capped so it reads as an inset, not a banner. */
@media (max-width:900px){ .about-portrait{ max-width:18rem; } }

/* ============ Portfolio page ============ */
/* Title -> rule -> rows. The section's own top border is that rule, so the
   section adds no top padding and .index drops its border to avoid a second
   line 64px below the first. Row padding supplies the gap. */
.work-index{ padding-top:0; }
.work-index .index{ border-top:0; }

/* Category index — three per ruled band, so six entries take two rows instead
   of six and nothing sits in empty columns. The third track holds the arrow and
   is reserved at rest (opacity 0, never display:none) so revealing it on hover
   can't reflow the name beside it. */
.cat-grid{ border-top:1px solid var(--rule); }
.cat-cell{ align-items:baseline; border-bottom:1px solid var(--rule); color:inherit; column-gap:var(--u2); display:grid; grid-column:span 4; grid-template-columns:auto 1fr auto; padding-block:var(--u3); text-decoration:none; transition:background-color .18s,padding-inline .18s; }
.cat-cell:hover{ background:var(--hover); padding-inline:var(--u2); }
.cat-num{ color:var(--ink-muted); font-family:var(--font-grotesk); font-size:var(--t--1); font-variant-numeric:tabular-nums; font-weight:500; grid-column:1/2; letter-spacing:var(--tr-label); }
.cat-name{ font-family:var(--font-grotesk); font-size:var(--t-1); font-weight:500; grid-column:2/3; letter-spacing:var(--tr-tight); line-height:var(--lh-snug); transition:color .15s; }
.cat-cell:hover .cat-name{ color:var(--blue); }
.cat-go{ color:var(--blue); grid-column:3/4; justify-self:end; opacity:0; transform:translateX(-4px); transition:opacity .18s,transform .18s; }
.cat-cell:hover .cat-go{ opacity:1; transform:translateX(0); }
@media (max-width:900px){ .cat-cell{ grid-column:span 6; } }

/* ============ Article cards (category pages) ============ */
/* Three up on desktop, two on tablet, one on phone. Thumb ratio matches the
   case studies on the homepage so both grids share one rhythm. */
.art-grid{ column-gap:var(--gutter); display:grid; grid-template-columns:repeat(var(--cols),minmax(0,1fr)); row-gap:var(--u8); }
.art-card{ color:inherit; display:flex; flex-direction:column; gap:var(--u2); grid-column:span 4; text-decoration:none; }
@media (max-width:900px){ .art-card{ grid-column:span 3; } }
@media (max-width:600px){ .art-card{ grid-column:1/-1; } }
.art-thumb{ aspect-ratio:2/1; border:1px solid var(--rule); display:block; overflow:hidden; }
.art-thumb img{ filter:grayscale(1) contrast(1.04); height:100%; mix-blend-mode:multiply; object-fit:cover; transition:filter .3s; width:100%; }
:root[data-theme="dark"] .art-thumb img{ mix-blend-mode:normal; }
.art-card:hover .art-thumb img{ filter:grayscale(0) contrast(1.04); }
@media (hover:none){ .art-thumb img{ filter:grayscale(0) contrast(1.04); } }
/* Blue block stands in until a real image is sourced. Themed, not a file. */
.art-thumb-empty{ background:var(--blue); border-color:var(--blue); }
.art-title{ font-family:var(--font-serif); font-size:var(--t-2); font-weight:500; letter-spacing:var(--tr-tight); line-height:var(--lh-snug); transition:color .15s; }
.art-card:hover .art-title{ color:var(--blue); }
.art-pub{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0); }
/* Clamped so uneven excerpt lengths can't make one card taller than its row. */
.art-excerpt{ color:var(--ink-muted); display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; font-family:var(--font-serif); font-size:var(--t-0); line-height:var(--lh-body); overflow:hidden; }
.art-card .label{ margin-bottom:calc(var(--u) * -1); }

/* ============ Listening (About) ============ */
/* Spotify's own player for a public playlist: it shows whatever the playlist
   holds when the page loads, so adding a song in Spotify updates the site. */
.spotify-embed{ border:0; border-radius:12px; display:block; width:100%; }
.sr-only{ clip:rect(0,0,0,0); height:1px; overflow:hidden; position:absolute; white-space:nowrap; width:1px; }



/* 71ch is the narrowest this column goes while the three paragraphs still
   break 3/3/3; at 70 the middle one spills to four lines. */
.what-prose{ max-width:71ch; }

/* ============ Client logo wall ============ */
/* One grid for the whole block. In the markup the label comes right before the
   logo box it names, so when the layout stacks on phones and tablets it stays
   with the box. On desktop it is lifted into row 1 to ride the title's
   baseline, with the prose and the box pinned to row 2. The pins are needed:
   left to auto-placement, the prose would drop a row behind the title. */
.wall-label{ color:var(--ink-muted); margin-left:var(--u6); }
@media (min-width:901px){
  .what-grid .what-title,.what-grid .wall-label{ align-self:baseline; grid-row:1; }
  .what-grid .what-prose,.what-grid .logo-wall{ grid-row:2; }
}
/* The wall stretches to the prose's height and distributes its three rows
   across it, so the top logos start level with the first line of text and the
   bottom row never runs past the last. */
/* Same frame as the Receipts block: paper fill, the grain, one hairline
   border. No internal rules, so the logos read as a set rather than a table.
   Hugs its content so the box ends with the logos, not with the prose. */
.logo-wall{ align-self:start; background-color:var(--paper); background-image:var(--grain);
  background-position:0 0; background-repeat:repeat; background-size:var(--grain-size);
  border:1px solid var(--rule); display:flex; margin-left:var(--u6); padding:var(--u4); }
/* Columns sized to their content and pushed apart, so the right-hand logos
   reach the margin instead of leaving a gutter of dead space. */
.logo-grid{ align-content:start; column-gap:var(--gutter); display:grid; flex:1;
  grid-template-columns:max-content max-content; justify-content:space-between;
  list-style:none; row-gap:var(--u3); }
.logo-grid li{ align-items:center; display:flex; justify-content:flex-start; }
/* Each file carries a different amount of transparent padding on its left
   edge (NOVA has 21%, Elevate none), which breaks the optical left edge of
   each column. --pad pulls the artwork flush; a percentage translate scales
   with the image, so it holds at every breakpoint. */
/* Heights are set per logo inline. When a phone column is narrower than the
   logo, max-width caps the box; contain keeps the artwork in proportion inside
   it instead of squashing it sideways. */
.logo-grid img{ max-width:100%; object-fit:contain; object-position:left center; transform:translateX(calc(-1 * var(--pad, 0%))); width:auto; }
/* Light: logos keep their own colour; multiply drops residual white into the
   paper, same as the case-study thumbnails. */
.logo-grid img{ mix-blend-mode:multiply; }
/* Dark: grayscale keeps knocked-out detail that a silhouette would seal shut.
   `.lift` inverts the ones dark enough to vanish otherwise. */
:root[data-theme="dark"] .logo-grid img{ filter:grayscale(1) contrast(1.04); mix-blend-mode:normal; }
:root[data-theme="dark"] .logo-grid img.lift{ filter:grayscale(1) invert(1); }
/* AVO and Elevate keep their marks but swap to a copy whose wordmark is drawn
   in light ink for dark mode, so the name reads as bright as the other logos. */
:root[data-theme="dark"] .logo-grid .logo-light,
:root:not([data-theme="dark"]) .logo-grid .logo-dark{ display:none; }
@media (max-width:900px){
/* Stacked: the label sits right over the box it names. The big gap goes
   above it, so it reads as the start of the logos, not the end of the prose. */
.wall-label{ margin:var(--u8) 0 var(--u3); }
/* Stacked here, so the wall aligns with the text rather than being inset. */
.logo-wall{ margin-left:0; padding:var(--u4); }
  /* max-content columns cannot shrink, so they overflow a phone. Equal columns
     here, and the images scale down inside them. */
  .logo-grid{ grid-template-columns:1fr 1fr; justify-content:normal; row-gap:var(--u6); }
}

/* ============ Case study page ============ */
/* Index link only — no "case 1 of 8" counter, no next/prev. */
.folio{ border-bottom:1px solid var(--rule); padding-bottom:var(--u3); }
.folio-link{ color:var(--ink-muted); text-decoration:none; transition:color .15s; }
.folio-link:hover{ color:var(--blue); }
.case-head{ padding-block:var(--u8) 0; }
/* Client name and tagline left, project image right, tops aligned. Bottom-
   aligning left a column of dead space above the title, since the image is
   much taller than the name. */
.case-hero{ align-items:start; margin-top:var(--u6); }
.case-intro{ container-type:inline-size; grid-column:1/7; }
/* .section-title sizes off the viewport, which knows nothing about this
   six-column cell — "Corporation" ran 38px past it and under the image. The
   cap sizes off the cell instead and only bites when a name is long enough to
   need it; hyphens catch anything longer still. */
.case-title{ font-size:min(clamp(2.25rem,5.5vw,4rem),12.5cqi); hyphens:auto; margin-bottom:0; }
.case-shot{ grid-column:7/-1; }
.case-shot img{ border:1px solid var(--rule); height:auto; mix-blend-mode:multiply; width:100%; }
:root[data-theme="dark"] .case-shot img{ mix-blend-mode:normal; }
/* No max-width — the six-column cell is already the measure. */
.case-tagline{ font-family:var(--font-serif); font-size:var(--t-2); line-height:1.4; margin-top:var(--u6); text-wrap:pretty; }
/* Role and Scope only. */
.credits{ margin-top:var(--u8); }
.credit-row{ border-top:1px solid var(--rule); column-gap:var(--gutter); display:grid;
  grid-template-columns:repeat(var(--cols),minmax(0,1fr)); padding-block:var(--u3); }
.credit-row dt{ grid-column:1/3; }
.credit-value{ font-family:var(--font-serif); font-size:var(--t-0); grid-column:3/-1; }
/* A serif pipe carries full stem weight, so it reads louder than the words it
   divides. Muted and thinned back to the weight a middot had. */
.credit-value .sep{ color:var(--ink-muted); padding-inline:.15em; }
/* Client note left, story right, under matching Gochi Hand kickers — same
   treatment as the section kickers on the homepage, so no tracking and no
   caps: both fight a script face. The story's kicker lives inside .prose, and
   `.prose p` outranks a single class, so the face is restated at
   .cs-body .cs-kicker; drop that and the two slip off each other's baseline. */
.cs-head{ grid-column:1/5; }
.cs-kicker,.cs-body .cs-kicker{ color:var(--ink); font-family:'Gochi Hand',cursive;
  font-size:var(--t-1); font-weight:300; letter-spacing:normal;
  line-height:var(--lh-ui); margin:0; text-transform:none; }
.cs-note{ color:var(--ink-muted); font-family:var(--font-serif); font-size:var(--t-0);
  line-height:var(--lh-body); margin-top:var(--u4); }
.cs-body{ grid-column:6/-1; }
/* Same 32px the note takes on the left, so both columns start level. */
.cs-body .cs-kicker + p{ margin-top:var(--u4); }
@media (max-width:900px){
  .case-intro,.case-shot{ grid-column:1/-1; }
  .case-shot{ margin-top:var(--u6); }
  .credit-row dt{ grid-column:1/-1; }
  .credit-value{ grid-column:1/-1; margin-top:var(--u); }
  .cs-head,.cs-body{ grid-column:1/-1; }
  .cs-body{ margin-top:var(--u6); }
}
