/* ==========================================================================
   ANNE NEFF PORTFOLIO — STYLE VARIABLES
   ==========================================================================
   Color system: "Hothouse" brand system, applied 2026-08-31. Replaces the
   "Cold Signal" system (2026-08-24). Hothouse runs in two modes — light
   (cream paper) and dark (near-black screen) — switched by a toggle in the
   header, defaulting to light, respecting the visitor's OS preference on
   first visit and remembering their choice after that (see assets/theme.js).

   Everything you'd want to tweak yourself — colors, fonts, sizes, spacing —
   lives in the :root block below (light mode) and the [data-theme="dark"]
   block right after it (dark mode overrides). Change a value here and it
   updates everywhere that variable is used, across all 7 pages,
   automatically. No need to touch anything below the variable blocks
   unless you want to change layout structure.

   See STYLE-GUIDE.md in this folder for a plain-language walkthrough of
   the most common tweaks (change the accent color, swap a font, etc).
   ========================================================================== */

:root {
  /* ---- Type roles ----
     Three faces, three jobs. Don't use one for another's role — see
     STYLE-GUIDE.md. Unchanged by the Hothouse recolor.
     --display  Unbounded 800/700 — headlines, single-word emphasis, short
                emphatic moments like "Let's talk." and the three
                questions. More than one per page is fine; keep it rare
                and never use it for long-form/paragraph text.
     --heading  Work Sans 600/500/400 — nav, labels, captions, buttons,
                page titles (h1/h2), every uppercase run.
     --text     Literata 400, italic for emphasis — body copy, long-form,
                anything read for more than a few seconds. */
  --display: "Unbounded", sans-serif;
  --heading: "Work Sans", system-ui, sans-serif;
  --text: "Literata", Georgia, serif;

  /* ---- Hothouse brand tokens: light mode ----
     Deep teal leads on cream paper. Seven brand colors plus derived
     neutrals and semantics — see STYLE-GUIDE.md for the full contrast
     notes on each. */
  --paper: #FAE9DE;
  --paper-tint: #F2DCCE;
  --rule: #E6D2C4;
  --muted: #6E5F57;
  --ink: #1E1E1E;
  --teal: #003C3A;
  --maroon: #6D1B2A;
  --coral: #FF6074;
  --ice: #B9D8DC;
  --pink: #FFCED1;
  --p-ok: #1F5D3C;
  --p-warn: #8A4A0B;
  --p-err: #9E1B1B;
  --p-info: #00494C;

  /* ---- Semantic aliases: light mode ----
     Everything below (and every rule further down the file) reads
     colors through these, never through the raw brand names above, so
     flipping [data-theme] is the only thing needed to retheme the whole
     site. Raw brand tokens stay available above for the handful of
     places that need a specific one regardless of mode (e.g. the footer,
     which is a dark ink panel in both modes). */
  --bg: var(--paper);
  --bg-alt: var(--paper-tint);
  --fg: var(--ink);
  /* A 50/50 blend of --muted and --teal, applied sitewide 2026-09-15 so
     every use of --fg-muted (captions, labels, section headers, the
     sub nav) leans into the brand's cool family instead of reading as
     plain neutral gray. --muted itself (the original gray) is no longer
     referenced anywhere, kept above only as a record of what this used
     to be. */
  --fg-muted: #374E49;
  --accent: var(--teal);
  --accent-2: var(--maroon);
  --accent-fill: var(--coral);
  --border: var(--rule);
  --tint-cool: var(--ice);
  --tint-warm: var(--pink);

  /* Site-specific roles built from the aliases above — see the
     Component Mapping table Anne supplied for where each is used. */
  --link: var(--accent);
  --link-hover: var(--accent-2);
  --underline: var(--coral);
  --focus-ring: var(--accent);
  --eyebrow: var(--accent);

  /* --nav-bg is intentionally NOT redefined in the dark block below —
     the header is a fixed teal panel in both modes, per Anne's call, not
     a per-mode color. */
  --nav-bg: var(--teal);
  --nav-text: var(--paper);
  --nav-accent: var(--ice);

  --btn-primary-bg: var(--accent);
  --btn-primary-text: var(--paper);
  --btn-primary-hover-bg: var(--accent-2);

  --card-bg: var(--bg-alt);
  --card-border: var(--border);

  /* The "AN" logo mark's two fills — mode-invariant, not redefined in
     the dark block below: --logo-accent (coral) always; --logo-secondary
     (originally the mark's maroon, unreadable against this header) is
     pink in both modes now, per Anne's call to match dark to light. */
  --logo-accent: var(--coral);
  --logo-secondary: var(--pink);

  /* --footer-bg is intentionally NOT redefined in the dark block below —
     the footer is the same fixed teal panel as the header, in both
     modes, per Anne's call. */
  --footer-bg: var(--teal);
  --footer-text: var(--paper);
  --footer-accent: var(--coral);
  /* These two are deliberately literal brand colors, not mode-switched
     aliases — they need to read the same way regardless of which page
     mode is active. --footer-link is kept visually distinct from
     --footer-accent (the "Let's talk." color) on purpose, so the two
     don't blend together. */
  --footer-link: var(--pink);
  --footer-link-hover: var(--ice);

  --tri-1: var(--coral);
  --tri-2: var(--maroon);
  --tri-3: var(--teal);

  /* ---- Font sizes ----
     Grouped by where they show up. Unchanged by this recolor — the brief
     is a color pass, not a resize; changing these would reflow the
     layout, which is out of scope. Adjust freely if you want to. */
  --fs-nav: 18px;
  --fs-brand-name: 28px;
  --fs-eyebrow: 16px;
  --fs-footer-label: 44px;
  --fs-footer-link: 16px;
  --fs-caption: 14px;

  /* ---- Shared role sizes ----
     One variable per job, used wherever that job shows up, instead of a
     separate same-valued variable per page.
     --fs-intro-paragraph is the homepage opening statement only.
     --fs-body-copy is long-form reading copy elsewhere (Literata): About
     body copy, case study body/subhead, and the Work page card caption.
     --fs-card-headline is a project grid card headline on the homepage or
     Work page. --fs-questions is the "three questions" block, identical
     on the homepage and About page. --fs-section-label is any uppercase
     section label/heading set in that style - "Selected Work," the "Work"
     page title, and the "How I Work" heading all match it exactly.
     --fs-cs-back-link also covers the case study prev/next label, which
     matches the back link in every way, not just size. */
  --fs-intro-paragraph: 18px;
  --fs-body-copy: 20px;
  --fs-cs-lede: 24px;          /* Visa Perspectives story layout: opening line */
  --fs-cs-section-head: 24px;  /* Visa Perspectives story layout: section headings */
  --max-prose-width: 42em;     /* longest comfortable line for body copy */
  --scroller-height: 560px;    /* Commerce of Tomorrow scroller window */
  --diagram-inset: 71px;       /* Visa Perspectives diagrams: space on each side */
  --space-caption: 12px;       /* gap between a case study image and its caption */
  /* Homepage About intro sizes (2026-09-28): the opening positioning line
     matches the How I Work principle titles ("Build for scale, not for
     now."); the two paragraphs after it match the "Everything I do starts
     here:" statement, so the first line reads as the lead. */
  --fs-about-lead: var(--fs-about-principle-title);
  --fs-about-supporting: var(--fs-intro-paragraph);
  --fs-card-headline: 24px;
  --fs-questions: var(--fs-brand-name);

  /* Doubled from the original 18px, and shared with the "Work" page
     title since the two are already styled to match exactly. */
  --space-section-top: 36px;
  --fs-section-label: 28px;
  --fs-cta: 14px;

  /* Uppercase section-header tracking (Selected Work / How I Work /
     the retired Work page title) - decreased from .18em 2026-09-15. */
  --tracking-section-label: .10em;

  --fs-about-principle-title: 24px;

  --fs-cs-back-link: 14px;
  --fs-cs-prevnext-headline: 14px;
  /* Case study hero headlines vary per project (24–28px) using inline
     "--fs-cs-headline" set per page; change a single page's headline size
     by editing that page's <div class="hero-headline" style="--fs-cs-headline:28px">. */

  /* ---- Spacing scale ----
     One shared set of gaps, in multiples of 8px, instead of independent
     one-off values per rule. Reach for the smallest one that still gives
     room to breathe - --space-xs for tightly related items (an icon and
     its label), up through --space-2xl for the biggest panel-level
     breathing room on the site. */
  --space-xs: 8px;
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 40px;
  --space-xl: 64px;
  --space-2xl: 96px;

  /* Side padding for nearly every section. Fluid: stays tight on mobile,
     opens up on wide screens instead of holding one fixed value at every
     width. */
  --pad-page-x: clamp(24px, 5vw, 48px);
  --gap-home-grid: 32px;
  --gap-work-grid: var(--space-xl); /* 64px - shared with .principle's own spacing below */
  --gap-cs-gallery: 32px;

  /* Widest the site's content column ever gets. Below this width the site
     just fills the browser edge to edge (minus --pad-page-x); above it,
     the content stays this wide and centers itself, with the extra space
     becoming left/right margin filled by the page background. */
  --max-content-width: 1100px;

  /* ---- Misc ---- */
  --radius: 0px; /* this design uses square corners throughout */
}

/* ==========================================================================
   HOTHOUSE — DARK MODE
   ==========================================================================
   Every variable below overrides its light-mode counterpart above when
   <html data-theme="dark"> is set (see assets/theme.js). Anything not
   redefined here — type, font sizes, spacing — carries over unchanged. */
[data-theme="dark"] {
  --d-base: #1E1E1E;
  --d-surface: #262626;
  --d-raised: #303030;
  --d-border: #454545;
  --d-muted: #A8A8A8;
  --d-text: #FAE9DE;
  --d-coral: #FF6074;
  --d-pink: #FFCED1;
  --d-teal: #08544F;
  --d-maroon: #8A2839;
  --d-ice: #8ACFDB;
  --p-ok: #6FD99B;
  --p-warn: #F0B44A;
  --p-err: #FF8A8A;
  --p-info: #8ACFDB;

  --bg: var(--d-base);
  --bg-alt: var(--d-surface);
  --fg: var(--d-text);
  /* A 50/50 blend of --d-muted and --d-ice - see the light-mode
     definition above for why. */
  --fg-muted: #99BCC2;
  --accent: var(--d-coral);
  --accent-2: var(--d-pink);
  --accent-fill: var(--d-coral);
  --border: var(--d-border);
  --tint-cool: var(--d-teal);
  --tint-warm: var(--d-maroon);

  --link: var(--accent);
  --link-hover: var(--accent-2);
  --underline: var(--d-coral);
  --focus-ring: var(--accent);
  --eyebrow: var(--accent);

  /* --nav-bg is NOT redefined here — the header stays the same teal
     panel in both modes. */
  --nav-text: var(--d-text);
  --nav-accent: var(--d-coral);

  --btn-primary-bg: var(--d-coral);
  --btn-primary-text: var(--d-base);
  --btn-primary-hover-bg: var(--d-pink);

  --card-bg: var(--d-surface);
  /* "No border" on dark neutral cards, per the component mapping — the
     border stays 1px (structure unchanged) but its color matches the
     card background so it doesn't read as a visible line. */
  --card-border: var(--d-surface);

  /* --footer-bg is NOT redefined here — same teal panel as the header,
     in both modes. --logo-secondary is NOT redefined here either — it's
     pink in both modes now, matching light mode, per Anne's call.
     --logo-accent was already mode-invariant (coral / d-coral share the
     same hex). */
  --footer-text: var(--d-text);
  --footer-accent: var(--d-coral);
  /* --footer-link / --footer-link-hover are NOT redefined here — they're
     literal, mode-invariant tokens (pink / ice) declared once in :root,
     since the footer stays a dark panel in both modes. */

  --tri-1: var(--d-ice);
  --tri-2: var(--d-pink);
  --tri-3: var(--d-coral);
}

/* Local aliases so the rules below stay readable — both point at
   brand tokens above, no new colors introduced. */
:root {
  --color-placeholder-bg: var(--card-bg);
  --color-placeholder-border: var(--card-border);
}

/* ==========================================================================
   BASE
   ========================================================================== */
* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--text);
  overflow-x: hidden;
}

/* Wraps every page's header, main content and footer. This is what stops
   the site from stretching edge to edge on wide screens — see
   --max-content-width above to change how wide it's allowed to get. */
.page-frame {
  max-width: var(--max-content-width);
  margin: 0 auto;
}

/* Explicit link styling, sitewide default: the mode's primary color at
   rest, the mode's secondary voice on hover/focus, coral underline in
   both modes per the brand system's link rule. Nav and a couple of
   chrome links (below) intentionally override this with a quieter
   treatment; card-wrapper links reset back to inherit since their
   visible text is colored by their own child elements, not the link
   itself. */
a {
  color: var(--link);
  text-decoration: none;
}
a:hover, a:focus-visible {
  color: var(--link-hover);
  text-decoration: underline;
  text-decoration-color: var(--underline);
}
a:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

img.placeholder-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Case study images: locked to whatever aspect ratio the uploaded file
   actually is. No forced box height, no object-fit crop - the image's
   own width/height decide its rendered shape at every screen size. */
.cs-hero-image img.placeholder-img,
.cs-gallery img.placeholder-img,
.cs-plate img.placeholder-img,
.pn-thumb img.placeholder-img,
.work-grid .card-thumb img.placeholder-img {
  height: auto;
  object-fit: unset;
}

.ph {
  background: var(--color-placeholder-bg);
  border: 1px solid var(--color-placeholder-border);
}

.cap {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-caption);
  letter-spacing: .02em;
  color: var(--fg-muted);
  margin-top: 5px;
  line-height: 1.4;
}

.eyebrow {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-eyebrow);
  color: var(--eyebrow);
}

/* Reusable three-color section divider - always the accent color, then
   the secondary voice, then the primary color, left to right, but never
   the same widths twice. Each instance sets its own --tri-a (where the
   first segment ends) and --tri-b (where the second ends; the third
   takes the rest) as an inline style, e.g.
   <div class="divider-tri" style="--tri-a:38%; --tri-b:81%"></div>

   Those two numbers come from a simple formula, not a guess, so new
   instances can keep the pattern going instead of picking widths by eye.
   Give each divider on the site a position number i (0, 1, 2, 3...) in
   the order it was added, then:
     segment 1 ends at:  15 + (i * 23) mod 47
     segment 2 ends at:  (segment 1 end) + 12 + (i * 31) mod 41
   47 and 41 are prime and bigger than the multipliers (23, 31), which is
   what keeps the formula from ever repeating or falling into a short
   cycle. If segment 2's end would land past 92% (leaving segment 3 too
   thin to read as a color) or land within 8 points of segment 1's end
   (same problem in the other direction), push it out to segment-1-end +
   8, capped at 92. Default values below only show up if an instance
   forgets to set its own - every real usage on the site sets both. */
.divider-tri {
  --tri-a: 30%;
  --tri-b: 80%;
  height: 5px;
  margin: var(--space-lg) var(--pad-page-x);
  background: linear-gradient(
    to right,
    var(--tri-1) 0%, var(--tri-1) var(--tri-a),
    var(--tri-2) var(--tri-a), var(--tri-2) var(--tri-b),
    var(--tri-3) var(--tri-b), var(--tri-3) 100%
  );
}

/* ==========================================================================
   HEADER / NAV
   ========================================================================== */
.site-header {
  background: var(--nav-bg);
  border-bottom: 1px solid var(--border);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

.site-header-inner {
  max-width: var(--max-content-width);
  margin: 0 auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  padding: var(--space-sm) var(--pad-page-x);
  position: relative;
}

/* The mark is a two-line lockup that reads slightly top-heavy when
   mathematically centered in the header row - nudged down a couple
   pixels to optically balance it. */
.wordmark { height: 44px; width: auto; position: relative; top: 2px; }
/* The mark is inlined SVG (not an <img>) specifically so its two paths
   can read the site's CSS variables and retint with the mode, the same
   way every other colored element on the site does. */
.wordmark .st0 { fill: var(--logo-secondary); }
.wordmark .st1 { fill: var(--logo-accent); }

/* Logo mark + wordname, grouped as one link so the two always move and
   color together. Added 2026-09-14 when the header dropped its Work/
   About links in favor of name + Contact only, per design review
   feedback that the nav needed to carry basic orientation (who's site
   is this) that it didn't before. */
.brand-logo {
  display: flex;
  align-items: flex-end;
  text-decoration: none;
}
/* Absolutely centered in the header row (.site-header-inner is
   position:relative) so it stays dead-center regardless of how wide the
   logo and Contact link are. Solid pink - both raw brand tokens (not
   mode-switched aliases) - since 2026-09-15, replacing the two-tone
   coral/pink split it briefly had; no longer a link, so no hover state. */
.brand-name {
  position: absolute;
  left: 50%;
  bottom: var(--space-sm);
  transform: translateX(-50%);
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-brand-name);
  line-height: 1;
  color: var(--pink);
  white-space: nowrap;
}

.site-nav {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-nav);
  display: flex;
  align-items: center;
  gap: 18px;
  color: var(--nav-text);
}

.site-nav a { color: var(--nav-text); text-decoration: none; }
.site-nav a:hover, .site-nav a:focus-visible { color: var(--nav-accent); text-decoration: underline; text-decoration-color: var(--nav-accent); }
.site-nav a[aria-current="page"] { color: var(--nav-accent); }

/* Second-tier nav: same-page jump links below the header, homepage only
   (case study pages don't have #how-i-work / #work sections to jump to).
   Added 2026-09-15. Quieter than the primary nav - smaller, muted color -
   since it's a wayfinding aid, not the main nav. */
.secondary-nav {
  background: var(--bg-alt);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}
.secondary-nav-inner {
  max-width: var(--max-content-width);
  margin: 0 auto;
  padding: 10px var(--pad-page-x);
  display: flex;
  justify-content: center;
  gap: var(--space-md);
}
.secondary-nav a {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-cs-back-link);
  color: var(--fg-muted);
  text-decoration: none;
}
.secondary-nav a:hover, .secondary-nav a:focus-visible {
  color: var(--link-hover);
  text-decoration: underline;
  text-decoration-color: var(--link-hover);
}

/* Theme toggle (2026-09-29): a small split-circle icon at the right end
   of the header, after Contact - moved back up from the footer, where
   few visitors would find it, but as an icon instead of a text button
   so it reads as a quiet utility rather than a nav item. Sized to the
   nav text, colored like it, no border or fill. The icon mirrors in dark
   mode so the filled half flips with the theme. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px;
  margin: -4px -4px -4px 0;
  background: transparent;
  border: 0;
  color: var(--nav-text);
  cursor: pointer;
  line-height: 0;
}
.theme-toggle .theme-icon {
  width: calc(var(--fs-nav) + 2px);
  height: calc(var(--fs-nav) + 2px);
}
[data-theme="dark"] .theme-toggle .theme-icon { transform: scaleX(-1); }
.theme-toggle:hover, .theme-toggle:focus-visible { color: var(--nav-accent); }
.theme-toggle:focus-visible {
  outline: 2px solid var(--nav-accent);
  outline-offset: 2px;
}

/* ==========================================================================
   FOOTER
   ========================================================================== */
.site-footer {
  background: var(--footer-bg);
  color: var(--footer-text);
  border-top: 1px solid var(--border);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

.site-footer-inner {
  max-width: var(--max-content-width);
  margin: 0 auto;
  padding: var(--space-sm) var(--pad-page-x);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}
.footer-main {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

/* The site's key call-to-action, set in the Display face (Unbounded) -
   see STYLE-GUIDE.md for how sparingly that face gets used elsewhere. */
.site-footer .say-hi {
  font-family: var(--display);
  font-weight: 800;
  font-size: var(--fs-footer-label);
  letter-spacing: -0.035em;
  color: var(--footer-accent);
}

.footer-links {
  font-family: var(--heading);
  font-weight: 500;
  letter-spacing: .02em;
  display: flex;
  gap: 14px;
  font-size: var(--fs-footer-link);
}

.footer-links a { color: var(--footer-link); text-decoration: underline; text-decoration-color: var(--footer-link); }
.footer-links a:hover, .footer-links a:focus-visible { color: var(--footer-link-hover); text-decoration-color: var(--footer-link-hover); }

/* Sticky footer removed 2026-09-14: a footer that follows the visitor
   mid-scroll read as competing with page content in design review. The
   footer now sits at the true end of each page, like the rest of the
   site. The .sticky class is no longer applied by tools/build.py. */

/* ==========================================================================
   HOMEPAGE
   ========================================================================== */
.opening-statement {
  /* A tinted content panel — the one extra background this page uses,
     alongside the page's own cream/base. */
  background: var(--bg-alt);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

.opening-statement-inner {
  max-width: var(--max-content-width);
  margin: 0 auto;
  padding: 76px var(--pad-page-x);
}

.opening-statement .statement-copy {
  font-family: var(--text);
  font-size: var(--fs-intro-paragraph);
  line-height: 1.65;
  color: var(--fg);
}

.opening-statement .questions {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-top: var(--space-md);
  padding: 0 100px 0 0;
}

.opening-statement .questions div {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-questions);
  color: var(--accent);
}

.section-label {
  padding: var(--space-sm) var(--pad-page-x) var(--space-md);
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-section-label);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-section-label);
}

.home-grid {
  padding: 12px var(--pad-page-x) 44px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: var(--gap-home-grid);
}

.home-grid .card-thumb { height: 300px; }

.home-grid a.card {
  text-decoration: none;
  color: inherit;
  display: block;
  position: relative;
  overflow: hidden;
}

.home-grid .card-overlay {
  position: absolute;
  inset: 0;
  /* Ink-based scrim (not pure black) so the one necessary overlay on the
     site still reads as part of the brand's own palette rather than a
     generic black wash. Same value in both modes since it's sitting
     directly on a photo, not on page background. */
  background: rgba(30, 30, 30, 0.75);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 4px;
  padding: 12px;
  opacity: 0;
  transition: opacity .2s ease;
}

.home-grid a.card:hover .card-overlay,
.home-grid a.card:focus-visible .card-overlay {
  opacity: 1;
}

.home-grid .card-overlay .eyebrow { margin: 0; }

.home-grid .card-headline {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-card-headline);
  color: var(--fg);
  margin-top: 0;
  line-height: 1.2;
}

/* ==========================================================================
   WORK PAGE
   ========================================================================== */
.page-title-bar {
  padding: var(--space-section-top) var(--pad-page-x) var(--space-md);
}

.page-title-bar h1 {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-section-label);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-section-label);
  margin: 0;
}

.work-grid {
  padding: 18px var(--pad-page-x) 44px;
  display: flex;
  flex-direction: column;
  gap: var(--gap-work-grid);
}

.work-grid .card {
  display: flex;
  align-items: flex-start;
  gap: 24px;
}

/* Whole card is clickable (2026-09-28, replacing the "View the work"
   link). The card itself is still a plain container, so the eyebrow/
   headline/caption stay ordinary, selectable text. The headline holds the
   real link (.card-link); assets/cards.js makes a click anywhere else on
   the card follow it, unless the visitor is selecting text. The thumbnail
   link stays for no-JS visitors but is hidden from keyboard and screen
   readers so each card is one stop, not two. Hover and keyboard focus
   both underline the headline and zoom the image slightly. */
.work-grid .card-thumb-link { display: block; }
.work-grid .card--linked { cursor: pointer; }
.work-grid .card-headline a.card-link { color: inherit; text-decoration: none; }
.work-grid .card:hover a.card-link,
.work-grid .card:focus-within a.card-link {
  color: var(--link-hover);
  text-decoration: underline;
  text-decoration-color: var(--underline);
  text-underline-offset: 4px;
}
.work-grid .card-thumb { overflow: hidden; }
.work-grid .card-thumb img { transition: transform .3s ease; }
.work-grid .card--linked:hover .card-thumb img,
.work-grid .card--linked:focus-within .card-thumb img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) {
  .work-grid .card-thumb img { transition: none; }
}

/* Image always sits to the left, description to the right - no hover
   state, the copy is always visible. Thumb is 75% of its former size
   (510x300 -> ~383x225 at the site's max content width). */
.work-grid .card-thumb-wrap { flex-shrink: 0; width: 383px; }

/* Height follows the image file (2026-09-29): was a fixed 225px box that
   cropped the 4:3 thumbnails top and bottom. At 383px wide, a 4:3 file
   now renders about 287px tall. */
.work-grid .card-thumb { height: auto; }

.work-grid .card-copy { flex: 1; min-width: 0; padding-top: 4px; }
/* Cards for case studies that aren't live yet (2026-10-29): a quiet
   note under the caption, so an unlinked card reads as intentional. */
.card-soon, .card-read {
  margin-top: 12px; /* 1.5x the original 8px */
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-caption);
  letter-spacing: .02em;
  color: var(--fg-muted);
}
/* The live card's cue (2026-10-29): same spot and size as "coming soon,"
   in link color. Not a second link - the headline is the link, and
   assets/cards.js makes the whole card clickable. */
.card-read { color: var(--link); }
.work-grid .card--linked:hover .card-read,
.work-grid .card--linked:focus-within .card-read { color: var(--link-hover); text-decoration: underline; text-decoration-color: var(--underline); }

.work-grid .card-headline {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-card-headline);
  color: var(--fg);
  margin-top: 6px;
  line-height: 1.2;
}

/* Caption now matches the homepage intro paragraph's size/style. */
.work-grid .cap {
  font-family: var(--text);
  font-weight: 400;
  font-size: var(--fs-body-copy);
  line-height: 1.65;
  letter-spacing: normal;
  color: var(--fg);
  margin-top: 10px;
}

/* ==========================================================================
   ABOUT PAGE
   ========================================================================== */
/* flow-root contains the floated photo below, so this block's own
   height still wraps around it (a floated child alone won't do that). */
.about-intro {
  padding: var(--space-md) var(--pad-page-x) 0;
  display: flow-root;
}

/* Square box (was a 270x330 portrait crop until 2026-09-14). Floated
   so the intro text wraps around it, top edge aligned with the first
   line of body copy beside it, instead of sitting in a fixed
   side-by-side column. aspect-ratio (not a fixed height) keeps it
   square at the mobile width override below too. */
.about-photo {
  float: left;
  width: 270px;
  aspect-ratio: 1 / 1;
  /* Top edge lines up with the top of paragraph 2's capital letters
     (2026-09-29). A 0 margin lined it up with the paragraph's line box
     instead, which sits higher: half the line spacing ((1.65 - 1) / 2 =
     .325em) plus Literata's space above cap height (~.1em). The offset
     is tied to --fs-about-supporting so it stays aligned if that size
     changes. */
  margin: calc(var(--fs-about-supporting) * .425) var(--space-md) var(--space-sm) 0;
}

.about-body {
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  line-height: 1.65;
  color: var(--fg);
}

/* Opening positioning line vs. the supporting paragraphs after it. */
.about-intro .about-body {
  font-size: var(--fs-about-supporting);
}
.about-intro .about-lead {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-about-lead);
  line-height: normal;
  /* Separates the lead from the paragraphs below. The floated photo sits
     after this block, so it moves down by the same amount and stays
     aligned with the first line of paragraph 2. */
  margin-bottom: var(--space-md);
}

/* Second paragraph in the intro block - gives it the same breathing room
   as every other paragraph gap on the site instead of running flush
   against the first. */
.about-intro .about-body + .about-body {
  margin-top: var(--space-sm);
}

.about-block {
  padding: var(--space-sm) var(--pad-page-x);
}

.about-cta {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-cta);
  letter-spacing: .02em;
  margin: var(--space-md) 0;
  padding: 10px 20px;
  background: var(--btn-primary-bg);
  color: var(--btn-primary-text);
  text-decoration: none;
  display: inline-block;
  /* Sits below the floated photo rather than wedging into whatever
     sliver of line-width is left beside it. */
  clear: left;
}
.about-cta:hover, .about-cta:focus-visible { background: var(--btn-primary-hover-bg); color: var(--btn-primary-text); }

.how-i-work-intro h2 {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-section-label);
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-section-label);
  margin: 0;
}

.how-i-work-intro p {
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  line-height: 1.65;
  color: var(--fg);
  margin-top: var(--space-sm);
}

/* No rule between items - matches the spacing standard used for the
   Work page's list of examples (--gap-work-grid), split across each
   item's own top/bottom padding since these aren't in a flex/grid list. */
.principle {
  padding: calc(var(--gap-work-grid) / 2) var(--pad-page-x);
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
}

/* Placeholder icon, roughly half the height of the text next to it.
   Percentage width (capped) so it scales fluidly with the page; square
   aspect ratio is locked and never adjusted; it's a plain placeholder
   box, not a cropped image, so nothing about it is ever masked. Swap the
   div for an <img> later the same way the other .ph placeholders work -
   just don't add object-fit:cover when you do, or it'll start cropping. */
.principle-icon {
  width: 12%;
  max-width: 60px;
  aspect-ratio: 1 / 1;
  flex-shrink: 0;
  /* No placeholder box here - these are icons, not photography, so the
     shared .ph background/border (meant to frame an empty photo slot)
     is switched off. */
  background: none;
  border: none;
}

/* Light/dark icon pairs: each principle icon ships as two files (one
   per mode, see assets/images/icons/light and /dark) rather than one
   file recolored via CSS, since final art will likely differ in more
   than just stroke color between modes. Same three-layer pattern used
   for the color tokens above - system preference as a fallback, the
   explicit [data-theme] toggle always wins. */
.principle-icon .icon-dark { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .principle-icon .icon-light { display: none; }
  :root:not([data-theme="light"]) .principle-icon .icon-dark { display: block; }
}

[data-theme="dark"] .principle-icon .icon-light { display: none; }
[data-theme="dark"] .principle-icon .icon-dark { display: block; }

.principle-copy { flex: 1; min-width: 0; }

.principle-title {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-about-principle-title);
  color: var(--fg);
}

.principle p {
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  color: var(--fg);
  margin-top: var(--space-sm);
  margin-bottom: 0;
  line-height: 1.6;
}

/* Small text link under a principle's copy, pointing to the case study
   that backs up its claim. Plain <a> styling (var(--link), underline on
   hover) already applies; this just adds weight and spacing so it reads
   as a label distinct from the paragraph above it, not more body copy. */
.principle-cta {
  /* Outlined twin of .about-cta (2026-09-28): same font, size, weight,
     tracking and outer dimensions, with a border instead of a fill, so
     the bio button reads as the primary action and these as secondary.
     Padding is .about-cta's 10px 20px minus the 1.5px border, so both
     buttons come out the same size. */
  display: inline-block;
  margin-top: var(--space-sm);
  padding: 8.5px 18.5px;
  border: 1.5px solid var(--btn-primary-bg);
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-cta);
  letter-spacing: .02em;
  color: var(--btn-primary-bg);
  text-decoration: none;
}
.principle-cta:hover, .principle-cta:focus-visible {
  border-color: var(--btn-primary-hover-bg);
  color: var(--btn-primary-hover-bg);
  text-decoration: none;
}

/* ==========================================================================
   CASE STUDY PAGES
   ========================================================================== */
.back-link {
  padding: 14px var(--pad-page-x) 0;
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-cs-back-link);
  color: var(--fg-muted);
  text-decoration: none;
  display: inline-block;
}
.back-link:hover, .back-link:focus-visible { color: var(--link-hover); text-decoration: underline; text-decoration-color: var(--link-hover); }

/* Case study front matter: Role / Scope / Duration, added 2026-09-14 for
   case studies restructured after design review. Renders only when a
   project supplies role/scope/duration in tools/build.py - most case
   studies don't have it yet and skip this block entirely. */
.cs-front-matter {
  margin-top: var(--space-sm);
  border-top: 1px solid var(--border);
  padding-top: 2px;
}
.fm-row {
  display: flex;
  gap: var(--space-sm);
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}
.fm-label {
  flex: 0 0 84px;
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-caption);
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--fg-muted);
}
.fm-value {
  flex: 1;
  min-width: 0;
  font-family: var(--heading);
  font-weight: 400;
  font-size: var(--fs-cs-back-link);
  color: var(--fg);
}

.cs-hero {
  display: flex;
  gap: var(--pad-page-x);
  padding: 20px var(--pad-page-x) 24px;
}

.cs-hero-copy { flex: 1; min-width: 0; }

.hero-headline {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-cs-headline, 26px);
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--fg);
  margin: 0;
}

.hero-subhead {
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  color: var(--fg-muted);
  font-style: italic;
  margin-top: var(--space-sm);
}

.hero-body p {
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  margin-top: var(--space-sm);
  color: var(--fg);
  line-height: 1.65;
}

.cs-hero-image { width: 190px; flex-shrink: 0; }

/* Visa Perspectives only: the hero image gets equal billing with the
   copy instead of sitting in the usual narrow 190px sidebar - added
   2026-09-15 per Anne's call, since this case study's screenshots carry
   more of the story than most. */
.cs-hero-split {
  align-items: flex-start;
}
.cs-hero-split .cs-hero-copy {
  flex: 1 1 0;
  min-width: 0;
}
.cs-hero-split .cs-hero-image {
  flex: 2 1 0;
  width: auto;
  min-width: 0;
}

/* Two-column body for a restructured case study, replacing the earlier
   single-column .cs-body + interleaved .cs-plate approach (2026-09-14
   through 2026-09-21). Used instead of (never alongside) .hero-body when
   a project sets restructured=True in tools/build.py. The left column
   carries both paragraphs together; the right column stacks the
   gallery's first two images as squares; a horizontal-scroll carousel
   of the remaining gallery images runs full width below. Needs at least
   3 gallery items - 2 for the squares, the rest for the carousel - see
   .cs-gallery below for the unrestructured version, which most case
   studies still use.

   Square and carousel images are deliberately cropped to a fixed aspect
   ratio via .ph's aspect-ratio (object-fit: cover comes from the base
   img.placeholder-img rule) instead of being left at their natural size
   like every other case-study image on the site - see the comment above
   .cs-hero-image img.placeholder-img. That's intentional here: the
   square/horizontal shapes are the point of the layout, not a byproduct
   of whatever gets uploaded. */
.cs-body-wrap {
  padding: 4px var(--pad-page-x) var(--space-lg);
}

/* Case study hero now runs straight into the body with no divider
   (2026-09-29). This keeps some separation without drawing a line
   that reads as the end of the page. */
.cs-hero + .cs-body-wrap,
.cs-hero + .cs-gallery { margin-top: var(--space-md); }

.cs-body-split {
  display: flex;
  gap: var(--space-lg);
  align-items: flex-start;
}

.cs-body-text {
  flex: 2 1 0;
  min-width: 0;
  font-family: var(--text);
  font-size: var(--fs-body-copy);
  line-height: 1.65;
  color: var(--fg);
}
.cs-body-text p { margin: 0 0 var(--space-sm); }

.cs-body-squares {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}
.cs-body-squares .ph { aspect-ratio: 1 / 1; }

/* Swipe/scroll carousel, no JS - matches the site's minimal-script
   approach (the only other script is the dark/light theme toggle).
   Revisit with prev/next controls if Anne wants explicit arrows later. */
.cs-carousel-wrap {
  position: relative;
  margin-top: var(--space-lg);
}

/* Right-edge fade signals there's more to scroll. CSS-only, so it can't
   detect end-of-scroll and disappear once everything's been seen - a
   known trade-off of not using JS here, matching the site's
   minimal-script approach (the only script on the site is the
   dark/light toggle). */
.cs-carousel-wrap::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 56px;
  background: linear-gradient(to right, transparent, var(--bg) 85%);
  pointer-events: none;
}

.cs-carousel {
  display: flex;
  gap: var(--space-sm);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
}
.cs-carousel-item {
  /* 2x the original one-third-of-the-row size, per Anne's request -
     only one to two items fit in view now, which is what makes the
     scroll (and the fade above) necessary in the first place. */
  flex: 0 0 calc(((100% - 2 * var(--space-sm)) / 3) * 2);
  min-width: 320px;
  scroll-snap-align: start;
}
.cs-carousel-item .ph { aspect-ratio: 16 / 9; }

/* Fluid 2-column grid instead of fixed 440px tracks - a fixed width
   here was the cause of the gallery getting clipped at the breakpoint
   between the 2-column desktop layout and the 720px mobile stack; 1fr
   columns can never exceed their container, at any width. */
.cs-gallery {
  padding: 22px var(--pad-page-x);
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap-cs-gallery);
  border-bottom: 1px solid var(--border);
}

.prevnext {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--border);
}

.pn-cell {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px var(--pad-page-x);
  background: var(--card-bg);
  text-decoration: none;
  color: inherit;
}

/* Prev: arrow, image, headline (left to right) - matches DOM order.
   Next: arrow, image, headline too, as of 2026-09-15 - both cells read
   the same left-to-right sequence so the two halves of the nav are
   symmetric, rather than mirroring the arrow out to the page edge. */
.pn-cell.next .pn-headline { order: 1; }
.pn-cell.next .pn-thumb { order: 2; }
.pn-cell.next .pn-label { order: 3; }

.pn-label {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-cs-back-link);
  color: var(--fg-muted);
  flex-shrink: 0;
}

.pn-thumb { width: 88px; flex-shrink: 0; }

.pn-headline {
  flex: 1;
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-cs-prevnext-headline);
  letter-spacing: .02em;
  color: var(--fg);
}

/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width: 720px) {
  :root { --scroller-height: 440px; }
  .home-grid, .cs-gallery { grid-template-columns: 1fr; }
  .work-grid .card { flex-direction: column; }
  .work-grid .card-thumb-wrap { width: 100%; }
  .cs-hero { flex-direction: column; }
  .cs-hero-image { width: 100%; }
  .cs-body-split { flex-direction: column; }
  .cs-carousel-item { flex-basis: 92%; min-width: 0; }
  .cs-carousel-wrap::after { width: 32px; }
  .about-photo { float: none; width: 100%; height: auto; margin: 0 0 var(--space-sm); }
  .fm-row { flex-direction: column; gap: 2px; }
  .fm-label { flex-basis: auto; } /* 2026-10-27: was taking its 84px desktop width as height once the row stacks */
  .prevnext { flex-direction: column; } /* 2026-10-27: side-by-side cells ran off the right edge on phones */
  .brand-name { position: static; transform: none; bottom: auto; }
}

/* Header on phones (2026-10-27): logo, name, Contact and the theme toggle
   didn't fit on one row below about 600px. The name now takes its own
   centered line under the logo and Contact. */
@media (max-width: 600px) {
  .site-header-inner { flex-wrap: wrap; align-items: center; row-gap: var(--space-xs); }
  .brand-name { order: 3; width: 100%; text-align: center; font-size: 24px; }
  .wordmark { height: 36px; }
}

/* ==========================================================================
   CASE STUDY STORY LAYOUT (2026-10-27)
   Visa Perspectives only (story=True in tools/build.py). The case study is
   told in sections, each with a heading, prose and full-size images, in
   place of the two-paragraph restructured layout above. Images keep their
   natural shape - no crop - except the Commerce of Tomorrow scroller,
   which shows a fixed-height window onto a very tall page.
   ========================================================================== */
.cs-story {
  padding: 4px var(--pad-page-x) var(--space-xl);
  font-family: var(--text);
  color: var(--fg);
}
.cs-hero + .cs-story { margin-top: var(--space-md); }

.cs-lede {
  font-size: var(--fs-cs-lede);
  line-height: 1.5;
  max-width: 34em;
  margin: 0;
}

.cs-section { margin-top: var(--space-xl); }

.cs-section-head {
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-cs-section-head);
  letter-spacing: -0.01em;
  line-height: 1.2;
  margin: 0 0 var(--space-sm);
}

.cs-prose {
  font-size: var(--fs-body-copy);
  line-height: 1.65;
  max-width: var(--max-prose-width);
}
.cs-prose p { margin: 0 0 var(--space-sm); }

.cs-footnote {
  font-family: var(--heading);
  font-size: var(--fs-caption);
  color: var(--fg-muted);
}

/* Prose beside a smaller image (the old blog, the scroller). */
.cs-split {
  display: flex;
  gap: var(--space-lg);
  align-items: flex-start;
}
.cs-split .cs-prose { flex: 3 1 0; min-width: 0; }
.cs-split .cs-fig--side { flex: 2 1 0; min-width: 0; margin-top: 6px; }

.cs-fig { margin: var(--space-lg) 0; }
.cs-split .cs-fig { margin: 6px 0 0; }
.cs-fig img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
}


.cs-results {
  margin: 0 0 var(--space-md);
  padding-left: 2.25em;
}
.cs-results li { margin-bottom: var(--space-xs); }
.cs-results li::marker { color: var(--accent-fill); }

.cs-outlink { margin: var(--space-lg) 0 0; }
/* Same look as the homepage's primary button (.about-cta). */
.cs-button {
  display: inline-block;
  padding: 10px 20px;
  font-family: var(--heading);
  font-weight: 600;
  font-size: var(--fs-cta);
  letter-spacing: .02em;
  background: var(--btn-primary-bg);
  color: var(--btn-primary-text);
  text-decoration: none;
}
.cs-button:hover, .cs-button:focus-visible {
  background: var(--btn-primary-hover-bg);
  color: var(--btn-primary-text);
  text-decoration: none;
}

/* The two diagrams are centered in the content width with room on both
   sides, so they read as supporting pictures rather than full-width
   features. */
.cs-fig--diagram {
  margin-inline: var(--diagram-inset);
}

/* Closing statement (2026-10-29): plain body copy, set apart only by
   space. Was a pull quote, then a reflection with an inset and vertical
   divider. */
.cs-closing { margin-top: var(--space-xl); }

.cs-footnote--end {
  margin-top: var(--space-xl);
  padding-top: var(--space-sm);
  border-top: 1px solid color-mix(in srgb, var(--fg) 35%, transparent);
}

/* Crisis publishing chart: two bars on one scale. 15 minutes is 15/2160
   of 36 hours, so the After bar is 0.694% of the track. Don't widen it -
   the near-invisible sliver is the point. */
.tc-chart { display: grid; gap: var(--space-md); }
.tc-row { display: grid; gap: 6px; }
.tc-label {
  font-family: var(--heading);
  font-size: var(--fs-intro-paragraph);
  color: var(--fg);
}
.tc-label b { font-weight: 600; margin-right: 6px; }
.tc-track { height: 36px; }
.tc-bar { height: 100%; }
.tc-bar--before { width: 100%; background: var(--tint-cool); }
.tc-bar--after { width: 0.694%; min-width: 2px; background: var(--accent-fill); }

/* Commerce of Tomorrow scroller: a window the same shape as the takeover
   image beside it, slowly panning down the full article and back. The
   image moves with top (0 to 100% of the window) plus translateY (0 to
   -100% of itself) together, which lands its bottom edge on the window's
   bottom edge without knowing either height. Pauses on hover and with the
   Pause button (assets/scroller.js shows the button; without JavaScript
   it stays hidden and hover still pauses). With reduced motion the pan is
   off and the window scrolls by hand. */
.scroller-window {
  position: relative;
  aspect-ratio: 1200 / 1246;
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--card-bg);
}
.cs-fig .scroller-window img {
  position: absolute;
  top: 0;
  left: 0;
  border: 0;
  animation: scroller-pan 120s ease-in-out infinite;
}
@keyframes scroller-pan {
  0%, 5% { top: 0; transform: translateY(0); }
  47%, 53% { top: 100%; transform: translateY(-100%); }
  95%, 100% { top: 0; transform: translateY(0); }
}
/* Contact Sales button pinned over the scroller, in the same spot it
   holds in the annotated screenshot: a 187px button on a 1215px-wide
   page is 15.4% wide, 2.1% of the width from the right and 2.3% of the
   width from the bottom. It sits outside the scrolling window (in
   .scroller-frame) so it stays put whether the page pans on its own or,
   with reduced motion, is scrolled by hand. */
.scroller-frame { position: relative; }
.cs-fig img.scroller-sticky {
  position: absolute;
  right: 2.1%;
  bottom: calc(2.3% * 1200 / 1246);
  width: 15.4%;
  height: auto;
  border: 0;
  pointer-events: none;
}
.scroller-window:hover img,
.scroller.is-paused img { animation-play-state: paused; }

.scroller-cap {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-sm);
}
.scroller-toggle {
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-caption);
  color: var(--link);
  background: transparent;
  border: 1px solid var(--border);
  padding: 10px 16px;
  min-height: 44px;
  cursor: pointer;
  flex-shrink: 0;
}
.scroller-toggle:hover, .scroller-toggle:focus-visible {
  color: var(--link-hover);
  border-color: var(--link-hover);
}
.scroller-toggle:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; }
.scroller-toggle[hidden] { display: none; }

/* Two figures side by side (Year one: takeover + scroller). */
.cs-two-up {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-lg);
  margin: var(--space-lg) 0;
  align-items: start;
}
.cs-two-up .cs-fig { margin: 0; }

/* Four sites into one: drawn in HTML so it follows light and dark mode,
   and kept small - it's context, not a headline. Four equal boxes in a
   row feed down into one. The crossbar runs between the centers of the
   first and last boxes: each box is (width - 3 gaps) / 4 wide, so its
   center is half that in from the edge. Below 720px it turns sideways
   (four stacked boxes feeding right into one), since four columns
   don't fit a phone. */
.sites-diagram {
  --sites-gap: var(--space-md);
  --sites-drop: 24px;
  --sites-line: 1.5px solid var(--accent-fill);
  display: grid;
  grid-template-rows: auto calc(var(--sites-drop) * 2) auto;
  font-family: var(--heading);
  font-size: var(--fs-caption);
  line-height: 1.3;
}
.sites-from {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sites-gap);
}
.sites-from li {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 12px 16px;
  background: var(--tint-warm);
  border: 1px solid var(--accent-fill);
  color: var(--fg);
}
.sites-from li::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  height: var(--sites-drop);
  border-left: var(--sites-line);
}
.sites-join { position: relative; }
.sites-join::before {
  content: "";
  position: absolute;
  top: calc(var(--sites-drop) - 1px);
  left: calc((100% - 3 * var(--sites-gap)) / 8);
  right: calc((100% - 3 * var(--sites-gap)) / 8);
  border-top: var(--sites-line);
}
.sites-join::after {
  content: "";
  position: absolute;
  top: calc(var(--sites-drop) - 1px);
  bottom: 0;
  left: 50%;
  border-left: var(--sites-line);
}
.sites-to {
  justify-self: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  min-width: calc((100% - 3 * var(--sites-gap)) / 4);
  padding: 16px 24px;
  background: var(--accent-2);
  color: var(--bg);
  font-size: var(--fs-intro-paragraph);
}
.sites-from b, .sites-to b { font-weight: 600; }
.sites-to span { font-size: var(--fs-caption); }

/* Annotated article: the screenshot on the left, numbered notes on the
   right. The outlines and callout lines are HTML drawn over the
   screenshot, placed in % of it, matching Anne's original callout
   version: each line runs from its outline's right edge across to the
   far side of the notes column, with the number and note sitting on it.
   Below 860px (see the end of this file) the notes drop under the
   screenshot as a list, the lines go away and numbered pins mark the
   outlines. */
.annotated-body {
  --ann-gap: 32px;
  --ann-stroke: 3px;
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  column-gap: var(--ann-gap);
}
.annotated-shot { position: relative; }
.ann-marks { position: absolute; inset: 0; pointer-events: none; }
.ann-box {
  position: absolute;
  box-sizing: border-box;
  border: var(--ann-stroke) solid var(--accent-fill);
  border-radius: 6px;
}
/* Notes column is half the screenshot's width (2fr / 1fr), so a line
   that reaches its far side ends 50% + the gap past the screenshot. */
.ann-line {
  position: absolute;
  right: calc(-50% - var(--ann-gap));
  height: var(--ann-stroke);
  transform: translateY(-50%);
  background: var(--accent-fill);
}
.ann-notes {
  position: relative;
  list-style: none;
  margin: 0;
  padding: 0;
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-cs-back-link);
  line-height: 1.4;
}
.ann {
  position: absolute;
  bottom: calc(100% - var(--y) + 10px);
  left: 0;
  right: 0;
  display: grid;
  grid-template-columns: 36px 1fr;
  gap: 10px;
  align-items: start;
}
.ann-num, .ann-pin {
  font-family: var(--display);
  font-weight: 800;
  color: var(--accent-2);
}
.ann-num { font-size: 34px; line-height: 1; }
.ann-pin { display: none; }

/* Custom illustration and photography: one large, one small beside it.
   Clicking the small one swaps them (assets/scroller.js). Without
   JavaScript both show at half width with both credits. */
.swap-stage {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-sm);
  align-items: start;
}
.swap-item {
  display: block;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: default;
}
.swap-label {
  display: block;
  margin-top: 6px;
  font-family: var(--heading);
  font-weight: 500;
  font-size: var(--fs-caption);
  color: var(--fg-muted);
}
.swap-credits span { display: block; }
.swap.is-enhanced .swap-stage { grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); }
.swap.is-enhanced .swap-item.is-active { grid-column: 1; grid-row: 1; }
.swap.is-enhanced .swap-item:not(.is-active) { grid-column: 2; grid-row: 1; cursor: pointer; }
.swap.is-enhanced .swap-item.is-active .swap-label { display: none; }
.swap.is-enhanced .swap-item:not(.is-active) .swap-label { color: var(--link); }
.swap.is-enhanced .swap-item:not(.is-active):hover .swap-label,
.swap.is-enhanced .swap-item:not(.is-active):focus-visible .swap-label {
  color: var(--link-hover);
  text-decoration: underline;
  text-decoration-color: var(--underline);
}
.swap.is-enhanced .swap-item:not(.is-active) img { transition: opacity .2s; }
.swap.is-enhanced .swap-item:not(.is-active):hover img { opacity: .85; }
/* Lets the swap animate as a resize where the browser supports view
   transitions; elsewhere it simply switches. */
.swap-item:nth-child(1) { view-transition-name: swap-a; }
.swap-item:nth-child(2) { view-transition-name: swap-b; }
.swap-item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.swap.is-enhanced .swap-credits span { display: none; }
.swap.is-enhanced .swap-credits span.is-active { display: block; }

@media (prefers-reduced-motion: reduce) {
  .scroller-window { overflow-y: auto; }
  .scroller-window img { animation: none; }
  .scroller-toggle { display: none; }
}

@media (max-width: 720px) {
  .cs-two-up { grid-template-columns: 1fr; }
  .cs-fig--diagram { margin-inline: 0; }
  .swap.is-enhanced .swap-stage { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
  .sites-diagram { --sites-gap: 8px; grid-template-rows: none; grid-template-columns: minmax(0, 1fr) 32px minmax(0, 0.8fr); }
  .sites-from { grid-template-columns: none; grid-auto-rows: 1fr; }
  .sites-from li::after { top: 50%; left: 100%; height: 0; width: 16px; border-left: 0; border-top: var(--sites-line); }
  .sites-join::before { top: calc((100% - 3 * var(--sites-gap)) / 8); bottom: calc((100% - 3 * var(--sites-gap)) / 8); left: 15px; right: auto; border-top: 0; border-left: var(--sites-line); }
  .sites-join::after { top: 50%; bottom: auto; left: 15px; right: 0; border-left: 0; border-top: var(--sites-line); }
  .sites-to { justify-self: stretch; align-self: center; align-items: flex-start; text-align: left; min-width: 0; padding: 12px; }
  .cs-split { flex-direction: column; }
  .cs-split .cs-fig--side { width: 100%; }
  .cs-lede { font-size: var(--fs-body-copy); }
}

/* Annotated article: the notes need the screenshot at full desktop size
   to fit beside their outlines without overlapping, so below 860px they
   drop under it as a list, with numbered pins on the outlines. */
@media (max-width: 860px) {
  .annotated-body { grid-template-columns: 1fr; }
  .ann-notes { margin-top: var(--space-sm); display: grid; gap: var(--space-xs); }
  .ann { position: static; }
  .ann-line { display: none; }
  .ann-num { font-size: 22px; }
  .ann-pin {
    display: flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: var(--y);
    left: var(--x, calc(100% - 28px));
    width: 24px;
    height: 24px;
    transform: translateY(-50%);
    background: var(--bg);
    border: 2px solid var(--accent-fill);
    border-radius: 50%;
    font-size: 12px;
  }
}

/* Image captions on case study pages (2026-10-27): more room between an
   image and its caption than the base .cap's 5px, which suits text-only
   captions (like the homepage cards) better than captions under images. */
.cs-hero-image .cap,
.cs-square .cap,
.cs-carousel-item .cap,
.cs-gallery .cap,
.cs-story .cs-fig > figcaption { margin-top: var(--space-caption); }
