/* ─────────────────────────────────────────────────────────────
   Jason Hart — Portfolio
   taste-skill: DESIGN_VARIANCE:8  MOTION_INTENSITY:6  VISUAL_DENSITY:4
   ───────────────────────────────────────────────────────────── */

/* ── Variables ─────────────────────────────────────────────── */
:root {
  --bg:           #F7F6F4;
  --fg:           #0D0D0B;
  --fg-muted:     #525250;
  --fg-faint:     #8A8A87;
  --accent:       #C1121F;
  --accent-soft:  rgba(193, 18, 31, 0.08);
  --border:       #E3E3DF;
  --surface:      #FFFFFF;
  --dark-bg:      #0D0D0B;
  --dark-surface: #161614;
  --dark-border:  #2C2C29;
  --dark-muted:   #9A9A96;

  --font: 'Cabinet Grotesk', system-ui, -apple-system, sans-serif;

  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  --ease-std:  cubic-bezier(0.4, 0, 0.2, 1);

  --r-sm:  6px;
  --r-md:  12px;
  --r-lg:  20px;
  --r-xl:  32px;

  --max-w: 1320px;

  /* Cap on the contact portrait, kept here rather than inline in
     .contact__portrait so the wide-screen block below can lift it in the
     same place it lifts --max-w. The two have to move together: see the
     note on that block for why raising --max-w alone backfires. */
  --contact-portrait-max: 520px;

  --focus-ring: var(--accent);
}

/* ── Wide screens ───────────────────────────────────────────────
   Above ~1600px the 1320px container leaves a wide band of dead margin
   down both sides and every section reads as a column floating in the
   middle of the display. Widening the container here reaches all 14
   consumers across both stylesheets — and therefore every page — from
   this one block, so the pages cannot drift apart.

   Safe to widen because every multi-column grid on the site is
   fractional (2fr 1fr, 1.3fr 1fr, 1fr 1.4fr, 1.25fr 1fr) and every run
   of body copy carries its own `ch` measure, so columns scale in
   proportion and no line gets a longer measure than it has today.

   --contact-portrait-max moves with it deliberately. The portrait is the
   one element in a --max-w container that is capped in px instead of
   fractional, so if --max-w alone went up, all 120px of new width would
   land in the form column and the portrait would end up *relatively*
   smaller — the opposite of the intent.

   Revert: delete this block. Nothing else references the 1600px break. */
@media (min-width: 1600px) {
  :root {
    --max-w: 1440px;
    --contact-portrait-max: 600px;
  }
}

/* ── Reset ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--fg);
  overflow-x: hidden;
}
img { display: block; max-width: 100%; height: auto; }
a { text-decoration: none; color: inherit; }
ul { list-style: none; }
button { background: none; border: none; cursor: pointer; font: inherit; }

/* ── Focus Visibility ───────────────────────────────────────────
   One ring, driven by --focus-ring so it can flip to white inside
   the dark sections where the accent red doesn't have the contrast. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

.hero,
.expertise,
.contact,
.footer,
.aishowcase:not(.aishowcase--light),
.nav:not(.scrolled) { --focus-ring: #FFFFFF; }

.nav.scrolled,
.nav__mobile { --focus-ring: var(--accent); }

/* ── Skip Link ──────────────────────────────────────────────── */
.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 200;
  padding: 12px 20px;
  background: var(--fg);
  color: var(--bg);
  font-size: 0.88rem;
  font-weight: 700;
  border-radius: var(--r-sm);
  transform: translateY(calc(-100% - 16px));
  transition: transform 0.2s var(--ease-std);
}
.skip-link:focus { transform: translateY(0); }

/* main is focus target for the skip link and in-page anchors */
main:focus { outline: none; }

/* ── Utilities ──────────────────────────────────────────────── */
.accent { color: var(--accent); }

.section-tag {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 16px;
}
.section-tag--light { color: rgba(255,255,255,0.45); }

.section-heading {
  font-size: clamp(2.4rem, 5vw, 3.8rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--fg);
}
.section-heading--light { color: #fff; }

.section-sub {
  font-size: 1.05rem;
  color: var(--fg-muted);
  line-height: 1.7;
  max-width: 52ch;
  margin-top: 16px;
}

/* ── Scroll Reveal ──────────────────────────────────────────── */
.reveal {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.75s var(--ease-out), transform 0.75s var(--ease-out);
}
.reveal.visible {
  opacity: 1;
  transform: translateY(0);
}
.reveal-delay-1 { transition-delay: 0.1s; }
.reveal-delay-2 { transition-delay: 0.2s; }
.reveal-delay-3 { transition-delay: 0.3s; }
.reveal-delay-4 { transition-delay: 0.4s; }

/* Safety net. The pair above hides the hero copy, every section heading and
   the whole contact form until script.js fades them in — so a page whose
   script never runs renders blank. Each page ships <html class="no-js"> and
   a head script that takes the class off, so the hiding only applies while
   JS is alive; the same script puts it back if script.js never checks in
   (blocked request, 404, syntax error in a bad deploy).

   Specificity is deliberate: (0,2,0) beats the base .reveal rule, and the
   Reduced Motion block at the foot of this file sets .reveal on its own
   terms — both land on opacity 1, so the two never fight.

   Not the same switch as the .js class the nav dropdowns use. That one is
   added by script.js itself, at the end of the body, which is far too late
   to gate anything that must not flash: the content would paint, then hide,
   then fade back in. This one is off before the first paint. */
.no-js .reveal { opacity: 1; transform: none; }

/* ── Buttons ────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 14px 28px;
  border-radius: var(--r-md);
  font-family: var(--font);
  font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  transition: all 0.25s var(--ease-std);
  white-space: nowrap;
}
.btn:active { transform: scale(0.98) translateY(1px); }

.btn--primary {
  background: var(--accent);
  color: #fff;
  border: 2px solid var(--accent);
}
.btn--primary:hover {
  background: #A30F1A;
  border-color: #A30F1A;
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(193,18,31,0.25);
}

.btn--outline {
  background: transparent;
  color: var(--fg);
  border: 2px solid var(--border);
}
.btn--outline:hover {
  border-color: var(--fg);
  transform: translateY(-2px);
}

.btn--primary-light {
  background: #fff;
  color: var(--fg);
  border: 2px solid #fff;
}
.btn--primary-light:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}

/* ── Navigation ─────────────────────────────────────────────── */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  transition: background 0.3s var(--ease-std), box-shadow 0.3s var(--ease-std);
}
.nav.scrolled {
  background: rgba(247,246,244,0.92);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: 0 1px 0 var(--border);
}

.nav__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
  height: 68px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav__logo {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 800;
  font-size: 1rem;
  letter-spacing: -0.01em;
  transition: opacity 0.2s;
}
.nav__logo:hover { opacity: 0.7; }
.nav__logo-icon {
  width: auto;
  height: 36px;
  position: relative;
  display: inline-flex;
  align-items: center;
}

.nav__logo-img {
  height: 36px;
  width: auto;
  transition: opacity 0.3s var(--ease-std);
}

.nav__logo-img--white {
  opacity: 1;
}

.nav__logo-img--black {
  position: absolute;
  left: 0;
  opacity: 0;
}
.nav__logo-text {
  font-size: 0.92rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: #fff;
}

.nav__links {
  display: flex;
  align-items: center;
  gap: 8px;
}

.nav__link {
  padding: 8px 14px;
  font-size: 0.88rem;
  font-weight: 600;
  color: rgba(255,255,255,0.75);
  border-radius: var(--r-sm);
  transition: color 0.2s, background 0.2s;
}
.nav__link:hover { color: #fff; background: rgba(255,255,255,0.08); }
.nav__link.is-active { color: #fff; }

/* Dropdown parents are buttons, not links — they open a menu rather than
   navigate. These rules only make one sit flush with the anchors beside it. */
.nav__parent {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  font-size: 0.88rem;
  font-weight: 600;
  background: none;
  border: 0;
  cursor: pointer;
}

.nav__group { position: relative; }

.nav__chevron {
  width: 10px;
  height: 7px;
  flex-shrink: 0;
  transition: transform 0.2s var(--ease-out);
}

.nav__dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 190px;
  display: flex;
  flex-direction: column;
  padding: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px rgba(13,13,11,0.14);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity 0.2s var(--ease-out), transform 0.2s var(--ease-out), visibility 0.2s;
}
/* Bridges the 6px gap so the panel doesn't close as the pointer travels. */
.nav__dropdown::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  top: -7px;
  height: 7px;
}

/* With JS off, hover and keyboard focus are the only way in. Once script.js
   runs it sets .js and takes over, driving .is-open for hover, click and
   Escape alike — otherwise :focus-within would hold a panel open against a
   click meant to close it, since the button keeps focus through the click. */
html:not(.js) .nav__group:hover .nav__dropdown,
html:not(.js) .nav__group:focus-within .nav__dropdown,
.nav__group.is-open .nav__dropdown {
  opacity: 1;
  visibility: visible;
  transform: none;
}
html:not(.js) .nav__group:hover .nav__chevron,
html:not(.js) .nav__group:focus-within .nav__chevron,
.nav__group.is-open .nav__chevron { transform: rotate(180deg); }

.nav__dropdown-link {
  padding: 9px 12px;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--fg-muted);
  border-radius: var(--r-sm);
  white-space: nowrap;
  transition: color 0.2s, background 0.2s;
}
.nav__dropdown-link:hover { color: var(--fg); background: rgba(13,13,11,0.05); }

.nav__cta {
  margin-left: 8px;
  padding: 9px 20px;
  background: #fff;
  color: var(--fg);
  font-size: 0.88rem;
  font-weight: 700;
  border-radius: var(--r-sm);
  transition: background 0.2s, transform 0.2s;
}
.nav__cta:hover {
  background: var(--accent);
  transform: translateY(-1px);
}

.nav__burger {
  display: none;
  flex-direction: column;
  gap: 5px;
  padding: 6px;
}
.nav__burger span {
  display: block;
  width: 22px;
  height: 2px;
  background: #fff;
  border-radius: 2px;
  transition: transform 0.3s var(--ease-out), opacity 0.3s;
}
.nav__burger.open span:first-child { transform: translateY(7px) rotate(45deg); }
.nav__burger.open span:last-child  { transform: translateY(-7px) rotate(-45deg); }

.nav.scrolled .nav__logo-img--white { opacity: 0; }
.nav.scrolled .nav__logo-img--black { opacity: 1; }
.nav.scrolled .nav__logo-text { color: var(--fg); }
.nav.scrolled .nav__link { color: var(--fg-muted); }
.nav.scrolled .nav__link:hover { color: var(--fg); background: rgba(13,13,11,0.05); }
.nav.scrolled .nav__link.is-active { color: var(--fg); }
.nav.scrolled .nav__cta { background: var(--fg); color: var(--bg); }
.nav.scrolled .nav__burger span { background: var(--fg); }

/* The panel slides open on a height JS measures for it — see slideOpen()
   in script.js. Two rules matter for that to land on a true zero height:
   nothing here may set vertical padding (a padded box can't collapse past
   its own padding), and the panel keeps overflow hidden so the content is
   clipped rather than spilling over the page while it moves. */
.nav__mobile {
  display: none;
  flex-direction: column;
  background: rgba(247,246,244,0.97);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid var(--border);
  padding: 0 40px;
  gap: 4px;
  height: 0;
  overflow: hidden;
  opacity: 0;
  /* visibility is what keeps the closed panel's links out of the tab order
     while it animates; listing it here holds it visible until the collapse
     finishes instead of snapping away at the first frame. */
  visibility: hidden;
  transition: height 0.36s var(--ease-out), opacity 0.26s var(--ease-std),
              visibility 0.36s;
}
/* The vertical breathing room the padding used to give, moved onto the
   children so the panel itself can still collapse to nothing. */
.nav__mobile > :first-child { margin-top: 24px; }
.nav__mobile > :last-child  { margin-bottom: 32px; }

/* .is-visible keeps the panel laid out for the whole animation, in both
   directions; .open is the state it settles into. */
.nav__mobile.is-visible { display: flex; }
/* height: auto is the resting state — the pixel height JS sets while the
   panel moves is inline, so it wins until the transition ends and JS
   drops it again. It also stands in for the animation entirely under
   reduced motion, where slideOpen() only flips the class. The cap and
   scroll are for a short viewport: the nav is fixed, so a panel taller
   than the screen has no other way to reach its last item. */
.nav__mobile.open {
  height: auto;
  max-height: calc(100dvh - 68px);
  overflow-y: auto;
  opacity: 1;
  visibility: visible;
}

/* Items drift down into place behind the panel edge, slightly staggered.
   The delays live on .open only, so closing takes them all at once
   instead of playing the stagger backwards. */
.nav__mobile > * {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity 0.26s var(--ease-std), transform 0.3s var(--ease-out);
}
.nav__mobile.open > * { opacity: 1; transform: none; }
.nav__mobile.open > :nth-child(1) { transition-delay: 0.05s; }
.nav__mobile.open > :nth-child(2) { transition-delay: 0.09s; }
.nav__mobile.open > :nth-child(3) { transition-delay: 0.13s; }
.nav__mobile.open > :nth-child(4) { transition-delay: 0.17s; }
.nav__mobile.open > :nth-child(n+5) { transition-delay: 0.21s; }

.nav__mobile-link {
  padding: 12px 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--fg-muted);
  border-bottom: 1px solid var(--border);
  transition: color 0.2s;
}
.nav__mobile-link:hover { color: var(--fg); }
.nav__mobile-link--cta { color: var(--accent); margin-top: 8px; border-bottom: none; }

/* Mobile equivalent of the dropdowns: an accordion, since a hover-opened
   panel has nothing to hover on a touch screen. */
.nav__mobile-group { display: flex; flex-direction: column; }

.nav__mobile-parent {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 12px 0;
  font-family: inherit;
  font-size: 1.1rem;
  font-weight: 600;
  text-align: left;
  color: var(--fg-muted);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: color 0.2s;
}
.nav__mobile-parent:hover,
.nav__mobile-parent[aria-expanded="true"] { color: var(--fg); }
.nav__mobile-parent[aria-expanded="true"] .nav__chevron { transform: rotate(180deg); }

/* Same collapse as the panel, one level in: no vertical padding, clipped,
   and driven by the same slideOpen(). It stays display: flex throughout —
   the panel around it is display: none when shut, so nothing shows. */
.nav__mobile-sub {
  display: flex;
  flex-direction: column;
  padding-left: 14px;
  height: 0;
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition: height 0.3s var(--ease-out), opacity 0.22s var(--ease-std),
              visibility 0.3s;
}
.nav__mobile-sub.open { height: auto; opacity: 1; visibility: visible; }

.nav__mobile-link--sub {
  font-size: 0.98rem;
  font-weight: 500;
  padding: 10px 0;
}

/* ── Hero ───────────────────────────────────────────────────── */
.hero {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding-top: 68px;
  position: relative;
  background: var(--dark-bg);
}

.hero__grid {
  flex: 1;
  max-width: var(--max-w);
  margin: 0 auto;
  width: 100%;
  padding: 0 40px;
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 48px;
  align-items: start;
  min-height: calc(100dvh - 68px);
}

.hero__content {
  padding: 64px 0;
}

.hero__location {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dark-muted);
  margin-bottom: 24px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.hero__location::before {
  content: '';
  display: block;
  width: 24px;
  height: 1.5px;
  background: var(--accent);
}

.hero__name {
  font-size: clamp(4rem, 8.5vw, 7rem);
  font-weight: 800;
  line-height: 0.95;
  letter-spacing: -0.04em;
  margin-bottom: 24px;
}
.hero__name-first {
  display: block;
  color: #fff;
}
.hero__name-last {
  display: block;
  -webkit-text-stroke: 2px #fff;
  color: transparent;
}

.hero__title {
  font-size: clamp(1rem, 2vw, 1.3rem);
  font-weight: 600;
  color: rgba(255,255,255,0.6);
  margin-bottom: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.hero__title-accent {
  color: var(--accent);
  font-weight: 700;
}
.hero__title-sep {
  color: var(--dark-border);
  font-size: 1.4em;
}

.hero__bio {
  font-size: 1.05rem;
  color: rgba(255,255,255,0.55);
  line-height: 1.7;
  max-width: 46ch;
  margin-bottom: 36px;
}

.hero__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 48px;
}

.hero__metrics {
  display: flex;
  gap: 32px;
  padding-top: 32px;
  border-top: 1px solid var(--dark-border);
  margin-bottom: 32px;
  flex-wrap: wrap;
}

.hero__metric-num {
  display: block;
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: #fff;
  line-height: 1.1;
}
.hero__metric-label {
  display: block;
  font-size: 0.73rem;
  font-weight: 600;
  color: var(--dark-muted);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  margin-top: 4px;
}

.hero__social {
  display: flex;
  gap: 14px;
  align-items: center;
}
.hero__social-link {
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1.5px solid var(--dark-border);
  border-radius: var(--r-sm);
  color: rgba(255,255,255,0.55);
  transition: color 0.2s, border-color 0.2s, transform 0.2s;
}
.hero__social-link svg { width: 17px; height: 17px; }
.hero__social-link:hover {
  color: var(--accent);
  border-color: var(--accent);
  transform: translateY(-2px);
}

/* Hero Visual */
.hero__visual {
  padding: 0;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  align-self: start;
  position: sticky;
  top: 68px;
  height: calc(100dvh - 68px);
}

.hero__parallax {
  position: relative;
  width: 100%;
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  overflow: hidden;
}

.hero__circle {
  position: absolute;
  width: 40dvh;
  height: 40dvh;
  border-radius: 50%;
  background: #ffffff;
  top: 24%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 0;
  will-change: transform;
}

.hero__photo-frame {
  position: relative;
  z-index: 1;
  height: calc(100dvh - 68px);
  max-height: 85dvh;
  width: auto;
  max-width: 100%;
  aspect-ratio: 880/873;
  will-change: transform;
}

.hero__photo-wrap {
  position: absolute;
  inset: 0;
  -webkit-mask-image:
    linear-gradient(to bottom, black 85%, transparent 100%),
    linear-gradient(to right, transparent 0%, black 4%, black 96%, transparent 100%);
  -webkit-mask-composite: destination-in;
  mask-image:
    linear-gradient(to bottom, black 85%, transparent 100%),
    linear-gradient(to right, transparent 0%, black 4%, black 96%, transparent 100%);
  mask-composite: intersect;
}

.hero__photo {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  /* Tight contact shadow to define the silhouette + soft ambient shadow */
  filter:
    drop-shadow(0 2px 8px rgba(10, 12, 20, 0.34))
    drop-shadow(0 10px 36px rgba(10, 12, 20, 0.45));
}

.hero .btn--outline { color: rgba(255,255,255,0.85); border-color: rgba(255,255,255,0.25); }
.hero .btn--outline:hover { border-color: rgba(255,255,255,0.7); color: #fff; }



.hero__photo-tag {
  position: absolute;
  bottom: 24px;
  left: 24px;
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(255,255,255,0.82);
  border: 1px solid rgba(13,13,11,0.08);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: 100px;
  padding: 8px 16px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--fg);
  letter-spacing: 0.01em;
  box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
.hero__photo-tag-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse-dot 2s ease-in-out infinite;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%       { opacity: 0.6; transform: scale(0.8); }
}

/* Hero Scroll indicator */
.hero__scroll {
  position: absolute;
  bottom: 36px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  color: var(--dark-muted);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: opacity 0.3s;
}
.hero__scroll:hover { opacity: 0.6; }

.hero__scroll-line {
  display: block;
  width: 1.5px;
  height: 40px;
  background: linear-gradient(to bottom, transparent, var(--dark-muted));
  animation: scroll-line 1.8s ease-in-out infinite;
}
@keyframes scroll-line {
  0%   { transform: scaleY(0); transform-origin: top; }
  50%  { transform: scaleY(1); transform-origin: top; }
  51%  { transform-origin: bottom; }
  100% { transform: scaleY(0); transform-origin: bottom; }
}

/* ── Hero, animated-bust variant ─────────────────────────────
   The live hero: the photo swapped for the waist-up 3D render, sitting on
   the depth-mapped mesh driven by assets/head-3d.js. Everything not listed
   here is inherited from .hero above, on purpose — only the visual differs,
   so the parked photo hero in index.html can be swapped back in without
   touching the layout.

   The section carries both classes and needs both: .hero--bust does the
   sizing, .hero--bust3d adds the motion. The .hero__head3d-* rules are the
   mesh's own plumbing — flat poster underneath, canvas over the top — and
   are what a visitor keeps when the mesh never starts.                    */

/* No white disc behind this one: the render brings its own lighting and
   wants the page's black behind it, and the mesh overhangs its box as the
   head turns, so the circle's edge kept cutting across the render. */
.hero--bust .hero__circle { display: none; }

/* Bottom-aligned in the sticky column: the waist-up crop hangs from the top
   of the viewport rather than floating in the middle of it. */
.hero--bust .hero__visual,
.hero--bust .hero__parallax {
  align-items: flex-end;
  justify-content: center;
}
.hero--bust .hero__parallax { height: 100%; }

/* The crop trades body for face: the head is 40% of this frame's height
   against 18% of the full standing figure's, so at the same column width
   the face lands roughly 2.5× larger — without going all the way to a
   floating head. The text column gives back a little width to suit. */
.hero--bust .hero__grid { grid-template-columns: 1.4fr 1fr; }

/* Width-driven, not height-driven: this render is far taller than it is
   wide, so sizing it by height would push it out of the column and into
   the crop. */
.hero--bust .hero__photo-frame {
  width: min(100%, 46dvh);
  height: auto;
  max-height: none;
  aspect-ratio: 710 / 1326;
}

/* The hero fades up with the page instead of snapping in. The whole stack
   fades as one, so the poster-to-mesh handover underneath is unaffected —
   .is-in is set by script.js once the poster actually has pixels to show. */
.hero__head3d {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.9s var(--ease-out);
}
.hero__head3d.is-in { opacity: 1; }

.hero__head3d-poster {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: opacity 0.5s;
  user-select: none;
  -webkit-user-select: none;
  /* The waist cut has to be dissolved — a cut-out sliced flat across the
     waistband reads as an amputation. 78% puts the fade's start below the
     crossed arms and inside the black t-shirt, so most of it happens
     black-on-black and the eye never finds the edge.

     It lives on the poster because the poster is exactly what the visitors
     who never get the mesh are left with: touch, reduced motion, and
     anything without WebGL. The shader's own fade (BUST_FADE_TOP in
     assets/head-3d.src.js) is the 1 - 0.78 of this, so the two agree and
     the handover is invisible. */
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
}
/* Set once the mesh has its first frame up, so the flat render underneath
   never blinks out on a device that ends up not drawing one. */
.hero__head3d.is-ready .hero__head3d-poster { opacity: 0; }

.hero__head3d-canvas {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Must match BUST_OVERSCAN in assets/head-3d.src.js — the mesh reprojects
     onto the poster box only while the two agree. The mesh draws larger
     than its box so the nose coming forward and the head swinging sideways
     have somewhere to go. */
  width: 125%;
  height: 125%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* The tap iOS insists on before it will report device orientation. Created and
   removed by assets/head-3d.js — it only ever exists on a browser that gates
   the sensor behind a permission, and it deletes itself once a reading lands,
   so on every other device this rule matches nothing. */
.hero__gyro-cue {
  position: absolute;
  left: 50%;
  /* Clears .hero__photo-tag, which sits 16-24px up with a ~33px pill. A fixed
     offset rather than a percentage so the gap holds at every frame height. */
  bottom: 68px;
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  white-space: nowrap;
  font-family: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #fff;
  background: var(--accent);
  border: 0;
  border-radius: 100px;
  cursor: pointer;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
  animation: gyro-cue-in 0.5s var(--ease-out) both;
}
.hero__gyro-cue::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #fff;
  animation: pulse-dot 2s ease-in-out infinite;
}
.hero__gyro-cue:disabled { opacity: 0.5; }

@keyframes gyro-cue-in {
  from { opacity: 0; transform: translate(-50%, 8px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__gyro-cue,
  .hero__gyro-cue::before { animation: none; }
}

/* Room for that overscan to swing into. */
.hero--bust3d .hero__parallax { overflow: visible; }

/* The overscan needs to bleed vertically — that is the whole point of it —
   but bleeding sideways past the viewport adds horizontal scroll. Measured,
   this canvas overhangs the right edge by up to 8px between about 900 and
   1152, and with the other hero candidates gone it is now what sets the
   page's scrollWidth. `clip` on one axis is what allows the other to stay
   visible; overflow-x: hidden would silently turn the block into a scroll
   container and cut the vertical bleed off too. */
.hero--bust3d { overflow-x: clip; overflow-y: visible; }

@media (max-width: 768px) {
  /* Touch gets the poster and no mesh, so there is no overscan left to
     protect — and letting it bleed only pushes the figure off a narrow
     screen. */
  .hero--bust3d .hero__parallax { overflow: hidden; }

  /* Restated at this specificity on purpose. The responsive block further
     down sets .hero__grid to a single column, but that is one class against
     the two here, so without this the hero would stay in two columns on a
     phone and push its own copy off the screen. */
  .hero--bust .hero__grid { grid-template-columns: 1fr; }

  /* Stacked single-column the figure has the full width but nothing like
     the height, so it flips to height-driven here — this keeps the crop
     whole above the copy instead of running off the screen.

     44svh, not 54dvh, for two separate reasons. The smaller share is what
     buys the copy below it enough room to put both CTAs above the fold —
     measured, 54dvh pushed them to y=927 on a 390x844 handset. And svh is
     the static viewport height: dvh grows as mobile Safari's URL bar
     collapses, which resized this figure mid-scroll and reflowed the whole
     hero underneath it. */
  .hero--bust .hero__photo-frame {
    width: auto;
    height: min(44svh, 380px);
    max-width: 100%;
  }
}

/* ── Journey ─────────────────────────────────────────────────── */
.journey {
  background: var(--bg);
  padding: 120px 0;
  position: relative;
  z-index: 10;
}

.journey__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
}

.journey__header {
  max-width: 600px;
  margin-bottom: 80px;
}
.journey .section-tag { color: var(--fg-faint); }
.journey .section-heading { color: var(--fg); }
.journey .section-sub { color: var(--fg-muted); }

.journey__timeline {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.timeline-item {
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: 0 32px;
  padding-bottom: 56px;
}
.timeline-item:last-child { padding-bottom: 0; }

.timeline-item__marker {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-top: 4px;
}

.timeline-item__dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--border);
  flex-shrink: 0;
  transition: border-color 0.3s;
  position: relative;
  z-index: 1;
}
.timeline-item__dot--accent {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(193,18,31,0.15);
}

.timeline-item__line {
  flex: 1;
  width: 1.5px;
  background: var(--border);
  margin-top: 8px;
}
.timeline-item:last-child .timeline-item__line { display: none; }

.timeline-item:hover .timeline-item__dot { border-color: var(--accent); }

.timeline-item__body {
  padding-bottom: 8px;
}

.timeline-item__tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 8px;
}
.timeline-item__tag--accent { color: var(--accent); }

.timeline-item__title {
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--fg);
  margin-bottom: 10px;
  letter-spacing: -0.02em;
}

.timeline-item__text {
  font-size: 0.95rem;
  color: var(--fg-muted);
  line-height: 1.75;
  max-width: 60ch;
}

/* ── Expertise ──────────────────────────────────────────────── */
.expertise {
  padding: 120px 0;
  background: var(--dark-bg);
}
.expertise .section-tag { color: rgba(255,255,255,0.35); }
.expertise .section-heading { color: #fff; }

.expertise__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
}

.expertise__header {
  margin-bottom: 64px;
}

/* Asymmetric bento — AI & Tech spans 2 rows as primary service */
.expertise__bento {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  grid-template-rows: auto auto;
  gap: 20px;
}

.bento-card {
  background: var(--dark-surface);
  border: 1px solid var(--dark-border);
  border-radius: var(--r-xl);
  padding: 44px;
  transition: transform 0.3s var(--ease-out), box-shadow 0.3s var(--ease-out);
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.bento-card:hover {
  transform: translateY(-4px);
  box-shadow: 0 20px 48px rgba(0,0,0,0.3);
}

.bento-card--tech {
  grid-column: 1;
  grid-row: 1 / 3;
}

.bento-card--speaking {
  grid-column: 2;
  grid-row: 1;
}

.bento-card--medicine {
  grid-column: 2;
  grid-row: 2;
  opacity: 0.75;
}

.bento-card__tag {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--dark-muted);
  margin-bottom: 16px;
}

.bento-card__title {
  font-size: 1.6rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  color: #fff;
  margin-bottom: 16px;
  line-height: 1.1;
}

.bento-card__text {
  font-size: 0.95rem;
  color: rgba(255,255,255,0.55);
  line-height: 1.7;
  margin-bottom: 28px;
  max-width: 48ch;
}

.bento-card__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.bento-card__list li {
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(255,255,255,0.9);
  display: flex;
  align-items: center;
  gap: 10px;
}
.bento-card__list li::before {
  content: '';
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--accent);
  flex-shrink: 0;
}

.bento-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 24px;
}
/* Cards sit on --dark-surface, so the outline variant needs the same
   light treatment the hero gives it. */
.bento-card .btn--outline { color: rgba(255,255,255,0.85); border-color: rgba(255,255,255,0.25); }
.bento-card .btn--outline:hover { border-color: rgba(255,255,255,0.7); color: #fff; }

.bento-card__accent-line {
  position: absolute;
  top: 0; left: 44px; right: 44px;
  height: 3px;
  background: var(--accent);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
}

/* ── Contact ─────────────────────────────────────────────────── */
.contact {
  padding: 140px 0 120px;
  background: var(--dark-bg);
}
.contact .section-tag { color: rgba(255,255,255,0.35); }

.contact__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
}

/* Both halves keep the flex-start behaviour the whole section used to have, so
   nothing inside them shifts now that they have siblings. */
.contact__head,
.contact__main {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* ── Contact portrait ────────────────────────────────────────
   The hero photo and its white circle, echoed beside the form. Sizes are
   percentages of the portrait box rather than the viewport units the hero
   uses, so it holds its proportions in a column instead of a full screen. */
.contact__visual { display: none; }

@media (min-width: 1024px) {
  .contact__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    /* Bottom-aligned: the figure fades out at its feet, so it wants to sit on
       the same line the social row ends on, not float against the form. */
    align-items: end;
    column-gap: 48px;
  }
  /* The heading spans both columns rather than sharing the row. Confined to
     the form's column it only had ~670px, which broke "Let's Build Something"
     onto a third line — the portrait would have to stay small to avoid it. */
  .contact__head { grid-column: 1 / -1; }

  /* The figure hangs out of its row instead of sitting in it. Left in flow it
     is the tallest thing in the row (595px against the form column's 499), so
     the bottom alignment above pushed the whole form down and opened a void
     under the intro line — 137px once the portrait reached 600. Zero height
     hands the row back to the form column, and the figure grows upward into
     the band beside the heading, which was empty anyway.

     The width is repeated here rather than left to the auto column, because
     an absolutely-positioned child contributes nothing to track sizing: the
     column would collapse to 0 and the social row would be free to run on
     under the portrait. Stated here, the grid still reserves the space.

     The matching `position: absolute` on .contact__portrait lives below the
     base rule, not in here — see the note there. */
  .contact__visual {
    display: block;
    position: relative;
    width: min(36vw, var(--contact-portrait-max));
    height: 0;
    align-self: end;
  }
}

.contact__portrait {
  position: relative;
  /* The cap is what governs on a wide screen; 36vw only takes over as the
     window narrows toward the 1024px breakpoint. The cap has to leave the
     column beside it ~670px, which is what the five social buttons need to
     stay on one line; at 560 against the 1320px container they wrapped once
     the viewport passed ~1770px. Both sizes clear that: 520 inside 1320
     leaves 672px, and the 600 that --contact-portrait-max steps up to at
     1600px sits inside a 1440px container, which leaves 712px. */
  width: min(36vw, var(--contact-portrait-max));
  /* Same frame ratio as .hero__photo-frame, so the figure sits in the circle
     exactly as it does up top. */
  aspect-ratio: 880 / 873;
}

/* Anchors the figure to the bottom of the zero-height .contact__visual set up
   above, so it grows upward. This has to sit *after* the base rule rather than
   inside that media block: a media query adds no specificity, so the
   `position: relative` above would otherwise win on source order and drop the
   figure a full 595px below the section, over the footer. */
@media (min-width: 1024px) {
  .contact__portrait {
    position: absolute;
    right: 0;
    bottom: 0;
  }
}

.contact__portrait-circle {
  position: absolute;
  /* 47% and 24% are the hero's 40dvh circle measured against its own frame at
     a typical viewport — the ratio, not the raw length, is what has to match. */
  width: 47%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #ffffff;
  top: 24%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 0;
}

.contact__portrait-frame {
  position: absolute;
  inset: 0;
  z-index: 1;
  -webkit-mask-image:
    linear-gradient(to bottom, black 85%, transparent 100%),
    linear-gradient(to right, transparent 0%, black 4%, black 96%, transparent 100%);
  -webkit-mask-composite: destination-in;
  mask-image:
    linear-gradient(to bottom, black 85%, transparent 100%),
    linear-gradient(to right, transparent 0%, black 4%, black 96%, transparent 100%);
  mask-composite: intersect;
}

.contact__portrait-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  filter:
    drop-shadow(0 2px 8px rgba(10, 12, 20, 0.34))
    drop-shadow(0 10px 36px rgba(10, 12, 20, 0.45));
}

.contact__heading {
  font-size: clamp(2.8rem, 6vw, 5rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.04em;
  color: #fff;
  margin-bottom: 20px;
}

.contact__sub {
  font-size: 1.05rem;
  color: rgba(255,255,255,0.55);
  line-height: 1.7;
  max-width: 50ch;
  margin-bottom: 40px;
}

.contact__email {
  font-size: clamp(1.2rem, 2.5vw, 1.8rem);
  font-weight: 700;
  color: #fff;
  letter-spacing: -0.02em;
  border-bottom: 2.5px solid var(--accent);
  padding-bottom: 4px;
  margin-bottom: 48px;
  transition: color 0.2s;
  display: inline-block;
}
.contact__email:hover { color: var(--accent); }

.contact__social {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.contact__social-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1.5px solid var(--dark-border);
  border-radius: var(--r-md);
  font-size: 0.85rem;
  font-weight: 600;
  color: rgba(255,255,255,0.55);
  transition: color 0.2s, border-color 0.2s, transform 0.2s, background 0.2s;
}
.contact__social-link svg { width: 16px; height: 16px; flex-shrink: 0; }
.contact__social-link:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(193,18,31,0.15);
  transform: translateY(-2px);
}

/* ── Contact Form ──────────────────────────────────────────────── */
.contact-form {
  width: 100%;
  max-width: 620px;
  margin-bottom: 48px;
}

/* Honeypot — Web3Forms rejects a submission that arrives with botcheck
   ticked. display:none keeps it off-screen and out of the tab order;
   an unticked checkbox is never serialised, so real senders are clean. */
.contact-form__botcheck { display: none; }

.contact-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}

.contact-form__field { margin-bottom: 18px; }
.contact-form__row .contact-form__field { margin-bottom: 0; }
.contact-form__row { margin-bottom: 18px; }

.contact-form__label {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.4);
  margin-bottom: 8px;
}

.contact-form__input {
  width: 100%;
  font-family: var(--font);
  font-size: 0.95rem;
  color: #fff;
  background: rgba(255,255,255,0.03);
  border: 1.5px solid var(--dark-border);
  border-radius: var(--r-md);
  padding: 13px 16px;
  transition: border-color 0.2s, background 0.2s;
}
.contact-form__input::placeholder { color: rgba(255,255,255,0.25); }
.contact-form__input:hover { border-color: rgba(255,255,255,0.25); }
/* The sitewide :focus-visible ring still lands on top of this. */
.contact-form__input:focus {
  border-color: rgba(255,255,255,0.5);
  background: rgba(255,255,255,0.06);
}
.contact-form__input[aria-invalid="true"] { border-color: var(--accent); }

.contact-form__textarea {
  display: block;
  min-height: 140px;
  line-height: 1.6;
  resize: vertical;
}

.contact-form__error {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent);
  margin-top: 7px;
}

.contact-form__submit[disabled] {
  opacity: 0.6;
  cursor: progress;
  transform: none;
  box-shadow: none;
}

.contact-form__status {
  font-size: 0.88rem;
  line-height: 1.6;
  margin-top: 16px;
  max-width: 46ch;
  color: rgba(255,255,255,0.55);
}
.contact-form__status:empty { display: none; }
.contact-form__status.is-error { color: var(--accent); }
.contact-form__status.is-success { color: #fff; font-weight: 600; }

/* Renders the native date picker and its icon dark to match the field. */
.contact-form__input[type="date"] { color-scheme: dark; }

.contact-form__optional {
  font-weight: 600;
  letter-spacing: 0.08em;
  color: rgba(255,255,255,0.25);
}

@media (max-width: 640px) {
  .contact-form__row { grid-template-columns: 1fr; }
  .contact-form__row .contact-form__field:first-child { margin-bottom: 0; }
}

/* ── Modal ─────────────────────────────────────────────────────── */
/* Hidden with the `hidden` attribute so it's out of the accessibility
   tree until opened; .is-open is added a frame later to drive the
   transition. Sits above the fixed nav (z-index 100). */
.modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.modal[hidden] { display: none; }

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(13,13,11,0.75);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.25s var(--ease-std);
}
.modal.is-open .modal__backdrop { opacity: 1; }

.modal__panel {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  padding: 40px;
  background: var(--dark-surface);
  border: 1px solid var(--dark-border);
  border-radius: var(--r-lg);
  box-shadow: 0 32px 80px rgba(0,0,0,0.55);
  opacity: 0;
  transform: translateY(16px) scale(0.985);
  transition: opacity 0.25s var(--ease-out), transform 0.3s var(--ease-out);
}
.modal.is-open .modal__panel { opacity: 1; transform: none; }

.modal__panel::before {
  content: '';
  position: absolute;
  top: 0; left: 40px; right: 40px;
  height: 3px;
  background: var(--accent);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
}

.modal__close {
  position: absolute;
  top: 20px; right: 20px;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255,255,255,0.5);
  background: transparent;
  border: 1.5px solid var(--dark-border);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s;
}
.modal__close svg { width: 18px; height: 18px; }
.modal__close:hover {
  color: #fff;
  border-color: var(--accent);
  background: rgba(193,18,31,0.15);
}

.modal__title {
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: #fff;
  margin-bottom: 12px;
}

.modal__sub {
  font-size: 0.95rem;
  line-height: 1.65;
  color: rgba(255,255,255,0.55);
  max-width: 52ch;
  margin-bottom: 28px;
}

/* The contact section's copy of this form is the wide one; in here it
   fills the panel and owns the bottom edge. */
.modal__form { max-width: none; margin-bottom: 0; }
.modal__form .contact-form__textarea { min-height: 110px; }

@media (max-width: 640px) {
  .modal { padding: 16px; }
  .modal__panel { padding: 32px 22px; max-height: calc(100vh - 32px); }
  .modal__panel::before { left: 22px; right: 22px; }
  .modal__close { top: 14px; right: 14px; }
}

/* ── Ask AI About Me ───────────────────────────────────────────── */
.ask-ai {
  padding: 80px 0;
  background: var(--surface);
  border-top: 1px solid var(--border);
  text-align: center;
}
.ask-ai__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ask-ai__heading {
  font-size: clamp(1.7rem, 3.5vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--fg);
  margin-bottom: 14px;
}

.ask-ai__sub {
  font-size: 0.98rem;
  color: var(--fg-muted);
  line-height: 1.7;
  max-width: 52ch;
  margin-bottom: 32px;
}

.ask-ai__buttons {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 14px;
}

.ask-ai__btn {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-md);
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--fg-muted);
  background: rgba(13,13,11,0.03);
  transition: color 0.2s, border-color 0.2s, transform 0.2s, background 0.2s;
}
.ask-ai__btn svg { width: 17px; height: 17px; flex-shrink: 0; }
.ask-ai__btn:hover {
  color: var(--fg);
  border-color: var(--accent);
  background: var(--accent-soft);
  transform: translateY(-2px);
}
.ask-ai__btn:active { transform: scale(0.98) translateY(0); }

.ask-ai__note {
  margin-top: 20px;
  font-size: 0.78rem;
  color: var(--fg-faint);
}

/* ── Footer ──────────────────────────────────────────────────── */
.footer {
  background: var(--dark-bg);
  border-top: 1px solid var(--dark-border);
  padding: 64px 0 0;
}

/* Brand block on the left, the three link columns on the right. `auto` on the
   second track rather than a fraction so the columns stay the width of their
   own content and the brand paragraph absorbs the slack — a 1fr there stretches
   the gaps between three short link lists into something that reads as
   unrelated groups. */
.footer__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px 48px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 48px 64px;
  align-items: start;
}

.footer__brand {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
  max-width: 34ch;
}
.footer__logo {
  transition: opacity 0.2s;
}

.footer__logo-img {
  height: 36px;
  width: auto;
  display: block;
}
.footer__logo:hover { opacity: 0.6; }

.footer__blurb {
  font-size: 0.88rem;
  line-height: 1.7;
  color: rgba(255,255,255,0.45);
}

.footer__email {
  font-size: 0.9rem;
  font-weight: 700;
  color: #fff;
  border-bottom: 2px solid var(--accent);
  padding-bottom: 2px;
  transition: color 0.2s;
}
.footer__email:hover { color: var(--accent); }

.footer__nav {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 40px;
}
.footer__col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}
.footer__col-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.35);
  margin-bottom: 4px;
}
.footer__link {
  font-size: 0.88rem;
  font-weight: 600;
  color: rgba(255,255,255,0.6);
  transition: color 0.2s;
  white-space: nowrap;
}
.footer__link:hover { color: var(--accent); }

.footer__bar {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 24px 40px;
  border-top: 1px solid var(--dark-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
}
.footer__copy {
  font-size: 0.82rem;
  color: rgba(255,255,255,0.3);
}
.footer__site {
  font-size: 0.82rem;
  font-weight: 600;
  color: rgba(255,255,255,0.4);
  transition: color 0.2s;
}
.footer__site:hover { color: var(--accent); }

/* Declared with the block rather than in the responsive section at the foot of
   this file, because every selector here is a single class with nothing
   outranking it — the .hero--* specificity trap that makes those later blocks
   mandatory does not apply. Two steps: the columns drop under the brand, then
   they go to two-up so four "Journey" links don't become a single tall stack. */
@media (max-width: 860px) {
  .footer { padding-top: 48px; }
  .footer__inner {
    grid-template-columns: 1fr;
    gap: 40px;
  }
  .footer__brand { max-width: none; }
}

@media (max-width: 560px) {
  .footer__nav {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 32px 24px;
  }
  .footer__link { white-space: normal; }
  .footer__bar {
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
  }
}

/* ── Journey Accordion ──────────────────────────────────────── */
.aishowcase {
  background: var(--dark-bg);
  padding: 60px 0 120px;
}

.aishowcase--light {
  background: var(--bg);
}

.aishowcase--light .aishowcase__heading {
  color: var(--fg);
}

.aishowcase--light .aishowcase__desc {
  color: var(--fg-muted);
}

.aishowcase__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
  display: grid;
  grid-template-columns: 1fr 1.4fr;
  gap: 64px;
  align-items: center;
}

.aishowcase__heading {
  font-size: clamp(2.2rem, 3.5vw, 3.2rem);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.035em;
  color: #fff;
  margin-bottom: 20px;
}

.aishowcase__desc {
  font-size: 1.05rem;
  color: var(--dark-muted);
  line-height: 1.7;
  margin-bottom: 36px;
  max-width: 440px;
}

/* Journey slides */
/* The six chapters are stacked in one grid cell so they cross-fade in
   place. Grid (rather than absolute positioning) means the box is as
   tall as the longest chapter automatically: with a fixed height the
   copy wrapped past it in the one-column layout and the "Explore this
   chapter" button landed on top of the accordion below. */
.journey-slides {
  display: grid;
  min-height: 260px;
  margin-bottom: 36px;
}

.journey-slide {
  grid-area: 1 / 1;
  width: 100%;
  opacity: 0;
  transition: opacity 0.45s var(--ease-std);
  pointer-events: none;
}

.journey-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.journey-slide__tag {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 14px;
}

.aishowcase__desc {
  margin-bottom: 0;
}

.journey-slide .btn {
  margin-top: 28px;
}

.journey-slide__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 20px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.02em;
  transition: gap 0.2s var(--ease-std), opacity 0.2s;
}
.journey-slide__link:hover {
  gap: 10px;
  opacity: 0.8;
}

/* Accordion */
.aishowcase__accordion {
  display: flex;
  flex-direction: row;
  gap: 10px;
  height: 450px;
}

.accordion-item {
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  cursor: pointer;
  flex: 0.6;
  min-width: 52px;
  opacity: 0;
  transform: scaleY(0);
  transform-origin: top center;
  transition: flex 0.7s var(--ease-std), opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
}

.accordion-item.acc-in {
  opacity: 1;
  transform: scaleY(1);
}

.accordion-item.is-active {
  flex: 5;
}

.accordion-item__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --crop is set per chapter in the stacked block below and inherits down to
   whichever element is showing the photo. Unset (desktop) means centred. */
.accordion-item__img,
.chapter-card__img { object-position: var(--crop, 50% 50%); }

.accordion-item::before {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  opacity: 1;
  transition: opacity 0.4s ease;
  z-index: 1;
}

.accordion-item.is-active::before,
.accordion-item:hover::before {
  opacity: 0;
}

.accordion-item__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.65) 0%, transparent 50%);
  opacity: 0;
  transition: opacity 0.4s ease;
  z-index: 2;
}

.accordion-item.is-active .accordion-item__overlay {
  opacity: 1;
}

.accordion-item__label {
  position: absolute;
  z-index: 3;
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
  bottom: 50%;
  left: 50%;
  transform: translate(-50%, 50%) rotate(90deg);
  transition: bottom 0.35s var(--ease-out), transform 0.35s var(--ease-out), font-size 0.35s;
}

.accordion-item.is-active .accordion-item__label {
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%) rotate(0deg);
  font-size: 1rem;
}

/* ── Chapter Deck ───────────────────────────────────────────────
   The touch replacement for the accordion: one swipeable card per
   chapter, each carrying its own photo, copy and CTA. Hidden outright
   here and switched on only in the stacked block far below, so it never
   appears on desktop and never appears before initChapterDeck() has
   filled it — an empty deck must not replace a working accordion. */
.chapter-deck { display: none; }

.chapter-deck__track {
  display: flex;
  align-items: stretch;
  gap: 16px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* Cards bleed to both screen edges while still coming to rest flush
     with the section's 24px text column. The negative margin widens the
     scroller past its padded parent; scroll-padding is what puts the
     snapped card back on the column, not the padding alone. */
  margin: 0 -24px;
  padding: 0 24px;
  scroll-padding-left: 24px;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.chapter-deck__track::-webkit-scrollbar { display: none; }

.chapter-card {
  /* Short of a full column on purpose: the sliver of the next card is
     what tells the reader this swipes, replacing the affordance the
     accordion's collapsed bars never had. */
  flex: 0 0 85%;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
}

.chapter-card__media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-bottom: 20px;
}

.chapter-card__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.chapter-card__media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.6) 0%, transparent 45%);
}

.chapter-card__label {
  position: absolute;
  z-index: 1;
  bottom: 14px;
  left: 16px;
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
}

.chapter-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* The cloned copy keeps .aishowcase__heading / __desc, which are sized for
   a half-page column rather than a 291px card. */
.chapter-card__body .aishowcase__heading {
  font-size: clamp(1.5rem, 6vw, 1.9rem);
  margin-bottom: 12px;
}
.chapter-card__body .aishowcase__desc {
  font-size: 0.95rem;
  line-height: 1.6;
}
/* The hand-placed line breaks in the headings are removed by the deck
   builder, not here — see initChapterDeck(). */
/* Cards stretch to the tallest of the six, so pushing the CTA down puts
   every one of them on the same line. */
.chapter-card__body .btn {
  margin-top: auto;
  align-self: flex-start;
}

.chapter-deck__dots {
  display: flex;
  justify-content: center;
  gap: 4px;
  margin-top: 22px;
}

.chapter-deck__dot {
  /* 28px of transparent hit area around an 8px dot — the visible size is
     nowhere near a usable touch target on its own. */
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  background: none;
  position: relative;
  cursor: pointer;
}
.chapter-deck__dot::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: var(--fg-faint);
  opacity: 0.4;
  transition: opacity 0.25s var(--ease-std),
              background 0.25s var(--ease-std),
              transform 0.25s var(--ease-std);
}
.chapter-deck__dot.is-active::before {
  background: var(--accent);
  opacity: 1;
  transform: scale(1.4);
}

/* ── Responsive ──────────────────────────────────────────────── */

/* Tablets — portrait iPads and the like, wide enough for two columns but
   nowhere near desktop proportions.

   The min-width is load-bearing, not tidiness. This block used to be a bare
   `max-width: 1024px` and was entirely inert: `.hero--bust .hero__grid` and
   `.hero--bust .hero__photo-frame` are two-class selectors, media queries add
   no specificity, and so the desktop 1.4fr/1fr survived here — measured at
   820px the grid was still `413px 295px`. Raising these selectors to match
   makes the block live, and without a floor it would then reach past 768 and
   clobber the single-column phone rules up at .hero--bust, which sit earlier
   in the file and would lose the tie.

   The min-height is the other half of that guard, and it is the actual
   predicate rather than a proxy for "tablet". The void this block exists to
   close only opens when the viewport is taller than the figure a half-width
   column can produce — about 730px at 820 wide, 920px at 1024. Below that
   there is nothing to fix and the block would do harm: a landscape phone
   (844x390) and a landscape iPad mini (1024x768) both sit in this width
   range, and a width-driven frame there resolves to 684px and 852px tall
   against viewports of 390px and 768px. */
@media (min-width: 769px) and (max-width: 1024px) and (min-height: 800px) {
  /* An even split, which is what the old block was reaching for. The frame
     holds its 710/1326 ratio, so the extra column width is what becomes
     figure height: 295x551 -> 354x661 at 820. */
  .hero--bust .hero__grid {
    grid-template-columns: 1fr 1fr;
    gap: 32px;
    align-items: end;
  }

  /* The cap has to come off or `min(100%, 46dvh)` just re-imposes the old
     width and the column gains nothing. */
  .hero--bust .hero__photo-frame {
    width: 100%;
    max-width: none;
    height: auto;
  }

  /* What actually strands the figure: the visual column is forced to a full
     viewport, the figure is bottom-aligned inside it, and at this width the
     figure is only half as tall as the column — so it hung 561px below the
     copy with nothing but black above it. Letting the section be as tall as
     its content puts the two columns back on a shared bottom edge. */
  .hero { min-height: auto; }
  .hero__grid {
    min-height: 0;
    padding: 24px 40px 48px;
  }
  .hero--bust .hero__visual {
    position: static;
    height: auto;
  }

  /* Same 3-up as the phone band. A ~354px copy column is too narrow for the
     base flex row, which wrapped the three stats into a lopsided 2 + 1. */
  .hero__metrics {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
  }
  .hero__metric-label { font-size: 0.66rem; }

  /* Nothing left to cue. Dropping the forced 100dvh above means the section
     ends inside the viewport and the band below it is already on screen. */
  .hero__scroll { display: none; }
}


@media (max-width: 768px) {
  .nav__links { display: none; }
  .nav__burger { display: flex; }

  /* Stacked, the figure comes first (order: -1) and everything below it is
     competing for what is left of the fold. The gap and the content's own
     top padding used to spend 72px between the two — that is 72px the CTAs
     were paying for. */
  .hero__grid {
    grid-template-columns: 1fr;
    padding: 24px 24px 64px;
    min-height: auto;
    gap: 24px;
  }
  .hero__content { padding: 8px 0 0; }
  .hero__visual {
    position: static;
    height: auto;
    align-self: auto;
    justify-content: center;
    order: -1;
  }
  /* No max-width: the frame is height-driven at this width (see
     .hero--bust .hero__photo-frame above) and resolves to 185–246px, so a
     420/320px cap never bound anything and only read as if it did. */
  .hero__parallax {
    height: auto;
    margin: 0 auto;
    justify-content: center;
  }
  .hero__circle {
    width: 60vw;
    height: 60vw;
    top: 36%;
    left: 50%;
  }
  .hero__photo-frame {
    height: auto;
    width: 100%;
    aspect-ratio: 3/4;
  }
  .hero__photo-tag {
    bottom: 16px;
    left: 16px;
    font-size: 0.72rem;
    padding: 6px 12px;
  }
  .hero__name { font-size: clamp(3rem, 13vw, 5rem); }
  .hero__title { margin-bottom: 14px; }
  .hero__bio { margin-bottom: 20px; }

  /* Three short stats in a row rather than a column. Stacked they cost 190px
     of a fold that had none to spare; as a 3-up they cost about 77px. */
  .hero__metrics {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
  }

  /* Dropped from the hero, not from the page — the same five links are in
     .contact__social further down. Up here they were below the fold anyway
     and were pushing the hero another ~62px taller. */
  .hero__social { display: none; }

  .hero__scroll { display: none; }

  .journey__inner,
  .expertise__inner,
  .contact__inner { padding: 0 24px; }

  .journey { padding: 80px 0; }
  .journey__header { margin-bottom: 48px; }

  .expertise { padding: 80px 0; }
  .expertise__bento {
    grid-template-columns: 1fr;
    grid-template-rows: auto;
  }
  .bento-card--tech     { grid-column: 1; grid-row: auto; }
  .bento-card--speaking { grid-column: 1; grid-row: auto; }
  .bento-card--medicine { grid-column: 1; grid-row: auto; opacity: 0.75; }
  .bento-card { padding: 32px; }

  /* Top padding is deliberately smaller than the bottom. The logos band
     directly above shares this section's background, so its 32px of bottom
     padding reads as part of the same empty stretch — 80px on top of that
     was 112px of blank column before "Background" appeared. The bottom keeps
     the full 80px, where the next section's dark background does separate
     them. */
  .aishowcase { padding: 32px 0 80px; }
  .aishowcase__inner {
    grid-template-columns: 1fr;
    padding: 0 24px;
    gap: 48px;
  }
  /* Stacked, the chapter images have to come first: they are the tappable
     control for this section, so burying them under a screen of copy hides
     the only way to move through it. The section's own label still belongs
     above them, so .aishowcase__content is dissolved and its two children
     are ordered individually — label, images, chapter copy.

     The cost of dissolving it: .reveal's fade is declared on that element,
     and a `display: contents` box generates none, so the copy arrives here
     without its fade. The accordion above it keeps its own entrance. */
  .aishowcase__content {
    display: contents;
  }
  .aishowcase__content > .section-tag {
    order: 1;
    margin-bottom: 0;   /* the grid's 48px gap already separates it */
  }
  .aishowcase__accordion {
    height: 360px;
    order: 2;
  }
  .aishowcase__content > .journey-slides {
    order: 3;
  }

  .contact { padding: 80px 0; }
  .contact__social { gap: 10px; }
  .contact__social-link span { display: none; }
  .contact__social-link { padding: 10px 12px; }
  .contact__social-link svg { width: 18px; height: 18px; }

  /* Side padding only — the bottom value has to be restated or the columns
     sit flush against the copyright rule. */
  .footer__inner { padding: 0 24px 40px; }
  .footer__bar { padding: 24px; }
  .nav__inner { padding: 0 24px; }
  .nav__mobile { padding: 24px; }
}

@media (max-width: 480px) {
  .hero__grid { padding: 20px 20px 56px; gap: 20px; }
  .hero__content { padding: 0; }
  .hero__location { font-size: 0.7rem; margin-bottom: 12px; }
  .hero__name { font-size: clamp(2.8rem, 13vw, 4rem); margin-bottom: 12px; }
  .hero__bio { font-size: 0.95rem; margin-bottom: 20px; }

  /* Side by side, not stacked: two full-width buttons cost 112px against
     50px for a row, and the second one was the difference between the pair
     landing above the fold and below it. */
  .hero__actions { flex-direction: row; gap: 10px; margin-bottom: 28px; }
  .hero__actions .btn { width: auto; flex: 1 1 0; }

  /* Left alone deliberately — the full-width button is right for the contact
     and modal forms further down, so the hero opts out above rather than
     this opting everyone out. */
  .btn { width: 100%; justify-content: center; }

  .hero__metrics { gap: 8px; padding-top: 20px; margin-bottom: 20px; }
  /* One step down so "Champs Silver Medalist" wraps to two lines rather than
     three in a ~110px cell, which is what sets the row's height. */
  .hero__metric-label { font-size: 0.66rem; }
}

/* Landscape phones — avoid forcing full viewport height with single-column stacking */
@media (max-height: 500px) and (orientation: landscape) {
  .hero { min-height: auto; }
  .hero__grid {
    grid-template-columns: 1fr 1fr;
    min-height: auto;
    padding: 16px 24px 48px;
    gap: 24px;
  }
  .hero__content { padding: 16px 0 0; }
  .hero__visual { order: 0; }
  .hero__photo-frame { aspect-ratio: unset; height: calc(100dvh - 68px); width: auto; }
  .hero__name { font-size: clamp(2rem, 5vw, 3rem); }
  .hero__bio { font-size: 0.9rem; line-height: 1.6; }
  .hero__metrics { gap: 16px; padding-top: 20px; margin-bottom: 20px; }
  .hero__actions { margin-bottom: 28px; }
}

/* ── Company Logos Band ──────────────────────────────────────── */
.logos-band {
  background: var(--bg);
  border-top: 1px solid var(--border);
  padding: 40px 0;
  overflow: hidden;
  display: none;
}
.logos-band.is-ready {
  display: block;
}
.logos-band__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 40px;
}
.logos-band__label {
  text-align: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-faint);
  margin-bottom: 28px;
}
.logos-band__viewport {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, transparent, black 12%, black 88%, transparent);
  mask-image: linear-gradient(to right, transparent, black 12%, black 88%, transparent);
}
.logos-band__track {
  display: flex;
  align-items: center;
  gap: 72px;
  width: max-content;
  animation: logoScroll 60s linear infinite;
}
@keyframes logoScroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.logos-band__item {
  flex-shrink: 0;
  height: 44px;
  display: flex;
  align-items: center;
}
.logos-band__img {
  height: 44px;
  width: auto;
  max-width: 140px;
  object-fit: contain;
  filter: grayscale(100%) opacity(0.45);
  transition: filter 0.3s var(--ease-out);
}
.logos-band__img--lg {
  height: 110px;
  max-width: 340px;
}
.logos-band__item:hover .logos-band__img {
  filter: grayscale(0%) opacity(1);
}
@media (max-width: 600px) {
  .logos-band { padding: 32px 0; }
  .logos-band__inner { padding: 0 24px; }
  .logos-band__track { gap: 48px; }
  .logos-band__item { height: 36px; }
  .logos-band__img { height: 36px; max-width: 110px; }
}

/* ── Touch / narrow: Journey Accordion ──────────────────────────
   Same accordion, turned on its side. Hover can't drive it here, so the
   first tap expands a chapter (initAIAccordion swallows that tap) and the
   second follows the link.

   Turning the column is what makes it fit, not just taste: the flex ROW
   can't compress past 6 × 52px + 5 × 10px gap = 362px, so under about
   410px it overflowed its padded column and pushed the whole page ~11px
   wide. Stacked, the floor is on height instead, which is free to grow.

   Placed after the responsive blocks so it wins on source order. */
@media (hover: none), (max-width: 640px) {
  /* The deck supersedes the accordion here — but only once it has actually
     been built. :has() is what makes that conditional: with JS off nothing
     gets the class, so the accordion below stays and the section still has
     content rather than collapsing to a bare heading. */
  .chapter-deck.is-built {
    display: block;
    order: 2;   /* the slot the accordion held, after the section label */
    /* The parent is a grid, so its `1fr` column is really `minmax(auto,1fr)`
       and the auto floor is this item's min-content width — six cards laid
       out in a row. Without this the column resolves to ~1224px at a 390px
       viewport, the track never scrolls, and the page zooms out to fit. */
    min-width: 0;
  }
  .aishowcase__inner:has(.chapter-deck.is-built) .aishowcase__accordion,
  .aishowcase__inner:has(.chapter-deck.is-built) .journey-slides {
    display: none;
  }

  .aishowcase__accordion {
    flex-direction: column;
    /* Every chapter opens to the same 4:3 window. Sizing each one to its
       own photo instead looked correct in isolation but swung the section
       between 492px and 907px, so a tap shifted the page under the finger
       and pushed the copy it had just swapped off-screen on the portrait
       chapters. One shape keeps the copy in view on all six; the crop is
       then aimed deliberately per photo below, which is the actual cure
       for a bad crop.
       --acc-h is written by initAIAccordion from this ratio and the
       measured width; 540px is the fallback before the script runs. */
    --acc-open-ratio: 0.75;
    height: var(--acc-h, 540px);
    gap: 8px;
    /* .reveal's pair is repeated here on purpose: this selector is later
       in the file, so a bare `transition: height` would win the cascade
       outright and drop the section's entrance fade. */
    transition: height 0.7s var(--ease-std),
                opacity 0.75s var(--ease-out),
                transform 0.75s var(--ease-out);
  }
  .accordion-item {
    min-width: 0;
    min-height: 52px;
    flex: 1;
  }
  /* The flex ratio has to be computed, not fixed: with a constant 0.6/5
     split the five bars grow in proportion to the container, so a tall
     chapter like the portrait Surgery shot lost 65px to them and cropped
     anyway. --acc-grow is set to open/bar, which lands every item on its
     exact target while still animating (flex-grow interpolates, so the
     closing chapter slides shut instead of snapping). */
  .accordion-item.is-active {
    flex-grow: var(--acc-grow, 5);
  }
  /* Aim each crop. The six photos run from 0.56 to 1.78 in ratio, so a
     centred cover crop lands wherever it lands — which is what put a neck
     in one 52px bar and a mouth in another. Held as an inherited custom
     property rather than written straight onto the image, because the
     chapter deck builds its cards from clones that would never match an
     `.accordion-item ...` selector; the builder copies --crop across and
     the value stays defined in exactly one place.

     Declared inside this block on purpose: with no --crop the fallback is
     plain centring, which is what the desktop accordion — a tall narrow
     slot rather than a wide panel — already wants. */
  .accordion-item[href="doctor.html"]  { --crop: 50%  8%; }
  .accordion-item[href="ai-tech.html"] { --crop: 45% 45%; }
  .accordion-item[href="speaker.html"] { --crop: 50% 34%; }
  .accordion-item[href="events.html"]  { --crop: 50% 22%; }
  .accordion-item[href="soldier.html"] { --crop: 50% 20%; }
  .accordion-item[href="athlete.html"] { --crop: 50% 35%; }
  /* Collapsed bars are wide and short, so the sideways label that saves
     width on desktop has nothing to save here — lay it flat, left-aligned,
     and let it slide down to the corner as the chapter opens. */
  .accordion-item__label {
    bottom: 50%;
    left: 18px;
    transform: translateY(50%) rotate(0deg);
    font-size: 0.9rem;
  }
  .accordion-item.is-active .accordion-item__label {
    bottom: 16px;
    left: 18px;
    transform: none;
    font-size: 1.05rem;
  }
}

/* ── Reduced Motion ─────────────────────────────────────────────
   .reveal, .accordion-item and .hero__head3d start at opacity 0 and are
   revealed *by* a transition, so they need explicit visible values here —
   killing the transition alone would leave them invisible. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  .reveal { opacity: 1; transform: none; }
  .accordion-item { opacity: 1; transform: none; }
  .hero__head3d { opacity: 1; }
  .logos-band__track { animation: none; }
  .hero__scroll-line { animation: none; }
  .hero__photo-tag-dot { animation: none; }
}
