/* Pure Studio V4 — layout after baudouinwillemart.com (Cargo 2), rebuilt from scratch.
   One white page, four columns: labels | links | content (two columns wide).
   Space Grotesk 1rem / 1.3 everywhere; the one accent marks the page you are on.
   Measured at 1440×900 (rem 12.96px): text starts 25.9px down, links column at
   378.6px, lists at 160px, content at 736px, 7px media radius. */

@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/fonts/SpaceGrotesk-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("/assets/fonts/SpaceGrotesk-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* Geist (Vercel × basement.studio, SIL Open Font License: assets/fonts/geist/
   LICENSE-OFL.txt), since 2026-10-12. Regular and Bold, the site's two weights.
   Space Grotesk stays loaded as the fallback. */
@font-face {
  font-family: "Geist";
  src: url("/assets/fonts/geist/Geist-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("/assets/fonts/geist/Geist-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* Archivo (Omnibus-Type, SIL Open Font License: assets/fonts/archivo/OFL.txt), voku.studio's
   text face, since 2026-10-13. Google Fonts' variable files (weights 100–900), latin + latin-ext. */
@font-face {
  font-family: "Archivo";
  src: url("/assets/fonts/archivo/Archivo-latin.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  src: url("/assets/fonts/archivo/Archivo-latin-ext.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --text-weight: 600;   /* Archivo SemiBold everywhere; bold (labels, titles) stays 700 */
  --track: 0.01em;      /* voku.studio's letter-spacing */
  --ink: #000;
  --bg: #fff;
  --accent: #8c8ba0;   /* purestudio.art's accent (the "worth more" purple) */
  --wash: rgba(0, 0, 0, 0.04);
  --pad: 2rem;      /* page padding: 25.9px in a 900px-tall window */
  --gut: 1.75rem;   /* column gutter */
  --top: 2rem;
  --line: 1.3rem;
  --radius: 7px;
  --ease: cubic-bezier(0.4, 0, 0.22, 1);       /* the template's quick view */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);  /* strong ease-out for UI entrances */
  --hl: color-mix(in srgb, var(--accent) 42%, #fff);   /* the marker: a light tint of the accent */
  --pill: #f1f1f1;         /* the "well" grey (skills.wheresryan.sh): buttons, LINK on hover, the selected project */
  --pill-hover: #e8e8e8;
  --dot: #bdbdbd;          /* the selected project's dot */
  --muted: #8f8f8f;        /* resting colour of the work names and LINK (hover makes them ink) */
  --cta: #0c0c0c;          /* Book a call: the one black button */
  --cta-ink: #fff;
  --cta-hover: #2a2a2a;
  /* Cargo sizes everything from the window HEIGHT: rem = 1.44% of it
     (900 tall → 12.96px, 1000 → 14.4px, 1080 → 15.55px), plus a little more
     when the window is taller than wide. Every space below is in rem, so the
     whole page scales together, exactly like the template. */
  font-size: calc(1.44vh + max(0px, 0.8229vh - 0.8229vw));
  -webkit-text-size-adjust: 100%;
}

/* the black version: the same page, its tokens inverted. Switched by
   <html data-theme="dark"> in index.html; remove the attribute for the white page. */
:root[data-theme="dark"] {
  --ink: #fff;
  --bg: #000;
  --wash: rgba(255, 255, 255, 0.06);
  --hl: color-mix(in srgb, var(--accent) 45%, #000);   /* the marker: a dark tint of the accent */
  --pill: rgba(255, 255, 255, 0.1);
  --pill-hover: rgba(255, 255, 255, 0.16);
  --dot: rgba(255, 255, 255, 0.45);
  --muted: rgba(255, 255, 255, 0.5);
  --cta: #fff;
  --cta-ink: #0c0c0c;
  --cta-hover: #dcdcdc;
  color-scheme: dark;
}
:root[data-theme="dark"] .brand img { filter: invert(1); }   /* the logo file is drawn in black */

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body {
  background: var(--bg);
  color: var(--ink);
  /* Archivo SemiBold (voku.studio), 2026-10-13. Earlier: Studio KO's Arial tracked 0.077em,
     and before that Geist ("Geist", "Space Grotesk", …, --track: 0) */
  font: var(--text-weight) 1rem/1.3 "Archivo", Arial, Helvetica, sans-serif;
  letter-spacing: var(--track);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
ul { list-style: none; margin: 0; padding: 0; }
p { margin: 0; }
h1, h2 { margin: 0; font: inherit; }   /* page titles are headings for search engines; they look like the text around them */
/* read by search engines and screen readers, not drawn */
.vh { position: absolute !important; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
b { font-weight: 700; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
img, video { display: block; }
::selection { background: var(--accent); color: var(--ink); }   /* as on purestudio.art */
:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }

/* links and labels are set in capitals */
.links, .label, .brand, .menu-btn, .title, .head, .intro__roles, .ft p, .back, .cta { text-transform: uppercase; }

.is-active { color: var(--accent); }   /* the page you are on */
/* …and a small dot left of its name, in the same accent. It sits in the gutter, so the
   name never moves. In 200ms (opacity + a 0.5 → 1 scale), out 150ms: on a click the
   selection reads as moving from one name to the next. */
.links a::before {
  content: "";
  position: absolute;
  left: -0.85em;
  top: 0.62em;          /* level with the middle of the capitals */
  width: 0.36em;
  height: 0.36em;
  margin-top: -0.18em;
  border-radius: 50%;
  background: currentColor;
  opacity: 0;
  transform: scale(0.5);
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
  pointer-events: none;
}
.links a.is-active::before { opacity: 1; transform: none; transition-duration: 200ms; }
/* …except the selected project (after the user's reference): a light grey pill around
   its name with a grey dot inside on the left, the name in black. The name stays exactly
   where it was: the pill reaches into the gutter (negative margin = its padding), and its
   vertical padding doesn't move the lines. The grey fades in (150ms). */
/* the hover, after skills.wheresryan.sh/anatomy's theme bar: names rest in grey and the
   one under the pointer turns ink, 150ms; no underline here, the colour is the hover.
   Pointer devices only: on touch screens the names stay ink. The home feed lights the
   name of the picture you hover (.is-hover) the same way. */
/* .links--pick = Selected works and the nav (Home · Research · About).
   Every name carries the pill's padding (cancelled by an equal negative margin), so the
   pill only fades its colour in and out: its shape never snaps when the selection
   moves from one name to the next. It also widens the hover target into the gutter. */
.links--pick a {
  border-radius: 0.45em;
  padding: 0.12em 0.6em 0.12em 1.4em;
  margin: 0 -0.6em 0 -1.4em;
  transition: color 150ms ease, background-color 150ms ease;
}
.links--pick a::after { display: none; }
@media (hover: hover) and (pointer: fine) {
  .links--pick a { color: var(--muted); }
  .links--pick a:hover, .links--pick a.is-hover, .links--pick a.is-active { color: var(--ink); }
}
.links--pick a:focus-visible { color: var(--ink); }   /* keyboard focus reads like hover */
.links--pick a.is-active { color: var(--ink); background: var(--pill); }
.links--pick a::before {
  left: 0.55em;
  top: 0.74em;           /* the capitals' middle, under the 0.12em top padding */
  width: 0.4em;
  height: 0.4em;
  margin-top: -0.2em;
  background: var(--dot);
}
/* LINK ↘: the word underlined, the arrow beside it at the reference's 15:11 ratio */
/* Link ↘ as a key, like the footer's X and Mail: the well-grey box always on, ink
   text, regular weight, 7px corners, 2.2em tall. Hover darkens the grey (150ms), a press
   sinks it to 0.97. The word stays on the column line (negative margin = padding). */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-weight: var(--text-weight);
  vertical-align: top;
  height: 2.2em;
  padding: 0 0.7em;
  margin: -0.45em 0 0 -0.7em;   /* the text sits where it was, on the head line */
  border-radius: 7px;
  background: var(--pill);
  color: var(--ink);
  text-transform: none;   /* "Link", as written, like the keys */
  transition: background-color 150ms ease, transform 120ms var(--ease-out);
}
.link:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) { .link:hover { background: var(--pill-hover); } }
.link:focus-visible { background: var(--pill-hover); }
.link span { text-decoration: none; }   /* a button now: no underline */
.link svg { width: 1.05em; height: 1.05em; flex: none; }   /* sized to sit inside the key with the word */

/* Link hover (after fancycomponents.dev's Underline Animation, "comes in, goes out"):
   a line 0.1× the type size draws under the word from the left on hover and carries
   on out to the right when the pointer leaves. Transform only: 300ms in, 250ms out on
   the strong ease-out. Every text link: the nav bar, the lists, the contact links, the
   About page's mail and call. The home feed draws it under a work's name when its
   picture is hovered (.is-hover). LINK ↘ keeps its arrow nudge. */
.links a, .cta, .mail { position: relative; }
.links a::after, .cta::after, .mail::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.01em;
  height: 0.1em;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform 250ms var(--ease-out);
}
.links a.is-hover::after {
  transform: scaleX(1);
  transform-origin: left center;
  transition-duration: 300ms;
}
.link svg { transition: transform 160ms var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .links a:hover::after, .cta:hover::after, .mail:hover::after {
    transform: scaleX(1);
    transform-origin: left center;
    transition-duration: 300ms;
  }
  .link:hover svg { transform: translate(0.15em, 0.15em); }
}


/* ---------- left columns: fixed ---------- */
.row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: var(--gut);
  align-items: start;
}
.hd {
  position: fixed;
  z-index: 20;
  top: 0;
  left: 0;
  width: 50vw;
  max-height: calc(100vh - 4.5rem);
  max-height: calc(100dvh - 4.5rem);   /* the visible height on tablets, browser bars included */
  overflow-y: auto;
  scrollbar-width: none;
  padding: var(--top) var(--gut) 0 var(--pad);
  padding-right: calc(var(--gut) / 2);
}
.hd::-webkit-scrollbar { display: none; }
/* the left half on a two-column grid (column 2 is the list column):
   - the top line: the logo | Home · Research · About, level with a work's title;
   - 8 lines down (161px at 1440×900): Services (the studio sentence, then the services
     as bullets), Selected works, Contact.
   .row--top only groups the logo and the nav for the phone bar and menu: here its
   children sit straight on the grid. */
.hd {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: var(--gut);
  align-items: start;
}
.row--top { display: contents; }
.brand { grid-column: 1; grid-row: 1; }
#nav { grid-column: 2; grid-row: 1; }   /* the list column's top line (the corner was tried and undone) */
.nav-label { grid-column: 1; grid-row: 1; }   /* NAV, labelling it like the sections below */
.sec + .sec { margin-top: calc(5 * var(--line)); }

.brand { display: block; width: max-content; height: var(--line); }
/* logo letters drawn at the text's cap height (25.76 of the file's 61 units) */
/* the studio in two short sentences, in the Services section: beside SERVICES, above
   the services, 3rem short of the content column so it never crowds a work's title.
   The copy at the top (#pitch) is for phones and portrait tablets only. */
.pitch { display: none; }
.pitch .pills { display: flex; }
.svc__pitch { padding-right: 3rem; margin-bottom: var(--line); }
.svc__pitch, .pitch { text-transform: uppercase; }   /* capitals */
.pitch .pills { text-transform: none; }   /* the buttons keep their own case on phones */
/* a hanging bullet: a long service wraps under its own text, never leaving the bullet alone */
.links--bullets li { padding-left: 1em; }
.links--bullets li::before { content: "•"; display: inline-block; width: 1em; margin-left: -1em; }
/* the actions under the sentence: light grey boxes, a small raised number after each
   label. Hover darkens the grey (150ms); a press sinks them to 0.97 (Emil's rule). */
.pills { display: flex; flex-wrap: wrap; gap: 4px; margin-top: var(--line); }
.pill {
  display: inline-flex;
  align-items: center;
  height: 2.2em;
  padding: 0 0.85em;
  border-radius: 7px;
  background: var(--pill);
  color: var(--ink);
  line-height: 1;
  transition: background-color 150ms ease, transform 120ms var(--ease-out);
}
.pill sup { font-size: 0.6em; line-height: 1; margin-left: 0.3em; align-self: flex-start; margin-top: 0.75em; }
.pill:active { transform: scale(0.97); }
.pill--primary { background: var(--cta); color: var(--cta-ink); }   /* Book a call: black, #0C0C0C */
@media (hover: hover) and (pointer: fine) {
  .pill:hover { background: var(--pill-hover); }
  .pill--primary:hover { background: var(--cta-hover); }
}
#sections { grid-column: 1 / -1; grid-row: 2; margin-top: calc(5 * var(--line)); }   /* under the 3-line nav: the lists 8 lines down */
/* the purple marker behind Seed, Series A, startups (after fancycomponents.dev's Text
   Highlighter): a flat stroke that draws left to right, once, as the page shows.
   One colour gradient whose width goes 0 → 100%; clone gives each wrapped line its
   own stroke. 900ms on the strong ease-out (the original is a 1s spring, no bounce). */
.hl {
  color: inherit;
  background-color: transparent;
  background-image: linear-gradient(var(--hl), var(--hl));
  background-repeat: no-repeat;
  background-position: 0 88%;   /* a low band under the letters, like a felt-tip line */
  background-size: 0% 0.42em;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding: 0 0.12em;
  margin: 0 -0.12em;
  transition: background-size 900ms var(--ease-out);
}
.hl.is-on { background-size: 100% 0.42em; }
.brand { max-width: 100%; }
.brand img { width: 15em; max-width: 100%; height: auto; position: relative; top: -0.2em; }   /* 15em = 1.658em tall; shrinks to fit its column */

.menu-btn, .menu-close { display: none; }

/* ---------- footer: fixed ---------- */
.ft {
  position: fixed;
  z-index: 20;
  left: 0;
  bottom: 0;
  width: 50vw;
  padding: 0 calc(var(--gut) / 2) 2rem var(--pad);
}
/* the studio mark (the cherubs of the logo) in place of (c): the white mark.svg used as
   a mask over the text colour, so it prints in the ink colour. At the logo's own mark size (1.4em),
   centred on the capitals. */
.ft__mark {
  display: inline-block;
  width: calc(1.4em * 75 / 51);
  height: 1.4em;
  vertical-align: -0.35em;
  background: currentColor;
  -webkit-mask: url("/assets/brand/mark.svg") center / contain no-repeat;
  mask: url("/assets/brand/mark.svg") center / contain no-repeat;
}
/* X and MAIL, beside the mark, as small keys (after the user's keycap reference): the
   well-grey box of the buttons, 2.2em tall, X square. Hover darkens the grey (150ms),
   a press sinks them to 0.97. */
.ft__row { display: flex; align-items: center; gap: 1.1em; }
.ft__links { display: flex; align-items: center; gap: 4px; }
.key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.2em;
  height: 2.2em;
  padding: 0 0.7em;
  border-radius: 7px;
  background: var(--pill);
  color: var(--ink);
  line-height: 1;
  text-transform: none;   /* "Mail", as written, like the buttons above */
  transition: background-color 150ms ease, transform 120ms var(--ease-out);
}
.key:active { transform: scale(0.97); }
/* the word inside a key or button, as letters (assets/js/site.js "keys that say where
   they go"): new letters arrive blurred and clear in 180ms; the old word blurs away in
   120ms, laid over the new one; the key's width glides 220ms on the strong ease-out */
.key, .pill, .link { overflow: hidden; white-space: nowrap; }
.key[data-alt], .pill[data-alt], .link[data-alt] { transition: width 220ms var(--ease-out), background-color 150ms ease, transform 120ms var(--ease-out); }
.key[data-alt] { justify-content: flex-start; }
.lbl { position: relative; display: inline-flex; }
.lbl__word { display: inline-flex; }
.lbl__word.is-out { position: absolute; left: 0; top: 0; }
.ch { display: inline-block; opacity: 0; filter: blur(4px); transition: opacity 180ms var(--ease-out), filter 180ms var(--ease-out); }
.lbl__word.is-in .ch { opacity: 1; filter: none; }
.lbl__word.is-out .ch { opacity: 0; filter: blur(4px); transition-duration: 120ms; transition-delay: 0ms !important; }
@media (hover: hover) and (pointer: fine) { .key:hover { background: var(--pill-hover); } }
.ft__mark { vertical-align: 0; }
/* the two actions, below Selected works, on the list column */
.sec--actions .pills { margin-top: 0; }

/* ---------- right: the content ---------- */
.main {
  margin: 0 var(--pad) 0 calc(50% + 1.25rem);   /* 736px at 1440×900 */
  padding: var(--top) 0 4rem;   /* from the top line, level with the logo and HOME, as on /atelier-constant-berger */
  outline: none;
}
.intro { max-width: 82.6%; }        /* 559.5px at 1440×900 */
.intro p + p { margin-top: var(--line); }
.intro .head + p { margin-top: 1.95rem; }   /* text 4.55rem under the title, as on /oken */
.title { font-weight: 700; }
.about { max-width: 25rem; }
/* work pages: the work | roles | link, on the site grid — the description fills
   grid column 3, roles and link share column 4 (the content box starts 0.375rem
   right of column 3, hence the + 0.375rem) */
.intro--cols {
  max-width: none;
  display: grid;
  grid-template-columns: 1fr repeat(2, calc(((100% + 0.375rem - var(--gut)) / 2 - var(--gut)) / 2));
  column-gap: var(--gut);
  align-items: start;
}

.media {
  display: flex;
  flex-direction: column;
  gap: 1rem;                        /* 13px, the template's image padding 1 */
  margin-top: 4.45rem;              /* text → media 58px; media at 227px on /oken */
}
/* the feed starts on the same line as the nav bar and the work pages (736px at 1440×900),
   so going from home to a work never shifts the pictures sideways (since 2026-10-15 only
   phones draw the feed: see the phone block) */
.media--home { margin-top: 0; gap: 0.75rem; }
/* Research wall (after unveil.fr/research): small tiles at their own shape,
   four across the content column, wide gaps, rows aligned to the top, square
   corners. ~125px tiles, ~60px between columns, 95px between rows at 1440×900. */
.wall {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: 4.6rem;
  row-gap: 7.33rem;
  align-items: start;
  margin-top: 4.45rem;
}
.wall .m { border-radius: 0; }

/* portraits sit two up, never full width. When that would leave one alone at the end
   (an odd count), the last three share a row instead: six columns, two per picture
   for the pairs, the last three take two each. */
.grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1rem;
  align-items: start;
}
.grid > * { grid-column: span 3; }
.grid > :nth-last-child(3):nth-child(odd),
.grid > :nth-last-child(3):nth-child(odd) ~ * { grid-column: span 2; }
.media--home .grid { gap: 0.75rem; }
/* two pictures side by side at their own shapes, one shared height: each grows in
   proportion to its width/height ratio (flex-grow set in site.js), no cropping */
.media-row { display: flex; gap: 1rem; align-items: flex-start; }
.media-row > .m { flex-basis: 0; flex-shrink: 1; min-width: 0; }
/* before/after slider (Samplify): the old site clipped to the left of the line, the
   redesign underneath; the clip follows --x (the range's value). A transparent range
   input covers the picture, so a press anywhere moves the line and dragging follows the
   pointer 1:1; arrow keys step it. The line is white with a round white knob. */
.cmp { --x: 50%; cursor: ew-resize; }
.cmp > img, .cmp__b img { position: absolute; inset: 0; }
.cmp__b { position: absolute; inset: 0; clip-path: inset(0 calc(100% - var(--x)) 0 0); }
.cmp__line {
  position: absolute; top: 0; bottom: 0; left: var(--x); z-index: 2;
  width: 2px; margin-left: -1px; background: #fff;
  box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.2);
  pointer-events: none;
}
.cmp__knob {
  position: absolute; top: 50%; left: 50%;
  display: grid; place-items: center;
  width: 2.6rem; height: 2.6rem; transform: translate(-50%, -50%);
  border-radius: 50%; background: #fff; color: #000;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.28);
  transition: transform 160ms var(--ease-out);
}
.cmp__knob svg { width: 1.2rem; height: 1.2rem; }
.cmp:active .cmp__knob { transform: translate(-50%, -50%) scale(0.94); }
.cmp__tag {
  position: absolute; top: 0.8rem; z-index: 2;
  padding: 0.3em 0.6em; border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.85);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  color: #000; text-transform: uppercase; font-size: 0.85em; line-height: 1.2;
  pointer-events: none;
}
.cmp__tag--l { left: 0.8rem; }
.cmp__tag--r { right: 0.8rem; }
.cmp__range {
  position: absolute; inset: 0; z-index: 3;
  width: 100%; height: 100%; margin: 0;
  -webkit-appearance: none; appearance: none; background: none;
  opacity: 0; cursor: ew-resize;
  touch-action: pan-y;   /* a vertical swipe still scrolls the page */
}
.cmp__range::-webkit-slider-thumb { -webkit-appearance: none; width: 2.6rem; height: 100%; }
.cmp:has(.cmp__range:focus-visible) .cmp__knob { outline: 2px solid var(--ink); outline-offset: 2px; }
.m {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--wash);
}
.m img, .m video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;   /* shown once loaded, all at once: no fade, no reveal, as on the template */
}
.m .is-in { opacity: 1; }
.m--zoom { cursor: pointer; }   /* opens the full-screen view; a hand, not zoom-in, so the cursor shows in every browser */
.m--zoom:active { opacity: 0.7; }
a.tile { display: block; }

.back { display: none; }

/* ---------- peek: a work's preview on hover of its name (after directoryyy.com) ----------
   Just the picture, square corners, at its own ratio (inside 16rem x 13rem). Enters 200ms, leaves 150ms: opacity + a 0.96 scale
   from the side facing the pointer. Pictures crossfade in 150ms. */
.peek {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 30;
  pointer-events: none;
  will-change: transform;
}
.peek__in {
  position: relative;
  opacity: 0;
  transform: scale(0.96);
  transform-origin: 0 0;          /* the anchor: the picture's left-middle, at the pointer */
  transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
}
.peek.is-on .peek__in {
  opacity: 1;
  transform: none;
  transition-duration: 200ms;
}
.peek__img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  object-fit: cover;
  background: var(--wash);
  transform: translateY(-50%);
  opacity: 0;
  transition: opacity 150ms var(--ease-out);
}
.peek__img.is-in { opacity: 1; }

/* ---------- quick view (click to zoom) ---------- */
.qv { position: fixed; inset: 0; z-index: 100; visibility: hidden; cursor: pointer; }
.qv.is-open { visibility: visible; }
.qv__bg {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.81);
  opacity: 0;
  transition: opacity 0.25s var(--ease);   /* closing: quicker than opening */
}
.qv.is-open .qv__bg { opacity: 1; transition-duration: 0.333s; }
.qv__img {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  transition: transform 0.333s var(--ease);
  will-change: transform;
}

/* ---------- mobile: logo + MENU, content full width ---------- */
/* tall, narrow windows (iPad portrait, a narrow desktop window): the content
   column is too narrow for three columns at that type size, so the work header
   stacks — text across the column, roles and link side by side below it.
   Landscape (1024×768, iPad landscape) keeps three columns. */
@media (min-width: 770px) and (max-width: 1100px) and (max-aspect-ratio: 1/1) {
  .intro--cols { grid-template-columns: 1fr 1fr; row-gap: 1.95rem; }
  .intro--cols > :first-child { grid-column: 1 / -1; }
  /* the list column is too narrow for the sentence here: it goes back to the top, under
     the logo and the nav, across both columns; Services keeps just the bullets */
  .pitch { display: block; grid-column: 1 / -1; grid-row: 2; margin-top: var(--line); }
  #sections { grid-row: 3; margin-top: calc(2 * var(--line)); }
  .svc__pitch { display: none; }
  .links .yc { display: none; }   /* the list column is too narrow for "(YC F26)": it stays in the work's title */
  /* the list column a little wider (0.9 / 1.1) so "DEVELOPMENT" fits beside its hanging bullet */
  .hd, .hd .row { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); }
}

/* phones and windows ≤ 769px: the template's mobile layout (logo + MENU) */
@media (max-width: 769px) {
  /* Cargo switches to width here: rem = 3.2vw (375 → 12px, 430 → 13.76px) */
  :root { font-size: min(3.2vw, calc(1.44vh + max(0px, 0.8229vh - 0.8229vw))); --pad: 0.625rem; --gut: 1.25rem; --top: 1.28rem; }   /* header 16px down at 390 */
  /* every page's text starts 8.16rem down (97.9px at 375), as baudouinwillemart.com's About and
     project pages on a phone: the bar, about four empty lines, then the text (2026-10-15,
     "look the space between text too"); the home's studio sentence included */
  :root { --text-top: calc(8.16rem - var(--top) - var(--line)); }   /* under .hd's own padding */

  .hd {
    position: relative;   /* in the flow: the studio sentence pushes the page down */
    display: block;
    width: 100%;
    max-height: none;
    overflow: visible;
    padding: calc(var(--top) + var(--line)) var(--pad) 0;   /* room for the fixed bar */
  }
  /* the bar (logo + MENU) stays fixed at the top on a band of the page colour; the page scrolls under it */
  .row--top {
    display: grid;
    grid-template-columns: 1fr auto;
    position: fixed;
    z-index: 30;
    top: 0;
    left: 0;
    right: 0;
    height: auto;
    padding: var(--top) var(--pad) 0.6rem;
    background: var(--bg);
  }
  .brand img { width: 13.1em; top: -0.12em; }
  .pitch { display: block; margin-top: var(--text-top); }   /* the home's text, where the other pages' text starts */
  /* after baudouinwillemart.com on a phone: the studio sentence and its buttons are the home's;
     About, a work and Research start on white, their text where the home's sentence starts
     (the footer's AVAILABLE line and Mail still book and write on every page) */
  html:not(.is-home) .pitch { display: none; }
  html:not(.is-home) .main { padding-top: var(--text-top); }
  .svc__pitch { display: none; }   /* already at the top of the page: the menu keeps just the bullets */
  #nav, #sections, .nav-label { display: none; }   /* NAV shows again as the menu's first title */
  .ft { position: static; width: auto; padding: 0 var(--pad) 2rem; }   /* at the end of the page: the mark, X, mail */
  .ft .row { display: block; }
  .ft .row > span:first-child { display: none; }
  #nav { font-weight: var(--text-weight); margin-top: 0; }   /* in the menu, like the lists */
  .menu-btn { display: block; line-height: 1.3; }

  /* the open menu, as the template's: one column, 15px type, an item every
     33px (comfortable to tap), bold section titles above their lists, a big × */
  .hd.is-open {
    position: fixed;
    inset: 0;
    z-index: 50;
    background: var(--bg);
    overflow-y: auto;
    padding: 17px 10px 90px;
    font-size: 15px;
    line-height: 21px;
    animation: menu-in 200ms var(--ease-out);   /* fades in over the page; closing is instant, a page usually follows */
  }
  @keyframes menu-in { from { opacity: 0; } }
  .hd.is-open .brand, .hd.is-open .pitch, .hd.is-open .menu-btn { display: none; }
  .hd.is-open .row--top { position: static; padding: 0; background: none; }
  .hd.is-open .row { display: block; min-height: 0; }
  .hd.is-open #nav, .hd.is-open #sections { display: block; }
  .hd.is-open #sections { margin-top: 0; }
  .hd.is-open .links li, .hd.is-open .label { display: block; margin-bottom: 12px; }
  /* no gutter at the screen edge: the selected name steps in to make room for its dot */
  .hd.is-open .links a.is-active { padding-left: 0.85em; }
  .hd.is-open .links a::before { left: 0; }
  .hd.is-open .links--pick a { margin: 0; padding-left: 0; }   /* no gutter at the screen edge… */
  .hd.is-open .links--pick a.is-active { padding-left: 1.4em; }   /* …so the selected name steps in */
  .hd.is-open .links--pick a::before { left: 0.55em; }
  .hd.is-open .sec { margin-top: 48px; }   /* a section title 81px under the last item, as the template */
  .hd.is-open .menu-close {
    display: block;
    position: fixed;
    top: -4px;
    right: 10px;
    width: 31px;
    font-size: 50px;
    line-height: 1;
    font-weight: 400;
  }

  .main, .main--home { margin: 0; padding: calc(2 * var(--line)) var(--pad) 3rem; }   /* the title two lines under the studio sentence */
  .intro { max-width: none; }
  .intro--cols { grid-template-columns: 1fr 1fr; row-gap: 1.95rem; }
  .intro--cols > :first-child { grid-column: 1 / -1; }
  .media { margin-top: 6rem; }
  .media--home { margin-top: 0; }
  /* the home's feed (phones only, 2026-10-15, after baudouinwillemart.com's phone home): under the
     studio sentence, full width within the page margins, 12px between pictures at 375 as there;
     a grid of two stays side by side */
  .media--home, .media--home .grid { gap: 1rem; }
  /* the prerendered home has no feed yet (build.mjs renders at 1440): keep its footer out of
     sight until site.js has drawn the feed, so it doesn't flash under the sentence and jump */
  html.is-home .main--home:not(:has(.media--home)) ~ .ft { visibility: hidden; }
  html.is-home body > .ink { display: none; }   /* the phone home is the work feed: no ink (ink.js doesn't build it here); outranks the .ink block's display:block further down */
  .media:not(.media--home) .grid { grid-template-columns: 1fr; }   /* work pages stack on phones, as /oken does */
  .media:not(.media--home) .grid > * { grid-column: auto; }
  .media-row { flex-direction: column; }   /* phones stack them, like the grids */
  .media-row > .m { flex: none; width: 100%; }
  .wall { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 1.6rem; row-gap: 3rem; margin-top: 6rem; }
  .back { display: block; font-weight: 700; margin-top: calc(3 * var(--line)); }   /* 4 lines under the last text line, as there */
  .link { margin-left: 0; }   /* no gutter at the screen edge: the key's box starts on the text line */
}

@media (prefers-reduced-motion: reduce) {
  .qv__bg, .qv__img { transition: none; }
  .peek__in { transform: none; }   /* fade only, no scale */
  .hl { transition: none; }   /* the marker is simply there */
  /* links: the underline appears without drawing; no arrow nudge */
  .link svg, .links a::after, .cta::after, .mail::after { transition: none; }
  .link:hover svg { transform: none; }
  .pill { transition: background-color 150ms ease; }
  .pill:active, .link:active, .key:active { transform: none; }
  .ch { filter: none; transition: opacity 150ms ease; }   /* the word just changes */
  .key[data-alt], .pill[data-alt], .link[data-alt] { transition: background-color 150ms ease; }
  .links a::before { transform: none; }   /* the selection dot only fades */
}

/* liquid hover (assets/js/liquid.js): the WebGL canvas laid over the picture under the
   pointer; it only shows once its first frame is drawn */
.liquid { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; }
.liquid.is-on { opacity: 1; }

/* ======================================================================
   Five columns (2026-10-13): the text on two (labels | lists), the work on three.
   One column = (page − 2 margins − 4 gutters) / 5: 259px at 1440×900. From 1180px
   wide: narrower windows keep the two-and-two grid above (at 1024 the buttons would
   stack into the footer), portrait tablets and phones their own layouts.
   ====================================================================== */
@media (min-width: 1180px) {
  :root { --col: calc((100vw - 2 * var(--pad) - 4 * var(--gut)) / 5); }
  /* left: columns 1–2 (the half gutter on the right keeps the old hover room) */
  .hd, .ft { width: calc(var(--pad) + 2 * var(--col) + 1.5 * var(--gut)); }
  /* right: columns 3–5 */
  .main { margin-left: calc(var(--pad) + 2 * var(--col) + 2 * var(--gut)); }
  /* a work's header on the three columns: the text | Roles | Link */
  .intro--cols { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Research's text: two of the three columns */
  .intro { max-width: calc(2 * var(--col) + var(--gut)); }
  .intro--cols { max-width: none; }
}
/* About in one column (2026-10-14): the short text sits in the first work column */
@media (min-width: 1180px) {
  .main > .about { max-width: var(--col); }
}
/* windows close to square or taller: two columns, so the text stays above the centred logo */
@media (min-width: 1180px) and (max-aspect-ratio: 6/5) {
  .main > .about { max-width: calc(2 * var(--col) + var(--gut)); }
}

/* ======================================================================
   The logo in the middle of the screen, inverted (2026-10-13): white with
   mix-blend-mode: difference, black over the white page, the picture's negative
   over the work. It lives outside the header, which is its own stacking context and
   would stop the blend. 15rem: 194px at 1440×900. (Home · Research · About were
   tried beside it, in a row then a column, and went back to the top of the left
   column.)
   ====================================================================== */
.center {
  position: fixed;
  z-index: 35;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  mix-blend-mode: difference;
}
.brand--center { display: block; width: auto; height: auto; max-width: none; }
.brand--center img { display: block; width: 15rem; max-width: none; top: 0; filter: invert(1); }   /* the black logo file, drawn white */
/* under 1180px the middle of the screen falls on the lists: the logo goes to the top
   centre and the left column and the work start three lines lower */
@media (min-width: 770px) and (max-width: 1179px) {
  .center { top: var(--top); transform: translateX(-50%); }
  .hd, .main { padding-top: calc(var(--top) + 3 * var(--line)); }
}
/* phones: the logo centred in the top bar, MENU on the right */
@media (max-width: 769px) {
  .center { top: var(--top); transform: translateX(-50%); }
  .brand--center img { width: 13.1em; }
  .menu-btn { grid-column: 2; }
}

/* ======================================================================
   Book a call (2026-10-13): Cal.com's calendar in the site's own window instead of
   Cal's pop-up. The page blurs behind a white veil (18px blur), the panel carries the
   site's 7px corners and a hairline + soft shadow, BOOK A CALL reads like the section
   labels, Close is a key like X and Mail (Esc on hover). Opens in 200ms (veil) /
   240ms (panel rising 8px from 0.98, strong ease-out); closes faster, 150ms.
   A modal, so it scales from its centre.
   ====================================================================== */
.bk {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
  padding: var(--pad);
  background: rgba(255, 255, 255, 0.5);
  -webkit-backdrop-filter: blur(18px) saturate(1.1);
  backdrop-filter: blur(18px) saturate(1.1);
  visibility: hidden;
  opacity: 0;
  transition: opacity 150ms ease, visibility 0s linear 150ms;
}
.bk.is-open { visibility: visible; opacity: 1; transition: opacity 200ms var(--ease-out); }
.bk__panel {
  display: flex;
  flex-direction: column;
  width: min(1080px, 100%);    /* sized like bakeddesign's booking window: Cal's month view at its
                                  roomy proportions (details ¼, calendar ½, times ¼). Below ~768px
                                  (Cal's own breakpoint, in px) it stacks and gets taller */
  max-height: 100%;            /* as tall as the calendar, never taller than the screen */
  background: var(--bg);
  border-radius: var(--radius);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06), 0 24px 64px -16px rgba(0, 0, 0, 0.22);
  overflow: hidden;
  transform: translateY(8px) scale(0.98);
  transition: transform 150ms ease;
}
.bk.is-open .bk__panel { transform: none; transition: transform 240ms var(--ease-out); }
.bk__head {
  display: flex;
  align-items: center;
  gap: var(--gut);
  padding: 0.75rem 0.75rem 0.75rem var(--pad);
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
}
.bk__meta { color: var(--muted); text-transform: uppercase; }
.bk__close { margin-left: auto; }
.bk__panel:focus { outline: none; }
.bk__cal { position: relative; flex: 0 1 auto; min-height: 30rem; overflow: auto; overscroll-behavior: contain; }   /* holds its place while Cal loads */
.bk.is-ready .bk__cal { min-height: 0; }
.bk__wait { position: absolute; inset: 0; display: grid; place-items: center; color: var(--muted); text-transform: uppercase; }
.bk.is-ready .bk__wait { display: none; }
@media (prefers-reduced-motion: reduce) {
  .bk__panel, .bk.is-open .bk__panel { transform: none; }
}
/* phones: the window fills the screen */
@media (max-width: 769px) {
  .bk { padding: 0; }
  .bk__panel { width: 100%; height: 100%; border-radius: 0; box-shadow: none; }
}

/* ======================================================================
   AVAILABLE FOR NEW PROJECTS (2026-10-13): parc.studio's footer line, value for value
   (read from parc.studio/projects, .u-mailto-breathe):
   - the words rest at black 5%, almost invisible;
   - a solid black band, 56px soft each side, in a gradient 250% the line's width,
     clipped to the text, sweeps left to right: 4s, linear, forever;
   - hover turns the words solid black in 0.4s on cubic-bezier(.83, 0, .17, 1);
   - uppercase, letter-spacing 0.025em, line-height 1 (Parc: 628 weight at 12.6px);
   - reduced motion: no sweep, black 20%.
   Bottom left, in the labels column, level with the X key; it opens Book a call.
   ====================================================================== */
.ft .row { align-items: center; }   /* the line and the keys share a centre */
.avail[hidden] { display: none; }
.avail {
  --shimmer-spread: 56px;
  justify-self: start;
  text-transform: uppercase;
  letter-spacing: 0.025em;
  line-height: 1;
  white-space: nowrap;
  background-image:
    linear-gradient(90deg, transparent calc(50% - var(--shimmer-spread)), var(--ink) 50%, transparent calc(50% + var(--shimmer-spread))),
    linear-gradient(color-mix(in srgb, var(--ink) 5%, transparent), color-mix(in srgb, var(--ink) 5%, transparent));
  background-size: 250% 100%, auto;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  animation: avail-shimmer 4s linear infinite;
  transition: color 0.4s cubic-bezier(0.83, 0, 0.17, 1), -webkit-text-fill-color 0.4s cubic-bezier(0.83, 0, 0.17, 1);
}
@keyframes avail-shimmer {
  0% { background-position: 100% center, 0 0; }
  100% { background-position: 0% center, 0 0; }
}
@media (hover: hover) and (pointer: fine) {
  .avail:hover { color: var(--ink); -webkit-text-fill-color: var(--ink); }
}
.avail:focus-visible { color: var(--ink); -webkit-text-fill-color: var(--ink); }
@media (prefers-reduced-motion: reduce) {
  .avail {
    background-image: none;
    color: color-mix(in srgb, var(--ink) 20%, transparent);
    -webkit-text-fill-color: color-mix(in srgb, var(--ink) 20%, transparent);
    animation: none;
  }
}
/* under 1180px the labels column can be narrower than the line: beside the mark it wraps onto
   two lines (about a key's height) instead of running under it. Portrait tablets, where the
   column is narrowest, put it on its own line above the mark, as phones do, and the left
   column stops higher to leave it room. */
@media (min-width: 770px) and (max-width: 1179px) {
  .avail { white-space: normal; line-height: 1.1; }
}
@media (min-width: 770px) and (max-width: 1100px) and (max-aspect-ratio: 1/1) {
  .ft .row { display: block; }
  .avail { display: inline-block; margin-bottom: 0.8rem; white-space: nowrap; line-height: 1; }
  .hd { max-height: calc(100vh - 6.5rem); max-height: calc(100dvh - 6.5rem); }
}
/* phones: the footer is one column at the end of the page; the line sits above the keys */
@media (max-width: 769px) {
  .avail { display: inline-block; margin-bottom: 1rem; }
}

/* ======================================================================
   Selected works dissolve into the page (2026-10-14, after the user's calendar
   reference; lighter on "a bit too much"): the last names blur a little and fade into
   white, the very last one still faintly there. Two layers over the list: a 1px blur over
   its lower half, a 3px one over its last quarter, each fading in through a gradient
   mask, plus a white wash at the bottom.
   Hover (or tabbing in): the fade slides down off the list, so the names come sharp from
   the top down, like a curtain lifting (--wf, an animatable custom property, shifts every
   stop): 320ms strong ease-out. Leaving: it slides back up, slower and a beat later (480ms
   ease-in-out, 120ms delay) so passing from one name to the next never flickers.
   Desktop and tablets only.
   ====================================================================== */
@property --wf { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@media (min-width: 770px) and (hover: hover) and (pointer: fine) {   /* touch has no hover to lift it */
  .hd .links--works { position: relative; }
  .hd .links--works::before,
  .hd .links--works::after {
    content: "";
    position: absolute;
    inset: -0.4em -0.8em -0.6em -1.6em;   /* past the pills' padding on every side */
    pointer-events: none;
    --wf: 0%;
    transition: --wf 480ms cubic-bezier(0.77, 0, 0.175, 1) 120ms;
  }
  .hd .links--works::before {
    -webkit-backdrop-filter: blur(1px);
    backdrop-filter: blur(1px);
    -webkit-mask-image: linear-gradient(to top, #000 calc(0% - var(--wf)), #000 calc(18% - var(--wf)), transparent calc(50% - var(--wf)));
    mask-image: linear-gradient(to top, #000 calc(0% - var(--wf)), #000 calc(18% - var(--wf)), transparent calc(50% - var(--wf)));
  }
  .hd .links--works::after {
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    -webkit-mask-image: linear-gradient(to top, #000 calc(0% - var(--wf)), transparent calc(26% - var(--wf)));
    mask-image: linear-gradient(to top, #000 calc(0% - var(--wf)), transparent calc(26% - var(--wf)));
    background: linear-gradient(to top,
      color-mix(in srgb, var(--bg) 82%, transparent) calc(0% - var(--wf)),
      color-mix(in srgb, var(--bg) 45%, transparent) calc(12% - var(--wf)),
      transparent calc(34% - var(--wf)));
  }
  .hd .links--works:hover::before, .hd .links--works:hover::after,
  .hd .links--works:focus-within::before, .hd .links--works:focus-within::after {
    --wf: 60%;
    transition: --wf 320ms var(--ease-out);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hd .links--works::before, .hd .links--works::after { transition: none; }
}
@media (prefers-reduced-transparency: reduce) {
  .hd .links--works::before, .hd .links--works::after { -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* ======================================================================
   Phones and tablets feel native (2026-10-14, before going live): no grey flash on
   tap (each control has its own :active), taps fire at once (no double-tap-zoom wait),
   a long press on a control doesn't select its label or pop the link callout, and the
   phone menu scrolls without pulling the page behind it. Content text stays selectable.
   ====================================================================== */
html { -webkit-tap-highlight-color: transparent; }
a, button, .pill, .key, .link, .menu-btn, .menu-close { touch-action: manipulation; }   /* not the slider: it keeps pan-y */
button, .pill, .key, .link, .menu-btn, .menu-close, .cmp {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.hd.is-open { overscroll-behavior: contain; }

/* ======================================================================
   The first load (2026-10-16, after quintinlodge.com's intro, analysed frame by frame): the
   first page of a visit opens on white paper. index.html sets html[data-veil="on"] before the
   first paint ("wait" instead while the tab is out of sight: blank paper until it's looked at,
   so the intro is never spent unseen); its script then steps the states:
   on    the logo's letters roll in through their own masks, PURE and the figure up from
         below, STUDIO down from above, 45ms apart (900ms each, CSS only, from the first paint).
   reel  a 12×15rem window opens in the paper at the middle of the screen (600ms) and cuts
         through eight stills of the work, 130ms each (assets/intro/); the logo's difference
         blend turns it to their negative where it crosses them. The last cut is the page
         itself (the ink on the home, the feed on phones). The reel loops until the page has loaded.
   off   the window opens to the whole screen on a critically damped spring (Apple's default: no
         overshoot, response 0.55 s, settled in 800ms; 1s in-out where linear() is missing), and the
         text blocks rise 6px into place 60ms apart as it passes. The script then drops the attribute.
   The stills run dark to light (2026-10-16, emilkowalski-apple-design: no strobing between
   tones, and no jump at the cut onto the white page).
   A click, a key or the wheel skips to "off". Later loads of the visit and in-site navigation:
   nothing. The state lives in a data attribute, which build.mjs's prerender doesn't copy.
   (2026-10-15 to 16 it was the paper alone fading out over the rising blocks.)
   ====================================================================== */
:root {
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);   /* strong in-out, for things moving on screen */
  --open: var(--ease-in-out);   /* the window opening to the screen, and when the text starts rising */
  --open-time: 1s;
  --open-lead: 350ms;
}
@supports (transition-timing-function: linear(0, 1)) {
  :root {
    /* a critically damped spring, 1 − (1 + ωt)e^(−ωt) with ω = 2π / 0.55 s, sampled every 40ms */
    --open: linear(0, 0.0775 5%, 0.2329 10%, 0.3985 15%, 0.5460 20%, 0.6663 25%, 0.7596 30%, 0.8295 35%, 0.8806 40%, 0.9173 45%, 0.9433 50%, 0.9615 55%, 0.9741 60%, 0.9828 65%, 0.9888 70%, 0.9928 75%, 0.9955 80%, 0.9974 85%, 0.9986 90%, 0.9994 95%, 1);
    --open-time: 800ms;
    --open-lead: 120ms;
  }
}
.veil, .reel, .brand__roll { display: none; }
/* the paper, with the window cut out of it (--hw, --hh: the window's half sizes) */
html[data-veil] .veil {
  --hw: 0px;
  --hh: 0px;
  display: block;
  position: fixed;
  inset: 0;
  z-index: 34;
  background: var(--bg);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, 0 50%,
    calc(50% - var(--hw)) 50%, calc(50% - var(--hw)) calc(50% + var(--hh)), calc(50% + var(--hw)) calc(50% + var(--hh)),
    calc(50% + var(--hw)) calc(50% - var(--hh)), calc(50% - var(--hw)) calc(50% - var(--hh)), calc(50% - var(--hw)) 50%, 0 50%);
}
html[data-veil="on"] .veil { animation: veil-out 400ms var(--ease-out) 2.5s forwards; }   /* lifts on its own if the script never runs */
html[data-veil="reel"] .veil { --hw: 6rem; --hh: 7.5rem; transition: clip-path 600ms var(--ease-out); }
html[data-veil="off"] .veil { --hw: 51%; --hh: 51%; pointer-events: none; transition: clip-path var(--open-time) var(--open); }
@keyframes veil-out { to { opacity: 0; visibility: hidden; } }
/* the reel: the stills fill the window, one shown at a time (index.html) */
html[data-veil] .reel {
  display: block;
  position: fixed;
  z-index: 34;
  top: calc(50% - 7.5rem);
  left: calc(50% - 6rem);
  width: 12rem;
  height: 15rem;
  pointer-events: none;
  clip-path: inset(50%);
}
html[data-veil="reel"] .reel { clip-path: inset(0); transition: clip-path 600ms var(--ease-out); }
html[data-veil="off"] .reel { visibility: hidden; }
.reel img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; }
.reel img.is-on { opacity: 1; }
/* the logo, letter by letter: each .lg is a copy of the logo file lying exactly on the real one,
   clipped to one glyph's box in the logo's own units (--x, --w across 552, --y, --h down 61);
   the copy slides through that fixed box, so at rest it is the real logo pixel for pixel */
html[data-veil] .brand--center > img { visibility: hidden; }
html[data-veil] .brand__roll { display: block; position: absolute; inset: 0; }
.brand--center { position: relative; }
.lg {
  position: absolute;
  inset: 0;
  clip-path: inset(calc(var(--y, 13) / 61 * 100%) calc((552 - var(--x) - var(--w)) / 552 * 100%)
    calc((61 - var(--y, 13) - var(--h, 36)) / 61 * 100%) calc(var(--x) / 552 * 100%));
}
.brand--center .lg img { position: absolute; top: 0; left: 0; width: 100%; max-width: none; height: auto; }
html[data-veil]:not([data-veil="wait"]) .lg img { animation: lg-up 900ms var(--ease-out) both; animation-delay: calc(var(--i) * 45ms); }
html[data-veil="wait"] .brand__roll { visibility: hidden; }   /* blank paper until the tab is looked at */
html[data-veil]:not([data-veil="wait"]) .lg--down img { animation-name: lg-down; }
@keyframes lg-up { from { transform: translateY(calc(var(--h, 36) / 61 * 105%)); } }   /* just past the glyph's box */
@keyframes lg-down { from { transform: translateY(calc(var(--h, 36) / 61 * -105%)); } }
/* the blocks, in reading order: the nav, Services, Selected works, the buttons, the footer (phones:
   MENU, the sentence, the footer), hidden under the paper until the window opens. The page itself
   (.main: the phone feed, a work's pictures) stays, so the reel's last cut shows it in the window */
.row--top { --i: 0; }
.pitch, #sections > .sec:nth-child(1) { --i: 1; }
#sections > .sec:nth-child(2) { --i: 2; }
#sections > .sec:nth-child(3) { --i: 3; }
.ft { --i: 4; }
@media (max-width: 769px) { .ft { --i: 2; } }
html[data-veil="on"] :is(.row--top, .pitch, #sections > .sec, .ft) { animation: veil-rise 400ms var(--ease-out) 2.5s both; }   /* with the paper's own fallback */
html[data-veil="reel"] :is(.row--top, .pitch, #sections > .sec, .ft) { opacity: 0; }
html[data-veil="off"] :is(.row--top, .pitch, #sections > .sec, .ft) {
  animation: veil-rise 500ms var(--ease-out) both;
  animation-delay: calc(var(--open-lead) + var(--i) * 60ms);
}
@keyframes veil-rise { from { opacity: 0; transform: translateY(6px); } }
/* reduced motion: no roll, no reel, no window; the paper fades out over the page */
@media (prefers-reduced-motion: reduce) {
  html[data-veil] .veil { clip-path: none; }
  html[data-veil] .reel { display: none; }
  html[data-veil]:not([data-veil="wait"]) .lg img { animation: none; }
  html[data-veil="off"] .veil { opacity: 0; transition: opacity 150ms ease; }
  html[data-veil="off"] :is(.row--top, .pitch, #sections > .sec, .ft) { animation: none; }
}

/* ======================================================================
   Ink behind the logo, on the home (2026-10-14, assets/js/ink.js): the user's Data Dune ink
   (its runtime and clip) under the logo and the columns; the logo's difference blend turns
   white over the ink. The runtime paints its paper at 252: brightness(1.012) lifts it to
   white and the box multiplies onto the page, so only the ink shows; a round mask dissolves
   the square's edges. It never catches the pointer. Home only; fades in over 900ms (a
   big-bang burst was tried on 2026-10-14 and rejected).
   ====================================================================== */
.ink {
  position: fixed;
  z-index: 1;
  pointer-events: none;
  display: none;
  opacity: 0;
  mix-blend-mode: multiply;
  isolation: isolate;
  -webkit-mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
  mask-image: radial-gradient(closest-side, #000 52%, transparent 100%);
}
html.is-home .ink { display: block; }
html.is-home .ink.is-shown { animation: ink-in 900ms var(--ease-out) forwards; }   /* the fade starts on the first ink: ink.js adds is-shown once the poster or the clip shows */
@keyframes ink-in { to { opacity: 1; } }
.ink__clip { position: absolute; inset: 0; width: 100%; height: 100%; display: block; filter: brightness(1.012); }
.ink__smoke { position: absolute; inset: 0; width: 100%; height: 100%; display: block; z-index: 3; pointer-events: none; }   /* smoke.js: above the clip, inside the mask and the multiply */
@media (prefers-reduced-motion: reduce) { html.is-home .ink, html.is-home .ink.is-shown { animation: none; opacity: 1; } }
