/* ============================================================
   FLIBBR BASE
   Universal resets, body, html, .container, .section, focus rings,
   base interactive transitions. Transcribed from canonical homepage.
   ============================================================ */

* { box-sizing: border-box; }

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  scroll-behavior: smooth;
  scroll-padding-top: var(--scroll-padding-y);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 18px;
  line-height: 1.6;
  font-feature-settings: "kern", "liga", "onum";
}

/* Focus ring — Stage 3 colour locked.
   Applied only on keyboard navigation (:focus-visible). */
:focus { outline: none; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
  transition: outline-offset var(--motion-duration-ui) var(--motion-ease-ui);
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  transition: outline-offset var(--motion-duration-ui) var(--motion-ease-ui);
}

/* Base transition for interactive elements */
a,
button,
[role="button"] {
  transition: all var(--motion-duration-ui) var(--motion-ease-ui);
  cursor: pointer;
}

/* Layout primitives */
.container {
  max-width: var(--max-w);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

.section {
  padding-block: var(--s-9);
  border-bottom: 1px solid var(--rule-soft);
}

.section:last-of-type { border-bottom: 0; }

/* Screen-reader-only — visually hidden but available to assistive tech.
   Lift verbatim from canonical (homepage L76-86 et al). Used on
   archive headings (e.g. archive-dispatch.php's .dispatch-archive
   <h2>) where the visible page-header H1 already names the page,
   but a structural h2 still helps screen readers. v0.5.45 (#237). */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0,0,0,0);
  white-space: nowrap; border: 0;
}

/* Skip link — accessibility, hidden until focus */
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.skip-link:focus {
  position: fixed;
  left: var(--s-4);
  top: var(--s-4);
  width: auto;
  height: auto;
  padding: var(--s-3) var(--s-4);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  z-index: 100;
}

/* Arrow-glyph utilities — shared SVG-mask data-URIs (v0.5.136, op-learning #72).
   Replace CSS `content: "→"` and `content: "↗"` codepoints (Safari/macOS
   substitutes ↗ U+2197 with Apple Color Emoji per playbook §5.3; we treat
   the wider arrow class structurally per op-learning #72). The mask-image
   approach lets pseudo-elements inherit text colour via `background-color:
   currentColor`, preserving the colour cascade the codepoints had. Geometry
   matches `flibbr_arrow()` in inc/helpers.php exactly (viewBox 0 0 24 24,
   stroke 1.5, stroke-linecap/linejoin round) so visual identity is preserved
   across PHP-emitted and CSS-emitted arrows. */
:root {
  /* Horizontal right (→ replacement): line 5,12 → 19,12 + arrowhead 13,6 19,12 13,18 */
  --arrow-svg-right: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><line x1='5' y1='12' x2='19' y2='12'/><polyline points='13 6 19 12 13 18'/></svg>");
  /* Diagonal up-right (↗ replacement): line 6,18 → 18,6 + arrowhead 9,6 18,6 18,15 */
  --arrow-svg-diagonal: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><line x1='6' y1='18' x2='18' y2='6'/><polyline points='9 6 18 6 18 15'/></svg>");
}

/* Base utility class — apply via `.arrow-glyph` on a pseudo-element to render
   the horizontal arrow inheriting parent colour. Page-CSS rules that already
   define their own `::after { content: "→"; }` switch to `content: ""` plus
   the size + mask + background-color triple below (or include this class as
   a composed mixin alternative — but most existing rules want inline). */
.arrow-glyph {
  display: inline-block;
  width: 0.85em;
  height: 0.85em;
  background-color: currentColor;
  -webkit-mask: var(--arrow-svg-right) no-repeat center / contain;
          mask: var(--arrow-svg-right) no-repeat center / contain;
  flex: none;
}
.arrow-glyph.arrow-glyph--diagonal {
  -webkit-mask-image: var(--arrow-svg-diagonal);
          mask-image: var(--arrow-svg-diagonal);
}


/* ============================================================
   AUTHOR PORTRAIT FAMILY — shared utility (op-learning #16)

   Used across: archive-dispatch.php (byline + author-rail + grid
   cards), single-flibbrati.php (hero + dispatch trail), the
   flibbrati-archive grid cards, and any future surface needing
   the warm-monochrome 4:5 portrait stand-in.

   Originally embedded verbatim in the dispatch-archive canonical
   (rj/sk/hg/gv) and re-emitted with 12 additions in the
   flibbrati-archive canonical (pg/sm/cr/bs/dc/ap/ts/nr/yr/skar/
   asx/vk). v0.5.46 lifts both sets to base.css per op-learning
   #16: shared canonical utility families belong in base.css
   ahead of any template that uses them, not just-in-time per
   template (mirroring v0.5.45's lift of .visually-hidden).

   At build phase, every gradient is replaced by a real <img>
   pointing at the locked Stage 5 4:5 800×1000 portrait file.

   v0.5.46 — lifted from dispatch-archive.css + canonical.
   ============================================================ */

.author-portrait {
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  background: var(--paper-warm);
  color: var(--ink-soft);
  position: relative;
}

/* Original four — verbatim from Dispatch archive canonical */
.author-portrait.rj {
  background: linear-gradient(160deg, #d8cfb8 0%, #a89d82 100%);
}
.author-portrait.sk {
  background: linear-gradient(160deg, #c5c0b3 0%, #908a7d 100%);
}
.author-portrait.hg {
  background: linear-gradient(160deg, #ddd3bf 0%, #ada392 100%);
}
.author-portrait.gv {
  background: linear-gradient(160deg, #c8baa3 0%, #98896f 100%);
}

/* Initials — verbatim from Dispatch archive canonical */
.author-portrait .portrait-initials {
  position: absolute;
  bottom: 6px;
  left: 8px;
  font-family: var(--serif);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: rgba(20, 20, 19, 0.55);
  line-height: 1;
}

/* Typographic variant — guests + any portrait without a gradient
   slug. Verbatim from Dispatch archive canonical. */
.author-portrait.typographic {
  background: var(--paper-warm);
  align-items: center;
  justify-content: center;
  border: 1px solid var(--rule-soft);
}
.author-portrait.typographic .portrait-initials {
  position: static;
  font-family: var(--serif); font-weight: 400;
  font-size: 28px;
  letter-spacing: -0.02em;
  color: var(--ink-soft);
}

/* The 12 additions — verbatim from Flibbrati archive canonical */
.author-portrait.pg {
  background: linear-gradient(160deg, #d4c8af 0%, #9a8e75 100%);
}
.author-portrait.sm {
  background: linear-gradient(160deg, #cdc4b0 0%, #97896f 100%);
}
.author-portrait.cr {
  background: linear-gradient(160deg, #d9d0bb 0%, #a59879 100%);
}
.author-portrait.bs {
  background: linear-gradient(160deg, #cfc4ad 0%, #948572 100%);
}
.author-portrait.dc {
  background: linear-gradient(160deg, #c8bea8 0%, #8d816a 100%);
}
.author-portrait.ap {
  background: linear-gradient(160deg, #dfd3bb 0%, #ad9d7e 100%);
}
.author-portrait.ts {
  background: linear-gradient(160deg, #d2c6ae 0%, #998b71 100%);
}
.author-portrait.nr {
  background: linear-gradient(160deg, #c5b9a1 0%, #8a7d65 100%);
}
.author-portrait.yr {
  background: linear-gradient(160deg, #d6cab1 0%, #9f9079 100%);
}
.author-portrait.skar {
  background: linear-gradient(160deg, #cabea5 0%, #8e8169 100%);
}
.author-portrait.asx {
  background: linear-gradient(160deg, #d2c8b3 0%, #968b73 100%);
}
.author-portrait.vk {
  background: linear-gradient(160deg, #cec3aa 0%, #968971 100%);
}

/* v0.5.97: real-image render inside the .author-portrait wrapper.
   When the dispatch render path resolves a Flibbrati portrait URL,
   it emits an <img> child inside the existing portrait div. Without
   sizing rules the img falls back to its intrinsic dimensions and
   overflows the wrapper. Mirrors the cropping pattern shipped at
   v0.5.94 for .flibbrati-portrait and .profile-hero-portrait img.

   When an <img> is present, the initials placeholder is suppressed
   by the template's conditional (img XOR span). The bg-gradient
   tint stays visible only at edges if the img has transparency or
   doesn't fully cover — which it should, given 4:5 source ratio. */
.author-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center top;
  display: block;
}


/* ============================================================
   DISPATCH-CARD FAMILY — shared utility (op-learning #16)

   Used across: archive-dispatch.php (year-grouped grid),
   single-flibbrati.php (Dispatch trail). Lifted from the
   dispatch-archive canonical (lines 1995-2070 of locked file)
   v0.5.46.

   Card grammar: 80px portrait + content column (headline,
   summary, meta strip). Hover: paper-warm background + accent
   headline.
   ============================================================ */

.dispatch-card {
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: var(--s-5);
  text-decoration: none;
  color: inherit;
  padding: var(--s-4);
  margin-inline: calc(var(--s-4) * -1);
  border-radius: 4px;
  transition: background var(--motion-duration-content) var(--motion-ease-content);
}
.dispatch-card:hover {
  background: var(--paper-warm);
}
.dispatch-card:hover .dispatch-card-headline {
  color: var(--accent);
}
.dispatch-card .author-portrait {
  width: 80px;
  aspect-ratio: 4/5;
  flex-shrink: 0;
  border-radius: 3px;
  overflow: hidden;
}
.dispatch-card-content {
  display: flex; flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
}
.dispatch-card-headline {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(20px, 2vw, 24px);
  line-height: 1.20;
  letter-spacing: -0.005em;
  color: var(--ink);
  max-width: 24ch;
  transition: color var(--motion-duration-content) var(--motion-ease-content);
}
.dispatch-card-headline em { font-style: italic; }
.dispatch-card-summary {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: 14.5px; line-height: 1.55;
  color: var(--ink-soft);
  max-width: 42ch;
}
.dispatch-card-summary strong { font-weight: 600; color: var(--ink); }
.dispatch-card-meta {
  display: flex; align-items: center; gap: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px solid var(--rule-soft);
  margin-top: auto;
  font-family: var(--sans);
  font-size: 11.5px;
  letter-spacing: 0.02em;
  color: var(--ink-quiet);
}
.dispatch-card-meta .meta-author { color: var(--ink-soft); font-weight: 500; }
.dispatch-card-meta .meta-sep { color: var(--ink-quiet); }


/* ============================================================
   CASE-RELATED HEADER FAMILY — shared utility (op-learning #16)

   Section-introduction wrapper for "tail-rail" sections that
   end a single-page or single-post template. Used across:
     - page-bootcamps.php (tail rail of 3 path cards)
     - single-case_study.php (tail rail of 2 related cases)
   Future consumers: single-bootcamp_event.php, single-work.php,
   single-dispatch.php — same wrapper, different inner card grammar.

   The wrapper itself (warm-paper backdrop, top rule, header grid
   with the small label and the serif headline) is the shared
   contract. The grid INSIDE (.recap-tail-paths, .case-related-grid,
   etc.) belongs to its consuming surface and stays in the
   surface-specific CSS file.

   Lifted from bootcamps-overview.css (lines 521-555 at v0.5.46)
   v0.5.47.
   ============================================================ */

.case-related {
  background: var(--paper-warm);
  padding-block: var(--s-9);
  border-top: 1px solid var(--rule);
}
.case-related-header {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: var(--s-7);
  align-items: end;
  margin-bottom: var(--s-7);
}
@media (max-width: 880px) {
  .case-related-header {
    grid-template-columns: 1fr;
    gap: var(--s-3);
  }
}
.case-related-label {
  font-family: var(--sans); font-weight: 600;
  font-size: 11px; line-height: 1.2;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-quiet);
  padding-top: var(--s-3);
  border-top: 1px solid var(--ink);
}
.case-related-headline {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(24px, 2.4vw, 30px); line-height: 1.22;
  letter-spacing: -0.005em;
  color: var(--ink);
  max-width: 24ch;
}
.case-related-headline em { font-style: italic; }


/* ============================================================
   .work-grid + .work-tile-* — shared films-grid family.
   Op-learning #16 — fourth application.

   First consumer: page-consulting.php (§6 Recent Work).
   Second consumer: archive-work.php (full films gallery, v0.5.48).
   Future consumer: any embedded "selected work" surface.

   Lifted verbatim from consulting.css (lines 191-314 at v0.5.47),
   which itself was lifted verbatim from
   final-the-work-archive.html canonical (lines 507-655 at lock).

   The grid uses 16:9 aspect-ratio for the poster (YouTube native).
   Click-to-play state classes (.is-playing) are managed by the
   surface-specific JS (the-work-archive-filter.js + click-to-play
   IIFE for the archive; consulting-films IIFE for the consulting
   page's section).
   ============================================================ */

.work-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s-9) var(--s-7);
}
@media (max-width: 1023px) {
  .work-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--s-8) var(--s-6);
  }
}
@media (max-width: 640px) {
  .work-grid {
    grid-template-columns: 1fr;
    gap: var(--s-8);
  }
}

.work-tile {
  display: flex; flex-direction: column;
  gap: var(--s-4);
}
.work-tile-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--paper-warm);
  overflow: hidden;
  border: 1px solid var(--rule-soft);
  cursor: pointer;
  transition: box-shadow var(--motion-duration-content) var(--motion-ease-content);
}
.work-tile-frame:hover {
  box-shadow: 0 8px 32px rgba(20,20,19,.08);
}
.work-tile-frame:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.work-tile-poster {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--motion-duration-content) var(--motion-ease-content),
              filter var(--motion-duration-content) var(--motion-ease-content);
}
.work-tile-frame:hover .work-tile-poster {
  transform: scale(1.015);
  filter: brightness(0.92);
}
.work-tile-play {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.work-tile-play::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(20,20,19,0) 40%,
    rgba(20,20,19,0.18) 100%
  );
  opacity: 0.85;
  transition: opacity var(--motion-duration-content) var(--motion-ease-content);
}
.work-tile-frame:hover .work-tile-play::before { opacity: 1; }
.work-tile-play-icon {
  position: relative; z-index: 1;
  width: 64px; height: 64px;
  border-radius: 50%;
  background: rgba(250,250,247,0.94);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink);
  box-shadow: 0 4px 16px rgba(20,20,19,0.18);
  transition: transform var(--motion-duration-content) var(--motion-ease-content),
              background var(--motion-duration-content) var(--motion-ease-content);
}
.work-tile-frame:hover .work-tile-play-icon {
  transform: scale(1.06);
  background: var(--paper);
}
.work-tile-play-icon svg {
  width: 22px; height: 22px;
  margin-left: 3px;
}
.work-tile-frame.is-playing .work-tile-poster,
.work-tile-frame.is-playing .work-tile-play {
  display: none;
}
.work-tile-frame.is-playing { cursor: default; }
.work-tile-frame.is-playing iframe {
  width: 100%; height: 100%;
  border: 0; display: block;
}
.work-tile-content {
  display: flex; flex-direction: column;
  gap: var(--s-2);
}
.work-tile-kicker {
  font-family: var(--sans); font-weight: 600;
  font-size: 11px; line-height: 1;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent);
}
.work-tile-title {
  margin: 0;
  font-family: var(--serif); font-weight: 400;
  font-size: clamp(20px, 1.8vw, 24px);
  line-height: 1.20;
  color: var(--ink);
  letter-spacing: -0.005em;
}
.work-tile-title em { font-style: italic; }
.work-tile-cta {
  font-family: var(--sans); font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-quiet);
  text-decoration: none;
  align-self: flex-start;
  margin-top: var(--s-2);
  transition: color var(--motion-duration-ui) var(--motion-ease-ui);
}
.work-tile-cta:hover { color: var(--accent); }


/* ============================================================
   .recognition + .recognition-item — shared 4-up awards grid.
   Op-learning #16 — fifth application.

   First consumer: front-page.php (Recognition section).
   Second consumer: archive-work.php (Recognition strip, v0.5.48).
   Future consumer: page-about.php (in a future ship — currently
   uses inline serif mosaic, not this grid).

   Lifted verbatim from home.css (lines 936-955 at v0.5.47), which
   itself was lifted from final-homepage.html canonical at lock.
   The work-archive canonical uses slightly different gap and adds
   a 540px 1-col breakpoint; those overrides live in
   the-work-archive.css scoped under .recognition-section.
   ============================================================ */

.recognition {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--s-5); margin-top: var(--s-5);
}
@media (max-width: 880px) {
  .recognition { grid-template-columns: 1fr 1fr; }
}
.recognition-item {
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
.recognition-item .award {
  font-family: var(--serif); font-size: 19px; font-weight: 400;
  color: var(--ink); line-height: 1.3;
  margin-block: var(--s-2) var(--s-3);
}
.recognition-item .award em { font-style: italic; }



/* ============================================================
   .recognition-grid — homepage carded variant (v0.5.107).
   New variant introduced to give Recognition section deserved
   visual weight between Case Studies and Films (per v174 RJ
   direction). Reuses the .links-rail-item chrome pattern from
   About canonical: rule-bordered cards with hover-warm fill.

   IMPORTANT: this is parallel to .recognition (which remains
   the work-archive consumer's flat-typographic variant).
   Homepage now emits .recognition-grid; archive-work.php
   continues to emit .recognition. Two distinct surfaces, two
   distinct treatments, shared content schema.

   Inner item classes (.recognition-item, .t-kicker, .award,
   .t-meta) are unchanged — they're still emitted by the
   flibbr_render_recognition_item() helper. The card chrome
   is purely a wrapper-level + descendant-styling layer.
   ============================================================ */
.recognition-grid {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 0; margin-top: var(--s-5);
  border-top: 2px solid var(--ink);
  border-left: 1px solid var(--rule);
}
@media (max-width: 880px) {
  .recognition-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .recognition-grid { grid-template-columns: 1fr; }
}
.recognition-grid .recognition-item {
  padding: var(--s-6) var(--s-5);
  border-top: 0;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
  transition: background var(--motion-duration-content) var(--motion-ease-content);
  display: flex; flex-direction: column; gap: var(--s-3);
}
.recognition-grid .recognition-item:hover {
  background: var(--paper-warm);
}

/* ============================================================
   .recognition-item--linked — v0.5.114.
   When a recognition row is an anchor (links to the work it
   recognises), kill link defaults so the row reads as a card,
   and lift the hover slightly so the affordance is felt.
   The base :hover above already shifts background to paper-warm;
   this variant additionally nudges the .award text into the
   accent ink on hover so the row's headline carries the
   click-affordance signal without an underline.
   ============================================================ */
.recognition-grid .recognition-item--linked {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.recognition-grid .recognition-item--linked:hover .award {
  color: var(--accent);
}
.recognition-grid .recognition-item--linked:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.recognition-grid .recognition-item .t-kicker {
  font-family: var(--sans); font-weight: 600;
  font-size: 11px; line-height: 1;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-quiet);
}
.recognition-grid .recognition-item .award {
  font-family: var(--serif); font-weight: 400;
  font-size: 22px; line-height: 1.2;
  color: var(--ink);
  margin-block: 0;
}
.recognition-grid .recognition-item .award em { font-style: italic; }
.recognition-grid .recognition-item .t-meta {
  margin-top: var(--s-2);
  font-family: var(--serif); font-weight: 400;
  font-size: 14px; line-height: 1.5;
  color: var(--ink-soft);
}


/* ============================================================
   .contact-tail — quiet closing line at bottom of an archive.
   New shared utility introduced at v0.5.48.

   First consumer: archive-work.php.
   Second consumer (anticipated): archive-flibbr_press.php.

   Pattern: italic serif framing prose + sans-caps "Get in touch →"
   CTA. Mirrors final-page / final-contact tail-link grammar.

   Lifted verbatim from final-the-work-archive.html canonical
   (lines 720-745 at lock).
   ============================================================ */

.contact-tail {
  padding-block: var(--s-9);
  border-top: 1px solid var(--rule);
  text-align: center;
}
.contact-tail p {
  margin: 0 auto var(--s-5);
  max-width: 48ch;
  font-family: var(--serif); font-style: italic;
  font-size: clamp(20px, 1.8vw, 24px);
  line-height: 1.4;
  color: var(--ink-soft);
}
.contact-tail-cta {
  display: inline-block;
  font-family: var(--sans); font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
}
.contact-tail-cta:hover { color: var(--ink); }

/* ============================================================
   .case-hero-* — full-width hero section (master video / image).
   Op-learning #16 — eighth application.

   First consumer: single-case_study.php (the case film hero).
   Second consumer: single-bootcamp_event.php in the recap branch
   (master recap video). Both surfaces treat the lead artefact as
   a structural section between the page header and the prose
   column — the same architectural intent earns the same chassis.

   Lifted verbatim from case-study-single.css (lines 116-162 at
   v0.5.48), which itself was lifted verbatim from
   final-single-case-study.html canonical at lock. The lift
   removes those rules from case-study-single.css to avoid
   duplication.

   v0.5.49.
   ============================================================ */

.case-hero {
  padding-block: var(--s-7) var(--s-6);
}
.case-hero-frame {
  background: var(--paper-warm);
  position: relative; overflow: hidden;
  border-radius: 4px;
}
.case-hero-frame.is-video { aspect-ratio: 16/9; }
.case-hero-frame.is-image { aspect-ratio: 16/10; }
.case-hero-frame iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0;
}
/* placeholder for the demonstrative mockup — shows what the
   Vimeo embed wrapper looks like when no real embed is present. */
.case-hero-placeholder {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--s-3);
  background: linear-gradient(135deg, rgba(20,20,19,.04) 0%, rgba(20,20,19,.01) 50%, rgba(20,20,19,.08) 100%);
}
.case-hero-placeholder .play-glyph {
  width: 64px; height: 64px; border-radius: 50%;
  background: rgba(20,20,19,.85);
  display: flex; align-items: center; justify-content: center;
}
.case-hero-placeholder .play-glyph::after {
  content: "";
  width: 0; height: 0;
  border-left: 18px solid var(--paper);
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
  margin-left: 4px;
}
.case-hero-placeholder .placeholder-label {
  font-family: var(--sans); font-size: 11px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-quiet);
}

/* Poster + click-to-play overlay (case_hero_type=video).
   Mirrors the work-tile pattern, scaled up for hero size.
   .is-playing class is added by single-case-study-hero.js
   when the user clicks; poster + play overlay hide and an
   iframe is injected into the frame. */
.case-hero-frame.is-video img.case-hero-poster {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--motion-duration-content) var(--motion-ease-content),
              filter var(--motion-duration-content) var(--motion-ease-content);
}
.case-hero-frame[role="button"] { cursor: pointer; }
.case-hero-frame[role="button"]:hover .case-hero-poster {
  transform: scale(1.012);
  filter: brightness(0.92);
}
.case-hero-frame[role="button"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.case-hero-play {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.case-hero-play::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(
    to bottom,
    rgba(20,20,19,0) 40%,
    rgba(20,20,19,0.18) 100%
  );
  opacity: 0.85;
  transition: opacity var(--motion-duration-content) var(--motion-ease-content);
}
.case-hero-frame[role="button"]:hover .case-hero-play::before { opacity: 1; }
.case-hero-play-icon {
  position: relative; z-index: 1;
  width: 88px; height: 88px;
  border-radius: 50%;
  background: rgba(250,250,247,0.94);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink);
  box-shadow: 0 6px 24px rgba(20,20,19,0.22);
  transition: transform var(--motion-duration-content) var(--motion-ease-content),
              background var(--motion-duration-content) var(--motion-ease-content);
}
.case-hero-frame[role="button"]:hover .case-hero-play-icon {
  transform: scale(1.06);
  background: var(--paper);
}
.case-hero-play-icon svg {
  width: 30px; height: 30px;
  margin-left: 4px;
}
.case-hero-frame.is-playing .case-hero-poster,
.case-hero-frame.is-playing .case-hero-play {
  display: none;
}
.case-hero-frame.is-playing { cursor: default; }
.case-hero-frame.is-playing iframe {
  width: 100%; height: 100%;
  border: 0; display: block;
}
.case-hero-caption {
  margin-top: var(--s-3);
  font-family: var(--sans); font-size: 12px;
  color: var(--ink-quiet); max-width: 60ch;
}


/* ============================================================
   .recap-tail-paths + .recap-tail-path-* — doorway tail rail
   grid, used inside the .case-related wrapper.
   Op-learning #16 — seventh application.

   v109 consumers (3-up — default, no modifier needed):
     1. archive-bootcamp_event.php — past-events archive tail
     2. single-bootcamp_event.php — recap state tail
     3. single-bootcamp_event.php — upcoming state tail

   v0.5.89 (10 May 2026) added a .recap-tail-paths--2up modifier
   for contact-paths.php (4 tiles, 2x2 grid). v0.5.90 (10 May
   2026, afternoon) RETIRED that modifier — the contact paths
   section was redesigned around the .links-rail pattern (see
   assets/css/links-rail.css), not the .recap-tail-paths
   chassis. The modifier had a single consumer and is now
   dead code. Default rule remains repeat(3, 1fr) for existing
   bootcamp consumers, unchanged. Closes #241's solution; the
   problem moved.

   Lifted verbatim from
   `flibbr-design-mockup-final-bootcamp-recap-bengaluru-april-2026.html`
   (locked 30 April 2026) inline <style> lines 2896-2956.

   Phase-1 audit at v0.5.49 confirmed the same 61-line block is
   byte-identical across all three locked canonicals (recap,
   upcoming, past-events archive). No scope-divergent variants —
   the lift is the proven copy.

   v0.5.49 — initial lift to base.css.
   v0.5.89 — added --2up modifier (closes #241).
   v0.5.90 — retired --2up modifier (single consumer migrated
             off; problem moved, not solved-here).
   ============================================================ */

  .recap-tail-paths {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--s-5);
  }
  @media (max-width: 880px) {
    .recap-tail-paths { grid-template-columns: 1fr; gap: var(--s-4); }
  }
  .recap-tail-path {
    display: flex; flex-direction: column;
    gap: var(--s-4);
    padding: var(--s-5);
    background: var(--paper);
    border: 1px solid var(--rule-soft);
    border-radius: 4px;
    text-decoration: none; color: inherit;
    transition: border-color var(--motion-duration-ui) var(--motion-ease-ui),
                box-shadow var(--motion-duration-content) var(--motion-ease-content),
                transform var(--motion-duration-ui) var(--motion-ease-ui);
  }
  .recap-tail-path:hover {
    border-color: var(--ink);
    box-shadow: 0 8px 24px rgba(20,20,19,.04);
    transform: translateY(-1px);
  }
  .recap-tail-path-kicker {
    font-family: var(--sans); font-weight: 600;
    font-size: 11px; line-height: 1;
    letter-spacing: 0.18em; text-transform: uppercase;
    color: var(--accent);
  }
  .recap-tail-path-headline {
    margin: 0;
    font-family: var(--serif); font-weight: 400;
    font-size: clamp(20px, 1.8vw, 24px); line-height: 1.22;
    color: var(--ink);
    letter-spacing: -0.005em;
  }
  .recap-tail-path-headline em { font-style: italic; }
  .recap-tail-path-supporting {
    font-family: var(--sans); font-weight: 400;
    font-size: 13px; line-height: 1.55;
    color: var(--ink-soft);
    flex: 1;
  }
  .recap-tail-path-cta {
    font-family: var(--sans); font-weight: 600;
    font-size: 12px; line-height: 1;
    letter-spacing: 0.06em;
    color: var(--ink);
    padding-top: var(--s-3);
    border-top: 1px solid var(--rule-soft);
    display: inline-flex; align-items: center; gap: var(--s-2);
    transition: color var(--motion-duration-ui) var(--motion-ease-ui);
  }
  .recap-tail-path:hover .recap-tail-path-cta { color: var(--accent); }
  .recap-tail-path-cta-arrow {
    transition: transform var(--motion-duration-ui) var(--motion-ease-ui);
  }
  .recap-tail-path:hover .recap-tail-path-cta-arrow {
    transform: translateX(3px);
  }
