/* ---------- Fonts: self-hosted variable WOFF2 (SIL OFL 1.1, licences in assets/fonts/). The six files are
   byte-identical copies of the ones Google Fonts serves for the design's css2 URL (Gabarito v9, Plus Jakarta Sans
   v12 upright and italic, latin and latin-ext), so small text rasterises exactly as in the design; a re-subset of
   the same fonts snaps glyph edges differently at DPR 1. tools/package.sh checks their sha256. Unicode ranges as
   in Google's CSS; latin-ext carries č š ž. The italic is the design's too: it loads only where an editor sets a
   word in italic. ---------- */
@font-face {
  font-family: Gabarito;
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/gabarito-latin-ext.woff2") format("woff2");
  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;
}
@font-face {
  font-family: Gabarito;
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/gabarito-latin.woff2") format("woff2");
  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: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-latin-ext.woff2") format("woff2");
  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;
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-latin.woff2") format("woff2");
  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: "Plus Jakarta Sans";
  font-style: italic;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-italic-latin-ext.woff2") format("woff2");
  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;
}
@font-face {
  font-family: "Plus Jakarta Sans";
  font-style: italic;
  font-weight: 200 800;
  font-display: swap;
  src: url("../fonts/plus-jakarta-sans-italic-latin.woff2") format("woff2");
  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;
}

/* =========================================================================
   Ana Topole, final website
   Base: direction 01 "Quiet Grid" (12-column grid, one edge,
   case-study template, About tables, images drifting inside fixed frames).
   From 03 "Loud Index": the interactive project index (cursor preview,
   hover shift) and the LET'S TALK block with the stacked contact list.
   From 01B: the card-swap view transition, the home thumbnail row and the
   About portrait drift with her signature moving over its corner.
   Single light theme by choice: this page deliberately commits to her
   light palette (#fffdfd / #0d0d0d / #808080). No dark-mode blocks.
   ========================================================================= */

/* ---------- 0. Tokens ---------- */
:root {
  --bg: #fffdfd;
  --ink: #0d0d0d;
  --grey: #808080;
  --line: rgba(13, 13, 13, .13);
  --surface: #f3f1f0;
  --g-orange: #ef471c;      /* Gardena colour chips (her brand rules), sampled from her product sheet */
  --g-turq: #2cb1bd;
  --g-grey: #4b4748;

  --edge: clamp(16px, 2.2vw, 32px);
  --gutter: 16px;
  --cols: 4;
  --hdr: 56px;
  --ease: cubic-bezier(.2, .7, 0, 1);
  /* one grid column plus its gutter: hovered index rows and contact words slide exactly this far */
  --pitch: calc((100vw - 2 * var(--edge) - (var(--cols) - 1) * var(--gutter)) / var(--cols) + var(--gutter));

  --f-display: Gabarito, "Helvetica Neue", Arial, sans-serif;
  --f-text: "Plus Jakarta Sans", "Helvetica Neue", Arial, sans-serif;

  --fs-body: clamp(15px, 1.07vw, 19px);
  --fs-tab: clamp(17px, 1.29vw, 22px);
  --fs-site: clamp(22px, 2.4vw, 40px);       /* ~35px at 1440: the wordmark spans ~15% of the width, as in her template */
  --fs-h2: clamp(28px, 2.8vw, 50px);
  --fs-idx: clamp(22px, 4vw, 64px);          /* [01]-[09]: display numerals, the slower layer (her note: half the round-3 size) */
  --fs-title: clamp(36px, 4.45vw, 80px);
  --fs-case: clamp(44px, 6vw, 108px);
  --fs-hero: clamp(36px, 6.1vw, 116px);       /* uppercase: "DESIGNING THOUGHTFUL" fits the 9-column title from 1024px up */
  --fs-stat: clamp(56px, 6.67vw, 120px);
  --fs-xl: clamp(46px, 6.67vw, 120px);
  --fs-row: clamp(32px, 4vw, 72px);          /* index rows: big uppercase names, a step under the case titles */
  --fs-cword: clamp(40px, 5.5vw, 100px);     /* MAIL / LINKEDIN / INSTAGRAM / CV under LET'S TALK */

  --sec: clamp(80px, 9vw, 168px);
  --sp: clamp(40px, 4.4vw, 80px);
}
@media (max-width: 767.98px) {
  :root { --fs-row: 7.2vw; }                 /* "PARAMEDIC" fits its column beside the thumbnail at 360px */
}
@media (min-width: 1024px) {
  :root { --gutter: 24px; --cols: 12; --hdr: 64px; }
}

/* ---------- 1. Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--f-text);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img { display: block; max-width: 100%; height: auto; }
/* her note: make casual saving hard. Pictures cannot be dragged out, selected or long-pressed to save, and every
   photo frame carries a clear layer on top (section 4), so "Save image" has no picture to save. This deters,
   it cannot stop a screenshot or the developer tools. */
img { -webkit-user-drag: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: pointer; text-align: left; }
p, h1, h2, h3, h4, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { list-style: none; padding: 0; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
[tabindex="-1"]:focus { outline: none; }
::selection { background: var(--ink); color: var(--bg); }
sub, sup { line-height: 0; }

.light { font-weight: 300; color: var(--grey); }
/* hidden but read aloud; core prints .screen-reader-text (pagination, more links). The -1px margin keeps the
   live region after #page from adding a pixel of scroll under the Contact page. */
.vh, .screen-reader-text { position: absolute !important; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.screen-reader-text:focus { position: fixed !important; left: var(--edge); top: 8px; z-index: 120; width: auto; height: auto; margin: 0; padding: 8px 12px; clip: auto; white-space: normal; background: var(--ink); color: var(--bg); }
.skip { position: fixed; left: var(--edge); top: 8px; z-index: 120; padding: 8px 12px; background: var(--ink); color: var(--bg); transform: translateY(-160%); }
.skip:focus { transform: none; }
@media print { .skip { display: none; } } /* it is fixed, so it printed at the foot of every page */

/* Text links inside copy */
.tlink { background: linear-gradient(currentColor, currentColor) 0 100% / 100% 1px no-repeat; padding-bottom: 1px; transition: background-size .5s var(--ease); }
.tlink:hover { background-size: 0 1px; background-position: 100% 100%; }
.arrow { display: inline-block; transition: transform .5s var(--ease); }
a:not(.vt-rest):hover .arrow, button:hover .arrow { transform: translateX(5px); }   /* .vt-rest: section 11 */

/* ---------- 2. Layout: one edge, one grid ---------- */
.wrap {
  padding-left: max(var(--edge), env(safe-area-inset-left, 0px));
  padding-right: max(var(--edge), env(safe-area-inset-right, 0px));
}
.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--gutter);
}
.grid > * { grid-column: 1 / -1; min-width: 0; }

/* Decorative hairline. Scales 0.6 -> 1 from the left when it enters (motion section). */
.rule { height: 1px; background: var(--line); transform-origin: 0 50%; }

/* Type roles */
.h2 { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-h2); line-height: 1.04; letter-spacing: -.015em; }
.label { font-weight: 300; color: var(--grey); }
.display { font-family: var(--f-display); font-weight: 800; letter-spacing: -.03em; line-height: .9; }

/* ---------- 3. Header ---------- */
.hdr {
  position: fixed; z-index: 50;
  top: env(safe-area-inset-top, 0px); left: 0; right: 0;
  background: var(--bg);
  transition: transform .6s var(--ease);
}
.hdr.is-hidden { transform: translate3d(0, calc(-100% - env(safe-area-inset-top, 0px)), 0); }
.hdr__in { position: relative; height: var(--hdr); display: flex; align-items: center; justify-content: space-between; }
.hdr__in::after {
  content: ""; position: absolute; bottom: 0; height: 1px; background: var(--line);
  left: max(var(--edge), env(safe-area-inset-left, 0px));
  right: max(var(--edge), env(safe-area-inset-right, 0px));
}
.brand { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-site); line-height: 1; letter-spacing: -.005em; white-space: nowrap; }
.nav { position: relative; display: none; gap: clamp(22px, 2.6vw, 46px); }
.nav__link { font-family: var(--f-display); font-weight: 500; font-size: var(--fs-tab); line-height: 1; padding: 10px 0; color: var(--ink); transition: color .3s; }
.nav__link:hover { color: var(--grey); }
/* her note: the open section's tab in ink, the other two in grey, and no underline; a grey tab darkens on hover.
   Home belongs to no tab, so all three stay ink there, as in her template. (JS sets .has-tab.) */
.has-tab > .nav__link, .has-tab > .mnav__link { color: var(--grey); }
.has-tab > .is-active, .has-tab > .nav__link:hover { color: var(--ink); }
.menu-btn { font-family: var(--f-display); font-weight: 500; font-size: var(--fs-tab); padding: 10px 0 10px 16px; }

.mnav { position: absolute; top: 100%; left: 0; right: 0; background: var(--bg); border-bottom: 1px solid var(--line); padding-bottom: 24px; }
.mnav__link { display: flex; justify-content: space-between; align-items: baseline; padding: 14px 0 10px; border-bottom: 1px solid var(--line); font-family: var(--f-display); font-weight: 800; font-size: 40px; line-height: 1; letter-spacing: -.02em; }
.mnav__link[aria-current="page"]::after { content: attr(data-current); font-family: var(--f-text); font-weight: 300; font-size: 15px; letter-spacing: 0; color: var(--grey); }
.mnav__meta { display: flex; flex-wrap: wrap; gap: 8px 24px; padding-top: 18px; }

@media (min-width: 640px) {
  .nav { display: flex; }
  .menu-btn, .mnav { display: none !important; }
}
@media (min-width: 1024px) {
  .hdr__in { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gutter); }
  .brand { grid-column: 1 / 4; justify-self: start; }
  .nav { grid-column: 7 / 13; justify-self: end; }
}

main { padding-top: calc(var(--hdr) + env(safe-area-inset-top, 0px)); }


/* ---------- 4. Photo frame: the image drifts inside a fixed frame ---------- */
.ph { margin: 0; --travel: .16; }           /* picture travels ±8% of its frame; openers set .2 */
/* her note: no grey plate behind screens, sheets and renders on white. A contained picture sits on the page
   itself; a flat one (a screen or sheet in a frame of its own ratio) is shown whole and still, on the column
   lines; a white-edged screen or sheet keeps a hairline (.ph--edge) so its edge still reads on the page. */
.ph--contain { --travel: .12; --ph-bg: var(--bg); }
.ph--flat { --travel: 0; --ph-bg: transparent; }
.ph--edge .ph__in img { outline: 1px solid var(--line); outline-offset: -1px; }
/* a wide sheet beside a taller photo sits on the bottom of its frame, so its caption follows it (Paramedic) */
.ph--contain.ph--bottom .ph__in img { margin: auto auto 0; }
.ph__frame {
  position: relative; overflow: hidden; isolation: isolate;
  aspect-ratio: var(--frm, var(--fr, 1.5));
  background: var(--ph-bg, var(--surface));
}
.ph__in { position: absolute; left: 0; right: 0; top: calc(var(--travel) * -50%); height: calc(100% + var(--travel) * 100%); background: var(--ph-bg, var(--surface)); will-change: transform; }
/* the clear layer over every picture (image-save deterrent): a right-click or long-press lands on it, not the image */
.ph__frame::after, .prow-thumb::after { content: ""; position: absolute; inset: 0; z-index: 1; }
.ph__in img {
  width: 100%; height: 100%; max-width: none;
  object-fit: cover; object-position: var(--op, 50% 50%);
  transform: scale(var(--sc, 1));
  transition: transform .7s var(--ease), opacity .45s linear;
}
/* contain: the whole sheet stays visible inside its frame */
.ph--contain .ph__in img {
  position: absolute; inset: 0; margin: auto;
  width: auto; height: auto; max-width: 100%;
  max-height: calc((1 - var(--travel) - .02) / (1 + var(--travel)) * 100%);   /* (1 - travel) of the frame, minus 2%: never cropped by the drift */
}
/* a render on white melts into the page with a 2% feathered edge; screens, sheets and photos keep a crisp edge */
.ph--contain.ph--mult .ph__in img {
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent), linear-gradient(180deg, transparent, #000 2%, #000 98%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent, #000 2%, #000 98%, transparent), linear-gradient(180deg, transparent, #000 2%, #000 98%, transparent);
  mask-composite: intersect;
}
/* renders on white: white takes the tone behind it (the page for contained renders, the pale surface for the
   tea set's studio renders, which fill their frames) */
.ph--mult .ph__in img { mix-blend-mode: multiply; }
.ph figcaption, .cap { margin-top: 12px; color: var(--grey); font-weight: 300; max-width: 56ch; }

@media (min-width: 1024px) {
  .ph__frame { aspect-ratio: var(--fr, 1.5); }
}
@media (max-width: 1023.98px) {
  .ph--contain { --travel: .08; }     /* sheets fill the column on small screens */
}
/* Desktop (her note: the work pictures were too big). In a case study no picture is taller than 75vh: a figure
   is at most 75vh x its frame ratio wide and starts on its column line. Full-width figures stop at column 9.
   (Fallback for browsers without round() or container units; the rules below keep the pictures on the grid.) */
@media (min-width: 1024px) {
  .case .ph, .case .vid { justify-self: start; width: 100%; max-width: calc(75vh * var(--fr, 1.5)); }
  .case .vid:not([style*="--fr"]) { max-width: calc(75vh * 16 / 9); }
  .case .fig-full { max-width: min(calc((100% - 11 * var(--gutter)) / 12 * 9 + 8 * var(--gutter)), calc(75vh * var(--fr, 1.5))); }
}
/* The same cap, kept on the grid: a picture is the widest whole number of columns, up to its slot, whose height
   stays within 75vh (--P is one column plus its gutter, measured on the content box, so a scrollbar does not skew
   it). Pairs and rows of three sit back to back, one gutter apart, from the left edge; a picture beside a note on
   its left keeps the right edge; full-width figures stop at column 9 or before; the opener's picture keeps the
   right edge with its [0n] above it. Nothing is shown above its own size. */
@supports (width: round(down, 1cqw, 1px)) and selector(:has(*)) {
  @media (min-width: 1024px) {
    .case > .wrap { container-type: inline-size; }
    .case { --P: calc((100cqw + var(--gutter)) / 12); }
    .case .pair > .ph, .case .pair > .vid, .case .opener__media > .ph, .case .wrap > .fig-full {
      /* 2px slack: a picture 1px over 75vh keeps its column. One line: LiteSpeed Cache's CSS minifier breaks this
         value when it runs over two lines (it drops the comma before the line break). */
      width: min(calc(var(--span) * var(--P) - var(--gutter)), calc(round(down, 75vh * var(--fr, 1.5) + var(--gutter) + 2px, var(--P)) - var(--gutter)));
      max-width: none;
    }
    .case .pair > .vid:not([style*="--fr"]) { --fr: 1.7778; }
    .case .p-6a, .case .p-6b, .case .opener__media > .ph { --span: 6; }
    .case .p-4a, .case .p-4m, .case .p-4b { --span: 4; }
    .case .p-8a, .case .p-8b { --span: 8; }
    .case .wrap > .fig-full { --span: 9; }
    .case .pair { grid-template-columns: none; grid-auto-columns: auto; justify-content: start; }
    .case .pair > :nth-child(1) { grid-column: 1; }
    .case .pair > :nth-child(2) { grid-column: 2; }
    .case .pair > :nth-child(3) { grid-column: 3; }
    /* a note on the left of a picture (in either source order): the note keeps columns 1-4, the picture the right edge */
    .case .pair:has(> div.p-4a) { grid-template-columns: calc(4 * var(--P) - var(--gutter)) minmax(0, 1fr); }
    .case .pair:has(> div.p-4a) > div.p-4a { grid-column: 1; }
    .case .pair:has(> div.p-4a) > .p-8b { grid-column: 2; justify-self: end; }
    /* HubKo's outcomes run on the left (capped at 25em) takes the free track, so the poster keeps the right edge */
    .case .pair:has(> div.pair__note--run.p-8a) { grid-template-columns: minmax(0, 1fr) auto; }
    .case .opener__media { display: grid; grid-template-columns: auto; justify-content: end; }
  }
}

/* Project index "[01]": display numerals that drift ±20px against their image (±10px below 1024px) */
.idx { display: block; font-family: var(--f-display); font-weight: 400; font-size: var(--fs-idx); line-height: .86; letter-spacing: -.035em; color: var(--grey); margin-bottom: clamp(16px, 1.6vw, 26px); will-change: transform; }
/* With motion on, the numeral becomes its own layer only once main.js has started (.motion) and written its first
   drift, in the same frame, as in the design, where the view is first drawn by its script. Chromium keeps the
   first raster of a will-change layer, and a frame drawn before main.js ran gave the numeral a different one. */
@media (prefers-reduced-motion: no-preference) { .js:not(.motion) .idx { will-change: auto; } }

/* Project details table (ref-05): the label sits above the list at every width, so the capitalised
   row labels ("RECOGNITION") get two columns and never run into their value */
.details { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: 12px; }
.details__label { grid-column: 1 / -1; font-weight: 500; }
.details__list { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; }
.details__list > div { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; border-top: 1px solid var(--line); padding: 7px 0 8px; }
.details__list > div:last-child { border-bottom: 1px solid var(--line); }
.details dt { grid-column: 1 / 3; color: var(--grey); font-weight: 300; }
.details dd { grid-column: 3 / -1; }
@media (min-width: 1024px) {
  .details { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* Thumbnail tile (ref-06 rows) */
.tile { display: block; }
.tile__no { display: block; font-family: var(--f-display); font-weight: 500; font-size: 15px; line-height: 1; color: var(--grey); margin-bottom: 10px; transition: color .4s; }
.tile__cap { display: block; margin-top: 12px; line-height: 1.4; }
.tile__name { display: block; font-weight: 500; overflow-wrap: anywhere; }
.tile__type { display: block; color: var(--grey); font-weight: 300; transition: color .4s; }
a.tile:not(.vt-rest):hover .tile__type, a.tile:not(.vt-rest):hover .tile__no { color: var(--ink); }
a.tile:not(.vt-rest):hover .ph__in img { transform: scale(calc(var(--sc, 1) * 1.04)); }

/* Section head row */
.sechead { align-items: baseline; row-gap: 8px; margin-bottom: var(--sp); }
.sechead__aside { color: var(--grey); font-weight: 300; }
a.sechead__aside { color: var(--ink); font-weight: 400; }
@media (min-width: 1024px) {
  .sechead > :first-child { grid-column: 1 / 7; }
  .sechead__aside { grid-column: 7 / 13; justify-self: end; }
}

/* Hairline rows (CV tables, recognitions): label 1-3, a 4-7, b 8-11, year 12 */
.rows > li { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--gutter); border-top: 1px solid var(--line); padding: 12px 0 13px; }
.rows > li:last-child { border-bottom: 1px solid var(--line); }
.r-a { grid-column: 1 / 2; }
.r-b { grid-column: 1 / 2; grid-row: 2; color: var(--grey); font-weight: 300; }
.r-c { grid-column: 2 / 3; grid-row: 1; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.r-b .sub { display: block; white-space: nowrap; }
@media (min-width: 1024px) {
  .rows { grid-column: 4 / 13; display: grid; grid-template-columns: subgrid; }
  .rows > li { grid-column: 1 / -1; grid-template-columns: subgrid; column-gap: var(--gutter); }
  .r-a { grid-column: 1 / 5; }
  .r-b { grid-column: 5 / 9; grid-row: 1; }
  .r-c { grid-column: 9 / 10; }
  .r-b .sub { display: inline; }
  .r-b .sub::before { content: ", "; }
}
.rowsec { row-gap: 20px; }
@media (min-width: 1024px) { .rowsec > .h2 { grid-column: 1 / 4; } }

/* Interactive project index (from 03, set on this grid): numbered hairline rows with big uppercase names.
   Desktop with a mouse: a preview follows the cursor, the hovered row slides one column, the others dim.
   Touch, narrow screens, reduced motion and no JS: each case study shows its cover inline instead. */
.plist { border-bottom: 1px solid var(--line); }
.prow { display: block; position: relative; border-top: 1px solid var(--line); }
.prow-in { display: grid; grid-template-columns: 2.1em minmax(0, 1fr) min(24vw, 128px); grid-template-rows: auto 1fr; column-gap: 12px; align-items: start; padding: clamp(10px, 1vw, 16px) 0 clamp(10px, 1vw, 16px); }
.prow-num { grid-column: 1; grid-row: 1; font-family: var(--f-display); font-weight: 500; color: var(--grey); padding-top: .2em; font-variant-numeric: tabular-nums; }
.prow-name { grid-column: 2; grid-row: 1; font-family: var(--f-display); font-weight: 800; font-size: var(--fs-row); line-height: .9; letter-spacing: -.015em; text-transform: uppercase; overflow-wrap: anywhere; transition: transform .7s var(--ease), color .4s; }
.prow-meta { grid-column: 2; grid-row: 2; margin-top: 10px; line-height: 1.35; }
.prow-meta span { display: block; }
.prow-meta .prow-type { color: var(--grey); font-weight: 300; }
.prow-thumb { grid-column: 3; grid-row: 1 / 3; display: block; position: relative; aspect-ratio: 4 / 5; overflow: hidden; background: var(--surface); }
.prow-thumb img { width: 100%; height: 100%; object-fit: cover; }
a.prow:not(.vt-rest):hover .prow-thumb img { transform: scale(1.04); }
.prow-thumb img { transition: transform .7s var(--ease), opacity .45s linear; }
@media (min-width: 1024px) {
  .prow-in { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gutter); }
  .prow-num { grid-column: 1; }
  .prow-name { grid-column: 2 / 10; }
  .prow-meta { grid-column: 10 / 12; grid-row: 1; margin-top: 0; padding-top: .15em; }
  .has-pv .prow-meta { grid-column: 10 / 13; }
  /* without the cursor preview (touch, reduced motion, no JS) the cover sits in the last column */
  .prow-thumb { grid-column: 12 / 13; grid-row: 1; }
  .has-pv .prow-thumb { display: none; }
  .is-hover .prow:not(.is-hot) .prow-name { color: var(--grey); }
  .prow.is-hot .prow-name { transform: translateX(var(--pitch)); }
}

/* Cursor preview: a frame that follows the pointer with a lag; its image counter-moves inside it.
   It sits above or below the hovered row, never on it, so it is sized to fit beside a row on short screens. */
.pv { position: fixed; left: 0; top: 0; z-index: 40; height: clamp(220px, 38vh, 400px); aspect-ratio: 4 / 5; pointer-events: none; display: none; }
.has-pv .pv.is-armed { display: block; }      /* armed once the index nears the screen: until then its covers are not fetched */
.pv-frame { position: relative; width: 100%; height: 100%; overflow: hidden; background: var(--surface); outline: 4px solid var(--bg); opacity: 0; transform: scale(.92); transition: opacity .3s var(--ease), transform .6s var(--ease); }
.pv.is-on .pv-frame { opacity: 1; transform: none; }
.pv-layers { position: absolute; inset: -18px; will-change: transform; }
.pv-l { position: absolute; inset: 0; opacity: 0; transition: opacity .3s linear; background: var(--surface); }
.pv-l.is-on { opacity: 1; }
.pv-l img { width: 100%; height: 100%; object-fit: cover; }
/* a tight square render on white (Gardena's cover) is shown whole in the 4:5 thumb and preview, its white
   multiplied into the surface, instead of cut to one plate */
.pv-l img.fit, .prow-thumb img.fit { object-fit: contain; mix-blend-mode: multiply; }

/* ---------- 5. Home ---------- */
/* Her template (Illustrator, 1366 x 768): the headline huge on the left over two lines; her motion-blurred
   portrait on the right, from the middle of column 8 to the end of column 11; the end of the headline running
   over the portrait's left part; the description on the portrait's top-left corner, its first two lines above
   the photo. Nothing sits under the portrait (her note). Phones and tablets: headline, portrait, description
   (type sizes as before), the headline running into the portrait (below). */
.hero { padding-top: clamp(28px, 3.4vw, 56px); row-gap: 28px; }
.hero__title { font-size: var(--fs-hero); }
.hero__title .ln { display: block; }
.hero__title .ln--grey { color: var(--grey); }
.hero__ph { grid-column: 2 / -1; }
.hero__desc { max-width: 46ch; }
@media (min-width: 768px) and (max-width: 1023.98px) { .hero__ph { grid-column: 3 / -1; } }
/* her note, 2026-10-28: on phones and tablets the headline keeps its size and its place and runs a little over
   the portrait. Her second note: the same line spacing as on wide screens (1.167). Her third note (2026-10-29):
   4px higher. The portrait moves up by the row gap (28px), those 4px and another .6835em (.55em plus the
   half-leading the taller lines add under the last baseline), so its top sits a little above a third of the way
   down the last line's capitals whatever the line count, and the headline is drawn over it */
@media (max-width: 1023.98px) {
  .hero__title { position: relative; z-index: 2; line-height: 1.167; }
  .hero__ph { margin-top: calc(-32px - var(--fs-hero) * .6835); }
}
@media (min-width: 1024px) {
  /* every measure follows the grid, so the composition scales with the page: --hp is one column plus its
     gutter, --half half a column, --pw and --phh the portrait's width and height (4:5), --pt its drop below the
     description's top (two lines of the description) */
  .hero {
    container-type: inline-size; padding-top: clamp(40px, 4.1vw, 80px); row-gap: 0;
    --hp: calc((100cqw + var(--gutter)) / 12);
    --half: calc((var(--hp) - var(--gutter)) / 2);
    --pw: calc(3.5 * var(--hp) - var(--gutter) / 2);
    --phh: calc(var(--pw) * 1.25);
    --pt: calc(var(--fs-body) * 2.4);
  }
  .hero > * { align-self: start; }
  /* line 2 ends about a quarter into the portrait (5.95cqw: "THOUGHTFUL PRODUCTS." is 11.64em wide in Gabarito 800);
     the cap top of line 1 sits 35% down the portrait (.2425em: cap top below the line top at this line height) */
  .hero__title { grid-column: 1 / 10; grid-row: 1; position: relative; z-index: 2; font-size: 5.95cqw; line-height: 1.167;
    margin-top: calc(var(--pt) + var(--phh) * .352 - .2425em); }
  .hero__ph { grid-column: 8 / 12; grid-row: 1; margin: var(--pt) 0 0 var(--half); }
  /* her template sets the description tight (leading ~1.25, slightly closed up), so at every width it keeps
     four lines and only the third sits on the photo, with "and for the planet." ending just left of it */
  .hero__desc { grid-column: 8 / 13; grid-row: 1; position: relative; z-index: 2; max-width: none;
    width: 18.6em; line-height: 1.25; letter-spacing: -.02em; margin-left: calc(var(--half) - 8.6em); }
}

/* Selected work: her four strongest projects as a 4-up thumbnail row under the headline (01B), two by two on phones */
.thumbs { margin-top: var(--sp); row-gap: 32px; }
.thumbs > li { grid-column: span 2; }
@media (min-width: 1024px) {
  .thumbs > li { grid-column: span 3; }
  /* her template: the [01]-[04] labels and the top of the thumbnails show at the bottom of the first screen */
  .hero + .thumbs { margin-top: calc(var(--sp) * .55); }
}
/* her template (1366 x 768) leaves 101px between the portrait and the labels. On a screen of that shape (1.78:1)
   or taller the row keeps that gap (7.4vw); on a wider, shorter screen it stays close, so the labels and the top
   of the thumbnails still show on the first screen */
@media (min-width: 1024px) and (max-aspect-ratio: 89/50) {
  .hero + .thumbs { margin-top: 7.4vw; }
}

.index { margin-top: var(--sec); }

/* ---------- 6. Work index ---------- */
.whead { padding-top: clamp(28px, 3.4vw, 56px); row-gap: 24px; align-items: end; }
.whead__title { font-size: var(--fs-hero); }
.whead__title .ln { display: block; }
.whead__title .ln--grey { color: var(--grey); }
.whead__note { max-width: 40ch; }
@media (min-width: 1024px) {
  .whead__title { grid-column: 1 / 10; }
  .whead__note { grid-column: 10 / 13; padding-bottom: .5em; }
}
.filters { margin-top: var(--sp); }
.filters__bar { display: flex; flex-wrap: wrap; gap: 6px clamp(18px, 2.4vw, 40px); padding: 16px 0 14px; }
.filter { font-family: var(--f-display); font-weight: 500; font-size: var(--fs-tab); line-height: 1.2; color: var(--grey); padding: 4px 0; transition: color .3s; }
.filter:hover, .filter[aria-pressed="true"] { color: var(--ink); }
.filter[aria-pressed="true"] { text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 7px; }
.filters__status { color: var(--grey); font-weight: 300; }
.cat { margin-top: var(--sp); }
.cat__head { padding-top: 14px; margin-bottom: clamp(20px, 2.2vw, 32px); align-items: baseline; row-gap: 4px; }
.cat__title { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-title); line-height: .95; letter-spacing: -.025em; }
@media (min-width: 1024px) {
  .cat__title { grid-column: 1 / 10; }
}
.tiles { row-gap: 32px; }
.tiles > li { grid-column: span 2; }
@media (min-width: 1024px) {
  .tiles > li { grid-column: span 4; }   /* three to a row: a category of two leaves one deliberate gap */
  /* her note: the work pictures were too big. On desktop the cards are 4:3, like the home row and the openers
     (each tile's --op holds the x for the 4:5 phone crop and the y for this one) */
  #work .tiles .ph__frame { aspect-ratio: 4 / 3; }
}
/* a tight square render on white (Gardena's cover) is shown whole in the 4:5 frames instead of cut to one plate */
@media (max-width: 1023.98px) { .ph--fit .ph__in img { object-fit: contain; } }
@keyframes fadeUp { from { opacity: 0; transform: translate3d(0, 14px, 0); } to { opacity: 1; transform: none; } }
.motion .is-shown { animation: fadeUp .6s var(--ease) both; }

/* ---------- 7. Case study ---------- */
.opener { padding-top: clamp(28px, 3.4vw, 56px); row-gap: 32px; }
.opener__text { display: flex; flex-direction: column; gap: 22px; }
.crumb { color: var(--grey); font-weight: 300; }
.crumb a { color: var(--ink); font-weight: 400; }
.opener__title { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-case); line-height: .92; letter-spacing: -.03em; overflow-wrap: anywhere; }   /* a long word breaks instead of pushing the page sideways */
.opener__tag { font-family: var(--f-display); font-weight: 500; font-size: clamp(20px, 1.7vw, 30px); line-height: 1.2; color: var(--grey); max-width: 26ch; margin-top: -6px; }
.opener__sum { max-width: 52ch; }
@media (max-width: 1023.98px) {
  .opener .opener__text { display: contents; }
  .opener.grid { row-gap: 22px; }
  .opener__text > * { grid-column: 1 / -1; min-width: 0; }
  .crumb { order: -4; }
  .opener__title { order: -3; }
  .opener__tag { order: -2; margin-top: -14px; }
  .opener__media { order: -1; }
}
@media (min-width: 1024px) {
  .opener__text { grid-column: 1 / 6; }
  .opener__text .details { margin-top: auto; }
  .opener__media { grid-column: 7 / 13; }
}

.chapter { margin-top: var(--sec); }
.chapter__grid { padding-top: 18px; row-gap: 18px; }
.chapter__label { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-h2); line-height: 1.04; letter-spacing: -.015em; }
.chapter__text { display: flex; flex-direction: column; gap: 1em; max-width: 60ch; }
.chapter__text .lead { font-family: var(--f-display); font-weight: 500; font-size: clamp(20px, 1.7vw, 30px); line-height: 1.25; }
@media (min-width: 1024px) {
  .chapter__label { grid-column: 1 / 4; }
  .chapter__text { grid-column: 5 / 10; }
}
.block { margin-top: var(--sp); }
.fig-full { margin-top: var(--sp); }

/* Image pairs: 6/6, mirrored 4/8 and 8/4 (wix pattern); a row of three is 4/4/4 (p-4a, p-4m, p-4b) */
.pair { margin-top: var(--sp); display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); column-gap: var(--gutter); row-gap: 32px; }
.pair > * { grid-column: 1 / -1; min-width: 0; }
@media (min-width: 1024px) {
  .pair { grid-template-rows: auto auto; row-gap: 0; }
  .pair > * { grid-row: span 2; display: grid; grid-template-rows: subgrid; }
  .pair .p-6a { grid-column: 1 / 7; } .pair .p-6b { grid-column: 7 / 13; }
  .pair .p-4a { grid-column: 1 / 5; } .pair .p-8b { grid-column: 5 / 13; }
  .pair .p-8a { grid-column: 1 / 9; } .pair .p-4b { grid-column: 9 / 13; }
  .pair .p-4m { grid-column: 5 / 9; }
  .pair .ph--fill .ph__frame { aspect-ratio: auto; height: 100%; }
  .pair .pair__note { align-self: end; grid-row: 1; padding-bottom: 4px; }
}
.pair__note { display: flex; flex-direction: column; gap: 10px; max-width: 34ch; }
.pair__note strong { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-h2); line-height: 1.04; letter-spacing: -.015em; text-wrap: balance; }

/* Key numbers */
.stats { margin-top: var(--sp); row-gap: 28px; }
.stat { border-top: 1px solid var(--line); padding-top: 14px; }
.stat__n { font-family: var(--f-display); font-weight: 800; font-size: var(--fs-stat); line-height: .92; letter-spacing: -.035em; white-space: nowrap; }
.stat__c { margin-top: 12px; color: var(--grey); font-weight: 300; max-width: 30ch; }
.stats--words .stat__n { font-size: var(--fs-h2); line-height: 1.04; letter-spacing: -.015em; white-space: normal; }
.stats--small .stat__n { font-size: var(--fs-title); }
@media (min-width: 1024px) {
  .stats > .stat { grid-column: span 4; }
  .stats--2 > .stat { grid-column: span 6; }
}

/* Spec tables inside chapters (flow steps, floors, problems, colours) */
.spec { margin-top: var(--sp); }
.spec__row { border-top: 1px solid var(--line); padding: 12px 0 13px; row-gap: 2px; align-items: baseline; }
.spec__row:last-child { border-bottom: 1px solid var(--line); }
.spec__k { font-weight: 500; }
.spec__v { color: var(--grey); font-weight: 300; }
.spec__n { color: var(--grey); font-variant-numeric: tabular-nums; }
.spec--steps .spec__n { grid-column: 1 / 2 !important; }
.spec--steps .spec__k { grid-column: 2 / -1 !important; }
.spec--steps .spec__v { grid-column: 2 / -1 !important; }
@media (min-width: 1024px) {
  .spec--steps .spec__k { grid-column: 2 / 5 !important; }
  .spec--steps .spec__v { grid-column: 5 / 13 !important; }
  .spec--kv .spec__k { grid-column: 1 / 5; }
  .spec--kv .spec__v { grid-column: 5 / 13; }
  .spec--narrow { margin-left: 0; }
  .spec--narrow .spec__k { grid-column: 5 / 8; }
  .spec--narrow .spec__v { grid-column: 8 / 13; }
}

/* Gardena colour logic table */
.colours { display: flex; flex-direction: column; gap: 14px; }
.colours__row { display: grid; grid-template-columns: 44px minmax(0, 1fr); column-gap: 14px; border-top: 1px solid var(--line); padding: 12px 0 13px; align-items: start; }
.colours__row:last-child { border-bottom: 1px solid var(--line); }
.chip { width: 44px; height: 22px; border-radius: 11px; background: var(--c); margin-top: 2px; }
.colours__row b { font-weight: 500; display: block; }
.colours__row span { color: var(--grey); font-weight: 300; }

/* her note: a list of outcomes (or brands, or what paramedics need) reads as one run of phrases with commas,
   in the same display type */
.chips { font-family: var(--f-display); font-weight: 800; font-size: clamp(22px, 2.1vw, 38px); line-height: 1.15; letter-spacing: -.01em; text-wrap: pretty; }
/* from tablet up an item never breaks inside: lines break only after a comma (on phones the items are longer
   than a line, so they wrap); a run on its own (Paramedic) stops at column 9, so a line stays readable */
.chips--block { grid-column: 1 / -1; }
@media (min-width: 768px) { .chips .it { white-space: nowrap; } }
@media (min-width: 1024px) { .chips--block { grid-column: 1 / 10; } }
/* Kärcher and HubKo: the outcomes run beside a picture (the laptop desk, the HubKo poster). HubKo's run stops at
   25em (its longest item is 24.3em), so it sets one or two items to a line at every width */
.pair__note--run { max-width: none; gap: 12px; }
.case .pair__note--run.p-8a .chips { max-width: 25em; }

/* A picture in a chapter's empty left column, under its label (Over a cup of tea: the Ribnica photo).
   Below 1024px it follows the text. */
.chapter__fig { margin-top: 4px; }
@media (min-width: 1024px) {
  .chapter__grid--fig > .chapter__text { grid-row: 1 / span 2; }
  .chapter__fig { grid-column: 1 / 5; grid-row: 2; align-self: start; margin-top: clamp(8px, 1.2vw, 20px); }
}

/* HubKo app: six phones, her iPhone frame baked into each picture; one row on desktop, three or two to a row
   below. The screens are 1x exports, so a phone is never shown wider than 300px. */
.phones { margin-top: var(--sp); display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gutter); row-gap: 28px; }
.phones .ph { width: 100%; max-width: 300px; margin-inline: auto; }
@media (min-width: 768px) { .phones { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .phones { grid-template-columns: repeat(6, minmax(0, 1fr)); } }

/* Next project */
.next { display: block; margin-top: var(--sec); }
.next__grid { padding-top: 18px; row-gap: 20px; align-items: end; }
.next__label { display: block; color: var(--grey); font-weight: 300; margin-bottom: 10px; }
.next__name { display: block; font-family: var(--f-display); font-weight: 800; font-size: var(--fs-title); line-height: .95; letter-spacing: -.025em; overflow-wrap: anywhere; }
.next__meta { display: block; margin-top: 12px; color: var(--grey); font-weight: 300; }
.nw { white-space: nowrap; }
.next:not(.vt-rest):hover .ph__in img { transform: scale(calc(var(--sc, 1) * 1.04)); }
.next:not(.vt-rest):hover .next__name .arrow { transform: translateX(10px); }
@media (min-width: 1024px) {
  .next__text { grid-column: 1 / 9; }
  .next__ph { grid-column: 9 / 13; }
}

/* ---------- 8. About ---------- */
.about__top { padding-top: clamp(28px, 3.4vw, 56px); row-gap: 28px; }
.about__h1 { font-weight: 300; color: var(--grey); font-size: var(--fs-body); }
.about__p1 { max-width: 46ch; }
.about__more { display: flex; flex-direction: column; gap: 1em; color: var(--grey); font-weight: 300; max-width: 78ch; }
.about__more a { color: var(--ink); font-weight: 400; }
.about__portrait { position: relative; width: min(74%, 420px); margin-top: 12px; }
/* Her hand-drawn signature (the raster from her Ana_podpis.png, as in 01B) over the portrait's top-right corner,
   running past it to the right. Shown in full by default; with motion it is revealed left to right when About
   opens (JS adds .sig-draw), and it drifts faster than the portrait (data-speed). */
.sig { position: absolute; z-index: 2; left: 55%; top: -9%; width: 72%; height: auto; max-width: none; pointer-events: none; transition: opacity .45s linear; }
@keyframes sigWipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
.motion .sig-draw .sig { animation: sigWipe .9s cubic-bezier(.45, .05, .35, 1) both; animation-delay: var(--sd, .15s); }
@media (min-width: 1024px) {
  .about__top { grid-template-rows: auto auto 1fr; }
  .about__h1 { grid-column: 1 / 4; grid-row: 1; }
  .about__p1 { grid-column: 1 / 5; grid-row: 2; padding-top: clamp(40px, 6vw, 110px); }
  .about__more { grid-column: 1 / 7; grid-row: 3; padding-top: clamp(40px, 5vw, 96px); }
  .about__portrait { grid-column: 9 / 12; grid-row: 2 / 4; width: auto; margin-top: clamp(72px, 9vw, 160px); align-self: start; }
  .sig { width: 80%; }
}
.cvsec { margin-top: clamp(56px, 6vw, 112px); }
.skills { margin-top: var(--sp); }
.skills__row { border-top: 1px solid var(--line); padding: 12px 0 13px; row-gap: 2px; }
.skills__row:last-child { border-bottom: 1px solid var(--line); }
.skills__k { font-family: var(--f-display); font-weight: 500; font-size: var(--fs-tab); line-height: 1.3; }
@media (min-width: 1024px) {
  .skills__k { grid-column: 1 / 4; }
  .skills__v { grid-column: 4 / 13; }
}

/* ---------- 9. Contact view: a short intro; the LET'S TALK block that follows is the page ---------- */
.ct { padding-top: clamp(28px, 3.4vw, 56px); row-gap: 6px; align-items: baseline; }
.ct__h1 { font-weight: 300; color: var(--grey); font-size: var(--fs-body); }
.ct__s { max-width: 60ch; }
@media (min-width: 1024px) {
  .ct__h1 { grid-column: 1 / 4; }
  .ct__s { grid-column: 4 / 13; }
}

/* ---------- 10. Footer: LET'S TALK and the contact list, at the end of every view ---------- */
.ftr { margin-top: var(--sec); padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }
.ftr.is-contact { margin-top: clamp(14px, 1.6vw, 28px); }
.ftr.is-contact .ftr__talk > .rule, .ftr.is-contact .talkhead { display: none; }
.talkhead { padding-top: 18px; row-gap: 6px; align-items: baseline; }
@media (min-width: 1024px) {
  .talkhead > .label { grid-column: 1 / 4; }
  .talkhead__s { grid-column: 4 / 13; }
}
/* LET'S TALK runs edge to edge: the font size is the box width divided by the ink width of the words in em
   (--em, measured in Gabarito 800), and the L's side bearing hangs outside, so the ink meets both edges.
   While a fallback font is showing (slow or blocked font load), JS measures the words and sets --em to fit. */
.talk-box { container-type: inline-size; overflow: hidden; margin-top: clamp(16px, 2.4vw, 44px); }
.talk { --em: 4.93; }   /* the ink width of “Let’s talk”; footer.php prints it inline only for those words, other words are measured by main.js (data-em-auto) */
.talk { display: block; margin-left: -.051em; font-family: var(--f-display); font-weight: 800; text-transform: uppercase; letter-spacing: -.01em; line-height: .84; white-space: nowrap; padding-top: .03em;
  font-size: calc((100vw - 2 * var(--edge)) / 5.2); font-size: calc(100cqw / var(--em)); }
.talk .ch { display: inline-block; }
.cstack { margin-top: clamp(24px, 3vw, 56px); border-bottom: 1px solid var(--line); }
.cstack > li { border-top: 1px solid var(--line); }
.crow { width: 100%; align-items: baseline; padding: clamp(10px, 1vw, 16px) 0 clamp(8px, .8vw, 12px); }
.cword { grid-column: 1 / -1; grid-row: 1; justify-self: start; font-family: var(--f-display); font-weight: 800; font-size: var(--fs-cword); line-height: .86; letter-spacing: -.015em; text-transform: uppercase; transition: transform .7s var(--ease), color .4s; }
.cval { grid-column: 1 / -1; grid-row: 2; padding-top: 8px; color: var(--grey); font-weight: 300; overflow-wrap: anywhere; transition: color .4s; }
.ftr.is-contact .cword { font-size: var(--fs-xl); }
.crow.is-copied .cword, .crow.is-copied .cval { color: var(--ink); }
@media (min-width: 1024px) {
  .cval { grid-column: 1 / 4; grid-row: 1; padding-top: 0; }
  .cword { grid-column: 4 / 13; }
  .crow:hover .cword, .crow:focus-visible .cword { transform: translateX(var(--pitch)); }
  .crow:hover .cval { color: var(--ink); }
  .cstack:hover > li:not(:hover) .cword { color: var(--grey); }
  /* her note: on desktop LET'S TALK and the contact words are half their former size (phones keep theirs) */
  .talk { font-size: calc((100vw - 2 * var(--edge)) / 10.4); font-size: calc((50cqw - var(--gutter) / 2) / var(--em)); }   /* the K ends on the column-6 line */
  .cword { font-size: calc(var(--fs-cword) / 2); }
  .ftr.is-contact .cword { font-size: calc(var(--fs-xl) / 2); }
}
.ftr__base { margin-top: var(--sp); padding-top: 14px; border-top: 1px solid var(--line); row-gap: 10px; align-items: center; }
/* Contact on desktop: the half-size block is shorter than the screen, so the page fills it and the base line
   sits at the foot of the screen instead of mid-page */
@media (min-width: 1024px) {
  body.at-contact #page { display: flex; flex-direction: column; min-height: 100svh; }
  .ftr.is-contact { flex: 1 0 auto; display: flex; flex-direction: column; }
  .ftr.is-contact > .wrap:last-child { margin-top: auto; }
}
.ftr__base > * { grid-column: span 2; }
.ftr__copy { color: var(--ink); text-wrap: balance; }   /* on phones it breaks after "Ana Topole," */
.totop { justify-self: end; color: var(--ink); font-weight: 400; }
@media (min-width: 1024px) {
  .ftr__base > * { grid-column: span 6; }
}

/* ---------- 11. Page transition: the card swap (01B) as a cross-document View Transition ----------
   The page being left rides out on a card over an ink stage: it scales to 86% and dims, then leaves left with a
   slight tilt while the new page slides in from the right (0.92 s, the prototype's swap() timeline). The header
   is captured on its own and stays still. Back plays it mirrored (head.js adds the "back" type). Browsers
   without cross-document View Transitions (Firefox) and reduced motion load pages normally.
   Both header images stay, unanimated and unblended, the new one on top. Safari does not draw the live new image
   of a named element while the root is animating, so there the outgoing snapshot shows instead: main.js sets the
   old page's tabs to the new page's in pageswap, just before the snapshot, so the two copies look the same. */
@media (prefers-reduced-motion: no-preference) { @view-transition { navigation: auto; } }
.hdr { view-transition-name: at-hdr; }                 /* the header stays put, as in the prototype */
::view-transition-group(at-hdr) { animation: none; }
::view-transition-old(at-hdr), ::view-transition-new(at-hdr) { animation: none; mix-blend-mode: normal; }
.vt-snap, .vt-snap * { transition: none !important; }  /* the tabs, and the cards under the pointer, change at once for that snapshot */
/* .vt-rest (main.js pageswap): after a mouse click the page being left shows its cards unhovered, as the design's
   card does once its page has moved away from under the pointer (the card hover rules exclude it) */
::view-transition-group(root) { background: #0d0d0d; } /* the ink stage */
::view-transition-old(root), ::view-transition-new(root) { mix-blend-mode: normal; transform-origin: 50% 50%; }
::view-transition-old(root) {
  animation: at-vt-scale .42s cubic-bezier(.76, 0, .24, 1) both,   /* power3.inOut: card to 86% */
             at-vt-dim .42s cubic-bezier(.33, 1, .68, 1) both,     /* power2.out: the ink shade at .5 */
             at-vt-out .58s cubic-bezier(.6, 0, .86, .24) .3s both; /* swapOut: leaves left, tilts */
  /* power3.inOut is GSAP's quartic in-out, which no cubic-bezier matches (up to 0.003 scale off): the exact curve
     sampled at 41 points. Browsers without linear() keep the line above. */
  animation-timing-function: linear(0, 0, .0001, .0003, .0008, .002, .004, .0075, .0128, .0205, .0312, .0458, .0648, .0893, .12, .1582, .2048, .261, .3281, .4073, .5, .5927, .672, .739, .7952, .8418, .88, .9107, .9352, .9542, .9688, .9795, .9872, .9925, .996, .998, .9992, .9997, 1, 1, 1), cubic-bezier(.33, 1, .68, 1), cubic-bezier(.6, 0, .86, .24);
}
::view-transition-new(root) { animation: at-vt-in .62s cubic-bezier(.22, 1, .32, 1) .3s both; } /* swapIn */
/* the new page's opener picture is still on its way (head.js): both pages wait in their 0.3 s delay, the old card
   scaled down and shaded, until it can be drawn */
html.vt-wait::view-transition-old(root) { animation-play-state: running, running, paused; }
html.vt-wait::view-transition-new(root) { animation-play-state: paused; }
@keyframes at-vt-scale { to { scale: .86; } }
@keyframes at-vt-dim { to { filter: brightness(.525); } }  /* = a .5 ink shade over the paper tone */
@keyframes at-vt-out { to { translate: -112% 0; rotate: -4deg; } }
@keyframes at-vt-in { from { translate: 108vw 0; rotate: 4deg; } to { translate: 0 0; rotate: 0deg; } }
@keyframes at-vt-out-back { to { translate: 112% 0; rotate: 4deg; } }
@keyframes at-vt-in-back { from { translate: -108vw 0; rotate: -4deg; } to { translate: 0 0; rotate: 0deg; } }
html:active-view-transition-type(back)::view-transition-old(root) { animation-name: at-vt-scale, at-vt-dim, at-vt-out-back; }
html:active-view-transition-type(back)::view-transition-new(root) { animation-name: at-vt-in-back; }

/* WordPress toolbar (signed-in visitors only): the fixed header sits below it, and it stays still in the swap */
.admin-bar .hdr { top: calc(46px + env(safe-area-inset-top, 0px)); }
@media (max-width: 600px) { .admin-bar #wpadminbar { position: fixed; } }   /* core lets it scroll away here, over the header */
@media (min-width: 783px) { .admin-bar .hdr { top: calc(32px + env(safe-area-inset-top, 0px)); } }
#wpadminbar { view-transition-name: at-wpbar; }
::view-transition-group(at-wpbar) { animation: none; }
::view-transition-old(at-wpbar), ::view-transition-new(at-wpbar) { animation: none; mix-blend-mode: normal; }   /* as the header (Safari) */

/* ---------- 12. Case-study video (Kärcher, Lumo): a muted loop in a fixed frame with a Pause/Play pill.
   Without JS the native controls show; with JS the pill replaces them (section 11 of the script). ---------- */
.vid__frame { position: relative; overflow: hidden; isolation: isolate; aspect-ratio: var(--fr, 16 / 9); background: var(--bg); }   /* no grey plate (her note) */
.vid__frame video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* a portrait phone recording on white: multiplied, so the white takes the page tone. The element is sized to
   the recording and its sides and foot are feathered, so the phone's shadow, cut by the frame, does not draw a box. */
.vid--contain video {
  width: auto; max-width: 100%; margin: 0 auto; aspect-ratio: 996 / 1920;
  object-fit: contain; mix-blend-mode: multiply;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent), linear-gradient(180deg, #000 95.5%, transparent);
  -webkit-mask-composite: source-in;
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent), linear-gradient(180deg, #000 95.5%, transparent);
  mask-composite: intersect;
}
.vid__btn {
  position: absolute; left: 12px; bottom: 12px;
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px;
  background: var(--bg); color: var(--ink); border: 1px solid var(--line);
  font-weight: 500; line-height: 1; transition: background-color .3s, color .3s;
}
.vid__btn:hover { background: var(--ink); color: var(--bg); }

/* Project subtitle: the second, grey line of every case title ("KÄRCHER / PRODUCT FINDER").
   It sits inside the h1, so it stays with the title when the opener is re-ordered on small screens. */
.opener__sub { display: block; margin-top: .3em; font-size: var(--fs-h2); line-height: 1.04; color: var(--grey); }
/* long subtitles break into even lines. Names need no balancing: "of&nbsp;tea" and the no-break space before
   the next-project arrow already make them break as "OVER A CUP / OF TEA", never leaving one word or the arrow alone */
.opener__sub { text-wrap: balance; }

/* ---------- 13. Headings in capitals (her note: every heading and title uppercase). The source text stays
   in normal case, so screen readers read words. Capitals get more open tracking than the lowercase
   settings above: display headings -.01em, small labels .05em. Body text stays in sentence case. ---------- */
.display, .opener__title, .opener__sub, .cat__title, .next__name, .chapter__label, .h2,
.pair__note strong, .stats--words .stat__n, .prow-name, .cword, .mnav__link, .chips {
  text-transform: uppercase; letter-spacing: -.01em;
}
.details__label, .label, .about__h1, .ct__h1, .next__label, .tile__name, .skills__k, .filter, .nav__link, .menu-btn,
.details dt, .spec__k, .colours__row b {
  text-transform: uppercase; letter-spacing: .05em;
}
/* key numbers keep their tight numeral tracking; words in them ("60 MILLION") go uppercase, units (kg, m) do not */
.stat__n { text-transform: uppercase; }
.stat__n .u { text-transform: none; }

/* ---------- 14. Motion ---------- */
/* First load (rawlab-style): only the background shows until the page is called in (.at-go: one soft fade and a
   10px rise), by the main script or, at 2.45 s, by the early script at the top. atFail is the last resort: it
   shows the page 3 s after it is first styled even if no script runs a timer. (It needs its own name: an
   animation that keeps its name is updated, not restarted, so atFade would jump to its end.) */
@keyframes atFail { from { opacity: 0; } to { opacity: 1; } }
@keyframes atFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes atRise { from { transform: translate3d(0, 10px, 0); } to { transform: none; } }
.at-gate .hdr, .at-gate #page { animation: atFail .3s linear 2.7s both; }
.at-gate.at-go .hdr, .at-gate.at-go #page { animation: atFade .6s cubic-bezier(.33, 0, .25, 1) both, atRise .7s var(--ease) both; }
/* when the early timer had to call the page in before the main script ran (a slow connection), pictures that
   arrive later fade in once loaded instead of painting in strips (the top script marks loaded ones .is-ld) */
.at-late .ph__in img:not(.is-ld), .at-late .prow-thumb img:not(.is-ld), .at-late .sig:not(.is-ld) { opacity: 0; transition: none; }   /* hidden at once; only the fade-in is timed */
@keyframes rise { from { opacity: 0; transform: translate3d(0, 24px, 0); } to { opacity: 1; transform: none; } }
.motion .view.is-entering .rise { animation: rise .65s var(--ease) both; animation-delay: calc(var(--i, 0) * 60ms); }
.motion .rule { transform: scaleX(.6); transition: transform .8s var(--ease); }
.motion .rule.in { transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  .rule { transform: none !important; }
  .ph__in, .idx, .sig, .talk .ch { transform: none !important; }
  .hdr.is-hidden { transform: none; }
  .prow.is-hot .prow-name, .crow:hover .cword, .crow:focus-visible .cword { transform: none; }
}
