/* ---------- tokens ----------
   Font: the Cargo build used Favorit (licensed through Cargo). To switch back,
   add an @font-face for your licensed Favorit woff2 and put it first in --font.
   Theme: one switch, [data-theme] on <html>, set before paint by the head script (system
   preference until the toggle picks one). Dark is the default without JS. */
:root {
  --font: "Inter Tight", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --bg: #000;
  --fg: rgba(255, 255, 255, .93);
  --line: rgba(255, 255, 255, .22);
  --underline: rgba(255, 255, 255, .45);
  --underline-soft: rgba(255, 255, 255, .35);
  --well: rgba(255, 255, 255, .06);            /* empty image slots */
  --img-outline: rgba(255, 255, 255, .08);     /* hairline on images */
  --soft: .55;    /* secondary text: 5.5:1 */
  --faint: .52;   /* tertiary text: 4.98:1 (was .4, 3.28:1) */
  --pad: 1rem;
  --gap: 1.25rem;
  --accent: oklch(0.56 0.29 269);              /* brand blue, changelog overlay only */
  --live: #c8f55a;                             /* status: live. Signal lime, used for nothing else */
  --on-live: #0d1400;                          /* text on --live: 15.9:1 */
}

/* Light mode: its own palette on warm paper white, not an inversion of dark */
:root[data-theme="light"] {
  --bg: #f3f1ec;
  --fg: rgba(0, 0, 0, .88);
  --line: rgba(0, 0, 0, .16);
  --underline: rgba(0, 0, 0, .4);
  --underline-soft: rgba(0, 0, 0, .3);
  --well: rgba(0, 0, 0, .05);
  --img-outline: rgba(0, 0, 0, .08);
  --soft: .65;    /* 5.0:1 on paper (.55 was 3.68:1) */
  --faint: .65;   /* 5.0:1 (.4 was 2.43:1) */
}

/* Type scales with the window, like Cargo's rem */
html {
  font-size: clamp(10px, 1.08vw, 17px); background: var(--bg);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }
body {
  margin: 0;
  background: transparent; /* the page colour lives on <html> so hover images can sit behind the text */
  color: var(--fg);
  font-family: var(--font);
  font-weight: 400;       /* 350 only on display sizes (h1); below 18px text stays 400+ */
  font-size: 1.2rem;
  line-height: 1.3;
  text-rendering: optimizeLegibility;
}
a { color: inherit; text-decoration: none; }
a:active { opacity: .7; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
:focus-visible { outline-offset: 2px; } /* the browser's own ring: 2px+, adapts to forced colors */
[id] { scroll-margin-top: 4rem; }       /* anchors land clear of the sticky header */
.skip-link { position: fixed; top: var(--pad); inset-inline-start: -999px; z-index: 10000; padding: .5em .8em; background: var(--fg); color: var(--bg); }
.skip-link:focus { inset-inline-start: var(--pad); }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
img, video { display: block; max-width: 100%; }
p { margin: 0; }

/* ---------- text styles (from the Cargo build) ---------- */
h1, h2 { margin: 0; font: inherit; }
h1 {
  font-weight: 350;
  font-size: 3.5rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0;
  text-wrap: balance;
}
/* The rem scale follows the window, so small roles get a 12px floor */
.caption  { font-weight: 400; font-size: max(12px, .85rem); line-height: 1.1733; }
.alt-body { font-weight: 400; font-size: max(12px, .85rem); line-height: 1.3; display: block; overflow-wrap: break-word; }
.label    { font-weight: 500; font-size: max(12px, .8125rem); line-height: 1; display: block; }
.yuge     { font-weight: 400; font-size: 9.7rem; line-height: .95; letter-spacing: -0.048em; }
.changelog { font-weight: 400; font-size: 12rem; line-height: .85; letter-spacing: -0.058em; }

.page-content { padding: 0 var(--pad); }

/* ---------- header ---------- */
.top {
  position: sticky; top: 0; z-index: 50;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 3rem;
  padding-top: var(--pad); padding-bottom: var(--pad);
  mix-blend-mode: difference;
  color: #fff; /* stays white: the difference blend darkens it over a light page */
}
.top-nav { display: flex; gap: 3.5em; }
.top-nav a, .top-name a, .top-mail > a { white-space: nowrap; display: inline-block; padding-block: 12px; margin-block: -12px; } /* 40px tall targets, same line box */
.top-nav a { padding-inline: 8px; margin-inline: -8px; }  /* short words get 40px+ widths too; the 3.5em gaps keep them apart */
.top-mail { grid-column: 4; display: flex; justify-content: flex-end; align-items: center; gap: 1.1em; }

/* theme toggle, beside the email: one button, open dot = light, filled dot = dark.
   The live mode is full strength and underlined, so it isn't carried by brightness alone. */
.theme-btn { display: flex; align-items: center; justify-content: center; gap: 5px; height: 40px; min-width: 40px; margin-block: -8px; padding-inline: 4px; transition: scale .2s ease-out; }
.theme-btn:active { scale: .96; }
.theme-btn i { position: relative; display: block; width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid currentColor; opacity: var(--faint); transition: opacity .15s ease-out; }
.theme-btn .d { background: currentColor; }
.theme-btn i::after { content: ""; position: absolute; left: -1.5px; right: -1.5px; bottom: -5px; height: 1.5px; background: currentColor; opacity: 0; transition: opacity .15s ease-out; }
.theme-btn[aria-pressed="false"] .l, .theme-btn[aria-pressed="true"] .d { opacity: 1; }
.theme-btn[aria-pressed="false"] .l::after, .theme-btn[aria-pressed="true"] .d::after { opacity: 1; }

/* ---------- bio ---------- */
.bio-section {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 2rem;
  margin-top: 8rem; margin-bottom: 16rem;
}
.bio-section h1 { grid-column: 1 / span 9; }

/* hover links */
.clip-trigger { color: inherit; cursor: pointer; }
h1 a.clip-trigger {
  text-decoration-line: underline;
  text-decoration-style: dotted;
  text-decoration-color: var(--underline);
  text-underline-position: from-font;
  text-decoration-thickness: from-font;
  text-decoration-skip-ink: auto;
  text-underline-offset: .12em;
}

/* floating clipping (position and motion come from the script) */
.clip-float {
  position: fixed; left: 0; top: 0; margin: 0;
  width: 480px; max-width: 60vw;
  pointer-events: none; z-index: 9999; opacity: 0;
  transform-origin: 50% 100%;
  will-change: transform, opacity;
}
.clip-float img { display: block; width: 100%; border-radius: 8px; box-shadow: 0 18px 40px rgba(0, 0, 0, .35); outline: 1px solid var(--img-outline); outline-offset: -1px; }
.clip-float figcaption { margin-top: 8px; font-size: 12px; opacity: .6; }

/* hover dimming (toggled by hover-media.js) */
body.hm-anim .page-content * { transition: color .5s ease-out !important; }
body.hm-on .page-content *:not(.hm-active) { color: var(--hm-dim) !important; }

/* ---------- editorial columns ---------- */
.ed-set {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 2rem;
  margin-top: 8rem; margin-bottom: 10rem;
}
.ed { display: flex; flex-direction: column; gap: 2.5rem; }
.ed .label { margin-bottom: .75em; }
.ed .alt-body { hanging-punctuation: first; }
.ed-p { display: block; line-height: 1.5; overflow-wrap: break-word; }
.ed-p + .ed-p { text-indent: 1.4em; }
.ed-idx .row { display: grid; grid-template-columns: 3.6em 1fr; column-gap: .5em; }
.ed-list .item { display: block; text-wrap: pretty; }          /* flush-left list, no year column (Press) */
.ed-list .item + .item { margin-top: .35em; }
.ed .y { font-style: italic; opacity: var(--soft); white-space: nowrap; }
.ed .d { opacity: var(--soft); }
.ed a.clip-trigger { text-decoration: underline dotted; text-decoration-thickness: 1px; text-underline-offset: 3px; text-decoration-skip-ink: auto; }
.ed .alt-body a { text-decoration: underline; text-decoration-color: var(--underline-soft); text-underline-position: from-font; text-decoration-skip-ink: auto; transition: text-decoration-color .15s ease-out; }

/* ---------- work ----------
   One 5-track grid: gutter | title | description | credits | gutter.
   The description track is exactly the M image width, so text and image share edges. */
.arc {
  --head-h: 3rem;
  --col: calc(50% - .625rem);   /* M image */
  --side: 15%;                  /* title + credits tracks */
  --tracks: minmax(0, 1fr) var(--side) var(--col) var(--side) minmax(0, 1fr);
  margin-top: 10rem; counter-reset: arc; scroll-margin-top: var(--head-h);
}

/* S / M / L: three 44px circles stacked in the left gutter, on the same left edge as the name
   in the header, sticky under it. Zero-height sticky bar, so it rides along without pushing
   anything down. Hairline outlines; only the live size is filled. */
.arc-bar {
  position: sticky; top: calc(var(--head-h) + 1.5rem); z-index: 40;
  display: grid; grid-template-columns: var(--tracks); grid-template-rows: 0;
  pointer-events: none;
}
.arc-sizes {
  grid-column: 1; justify-self: start; align-self: start;
  display: flex; flex-direction: column; gap: 8px;
  pointer-events: auto;
}
.arc-btn {
  width: 44px; height: 44px; flex: none; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 500; line-height: 1; letter-spacing: .02em;
  background: none;
  color: var(--fg);
  border: 1px solid var(--line);
  transition-property: background-color, border-color, color, scale;
  transition-duration: .15s, .15s, .15s, .2s; transition-timing-function: ease-out;
}
.arc-btn > span { opacity: var(--soft); transition: opacity .15s ease-out; }
.arc-btn:active { scale: .96; }
.arc-btn.is-on { background: var(--fg); border-color: var(--fg); color: var(--bg); }
.arc-btn.is-on > span { opacity: 1; }

.arc-item {
  container-type: inline-size;   /* lets the slide bar size itself to the M column at any image size */
  counter-increment: arc;
  margin-bottom: 9rem;
  scroll-margin-top: 6rem;
}

/* project info: title left, description over the image, credits right */
.arc-head {
  display: grid; grid-template-columns: var(--tracks);
  margin-bottom: 2.5rem;
}
.arc-head::before { content: ""; grid-column: 2 / 5; grid-row: 1; border-top: 1px solid var(--line); margin-bottom: .9em; }
.arc-title { grid-column: 2; grid-row: 2; padding-right: var(--gap); display: grid; grid-template-columns: 2.4em 1fr; }
.arc-title::before { content: counter(arc, decimal-leading-zero); opacity: var(--faint); }
.arc-desc { grid-column: 3; grid-row: 2; max-width: 30em; line-height: 1.5; text-wrap: pretty; overflow-wrap: break-word; } /* ≈ 72–74 characters in Inter Tight: the top of 60–75 */
.arc-meta { grid-column: 4; grid-row: 2; padding-left: calc(var(--gap) * 1.5); display: grid; grid-template-columns: 3em 1fr; row-gap: .1em; align-content: start; }
.arc-meta .k { opacity: var(--faint); }

/* Side quests: the first card starts at the description's left edge, the row runs out to the
   right edge of the window, and scrolled cards slide off the left edge. Cards are sized by width
   (S and M ≈ 18.5vw, L ≈ 28vw); the counter + arrows sit in the description column.
   --inset is measured from .arc-desc by archive.js; the calc is the no-JS fallback. */
.tools-wrap { display: grid; grid-template-columns: var(--tracks); }
.tools-wrap > .tools-bar { grid-column: 3; }   /* counter under the first card, arrows at the description column's edge */
.tools {
  --w: 18.5vw;
  grid-column: 1 / -1;
  --inset: calc(var(--pad) + (100vw - 2 * var(--pad)) * .25 + .3125rem);
  margin-inline: calc(var(--pad) * -1);   /* out of the page padding, to the window edges */
  padding-inline: var(--inset) var(--pad);
  scroll-padding-inline: var(--inset) var(--pad);
  list-style: none; margin-block: 0;
  display: grid; grid-auto-flow: column; column-gap: var(--gap);   /* 20px between cards, 8px inside one */
  grid-auto-columns: var(--w);
  overflow-x: auto; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory; scrollbar-width: none;
}
.tools::-webkit-scrollbar { display: none; }
.arc[data-size="l"] .tools { --w: 28vw; }
.tools-bar { margin-top: .9em; }
.tool { scroll-snap-align: start; }
.tool > a, .tool > div { display: flex; flex-direction: column; }
.tool-img { position: relative; margin: 0 0 .6em; aspect-ratio: 16 / 10; overflow: hidden; border-radius: 8px; background: var(--well); }
.tool-img::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--img-outline); pointer-events: none; } /* outline stays on top of the zoom */
.tool-img img, .tool-img video { width: 100%; height: 100%; object-fit: cover; }
.tool-title { display: flex; align-items: center; gap: .5em; min-width: 0; }
.tool-name { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tool-desc { opacity: var(--faint); text-wrap: pretty; }

/* status badge beside the name: Live is filled lime, In progress is a hairline outline */
.tool-badge {
  flex: none; display: inline-flex; align-items: center;
  height: 18px; padding: 0 7px; border-radius: 999px;
  font-size: 12px; font-weight: 500; line-height: 1; letter-spacing: .04em; text-transform: uppercase;
  white-space: nowrap;
}
.tool-badge.is-live { background: var(--live); color: var(--on-live); }
.tool-badge.is-progress { box-shadow: inset 0 0 0 1px var(--underline); color: var(--fg); }
.tools-bar[hidden] { display: none; }
/* video cards: a small "Hover to play" pill (pointer devices only) that fades once the video runs */
.tool-hint {
  position: absolute; left: 8px; bottom: 8px; z-index: 1;
  display: none; align-items: center; gap: 6px;
  padding: 5px 9px 5px 8px; border-radius: 999px;
  background: rgba(0, 0, 0, .55); color: #fff;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  font-size: 12px; line-height: 1; white-space: nowrap; pointer-events: none;
  transition: opacity .15s ease-out;
}
.tool-hint::before { content: ""; border-style: solid; border-width: 4px 0 4px 7px; border-color: transparent transparent transparent currentColor; }
@media (hover: hover) { .tool-hint { display: inline-flex; } }
.tool:hover .tool-hint, .tool:focus-within .tool-hint { opacity: 0; }

/* image: centered, S / M / L on the same 4-column grid */
.arc-img { margin: 0 auto; width: var(--col); }
.arc[data-size="s"] .arc-img { width: calc(25% - .9375rem); }
.arc[data-size="l"] .arc-img { width: 100%; }

/* ---------- slideshow ---------- */
.ss {
  position: relative;
  aspect-ratio: var(--ar, 1.5);
  overflow: hidden;
  outline-offset: 6px;
}
.ss-slide {
  position: absolute; inset: 0; margin: 0;
  display: flex; align-items: flex-end; justify-content: center; /* bottom-anchored: the counter always sits right under the image */
  opacity: 0; visibility: hidden;
  transition: opacity .6s ease-out, visibility 0s linear .6s;
}
.ss-slide.is-on { opacity: 1; visibility: visible; transition: opacity .6s ease-out; }
.ss-slide > img, .ss-slide > video {
  width: auto; height: auto; max-width: 100%; max-height: 100%;   /* the element is the picture, so radius + outline sit on its edges */
  border-radius: 8px; outline: 1px solid var(--img-outline); outline-offset: -1px;
}
/* two pieces shown as one image: no gap, one rounded edge, sized to the frame by its combined ratio */
.ss-pair { position: relative; display: flex; overflow: hidden; border-radius: 8px; }
.ss-pair.fit-w { width: 100%; }
.ss-pair.fit-h { height: 100%; }
.ss-pair > * { flex: 1 1 0; min-width: 0; width: 100%; height: 100%; object-fit: cover; }
.ss-pair::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 0 1px var(--img-outline); pointer-events: none; }
.ss-hit { position: absolute; top: 0; bottom: 0; width: 50%; z-index: 2; }
.ss-hit-prev { left: 0; cursor: w-resize; }
.ss-hit-next { right: 0; cursor: e-resize; }
.ss-bar { display: flex; justify-content: space-between; align-items: center; margin-top: .45em; font-variant-numeric: tabular-nums; }
/* counter + arrows stay in the description column at M and L (not out at the edges of a full-width image);
   at S they hug the small image's own edges */
.arc-img .ss-bar { width: calc(50cqw - .625rem); margin-inline-start: calc(50% - (50cqw - .625rem) / 2); }
.arc[data-size="s"] .arc-img .ss-bar { width: auto; margin-inline-start: 0; }
.ss-count { opacity: var(--faint); font-size: 11px; letter-spacing: .02em; } /* the one deliberate exception to the 12px floor: a caption to the image */
.ss-nav { display: flex; margin-block: -12px; margin-inline-end: -14px; } /* glyphs stay flush with the image edge */
.ss-nav button {
  opacity: var(--faint);
  display: grid; place-items: center; min-width: 40px; height: 40px; padding-inline: 6px;   /* 40px targets, side by side, no overlap */
  transition-property: opacity, scale; transition-duration: .15s, .2s; transition-timing-function: ease-out;
}
.ss-nav button:active { scale: .96; }
.ss-play[hidden] { display: none; }

/* ---------- footer ---------- */
footer { font-weight: 350; } /* footer typography is out of scope for the type pass */
.ft { border-top: 1px solid var(--line); padding-top: 8rem; margin-bottom: 9rem; }
.ft-big { display: block; text-align: center; text-wrap: balance; }
.ft-big a, .ft-btn { transition: opacity .15s ease-out; }
/* Not-yet items (On File, CV): faded, aria-disabled so they stay focusable, each with a tooltip
   centred on its own word (so it never collides with the big lines around it) that shows on
   hover or focus, rises into place and closes with Escape (footer.js). */
.soon { position: relative; display: inline-block; }
.ft-soon { opacity: .3; cursor: default; }
.tip {
  position: absolute; top: 50%; left: 50%; z-index: 20;
  translate: -50% calc(-50% + 4px); opacity: 0; pointer-events: none;
  padding: 7px 11px; border-radius: 999px;
  background: var(--fg); color: var(--bg);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .12), 0 6px 16px rgba(0, 0, 0, .18);
  font: 400 12px/1.2 var(--font); letter-spacing: 0; white-space: nowrap;
  transition-property: opacity, translate; transition-duration: .15s; transition-timing-function: ease-out;
}
.soon:focus-within .tip { opacity: 1; translate: -50% -50%; }
.soon.tip-off .tip { opacity: 0; }
.ft-btn { cursor: pointer; }
.ft-base {
  display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap;
  font-size: 12px; line-height: 1.4;
  padding-bottom: var(--pad);
}
.ft-meta { cursor: pointer; font-size: inherit; line-height: inherit; font-variant-numeric: tabular-nums; padding-block: 12px; margin-block: -12px; }

/* Overlays (changelog + address). Labels on grid column 1, text on column 2 —
   footer.js measures both from the archive and sets --pad-l / --ver-col. */
ft-log {
  --pad-l: 1.25rem;
  --ver-col: 27vw;
  --overhang: 8vw;
  position: fixed; inset: 0; z-index: 9999;
  display: flex; flex-direction: column; justify-content: flex-start; gap: .6em;
  padding: 1.25rem 0 0 var(--pad-l); box-sizing: border-box;
  overflow: hidden;
  mix-blend-mode: difference;
  pointer-events: none; opacity: 0; visibility: hidden;
  transition: opacity .18s ease-out, visibility 0s linear .18s;
  font-family: var(--font);
}
ft-log.on { opacity: 1; visibility: visible; transition: opacity .18s ease-out; }
ft-log .e {
  display: grid;
  grid-template-columns: var(--ver-col) calc(100vw - var(--pad-l) - var(--ver-col) + var(--overhang));
  align-items: start; text-align: left;
  color: var(--accent);
  visibility: hidden;
}
ft-log.on .e.go { visibility: visible; }
ft-log .t { white-space: pre-line; }

/* ---------- hover: pointer devices only, so a tap never leaves an item looking selected ---------- */
@media (hover: hover) {
  .top a:hover { opacity: .6; }
  .theme-btn:hover i { opacity: 1; }
  .ed .alt-body a:hover { text-decoration-color: currentColor; }
  .arc-btn:hover { border-color: var(--underline); }
  .arc-btn:hover > span { opacity: 1; }
  .arc-btn.is-on:hover { border-color: var(--fg); }
  .arc-title a:hover { opacity: .6; }
  .ss-nav button:hover { opacity: 1; }
  .ft-big a:not([data-quad]):hover, .ft-btn:hover { opacity: .6; }
  .soon:hover .tip { opacity: 1; translate: -50% -50%; }
  .soon.tip-off:hover .tip { opacity: 0; }
}

/* ---------- motion: opt-in, only without a reduced-motion preference ---------- */
@media (prefers-reduced-motion: no-preference) {
  .arc-img { transition: width .5s cubic-bezier(.2, .7, .2, 1); }
  .arc.is-settling .arc-img { transition: none; }   /* no size animation on page load */
  .tool-img img { transition: scale .25s ease-out; }
}
@media (prefers-reduced-motion: no-preference) and (hover: hover) {
  .tool > a:hover .tool-img img { scale: 1.03; }
}
@media (prefers-reduced-motion: reduce) {
  .ss-slide, .ss-slide.is-on, ft-log, ft-log.on { transition: none; }
  .tip { translate: -50% -50%; transition-property: opacity; }
  .arc-btn:active, .ss-nav button:active, .theme-btn:active { scale: 1; }
}

/* ---------- mobile ---------- */
@media (max-width: 768px) {
  html { font-size: 13px; }
  .top { grid-template-columns: 1fr auto auto; column-gap: 1.25rem; }
  .top-mail { grid-column: 3; }
  .top-nav { gap: 1.5em; justify-self: end; }
  .top-mail > a { display: none; }
  h1 { font-size: 22px; }
  .bio-section { display: block; margin-top: 4rem; margin-bottom: 6rem; }
  .ed-set { grid-template-columns: 1fr; margin-top: 4rem; }
  .ed { margin-bottom: 2.5rem; }
  .alt-body, .caption { font-size: 1rem; }

  .arc { margin-top: 5rem; }
  .arc-bar { display: none; }
  .arc-item { margin-bottom: 4.5rem; }
  .arc-head { grid-template-columns: 1fr; row-gap: 1em; margin-bottom: 2rem; }
  .arc-head::before { grid-column: 1; margin-bottom: 0; }
  .arc-title, .arc-desc, .arc-meta { grid-column: 1; grid-row: auto; padding: 0; }
  .arc-img, .arc[data-size] .arc-img { width: 100%; }
  .arc-img .ss-bar { width: auto; margin-inline-start: 0; }
  .tools-wrap { grid-template-columns: 1fr; }
  .tools-wrap > .tools-bar { grid-column: 1; }
  .tools, .arc[data-size] .tools { --w: 62vw; --inset: var(--pad) !important; }

  .yuge { font-size: 3.6rem; }
  .changelog { font-size: 4.5rem; }
  .ft { margin-top: 6rem; padding: 4.5rem 0 1rem; }
  .ft-big { margin-bottom: 4.5rem; }
  .ft-base { flex-direction: column; gap: .25rem; }
  ft-log { --pad-l: 1rem; --ver-col: 22vw; --overhang: 6vw; }
}
