/* ==========================================================================
   Case study pages
   Shares tokens.css with the homepage. The chrome (nav, buttons, footer) is
   restated here rather than imported, because the homepage keeps its styles
   inline; design-system.md is the contract both sides answer to.
   There is no per-page accent any more. Seven projects in seven different
   colours made seven different websites; the accent is one blue, everywhere.
   ========================================================================== */

/* ==========================================================================
   Cross-document view transitions
   The other half of the opt-in that index.html declares. With both pages
   opted in, the browser keeps the project's name alive across the navigation
   and tweens it from the row it was clicked in to the headline below.

   Unlike the homepage, the name can live in CSS here: a case study has
   exactly one h1, so it can never collide with a second element wearing the
   same name (which is the one thing that makes the browser drop a morph).
   ========================================================================== */
@view-transition { navigation: auto; }

.study h1 { view-transition-name: case-title; }

::view-transition-group(case-title) {
  animation-duration: 620ms;
  animation-timing-function: var(--ease-out, cubic-bezier(.22,.61,.36,1));
}
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 420ms; }

/* Arrive with #slow to inspect the curve at a fifth speed. The ARRIVING
   document owns the timing, so this has to live here, not on the page the
   click came from. The class is set by a one-line script in each page head. */
html.slow-mo::view-transition-group(*),
html.slow-mo::view-transition-old(*),
html.slow-mo::view-transition-new(*) { animation-duration: 3200ms !important; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0; overflow-x: hidden;
  background: var(--bg);
  font-family: var(--font-sans); font-size: var(--text-body);
  font-weight: var(--weight-medium); line-height: var(--lh-body);
  color: var(--ink-muted); font-feature-settings: var(--font-features);
  -webkit-font-smoothing: antialiased;
}
img, svg, video { display: block; max-width: 100%; }
a { color: inherit; }
h1, h2, h3, h4 {
  margin: 0; font-family: var(--font-display); font-weight: var(--weight-semibold);
  color: var(--ink); letter-spacing: var(--ls-h2); line-height: var(--lh-h2);
}
p { margin: 0; }

.container {
  width: 100%; max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto; padding-inline: var(--gutter);
}
.container-inner { max-width: calc(var(--container-inner) + var(--gutter) * 2); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: clip; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ---------- Nav ---------- */
.nav-wrap {
  position: fixed; top: 20px; left: 0; right: 0; z-index: var(--z-nav);
  display: flex; justify-content: center; padding: 0 var(--gutter); pointer-events: none;
}
.nav {
  pointer-events: auto; display: flex; align-items: center; gap: 0;
  width: auto; max-width: 100%; padding: 10px 10px 10px 22px;
  background: rgba(255,255,255,0.82);
  backdrop-filter: blur(18px) saturate(1.6); -webkit-backdrop-filter: blur(18px) saturate(1.6);
  border: 1px solid rgba(255,255,255,0.9); border-radius: var(--radius-pill);
  box-shadow: 0 10px 30px rgba(29,45,72,0.10), 0 2px 6px rgba(29,45,72,0.06);
}
.wordmark {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-display); font-size: 19px; font-weight: var(--weight-semibold);
  letter-spacing: -0.4px; color: var(--ink); text-decoration: none; white-space: nowrap;
  /* Padding, not size: a 24px wordmark is the smallest tap target in the
     nav and in the footer, and it is the way home from both. */
  padding-block: 10px; margin-block: -10px;
}
/* The mark and the mobile menu. Same components as index.html; kept in step
   with it by hand, the way every other rule in this file already is. */
/* NOT `.mark`: the hero headline already owns that class for the span that
   wraps "get" and carries its hand-drawn underline. Scoping a 30px circle and
   a white ring onto it collapsed the word into a badge with a ring around it.
   `.brandmark` is unambiguous.

   The mark is his own photograph, cropped to the head. A ring, not a border:
   the nav is translucent glass over a photograph, and a 1px inset border would
   read as a hairline box while a ring reads as a cut-out. */
.brandmark {
  flex: none; display: block; width: 30px; height: 30px;
  border-radius: var(--radius-circle); object-fit: cover;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.92), 0 1px 4px rgba(29,45,72,0.20);
  transition: transform var(--dur-3) var(--ease-out);
}
.wordmark:hover .brandmark { transform: scale(1.06); }
.nav-toggle {
  display: none; flex: none; place-items: center;
  width: 38px; height: 38px; margin-left: 6px; padding: 0;
  border: 0; border-radius: var(--radius-circle);
  background: var(--surface); cursor: pointer;
  transition: background var(--dur-3) var(--ease-out);
}
.nav-burger { display: grid; gap: 5px; }
.nav-burger i {
  display: block; width: 16px; height: 2px; border-radius: 2px;
  background: var(--ink);
  transition: transform var(--dur-3) var(--ease-out), background var(--dur-3) var(--ease-out);
}
.nav-toggle[aria-expanded="true"] { background: var(--ink); }
.nav-toggle[aria-expanded="true"] .nav-burger i { background: #FFFFFF; }
.nav-toggle[aria-expanded="true"] .nav-burger i:first-child { transform: translateY(3.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-burger i:last-child  { transform: translateY(-3.5px) rotate(-45deg); }
.nav-menu {
  pointer-events: auto;
  position: absolute; top: calc(100% + 8px); left: 0; right: 0;
  width: calc(100% - var(--gutter) * 2); max-width: 520px; margin-inline: auto;
  padding: 8px 8px 10px;
  background: rgba(255,255,255,0.86);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  border: 1px solid rgba(255,255,255,0.9);
  border-radius: 22px;
  box-shadow: 0 18px 44px rgba(29,45,72,0.16), 0 2px 6px rgba(29,45,72,0.06);
  opacity: 0; transform: translateY(-8px) scale(0.985); transform-origin: top center;
  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.nav-menu.open { opacity: 1; transform: none; }
.nav-menu-cta { display: none; }
.nav-menu ul { list-style: none; margin: 0; padding: 0; }
.nav-menu a {
  display: block; padding: 11px 14px; border-radius: 13px;
  font-family: var(--font-display); font-size: 17px; font-weight: var(--weight-semibold);
  line-height: 1.2; letter-spacing: -0.3px; color: var(--ink); text-decoration: none;
  transition: background var(--dur-2) var(--ease-standard);
}
.nav-menu a:active { background: rgba(29,45,72,0.06); }
.nav-menu-mail {
  display: block; margin: 6px 4px 0; padding-top: 11px; padding-bottom: 3px;
  border-top: 1px solid var(--hairline);
  font-family: var(--font-sans) !important; font-size: 14px !important;
  font-weight: var(--weight-medium) !important; color: var(--ink-muted) !important;
}
@supports not (backdrop-filter: blur(1px)) {
  .nav-menu { background: rgba(255,255,255,0.97); }
}
.nav-links { display: flex; align-items: center; gap: 34px; margin: 0 44px 0 56px; }
.nav-links a {
  position: relative; font-size: var(--text-small); font-weight: var(--weight-semibold);
  color: var(--ink-muted); text-decoration: none;
  transition: color var(--dur-2) var(--ease-standard);
}
.nav-links a:hover { color: var(--ink); }
.nav-cta {
  --dot-d: 22px;
  position: relative; display: inline-flex; align-items: center;
  height: 38px; padding: 0 38px 0 16px; border-radius: var(--radius-pill);
  background: var(--btn-dark-grad); box-shadow: var(--btn-dark-shadow);
  color: var(--ink-inverse); font-size: var(--text-caption); font-weight: var(--weight-semibold);
  text-decoration: none; white-space: nowrap; overflow: hidden;
}

/* ---------- Buttons (matches the homepage system) ---------- */
.btn {
  --dot-d: 40px;
  position: relative; display: inline-flex; align-items: center;
  height: 56px; padding: 0 62px 0 30px; border-radius: var(--radius-pill);
  font-size: var(--text-body); font-weight: var(--weight-semibold); line-height: var(--lh-body);
  text-decoration: none; white-space: nowrap;
  border: 1px solid transparent; overflow: hidden; cursor: pointer;
}
.btn-primary {
  background: var(--btn-primary-grad); border-color: var(--blue-light);
  color: var(--ink-inverse); box-shadow: var(--btn-primary-shadow);
}
.btn:focus-visible, .nav-cta:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.btn-label, .nav-cta-label { display: inline-block; transition: transform var(--dur-btn) var(--ease-btn); }
.btn:hover .btn-label, .nav-cta:hover .nav-cta-label { transform: translateX(var(--btn-label-shift)); }
.dot {
  position: absolute; top: var(--btn-icon-inset); bottom: var(--btn-icon-inset);
  width: auto; aspect-ratio: 1; display: grid; place-items: center;
  background: #fff; border-radius: var(--radius-circle);
  transition: transform var(--dur-btn) var(--ease-btn);
}
.dot svg { width: 14px; height: 14px; stroke: var(--ink); fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.dot--out { right: var(--btn-icon-inset); transform: rotate(0deg); }
.dot--in  { left: calc(-1 * var(--dot-d)); transform: rotate(calc(var(--btn-icon-spin) * -1)); }
.btn:hover .dot--out, .nav-cta:hover .dot--out { transform: translateX(calc(var(--dot-d) + var(--btn-icon-inset))) rotate(var(--btn-icon-spin)); }
.btn:hover .dot--in,  .nav-cta:hover .dot--in  { transform: translateX(calc(var(--dot-d) + var(--btn-icon-inset))) rotate(0deg); }

/* The vertical padding is the tap target, not decoration: on a phone the nav
   links are hidden and this back link is the main way out of an article, and
   at its natural 23px it was half the size a thumb needs. The negative margin
   keeps the text exactly where it was. */
.tlink {
  display: inline-flex; align-items: center; gap: 8px;
  padding-block: 11px; margin-block: -11px;
  font-size: var(--text-small); font-weight: var(--weight-semibold);
  color: var(--ink); text-decoration: none;
}
.tlink svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: transform var(--dur-3) var(--ease-out); }
.tlink:hover svg { transform: translateX(5px); }
.tlink.back:hover svg { transform: translateX(-5px); }

/* ---------- Reveals ---------- */
[data-reveal] {
  opacity: 0; transform: translateY(var(--reveal-y));
  transition: opacity var(--dur-5) var(--ease-out), transform var(--dur-5) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--reveal-stagger));
}
[data-reveal].in { opacity: 1; transform: none; }

/* ==========================================================================
   Study layout
   ========================================================================== */
.study { padding-top: 132px; }

/* Top bar: back out, and where you are in the set. Both always reachable. */
.study-top {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding-bottom: 30px;
}
.study-pos { font-size: var(--text-caption); font-weight: var(--weight-semibold); color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.study-pos b { color: var(--ink); }

.study-eyebrow {
  display: inline-flex; align-items: center;
  padding: 9px 18px; border-radius: var(--radius-pill);
  background: rgba(255,255,255,0.55);
  border: 1px solid rgba(255,255,255,0.85);
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: 0 2px 10px rgba(29,45,72,0.05);
  font-size: var(--text-caption); font-weight: var(--weight-medium);
  letter-spacing: -0.01em; color: var(--ink);
}

.study h1 {
  margin: 18px 0 0; max-width: 16ch;
  font-size: var(--text-h1); line-height: var(--lh-h1); letter-spacing: var(--ls-h1);
}
.study-sum {
  margin-top: 20px; max-width: 46ch;
  font-size: var(--text-lead); font-weight: var(--weight-medium); color: var(--ink-muted);
}

/* Project meta.
   An earlier four-cell credits strip died because its Client cell repeated
   the H1 and its Type cell repeated the eyebrow. This one only carries what
   the page hasn't said yet: the industry, the services, the year, and what
   shipped. Labels quiet, values firm, no boxes. */
.study-meta {
  display: flex; flex-wrap: wrap;
  justify-content: flex-start; gap: 18px 46px;
  margin: 28px 0 0; padding-top: 24px;
  border-top: 1px solid var(--hairline);
}
.study-meta div { min-width: 0; }
.study-meta dt {
  font-size: 12px; font-weight: var(--weight-semibold);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint);
}
.study-meta dd {
  margin: 7px 0 0;
  font-size: 15px; font-weight: var(--weight-semibold); color: var(--ink);
}
@media (max-width: 809px) {
  .study-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 24px; }
}

/* Hero film */
.study-film {
  position: relative; margin-top: 48px; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius-2xl); border: 1px solid var(--panel-border);
  background: #0B0C0F;
  box-shadow: 0 40px 90px rgba(20,40,70,0.20), 0 8px 24px rgba(20,40,70,0.10);
}
.study-film video, .study-film img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* ---------- Hosted film (Wistia) ----------
   A case study whose film is hosted uses the SAME .study-film frame as one
   with a local file, so the two are indistinguishable: same radius, border
   and shadow. The player fills the frame rather than sizing itself, because
   .study-film already owns the 16/9 aspect ratio.

   The :not(:defined) state is the load-in poster. Wistia serves a "swatch"
   thumbnail per media, and blurring it gives a blur-up while the custom
   element upgrades, which is why these pages drop .study-ph entirely. The
   media-specific background-image is set per page; everything reusable is
   here. padding-top is zeroed because Wistia's own snippet uses it to fake an
   aspect ratio that this frame already provides. */
.study-film wistia-player {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%;
}
.study-film wistia-player:not(:defined) {
  padding-top: 0;
  filter: blur(5px);
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
.study-ph {
  position: absolute; inset: 0; display: grid; place-content: center; justify-items: center; gap: 16px;
  background: radial-gradient(120% 130% at 28% 18%, #2C3644 0%, #10141A 72%); text-align: center;
}
.study-ph span {
  width: 68px; height: 68px; border-radius: var(--radius-circle);
  display: grid; place-items: center; background: rgba(255,255,255,0.14);
}
.study-ph span svg { width: 22px; height: 22px; fill: #fff; margin-left: 4px; }
.study-ph b { font-family: var(--font-display); font-size: 20px; color: rgba(255,255,255,0.94); }
.study-ph i { font-style: normal; font-size: 12px; color: rgba(255,255,255,0.42); }

/* ---------- Public company context ----------
   Sits between the film and the story: who this company is, in facts, before
   any of my own claims about the work. Everything in it is publicly reported. */
.company {
  margin-top: 64px; padding: 38px 42px;
  border-radius: var(--radius-2xl); background: #FFFFFF; border: 1px solid var(--hairline);
}
.company h2 {
  display: flex; align-items: center; gap: 10px;
  font-family: var(--font-sans); font-size: var(--text-small);
  font-weight: var(--weight-semibold); color: var(--ink-muted); letter-spacing: 0;
}
.company h2::before {
  content: ""; width: 8px; height: 8px; border-radius: 3px; flex: none;
  background: linear-gradient(150deg, var(--a), var(--b));
  box-shadow: 0 3px 9px var(--g);
}
.company-note {
  margin-top: 14px; max-width: 68ch;
  font-size: var(--text-body); font-weight: var(--weight-medium); color: var(--ink-muted);
}
.facts {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 26px 30px;
  margin-top: 32px; padding-top: 28px; border-top: 1px solid rgba(29,45,72,0.10);
}
.fact h3 {
  font-family: var(--font-sans); font-size: var(--text-caption);
  font-weight: var(--weight-semibold); color: var(--ink-muted); letter-spacing: 0;
}
.fact p { margin-top: 8px; font-size: var(--text-small); font-weight: var(--weight-semibold); color: var(--ink); }

/* The stand-back line */
.study-lede {
  margin-top: 76px; max-width: 30ch;
  font-family: var(--font-display); font-size: var(--text-h2-sm);
  font-weight: var(--weight-semibold); line-height: 1.22; letter-spacing: -0.6px; color: var(--ink);
}
/* Solid accent, not a gradient fill. See the note on .hl in index.html. */
.study-lede em { font-style: normal; color: var(--blue); }

/* Chapters.
   The headings are not labels ("The process") but statements that carry the
   story on their own: read only the h2s and you should still get the arc. */
.chapters { margin-top: 60px; border-top: 1px solid var(--hairline); }
.chapter {
  display: grid; grid-template-columns: 280px 1fr; gap: 40px;
  padding: 38px 0; border-bottom: 1px solid var(--hairline);
}
.chapter h2 {
  display: flex; align-items: flex-start; gap: 12px;
  font-family: var(--font-display); font-size: 20px;
  font-weight: var(--weight-semibold); color: var(--ink);
  letter-spacing: -0.3px; line-height: 1.3;
}
.chapter h2::before {
  content: ""; width: 8px; height: 8px; border-radius: 3px; flex: none;
  margin-top: 8px;
  background: var(--blue);
}
.chapter p { max-width: 62ch; font-size: var(--text-body); color: var(--ink-muted); }
/* A chapter that needs more than one paragraph wraps them in .chapter-body,
   so the grid still sees a single second column. */
.chapter-body { max-width: 62ch; min-width: 0; }
.chapter-body p + p { margin-top: 14px; }

/* ---------- The highlight layer ----------
   A second reading speed. Skim only the .mk phrases and the page still tells
   its story; that is the test for where one belongs. One or two per chapter,
   never more. `strong` is for names and terms; it lifts to ink, no wash. */
.mk {
  color: var(--ink); font-weight: var(--weight-semibold);
  background: linear-gradient(180deg, transparent 60%, #DBEAFD 60%);
  padding: 0 2px; margin: 0 -2px; border-radius: 2px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}
.chapter-body strong, .chapter p strong, .company-note strong, .media-sec > p strong {
  color: var(--ink); font-weight: var(--weight-semibold);
}

/* Results */
.results {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px;
  margin-top: 64px; padding-top: 40px; border-top: 1px solid var(--hairline);
}
.result b {
  display: block; font-family: var(--font-display); font-size: var(--text-stat);
  /* Not --lh-stat (0.72): bracketed placeholders like [$X]M clip at that. */
  font-weight: var(--weight-bold); line-height: 0.98; letter-spacing: -2px;
  color: var(--blue);
  /* Figures line up in a three-column row, so they need fixed-width digits. */
  font-variant-numeric: tabular-nums;
}
.result span { display: block; margin-top: 16px; font-size: var(--text-small); color: var(--ink-muted); }

/* Deliverables */
.delivered { margin-top: 60px; }
.delivered h2 { font-family: var(--font-sans); font-size: var(--text-small); font-weight: var(--weight-semibold); color: var(--ink-muted); }
.chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 0; padding: 0; list-style: none; }
.chips li {
  padding: 9px 16px; border-radius: var(--radius-pill);
  background: #FFFFFF; border: 1px solid var(--hairline); font-size: var(--text-small); font-weight: var(--weight-medium); color: var(--ink);
}

/* ---------- Media sections ----------
   A labelled group of films or embeds that sits between chapters. Same label
   grammar as .chapter h2 and .delivered h2, so the page keeps one voice. */
.media-sec { margin-top: 64px; }
.media-sec > h2 {
  display: flex; align-items: center; gap: 11px;
  font-family: var(--font-sans); font-size: var(--text-small);
  font-weight: var(--weight-semibold); color: var(--ink-muted); letter-spacing: 0;
}
.media-sec > h2::before {
  content: ""; width: 8px; height: 8px; border-radius: 3px; flex: none;
  background: var(--blue);
}
.media-sec > p { margin-top: 12px; max-width: 62ch; font-size: var(--text-body); color: var(--ink-muted); }

/* ---------- Film sets ----------
   Several films in one grid. Each frame is the same dark placeholder the hero
   uses; when a real clip exists, drop a <video> inside .mini-film exactly like
   the hero film and the placeholder sits behind it as the loading state. */
.film-set { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px 20px; margin-top: 26px; }
.film-set figure { margin: 0; min-width: 0; }
.mini-film {
  position: relative; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--radius-lg); border: 1px solid var(--panel-border);
  background: #0B0C0F; box-shadow: 0 14px 34px rgba(20,40,70,0.12);
}
.mini-film video, .mini-film img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mini-film .study-ph { gap: 10px; padding: 12px; }
.mini-film .study-ph span { width: 44px; height: 44px; }
.mini-film .study-ph span svg { width: 15px; height: 15px; margin-left: 3px; }
.mini-film .study-ph b { font-size: 15px; }
.mini-film .study-ph i { font-size: 11px; }
.mini-film--tall { aspect-ratio: 9 / 16; }
.film-run {
  position: absolute; right: 10px; bottom: 10px; z-index: 1;
  padding: 4px 10px; border-radius: var(--radius-pill);
  background: rgba(10,12,16,0.62); border: 1px solid rgba(255,255,255,0.16);
  font-size: 12px; font-weight: var(--weight-semibold); color: rgba(255,255,255,0.92);
  font-variant-numeric: tabular-nums;
}
.film-set figcaption, .film-cap { display: block; margin-top: 12px; font-size: var(--text-caption); line-height: 1.5; color: var(--ink-muted); }
.film-set figcaption b, .film-cap b {
  display: block; margin-bottom: 3px;
  font-size: var(--text-small); font-weight: var(--weight-semibold); color: var(--ink);
}

/* ---------- Clickable film cards ----------
   Real <button>s so the keyboard gets them. The card opens the film modal;
   study.js reads the data-* attributes. Hover follows the site rule, an
   inversion, not a lift: the frame's border darkens and the play badge goes
   brand blue. */
.film-card {
  display: block; width: 100%; margin: 0; padding: 0; border: 0;
  background: none; text-align: left; cursor: pointer;
  font: inherit; color: inherit; min-width: 0;
}
.film-card:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; border-radius: var(--radius-lg); }
.film-card .mini-film { display: block; transition: border-color var(--dur-2) var(--ease-standard); }
.film-card:hover .mini-film { border-color: #6B7684; }
.play-badge {
  position: absolute; left: 10px; bottom: 10px; z-index: 1;
  width: 34px; height: 34px; border-radius: var(--radius-circle);
  display: grid; place-items: center;
  background: rgba(10,12,16,0.62); border: 1px solid rgba(255,255,255,0.16);
  transition: background var(--dur-2) var(--ease-standard), border-color var(--dur-2) var(--ease-standard);
}
.play-badge svg { width: 12px; height: 12px; fill: #fff; margin-left: 2px; }
.film-card:hover .play-badge { background: var(--blue); border-color: var(--blue); }

/* ---------- The film modal ----------
   One native <dialog> per page, created by study.js. Poster-only until the
   web file exists at data-src; then the same click plays the real film. */
dialog.film-modal {
  width: min(920px, calc(100vw - 32px)); max-height: calc(100dvh - 40px);
  margin: auto; padding: 0; border: 0; border-radius: var(--radius-2xl);
  background: #FFFFFF; box-shadow: 0 60px 120px rgba(9,9,17,0.35);
  overflow: hidden;
}
dialog.film-modal.film-modal--tall { width: min(430px, calc(100vw - 32px)); }
dialog.film-modal::backdrop {
  background: rgba(9,12,18,0.6);
  -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
}
dialog.film-modal[open] { animation: fm-in 240ms var(--ease-out); }
@keyframes fm-in { from { opacity: 0; transform: translateY(14px); } }
.fm-media { position: relative; aspect-ratio: 16 / 9; background: #0B0C0F; }
.film-modal--tall .fm-media { aspect-ratio: 9 / 16; }
.fm-media video, .fm-media img { position: absolute; inset: 0; width: 100%; height: 100%; }
/* A hosted film inside the modal fills it the same way a local one does. */
.fm-media wistia-player { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
.fm-media video { object-fit: contain; }
.fm-media img { object-fit: cover; }
.fm-close {
  position: absolute; top: 14px; right: 14px; z-index: 2;
  width: 38px; height: 38px; border-radius: var(--radius-circle);
  display: grid; place-items: center; cursor: pointer;
  background: rgba(10,12,16,0.55); border: 1px solid rgba(255,255,255,0.2); color: #fff;
  transition: background var(--dur-2) var(--ease-standard), color var(--dur-2) var(--ease-standard);
}
.fm-close:hover { background: #fff; color: var(--ink); }
.fm-close svg { width: 14px; height: 14px; stroke: currentColor; stroke-width: 2.4; fill: none; stroke-linecap: round; }
.fm-close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.fm-body { padding: 20px 26px 26px; }
.fm-kicker {
  font-size: 11px; font-weight: var(--weight-semibold);
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint);
}
.fm-title { margin-top: 6px; font-family: var(--font-display); font-size: var(--text-h4); letter-spacing: var(--ls-h4); color: var(--ink); }
.fm-desc { margin-top: 8px; max-width: 62ch; font-size: var(--text-small); color: var(--ink-muted); }
.fm-note { margin-top: 12px; font-size: 12.5px; color: var(--ink-faint); }
.fm-note[hidden] { display: none; }
@media (max-width: 809px) {
  .fm-body { padding: 16px 18px 20px; }
}
@media (prefers-reduced-motion: reduce) {
  dialog.film-modal[open] { animation: none; }
}

/* ---------- The deliverable, as one line ----------
   Replaces the chip inventory on single-film projects, where five chips made
   one video read as five deliverables. */
.deliv-one b {
  display: block; font-family: var(--font-display); font-size: var(--text-h4);
  letter-spacing: var(--ls-h4); color: var(--ink); margin-top: 14px;
}
.deliv-one span { display: block; margin-top: 8px; max-width: 62ch; font-size: var(--text-small); color: var(--ink-muted); }

/* ---------- Social embed slots ----------
   Dashed border on purpose: these are declared placeholders, not fake posts.
   Each gets replaced by a real platform embed (blockquote + platform script),
   and the dashed frame disappears with it. */
.embed-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 26px; }
.embed {
  display: flex; flex-direction: column; gap: 14px;
  padding: 22px; border-radius: var(--radius-xl);
  background: #FFFFFF; border: 1px dashed #AFBECB;
}
.embed-top { display: flex; align-items: center; gap: 12px; }
.embed-av { width: 40px; height: 40px; flex: none; border-radius: var(--radius-circle); background: var(--card-grad); border: 1px solid var(--hairline); }
.embed-id { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.embed-id i { display: block; height: 9px; border-radius: 5px; background: var(--surface); }
.embed-id i:first-child { width: 46%; }
.embed-id i:last-child { width: 30%; }
.embed-mark {
  margin-left: auto; flex: none;
  font-family: var(--font-display); font-size: 15px;
  font-weight: var(--weight-semibold); color: #9AA8B5;
}
.embed-media {
  position: relative; aspect-ratio: 16 / 10; border-radius: var(--radius-md); overflow: hidden;
  background: radial-gradient(120% 130% at 28% 18%, #2C3644 0%, #10141A 72%);
  display: grid; place-content: center; justify-items: center;
}
.embed-media span {
  width: 44px; height: 44px; border-radius: var(--radius-circle);
  display: grid; place-items: center; background: rgba(255,255,255,0.14);
}
.embed-media span svg { width: 15px; height: 15px; fill: #fff; margin-left: 3px; }
/* The two parent posts end and this begins; without a gap they read as one
   run of embeds rather than "here are the films" then "here is what came back". */
.reactions { margin-top: 52px; }
.embed-note { font-size: var(--text-caption); font-weight: var(--weight-semibold); color: var(--ink); }
.embed-sub { font-size: 12.5px; line-height: 1.45; color: var(--ink-faint); }
.embed--wide { max-width: 560px; }

/* ---------- Real LinkedIn embed ----------
   LinkedIn gives you an iframe, not a script widget, and it does NOT report
   its height back, so the height has to be set here and it changes with width:
   the post is 1124px tall at 700px wide but 1411px at 330px, because the text
   rewraps. Measured at each breakpoint rather than guessed. Overshooting is
   safe (LinkedIn's card sits on white, so spare height reads as plain
   whitespace); undershooting clips the reaction count off the bottom, which is
   the whole reason the post is here. */
/* Shown at natural size, with the MIDDLE removed rather than the bottom.
   Cropping from the top kept all eight paragraphs and cut off the film, which
   is backwards: the film is the thing worth seeing. Scaling the whole embed
   down instead shrank LinkedIn's body copy to ~10px.

   So the post is windowed. Two bands of the same embed sit stacked, each
   clipping a different slice of one 1140px-tall page, with the long middle of
   the text dropped between them:

       band 1   y    0-185   author, LinkedIn chrome, the announcement line
       (cut)                 the six paragraphs nobody reads in a portfolio
       band 2   y  627-1090  the film, and the reaction bar under it

   Geometry measured off the live embed at 700px wide, not guessed. If the
   post is ever edited the offsets move, so re-measure rather than nudge.

   The cost is honest: the same embed loads twice. Both are lazy and well
   below the fold, and it is one component on one page. */
.li-embed-wrap {
  width: 100%; max-width: 700px;
}
.li-band { position: relative; overflow: hidden; }
.li-band--head { height: 185px; }
.li-band--body { height: 463px; }
.li-band iframe {
  position: absolute; left: 0; width: 100%; height: 1140px; border: 0; display: block;
}
.li-band--head iframe { top: 0; }
.li-band--body iframe { top: -627px; }

/* The join. Without a mark here the text simply jumps and reads as a glitch;
   the ellipsis says the post was trimmed on purpose. Borders continue the
   LinkedIn card's own edge so the two bands read as one object. */
.li-cut {
  border-left: 1px solid #E5E7EB; border-right: 1px solid #E5E7EB;
  background: #FFFFFF; text-align: center;
  font-size: 15px; letter-spacing: 0.3em; color: #9CA3AF;
  padding: 2px 0 8px;
}
.li-more {
  display: inline-flex; align-items: center; gap: 7px; margin-top: 14px;
  font-size: var(--text-small); font-weight: var(--weight-semibold);
  color: var(--ink); text-decoration: none;
  /* 15px type on a 1.5 leading is a 23px box, one short of WCAG 2.2's 24px
     target minimum. Padding buys the target, the negative margin gives back
     the layout, the same trick `.tlink` uses. */
  padding-block: 11px; margin-block: 3px -11px;
}
.li-more svg {
  width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform var(--dur-3) var(--ease-out);
}
.li-more:hover svg { transform: translateX(4px); }
/* Below ~740px the embed stops being 700 wide and starts narrowing, and the
   text rewraps continuously as it does: measured at 350px the film has moved
   from y=627 to y=1068 and the page from 1124 to 1368 tall. Fixed offsets
   cannot track that, and a wrong offset windows onto the middle of a
   paragraph instead of the film.

   So narrow screens do not get windowed at all. They get the whole post at
   natural size, which is the right trade on a phone: vertical space is cheap
   when you are already scrolling, and legibility is not. */
@media (max-width: 739px) {
  .li-band--head, .li-band--head iframe { height: 1400px; }
  .li-band--head iframe { position: relative; }
  .li-cut, .li-band--body { display: none; }
}

/* ---------- Real X embeds ----------
   Two posts sit wider than the three-up placeholder row did. X renders a tweet
   at up to 550px and these carry video, so a ~340px column starved them.
   The blockquote is only what shows for the instant before widgets.js swaps in
   its iframe, so it gets a quiet card rather than raw quoted text. Once the
   widget upgrades, X owns the styling and these rules stop applying. */
.embed-row--two { grid-template-columns: repeat(2, 1fr); }
.embed-row blockquote.twitter-tweet {
  margin: 0; padding: 22px 24px; border-radius: var(--radius-xl);
  background: #FFFFFF; border: 1px solid var(--hairline);
  font-size: var(--text-small); line-height: var(--lh-body); color: var(--ink-muted);
}
.embed-row blockquote.twitter-tweet p { margin: 0; }
.embed-row blockquote.twitter-tweet a { color: var(--blue); }
/* Their iframe arrives with its own chrome; centre it in the grid cell. */
.embed-row .twitter-tweet-rendered { margin: 0 auto !important; }

/* ---------- Cropping the embed action bar ----------
   X renders the whole card inside a cross-origin iframe, so nothing here can
   reach the like count: no CSS, no script, and there is no widget option for
   it (data-conversation only hides a parent thread; it leaves the bar alone).
   The one thing this side of the boundary CAN do is stop showing the bottom of
   the frame.

   So the wrapper the widget creates gets a height, `overflow: hidden`, and its
   own bottom edge redrawn. What goes is the hairline, the heart and its count,
   Reply, Copy link, and the "Read N replies" button. What stays is the author,
   the post, the media and the timestamp, all still X's own render of the real
   post.

   The two crop numbers were measured off 1:1 screenshots by finding the
   hairline above the action bar: 90px on a full post, 85px on a reply. If X
   changes that bar, these are what to re-measure, and a sliver of pink at the
   bottom of a card is the tell that they have drifted. */
.embed-row .twitter-tweet-rendered {
  overflow: hidden;
  border-radius: 12px;
  border-bottom: 1px solid #CFD9DE;
  box-sizing: content-box;
}


/* ---------- Reply embeds ----------
   These are replies, not posts, so they are short: three across rather than the
   two the parent posts get. X will not render a tweet below about 250px, so
   this drops to two columns and then one before it gets there. `align-items:
   start` keeps a taller card from stretching its neighbours. */
.embed-row--replies {
  grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start;
}
.embed-row--replies .twitter-tweet-rendered { margin: 0 auto !important; }
@media (max-width: 1199px) { .embed-row--replies { grid-template-columns: repeat(2, 1fr); } }
/* The cell is what the grid sizes; the card inside it is scaled by study.js's
   clip() when the cell is narrower than X's 300px floor. */
/* `overflow: hidden` is load-bearing, not tidiness. Below 300px of cell the
   card is BUILT at 300px and scaled down, and a transform does not change the
   layout box: the wrapper is still 300px wide in layout, so in a 170px cell it
   sticks 130px out and can widen the page. Clipping the cell contains it. */
.embed-cell { min-width: 0; overflow: hidden; }
.embed-cell > .twitter-tweet-rendered { transform-origin: top left; }

/* While the widget builds, the raw blockquote is what shows. It is styled as a
   quiet card so it never looks broken, but its height is not the height of the
   finished embed, and the difference is the jump you see. Reserving a floor
   keeps the row roughly the right size from the first paint; the script clears
   it the moment the real card lands and sets an exact height. */
.embed-cell.is-rendered { min-height: 0 !important; }



@media (max-width: 809px) {
}

@media (max-width: 1199px) {
  .film-set { grid-template-columns: repeat(2, 1fr); }
  .film-set--social { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 809px) {
  .film-set { grid-template-columns: 1fr; }
  .film-set--social { grid-template-columns: repeat(2, 1fr); gap: 14px; }
  .media-sec { margin-top: 48px; }
}

/* ---------- Prev / next ----------
   Two-up and equal weight. The original only ever offered "next", which
   assumes the reader agrees with your ordering. */
.pager { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 96px; }
/* Both ends of the set carry a placeholder instead of a link, and it is a
   <span>, not an <a>. It has to be styled alongside the links or it drops out
   of the flex column entirely: the name and the kind end up on one line,
   overlapping, with no card around them. */
.pager a, .pager > span {
  position: relative; display: flex; flex-direction: column; gap: 8px; overflow: hidden;
  padding: 30px 28px; border-radius: var(--radius-xl);
  background: #FFFFFF; border: 1px solid var(--hairline); text-decoration: none;
  transition: background var(--dur-3) var(--ease-standard), transform var(--dur-4) var(--ease-out);
}
.pager a:hover { border-color: #C6D3DE; }
.pager a:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.pager .dir {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--text-caption); font-weight: var(--weight-semibold); color: var(--ink-muted);
}
.pager .dir svg { width: 14px; height: 14px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; transition: transform var(--dur-3) var(--ease-out); }
.pager .name { font-family: var(--font-display); font-size: var(--text-h3); font-weight: var(--weight-semibold); letter-spacing: -0.6px; color: var(--ink); }
.pager .kind { font-size: var(--text-small); color: var(--ink-muted); }
.pager .prev { align-items: flex-start; text-align: left; }
.pager .next { align-items: flex-end; text-align: right; }
.pager .prev:hover .dir svg { transform: translateX(-5px); }
.pager .next:hover .dir svg { transform: translateX(5px); }
.pager .disabled { opacity: 0.4; pointer-events: none; }

/* CTA */
.study-cta {
  display: flex; align-items: center; justify-content: space-between; gap: 30px; flex-wrap: wrap;
  margin: 84px 0 0; padding: 44px 48px; border-radius: var(--radius-2xl);
  background: linear-gradient(150deg, var(--dark-grad-start) 0%, var(--dark-grad-end) 100%);
}
.study-cta h2 { color: #fff; font-size: var(--text-h3); max-width: 22ch; }
.study-cta p { margin-top: 10px; font-size: var(--text-small); color: rgba(255,255,255,0.62); }

/* Footer */
.study-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  margin-top: 70px; padding: 30px 0 60px; border-top: 1px solid var(--hairline);
  font-size: var(--text-small); color: var(--ink-muted);
}

/* ---------- Responsive ---------- */
@media (max-width: 1199px) {
  .facts { grid-template-columns: repeat(2, 1fr); }
  .nav-links { display: none; }
  .nav-cta { margin-left: 44px; }
  .study { padding-top: 116px; }
  .study h1 { font-size: var(--text-h2); }
  .chapter { grid-template-columns: 1fr; gap: 14px; }
}
@media (max-width: 809px) {
  /* Two across all the way down. Below about 660px there is not room for two
     300px cards, so each one is scaled into its cell instead. */
  .embed-row, .embed-row--two, .embed-row--replies {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; align-items: start;
  }
  /* `margin: 0 auto` would centre a 300px card inside a 170px cell before the
     scale is applied, pushing it 65px left of where the transform expects it. */
  .embed-row .twitter-tweet-rendered,
  .embed-row--replies .twitter-tweet-rendered { margin: 0 !important; }
  /* Measured heights of the finished cards at this width, so the row is close
     to right before the widget has said anything. */
  .embed-row--two .embed-cell     { min-height: 250px; }
  .embed-row--replies .embed-cell { min-height: 100px; }
  .reactions { margin-top: 40px; }
  .nav-wrap { top: 12px; padding: 0 16px; }
  .nav-menu { width: calc(100% - 32px); }
  .nav { width: 100%; max-width: 520px; padding: 8px; gap: 6px; }
  .wordmark { font-size: 15px; gap: 8px; padding-left: 2px; }
  .brandmark { width: 28px; height: 28px; }
  /* The pill holds the mark, the name and the menu, and that is enough at this
     width. The booking link moves into the panel, where it can be a full-width
     button instead of a 124px pill competing with the wordmark. */
  .nav-cta { display: none; }
  /* `.nav-menu a` is class+element (0,1,1) and outranks a bare `.nav-menu-cta`
     (0,1,0), so the panel's own link styling was painting this label near-black
     on a near-black pill and rounding the pill to 13px. Same trap as the footer
     button. */
  .nav-menu a.nav-menu-cta {
    display: flex; --dot-d: 32px;
    width: calc(100% - 8px); height: 48px; margin: 12px 4px 0;
    padding: 0 48px 0 18px; border-radius: var(--radius-pill);
    font-family: var(--font-sans); font-size: 15px; font-weight: var(--weight-semibold);
    color: var(--ink-inverse);
  }
  .nav-toggle { display: grid; width: 44px; height: 44px; margin-left: auto; }
  .study { padding-top: 104px; }
  .study h1 { font-size: var(--text-h2-sm); }
  .company { padding: 26px 22px; margin-top: 48px; }
  .facts { grid-template-columns: 1fr 1fr; gap: 22px; }
  .study-lede { font-size: var(--text-h3); margin-top: 54px; }
  .results { grid-template-columns: 1fr; gap: 26px; }
  .result b { font-size: 62px; }
  /* Side by side. Stacked, two cards of 30px padding around a 24px project
     name ran 333px for what is a back and a forward. The name drops to the h4
     token so the longest of the seven ("TES Affiliate Conferences") still sits
     inside a 169px card, and the kind line goes because at that width it was
     three lines of grey under a name that already says which project it is. */
  .pager { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; margin-top: 64px; }
  .pager a, .pager > span { padding: 16px 15px 17px; gap: 6px; border-radius: var(--radius-lg); }
  .pager .next { align-items: flex-start; text-align: left; }
  .pager .dir { font-size: 12px; gap: 6px; }
  .pager .dir svg { width: 13px; height: 13px; }
  .pager .name { font-size: var(--text-h4); letter-spacing: -0.3px; line-height: 1.2; }
  .pager .kind { display: none; }
  .study-cta { padding: 32px 26px; }
  .btn { width: 100%; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  [data-reveal] { opacity: 1; transform: none; }
  .pager a, .tlink svg, .pager .dir svg { transition: none; }
}

/* Below this the pill cannot hold the spelt-out name as well as the CTA and
   the menu button, so the mark carries the identity on its own. */
@media (max-width: 379px) {
  .wordmark-name { display: none; }
}

/* Under 360px a cell is 155px, which would scale a 300px card to 0.52 and put
   X's 15px body text under 8px. One column instead. This has to sit after the
   809px block, which is where the two-column rule lives. */
@media (max-width: 359px) {
  .embed-row, .embed-row--two, .embed-row--replies { grid-template-columns: minmax(0, 1fr); }
}
