/* ============================================================
   JASON BRYANT — PORTFOLIO SITE
   styles.css

   HOW THIS FILE IS ORGANIZED:
   1.  Design tokens (colors, fonts, spacing — start here to re-theme)
   2.  Reset & base
   3.  Navigation (page links + logo)
   4.  Case-studies password gate (.cs-gate)
   5.  Hero band (My Work)
   6.  Section wrappers
   7.  Two-column layout (used inside full case study pages)
   8.  Display headlines (h1/h2/h3/h4 sizes used across the site)
   9.  Body text
   10. Colon lists (decorative bullet style)
   11. Case cards (My Work + the personal bio on My Story)
   13. Full case study pages (stats block, back button, before/after, flow)
       + Testimonials + testimonial modal (My Work)
       + My Story page (career journey block)
   17. Contact band (top portion of the shared footer — all five pages)
   18. Footer (identity strip, below the contact band above it)
   18. Case study figures (real photos/screens inside detail pages)
   19. Material Design icons
   20. Responsive / mobile
   21. Animations
   22. Password gate
   Accessibility: screen-reader-only text, skip nav, focus rings
   (grouped at the end so they're easy to find and never accidentally
   deleted along with a component section above)
============================================================ */


/* ── 1. DESIGN TOKENS ────────────────────────────────────────
   Change colors and fonts here. Everything else inherits.

   ★ ACCENT COLOR PALETTE — add swatches here, never skip a number.
     JS picks one at random on each page load, never repeating the
     previous visit's color. --accent is set by JS at runtime;
     the value below is just the initial fallback before JS fires.

   --ink through --ink4 = text grays, darkest to lightest
   --off / --rule = light background and divider colors
──────────────────────────────────────────────────────────── */
:root {
  /* ── Accent palette — add more as --palette-5, --palette-6, … ── */
  --palette-1: #0C8284;   /* Teal        */
  --palette-2: #B8860B;   /* Amber       */
  --palette-3: #5074C0;   /* Blue        */
  --palette-4: #E51050;   /* Pink        */
  --palette-5: #D95D39;   /* Terracotta  */
  /* --palette-5: #RRGGBB; */

  /* Fallback accent used until JS fires (first paint) */
  --accent:     #E51050;
  /* The three terra variables below derive automatically — don't touch them */
  --terra:      var(--accent);
  --terra-text: color-mix(in oklch, var(--accent), black 35%);  /* darker — for small text on light backgrounds, stays WCAG AA */
  /* Mixes toward var(--white), not the literal white keyword, so it stays
     theme-aware: a light tint on the light page, a dark muted tint on the
     dark page (§1b) — the .callout/.ev-block text sitting on top of it
     (var(--ink)/var(--ink2)) already inverts correctly either way, but only
     works if this background follows it rather than staying frozen light. */
  --terra-lt:   color-mix(in oklch, var(--accent), var(--white) 85%);  /* very light tint (dark mode: muted dark tint) — for callout backgrounds */

  /* Grays */
  --black:      #0f0f0d;
  --ink:        #1a1a18;
  --ink2:       #3a3a36;
  --ink3:       #595955;        /* passes WCAG AA on white (7:1) */
  --ink4:       #b0b0aa;        /* use only on dark backgrounds */
  --white:      #ffffff;
  --off:        #f7f6f3;
  --rule:       #e0dfda;

  /* Legacy aliases (kept for backward compatibility) */
  --purple:     var(--terra);
  --purple-lt:  var(--terra-lt);

  /* Before/After comparison colors (.ba-label / .ba-card — see §13 below).
     Tokenized so the dark theme remap (below) can supply legible dark-mode
     counterparts; these light-mode values are identical to the hardcoded
     hex/rgba they replace, so this is a no-op visually in light mode. */
  --ba-before:    #a03020;
  --ba-after:     #1a6b2f;
  --ba-before-bg: rgba(160, 48, 32, 0.07);
  --ba-after-bg:  rgba(26, 107, 47, 0.07);

  /* Typography — two-font system: Oswald for display/UI, Merriweather for body/emotional copy */
  --display: 'Oswald', 'Arial Narrow', sans-serif;
  --serif:   'Merriweather', Georgia, serif;

  /* Layout */
  --max:      1100px;           /* max content width */
  --col-left: 220px;            /* left label column width */
  --side:     clamp(1.5rem, 5vw, 4rem); /* horizontal page padding */

  /* Phi-based spacing scale — each step × 1.618
     xs   ~10px  | sm  ~16px  | md  ~26px
     lg   ~42px  | xl  ~68px  | xxl ~110px  */
  --space-xs:  0.618rem;
  --space-sm:  1rem;
  --space-md:  1.618rem;
  --space-lg:  2.618rem;
  --space-xl:  4.236rem;
  --space-xxl: 6.854rem;
}


/* ── 1b. DARK THEME TOKEN REMAP ──────────────────────────────
   Same custom-property names as §1 above, inverted values, scoped under
   [data-theme="dark"] on <html> (set by script.js — see design.md §2b).
   Every component already consuming var(--ink)/var(--white)/var(--off)/
   var(--rule) re-themes automatically — that's the whole point, and it's
   why almost nothing below this block needed to change.

   NOT remapped here, on purpose:
   --black  — already the site's one "always dark" fill (nav, hero band,
              contact band, dark stat/flow cards). Those bands don't invert
              between themes; they're dark in both. Remapping --black would
              turn them light, which design.md's dark-mode brief explicitly
              says NOT to do ("the nav band is already black").
   --ink4   — documented (§2 below) as "reversed text on dark backgrounds
              ONLY." Every current use of --ink4 already lives inside one of
              those constant --black bands, so it needs no dark-mode value
              of its own — it was already right for a dark surface.
   --accent/--terra/--palette-N — the rotating accent system; untouched,
              already renders correctly on black in both themes.
──────────────────────────────────────────────────────────── */
:root[data-theme="dark"] {
  --white: #151412;  /* page background (was #fff) — slightly warmer than
                         --black so the two constant-dark bands (nav/hero/
                         contact) still read as a shade darker than the page */
  --off:   #1e1c19;  /* section-alt / card bg — a step LIGHTER than --white
                         here (inverted from light mode, where --off is a
                         step darker than --white) — matches the common
                         dark-UI convention that an "elevated" surface reads
                         lighter, not darker, against the base page */
  --rule:  #33322d;  /* borders/dividers */
  --ink:   #f2f0ea;  /* primary text — ~16:1 on the new --white */
  --ink2:  #c8c6be;  /* secondary text */
  --ink3:  #9c9a92;  /* captions/meta — ~6.5:1 on the new --white, matching
                         the ~7:1 --ink3-on-white ratio in light mode */

  /* --terra-text is documented (§2 below) as a DARKENED accent for small
     text on LIGHT backgrounds only — never dark. Since most content
     backgrounds flip dark in this theme (case-card body, ghost-button
     default state, .meta-val.terra, the colon-list bullet, breadcrumb
     hover, .ev-pill), invert the mix direction instead of leaving it
     stuck at its light-mode value: every current consumer re-themes
     automatically, no per-component edits needed. */
  --terra-text: color-mix(in oklch, var(--accent), white 50%);

  /* Before/after labels + card tints (§13, .ba-label / .ba-card) — the
     light-mode red-brown/green lose all contrast on a dark card, so swap
     in lighter, more saturated counterparts (~6–8:1 against the new
     --white) rather than leaving the light values in place. */
  --ba-before:    #e2725a;
  --ba-after:     #5fbf7a;
  --ba-before-bg: rgba(226, 114, 90, 0.10);
  --ba-after-bg:  rgba(95, 191, 122, 0.08);
}

/* Three "light island" popups (design.md §4) — the password gate, the
   testimonial modal, and the breadcrumb's case-study dropdown — stay a
   white card with dark text in BOTH themes, rather than dark-theming three
   separate card treatments. All three already float above the page (two on
   a near-black scrim that isn't a theme token to begin with, one as an
   absolutely-positioned dropdown), so a consistent light card reads as a
   deliberate popup in either theme and needs zero changes to their own
   rules — just pin the tokens their subtree consumes back to the light-mode
   values here. */
:root[data-theme="dark"] .pw-box,
:root[data-theme="dark"] .testi-modal-box,
:root[data-theme="dark"] .crumb-dropdown {
  --white:      #ffffff;
  --off:        #f7f6f3;
  --ink:        #1a1a18;
  --ink2:       #3a3a36;
  --ink3:       #595955;
  --rule:       #e0dfda;
  --terra-text: color-mix(in oklch, var(--accent), black 35%);
  /* --terra-lt is mixed with --white (§1), so without its own pin here it
     inherits dark mode's *dark* --white and comes out as a muted dark tint
     instead of a light one — the crumb-dropdown's current-case highlight
     (the only light-island consumer of --terra-lt) rendered as a near-black
     box with orange text instead of a light pink one. Recompute it against
     the light --white pinned above, same recipe as §1's definition. */
  --terra-lt:   color-mix(in oklch, var(--accent), #ffffff 85%);
}

/* These elements are ALWAYS a dark fill regardless of theme (the dark
   stat/flow cards and "solid black" ghost-button states) — they use --white
   purely as literal white text/icon fill on that constant dark fill, not in
   --white's page-background role. Pin it back to literal white here so the
   remap above (which repurposes --white as the *page's* dark background)
   doesn't turn this text invisible against the dark fill it's actually
   sitting on. (nav/.hero-band/.contact-band/footer used to be pinned here
   too — see the full-invert block below instead, they now flip with the
   theme rather than staying constant.) */
:root[data-theme="dark"] .stat-cell,
:root[data-theme="dark"] .flow-step,
:root[data-theme="dark"] .ev-cta.neutral,
:root[data-theme="dark"] .md-icon.white {
  --white: #ffffff;
}

/* Nav, hero band, and the merged contact/footer band match the BODY's
   background and text tokens (2026-07-21, superseding an earlier full-invert
   attempt) rather than being a distinct black/inverted band at all: they
   use var(--white) for background and the standard content tokens
   (--ink/--ink2/--ink3/--terra-text/--rule) for text, borders, and links —
   the exact same tokens every other section on the page already uses. That
   means they need no dark-mode-specific override block of their own: they
   toggle automatically in lockstep with the rest of the page, because
   they're reading the same tokens the rest of the page reads. See §3
   (NAVIGATION), §5 (HERO BAND), and §17/§18 (CONTACT BAND / FOOTER) for the
   component rules themselves — this note exists so a future reader doesn't
   go looking for a nav/hero/footer-specific dark-mode block that no longer
   exists. The nav logo's two main letterform paths (_nav.html) use a
   literal fill="white" SVG attribute in the markup; `.nav-logo path` below
   overrides it to `var(--ink)` so the logo stays visible against the
   band's now-toggle-aware background without editing the markup itself —
   a stylesheet fill rule beats an inline presentation attribute. */
.nav-logo path:not(.logo-accent) { fill: var(--ink); }


/* ── 2. RESET & BASE ─────────────────────────────────────────
   Minimal reset. Box-sizing on everything, smooth scrolling.
──────────────────────────────────────────────────────────── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
}

/* Sticky footer: on short pages (e.g. the case-studies password gate,
   before it's unlocked) main grows to fill the viewport so the footer
   sits flush at the bottom instead of floating right under the content
   with empty page below it. */
body {
  font-family: var(--serif);
  background: var(--white);
  color: var(--ink);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

main { flex: 1 0 auto; }


/* ── 3. NAVIGATION ───────────────────────────────────────────
   Sticky black nav bar with terracotta underline on active item.
──────────────────────────────────────────────────────────── */
nav {
  position: sticky;
  top: 0;
  z-index: 200;
  background: var(--white);   /* matches the body's own background/toggle exactly — see §1b's note above §3 */
  padding: 0 var(--side);
  border-bottom: 2px solid var(--terra);
  /* iOS Safari fix: force the nav onto its own GPU compositing layer.
     Without this, Safari sometimes renders animated page content (the
     fadeUp transform) on top of the sticky nav, creating a white bleed. */
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

.nav-inner {
  max-width: var(--max);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 58px;
}

.nav-logo {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  cursor: pointer;
  /* Vertical padding only — width (47px) already clears the 40px tap-target
     minimum, height (30px, from the logo svg's own inline height) didn't.
     Vertically centered in the fixed 58px nav bar, so this is layout-safe. */
  padding: 5px 0;
}

.nav-logo svg {
  height: 32px;
  width: auto;
}

/* Logo accent shapes — tied to --terra so they update when --accent changes */
.nav-logo .logo-accent { fill: var(--terra); }

.nav-links {
  display: flex;
  gap: 2px;
  /* Pull the group left by the nav-link's own horizontal padding (16px, below)
     so the FIRST label's TEXT sits on the shared content spine — the same left
     edge as the breadcrumb, case labels, hero name, etc. Without this the links'
     padding insets the text 16px and the nav drifts off every other edge on the
     page. Must stay equal to the `.nav-links a` left padding. */
  margin-left: -16px;
}

/* Nav items are real page links (<a>) now, not JS buttons. */
.nav-links a {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink3);   /* was --ink4 ("dark-background only") — --ink3 is the equivalent muted gray for the body-matched nav, same one used for meta/caption text everywhere else on the site */
  padding: 10px 16px;   /* vertical bumped 8→10px so the tap target clears 40px tall (was 37px) */
  transition: color 0.15s;
  position: relative;
}

/* Terracotta underline that slides in on hover / active */
.nav-links a::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 16px;
  right: 16px;
  height: 2px;
  background: var(--terra);
  transform: scaleX(0);
  transition: transform 0.2s;
}

.nav-links a:hover         { color: var(--ink); }
.nav-links a:hover::after  { transform: scaleX(1); }
.nav-links a.active        { color: var(--ink); }
.nav-links a.active::after { transform: scaleX(1); background: var(--terra); }


/* ── 4. CASE-STUDIES GATE ────────────────────────────────────
   The site is now separate HTML pages (index / story / case-studies),
   so the old single-page .page show/hide system is gone. The only
   page-level toggle left is the case-studies password gate: .cs-gate
   holds the password prompt centered in the content area, and JS
   (in case-studies.html) sets it display:none after a correct
   password decrypts the content into #cs-content.
──────────────────────────────────────────────────────────── */
.cs-gate {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: var(--space-xxl) var(--side);
}


/* ── 5. HERO BAND ────────────────────────────────────────────
   Black band at top of the Work page: big name left, eyebrow +
   bio right. Left column is a fixed 420px to match the image
   column in .case-card below, so the two sections line up.
──────────────────────────────────────────────────────────── */
/* VERTICAL RHYTHM: all vertical spacing on the content pages uses the
   phi --space-* tokens (see DESIGN TOKENS), one step per relationship:
     --space-xs  eyebrow → title            (tightest: labels hug their heading)
     --space-sm  title → body               (related, same thought)
     --space-md  body → button, attribution (new element, same block)
     --space-lg  between stacked blocks / card padding
     --space-xl  band + section padding     (new section)
   When adding spacing, pick the token for the relationship — don't
   invent a new pixel value. */
.hero-band {
  background: var(--white);   /* matches the body's own background/toggle exactly — see §1b's note above §3 */
  padding: var(--space-xl) var(--side); /* same token as .section — bands and sections share one rhythm */
  border-bottom: 1px solid var(--rule); /* same divider treatment as .section-rule above the testimonials */
}

.hero-inner {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 420px 1fr;
  /* Row gap only matters when this stacks to one column (≤900px) —
     without it the eyebrow rides right against the name's descenders. */
  gap: var(--space-lg) 3rem;
  align-items: start;
}

.hero-name {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(3.4rem, 7vw, 6.4rem);
  line-height: 0.92;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-transform: uppercase;
  margin-top: -0.14em; /* cancels font leading so the cap-height lines up with the eyebrow */
}

/* "Andrew" in terracotta */
.hero-name span { color: var(--terra); }

.hero-eyebrow {
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--terra-text); /* accent, matching .case-card-eyebrow (2026-08-06) — both are
    "eyebrow" labels at the same type scale, previously inconsistent (this one muted,
    that one accented) with no documented reason for the difference */
  margin-bottom: var(--space-sm); /* eyebrow sits directly over body copy here (no title between) */
}

.hero-body {
  font-family: var(--serif);
  font-weight: 300;
  font-size: 1.0625rem;
  color: var(--ink2);
  line-height: 1.85;
  max-width: 560px;
}

/* Quiet lead-in to the case cards below (2026-08-05) — same "quiet closing
   line, different register" treatment as .contact-signoff: italic serif,
   smaller and lighter than the body it follows, so it reads as a soft nudge
   to keep scrolling rather than a second sentence competing with the body's
   actual claim. Replaces the "View on LinkedIn" button that used to sit
   here (see content.md) — this keeps visitors on the page instead of
   sending them off to LinkedIn before they've seen any of the work.
   Color switched to --terra-text (2026-08-05) — same AA-safe accent used
   for small colored text elsewhere (.testi-name:hover, .meta-val.terra) —
   so this line reads as a deliberate accent touch, not just muted gray. */
.hero-lead {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: 0.9375rem;
  color: var(--terra-text);
  margin-top: var(--space-sm);
}


/* ── 6. SECTION WRAPPERS ─────────────────────────────────────
   .section adds vertical padding. .section-alt = off-white bg.
   .section-rule = same divider line used between .case-cards,
   for sections that should read as "next block" rather than a
   shaded band.
──────────────────────────────────────────────────────────── */
.section      { padding: var(--space-xl) var(--side); }
.section-inner { max-width: var(--max); margin: 0 auto; }
.section-alt  { background: var(--off); }
.section-rule { border-top: 1px solid var(--rule); }


/* ── 7. CASE STUDY CONTENT LABELS ────────────────────────────
   Section labels + metadata used inside the case-study .case-row grid
   (see the FULL-SPINE CASE LAYOUT block below for the grid itself).
──────────────────────────────────────────────────────────── */
/* Section label (e.g. "The situation", "Career journey") */
.col-left-label {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink3);
  margin-bottom: 1.5rem;
  padding-top: 6px;
}

/* Left column metadata (Role, Timeline, etc.) */
.meta-item { margin-bottom: var(--space-sm); }
.meta-item:last-child { margin-bottom: 0; }   /* no trailing space under the last field — matches .body-serif:last-child */

.meta-key {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink3);
  margin-bottom: 2px;
}

.meta-val {
  font-family: var(--serif);   /* deliberate exception to the Oswald-for-labeled-data rule — see design.md §3 */
  font-size: 14px;
  font-weight: 400;
  color: var(--ink);
  letter-spacing: 0.02em;
}

.meta-val.purple,
.meta-val.terra  { color: var(--terra-text); }


/* ── FULL-SPINE CASE LAYOUT ───────────────────────────────────
   Used by all three case studies. The MAIN content leads on the left spine, and
   supplementary info sits in a right sidebar (.case-aside) that stacks AFTER the
   content on mobile — the overview's "Extra Case Info" (Status/Role/Partners/
   Focus) and per-section colon-lists live there. Section labels move inline as
   eyebrows leading their content (.case-main > .col-left-label). */
.case-row {
  display: grid;
  grid-template-columns: 1fr var(--col-left);   /* content left, sidebar right */
  gap: 0 4rem;
  padding: var(--space-lg) 0;
  border-top: 1px solid var(--rule);
  align-items: start;
}
.case-row:first-child { border-top: none; }
.case-main  { min-width: 0; }   /* let figures / long words shrink, not overflow */
.case-aside { min-width: 0; }

/* Section label, now an eyebrow leading the content instead of a rail item */
.case-main > .col-left-label {
  padding-top: 0;
  margin-bottom: var(--space-xs);
}
/* The overview sidebar's "Case 01" lines up with the top of .case-main's figure.
   The 6px padding-top + inherited body line-height (1.6) this used to carry were
   calibrated to a 56px .project-icon.lg that sat above the headline — since removed
   (see design.md §0/§4 history). With the figure now first in .case-main, both
   columns should start flush at the grid row's top edge (align-items: start):
   line-height: 1 drops the ~5px of inherited leading a short single-line label
   doesn't need, so the label's own cap-height sits at its box's top edge instead
   of floating inside extra line-box space. Paired with the .case-figure:first-child
   rule below, which removes the figure's own top margin for the same reason. */
.case-aside > .col-left-label:first-child { padding-top: 0; line-height: 1; margin-bottom: var(--space-sm); }
/* .case-figure normally follows a paragraph (2rem gap reads as "next block"), but
   in the Overview row it's .case-main's first child with nothing above it — that
   margin doesn't collapse away (grid items are their own block-formatting context),
   so it was rendering as a floating ~32px gap before the image, throwing off the
   top alignment against .case-aside above. Zero it only when the figure is first. */
.case-main > .case-figure:first-child { margin-top: 0; }
/* When a meta field's value is framed as chips (Partners/Focus), give the chip
   row a little air below its label (reuses the shared .chips/.chip from §My Story) */
.meta-item > .chips { margin-top: 7px; }


/* ── 8. DISPLAY HEADLINES ────────────────────────────────────
   Three sizes: xl (page hero), lg (section header), md (subsection).
   .accent = terracotta highlight within a headline.
──────────────────────────────────────────────────────────── */
.display-xl {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 0.95;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 1.25rem;
}

.display-xl .accent { color: var(--terra); }

.display-lg {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.8rem, 4vw, 3rem);
  line-height: 1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 1rem;
}

.display-md {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.3rem;
  line-height: 1.1;
  text-transform: uppercase;
  color: var(--ink);
  letter-spacing: 0.01em;
  margin-bottom: 0.5rem;
}

/* ── 9. BODY TEXT ────────────────────────────────────────────
   Merriweather Light (weight 300), 17px / 1.85 line-height for
   main narrative paragraphs inside the full case study pages.
──────────────────────────────────────────────────────────── */
.body-serif {
  font-family: var(--serif);
  font-weight: 300;              /* Merriweather Light */
  font-size: 1.0625rem;          /* 17px — slightly larger to compensate for light weight */
  color: var(--ink2);
  line-height: 1.85;             /* Merriweather needs a little more air than most serifs */
  margin-bottom: 1.1rem;
}

.body-serif:last-child { margin-bottom: 0; }


/* ── 10. CASE CARDS ──────────────────────────────────────────
   Selected work on the Work page — image + copy side by side.
   420px image column matches the hero's left column so the
   whole page reads as one aligned grid.
──────────────────────────────────────────────────────────── */
.case-card {
  display: grid;
  grid-template-columns: 420px 1fr;
  gap: var(--space-lg) 3rem;
  align-items: start;
  padding: var(--space-lg) 0;
  border-bottom: 1px solid var(--rule);
}
.case-card:first-of-type { padding-top: 0; }
.case-card:last-of-type { border-bottom: none; padding-bottom: 0; }

/* Wraps .case-card-img so tapping the image opens the same case study
   as the "Read the full case study" button below it — block so it
   fills the grid's image column like the <img> itself used to. */
.case-card-img-link { display: block; }

.case-card-img {
  width: 100%;
  height: 280px;
  object-fit: cover;
  display: block;
  background: var(--off);
}

.case-card-eyebrow {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--terra-text); /* was raw --terra (2026-08-06 fix) — fails AA outright on
    amber/terracotta accents at this size; --terra-text is the AA-safe variant every
    other small accent-colored text on the site already uses */
  margin-bottom: var(--space-xs); /* eyebrow → title: tightest step, the label hugs its heading */
}

.case-card-title {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.65rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: var(--space-sm); /* title → body */
  max-width: 560px;
}

.case-card-body {
  font-family: var(--serif);
  font-size: 0.9375rem;
  color: var(--ink2);
  line-height: 1.75;
  max-width: 560px;
  margin-bottom: var(--space-md); /* body → button */
}

.case-card-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none; /* it's a link (<a>) to case-studies.html now */
  color: var(--terra-text);
  background: transparent;
  border: 1px solid var(--terra);
  padding: 0.875rem 1.5rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.case-card-cta:hover {
  background: var(--terra);
  color: var(--black); /* not white — amber/terracotta accents fail 4.5:1 with white text */
}

/* .hero-inner shares this breakpoint with .case-card on purpose — both
   use the same fixed-420px-column-plus-flexible-content pattern, so they
   need to stack at the same width. If they stacked at different widths
   (as .hero-inner used to, at 700px instead of 900px), there's a gap
   where the 420px column is fixed but the sibling content column has
   been squeezed too narrow — the content wraps onto many more lines,
   the grid row stretches to fit it, and whatever's in the 420px column
   (name, image) ends up floating over a lot of empty space above content
   that's no longer tall enough to fill the row it's sizing. */
@media (max-width: 900px) {
  .case-card  { grid-template-columns: 1fr; gap: var(--space-md); }
  .hero-inner { grid-template-columns: 1fr; }
  .case-card-img { height: 56vw; } /* shorter on narrow screens so less of the image gets cropped by object-fit: cover */
}




/* ── 13. FULL CASE STUDY PAGES ───────────────────────────────
   Layout elements used inside the case-studies.html articles.
──────────────────────────────────────────────────────────── */

/* Each case study is an <article id="martech|apex|ease">. The offset
   keeps the sticky nav from covering the top when you deep-link to one
   (e.g. case-studies.html#apex from a My Work card). */
.case-study { scroll-margin-top: 76px; }

/* ── STATS BLOCK ─────────────────────────────────────────────
   Dark metric grid used at the top of case study pages. .stats-row shares
   .case-row's exact column formula (1fr + --col-left, same 4rem gap) so
   .stats-block — including its own card padding — renders at exactly the
   width of .case-main above it at every viewport, not just the widest one.
   A hardcoded calc() of the same math would only match at the one breakpoint
   where .section-inner is at its full --max width; below that, .case-main
   narrows proportionally while a fixed calc() wouldn't, so the two would
   drift apart. Sharing the grid instead of duplicating the arithmetic keeps
   them locked together by construction. The sidebar track is left empty on
   purpose, it's reserved space, not a second column of content.
──────────────────────────────────────────────────────────── */
.stats-row {
  display: grid;
  grid-template-columns: 1fr var(--col-left);
  gap: 0 4rem;
}

/* min-width: 0 matches .case-main's own reset (§ above) — without it, a grid
   item's default min-width:auto lets its content (the stat labels' min-content
   size) blow the 1fr track past .case-main's width at in-between viewports,
   the same overflow .case-main already guards against for its own content. */
.stats-block { padding: 2.5rem; margin: 2rem 0; min-width: 0; }

.stats-grid {
  display: grid;
  gap: 2px;
}

.stat-cell {
  background: var(--black);
  padding: 1.5rem 2rem;
}

.stat-number {
  font-family: var(--display);
  font-size: 2.5rem;
  font-weight: 900;
  color: var(--white);
  line-height: 1;
}

.stat-label {
  font-family: var(--display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink4);
  margin-top: 6px;
}

/* Breadcrumb — sits at the top of a case study where the old back button used to.
   A simple home-icon + chevron trail on the light page (no dark bar): the site's
   Oswald label type (uppercase, 0.1em tracking, like the nav) with Material
   OUTLINED icons (home + work_outline + chevron_right, per design.md's icon
   language). The home icon is the root (links to My Work); the middle "Case
   Studies" crumb is a disclosure button that opens a menu of the three studies;
   the last crumb is the current page. Clickable crumbs warm to the accent on
   hover; the current page is a muted, non-interactive label. */
.breadcrumb {
  /* It's a <nav>, so it matches the global `nav {}` top-bar rule — reset the
     sticky black bar that pulls in (black bg, padding, border, sticky) so the
     breadcrumb sits plainly on the light page, flush-left with the content. */
  position: static;
  z-index: auto;
  background: none;
  border-bottom: none;
  padding: 0;
  transform: none;
  margin-bottom: var(--space-xs);   /* ~1/4 of the old gap, on the phi scale */
}
/* Tighten the space ABOVE the breadcrumb to ~1/4 (--space-xl → --space-sm),
   same rhythm idea. Auto-applies to any case-study section that leads with a
   breadcrumb (so APEX/EASE inherit it when they adopt the pattern). */
.section:has(.breadcrumb) { padding-top: var(--space-sm); }

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  list-style: none;
  gap: 0.5rem;             /* breathing room around the chevron separators */
}
.breadcrumb > ol > li {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}
/* Chevron separator before every top-level crumb after the first. Material
   chevron_right, baked a neutral light gray so it stays decorative while the
   accent rotates. (Scoped to top-level crumbs so menu items get no chevrons.) */
.breadcrumb > ol > li + li::before {
  content: '';
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23c4c2bb' d='M10 6 8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E") no-repeat center / contain;
}
/* The three top-level crumbs (home link, menu trigger, current) share the
   Oswald nav-label treatment */
.breadcrumb > ol > li > a,
.breadcrumb .crumb-trigger,
.breadcrumb .crumb-current {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
}
/* Clickable crumbs (home link + the menu trigger) — warm to accent on hover.
   Padding + negative margin grow the hit area (esp. the icons) without shifting
   the visual position or breaking flush-left alignment with the content.
   10px (not 6px) so the home icon's 22×22 box reaches the 40×40 tap-target
   minimum (22 + 10×2 = 42) — padding and margin must stay equal-and-opposite
   or the spine alignment breaks (see design.md §5 One Left Spine). */
.breadcrumb > ol > li > a,
.breadcrumb .crumb-trigger {
  position: relative;      /* for the hover underline */
  color: var(--ink2);
  background: none;
  border: none;
  cursor: pointer;
  padding: 10px;
  margin: -10px;
  transition: color 0.15s;
}
.breadcrumb > ol > li > a:hover,
.breadcrumb .crumb-trigger:hover,
.breadcrumb .crumb-trigger[aria-expanded="true"] {
  color: var(--terra-text);
}
/* Sliding terracotta underline on hover — the same mechanic and feel as the
   top-bar nav (.nav-links a::after). Inset by the 6px hit-area padding so it
   spans the crumb's content, not the padded box. Also shown while the menu is
   open (aria-expanded), like the nav's active item. */
.breadcrumb > ol > li > a::after,
.breadcrumb .crumb-trigger::after {
  content: '';
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: 0;
  height: 2px;
  background: var(--terra);
  transform: scaleX(0);
  transition: transform 0.2s;
}
.breadcrumb > ol > li > a:hover::after,
.breadcrumb .crumb-trigger:hover::after,
.breadcrumb .crumb-trigger[aria-expanded="true"]::after {
  transform: scaleX(1);
}
.breadcrumb .crumb-home {
  width: 22px;
  height: 22px;
  fill: currentColor;      /* inherits the link color, incl. accent on hover */
  display: block;
}
.breadcrumb .crumb-icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
  flex-shrink: 0;
}
.breadcrumb .crumb-caret {
  width: 16px;
  height: 16px;
  fill: currentColor;
  flex-shrink: 0;
  opacity: 0.75;
  transition: transform 0.2s;
}
.breadcrumb .crumb-trigger[aria-expanded="true"] .crumb-caret {
  transform: rotate(180deg);
}
/* Current page — muted, not a link */
.breadcrumb .crumb-current {
  color: var(--ink3);
}

/* ── Breadcrumb dropdown (case-study menu) ────────────────────
   Elegant, flat (no shadow — separated by a solid border + white fill, per the
   design system), mobile-usable (44px touch targets, capped width). */
.crumb-menu { position: relative; }
.crumb-dropdown {
  position: absolute;
  top: calc(100% + 10px);
  left: -6px;                    /* aligns roughly under the trigger's icon */
  z-index: 210;                  /* above the sticky nav, just in case */
  min-width: 240px;
  max-width: min(300px, 82vw);   /* never overflow a small screen */
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--white);
  border: 1px solid var(--rule);
}
.crumb-dropdown[hidden] { display: none; }
.crumb-dropdown li { display: block; }
.crumb-dropdown a {
  display: flex;
  align-items: center;
  min-height: 44px;              /* comfortable touch target */
  padding: 0 14px;
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink2);
  text-decoration: none;
  transition: background 0.12s, color 0.12s;
}
.crumb-dropdown a:hover { background: var(--off); color: var(--ink); }
/* The current case — highlighted so "you are here" is obvious in the menu */
.crumb-dropdown a[aria-current="page"] {
  color: var(--terra-text);
  background: var(--terra-lt);
}


/* Before / After comparison grid */
.before-after {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  margin: 2rem 0;
}

.ba-card               { padding: 1.5rem; background: var(--off); }
.ba-card:first-child   { background: var(--ba-before-bg); }  /* faint red — Before */
.ba-card:last-child    { background: var(--ba-after-bg);  }  /* faint green — Direction */

.ba-label {
  font-family: var(--display);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin-bottom: 0.75rem;
}

.ba-label.before { color: var(--ba-before); }   /* red-brown for "before" */
.ba-label.after  { color: var(--ba-after); }    /* green for "direction" */
.ba-label        { color: var(--ink3); }        /* default when neither .before nor .after applies (see .compare-3 below) */

.ba-item {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink2);
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  display: flex;
  gap: 10px;
}

.ba-item:last-child { border-bottom: none; }
.ba-icon { color: var(--ink3); }

/* Multi-way comparison variant (EASE's three competing prototypes) — reuses
   the same .before-after/.ba-card/.ba-label shell as the standard 2-up
   before/after above, just with three columns and a plain descriptive
   paragraph per card instead of .ba-item feature lists — the same "one
   shell, two content shapes" reuse .ev-block already does for EASE's
   design-principle blocks (see below). Previously implemented with inline
   style="" attributes directly in ease.src.html (background/label color per
   card, plus a full font-family/size/color/line-height override on the body
   paragraph) — moved here so it responds like every other component instead
   of being frozen at one fixed, non-token appearance. */
.before-after.compare-3 { grid-template-columns: repeat(3, 1fr); }
/* Base .ba-card (--off bg) already matches two of the three cards; only the
   first/last-child red/green before-after coloring (built for exactly two
   cards) needs neutralizing here — the winning option gets an explicit
   .winner highlight instead of an accidental one from :last-child. */
.before-after.compare-3 .ba-card:first-child:not(.winner),
.before-after.compare-3 .ba-card:last-child:not(.winner) { background: var(--off); }
.ba-card.winner { background: var(--terra-lt); }
.ba-card.winner .ba-label { color: var(--terra-text); }
.ba-body {
  font-family: var(--serif);
  font-weight: 300;
  font-size: 0.9375rem;
  color: var(--ink2);
  line-height: 1.7;
  margin: 0;
}

/* Horizontal process flow (5 steps) */
.flow {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  margin: 2rem 0;
}

.flow-step {
  background: var(--black);
  padding: 1.1rem 0.85rem;
  text-align: center;
  position: relative;
}

/* Arrow between steps */
.flow-step::after {
  content: "→";
  position: absolute;
  right: -10px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 14px;
  color: var(--ink4);    /* ink4 is fine here — dark background context */
  z-index: 1;
}

.flow-step:last-child::after { display: none; }

.flow-num {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.12em;
  color: rgba(255,255,255,0.55);
  margin-bottom: 5px;
  text-transform: uppercase;
}

.flow-label {
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  color: var(--white);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 3px;
  line-height: 1.1;
}

.flow-sub {
  font-size: 12px;
  color: var(--ink4);    /* ink4 is fine here — dark background */
  line-height: 1.3;
}

/* Highlighted callout box */
.callout {
  border-left: 4px solid var(--terra);
  background: var(--terra-lt);
  padding: 1.25rem 1.5rem;
  margin: 2rem 0;
}


/* ── EVIDENCE BLOCKS ─────────────────────────────────────────
   The "thinking behind the decisions" summary at the foot of a case study:
   a sub-header + a small tag, then takeaway bullets with terracotta dots.
   .ev-block is also reused (without .bullet children) by EASE's "The
   design" section for its three plain-paragraph principles (Simplicity /
   Humanity / Ingenuity) — a different content shape, so the box treatment
   below is scoped to only the bulleted (genuine takeaway) blocks via
   :has(.bullet); the plain paragraph usage keeps its original spacing so it
   isn't turned into a stack of orange boxes by a change aimed at Evidence. */
.ev-block { margin-top: var(--space-md); }
.ev-block + .ev-block { margin-top: var(--space-lg); }
/* Plain (non-bulleted) .ev-block usage — EASE's design-principle blocks —
   keeps the original hairline divider between consecutive blocks. */
.ev-block:not(:has(.bullet)) + .ev-block:not(:has(.bullet)) {
  padding-top: var(--space-lg);
  border-top: 1px solid var(--rule);
}
/* Extra lead-in gap for the first plain .ev-block in a group (EASE's
   "Simplicity" block, immediately after a lead paragraph) — gives it the
   same total --space-lg "between stacked blocks" gap the consecutive-block
   rule above already gives every plain .ev-block after the first, expressed
   as the token difference from the base margin-top rather than a fixed
   pixel value. Previously an inline style="padding-top:1rem" stacked on top
   of that base margin-top — same visual result (1rem), now token-derived
   so it responds correctly at any breakpoint. */
.ev-block.lead-gap { padding-top: calc(var(--space-lg) - var(--space-md)); }
/* 2026-07-21: boxed to match .callout above it on the same page — same
   --terra-lt background + terra left-border + padding recipe — so Evidence
   reads as one more "chunk" in the callout/before-after/flow sequence
   instead of dropping to plain list text at the foot of the page. The box
   edge (not a hairline rule) separates consecutive boxed blocks, matching
   how .before-after/.flow separate their own pieces. Shared by all three
   case studies. */
.ev-block:has(.bullet) {
  background: var(--terra-lt);
  border-left: 4px solid var(--terra);
  padding: 1.25rem 1.5rem;
}
.ev-header {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-bottom: var(--space-sm);
}
.ev-header .display-md { margin-bottom: 0; }
/* Same box metrics as .chip (padding/font-size/border/background) so the page
   has one tag shape, not two — .chip's white-on-rule box would be invisible
   sitting directly on .ev-block's own --terra-lt fill, so the background stays
   white here; --terra-text is kept as the one intentional accent difference,
   tying the tag back to .callout's terra-lt/terra-text pairing without
   fighting the block's new background. */
.ev-pill {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--terra-text);
  background: var(--white);
  border: 1px solid var(--rule);
  padding: 5px 10px;
  white-space: nowrap;
}
.bullet {
  display: flex;
  gap: 0.85rem;
  margin-bottom: var(--space-sm);
}
.bullet:last-child { margin-bottom: 0; }
.bullet-dot {
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--terra);
  margin-top: 0.6rem;      /* drop the dot onto the first line of text */
}
.bullet-text {
  font-family: var(--serif);
  font-weight: 300;        /* Merriweather Light, like .body-serif */
  font-size: 1rem;
  line-height: 1.75;
  color: var(--ink2);
}
.bullet-text strong { font-weight: 700; color: var(--ink); }   /* bold lead-in pops */

/* Closing CTA row ("Back to work" / "View Next Case") — the site's real
   ghost-button pattern, not the legacy .read-btn (solid black, arrow
   characters) that used to live here. Accent variant for the primary/
   forward action, neutral (ink, text-only, no icon) for the secondary/exit
   action — same two-variant split as .testi-modal-linkedin/.testi-modal-back.
   Same metrics as .case-card-cta on purpose (one accent-button recipe). */
.ev-actions {
  margin-top: var(--space-md);
  display: flex;
  gap: 0.75rem;         /* matches .testi-modal-actions' button-row gap */
  flex-wrap: wrap;
}
.ev-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--terra-text);
  background: transparent;
  border: 1px solid var(--terra);
  padding: 0.875rem 1.5rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.ev-cta:hover { background: var(--terra); color: var(--black); }
.ev-cta.neutral { color: var(--ink); border-color: var(--ink); }
/* Uses --black (not --ink) for the hover fill: --ink is primary TEXT and
   inverts to a light color in dark mode (design.md §2b), which would break
   this fill/white-text pairing there. --black stays a constant dark fill in
   both themes, and is visually identical to --ink in light mode. */
.ev-cta.neutral:hover { background: var(--black); color: var(--white); }

.callout p       { font-family: var(--serif); font-weight: 300; font-size: 0.9375rem; color: var(--ink2); line-height: 1.8; margin-bottom: 0; }
.callout strong  { color: var(--ink); font-weight: 600; }


/* ── TESTIMONIALS (Work page) ────────────────────────────────
   Three condensed quotes side by side. Each links to a modal
   with the full long-form quote — see TESTIMONIAL MODAL below.
──────────────────────────────────────────────────────────── */
.testi-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2.5rem;
}

.testi-quote {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
  margin-bottom: var(--space-md); /* quote → attribution */
}

.testi-attrib {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  margin-bottom: var(--space-md); /* attribution → button */
}

/* Photo + name both link out to the person's LinkedIn (2026-07-20) —
   .testi-photo-link is the flex item now (was the bare <img>), so it
   carries flex-shrink: 0 and just wraps tightly around the photo. */
.testi-photo-link {
  display: block;
  flex-shrink: 0;
  line-height: 0; /* removes the few px of inline-image gap under the circle */
}

.testi-photo-link:hover .testi-photo { opacity: 0.85; }

.testi-photo {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  object-fit: cover;
  transition: opacity 0.15s;
}

.testi-name {
  display: block;
  font-family: var(--display);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink);
  text-decoration: none;
  transition: color 0.15s;
}

.testi-name:hover { color: var(--terra-text); }

.testi-role {
  font-size: 0.75rem;
  color: var(--ink3);
  line-height: 1.4;
}

.testi-cta {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terra-text);
  background: transparent;
  border: 1px solid var(--terra);
  padding: 0.75rem 1.25rem;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.testi-cta:hover {
  background: var(--terra);
  color: var(--black); /* not white — amber/terracotta accents fail 4.5:1 with white text */
}

@media (max-width: 900px) {
  .testi-grid { grid-template-columns: 1fr; gap: 2.5rem; }
}


/* ── TESTIMONIAL MODAL ────────────────────────────────────────
   Shows the full long-form quote + a link to LinkedIn. Same
   overlay/card shell as the password gate, so it's a second
   coat of paint rather than a second modal system.
──────────────────────────────────────────────────────────── */
.testi-modal {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10,10,10,0.92);
  z-index: 500;
  align-items: center;
  justify-content: center;
  padding: 2rem;
}
.testi-modal.active { display: flex; }

.testi-modal-box {
  background: var(--white);
  max-width: 560px;
  width: 100%;
  position: relative;
}

/* Scrolls independently of .testi-modal-box — on mobile the box fills
   the screen, so this is what lets a long quote scroll while the box
   itself stays sized to the viewport (see the dvh max-height below). */
.testi-modal-scroll {
  padding: 3rem;
  max-height: calc(100vh - 4rem);
  max-height: calc(100dvh - 4rem); /* accounts for mobile browser toolbars */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.testi-modal-attrib { display: flex; align-items: center; gap: 0.9rem; margin-bottom: var(--space-md); }
.testi-modal-photo  { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; }

/* Holds one <p> per paragraph — built in script.js so a long quote
   reads as a few short paragraphs instead of one dense block. */
.testi-modal-quote { margin-bottom: var(--space-md); }

.testi-modal-quote p {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: 1.125rem;
  line-height: 1.7;
  color: var(--ink);
  margin-bottom: var(--space-sm);
}

.testi-modal-quote p:last-child { margin-bottom: 0; }

/* Same ghost-button pattern as everywhere else on the site. */
.testi-modal-linkedin {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--terra-text);
  background: transparent;
  border: 1px solid var(--terra);
  padding: 0.875rem 1.5rem;
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
}

.testi-modal-linkedin:hover {
  background: var(--terra);
  color: var(--black);
}

.testi-modal-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}

/* Neutral sibling of .testi-modal-linkedin — closes the modal. Replaces
   the old absolutely-positioned "×" (top-right), which was unreliable
   to tap on mobile; a real button in the flow, next to the other
   action, doesn't have that problem. */
.testi-modal-back {
  display: inline-flex;
  align-items: center;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink);
  padding: 0.875rem 1.5rem;
  line-height: normal; /* matches .testi-modal-linkedin's <a> box height */
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.testi-modal-back:hover { background: var(--ink); color: var(--white); }

@media (max-width: 600px) {
  .testi-modal { padding: 0; }
  .testi-modal-scroll {
    max-height: 100vh;
    max-height: 100dvh;
    padding: 2rem 1.5rem;
  }
}


/* ── MY STORY PAGE ────────────────────────────────────────────
   "At home" (personal bio) then "at work" (career journey) — all
   four blocks (bio + intro + Era 01 + Era 02) reuse .case-card
   directly (image + copy). The base .case-card:last-of-type rule
   already drops the bottom rule from whichever card ends up last,
   so no story-grid-specific override is needed here.
──────────────────────────────────────────────────────────── */
.story-grid {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.chip {
  font-family: var(--display);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  background: var(--white);
  color: var(--ink3);
  padding: 5px 10px;
  border: 1px solid var(--rule);
}


/* ── 17. CONTACT BAND ────────────────────────────────────────
   2026-07-21: moved into the shared _footer.html partial (was a
   hand-duplicated block in index.src.html + story.src.html). Now
   renders once, at the top of <footer>, on all five pages — the
   case study pages get it for the first time. See design.md §4.
──────────────────────────────────────────────────────────── */
.contact-band {
  background: var(--white);   /* matches the body's own background/toggle exactly — see §1b's note above §3 */
  border-top: 2px solid var(--terra); /* the merged footer's one top edge — footer itself carries no border now */
  padding: var(--space-xl) 0; /* horizontal --side now lives on <footer>, shared with .footer-inner
    below it — see <footer>'s own rule. Was `var(--space-xl) var(--side)` on this element directly,
    which double-applied --side once <footer> also started supplying it (2026-07-22, same day as
    that change): at very wide viewports both .contact-inner and .footer-inner still hit their
    max-width:1100px cap with room to spare, so the extra padding was invisible — the two boxes'
    centered position happened to land on the same point either way. At narrower ones (~1280px)
    where .contact-inner's double-padded available width dropped below 1100px and stopped being
    max-width-clamped while .footer-inner's single-padded one was still comfortably above it, the
    two diverged for real — "Genuinely open." sat 38px right of the footer identity line below it. */
}

.contact-inner {
  max-width: var(--max);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 2rem;
}

.contact-headline {
  font-family: var(--display);
  font-weight: 400;
  font-size: 2rem;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: var(--space-xs); /* headline → body: tight, one thought */
}

.contact-body {
  font-family: var(--serif);
  font-size: 0.9375rem;
  color: var(--ink2);
  max-width: 480px;
  line-height: 1.7;
}

/* Quiet personal sign-off (2026-07-30) — Option 3 from the-designer's
   review: rather than repeating the footer identity line's full
   "Design Director · Richmond, VA · Remote or hybrid" facts up here
   (which would either compete with .contact-headline or force a
   restructure of .footer-inner's grid for information already one
   scroll-inch below), this is a different KIND of line — a personal
   close to the invitation, not a restatement of title/location. Serif
   italic like a testimonial quote (not Oswald caps like every label on
   the site) is deliberate: it should read as a voice, not a fact.
   Color switched to --terra-text (2026-08-05), matching .hero-lead's same
   change — this footer is a shared partial, so it also colors "— Jason"
   on My Story, not just My Work; consistent with one shared component
   rather than a My-Work-only override. */
.contact-signoff {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 300;
  font-size: 0.9375rem;
  color: var(--terra-text);
  margin-top: var(--space-sm);
}

/* Two stacked CTAs (2026-07-22) — "Send me a message" then "View on
   LinkedIn", the latter moved here from the footer's identity strip so
   both ways to reach out sit together next to the invitation copy. */
.contact-ctas {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex-shrink: 0;
}

.contact-cta {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--display);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--terra);
  padding: 1rem 1.8rem;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}

.contact-cta:hover {
  background: var(--terra);
  color: var(--black);
}

/* Neutral variant (2026-08-06) — "View on LinkedIn" and "Read My Resume"
   demoted to this, "Send me a message" keeps the default accent-bordered
   treatment above. Resolves a design-audit finding: once the contact band
   grew to three stacked CTAs (was two), all three read as equally primary
   with no hierarchy. Same accent/neutral split design.md §4 already defines
   and uses elsewhere (.ev-cta.neutral, .pw-secondary, .testi-modal-back) —
   not a new pattern, same padding/metrics, only the color role changes. */
.contact-cta.neutral { border-color: var(--ink); }
.contact-cta.neutral:hover { background: var(--ink); color: var(--white); }
/* Fill is --ink, not --black (2026-08-06 fix) — .contact-band's own
   background IS --white (see §17), which is the page's dark background in
   dark mode, so a --black fill on hover was nearly invisible against it:
   both stayed near-black. --ink naturally inverts per theme (dark in light
   mode, light in dark mode) same as the border already does, so the hover
   fill now has real contrast against the band in both themes — light mode
   looks effectively unchanged (--ink ≈ --black there), dark mode gets an
   actually-visible light fill instead of a near-invisible dark-on-dark one.
   --white stays unpinned here on purpose: it already resolves to the
   opposite polarity of --ink in both themes (page-bg white in light mode,
   page-bg dark in dark mode), so text contrast is automatic — no dark-mode
   override block needed, unlike .ev-cta.neutral's fixed --black/--white
   pair which does need one (that component doesn't share this problem
   since it isn't sitting on a background that itself uses --white). */

@media (max-width: 700px) {
  .contact-inner { flex-direction: column; align-items: flex-start; }
}


/* ── 18. FOOTER ──────────────────────────────────────────────
   Permanent bar visible on all pages: the .contact-band invitation
   leads (see § 17 above), then this identity strip — name/meta in
   the left column slot, links on the right — as a lighter-weight
   "business card" beneath it, separated by its own hairline border.
──────────────────────────────────────────────────────────── */
footer {
  background: var(--white);   /* matches the body's own background/toggle exactly — see §1b's note above §3 */
  padding: 0 var(--side); /* horizontal inset lives here, not on .footer-inner (2026-07-22) —
    matches every other *-inner pattern on the site (.section-inner, .hero-inner, .contact-inner):
    the full-width outer element supplies --side, the max-width:1100px inner div supplies none of
    its own. .footer-inner previously padded itself on TOP of being centered at --max, which pushed
    its content --side further in than .contact-inner's — the name/toggle didn't line up with the
    headline/buttons above them, and the border-top (unpadded, at the wider box edge) spanned past
    where the content actually sat instead of matching it. */
}

.footer-inner {
  max-width: var(--max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: max-content 1fr; /* was var(--col-left) — a fixed 220px, borrowed from
    the case-study label column, that was too narrow for "Design Director · Richmond, VA · Remote
    or hybrid" and wrapped it to two lines. max-content sizes the column to whichever line
    (name or meta) is actually wider, so both render on one line. */
  gap: 0 4rem;
  align-items: center;
  padding: 1.5rem 0;
  border-top: 1px solid var(--rule); /* divider between the contact-band above and this strip */
}

/* Name + role/location/remote-status as one grouped identity unit
   (2026-07-22) — previously .footer-meta sat in .footer-right, wedged
   between the name and the LinkedIn/theme-toggle links, which read as
   a third "action" alongside them rather than a description of the
   name beside it. Tight --space-xs gap, same "label hugs its heading"
   relationship design.md uses for eyebrow → title elsewhere. */
/* One line (2026-07-22, was stacked name/meta) — font styling lives on
   .footer-identity itself so the name and meta spans inside it render at
   one consistent size instead of two slightly different ones on the same
   line. .footer-name/.footer-meta stay as separate spans (not one flat
   string) so either can still be edited or restyled independently later. */
.footer-identity {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink3);
  /* No white-space: nowrap — this fits one line at normal desktop widths
     (the identity grid column is now max-content, see .footer-inner) but
     would force horizontal overflow on narrow mobile screens if forced. */
}

.footer-name {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.footer-right {
  display: flex;
  align-items: center;
  justify-content: space-between; /* copyright anchors to the left edge of
    this column, the toggle to the right — split, not grouped together
    (2026-07-22) — since they're two unrelated things (legal fine print vs.
    a UI control) rather than one cluster */
  gap: 1.25rem;
  flex-wrap: wrap;
}

/* Legal boilerplate, not part of the identity line above — deliberately
   smaller and quieter than both .footer-identity (11px, --ink3) and
   .theme-toggle (11px, --terra-text) so it reads as footer fine print,
   not as content competing with either. --ink3 still passes AA (7:1) at
   any size, so going small here doesn't create a contrast problem the
   way going small with --ink4 would have (design.md: --ink4 is dark-mode-
   surface-only, not safe as small text on a light background). */
.footer-copyright {
  font-family: var(--display);
  font-size: 9px;
  font-weight: 400;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink3);
}

.footer-meta {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.footer-link {
  display: flex;
  align-items: center;
  gap: 6px;
  /* Vertical padding so the tap target clears 40px tall (text alone was only
     ~18px) — symmetric padding doesn't shift the visible text position, and
     .footer-right's align-items:center absorbs the extra box height. */
  padding: 12px 0;
  margin: -12px 0;
}

.footer-link svg {
  fill: var(--accent);
  flex-shrink: 0;
  transition: fill 0.15s;
}

.footer-link:hover svg { fill: var(--ink); }

/* Dark-mode toggle, moved into the footer identity strip (2026-07-21,
   previously an icon-only button in the nav; the LinkedIn link that used to
   share .footer-right with it moved to the contact band 2026-07-22, so the
   toggle is now the sole item here — see .footer-right's justify-content).
   Styled as .footer-link for the same flex layout and 40px tap target every
   other footer control uses; this just adds the button reset and text
   styling that a plain <a> doesn't need, since this is a <button>.
   Icon AND label both name the ACTION a click takes, not the current
   state (moon + "Dark mode" while light mode is active; sun + "Light mode"
   while dark mode is active) — both pairs are keyed directly off
   :root[data-theme="dark"] below, not a separate aria-pressed attribute,
   so there's only one source of truth for which pair shows. No
   aria-label either: the button's accessible name comes from its own
   (currently-visible) text content, so it always matches what's on
   screen — same "mute/unmute"-style pattern as any button whose label
   names the next action rather than a fixed setting name. */
.theme-toggle {
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--display);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--terra-text);
  transition: color 0.15s;
}
.theme-toggle:hover { color: var(--ink); }

.theme-toggle-icon, .theme-toggle-label {
  display: none;   /* both pairs render; only the matching one below is shown */
}

/* Default (light mode active): show the moon + "Dark mode" — click to turn dark mode on */
.theme-toggle-icon-moon  { display: block; }
.theme-toggle-label-moon { display: inline; }

/* Dark mode active: show the sun + "Light mode" — click to turn dark mode off */
:root[data-theme="dark"] .theme-toggle-icon-moon,
:root[data-theme="dark"] .theme-toggle-label-moon { display: none; }
:root[data-theme="dark"] .theme-toggle-icon-sun  { display: block; }
:root[data-theme="dark"] .theme-toggle-label-sun { display: inline; }

hr { border: none; border-top: 1px solid var(--rule); margin: 2.5rem 0; }

@media (max-width: 700px) {
  .footer-inner        { grid-template-columns: 1fr; gap: 0.75rem; }
  /* .footer-right's gap no longer needs a mobile override — its base value
     is already 1.25rem (2026-07-22, when .footer-copyright was added). */
}


/* ── 18. CASE STUDY FIGURES ──────────────────────────────────
   Real images inside the full case study pages.
──────────────────────────────────────────────────────────── */
.case-figure {
  margin: 2rem 0;
  padding: 0;
  border: 1px solid var(--rule);
  line-height: 0;
}
.case-figure img {
  width: 100%;
  height: auto;
  display: block;
}


/* ── 19. MATERIAL DESIGN ICONS ───────────────────────────────
   Inline SVG icons sourced from google/material-design-icons.
   Each icon uses fill="currentColor" so CSS drives the color.

   HOW TO USE:
     <svg class="md-icon" viewBox="0 0 24 24"><path d="…"/></svg>

   COLOR VARIANTS:
     .accent  → site accent color (var(--terra))
     .muted   → secondary gray (var(--ink3))
     .white   → white (for dark backgrounds)
     (default → inherits color from parent element)

   COMMON ICON PATHS (copy the path value into a new <svg>):
     add    "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"
     close  "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"

   To find more paths: github.com/google/material-design-icons
   → navigate to the icon folder → [icon-name]/materialicons/24px.svg
──────────────────────────────────────────────────────────── */




/* ── 20. RESPONSIVE / MOBILE ─────────────────────────────────
   Below 700px: single-column everything, stacked stats.
──────────────────────────────────────────────────────────── */
@media (max-width: 700px) {
  :root { --col-left: 120px; }

  /* !important: .stats-grid still sets grid-template-columns inline per case
     (e.g. style="grid-template-columns:repeat(4,1fr)" on APEX, repeat(3,1fr)
     on EASE) to vary its column count. An inline style always beats a plain
     stylesheet rule regardless of media query, so without !important it never
     collapses on mobile — cards squeezed into a phone-width row, overflowing
     and dragging the whole page into horizontal scroll. .before-after no
     longer has an inline-style cause of its own (EASE's 3-way comparison now
     uses the .compare-3 class, see §before-after above) but keeps the same
     !important here too, both for defense-in-depth and so the two grids stay
     governed by one consistent mobile rule. */
  .before-after    { grid-template-columns: 1fr !important; }
  .stats-grid      { grid-template-columns: 1fr !important; }
  .flow            { grid-template-columns: 1fr 1fr; }
  .flow-step::after { display: none; }

  /* Full-spine layout stacks: main content first, then the sidebar
     (Extra Case Info / colon-lists) AFTER it, per DOM order. */
  .case-row        { grid-template-columns: 1fr; }
  .case-aside      { margin-top: var(--space-lg); }

  /* .stats-row collapses in step with .case-row above — same breakpoint,
     same reserved-sidebar-track logic — so .stats-block stays exactly as
     wide as .case-main once the sidebar stacks below instead of beside it. */
  .stats-row       { grid-template-columns: 1fr; }

  /* ── Mobile type scale ──
     Merriweather Light reads well at 15px on small screens.
     Drop body sizes slightly and tighten spacing so paragraphs
     don't feel overwhelming on a phone.
  ── */
  .body-serif  { font-size: 1rem; line-height: 1.8; margin-bottom: 1rem; }
  /* Matches .body-serif's mobile size/line-height exactly — previously dropped
     to 0.875rem, a step smaller than the paragraph copy right above Evidence,
     which read as the closing section shrinking rather than landing the case. */
  .bullet-text { font-size: 1rem; line-height: 1.8; }
}

/* ── NAV: narrow phones (≤480px) ────────────────────────────
   Nav is just two links (My Work, My Story) + the logo mark, but at
   375-430px wide even that can crowd the bar. Shrink button padding
   and text so both links + logo fit comfortably on one line. */
@media (max-width: 480px) {
  /* Squeeze the logo slightly */
  .nav-logo svg { height: 26px; }

  /* Tighter buttons — less padding, smaller text */
  .nav-links a {
    font-size: 10px;
    letter-spacing: 0.06em;
    padding: 8px 7px;
  }

  /* Keep the underline indicator aligned with the tighter padding */
  .nav-links a::after {
    left: 7px;
    right: 7px;
  }

  /* The spine offset must match the link's padding (now 7px, not 16px) so the
     first nav label stays on the content spine — see §5 Alignment. */
  .nav-links { margin-left: -7px; }

  /* Prevent any single nav item from wrapping to a second line */
  .nav-links {
    flex-wrap: nowrap;
  }

  /* Reduce side padding so content doesn't clip on narrow screens */
  :root { --side: 1rem; }

  /* On phones the whole rhythm steps down one token (--space-xl → --space-lg)
     rather than switching to different one-off values — same beat, smaller. */
  .section      { padding: var(--space-lg) var(--side); }
  .hero-band    { padding: var(--space-lg) var(--side); }
  .contact-band { padding: var(--space-lg) var(--side); }

  /* Reduce case-row internal padding — stacks with section padding */
  .case-row   { padding: 0.75rem 0; }

  /* Scale display headlines down on narrow screens.
     clamp minimum of 2.5rem (40px) is too large for a phone —
     "I GOT INTO DESIGN TO HELP PEOPLE" becomes a wall of text. */
  .display-xl { font-size: clamp(1.75rem, 7vw, 4.5rem); }
  .display-lg { font-size: clamp(1.4rem, 5.5vw, 3rem); }

  /* Stats grid: stack to single column on mobile so numbers
     don't get squeezed into tiny columns or run off-screen */
  .stats-grid { grid-template-columns: 1fr !important; }
  .stats-block { padding: 1.5rem !important; }

  /* Process flow diagram → single column on narrow phones */
  .flow { grid-template-columns: 1fr !important; }
}


/* ── 21. ANIMATIONS ──────────────────────────────────────────
   Elements tagged .anim (the hero name / eyebrow / bio / button)
   fade up on page load. Staggered delay for a sequential feel.
──────────────────────────────────────────────────────────── */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.anim                   { animation: fadeUp 0.4s ease both; }
.hero-content .anim:nth-child(1) { animation-delay: 0.07s; }
.hero-content .anim:nth-child(2) { animation-delay: 0.14s; }
.hero-content .anim:nth-child(3) { animation-delay: 0.21s; }


/* ── 22. PASSWORD GATE ───────────────────────────────────────
   Styles for the case-studies.html password prompt (the .pw-box
   card and its input/button/error bits). The prompt sits inside
   .cs-gate (see section 4) rather than a full-screen overlay, so
   the nav stays visible and usable while locked.
──────────────────────────────────────────────────────────── */
.pw-box {
  background: var(--white);
  max-width: 480px;
  width: 100%;
  padding: 3rem;
  position: relative;
}

.pw-eyebrow {
  font-family: var(--display);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--terra-text); /* was raw --terra (2026-08-06 fix) — same AA fix as
    .case-card-eyebrow above; this is a "light island" component (§2b) but the
    color pin there only re-pins --terra-lt for dark mode, not raw --terra usage */
  margin-bottom: 0.5rem;
}

.pw-headline {
  font-family: var(--display);
  font-size: 1.75rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--black);
  line-height: 1.1;
  margin-bottom: 0.875rem;
}

.pw-sub {
  font-family: var(--serif);
  font-size: 0.875rem;
  font-weight: 300;
  color: var(--ink2);
  line-height: 1.7;
  margin-bottom: 1.5rem;
}
.pw-sub a {
  color: var(--terra);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.pw-sub a:hover { color: var(--ink); }

.pw-input-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.pw-input {
  width: 100%;
  border: 1px solid var(--rule);
  border-radius: 0;
  padding: 0.75rem 2.75rem 0.75rem 1rem; /* right padding for eye button */
  font-family: var(--serif);
  font-size: 1rem;
  color: var(--black);
  background: var(--off);
  outline: none;
  box-sizing: border-box;
  transition: border-color 0.15s;
}

.pw-input:focus { border-color: var(--terra); }

/* 44×44 tap target, right-flush with the input (the input reserves 2.75rem/
   44px of right padding for exactly this). The 20px icon is flex-centered
   inside, landing within ~1px of its old visual position (previously right:
   0.75rem with no padding, i.e. icon-only ~20×20 hit box) — the input's own
   height (~49.6px) comfortably fits the taller button without overflow. */
.pw-eye {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink3);
  transition: color 0.15s;
}

.pw-eye:hover { color: var(--black); }

.pw-eye-icon {
  width: 20px;
  height: 20px;
  fill: currentColor;
  display: block;
}

.pw-error {
  font-family: var(--display);
  font-size: 0.8125rem;
  color: #c0392b;
  margin-top: 0.5rem;
  display: none;
}

.pw-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

.pw-submit {
  background: var(--black);
  color: var(--white);
  border: none;
  font-family: var(--display);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.75rem 1.5rem;
  cursor: pointer;
  transition: background 0.15s;
}

/* color goes to --black on hover too — a pre-existing gap found while
   auditing every --white/--terra pairing for dark mode: without this, hover
   left white text sitting on a solid accent fill, which fails AA on amber/
   terracotta (design.md §2/§7 hard rule). Unrelated to dark mode itself,
   fixed here since it's the same rule this whole pass exists to protect. */
.pw-submit:hover { background: var(--terra); color: var(--black); }

/* Outline sibling of .pw-submit — same type metrics/height, but the
   1px border is offset out of the padding (calc − 1px) so both buttons
   render exactly the same height sitting side by side. */
.pw-secondary {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--ink);
  font-family: var(--display);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-align: center; /* match .pw-submit (a <button>, which centers by UA default) — invisible until the label wraps to two lines on narrow phones, where "GO"/"BACK" would otherwise hug the left edge instead of centering like the sibling button */
  padding: calc(0.75rem - 1px) calc(1.5rem - 1px);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  line-height: normal; /* match .pw-submit (a <button>, which defaults to normal) so heights are identical — the <a> would otherwise inherit body's 1.6 and render ~1px taller */
  transition: background 0.15s, color 0.15s;
}

.pw-secondary:hover { background: var(--ink); color: var(--white); }

/* Shake animation for wrong password */
@keyframes pwShake {
  0%, 100% { transform: translateX(0); }
  20%       { transform: translateX(-6px); }
  40%       { transform: translateX(6px); }
  60%       { transform: translateX(-4px); }
  80%       { transform: translateX(4px); }
}

.pw-shake { animation: pwShake 0.4s ease; }


/* ── SCREEN-READER-ONLY TEXT ─────────────────────────────────
   Used for headings that give screen reader users page/section
   structure (e.g. "Selected work") without showing them visually.
   Content stays in the accessibility tree; sighted layout is
   unaffected. Don't use `display:none` for this — that removes
   the element from the accessibility tree too.
──────────────────────────────────────────────────────────── */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}


/* ── SKIP NAVIGATION ─────────────────────────────────────────
   WCAG 2.1 AA: allows keyboard users to jump past the nav bar.
   Visually hidden until focused via Tab key.
──────────────────────────────────────────────────────────── */
.skip-nav {
  position: absolute;
  top: -100%;
  left: 0;
  background: var(--terra);
  /* --black, not --white — a pre-existing gap found while auditing every
     --white/--terra pairing for dark mode: white text on a solid accent
     fill fails AA on amber/terracotta (design.md §2/§7 hard rule). It also
     would have been the one place left theme-DEPENDENT by the dark-mode
     token remap (right in dark mode by accident, still wrong in light) had
     it stayed --white, so fixing it outright avoids that inconsistency. */
  color: var(--black);
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 12px 20px;   /* was 10px — same 40px-minimum safety-margin bump as .read-btn */
  text-decoration: none;
  z-index: 999;
  transition: top 0.1s;
}

.skip-nav:focus { top: 0; }


/* ── FOCUS STYLES ────────────────────────────────────────────
   WCAG 2.1 AA: visible focus ring for keyboard navigation.
   :focus-visible targets keyboard focus only, not mouse clicks.
──────────────────────────────────────────────────────────── */
*:focus-visible {
  outline: 3px solid var(--terra);
  outline-offset: 2px;
}

/* Nav buttons: use the existing underline indicator instead of outline */
.nav-links a:focus-visible {
  outline: none;
  color: var(--ink);
}

.nav-links a:focus-visible::after { transform: scaleX(1); }

/* Nav logo: subtle ring on the svg, not the whole div */
.nav-logo:focus-visible {
  outline: 3px solid var(--terra);
  outline-offset: 3px;
}
