/* ==========================================================================
   gideonong.me — style.css

   Palette library — see CLAUDE.md for full documentation of all four
   palettes. Palette B (Warm Neutral) is active below. To switch palettes,
   swap which :root block is commented out (A and C are provided as
   commented-out alternatives).
   ========================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,500;0,600;1,400&family=DM+Sans:wght@400;500;600&display=swap');

/* --------------------------------------------------------------------------
   PALETTE A — Oenothera (uncomment :root block, comment out Palette B)

:root {
  --bg:           #FAFAF8;
  --bg-section:   #EDE7E4;
  --nav-bg:       #FFFFFF;
  --ink:          #1C1810;
  --muted:        #6E6560;
  --bronze:       #714A14;
  --bronze-mid:   #9B7340;
  --rose:         #B87A7A;
  --rose-light:   #E8D0D4;
  --border:       #DDD5C8;
  --footer-bg:    #1C1510;
  --footer-text:  #D4C9BB;
  --search-bg:    #E1D7CF;

  --font-serif: 'Lora', Georgia, 'Times New Roman', serif;
  --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   PALETTE B — Warm Neutral (ACTIVE)
   -------------------------------------------------------------------------- */
:root {
  --bg:           #FAFAF8;
  --bg-section:   #EDE6D5;
  --nav-bg:       #FFFFFF;
  --ink:          #1A1A17;
  --muted:        #6B6963;
  --bronze:       #6B4F1A;
  --bronze-mid:   #9B7340;
  --rose:         #B87A7A;
  --rose-light:   #E8D0D4;
  --border:       #DDD5C8;
  --footer-bg:    #1A1510;
  --footer-text:  #D4C9BB;
  --search-bg:    #E0D7C2;   /* projects.html search band: bronze ≈10% over --bg-section */

  --font-serif: 'Lora', Georgia, 'Times New Roman', serif;
  --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* --------------------------------------------------------------------------
   PALETTE C — Bluebonnet (uncomment :root block, comment out Palette A)

:root {
  --bg:           #F5F7FC;
  --bg-section:   #EBF0FA;
  --nav-bg:       #FFFFFF;
  --ink:          #1A1F2E;
  --muted:        #5A6880;
  --bronze:       #2E4D7B;
  --bronze-mid:   #5A7AAB;
  --rose:         #B87A7A;
  --rose-light:   #E8D0D4;
  --border:       #D4D8E4;
  --footer-bg:    #1A1F2E;
  --footer-text:  #C8D4E4;
  --search-bg:    #D8E0ED;

  --font-serif: 'Lora', Georgia, 'Times New Roman', serif;
  --font-sans: 'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100vh;
  width: 100%;
  max-width: 100%;
  overflow-x: hidden;
}

img,
picture,
video,
svg {
  display: block;
  max-width: 100%;
}

ul,
ol {
  list-style: none;
}

a {
  text-decoration: none;
  color: inherit;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  cursor: pointer;
  background: none;
  border: none;
}

/* The hidden attribute must win over any display rule below (e.g. .btn-resume
   is inline-block). js/main.js hides document buttons whose type has no file. */
[hidden] {
  display: none !important;
}

/* --------------------------------------------------------------------------
   Base typography
   -------------------------------------------------------------------------- */
body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background-color: var(--bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-serif);
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
}

p {
  color: var(--ink);
}

a {
  transition: color 150ms ease;
}

a:hover {
  color: var(--bronze);
}

/* ==========================================================================
   index.html — page layout
   ========================================================================== */

/* --------------------------------------------------------------------------
   Navbar (always opaque white, sticky)
   -------------------------------------------------------------------------- */
.navbar {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--nav-bg);
  border-bottom: 0.5px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 clamp(16px, 4vw, 36px);
  height: 56px;
  width: 100%;
}

.navbar .logo {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 17px;
  color: var(--ink);
}

.nav-right {
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 22px);
  flex-wrap: wrap;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(14px, 3vw, 22px);
}

.nav-links a {
  font-size: clamp(13px, 0.5vw + 11px, 15.5px);
  color: #4A4945;
}

.nav-links a:hover {
  color: var(--ink);
}

.lang-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 3px;
  overflow: hidden;
  font-size: 12px;
  font-weight: 500;
  padding: 0;
}

.lang-toggle .lang-btn {
  padding: 3px 10px;
  color: var(--muted);
  pointer-events: none;
}

.lang-toggle .lang-btn.is-active {
  background: var(--ink);
  color: var(--bg);
}

/* --------------------------------------------------------------------------
   Under-development banner
   -------------------------------------------------------------------------- */
.dev-banner {
  display: none; /* hidden for now — delete this line to show the banner again */
  background: #8B3A2A;
  color: #FAFAF8;
  font-size: clamp(14px, 1.5vw + 5px, 16px);
  text-align: center;
  padding: 7px clamp(16px, 4vw, 36px);
  letter-spacing: 0.02em;
}

/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.hero {
  background: var(--bg);
  padding: clamp(36px, 6vw, 56px) clamp(16px, 4vw, 36px) 44px;
  max-width: 800px;
  width: 100%;
}

.hero-name {
  font-size: clamp(50px, 6vw, 64px);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.01em;
  margin: 0 0 14px;
}

.hero-rule {
  width: 40px;
  height: 2px;
  background: var(--bronze);
  margin: 0 0 16px;
}

.hero-desc-group {
  display: flex;
  flex-direction: column;
}

.hero-desc {
  font-size: clamp(16px, 2.1vw + 4.8px, 20px);
  color: var(--ink);
  line-height: 1.65;
  margin: 0 0 7px;
}

.hero-desc.is-active {
  order: 1;
  margin: 0 0 7px;
}

.hero-desc.is-inactive {
  order: 2;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(14px, 2.1vw + 2.8px, 18px);
  color: var(--muted);
  line-height: 1.55;
  margin: 0 0 24px;
}

.iel-chip {
  text-decoration: none;
  background: rgba(0, 0, 0, 0.06);
  border: none;
  padding: 1px 5px;
  display: inline;
  border-radius: 3px;
  color: inherit;
  font-size: inherit;
}

.iel-chip:hover {
  background: rgba(0, 0, 0, 0.10);
}

/* --------------------------------------------------------------------------
   Social chips + resume button
   -------------------------------------------------------------------------- */
.chip-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 18px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: clamp(14px, 1.5vw + 6px, 17px);
  color: #4A4945;
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 5px 10px;
  background: var(--bg);
}

.chip:hover {
  color: var(--ink);
  border-color: var(--bronze-mid);
}

.chip svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

.chip-rose {
  color: var(--rose);
  border-color: var(--rose-light);
  background: var(--bg);
}

.chip-rose:hover {
  color: var(--rose);
  border-color: var(--rose);
}

.chip-linkedin {
  color: #0A66C2;
  border-color: #0A66C2;
}

.chip-linkedin:hover {
  color: #004182;
  border-color: #004182;
}

.btn-resume {
  display: inline-block;
  padding: 10px 22px;
  border: 1.5px solid var(--bronze);
  color: var(--bronze);
  font-size: clamp(15px, 2.1vw + 3.8px, 19px);
  font-weight: 500;
  border-radius: 2px;
  letter-spacing: 0.02em;
}

.btn-resume:hover {
  color: var(--ink);
  border-color: var(--ink);
}

/* --------------------------------------------------------------------------
   Featured work
   -------------------------------------------------------------------------- */
.featured-work {
  background: var(--bg-section);
  border-top: 1px solid var(--border);
  padding: 36px clamp(16px, 4vw, 36px) 40px;
  width: 100%;
}

/* Two heading kinds (2026-09-27):
   .list-heading    — the visible top of a LIST of entries: index.html "Featured
                      work", projects.html "Featured projects" / "All projects".
                      Real <h2>s, clearly larger than body text, left on the
                      column edge, no rule or decoration.
   .section-heading — a small panel / block label (about.html "Viewing settings"
                      and the hidden About blocks). Deliberately label-sized. */
.list-heading {
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 1.2rem + 1.2vw, 2rem);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0 0 clamp(20px, 3vw, 28px);
}

.section-heading,
h2.section-heading {
  font-family: var(--font-serif);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
  margin: 0 0 18px;
}

/* The entries are .featured-entry articles built by js/home.js with the same
   markup as projects.html §2 (styles under "§2 — Featured projects" below).
   The column is centered at the same content width as the projects.html
   index (1000px, 1120px at 1440+), heading aligned to it (2026-09-27). */
.featured-work .list-heading,
.featured-work .featured-list {
  max-width: 1000px;
  margin-inline: auto;
}

/* Wide-screen option (js/site-config.js → homeFeaturedSideBySide: true):
   js/home.js adds .featured-list--side-by-side and alternates imageLeft /
   imageRight. The two-column presets apply at 1200px and wider only; below
   that the entries stack, image above text. A collage entry keeps its grid
   in the image column (.featured-entry--side-left / --side-right). */
@media (max-width: 1199px) {
  .featured-list--side-by-side .featured-entry--image-left,
  .featured-list--side-by-side .featured-entry--image-right {
    grid-template-columns: minmax(0, 1fr);
  }

  .featured-list--side-by-side .featured-entry--image-left .featured-entry-media,
  .featured-list--side-by-side .featured-entry--image-right .featured-entry-media {
    grid-column: 1;
    grid-row: 1;
  }

  .featured-list--side-by-side .featured-entry--image-left .featured-entry-body,
  .featured-list--side-by-side .featured-entry--image-right .featured-entry-body {
    grid-column: 1;
    grid-row: 2;
  }
}

@media (min-width: 1200px) {
  .featured-list--side-by-side .featured-entry--side-left {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  }
  .featured-list--side-by-side .featured-entry--side-left .featured-entry-media { grid-column: 1; grid-row: 1; }
  .featured-list--side-by-side .featured-entry--side-left .featured-entry-body  { grid-column: 2; grid-row: 1; }

  .featured-list--side-by-side .featured-entry--side-right {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  }
  .featured-list--side-by-side .featured-entry--side-right .featured-entry-media { grid-column: 2; grid-row: 1; }
  .featured-list--side-by-side .featured-entry--side-right .featured-entry-body  { grid-column: 1; grid-row: 1; }
}

/* --------------------------------------------------------------------------
   Stub pages (about / experience / projects — bare-bones build)
   -------------------------------------------------------------------------- */
.stub-section {
  background: var(--bg);
  padding: clamp(56px, 12vw, 96px) clamp(16px, 4vw, 36px);
  width: 100%;
  text-align: center;
}

.stub-heading {
  font-size: clamp(28px, 5vw, 38px);
  font-weight: 600;
  margin: 0 0 14px;
}

.stub-body {
  font-size: 15px;
  color: var(--muted);
  line-height: 1.6;
  max-width: 480px;
  margin: 0 auto;
}

/* --------------------------------------------------------------------------
   Image links (2026-09-27) — the ONE sanctioned motion exception.

   An image with a destination (index thumbnail / featured image / collage →
   its sub-page; band image → its `imageLink`) is wrapped in <a class="image-link">
   by js/data-helpers.js / js/experience.js. The link is a clipped frame; the
   image (or the collage grid) scales to 1.03 inside it on hover and on
   :focus-visible, 250ms ease, pointer cursor. Images with no destination are
   never wrapped, so they never move. Off under prefers-reduced-motion.
   -------------------------------------------------------------------------- */
.image-link {
  display: block;
  overflow: hidden;
  border-radius: 3px;
  cursor: pointer;
  color: inherit;
}

.image-link > img,
.image-link > .featured-collage {
  transition: transform 250ms ease;
  will-change: transform;
}

.image-link:hover > img,
.image-link:hover > .featured-collage,
.image-link:focus-visible > img,
.image-link:focus-visible > .featured-collage {
  transform: scale(1.03);
}

.image-link:focus-visible {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 2px;
}

.project-thumb .image-link {
  border-radius: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .image-link > img,
  .image-link > .featured-collage {
    transition: none;
  }

  .image-link:hover > img,
  .image-link:hover > .featured-collage,
  .image-link:focus-visible > img,
  .image-link:focus-visible > .featured-collage {
    transform: none;
  }
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--footer-bg);
  color: var(--footer-text);
  padding: 24px clamp(16px, 4vw, 36px);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  width: 100%;
}

.footer-links {
  display: flex;
  gap: 18px;
  flex-wrap: wrap;
  align-items: center;
}

.footer-links a {
  font-size: 12px;
  color: var(--footer-text);
}

.footer-links a:hover {
  color: var(--bronze-mid);
}

.footer-links svg {
  width: 14px;
  height: 14px;
  vertical-align: -2px;
}

.btn-resume-footer {
  display: inline-block;
  padding: 8px 18px;
  border: 1px solid var(--bronze-mid);
  color: var(--footer-text);
  font-size: 12px;
  font-weight: 500;
  border-radius: 2px;
}

.btn-resume-footer:hover {
  color: var(--bg);
  border-color: var(--footer-text);
}

.footer-copy {
  font-size: 11px;
  color: var(--footer-text);
  opacity: 0.75;
  width: 100%;
  order: 10;
}

/* ==========================================================================
   Mobile nav — hamburger + dropdown (≤767px)
   ========================================================================== */

/* Hamburger button — hidden on desktop */
.hamburger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--ink);
  flex-shrink: 0;
  padding: 0;
}

.hamburger svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* Hamburger → X animation: SVG lines rotate into a cross when .is-open */
.hamburger svg line {
  transition: transform 220ms ease, opacity 180ms ease;
  transform-box: fill-box;
  transform-origin: center;
}

.hamburger.is-open .ham-top {
  transform: translateY(6px) rotate(45deg);
}

.hamburger.is-open .ham-mid {
  opacity: 0;
  transform: scaleX(0);
}

.hamburger.is-open .ham-bot {
  transform: translateY(-6px) rotate(-45deg);
}

/* Nav dropdown — animates open with max-height unroll */
.nav-dropdown {
  position: fixed;
  top: 56px; /* matches .navbar height */
  left: 0;
  right: 0;
  z-index: 99;
  background: var(--nav-bg);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
  max-height: 0;
  overflow: hidden;
  visibility: hidden;
  opacity: 0;
  /* When closing: delay visibility until after animation */
  transition: max-height 220ms ease, opacity 160ms ease, visibility 0ms linear 220ms;
}

.nav-dropdown.is-open {
  max-height: 240px; /* enough for 3 links */
  visibility: visible;
  opacity: 1;
  /* When opening: visibility becomes visible immediately */
  transition: max-height 220ms ease, opacity 160ms ease, visibility 0ms linear 0ms;
}

.nav-dropdown a {
  display: block;
  padding: 13px clamp(16px, 4vw, 36px);
  font-size: 15px;
  color: var(--ink);
  border-bottom: 1px solid var(--border);
}

.nav-dropdown a:last-child {
  border-bottom: none;
}

.nav-dropdown a:hover {
  color: var(--bronze);
}

@media (max-width: 767px) {
  .nav-links {
    display: none; /* replaced by the dropdown */
  }

  .hamburger {
    display: flex;
  }

  /* clamp() minimums already set phone-size floors; padding bump for tap target */
  .btn-resume {
    padding: 11px 24px;
  }
}

/* ==========================================================================
   Wide-screen text scaling (1440px+)
   Keeps layout and proportions identical — just larger text throughout.
   ========================================================================== */
@media (min-width: 1440px) {
  body {
    font-size: 17px;
  }

  .navbar .logo {
    font-size: 18px;
  }

  .dev-banner {
    font-size: 13px;
  }

  /* Second scaling phase for the hero name on very large screens */
  .hero-name {
    font-size: clamp(46px, 3.5vw, 72px);
  }

  .section-heading {
    font-size: 16px;
  }
}

/* ==========================================================================
   Dark mode — Palette B (Warm Neutral) translated to a dark warm scheme.
   Light mode is the primary design target; dark mode preserves readability
   with the same warm feel. Palette tones stay consistent, just inverted.
   ========================================================================== */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:           #1C1914;
    --bg-section:   #231F1A;
    --nav-bg:       #1C1914;
    --ink:          #EDE8DF;
    --muted:        #9A948D;
    --bronze:       #C08840;
    --bronze-mid:   #D4A060;
    --rose:         #C89090;
    --rose-light:   #4A3535;
    --border:       #3C3530;
    --footer-bg:    #0F0D0B;
    --footer-text:  #C8BDB0;
    --search-bg:    #332A1E;
  }

  /* IEL chip: light overlay on dark background */
  :root:not([data-theme="light"]) .iel-chip {
    background: rgba(255, 255, 255, 0.10);
  }

  :root:not([data-theme="light"]) .iel-chip:hover {
    background: rgba(255, 255, 255, 0.16);
  }

  /* Override hardcoded non-variable colors that won't adapt automatically */
  :root:not([data-theme="light"]) .nav-links a {
    color: var(--muted);
  }

  :root:not([data-theme="light"]) .nav-links a:hover {
    color: var(--ink);
  }

  :root:not([data-theme="light"]) .chip {
    color: var(--ink);
  }
}

/* --------------------------------------------------------------------------
   Manual dark-mode override (about.html "Viewing settings" panel only).
   Sets data-theme="dark" on <html>, overriding prefers-color-scheme in
   either direction. Duplicates the block above since plain CSS has no
   mixins and this repo intentionally has no build step.
   -------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  --bg:           #1C1914;
  --bg-section:   #231F1A;
  --nav-bg:       #1C1914;
  --ink:          #EDE8DF;
  --muted:        #9A948D;
  --bronze:       #C08840;
  --bronze-mid:   #D4A060;
  --rose:         #C89090;
  --rose-light:   #4A3535;
  --border:       #3C3530;
  --footer-bg:    #0F0D0B;
  --footer-text:  #C8BDB0;
  --search-bg:    #332A1E;
}

:root[data-theme="dark"] .iel-chip {
  background: rgba(255, 255, 255, 0.10);
}

:root[data-theme="dark"] .iel-chip:hover {
  background: rgba(255, 255, 255, 0.16);
}

:root[data-theme="dark"] .nav-links a {
  color: var(--muted);
}

:root[data-theme="dark"] .nav-links a:hover {
  color: var(--ink);
}

:root[data-theme="dark"] .chip {
  color: var(--ink);
}

/* ==========================================================================
   about.html — page layout
   ========================================================================== */

/* --------------------------------------------------------------------------
   §1 + §2 — About intro
   Full-width background split (like hero -> featured-work on index.html),
   a vertically-centred header, and a single portrait+buttons column that
   travels across the split while the resume button morphs into CV + Transcript.

   LAYOUT TUNABLES live on .about-intro as custom properties:
     --header-h   height of the §1 header band. Lower = bigger peek of §2.
     --btn-gap    gap between the CV and Transcript buttons.
     --photo-gap  gap between the portrait and the button area.
   (Spike shape + travel-speed tunables live at the top of about.js.)
   -------------------------------------------------------------------------- */
.about-intro{
  --header-h: 84vh;        /* header band height; lower = more of "Who am I?" peeks */
  --btn-gap: 16px;         /* button-to-button gap */
  --photo-gap: 20px;       /* portrait-to-buttons gap */
  position: relative;
  background: var(--bg-section);                 /* cream base = the §2 band */
}
/* full-width §1 header band, painted behind the content, crisp bottom edge */
.about-intro::before{
  content:""; position:absolute; left:0; right:0; top:0;
  height: var(--header-h); background: var(--bg); pointer-events:none;
}
.about-intro::after{
  content:""; position:absolute; left:0; right:0; top: var(--header-h);
  height:1px; background: var(--border); pointer-events:none;
}

.about-intro-grid{
  position: relative; z-index:1;
  display:grid; grid-template-columns: 3fr 2fr;
  gap: clamp(24px, 4vw, 56px);
  max-width:1100px; width:100%; margin:0 auto;
  padding:0 clamp(16px, 4vw, 36px);
  align-items:start;
}
.about-intro-left{ min-width:0; }

/* §1 header: name + identifiers, vertically centred within the header band */
.section-about-hero{
  min-height: var(--header-h);
  display:flex; flex-direction:column; justify-content:center;
  box-sizing:border-box;
}
.about-identifiers p{
  font-family: var(--font-serif); color: var(--bronze);
  font-size: clamp(15px, 1vw + 8px, 19px); margin:10px 0 0;
}
.about-identifiers [data-lang-variant]{ display:none; }
html[lang="en"] .about-identifiers [data-lang-variant="en"]{ display:block; }
html[lang="es"] .about-identifiers [data-lang-variant="es"]{ display:block; }

/* §2 "Who am I?" (background comes from the .about-intro cream base) */
.section-about-whoami{
  min-height: 84vh;                       /* ~header height, like §1 */
  display:flex; flex-direction:column; justify-content:center;
  padding: clamp(40px, 7vh, 80px) 0;
  box-sizing:border-box;
}
.whoami-title{
  font-family: var(--font-serif); color: var(--ink);
  font-size: clamp(40px, 6.5vw, 82px);    /* much larger title */
  font-weight:600; line-height:1.04; letter-spacing:-0.015em;
  margin:0 0 clamp(18px, 3.5vh, 32px);
}
.whoami-content{ max-width:760px; }       /* wider text block */
.whoami-content p{ font-size: clamp(15px, 0.6vw + 12px, 18px); line-height:1.75; margin:0 0 18px; }

/* Portrait + buttons column. about.js sets its transform per frame (the travel);
   the value below is only a no-JS fallback. */
.about-photo-col{
  position: relative; z-index:1;
  display:flex; flex-direction:column; gap: var(--photo-gap);
  will-change: transform;
}
.about-headshot{
  width:100%; aspect-ratio:3/4; object-fit:cover;
  border-radius:4px; background-color: var(--bg-section); display:block;
}

/* Button area: height is set by about.js to (2*button + --btn-gap). The resume
   button sits in the top slot, the CV/Transcript group fills the box; about.js
   cross-hands them with the injected .morph-svg shape so nothing ever overlaps. */
.about-buttons{ position:relative; min-height: 112px; }
.about-buttons > .btn-resume{ position:absolute; left:0; right:0; top:0; text-align:center; }
.btn-resume-group{
  position:absolute; left:0; right:0; bottom:0;
  display:flex; flex-direction:column; gap: var(--btn-gap);
  opacity:0; pointer-events:none;
}
.btn-resume-group .btn-resume{ text-align:center; }

/* the deforming loop (radial-fan starburst), injected by about.js: two visible
   lobes (.morph-lobe) plus an invisible fat-stroked "hit" twin over each
   (.morph-hit — stroke width set inline from HIT_PAD in about.js). The svg
   itself ignores the pointer; only the hit pads catch clicks. */
.morph-svg{ position:absolute; left:0; top:0; width:100%; height:100%; overflow:visible; pointer-events:none; opacity:0; }
.morph-svg path{ fill: var(--bronze); fill-opacity:0; stroke: var(--bronze); stroke-width:1.6; stroke-linejoin:round; }
.morph-svg .morph-lobe{ transition: stroke-width 150ms ease; }
.morph-svg .morph-lobe.is-hover{ stroke-width:2.4; }
.morph-svg .morph-hit{ fill:transparent; stroke:transparent; stroke-linejoin:round; pointer-events:all; cursor:pointer; }

/* --------------------------------------------------------------------------
   §3 — "What have I been reading?"
   Pins full-screen ("locks in"). Book covers live on a layer BEHIND the page and
   are revealed only through a rectangular hole (a page-coloured mask). Covers
   slide vertically behind the hole as you scroll; title top-left; the active
   book's title + description sit to the RIGHT of the hole.

   TUNABLES on .reading-sticky:
     --hole-h / --hole-w   window size (cover aspect ~2:3)
     --hole-left           window's distance from the LEFT edge
   Scroll pace / velocity feel = READ_STEP_VH / VEL_BOOST in about.js.
   -------------------------------------------------------------------------- */
.section-reading{ position:relative; background:var(--bg); width:100%; }

.reading-sticky{
  --hole-h: 60vh;
  --hole-w: calc(var(--hole-h) * 2 / 3);
  --hole-left: 9vw;
  --hole-cy: 57%;                          /* window centre (pushed below the title) */
  position: sticky; top:0; height:100vh; overflow:hidden;
}

/* covers strip, on the layer behind everything (JS slides it vertically) */
.reading-strip{
  position:absolute; z-index:0;
  left: var(--hole-left);
  top: calc(var(--hole-cy) - var(--hole-h) / 2);   /* cover 0 centred in the hole at rest */
  width: var(--hole-w);
  will-change: transform;
}
.reading-cover-item{ display:block; }
.reading-cover{
  width:100%; height:var(--hole-h);
  background-size:cover; background-position:center; display:block;
}
.reading-cover-cap{ display:none; }        /* per-cover caption used on mobile cards only */

/* page-coloured mask: box-shadow fills the page AROUND this rectangle -> a hole */
.reading-hole{
  position:absolute; z-index:1; pointer-events:none;
  left: var(--hole-left); top: var(--hole-cy);
  width: var(--hole-w); height: var(--hole-h);
  transform: translateY(-50%);
  box-shadow: 0 0 0 100vmax var(--bg),
              inset 0 0 0 1px var(--border),
              inset 0 20px 44px rgba(0,0,0,0.30);
  border-radius:2px;
}

/* foreground text */
.reading-fg{ position:absolute; z-index:2; inset:0; pointer-events:none; }
.reading-fg > *{ pointer-events:auto; }
.reading-title{
  position:absolute; top: clamp(24px,7vh,72px); left: clamp(16px,4vw,56px);
  margin:0; max-width:46vw;
  font-family:var(--font-serif); color:var(--ink);
  font-size: clamp(34px,5vw,68px); font-weight:600; line-height:1.04; letter-spacing:-0.015em;
}
.book-text{                                /* to the RIGHT of the hole */
  position:absolute; top: var(--hole-cy); transform: translateY(-50%);
  left: calc(var(--hole-left) + var(--hole-w) + clamp(28px,4vw,64px));
  right: clamp(16px,4vw,56px);
  max-width: 440px;
}
.book-title{ font-family:var(--font-serif); font-size: clamp(20px,1.4vw+12px,28px); color:var(--ink); margin:0 0 10px; }
.book-desc{ font-size: clamp(14px,0.5vw+11px,16px); line-height:1.7; color:var(--muted); margin:0; }
.book-title, .book-desc{ transition: opacity .25s ease, transform .25s ease; }
.book-text.is-exiting .book-title, .book-text.is-exiting .book-desc{ opacity:0; transform:translateY(8px); }
.book-text.is-entering .book-title, .book-text.is-entering .book-desc{ opacity:0; transform:translateY(-10px); }

/* --------------------------------------------------------------------------
   §4 — Interview FAQ accordion
   -------------------------------------------------------------------------- */
.section-faq {
  background: var(--bg-section);
  padding: clamp(48px, 8vw, 72px) clamp(16px, 4vw, 36px);
}

.faq-accordion {
  max-width: 760px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.faq-item {
  border-bottom: 1px solid var(--border);
}

.faq-item:last-child {
  border-bottom: none;
}

.faq-question {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 16px 20px;
  font-size: 15px;
  font-weight: 500;
  background: var(--bg);
  color: var(--ink);
}

.faq-question:hover {
  color: var(--bronze);
}

.faq-chevron {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--bronze-mid);
}

.faq-answer {
  max-height: 0;
  overflow: hidden;
  padding: 0 20px;
  background: var(--bg);
  transition: max-height 300ms ease, padding 300ms ease;
}

.faq-item.is-open .faq-answer {
  max-height: 480px;
  padding: 0 20px 18px;
}

.faq-answer p {
  font-size: 14px;
  line-height: 1.65;
  color: var(--muted);
  margin: 0;
}

/* --------------------------------------------------------------------------
   §5 — Statement on AI
   -------------------------------------------------------------------------- */
/* Sits in the same centered 1100px container as §1/§2 (.about-intro-grid), so
   "Statement on AI" left-aligns with "Who am I?" at every width (2026-09-27).
   The text keeps the §2 measure (760px) inside that container. */
.section-ai {
  background: var(--bg);
  padding: clamp(48px, 8vw, 72px) 0;
}

/* Same box as .about-intro-grid: 1100px max INCLUDING the side padding. */
.ai-content {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 clamp(16px, 4vw, 36px);
}

.ai-heading,
.ai-content p {
  max-width: 760px;
}

.ai-heading {
  font-size: clamp(24px, 3vw, 30px);
  margin: 0 0 20px;
}

.ai-content p {
  font-size: 15px;
  line-height: 1.75;
  margin: 0 0 16px;
}

/* about.html §1/§2/§5 — wide screens (1440px+). Placed after the base rules
   above (not in the earlier site-wide "wide-screen text scaling" block) since
   equal-specificity rules resolve by source order — a copy declared before the
   base .about-intro-grid/.ai-content/.section-about-whoami rules gets silently
   overridden by them. On large monitors the 1100px box leaves "Who am I?"
   feeling tight with unused space outside it; widen the box, gap and section
   height on big screens only. .ai-content must grow by the same max-width so
   "Statement on AI" keeps left-aligning with "Who am I?". */
@media (min-width: 1440px) {
  .about-intro-grid,
  .ai-content {
    max-width: 1250px;
  }

  .about-intro-grid {
    gap: 110px;
  }

  .section-about-whoami {
    min-height: 105vh;
    padding: clamp(64px, 10vh, 110px) 0;
  }
}

/* --------------------------------------------------------------------------
   §6 — Viewing settings + Interesting sites
   -------------------------------------------------------------------------- */
/* auto-fit, not a fixed two columns: while the interesting-sites block is
   hidden (about.html, `hidden` attribute) the settings panel takes the row. */
.section-settings {
  background: var(--bg-section);
  padding: clamp(48px, 8vw, 72px) clamp(16px, 4vw, 36px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: clamp(32px, 5vw, 64px);
  max-width: 1100px;
  margin: 0 auto;
}

.settings-panel,
.interesting-sites {
  min-width: 0;
}

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
  cursor: pointer;
}

.toggle-row:last-child {
  border-bottom: none;
}

.toggle-visual {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.toggle-visual input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
}

.toggle-switch {
  position: relative;
  width: 38px;
  height: 22px;
  border-radius: 11px;
  background: var(--border);
  transition: background 150ms ease;
}

.toggle-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #FFFFFF;
  transition: transform 150ms ease;
}

.toggle-visual input:checked + .toggle-switch {
  background: var(--bronze);
}

.toggle-visual input:checked + .toggle-switch::before {
  transform: translateX(16px);
}

.toggle-visual input:focus-visible + .toggle-switch {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 2px;
}

.interesting-sites ul {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.interesting-sites a {
  font-weight: 600;
  font-size: 15px;
  color: var(--ink);
}

.interesting-sites a:hover {
  color: var(--bronze);
}

.site-desc {
  font-size: 13px;
  color: var(--muted);
  margin: 2px 0 0;
}

/* ==========================================================================
   §6 — VIEWING SETTINGS (site-wide, attribute-driven)

   State lives on <html> as data-* attributes and is persisted in localStorage.
   A no-flash inline script in every page's <head> applies the saved state
   BEFORE first paint; js/about.js §6 wires the toggle switches. Keys:
     data-theme="dark"|"light"   viewTheme            (falls back to OS setting)
     data-readability="1"        viewReadability
     data-colorblind="1"         viewColorblind
     data-hide-precollege="1"    viewShowPrecollege != "1"
   ========================================================================== */

/* ---- Dark mode: override the palette tokens; everything token-driven follows.
   Bronze/rose are lightened so they stay legible on the dark background. ---- */
:root[data-theme="dark"] {
  --bg:           #16130E;
  --bg-section:   #211C14;
  --nav-bg:       #1B1710;
  --ink:          #ECE6DA;
  --muted:        #E4DCCE;
  --bronze:       #CE9E4F;   /* lightened — the #6B4F1A brand bronze is unreadable on dark */
  --bronze-mid:   #B9894A;
  --rose:         #D89A9A;
  --rose-light:   #4A3234;
  --border:       #38301F;
  --footer-bg:    #0E0B07;
  --footer-text:  #D4C9BB;
  --search-bg:    #32291A;   /* bronze #CE9E4F ≈10% over the dark --bg-section */
  color-scheme: dark;        /* native form controls, scrollbars follow */
}
/* Soften images a touch so they don't glare against the dark page */
:root[data-theme="dark"] img { filter: brightness(0.92); }

/* ---- Readability mode: larger type, looser leading, wider muted contrast,
   comfortable measure, and underlined inline links (color isn't the only cue). */
:root[data-readability] body {
  font-size: 19px;
  line-height: 1.85;
  letter-spacing: 0.01em;
}
:root[data-readability] { --muted: #4F4D48; }
:root[data-theme="dark"][data-readability] { --muted: #C4BBAC; }
:root[data-readability] .whoami-content p,
:root[data-readability] .ai-content p,
:root[data-readability] .exp-hero-para,
:root[data-readability] .site-desc { max-width: 66ch; }
:root[data-readability] a.iel-chip,
:root[data-readability] .interesting-sites a,
:root[data-readability] .ai-content a { text-decoration: underline; text-underline-offset: 2px; }

/* ---- Colorblind mode: don't rely on the rose/bronze hue difference. Swap the
   rose accent for a blue (orange↔blue reads under every CVD type) and underline
   text links so meaning never depends on color alone. Most impactful once
   projects/experience gain colored tags — see the toggle note in the panel. */
:root[data-colorblind] {
  --rose:       #3F79A8;
  --rose-light: #D6E3EF;
}
:root[data-colorblind] a.iel-chip,
:root[data-colorblind] .interesting-sites a,
:root[data-colorblind] .ai-content a { text-decoration: underline; text-underline-offset: 2px; }

/* ---- Pre-college toggle: hides .pre-college elements site-wide when off.
   .always-visible (e.g. Eagle Scout) ignores this toggle. -------------------- */
:root[data-hide-precollege] .pre-college:not(.always-visible) {
  display: none;
}

/* --------------------------------------------------------------------------
   about.html — mobile fallback (stack single column below 768px)
   The scroll travel + morph are desktop-only; on mobile the portrait is static
   and all three buttons (resume, CV, transcript) show stacked and tappable.
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
  /* ===== About intro (mobile): single column; the portrait travels down a TALL
     header and the buttons morph — same engine as desktop (see about.js). ===== */
  .about-intro{ position:relative; background: var(--bg); }
  .about-intro::before, .about-intro::after{ display:none; }
  .about-intro-grid{ display:block; max-width:none; padding:0; }
  .about-intro-left{ display:block; }

  #about-s1{
    position:relative; width:100%;
    min-height: 118vh;                                  /* header runway: room to travel + morph */
    padding: 56px clamp(16px,5vw,28px) 0;               /* 56px = breathing room below the red banner */
  }
  .section-about-hero{ min-height:0; display:block; }   /* name at the TOP of the header */
  .hero-name{ font-size: clamp(40px,12vw,60px); }

  /* portrait + buttons overlay the header; about.js sets translateY (the travel).
     Centred with left+margin so the JS transform (Y only) doesn't fight it. */
  .about-photo-col{
    position:absolute; top:0; z-index:2; margin:0;
    left:50%; margin-left: calc(-1 * min(31vw,150px));
    width: min(62vw,300px);
  }
  .morph-svg{ display:block !important; }

  /* The portrait finishes its travel INSIDE this section; about.js sets an
     inline padding-top (measured from the portrait height) so the "Who am I?"
     text always sits just below where the portrait lands. min-height keeps the
     section tall even before that runs / if JS is off. */
  #about-s2{
    position:relative; width:100vw; margin-left:calc(50% - 50vw);
    box-sizing:border-box; background: var(--bg-section); border-top:1px solid var(--border);
    min-height:92vh; display:block; padding: clamp(32px,8vw,48px) clamp(16px,5vw,28px) 56px;
  }
  /* Statement on AI keeps the same side padding as "Who am I?" on phones. */
  .ai-content{
    padding: 0 clamp(16px,5vw,28px);
  }
  .whoami-title{ font-size: clamp(34px,10vw,50px); }
  .whoami-content{ max-width:none; }

  /* ===== Reading carousel (mobile): works like desktop but stacked — title on
     top, a smaller centred window, the book's title + description below. ===== */
  .reading-sticky{
    --hole-h: 40vh; --hole-w: calc(var(--hole-h) * 2 / 3);
    --hole-left: calc(50% - var(--hole-w)/2);           /* centre the window */
    --hole-cy: 47%;
    position:sticky; top:0; height:100vh; overflow:hidden; padding:0;
  }
  .reading-title{
    position:absolute; top: clamp(66px,9vh,88px); left:0; right:0;   /* clear the sticky navbar */
    text-align:center; padding:0 clamp(16px,5vw,28px); max-width:none;
    font-size: clamp(24px,7vw,34px);
  }
  .reading-hole{ transform: translateY(-50%); }
  .book-text{
    display:block; position:absolute; left:clamp(16px,6vw,32px); right:clamp(16px,6vw,32px);
    top: calc(var(--hole-cy) + var(--hole-h)/2 + clamp(16px,3.5vh,30px));
    transform:none; max-width:none; text-align:center;
  }
  .reading-cover-cap{ display:none; }                   /* single book-text below the window */

  .section-settings{ grid-template-columns:1fr; }
}

/* ==========================================================================
   experience.html — page layout
   ========================================================================== */

/* --------------------------------------------------------------------------
   §1 — Hero (2026-09-27): a solid text panel + three captioned photo tiles.

   The panel (var(--footer-bg), footer-text) holds the status sentence and the
   paragraph; the tiles start exactly where it ends, so no image ever sits under
   text and no scrim is needed. Desktop: tile 1 tall on the left of the tile
   area, tiles 2–3 stacked to its right, thin gap. Height is capped at
   min(80vh, 640px). Under 768px (see the mobile block) the text comes first
   and the three tiles stack below it, captions visible.
   -------------------------------------------------------------------------- */
.exp-hero {
  display: grid;
  grid-template-columns: min(540px, 52%) minmax(0, 1fr);
  /* One explicit row locked to the hero height: an implicit auto row would be
     sized by the tile images' intrinsic heights and overflow past the bottom,
     clipping the captions on tiles 1 and 3. */
  grid-template-rows: minmax(0, 1fr);
  height: min(80vh, 640px);
  width: 100%;
  overflow: hidden;
  background: var(--footer-bg);
}

.exp-hero-content {
  min-width: 0;
  padding: clamp(40px, 6vw, 72px) clamp(16px, 4vw, 36px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  color: var(--footer-text);
}

.exp-hero-status {
  font-family: var(--font-serif);
  font-size: clamp(20px, 2.4vw + 10px, 30px);
  line-height: 1.3;
  color: var(--footer-text);
  margin: 0 0 16px;
}

.exp-hero-para {
  font-size: clamp(14px, 0.4vw + 12px, 16px);
  line-height: 1.65;
  color: var(--footer-text);
  opacity: 0.9;
  margin: 0;
}

.exp-hero-tiles {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  grid-template-rows: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding-left: 4px;
  background: var(--footer-bg);
}

.exp-tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  margin: 0;
  overflow: hidden;
  background: var(--bg-section);
}

.exp-tile--tall {
  grid-row: 1 / 3;
}

.exp-tile img {
  /* Out of flow so the photo's intrinsic size never drives the track sizing;
     the figure (grid cell on desktop, 4:3 box on mobile) is the frame. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Dark translucent caption bar along the bottom of each tile. */
.exp-tile-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 6px 10px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--footer-text);
  background: rgba(26, 21, 16, 0.72);
}

/* --------------------------------------------------------------------------
   §2 — Experience entries: full-width bands

   Each entry is a full-bleed band carrying a restrained tint of its employer's
   color; the color change between bands IS the divider, so there is no gap
   between them. Content sits in a centered max-width container — never a fixed
   pixel width.

   The four --entry-* custom properties are set as an INLINE STYLE on each band
   by js/experience.js, from js/experience-data.js. Per-entry values cannot live
   in :root, so that inline style is the sanctioned exception to CLAUDE.md's
   token convention — do not move these into this stylesheet. Every rule below
   reads them through a var() fallback so a band with no color still renders.

   Animation: the band background is painted at first render and never fades.
   Only .exp-band-inner (the content) fades in on scroll — a background that
   faded in would flash the page color through it.
   -------------------------------------------------------------------------- */
.exp-entries {
  width: 100%;
  max-width: 100%;
  background: var(--bg);   /* fallback beneath the bands, and below the last one */
}

.exp-list {
  width: 100%;
}

.exp-band {
  width: 100%;
  max-width: 100%;
  background: var(--entry-bg, var(--bg));
  color: var(--entry-ink, var(--ink));
  border-top: 1px solid var(--entry-border, var(--border));
  padding: clamp(36px, 5vw, 64px) clamp(16px, 4vw, 36px);
  /* experience.html#<slug>: keep the band clear of the sticky nav + dev banner. */
  scroll-margin-top: 92px;
}

.exp-band:last-child {
  border-bottom: 1px solid var(--entry-border, var(--border));
}

.exp-band-inner {
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  display: grid;
  gap: clamp(20px, 3vw, 36px);
  align-items: start;
  opacity: 0;
  transition: opacity 300ms ease;
}

.exp-band-inner.is-visible {
  opacity: 1;
}

/* ---- Layout presets. One class per `layout` value in experience-data.js; a
   new preset is one new class here, never a markup fork for a single entry. --- */
.exp-band--image-left .exp-band-inner {
  grid-template-columns: minmax(0, 40%) minmax(0, 60%);
}
.exp-band--image-left .exp-band-media { grid-column: 1; grid-row: 1; }
.exp-band--image-left .exp-band-body  { grid-column: 2; grid-row: 1; }

.exp-band--image-right .exp-band-inner {
  grid-template-columns: minmax(0, 60%) minmax(0, 40%);
}
.exp-band--image-right .exp-band-media { grid-column: 2; grid-row: 1; }
.exp-band--image-right .exp-band-body  { grid-column: 1; grid-row: 1; }

/* Wide image above, text below — for entries with strong photography. */
.exp-band--full-bleed .exp-band-inner {
  grid-template-columns: minmax(0, 1fr);
}
.exp-band--full-bleed .exp-band-image {
  aspect-ratio: 16 / 7;
}

/* No image; text sits in a narrower measure, centered. */
.exp-band--text-only .exp-band-inner {
  grid-template-columns: minmax(0, 1fr);
  max-width: 680px;
}

/* ---- Current role. Author-side flag (status: "current"), not a visitor
   toggle: accent left edge + the "Current" eyebrow above the role title. ----- */
.exp-band--current {
  border-left: 3px solid var(--entry-accent, var(--bronze));
}

/* Deep-link target (experience.html#<slug>): the same left rule, in the site
   bronze. Static — no animation. Declared after --current so it wins when the
   targeted band is also the current one. */
.exp-band.is-target {
  border-left: 3px solid var(--bronze);
}

.exp-band-eyebrow {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--entry-accent, var(--bronze));
  margin: 0 0 6px;
}

.exp-band-media {
  min-width: 0;
}

.exp-band-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 3px;
  background: var(--bg-section);
}

.exp-band-body {
  min-width: 0;
}

.exp-band-role {
  font-family: var(--font-serif);
  font-size: clamp(22px, 1.8vw + 12px, 30px);
  font-weight: 500;
  line-height: 1.25;
  color: var(--entry-ink, var(--ink));
  margin: 0 0 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--entry-border, var(--border));
}

.exp-band-meta {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 14px;
}

.exp-band-bullets {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 14px;
}

.exp-band-bullets li {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--entry-ink, var(--ink));
  padding-left: 14px;
  position: relative;
}

.exp-band-bullets li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--entry-accent, var(--bronze-mid));
}

/* Tag pills — shared by index rows, featured entries, experience bands and the
   project sub-pages (js/data-helpers.js buildTagRow). Every pill is a LINK to
   projects.html?tag=<id>. Deliberately quiet: pointer cursor only, no hover
   effect, no enlarged tap target — the global a:hover bronze is cancelled
   below so a pill never changes color. Inside a band they pick up the entry
   accent instead of the site-wide border/muted pair. */
.tag-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 14px;
}

.tag-pill {
  display: inline-block;
  font-size: 11.5px;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--muted);
  cursor: pointer;
  transition: none;
}

.tag-pill:hover {
  color: var(--muted);
}

.tag-pill:focus-visible {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 2px;
}

.exp-band .tag-pill,
.exp-band .tag-pill:hover {
  border-color: var(--entry-accent, var(--border));
  color: var(--entry-accent, var(--muted));
}

.exp-band-link {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--entry-accent, var(--bronze));
  transition: color 150ms ease;
}

.exp-band-link:hover {
  color: var(--entry-ink, var(--ink));
}

/* "Projects from this role" — the role → project half of the two-way link.
   Rendered after the tags by js/experience.js, only when at least one index
   project points at the band. Title links to projects.html#<slug>. */
.exp-related {
  margin: 4px 0 14px;
  padding-top: 12px;
  border-top: 1px solid var(--entry-border, var(--border));
}

.exp-related-heading {
  font-family: var(--font-serif);
  font-size: 14px;
  font-weight: 600;
  color: var(--entry-ink, var(--ink));
  margin: 0 0 8px;
}

.exp-related-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.exp-related-list li {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--entry-ink, var(--ink));
}

.exp-related-title {
  font-weight: 500;
  color: var(--entry-accent, var(--bronze));
  transition: color 150ms ease;
}

.exp-related-title:hover {
  color: var(--entry-ink, var(--ink));
}

.exp-related-desc {
  color: var(--muted);
}

.exp-related-desc::before {
  content: " — ";
}

@media (max-width: 767px) {
  /* Hero: text panel first, then the three tiles stacked below it (4:3),
     captions visible. No overlay of any kind. */
  .exp-hero {
    grid-template-columns: minmax(0, 1fr);
    height: auto;
  }

  .exp-hero-content {
    padding: 40px clamp(16px, 4vw, 36px);
  }

  .exp-hero-tiles {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: none;
    padding: 0;
    gap: 4px;
  }

  .exp-tile,
  .exp-tile--tall {
    grid-row: auto;
    aspect-ratio: 4 / 3;
  }

  /* Every layout preset collapses to one column; the image always leads. */
  .exp-band-inner,
  .exp-band--image-left .exp-band-inner,
  .exp-band--image-right .exp-band-inner,
  .exp-band--full-bleed .exp-band-inner,
  .exp-band--text-only .exp-band-inner {
    grid-template-columns: minmax(0, 1fr);
  }

  .exp-band--image-left .exp-band-media,
  .exp-band--image-right .exp-band-media {
    grid-column: 1;
    grid-row: 1;
  }

  .exp-band--image-left .exp-band-body,
  .exp-band--image-right .exp-band-body {
    grid-column: 1;
    grid-row: 2;
  }

  .exp-band--full-bleed .exp-band-image {
    aspect-ratio: 16 / 9;
  }
}

@media (min-width: 1440px) {
  .exp-hero-status {
    font-size: clamp(22px, 1.8vw + 12px, 32px);
  }

  .exp-band-inner {
    max-width: 1120px;
  }

  .exp-band-role {
    font-size: clamp(26px, 1.4vw + 14px, 34px);
  }

  .exp-band-bullets li {
    font-size: 15.5px;
  }
}

/* ==========================================================================
   projects.html — page layout

   Three zones in descending visual weight: a (hidden) search band, a small
   image-forward featured set, and a compact complete index. The index is
   deliberately quieter than the featured block above it — the page must not
   read as one repeating layout loop top to bottom.

   §2 and §3 are rendered by js/projects.js from the single array in
   js/projects-data.js. Nothing below assumes a second data source.
   ========================================================================== */

/* --------------------------------------------------------------------------
   §1 — Search band: live keyword search over the §3 index (js/projects.js).

   Background: var(--search-bg), a bronze wash (~10% bronze over --bg-section,
   per palette; Gideon, 2026-09-27) so the band reads as a control strip and
   never matches the featured section under it. The 2px bronze rule at the
   bottom is the transition into the featured section, or straight into the
   index while the featured section is collapsed.
   -------------------------------------------------------------------------- */
.search-band {
  width: 100%;
  max-width: 100%;
  background: var(--search-bg);    /* bronze wash token (2026-09-27), distinct from the featured section */
  border-bottom: 2px solid var(--bronze);
  padding: clamp(20px, 3vw, 32px) clamp(16px, 4vw, 36px);
}

.search-band-inner {
  max-width: 560px;
  width: 100%;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Input + the × clear control. The × is shown only while the input has text
   (.has-text, toggled by js/projects.js). */
.search-field {
  position: relative;
  flex: 1 1 200px;
  min-width: 0;
  display: flex;
}

.search-input {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  padding: 9px 50px 9px 12px;
  font-size: 14px;
  color: var(--ink);
  background: var(--nav-bg);
  border: 1px solid var(--border);
  border-radius: 2px;
}

/* The site draws its own clear control — suppress the browser's. */
.search-input::-webkit-search-cancel-button,
.search-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
  display: none;
}

.search-input::placeholder {
  color: var(--muted);
}

.search-input:focus-visible {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 1px;
}

/* The × clear control: a small bordered chip inside the field's right edge,
   present only while the input has text (opacity-only appear, 150ms). It
   clears the query, which also brings the featured block back. */
.search-clear {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 36px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 2px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 150ms ease, visibility 0s linear 150ms, color 150ms ease, border-color 150ms ease;
}

.search-field.has-text .search-clear {
  opacity: 1;
  visibility: visible;
  transition: opacity 150ms ease, visibility 0s linear 0s, color 150ms ease, border-color 150ms ease;
}

.search-clear:hover {
  color: var(--ink);
  border-color: var(--bronze-mid);
}

.search-clear:focus-visible {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 2px;
}

.search-btn {
  flex: 0 0 auto;
  padding: 9px 18px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--bronze);
  border: 1px solid var(--bronze);
  border-radius: 2px;
  transition: color 150ms ease, background-color 150ms ease;
}

.search-btn:hover {
  background: var(--bronze);
  color: var(--bg);
}

/* --------------------------------------------------------------------------
   §2 — Featured projects

   Image is the anchor. Arrangement alternates image-left / image-right, set by
   js/projects.js from the entry index. Alternation is safe here and nowhere
   else: the set is 2-3 entries and fixed.

   Order inside the body is title, dates, description, tags, link. Tags go
   BELOW the description, never above the title (CLAUDE.md).
   -------------------------------------------------------------------------- */
.featured-projects {
  background: var(--bg-section);
  border-top: 1px solid var(--border);
  padding: clamp(32px, 5vw, 56px) clamp(16px, 4vw, 36px);
  width: 100%;
  /* Collapse mechanics (2026-09-27): a one-row grid whose row animates 1fr → 0fr
     while the section fades, so the index glides up under the search band as
     soon as any filter is active. This is a recorded MOVEMENT exception
     (CLAUDE.md → Animation) and may be swapped for fade-then-instant later:
     to do that, drop grid-template-rows and padding from the transition. */
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows 300ms ease, padding 300ms ease, border-top-width 300ms ease,
              opacity 200ms ease, visibility 0s linear 0s;
}

.featured-projects-inner {
  min-height: 0;
  overflow: hidden;
}

/* While a search query, ?tag= or ?part= is active. visibility waits for the
   collapse to finish so the links inside stop being focusable once hidden. */
.featured-projects.is-collapsed {
  grid-template-rows: 0fr;
  padding-block: 0;
  border-top-width: 0;
  opacity: 0;
  visibility: hidden;
  transition: grid-template-rows 300ms ease, padding 300ms ease, border-top-width 300ms ease,
              opacity 200ms ease, visibility 0s linear 300ms;
}

/* .is-settling is on the section in the HTML and removed by js/projects.js one
   frame after the first render: a page opened already filtered (?tag=, ?part=)
   starts collapsed with no animation, even if the browser painted before the
   script ran. */
.featured-projects.is-settling,
.featured-projects.is-settling.is-collapsed {
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .featured-projects,
  .featured-projects.is-collapsed {
    transition: none;
  }
}

.featured-projects .list-heading,
.project-index-section .list-heading {
  max-width: 1000px;
  margin-inline: auto;
}

.featured-list {
  max-width: 1000px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: clamp(32px, 5vw, 56px);
}

/* Scroll-triggered fade, opacity only, no movement — standard CLAUDE.md rule,
   no carve-out on this page. js/projects.js adds .is-visible. */
.featured-entry {
  display: grid;
  gap: clamp(18px, 3vw, 32px);
  align-items: start;
  opacity: 0;
  transition: opacity 300ms ease;
}

.featured-entry.is-visible {
  opacity: 1;
}

.featured-entry--image-left {
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
}

.featured-entry--image-left .featured-entry-media { grid-column: 1; grid-row: 1; }
.featured-entry--image-left .featured-entry-body  { grid-column: 2; grid-row: 1; }

.featured-entry--image-right {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
}

.featured-entry--image-right .featured-entry-media { grid-column: 2; grid-row: 1; }
.featured-entry--image-right .featured-entry-body  { grid-column: 1; grid-row: 1; }

/* ---- Home featured presets (index.html, per-project `homeLayout`) --------
   stacked (default): full-width 16:9 image, text below. collage: full-width
   16:9 grid of imageSrc + gallery. imageLeft / imageRight reuse the two rules
   above. text-only: no imageSrc — the body takes the full width, no empty
   frame. projects.html §2 never uses stacked/collage; it alternates. */
.featured-entry--stacked,
.featured-entry--collage,
.featured-entry--text-only {
  grid-template-columns: minmax(0, 1fr);
}

.featured-entry--stacked .featured-entry-media,
.featured-entry--collage .featured-entry-media { grid-column: 1; grid-row: 1; }
.featured-entry--stacked .featured-entry-body,
.featured-entry--collage .featured-entry-body  { grid-column: 1; grid-row: 2; }
.featured-entry--text-only .featured-entry-body { grid-column: 1; grid-row: 1; }

.featured-entry-media,
.featured-entry-body {
  min-width: 0;
}

.featured-entry-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 3px;
  background: var(--border);
}

/* Collage grid: overall 16:9, every cell object-fit: cover. Cell count is the
   modifier (--2 / --3 / --4) set by js/data-helpers.js; the first cell is
   always imageSrc. No fixed pixel widths anywhere. */
.featured-collage {
  display: grid;
  width: 100%;
  aspect-ratio: 16 / 9;
  gap: 6px;
  overflow: hidden;
  border-radius: 3px;
}

.featured-collage-image {
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  display: block;
  background: var(--border);
}

/* 2: two equal columns */
.featured-collage--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* 3: imageSrc large left (2/3), two stacked right */
.featured-collage--3 {
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  grid-template-rows: repeat(2, minmax(0, 1fr));
}
.featured-collage--3 .featured-collage-image:first-child {
  grid-row: 1 / 3;
}

/* 4: 2×2 */
.featured-collage--4 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
}

.featured-entry-title {
  font-family: var(--font-serif);
  font-size: clamp(20px, 1.4vw + 12px, 28px);
  font-weight: 600;
  line-height: 1.25;
  color: var(--ink);
  margin: 0 0 6px;
}

.featured-entry-meta {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 12px;
}

.featured-entry-desc {
  font-size: 14.5px;
  line-height: 1.65;
  color: var(--ink);
  margin: 0 0 14px;
}

/* Shared by featured entries and index rows. */
.project-link {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--bronze);
  transition: color 150ms ease;
}

.project-link:hover {
  color: var(--ink);
}

/* "Part of: <role>, <org> →" — the project → role half of the two-way link.
   Sits below the tags, above the "View project →" link, on any entry whose
   `experience` field resolves to a visible role. Quieter than .project-link:
   it is context, not the primary action. */
.project-part-of {
  display: block;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--muted);
  margin: -4px 0 8px;
  transition: color 150ms ease;
}

.project-part-of:hover {
  color: var(--bronze);
}

/* --------------------------------------------------------------------------
   §3 — Project index

   Coverage, not impression: rows are compact because the featured block above
   is already image-forward. Rows also absorb the EN to ES length swing without
   breaking alignment and degrade cleanly when an entry has no photograph.
   -------------------------------------------------------------------------- */
.project-index-section {
  background: var(--bg);
  border-top: 1px solid var(--border);
  padding: clamp(32px, 5vw, 56px) clamp(16px, 4vw, 36px) clamp(48px, 6vw, 72px);
  width: 100%;
}

/* Live count, and beside it the active-tag chip ("<Label> ✕") when the page
   was opened as projects.html?tag=<id>. There is no filter row (2026-09-27):
   the pills on rows, featured entries, bands and sub-pages are the filter. */
.index-controls {
  max-width: 1000px;
  margin: 0 auto 8px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: 10px 14px;
}

/* Filled bronze with var(--bg) text, not a hardcoded white — in dark mode the
   bronze token lightens and --bg darkens, so the pair stays legible both ways. */
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-sans);
  font-size: 12px;
  padding: 3px 6px 3px 11px;
  background: var(--bronze);
  border: 1px solid var(--bronze);
  border-radius: 12px;
  color: var(--bg);
}

.tag-chip-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  font-size: 11px;
  line-height: 1;
  color: var(--bg);
  border-radius: 50%;
  transition: background-color 150ms ease;
}

.tag-chip-clear:hover {
  background: rgba(255, 255, 255, 0.18);
}

.tag-chip-clear:focus-visible {
  outline: 2px solid var(--bg);
  outline-offset: 1px;
}

.index-count {
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--muted);
  margin: 0;
  white-space: nowrap;
}

.index-empty {
  max-width: 1000px;
  margin: 0 auto;
  padding-top: 20px;
  font-size: 14px;
  color: var(--muted);
}

.project-index {
  max-width: 1000px;
  width: 100%;
  margin: 0 auto;
}

/* ---- Rows view (default) ------------------------------------------------ */
.project-index--rows .project-row {
  display: grid;
  grid-template-columns: clamp(140px, 18vw, 180px) minmax(0, 1fr);
  gap: clamp(14px, 2.5vw, 24px);
  align-items: start;
  padding: 18px 0;
  border-bottom: 1px dotted var(--border);
}

/* No imageSrc: no thumbnail column and no img node at all. Never a broken
   image, a grey box, or a placeholder icon. At 768px and up the row keeps the
   two-column grid and the text sits in the text column (same width, same left
   edge as the image rows, still left-aligned) — 2026-09-27. Phones: the
   mobile block below collapses it to one column like every other row. */
.project-index--rows .project-row--no-image .project-row-body {
  grid-column: 2;
}

/* ---- Grid view (written, not applied) -----------------------------------
   Reserved hook. Switching the index to cards is a class swap on the container
   in projects.html — .project-index--rows becomes .project-index--grid — with
   NO change to js/projects-data.js and no change to the markup js/projects.js
   generates. Every rule here targets the same nodes the rows view does.
   A visitor-facing view switcher is Phase 3 at the earliest, and optional. --- */
.project-index--grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: clamp(18px, 3vw, 28px);
}

.project-index--grid .project-row {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0;
  border-bottom: 0;
}

/* A card with no thumbnail is simply a text card — its text starts at the top
   of the cell. Cards are not forced to a common height. */
.project-index--grid .project-thumb {
  width: 100%;
}

.project-index--grid .project-row-title {
  font-size: 16px;
}

.project-index--grid .project-row-desc {
  margin-bottom: 0;
}

/* ---- Row internals (shared by both views) ------------------------------- */
.project-row {
  position: relative;
  transition: opacity 150ms ease;
  /* projects.html#<slug>: keep the row clear of the sticky nav (56px) and the
     dev banner (~36px) when it is scrolled into view. */
  scroll-margin-top: 104px;
}

/* Deep-link target: a static bronze rule in the left gutter. No animation, no
   layout shift — the rule sits outside the row's box. */
.project-row.is-target::before {
  content: "";
  position: absolute;
  left: -10px;
  top: 18px;
  bottom: 18px;
  width: 3px;
  background: var(--bronze);
}

.project-thumb,
.project-row-body {
  min-width: 0;
}

/* One aspect ratio, enforced on every thumbnail in both views. 4:3 matches the
   band images on experience.html, stays tall enough beside three lines of text
   at a 140-180px width, and crops landscape shop and CAD photography far less
   brutally than 1:1 would. */
.project-thumb-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 2px;
  background: var(--bg-section);
}

.project-row-title {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
  margin: 0 0 5px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
}

.project-row-dates {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 400;
  color: var(--muted);
}

.project-row-desc {
  font-size: 14px;
  line-height: 1.6;
  color: var(--muted);
  margin: 0 0 10px;
  max-width: 68ch;
}

.project-row .tag-row {
  margin: 0 0 8px;
}

.project-row .project-link {
  font-size: 13px;
}

/* Filtered out: removed from flow so the list closes up instead of leaving a
   gap. Only opacity is transitioned — rows never slide or animate position.
   Specificity here deliberately beats both view blocks above. */
.project-index .project-row.is-filtered-out {
  display: none;
  opacity: 0;
}

/* --------------------------------------------------------------------------
   projects.html — mobile
   -------------------------------------------------------------------------- */
@media (max-width: 767px) {
  /* Featured collapses to one column; the image always leads. */
  .featured-entry,
  .featured-entry--image-left,
  .featured-entry--image-right {
    grid-template-columns: minmax(0, 1fr);
  }

  .featured-entry--image-left .featured-entry-media,
  .featured-entry--image-right .featured-entry-media {
    grid-column: 1;
    grid-row: 1;
  }

  .featured-entry--image-left .featured-entry-body,
  .featured-entry--image-right .featured-entry-body {
    grid-column: 1;
    grid-row: 2;
  }

  /* Collage on phones: 2 stays side by side; 3 → large on top + two below;
     4 stays 2×2. Still 16:9 overall. */
  .featured-collage--3 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: minmax(0, 2fr) minmax(0, 1fr);
  }
  .featured-collage--3 .featured-collage-image:first-child {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  /* Narrower thumbnail so the row keeps a usable text measure at 375px.
     The 4:3 ratio is unchanged. */
  .project-index--rows .project-row {
    grid-template-columns: 104px minmax(0, 1fr);
    gap: 14px;
  }

  /* Text-only rows collapse to one column here, text at full width. */
  .project-index--rows .project-row--no-image {
    grid-template-columns: minmax(0, 1fr);
  }

  .project-index--rows .project-row--no-image .project-row-body {
    grid-column: 1;
  }

  .index-controls {
    justify-content: flex-start;
  }

  .project-row-desc {
    font-size: 13.5px;
  }
}

/* --------------------------------------------------------------------------
   projects.html — wide screens. Text and measure grow; layout is unchanged.
   -------------------------------------------------------------------------- */
@media (min-width: 1440px) {
  .featured-work .list-heading,
  .featured-work .featured-list,
  .featured-projects .list-heading,
  .project-index-section .list-heading,
  .featured-list,
  .index-controls,
  .index-empty,
  .project-index {
    max-width: 1120px;
  }

  .featured-entry-desc {
    font-size: 15.5px;
  }

  .project-row-title {
    font-size: 18.5px;
  }

  .project-row-desc {
    font-size: 15px;
  }

  .index-count,
  .tag-chip {
    font-size: 13px;
  }
}

/* ==========================================================================
   projects/<slug>.html — project sub-pages (js/project-page.js)

   One narrow reading measure. Every block is optional and absent when its
   data is missing — no empty boxes. Tags sit near the end, after the credit,
   never above the title (CLAUDE.md).
   ========================================================================== */
.project-page {
  max-width: 800px;
  width: 100%;
  margin: 0 auto;
  padding: clamp(28px, 4vw, 44px) clamp(16px, 4vw, 36px) clamp(48px, 6vw, 72px);
}

.project-page-hero {
  margin: 0 0 22px;
}

.project-page-hero-image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 3px;
  background: var(--bg-section);
}

.project-page-title {
  font-family: var(--font-serif);
  font-size: clamp(28px, 2.4vw + 14px, 40px);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 8px;
}

.project-page-meta {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--muted);
  margin: 0 0 8px;
}

/* "Part of" on the sub-page sits under the meta line; the shared rule gives it
   a negative top margin meant for the tag row, so reset it here. */
.project-page > .project-part-of {
  margin: 0 0 24px;
}

.project-page-section {
  margin: 0 0 22px;
}

.project-page-section-heading {
  font-family: var(--font-serif);
  font-size: clamp(19px, 1vw + 13px, 23px);
  font-weight: 600;
  margin: 0 0 8px;
}

.project-page-section-body {
  font-size: 15.5px;
  line-height: 1.7;
  margin: 0;
}

/* Photo grid: 2-up on desktop, 1-up below 768px. Each cell links to the full
   image in a new tab. */
.project-page-photos {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin: 28px 0;
}

.project-page-photo {
  display: block;
  min-width: 0;
}

.project-page-photo-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 3px;
  background: var(--bg-section);
}

/* Quick-facts box: a <dl> of label/value rows. */
.project-page-facts {
  margin: 0 0 22px;
  padding: 16px 20px;
  background: var(--bg-section);
  border: 1px solid var(--border);
  border-radius: 3px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.project-page-fact {
  display: grid;
  grid-template-columns: minmax(96px, 30%) minmax(0, 1fr);
  gap: 4px 14px;
  font-size: 14px;
  line-height: 1.5;
}

.project-page-fact dt {
  font-weight: 500;
  color: var(--ink);
}

.project-page-fact dd {
  margin: 0;
  color: var(--muted);
}

.project-page-report {
  margin: 0 0 16px;
}

.project-page-credit {
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 16px;
}

.project-page > .tag-row {
  margin: 0 0 22px;
}

.project-page-back {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--bronze);
  transition: color 150ms ease;
}

.project-page-back:hover {
  color: var(--ink);
}

/* ---- Lightbox (js/project-page.js) ------------------------------------------
   Native <dialog>. Image fits ~92vw × 88vh, caption from the alt text, ✕ top
   right, prev / next at the sides. Opacity-only fade in (.is-open lands one
   frame after showModal). */
.lightbox {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--footer-text);
  display: none;
  opacity: 0;
  transition: opacity 200ms ease;
}

.lightbox[open] {
  display: grid;
  place-items: center;
}

.lightbox.is-open {
  opacity: 1;
}

.lightbox::backdrop {
  background: rgba(0, 0, 0, 0.82);
}

.lightbox-figure {
  margin: 0;
  max-width: 92vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.lightbox-image {
  max-width: 92vw;
  max-height: 88vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 3px;
  background: #000;
}

.lightbox-caption {
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--footer-text);
  text-align: center;
  max-width: 70ch;
  padding: 0 16px;
}

.lightbox-close,
.lightbox-nav {
  position: fixed;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--footer-text);
  background: rgba(0, 0, 0, 0.45);
  border-radius: 50%;
  transition: background-color 150ms ease;
  z-index: 1;
}

.lightbox-close:hover,
.lightbox-nav:hover {
  background: rgba(255, 255, 255, 0.18);
}

.lightbox-close:focus-visible,
.lightbox-nav:focus-visible {
  outline: 2px solid var(--bronze-mid);
  outline-offset: 2px;
}

.lightbox-close {
  top: 16px;
  right: 16px;
  width: 40px;
  height: 40px;
  font-size: 18px;
}

.lightbox-nav {
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  font-size: 30px;
  line-height: 1;
}

.lightbox-prev { left: 12px; }
.lightbox-next { right: 12px; }

@media (prefers-reduced-motion: reduce) {
  .lightbox {
    transition: none;
  }
}

@media (max-width: 767px) {
  .project-page-photos {
    grid-template-columns: minmax(0, 1fr);
  }

  .project-page-fact {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
}

@media (min-width: 1440px) {
  .project-page {
    max-width: 860px;
  }

  .project-page-section-body {
    font-size: 16.5px;
  }
}
