/* ══════════════════════════════════════════════════════════════════════
   One screen. No scroll. Everything reachable without moving the page.
   Geist, a strict 12-column field, and the work held as a deck.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Scheme, type, space, state, motion, layers ─────────────────────────
   All of it lives in tokens.css now, which every page loads before this
   one. Nothing in this file may declare a raw colour, size, duration or
   z-index: if a value is needed that is not in there, it goes in there
   first. */

* { box-sizing: border-box; }
/* `hidden` means hidden. Without this it is a 0,1,0 rule losing to every
   component that sets its own display, which is how a hidden button went
   on being drawn: `.solid[hidden]` and `.solid.ico` are both 0,2,0 and
   the later one won. One rule, at the top, that nothing has to remember. */
[hidden] { display: none !important; }
html, body { height: 100%; }
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  margin: 0;
  /* Something has to be behind the glass or it is only a darker fill.
     Two washes, far enough down that they read as unevenness in the
     black rather than as colour, and fixed so the cards move over them.
     These were written before and then wiped on the next line: the
     background shorthand takes the image with it. */
  background-color: var(--bg);
  background-image: var(--wash);
  background-attachment: fixed;
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--t-md);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.011em;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;              /* the whole point: one screen */
}
a { color: inherit; text-decoration: none; -webkit-tap-highlight-color: transparent; }
button { font: inherit; color: inherit; -webkit-tap-highlight-color: transparent; }
.mono { font-family: var(--mono); font-weight: 400; letter-spacing: 0; font-variant-numeric: tabular-nums; }
.sp { flex: 1 1 auto; }
.defs { position: absolute; width: 0; height: 0; }

/* Icons: one stroke weight throughout, never filled unless it must be */
svg { width: 16px; height: 16px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.25; stroke-linecap: round; stroke-linejoin: round; }
.p-record .solid svg path { fill: currentColor; stroke: none; }
.flip { transform: rotate(180deg); }

/* ── Field ──────────────────────────────────────────────────────────── */
/* A tray of cards on the page ground: each one rounded, bordered and set
   apart by a real gap, so the field reads as objects laid out rather than
   as a sheet ruled into boxes. */
.screen {
  position: relative;          /* the film square is placed against this */
  height: 100dvh;
  padding: var(--gap);
  background: var(--bg);
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: 44px minmax(0,1fr) minmax(0,.58fr) minmax(0,.95fr);
}
.p {
  position: relative;
  min-width: 0; min-height: 0;
  padding: var(--sp-18) var(--sp-20);
  /* Dark glass: the fill is the panel let down to two thirds, so the
     card reads darker than the flat one did, and the blur gives it
     something to be made of. The lift along the top edge is the tell. */
  background: color-mix(in srgb, var(--panel) 64%, transparent);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: inset 0 1px 0 var(--lift), 0 1px 22px rgba(0, 0, 0, .45);
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* Where the browser cannot blur, the card stays solid rather than
   turning into a flat wash of the page behind it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .p { background: var(--panel); }
}
/* Helmut Lang: the name carries the page and everything else is small and
   precise beside it. Work and the studio are the two assets, so they take
   the width and the height; contact is a line, not a panel. */
/* One card down the left: who he is, then where he is, then how to
   reach him. They were two cards saying the same thing in two boxes,
   and the globe is what absorbs whatever height is left over, so the
   column cannot open a hole in itself at any window size. */
.p-about   { grid-column: 1 / 4;   grid-row: 2 / 5; }
.p-work    { grid-column: 4 / 9;   grid-row: 2 / 4; }
.p-studio  { grid-column: 9 / 13;  grid-row: 2 / 4; }
/* The bottom row was two cards; it is three now, and the width comes
   out of Music and Canvas. Both were wider than their contents needed —
   a disc and a single stroke — and neither loses anything it was using. */
.p-record  { grid-column: 4 / 7;   grid-row: 4; }
.p-draw    { grid-column: 7 / 10;  grid-row: 4; }
.p-setup   { grid-column: 10 / 13; grid-row: 4; }

/* ── Bar ────────────────────────────────────────────────────────────── */
.bar {
  grid-column: 1 / 13; grid-row: 1;
  position: relative;
  display: flex; align-items: center; gap: var(--sp-14);
  /* The same gutter as a card, and the same fill and hairline, so a name
     starting 4px shy of where every card's label starts was the one
     thing giving it away.

     The corner is the exception. --r is chosen for a card four hundred
     pixels tall, where sixteen is a four per cent nick out of the
     corner; on a bar forty-four pixels tall the same number is more
     than a third of the height, and the shared token stopped reading as
     a shared corner and started reading as a pill. The bar is between a
     button and a card in size, so it takes the step between them. */
  padding: 0 var(--sp-20);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-12);
  box-shadow: inset 0 1px 0 var(--lift);
}
/* The name and the role are two sizes, so centring their boxes left
   them sitting at two different heights. They share a baseline instead,
   which is the only alignment that means anything between two runs of
   type. */
.bar-id { display: inline-flex; align-items: baseline; gap: var(--sp-14); min-width: 0; }
.mk { font-weight: var(--w-med); letter-spacing: -0.024em; font-size: var(--t-md); }
/* Still used: the spec page's bar names the page with it. The dashboard
   dropped its line, because the About card says the same thing two
   inches below and saying it twice made neither one land. */
.bar-role { color: var(--muted); white-space: nowrap; }

/* ── Nav ────────────────────────────────────────────────────────────────
   It was hidden on the one screen, on the reasoning that everything is
   already in view so there is nowhere to go. True, and beside the point:
   what a reader wants from a nav here is not travel but naming — which
   of these six is the work, which is the music. So it stays, and
   pointing at a name lifts that card's outline. Below the threshold the
   page scrolls and the same links do the ordinary job. */
/* Centred on the bar itself, not in the space left over between the
   name and the clock: those two are different widths and always will
   be, so a flex gap would land the nav a little off centre and it would
   drift every time the clock ticked past a wider digit. */
.bar-nav {
  display: flex; align-items: center; gap: var(--sp-2);
  position: absolute; left: 50%; transform: translateX(-50%);
}
.bar-nav a {
  /* The same caps marker the cards wear on their own labels: these point
     at those cards, so they are set as the cards are, not as prose. */
  font-size: var(--t-xs); font-weight: var(--w-med);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--faint);
  padding: var(--sp-6) var(--sp-8); border-radius: var(--r-8);
  transition: color var(--d-base) var(--ease), background var(--d-base) var(--ease);
}
@media (hover: hover) { .bar-nav a:hover { color: var(--fg); background: var(--s-rest); } }
/* Where you are. The case panel covers the field without scrolling it,
   so nothing else in the bar reports that you left the dashboard and
   went into a project — Work sat at --faint the whole time you were
   inside one. It takes the ink and the same resting fill a hover uses:
   being somewhere is a fact about the page, not an invitation, so it
   should not out-shout the link you are actually pointing at. */
.bar-nav a[aria-current] { color: var(--fg); background: var(--s-rest); }
.bar-nav a:focus-visible { outline: var(--s-ring); outline-offset: 2px; }

/* ── The setup list ─────────────────────────────────────────────────────
   Name on the left, the detail after it in the muted grey, one per
   line. No columns: half these entries have a note and half do not, and
   a table of mostly-empty second cells looks like missing data rather
   than like a short answer. */
.gear-wrap { margin-top: var(--sp-12); min-height: 0; overflow: hidden; }
.gear-group + .gear-group { margin-top: var(--sp-14); }
/* Not caps. The caps marker on this page is a card's own tab — the
   SETUP above these — and using it again for the groups inside put the
   card and its contents at the same level. Sentence case reads as
   subordinate, which is what they are. */
.gear-h {
  font-size: var(--t-xs); font-weight: var(--w-med);
  letter-spacing: -0.01em;
  color: var(--faint);
  margin: 0 0 var(--sp-4);
}
.gear { list-style: none; margin: 0; padding: 0; }
.gear li {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 0 var(--sp-8);
  padding: var(--sp-2) 0;
}
.gear-n { color: var(--fg); font-size: var(--t-sm); }
.gear-t { color: var(--faint); font-size: var(--t-xs); }

/* ── The other way round: the field goes to ground ─────────────────────
   First attempt put the bar and the identity card on the page's black
   and left the rest raised. This is the inverse: the cards drop to
   black and are held by their hairline alone, and the bar keeps its
   fill — so the one object floating above the field is the chrome,
   which is the only thing that is genuinely above it.

   The blur comes off with the fill. There is nothing to see through a
   surface the same colour as what is behind it, and a backdrop-filter
   that changes nothing is a compositing layer for free — six of them
   on a page that has canvases running in it. */
.p {
  background: var(--bg);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── A live embed, carried over from the project's own page ────────────
   Shown at phone proportion, as the page shows it: the studio is built
   to be used on a device, and a panel-width iframe would be a desktop
   app squeezed into a column. The link underneath goes to the full
   thing, so both are available exactly as they are on the page. */
.case-embed {
  position: relative;
  width: 100%; max-width: 400px;
  aspect-ratio: 400 / 780;
  margin: var(--sp-18) 0 var(--sp-10);
  border: 1px solid var(--line);
  border-radius: var(--r-16);
  overflow: hidden;
  background: var(--card);
}
.case-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.case-embed-go {
  display: inline-flex; align-items: center;
  font-size: var(--t-sm);
  color: var(--muted);
  text-decoration: underline;
  text-decoration-color: var(--edge);
  text-underline-offset: 3px;
  transition: color var(--d-base) var(--ease), text-decoration-color var(--d-base) var(--ease);
}
@media (hover: hover) {
  .case-embed-go:hover { color: var(--fg); text-decoration-color: var(--fg); }
}

/* Prose in the case panel, when it has been read off the project's own
   page. Same measure and colour as the page it came from. */
.case-para {
  margin: 0 0 var(--sp-12);
  font-size: var(--t-md);
  line-height: var(--lh-body);
  color: var(--muted);
}
.case-para:last-child { margin-bottom: 0; }

/* ── Grid items do not get to set the width ─────────────────────────────
   A grid item's automatic minimum size is its min-content, so any one
   card whose contents will not wrap below some width silently widens
   its track — and every other card with it. On a 375px phone that put
   the whole field at 402 and left 37px of horizontal scroll on a page
   that is not supposed to scroll sideways at all. The cards already
   handle their own overflow; none of them needs to push. */
.screen > .p, .screen > .bar { min-width: 0; }

/* The card the nav is pointing at. Still no fill — a fill would make it
   look selected, and nothing has been selected; the pointer is only
   resting on a word. But border-colour alone was 0.075 to 0.19 on a
   hairline, which is a change you have to already be looking for. So
   the line goes up another rung and the card carries a little light
   off its edge: enough to find in peripheral vision, not enough to
   read as a state. */
.p {
  transition: border-color var(--d-base) var(--ease),
              box-shadow var(--d-base) var(--ease);
}
.p.lit {
  /* Down a rung from --e-press. At .24 the card being pointed at had a
     harder edge than anything else on the field, which read as selected
     rather than as indicated — and nothing has been selected. .19 is
     still two and a half times the resting hairline, and the light off
     the edge does the rest. */
  border-color: var(--e-hover);
  box-shadow: inset 0 1px 0 var(--lift),
              0 1px 22px rgba(0, 0, 0, .45),
              0 0 0 1px rgba(238, 238, 236, .035),
              0 0 22px rgba(238, 238, 236, .035);
}
/* The two cards carved around the film square have no border to lift;
   their outline is the path drawn over them, so the light goes on the
   stroke instead of around the box. */
.film-edge path {
  transition: stroke var(--d-base) var(--ease), filter var(--d-base) var(--ease);
}
.film-edge.lit path {
  stroke: var(--e-hover);
  filter: drop-shadow(0 0 5px rgba(238, 238, 236, .14));
}
.film.lit {
  border-color: var(--e-hover); color: var(--fg);
  box-shadow: var(--s-inset-hi), 0 0 16px rgba(238, 238, 236, .05);
}
.bar-item { display: inline-flex; align-items: center; gap: var(--sp-6); color: var(--muted); font-size: var(--t-sm); }
.dot { width: 6px; height: 6px; border-radius: var(--r-2); background: var(--accent); }

/* ── Labels ─────────────────────────────────────────────────────────── */
/* Mono earns its place on numbers that tick, on coordinates, on anything
   that has to align in a column. It does not earn it on a word like
   "About" — tiny tracked-out uppercase monospace is the house style of
   every generated landing page of the last two years, and it reads as
   costume. Labels are set in the sans, quietly. */
.lbl {
  /* A caps label: it is a tab on a card, not a sentence, and at this
     size uppercase reads as a marker rather than as a second heading
     competing with the card's own title. */
  font-size: var(--t-xs); font-weight: 500;
  text-transform: uppercase; letter-spacing: .075em;
  color: var(--faint);
  display: flex; align-items: center; gap: var(--sp-6);
}
.lbl i { font-style: normal; opacity: .5; }

/* ── About ──────────────────────────────────────────────────────────── */
.say { margin: var(--sp-20) 0 0; font-size: var(--t-lg); font-weight: 400; line-height: 1.26; letter-spacing: -0.022em; }
.say-sub { margin: var(--sp-8) 0 0; color: var(--muted); font-size: var(--t-md); line-height: 1.45; }
/* The one fact the page never stated: what he is actually available for.
   It earns the space that the cell was otherwise leaving empty. */
/* Three figures, set large enough to be read before the paragraph is.
   They are what anyone hiring actually wants off the page first. */
.figs {
  /* The rule wants air on both sides of it, not just below. margin-top
     was auto, which resolves to nothing here because the globe takes
     all the slack in this column. */
  margin: var(--sp-24) 0 0;
  /* Columns from the count, not a hardcoded three: the run figure only
     appears when Strava answers, and the row has to read as deliberate
     at three or at four rather than wrapping one orphan onto a second
     line. */
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  padding-top: var(--sp-18);
  border-top: 1px solid var(--hair);
}
.figs > div + div { padding-left: var(--sp-14); border-left: 1px solid var(--hair); }
.figs dt { font-size: var(--t-xs); color: var(--faint); }
.figs dd {
  margin: var(--sp-4) 0 0; font-size: var(--t-lg);
  font-weight: 500; letter-spacing: -0.038em; line-height: 1;
}
.p-about .soc { margin-top: var(--sp-10); }

/* ── Contact ────────────────────────────────────────────────────────────
   Folded into the about card. They were two thin panels stacked down the
   left, each with more air than content; together they are one block
   that reads top to bottom — who, what, available for, and how to get
   hold of him. */
.mailto {
  margin: var(--sp-16) 0 var(--sp-4);
  font-weight: 500;
  display: inline-flex; align-items: center; gap: var(--sp-8);
  font-size: var(--t-md); letter-spacing: -0.02em;
}
.mailto svg { color: var(--faint); transition: color var(--d-fast) var(--ease); }
@media (hover: hover) { .mailto:hover svg { color: var(--fg); } }
.soc { list-style: none; display: flex; gap: var(--sp-14); margin: var(--sp-8) 0 0; padding: 0; }
.soc a { display: inline-flex; align-items: center; gap: var(--sp-4); font-size: var(--t-sm); color: var(--muted); transition: color var(--d-fast) var(--ease); }
/* The arrow goes where the link goes. */
.soc svg { width: 11px; height: 11px; color: var(--faint); transition: transform var(--d-base) var(--ease), color var(--d-fast) var(--ease); }
@media (hover: hover) {
  .soc a:hover, .soc a:hover svg { color: var(--fg); }
  /* The arrow points the way the link goes, and leans that way on
     hover: out to the right, not up and off the corner. */
  .soc a:hover svg { transform: translateX(3px); }
}

/* ── Work: a ruled index ────────────────────────────────────────────────
   Seven rows of exactly equal height with a hairline above each, which
   makes the rules evenly spaced by construction rather than by a spacing
   value that has to be retuned every time the card changes size. A
   catalogue, not a list: name and year on one baseline, the credits
   under them, and the rule doing the separating instead of a gap. */
.work {
  list-style: none; margin: var(--sp-12) 0 0; padding: 0;
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
}
/* Direct children only. `.work li` also matched every tag, so each tag
   became a full-height flex row and stretched to a third of the card
   instead of hugging its own word. */
.work > li {
  flex: 1 1 0; min-height: 0;
  display: flex; flex-direction: column; justify-content: center;
  border-top: 1px solid var(--hair);
  padding: var(--sp-2) 0;
}
/* No rule under the label: the label is already the top of the list. */
.work > li:first-child { border-top: 0; }

/* The rules do not react at all. They went through a version that drew
   itself across from the left and a version that brightened in place,
   and both were a third signal on a row that already answers in the
   year and the tags. A divider's job is to separate seven things evenly;
   the moment one of them lights up it stops being a rule and starts
   being a state, and the list reads as unevenly ruled. */
/* Tags rather than a middot-strung fragment: what the job was, scannable
   at a glance and the same shape on every row. */
/* The tag is a component, so it is written as one. It was only ever
   `.work .tags`, which meant it did not exist anywhere else: put it on a
   page of its own and it fell back to a bulleted list. A thing that only
   works inside one parent is not a part, it is a local arrangement. */
.tags {
  list-style: none; display: flex; flex-wrap: wrap; gap: var(--sp-4);
  margin: var(--sp-6) 0 0; padding: 0;
}
.tags > li {
  flex: 0 0 auto;
  font-size: var(--t-xs); line-height: var(--lh-flat); color: var(--faint);
  /* One rung up from --hair. At .045 over black the chip was about
     11/255 — present in the stylesheet and not quite present on the
     screen, which got worse when the cards lost their fill and there
     was no longer a lit surface behind it. --line is 18/255: still a
     hairline, but one you can see without deciding to. */
  padding: var(--sp-4) var(--sp-8); border: 1px solid var(--line); border-radius: var(--r-4);
  white-space: nowrap;
  transition: color var(--d-base) var(--ease), border-color var(--d-base) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .work > li:has(a:hover) .tags > li { color: var(--muted); border-color: var(--edge); }
}
.work a {
  display: flex; align-items: baseline; gap: var(--sp-14);
  padding: 0;
  /* A project name is a card title like any other. It was running at
     34px beside headings at 20px, which made one card shout across a
     field where every card is meant to carry the same weight. */
  font-size: var(--t-lg);
  font-weight: 500;
  letter-spacing: -0.022em;
  line-height: 1.2;
  color: var(--fg);
  transition: color var(--d-base) var(--ease);
}
.work .nm { flex: 1 1 auto; min-width: 0; }
.work .yr { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0; color: var(--faint); flex: none; margin-left: auto; transition: color var(--d-base) var(--ease); }

.work a { transition: color var(--d-base) var(--ease); }
@media (hover: hover) and (pointer: fine) {
  /* The rest of the index steps back. Taking this out and living with
     the alternative made the case for it: with nothing dimming, the row
     under the pointer had to announce itself through its own year and
     tags alone, and small print brightening slightly is not enough to
     read as focus on a list of seven.

     Both rules go through :has, or the dim outranks the lift. :has()
     carries the specificity of its argument, so `.work:has(a:hover) a`
     scores higher than a bare `.work a:hover` and the row you are
     pointing at would fade along with the rest.

     --fg to --muted is 236 to 142: a clear step back, and not so far
     that the other six stop being readable — they are still the index,
     just not the thing being asked about. */
  .work:has(a:hover) a { color: var(--muted); }
  .work:has(a:hover) a:hover { color: var(--fg); }
  /* The hovered row's year lifts out of --faint. It used to be set to
     --muted here, which dimmed the year of the row you were on. */
  .work a:hover .yr { color: var(--muted); }
}
.work a:focus-visible { outline: none; color: var(--fg); }

/* One line, always: a longer credit used to wrap to two and nudge the
   whole card as you moved down the index. */

/* ── Studio ─────────────────────────────────────────────────────────────
   The tool opens on its own page; what sits here is its output. Four
   faces drawn on a canvas by this page, ticking, which is a truer sample
   of a watch-face tool than a screenshot of one, and it fills the card
   with the thing the card is about. */
.p-studio { padding: var(--sp-18) var(--sp-20); }
.faces { flex: 1 1 auto; min-height: 120px; width: 100%; display: block; margin: var(--sp-14) 0 0; }

/* ── Which face ─────────────────────────────────────────────────────────
   The marks under the face. They were painted on the canvas at 1.7px,
   which is a picture of a control rather than one: nothing to click,
   nothing to focus, nothing for a screen reader. The dot stays small
   because it is a mark, but the button around it is a real target. */
.face-dots {
  display: flex; justify-content: center; align-items: center;
  gap: var(--sp-2);
  flex: none;
  margin-top: var(--sp-4);
}
.face-dot {
  appearance: none; border: 0; background: none; cursor: pointer;
  padding: var(--sp-8);              /* 22px of target around a 6px dot */
  line-height: 0;
  border-radius: var(--r-full);
}
.face-dot i {
  display: block; width: 6px; height: 6px;
  border-radius: var(--r-full);
  background: var(--e-rest);
  /* Colour only. Nothing here moves, for the same reason no other
     control on this page does. */
  transition: background var(--d-base) var(--ease);
}
.face-dot[aria-pressed="true"] i { background: var(--fg); }
@media (hover: hover) {
  .face-dot:hover i { background: var(--muted); }
  .face-dot[aria-pressed="true"]:hover i { background: var(--fg); }
}
.face-dot:focus-visible { outline: var(--s-ring); outline-offset: 0; }
.st-h { margin: var(--sp-18) 0 0; font-size: var(--t-lg); font-weight: 500; letter-spacing: -0.032em; line-height: 1; }
.st-p { margin: var(--sp-10) 0 var(--sp-20); font-size: var(--t-sm); line-height: 1.42; color: var(--muted); max-width: 42ch; }

/* ── Button ─────────────────────────────────────────────────────────────
   One component. `.solid` is the label form, `.solid.sm` is the same
   thing tighter, `.solid.ico` is the icon-only form. They differ in
   shape and nothing else: every one of them climbs the same state
   ladder, so a press looks like a press wherever you are on the page. */
.solid {
  appearance: none; cursor: pointer; align-self: flex-start;
  display: inline-flex; align-items: center; gap: var(--sp-8);
  padding: var(--sp-8) var(--sp-12); border-radius: var(--r-8);
  background: var(--s-rest);
  border: 1px solid var(--e-rest);
  box-shadow: var(--s-inset);
  -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
  color: var(--fg);
  font-size: var(--t-sm); font-weight: var(--w-med); line-height: var(--lh-flat);
  /* Nothing here moves. A button made of glass answers by clearing and
     catching more light along its top edge, which is what glass does;
     lifting it a pixel and putting it back was a twitch, and two
     twitches at once read as a wobble. */
  transition: background var(--d-base) var(--ease),
              border-color var(--d-base) var(--ease),
              box-shadow var(--d-base) var(--ease),
              backdrop-filter var(--d-base) var(--ease),
              -webkit-backdrop-filter var(--d-base) var(--ease),
              color var(--d-base) var(--ease);
}
.solid svg { width: 13px; height: 13px; transition: transform var(--d-base) var(--ease); }
@media (hover: hover) {
  .solid:hover {
    background: var(--s-hover);
    border-color: var(--e-hover);
    box-shadow: var(--s-inset-hi);
    -webkit-backdrop-filter: var(--s-blur-hi); backdrop-filter: var(--s-blur-hi);
  }
  /* the arrow still leans the way the button goes: that is direction,
     not decoration, and it is the one thing left that moves */
  .solid:hover svg { transform: translateX(3px); }
  /* A flipped arrow has to keep its rotation through the hover, or the
     base rule above replaces the whole transform: the arrow on the back
     button un-flipped to point right and then slid right, on a control
     whose entire job is going left. Rotation first, then the travel,
     which in the rotated frame moves it back towards the dashboard. */
  .solid:hover svg.flip { transform: rotate(180deg) translateX(3px); }
}
.solid:active {
  background: var(--s-press); border-color: var(--e-press);
  box-shadow: var(--s-sunk);
  -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
}
.solid:focus-visible { outline: var(--s-ring); outline-offset: 3px; }
/* A variant has to be visibly a variant. Both were within a pixel of
   each other before, so putting them on the grid landed them on the
   same step and the small button stopped being small. */
.solid.sm { padding: var(--sp-6) var(--sp-10); }

/* ── Key ────────────────────────────────────────────────────────────────
   The panel's one action. Every other control on this page is an
   outline on black, which is right for a field of equal things and
   wrong for a bar containing exactly one thing worth doing. It keeps
   the shape and the size and takes the ink: dark type on light, which
   no other control here does, so it cannot be mistaken for another
   hairline. */
.solid.key {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
  box-shadow: none;
}
@media (hover: hover) {
  .solid.key:hover {
    background: #ffffff;
    border-color: #ffffff;
    color: var(--bg);
    box-shadow: none;
  }
}
.solid.key:active { background: var(--muted); border-color: var(--muted); color: var(--bg); }

/* The icon form. Same fill, same hairline, same corner as the label form
   and the same ink: it was a disc in the muted grey, which read as a
   different component rather than as the same one without a word in it.
   The icon does not travel on hover because there is nowhere for it to
   go. */
.solid.ico {
  width: 33px; height: 33px; padding: 0;
  display: inline-grid; place-items: center;
}
.solid.ico svg { width: 12px; height: 12px; }
@media (hover: hover) {
  .solid.ico:hover svg { transform: none; }
}

/* Disabled comes last, so a variant cannot out-rank it. */
.solid[aria-disabled="true"], .solid:disabled { color: var(--faint); cursor: default; }
@media (hover: hover) {
  .solid[aria-disabled="true"]:hover, .solid:disabled:hover {
    background: var(--s-rest); border-color: var(--e-rest); color: var(--faint);
    box-shadow: var(--s-inset);
    -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
  }
  .solid[aria-disabled="true"]:hover svg, .solid:disabled:hover svg { transform: none; }
}
.solid[aria-disabled="true"]:active, .solid:disabled:active {
  transform: none; background: var(--s-rest); border-color: var(--e-rest); box-shadow: var(--s-inset);
}
.p-record .solid.sm:hover svg { transform: none; }

/* ── Globe ────────────────────────────────────────────────────────────
   A band inside the about card. It is the only flexible thing in that
   column, so every spare pixel goes into the earth rather than into a
   gap, and contact stays on the floor of the card. */
/* The grab cursor is the whole affordance. A line of instructions under
   the globe said out loud what the cursor already says by changing, and
   a dashboard that explains itself in words is one that stopped
   trusting its own controls. */
#globe { width: 100%; flex: 1 1 auto; min-height: 150px; display: block; margin: var(--sp-22) 0 0; }


/* ── Record ─────────────────────────────────────────────────────────── */
.p-record { align-items: center; }
.p-record .lbl { align-self: flex-start; }
#record { flex: 1 1 auto; min-height: 0; width: 100%; display: block; }
.rec-foot { display: flex; align-items: center; gap: var(--sp-8); width: 100%; margin-top: var(--sp-4); }
/* The transport is the icon button; only its own icon swapping stays. */
.rec-play .ic-s { display: none; }
.rec-play.on .ic-s { display: block; }
.rec-play.on .ic-p { display: none; }
.rec-play svg path { fill: currentColor; stroke: none; }
.rec-play .ic-p { margin-left: 1px; }   /* the triangle's own optical centre */
.rec-now { font-family: var(--sans); font-size: var(--t-xs); letter-spacing: -0.01em; color: var(--faint); min-width: 0; flex: 1 1 auto; }

/* ── What is on ─────────────────────────────────────────────────────────
   Two lines in a card this size: the state above, the track below. The
   title takes the whole width and truncates; the artist follows it and
   gives way first, because a title you cannot read is worse than an
   artist you cannot read. */
.rec-link {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--sp-2);
  text-decoration: none; color: inherit;
  min-width: 0; max-width: 100%;
}
.rec-line {
  display: flex; align-items: baseline; gap: var(--sp-6);
  min-width: 0; max-width: 100%;
}
/* Not caps, for the same reason the setup groups are not: the caps
   marker belongs to a card's own tab — the MUSIC above this — and using
   it again inside put the card and its contents on one level. */
.rec-state {
  display: flex; align-items: center; gap: var(--sp-6);
  letter-spacing: -0.01em;
  color: var(--faint);
}
/* A dot, and it only exists while something is actually playing. */
.rec-state.on::before {
  content: ""; width: 5px; height: 5px; flex: none;
  /* The page's ink, not the accent. Blue read as a status light on a
     card that is otherwise entirely monochrome. */
  border-radius: var(--r-full); background: var(--fg);
  animation: recPulse 2.4s var(--ease-io) infinite;
}
@keyframes recPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .rec-state.on::before { animation: none; } }
/* Both truncate, but the artist gives way twice as fast: a title you
   cannot read is worse than an artist you cannot read. */
.rec-ttl {
  flex: 0 1 auto; min-width: 0;
  color: var(--fg); font-weight: var(--w-med);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rec-art {
  flex: 0 2 auto; min-width: 0;
  color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
@media (hover: hover) {
  a.rec-link:hover .rec-ttl { text-decoration: underline; text-decoration-color: var(--edge); text-underline-offset: 3px; }
  a.rec-link:hover .rec-state { color: var(--muted); }
}

/* ── Globe foot ─────────────────────────────────────────────────────── */
.globe-foot {
  display: flex; flex-direction: row; align-items: baseline;
  gap: 0 var(--sp-6); margin-top: var(--sp-8); flex: none;
}
.gf-city { font-size: var(--t-lg); font-weight: 500; letter-spacing: -0.03em; line-height: 1.05; }
.gf-country { font-size: var(--t-lg); font-weight: 400; letter-spacing: -0.03em; line-height: 1.05; color: var(--muted); }

/* ── Reveal, grain, grid ────────────────────────────────────────────── */
.grain { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; opacity: .03; z-index: var(--z-grain); mix-blend-mode: overlay; }
#slats { position: fixed; inset: 0; z-index: var(--z-slats); display: flex; pointer-events: none; }
#slats i { flex: 1 1 auto; background: var(--bg); transform-origin: top center; }
#slats.go i { transition: transform var(--d-reveal) var(--ease-io); transform: scaleY(0); }
#slats.done { display: none; }
/* ── Narrow: one screen stops being kind, so it yields ──────────────────
   The single screen needs width and height both. At 1090 by 717 the
   twelve columns still applied and the about card was 260px wide with a
   paragraph in it, so the threshold tests height as well: too short is
   as unusable as too narrow. Below it the page becomes a page and
   scrolls, on six columns, and every card is reassigned. Leaving one
   out is what put the canvas card on columns 7 to 13 of a six-column
   grid, which is how the layout came apart. */
@media (max-width: 1280px), (max-height: 760px) {
  body { overflow: auto; }
  .screen {
    height: auto; min-height: 100dvh;
    grid-template-columns: repeat(6, 1fr);
    grid-template-rows: auto;
  }
  /* Rows are pinned, not left to auto-placement: the cards are in DOM
     order about, work, studio, and sparse placement will not go back to
     fill the half row beside about, so studio ends up below the index
     with a hole where it should have been. */
  .bar {
    grid-column: 1 / 7; grid-row: 1;
    height: auto; min-height: 44px;
    flex-wrap: wrap; padding: var(--sp-8) var(--sp-20); gap: var(--sp-6) var(--sp-10);
    position: sticky; top: var(--gap); z-index: var(--z-bar);
    /* Real glass: the bar has to let the page move under it, so the
       fill comes down and the blur goes up until you can see it work. */
    /* The blur stays strong, because the bar has cards passing under it.
       The saturation boost is what turned it milky grey, so it goes. */
    -webkit-backdrop-filter: blur(26px);
    backdrop-filter: blur(26px);
    background: color-mix(in srgb, var(--panel) 62%, transparent);
    box-shadow: var(--s-inset), 0 10px 28px rgba(0, 0, 0, .5);
  }
  .bar .sp { display: none; }
  .bar-nav {
    /* back into the flow: on a scrolling page it is a row under the
       name, not a thing floating in the middle of a wrapped bar */
    position: static; left: auto; transform: none;
    display: flex; align-items: center; gap: var(--sp-2);
    /* The row is the full width of the bar, so the labels are spread
       across it rather than bunched at the left with forty-odd pixels
       of nothing after Images. The outer two then land on the same
       verticals as the name and the clock above them, and the gap
       between labels is what the row has left over instead of a fixed
       number that happens to leave a tail. */
    justify-content: space-between;
    /* 100% is the bar's content width, which is where the name starts
       and where the clock ends. The row has to reach a link's padding
       further than that at each end for the two outer labels' text to
       land on those same verticals, so the basis carries both pulls —
       otherwise the row stops 4px inside the clock and the spread looks
       like it missed. */
    order: 3; flex-basis: calc(100% + 2 * var(--sp-4));
    /* Pulled out by exactly the nav link's own padding, so the first
       label's text starts on the same vertical as the name above it. */
    margin: var(--sp-2) calc(-1 * var(--sp-4)) calc(-1 * var(--sp-2));
    overflow-x: auto; scrollbar-width: none;
  }
  .bar-nav::-webkit-scrollbar { display: none; }
  /* Six labels wanted 343px of a 313px row, so Images hung a pixel past
     the bar's own right edge and the last section on the page was the
     one you could not see. The row scrolls, which saved it from being
     lost but not from being hidden: a nav that keeps a section behind a
     sideways swipe, with nothing to say the swipe exists, is a nav with
     five items as far as anyone can tell.

     The 30px comes back out of the padding rather than the type. Half
     the horizontal padding is 48px across six links, which is more than
     enough, and it leaves the size, weight, caps and tracking exactly
     as they are everywhere else — the labels still read as the same
     marker the cards wear. The scroll stays underneath as the floor for
     anything narrower than a phone. */
  .bar-nav a { white-space: nowrap; padding-left: var(--sp-4); padding-right: var(--sp-4); }
  #clock { margin-left: auto; }
  .p { scroll-margin-top: var(--scroll-clear); }
  .p-about   { grid-column: 1 / 4; grid-row: 2; min-height: 400px; }
  .p-studio  { grid-column: 4 / 7; grid-row: 2; min-height: 400px; }
  .p-work    { grid-column: 1 / 7; grid-row: 3; min-height: 0; }
  /* Rows stop sharing the height once the card stops being a fixed one:
     equal thirds of a short card cannot hold a name and its tags, and
     they were overlapping. */
  .work > li { flex: 0 0 auto; padding: var(--sp-12) 0; }
  .work { margin-top: var(--sp-10); }
  .p-record  { grid-column: 4 / 7; grid-row: 4; min-height: 250px; }
  /* Columns 1 to 4 of this row were empty, which is where it goes. */
  .p-setup   { grid-column: 1 / 4; grid-row: 4; min-height: 250px; }
  .p-draw    { grid-column: 1 / 7; grid-row: 5; min-height: 0; }
  .case-in   { grid-column: 1 / 7; grid-row: 2 / 6; min-height: 88vh; }
  .work a    { font-size: var(--t-lg); }
}
@media (max-width: 720px) {
  .screen { grid-template-columns: 1fr; grid-template-rows: auto; }
  .p, .bar { grid-column: 1 !important; grid-row: auto !important; }
  /* A phone gets this rule, not the one in the block above. It was 14
     against 10 — uneven, and neither of them the card's gutter. */
  .bar { padding: var(--sp-8) var(--sp-20); }
  /* The role is gone from the dashboard's bar, but system.html still
     carries one, and this is where a phone sizes it. */
  .bar-role { font-size: var(--t-sm); }
  .bar-id { gap: var(--sp-10); }
  .p-work { min-height: 440px; }
  .p-studio { min-height: 340px; }
  .p-record { min-height: 240px; }
  .p-draw { min-height: 200px; }
  .p-about { min-height: 360px; }
  .p-setup { min-height: 0; }
  .work a { font-size: var(--t-lg); }
}

@media (prefers-reduced-motion: reduce) {
  .work a, .solid, .solid svg { transition: none !important; animation: none !important; }
  #slats.go i { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   Numbered navigation. The labels were already an index — 01 About, 02
   Work — so they become the way around. Press a number and that panel
   takes the whole field; the rest step out. Which is also how the studio
   finally gets the room it deserves.
   ══════════════════════════════════════════════════════════════════════ */

/* ── The system ─────────────────────────────────────────────────────────
   A specimen, not a screenshot of one: every value here is read out of
   the running stylesheet at load, so the panel cannot drift from the
   page it documents. That is the difference between saying you keep a
   design system and showing one that reports itself. ────────────────── */






/* ── About, extended ────────────────────────────────────────────────── */
.p-about .say-sub + .say-sub { margin-top: var(--sp-8); }

/* ── Contact, a line ────────────────────────────────────────────────── */
.p-about .mailto { font-weight: 500; font-size: var(--t-md); letter-spacing: -0.02em; }
.p-about .soc { padding: 0; border-top: 0; }



/* ── The other tool ─────────────────────────────────────────────────────
   draw.js is the site's second piece of real software and it was left out
   of this prototype entirely. It arrives whole — markup, its own
   stylesheet, its own script — and is named rather than hidden. */
.txt {
  appearance: none; background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--fg); font: inherit; text-decoration: underline;
  text-decoration-color: var(--line); text-underline-offset: 3px;
  transition: text-decoration-color var(--d-fast) var(--ease);
}
@media (hover: hover) { .txt:hover { text-decoration-color: var(--fg); } }


/* ── Life on film ───────────────────────────────────────────────────────
   A square set between the cards, with no box around it. Each card it
   lands between keeps its whole outline and bends it around the square:
   the edge leaves the straight run early, swells around the square and
   eases back in. The border never breaks and the square is never inside
   anything, the cards simply move aside for it. */
/* The slot is the square's own box, so the square sits in the middle of
   it by construction. It draws nothing: a ring here on top of the
   square's own border put the square inside a second box, which is the
   one thing it is not. */
.film-slot {
  position: absolute; z-index: var(--z-square);
  width: var(--sq, 44px); height: var(--sq, 44px);
  transform: translate(-50%, -50%);
  display: grid; place-items: center;
  /* the slot is geometry, not a target: only the square takes the pointer */
  pointer-events: none;
}

/* The carved card hands its outline over: a clip cuts the border away
   wherever the path leaves the border box, so the whole outline is drawn
   here instead, over the card and outside its clip, on the same path. */
.p.film-cut { border-color: transparent; }
.film-edge {
  position: absolute; z-index: var(--z-seam);
  pointer-events: none; overflow: visible;
}
.film-edge path {
  fill: none; stroke: var(--line); stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.screen.case .film-edge { display: none; }
/* The seams belong to the dashboard; while a case has the field there
   are none, so this goes with them. */
.screen.case .film-slot { display: none; }

.film {
  position: relative;
  pointer-events: auto;
  width: 44px; height: 44px; padding: 0;
  appearance: none; cursor: pointer;
  border-radius: var(--r-12);
  background: var(--s-rest);
  border: 1px solid var(--e-rest);
  box-shadow: var(--s-inset);
  -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
  color: var(--faint);
  display: grid; place-items: center;
  /* The same rule the other buttons keep: nothing moves. This one has a
     reason of its own as well — the pocket in the cards around it is cut
     for a 44px square with 10px of air, and that outline is drawn once.
     Scaling the button ate into clearance the carve could not give back,
     so it grew towards an edge that stayed where it was. */
  transition: color var(--d-base) var(--ease), background var(--d-base) var(--ease),
              border-color var(--d-base) var(--ease), box-shadow var(--d-base) var(--ease),
              backdrop-filter var(--d-base) var(--ease),
              -webkit-backdrop-filter var(--d-base) var(--ease);
}
.film svg { width: 18px; height: 18px; }

/* ── The glint ──────────────────────────────────────────────────────────
   The square is hidden in a seam between two cards and says nothing
   about itself. Left completely still it is furniture; lit up it would
   be a call to action, which is the opposite of what it is. So it
   catches the light once in a while and goes quiet again — eight
   seconds of nothing, then a sheen crossing it in about a second.

   It is a moving background position rather than a transform, so
   nothing about the button's geometry changes: the pocket carved into
   the cards around it is drawn once and cannot follow a shape that
   moves. And it stops on hover, where the glass answers properly. */
@keyframes filmGlint {
  0%, 68%    { background-position: 140% 0; opacity: 0; }
  72%        { opacity: 1; }
  84%        { opacity: 1; }
  88%, 100%  { background-position: -40% 0; opacity: 0; }
}
.film::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(104deg,
    transparent 40%,
    rgba(255, 255, 255, .13) 50%,
    transparent 60%);
  background-size: 300% 100%;
  background-position: 140% 0;
  opacity: 0;
  animation: filmGlint 8.5s var(--ease-io) infinite;
}
@media (hover: hover) {
  .film:hover::after { animation: none; opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .film::after { animation: none; opacity: 0; }
}
@media (hover: hover) {
  .film:hover {
    color: var(--fg); background: var(--s-hover); border-color: var(--e-hover);
    box-shadow: var(--s-inset-hi);
    -webkit-backdrop-filter: var(--s-blur-hi); backdrop-filter: var(--s-blur-hi);
  }
}
.film:active {
  background: var(--s-press); border-color: var(--e-press);
  box-shadow: var(--s-sunk);
  -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
}
.film:focus-visible { outline: var(--s-ring); outline-offset: 3px; }

/* The name, on hover. The slot is 44 wide and the label is not, so it
   centres on the slot rather than sitting inside it. */
.film-tip {
  position: absolute; left: 50%; top: calc(100% + 10px);
  transform: translate(-50%, -4px);
  padding: var(--sp-4) var(--sp-8); border-radius: var(--r-8);
  white-space: nowrap; pointer-events: none;
  font-size: var(--t-xs); font-weight: var(--w-med);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--muted);
  background: color-mix(in srgb, var(--panel) 82%, transparent);
  border: 1px solid var(--e-rest); box-shadow: var(--s-inset);
  -webkit-backdrop-filter: var(--s-blur); backdrop-filter: var(--s-blur);
  opacity: 0;
  transition: opacity var(--d-base) var(--ease), transform var(--d-base) var(--ease);
}
.film-slot.up .film-tip { top: auto; bottom: calc(100% + 10px); transform: translate(-50%, 4px); }
@media (hover: hover) {
  .film-slot:has(.film:hover) .film-tip,
  .film-slot:has(.film:focus-visible) .film-tip {
    opacity: 1; transform: translate(-50%, 0);
  }
}
@media (prefers-reduced-motion: reduce) { .film-tip { transition: none; } }

/* A contact sheet read as a specimen sheet. Every frame is an entry:
   numbered in the corner, its own shape kept, its ratio stated under it.
   A landscape frame takes the width of two portraits, and the grid packs
   dense, so the sheet tiles unevenly the way the frames actually are. */
.film-sheet { position: relative; margin-top: var(--sp-2); }
.film-sheet .frame {
  position: absolute; display: block; overflow: hidden;
  padding: 0; margin: 0; appearance: none; cursor: pointer;
  background: var(--panel-up); color: var(--faint);
  border: 1px solid var(--line); border-radius: var(--r-12);
  opacity: 0; transition: opacity var(--d-slow) var(--ease),
                          border-color var(--d-base) var(--ease);
}
.film-sheet .frame.in { opacity: 1; }
.film-sheet .frame > img {
  display: block; width: 100%; height: 100%; object-fit: cover;
  transition: transform var(--d-slow) var(--ease), opacity var(--d-base) var(--ease);
}
@media (hover: hover) {
  .film-sheet .frame:hover { border-color: var(--e-hover); }
  .film-sheet .frame:hover > img { transform: scale(1.02); opacity: .78; }
}
.film-sheet .frame:focus-visible { outline: var(--s-ring); outline-offset: 2px; }

/* The index, two digits stacked, and the ratio. Both sit on the picture
   rather than in a bar beside it, and both stay quiet. */
.film-sheet .no, .film-view .fv-no {
  position: absolute; left: var(--sp-8); top: var(--sp-6); display: grid;
  font: var(--w-med) var(--t-2xs)/1 var(--mono);
  letter-spacing: .04em; color: var(--fg); opacity: .62;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
}
.film-sheet .spec, .film-view .fv-spec {
  position: absolute; left: var(--sp-8); bottom: var(--sp-6);
  font: var(--w-med) var(--t-2xs)/1 var(--mono);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--fg); opacity: .5;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
}

/* Empty, the sheet shows its own shape: numbered slots named after the
   files that would fill them. */
.film-sheet.empty .frame { cursor: default; background: var(--card); }
.film-sheet.empty .frame::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(var(--line), var(--line)) center/1px 100% no-repeat,
    linear-gradient(var(--line), var(--line)) center/100% 1px no-repeat;
  opacity: .55;
}
.film-note {
  margin: var(--sp-12) 0 0; max-width: 62ch;
  font-size: var(--t-sm); line-height: var(--lh-body); color: var(--muted);
}
.film-note b { color: var(--fg); font-weight: var(--w-med); }

/* The viewer: one frame, black to the edges, the numbers kept small and
   low. Nothing around the picture, because the picture is the point. */
.film-view {
  position: fixed; inset: 0; z-index: var(--z-view);
  display: grid; place-items: center;
  padding: var(--sp-44) var(--sp-20);
  background: rgba(0, 0, 0, .94);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  opacity: 0; transition: opacity var(--d-base) var(--ease);
}
.film-view.on { opacity: 1; }
.film-view .fv-stage {
  position: relative; margin: 0; line-height: 0;
  max-width: 100%; max-height: 100%;
  transform: scale(.985);
  transition: transform var(--d-slow) var(--ease);
}
.film-view.on .fv-stage { transform: none; }
.film-view .fv-img {
  display: block; width: auto; height: auto;
  max-width: 100%; max-height: calc(100dvh - 88px);
  border-radius: var(--r-2);
}
.film-view .fv-no, .film-view .fv-of, .film-view .fv-spec { opacity: .6; }
.film-view .fv-no { left: var(--sp-10); top: var(--sp-8); font-size: var(--t-xs); }
.film-view .fv-of {
  position: absolute; left: var(--sp-10); top: var(--sp-44);
  font: var(--w-med) var(--t-2xs)/1 var(--mono);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--fg);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
}
.film-view .fv-spec { left: var(--sp-10); bottom: var(--sp-8); font-size: var(--t-2xs); }
.film-view .fv-x {
  position: absolute; right: var(--sp-20); top: var(--sp-16);
  padding: 0; appearance: none; background: none; border: 0; cursor: pointer;
  font: var(--w-med) var(--t-2xs)/1 var(--mono);
  letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--fg); opacity: .55;
  transition: opacity var(--d-fast) var(--ease);
}
.film-view .fv-x:hover { opacity: 1; }
/* The halves you click to move: no buttons drawn, just the two sides. */
.film-view .fv-nav {
  position: absolute; top: 0; bottom: 0; width: 26%;
  appearance: none; background: none; border: 0; padding: 0;
  cursor: pointer; opacity: 0;
}
.film-view .fv-prev { left: 0; }
.film-view .fv-next { right: 0; }
.film-view .fv-nav:focus-visible { outline: var(--s-ring); outline-offset: -3px; opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .film-view, .film-view .fv-stage, .film-sheet .frame, .film-sheet .frame > img { transition: none; }
}

/* ── The case takes the field ───────────────────────────────────────────
   Opening a project does not cover the dashboard and does not leave it:
   the five cards step out and the case takes their place in the same
   grid, under the same bar. The way back is the first thing in it.

   Chapters down the left, the project's own material down the middle. */
.case-in {
  grid-column: 1 / 13; grid-row: 2 / 5;
  padding: 0;
  animation: caseIn var(--d-slow) var(--ease) both;
}
.screen.case .p:not(.case-in) { display: none; }
@keyframes caseIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .case-in { animation: none; } }

/* The bar never scrolls away: the way out and the way through stay put. */
/* The bar is laid out on the body's columns, not on its own. The back
   button sits over the chapter rail and the title starts exactly where
   the prose starts, so the panel reads as two columns from top to
   bottom instead of a bar that happens to sit above them.

   The first track is the rail's 190 plus the scroll's 22 gutter, less
   the bar's own 18 of padding and less the 10 of column gap that falls
   after it — which is why it is not simply 190. */
.case-bar {
  flex: none;
  display: grid;
  grid-template-columns: calc(190px + var(--sp-22) - var(--sp-18) - var(--sp-10)) minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 var(--sp-10);
  padding: var(--sp-14) var(--sp-18);
  border-bottom: 1px solid var(--hair);
}
.case-bar > #caseBack { justify-self: start; }
/* The spacer was doing the flex layout's pushing; the grid does it. */
.case-bar .sp { display: none; }
.case-bar .case-name { margin: 0; }
.case-name {
  font-size: var(--t-lg); font-weight: var(--w-med);
  letter-spacing: var(--ls-tight); line-height: var(--lh-tight);
}

.case-body { flex: 1 1 auto; min-height: 0; display: flex; }
/* No chapters, no rail: a contact sheet has nothing to divide. */
.case-rail:empty { display: none; }

/* Chapters. Clicking one takes you to it; scrolling marks where you are. */
.case-rail {
  flex: none; width: 190px; padding: var(--sp-20) var(--sp-12) var(--sp-20) var(--sp-18);
  border-right: 1px solid var(--hair);
  overflow-y: auto;
  display: flex; flex-direction: column; gap: var(--sp-2);
}
.case-rail button {
  appearance: none; background: none; border: 0; cursor: pointer;
  text-align: left; padding: var(--sp-8) var(--sp-8); border-radius: var(--r-8);
  font-size: var(--t-sm); font-weight: var(--w-med);
  color: var(--faint);
  transition: color var(--d-base) var(--ease), background var(--d-base) var(--ease);
}
@media (hover: hover) { .case-rail button:hover { color: var(--muted); background: var(--s-rest); } }
.case-rail button[aria-current="true"] { color: var(--fg); background: var(--s-hover); }
.case-rail button:focus-visible { outline: var(--s-ring); outline-offset: 2px; }

.case-scroll {
  flex: 1 1 auto; min-width: 0;
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--sp-20) var(--sp-22) var(--sp-36);
  scrollbar-width: thin;
}
.case-role {
  margin: 0 0 var(--sp-4); font-size: var(--t-sm); color: var(--muted);
  line-height: var(--lh-body);
}

/* One spacing mechanism, not two. Chapters carried 24 of padding and
   36 of margin, so 60 sat between them by accident rather than by
   choice — and the first chapter got 24 of padding separating it from
   nothing, since the role line above brings its own margin. */
.chapter { padding-top: 0; }
/* Space between chapters, not a rule. The heading is already a break —
   caps, its own colour, its own line — and a divider above it made the
   panel read as a stack of separate documents rather than one piece
   with parts. */
.chapter + .chapter { margin-top: var(--sp-44); }

/* The measure, and the heading shares it. The prose was capped at 68ch,
   which resolves to 631 here against 660 on the project page the words
   are lifted from: the same paragraphs breaking in two different places
   depending on which door you came through. 660 in both, chosen rather
   than computed.

   The heading had no cap at all and ran the panel's full 1449, so a
   long chapter title would have crossed the whole screen while its own
   paragraphs stopped less than halfway. */
.chapter h3, .case-para { max-width: 660px; }
.chapter h3 {
  margin: 0 0 var(--sp-14);
  font-size: var(--t-xs); font-weight: var(--w-med);
  text-transform: uppercase; letter-spacing: var(--ls-caps);
  color: var(--faint);
}
.case-pts { list-style: none; margin: 0 0 var(--sp-4); padding: 0; max-width: 78ch; }
.case-pts li {
  position: relative; padding-left: var(--sp-20); margin: 0 0 var(--sp-12);
  font-size: var(--t-md); line-height: var(--lh-body); color: var(--muted);
}
.case-pts li::before {
  content: ""; position: absolute; left: 0; top: .66em;
  width: 9px; height: 1px; background: var(--faint);
}
.case-pts li b { color: var(--fg); font-weight: var(--w-reg); }

/* One under another, on the text's own measure. They were in a two
   column grid at the panel's full width, which put a website capture on
   a 1447px plate beside another one — bigger than the screen the site
   was designed for, and nothing like how the same images sit on the
   project page they came from.

   Stacked and capped, they read as figures in a document: the reader
   goes down one column for the whole case, words and pictures on the
   same line, instead of scanning across a contact sheet. */
.case-media {
  display: flex; flex-direction: column;
  gap: var(--sp-14);
  margin-top: var(--sp-16);
  max-width: 660px;
}
/* A plate takes the shape of what is in it. Every asset was being cropped
   to 16:10 or 16:7.2, which is survivable for a landscape screenshot and
   ruinous for a full-page capture: the Iconomi wealth shot is 1440 by
   8004, so the plate was showing the top three per cent of it. The ratio
   comes from the file, so nothing is cropped and nothing is letterboxed. */
.cm {
  margin: 0; position: relative; overflow: hidden;
  aspect-ratio: var(--ar, 16 / 10);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-12);
}
.cm img, .cm video {
  width: 100%; height: 100%; display: block;
  object-fit: contain; object-position: 50% 50%;
  opacity: 0; transition: opacity var(--d-slow) var(--ease);
}
.cm.in img, .cm.in video { opacity: 1; }

/* Past a point a page capture is not an image, it is a page. Those keep a
   plate you can look at and scroll the capture inside it, rather than
   becoming eight thousand pixels of card. */
.cm.tall { aspect-ratio: 4 / 3; overflow-y: auto; overscroll-behavior: contain; }
.cm.tall img { height: auto; }
.cm.tall::after {
  content: ""; position: sticky; bottom: 0; left: 0; display: block; height: 46px;
  background: linear-gradient(transparent, var(--card));
  pointer-events: none;
}

@media (max-width: 1280px), (max-height: 760px) {
  .case-in { grid-column: 1 / 7; grid-row: 2; min-height: 86vh; }
  .screen.case .bar { position: static; }
}
@media (max-width: 900px) {
  .case-body { flex-direction: column; }
  .case-rail {
    width: auto; flex-direction: row; gap: var(--sp-6); overflow-x: auto;
    border-right: 0; border-bottom: 1px solid var(--hair);
    padding: var(--sp-12) var(--sp-14);
  }
  .case-rail button { white-space: nowrap; }
  .case-scroll { padding: var(--sp-16) var(--sp-14) var(--sp-28); }
  /* The bar is a grid, so the flex-wrap that used to be here wrapped
     nothing: the phone kept the desktop's three columns, the first of
     them a fixed 184px for the back button and the last as wide as the
     action wanted, which left the title's 1fr with nothing. The project
     name was being rendered at zero width with the white button sitting
     over the top of it. Two rows instead: the two controls hold the
     first, at either end of it, and the name gets a line of its own. */
  .case-bar {
    grid-template-columns: auto auto;
    justify-content: space-between;
    padding: var(--sp-12) var(--sp-14); gap: var(--sp-8) var(--sp-10);
  }
  .case-bar > #caseBack { grid-column: 1; grid-row: 1; }
  .case-bar > #caseFull { grid-column: 2; grid-row: 1; justify-self: end; }
  .case-bar > .case-name { grid-column: 1 / -1; grid-row: 2; }
  /* .case-media is a flex column and .cm.w2 no longer exists, so both
     of the rules that were here were describing a grid that is gone.
     The aspect-ratio was not merely dead, though: coming later in the
     sheet it beat .cm's own aspect-ratio: var(--ar), so every plate on
     a phone was pressed into 16:10 no matter what shape its file is —
     the square videos, the eight-thousand-pixel page capture, all of
     them. The plate takes its shape from the file on a phone for the
     same reason it does anywhere else. */
}

/* ── Canvas, its own cell ───────────────────────────────────────────────
   It was a sentence inside the studio's copy, which made the site's second
   piece of software read as a footnote to the first. */
/* The card claimed brushes and colour and showed a button. It draws a
   pencil now, and the line the pencil has just laid down. */
.strokes { flex: 1 1 auto; min-height: 90px; max-height: 240px; width: 100%; display: block; margin: var(--sp-6) calc(-1 * var(--sp-6)) 0; }
.p-draw .draw-in { margin-top: auto; padding-top: var(--sp-10); }
.p-draw h2 { margin: 0; font-size: var(--t-lg); font-weight: 500; letter-spacing: -0.03em; line-height: 1; }
.p-draw p { margin: var(--sp-10) 0 var(--sp-20); font-size: var(--t-sm); color: var(--muted); }


/* ── Portrait ───────────────────────────────────────────────────────────
   Small, and kept small. A portrait blown up to fill the column took the
   card over and turned an about card into a headshot; at 84px it is a
   mark beside the statement, which is all it needs to be. It still turns
   to the monogram on hover. */
/* ── The face ───────────────────────────────────────────────────────────
   It used to turn over to show the initials. That was the last trick
   left on the page after the buttons stopped lifting and the film
   square stopped scaling — and a photograph of a person rotating on
   its axis is the kind of thing that is charming once and then is
   simply in the way.

   The initials stay, behind the photograph rather than on the back of
   it: when the image fails to load — which it does — there is still
   something in the circle instead of a hole and an alt string. */
.avatar { width: 84px; height: 84px; flex: none; margin: var(--sp-14) 0 0; }
.avatar-in { position: relative; width: 100%; height: 100%; }
.av {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border-radius: var(--r-full);
  object-fit: cover;
  border: 1px solid var(--line);
  background: var(--card);
}
.av-front { filter: grayscale(1) contrast(1.04); }
.av-back {
  z-index: -1;                     /* underneath, not on the reverse */
  display: grid; place-items: center;
  background: var(--card); border-color: var(--edge);
  color: var(--fg);
  font-size: var(--t-xl); font-weight: var(--w-med);
  letter-spacing: -.05em; line-height: var(--lh-flat);
}
