/* ============================================================================
   SECTIONS — components for the spine.
   Left-aligned by default (DDB p.28). One accent only (both rulebooks).
   Square by default (SAB). Nothing here introduces a second colour.
   ============================================================================ */

.section > .inner > .t-eyebrow { display: block; margin-bottom: var(--space-sm); }
.section > .inner > .t-h2      { max-width: 20ch; }
.measure                       { max-width: 62ch; margin-top: var(--space-md); }

/* two-up layout used by the centenary block */
.split {
  display: grid; gap: var(--space-2xl);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  align-items: center;
}
.split img { width: 100%; border-radius: var(--radius-sm); }

/* ---- tinted section frames ----
   A wash of the section's DDB colour plus a hairline of it at full strength on
   the top edge. The tint is deliberately near-invisible — at 6% it reads as
   "this page has colour in it" rather than as a coloured panel, and white type
   grounds and SAB red both still behave normally on top of it.

   NEVER tint with DDB yellow. Yellow desaturates to cream long before it reads
   as colour, so at these percentages it looks like dirty white rather than a
   deliberate choice. Yellow earns its place at full strength only — the bands,
   the buttons, the mark. Tints use green, blue and grey. */
.section--tint {
  background: color-mix(in srgb, var(--sec, var(--sab-grey-100)) 22%, white);
  border-top: 3px solid var(--sec, var(--border-soft));
}

/* ---- the mantra, opposite the mark ----
   The fanned lockup is a supplied asset (img/ddb-b-fan.png), a transparent
   cutout — so it needs `contain`, not `cover`, or the fan gets clipped. */
.mantra-split {
  display: grid; gap: var(--space-2xl);
  grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
  align-items: center;
}
.mantra-mark {
  width: 100%; height: auto; max-height: 460px;
  object-fit: contain; object-position: center;
  justify-self: end;
}
/* below two columns the mark would squash the copy — it steps down and out */
@media (max-width: 860px) {
  .mantra-split { grid-template-columns: 1fr; }
  .mantra-mark { max-height: 280px; justify-self: start; }
}

/* ---- stats ---- */
.stat-row {
  margin-top: var(--space-2xl);
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  border-top: 1px solid var(--border-soft);
  padding-top: var(--space-lg);
}
.stat-value {
  font-size: clamp(30px, 3.4vw, 44px); font-weight: 700;
  line-height: 1.05; letter-spacing: -.02em;
}
.stat p { margin-top: 10px; }
/* One stat carries more weight than the rest — it spans the row and sits on the
   accent, so it reads as the answer to "why you, here". Only ever one. */
.stat--wide {
  grid-column: 1 / -1;
  display: flex; align-items: baseline; gap: var(--space-md); flex-wrap: wrap;
  margin-top: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  background: var(--accent); color: #fff;
}
.stat--wide .stat-value { color: #fff; }
.stat--wide p { margin-top: 0; color: rgba(255,255,255,.86); }

/* ---- methodology ---- */
/* <ol>, so list-style must be cleared explicitly — the base reset only does
   <ul>, which is what produced "6. 06" next to the step number. */
.steps {
  margin-top: var(--space-2xl);
  display: grid; gap: 0; list-style: none; padding: 0;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.step {
  position: relative;
  padding: var(--space-lg) var(--space-md) var(--space-xl) 0;
  border-top: 2px solid var(--border-soft);
  margin-right: var(--space-md);
}
/* The rule is the sequence made visible: it wipes in from the left on reveal,
   staggered by js/site.js, so the seven read as an order rather than a grid.
   clip-path, not width — width would relayout on every frame. */
.step::before {
  content: ""; position: absolute; inset: -2px 0 auto 0; height: 2px;
  background: var(--accent);
  clip-path: inset(0 100% 0 0);
  transition: clip-path 520ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.step.is-in::before { clip-path: inset(0 0 0 0); }

/* the number carries the sequence — set large and pale so it reads as position,
   not as a label competing with the stage name */
.step-n {
  display: block; margin-bottom: 6px;
  font-size: 30px; font-weight: 700; line-height: 1;
  letter-spacing: -.03em;
  color: color-mix(in srgb, var(--accent) 26%, transparent);
  transition: color 200ms ease;
}
.step h3 { margin-bottom: 8px; }

/* Hover is for scanning, not decoration: it tells you which column your eye is
   in across a dense seven-part grid. Gated — on touch this would fire on tap. */
@media (hover: hover) and (pointer: fine) {
  .step:hover .step-n { color: var(--accent); }
}

/* 07 is the payoff, not a seventh equal step. It spans the row and sits on the
   accent, which also resolves the orphaned two-card row the grid was leaving. */
.step--payoff {
  grid-column: 1 / -1;
  margin-right: 0; margin-top: var(--space-lg);
  padding: var(--space-lg);
  border-top: 0;
  background: var(--accent); color: #fff;
}
.step--payoff::before { display: none; }
.step--payoff .step-n { color: rgba(255,255,255,.55); }
.step--payoff h3 { font-size: clamp(24px, 2.6vw, 34px); }
.step--payoff .t-small { color: rgba(255,255,255,.82); max-width: 52ch; }

/* ---- people ---- */
.people-grid {
  margin-top: var(--space-2xl);
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.person img, .person-ph {
  width: 100%; aspect-ratio: 3 / 4; object-fit: cover;
  background: var(--bg-surface); border-radius: var(--radius-sm);
}
.person figcaption { margin-top: 12px; font-size: 15px; }
.person figcaption b { display: block; font-weight: 700; }
.person figcaption span { color: var(--text-muted); font-size: 13px; }

/* placeholder note while real content is outstanding */
.awaiting {
  margin-top: var(--space-xl); padding: var(--space-md);
  border: 1px dashed var(--border-default); color: var(--text-muted);
}

/* ---- work ---- */
.case-grid {
  margin-top: var(--space-2xl);
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
/* Plain rectangle. The B-shaped graphic device (guidelines p.39–45) was tried
   here and rejected — the DDB presence comes from colour, not from shape. */
.case-media {
  display: block; position: relative;
  width: 100%; aspect-ratio: 16 / 10;
  /* the DDB colour sits under the still, so it shows while the image loads and
     as a hairline along the top of every tile */
  background: var(--ddb, var(--sab-grey-150));
  border-top: 5px solid var(--ddb, var(--border-soft));
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-md);
  overflow: hidden;
}
.case-media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
}
/* client name sits behind a chip of the tile colour, so the palette reads down
   the whole grid and not just along the top edge */
.case-client {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 700;
  color: var(--text-primary); margin-bottom: 6px;
}
.case-client::before {
  content: ""; flex: none; width: 12px; height: 12px;
  background: var(--ddb, var(--accent));
}
.case h3 { margin-bottom: 8px; }
.case-tag {
  display: inline-block; margin-top: 12px;
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  border: 1px solid var(--border-default);
  padding: 4px 10px; border-radius: var(--radius-0);
}

/* ---- solid CTA ----
   SAB's own button: filled accent, square, white bold label, no arrow. It is
   the one place on the site that carries a red fill, so it has to stay rare —
   two on the page body, one per person card. Adding a third use would turn
   the red from "act here" into decoration. */
.btn-solid {
  display: inline-block; align-self: start;
  padding: 13px 22px;
  background: var(--accent); color: #fff;
  font-size: 14px; font-weight: 700; line-height: 1;
  border: 0; border-radius: var(--radius-0);
  transition: background var(--dur-fast) var(--ease-out),
              transform 160ms var(--ease-out);
}
.btn-solid--sm { padding: 10px 16px; font-size: 13px; }
@media (hover: hover) and (pointer: fine) {
  .btn-solid:hover { background: var(--sab-red-logo); }
}
.btn-solid:active { transform: scale(.97); }

/* In the three DDB-coloured sections the button takes its block's colour
   instead of SAB red. The palette is pale, so the label flips to DDB black —
   white on any of these fails contrast badly. Red is not lost: it still owns
   every CTA outside these sections, and every link inside them. */
.btn-solid--ddb {
  background: var(--ddb, var(--accent));
  color: var(--ddb-black);
}
@media (hover: hover) and (pointer: fine) {
  /* darken the block's own colour rather than jumping to a different hue */
  .btn-solid--ddb:hover {
    background: color-mix(in srgb, var(--ddb, var(--accent)) 82%, var(--ddb-black));
  }
}

/* ============================================================================
   SECTION DIVIDER — full bleed, the word set as large as it will go.
   Modelled on ddb.com's own page breaks. The ground is a soft wash built from
   ONE palette colour (p.18) against white, so nothing outside the palette is
   introduced and each divider stays keyed to its section's colour.
   Marked aria-hidden: the real <h2> follows immediately, and a screen reader
   should not hear the section named twice.
   ============================================================================ */
.sec-divider {
  position: relative; isolation: isolate;
  display: grid; place-items: center;
  min-height: min(68vh, 660px);
  padding: var(--space-2xl) 0;
  overflow: hidden;
  /* The mesh was five stacked radial gradients computed live across a
     full-bleed 68vh band, five times down the page. A Safari timeline off an
     iPhone showed Paint dense throughout a scroll, and the site was smoother
     on newer hardware than old — the signature of a fill-rate cost, not a
     script one. Each mesh is now rendered once and shipped as a 4 KB webp, so
     repainting is a bitmap blit. They are soft gradients, so a 760px image
     scaled across any viewport is indistinguishable from the original.
     Regenerate rather than hand-edit: the source gradients are recorded in
     the commit that introduced these files. */
  background: var(--mesh, url("../img/divider-0.webp")) center / cover no-repeat, #fff;
}
.sec-divider--v0 { --mesh: url("../img/divider-0.webp"); }
.sec-divider--v1 { --mesh: url("../img/divider-1.webp"); }
.sec-divider--v2 { --mesh: url("../img/divider-2.webp"); }
.sec-divider--v3 { --mesh: url("../img/divider-3.webp"); }

.sec-divider::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  pointer-events: none; opacity: .95;
  /* Coarse and heavy — the reference reads as printed grain, not a faint
     dither. A finer baseFrequency disappears the moment the page is scaled.

     This WAS a live feTurbulence filter in an SVG data URI, and that is why
     the site hitched on the first scroll after every change of direction:
     feTurbulence generates Perlin noise, and the browser re-ran that
     generation every time it re-rastered the tile — across five full-bleed
     68vh bands. Reversing direction discards and re-rasters tiles, so the
     cost landed exactly on that first frame.

     img/grain-160.png is that same filter, rendered once and baked. Same
     grain, no per-paint work. This is the rule HANDOFF.md already states for
     the hero ("grain is a static PNG") — it simply had not been applied here.
     Do not put a live filter back. */
  background-image: url("../img/grain-160.png");
}
.sec-divider-word {
  position: relative;
  /* ONE line, edge to edge. Whichever is smaller: the display ceiling, or the
     size at which the whole string still fits the width. 0.60em is about the
     average advance of this grotesk at 700 weight, spaces included.
     94vw, not var(--gutter): that token is a clamp(), and multiplying a
     clamp() inside calc() collapses the whole declaration to 0 here. */
  font-size: max(36px, min(
    calc(84vw / (var(--chars, 6) * 0.60)),   /* their fill ratio, whatever the length */
    56vh                                      /* unless that would out-grow the band */
  ));
  font-weight: 700; line-height: .9; letter-spacing: -.045em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--ddb-black);
  text-align: center;
}

/* ---- the mantra divider ----
   DDB's own saturated wash, not the p.18 palette. It exists once, for the
   mantra, which is the one moment the agency speaks at full volume. The other
   dividers stay on the guidelines' pale colours so this one keeps its weight. */
.sec-divider--hot { --mesh: url("../img/divider-hot.webp"); }
.sec-divider--hot .sec-divider-word {
  /* DDB black, same as every other divider. The ground is what makes this one
     the loud moment — the type stays consistent across all four. */
  color: var(--ddb-black);
}

/* ---- scope ---- */
.scope-list {
  margin-top: var(--space-2xl);
  display: grid; gap: 0;
  border-top: 1px solid var(--border-soft);
}
.scope-item {
  display: grid; gap: 6px;
  grid-template-columns: minmax(200px, 1fr) 2fr;
  padding-block: var(--space-md);
  border-bottom: 1px solid var(--border-soft);
}
.scope-item h3 { font-weight: 700; }
@media (max-width: 720px) { .scope-item { grid-template-columns: 1fr; } }

/* ---- pitch + footer ---- */
.pitch-band { background: var(--bg-invert); color: var(--text-inverse); }
.pitch-band .t-h2 { color: var(--text-inverse); }
.pitch-band .t-body { color: rgba(255,255,255,.66); }
.pitch-band .btn-row { margin-top: var(--space-lg); display: flex; gap: 12px; flex-wrap: wrap; }
.pitch-band .btn--ghost { color: #fff; border-color: rgba(255,255,255,.4); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

.site-footer {
  padding-block: var(--space-lg);
  border-top: 1px solid var(--border-soft);
}
.site-footer .inner { padding-inline: var(--gutter); }

/* ============================================================================
   DDB MOMENTS
   Full-bleed yellow. DDB p.19 sanctions colour bleed "to create more impact",
   and yellow is the ONLY accent inside this block — so the one-accent law that
   both rulebooks share still holds. This is where the agency gets to speak.
   ============================================================================ */
.ddb-band {
  background: var(--signature);
  color: var(--ddb-black);
  padding-block: var(--space-2xl);
  padding-inline: var(--gutter);
}
.ddb-kicker {
  display: block; font-size: 13px; font-weight: 700;
  letter-spacing: .03em; margin-bottom: var(--space-sm);
  color: color-mix(in srgb, var(--ddb-black) 62%, transparent);
}
.ddb-line {
  font-size: clamp(26px, 3.6vw, 46px); font-weight: 300;
  line-height: 1.14; letter-spacing: -.02em; max-width: 22ch;
}
.ddb-line b { font-weight: 700; }

/* People photos run plain — the mark stays in the hero and the header, and
   repeating it as a crop was diluting it. */
.people-grid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }

/* ---- methodology: The Power to Move ---- */
.step-q { margin-top: 10px; display: grid; gap: 4px; }
.step-q li {
  font-size: 13px; line-height: 1.5; color: var(--text-muted);
  padding-left: 12px; position: relative;
}
.step-q li::before {
  content: ''; position: absolute; left: 0; top: .62em;
  width: 4px; height: 4px; background: var(--accent);
}

/* ---- clients ---- */
.clients { margin-top: var(--space-2xl); }
.clients-label {
  display: block; font-size: 13px; font-weight: 700;
  color: var(--text-muted); margin-bottom: var(--space-sm);
}
.clients ul { display: flex; flex-wrap: wrap; gap: 10px 12px; }
.clients li {
  font-size: 14px; font-weight: 600;
  border: 1px solid var(--border-soft);
  padding: 7px 13px;
}

/* ---- service levels ----
   Scan layer (always visible): tier + turnaround + examples.
   Detail layer (folded): definition, quality standards, numbered process. */
.sla { margin-top: var(--space-xl); border-top: 1px solid var(--border-soft); }
.sla-row { border-bottom: 1px solid var(--border-soft); }

.sla-head {
  width: 100%; display: flex; align-items: baseline; gap: var(--space-md);
  padding: var(--space-md) 0 6px;
  background: none; border: 0; text-align: left; cursor: pointer;
  font: inherit; color: inherit;
}
.sla-n { font-size: 12px; font-weight: 700; color: var(--accent); }
.sla-tier { flex: 1 1 auto; font-size: 19px; font-weight: 700; }
/* the number is the answer — it gets the size and the accent */
.sla-days {
  font-size: clamp(20px, 2.2vw, 28px); font-weight: 700;
  color: var(--accent); white-space: nowrap; letter-spacing: -.01em;
}
.sla-chev {
  width: 9px; height: 9px; flex: none; align-self: center;
  border-right: 2px solid var(--sab-grey-400);
  border-bottom: 2px solid var(--sab-grey-400);
  transform: rotate(45deg); transform-origin: 60% 60%;
  transition: transform .28s var(--ease-out);
}
.sla-row.is-open .sla-chev { transform: rotate(-135deg); }
@media (hover: hover) and (pointer: fine) {
  .sla-head:hover .sla-chev { border-color: var(--accent); }
}
.sla-head:active { transform: scale(0.995); }

.sla-eg {
  margin: 0 0 var(--space-md);
  max-width: 74ch; font-size: 14px; line-height: 1.6; color: var(--text-muted);
}

/* minmax(0, 0fr) — a bare 0fr keeps the content's min-content height */
.sla-body {
  display: grid; grid-template-rows: minmax(0, 0fr);
  opacity: 0; transition: grid-template-rows .34s var(--ease-out), opacity .22s;
}
.sla-body-in { overflow: hidden; }
.sla-row.is-open .sla-body { grid-template-rows: minmax(0, 1fr); opacity: 1; }

.sla-meta {
  display: grid; gap: var(--space-md) var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  padding-bottom: var(--space-md);
}
.sla-lbl {
  display: block; margin-bottom: 6px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--sab-grey-500);
}

/* <ol> — the base reset only clears <ul>, so browser markers would double up
   with the counter below */
.sla-steps {
  list-style: none; padding: 0; margin: 10px 0 var(--space-lg);
  counter-reset: s;
}
.sla-steps li {
  position: relative; padding: 0 0 14px 40px;
  font-size: 15px; line-height: 1.6; color: var(--text-muted);
}
.sla-steps li::before {
  counter-increment: s; content: counter(s);
  position: absolute; left: 0; top: -1px;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; color: #fff; background: var(--accent);
}
/* the rail — connects the steps, stops short of the last marker */
.sla-steps li:not(:last-child)::after {
  content: ''; position: absolute; left: 11px; top: 26px; bottom: 4px;
  width: 2px; background: var(--border-soft);
}

/* case stills */
.case-media img { object-fit: cover; }

/* ============================================================================
   CLIENT MARQUEES
   Two rows travelling in opposite directions. The list is duplicated in the
   markup so a -50% translate loops with no seam. Pauses on hover so a name
   can actually be read, and stops entirely under reduced motion.
   ============================================================================ */
.clients { margin-top: var(--space-2xl); display: grid; gap: var(--space-lg); }
.mq { overflow: hidden; }
.mq-label {
  display: block; font-size: 13px; font-weight: 700;
  color: var(--text-muted); margin-bottom: 12px;
}
/* Paused unless the rail is actually on screen. An infinite animation keeps
   the compositor producing a frame every 16ms for the whole life of the page
   — a Safari timeline on an iPhone showed Composite as one unbroken band
   across 15 seconds of scrolling, with both of these rails sitting far above
   the viewport the entire time. js/site.js flips .is-on as they come into
   view. The animation itself is fine (transform, not layout); it was the
   never-stopping that cost. */
.mq-track {
  display: flex; width: max-content;
  animation: mq-l 46s linear infinite;
  animation-play-state: paused;
}
.mq.is-on .mq-track { animation-play-state: running; }
.mq-track[data-dir="r"] { animation-name: mq-r; animation-duration: 38s; }
.mq-track ul { display: flex; gap: 10px; padding-right: 10px; }
.mq-track li {
  font-size: 15px; font-weight: 600; white-space: nowrap;
  border: 1px solid var(--border-soft); padding: 9px 16px;
}
@keyframes mq-l { to { transform: translateX(-50%); } }
@keyframes mq-r { from { transform: translateX(-50%); } to { transform: none; } }
@media (hover: hover) { .mq:hover .mq-track { animation-play-state: paused; } }
@media (prefers-reduced-motion: reduce) {
  .mq-track { animation: none; }
  .mq { overflow-x: auto; }
}

/* ---- case tiles are now buttons ---- */
.case { cursor: pointer; }
.case-cta {
  display: inline-block; margin-top: 12px;
  font-size: 13px; font-weight: 700; color: var(--accent);
}
@media (hover: hover) and (pointer: fine) {
  .case:hover .case-cta { text-decoration: underline; }
  .case:hover .case-media img { opacity: .9; }
}
.case-media img { transition: opacity var(--dur-fast) var(--ease-out); }

/* ---- scope / SLA launchers ---- */
.doc-cards {
  margin-top: var(--space-2xl);
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.doc-card {
  text-align: left; padding: var(--space-lg);
  border: 1px solid var(--border-default);
  border-top: 5px solid var(--ddb, var(--border-default));
  background: var(--bg-page);
  display: grid; gap: 10px;
  /* title, body, then a flexible row — so the CTA sits on the card's floor
     rather than wherever its paragraph happens to end. One card's copy wraps
     to two lines and the other's doesn't, which was knocking them out of line
     with each other. */
  grid-template-rows: auto auto 1fr;
  transition: border-color var(--dur-fast) var(--ease-out),
              transform 160ms var(--ease-out);
}
.doc-card:active { transform: scale(.99); }
@media (hover: hover) and (pointer: fine) {
  .doc-card:hover { border-color: var(--accent); }
}
.doc-card .btn-solid { margin-top: 6px; align-self: end; justify-self: start; }

/* ---- production houses ---- */
.house-grid {
  margin-top: var(--space-2xl);
  display: grid; gap: var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.house-logo { height: 34px; width: auto; margin-bottom: var(--space-sm); }
.house h3 { margin-bottom: 8px; }
.house-reel {
  margin-top: var(--space-md); aspect-ratio: 16/9;
  background: #000; border-radius: var(--radius-sm); overflow: hidden;
}
.house-reel iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ============================================================================
   OVERLAY
   ============================================================================ */
.ov { position: fixed; inset: 0; z-index: 300; }
.ov-scrim {
  position: absolute; inset: 0; background: rgba(0,0,0,.55);
  opacity: 0; transition: opacity 240ms var(--ease-out);
}
.ov.is-open .ov-scrim { opacity: 1; }
.ov-panel {
  position: absolute; inset: 0 0 0 auto;
  width: min(880px, 100%); background: var(--bg-page);
  overflow-y: auto; overscroll-behavior: contain;
  padding: var(--space-2xl) clamp(24px, 5vw, 64px) var(--space-3xl);
  transform: translateX(3%); opacity: 0;
  transition: transform 320ms var(--ease-out), opacity 240ms var(--ease-out);
}
.ov.is-open .ov-panel { transform: none; opacity: 1; }
/* ---- full-bleed variant, work cases only ----
   A case is something you look at, so it takes the whole screen. Scope, SLA and
   bios keep the side panel — they are documents, and a reading column is right
   for them. Same dialog, same focus and escape rules; only the frame changes. */
.ov--full .ov-panel {
  inset: 0; width: 100%;
  padding: 0 0 var(--space-3xl);
}
.ov--full .ov-close {
  position: fixed; top: 14px; right: 18px; float: none;
  width: 44px; height: 44px; display: grid; place-items: center;
  color: #fff; background: rgba(20,18,18,.5); backdrop-filter: blur(6px);
  z-index: 3;
}
/* the writing below the hero still needs a reading measure — full bleed is for
   the imagery, not for 90-character lines of body copy */
.ov--full .ov-cols,
.ov--full .ov-film,
.ov--full .ov-stills {
  max-width: 1100px; margin-inline: auto;
  padding-inline: clamp(24px, 5vw, 64px);
}
.ov--full .ov-hero { margin: 0 0 var(--space-2xl); min-height: min(78vh, 760px); }
.ov--full .ov-hero-in { max-width: 100%; }
.ov--full .ov-hero-title { font-size: clamp(34px, 6.4vw, 86px); }

/* ---- moving between cases ----
   Sits at the foot of a case, full width in the full-bleed variant. Two targets
   rather than arrows alone: the title tells you where you are going. */
.case-nav {
  margin-top: var(--space-2xl);
  display: grid; gap: 1px;
  grid-template-columns: 1fr 1fr;
  background: var(--border-soft);
  border-block: 1px solid var(--border-soft);
}
.case-nav-btn {
  display: grid; gap: 6px; align-content: center;
  padding: var(--space-lg) var(--space-md);
  background: var(--bg-page); text-align: left;
  transition: background var(--dur-fast) var(--ease-out),
              transform 160ms var(--ease-out);
}
.case-nav-btn--next { text-align: right; }
.case-nav-dir {
  font-size: 12px; font-weight: 700; letter-spacing: .04em;
  color: var(--accent);
}
.case-nav-title {
  font-size: clamp(17px, 2vw, 24px); font-weight: 700;
  line-height: 1.15; letter-spacing: -.02em;
}
@media (hover: hover) and (pointer: fine) {
  .case-nav-btn:hover { background: var(--bg-surface); }
}
.case-nav-btn:active { transform: scale(.99); }
/* the writing keeps a reading measure in the full-bleed variant, and so should
   this — see the .ov--full rules above */
.ov--full .case-nav { max-width: 1100px; margin-inline: auto; }

/* ---- case hero ----
   Breaks the panel's own padding so the still runs edge to edge, the way a case
   opens on ddb.ae. Negative margins rather than a layout change, so every other
   overlay (scope, SLA, bios) is untouched. */
.ov-hero {
  position: relative; isolation: isolate;
  margin: calc(var(--space-2xl) * -1) calc(clamp(24px, 5vw, 64px) * -1) var(--space-xl);
  min-height: min(58vh, 460px);
  /* centred, the way ddb.ae sets a case title — it reads as a title card rather
     than a caption sitting in the corner of a photograph */
  display: grid; place-items: center;
  padding: var(--space-2xl) clamp(24px, 5vw, 64px);
  text-align: center;
  background-color: var(--ddb-black);
}
.ov-hero-shot {
  position: absolute; inset: 0; z-index: -2;
  width: 100%; height: 100%; object-fit: cover;
}
/* Scrim. Weighted to the CENTRE, because that is where the type sits — the
   original bottom-up gradient was built for a bottom-left title and left
   centred copy stranded on the lightest part of a pale still. */
.ov-hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(64% 56% at 50% 48%,
      rgba(18,16,16,.72) 0%, rgba(18,16,16,.34) 62%, transparent 100%),
    linear-gradient(to top,
      rgba(18,16,16,.52) 0%, rgba(18,16,16,.22) 55%, rgba(18,16,16,.28) 100%);
}
/* measured in em, so it scales WITH the headline. A ch-based cap is unreliable
   for display type — at 86px it was forcing one word per line. */
.ov-hero-in { max-width: 100%; }
.ov-hero-title { max-width: 13em; margin-inline: auto; }
/* uppercase yellow client name — DDB's own treatment over imagery. The site's
   usual eyebrow law (sentence case, .04em) is a light-ground rule; this is the
   agency speaking on its own work. */
.ov-hero-client {
  display: block; margin-bottom: 12px;
  font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--ddb-yellow);
}
.ov-hero-title {
  font-size: clamp(30px, 5.2vw, 62px); font-weight: 700;
  line-height: 1.02; letter-spacing: -.03em; color: #fff;
}
/* The campaign line runs at the SAME size as the title — it is the line the
   work actually ran on, and setting it small made the punchline the caption.
   Weight carries the hierarchy instead: 700 for the campaign name, 400 here. */
/* scoped to .ov-hero to outrank `.ov-body p` further down the file, which is
   (0,1,1) and was silently winning over a bare `.ov-hero-tag` */
.ov-hero .ov-hero-tag {
  margin-top: 18px;
  font-size: clamp(30px, 5.2vw, 62px); font-weight: 400;
  line-height: 1.08; letter-spacing: -.025em;
  color: #fff;
  max-width: 15em; margin-inline: auto;
}

.ov-close {
  position: sticky; top: 0; float: right;
  font-size: 30px; line-height: 1; color: var(--text-muted);
  padding: 4px 8px; z-index: 2;
}
.ov-close:hover { color: var(--text-primary); }
.ov-eyebrow {
  display: block; font-size: 13px; font-weight: 700;
  color: var(--accent); margin-bottom: 10px;
}
.ov-body h2 { max-width: 22ch; }
.ov-body p { font-size: 16px; line-height: 1.75; color: var(--text-muted); margin-top: 12px; }
.ov-sub { margin-top: var(--space-xl); }
.ov-film {
  margin-top: var(--space-lg); aspect-ratio: 16/9;
  background: #000; border-radius: var(--radius-sm); overflow: hidden;
}
.ov-film iframe { width: 100%; height: 100%; border: 0; display: block; }
.ov-cols {
  margin-top: var(--space-xl);
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
/* auto-fit collapses the empty track, so a lone column would otherwise run the
   full panel width — far too long a line to read comfortably */
.ov-cols:has(> :only-child) { grid-template-columns: minmax(0, 62ch); }
.ov-stills { margin-top: var(--space-xl); display: grid; gap: var(--space-md); }
.ov-stills img { width: 100%; border-radius: var(--radius-sm); }
/* the reset clears <ul> markers but not <ol> — without this the browser's
   own numbering sits next to the counter and you get '1. 01' */
.ov-list { margin-top: var(--space-md); display: grid; gap: 10px;
  counter-reset: n; list-style: none; padding: 0; }
.ov-list li {
  position: relative; padding-left: 26px;
  font-size: 15px; line-height: 1.6; color: var(--text-muted);
}
.ov-list li::before {
  counter-increment: n; content: counter(n, decimal-leading-zero);
  position: absolute; left: 0; top: .1em;
  font-size: 12px; font-weight: 700; color: var(--accent);
}
.ov-list--tight li { padding-left: 16px; }
.ov-list--tight li::before { content: ''; top: .62em; width: 4px; height: 4px; background: var(--accent); }
.ov-tier { margin-top: var(--space-lg); padding-top: var(--space-md); border-top: 1px solid var(--border-soft); }
.ov-tier-n { font-size: 12px; font-weight: 700; color: var(--accent); }
.ov-tier h4 { font-size: 20px; font-weight: 700; margin: 6px 0 8px; }
.ov-tier-label { display: block; margin-top: 14px; font-size: 13px; font-weight: 700; }
/* the ref / name / quantity row — quantity is the number the client will look
   for first, so it sits on the right where the RFP's QTY column was */
.ov-tier-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.ov-tier-head h4 { margin: 0; flex: 1 1 auto; }
.ov-qty { display: flex; align-items: baseline; gap: 6px; white-space: nowrap; }
.ov-qty b { font-size: 24px; font-weight: 700; color: var(--accent); line-height: 1; }
.ov-qty span { font-size: 12px; color: var(--text-muted); }
.ov-tier > .t-small:first-of-type { margin-top: 8px; }
/* the client's own footnote — set smaller than the scope it qualifies */
.ov-note {
  margin-top: 14px; padding-left: 12px;
  border-left: 2px solid var(--border-soft);
  font-size: 13px; line-height: 1.55; color: var(--text-muted);
}
.ov-uline { margin-top: 14px; text-decoration: underline; }
.ov-slacat { margin-top: var(--space-xl); }
@media (prefers-reduced-motion: reduce) {
  .ov-panel, .ov-scrim { transition: none; }
}

/* ============================================================================
   PEOPLE — departments
   Closed by default. Fifteen cards open on arrival is a wall of faces; the
   point of the section is choosing who to read.
   ============================================================================ */
/* ---- DDB colour, not DDB shape ----
   The palette is p.18; cycling rather than role-coding follows p.32. Each rule
   is set from a --ddb custom property written by js/sections.js, so the cycle
   lives in one place and the CSS never hardcodes a colour.
   These are pale by design — they mark a section as ours without competing
   with SAB red, which stays the only thing that means "act here". */
.pdept { border-top: 5px solid var(--ddb, var(--border-soft)); }
.pdept:last-of-type { border-bottom: 1px solid var(--border-soft); }
/* The whole row opens the department, not just the button on the right — see
   the toggle handler in js/site.js. With only the button live, tapping the
   department name did nothing, which reads as an ordering rule rather than a
   missed target. */
.pdept-head {
  display: flex; align-items: baseline; gap: var(--space-md);
  padding-block: var(--space-md);
  cursor: pointer;
  -webkit-user-select: none; user-select: none;
  /* Sits above the department's own content. Team photos from an open
     department were reaching over the row below and swallowing clicks on its
     Show button: the cursor stayed an arrow on the button and became a hand
     just off it, because the topmost thing under the pointer was an image,
     not the button. The head is the control — it wins. */
  position: relative;
  z-index: 2;
}
/* the ref number becomes a colour block rather than red type — it is a label,
   not an action, so it should not borrow the accent */
.pdept-num {
  font-size: 12px; font-weight: 700; line-height: 1;
  color: var(--ddb-black); background: var(--ddb, var(--sab-grey-150));
  padding: 7px 9px; align-self: center;
}
.pdept-name { font-size: clamp(22px, 2.6vw, 34px); font-weight: 700; letter-spacing: -.02em; }
.pdept-count { font-size: 13px; color: var(--text-muted); }
/* solid, like the other CTAs — four of them, one per department, so the fill
   still reads as "act here" rather than decoration */
.pdept-toggle {
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px;
}
.pdept-toggle .chev { transition: transform var(--dur-fast) var(--ease-out); }
.pdept.is-open .pdept-toggle .chev { transform: rotate(180deg); }
html[dir="rtl"] .pdept-toggle { margin-left: 0; margin-right: auto; }

/* 0fr→1fr animates height without measuring it. It must be minmax(0, 0fr):
   a bare 0fr can keep the row at its content's min-content height, which
   leaves a dead gap under every closed department. */
/* The row size is NOT transitioned. Animating grid-template-rows re-lays out
   every card in the department on every frame, and on a loaded page the panel
   took over a second to start moving after the click — measured on the live
   site. A button that does nothing for a second reads as broken, and clicking
   again just toggles it shut, which is exactly how it was reported.

   The row now snaps and only the opacity fades, so the response is immediate.
   Height is not an animatable property worth having here. */
.pdept-body {
  display: grid; grid-template-rows: minmax(0, 0fr);
  transition: opacity 180ms var(--ease-out);
  opacity: 0;
  /* clip on the body itself as well as on its child. The child rule alone left
     a route for card content to reach past the department and over the next
     one's Show button. */
  overflow: hidden;
}
.pdept-body > * { overflow: hidden; min-height: 0; }
/* a closed department must not be reachable at all — no stray hit areas from
   content that is clipped to zero height but still laid out */
.pdept:not(.is-open) .pdept-body { pointer-events: none; }
.pdept.is-open .pdept-body { grid-template-rows: minmax(0, 1fr); opacity: 1; }

.pcard-grid {
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  padding-bottom: var(--space-xl);
}
/* The headshots are transparent cutouts with the colour shape baked in, so
   they must sit INSIDE the well whole — object-fit: cover crops the cutout and
   takes the top of people's heads off. contain + padding shows the artwork as
   supplied, which is how it was shot. */
.pcard-photo {
  position: relative;
  aspect-ratio: 4 / 5; background: var(--bg-surface);
  border-radius: var(--radius-sm); overflow: hidden;
}
.pcard-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain; object-position: center bottom;
  padding: 14px 10px 0;
}
/* Awaiting a cutout. Deliberately quiet: on DDB yellow these read as the
   loudest tiles in the grid, which puts the emphasis on the four people we
   are missing. Same surface as the real tiles, initials in muted grey — the
   gap is legible without being the first thing you see. */
.pcard-await {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: var(--bg-surface);
  color: var(--sab-grey-400);
  font-size: clamp(26px, 3.4vw, 40px); font-weight: 700; letter-spacing: -.02em;
}
.pcard-name { margin-top: 16px; font-size: 18px; font-weight: 700; }
.pcard-role { display: block; margin-top: 4px; font-size: 13px; color: var(--accent); font-weight: 600; }
.pcard-rule { display: block; width: 36px; height: 2px; background: var(--border-default); margin: 14px 0; }
.pcard-bio {
  font-size: 14px; line-height: 1.55; color: var(--text-muted);
  display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
}
/* 44px is Apple's minimum tap target and this was rendering at 33. The button
   keeps its size visually; the target is grown underneath it, so a near-miss
   on a phone still opens the bio instead of doing nothing at all. */
.pcard-more {
  margin-top: 12px;
  position: relative;
  touch-action: manipulation;
}
.pcard-more::after {
  content: "";
  position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: 44px;
  min-width: 44px;
}

/* ---- 04 DDB Studio ---- */
.studio-card {
  display: grid; gap: var(--space-lg);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  align-items: center; padding-bottom: var(--space-xl);
}
.studio-reel { aspect-ratio: 16/9; background: #000; border-radius: var(--radius-sm); overflow: hidden; }
.studio-reel iframe { width: 100%; height: 100%; border: 0; display: block; }
.studio-logo { height: 30px; width: auto; margin-bottom: var(--space-sm); }
.studio-name { font-size: 22px; font-weight: 700; }
.studio-bio { margin-top: 12px; font-size: 15px; line-height: 1.7; color: var(--text-muted); white-space: pre-line; }

/* portrait inside the bio overlay */
.ov-portrait {
  width: 220px; aspect-ratio: 4/5;
  object-fit: contain; object-position: center bottom;   /* cutout, not a crop */
  padding: 12px 10px 0;
  border-radius: var(--radius-sm); background: var(--bg-surface);
  margin-top: var(--space-lg);
}

/* ============================================================================
   WAYS OF WORKING — the Trello board
   Ported from the Sirar build and re-cut for this system. Three deliberate
   departures from that original: corners are square (5px, our --radius-sm)
   instead of pill-and-15px, nothing glows, and SAB red appears nowhere on the
   board. Red is the accent and would read as "act here" on a label that is
   only describing a deliverable type. The labels and avatars carry the DDB
   palette instead, which is what it is here for.
   ========================================================================= */
.tr-head { position: relative; }

/* Trello's own blue, on Trello's own mark. A product logo, not a second
   accent — flat, no gradient, no shadow, and small enough to read as a
   credit rather than a badge. */
.tr-mark {
  position: absolute; top: 0; inset-inline-end: 0;
  width: 88px; height: 88px;
  display: grid; place-items: center;
  background: #0052CC;
  border-radius: var(--radius-sm);
}

.tr-chips {
  margin-top: var(--space-lg);
  display: flex; flex-wrap: wrap; gap: 10px;
}
.tr-chip {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 9px 15px;
  border: 1px solid var(--border-soft);
  background: var(--bg-page);
  border-radius: var(--radius-0);
  font-size: 14px; color: var(--text-primary);
}
/* the one place red is allowed here: a live-status dot, which is the only
   thing on the board that means anything is happening */
.tr-chip::before {
  content: ""; flex: 0 0 auto;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
}

.tr-board {
  margin-top: var(--space-xl);
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-md); align-items: start;
}
.tr-col {
  background: var(--sab-grey-100);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: var(--space-md);
}
.tr-col-head {
  display: flex; align-items: baseline; gap: 10px;
  margin-bottom: var(--space-md);
  font-size: 15px; font-weight: 600; color: var(--text-primary);
}
.tr-col-count { color: var(--text-muted); font-size: 13px; font-weight: 500; }

.tr-cards { display: grid; gap: 10px; }
.tr-card {
  background: var(--bg-page);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 14px;
}
@media (hover: hover) and (pointer: fine) {
  .tr-card { transition: border-color 180ms var(--ease-out); }
  .tr-card:hover { border-color: var(--sab-grey-400); }
}

.tr-labels { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 11px; }
.tr-label {
  font-size: 11px; font-weight: 700; letter-spacing: .01em;
  padding: 3px 8px; border-radius: var(--radius-0);
  background: var(--ddb, var(--sab-grey-200));
  color: var(--ddb-black);
}
.tr-card-title {
  font-size: 15px; font-weight: 600; line-height: 1.32;
  color: var(--text-primary);
}
.tr-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 13px;
}
.tr-due { font-size: 12px; color: var(--text-muted); }
/* approved reads as done without borrowing the accent for it */
.tr-due--done { color: var(--sab-grey-600); font-weight: 600; }
.tr-due--done::after { content: " ✓"; }

.tr-who {
  flex: 0 0 auto;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 10px; font-weight: 700;
  background: var(--ddb, var(--sab-grey-200));
  color: var(--ddb-black);
}

@media (max-width: 900px) {
  .tr-board { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  /* the mark stops floating and sits above the copy, or it collides */
  .tr-mark { position: static; width: 64px; height: 64px; margin-bottom: var(--space-md); }
  .tr-head { display: flex; flex-direction: column-reverse; align-items: flex-start; }
}

/* Key visuals supplied below full-page width. Rendering these edge-to-edge
   upscales them roughly 2x and the small Arabic body copy is the first thing
   to go soft, so they sit at their true size instead, centred. Set by
   `stillsNative: true` on the case. */
.ov-stills--native img {
  width: auto; max-width: 100%;
  margin-inline: auto;
}
