/* =========================================================================
   case.html — fresh editorial template
   All classes prefixed with .v2- to isolate from the existing template.
   Same structure in dark + light modes; only colors change.
========================================================================= */

:root{
  /* Dark mode (atmospheric) — default until the head script flips theme */
  --v2-bg:            #181818;
  --v2-surface:       #1F1F1F;
  --v2-surface-2:     #262626;
  --v2-text:          #ECE7DE;
  --v2-text-muted:    #999591;
  --v2-text-subtle:   #6B6864;
  --v2-rule:          rgba(255, 255, 255, .08);
  --v2-rule-strong:   rgba(255, 255, 255, .18);
  /* Highlight derived from --v2-accent (soft pink) at 32% opacity —
     threads the brand accent through marked-up text so the page reads
     as a single authored surface, not generic editorial chrome.
     Bumped from .24 → .32 so the wash registers as emphasis on dark
     surfaces without overpowering the text on top. */
  --v2-highlight:     rgba(255, 184, 209, .32);
  --v2-pill-bg:       #ECE7DE;
  --v2-pill-text:     #181818;
  --v2-link:          #ECE7DE;

  /* Accent — used sparingly for interactive emphasis (BA toggle pill,
     TOC active dot). Soft pink in dark mode mirrors the homepage. */
  --v2-accent:        #FFB8D1;
  --v2-accent-soft:   rgba(255, 184, 209, .18);
  --v2-on-accent:     #181818;

  /* Case-study content caps at 1280px (narrower than the homepage's
     1750px) — the editorial column + sticky TOC don't benefit from
     extra canvas, and tightening the container keeps the reading
     experience focused.
     --v2-pad inherits the homepage's --pad-x token so both surfaces
     ramp padding identically across breakpoints (60/32/20 at
     1200/745). One source of truth for edge padding. */
  --v2-container:     1280px;
  --v2-pad:           var(--pad-x, 60px);
  --v2-ease:          cubic-bezier(.2,.7,.2,1);
  --v2-ease-soft:     cubic-bezier(.4,0,.2,1);
}

:root[data-theme="light"]{
  /* Light mode — harmonized with the homepage's warm-family palette.
     Previous values (#FAFAFA / #F0F0F0 / #ECECEC) were cool grays,
     which read as a different "room" from the warm-white --ground on
     the homepage. These warm-family equivalents keep the same lightness
     relationships (page → recessed surface → deeper recessed) but
     stay in temperature with --ground and --paper-2. */
  --v2-bg:            #F9F8F4;
  --v2-surface:       #EBEAE5;
  --v2-surface-2:     #E5E3DE;
  --v2-text:          #1A1A1A;
  --v2-text-muted:    #6B6B6B;
  --v2-text-subtle:   #A0A0A0;
  --v2-rule:          #E0E0E0;
  --v2-rule-strong:   #CBCBCB;
  /* Light-mode highlight uses --v2-accent (deep rose) at 24% opacity.
     Bumped from .18 → .24 so the wash holds its weight against the
     bright page background. Deep rose is a saturated hue, so this
     stays well under the threshold where the text on top would lose
     contrast. */
  --v2-highlight:     rgba(168, 77, 115, .24);
  --v2-pill-bg:       #1A1A1A;
  --v2-pill-text:     #FFFFFF;
  --v2-link:          #1A1A1A;

  --v2-accent:        #A84D73;
  --v2-accent-soft:   rgba(168, 77, 115, .14);
  --v2-on-accent:     #FFFFFF;
}

/* =========================================================================
   Base
========================================================================= */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* scroll-behavior left at default ('auto'). In-page anchor clicks are
   animated by the smooth-scroll JS (which preventDefault's and runs
   its own easing). Default 'auto' means initial-load hash navigation
   snaps to the anchor instead of animating from the top — matters
   when a case study's "Back to work" link returns to index.html#work. */
body{
  /* Body font, bg, color now use homepage tokens for visual cohesion
     with the rest of the site. Reading metrics (size/line-height/
     letter-spacing) kept at the v2 values for now — case studies are
     reading-heavy, so the slightly smaller size + looser leading is
     intentional. Adjust later if it looks off against Plus Jakarta Sans.
     Original values (for revert):
       font-family: 'Inter', system-ui, -apple-system, sans-serif;
       color: var(--v2-text); background: var(--v2-bg); */
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 15px;
  line-height: 1.6;
  letter-spacing: -0.002em;
  color: var(--ink);
  background: var(--ground);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color .35s var(--v2-ease), color .35s var(--v2-ease);
  min-height: 100vh;
}

/* Nav background matches the page bg — same "invisible chrome at rest"
   pattern as homepage. Now that body bg is var(--ground), nav matches.
   Original (for revert): background: var(--v2-bg); */
nav.top{
  background:var(--ground);
}
a{ color: var(--v2-link); text-decoration: none; }
a:hover{ text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; text-decoration-color: var(--v2-rule-strong); }
/* Nav links on case study pages should NOT get the global underline-on-hover
   behavior — they have their own color-shift hover treatment that matches
   the homepage nav. Without this override, the underline animation appears
   on case studies but not the homepage, breaking nav consistency. */
nav.top a:hover{ text-decoration: none; }
button{ background: none; border: 0; color: inherit; font: inherit; cursor: pointer; }
img{ max-width: 100%; height: auto; display: block; }
mark{
  background: var(--v2-highlight);
  color: var(--v2-text);
  padding: 1px 3px;
  border-radius: 2px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* =========================================================================
   Eyebrow — used across the template
========================================================================= */
.v2-eyebrow{
  display: inline-block;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  /* Accent pop — adopted from the PIV "HOW I WORK" reference. Section
     eyebrows now carry a small pink moment that signals "this is its
     own chapter," giving the page the editorial rhythm the references
     all share without adding chrome.
     Original (for revert): color: var(--v2-text-muted); */
  color: var(--accent);
}

/* =========================================================================
   The case study now uses the homepage's global nav (.top) and footer
   from styles.css. The v2 .v2-nav / .v2-footer rules have been removed.
   Back-to-work row sits beneath the floating nav at the top of main.
========================================================================= */
.v2-back-row{
  padding: 28px 0 0;
}
/* Back to work link — was muted + tiny + floaty. Now stronger:
   - Color shifts from --v2-text-muted to --v2-text (full text weight)
   - Font-size bumps from 12px to 14px (Inter at 500)
   - Letter-spacing tightens slightly
   - Hover treatment unchanged (color stays full, arrow slides) */
.v2-back-link{
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 14px;
  letter-spacing: -0.005em;
  color: var(--v2-text);
  transition: color .2s var(--v2-ease), gap .25s var(--v2-ease);
}
.v2-back-link:hover{
  color: var(--v2-accent);
  text-decoration: none;
  gap: 12px;
}

/* =========================================================================
   Theme toggle — floating pill (mirrors the homepage component)
========================================================================= */
.theme-toggle{
  position: fixed;
  bottom: 56px; right: 56px;
  width: 88px; height: 42px;
  border-radius: 999px;
  background: var(--v2-surface);
  border: 1px solid var(--v2-rule);
  display: flex; align-items: center;
  padding: 3px;
  cursor: pointer;
  z-index: 90;
  box-shadow:
    0 16px 36px -14px rgba(0, 0, 0, .35),
    0 6px 14px -4px rgba(0, 0, 0, .22),
    0 1px 2px rgba(0, 0, 0, .14);
  transition:
    background-color .35s var(--v2-ease),
    border-color .35s var(--v2-ease),
    box-shadow .35s var(--v2-ease);
}
.theme-toggle:hover{
  border-color: var(--v2-rule-strong);
}
.theme-toggle:focus-visible{
  outline: none;
  border-color: var(--v2-accent);
  box-shadow:
    0 0 0 3px var(--v2-accent-soft),
    0 16px 36px -14px rgba(0, 0, 0, .35);
}
.theme-toggle .knob{
  position: relative;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: var(--v2-accent);
  color: var(--v2-on-accent);
  display: flex; align-items: center; justify-content: center;
  transform: translateX(0);
  transition:
    transform .48s cubic-bezier(.34, 1.5, .64, 1),
    background-color .35s var(--v2-ease),
    color .35s var(--v2-ease);
  box-shadow:
    0 2px 6px rgba(0, 0, 0, .22),
    0 1px 2px rgba(0, 0, 0, .14);
}
.theme-toggle .icon{
  position: absolute;
  width: 17px; height: 17px;
  transform-origin: center;
  transition:
    opacity .28s var(--v2-ease),
    transform .42s var(--v2-ease-soft);
}
.theme-toggle .icon-moon{
  opacity: 1;
  transform: rotate(0) scale(1);
}
.theme-toggle .icon-sun{
  opacity: 0;
  transform: rotate(-70deg) scale(.6);
}
/* Light mode: knob slides right, sun replaces moon. Travel = 88 - 6 - 36 = 46. */
:root[data-theme="light"] .theme-toggle .knob{ transform: translateX(46px); }
:root[data-theme="light"] .theme-toggle .icon-moon{ opacity: 0; transform: rotate(70deg) scale(.6); }
:root[data-theme="light"] .theme-toggle .icon-sun{ opacity: 1; transform: rotate(0) scale(1); }

/* =========================================================================
   Main container — top padding clears the floating .top nav.
========================================================================= */
.v2-main{
  max-width: var(--v2-container);
  margin: 0 auto;
  padding: 64px var(--v2-pad) 0;
  /* content-box so max-width caps CONTENT at 1280px, padding adds
     outside that — matches the spec ("max width excluding padding"). */
  box-sizing: content-box;
}

/* =========================================================================
   Hero — content structure mirrors the original template (title,
   pills, brief overview, metadata) with a divider beneath.
========================================================================= */
/* Hero — two-column composition.
   • LEFT (1.6fr): titleblock — label + description + pills, max-width
     constrained so it doesn't fill the column when content is short.
   • RIGHT (1fr): meta — Role / Tools / Timeline as a vertical stack
     ("spec sheet" pattern), top-aligned with the title.
   gap:80px gives intentional breathing room between the two columns.
   align-items:start so meta hugs the top edge alongside the label.
   Mobile collapses to single column (see responsive block below). */
.v2-hero{
  padding: 64px 0 36px;
  border-bottom: 1px solid var(--v2-rule);
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 80px;
  align-items: start;
}
.v2-hero-titleblock{
  /* Cap so the description doesn't pull wide and leave the layout looking
     like the right column is "wasted space" — at narrower widths the
     titleblock fills naturally. */
  max-width: 56ch;
}
/* Editorial two-tier hero — mirrors the homepage work-row pattern:
   • .v2-hero-label = short Anton uppercase concept noun (e.g. "AI COWORKER")
                      that sets the case's identity, big and brand-anchoring.
   • .v2-hero-desc  = longer Inter descriptive sentence (the "dek") that
                      carries the actual meaning in a more readable register.
   Anton works at this scale because the labels are 2-3 words. The longer
   descriptive sentence stays in Inter so it remains legible as continuous
   reading copy. */
.v2-hero-label{
  /* Was 'Anton', sans-serif. Swapped to Antonio so case study big-display
     type matches the homepage's hero/about/work type (all Antonio).
     To revert: font-family: 'Anton', sans-serif; */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(40px, 5vw, 76px);
  line-height: 0.98;
  letter-spacing: -0.012em;
  color: var(--v2-text);
  margin: 0 0 14px 0;
}
.v2-hero-desc{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: clamp(18px, 1.5vw, 22px);
  line-height: 1.45;
  letter-spacing: -0.003em;
  color: var(--v2-text-muted);
  margin: 0 0 24px 0;
  max-width: 55ch;
}
.v2-hero-title{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: clamp(28px, 3.4vw, 42px);
  line-height: 1.18;
  letter-spacing: -0.022em;
  color: var(--v2-text);
  margin-bottom: 18px;
  /* No max-width — the title spans the full hero column. text-wrap:
     balance still distributes characters evenly across whatever number
     of lines the title needs, so longer titles wrap into balanced two-
     or three-liners without a hard-coded ceiling. */
  text-wrap: balance;
}

/* Pills — primary category + lenses. Now styled to match the homepage's
   row-pills treatment exactly: plain uppercase text, no pill shape, same
   typography (Plus Jakarta Sans 600, 11px, .08em tracked uppercase, muted
   color via color-mix). The pill-shape (border + border-radius + padding +
   filled bg) was the old case-study-specific styling; dropping it for
   cohesion with the work section.
   Original v2 styles (for revert):
     .v2-pill: padding:5px 11px; border-radius:999px;
       border:1px solid var(--v2-rule-strong); color:var(--v2-text-muted);
       background:transparent; font-weight:500; letter-spacing:0;
     .v2-pill-filled: background:var(--v2-pill-bg); color:var(--v2-pill-text);
       border-color:var(--v2-pill-bg); font-weight:600; */
.v2-hero-pills{
  display: flex;
  flex-wrap: wrap;
  /* Wider gap since pills no longer have enclosing shapes — the gap
     does the work of visually separating each label. */
  gap: 18px;
  align-items: center;
  margin-bottom: 28px;
}
/* All pills render at full --ink. No primary/secondary differentiation
   because the case study's hero pills aren't interactive and don't need
   to communicate hierarchy the way the homepage row pills do (which use
   the muted-to-ink shift to signal "row is active"). On the case study,
   the pills are just the project's taxonomy at the top of the page —
   read at a glance, all equally relevant. The .v2-pill-filled override
   is left as a no-op so existing markup (class="v2-pill v2-pill-filled")
   doesn't need to change. */
.v2-pill{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
}
.v2-pill-filled{
  /* No-op kept for markup compatibility. */
}

/* Metadata — vertical "spec sheet" stack with breathing room.
   Each item: small uppercase eyebrow (label) + bigger Inter value.
   Designed to feel substantial on the right side of the hero, not
   floating-and-tight. Padding-top aligns first eyebrow with the cap
   line of the Anton title; gap creates editorial spacing between items. */
.v2-hero-meta{
  display: flex;
  flex-direction: column;
  gap: 36px;
  padding-top: 14px;
}
.v2-hero-meta-item{
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.v2-hero-meta-label{
  /* Was 'Satoshi', 'Inter', sans-serif; weight 500. Swapped to PJS 600
     to fully decommission Satoshi and match the homepage's small-label
     register. Weight bumped to 600 because PJS at 500 reads softer than
     Satoshi at 500 — 600 restores the visual confidence. */
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-hero-meta-value{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 16px;
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: var(--v2-text);
}

/* =========================================================================
   TLDR
========================================================================= */
.v2-tldr-section{
  display: grid;
  /* 180px aside + 140px gap = the right column starts at the same
     horizontal position as the story's content column below. Aside
     stays tight to its eyebrow text so the table sits close to the
     reader's eye, not floating across an empty column. */
  grid-template-columns: 180px 1fr;
  gap: 140px;
  padding: 40px 0;
  /* No border-top — the hero's own border-bottom is the divider. */
}
.v2-tldr-aside .v2-eyebrow{
  position: sticky;
  top: 88px;
}
.v2-tldr-table{
  display: flex;
  flex-direction: column;
}
.v2-tldr-row{
  display: grid;
  grid-template-columns: 140px 1fr;
  gap: 32px;
  padding: 18px 0;
  /* Hairline at 40% strength of --v2-rule so the row dividers register
     as quiet rhythm rather than a fence. Was: 1px solid var(--v2-rule).
     Once we fully migrate v2 tokens this should become var(--rule). */
  border-bottom: 1px solid color-mix(in oklab, var(--v2-rule) 40%, transparent);
  align-items: baseline;
}
.v2-tldr-row:first-child{ padding-top: 0; }
.v2-tldr-row:last-child{ padding-bottom: 0; border-bottom: 0; }
.v2-tldr-key{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 13px;
  color: var(--v2-text-muted);
  letter-spacing: -0.002em;
}
.v2-tldr-val{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 14.5px;
  line-height: 1.62;
  color: var(--v2-text);
  letter-spacing: -0.002em;
}

/* =========================================================================
   Key metrics hero — editorial display.
   Label sits ABOVE the value as an eyebrow. Value is set in Anton at
   display scale so the numbers anchor the page. Generous gap creates
   the negative space the layout needs to feel intentional, not crammed.
========================================================================= */
.v2-metric-hero{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 56px 48px;
  padding: 72px 0 80px;
}
.v2-metric-hero-item{
  display: flex;
  flex-direction: column;
  /* Label reads as caption under the big number — close enough to
     pair, far enough to breathe (was 8px, felt too tight). */
  gap: 14px;
  padding-top: 0;
  border-top: 0;
}
.v2-metric-hero-label{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: .10em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-metric-hero-value{
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: clamp(64px, 9vw, 112px);
  line-height: .92;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  /* Accent-2 differentiates the metric VALUE (the result) from its
     accent-1 LABEL (the category). Different semantic role → different
     color. Note: --accent-2 was previously reserved for the You cursor;
     this is an intentional relaxation of that reservation. */
  color: var(--accent-2);
}

/* Inline section metrics — same editorial logic at smaller scale.
   Used when a section drops in a {type:'metrics'} module mid-story.
   Larger bottom margin (80px) gives meaningful breathing room from
   whatever module follows (often a callout). */
.v2-metric-row{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 32px;
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
}
.v2-metric{
  display: flex;
  flex-direction: column;
  /* Label reads as caption under the number, with breathing room
     (was 8px, felt too tight). Hairlines + top padding dropped —
     same flat editorial treatment as v2-metric-hero. */
  gap: 14px;
  padding-top: 0;
  border-top: 0;
}
.v2-metric-label{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 10.5px;
  letter-spacing: .10em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-metric-value{
  /* Was 'Anton', 'Inter', sans-serif. The Inter fallback was dead (very
     different letter shapes) — swapped to Antonio with a clean sans
     fallback so big display numbers match the homepage display voice. */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: clamp(44px, 6vw, 72px);
  line-height: .92;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--v2-text);
}

/* =========================================================================
   Story (2-col)
========================================================================= */
.v2-story{
  display: grid;
  /* 180px TOC + 140px gap. The widened gap separates the navigation
     from the body column intentionally, giving the case study a
     designed "rail + read" composition rather than a tight column
     pair. Matches the TLDR section above for visual rhythm. */
  grid-template-columns: 180px 1fr;
  gap: 140px;
  padding: 96px 0;
  align-items: start;
}

/* =========================================================================
   Sticky TOC
========================================================================= */
.v2-toc{
  position: sticky;
  top: 88px;
}
.v2-toc-inner{ outline: none; }
/* TOC — continuous rounded vertical track + rounded accent pill on the
   current section. The track is 4px wide so the pill (also 4px) sits
   inside it, not extending beyond. Both have border-radius:999px so
   their ends read as fully rounded (pill, not bar). Replaces the older
   per-link dot indicator. */
.v2-toc-list{
  position: relative;
  margin: 0;
  padding: 0 0 0 20px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Continuous quiet track — runs the full vertical extent of the list. */
.v2-toc-list::before{
  content: '';
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 4px;
  background: color-mix(in oklab, var(--mid) 25%, transparent);
  border-radius: 999px;
  pointer-events: none;
}
.v2-toc-list li{
  position: relative;
}
.v2-toc-link{
  position: relative;
  display: block;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--mid);
  padding: 8px 0;
  transition: color .2s var(--ease);
}
.v2-toc-link:hover{ color: var(--ink); text-decoration: none; }
.v2-toc-link.is-current,
.v2-toc-link[aria-current="true"]{
  color: var(--ink);
}
/* Active indicator — rounded accent pill overlaying the track at the
   current item's position. Same width as the track (4px) so it sits
   exactly inside it. */
.v2-toc-link.is-current::before,
.v2-toc-link[aria-current="true"]::before{
  content: '';
  position: absolute;
  left: -20px;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 18px;
  background: var(--accent);
  border-radius: 999px;
  pointer-events: none;
}

/* =========================================================================
   Content + section
========================================================================= */
.v2-content{
  /* 800px gives ~80 characters per line in Inter 15px — right at
     Bringhurst's comfortable upper bound. A touch wider than the
     original 760px (which felt tight) but tighter than 840px (which
     read as utilitarian rather than editorial). */
  max-width: 800px;
  min-width: 0;
}
.v2-section{
  /* Unified spacing system:
     - 128px between sections (this margin-bottom)
     - 40px between section header and first module (header's margin-bottom)
     - 64px between modules within a section (each module's margin-bottom) */
  margin-bottom: 128px;
  /* Sticky nav (.top) reserves the top ~80px. Anchor scrolls add 24px
     of breathing room so the section title isn't kissing the nav edge. */
  scroll-margin-top: 104px;
}
.v2-section:last-child{ margin-bottom: 0; }
.v2-section-header{ margin-bottom: 40px; }
.v2-section-header .v2-eyebrow{
  display: block;
  margin-bottom: 12px;
}
.v2-section-title{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: clamp(20px, 2.2vw, 26px);
  line-height: 1.3;
  letter-spacing: -0.018em;
  color: var(--v2-text);
  /* No max-width — title fills the full body column. text-wrap:
     balance still distributes long titles into balanced lines. */
  text-wrap: balance;
}
.v2-section-body{
  margin-bottom: 40px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* No max-width — the .v2-content column already caps width at 760px,
     which is a comfortable reading measure for 15px body copy. */
}
.v2-section-body p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.72;
  color: var(--v2-text);
  letter-spacing: -0.002em;
}

/* =========================================================================
   Insight cards (decisions, findings, observations)
   .v2-insight-group wraps the optional label + card grid so the
   label sits tight against the cards (12px gap), reads as a group
   label, and matches the bottom margin of a plain decisions module
   (no label) so the overall rhythm stays consistent.
========================================================================= */
.v2-insight-group{
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
}
.v2-insight-group-label{
  display: block;
  margin-bottom: 12px;
}
.v2-insight-grid{
  display: grid;
  gap: 40px;
  /* Small L/R padding insets the row a touch from the body copy
     boundaries — keeps the cards as a recognizable group without
     feeling flush against the column edges. Cards inside have no
     padding (see .v2-insight-card); this is the only chrome. */
  padding: 0 12px;
}
/* When a grid sits inside a group, the group already handles outer
   spacing — the grid resets its own. */
.v2-insight-group .v2-insight-grid{
  margin: 0;
}
.v2-insight-grid[data-count="2"]{ grid-template-columns: 1fr 1fr; }
.v2-insight-grid[data-count="3"]{ grid-template-columns: 1fr 1fr 1fr; }
.v2-insight-grid[data-count="4"]{ grid-template-columns: 1fr 1fr; }

.v2-insight-card{
  /* No fill, no border, no radius, no padding — typography carries
     the cards. Removing L/R padding lets the leftmost card's text
     align flush with body copy's left edge (and rightmost with
     right edge), so the row reads as a natural continuation of the
     content column rather than three inset blocks. Visual breathing
     room between cards comes from the grid gap alone. */
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.v2-insight-num{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .04em;
  color: var(--v2-text-subtle);
  margin-bottom: 2px;
}
.v2-insight-title{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.35;
  color: var(--v2-text);
  letter-spacing: -0.012em;
}
.v2-insight-body{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v2-text-muted);
  letter-spacing: -0.002em;
}

/* =========================================================================
   Figure (single image, gallery, comparison cells, etc.)
   Height cap: 80vh. Anything taller letterboxes inside the bordered
   frame (reads as intentional matting). The zoom modal handles
   "see at full fidelity" — page-level layout is about pacing.
========================================================================= */
.v2-figure{
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
  /* Containerization — matches .v2-ba treatment. Image + caption sit
     inside a single warm-fill container so they read as one unit
     instead of stacked separate elements. Uses --asset-bg (was
     --paper-2) — asset containers diverged from row-hover chrome
     when --paper-2 went quieter (#F3F1EA) in light mode; --asset-bg
     keeps the more-defined #EBEAE5 framing here. Dark-mode value
     is unchanged. */
  background: var(--asset-bg);
  border-radius: 16px;
  padding: 32px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}
/* Inside the container, the caption's own margin-top is redundant
   (the flex gap already handles spacing). Null it to prevent
   compounded spacing. */
.v2-figure > .v2-caption{
  margin-top: 0;
}
/* Single image container — a frame around the image, NOT a fixed box.
   The cap is on the image itself (below), so the container hugs the
   image's rendered size + padding. No overflow:hidden, no max-height
   on the container — those were what produced the cropped-artifact bug.
   The image always renders complete; the magnifier cursor signals
   "click to zoom" for closer reading. */
.v2-figure-img{
  width: 100%;
  /* No fill, no border, no radius — image sits on the page bg directly.
     Removes the SaaS-chrome "framed" look in favor of editorial
     image-on-page presentation. */
  border-radius: 0;
  background: transparent;
  border: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Padding kept at 0 — no matting needed without a frame. */
  padding: 0;
}
.v2-figure-img img{
  display: block;
  max-width: 100%;
  /* 60vh cap matches the gallery slide height and BA stage height —
     all three asset module types now top out at the same image
     vertical extent for visual consistency. */
  max-height: 60vh;
  width: auto;
  height: auto;
  object-fit: contain;
}
.v2-caption{
  display: block;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  /* Unified caption typography — matches .v2-gallery-caption so every
     asset caption across the case study (standalone, gallery, pair,
     etc.) shares one consistent voice. */
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v2-text);
  /* 20px gap between asset and caption matches .v2-gallery's flex
     column gap (between track-wrap and footer). Standalone images
     and before/after captions now share the same vertical rhythm
     as gallery captions. */
  margin-top: 20px;
  letter-spacing: -0.002em;
  /* Explicitly null the max-width that the homepage's generic
     `figcaption, .module-caption { max-width: 48ch }` rule (in
     styles.css) imposes on every figcaption. Without this override
     the caption stays capped at ~280px even though the column is
     800px. The case study caption should fill its container. */
  max-width: none;
  width: 100%;
}

/* =========================================================================
   Pair (two-column / comparison)
   Tighter height cap (60vh per cell) because two side-by-side artifacts
   at 80vh each would each fill a screen — heavy. 60vh reads as a
   genuine comparison, not two competing hero shots.
========================================================================= */
.v2-pair{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin: 40px 0 48px;
  align-items: start;
}
.v2-pair-col{ min-width: 0; }
.v2-pair .v2-figure{ margin: 0; }
/* Pair-cell image cap (smaller than single, since two cells share
   the row). Same calc pattern — IMG-level cap, container hugs. */
.v2-pair .v2-figure-img img{
  max-height: calc(60vh - 48px);
}

/* =========================================================================
   Stage-frame — case-study-stage composite. Renders 2-4 numbered panels
   in a row or 2×2 grid, with a stage label on top and per-image captions
   under each panel. Used by TTM Sections 4-6 to show within-stage
   evolution of a concept across multiple wireframes. Designed to read as
   one intentional grouping, not a screenshot dump.
========================================================================= */
.v2-stage-frame{
  margin: 40px 0 48px;
  padding: 28px;
  background: var(--v2-surface);
  border: 1px solid var(--v2-rule);
  border-radius: 10px;
}
.v2-stage-frame-label{
  /* Eyebrow at the top of the composite — same treatment as section
     eyebrows so stage frames feel like nested sections. */
  display: block;
  margin-bottom: 6px;
}
.v2-stage-frame-headline{
  /* Module headline — was 'Fraunces', serif. Swapped to Antonio so case
     study editorial subheads share the homepage's display voice (work
     row h3s, about h2, etc. all use Antonio). To revert this slot alone,
     change back to: font-family: 'Fraunces', serif; */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--v2-text);
  margin: 0 0 24px;
}
.v2-stage-frame-grid{
  display: grid;
  gap: 28px;
}
/* Row layout — 2 or 3 panels side by side. Used when image count is 2 or 3. */
.v2-stage-frame[data-layout="row"][data-count="2"] .v2-stage-frame-grid{
  grid-template-columns: 1fr 1fr;
}
.v2-stage-frame[data-layout="row"][data-count="3"] .v2-stage-frame-grid{
  grid-template-columns: 1fr 1fr 1fr;
}
/* 2×2 grid — used when image count is 4. Reading order is
   left → right → down (matches default web reading pattern). */
.v2-stage-frame[data-layout="grid"][data-count="4"] .v2-stage-frame-grid{
  grid-template-columns: 1fr 1fr;
}

/* Individual panel — image with numeric tag overlay + caption underneath.
   Each panel is its own figure so semantics stay clean and screen readers
   announce each image+caption pair separately. */
.v2-stage-frame-panel{ margin: 0; }
.v2-stage-frame-img{
  position: relative;
  width: 100%;
  border-radius: 6px;
  background: var(--v2-surface-2);
  border: 1px solid var(--v2-rule);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  /* Square-ish aspect so all panels read as a coherent grid even when
     the underlying image proportions differ. Image inside scales via
     object-fit:contain so nothing crops. */
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
.v2-stage-frame-img img{
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Numeric tag in the top-left corner — small editorial badge that
   tells the reader the panel order. Important for the 2×2 grid where
   reading order would otherwise be ambiguous. */
.v2-stage-frame-num{
  position: absolute;
  top: 10px;
  left: 10px;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 10px;
  letter-spacing: .12em;
  color: var(--v2-text);
  background: var(--v2-surface);
  border: 1px solid var(--v2-rule);
  border-radius: 3px;
  padding: 3px 6px;
  /* Sits above the image without blocking interaction. */
  pointer-events: none;
}
.v2-stage-frame-caption{
  /* Per-panel caption sits directly under its image, not collected
     at the end of the frame. Each panel tells its own story. */
  margin-top: 12px;
}

/* Responsive — at narrow widths, collapse 3-in-a-row and 2×2 grids
   to single-column stacks so individual screen UI stays readable. */
@media (max-width: 720px){
  .v2-stage-frame[data-layout="row"][data-count="3"] .v2-stage-frame-grid,
  .v2-stage-frame[data-layout="grid"][data-count="4"] .v2-stage-frame-grid{
    grid-template-columns: 1fr;
  }
}

/* =========================================================================
   Prose — inline body paragraphs between modules. Visually identical
   to .v2-section-body paragraphs (same font, size, line-height, color)
   so an inline paragraph reads as part of the section's voice rather
   than as a separate styled element. The margin matches the spacing
   between other modules.
========================================================================= */
.v2-prose{
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.v2-prose p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 15px;
  line-height: 1.72;
  color: var(--v2-text);
  letter-spacing: -0.002em;
}

/* =========================================================================
   Wire-grid — dense thumbnail mosaic. Used in the Process section to
   visualize iteration VOLUME per pillar. Each thumb is small (~140px)
   on purpose — readers see density and trust the iteration happened,
   they don't try to decode individual screens. Click-to-zoom still
   surfaces full detail for anyone curious.
========================================================================= */
.v2-wire-grid{
  margin: 32px 0 40px;
}
.v2-wire-grid-label{
  display: block;
  margin-bottom: 4px;
}
.v2-wire-grid-headline{
  /* Module headline — was 'Fraunces', serif. Swapped to Antonio so case
     study editorial subheads share the homepage's display voice (work
     row h3s, about h2, etc. all use Antonio). To revert this slot alone,
     change back to: font-family: 'Fraunces', serif; */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--v2-text);
  margin: 0 0 16px;
}
.v2-wire-grid-mosaic{
  /* Auto-fill grid — adapts to available width with thumbnails at a
     fixed minimum size. Result reads as a designed mosaic rather than
     a uniform grid. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 8px;
}
.v2-wire-grid-thumb{
  margin: 0;
}
.v2-wire-grid-thumb .v2-stage-frame-img{
  width: 100%;
  margin: 0;
  aspect-ratio: 4 / 3;
  padding: 6px;
}
.v2-wire-grid-caption{
  margin-top: 10px;
}

/* =========================================================================
   Walkthrough — sequential numbered hi-fi tour for a pillar section.
   Each step gets a header (number + label) and the image renders full
   width like a single figure. Steps stack vertically so the reader
   scrolls through the workflow naturally.
========================================================================= */
.v2-walkthrough{
  margin: 24px 0 40px;
  display: flex;
  flex-direction: column;
  gap: 56px;
}
.v2-walkthrough-step{
  margin: 0;
}
.v2-walkthrough-step-header{
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-bottom: 14px;
}
.v2-walkthrough-step-num{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-walkthrough-step-label{
  /* Module headline — was 'Fraunces', serif. Swapped to Antonio so case
     study editorial subheads share the homepage's display voice (work
     row h3s, about h2, etc. all use Antonio). To revert this slot alone,
     change back to: font-family: 'Fraunces', serif; */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--v2-text);
}

/* =========================================================================
   Evolution-strip — horizontal flow of screens showing ONE piece of the
   product evolving across stages, ending at hi-fi. Each step is one
   screen with a version label above it. Subtle arrow connectors between
   steps signal evolution direction. The whole strip lives inside a
   bordered frame that matches the existing v2-figure-img matting so it
   reads as a single designed unit, not a row of screenshots.

   Image scale varies with step count — 2 steps = larger images, 4 steps
   = smaller. Each individual image still click-to-zooms for full detail.
========================================================================= */
.v2-evolution-strip{
  margin: 40px 0 48px;
}
.v2-evolution-strip-label{
  display: block;
  margin-bottom: 10px;
}
.v2-evolution-strip-frame{
  /* Mirrors v2-figure-img: same matting background, border, radius,
     and internal padding — so a strip reads visually like the other
     bordered artifact containers in the case study. */
  border-radius: 6px;
  background: var(--v2-surface-2);
  border: 1px solid var(--v2-rule);
  padding: 24px;
}
.v2-evolution-strip-row{
  display: flex;
  align-items: stretch;
  gap: 10px;
}
.v2-evolution-strip-step{
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
}
.v2-evolution-strip-step-label{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 10px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
  text-align: center;
}
/* The image container inside a strip step. Reuses .v2-stage-frame-img
   for visual consistency with stage-gallery panels, but overrides the
   aspect-ratio to be more landscape (matching how the wireframes were
   captured) and reduces padding so more pixels go to the image. */
.v2-evolution-strip-step .v2-stage-frame-img{
  width: 100%;
  margin: 0;
  aspect-ratio: 4 / 3;
  padding: 10px;
  flex: 1;
}
.v2-evolution-strip-connector{
  /* Arrow between steps. SVG inside is sized to match the label height
     so the connector visually centers with the label row above the
     images. */
  flex-shrink: 0;
  width: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--v2-text-muted);
}
.v2-evolution-strip-connector svg{
  width: 18px;
  height: 18px;
}
.v2-evolution-strip-caption{
  margin-top: 14px;
}

/* Responsive — collapse the horizontal row into a vertical stack on
   narrow widths so individual images stay readable. Connectors flip
   from horizontal arrows to vertical (down arrows) via rotation. */
@media (max-width: 720px){
  .v2-evolution-strip-row{ flex-direction: column; }
  .v2-evolution-strip-connector{
    width: 100%;
    height: 24px;
  }
  .v2-evolution-strip-connector svg{
    transform: rotate(90deg);
  }
}

/* =========================================================================
   Stage-gallery — carousel where each slide is a per-stage composite.
   One paper-surface card containing: counter (top), prev/next arrows
   alongside the slide area, per-stage header (eyebrow + headline),
   image grid (2-4 panels), and a single per-stage caption below the
   grid. Two layout variants — see v2RenderStageGallery JS comment.

   Slide-area HEIGHT stays constant across stages so the carousel frame
   doesn't grow or shrink between slides — only the content inside the
   frame changes. That keeps the prev/next button position, caption
   position, and overall page flow stable as the reader navigates.
========================================================================= */
.v2-stage-gallery{ margin: 40px 0 48px; }
.v2-stage-gallery-card{
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px;
  background: var(--v2-surface);
  border: 1px solid var(--v2-rule);
  border-radius: 10px;
}
.v2-stage-gallery-header{
  display: flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 14px;
}
.v2-stage-gallery-counter{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-stage-gallery-counter-sep{ opacity: .5; }

/* Row holds the prev/next buttons alongside the slide area. Arrows
   stretch the full height of the slide area so they're easy to hit
   without overlapping content. */
.v2-stage-gallery-row{
  display: flex;
  align-items: stretch;
  gap: 12px;
}
.v2-stage-gallery-nav{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  background: transparent;
  border: 1px solid var(--v2-rule);
  border-radius: 6px;
  color: var(--v2-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color .15s, color .15s;
}
.v2-stage-gallery-nav:hover:not(:disabled){
  border-color: var(--v2-text);
  color: var(--v2-text);
}
.v2-stage-gallery-nav:disabled{
  opacity: 0.3;
  cursor: not-allowed;
}
.v2-stage-gallery-nav svg{ width: 18px; height: 18px; }

/* Stage area — the slide content lives here. flex:1 + min-width:0
   lets it absorb whatever width remains after the arrows take their
   fixed space. */
.v2-stage-gallery-stage{
  position: relative;
  flex: 1;
  min-width: 0;
}

/* Slides — only the .is-current slide is visible. No crossfade because
   the composite content (multi-image grids + structured text headers)
   would not crossfade cleanly. Simple show/hide reads as cleaner page
   navigation than a fade that briefly shows two slides on top of each
   other. */
.v2-stage-gallery-slide{ display: none; }
.v2-stage-gallery-slide.is-current{ display: block; }

.v2-stage-gallery-slide-header{ margin-bottom: 24px; }
.v2-stage-gallery-slide-header .v2-eyebrow{
  display: block;
  margin-bottom: 6px;
}
.v2-stage-gallery-headline{
  /* Module headline — was 'Fraunces', serif. Swapped to Antonio so case
     study editorial subheads share the homepage's display voice (work
     row h3s, about h2, etc. all use Antonio). To revert this slot alone,
     change back to: font-family: 'Fraunces', serif; */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  font-size: 22px;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--v2-text);
  margin: 0;
}

/* Image grid inside a slide. Two layout variants:
     - 'adaptive' (Option A): grid template tracks the image count.
         2 → 1fr 1fr, 3 → 1fr 1fr 1fr, 4 → 1fr 1fr (2 rows).
         Image size varies between stages.
     - 'fixed' (Option C): always 2×2. Empty slots stay empty.
         Image size stays consistent across all stages. Slides with
         fewer than 4 images show visible negative space. */
.v2-stage-gallery-grid{
  display: grid;
  gap: 16px;
  margin-bottom: 24px;
}
.v2-stage-gallery-grid[data-layout="adaptive"][data-count="2"]{
  grid-template-columns: 1fr 1fr;
}
.v2-stage-gallery-grid[data-layout="adaptive"][data-count="3"]{
  grid-template-columns: 1fr 1fr 1fr;
}
.v2-stage-gallery-grid[data-layout="adaptive"][data-count="4"]{
  grid-template-columns: 1fr 1fr;
}
.v2-stage-gallery-grid[data-layout="fixed"]{
  grid-template-columns: 1fr 1fr;
  /* Force two rows even when fewer than 4 images. The empty slot
     in the bottom row stays as negative space rather than the row
     collapsing — which is what gives Option C its consistent slide
     height across stages. */
  grid-template-rows: 1fr 1fr;
}

.v2-stage-gallery-panel{ margin: 0; }

/* Per-stage caption — single block of evolution-story prose below the
   grid. Full content width so line length stays readable. Slightly
   larger than v2-caption (per-image caption style) because it's
   carrying the narrative weight of the whole stage. */
.v2-stage-gallery-caption{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--v2-text);
  letter-spacing: -0.002em;
  max-width: none;
}

/* Responsive — collapse multi-column grids to single column on narrow
   widths so individual screen UI stays readable. */
@media (max-width: 720px){
  .v2-stage-gallery-grid[data-layout="adaptive"][data-count="3"],
  .v2-stage-gallery-grid[data-layout="adaptive"][data-count="4"],
  .v2-stage-gallery-grid[data-layout="fixed"]{
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }
}

/* =========================================================================
   Gallery — horizontal carousel (Pattern B + B2)

   Layout: counter eyebrow (top) → horizontal track with all slides at
   uniform height (60vh) and variable widths → caption (bottom). No
   container chrome, no thumbnails. Each image is its own moment on
   the page. Track scrolls horizontally with native scroll-snap; arrows
   trigger smooth scrollIntoView; drag scrolls by dx; JS tracks which
   slide is most-centered to update caption + counter.

   Grouping comes from tight INNER spacing (counter ↔ track ↔ caption
   all within ~20px) and wide OUTER spacing (96px above/below the
   gallery) — no fill or border needed.
========================================================================= */
.v2-gallery{
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Containerization — matches .v2-ba and .v2-figure. Counter, track,
     caption, and arrows all sit inside one warm-fill container,
     reading as one cohesive carousel unit. Images now cut off at the
     CONTAINER edges (not the rail / viewport edge) — overflow:hidden
     enforces that boundary. Uses --asset-bg (was --paper-2) — see
     .v2-figure for the rationale on why asset containers route
     through their own token now. */
  background: var(--asset-bg);
  border-radius: 16px;
  padding: 32px;
  overflow: hidden;
}

.v2-gallery-header{
  display: flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 14px;
}
.v2-gallery-counter{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
}
.v2-gallery-counter-sep{ opacity: .5; }

/* Track wrap — no longer holds the nav arrows (they're in the footer
   row now). Kept as a wrapper so we can add scoped behavior later
   (e.g. fade edges) without restructuring. */
.v2-gallery-track-wrap{
  position: relative;
}

/* Track — horizontal scroll container. All slides sit in a row at
   uniform 60vh height. CSS scroll-snap locks each slide into place
   on scroll end. Scrollbar hidden (we use arrows + drag + trackpad
   for navigation; visible scrollbar would compete with the editorial
   feel).
   cursor:grab + .is-dragging cursor:grabbing tells the user the track
   is draggable. */
.v2-gallery-track{
  display: flex;
  gap: 24px;
  overflow-x: auto;
  /* Proximity (not mandatory): snap if the user releases near a snap
     point, but allow scrolling past the last valid snap point to reach
     slides at the end whose snap position is unreachable (would
     require scrolling past the content end). With mandatory, those
     slides snap-back-blocked and were unreachable.
     Trade-off: snap feels slightly less "locked" — but every slide
     becomes navigable. */
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
  /* user-select: none prevents text/image selection during drag. */
  user-select: none;
  -webkit-user-select: none;
}
.v2-gallery-track::-webkit-scrollbar{ display: none; }
.v2-gallery-track.is-dragging{
  cursor: grabbing;
  /* Disable snap during active drag so the track doesn't fight the
     user's finger; snap re-engages when drag ends (mouseup removes
     .is-dragging, snap kicks in at the next layout). */
  scroll-snap-type: none;
  scroll-behavior: auto;
}
/* Smooth scroll behavior for arrow-button navigation. */
.v2-gallery-track{
  scroll-behavior: smooth;
}

/* Slide — full track viewport width, fixed 60vh height. Only one
   slide visible at a time; arrows/drag scroll between slides one full
   viewport at a time. This replaces the earlier "intrinsic-width
   slides on a scrolling strip" design — variable image widths kept
   either overflowing the viewport (wide assets cut off) or rendering
   small inside an oversized slide (small assets dwarfed). Fixed-size
   slide + object-fit:contain on the image fixes both. */
.v2-gallery-slide{
  flex: 0 0 100%;
  min-width: 0;
  height: 60vh;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  scroll-snap-align: start;
}
.v2-gallery-slide img{
  display: block;
  width: 100%;
  height: 100%;
  /* object-fit:contain — image content scales to fit the slide while
     preserving aspect ratio. Letterboxing appears where aspect
     ratios diverge; this is the cost of "always fully visible, never
     cropped." Container takes the page bg so the bars read as
     intentional whitespace. */
  object-fit: contain;
  /* Pointer-events controlled so drag doesn't fire image clicks
     mid-drag (JS adds .is-dragging on track during drag). */
  pointer-events: auto;
}
.v2-gallery-track.is-dragging img{
  pointer-events: none;
}

/* Footer row — caption on the left, arrow nav on the right, both on
   the same horizontal baseline. WeWork inspo pattern. */
.v2-gallery-footer{
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
}

/* Caption — editorial body text. Sits on the left of the footer row,
   takes whatever width remains after the nav group. Updates per slide
   via JS (innerHTML swap on scroll). */
.v2-gallery-caption{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v2-text);
  letter-spacing: -0.002em;
  min-height: 20px;
  flex: 1;
  min-width: 0;
}

/* Nav group — prev + next arrows, tight together on the right side
   of the footer row. */
.v2-gallery-nav-group{
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

/* Nav button — circular, sized slightly smaller than the previous
   40px so they feel like quiet pagination controls rather than primary
   actions. Same visual vocabulary (circular pill, accent on focus). */
.v2-gallery-nav{
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .9);
  border: 0;
  color: #181818;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color .2s var(--v2-ease), opacity .2s var(--v2-ease);
}
.v2-gallery-nav svg{ width: 14px; height: 14px; }
.v2-gallery-nav:hover{
  background: rgba(255, 255, 255, 1);
}
.v2-gallery-nav:focus-visible{
  outline: 2px solid var(--v2-accent);
  outline-offset: 2px;
}
.v2-gallery-nav:disabled{
  opacity: .25;
  cursor: default;
  pointer-events: none;
}

:root[data-theme="light"] .v2-gallery-nav{
  background: rgba(40, 40, 40, .92);
  color: #FFFFFF;
}
:root[data-theme="light"] .v2-gallery-nav:hover{
  background: rgba(0, 0, 0, 1);
}

/* =========================================================================
   Before / after — centered toggle with sliding accent mask
   The toggle has a muted-text button layer underneath. A pill-shaped
   accent mask layer sits on top with accent-colored text in the same
   positions. As the mask's clip-path slides between buttons, the muted
   text below is progressively replaced by the accent text above —
   producing a smooth color reveal that follows the pill, not a sudden
   swap at the end of the animation.
========================================================================= */
.v2-ba{
  margin: 0 0 64px 0;
  display: flex;
  flex-direction: column;
  align-items: center;     /* center the toggle horizontally */
  /* 20px gap matches .v2-gallery's gap between track and caption,
     standardizing asset-to-caption rhythm across all module types. */
  gap: 20px;
  /* Containerization — wraps toggle + stage + bullets into one
     coherent comparison unit. Uses --asset-bg (was --paper-2) so
     all three asset container types (BA, single image, gallery)
     share the same fill. See .v2-figure for why this routes through
     --asset-bg instead of --paper-2 directly. */
  background: var(--asset-bg);
  border-radius: 16px;
  padding: 32px;
}
.v2-ba-stage,
.v2-ba > figcaption,
.v2-ba > .v2-ba-bullets{
  width: 100%;
  align-self: stretch;
}
/* Inside BA, the figcaption uses .v2-caption styling but the wrap's
   gap already handles spacing — null the caption's own margin-top so
   it doesn't add on top of the flex gap. */
.v2-ba > .v2-caption{
  margin-top: 0;
}

/* -------------------------------------------------------------------------
   Before/After bullet captions — replaces single-prose caption with
   two columns of bullet points (issues on before side, improvements
   on after side). Dot indicators are color-coded: muted gray for
   before/issues, accent pink for after/improvements. Spells out WHAT
   changed without making the reader figure it out from images alone.
------------------------------------------------------------------------- */
.v2-ba-bullets{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 32px;
  /* Margin-top zero — .v2-ba's flex gap (20px) already provides the
     stage-to-bullets spacing. */
  margin-top: 0;
}
.v2-ba-bullets-col{
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.v2-ba-bullets-col li{
  display: flex;
  align-items: flex-start;
  gap: 10px;
  /* Same typography as unified caption style. */
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--v2-text);
  letter-spacing: -0.002em;
}
.v2-ba-bullet-dot{
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 7px;
}
/* Before side — muted gray dots, conveys "this was wrong." */
.v2-ba-bullets-before .v2-ba-bullet-dot{
  background: color-mix(in oklab, var(--v2-text-muted) 80%, transparent);
}
/* After side — accent pink dots, conveys "this is the improvement." */
.v2-ba-bullets-after .v2-ba-bullet-dot{
  background: var(--v2-accent);
}

.v2-ba-toggle{
  position: relative;
  display: inline-flex;
  align-items: center;
  /* Toggle bg matches the PAGE bg (--ground), creating the visual
     impression of a slot cut through the container's matting. The
     active pill (--v2-text, high-contrast ink) lands on this page-bg
     well and reads as definitively "selected." No border. */
  background: var(--ground);
  border: 0;
  border-radius: 999px;
  padding: 3px;
}
.v2-ba-btn{
  position: relative;
  z-index: 1;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .04em;
  padding: 7px 16px 6px;
  border-radius: 999px;
  background: transparent;
  color: var(--v2-text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color .25s var(--v2-ease);
}
.v2-ba-btn:hover{ color: var(--v2-text); }

/* Sliding accent mask — clip-path moves between buttons. The inset()
   uses `round 999px` so the revealed shape stays pill-edged at every
   position in the animation. */
.v2-ba-mask{
  position: absolute;
  top: 3px;
  left: 3px;
  right: 3px;
  bottom: 3px;
  pointer-events: none;
  z-index: 2;
  clip-path: inset(0 100% 0 0 round 999px);
  transition: clip-path .45s var(--v2-ease-soft);
}
.v2-ba-mask-inner{
  display: flex;
  align-items: stretch;
  height: 100%;
  /* Active pill uses --v2-text (ink in light, paper in dark) instead
     of accent pink — reads as editorial "ink on paper" rather than
     branded. Less garish, more design-system-quiet. */
  background: var(--v2-text);
  border-radius: 999px;
}
.v2-ba-mask-label{
  display: inline-flex;
  align-items: center;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .04em;
  padding: 7px 16px 6px;
  /* Label color inverts the mask bg — uses --v2-bg so text on the
     ink pill reads as paper (auto-adapts per theme). */
  color: var(--v2-bg);
  white-space: nowrap;
}

/* Fixed-height stage. The previous max-height approach let the first
   state (in-flow) drive the stage size, which meant the second state
   (absolutely positioned) rendered against a different ruler. That
   produced the misaligned-frames effect where the toggle felt like
   the whole panel shifted, not just the design.
   With a fixed height, BOTH states render inside the same absolute
   box — so two matched-dimension images land in the exact same
   spatial frame and the toggle is a clean swap. */
.v2-ba-stage{
  position: relative;
  width: 100%;
  /* Stage height reduced from 80vh — that was overwhelming inside the
     new container (with toggle above + bullets below + 32px matting).
     60vh feels proportional and still gives the comparison images
     room to read. */
  height: 60vh;
  overflow: hidden;
  /* No fill, no border, no radius — both states render on page bg. */
  border-radius: 0;
  background: transparent;
  border: 0;
}
.v2-ba-state{
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 24px internal padding so the artifact doesn't touch container edges */
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .6s var(--v2-ease-soft);
  will-change: opacity;
}
.v2-ba-state.is-current{
  opacity: 1;
  pointer-events: auto;
  z-index: 2;
}
.v2-ba-state img{
  display: block;
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}

/* =========================================================================
   Callout (transition + takeaway variants)

   Reworked from the heavy "card box" treatment (background + border +
   border-radius + padding) to the Nightingale-style accent-bar
   pull-quote: vertical accent line on the left, slightly heavier
   body type, no surface chrome. Reads as an emphasized passage WITHIN
   the narrative instead of a UI box pulled OUT of the flow.

   Three variants:
     • .v2-callout (base) — accent (pink) bar, primary emphasis
     • .v2-callout-takeaway — ink (dark) bar, slightly bolder, "this is
       the lesson"
     • .v2-callout-transition — quiet centered italic, no bar (kept as a
       structural transition cue between sections)
========================================================================= */
.v2-callout{
  position: relative;
  /* Standardized module spacing: 64px below. */
  margin: 0 0 64px 0;
  /* Padding-left = bar width + tighter breathing room (was 24px). */
  padding: 8px 0 8px 18px;
  background: transparent;
  border: 0;
  border-radius: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Bar drawn as ::before pseudo (not border-left) so it can have
   rounded corners. 1.5px radius reads as a rectangle with softened
   edges — at 5px bar width, the previous 3px (60% ratio) read as
   nearly pill; 1.5px (30%) keeps the rectangle shape clearly. */
.v2-callout::before{
  content: '';
  position: absolute;
  left: 0;
  top: 2px;
  bottom: 2px;
  width: 5px;
  background: var(--accent);
  border-radius: 1.5px;
}
.v2-callout p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  /* Slightly heavier than body (which is 400) so callouts read as
     emphasized — but not full bold (600+), so inline **bold** within
     the callout text still stands out as bolder. */
  font-weight: 500;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink);
  letter-spacing: -0.003em;
  /* All callout variants left-align. The previous center-aligned
     "transition" variant is gone. */
  text-align: left;
}
/* Variants kept for backward compatibility, but visual treatment is
   now UNIFIED — same bar, same alignment, same weight as the base
   callout. The semantic distinction (base / takeaway / transition)
   no longer maps to a visual difference; intentional simplification
   so callouts feel like one consistent vocabulary. */
.v2-callout-transition,
.v2-callout-takeaway{
  background: transparent;
  text-align: left;
}
.v2-callout-transition p,
.v2-callout-takeaway p{
  color: var(--ink);
  font-size: 17px;
  font-style: normal;
  font-weight: 500;
  text-align: left;
}

/* =========================================================================
   Callout Variations (TEMP — exploration for the new design)
   Stack of design options for the takeaway callout. Replace the
   `callout` module with `callout-variations` to render this block.
   The mode-inverting interior uses --v2-surface + --v2-text, so the
   callout reads natively in both themes; the accent panel + outline
   carry the "stop and read this" emphasis.
========================================================================= */
.v2-callout-variations{
  margin: 40px 0 48px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}
.v2-callout-var-wrap{
  display: flex;
  flex-direction: column;
}
.v2-callout-var-label{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--v2-text-muted);
  margin-bottom: 10px;
  font-weight: 500;
}
.v2-callout-var{
  display: flex;
  align-items: stretch;
  border-radius: 12px;
  overflow: hidden;
  border: 1.5px solid var(--v2-accent);
  background: var(--v2-surface);
}
.v2-callout-var-icon{
  flex: 0 0 48px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--v2-accent);
  color: var(--v2-bg);
  user-select: none;
}
.v2-callout-var-icon > span{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 700;
  font-size: 20px;
  line-height: 1;
  display: block;
}
.v2-callout-var-body{
  flex: 1;
  padding: 14px 24px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
}
.v2-callout-var-body p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-size: 16px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--v2-text);
  margin: 0;
  letter-spacing: -0.003em;
}
.v2-callout-var-body p strong{
  font-weight: 600;
  color: var(--v2-text);
}

/* SVG icons inherit color via currentColor; size set inline per SVG */
.v2-callout-var-icon svg{
  display: block;
  width: 28px;
  height: 28px;
}

/* Icon-badge wrapper — present in both styles. In Style 1 it's a passive
   centering wrapper; in Style 2 it becomes the circular accent badge. */
.v2-callout-var-icon .icon-badge{
  display: flex;
  align-items: center;
  justify-content: center;
}

/* =========================================================================
   Style 2 — Soft accent card with a circular icon badge.
   Card background is desaturated (--v2-accent-soft). Icon sits in a
   solid-accent circle with breathing room from the card edge. Body text
   stays neutral (--v2-text inherited) so the accent carries through the
   bg + icon without overwhelming the message.
========================================================================= */
.v2-callout-var.style-2{
  background: var(--v2-accent-soft);
  border: 0;
  border-radius: 12px;
}
.v2-callout-var.style-2 .v2-callout-var-icon{
  background: transparent;
  flex: 0 0 64px;
}
.v2-callout-var.style-2 .v2-callout-var-icon .icon-badge{
  width: 36px;
  height: 36px;
  background: var(--v2-accent);
  border-radius: 50%;
  color: var(--v2-bg);
}
.v2-callout-var.style-2 .v2-callout-var-icon svg{
  width: 22px;
  height: 22px;
}

/* =========================================================================
   Style 3 — Minimal. Subtle outlined container with accent-colored
   standalone icon next to copy set in the homepage display font (Anton).
   The thin rule keeps the callout from reading as floating page copy
   while keeping it visually quieter than the bold banner.
========================================================================= */
.v2-callout-var.style-3{
  background: transparent;
  /* Dark mode (default) — explicit rgba so the stroke is perceptible;
     --v2-rule at .08 fades into the page in dark mode. Light mode
     overrides below back to --v2-rule, which works there. */
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 12px;
  align-items: center;
  gap: 16px;
  padding: 16px 12px;
}
:root[data-theme="light"] .v2-callout-var.style-3{
  border-color: var(--v2-rule);
}
.v2-callout-var.style-3 .v2-callout-var-icon{
  background: transparent;
  flex: 0 0 auto;
  color: var(--v2-accent);
}
.v2-callout-var.style-3 .v2-callout-var-icon .icon-badge{
  width: auto;
  height: auto;
  background: transparent;
  border-radius: 0;
  color: var(--v2-accent);
}
.v2-callout-var.style-3 .v2-callout-var-icon svg{
  width: 28px;
  height: 28px;
}
.v2-callout-var.style-3 .v2-callout-var-body{
  flex: 1;
  padding: 0;
}
.v2-callout-var.style-3 .v2-callout-var-body p{
  /* Was 'Anton', 'Formula Condensed', sans-serif. Swapped to Antonio
     so case study display + callout type shares the homepage's voice. */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  text-transform: uppercase;
  font-size: 18px;
  line-height: 1.2;
  letter-spacing: 0.02em;
  color: var(--v2-text);
}

/* Inter override — same minimal container, but body text drops to the
   standard callout typography (matches the bold-banner body type).
   Weight stays at 400 by default; **markdown** turns into <strong> at
   600 for intentional emphasis (used for "Before:" / "After:" labels,
   key phrases, etc). */
.v2-callout-var.style-3.use-inter .v2-callout-var-body p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  text-transform: none;
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -0.003em;
}

/* Standalone wrapper — used when a callout-var is rendered directly in
   a case section (outside the v2-callout-variations stack), so it gets
   the same vertical breathing room as a legacy v2-callout. */
.v2-callout-finalist{
  margin: 40px 0 48px;
}

/* =========================================================================
   Display — oversized Anton billboard moment. One per case study, used
   as a punctuation beat where a single statement deserves display
   treatment (matching the homepage hero typography).
========================================================================= */
.v2-display{
  margin: 72px 0 64px;
  /* Was 'Anton', 'Formula Condensed', sans-serif. Swapped to Antonio
     so case study display + callout type shares the homepage's voice. */
  font-family: 'Antonio', sans-serif;
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(36px, 5vw, 56px);
  line-height: 0.95;
  letter-spacing: -0.012em;
  color: var(--v2-text);
  max-width: 20ch;
}
.v2-display em{
  color: var(--v2-accent);
  font-style: normal;
}

/* =========================================================================
   PDF — PDF.js viewer with a custom minimal toolbar.
   The browser's native PDF chrome is replaced entirely. The toolbar
   exposes paging, zoom, and "open in new tab" — no download, print,
   or save. Frame uses 4/3 aspect to match the rest of the artifact
   modules (gallery, placeholder).
========================================================================= */
.v2-pdf{ margin: 40px 0 48px; }
.v2-pdf-frame{
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--v2-rule);
  background: var(--v2-surface);
  display: flex;
  flex-direction: column;
}
.v2-pdf-viewport{
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 24px;
  background: var(--v2-surface-2);
  scrollbar-width: thin;
  scrollbar-color: var(--v2-rule-strong) transparent;
}
.v2-pdf-canvas{
  display: block;
  /* The canvas's natural dimensions are device-pixel sized; CSS px
     are set explicitly by the renderer. A subtle shadow lifts the
     page off the viewport surface so the artifact reads as paper. */
  box-shadow: 0 8px 24px -12px rgba(0, 0, 0, .45),
              0 2px 6px -2px rgba(0, 0, 0, .25);
  background: #fff;
}
.v2-pdf-loading{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--v2-text-subtle);
  align-self: center;
  margin: auto;
}

/* Inline error state — shown when PDF.js can't load the file (file://
   protocol, CDN blocked, missing asset, etc.). Keeps the toolbar's
   open-in-new-tab link visible so the artifact is still reachable. */
.v2-pdf-error{
  margin: auto;
  max-width: 56ch;
  display: flex;
  flex-direction: column;
  gap: 8px;
  text-align: center;
  padding: 24px;
}
.v2-pdf-error strong{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 14px;
  color: var(--v2-text);
  letter-spacing: -0.01em;
}
.v2-pdf-error span{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13px;
  line-height: 1.5;
  color: var(--v2-text-muted);
  letter-spacing: -0.002em;
}

/* Toolbar — pinned to the bottom of the frame, dark pill chrome that
   reads as a control surface (not editorial type). Always visible so
   readers don't hunt for controls. */
.v2-pdf-toolbar{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 12px;
  background: rgba(24, 24, 24, .92);
  border-top: 1px solid rgba(255, 255, 255, .08);
  color: #ECE7DE;
  flex-wrap: wrap;
}
.v2-pdf-tool-group{
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.v2-pdf-tool-sep{
  width: 1px;
  height: 18px;
  background: rgba(255, 255, 255, .12);
  margin: 0 4px;
}
.v2-pdf-tool{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  color: inherit;
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background-color .18s var(--v2-ease), opacity .18s var(--v2-ease);
}
.v2-pdf-tool svg{ width: 15px; height: 15px; }
.v2-pdf-tool:hover{ background: rgba(255, 255, 255, .12); }
.v2-pdf-tool:focus-visible{
  outline: 2px solid var(--v2-accent);
  outline-offset: 2px;
}
.v2-pdf-tool:disabled{
  opacity: .35;
  cursor: default;
  background: transparent;
}
.v2-pdf-tool-link{
  text-decoration: none;
}
.v2-pdf-tool-link:hover{ text-decoration: none; }
.v2-pdf-page-indicator,
.v2-pdf-zoom-indicator{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .04em;
  min-width: 52px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: rgba(236, 231, 222, .82);
}

/* =========================================================================
   Quote
========================================================================= */
.v2-quote{
  margin: 40px 0 48px;
  padding: 8px 0 8px 24px;
  border-left: 2px solid var(--v2-text);
  max-width: 60ch;
}
.v2-quote p{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 17px;
  line-height: 1.55;
  color: var(--v2-text);
  letter-spacing: -0.005em;
}
.v2-quote cite{
  display: block;
  margin-top: 14px;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-style: normal;
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--v2-text-muted);
}

/* =========================================================================
   Impact stat
========================================================================= */
.v2-impact{
  margin: 56px 0;
  padding: 32px 0;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
}
.v2-impact-value{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: clamp(56px, 8vw, 96px);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--v2-text);
}
.v2-impact-label{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13px;
  color: var(--v2-text-muted);
  max-width: 36ch;
  letter-spacing: -0.002em;
}

/* =========================================================================
   Placeholder (when asset src is missing)
========================================================================= */
.v2-placeholder{
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--v2-surface-2);
  border: 1px dashed var(--v2-rule-strong);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--v2-text-subtle);
  font-size: 11px;
  letter-spacing: .04em;
}
.v2-placeholder::before{
  content: attr(data-label);
}
.v2-placeholder[data-label=""]::before{ content: 'Image'; }

/* =========================================================================
   More case studies
========================================================================= */
.v2-more{
  padding: 64px 0 96px;
  border-top: 1px solid var(--v2-rule);
}
.v2-more .v2-eyebrow{
  display: block;
  margin-bottom: 28px;
}
.v2-more-grid{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}
.v2-more-card{
  background: var(--v2-surface);
  border: 1px solid var(--v2-rule);
  border-radius: 8px;
  padding: 28px 28px 32px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: border-color .2s var(--v2-ease), transform .25s var(--v2-ease);
}
.v2-more-card:hover{
  border-color: var(--v2-rule-strong);
  text-decoration: none;
  transform: translateY(-1px);
}
.v2-more-card .v2-eyebrow{ margin-bottom: 4px; }
.v2-more-title{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 600;
  font-size: 17px;
  line-height: 1.32;
  color: var(--v2-text);
  letter-spacing: -0.014em;
  max-width: 26ch;
}

/* Cover image (optional). Renders only when a project sets `coverImage` in
   projects.js. The card collapses the cover slot when no image is set, so
   text-only cards still look identical to the legacy layout. When a cover
   IS present, the card adjusts padding so the image sits flush with the
   card edges and the meta block (eyebrow + title) drops in beneath it. */
.v2-more-card.has-cover{
  padding: 0 0 24px;
  gap: 16px;
}
.v2-more-cover{
  width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border-radius: 8px 8px 0 0; /* match card top corners; flat at meta seam */
  background: var(--v2-bg); /* prevents flash before image paints */
}
.v2-more-cover img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .35s var(--v2-ease);
}
.v2-more-card.has-cover:hover .v2-more-cover img{
  transform: scale(1.02);
}
.v2-more-card.has-cover .v2-more-meta{
  padding: 0 28px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* =========================================================================
   Footer
========================================================================= */
/* (Footer rules removed — the case study uses the homepage footer
   component from styles.css.) */

/* =========================================================================
   Artifact zoom modal
   Click any artifact → centered modal at native resolution with mouse-
   wheel zoom + click-drag pan + +/- controls + ESC/backdrop close.
   The modal sits above everything (z-index 1000), inherits the page
   theme tokens so it reads as part of the case study, not a foreign
   lightbox UI.
========================================================================= */
/* =========================================================================
   Custom zoom-in cursor for clickable artifacts
   Magnifying-glass-plus icon stroked in the SAME color as the visitor's
   "You" peer cursor (--accent-2 token from styles.css). The peer cursor
   is yellow (#F5D76E) in dark mode and teal (#2E7F8A) in light. Holding
   that color constant across the regular pointer and the zoom hover
   prevents the "wait, is that still my cursor?" moment for users who
   aren't expecting cursors to morph color.
   Cursor applies to the WHOLE artifact container (frame + image), so
   the matted padding around the image is also a click target. Hotspot
   sits at the center of the circle (11,11). Native `zoom-in` is the
   fallback if the data URI fails. */
.v2-figure-img,
.v2-ba-stage,
.v2-gallery-slide{
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cg fill='none' stroke='%23F5D76E' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='7.5' y1='11' x2='14.5' y2='11'/%3E%3Cline x1='11' y1='7.5' x2='11' y2='14.5'/%3E%3Cline x1='16.5' y1='16.5' x2='22.5' y2='22.5'/%3E%3C/g%3E%3C/svg%3E") 11 11, zoom-in;
}
/* Light-mode swap — muted teal stroke (matches --accent-2 #2E7F8A). */
:root[data-theme="light"] .v2-figure-img,
:root[data-theme="light"] .v2-ba-stage,
:root[data-theme="light"] .v2-gallery-slide{
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='28' viewBox='0 0 28 28'%3E%3Cg fill='none' stroke='%232E7F8A' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='7.5' y1='11' x2='14.5' y2='11'/%3E%3Cline x1='11' y1='7.5' x2='11' y2='14.5'/%3E%3Cline x1='16.5' y1='16.5' x2='22.5' y2='22.5'/%3E%3C/g%3E%3C/svg%3E") 11 11, zoom-in;
}

/* While the user is hovering a zoomable artifact, hide the "You" peer
   cursor so it doesn't compete with the magnifier. The body class is
   added by the script below via delegated mouseover/out. */
body.v2-zooming-hover .peer{ display: none; }

/* Non-zoomable case-study controls — hide the native cursor so the
   custom "You" pointer is the only visual signal. (The zoomable
   artifacts above are NOT in this list — they intentionally show the
   magnifier.) */
body.cursor-on .v2-gallery-nav,
body.cursor-on .v2-gallery-thumb,
body.cursor-on .v2-ba-btn,
body.cursor-on .v2-back-link,
body.cursor-on .v2-toc-link,
body.cursor-on .v2-pdf-tool,
body.cursor-on .v2-more-card{
  cursor: none;
}
/* While the zoom modal is open, the native grab / grabbing cursor on
   the stage needs to be the visible affordance for panning. Hide the
   "You" cursor so it doesn't compete. */
body.v2-zoom-locked .peer{ display: none; }

/* Without these overrides, the modal's close/zoom buttons inherit
   cursor:none from `body.cursor-on button{cursor:none}` (styles.css),
   which beats the modal's own `.v2-zoom-close{cursor:pointer}` rule
   on specificity. The user sees no cursor at all when hovering the
   X. These higher-specificity rules restore pointer on modal controls
   and grab on the pan stage. */
body.cursor-on .v2-zoom-close,
body.cursor-on .v2-zoom-btn,
body.cursor-on .v2-zoom-arrow,
body.cursor-on .v2-zoom-thumb,
body.cursor-on .v2-zoom-ba-toggle .v2-ba-btn{ cursor: pointer; }
body.cursor-on .v2-zoom-stage{ cursor: grab; }
body.cursor-on .v2-zoom-stage.is-grabbing{ cursor: grabbing; }
body.cursor-on .v2-zoom-overlay{ cursor: default; }
/* =========================================================================
   Zoom modal — new layout (full-screen "module-aware" viewer)
   The modal is a flex column with three rows:
     TOP    — counter (left), BA toggle (center), zoom toolbar + close (right)
     MIDDLE — side arrows (gallery only) flank a flex-filling stage
     BOTTOM — caption (always visible when present) + thumbs (gallery only)
   Nothing overlays the artifact. Every affordance has its own dedicated row
   or column so the stage stays clean. The stage flex-fills the available
   space between the top bar and the bottom panel. */
.v2-zoom-overlay{
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(0, 0, 0, .82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex;
  flex-direction: column;
  padding: 22px 28px;
  gap: 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s var(--v2-ease);
}
:root[data-theme="light"] .v2-zoom-overlay{
  background: rgba(20, 20, 20, .78);
}
.v2-zoom-overlay.is-open{
  opacity: 1;
  pointer-events: auto;
}

/* TOP BAR — three-column grid so the BA toggle sits centered regardless
   of how wide the counter or toolbar grow. */
.v2-zoom-top{
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  min-height: 44px;
}
.v2-zoom-top-left   { justify-self: start;  display: flex; align-items: center; }
.v2-zoom-top-center { justify-self: center; display: flex; align-items: center; }
.v2-zoom-top-right  { justify-self: end;    display: flex; align-items: center; gap: 12px; }

/* Counter — gallery position indicator (e.g. "03 / 05"). */
.v2-zoom-counter{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: rgba(236, 231, 222, .82);
  font-variant-numeric: tabular-nums;
  padding: 6px 14px;
  background: rgba(24, 24, 24, .82);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 999px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.v2-zoom-counter[hidden]{ display: none; }

/* BA toggle in modal — reuses the page .v2-ba-toggle component (same
   markup, same sliding-mask animation). The modal overlay is always
   dark regardless of theme, so the toggle styles below are hardcoded
   light/dark rather than theme-token-driven.

   Updates in sync with the page-level .v2-ba-toggle changes:
   - Border removed (theme-token border-color line was redundant after
     we set border:0 on the base)
   - Active mask color overridden to a static light cream — base uses
     var(--v2-text) which resolves to DARK in light mode, making the
     active pill invisible against the always-dark modal overlay
   - Active label color overridden to dark for high contrast on the
     light mask
*/
.v2-zoom-ba-toggle{
  background: rgba(24, 24, 24, .82);
  border: 0;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.v2-zoom-ba-toggle .v2-ba-mask-inner{
  background: #ECE7DE;
}
.v2-zoom-ba-toggle .v2-ba-mask-label{
  color: #1A1A1A;
}
.v2-zoom-ba-toggle .v2-ba-btn{
  color: rgba(236, 231, 222, .72);
}
.v2-zoom-ba-toggle .v2-ba-btn:hover{ color: #ECE7DE; }
.v2-zoom-ba-toggle[hidden]{ display: none; }

/* MIDDLE ROW — arrows flank the stage. Stage flex-fills. */
.v2-zoom-middle{
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

/* Stage — no longer fixed dimensions. Takes whatever space remains
   between the top bar, the bottom panel, and the arrow gutters. */
.v2-zoom-stage{
  position: relative;
  flex: 1;
  min-width: 0;
  align-self: stretch;
  overflow: hidden;
  border-radius: 10px;
  background: transparent;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}
.v2-zoom-stage.is-grabbing{ cursor: grabbing; }

/* Image — primary (single/gallery/BA before) + secondary (BA after).
   Both share the same transform via JS so zoom/pan applies to both
   in lockstep. Opacity transitions drive the BA crossfade. */
.v2-zoom-img{
  position: absolute;
  top: 50%;
  left: 50%;
  max-width: none;
  max-height: none;
  transform-origin: center center;
  transform: translate(-50%, -50%) scale(1);
  transition: transform .18s var(--v2-ease-soft), opacity .6s var(--v2-ease-soft);
  will-change: transform, opacity;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  opacity: 1;
}
.v2-zoom-stage.is-grabbing .v2-zoom-img{
  transition: opacity .6s var(--v2-ease-soft);
}
.v2-zoom-img-secondary{ opacity: 0; }
/* BA crossfade — when the stage's data-ba-current flips to "after",
   primary fades out and secondary fades in. Both use the same 600ms
   transition (matches the page-level BA fade) so the swap reads as
   one motion in both contexts. */
.v2-zoom-stage[data-ba-current="after"] .v2-zoom-img{ opacity: 0; }
.v2-zoom-stage[data-ba-current="after"] .v2-zoom-img-secondary{ opacity: 1; }
.v2-zoom-img-secondary[hidden]{ display: none; }

/* Side arrows — large circular pills that sit OUTSIDE the stage in
   the middle-row gutters. Never overlay the artifact. */
.v2-zoom-arrow{
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(24, 24, 24, .82);
  border: 1px solid rgba(255, 255, 255, .14);
  color: #ECE7DE;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background-color .2s var(--v2-ease), transform .15s var(--v2-ease);
}
.v2-zoom-arrow svg{ width: 18px; height: 18px; }
.v2-zoom-arrow:hover{ background: rgba(40, 40, 40, .96); transform: scale(1.06); }
.v2-zoom-arrow:active{ transform: scale(.96); }
.v2-zoom-arrow:focus-visible{
  outline: 2px solid var(--v2-accent);
  outline-offset: 2px;
}
.v2-zoom-arrow:disabled{
  opacity: .4;
  cursor: default;
  background: rgba(24, 24, 24, .6);
}
.v2-zoom-arrow[hidden]{ display: none; }

/* BOTTOM PANEL — caption (always visible when set) + optional thumb strip. */
.v2-zoom-bottom{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.v2-zoom-caption{
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.55;
  letter-spacing: -0.002em;
  color: rgba(236, 231, 222, .9);
  text-align: center;
  /* Readability cap in the modal — the overlay has no natural
     containing column, and uncapped width would let captions extend
     1500+px wide on big monitors. 110ch is wider than the typical
     "ideal" 65-80ch reading measure but still inside the readable
     range, and the extra horizontal room means long captions wrap
     onto fewer lines — which reduces vertical caption-height variance
     between slides, so navigating gallery → gallery feels less jumpy.
     text-wrap: balance still applies for balanced wrapping when
     captions exceed one line. */
  max-width: 110ch;
  text-wrap: balance;
  margin: 0;
}
.v2-zoom-caption[hidden]{ display: none; }

/* Toolbar — same pill shape, now lives in the top-right with the
   close button instead of bottom-center. */
.v2-zoom-toolbar{
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: rgba(24, 24, 24, .88);
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 999px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.v2-zoom-sep{
  width: 1px;
  height: 20px;
  background: rgba(255, 255, 255, .14);
  margin: 0 4px;
}
.v2-zoom-btn{
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ECE7DE;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 16px;
  background: transparent;
  border: 0;
  cursor: pointer;
  transition: background-color .18s var(--v2-ease);
}
.v2-zoom-btn:hover{ background: rgba(255, 255, 255, .12); }
.v2-zoom-btn:focus-visible{
  outline: 2px solid var(--v2-accent);
  outline-offset: 2px;
}
.v2-zoom-btn svg{ width: 16px; height: 16px; }

/* Range slider — direct manipulation for users who don't want to
   click +/- many times. Custom-styled track and thumb so it doesn't
   read as a browser default form control. */
.v2-zoom-slider{
  -webkit-appearance: none;
  appearance: none;
  width: 140px;
  height: 22px;
  background: transparent;
  cursor: pointer;
  margin: 0 4px;
}
.v2-zoom-slider::-webkit-slider-runnable-track{
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
}
.v2-zoom-slider::-moz-range-track{
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
  border: 0;
}
.v2-zoom-slider::-webkit-slider-thumb{
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--v2-accent);
  border: 0;
  margin-top: -5px; /* center the 14px thumb on the 4px track */
  cursor: grab;
  transition: transform .15s var(--v2-ease);
}
.v2-zoom-slider::-moz-range-thumb{
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--v2-accent);
  border: 0;
  cursor: grab;
}
.v2-zoom-slider:active::-webkit-slider-thumb{ cursor: grabbing; transform: scale(1.15); }
.v2-zoom-slider:active::-moz-range-thumb{ cursor: grabbing; }
.v2-zoom-slider:focus-visible{ outline: none; }
.v2-zoom-slider:focus-visible::-webkit-slider-thumb{
  box-shadow: 0 0 0 3px var(--v2-accent-soft);
}
.v2-zoom-slider:focus-visible::-moz-range-thumb{
  box-shadow: 0 0 0 3px var(--v2-accent-soft);
}
.v2-zoom-level{
  min-width: 56px;
  padding: 0 6px;
  text-align: center;
  font-family: 'Plus Jakarta Sans', sans-serif;
  font-weight: 500;
  font-size: 11px;
  letter-spacing: .04em;
  color: rgba(236, 231, 222, .8);
  font-variant-numeric: tabular-nums;
}
/* Close button — now lives inline in the top-right (no longer
   absolutely positioned). Same pill styling as before. */
.v2-zoom-close{
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(24, 24, 24, .82);
  border: 1px solid rgba(255, 255, 255, .12);
  color: #ECE7DE;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background-color .18s var(--v2-ease);
}
.v2-zoom-close:hover{ background: rgba(40, 40, 40, .92); }
.v2-zoom-close svg{ width: 16px; height: 16px; }

/* Thumbnail strip — sits in the BOTTOM panel below the caption.
   Centered, scrolls horizontally if a gallery has many thumbs. */
.v2-zoom-thumbs{
  display: flex;
  gap: 6px;
  padding: 6px;
  max-width: min(720px, calc(100vw - 60px));
  overflow-x: auto;
  background: rgba(24, 24, 24, .82);
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 10px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .2) transparent;
}
.v2-zoom-thumbs[hidden]{ display: none; }
.v2-zoom-thumb{
  flex: 0 0 64px;
  height: 44px;
  background: rgba(255, 255, 255, .06);
  border: 0;
  border-radius: 4px;
  overflow: hidden;
  padding: 0;
  cursor: pointer;
  opacity: .55;
  box-shadow: 0 0 0 0 transparent;
  transition: opacity .2s var(--v2-ease), box-shadow .2s var(--v2-ease);
}
.v2-zoom-thumb img{
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.v2-zoom-thumb:hover{ opacity: 1; }
.v2-zoom-thumb.is-current{
  opacity: 1;
  box-shadow: 0 0 0 2px var(--v2-accent);
}
.v2-zoom-thumb:focus-visible{
  outline: 2px solid var(--v2-accent);
  outline-offset: 2px;
}
body.v2-zoom-locked{
  overflow: hidden;
}

/* =========================================================================
   Mobile + small tablet — layout collapse + chrome compression.
   With the 180px TOC and 800px content (total 1060px), the two-column
   layout compresses gracefully down to about 900px viewport — past
   that, the body column gets too cramped, so the whole layout stacks.
========================================================================= */
@media (max-width: 900px){
  .v2-nav{ grid-template-columns: auto 1fr auto; gap: 16px; }
  .v2-nav-brand{ display: none; }
  .v2-nav-back{ font-size: 12px; }

  /* Hero collapses to single-column on mobile: titleblock stacks above
     meta, both span full width. Cleaner reading on narrow viewports. */
  .v2-hero{ padding: 48px 0 36px; grid-template-columns: 1fr; gap: 36px; }
  .v2-hero-titleblock{ max-width: none; }
  .v2-hero-label{ font-size: clamp(32px, 8vw, 52px); }
  .v2-hero-desc{ font-size: clamp(16px, 4vw, 19px); }
  .v2-hero-title{ font-size: clamp(24px, 6vw, 32px); }
  .v2-hero-meta{ gap: 18px; padding-top: 0; }

  .v2-tldr-section{
    grid-template-columns: 1fr;
    gap: 18px;
    padding: 32px 0;
  }
  .v2-tldr-aside .v2-eyebrow{ position: static; }
  .v2-tldr-row{
    grid-template-columns: 1fr;
    gap: 6px;
  }

  .v2-metric-hero{ padding: 36px 0; gap: 24px; }

  .v2-story{
    grid-template-columns: 1fr;
    gap: 32px;
    padding: 48px 0;
  }
  .v2-toc{
    position: relative;
    top: auto;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--v2-rule);
  }
  .v2-toc-list{
    flex-direction: row;
    flex-wrap: wrap;
    gap: 12px 18px;
  }
  .v2-toc-link{ padding-left: 14px; }
  .v2-content{ max-width: none; }
  .v2-section{ margin-bottom: 64px; }

  .v2-insight-grid[data-count="2"],
  .v2-insight-grid[data-count="3"],
  .v2-insight-grid[data-count="4"]{
    grid-template-columns: 1fr;
  }
  .v2-pair{ grid-template-columns: 1fr; }

  /* Gallery — slightly tighter card padding and smaller arrows. */
  .v2-gallery-card{ padding: 14px; gap: 12px; }
  .v2-gallery-row{ gap: 8px; }
  .v2-gallery-nav{ flex: 0 0 32px; width: 32px; height: 32px; }
  .v2-gallery-thumb{ flex: 0 0 60px; height: 44px; }

  .v2-pdf-frame{ aspect-ratio: 3 / 4; }
  .v2-more-grid{ grid-template-columns: 1fr; }

  /* Floating theme toggle — smaller + tucked closer on mobile */
  .theme-toggle{
    bottom: 24px; right: 24px;
    width: 76px; height: 38px;
  }
  .theme-toggle .knob{ width: 32px; height: 32px; }
  :root[data-theme="light"] .theme-toggle .knob{ transform: translateX(38px); }
}

/* =========================================================================
   Mobile (< 745px) — artifact heights tighten so a single artifact
   never dominates a small viewport.
========================================================================= */
@media (max-width: 744px){
  /* IMG-level caps for mobile. Tighter heights so a single artifact
     doesn't dominate the small viewport. */
  .v2-figure-img img{
    max-height: calc(70vh - 48px);
  }
  .v2-pair .v2-figure-img img{
    max-height: calc(60vh - 48px);
  }
  /* Before/after stage matches the singles cap on mobile. */
  .v2-ba-stage{ height: 70vh; }
}

@media (prefers-reduced-motion: reduce){
  *{ transition: none !important; animation: none !important; }
  html{ scroll-behavior: auto; }
}
