/* Inter, hosted on this site (OFL licence): used only where SF Pro isn't installed */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("assets/fonts/inter-latin-400.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("assets/fonts/inter-latin-500.woff2") format("woff2"); }
@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("assets/fonts/inter-latin-600.woff2") format("woff2"); }

/* ---------- Tokens: one design system for the whole site (modelled on calebwu.ca) ---------- */
/* Light is the default. The footer toggle sets data-theme="dark" on <html> and remembers it. */
:root {
  --bg: #ffffff;
  --ink: #171717;       /* headings, titles, links */
  --text: #635d58;      /* paragraphs */
  --muted: #635d58;
  --faint: #77716c;     /* labels, dates, captions */
  --surface: #f8f8f8;   /* boxes behind images, cards, hovered rows */
  --surface-2: #f8f8f8;
  --media: #f8f8f8;
  --raised: #efefee;
  --line: rgb(23 23 23 / .22);
  --hairline: rgb(0 0 0 / .08);
  --brand: #f54900;     /* focus ring */
  --glyph-shadow: drop-shadow(0 2px 3px rgb(0 0 0 / .22));
  --radius: 12px;
  --radius-lg: 12px;
  --text-max: 575px;
  /* SF Pro Display where available (Apple devices, or installed locally), Inter as fallback */
  --font: "SF Pro Display", "SF Pro", -apple-system, BlinkMacSystemFont, "Inter", system-ui, "Segoe UI", sans-serif;
  --ease: cubic-bezier(.22, 1, .36, 1);
  --dur: .15s;
  /* Role colors, swapped by JS when the personality or theme changes */
  --role-1: #2f6fd6;
  --role-2: #4d8be8;
  --role-3: #3a7ce0;
  color-scheme: light;
}
:root[data-theme="dark"] {
  --bg: #111111;
  --ink: #ededed;
  --text: #a8a29e;
  --muted: #a8a29e;
  --faint: #8c8682;
  --surface: #1c1c1c;
  --surface-2: #1c1c1c;
  --media: #1c1c1c;
  --raised: #262626;
  --line: rgb(237 237 237 / .25);
  --hairline: rgb(255 255 255 / .1);
  --brand: #ff6a2b;
  --glyph-shadow: drop-shadow(0 2px 3px rgb(0 0 0 / .55));
  color-scheme: dark;
}

@view-transition { navigation: auto; }

/* Page transitions (like calebwu.ca): the old page fades out, then the new one fades in.
   Nothing morphs, so the home rows carry no view-transition names. */
::view-transition-old(*), ::view-transition-new(*) { mix-blend-mode: normal; }
::view-transition-old(*) { animation: vt-out .22s cubic-bezier(.4, 0, 1, 1) both; }
::view-transition-new(*) { animation: vt-in .3s cubic-bezier(0, 0, .2, 1) .12s both; }
::view-transition-group(*) { animation: none; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }
/* Theme switch: the new theme grows as a circle from the switch, over the old one.
   No cross-fade, so colors never blend into grey. The circle itself is animated in JS. */
html.theme-switching [style*="view-transition-name"] { view-transition-name: none !important; }
html.theme-switching::view-transition-old(root),
html.theme-switching::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
html.theme-switching::view-transition-new(root) { z-index: 1; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ---------- Base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1.1rem;
  line-height: 1.545;
  letter-spacing: .014em;
  -webkit-font-smoothing: antialiased;
}
::selection { background: var(--raised); }
a { color: inherit; }
img { display: block; max-width: 100%; }
ul { list-style: none; margin: 0; padding: 0; }
button { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 4px; }

/* ---------- Home layout ---------- */
.page {
  max-width: calc(var(--text-max) + 48px);
  margin: 0 auto;
  padding: 120px 24px 32px; /* the page ends just below the footer */
}
.block { margin-top: 80px; }
.block__title {
  margin: 0 0 16px;
  font-size: 1.3rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0;
}
.prose p { margin: 0 0 16px; color: var(--text); }
.prose p:last-child { margin-bottom: 0; }

/* ---------- Intro + role switch ---------- */
.avatar {
  width: 64px; height: 64px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 24px;
}
.avatar.is-bump { animation: bump .5s var(--ease); }
@keyframes bump { 30% { transform: scale(.9) rotate(-6deg); } 65% { transform: scale(1.06) rotate(3deg); } }

.intro__name { margin: 0; font-size: 1.3rem; font-weight: 500; line-height: 1.4; letter-spacing: 0; }
.intro__role { margin: 4px 0 0; color: var(--text); }
.intro__bio { margin-top: 24px; }
/* Balanced line breaks (no lonely last word) and clickable words never split across lines */
.intro__bio p { text-wrap: pretty; }
.intro__bio .hover-link { white-space: nowrap; }
.entry__desc { grid-column: 1 / -1; margin: 8px 0 0; color: var(--text); }
.role-wrap { position: relative; display: inline-block; }
.role-btn {
  position: relative;
  display: inline-flex;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  touch-action: manipulation;
}
.role-btn::before { content: ""; position: absolute; inset: -11px -6px; } /* bigger hit area */
.role-word {
  position: relative;
  z-index: 1;
  display: inline-block;
  white-space: nowrap;
  font-weight: 600;
  background-image: linear-gradient(105deg, var(--role-1) 0%, var(--role-2) 38%, var(--role-3) 72%, var(--role-2) 100%);
  background-size: 240% 100%;
  background-position: 8% 50%;
  background-repeat: no-repeat;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  transition: background-position .18s var(--ease), scale .15s cubic-bezier(.32, .72, 0, 1);
}
.role-btn:hover .role-word, .role-btn:focus-visible .role-word { background-position: 78% 50%; }
.role-btn:active .role-word { scale: .98; }
.role-word.is-out { animation: role-out .16s cubic-bezier(.23, 1, .32, 1) forwards; }
.role-word.is-in { animation: role-in .22s cubic-bezier(.32, .72, 0, 1) both; }
@keyframes role-out { to { opacity: 0; transform: translateY(-4px); } }
@keyframes role-in { from { opacity: 0; transform: translateY(4px); } }

/* Emoji that fan out around the role on hover */
.role-glyphs { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.role-glyph {
  position: absolute;
  width: 22px; height: 22px;
  margin: -11px 0 0 -11px;
  display: grid; place-items: center;
  font-size: 19px;
  line-height: 1;
  filter: var(--glyph-shadow);
  opacity: 0;
  transform: translate(0, 0) scale(.86);
  transition: transform .26s cubic-bezier(.32, .72, 0, 1) var(--delay, 0s), opacity .26s cubic-bezier(.32, .72, 0, 1) var(--delay, 0s);
}
.role-btn:hover .role-glyph, .role-btn:focus-visible .role-glyph, .role-btn.is-open .role-glyph {
  opacity: 1;
  transform: translate3d(var(--dx), var(--dy), 0) rotate(var(--rot)) scale(1);
}
.role-glyph.is-exit {
  opacity: 0 !important;
  transform: translate3d(var(--dx), calc(var(--dy) - 8px), 0) rotate(var(--rot)) scale(.96) !important;
  transition-duration: .16s;
  transition-delay: 0s;
}
.role-glyph.is-pre { opacity: 0 !important; transform: translate(0, 0) scale(.86) !important; transition: none; }
.role-glyph__bob { display: block; animation: bob 3.2s ease-in-out infinite; }
.role-glyph:nth-child(2) .role-glyph__bob { animation-duration: 3.55s; animation-delay: -.7s; }
.role-glyph:nth-child(3) .role-glyph__bob { animation-duration: 2.85s; animation-delay: -1.2s; }
@keyframes bob { 50% { transform: translateY(-3px); } }

/* Click burst: emoji pop out and fall */
.burst {
  position: fixed;
  z-index: 60;
  pointer-events: none;
  font-size: 20px;
  line-height: 1;
  filter: var(--glyph-shadow);
  will-change: transform, opacity;
}

/* Hint bubble on the right of the role, until the first click */
.role-hint {
  position: absolute;
  top: 50%;
  left: calc(100% + 12px);
  z-index: 2;
  margin: 0;
  padding: .35rem .55rem;
  border-radius: 8px;
  background: var(--ink);
  color: var(--bg);
  font-size: 14px;
  line-height: 1.35;
  letter-spacing: 0;
  white-space: nowrap;
  pointer-events: none;
  transform-origin: 0 50%;
  opacity: 0;
  transform: translateY(-50%) scale(.96);
  transition: opacity .16s cubic-bezier(.23, 1, .32, 1), transform .22s cubic-bezier(.32, .72, 0, 1);
}
.role-hint::before {
  content: "";
  position: absolute;
  top: 50%; left: -4px;
  width: 8px; height: 8px;
  border-radius: 1px;
  background: var(--ink);
  transform: translateY(-50%) rotate(45deg);
}
.role-wrap:hover .role-hint, .role-btn:focus-visible + .role-hint { opacity: 1; transform: translateY(-50%) scale(1); }
.role-wrap.hint-done .role-hint { display: none; }
@media (max-width: 640px) { .role-hint { display: none; } }


/* Text not typed yet: keeps its space so lines never reflow while typing */
.type-rest { visibility: hidden; }
/* Typing trail: the last characters glow in the role colors */
.type-trail {
  background: linear-gradient(90deg, var(--role-1), var(--role-2), var(--role-3));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---------- Case study list ---------- */
.block__head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
.block__head .block__title { margin: 0; }
/* Sort: one word that flips between Relevance and Date */
.sort-word {
  position: relative;
  padding: 0;
  border: 0;
  background: none;
  color: var(--faint);
  font-size: 16px;
  letter-spacing: .018em;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 3px;
  transition: color var(--dur) var(--ease), text-decoration-color var(--dur) var(--ease);
}
.sort-word:hover { color: var(--ink); text-decoration-color: var(--ink); }
/* No underline; the up/down arrows show it can be clicked */
.sort-word { display: inline-flex; align-items: center; gap: 5px; text-decoration: none; }
.sort-word__icon { width: 13px; height: 13px; flex: none; }
.sort-word__label { display: inline-block; }
.sort-word__label.is-out { animation: role-out .16s cubic-bezier(.23, 1, .32, 1) forwards; }
.sort-word__label.is-in { animation: role-in .22s cubic-bezier(.32, .72, 0, 1) both; }

.work-wrap { position: relative; }
/* A little air between projects: 12px inside each row and a 4px gap, so a highlighted row never touches its neighbours */
.work-list { display: grid; gap: 4px; margin: 0 -12px; }
.work-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px;
  align-items: baseline;
  padding: 12px;
  border-radius: var(--radius);
  text-decoration: none;
  transition: background-color var(--dur) var(--ease);
}
/* Two clear states:
   - projects you can open: dark title, darker details and a grey hover box;
   - NDA projects: greyed text, no hover box (only "Contact me" is a link). */
a.work-row:hover, a.work-row:focus-visible { background: var(--surface); }
.work-row__main { display: flex; flex-direction: column; min-width: 0; }
.work-row__title { font-weight: 500; }
a.work-row .work-row__title { text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 3px; transition: text-decoration-color var(--dur); }
a.work-row:not(.is-nda):hover .work-row__title { text-decoration-color: var(--line); }
.work-row__meta { color: var(--text); font-size: 16px; line-height: 1.5; letter-spacing: .018em; }
.work-row.is-nda .work-row__title { color: var(--faint); }
.work-row.is-nda .work-row__meta { color: var(--faint); }
/* Open-able projects: the result darkens a little on hover */
a.work-row:hover .work-row__result { color: var(--text); }
.work-row__result { color: var(--faint); font-size: 16px; letter-spacing: .018em; white-space: nowrap; }
.work-row.is-nda { cursor: default; }
/* Work in progress: greyed and not a link; its preview is a blurred picture with a tag */
.work-row.is-wip { cursor: default; }
.work-row.is-wip .work-row__title, .work-row.is-wip .work-row__meta { color: var(--faint); }
/* Only the "Contact me" text opens the email, not the whole row */
.work-row.is-nda .work-row__ask { position: relative; cursor: pointer; text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 3px; }
/* Click and tap target a little larger than the text, so it is easy to hit without spreading over the row */
.work-row.is-nda .work-row__ask::before { content: ""; position: absolute; inset: -6px -8px -6px -10px; }
.work-row.is-nda .work-row__ask:hover { text-decoration-color: var(--line); }
.work-row.is-nda .work-row__ask:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }
/* NDA rows open an access-request email. On hover the result swaps to the action. */
.work-row.is-nda .work-row__result { display: grid; justify-items: end; }
/* The old label leaves first (quick, upwards), the new one follows. */
.work-row__result-text, .work-row__ask { grid-area: 1 / 1; }
.work-row__result-text { color: var(--faint); opacity: 1; transition: opacity .26s cubic-bezier(.22, 1, .36, 1) .07s, transform .32s cubic-bezier(.22, 1, .36, 1) .07s, filter .26s ease .07s; }
.work-row__ask { color: var(--ink); opacity: 0; transform: translateY(6px); filter: blur(2px); transition: opacity .14s cubic-bezier(.4, 0, 1, 1), transform .14s cubic-bezier(.4, 0, 1, 1), filter .14s ease; }
.work-row__ask::after { content: " ↗"; }
.work-row.is-nda:hover .work-row__result-text, .work-row.is-nda:focus-within .work-row__result-text { opacity: 0; transform: translateY(-6px); filter: blur(2px); transition: opacity .14s cubic-bezier(.4, 0, 1, 1), transform .14s cubic-bezier(.4, 0, 1, 1), filter .14s ease; }
.work-row.is-nda:hover .work-row__ask, .work-row.is-nda:focus-within .work-row__ask { opacity: 1; transform: none; filter: none; transition: opacity .26s cubic-bezier(.22, 1, .36, 1) .07s, transform .32s cubic-bezier(.22, 1, .36, 1) .07s, filter .26s ease .07s; }
@media (prefers-reduced-motion: reduce) { .work-row__result-text, .work-row__ask { transform: none !important; filter: none !important; } }

/* ---------- Work / education entries ---------- */
.entry-list li + li { margin-top: 16px; }
.entry { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: baseline; }
.entry__main { display: flex; flex-direction: column; min-width: 0; }
.entry__title { font-weight: 500; }
.entry__meta { color: var(--faint); font-size: 16px; line-height: 1.5; letter-spacing: .018em; }
.entry__date { color: var(--faint); font-size: 16px; letter-spacing: .018em; white-space: nowrap; font-variant-numeric: tabular-nums; }
.entry__points {
  grid-column: 1 / -1;
  margin-top: 6px;
  padding-left: 18px;
  list-style: disc;
  color: var(--muted);
  font-size: 14px;
}
.entry__points li + li { margin-top: 4px; }
.entry__points li::marker { color: var(--faint); }

/* ---------- Work: Currently / Previously lines ---------- */
.timeline p { margin: 0; color: var(--faint); font-size: 1.15rem; line-height: 1.6; }
.timeline p + p { margin-top: 2px; }
.timeline .at-link { color: inherit; text-decoration-color: transparent; }
.timeline .at-link:hover, .timeline .at-link:focus-visible { color: var(--ink); text-decoration-color: var(--line); }
/* Small, quiet "Present" tag */
.present {
  display: inline-block;
  margin-left: 8px;
  padding: 0 6px;
  border-radius: 5px;
  background: rgb(59 139 245 / .09);
  color: #2463c4;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 18px;
  letter-spacing: .01em;
  vertical-align: 2px;
}
:root[data-theme="dark"] .present { background: rgb(125 179 255 / .12); color: #8dbbff; }
.timeline__desc { margin: 20px 0 0; color: var(--text); }

/* ---------- Now cards ---------- */
/* Plain text like the Work and Education entries: no box, since nothing here is clickable
   (a soft grey box on this site means you can interact with it) */
.now-list { display: grid; gap: 16px; margin: 0; padding: 0; list-style: none; }
.now-card { padding: 0; }
.now-card__text { display: flex; flex-direction: column; gap: 2px; }
.now-card__title { font-weight: 500; }
.now-card__sub { color: var(--text); font-size: 16px; line-height: 1.5; }

/* ---------- Links ---------- */
.link-list li + li { margin-top: 8px; }
.link-list a, .hover-link {
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.link-list a:hover, .hover-link:hover, .hover-link:focus-visible { text-decoration-color: var(--ink); }
/* Words that open a picture card: no underline (nothing else on the site is underlined);
   they stand out by colour and weight instead, like titles, and fade slightly on hover */
.hover-link { color: var(--ink); font-weight: 500; text-decoration: none; cursor: pointer; transition: opacity var(--dur) var(--ease); }
.hover-link:hover { opacity: .7; }
.hover-link:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 3px; }
/* Contact: logo + name in a row, grey, turning dark on hover (no underline) */
.social-list { display: flex; flex-wrap: wrap; gap: 12px 32px; margin: 0; padding: 0; list-style: none; }
.social-list li + li { margin-top: 0; }
.social-list a { display: inline-flex; align-items: center; gap: 10px; color: var(--faint); text-decoration: none; }
.social-list a:hover, .social-list a:focus-visible { color: var(--ink); }
.social-list svg { flex-shrink: 0; width: 17px; height: 17px; }

.footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 120px;
  padding-top: 0;
  color: var(--faint);
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: .018em;
}
/* One line: the credit on the left, the local time on the right */
.local-time { order: 2; margin: 0 0 0 auto; font-variant-numeric: tabular-nums; text-align: right; }
.footer__credit { order: 1; }
/* Phones: smaller text so the credit and the time still share one line */
@media (max-width: 560px) { .footer { flex-wrap: nowrap; gap: 12px; font-size: 13px; } .footer > * { white-space: nowrap; } }
/* Very narrow phones (under 360px): not enough room, so the time goes below again */
@media (max-width: 359px) { .footer { flex-wrap: wrap; } }

/* Dark mode: floating round button, bottom right (added by script.js on every page).
   Shows the sun in light mode and the moon in dark mode; they turn and swap on change. */
.theme-fab {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 40;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  color: var(--ink);
  background: color-mix(in oklab, var(--bg) 82%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  box-shadow: 0 0 0 .5px var(--hairline), 0 6px 20px -6px rgb(0 0 0 / .18);
  cursor: pointer;
  transition: transform .3s cubic-bezier(.32, .72, 0, 1), box-shadow .3s cubic-bezier(.32, .72, 0, 1);
}
.theme-fab:hover { transform: scale(1.06); box-shadow: 0 0 0 .5px var(--hairline), 0 10px 26px -8px rgb(0 0 0 / .24); }
.theme-fab:active { transform: scale(.94); }
.theme-fab:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.theme-fab svg { grid-area: 1 / 1; width: 20px; height: 20px; transition: opacity .35s cubic-bezier(.32, .72, 0, 1), transform .5s cubic-bezier(.32, .72, 0, 1); }
.theme-fab__moon { opacity: 0; transform: rotate(-60deg) scale(.6); }
:root[data-theme="dark"] .theme-fab__sun { opacity: 0; transform: rotate(60deg) scale(.6); }
:root[data-theme="dark"] .theme-fab__moon { opacity: 1; transform: none; }
:root[data-theme="dark"] .theme-fab { box-shadow: 0 0 0 .5px var(--hairline), 0 6px 20px -6px rgb(0 0 0 / .6); }
@media (max-width: 560px) { .theme-fab { right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); width: 42px; height: 42px; } }
/* At the end of the page the button moves up above the footer line */
.theme-fab { translate: 0 0; transition: transform .3s cubic-bezier(.32, .72, 0, 1), box-shadow .3s cubic-bezier(.32, .72, 0, 1), translate .45s cubic-bezier(.32, .72, 0, 1); }
.theme-fab.is-lifted { translate: 0 -44px; }

/* ---------- Project preview beside the list ---------- */
.case-stage {
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s cubic-bezier(.23, 1, .32, 1);
}
.case-stage.is-visible { opacity: 1; }
/* Switching between the arrow and card layouts: hide instantly, no transitions */
.case-stage.is-switching, .case-stage.is-switching * { transition: none !important; }
/* Pointer: a small grey arrow fixed in the viewport just left of the list
   (script.js sets its top and left). Rows scroll past it. */
.case-pointer {
  position: fixed;
  z-index: 2;
  width: 0;
  height: 0;
  border-top: 7px solid transparent;
  border-bottom: 7px solid transparent;
  border-left: 11px solid var(--line);
  transform: translateY(-50%);
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s cubic-bezier(.23, 1, .32, 1);
}
.case-pointer.is-visible { opacity: 1; }
/* Docked preview: fixed beside the arrow, showing the project the arrow points at */
.case-stage--dock {
  position: fixed;
  z-index: 2;
  width: min(340px, calc((100vw - var(--text-max) - 48px) / 2 - 88px));
  /* left, top and an exact width are set by script.js, in whole pixels */
}
.case-stage--dock .case-stage__frame { box-shadow: 0 0 0 .5px var(--hairline); }
/* Caption hangs below, so the picture itself is centred on the arrow */
.case-stage--dock .case-stage__caption { position: absolute; top: 100%; left: 0; right: 0; }
/* The row the arrow points at reads as selected */
.work-row.is-aligned { background: var(--surface); }
.case-stage__frame {
  position: relative;
  aspect-ratio: 3 / 2;
  border-radius: 12px;
  overflow: hidden;
  background: var(--surface-2);
}
/* Pictures stack inside the frame so the old and new one can overlap while they change.
   They overlap the frame by 1px on every side, so the frame's grey background can never
   peek through a sub-pixel gap along the rounded top edge. */
.case-stage__img { position: absolute; inset: -1px; display: block; width: calc(100% + 2px); max-width: none; height: calc(100% + 2px); object-fit: cover; }
.case-stage__frame { isolation: isolate; }
/* Under the docked picture: which project it is */
.case-stage__caption { display: flex; justify-content: space-between; gap: 12px; margin: 12px 2px 0; color: var(--faint); font-size: 14px; line-height: 1.4; letter-spacing: .018em; }
.case-stage__caption:empty { display: none; }
.case-stage__name { color: var(--ink); font-weight: 500; }
/* Narrower screens and touch screens: no arrow; the preview is a card that sits just
   above the current project and slides from row to row (moved by script.js) */
@media (max-width: 1199px), (hover: none), (pointer: coarse) {
  .case-pointer { display: none; }
  .case-stage--dock.is-popup {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 5;
    width: 100%; /* same width as the page content */
    transform: translate3d(0, var(--pop-y, 0px), 0);
    transition: opacity .25s cubic-bezier(.23, 1, .32, 1), transform .5s cubic-bezier(.32, .72, 0, 1);
  }
  .case-stage--dock.is-popup.is-snap { transition: opacity .25s cubic-bezier(.23, 1, .32, 1); }
  .case-stage--dock.is-popup .case-stage__frame { box-shadow: 0 0 0 .5px var(--hairline), 0 18px 40px -16px rgb(0 0 0 / .32); }
  .case-stage--dock.is-popup .case-stage__caption { display: none; }
}
/* Phones and tablets: the card is pinned to the top of the screen, as wide as the
   content, and blurs in and out; the project just below it is highlighted */
@media (hover: none), (pointer: coarse) {
  .case-stage--dock.is-popup {
    position: fixed;
    top: calc(12px + env(safe-area-inset-top));
    left: 50%;
    z-index: 30;
    width: min(var(--text-max), 100vw - 48px);
    transform: translate3d(-50%, -6px, 0) scale(.98);
    filter: blur(10px);
    transition: opacity .3s cubic-bezier(.23, 1, .32, 1), transform .45s cubic-bezier(.32, .72, 0, 1), filter .35s cubic-bezier(.23, 1, .32, 1);
  }
  .case-stage--dock.is-popup.is-visible { transform: translate3d(-50%, 0, 0); filter: none; }
  .work-row.is-aligned { background: var(--surface); }
}
/* The docked preview needs the empty space beside the column */


/* ---------- Floating hover card ---------- */
.hover-card {
  position: fixed;
  top: 0; left: 0;
  z-index: 50;
  pointer-events: none;
  transform-origin: 50% 100%;
  will-change: transform;
}
.hover-card.is-below { transform-origin: 50% 0%; }
/* Enter: fade, rise 6px, scale from .95 in .2s. Exit: same, reversed, .15s ease-in. */
.hover-card__inner {
  opacity: 0;
  transform: translateY(6px) scale(.95);
  transition: opacity .15s ease-in, transform .15s ease-in;
  /* No frame: just the content with a soft shadow */
  border-radius: 10px;
  box-shadow:
    0 4px 12px -4px color-mix(in oklab, var(--ink) 8%, transparent),
    0 18px 36px -12px color-mix(in oklab, var(--ink) 14%, transparent);
}
.hover-card.is-visible .hover-card__inner {
  opacity: 1;
  transform: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.hc-photo {
  width: 240px; height: 160px;
  object-fit: cover;
  border-radius: 10px;
  background: var(--surface);
}
.hc-info {
  width: 280px;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface);
  font-size: 13px;
  line-height: 1.4;
}
.hc-info__head { display: flex; align-items: center; gap: 10px; }
.hc-info__head > span { display: flex; flex-direction: column; }
.hc-info__logo { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; }
.hc-info__title { font-weight: 600; }
.hc-info__sub { color: var(--faint); }
.hc-info__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline);
  color: var(--faint);
  font-size: 12px;
}
.hc-info__stats strong { display: block; color: var(--ink); font-size: 14px; font-weight: 600; }
/* Touch screens: the card opens on tap instead of hover (see script.js) */

/* ---------- Load motion (CSS only so content never stays hidden) ---------- */
@keyframes rise { from { opacity: 0; transform: translateY(10px); } }
@media (prefers-reduced-motion: no-preference) {
  .reveal { animation: rise .8s var(--ease) both; }
  .reveal:nth-child(2) { animation-delay: 80ms; }
  .reveal:nth-child(3) { animation-delay: 160ms; }
  .reveal:nth-child(n+4) { animation-delay: 240ms; }
}

@media (max-width: 560px) {
  /* The introduction fills the first screen, centred (a bit above the middle, optically) */
  /* Room at the bottom so the floating sun/moon button never sits on the footer */
  .page { padding-top: 0; padding-bottom: calc(24px + env(safe-area-inset-bottom)); }
  .intro { display: flex; flex-direction: column; justify-content: center; min-height: 100vh; min-height: 100svh; padding: 48px 0 12svh; box-sizing: border-box; }
  .intro + .block { margin-top: 8px; }
  .block { margin-top: 72px; }
  .block__head, .block__title { margin-bottom: 20px; }
  .entry-list li + li { margin-top: 20px; }
  .now-list { gap: 20px; }
  .footer { margin-top: 88px; }
  .entry { grid-template-columns: 1fr; gap: 2px; }
  /* Case studies: title and result share the first line (result on the right), details below */
  .work-row { grid-template-columns: auto minmax(0, 1fr); grid-template-areas: "title result" "meta meta"; column-gap: 12px; row-gap: 2px; align-items: baseline; }
  .work-row__main { display: contents; }
  .work-row__title { grid-area: title; white-space: nowrap; }
  .work-row__meta { grid-area: meta; }
  /* The title never wraps; a long result is shortened with an ellipsis instead */
  .work-row__result { grid-area: result; min-width: 0; font-size: 14px; text-align: right; overflow: hidden; text-overflow: ellipsis; }
  .work-row.is-nda .work-row__result { justify-items: stretch; }
  .work-row__result-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
}

/* Touch screens: small text controls get a finger-sized hit area (about 44px)
   without changing how they look */
@media (pointer: coarse) {
  .sort-word, .link-list a, .cs-member > a, .cs-rail__home { position: relative; }
  .sort-word::after, .link-list a::after, .cs-member > a::after {
    content: "";
    position: absolute;
    inset: -12px -8px;
  }
  .link-list li + li { margin-top: 14px; }
  .social-list li + li { margin-top: 0; }
}

/* =========================================================
   Project page. Layout, type sizes and spacing copied from
   calebwu.ca/RevisionDojo (measured at 1440px); colors from the site tokens.
   ========================================================= */
.cs-page { letter-spacing: .014em; }
.cs-rail a, .cs-main a { text-decoration: none; }

/* Soft blur at the top: content scrolling under it fades and blurs out */
body.cs-page::before {
  content: "";
  position: fixed;
  inset: 0 0 auto;
  z-index: 3;
  height: 96px;
  pointer-events: none;
  background: linear-gradient(var(--bg) 10%, color-mix(in oklab, var(--bg) 70%, transparent) 55%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  -webkit-mask-image: linear-gradient(#000 45%, transparent);
  mask-image: linear-gradient(#000 45%, transparent);
  transition: opacity .25s var(--ease);
}

/* Left index: appears once the intro has scrolled away */
.cs-rail {
  position: fixed;
  top: 72px;
  left: 24px;
  z-index: 4;
  width: 192px;
  padding-left: 24px;
  opacity: 0;
  pointer-events: none;
  /* Leaves quickly, arrives slowly */
  transition: opacity .18s cubic-bezier(.4, 0, 1, 1);
}
.cs-rail.is-shown { opacity: 1; pointer-events: auto; transition: opacity .7s cubic-bezier(.62, .61, .02, 1); }
.cs-rail__home { display: inline-flex; align-items: center; gap: 8px; color: var(--faint); font-size: 16px; line-height: 1.75; letter-spacing: .018em; transition: color .2s; }
.cs-rail__home:hover { color: var(--ink); }
.cs-toc { margin-top: 64px; }
.cs-rail__arrow { flex-shrink: 0; }
/* Items rise 16px and fade in one after another, top to bottom */
.cs-toc a { display: block; color: var(--faint); font-size: 16px; line-height: 1.8; letter-spacing: .018em; opacity: 0; transform: translateY(16px); transition: color .2s cubic-bezier(.4, 0, .2, 1), opacity .3s cubic-bezier(.4, 0, .2, 1), transform .3s cubic-bezier(.4, 0, .2, 1); }
.cs-rail.is-shown .cs-toc a { opacity: 1; transform: none; transition: color .2s cubic-bezier(.4, 0, .2, 1), opacity .6s cubic-bezier(.22, 1, .36, 1) calc(var(--i, 0) * 50ms + 80ms), transform .7s cubic-bezier(.22, 1, .36, 1) calc(var(--i, 0) * 50ms + 80ms); }
@media (prefers-reduced-motion: reduce) { .cs-toc a, .cs-rail.is-shown .cs-toc a { transform: none; transition-duration: .01s; transition-delay: 0s; } }
.cs-toc a:hover, .cs-toc a.is-active { color: var(--ink); }

/* Column: 929px, centered */
.cs-main { width: min(929px, 100% - 48px); margin: 0 auto; padding-top: 128px; }

/* Shared type */
.cs-main h1, .cs-main h2 { margin: 0; color: var(--ink); font-size: 1.3rem; font-weight: 500; line-height: 1.4; letter-spacing: 0; }
.cs-main p, .cs-main li { margin: 0; font-size: 1.1rem; line-height: 27.2px; letter-spacing: .014em; }
.cs-label, .cs-main .cs-caption, .cs-main .cs-chapter { display: block; color: var(--faint); font-size: 16px; line-height: 1.8; letter-spacing: .018em; }

/* Intro: 60% text, 30% team, 10% gap */
.cs-intro { display: grid; grid-template-columns: 6fr 3fr; column-gap: 10%; margin-top: 0; scroll-margin-top: 120px; }
.cs-intro > h1 { grid-column: 1 / -1; margin-bottom: 48px; }
.cs-intro__main p { margin-top: 16px; color: var(--ink); }
.cs-intro__main p + p { margin-top: 24px; }
.cs-team ul { margin: 16px 0 0; padding: 0; list-style: none; }
.cs-team li { color: var(--ink); }
.cs-team .is-dim { color: var(--text); }
/* Team: one person per row (photo + name). Hover or focus shows what they did
   in a bubble to the left. */
.cs-team ul { display: grid; gap: 10px; }
.cs-member { position: relative; width: fit-content; outline: none; cursor: default; }
.cs-member > a, .cs-member { display: flex; align-items: center; gap: 10px; color: var(--ink); }
.cs-member > a { cursor: pointer; }
.cs-member img {
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: var(--raised);
  box-shadow: 0 0 0 1px var(--hairline);
  transition: transform .25s cubic-bezier(.34, 1.56, .64, 1);
}
.cs-member:hover img, .cs-member:focus-within img, .cs-member:focus-visible img { transform: scale(1.12); }
.cs-member:focus-visible, .cs-member > a:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 6px; }
/* Role bubble: opens below the name, wraps if needed and never sticks out of the screen */
.cs-member__role {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 2;
  width: max-content;
  max-width: min(280px, calc(100vw - 48px));
  padding: 6px 12px;
  border-radius: 10px;
  background: var(--ink);
  color: var(--bg);
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: .01em;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity .18s, transform .3s cubic-bezier(.32, .72, 0, 1);
}
/* little tail pointing up at the person */
.cs-member__role::after {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 6px;
  border: 5px solid transparent;
  border-bottom-color: var(--ink);
}
.cs-member:hover .cs-member__role, .cs-member:focus-within .cs-member__role, .cs-member:focus-visible .cs-member__role {
  opacity: 1;
  transform: none;
}
.cs-year { margin-top: 54px !important; color: var(--ink); }

/* Media: grey box, 12px corners */
.cs-media { margin: 0; overflow: hidden; border-radius: var(--radius); background: var(--media); }
.cs-media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.cs-hero { display: grid; place-items: center; margin: 72px 0 0; padding: 32px; border-radius: var(--radius); background: var(--media); }
.cs-hero img { display: block; width: 400px; max-width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.cs-pair { display: grid; grid-template-columns: 1fr 2fr; gap: 8px; margin-top: 80px; }
.cs-pair .cs-media:last-child { aspect-ratio: 614 / 345; }

/* Sections: label, heading, paragraph (max 575px), then media 32px below */
.cs-section { margin-top: 80px; scroll-margin-top: 120px; }
.cs-text { max-width: 575px; }
.cs-text h2 { margin-top: 8px; }
.cs-text p { margin-top: 16px; color: var(--text); }
.cs-text p + p { margin-top: 27.2px; }
.cs-text + .cs-media, .cs-text + .cs-split { margin-top: 32px; }
.cs-media--wide { aspect-ratio: 929 / 600; }
.cs-main .cs-caption { padding-top: 16px; text-align: center; }
.cs-split { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.cs-split .cs-media { margin-top: 32px; aspect-ratio: 460 / 480; }

/* Image box with breathing room around the picture */
.cs-media--inset { padding: 64px; }
.cs-media--inset img { border-radius: 8px; }

/* Words: a paragraph of inline phrases in alternating tones. On desktop, hovering one keeps it sharp
   and blurs and fades the rest. On mobile or touch it stays a plain paragraph. */
.cs-words { display: flex; flex-wrap: wrap; margin: 0; padding: 32px; list-style: none; border-radius: var(--radius); background: var(--media); font-size: clamp(20px, 2.8vw, 28px); font-weight: 500; line-height: 1.3; letter-spacing: -.01em; }
/* Each word grows to fill its row, so the words touch and there is no gap to hover in */
.cs-main .cs-words li { flex: 1 1 auto; padding: .3em .45em; text-align: center; color: var(--ink); font-size: inherit; line-height: inherit; letter-spacing: inherit; }
.cs-main .cs-words li:nth-child(even) { color: var(--faint); }
@media (hover: hover) {
  .cs-main .cs-words li { cursor: default; transition: filter .4s var(--ease), opacity .4s var(--ease); }
  .cs-main .cs-words:has(.is-on) li:not(.is-on) { filter: blur(5px); opacity: .35; }
}
@media (max-width: 760px) { .cs-words { padding: 20px 16px; } }
/* Stats: one grey box. A field of 100 dots (people) on the left, the numbers on the right.
   Hovering, focusing or tapping a number fills its share of the dots in the brand orange. */
.cs-stats { display: grid; grid-template-columns: auto auto; justify-content: center; align-items: center; gap: 80px; padding: 48px; border-radius: var(--radius); background: var(--media); }
.cs-stats__fig { display: block; width: 200px; height: 200px; fill: none; stroke: var(--faint); stroke-width: 1; overflow: visible; }
.cs-stats__fig circle { transition: fill .35s var(--ease), stroke .35s var(--ease); transition-delay: calc(var(--i) * 3ms); }
.cs-stats__fig .on { fill: var(--brand); stroke: var(--brand); }
.cs-stats__fig .rng { stroke: var(--brand); }
.cs-main .cs-stats__list { margin: 0; padding: 0; list-style: none; }
.cs-stats__list button { all: unset; box-sizing: border-box; display: flex; align-items: baseline; gap: 20px; width: 100%; padding: 6px 0; cursor: pointer; opacity: .4; transition: opacity .3s var(--ease); }
.cs-stats__list button[aria-pressed="true"] { opacity: 1; }
.cs-stats__list button:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 6px; }
.cs-stats__n { min-width: 3.4em; color: var(--ink); font-size: 28px; font-weight: 500; line-height: 1.2; letter-spacing: -.02em; }
.cs-stats__l { color: var(--faint); font-size: 15px; line-height: 1.4; letter-spacing: .018em; }
@media (max-width: 760px) {
  .cs-stats { grid-template-columns: 1fr; gap: 32px; padding: 28px 20px; }
  .cs-stats__fig { justify-self: center; width: 100%; height: auto; aspect-ratio: 1; }
}
@media (prefers-reduced-motion: reduce) { .cs-stats__fig circle, .cs-stats__list button { transition: none; } }
/* Challenges: three columns, each a title and short text above a grey box with a letter,
   a thin arrow pointing the way and a centred caption. Hovering a column nudges its arrow. */
.cs-challenges { display: grid; grid-template-columns: repeat(3, 1fr); column-gap: 8px; margin-top: 32px; }
/* Subgrid keeps titles, texts and boxes on the same lines even when one text runs longer */
.cs-challenge { display: block; min-width: 0; }
.cs-main .cs-challenge h3 { flex-basis: 100%; margin: 4px 0 0; color: var(--ink); font-size: 1.1rem; font-weight: 500; line-height: 1.3; text-align: center; }
.cs-main .cs-challenge > p { margin-top: 8px; padding-right: 16px; color: var(--text); }
.cs-challenge__box { display: flex; flex-wrap: wrap; justify-content: center; align-content: center; align-items: center; gap: 10px 14px; margin-top: 0; padding: 20px; aspect-ratio: 2 / 1; border-radius: var(--radius); background: var(--media); }
.cs-challenge__letter { color: var(--ink); font-size: 34px; font-weight: 400; line-height: 1; letter-spacing: -.02em; }
.cs-challenge__arrow { width: 26px; height: 26px; fill: none; stroke: var(--brand); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; rotate: var(--rot); transition: translate .5s var(--ease); }
.cs-main .cs-challenge__caption { flex-basis: 100%; margin: 0; color: var(--faint); font-size: 17px; line-height: 1.45; text-align: center; text-wrap: balance; }
.cs-challenge__caption strong { color: var(--ink); font-weight: 500; }
@media (hover: hover) {
  .cs-challenge:hover .cs-challenge__arrow { translate: calc(var(--dx) * 8px) calc(var(--dy) * 8px); }
}
@media (max-width: 760px) {
  .cs-challenges { grid-template-columns: 1fr; row-gap: 48px; }
  .cs-challenge { display: block; }
}
@media (prefers-reduced-motion: reduce) { .cs-challenge__arrow { transition: none; } }
/* Bento: the product's features as grey tiles around a tall product shot. Six columns and three
   rows on desktop; hovering a tile blurs and fades the others (script.js keeps the last tile
   sharp while the pointer crosses the gaps). On phones it becomes two plain columns. */
.cs-bento { display: grid; grid-template-columns: repeat(6, 1fr); grid-template-rows: repeat(3, auto); gap: 8px; margin-top: 32px;
  grid-template-areas: "strap feedback mid mid bluetooth knob" "battery battery mid mid size size" "led app mid mid base modes"; }
.cs-bento__mid { grid-area: mid; display: grid; grid-template-rows: auto 1fr auto; gap: 8px; }
.cs-tile { display: flex; flex-direction: column; justify-content: space-between; gap: 16px; min-width: 0; margin: 0; padding: 16px; border-radius: var(--radius); background: var(--media); }
.cs-main .cs-tile h3 { margin: 0; color: var(--ink); font-size: 15px; font-weight: 500; line-height: 1.3; }
.cs-main .cs-tile p { margin: 4px 0 0; color: var(--faint); font-size: 13px; line-height: 1.4; }
.cs-main .cs-tile .cs-tile__big { margin-top: 8px; color: var(--ink); font-size: 22px; font-weight: 500; line-height: 1.1; letter-spacing: -.02em; }
.cs-tile--row { flex-direction: row; align-items: center; justify-content: flex-start; gap: 20px; }
.cs-tile--reverse { justify-content: space-between; }
.cs-tile--strip { flex-direction: row; align-items: center; justify-content: space-between; padding: 14px 16px; }
.cs-tile--strip svg:first-child { order: 0; }
.cs-tile--photo { padding: 0; overflow: hidden; }
.cs-tile--photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Single tiles are square; the two wide tiles and the centre column keep their shape */
.cs-bento > .cs-tile:not(.cs-tile--row) { aspect-ratio: 1; }
/* Photo tiles: the picture fills the tile and the title sits on top, bottom left */
.cs-tile--img { position: relative; justify-content: flex-end; overflow: hidden; }
.cs-tile--img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.cs-tile--img h3 { position: relative; }
/* Renders on white: shown whole above the title, and multiply turns their white background into the tile grey */
.cs-tile--blend img { mix-blend-mode: multiply; }
/* Cropped photos: each tile sets the picture width (--w) and offsets (--x, --y) so the object fills the tile */
.cs-tile--crop img { inset: auto; left: var(--x); top: var(--y); width: var(--w); max-width: none; height: auto; }
/* Knob: crop of the real top view (knob at x 410-610 of 1016) with the side button above it */
.cs-tile--knob { justify-content: flex-end; }
.cs-knob__crop { position: absolute; inset: 0; rotate: 180deg; }
.cs-tile--knob img { inset: auto; left: -60%; width: 220%; max-width: none; height: auto; }
.cs-tile--knob img:not(.cs-knob__cap) { top: 35%; }
.cs-tile--knob .cs-knob__cap { top: 30%; }
/* Size: a light line drawing of the top view at its real 2:1 ratio, with thin dimension lines */
.cs-size { flex: none; width: 104px; height: auto; overflow: visible; }
.cs-size__body { fill: none; stroke: var(--faint); stroke-width: 1.2; }
.cs-size__dim { fill: none; stroke: var(--brand); stroke-width: 1; }
.cs-size text { fill: var(--faint); font-size: 9px; font-family: inherit; }
.cs-main .cs-tile--row .cs-tile__big { white-space: nowrap; }
/* Designed for ADHD: a tangled line that resolves into a straight one; it redraws on hover */
.cs-tile__order { flex: 1; max-width: 140px; height: 24px; fill: none; stroke: var(--brand); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.cs-tile__order circle { fill: var(--brand); stroke: none; }
.cs-tile__order path { stroke-dasharray: 1; stroke-dashoffset: 0; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .cs-tile--strip.is-on .cs-tile__order path { animation: cs-draw 1.4s var(--ease); }
}
@keyframes cs-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
/* Dark photo (the lit LED): black tile, white title, and a soft fade so the title stays readable over the glow */
.cs-tile--dark { background: #0d0d0d; }
.cs-tile--dark::after { content: ""; position: absolute; inset: 0; background: linear-gradient(to top, rgb(0 0 0 / .8), transparent 50%); pointer-events: none; }
.cs-main .cs-tile--dark h3 { z-index: 1; color: #fff; }
.cs-tile--modes { justify-content: center; align-items: center; gap: 2px; text-align: center; }
.cs-tile--modes span { color: var(--brand); font-size: 13px; line-height: 1; }
/* Icons: one thin stroke in the text grey, with one accent part in the brand orange */
.cs-tile__icon { display: block; flex: none; width: 40px; height: 40px; fill: none; stroke: var(--faint); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.cs-tile__icon--lg { width: 64px; height: 64px; }
.cs-tile__icon--sm { width: 28px; height: 28px; }
.cs-tile__icon .ac { stroke: var(--brand); }
.cs-tile__icon .ac--fill { fill: var(--brand); stroke: none; }
.cs-tile__icon .ac--thick { stroke-width: 3; }
@media (hover: hover) {
  .cs-main .cs-tile { transition: filter .4s var(--ease), opacity .4s var(--ease); }
  .cs-main .cs-bento:has(.is-on) .cs-tile:not(.is-on) { filter: blur(4px); opacity: .4; }
}
@media (max-width: 760px) {
  .cs-bento { grid-template-columns: 1fr 1fr; grid-template-rows: none; grid-auto-rows: minmax(132px, auto);
    grid-template-areas: "mid mid" "strap feedback" "battery battery" "led app" "bluetooth knob" "size size" "base modes"; }
  .cs-tile--photo { aspect-ratio: 16 / 10; }
}
@media (prefers-reduced-motion: reduce) { .cs-main .cs-tile { transition: none; } }
/* Exploded view: the render sits on the grey surface; multiply turns its white background grey */
.cs-exploded { display: grid; place-items: center; padding: 56px 32px; }
.cs-exploded picture { display: contents; }
.cs-media.cs-exploded img { width: 100%; max-width: 700px; height: auto; object-fit: contain; mix-blend-mode: multiply; }
@media (max-width: 760px) { .cs-exploded { padding: 32px 16px; } }
/* Interface: one grey box. Modes on the left; hovering, focusing or tapping one shows on the right
   what it does, a 24-LED strip playing its light pattern and its vibration as bars
   (bar length = duration, bar height = strength). */
.cs-ui { display: grid; grid-template-columns: 220px 1fr; gap: 48px; margin-top: 32px; padding: 40px; border-radius: var(--radius); background: var(--media); }
.cs-main .cs-ui__modes { margin: 0; padding: 0; list-style: none; }
.cs-ui__modes button { all: unset; box-sizing: border-box; display: block; width: 100%; padding: 10px 0; cursor: pointer; opacity: .4; transition: opacity .3s var(--ease); }
.cs-ui__modes button[aria-pressed="true"] { opacity: 1; }
.cs-ui__modes button:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; border-radius: 6px; }
.cs-ui__name { display: block; color: var(--ink); font-size: 18px; font-weight: 500; line-height: 1.3; }
.cs-ui__how { display: block; margin-top: 2px; color: var(--faint); font-size: 14px; line-height: 1.4; }
.cs-ui__view { display: grid; align-content: start; gap: 28px; min-width: 0; padding-top: 10px; }
.cs-main .cs-ui__desc { margin: 0; color: var(--text); }
.cs-main .cs-ui__k { margin: 0 0 10px; color: var(--faint); font-size: 13px; line-height: 1.4; letter-spacing: .018em; }
.cs-ui__leds { display: flex; align-items: center; gap: 16px; }
.cs-ui__track { flex: 1; display: grid; grid-template-columns: repeat(24, 1fr); gap: 3px; padding: 10px 12px; border-radius: 999px; background: var(--bg); }
.cs-ui__track span { height: 6px; border-radius: 3px; background: var(--faint); opacity: .18; transition: opacity .25s var(--ease), background-color .25s var(--ease); }
.cs-ui__track span.on { background: var(--brand); opacity: var(--l, 1); }
.cs-ui__track span.gap { visibility: hidden; }
.cs-ui__frame { min-width: 4.5em; color: var(--faint); font-size: 14px; text-align: right; font-variant-numeric: tabular-nums; }
.cs-ui__haptics { display: flex; flex-wrap: wrap; gap: 20px 40px; }
.cs-ui__bars { display: flex; align-items: flex-end; gap: 3px; height: 26px; }
.cs-ui__bars span { width: calc(var(--d) * 40px); height: calc(var(--v) * 12px); border-radius: 2px; background: var(--ink); }
.cs-ui__bars i { align-self: center; color: var(--ink); font-style: normal; font-size: 14px; line-height: 1; }
.cs-ui__when { display: block; margin-top: 8px; color: var(--ink); font-size: 14px; line-height: 1.4; }
.cs-main .cs-ui__legend { margin: 0; color: var(--faint); font-size: 13px; line-height: 1.4; }
@media (max-width: 760px) {
  .cs-ui { grid-template-columns: 1fr; gap: 24px; padding: 24px 20px; }
  .cs-ui__modes { display: flex; flex-wrap: wrap; gap: 4px 20px; }
  .cs-ui__modes button { width: auto; padding: 6px 0; }
  .cs-ui__how { display: none; }
  .cs-ui__modes button[aria-pressed="true"] .cs-ui__how { display: none; }
}
@media (prefers-reduced-motion: reduce) { .cs-ui__track span, .cs-ui__modes button { transition: none; } }
/* Device version: the real top view, flipped so the knob sits at the bottom, with the side button
   (1016 x 24 above the 1016 x 490 top view, before the flip). The button hides behind the knob and
   sinks into it when pressed. The 24 LEDs sit on the track (x 194-832, y 339-348 of the top view).
   A curved arrow by the knob rocks while the knob is turned (brightness). */
.cs-ui[hidden] { display: none; }
.cs-ui[hidden] + .cs-ui--device { margin-top: 32px; }
.cs-ui--device { grid-template-columns: 180px 1fr; align-items: center; gap: 32px; margin-top: 8px; padding: 28px 32px; }
.cs-ui--device .cs-ui__modes { align-self: center; padding-top: 0; }
.cs-ui--device .cs-ui__view { gap: 0; padding-top: 0; }
/* Bottom padding leaves room for the button cap and the brightness arrow below the flipped device */
.cs-ui__stage { display: grid; place-items: center; padding: 8px 0 40px; }
.cs-ui__device { position: relative; width: 100%; max-width: 600px; aspect-ratio: 1016 / 514; rotate: 180deg; }
.cs-ui__device img { position: absolute; left: 0; width: 100%; max-width: none; height: auto; }
.cs-ui__top { top: 4.67%; z-index: 1; }
.cs-ui__button { top: 0; z-index: 0; transition: translate .12s var(--ease); }
.cs-ui__button.is-down { translate: 0 60%; }
/* LEDs: dim segments behind the diffuser when off; warm white core and a layered glow when on */
.cs-ui--device .cs-ui__track { position: absolute; z-index: 2; left: 19.1%; width: 62.8%; top: 70.6%; height: 1.95%; display: grid; grid-template-columns: repeat(24, 1fr); gap: 0 1.1%; padding: 0 .5%; align-items: center; border-radius: 0; background: none; }
.cs-ui--device .cs-ui__track span { height: 72%; min-height: 2px; border-radius: 1.5px; background: rgb(255 255 255 / .07); opacity: 1; box-shadow: none; transition: background-color .3s var(--ease), box-shadow .3s var(--ease); }
.cs-ui--device .cs-ui__track span.on { opacity: 1; background: rgb(255 251 242 / calc(.35 + var(--l) * .65));
  box-shadow: 0 0 1px rgb(255 255 255 / var(--l)), 0 0 4px 1px rgb(255 246 230 / calc(var(--l) * .6)), 0 0 12px 3px rgb(255 238 214 / calc(var(--l) * .22)); }
/* Turning the knob: a flat double arrow just past the knob, sliding side to side */
.cs-ui__turn { position: absolute; z-index: 2; left: 41%; top: -13%; width: 18%; height: auto; fill: none; stroke: var(--brand); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; opacity: 0; transition: opacity .25s var(--ease); }
.cs-ui__turn.is-on { opacity: 1; animation: cs-turn .9s ease-in-out infinite alternate; }
@keyframes cs-turn { from { translate: -8% 0; } to { translate: 8% 0; } }
/* Below the device: the description on the left, the vibrations on the right */
.cs-ui__info { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 12px 32px; min-height: calc(2 * 1.5 * 17px); padding-top: 0; }
.cs-main .cs-ui--device .cs-ui__desc { flex: 1 1 320px; max-width: 46ch; }
/* Timer: pick 4, 5 or 6 intervals */
.cs-ui__opts { display: flex; align-items: center; gap: 4px; flex: none; }
.cs-ui__opts[hidden] { display: none; }
.cs-ui__opts span { margin-right: 8px; color: var(--faint); font-size: 14px; }
.cs-ui__opts button { all: unset; box-sizing: border-box; width: 36px; height: 32px; border-radius: 8px; color: var(--faint); font-size: 15px; font-weight: 500; text-align: center; line-height: 32px; cursor: pointer; transition: background-color .2s var(--ease), color .2s var(--ease); }
.cs-ui__opts button:hover { color: var(--ink); }
.cs-ui__opts button[aria-pressed="true"] { background: var(--bg); color: var(--ink); }
.cs-ui__opts button:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cs-main .cs-ui--device .cs-ui__desc { color: var(--ink); font-size: 17px; line-height: 1.5; }
@media (max-width: 760px) {
  .cs-ui--device { grid-template-columns: 1fr; gap: 16px; padding: 24px 20px; }
  .cs-ui__stage { padding: 16px 0 24px; }
  .cs-ui__info { min-height: 0; padding-top: 20px; }
  .cs-ui--device .cs-ui__modes { padding-top: 0; }
}
.cs-ui__track.is-instant span { transition: none; }
@media (prefers-reduced-motion: reduce) { .cs-ui__button { transition: none; } .cs-ui__turn.is-on { animation: none; } }
/* Two renders side by side on the grey surface (multiply turns their white background grey) */
.cs-duo { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
.cs-media.cs-duo__item { aspect-ratio: 1; }
.cs-media.cs-duo__item img { object-fit: contain; padding: 24px; mix-blend-mode: multiply; }
.cs-media.cs-duo__item img[style*="cover"] { padding: 0; }
@media (max-width: 760px) { .cs-duo { grid-template-columns: 1fr; } }
/* Full-bleed picture: breaks out of the text column to the full width of the window.
   The page clips horizontal overflow so 100vw never adds a sideways scroll. */
html:has(body.cs-page), body.cs-page { overflow-x: clip; }
.cs-bleed { width: 100%; margin: 8px 0 0; aspect-ratio: 16 / 10; overflow: hidden; border-radius: var(--radius); background: #d3d3d3; }
.cs-bleed img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 52%; }
/* Closing render at the end of the page, on the grey surface */
.cs-final { position: relative; margin-top: 96px; aspect-ratio: 2000 / 1414; display: grid; place-items: center; }
/* The disclaimer sits on the bottom of the closing render, over a soft dark fade for contrast */
.cs-final::after { content: ""; position: absolute; inset: 30% 0 0; border-radius: inherit; background: linear-gradient(to top, rgb(0 0 0 / .62), rgb(0 0 0 / .34) 28%, rgb(0 0 0 / .12) 60%, rgb(0 0 0 / .03) 82%, transparent); pointer-events: none; }
.cs-main .cs-final .cs-disclaimer { position: absolute; z-index: 1; left: 24px; right: 24px; bottom: 20px; max-width: 575px; margin: 0; color: rgb(255 255 255 / .92); }
.cs-media.cs-final img { width: 88%; height: auto; object-fit: contain; mix-blend-mode: multiply; }
/* Colour variations: an endless row that drifts to the left, faded at both ends.
   Two copies of the row sit side by side; each slides by its own width, so the loop has no jump. */
.cs-marquee { display: flex; gap: 16px; margin-top: 8px; overflow: hidden; -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent); }
.cs-marquee__track { display: flex; flex: none; gap: 16px; animation: cs-marquee 45s linear infinite; }
.cs-marquee figure { flex: none; width: 224px; aspect-ratio: 1; margin: 0; overflow: hidden; border-radius: var(--radius); background: var(--media); }
/* Renders on white: multiply turns the white into the tile grey */
.cs-marquee img { display: block; width: 100%; height: 100%; object-fit: contain; padding: 10px; mix-blend-mode: multiply; }
@keyframes cs-marquee { to { translate: calc(-100% - 16px) 0; } }
@media (max-width: 760px) { .cs-marquee figure { width: 180px; } }
@media (prefers-reduced-motion: reduce) { .cs-marquee { overflow-x: auto; } .cs-marquee__track { animation: none; } .cs-marquee__track[aria-hidden] { display: none; } }
/* Stats and words read as one block: same section, the usual 8px gap */
.cs-stats + .cs-words { margin-top: 8px; }
/* Two renders in one grey block, side by side; multiply turns their white background grey */
.cs-media.cs-twin { display: grid; grid-template-columns: 1fr 1fr; margin-top: 8px; padding: 24px; }
.cs-media.cs-twin img { width: 100%; height: auto; aspect-ratio: 1; object-fit: contain; mix-blend-mode: multiply; }
@media (max-width: 760px) { .cs-media.cs-twin { padding: 12px; } }
.cs-text + .cs-twin, .cs-text + .cs-marquee, .cs-text + .cs-stats { margin-top: 32px; }
/* Solution render: a portrait shot on pure black, shown whole in a wide black box */
.cs-media.cs-hero-dark { aspect-ratio: 16 / 10; background: #000; }
.cs-media.cs-hero-dark img { object-fit: contain; }
.cs-solution + .cs-twin { margin-top: 8px; }
@media (max-width: 760px) { .cs-media.cs-hero-dark { aspect-ratio: 4 / 5; } }
/* Solution: the dark render on the left, a mood board of references on the right, same height */
.cs-text + .cs-solution { margin-top: 32px; }
/* Render about 60%, mood board about 40%: with these proportions both columns end up the same height */
.cs-solution { display: grid; grid-template-columns: 1.48fr 1fr; gap: 8px; }
.cs-solution .cs-media.cs-hero-dark { aspect-ratio: 4 / 5; }
/* Balanced collage: OP-1 across the top, the person as a tall light column, the bright bag over the dark metal,
   the wheels across the bottom. Crops only trim background, never the objects. */
.cs-mood { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1.5fr 2fr 2fr 1.4fr; grid-template-areas: "op op" "person bag" "person metal" "wheels wheels"; gap: 0; overflow: hidden; border-radius: var(--radius); background: var(--media); }
/* One collage: the pictures touch edge to edge inside a single rounded box */
.cs-mood figure { position: relative; min-height: 0; margin: 0; overflow: hidden; }
/* Pictures are taken out of the flow so their own size never stretches the rows */
.cs-mood img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 760px) { .cs-solution { grid-template-columns: 1fr; } .cs-mood { aspect-ratio: 1 / 1.85; } }
/* App screens: three phones side by side on the grey surface. Each screen (780 x 1688) sits in the
   mockup's screen area (24 px bezel on a 828 x 1736 frame); the frame has a transparent screen
   and goes on top, so only the bezel and the dynamic island cover the content. */
.cs-text + .cs-caption--above { margin-top: 24px; }
.cs-main .cs-caption--above { padding: 0 0 16px; }
.cs-phones { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; padding: 40px; border-radius: var(--radius); background: var(--media); }
.cs-phone { position: relative; margin: 0; aspect-ratio: 828 / 1736; }
.cs-phone__screen { position: absolute; left: 2.9%; top: 1.38%; width: 94.2%; height: 97.24%; object-fit: cover; border-radius: 13.6% / 6.3%; background: #0f0f0f; }
.cs-phone__frame { position: absolute; inset: 0; width: 100%; height: 100%; max-width: none; pointer-events: none; }
/* Each phone has a short label above it, inside the grey box */
.cs-phone-item { display: grid; gap: 12px; min-width: 0; }
.cs-main .cs-phone__label { margin: 0; color: var(--faint); font-size: 15px; line-height: 1.3; text-align: center; }
/* Two phones: same size as in the row of three, centred */
.cs-phones--two { grid-template-columns: repeat(2, minmax(0, 267px)); justify-content: center; }
.cs-text + .cs-phones { margin-top: 32px; }
@media (max-width: 760px) { .cs-phones { gap: 10px; padding: 16px; } }
/* Cover that turns square on phones; the crop only trims background around the centred product */
@media (max-width: 760px) { .cs-hero--square-sm { aspect-ratio: 1; } .cs-hero--square-sm img { height: 100%; aspect-ratio: auto; object-fit: cover; } }
/* Phones: one inner padding for every large grey box, so the edges line up down the page */
@media (max-width: 760px) {
  .cs-stats, .cs-words, .cs-ui.cs-ui--device, .cs-phones, .cs-exploded { padding: 20px; }
}
/* Dark mode: renders shot on white are blended onto their box, which would turn them almost black on
   the dark surface. These boxes keep a light studio grey instead, so the renders look as in light mode. */
:root[data-theme="dark"] :is(.cs-marquee figure, .cs-media.cs-exploded, .cs-media.cs-duo__item, .cs-media.cs-final, .cs-media.cs-twin, .cs-tile--blend) { background: #e9e9e7; }
:root[data-theme="dark"] .cs-main .cs-tile--blend h3 { color: #171717; }
/* Legal note at the very end of a project page */
.cs-main .cs-disclaimer { max-width: 575px; margin: 64px 0 0; color: var(--faint); font-size: 12px; line-height: 1.5; letter-spacing: .01em; }
/* Phones: the word list animates by itself (script.js adds .is-auto); slower, softer transitions */
.cs-main .cs-words.is-auto li { transition: filter .9s var(--ease), opacity .9s var(--ease); }
.cs-main .cs-words.is-auto:has(.is-on) li:not(.is-on) { filter: blur(4px); opacity: .35; }
/* Small heading above a list, e.g. "Goals:" */
.cs-goals { margin: 32px 0 0; color: var(--ink); font-size: 16px; font-weight: 500; line-height: 1.5; letter-spacing: .015em; }
.cs-goals + .cs-steps { margin-top: 16px; }
.cs-steps + .cs-media { margin-top: 32px; }
/* Last section (Key Learnings) sits a little further down */
.cs-section--end { margin-top: 133px; }
.cs-section--end .cs-dashes { margin-top: 16px; }

/* Centered chapter divider, e.g. "Research" */
.cs-main .cs-chapter { margin: 80px 0 0; text-align: center; }

/* Numbered and dashed lists */
.cs-steps { margin: 16px 0 0; padding: 0; list-style: none; max-width: 672px; }
.cs-steps li { color: var(--text); }
.cs-steps li + li { margin-top: 8px; }
.cs-dashes { margin: 16px 0 0; padding: 0; list-style: none; max-width: 672px; }
.cs-dashes li { position: relative; padding-left: 16px; color: var(--text); }
.cs-dashes li + li { margin-top: 24px; }
.cs-dashes li::before { content: "-"; position: absolute; left: 0; }

/* Space after the last section; also the point where the home starts loading */
.cs-footer { height: 160px; }

@media (max-width: 1279px) { .cs-rail { display: none; } }
@media (max-width: 760px) {
  .cs-main { padding-top: 88px; }
  .cs-intro { grid-template-columns: 1fr; row-gap: 40px; margin-top: 0; }
  .cs-hero { margin-top: 48px; }
  .cs-pair { margin-top: 64px; }
  .cs-intro > h1 { margin-bottom: 8px; }
  .cs-pair, .cs-split { grid-template-columns: 1fr; }
  .cs-split .cs-media { margin-bottom: 40px; }
}

/* ---------- Home appended below a project page ---------- */
/* The project lies on top of the home like a sheet of paper: its bottom edge is
   rounded and casts a soft shadow onto the home, which starts just underneath it
   (the home is pulled up by the corner radius so it shows in the corners).
   --sheet goes 0 → 1 as the home's top travels from the bottom of the screen to
   the top (set in script.js). */
body:has(.home-continue) { --r: 28px; }
@supports (corner-shape: squircle) { body:has(.home-continue) { --r: 44px; } }
body:has(.home-continue) .cs-footer {
  position: relative;
  z-index: 1;
  background: var(--bg);
  border-radius: 0 0 var(--r) var(--r);
  corner-shape: squircle;
  box-shadow: 0 .5px 0 var(--hairline), 0 18px 48px -14px rgb(0 0 0 / .1);
}
:root[data-theme="dark"] body:has(.home-continue) .cs-footer {
  box-shadow: 0 .5px 0 rgb(255 255 255 / .12), 0 18px 48px -14px rgb(0 0 0 / .6);
}
.home-continue {
  position: relative;
  margin-top: calc(var(--r) * -1);
  background: var(--bg);
}
/* Grabber, like a sheet on iOS: it says "a separate page starts here".
   It can be held and dragged up (or clicked) to go to the home. The button is a
   generous target; the bar itself is drawn by ::before. */
.home-grab {
  position: absolute;
  top: calc(var(--r) - 6px);
  left: 50%;
  z-index: 1;
  width: 120px;
  height: 44px;
  margin-left: -60px;
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;
  opacity: calc(1 - var(--sheet, 0) * 1.4);
}
.home-grab::before {
  content: "";
  position: absolute;
  top: 20px;
  left: 50%;
  width: 36px;
  height: 5px;
  margin-left: -18px;
  border-radius: 3px;
  background: rgb(0 0 0 / .12);
  transition: width .3s cubic-bezier(.32, .72, 0, 1), margin-left .3s cubic-bezier(.32, .72, 0, 1), background-color .2s;
}
.home-grab:hover::before, .home-grab:focus-visible::before, html.is-grabbing .home-grab::before { width: 48px; margin-left: -24px; background: rgb(0 0 0 / .24); }
.home-grab:focus-visible { outline: none; }
:root[data-theme="dark"] .home-grab::before { background: rgb(255 255 255 / .14); }
:root[data-theme="dark"] .home-grab:hover::before, :root[data-theme="dark"] .home-grab:focus-visible::before, :root[data-theme="dark"].is-grabbing .home-grab::before { background: rgb(255 255 255 / .3); }
html.is-grabbing, html.is-grabbing * { cursor: grabbing !important; user-select: none; }
.home-continue__edge { height: 1px; }
body.cs-page.in-home::before { opacity: 0; pointer-events: none; }

/* ---------- Apple-style squircle corners ----------
   Where the browser supports corner-shape (Chrome/Edge), pictures and cards get a
   continuous squircle curve. A squircle looks smaller than a plain radius of the same
   size, so the radius is larger. Other browsers keep the plain rounded corners. */
@supports (corner-shape: squircle) {
  .cs-media, .cs-hero, .cs-words, .cs-stats, .cs-ui--device, .cs-challenge__box, .cs-duo__item, .cs-bleed, .cs-final, .cs-marquee figure, .cs-mood, .cs-phones, .case-stage__frame { border-radius: 26px; corner-shape: squircle; }
  .cs-media--inset img { border-radius: 18px; corner-shape: squircle; }
  .hc-photo, .hc-info, .hover-card__inner { border-radius: 20px; corner-shape: squircle; }
  .work-row { border-radius: 22px; corner-shape: squircle; }
}

/* Phones: roomier case study rows (kept last so it wins over the phone layout above) */
@media (max-width: 560px) and (hover: none), (max-width: 560px) and (pointer: coarse) {
  /* More air: taller rows and a gap between them */
  .work-list { display: grid; gap: 6px; }
  .work-row { row-gap: 4px; padding: 12px; }
  .work-row__title { line-height: 1.3; }
  .work-row__meta { font-size: 14px; line-height: 1.35; }
  .work-row__meta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* NDA rows: no result and no link; tapping the row opens a small NDA pop-up instead */
  .work-row.is-nda .work-row__result { display: none; }
  .work-row.is-nda { cursor: pointer; }
}

/* ---------- Scroll reveal: each element eases in as it enters the screen ----------
   script.js adds .sr only when motion is allowed, so without it nothing is hidden. */
.sr { opacity: 0; transform: translateY(18px); }
.sr.is-in { opacity: 1; transform: none; transition: opacity .7s cubic-bezier(.22, 1, .36, 1) var(--sr-delay, 0ms), transform .9s cubic-bezier(.22, 1, .36, 1) var(--sr-delay, 0ms); }
/* Phones: the case studies come in more slowly, and further apart */
@media (max-width: 560px) {
  .work-list > li.sr { transform: translateY(24px); }
  .work-list > li.sr.is-in { transform: none; transition: opacity 1.1s cubic-bezier(.22, 1, .36, 1) calc(var(--sr-delay, 0ms) * 1.8), transform 1.4s cubic-bezier(.22, 1, .36, 1) calc(var(--sr-delay, 0ms) * 1.8); }
}

/* NDA tag next to the title of projects under NDA: a quiet filled label */
.nda-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 6px;
  /* Soft fill, no outline: a bit darker than the grey row highlight so it stays visible on it */
  background: color-mix(in oklab, var(--ink) 7%, var(--bg));
  color: var(--text);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: .06em;
  vertical-align: 2px;
}

/* ---------- NDA pop-up (touch screens): a small iOS-style alert ---------- */
.nda-sheet {
  width: min(300px, calc(100vw - 48px));
  margin: auto;
  padding: 10px 10px 12px;
  border: 0;
  border-radius: 22px;
  color: var(--ink);
  background: color-mix(in oklab, var(--bg) 88%, transparent);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: 0 0 0 .5px var(--hairline), 0 24px 60px -16px rgb(0 0 0 / .35);
  text-align: center;
}
@supports (corner-shape: squircle) { .nda-sheet { border-radius: 34px; corner-shape: squircle; } }
.nda-sheet::backdrop { background: rgb(0 0 0 / .28); }
.nda-sheet { overscroll-behavior: contain; touch-action: none; }
.nda-sheet__img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; border-radius: 14px; background: var(--surface-2); }
@supports (corner-shape: squircle) { .nda-sheet__img { border-radius: 24px; corner-shape: squircle; } }
.nda-sheet__title { margin: 18px 10px 18px; font-size: 17px; font-weight: 600; line-height: 1.3; }
.nda-sheet__text { margin: 6px 0 20px; color: var(--text); font-size: 14px; line-height: 1.45; }
.nda-sheet__actions { display: grid; gap: 8px; padding: 0 6px; }
.nda-sheet__btn {
  display: block;
  padding: 12px;
  border: 0;
  border-radius: 14px;
  font: inherit;
  font-size: 16px;
  font-weight: 500;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.nda-sheet__btn--primary { color: var(--bg); background: var(--ink); }
.nda-sheet__btn--secondary { color: var(--ink); background: color-mix(in oklab, var(--ink) 8%, transparent); }
.nda-sheet__btn:active { opacity: .7; }
.nda-sheet__btn:focus { outline: none; }
.nda-sheet__btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* Opens with a soft scale-up, closes with a quick fade */
.nda-sheet[open] { animation: nda-in .32s cubic-bezier(.32, .72, 0, 1); }
.nda-sheet[open]::backdrop { animation: nda-fade .32s ease; }
.nda-sheet.is-closing { animation: nda-out .18s ease forwards; }
.nda-sheet.is-closing::backdrop { animation: nda-fade .18s ease reverse forwards; }
@keyframes nda-in { from { opacity: 0; transform: scale(1.08); } }
@keyframes nda-out { to { opacity: 0; transform: scale(.96); } }
@keyframes nda-fade { from { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .nda-sheet[open], .nda-sheet.is-closing { animation-duration: .01s; } }

/* Touch: no grey flash or leftover focus box on tapped rows, links and pop-up buttons.
   Keyboard users still get a visible ring through :focus-visible. */
.work-row, .nda-sheet a, .nda-sheet button, .hover-link { -webkit-tap-highlight-color: transparent; }
.work-row.is-nda[role="button"]:focus { outline: none; }
.work-row.is-nda[role="button"]:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.nda-sheet:focus { outline: none; }
/* Phones keep :hover stuck on the last thing tapped; hover looks are for mice only */
@media (hover: none) {
  a.work-row:hover { background: none; }
  a.work-row:hover .work-row__title { text-decoration-color: transparent; }
  .work-row.is-nda:hover .work-row__result-text { opacity: 1; transform: none; filter: none; }
  .work-row.is-nda:hover .work-row__ask { opacity: 0; }
}

/* ---------- Email signup (copied from email-signup/email-signup.css, themed below) ---------- */
/* EmailSignup — theme by overriding these on .es (or any ancestor). */
.es {
  --es-ink: #111111; /* text, focus line, ready arrow */
  --es-muted: #74746f; /* placeholder, idle arrow */
  --es-subtle: #5b5b59; /* error message */
  --es-line: #e7e7e3; /* resting hairline */
  --es-line-strong: #d4d4cf; /* idle arrow border */
  --es-paper: #ffffff; /* background behind the field (for autofill) */
  --es-accent: #2647f2; /* the hand-written "Submitted :)" */
  --es-prompt-size: clamp(1.25rem, 1.08rem + 0.5vw, 1.5rem);
  --es-field-size: clamp(1.75rem, 1.3rem + 1.25vw, 2.5rem);
  --es-ease-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --es-ease-quart: cubic-bezier(0.25, 1, 0.5, 1);

  position: relative;
  font-family: inherit;
  color: var(--es-ink);
}

.es-label {
  display: block;
  font-size: var(--es-prompt-size);
  line-height: 1.42;
  letter-spacing: -0.012em;
}

.es-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 1rem;
}
@media (min-width: 640px) {
  .es-row {
    margin-top: 1.25rem;
  }
}

/* Field ------------------------------------------------------------- */

.es-input {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 0.75rem 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  font: inherit;
  font-size: var(--es-field-size);
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.026em;
  color: var(--es-ink);
  outline: none;
  transition: opacity 220ms var(--es-ease-quart);
}
.es-input::placeholder {
  color: color-mix(in srgb, var(--es-muted) 80%, transparent);
  opacity: 1;
}
.es-input:-webkit-autofill {
  box-shadow: inset 0 0 0 100px var(--es-paper);
  -webkit-text-fill-color: var(--es-ink);
}
.es:not([data-phase="idle"]) .es-input {
  opacity: 0;
  transition-duration: 160ms;
}

/* Lines ------------------------------------------------------------- */

.es-line,
.es-line-focus {
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 1px;
  pointer-events: none;
}
.es-line {
  background: var(--es-line);
}
.es-line-focus {
  background: var(--es-ink);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 450ms var(--es-ease-expo);
}
.es-input:focus ~ .es-line-focus {
  transform: scaleX(1);
  transform-origin: left;
}

/* Submit arrow ------------------------------------------------------ */

.es-submit {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--es-line-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--es-muted);
  cursor: pointer;
  transition:
    opacity 300ms var(--es-ease-quart),
    scale 300ms var(--es-ease-quart),
    border-color 300ms var(--es-ease-quart),
    color 300ms var(--es-ease-quart);
}
.es-submit:hover,
.es[data-ready] .es-submit {
  border-color: var(--es-ink);
  color: var(--es-ink);
}
.es-submit:active {
  scale: 0.92;
}
.es-submit:focus-visible {
  outline: 2px solid var(--es-accent);
  outline-offset: 3px;
}
.es:not([data-phase="idle"]) .es-submit {
  opacity: 0;
  scale: 0.9;
  pointer-events: none;
}

/* The arrow leaves to the right while a twin slides in from the left. */
.es-arrow {
  position: relative;
  display: inline-block;
  width: 0.95em;
  height: 0.95em;
  overflow: hidden;
}
.es-arrow svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transition: transform 500ms var(--es-ease-expo);
}
.es-arrow svg:last-child {
  transform: translateX(-100%);
}
.es-submit:hover .es-arrow svg:first-child,
.es-submit:focus-visible .es-arrow svg:first-child {
  transform: translateX(100%);
}
.es-submit:hover .es-arrow svg:last-child,
.es-submit:focus-visible .es-arrow svg:last-child {
  transform: none;
}

/* Hand-written "Submitted :)" --------------------------------------- */

.es-ink {
  position: absolute;
  left: 0;
  top: 50%;
  width: auto;
  height: 1.8em;
  overflow: visible;
  font-size: var(--es-field-size);
  color: var(--es-accent);
  pointer-events: none;
  transform: translate(-0.06em, -55%);
  transition: opacity 220ms var(--es-ease-quart);
}
.es[data-phase="erasing"] .es-ink {
  opacity: 0;
}

/* Each path has pathLength=1 and its own --delay / --dur. Hidden until its turn so
   the round cap doesn't leave a dot early. Longhands on purpose: some CSS minifiers
   drop keyframes referenced only from a shorthand that contains var(). */
.es-stroke {
  stroke-dasharray: 1 2;
  stroke-dashoffset: 1;
  opacity: 0;
  animation-name: es-ink-draw;
  animation-duration: var(--dur, 200ms);
  animation-delay: var(--delay, 0ms);
  animation-timing-function: cubic-bezier(0.37, 0, 0.63, 1);
  animation-fill-mode: forwards;
}
@keyframes es-ink-draw {
  from {
    stroke-dashoffset: 1;
    opacity: 1;
  }
  to {
    stroke-dashoffset: 0;
    opacity: 1;
  }
}

/* Error ------------------------------------------------------------- */

.es-error {
  position: absolute;
  left: 0;
  top: 100%;
  margin: 0.75rem 0 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--es-subtle);
  animation: es-error-in 400ms var(--es-ease-expo) both;
}
@keyframes es-error-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
}

.es-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Reduced motion: ink appears at once, nothing slides. */
@media (prefers-reduced-motion: reduce) {
  .es-stroke {
    animation: none;
    stroke-dashoffset: 0;
    opacity: 1;
  }
  .es-arrow svg,
  .es-line-focus,
  .es-error {
    transition: none;
    animation: none;
  }
}

/* Site theme for the signup: same greys, lines, background and type as the rest of the page.
   The prompt reads like a section title, the field like body text. No arrow: Enter sends. */
.es {
  --es-prompt-size: 1.3rem;
  --es-field-size: 1.1rem;
  --es-ink: var(--ink);
  --es-muted: var(--faint);
  --es-subtle: var(--text);
  --es-line: var(--hairline);
  --es-line-strong: var(--line);
  --es-paper: var(--bg);
}
:root[data-theme="dark"] .es { --es-accent: #8ea0ff; }
.es .es-label { font-weight: 500; line-height: 1.4; letter-spacing: 0; color: var(--ink); }
.es .es-row { margin-top: 12px; }
.es .es-input { font-weight: 400; line-height: 1.5; letter-spacing: .014em; padding: 10px 0; }
.es .es-input::placeholder { color: var(--faint); }
.es .es-error { font-size: 14px; color: var(--text); }
/* Contact, reduced to one line + the field: the prompt reads like a section title, the
   LinkedIn / Email icons sit at the right end of the same line, grey until hovered */
.contact__head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.contact .es-label { margin: 0; }
.contact__links { display: flex; gap: 4px; margin: 0 -10px 0 0; padding: 0; list-style: none; }
.contact__links a { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 999px; color: var(--faint); transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease); -webkit-tap-highlight-color: transparent; }
.contact__links a:hover, .contact__links a:focus-visible { color: var(--ink); background: var(--surface); }
.contact__links a:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.contact__links svg { width: 19px; height: 19px; }
.contact .es-row { margin-top: 4px; }

/* Email field in the site's own language: no underline, a soft grey filled box with
   rounded corners like the case study rows, the cards and the NDA tag. It darkens a touch
   on hover and while typing. */
.es .es-line, .es .es-line-focus { display: none; }
.es .es-row { margin-top: 10px; }
.es .es-input {
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--surface);
  transition: background-color .3s var(--ease), opacity 220ms var(--es-ease-quart);
}
.es .es-input:hover { background: color-mix(in oklab, var(--ink) 5%, var(--bg)); }
.es .es-input:focus { background: color-mix(in oklab, var(--ink) 7%, var(--bg)); }
.es .es-input:-webkit-autofill { box-shadow: inset 0 0 0 100px var(--surface); }
/* While "Submitted :)" is written, the box stays; only the typed text fades */
.es:not([data-phase="idle"]) .es-input { opacity: 1; color: transparent; transition: color 160ms var(--es-ease-quart), background-color .3s var(--ease); }
.es:not([data-phase="idle"]) .es-input::placeholder { color: transparent; }
/* The hand-written "Submitted :)" starts where the typed text starts */
.es .es-ink { left: 14px; }
@supports (corner-shape: squircle) { .es .es-input { border-radius: 18px; corner-shape: squircle; } }
:root[data-theme="dark"] .nda-tag { background: color-mix(in oklab, var(--ink) 11%, var(--bg)); }

/* Theme switch on touch screens: every colour eases to the new theme at once (no snapshot) */
html.theme-fading, html.theme-fading *, html.theme-fading *::before, html.theme-fading *::after {
  transition-property: background-color, color, border-color, fill, stroke, box-shadow, opacity, transform, translate, filter !important;
  transition-duration: .4s !important;
  transition-timing-function: cubic-bezier(.32, .72, 0, 1) !important;
  transition-delay: 0s !important;
}

/* Small print: the note under the email field and the Privacy link in the footer */
.es-note { margin: 8px 0 0; color: var(--faint); font-size: 12px; line-height: 1.4; letter-spacing: .02em; }
.es-note a, .footer__privacy { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; transition: color var(--dur) var(--ease); }
.es-note a:hover, .footer__privacy:hover { color: var(--ink); }
.footer__privacy { margin-left: 10px; font-size: 12px; }

/* Privacy page */
.privacy h1 { margin-bottom: 32px; }
.privacy h2 { margin: 32px 0 6px; color: var(--ink); font-size: 1rem; font-weight: 500; }
.privacy p { margin: 0; color: var(--text); line-height: 1.6; }
.privacy p a { color: var(--ink); }
.privacy__back { margin-bottom: 48px !important; }
.privacy__back a { color: var(--faint) !important; text-decoration: none; }
.privacy__back a:hover { color: var(--ink) !important; }
.privacy .privacy__date { margin-top: 48px; color: var(--faint); font-size: 13px; }

/* Cover that is already a full picture: it fills the card edge to edge */
.cs-hero--full { padding: 0; overflow: hidden; }
.cs-hero--full img { width: 100%; aspect-ratio: auto; }
