/* ===== Design tokens (from Figma) ===== */
:root {
  --dark: #313030;
  --ash-grey: #535252;
  --dark-grey: #cecdcd;
  --light-grey: #d9d9d9;
  --header-grey: #6b6b6b;
  --medium-grey-bg: #efefef;
  --light-grey-bg: #eaeaea;
  --orange: #ff3700;
  --white: #ffffff;

  --side-pad: 64px;
  --max-content: 1216px;
  --font: 'Montserrat', sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* overflow-x here (not on body) intentionally: overflow-x on body with no
   overflow-y set computes overflow-y to `auto` per spec, silently turning
   body into its own scroll container — which breaks position:sticky's
   engage/release math for every sticky element after the first (the sticky
   stack below depends on getting this right for who/what/how in turn). */
html {
  scroll-behavior: smooth;
  overflow-x: hidden;
}

body {
  font-family: var(--font);
  background: var(--white);
  color: var(--dark);
}

img { display: block; max-width: 100%; }

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

section, header, footer { width: 100%; }

/* ===== Reusable type ===== */
.section-title {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  flex-wrap: wrap;
  line-height: 1;
}
.title-bold {
  font-weight: 700;
  font-size: clamp(48px, 8vw, 120px);
  letter-spacing: -2.4px;
  line-height: 1;
}
.title-thin {
  font-weight: 400;
  font-size: clamp(24px, 4.4vw, 64px);
  line-height: 1.4;
}

.text-link {
  display: inline-flex;
  padding: 8px 12px;
  font-size: 20px;
  color: var(--orange);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.logo img { width: 90px; height: 90px; object-fit: cover; }
.logo--light img { opacity: 0.6; }

/* ===== HERO ===== */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* min() of a vw-based and an svh-based clamp: on a normal-height screen
     the vw component wins and this reads exactly as before, but on a
     *short* one (a resized laptop window, a landscape phone) the svh
     component takes over and shrinks it — keeping the whole hero inside
     one screen instead of pushing the menu row below the fold, invisible
     under this section's own overflow:hidden with no scroll to reach it. */
  gap: clamp(16px, min(8vw, 3svh), 120px);
  padding: clamp(16px, 4svh, 40px) 0 clamp(20px, 5svh, 60px);
  overflow: hidden;
  color: var(--light-grey);
}

.hero__bg {
  position: absolute;
  inset: 0;
  width: 103.26%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  z-index: 0;
}

.hero__inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* Gap between the headline+menu block and the "Scroll Down" cue. Same
     min(vw, svh) safety net as .hero's own gap above. */
  gap: clamp(16px, min(14vw, 3svh), 176px);
  flex: 1;
  width: 100%;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 0 var(--side-pad);
  /* Still flex-end (keeps the What/Who/How row's established bottom-
     anchored position), but nudged up off the true bottom edge — with the
     headline trimmed to 84px there was a lot of unused wall space above
     it in the portrait; this reclaims some of it instead of leaving the
     whole block pinned to the floor. */
  padding-bottom: clamp(16px, 5vw, 72px);
}

.hero__content {
  display: flex;
  flex-direction: column;
  /* Gap between the headline and the What/Who/How menu below it. Widened
     so the headline sits higher, clear of the laptop/subject in the
     portrait, rather than running down into them — since .hero__inner
     anchors this whole block to the bottom (flex-end), growing this gap
     pushes the headline's own top edge further up without moving the
     menu row, which stays exactly where it's anchored. */
  gap: clamp(16px, min(20vw, 22svh), 260px);
}

.hero__headline-block {
  display: flex;
  flex-direction: column;
  /* Shifts both headline lines up together, as one unit, by 2 lines' worth
     of the headline's own font-size (matching .hero__headline's min()
     clamp via calc). transform, not margin: .hero__inner bottom-anchors
     .hero__content as a whole (justify-content:flex-end), so a margin
     here that shrinks this block's height just gets re-absorbed — content
     re-anchors lower by the same amount and the headline doesn't visibly
     move at all. transform is layout-inert, so it actually moves the
     headline without .hero__content's measured height (and therefore the
     What/Who/How row's anchored position) changing at all. */
  transform: translateY(calc(-2 * min(84px, 7svh)));
}
/* Figma (node 2683:1332) sits "10 yrs in." directly under "design." with
   no blank-line gap between them — both share one leading-none text block.
   No margin here reproduces that. */
.hero__headline--line2 { margin-top: 0; }

.hero__headline {
  /* Figma (node 2683:1327) has this flat at 96px, but with "10 yrs in."
     now sitting flush under "design." (no blank-line gap — see
     .hero__headline--line2) the block reads taller and starts crowding
     the What/Who/How row below it; trimmed to 84px to give that row its
     own breathing room back. */
  max-width: 633px;
  /* min(), not flat 84px — a short viewport (a resized laptop window, a
     landscape phone) shrinks this via the svh term instead of overflowing
     the single-screen hero. */
  font-size: min(84px, 7svh);
  line-height: 1.15;
  letter-spacing: -1.92px;
  color: #eaeaea;
}
.hero__headline .headline-bold { font-weight: 700; }
.hero__headline .headline-thin { font-weight: 400; font-style: italic; }

.hero__top {
  /* Direct child of .hero (not .hero__inner) so it always spans the true
     full viewport width, like the bg photo — unaffected by the --max-content
     cap that constrains the rest of the hero content. */
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
  padding: 0 var(--side-pad);
}

.hero__name { text-align: right; }
.hero__name .name {
  /* Zeyada (Figma node 2683:1237) — a script/signature face, matching the
     hand-drawn logo mark instead of echoing the site's own Montserrat.
     44px flat in Figma; clamped down for narrow screens rather than left
     flat, same reasoning as everywhere else in the hero. */
  font-family: 'Zeyada', cursive;
  font-size: clamp(28px, 3.5vw, 44px);
  color: var(--dark-grey);
  line-height: 1.1;
}
.hero__name .role {
  font-size: clamp(12px, 1.4vw, 16px);
  font-weight: 500;
  color: var(--white);
  line-height: 1.4;
}

/* Figma (node 2683:1327) moves What/Who/How from a vertical stack into a
   horizontal row, arrow pointing straight down under each — replacing the
   separate "Scroll Down" cue entirely (the arrows now do that job). Node
   2683:1237 then brings back the hover-only reveal on the sub-line (see
   .feature-sub below) — What/Who/How plus the arrow stay visible by
   default; "I do ?"/"am I ?"/"to reach me ?" only show on hover. */
.hero__menu {
  display: flex;
  flex-direction: row;
  /* flex-start, not flex-end: on narrow screens "to reach me ?" wraps to
     two lines while What/Who's single-line subtitles don't, and bottom-
     aligning the row would pull "How" itself out of line with "What"/
     "Who" to keep the arrows flush instead — keeping the primary words
     level matters more here than the small arrows underneath. */
  align-items: flex-start;
  gap: clamp(12px, min(4vw, 1.5svh), 60px);
  /* Breathing room below the row so the arrows don't crowd the hero's own
     bottom edge (Figma pairs its 60px outer padding with a further 64px
     on the content block itself). Capped by svh too, same reasoning as
     everywhere else in the hero — this is the row most likely to actually
     get pushed off a short screen since it's last in the stack. */
  padding-bottom: clamp(8px, min(6vw, 2svh), 64px);
}

.hero__menu-item {
  /* Figma (node 2683:1333) centers the whole item — word, sub-label, and
     arrow all share one center line per column — rather than left-aligning
     it inside its flex:1 slot. */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(8px, 1.5vw, 12px);
  flex: 1 1 0;
  min-width: 0;
  color: #eaeaea;
  /* How far the arrow drops on hover — and, via .feature-sub's own height,
     how tall the sub-label's reveal box is, so the two stay in sync from
     one shared value instead of two separately-tuned clamps. */
  --menu-reveal-shift: clamp(16px, 4.5vw, 60px);
}

.hero__menu-labels {
  /* Sub-label is position:absolute (see .feature-sub) so it never
     contributes to this box's own height — .hero__menu-item's total
     height (and therefore .hero__menu's) stays constant whether an item
     is hovered or not. Without that, revealing it would grow the row and,
     since .hero__inner anchors this whole block to the bottom (flex-end),
     push the headline above it around on every hover — one item reacting
     shouldn't move its siblings or the text above them. */
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  text-align: center;
}

.feature {
  font-size: clamp(18px, min(5vw, 3svh), 64px);
  font-weight: 400;
  letter-spacing: -2.4px;
  line-height: 1;
}
/* Hover-only reveal (Figma node 2683:1237) — hidden until the item is
   hovered (or focused), on every device, no touch-device fallback.
   Absolutely positioned right under the word, out of flow, so revealing
   it never resizes .hero__menu-labels/.hero__menu-item — see the comment
   on .hero__menu-labels. The arrow below animates down via `transform`
   (also layout-inert) instead of real spacing, so the whole reveal is
   paint-only: nothing outside the hovered item itself moves. */
.feature-sub {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  /* Same height as the arrow's own translateY below (--menu-reveal-shift)
     — makes this box exactly as tall as the gap the arrow opens up, so
     centering the text inside it (align-items) puts "to reach me ?"
     squarely in the middle of that reveal, not stuck against its top. */
  height: var(--menu-reveal-shift);
  font-size: clamp(11px, min(3.4vw, 3svh), 48px);
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1.4;
  /* nowrap, not normal — "to reach me ?" wrapping mid-phrase dropped its
     "?" onto a second line, landing right on top of the arrow (which only
     makes room for one line when it animates down). */
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.hero__menu-item:hover .feature-sub,
.hero__menu-item:focus-visible .feature-sub {
  opacity: 1;
}
.hero__menu-item .arrow-icon { transition: transform 0.25s ease; }
.hero__menu-item:hover .arrow-icon,
.hero__menu-item:focus-visible .arrow-icon {
  transform: translateY(var(--menu-reveal-shift));
}

.arrow-icon {
  display: inline-flex;
  padding: 6px 4px;
  width: 24px;
  flex-shrink: 0;
}
.arrow-icon svg {
  width: 15px;
  height: 12px;
  /* Same down-pointing formula the old "Scroll Down" icon used (rotate
     first, then flip — not the reverse, which points sideways instead). */
  transform: rotate(-90deg) scaleY(-1);
}

/* ===== STICKY NAV WRAPPER (Who + What + How sections) ===== */
.nav-wrap { position: relative; }

/* A position:sticky element only gets a real pin range for as long as
   something else follows it inside the same containing block — with
   nothing after it, its own bottom edge and the containing block's bottom
   edge are the same point, so it "sticks" for zero pixels of scroll and
   just behaves like static content. How is the last section, so this
   gives it that trailing room (kept small since nothing needs to show
   through it — How simply holds its pinned position across it). */
.stack-tail { height: 0; } /* sized in script.js, off measured section heights */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 20;
  /* 76px total — 12px of top padding + What's own 64px height, exactly,
     so the bar's bottom edge lands flush with What's bottom (What never
     grows, unlike Who/How, which are meant to overflow past this edge
     once active — see .nav-tab.is-enabled below). Height, not just
     padding, since script.js measures this element's real rendered
     height (getBoundingClientRect) to size --nav-h and every section's
     own sticky offset off of it — growing it here is what they need to
     read the extra padding too, with no JS changes required. */
  height: 76px;
  padding-top: 12px;
  box-sizing: border-box;
  /* While hidden (over the hero), match What's bg (the first section in the
     stack) so the empty nav strip blends straight into it instead of
     flashing the wrong color. --nav-progress (set continuously in
     script.js) still tracks the hero's scroll, but nothing here is bound
     to it anymore — the bar snaps straight to its real bg + full content,
     no fade, the instant .is-visible flips at progress 1 (exactly when
     What snaps to the top). */
  background: var(--white);
}
.site-nav.is-visible {
  background: var(--header-grey);
}
.site-nav__inner {
  display: flex;
  align-items: center;
  gap: 12px;
  height: 100%;
  width: 100%;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 0 var(--side-pad);
  /* Hard cut, not a fade: opacity flips 0→1 in the same instant
     .is-visible lands (see script.js's progress>=1 toggle), so the whole
     bar — bg, logo, tabs — appears together exactly when What's snap
     animation finishes, instead of gradually over the hero scroll. */
  opacity: 0;
  pointer-events: none;
}
.site-nav.is-visible .site-nav__inner {
  opacity: 1;
  pointer-events: auto;
}
.site-nav.is-visible .site-nav__inner {
  pointer-events: auto;
}
.logo--dark img { opacity: 1; }
.site-nav .logo img { width: 52px; height: 52px; }

/* Kebab toggle for the mobile nav (see the 600px breakpoint below, where
   the folder-tab row gives way to a plain dropdown list) — hidden here by
   default so it never shows above that breakpoint. */
.nav-kebab {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  border-radius: 9999px;
  background: none;
  cursor: pointer;
  flex-shrink: 0;
  transition: background-color 0.2s ease;
}
.nav-kebab span {
  display: block;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--dark);
  transition: opacity 0.2s ease;
}
/* Open state: a plain hover-style highlight behind the (unchanged) dots
   is enough feedback that the menu is expanded — dots morphing into
   something else isn't the kebab convention the way bars-into-an-X is
   for a hamburger. */
.nav-kebab[aria-expanded="true"] { background: var(--medium-grey-bg); }

.site-nav__links {
  position: relative;
  display: flex;
  flex: 1;
  /* flex-start, not stretch: a reached tab now grows *taller* than the
     74px row height (see .nav-tab.is-enabled below), and stretch would
     otherwise squeeze it back down to match its siblings. flex-start on
     this row lets each tab hang however far past 74px it needs to. */
  align-self: flex-start;
  align-items: flex-start;
  justify-content: flex-end;
  gap: 1px;
  flex-wrap: wrap;
}

/* Flat-pill nav, per the Figma reference (nodes 2572:595, 2573:1608,
   2575:1869) — every tab is 142px/74px tall by default with a 1px gap
   (.site-nav__links), 3×142 + 2×1 = 428px total, which is the row's own
   fixed frame width and never grows past it. A reached tab (is-enabled)
   widens to fit its own label (What 155px, Who 166px, How 142px) without
   pushing that frame wider: it carries a negative margin-right equal to
   its own extra width, so its *flow* contribution stays 142px and the
   overflow visually overlaps onto whichever tab comes after it — the
   classic "physical tab" effect, just on flat rounded pills now instead
   of the old curved flare. Whichever tab is current (is-active) gets the
   highest z-index so it's the one sitting on top at that overlap seam,
   regardless of whether it's overlapping forward onto a not-yet-reached
   tab or sitting underneath an earlier reached one's overlap; reached
   tabs otherwise stack above plain idle ones. Pure visual mechanics only
   — is-enabled/is-active are still the exact same classes script.js
   already toggles off the same scroll math as before; nothing about the
   reveal timing, sticky pinning, or nav-progress fade changed. */
.nav-tab {
  position: relative;
  display: flex;
  /* flex-start + a fixed padding-top (measured off the old center-aligned
     64px box: 22.5px), not align-items:center — center would re-center
     the text within each tab's *own* height once those start differing
     (see is-enabled below), sinking "Who"/"How"'s label 6px/12px below
     "What"'s. A constant top offset keeps every label at the exact same
     height regardless of how far its own tab reaches down below that. */
  align-items: flex-start;
  justify-content: center;
  height: 64px;
  width: 142px;
  padding: 22.5px 24px 0;
  box-sizing: border-box;
  font-size: 20px;
  font-weight: 700;
  color: var(--white);
  background: #5a5959;
  border-radius: 24px 24px 0 0;
  white-space: nowrap;
  z-index: 1;
  transition: color 0.3s ease, background-color 0.3s ease, width 0.3s ease, height 0.3s ease, margin-right 0.3s ease;
}

.nav-tab.is-enabled {
  background: var(--tab-bg);
  z-index: 2;
}
/* Figma gives each reached tab its own fixed width (2573:1842/1608), not a
   shared auto-hug. margin-right is the overlap: 142px minus that tab's own
   width, so the extra width it grew by overlaps forward onto the next tab
   instead of growing the row. What is never overlapped from the left (it
   has no earlier tab), so its padding stays symmetric; Who/How can be, so
   their text nudges 12px right (asymmetric padding) to stay centered in
   whatever's left visible instead of crowding the overlapped seam.
   Height is the other half of "touches the page": once reached, each tab
   grows exactly as far past the base 64px as its own section sits below
   the nav (0/12/24 — same numbers .who/.how's own `top` uses), so its
   bottom edge lands flush against that section's snapped top instead of
   leaving a gap. Growing straight down (not centering per-tab, see the
   comment above) is what keeps the labels level despite it. */
.nav-tab.is-enabled[data-nav-tab="what"] { color: var(--dark); --tab-bg: var(--white); width: 155px; height: 64px; margin-right: -13px; }
.nav-tab.is-enabled[data-nav-tab="who"] { color: var(--dark); --tab-bg: var(--light-grey-bg); width: 166px; height: 76px; margin-right: -24px; padding: 22.5px 24px 0 36px; }
.nav-tab.is-enabled[data-nav-tab="how"] { color: var(--light-grey); --tab-bg: var(--dark); width: 142px; height: 88px; margin-right: 0; padding: 22.5px 24px 0 36px; }

.nav-tab.is-active {
  z-index: 3;
}

/* The active tab's own color continuing rightward as a strip to the
   page's edge — reads as that section's color already "underneath"
   whatever hasn't been reached yet. A plain sibling element (see
   .nav-tab-bar in index.html) driven by :has(), not a pseudo-element of
   .nav-tab.is-active: that tab now carries a real z-index for the overlap
   above, which would otherwise trap a ::after here in its own stacking
   context and stop it from sitting behind later sibling tabs the way it
   needs to. z-index:0 — below every .nav-tab (idle 1, enabled 2, active
   3) uniformly, so it stays tucked behind all of them the same way
   regardless of which one is currently active. width:100vw run past the
   row's own right edge is caught harmlessly by html's own
   overflow-x:hidden. Height matches each section's own reveal depth
   (0/12/24 — same numbers the old flare's scaleY used), so the strip is
   flush for What, and a 12px/24px sliver for Who/How. */
.nav-tab-bar {
  position: absolute;
  left: 100%;
  bottom: 0;
  width: 100vw;
  height: 0;
  z-index: 0;
  pointer-events: none;
}
.site-nav__links:has(.nav-tab[data-nav-tab="who"].is-active) .nav-tab-bar {
  height: 12px;
  background: var(--light-grey-bg);
}
.site-nav__links:has(.nav-tab[data-nav-tab="how"].is-active) .nav-tab-bar {
  height: 24px;
  background: var(--dark);
}

/* ===== WHAT SECTION (first in the stack — pins once fully read) ===== */
/* Unlike who/how, what doesn't pin flush under the nav the moment it's
   reached — its content (case studies + side projects) runs well past one
   screen, so it needs to stay plain document flow (fully, normally
   scrollable) until its own bottom has scrolled into view. --what-top
   (script.js) is a *negative* offset sized off what's real height, so
   sticky only engages once that's happened — see the comment there for
   the mechanics. Once pinned, who (next, still plain flow at that point)
   keeps sliding up on its own and covers what's now-frozen frame, leaving
   the 12px reveal sliver above it once who itself settles. */
.what {
  position: sticky;
  top: var(--what-top, 0px);
  z-index: 1;
  min-height: 100svh;
  background: var(--white);
  padding: 176px 0 120px;
}
.what__inner {
  display: flex;
  flex-direction: column;
  gap: 96px;
  width: 100%;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 0 var(--side-pad);
}
/* Pulls just the "Case studies" group up closer to "What I do?" — the
   flex gap above is shared with the (unchanged) gap before "Side
   projects", so this is a targeted margin instead of a smaller gap. */
.what__inner > .section-title { margin-bottom: -48px; }

.projects-group {
  display: flex;
  flex-direction: column;
  gap: 48px;
}
.group-title {
  font-size: clamp(28px, 4vw, 44px);
  font-weight: 400;
  line-height: 1.2;
}

/* ===== WHO SECTION (second — pins 12px below the nav) ===== */
.who {
  position: sticky;
  top: calc(var(--nav-h, 98px) + 12px);
  z-index: 2;
  min-height: 100svh;
  background: var(--light-grey-bg);
  padding: 176px 0 120px;
  /* Same runway trick as .what's padding-bottom, sized for How's slide. */
  padding-bottom: calc(120px + var(--who-pad, 0px));
}
/* The 12px gap above (left by .who's own `top` offset) reveals whatever of
   What's pinned frame is sitting behind it — which, since what is pinned
   bottom-anchored against a much taller scroll of content, is generally an
   actual sliver of a card or line of text, not blank space. A solid bar in
   What's own white masks that down to a clean edge instead of a
   distracting fragment of stale content peeking through. */
.who::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: -12px;
  height: 12px;
  background: var(--white);
}
.who__content {
  display: flex;
  /* flex-end, not center — pins the quote's last line to "View my
     resume"'s baseline exactly, rather than relying on the two columns'
     heights happening to be close enough for center to look aligned. */
  align-items: flex-end;
  gap: 24px;
  flex-wrap: wrap;
  width: 100%;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 0 var(--side-pad);
}
.who__left, .who__right {
  flex: 1 1 480px;
  min-width: 0;
}
.who__left {
  display: flex;
  flex-direction: column;
  gap: var(--who-left-gap, clamp(32px, 6vw, 96px));
  color: var(--dark);
}
.who__right {
  /* .who__content bottom-aligns the two columns, which would line the
     quote up with "View my resume" (.who__left's actual last child) —
     pulling this up by that link's own height + the gap above it lines
     the quote's last line up with "Want to see..." (text-block's last
     line) instead, which is the pairing that actually reads as matching. */
  margin-bottom: calc(var(--who-left-gap, clamp(32px, 6vw, 96px)) + 40px);
}

.text-block {
  display: flex;
  flex-direction: column;
  gap: 20px;
  /* Matches the case-study pages' own body copy size (17px) instead of
     running noticeably bigger than the rest of the site. */
  font-size: clamp(15px, 1.6vw, 17px);
  line-height: 1.4;
  max-width: 560px;
}

.who__quote {
  /* Sized so the block's total height roughly matches the left column's
     bio text-block (title through "...into practice?"), not tied to its
     20px body font itself. */
  max-width: 560px;
  /* 64px flat in Figma (node 2659:548). */
  font-size: clamp(28px, 4.4vw, 64px);
  font-weight: 700;
  /* 1.2, not 1 — matches Figma (node 2659:548) exactly, and the taller
     lines also help this block's total height keep pace with the left
     column now that its own title runs bigger (see .title-thin). */
  line-height: 1.2;
  letter-spacing: -1.48px;
  color: var(--dark-grey);
}
/* Starts uniformly light; script.js wraps each word in a .word span and
   darkens them in reading order as you scroll through Who's pinned
   window, so the quote visually "writes in" line by line. */
.who__quote .word {
  color: var(--dark-grey);
  transition: color 0.4s ease;
}
.who__quote .word.is-revealed { color: var(--dark); }

.projects {
  display: flex;
  flex-wrap: wrap;
  gap: 48px 24px;
}

.tile {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 24px;
  border: 1px solid var(--light-grey);
  border-radius: 24px;
  transition: border-color 0.2s ease;
}
.tile:hover { border-color: var(--dark); }
.tile__img {
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  background: var(--medium-grey-bg);
}
.tile__img img { width: 100%; height: 100%; object-fit: cover; }
.tile__content { display: flex; flex-direction: column; gap: 24px; }
.tile__meta { font-weight: 500; }
.tile__text { font-size: 14px; line-height: 1.4; }

.tile__heading { display: flex; flex-direction: column; }
.tile__pills { display: flex; flex-wrap: wrap; gap: 8px; }
.pill {
  background: var(--medium-grey-bg);
  color: var(--dark);
  font-size: 14px;
  font-weight: 500;
  padding: 4px 14px;
  border-radius: 9999px;
}

.tile--large { flex: 1 1 480px; }
.tile--large .tile__img { aspect-ratio: 630 / 475; }
.tile--large h3 { font-size: clamp(28px, 4vw, 44px); font-weight: 700; line-height: 1.4; }
.tile--large .tile__heading { gap: 12px; }

.hobby {
  display: flex;
  flex-direction: column;
  gap: 48px;
  /* Extra runway below the side-projects row, sized off the viewport (not
     a fixed px) so it holds up across screen heights — gives the tiles
     room to settle rather than finishing right at the bottom edge as What
     runs out. Trimmed twice now (50svh → 25svh → this): still read as too
     much once the arrow nav and equal-height cards were in place, so cut
     another 3/4 down to a quarter of that. */
  padding-bottom: 6.25svh;
}
.hobby__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.hobby__title { font-size: clamp(28px, 4vw, 44px); font-weight: 400; line-height: 1.2; }

.hobby__nav { display: flex; gap: 12px; }
.hobby__arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--light-grey);
  border-radius: 9999px;
  background: none;
  color: var(--dark);
  cursor: pointer;
  transition: border-color 0.2s ease, opacity 0.2s ease;
}
.hobby__arrow svg { width: 20px; height: 20px; }
.hobby__arrow:hover { border-color: var(--dark); }
/* Set in script.js off the row's actual scroll position — hidden entirely
   when every tile already fits (nothing to scroll to), and dimmed +
   inert at whichever end the row is currently scrolled to. */
.hobby__nav.is-unnecessary { display: none; }
.hobby__arrow:disabled {
  opacity: 0.35;
  cursor: default;
  pointer-events: none;
}

.projects--small {
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 8px;
}
.projects--small::-webkit-scrollbar { display: none; }
.tile--small {
  flex: 0 0 376px;
  max-width: 376px;
  scroll-snap-align: start;
  padding: 12px;
  gap: 12px;
  border-radius: 12px;
}
.tile--small .tile__img { aspect-ratio: 376 / 286; }
.tile--small h4 { font-size: 24px; font-weight: 700; line-height: 1.4; }
.tile--small .tile__meta { font-size: 16px; }

/* ===== HOW / FOOTER (third — pins 24px below the nav, last in the stack) ===== */
.how {
  position: sticky;
  /* Set by script.js (updateStackMetrics) — navHeight + 24px on
     desktop/tablet (matches what/who's own staggered peek-through gap),
     but collapses to just navHeight on mobile so how fills the rest of
     the screen completely as the final scroll, nothing peeking above it. */
  top: var(--how-top, 100px);
  z-index: 3;
  background: var(--dark);
  color: var(--light-grey);
  padding: 96px 0 48px;
  /* Always at least fills the viewport below the sticky nav (see script.js,
     which keeps this in sync with the nav's real height), so scrolling to
     the very bottom of the page always lands squarely on the How section
     instead of stopping short of it with the "How" tab already active. */
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
}
/* Same masking as .who::before, for the gap above How that peeks Who's
   frame instead — sized off --how-top/--nav-h so it automatically becomes
   0 height on mobile, where script.js collapses that gap to nothing. */
.how::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: calc(-1 * (var(--how-top, 100px) - var(--nav-h, 98px)));
  height: calc(var(--how-top, 100px) - var(--nav-h, 98px));
  background: var(--light-grey-bg);
}
.how__inner {
  display: flex;
  flex-direction: column;
  gap: 24px;
  width: 100%;
  max-width: var(--max-content);
  margin: 0 auto;
  padding: 0 var(--side-pad);
  flex: 1;
}
.section-title--footer { color: var(--light-grey); }

.how__card {
  display: flex;
  flex-wrap: wrap;
  gap: 48px;
  align-items: stretch;
  padding: 24px;
  border: 1px solid var(--ash-grey);
  border-radius: 24px;
}
.how__card-img {
  flex: 1 1 480px;
  aspect-ratio: 630 / 475;
  border-radius: 8px;
  overflow: hidden;
}
.how__card-img img { width: 100%; height: 100%; object-fit: cover; }

.how__card-content {
  flex: 1 1 400px;
  display: flex;
  flex-direction: column;
  gap: 24px;
  justify-content: center;
  padding: 24px 0;
}
.how__card-name {
  font-size: clamp(36px, 5vw, 90px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -1.8px;
  color: var(--dark-grey);
}

.how__contact-details {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-bottom: 24px;
}
.how__contact-item {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  font-size: 20px;
  font-weight: 400;
  color: var(--light-grey);
  transition: color 0.2s ease;
}
.how__contact-text {
  /* The email has no spaces to wrap at, so without min-width:0 a flex
     item won't shrink below its unbroken text's natural width — it'd
     just overflow past the card edge instead of breaking onto a line. */
  min-width: 0;
  overflow-wrap: break-word;
}
.how__contact-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  flex-shrink: 0;
}
.how__contact-icon img { width: 17px; height: auto; }
.how__contact-icon svg { width: 20px; height: 20px; }
.how__contact-item:hover { color: var(--orange); }
.how__contact-item:hover .how__contact-icon { color: var(--orange); }

.how__cta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.how__btn {
  padding: 12px 24px;
  background: var(--orange);
  border: 1px solid var(--orange);
  border-radius: 9999px;
  font-size: 20px;
  font-weight: 600;
  color: var(--white);
  transition: opacity 0.2s ease;
}
.how__btn:hover { opacity: 0.85; }

/* Full-width bar pinned to the very bottom of the footer, sitting below
   .how__inner (which takes flex:1 and absorbs all the extra height), so
   this row always lands flush with the bottom of the section. */
.how__bottom-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 16px;
  /* Unlike every other inner content wrapper on the site, this one is
     deliberately NOT capped to --max-content — the icon and copyright
     line should ride the true left/right edges of the viewport on any
     screen width, not sit centered in a --max-content column with dark
     empty margins on either side. */
  width: 100%;
  padding: 12px var(--side-pad);
  border-top: 1px solid rgba(217, 217, 217, 0.15);
}

.how__socials { display: flex; gap: 12px; }
.how__social-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--dark-grey);
  transition: color 0.2s ease;
}
/* Sized to match the copyright text's own line-height (12px font, 1.4
   line-height) rather than the old fixed 36px circular button. */
.how__social-icon svg { width: 16.8px; height: 16.8px; }
.how__social-icon:hover { color: var(--orange); }

.how__copyright {
  text-align: right;
  font-size: 12px;
  line-height: 1.4;
  color: var(--dark-grey);
}

/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
  :root { --side-pad: 32px; }

  .who__content { flex-direction: column; gap: 32px; }
  .who__left, .who__right { flex: 1 1 auto; }
  .text-block { max-width: 100%; }

  .hero__bg { object-position: 70% center; }

  /* Scaled down from the 96px/633px desktop default, keeping roughly the
     same proportions so the five-line wrap still reads similarly here,
     just smaller — a flat 96px at tablet widths would either overflow or
     force a much narrower column than there's room for. */
  .hero__headline { font-size: min(clamp(40px, 8vw, 72px), 7svh); max-width: 480px; }

  /* Header nav is deliberately left untouched here — tablet keeps the
     exact desktop nav (fixed 76px bar, 52px logo, the folder-tab pill
     row, no wrapping) all the way down to the 600px breakpoint, where it
     switches straight to the mobile hamburger instead. The height:auto +
     wrapping + bigger-logo overrides that used to live here only made the
     bar taller and let the tab row wrap onto a second line once it ran
     out of horizontal room — broken, not a real intermediate design. */

  .how__card { flex-direction: column; }
  .how__card-img { flex-basis: auto; width: 100%; }
}

@media (max-width: 600px) {
  :root { --side-pad: 20px; }

  .hero { padding-top: 24px; padding-bottom: 40px; }
  .hero__top { flex-direction: row; align-items: flex-start; }

  /* Same proportions again, scaled down further for phone widths. */
  .hero__headline { font-size: min(clamp(28px, 10vw, 44px), 7svh); max-width: 320px; }

  /* Tighter than the 900px tablet rule above (16px 32px): shorter
     top/bottom padding caps the bar's height down to 76px (52px logo +
     12px each side — the same 76px the desktop nav already uses, not an
     arbitrary number), and the side padding switches from a flat 32px to
     --side-pad so the bar lines up with every other section's edges
     instead of running its own, wider inset. */
  .site-nav__inner { padding: 12px var(--side-pad); }

  /* The folder-tab row (fixed-width flared pills, built for a wide bar)
     doesn't have room to work on a phone screen, so it's replaced outright:
     .nav-kebab takes its place in the bar, and .site-nav__links becomes a
     plain dropdown list that only appears once toggled open (see
     script.js for the open/close handling). */
  .nav-kebab { display: flex; margin-left: auto; }
  .site-nav__links {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 12px var(--side-pad) 20px;
    background: var(--header-grey);
    box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
  }
  .site-nav__links.is-menu-open { display: flex; }
  /* Same selector (and so same specificity) as the desktop overlap rules
     above, just declared later — the folder-pill overlap doesn't apply to
     a vertical stacked list, enabled or not, so this flattens it for both. */
  .nav-tab:not(:first-child) { margin-left: 0; }
  .nav-tab {
    width: 100%;
    height: auto;
    padding: 14px 16px;
    justify-content: flex-start;
    font-size: 17px;
    border-radius: 12px;
  }
  .nav-tab.is-enabled {
    width: 100%;
    min-width: 0;
    padding: 14px 16px;
    background: var(--tab-bg);
    border-radius: 12px;
  }
  /* Kill the desktop active-tab extending color bar entirely — it's a
     connector reading "this section's color continues rightward along
     the bar", which doesn't exist in this stacked-list layout. Also drop
     the desktop overlap: margin-right/z-index-stacking assume a single
     right-aligned row, not a vertical list. */
  .nav-tab-bar { display: none; }
  .nav-tab.is-enabled { margin-right: 0; z-index: auto; }
  /* The per-tab height growth (74/86/98) is a desktop-only "touches the
     page" effect for the right-aligned pill row — its selectors are
     specific enough to otherwise win over the plain .nav-tab{height:auto}
     above despite coming first in the file, so it needs its own explicit
     reset here rather than relying on source order. */
  .nav-tab.is-enabled[data-nav-tab="what"],
  .nav-tab.is-enabled[data-nav-tab="who"],
  .nav-tab.is-enabled[data-nav-tab="how"] {
    height: auto;
  }

  .hero__bg { object-position: 78% center; }

  /* Only Home and How fit a single mobile screen outright — What and Who
     both run well past one, so on top of the same pin-and-cover stacking
     desktop uses, script.js also keeps translating each one's own content
     upward internally once pinned (see the What/Who force-pin IIFEs)
     until its bottom has fully cleared the frame, only then handing off
     to the next section's own snap-and-cover. Plain position:static is
     just the at-rest fallback here — script.js switches each to fixed
     inline for the ranges it's actually pinned. */
  .what { position: static; top: auto; padding-top: 60px; padding-bottom: 60px; }
  .what__inner { gap: 48px; }
  /* No --who-pad runway needed here (unlike desktop) — once who is
     force-pinned, the spacer script.js inserts in its place is sized to
     who's own full natural height, so how's own engage point is already
     pushed back exactly that far without any extra padding.
     position:relative (not static) is deliberate, unlike what above:
     static elements are ignored for z-index entirely, so while who is
     still just scrolling by normally (pre-engage), what's own fixed,
     opaque frame — a *positioned* element — would paint over it
     regardless of DOM order, hiding who completely until the instant it
     also becomes fixed (an abrupt pop, not a stack). Relative behaves
     identically to static for layout, but lets who's z-index:2 (set
     above) win against what's z-index:1 the whole time, so who is
     visibly sliding up from the bottom the entire approach, not just at
     the moment it locks. What doesn't need this — nothing else is ever
     competing to render above *it* while it's merely at rest. */
  .who { position: relative; top: auto; padding-top: 60px; padding-bottom: 60px; }
  /* No sticky pin here (see above), so there's no reveal gap left for
     .who::before to mask — without this it'd just sit as a stray white
     bar in normal flow. */
  .who::before { display: none; }
  /* The desktop rule's tight leading/tracking (line-height:1, -1.48px)
     suits a big, short display headline — at mobile's clamp-floor size
     (28px) the same quote wraps across several lines, and that treatment
     turns cramped rather than punchy. Bigger, looser, and more open here
     for actual reading; word-by-word reveal logic (script.js) untouched. */
  .who__quote { font-size: 34px; line-height: 1.3; letter-spacing: -0.5px; }

  .tile--small { flex: 0 0 78%; max-width: 78%; }

  .how { padding: 32px 0 32px; }
  .how__inner {
    padding: 0 24px;
    /* Below, the min-height JS sets on .how (fills the viewport under the
       nav, so the whole page settles exactly at How on desktop) leaves
       real slack here on a phone-sized viewport once the card is this
       compact — center the title+card block in it rather than pinning it
       to the top and leaving one big gap before .how__bottom-bar. */
    justify-content: center;
  }
  .how__bottom-bar { padding: 12px 24px; }
  .how__card { padding: 16px; gap: 16px; }
  .how__card-img { max-height: 34svh; aspect-ratio: auto; }
  .how__card-content {
    /* Was flex:1 1 400px + justify-content:center (see the base rule) —
       fine when the card has room to spare on desktop, but on a phone
       that stretches this to fill the card's leftover height and then
       centers within *that*, opening a large gap between the photo and
       the name. Compact + top-aligned instead, so title/card/footer
       actually fit in one screen without scrolling. */
    flex: 0 0 auto;
    justify-content: flex-start;
    gap: 16px;
    padding: 0;
  }
  .how__card-name { font-size: 32px; }
  .how__contact-details { padding-bottom: 8px; }
  .how__contact-item { font-size: 16px; gap: 6px; }
  .how__btn { font-size: 14px; padding: 10px 18px; }
}

/* ---- Shared minimal scroll-reveal (see reveal.js) ----
   Same effect as design-system.html's own reveal system, applied here so
   index.html reads as part of the same site. Safe default: fully visible
   with no JS / reduced motion; JS opts elements into a fade+rise only
   once confirmed running. */
[data-reveal] { opacity: 1; transform: none; }
html.reveal-js [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
html.reveal-js [data-reveal].is-visible { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  html.reveal-js [data-reveal] { opacity: 1; transform: none; transition: none; }
}
