/* site.css: the public shell (docs/design-pov.md, sections 5 and 6), on
   base.css. The transcript is the identity: the plain-HTML site that search
   engines, screen readers and browsers without WebGPU (or JavaScript) get, set
   in the same composition as the default front end so one can cross-fade
   into the other. Then the parent-drawn chrome over every front end: the
   "Make your own version" button and the draft tape. */

@view-transition { navigation: auto; }

body.public { min-height: 100vh; min-height: 100svh; }
/* grain: one static tile, never animated, under nothing interactive */
body.public::after {
  content: ""; position: fixed; inset: 0; z-index: var(--z-grain); pointer-events: none;
  background: url("/static/grain.png") repeat; background-size: 256px 256px;
  opacity: var(--grain-opacity); mix-blend-mode: overlay;
}
.fe-live body.public::after { display: none; } /* front ends bring their own surface */
@media (prefers-reduced-transparency: reduce) { body.public::after { display: none; } }

/* ── transcript ─────────────────────────────────────────────────────────── */
#transcript {
  position: relative; padding: 0 var(--m);
  display: flex; flex-direction: column; min-height: 100vh; min-height: 100svh;
}
#transcript main { flex: 1; }
.grid, .site-header, .hero, .bio, .index-row, .page-head, .site-footer {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--g);
}

/* meta row */
.site-header {
  position: absolute; top: 0; left: var(--m); right: var(--m); z-index: 2;
  padding-top: calc(var(--m) + env(safe-area-inset-top)); align-items: baseline;
  view-transition-name: meta-row;
}
.site-header, .site-header a, .label, .cue, .idx, .site-footer {
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3;
  letter-spacing: .06em; text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.site-header a { text-decoration: none; }
.brand { grid-column: 1 / 4; color: var(--fg); }
.role { grid-column: 4 / 7; color: var(--fg-2); }
.site-header nav {
  grid-column: 7 / 13; justify-self: end;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s-2) var(--s-5);
}
.site-header nav a { position: relative; color: var(--fg); }
.site-header nav .idx { color: var(--fg-2); margin-right: .35em; transition: color var(--d-2) var(--ease-quint); }
.site-header nav a[aria-current="page"]::before {
  content: ""; position: absolute; left: -12px; top: 50%; width: 6px; height: 6px; margin-top: -3px;
  border-radius: var(--r-pill); background: var(--accent);
}
@media (hover: hover) and (pointer: fine) {
  .site-header nav a:hover .idx { color: var(--accent); }
  .brand:hover { color: var(--fg-2); }
}
/* the transcript's builder link is the no-JavaScript entry; with JavaScript
   the corner button (frontend-host.js) takes its place */
.has-cta .make-own-link { display: none; }

/* the line above the name: secondary, in the text face */
.kicker {
  grid-column: 1 / -1; align-self: end; margin: 0 0 var(--s-5);
  font-family: var(--font-text); font-weight: 400; font-size: var(--t-lede);
  line-height: 1.15; letter-spacing: -.015em; color: var(--fg);
}

/* the one gesture: the name, bottom-left of the first viewport */
.hero {
  min-height: 100vh; min-height: 100svh; align-items: end; align-content: end;
  padding-bottom: calc(var(--s-8) + env(safe-area-inset-bottom));
}
.name {
  grid-column: 1 / 11; align-self: end;
  font-family: var(--font-display); font-weight: 300; font-size: var(--t-mega);
  font-variation-settings: "opsz" 72; line-height: .86; letter-spacing: -.045em;
  margin-left: -.04em; /* optical: the B's stem sits on the margin */
  view-transition-name: name;
}
.name .line { display: block; overflow: clip; padding: .2em 0 .24em; margin: -.2em 0 -.44em; } /* room for ascenders and the y's tail: the clip is only for the rise-in (sibling margins collapse, so the bottom one carries both) */
.name .line:last-child { margin-bottom: -.24em; }
.name .line > span { display: inline-block; }
.cue {
  grid-column: 11 / 13; justify-self: end; align-self: end; color: var(--fg-2);
  padding-bottom: calc(var(--t-mega) * .86 + .2em); /* the baseline of the first line, clear of the corner button */
}

/* labels: mono, in brackets, in the first three columns */
.label { color: var(--fg-2); }

/* bio */
.bio { margin-top: var(--gap-section); align-items: start; }
.bio > .label { grid-column: 1 / 4; padding-top: .75em; }
.bio-text { grid-column: 5 / 12; display: grid; gap: var(--s-6); }
.prose-aside, .prose-lede {
  font-family: var(--font-display); font-size: var(--t-lede); font-weight: 340;
  font-variation-settings: "opsz" 24; line-height: 1.22; letter-spacing: -.01em;
  max-width: 34ch; text-wrap: pretty;
}
.prose-aside { font-style: italic; color: var(--fg-2); }
.prose-lede { color: var(--fg); }
.prose-body {
  max-width: 62ch; color: var(--fg-2); text-wrap: pretty;
  margin-right: calc((100% - 62ch) * 0); /* columns 5-9 on wide screens via max-width */
}
.bio-text .prose-aside + .prose-lede { margin-top: calc(var(--s-2) * -1); }

/* experiments index: hairline rows, numbered */
.index { margin-top: var(--gap-section); }
.index > .label { display: block; margin-bottom: var(--s-6); }
.index-list { border-bottom: 1px solid var(--rule); }
.index-row {
  position: relative; align-items: baseline;
  padding: var(--s-6) 0; border-top: 1px solid var(--rule);
}
.index-row .idx { grid-column: 1 / 3; color: var(--fg-2); }
.index-title {
  grid-column: 3 / 9;
  font-family: var(--font-display); font-weight: 340; font-size: var(--t-title);
  font-variation-settings: "opsz" 48; line-height: 1; letter-spacing: -.02em; text-wrap: balance;
}
.index-summary { grid-column: 9 / 13; color: var(--fg-2); max-width: 40ch; text-wrap: pretty; }

/* experience (v1.8): dates, role, company; one line under it at most */
.xp-row { padding: var(--s-5) 0; }
.xp-dates { grid-column: 1 / 4; color: var(--fg-2); }
.xp-role {
  grid-column: 4 / 10; margin: 0;
  font-family: var(--font-display); font-weight: 340; font-size: var(--t-lede, 1.5rem);
  line-height: 1.1; letter-spacing: -.01em; text-wrap: balance;
}
.xp-company { grid-column: 10 / 13; margin: 0; color: var(--fg-2); }
.xp-note { grid-column: 4 / 13; margin: var(--s-2) 0 0; color: var(--fg-2); max-width: 60ch; }

/* an experiment's loop: a strip at the row's right edge */
.index-row.has-media .index-title { grid-column: 3 / 7; }
.index-row.has-media .index-summary { grid-column: 7 / 10; }
.index-thumb { grid-column: 10 / 13; align-self: start; }
.index-title a { text-decoration-color: var(--rule-strong); }

/* media: hairline-framed, square corners, never upscaled into mush */
.work-thumb video, .work-thumb img, .index-thumb video,
.project-hero img, .loop video, .loop img {
  display: block; width: 100%; height: auto; background: var(--bg-raised);
}
.work-thumb video, .work-thumb img, .index-thumb video {
  aspect-ratio: 4 / 1; object-fit: cover; opacity: .82;
  transition: opacity var(--d-2) var(--ease-quint);
}

/* the work index: numbered hairline rows, each a link */
.work-list { border-bottom: 1px solid var(--rule); }
.work-row { position: relative; border-top: 1px solid var(--rule); }
.work-link {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--g);
  align-items: center; padding: var(--s-5) 0; text-decoration: none; color: var(--fg);
}
.work-link .idx { grid-column: 1 / 3; color: var(--fg-2); transition: color var(--d-2) var(--ease-quint); }
.work-title {
  grid-column: 3 / 8;
  font-family: var(--font-display); font-weight: 340; font-size: var(--t-title);
  font-variation-settings: "opsz" 48; line-height: 1; letter-spacing: -.02em; text-wrap: balance;
  transition: transform var(--d-2) var(--ease-quint);
}
.work-meta {
  grid-column: 8 / 10; color: var(--fg-2);
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3; letter-spacing: .06em;
  text-transform: uppercase; font-variant-numeric: tabular-nums;
}
.work-thumb { grid-column: 10 / 13; }
/* the row's rule draws over in ink on hover; the rest of the list dims */
.work-row::before {
  content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px; background: var(--fg);
  transform: scaleX(0); transform-origin: left; transition: transform var(--d-3) var(--ease-inout);
}
@media (hover: hover) and (pointer: fine) {
  .work-list:hover .work-row { opacity: .55; }
  .work-list .work-row { transition: opacity var(--d-2) var(--ease-quint); }
  .work-list .work-row:hover, .work-list .work-row:focus-within { opacity: 1; }
  .work-row:hover::before, .work-row:focus-within::before { transform: scaleX(1); }
  .work-row:hover .work-title, .work-row:focus-within .work-title { transform: translateX(.25em); }
  .work-row:hover .idx, .work-row:focus-within .idx { color: var(--accent); }
  .work-row:hover .work-thumb video, .work-row:hover .work-thumb img,
  .work-row:focus-within .work-thumb video, .work-row:focus-within .work-thumb img { opacity: 1; }
}
.work-link:focus-visible { outline-offset: 6px; }
.index-more { margin-top: var(--s-5); }
.index-more a { text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .index-more a:hover { color: var(--fg); } }

/* a project: title, meta row, hero, brief, loops, film, next */
.project-head { padding-bottom: 0; }
.project-meta {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--g); row-gap: var(--s-5);
  margin: var(--s-8) 0 0; padding-top: var(--s-4); border-top: 1px solid var(--rule);
}
.project-meta > div { grid-column: span 3; min-width: 0; }
.project-meta .pm-year { grid-column: span 2; }
.project-meta .pm-roles { grid-column: span 4; }
.project-meta .pm-tags { grid-column: 1 / 9; }
.project-meta .pm-palette { grid-column: 9 / 13; }
.project-meta dt {
  margin-bottom: var(--s-2); color: var(--fg-2);
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3; letter-spacing: .06em; text-transform: uppercase;
}
.project-meta dd { margin: 0; font-weight: 500; font-size: var(--t-ui); font-stretch: 90%; line-height: 1.35; }
.swatches { display: block; height: 12px; width: auto; }
/* the hero: a full-bleed stage tinted with the project's first colour
   (the accent if it has none); the still sits centred at its native
   size, since the old stills are small (735px) and never upscale well */
.project-hero {
  --c: var(--accent);
  display: grid; place-items: center; position: relative; overflow: clip;
  margin: var(--s-7) calc(var(--m) * -1) 0; padding: clamp(32px, 7vw, 120px) var(--m);
  background:
    radial-gradient(120% 90% at 50% 100%, color-mix(in oklab, var(--c) 34%, transparent), transparent 70%),
    color-mix(in oklab, var(--c) 10%, var(--bg-raised));
  border-block: 1px solid var(--rule);
}
.project-hero img {
  width: 100%; max-width: min(100%, 920px); /* media.js refines it to 1.25x the still */ box-shadow: 0 40px 80px -30px rgb(0 0 0 / .6), 0 0 0 1px var(--rule);
  animation: hero-wipe var(--d-5) var(--ease-out) 200ms backwards;
}
@keyframes hero-wipe { from { clip-path: inset(0 0 100% 0); transform: translateY(24px) scale(1.02); } }
/* a project without media: a typographic plate in its colour */
.plate {
  --c: var(--accent);
  display: flex; flex-direction: column; justify-content: space-between; gap: var(--s-2);
  aspect-ratio: 4 / 1; padding: var(--s-3) var(--s-4); overflow: clip; container-type: inline-size;
  background:
    linear-gradient(135deg, color-mix(in oklab, var(--c) 46%, var(--bg)), color-mix(in oklab, var(--c) 12%, var(--bg)) 70%);
  color: var(--fg); box-shadow: inset 0 0 0 1px var(--rule);
}
.plate-word {
  font-family: var(--font-display); font-weight: 320; font-style: italic; line-height: .9; letter-spacing: -.03em;
  font-size: clamp(1.25rem, 15cqi, 4rem); white-space: nowrap;
}
.plate-meta { font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .06em; text-transform: uppercase; color: var(--fg-2); }
.work-thumb.plate { opacity: .82; transition: opacity var(--d-2) var(--ease-quint); }
@media (hover: hover) and (pointer: fine) {
  .work-row:hover .work-thumb.plate, .work-row:focus-within .work-thumb.plate { opacity: 1; }
  .work-thumb { overflow: clip; }
  .work-thumb > *, .plate-word { transition: transform var(--d-4) var(--ease-out); }
  .work-row:hover .work-thumb > video, .work-row:hover .work-thumb > img { transform: scale(1.06); }
  .work-row:hover .plate-word { transform: translateX(-.15em); }
}
.project-hero-plate { padding: 0; place-items: stretch; }
.plate-hero { aspect-ratio: 21 / 9; padding: var(--m); background: none; box-shadow: none; animation: fade-in var(--d-5) var(--ease-out) 150ms backwards; }
.plate-hero .plate-word { font-size: clamp(3rem, 16cqi, 14rem); align-self: start; }
.plate-hero .plate-meta { align-self: end; }
/* "Coming soon": said at display size, not as a footnote */
.index-page > .prose-aside:only-child { font-size: var(--t-display); max-width: none; line-height: 1; letter-spacing: -.03em; }
.project-text, .project-media {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--g);
  margin-top: var(--s-9); align-items: start;
}
.project-text > .label, .project-media > .label { grid-column: 1 / 4; padding-top: .3em; }
.project-prose { grid-column: 5 / 12; display: grid; gap: var(--s-4); }
.project-prose p { max-width: 62ch; text-wrap: pretty; }
.project-prose-2 p { color: var(--fg-2); }
.project-links {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); margin: var(--s-7) 0 0;
  padding-left: calc((100% - 11 * var(--g)) / 12 * 4 + 4 * var(--g));
}
.project-links a {
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3; letter-spacing: .06em;
  text-transform: uppercase; padding: var(--s-2) 0;
}
.project-loops { grid-column: 5 / 13; display: flex; flex-wrap: wrap; gap: var(--s-5); align-items: flex-start; }
.loop { margin: 0; }
.loop-strip, .loop-wide { flex: 1 1 100%; max-width: 100%; }
.loop-box { flex: 0 1 calc(50% - var(--s-5) / 2); max-width: 320px; }
.film { grid-column: 5 / 13; position: relative; aspect-ratio: 16 / 9; background: var(--bg-raised); }
.film iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.film-play { position: absolute; inset: 0; display: block; text-decoration: none; color: var(--fg); overflow: hidden; }
.film-play img { width: 100%; height: 100%; object-fit: cover; opacity: .45; transition: opacity var(--d-2) var(--ease-quint); }
.film-label {
  position: absolute; left: var(--s-5); bottom: var(--s-5);
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3; letter-spacing: .06em; text-transform: uppercase;
}
.film-label::before {
  content: ""; display: inline-block; width: 6px; height: 6px; margin-right: var(--s-3); vertical-align: .1em;
  border-radius: var(--r-pill); background: var(--accent);
}
@media (hover: hover) and (pointer: fine) { .film-play:hover img { opacity: .7; } }
.project-next {
  display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--g);
  margin-top: var(--gap-section); padding-top: var(--s-5); border-top: 1px solid var(--rule); align-items: baseline;
}
.project-next > .label:first-child { grid-column: 1 / 4; }
.next-title {
  grid-column: 5 / 11; text-decoration: none;
  font-family: var(--font-display); font-weight: 340; font-size: var(--t-title);
  font-variation-settings: "opsz" 48; line-height: 1; letter-spacing: -.02em; text-wrap: balance;
  transition: transform var(--d-2) var(--ease-quint);
}
.all-work { grid-column: 11 / 13; justify-self: end; text-decoration: none; }
@media (hover: hover) and (pointer: fine) {
  .next-title:hover { transform: translateX(.25em); }
}

/* other pages: a display title, top-left under the meta row */
.page-head {
  padding-top: calc(var(--m) + var(--s-10) + env(safe-area-inset-top)); align-items: end;
  view-transition-name: page-head;
}
.display {
  grid-column: 1 / 10;
  font-family: var(--font-display); font-weight: 320; font-size: var(--t-display);
  font-variation-settings: "opsz" 72; line-height: .95; letter-spacing: -.035em; text-wrap: balance;
  margin-left: -.03em;
}
.page-head .label { grid-column: 10 / 13; justify-self: end; padding-bottom: .5em; }
.page-404 .label { text-transform: none; letter-spacing: 0; font-family: var(--font-text); font-size: var(--t-body); }
.page-404 .label a { font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .06em; text-transform: uppercase; }
.bio-page { margin-top: var(--s-9); }
.index-page { margin-top: var(--s-9); }

/* footer: one quiet meta row; the right edge stays clear for the corner button */
.site-footer {
  margin-top: var(--gap-section); padding: var(--s-5) 0 calc(var(--s-9) + env(safe-area-inset-bottom));
  border-top: 1px solid var(--rule); color: var(--fg-2);
}
.site-footer p:first-child { grid-column: 1 / 4; }
.site-footer p:last-child { grid-column: 4 / 10; }

/* load sequence (docs/design-pov.md, section 6): CSS only, text readable from
   the first paint; skipped under reduced motion (base.css) */
@keyframes rise-in { from { transform: translateY(105%); } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes draw-x { from { transform: scaleX(0); } }
.site-header > * { animation: fade-in var(--d-3) var(--ease-out) backwards; }
.site-header > :nth-child(1) { animation-delay: 60ms; }
.site-header > :nth-child(2) { animation-delay: 110ms; }
.site-header > :nth-child(3) { animation-delay: 160ms; }
.name .line > span { animation: rise-in var(--d-5) var(--ease-out) backwards; animation-delay: 120ms; }
.name .line + .line > span { animation-delay: 200ms; }
.kicker { animation: rise-in var(--d-5) var(--ease-out) backwards; animation-delay: 40ms; }
.cue { animation: fade-in var(--d-4) var(--ease-out) 750ms backwards; }
.display { animation: fade-in var(--d-4) var(--ease-out) backwards; }

/* scroll reveals, once each, where scroll-driven animations exist */
@supports (animation-timeline: view()) {
  @keyframes reveal-up { from { opacity: 0; transform: translateY(24px); } }
  .bio .label, .index > .label, .bio-text > *, .index-row > *, .work-row, .project-text > *, .project-media > * {
    animation: reveal-up linear both; animation-timeline: view(); animation-range: entry 0% entry 40%;
  }
  .bio .label, .index > .label { animation-name: fade-in; }
}

/* view transitions between transcript pages */
::view-transition-old(root) { animation: fade-out 200ms var(--ease-in) both; }
::view-transition-new(root) { animation: route-in var(--d-4) var(--ease-out) both; }
@keyframes fade-out { to { opacity: 0; } }
@keyframes route-in { from { opacity: 0; transform: translateY(16px); } }

/* tablet: 6 columns */
@media (max-width: 1023px) {
  .xp-dates { grid-column: 1 / 7; margin-bottom: var(--s-2); }
  .xp-role { grid-column: 1 / 5; }
  .xp-company { grid-column: 5 / 7; }
  .xp-note { grid-column: 1 / 7; }
  .grid, .site-header, .hero, .bio, .index-row, .page-head, .site-footer { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .brand { grid-column: 1 / 3; }
  .role { grid-column: 3 / 5; }
  .site-header nav { grid-column: 5 / 7; }
  .name { grid-column: 1 / 7; }
  .cue { display: none; }
  .bio > .label { grid-column: 1 / 7; padding: 0 0 var(--s-5); }
  .bio-text { grid-column: 1 / 7; }
  .index-row .idx { grid-column: 1 / 2; }
  .index-title { grid-column: 2 / 7; }
  .index-summary { grid-column: 2 / 7; margin-top: var(--s-3); }
  .display { grid-column: 1 / 7; }
  .page-head .label { grid-column: 1 / 7; justify-self: start; padding-top: var(--s-5); }
  .site-footer p:first-child { grid-column: 1 / 3; }
  .site-footer p:last-child { grid-column: 3 / 7; }
  .index-row.has-media .index-title, .index-row.has-media .index-summary { grid-column: 2 / 7; }
  .index-thumb { grid-column: 2 / 7; margin-top: var(--s-4); }
  .work-link, .project-meta, .project-text, .project-media, .project-next { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .work-link { row-gap: var(--s-3); align-items: baseline; }
  .work-link .idx { grid-column: 1 / 2; }
  .work-title { grid-column: 2 / 7; }
  .work-meta { grid-column: 2 / 5; }
  .work-thumb { grid-column: 2 / 7; }
  .project-meta > div, .project-meta .pm-year { grid-column: span 2; }
  .project-meta .pm-roles, .project-meta .pm-tags { grid-column: span 4; }
  .project-meta .pm-palette { grid-column: span 2; }
  .project-text > .label, .project-media > .label { grid-column: 1 / 7; padding: 0 0 var(--s-4); }
  .project-prose, .project-loops, .film { grid-column: 1 / 7; }
  .project-links { padding-left: 0; }
  .project-next > .label:first-child { grid-column: 1 / 7; margin-bottom: var(--s-3); }
  .next-title { grid-column: 1 / 5; }
  .all-work { grid-column: 5 / 7; }
}
/* phone: 4 columns, recomposed (docs/design-pov.md, 5.2) */
@media (max-width: 599px) {
  .xp-role, .xp-company, .xp-note { grid-column: 1 / -1; }
  .xp-company { margin-top: var(--s-1); }
  .grid, .site-header, .hero, .bio, .index-row, .page-head, .site-footer { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .site-header { row-gap: var(--s-2); }
  .brand { grid-column: 1 / 3; }
  .role { display: none; }
  .site-header nav { grid-column: 3 / 5; flex-direction: column; align-items: flex-end; gap: var(--s-2); }
  .hero { padding-bottom: calc(var(--s-9) + env(safe-area-inset-bottom)); }
  .name { grid-column: 1 / 5; font-size: min(29vw, 9rem); }
  .hero::after {
    content: "Creative technology / AI"; grid-column: 1 / 5; margin-top: var(--s-5);
    font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .06em; text-transform: uppercase; color: var(--fg-2);
    animation: fade-in var(--d-4) var(--ease-out) 750ms backwards;
  }
  .bio > .label, .bio-text { grid-column: 1 / 5; }
  .bio-text { gap: var(--s-5); }
  .index-row { row-gap: var(--s-3); padding: var(--s-5) 0; }
  .index-row .idx { grid-column: 1 / 2; }
  .index-title { grid-column: 2 / 5; }
  .index-summary { grid-column: 2 / 5; margin: 0; }
  .display { grid-column: 1 / 5; }
  .page-head .label { grid-column: 1 / 5; }
  .site-footer { row-gap: var(--s-2); }
  .site-footer p:first-child, .site-footer p:last-child { grid-column: 1 / 5; }
  .index-row.has-media .index-title, .index-row.has-media .index-summary, .index-thumb { grid-column: 2 / 5; }
  .index-thumb { margin-top: 0; }
  .work-link, .project-meta, .project-text, .project-media, .project-next { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .work-link { padding: var(--s-5) 0; }
  .work-title { grid-column: 2 / 5; }
  .work-meta { grid-column: 2 / 5; }
  .work-thumb { grid-column: 1 / 5; margin-top: var(--s-2); }
  .work-thumb video, .work-thumb img { opacity: 1; }
  .project-meta { margin-top: var(--s-7); grid-auto-flow: row dense; }
  .project-meta > div, .project-meta .pm-year, .project-meta .pm-palette { grid-column: span 2; }
  .project-meta .pm-roles, .project-meta .pm-tags { grid-column: 1 / 5; }
  .project-hero { padding: var(--s-6) 0; }
  .plate-hero { aspect-ratio: 4 / 3; }
  .project-text > .label, .project-media > .label { grid-column: 1 / 5; }
  .project-prose, .project-loops, .film { grid-column: 1 / 5; }
  .project-text, .project-media { margin-top: var(--s-8); }
  .loop-box { flex-basis: calc(50% - var(--s-5) / 2); }
  .project-next > .label:first-child { grid-column: 1 / 5; }
  .next-title { grid-column: 1 / 5; }
  .all-work { grid-column: 1 / 5; justify-self: start; margin-top: var(--s-5); }
}

/* ── the front end over the transcript (web/static/frontend-host.js) ───────
   While a front end loads (html.fe-loading) the page is plain ground; once
   it reports ready (html.fe-live) its iframe cross-fades in over the
   viewport. In both states the transcript is visually hidden but stays in
   the accessibility tree. With neither class the transcript is the site. */
.fe-loading #transcript, .fe-live #transcript {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; min-height: 0;
}
.fe-loading body, .fe-live body { overflow: hidden; }
#frontend {
  position: fixed; inset: 0; z-index: var(--z-frontend);
  top: var(--bar-h, 0px);
  width: 100vw; height: calc(100vh - var(--bar-h, 0px)); height: calc(100dvh - var(--bar-h, 0px));
  border: 0; margin: 0; padding: 0; display: block; background: transparent;
  visibility: hidden; opacity: 0; /* not display:none: the front end lays out at full size while loading */
  transform-origin: left center;
  transition: opacity var(--d-4) var(--ease-out), transform var(--d-4) var(--ease-out);
}
.fe-live #frontend { visibility: visible; opacity: 1; }

/* ── the site bar (docs/frontend-protocol.md, v1.5; design-pov.md, 5.4) ────
   Drawn by frontend-host.js across the top of every page: the concept line,
   shuffle and Re-imagine. The site owns it, so its style never changes with
   the front end; only its theme does (site.theme sets data-theme; without it
   the bar follows the system). The front end starts below it (--bar-h).
   It's deliberately not in any front end's style, the default's included:
   the device's own sans-serif and neutral greys, no house serif, no paper. */
.site-bar {
  --font-text: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --bg: #0B0B0C; --bg-raised: #18181A; --fg: #F5F5F6; --fg-2: #A1A1A8;
  --rule: rgb(255 255 255 / .12); --rule-strong: rgb(255 255 255 / .28); --accent: #FF5B1F; --accent-ink: #0B0B0C;
  font-family: var(--font-text);
  position: fixed; z-index: var(--z-cta); top: 0; left: 0; right: 0;
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-4);
  min-height: calc(44px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) max(var(--m), env(safe-area-inset-right)) 0 max(var(--m), env(safe-area-inset-left));
  background: var(--bg); color: var(--fg); border-bottom: 1px solid var(--rule);
  transition: background-color var(--d-3) var(--ease-out), color var(--d-3) var(--ease-out), border-color var(--d-3) var(--ease-out);
}
@media (prefers-color-scheme: light) {
  .site-bar:not([data-theme="dark"]) {
    --bg: #FFFFFF; --bg-raised: #F2F2F3; --fg: #111113; --fg-2: #5E5E66;
    --rule: rgb(0 0 0 / .1); --rule-strong: rgb(0 0 0 / .26); --accent: #E5480F; --accent-ink: #FFFFFF;
  }
}
.site-bar[data-theme="light"] {
  --bg: #FFFFFF; --bg-raised: #F2F2F3; --fg: #111113; --fg-2: #5E5E66;
  --rule: rgb(0 0 0 / .1); --rule-strong: rgb(0 0 0 / .26); --accent: #E5480F; --accent-ink: #FFFFFF;
}
.site-bar-line {
  margin: 0; padding: 6px 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left;
  font: 500 14.5px/1.25 var(--font-text); letter-spacing: -.01em;
  color: inherit; background: none; border: 0; cursor: pointer;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .25em;
  text-decoration-color: transparent; transition: text-decoration-color var(--d-2) var(--ease-quint);
}
@media (hover: hover) { .site-bar-line:hover { text-decoration-color: var(--rule-strong); } }
.site-bar-line:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; text-decoration-color: var(--fg); }
.make-own-plus { display: none; }
.make-own-plus svg { display: block; width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }
.site-bar .make-own { font-stretch: normal; letter-spacing: -.005em; }
.site-bar-actions { flex: none; display: flex; align-items: center; gap: var(--s-2); }
.has-bar body { padding-top: var(--bar-h, 45px); } /* the transcript, when it's the site */
.has-bar .site-footer .concept, .has-bar .kicker { display: none; } /* the bar says it */
@media (max-width: 599px) {
  /* the line always reads in full: on phones it wraps to two lines (the bar grows, the front end follows) */
  .site-bar { gap: var(--s-3); padding-top: calc(env(safe-area-inset-top) + 6px); padding-bottom: 6px; }
  .site-bar-line { font-size: 14px; line-height: 1.2; white-space: normal; overflow: visible; text-wrap: balance; }
  /* Re-imagine becomes a round plus button */
  /* (scoped to the bar so it outranks .make-own's base padding, which comes later) */
  .site-bar .make-own { width: 38px; padding: 0; justify-content: center; gap: 0; }
  .site-bar .make-own-dot, .site-bar .make-own-roll, .site-bar .make-own-kbd { display: none; }
  .make-own-plus { display: block; color: var(--accent); }
  .make-own:hover .make-own-plus, .make-own:focus-visible .make-own-plus { color: var(--bg); }
}
@media (max-width: 359px) { .site-bar-line { font-size: 13px; } }
.shuffle-fe {
  position: relative; flex: none; display: grid; place-items: center;
  width: 32px; height: 32px; padding: 0; margin: 0;
  border: 1px solid var(--rule-strong); border-radius: var(--r-pill);
  background: var(--bg-raised); color: var(--fg); cursor: pointer;
  transition: transform var(--d-1) var(--ease-quint), background-color var(--d-2) var(--ease-out), color var(--d-2) var(--ease-out);
}
.shuffle-fe[hidden] { display: none; }
.shuffle-fe svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* hover only where there's a pointer that hovers: on touch it would stick */
@media (hover: hover) { .shuffle-fe:hover { background: var(--fg); color: var(--bg); } }
.shuffle-fe:focus-visible { background: var(--fg); color: var(--bg); }
.shuffle-fe:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.shuffle-fe:active { transform: scale(.94); }
.shuffle-fe.is-spinning svg { animation: shuffle-turn 900ms var(--ease-out); }
@keyframes shuffle-turn { from { transform: rotate(-180deg) scale(.8); opacity: .4; } }
@media (prefers-reduced-motion: reduce) { .shuffle-fe.is-spinning svg { animation: none; } }
.make-own {
  position: relative;
  display: inline-flex; align-items: center; gap: var(--s-3);
  height: 32px; padding: 0 14px 0 12px; margin: 0;
  border: 1px solid var(--rule-strong); border-radius: var(--r-pill); overflow: hidden; isolation: isolate;
  background: var(--bg-raised); color: var(--fg); cursor: pointer;
  font: 500 var(--t-ui)/1 var(--font-text); font-stretch: 90%; letter-spacing: .005em; white-space: nowrap;
  transition: transform var(--d-1) var(--ease-quint), color var(--d-3) var(--ease-out);
}
/* the fill wipes in from the left on hover and focus */
.make-own::before {
  content: ""; position: absolute; inset: 0; z-index: -1; background: var(--fg);
  clip-path: inset(0 100% 0 0); transition: clip-path var(--d-3) var(--ease-out);
}
.make-own-dot {
  flex: none; width: 8px; height: 8px; border-radius: var(--r-pill); background: var(--accent);
  animation: breathe 2.4s ease-in-out infinite;
}
@keyframes breathe { 50% { opacity: .55; } }
/* the label rolls: the line leaves upward and its twin arrives from below */
.make-own-roll { position: relative; display: block; overflow: hidden; height: 1.2em; line-height: 1.2em; }
.make-own-roll > span { display: block; transition: transform var(--d-3) var(--ease-out); }
.make-own-roll > span + span { position: absolute; left: 0; top: 0; transform: translateY(100%); }
.make-own-short { display: none; }
.make-own-kbd {
  font-family: var(--font-mono); font-size: var(--t-meta); letter-spacing: .06em; text-transform: uppercase;
  color: var(--fg-2); transition: color var(--d-3) var(--ease-out);
}
.make-own:hover, .make-own:focus-visible { color: var(--bg); }
.make-own:hover::before, .make-own:focus-visible::before { clip-path: inset(0); }
.make-own:hover .make-own-kbd, .make-own:focus-visible .make-own-kbd { color: color-mix(in oklab, var(--bg) 70%, var(--fg)); }
.make-own:hover .make-own-roll > span:first-child, .make-own:focus-visible .make-own-roll > span:first-child { transform: translateY(-100%); }
.make-own:hover .make-own-roll > span + span, .make-own:focus-visible .make-own-roll > span + span { transform: none; }
.make-own:active { transform: scale(.97); }
.make-own:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
@media (hover: none), (pointer: coarse) { .make-own-kbd { display: none; } }
@media (pointer: coarse) {
  .site-bar { min-height: calc(50px + env(safe-area-inset-top)); }
  .make-own { height: 38px; }
  .shuffle-fe { width: 38px; height: 38px; }
}
@media (max-width: 599px) {
  .make-own-long { display: none; }
  .make-own-short { display: block; }
}
@media (prefers-reduced-motion: reduce) {
  .make-own-dot { animation: none; }
  .make-own::before { transition: none; }
}

/* ── the draft tape (docs/design-pov.md, 5.6) ─────────────────────────────
   Drawn by frontend-host.js while the visitor views their own draft. The
   front end starts below it (--draft-h), so it never covers anything. */
.draft-banner {
  position: fixed; z-index: var(--z-banner); top: var(--bar-h, 0px); left: 0; right: 0;
  display: flex; align-items: center; gap: var(--s-5);
  min-height: 32px;
  padding: 0 max(var(--m), env(safe-area-inset-right)) 0 max(var(--m), env(safe-area-inset-left));
  background: var(--bg); color: var(--fg);
  border-bottom: 2px solid var(--accent);
  font-family: var(--font-mono); font-size: var(--t-meta); line-height: 1.3; letter-spacing: .06em;
  text-transform: uppercase; font-variant-numeric: tabular-nums; white-space: nowrap;
  animation: tape-in var(--d-3) var(--ease-out) backwards;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .draft-banner {
    background: color-mix(in oklab, var(--bg) 88%, transparent);
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  }
}
@keyframes tape-in { from { transform: translateY(-100%); } }
.draft-banner-dot { flex: none; width: 6px; height: 6px; border-radius: var(--r-pill); background: var(--accent); }
.draft-banner-text { flex: 1 1 0; overflow: hidden; text-overflow: ellipsis; }
.draft-banner-which { flex: 1 1 0; overflow: hidden; text-overflow: ellipsis; text-align: center; color: var(--fg-2); }
.draft-banner-actions { flex: 1 1 0; display: flex; justify-content: flex-end; gap: var(--s-5); }
.draft-banner button {
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--fg);
  background: none; border: 0; padding: var(--s-2) 0; cursor: pointer;
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .3em;
  text-decoration-color: var(--rule-strong);
  transition: text-decoration-color var(--d-2) var(--ease-quint);
}
.draft-banner button:hover { text-decoration-color: var(--fg); }
.draft-banner button:disabled { color: var(--fg-2); cursor: default; }
.draft-banner-short { display: none; }
.fe-draft #frontend {
  top: calc(var(--bar-h, 0px) + var(--draft-h, 34px));
  height: calc(100vh - var(--bar-h, 0px) - var(--draft-h, 34px)); height: calc(100dvh - var(--bar-h, 0px) - var(--draft-h, 34px));
}
@media (max-width: 767px) {
  .draft-banner-which { display: none; }
}
@media (max-width: 599px) {
  .draft-banner { gap: var(--s-3); }
  .draft-banner-long { display: none; }
  .draft-banner-short { display: inline; }
  .draft-banner-actions { gap: var(--s-4); }
}
/* the front end's credit (v1.9), after the concept line */
.site-bar-credit { color: var(--fg-2); font-weight: 450; }
.site-bar-credit[hidden] { display: none; }
/* a build in progress (v1.11, build-modal.js sets html.bm-generating): the
   persistent site bar wears the same moving diagonal stripes as the builder */
@keyframes site-bar-stripes { to { background-position: 28.28px 0; } }
html.bm-generating .site-bar {
  background-image: repeating-linear-gradient(-45deg, var(--bg) 0 10px, var(--accent) 10px 20px);
  background-size: 28.28px 28.28px;
  animation: site-bar-stripes 1.1s linear infinite;
}
/* the line keeps a backing of the theme colour over the stripes */
html.bm-generating .site-bar-line {
  background: color-mix(in oklab, var(--bg) 88%, transparent); border-radius: 4px; padding-inline: 8px; margin-inline: -8px;
}
@media (prefers-reduced-motion: reduce) { html.bm-generating .site-bar { animation: none; } }

/* the Re-imagine button during a build (v1.11, build-modal.js): "Building…" on
   moving accent stripes, in the same place; just after, how it went
   ("Version 3 is ready"); both open the builder */
.make-own-status { display: none; }
html.bm-generating .site-bar .make-own-roll, html.bm-generating .site-bar .make-own-kbd,
html.bm-btn-status .site-bar .make-own-roll, html.bm-btn-status .site-bar .make-own-kbd { display: none; }
html.bm-generating .make-own-status, html.bm-btn-status .make-own-status { display: block; line-height: 1.2em; }
/* solid accent, so it stands out on the striped bar */
html.bm-generating .site-bar .make-own, html.bm-btn-status .site-bar .make-own {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600;
}
html.bm-generating .site-bar .make-own-dot { background: var(--accent-ink); animation: bm-blink-dot 1s steps(2, jump-none) infinite; }
@keyframes bm-blink-dot { 50% { opacity: .15; } }
html.bm-btn-status .site-bar .make-own-dot { background: var(--accent-ink); animation: none; }
@media (max-width: 599px) {
  /* the round button: stripes say it; the plus steps aside */
  html.bm-generating .site-bar .make-own-plus, html.bm-generating .make-own-status { display: none; }
  html.bm-btn-status .make-own-status { display: none; }
}
@media (prefers-reduced-motion: reduce) { html.bm-generating .site-bar .make-own-dot { animation: none; } }

/* building paused for the budget (v1.12, build-modal.js sets html.bm-paused):
   the Re-imagine button stays as it is and still opens the builder; only its
   dot goes still and hollow (its title and label say why). Subtle on purpose. */
html.bm-paused:not(.bm-generating):not(.bm-btn-status) .site-bar .make-own-dot { background: transparent; box-shadow: inset 0 0 0 1.5px var(--fg-2); animation: none; }
html.bm-paused:not(.bm-generating):not(.bm-btn-status) .site-bar .make-own:hover .make-own-dot, html.bm-paused:not(.bm-generating):not(.bm-btn-status) .site-bar .make-own:focus-visible .make-own-dot { box-shadow: inset 0 0 0 1.5px var(--bg); }
@media (max-width: 599px) { html.bm-paused:not(.bm-generating) .make-own-plus { color: var(--fg-2); } }
