/* ===== CSS Variables ===== */
:root {
  --bg: #111323;
  --cream: #F9F5EB;
  --cream-dark: #EDEAE4;
  --navy-light: #B3BAE1;
  --gray-navy: #5A5D70;
  --overlay: rgba(249, 245, 235, 0.04);
  --nav-bg: rgba(12, 15, 32, 0.8);
}

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

/* ===== Base ===== */
html,
body {
  width: 100%;
  background-color: var(--bg);
  font-family: "Figtree", sans-serif;
  font-weight: 400;
  color: var(--cream);
  /* `clip` (not `hidden`) prevents horizontal overflow WITHOUT turning the
     element into a scroll container — `hidden` would make overflow-y compute to
     `auto`, which breaks position: sticky on the sidebars. */
  overflow-x: clip;
}

/* Hide scrollbar but keep scrolling functional */
html {
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* IE / legacy Edge */
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
  display: none;                /* Chrome / Safari / WebKit */
}

/* Media never overflows its container */
img,
video {
  max-width: 100%;
  height: auto;
}

/* Per-section identity header — only shown on mobile (< 768px), where the
   shared left sidebar is hidden. See the responsive block at the end. */
.section-identity {
  display: none;
}

/* Utility to temporarily hide a section/block without deleting its markup.
   !important so it wins over later/breakpoint display rules (e.g. .mid-block). */
.hidden-section {
  display: none !important;
}

/* ===== Layout ===== */
main {
  width: 100%;
  /* No curtain offset: #home sits at scroll position 0, always present behind
     the fixed splash overlay. The splash fades away on scroll to reveal it. */
}

section {
  width: 100%;
  min-height: 100vh;
}

/* ===== #splash — full-viewport intro overlay ===== */
#splash {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1000;
  width: 100%;
  height: 100vh;
  background-color: var(--bg);
  /* Figma 662:8524 — column, horizontally centered, 120px padding all sides. */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 120px;
  /* Exit (opacity 1→0, scale 1→0.95) is driven on scroll in JS; scales from its
     center so all content shrinks as one unit. */
  transform-origin: center center;
  will-change: opacity, transform;
}

/* Star field — full-bleed layer behind the splash content (z-index 0). Stars are
   generated programmatically in initSplashStars(). */
.splash-stars {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* A single star — position/size/color/opacity are set inline per star. */
.splash-star {
  position: absolute;
  border-radius: 50%;
}

/* Near (Layer 3) stars breathe: a gentle opacity pulse of ±0.2 around their base
   (--op). Duration/delay are randomised per star inline (3–7s). transform-origin
   stays centred so the accent-pulse scale grows from the star's middle. */
.splash-star--near {
  transform-origin: center;
  will-change: opacity, transform;
  animation: splash-breathe 5s ease-in-out infinite;
}

@keyframes splash-breathe {
  0%, 100% { opacity: var(--op); }
  25%      { opacity: calc(var(--op) + 0.2); }
  50%      { opacity: var(--op); }
  75%      { opacity: calc(var(--op) - 0.2); }
}

/* Bio container (Figma 662:8525) — fills the padded area; 3 equal rows, 40px gap.
   position/z-index keep it above the star layer. */
.splash-content {
  position: relative;
  z-index: 1;
  flex: 1 0 0;
  width: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
}

/* Info row (Figma 662:8533 / 822:10185 / 822:10190) — equal third, content
   vertically + horizontally centered, 40px vertical padding. */
.splash-info {
  flex: 1 0 0;
  width: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 0;
}

/* Title (Figma 720:9080 / 662:8534) — one centered line, "Pari" larger than the
   rest, cream at 70%, 0.5px tracking. (Role removed in the updated design.) */
.splash-name {
  letter-spacing: 0.5px;
  line-height: normal;
  color: var(--cream);
  white-space: nowrap;
  text-align: center;
}
.splash-name-lg { font-size: 32px; }
.splash-name-sm { font-size: 20px; }

/* Middle third stacks the visible lead sentence and the hidden tagline in the
   same spot (tagline absolutely centred) so the spotlight can swap them. */
.splash-mid {
  position: relative;
}

/* Visible lead sentence (Figma 875:10797) — Figtree Light 40px, #D9D9D9. */
.splash-lead {
  max-width: 792px;
  font-weight: 300;
  font-size: 40px;
  line-height: 1.8;
  color: #d9d9d9;
  text-align: center;
}
.splash-lead .lead-strong { font-weight: 400; }
.splash-lead .lead-em { font-style: italic; }

/* Inverse spotlight mask: the visible title + lead are hidden INSIDE the cursor
   circle — a transparent hole punched in an otherwise-opaque mask. cursor.js sets
   --mx/--my to the cursor position (element-relative) and --mr to the hole radius;
   --mr 0 leaves the element fully visible (default / touch devices). */
.splash-name,
.splash-lead {
  --mx: 50%;
  --my: 50%;
  --mr: 0px;
  -webkit-mask-image: radial-gradient(circle at var(--mx) var(--my), transparent var(--mr), #000 var(--mr));
          mask-image: radial-gradient(circle at var(--mx) var(--my), transparent var(--mr), #000 var(--mr));
}

/* CTA — dashed border pill (Figma 822:10192), no icon */
.splash-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  border: 1px dashed #F9F5EB;
  border-radius: 160px;
  background: transparent;
  color: var(--cream);
  cursor: pointer;
  font-family: inherit;
  transition: border-style 0.3s ease, background 0.3s ease;
}

/* Hover: dashed border becomes solid */
.splash-cta:hover {
  border-style: solid;
}

/* CTA icon (Figma 662:8557) — 28px down-chevron, cream. Gently bobs to hint
   scrolling; only the icon moves, not the pill. */
.splash-cta-icon {
  display: block;
  width: 24px;
  height: 24px;
  color: var(--cream);
  animation: splash-cta-bob 1.5s ease-in-out infinite;
}
@keyframes splash-cta-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

/* Hidden tagline (Figma 822:10189) — Figtree Medium 32px, coloured like the page
   bg so it reads as invisible, while staying in the DOM and selectable. The
   custom cursor's cream circle is what makes it legible (see .cursor-reveal). */
.invisible-text {
  /* Overlaid on the visible lead sentence, centred in the middle third. */
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 32px;
  font-weight: 500;
  letter-spacing: 0.5px;
  line-height: 1.8;        /* Figma 822:10189 — 2-line block, leading 1.8 */
  text-align: center;      /* each line centred (Figma text-center) */
  white-space: nowrap;
  /* Fully transparent — NOT var(--bg). It sits on top of the visible light lead
     sentence, so bg-coloured (dark) glyphs would show through over the lead.
     Transparent paints nothing, so it's invisible over both the dark page and
     the light lead. The spotlight reveal uses the dark .cursor-reveal clone,
     so hiding the original here doesn't affect the hover reveal. */
  color: transparent;
}

/* ===== Custom cursor (desktop pointers only) ===== */
/* Hide the system cursor everywhere on fine-pointer/hover devices; the custom
   cursor replaces it. Touch / coarse pointers keep the system cursor untouched. */
@media (hover: hover) and (pointer: fine) {
  *, *::before, *::after {
    cursor: none !important;
  }
}

/* Cream follower dot — created in initCustomCursor(). Centres on its (cx,cy)
   via the translate set in JS; grows to 120px over the hidden tagline. */
.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--cream);
  pointer-events: none;
  z-index: 9998;
  opacity: 0; /* revealed on first mousemove */
  transition: width 0.2s ease-out, height 0.2s ease-out, opacity 0.2s ease;
  will-change: transform, width, height;
}
.custom-cursor.is-over {
  width: 120px;
  height: 120px;
}

/* Reveal copy of the tagline — a body-level clone sitting ABOVE the cursor so the
   dark text reads on the cream circle. Clipped to a circle at the cursor; the
   circle is collapsed (radius 0) when the cursor isn't over the tagline. */
.cursor-reveal {
  position: fixed;
  z-index: 9999;
  margin: 0;
  /* Reset the centring transform inherited from .invisible-text — the clone is
     placed by JS via left/top (the tagline's viewport rect), no offset needed. */
  transform: none;
  pointer-events: none;
  color: #111323;
  clip-path: circle(0px at 0 0);
  will-change: clip-path;
}

/* Scroll progress bar — case-study pages only. The .scroll-progress element is
   added per case study page (index.html omits it), so this rule is inert there.
   Fixed to the viewport bottom; only the filled portion shows (no track). JS sets
   width 0→100% as a direct mapping of scroll position. z 1000 sits above page
   content and the footer card, below the custom cursor (9998). */
.scroll-progress {
  position: fixed;
  bottom: 0;
  left: 0;
  height: 4px;
  width: 0;
  background: var(--cream);
  z-index: 1000;
  pointer-events: none;
  will-change: width;
}

/* ===== Floating navigation ===== */
.floating-nav {
  position: fixed;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  gap: 16px;
  padding: 4px;
  background: var(--nav-bg);
  border: 0.2px solid #F9F5EB;
  border-radius: 122px;
  /* Hidden until the splash curtain begins lifting */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s ease;
}

.floating-nav.visible {
  opacity: 1;
  pointer-events: auto;
}

.nav-item {
  position: relative;
  width: 44px;
  height: 44px;
  padding: 12px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: transparent;
  transition: background 0.4s ease;
}

/* Tooltip bubble — page name (from aria-label), centered 4px above the icon.
   Only revealed for UNSELECTED items on hover. */
.nav-item::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 4px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 1;
  padding: 12px 24px;
  background: #000000;
  color: #F9F5EB;
  font-family: "Figtree", sans-serif;
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  border-radius: 8px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

/* Small triangle pointing down from the tooltip toward the icon, bridging the
   4px gap (base meets the bubble, apex at the icon top). */
.nav-item::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 4px solid #000000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.nav-item:not(.active):hover::after,
.nav-item:not(.active):hover::before {
  opacity: 1;
}

/* Recolor the Figma icons to cream via their CSS-variable fallbacks */
.nav-item .icon {
  --fill-0: #F9F5EB;
  --stroke-0: #F9F5EB;
  width: 20px;   /* 44 - 2 * 12 padding */
  height: 20px;
  display: block;
  transition: filter 0.2s ease, transform 0.2s ease;
}

/* Outline by default, filled when active */
.nav-item .icon-filled {
  display: none;
}

.nav-item.active {
  background: rgba(249, 245, 235, 0.12);
}

.nav-item.active .icon-outline {
  display: none;
}

.nav-item.active .icon-filled {
  display: block;
}

/* Hover on inactive items: most icons are fill-based so stroke width can't be
   changed uniformly — use the brightness/scale fallback for a subtle emphasis. */
.nav-item:not(.active):hover .icon {
  filter: brightness(1.15);
  transform: scale(1.08);
}

/* ===== Continuous 3-column page layout =====
   One layout wraps every section. The two sidebars are sticky siblings of the
   long middle column, so they persist (never reset/repeat) across #home →
   #works — including while the footer expands into the contact view. */
.layout {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
}

/* Proportional sidebars: 360 / 1512 = 23.81% (360px each at the 1512 design
   width, scaling down proportionally — ~305px @1280, ~244px @1024). Middle
   column takes the remaining ~52.38% via flex: 1. */
.layout-left,
.layout-right {
  width: 23.81%;
  flex-shrink: 0;
  position: sticky;
  /* top: 0 — the 48px top inset comes from each column's padding-top (below), so
     the sticky box hugs the viewport top and the content sits a steady 48px down,
     both at rest and when stuck. Using top: 48px here would STACK with the
     padding-top:48px to push content to 96px and make it jump on scroll. */
  top: 0;
  align-self: flex-start;
  height: fit-content;
}

/* Left sidebar content — 40px padding all sides (Figma Bio Container 662:7967),
   8px gap between name and role. (Sticky offset handled by the shared rule above.) */
.layout-left {
  /* 48px top inset — aligns with the middle column and right sidebar. The sticky
     box is top:0 (see shared rule above), so this padding is the only source of
     the 48px and the content stays put at 48px whether at rest or stuck. */
  padding: 48px 40px 40px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

/* Case-study left sidebar (onton + future case studies): the Bio Summary group
   and the "Go Back" button are separated by 40px (Figma Bio Container 767:14044),
   vs the 8px name→role gap inside the group. Index.html keeps the base 8px gap
   (it has no .layout-left--case modifier). Mobile (≤768px) overrides this back to
   the shared row layout further down. */
.layout-left--case {
  gap: 40px;
}
.bio-summary {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

/* Right sidebar — mirrors the left: 48px top inset, 40px sides/bottom; sticky
   top:0 (shared rule above). Top-aligned link list. */
.layout-right {
  /* 48px top inset — matches the other columns (see .layout-left). */
  padding: 48px 40px 40px;
}
/* Case-study right sidebar: TOC group + project-links group, separated by 40px
   (Figma 744-9630). */
.layout-right--toc {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 40px;
}
.side-links {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

/* Sticky table of contents (Figma 864:10768). A 0.6px Gray-Navy rail runs the
   full height; the active item gets a brighter 1px Cream-Dark left stroke. */
.toc-nav {
  display: flex;
  flex-direction: column;
  gap: 16px;
  border-left: 0.6px solid var(--gray-navy);
}
.toc-item {
  box-sizing: border-box;
  /* Pull each item's left edge onto the rail so the active stroke covers it. */
  margin-left: -0.6px;
  border-left: 1px solid transparent;
  padding: 4px 0 4px 12px; /* 12px = stroke→text offset */
  font-size: 14px;
  line-height: normal;
  color: rgba(249, 245, 235, 0.7); /* inactive: cream 70% */
  text-decoration: none;
  transition: color 0.2s ease;
}
/* Hover affordance (not specified in Figma): inactive items brighten to cream. */
.toc-item:hover {
  color: var(--cream);
}
.toc-item.is-active {
  border-left-color: var(--cream-dark); /* active stroke */
  font-size: 15px;
  color: var(--cream); /* 100% */
}
/* Right-sidebar links — Tertiary Button (Figma 662:8615): dashed cream
   underline, pb 4px; hover → solid + Medium. Matches .home-portfolio/.footer-link. */
.side-link {
  font-size: 15px;
  color: var(--cream);
  text-decoration: none;
  border-bottom: 1px dashed var(--cream);
  padding-bottom: 4px;
}
.side-link:hover {
  border-bottom-style: solid;
  font-weight: 500;
}

/* Middle column — every section's content stacks here */
.layout-main {
  flex: 1;
  min-width: 0;
}

.layout-main > section {
  width: 100%;
}

/* #home is always present behind the fixed splash overlay — no reveal needed. */

/* Left sidebar */
.home-name {
  font-size: 20px;
  font-weight: 400;
  line-height: 28px;
  color: #F9F5EB;
}

.home-role {
  font-size: 15px;
  font-weight: 400;
  color: #F9F5EB;
  opacity: 0.7;
}

/* Tertiary Button (Figma 662:8615) — dashed cream underline, pb 4px. */
.home-portfolio {
  font-size: 15px;
  color: #F9F5EB;
  text-decoration: none;
  border-bottom: 1px dashed var(--cream);
  padding-bottom: 4px;
  /* Figma Go Back is a Tertiary Button with a 14px back-arrow icon, 4px gap. */
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* Tertiary Button hover: underline dashed→solid, weight Regular→Medium. */
.home-portfolio:hover {
  border-bottom-style: solid;
  font-weight: 500;
}
/* Back-arrow icon before "Go Back" — 14px, inherits the link's cream colour. */
.go-back-icon {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* Per-section content wrapper inside the middle column */
.home-middle {
  width: 100%;
  /* 48px top inset — aligns the first section header with both sidebars. (This is
     the middle column's effective top padding; .layout-main itself has none.)
     Sides/bottom stay 40px. */
  padding: 48px 40px 40px;
  display: flex;
  flex-direction: column;
  gap: 80px;
}

/* ===== #home middle — content blocks ===== */
.mid-block {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Section header: uppercase label on the LEFT + dashed rule filling the rest of
   the row (Figma: flex row, align-items center, 16px gap). */
.mid-header {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
}

.mid-label {
  font-size: 15px;
  font-weight: 400;
  line-height: 28px;
  text-transform: uppercase;
  color: var(--navy-light);
  white-space: nowrap;
}

.mid-rule {
  flex: 1;
  height: 0;
  /* Solid, low-opacity divider (never dashed) */
  border-top: 1px solid rgba(249, 245, 235, 0.15);
}

.mid-body {
  font-size: 15px;
  font-weight: 400;
  line-height: 1.8;
  color: var(--cream);
}

/* Inline emphasis (e.g. "cycle" in the How I Work intro) — Figtree SemiBold. */
.mid-body strong {
  font-weight: 600;
}

/* Summary location line */
.mid-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  line-height: 28px;
  text-transform: uppercase;
  color: var(--navy-light);
}

.mid-flag {
  font-size: 15px;
}

/* === FIGMA DESIGN TOKENS — Item component (node 662:8621) ===
   Vars: Cream #F9F5EB · Cream Dark #EDEAE4 · White #FFFFFF · Navy lightest #B3BAE1 · Gray Navy #5A5D70
   ---
   SMALL / Default (662:8622)  — row: flex, gap 12, items-center
     title  : Figtree Regular 15px, #F9F5EB, underline 0.5px dashed #EDEAE4, pb 4px
     label  : Figtree Regular 14px, #F9F5EB @ 70%   (org; absent on Works list rows)
     leader : 0.5px line, #EDEAE4 @ 40%, dash 2/4  (PRESENT in default)
     time   : Figtree Regular 14px, #F9F5EB @ 100%
   SMALL / Hover (662:8628)
     title #FFFFFF · label #EDEAE4 @100% · time #FFFFFF · leader unchanged
     icon  : vuesax send (Arrow/link), 14px, flipped scaleX(-1) — EXTERNAL items only
   LARGE / Default (662:8634) — col: gap 8, items-start, padding 8 0
     header: flex gap 16 items-center → title (Figtree Regular 15px/28, #B3BAE1, UPPERCASE)
             + line (flex-1, 0.6px SOLID #5A5D70).  NO year.
     body  : flex gap 16 items-start → thumb 343x240 bg rgba(255,255,255,0.08) r16
             + info (Figtree Light 300, gap 16, leading 1.4, #F9F5EB):
                 desc 14px · meta row gap 8 → label 11px @70% w44 + value 13px
   LARGE / Hover (662:8654)
     container border 1px dashed #F9F5EB, r16, padding 8 16, col gap 16
     header gap 8 → title #F9F5EB (line hidden) + chevron far-right
             (vuesax arrow-down chevron, rotate -90°, 16px, #F9F5EB)
     thumb bg rgba(255,255,255,0.16)
   ============================================================ */

/* --- Works list (Small item, INTERNAL: scroll to a project → no hover icon) --- */
.work-list {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.work-row {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  font-size: 15px;
  color: var(--cream);
  transition: all 0.2s ease;
}

/* Title: dashed underline, 0.5px #EDEAE4, pb 4px */
.work-name {
  border-bottom: 0.5px dashed var(--cream-dark);
  padding-bottom: 4px;
  white-space: nowrap;
  transition: all 0.2s ease;
}

/* Leader line — present in BOTH states: 0.5px, #EDEAE4 @40%, dash 2px / gap 4px */
.work-dots {
  flex: 1;
  height: 0.5px;
  background: repeating-linear-gradient(
    to right,
    rgba(237, 234, 228, 0.4) 0,
    rgba(237, 234, 228, 0.4) 2px,
    transparent 2px,
    transparent 6px
  );
}

.work-year {
  white-space: nowrap;
  font-size: 14px;
  color: var(--cream);
  transition: all 0.2s ease;
}

/* Hover: title + year → white (leader unchanged) */
.work-row:hover .work-name {
  color: #FFFFFF;
}

.work-row:hover .work-year {
  color: #FFFFFF;
}

/* --- Service / filter chips — 4 equal columns filling the row --- */
.service-chips {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.chip {
  font-family: inherit;
  font-size: 15px;
  line-height: 1.2;
  color: var(--cream);
  background: transparent;
  border: 1px dashed #F9F5EB;
  border-radius: 122px;
  padding: 12px 16px;
  text-align: center;
  /* wrap rather than overflow when a label is too long for its 1fr column */
  min-width: 0;
  cursor: pointer;
  transition: background 0.3s ease, border-color 0.3s ease;
}

/* Active chip: solid 0.2px border + 4% cream fill (Figma) */
.chip.active {
  background: rgba(249, 245, 235, 0.04);
  border: 0.2px solid #F9F5EB;
}

/* --- Strengths --- */
.strengths {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.strength-p {
  font-size: 15px;
  font-weight: 400;
  line-height: 1.8;
  color: var(--cream);
}

.strength-p strong {
  font-weight: 600;
}

/* --- How I Work: cycle diagram (Figma 662:8015) ---
   The .cycle box reproduces the exact 792×421 Figma layout (cards at fixed px);
   initCycle() in script.js scales it (transform-origin top-left) to fit the
   column width. Below 768px it falls back to a vertical stack. */
.cycle-wrap {
  width: 100%;
}

.cycle {
  position: relative;
  width: 792px;
  height: 421px;
  transform-origin: top left;
}

.cycle-lines {
  position: absolute;
  inset: 0;
  width: 792px;
  height: 421px;
  overflow: visible;
  pointer-events: none;
  z-index: 0;
}

/* Step card — very subtle 3% cream bg, centered content, 24/32 padding (Figma) */
.cycle-card {
  position: absolute;
  width: 320px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 24px 32px;
  background: rgba(249, 245, 235, 0.03);
  border-radius: 16px;
  z-index: 1;
}
.cycle-card[data-pos="define"]   { left: 236px; top: 0; }
.cycle-card[data-pos="validate"] { left: 0;    top: 154px; }
.cycle-card[data-pos="research"] { left: 472px; top: 154px; }
.cycle-card[data-pos="design"]   { left: 236px; top: 308px; }

.cycle-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  white-space: nowrap;
}
/* Number prefix (01–04): light weight, dimmed cream, inline before the title */
.cycle-num {
  font-size: 12px;
  line-height: 16px;
  font-weight: 300;
  color: rgba(249, 245, 235, 0.5);
}
.cycle-title {
  font-size: 15px;
  line-height: 28px;
  font-weight: 500;
  color: #F9F5EB;
}
.cycle-desc {
  font-size: 11px;
  font-weight: 400;
  line-height: 1.5;
  text-align: center;
  color: rgba(249, 245, 235, 0.6);
}

/* --- What Others Think: testimonial cards (Figma 699:9142) ---
   Horizontal scroll-snap row: ~2 cards plus a peek of the third are visible,
   and the rest scroll into view. Scrollbar hidden but scrolling stays
   functional. */
.testi-row {
  display: flex;
  gap: 24px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: 0;          /* row starts at the column's content edge */
  scrollbar-width: none;           /* Firefox */
}
.testi-row::-webkit-scrollbar {    /* WebKit — hide but keep functional */
  display: none;
}
.testi-card {
  flex: 0 0 auto;
  /* ~2 cards + a peek of the third at desktop column width */
  width: calc((100% - 24px) / 2.2);
  height: 280px;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 24px;
  background: rgba(249, 245, 235, 0.03);
  border-radius: 16px;
  overflow: hidden;
}
.testi-top {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}
.testi-avatar {
  flex-shrink: 0;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: #ffffff;
}
.testi-id {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  min-width: 0;
}
.testi-name {
  font-size: 15px;
  line-height: 20px;
  color: var(--cream);
  white-space: nowrap;
}
/* Role/subtitle — clamps to exactly 2 lines, ellipsis at the END only. */
.testi-role {
  font-size: 12px;
  font-weight: 300;
  line-height: 1.4;
  color: var(--cream);
  opacity: 0.7;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
.testi-quote {
  flex: 1;
  min-height: 0;
  font-size: 12px;
  font-weight: 300;
  line-height: 1.4;
  color: var(--cream);
  opacity: 0.7;
  overflow: hidden;
}
/* Tertiary Button (Figma 662:8615) instance — see .home-portfolio. */
.testi-link {
  flex-shrink: 0;
  align-self: flex-start;
  font-size: 15px;
  color: var(--cream);
  text-decoration: none;
  border-bottom: 1px dashed var(--cream);
  padding-bottom: 4px;
}
.testi-link:hover {
  border-bottom-style: solid;
  font-weight: 500;
}

/* --- Footer component --- */
.mid-footer {
  width: 100%;
  background: rgba(249, 245, 235, 0.04);
  border-radius: 16px;
  /* Figma 662:8687 — 24px vertical / 40px horizontal. */
  padding: 24px 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  text-align: center;
}

/* Links group (Figma 662:8688): fills the width left of the title (flex:1) and
   spreads the 3 links across equal, centered thirds — NOT bunched at the right. */
.mid-footer-links {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 80px;
  justify-items: center;
  align-items: center;
}

/* Tertiary Button (Figma 662:8615) instance — see .home-portfolio. */
.footer-link {
  font-size: 15px;
  color: var(--cream);
  text-decoration: none;
  border-bottom: 1px dashed var(--cream);
  padding-bottom: 4px;
  transition: opacity 0.2s ease;
}
.footer-link:hover {
  border-bottom-style: solid;
  font-weight: 500;
}

/* Hover dimming: hovering one footer link dims the others to 40% (only when a
   specific link is hovered, not the empty space between them). */
.mid-footer-links:has(.footer-link:hover) .footer-link:not(:hover) {
  opacity: 0.4;
}

/* Default footer layout (Figma 662:8687): the card is a flex row — title hugs
   its text on the left, links group (flex:1) fills the rest, gap 80px between. */
.footer-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 80px;
}

/* "CONTACT ME" — Figma: Figtree Regular 14px / 20px, uppercase, #B3BAE1, hug. */
.mid-footer-title {
  flex-shrink: 0;
  font-size: 14px;
  line-height: 20px;
  text-transform: uppercase;
  color: var(--navy-light);
  white-space: nowrap;
}

.mid-footer-copy {
  font-size: 12px;
  line-height: 16px;
  color: var(--cream-dark);
  opacity: 0.7;
}

/* --- Lead rows (Academia / Contribution / Certificate) --- */
.lead-list {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Small item, EXTERNAL variant (About: Academia / Contribution / Certificate).
   Matches Figma Item/Small + Small/Hover. Default: dashed-underlined title + org
   (70%) + leader (present) + year (100%). Hover: title/year → white, org →
   #EDEAE4, and the vuesax send (↗) icon appears between org and leader. */
.lead-row {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 15px;
  color: var(--cream);
  text-decoration: none;
  transition: all 0.2s ease;
}

/* DOM order is now [title] [arrow] [org] [leader] [year] — the arrow is a real
   sibling immediately after the underlined title (no flex `order` needed). */

.lead-name {
  border-bottom: 0.5px dashed var(--cream-dark);
  padding-bottom: 4px;
  white-space: nowrap;
  transition: all 0.2s ease;
}

.lead-org {
  font-size: 14px;
  color: var(--cream);
  opacity: 0.7;
  white-space: nowrap;
  transition: all 0.2s ease;
}

/* ↗ link arrow (clean diagonal). 14px, hover only. Sits AFTER the label/org:
   [title] [label] [arrow] … leader … [year]. Reserves NO space when hidden:
   width 0 + overflow hidden, and a -12px left margin cancels the row's 12px gap
   so the label sits flush against the leader. On hover it expands to 14px
   (margin → 0), appearing between the label and the leader. */
.lead-arrow {
  flex-shrink: 0;
  width: 0;
  height: 14px;
  margin-left: -12px;
  overflow: hidden;
  opacity: 0;
  /* The icon is its own element (a sibling of the underlined title), so it is
     never inside the underline and carries NO line beneath it. */
  border-bottom: none;
  transition: all 0.2s ease;
}

.lead-arrow svg {
  width: 14px;
  height: 14px;
  display: block;
}

.lead-row:hover .lead-arrow {
  width: 14px;
  margin-left: 0;
  opacity: 1;
}

/* Leader line — present in BOTH states: 0.5px, #EDEAE4 @40%, dash 2px / gap 4px */
.lead-dots {
  flex: 1;
  height: 0.5px;
  background: repeating-linear-gradient(
    to right,
    rgba(237, 234, 228, 0.4) 0,
    rgba(237, 234, 228, 0.4) 2px,
    transparent 2px,
    transparent 6px
  );
}

.lead-year {
  white-space: nowrap;
  font-size: 14px;
  color: var(--cream);
  transition: all 0.2s ease;
}

.lead-row:hover .lead-name {
  color: #FFFFFF;
}

.lead-row:hover .lead-org {
  color: var(--cream-dark);
  opacity: 1;
}

.lead-row:hover .lead-year {
  color: #FFFFFF;
}

/* ===== List hover dimming =====
   Dimming triggers ONLY when a specific item is hovered (via :has) — NOT when
   the cursor is merely in the empty space between items. Non-hovered siblings
   drop to 40%; the hovered item stays full. The dim rule precedes the highlight
   rule so the hovered item (equal specificity) wins. */
.work-list:has(.work-row:hover) .work-row:not(:hover),
.lead-list:has(.lead-row:hover) .lead-row:not(:hover),
.contact-list:has(.contact-row:hover) .contact-row:not(:hover) {
  opacity: 0.4;
}

/* contact-row lacks its own transition; add one so dimming animates smoothly
   (work-row and lead-row already transition: all 0.2s ease). */
.contact-row {
  transition: opacity 0.2s ease;
}

/* --- Tri cards (Skill / All Time Favorite) --- */
.tri-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.tri-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  background: rgba(249, 245, 235, 0.04);
  border-radius: 16px;
  padding: 24px 16px;
}

/* Card head: 16px icon + label (gap 8, centered), with a solid low-opacity
   divider beneath. Title is cream (#F9F5EB) per spec; the icon keeps its Figma
   colour (#B3BAE1, set on each inline SVG). */
.tri-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: #F9F5EB;
  padding-bottom: 16px;
  border-bottom: 1px solid rgba(249, 245, 235, 0.15);
}

/* Column-title icon (16×16, inline SVG exported from Figma) */
.tri-card-icon {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  display: block;
  position: relative;
}

.tri-card-icon svg {
  display: block;
  width: 100%;
  height: 100%;
}

.tri-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.tri-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.tri-item-name {
  font-size: 14px;
  color: var(--cream);
}

.tri-item-sub {
  font-size: 12px;
  color: var(--cream);
  opacity: 0.7;
}

/* --- Works projects --- */
/* Projects sit 40px apart (Figma), tighter than the 80px section rhythm. */
#works .home-middle {
  gap: 40px;
}

/* Large/Desktop item (clickable link). Default: no border/bg, 8px vertical
   padding, 8px gap between header and body. A 1px dashed TRANSPARENT border is
   reserved so the hover border doesn't shift layout (border-box). */
.project {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0;
  border: 1px dashed transparent;
  border-radius: 16px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: all 0.2s ease;
}

/* Hover (Large/Desktop/Hover): 1px dashed #F9F5EB border, 16px horizontal
   padding, header↔body gap grows 8 → 16. */
.project:hover {
  border-color: #F9F5EB;
  padding: 8px 16px;
  gap: 16px;
}

/* Filtered-out projects collapse out of the layout */
.project[hidden] {
  display: none;
}

/* Header row — gap 16 (default) / 8 (hover) */
.project-head {
  display: flex;
  align-items: center;
  gap: 16px;
}

.project:hover .project-head {
  gap: 8px;
}

/* Title: Figtree Regular 15px / 28, uppercase. #B3BAE1 → #F9F5EB on hover. */
.project-title {
  font-size: 15px;
  line-height: 28px;
  text-transform: uppercase;
  color: var(--navy-light);
  white-space: nowrap;
  transition: all 0.2s ease;
}

.project:hover .project-title {
  color: #F9F5EB;
}

/* Header line — 0.6px SOLID #5A5D70. Stays flex-1 always; on hover the stroke
   goes transparent (line disappears) and the chevron takes the far right. */
.project-rule {
  flex: 1;
  height: 0;
  border-top: 0.6px solid var(--gray-navy);
  transition: all 0.2s ease;
}

.project:hover .project-rule {
  border-top-color: transparent;
}

/* Right chevron (vuesax arrow-down chevron rotated -90°). 16px, far right,
   hover only — sits after the (transparent) rule so it lands at the row end. */
.project-arrow {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  display: none;
}

.project-arrow svg {
  width: 16px;
  height: 16px;
  display: block;
  transform: rotate(-90deg);
}

.project:hover .project-arrow {
  display: block;
}

.project-body {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

/* Info column — Figtree Light (300), gap 16, leading 1.4, #F9F5EB */
.project-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  font-weight: 300;
}

.project-desc {
  font-size: 14px;
  line-height: 1.4;
  color: var(--cream);
}

.project-meta {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

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

.meta-label {
  flex: 0 0 44px;
  font-size: 11px;
  color: var(--cream);
  opacity: 0.7;
}

.meta-value {
  font-size: 13px;
  color: var(--cream);
}

/* Thumbnail — fixed 343 × 240, on the LEFT. rgba(255,255,255,0.08) →
   0.16 on hover. */
.project-media {
  flex: 0 0 343px;
  width: 343px;
  height: 240px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 16px;
  transition: all 0.2s ease;
}

.project:hover .project-media {
  background: rgba(255, 255, 255, 0.16);
}

/* ===== Footer → Contact: ONE element, two states =====
   #footer-cta is the single morphing card. .footer-stage is a fixed-height
   scroll runway below the Works content; JS sets its height to (collapsed card
   height + one viewport) so the card can expand to fill the viewport WITHOUT
   reflowing the document. Expansion progress (0 → 1) is driven by scroll
   position (1:1) or by a 400ms click animation — both go through the same JS
   render, which switches the card to position: fixed while expanding. */
.footer-stage {
  position: relative;
  width: 100%;
}

/* The morphing card. Inherits bg + base box from .mid-footer; padding is dropped
   here (each state layer owns its own padding) and the card is hinted clickable. */
#footer-cta {
  position: relative;
  padding: 0;
  overflow: hidden;
  cursor: pointer;
  /* OPAQUE background = the page (#111323) with .mid-footer's 4% cream tint
     baked in (rgba(249,245,235,0.04) over #111323 = #1A1C2B). Identical to the
     translucent card in the default state, but when the card goes position:fixed
     and overlays the page while expanding, page content can't bleed through it. */
  background: #1A1C2B;
  border-radius: 16px; /* all corners (Figma 662:8696) */
  /* While 0 < progress ≤ 1 the JS switches this to position: fixed, sized to the
     .footer-stage column box (so it never bleeds into the sidebars) and leaving
     clearance at the bottom for the floating nav. */
}

/* DEFAULT state — the 4-link row */
.footer-default {
  width: 100%;
  /* Figma default footer — 24px vertical / 40px horizontal. */
  padding: 24px 40px;
}

/* EXPANDED state — full contact, overlaid (absolute) so it never adds to the
   card's natural collapsed height. JS crossfades opacity + pointer-events. */
.footer-expanded {
  position: absolute;
  inset: 0;
  padding: 40px; /* 40px all sides (Figma 662:8696) */
  display: flex;
  flex-direction: column;
  gap: 40px;
  overflow-y: auto;
  text-align: left;
  /* Content is interactive only once the card opens (class-driven below). */
  pointer-events: none;
}
.contact-open .footer-expanded {
  pointer-events: auto;
}

/* Figma 662:8697 — the 3 groups fill the card height, spread by space-between
   (≈55px between groups at the design height). The 48px gap is a floor so the
   inter-group spacing never collapses below ~2× the 24px intra-item gap on
   shorter viewports (it grows beyond 48px when there's room to fill). */
.contact-groups {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 48px;
}

/* Header→rows gap + label sizing, scoped to the expanded contact only. */
.footer-expanded .mid-block {
  gap: 24px;
}

.footer-expanded .mid-label {
  font-size: 14px; /* Figma 662:8700 */
}

/* Group headers reuse .mid-header / .mid-label / .mid-rule (solid rule). */

.contact-list {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.contact-row {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  font-size: 15px; /* Figma 662:8704/8706 */
  color: var(--cream);
}

/* Platform name — Tertiary Button: dashed CREAM underline (Figma 662:8704). */
.contact-name {
  border-bottom: 1px dashed var(--cream);
  padding-bottom: 4px;
  white-space: nowrap;
}

/* Dotted leader line filling the space between name and handle (Figma 662:8705). */
.contact-leader {
  flex: 1;
  height: 0;
  position: relative;
  top: 6px;
  border-top: 1px dotted rgba(249, 245, 235, 0.3);
}

.contact-value {
  white-space: nowrap;
  color: var(--cream);
  opacity: 0.7;
}

/* Bottom row: copyright left, last-updated right */
.contact-bottom {
  /* 40px above the copyright row comes from .footer-expanded's gap (Figma
     card gap-40); no extra margin needed. */
  width: 100%;
  display: flex;
  justify-content: space-between;
}

.contact-copy,
.contact-updated {
  font-size: 12px;
  line-height: 16px;
  color: var(--cream-dark); /* #EDEAE4 */
  opacity: 0.7;
}

/* ---- Expand animation: staggered content entrance ----
   Each group header, contact row and the bottom line lifts + fades in. JS sets a
   per-element transition-delay for the cascade (groups ~80ms apart, items ~40ms,
   starting ~200ms into the 600ms height grow) when the card opens (.contact-open),
   and a fast no-delay fade-out (.contact-closing) when it collapses. */
.footer-expanded .mid-header,
.footer-expanded .contact-row,
.footer-expanded .contact-bottom {
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity 400ms ease-out,
    transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
.contact-open .footer-expanded .mid-header,
.contact-open .footer-expanded .contact-row,
.contact-open .footer-expanded .contact-bottom {
  opacity: 1;
  transform: none;
}
.contact-closing .footer-expanded .mid-header,
.contact-closing .footer-expanded .contact-row,
.contact-closing .footer-expanded .contact-bottom {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 200ms ease, transform 200ms ease;
  transition-delay: 0ms !important;
}
/* Default 4-link row fades out as the card opens. */
.footer-default {
  transition: opacity 200ms ease;
}
.contact-open .footer-default {
  opacity: 0;
  pointer-events: none;
}

/* Right sidebar — intentionally empty; width + sticky come from the shared
   .home-left, .home-right rule above. */

/* ============================================================================
   RESPONSIVE — breakpoints cascade via max-width (each tier overrides the last)
     Desktop  ≥ 1280px : base styles above
     Laptop   ≤ 1279px : proportional columns, tighter padding/gap
     ≤ 1023px          : proportional columns, smaller text + thumbnails
     Tablet   ≤  768px : single column — left sidebar becomes full-width header,
                         right column hidden
     Mobile   ≤  576px : sidebars gone, per-section identity header, content
                         full-width with 24px padding, blocks stack
   The 3-column proportions (21.16% / flex-1 / 21.16%) stay consistent at every
   width ABOVE 768px — no fixed px overrides.
   ============================================================================ */

/* ---------- Laptop (1024–1279px) — columns stay proportional ---------- */
@media (max-width: 1279px) {
  .home-middle {
    padding: 32px;
    gap: 60px;
  }
  #works .home-middle {
    gap: 36px;
  }
}

/* ---------- ≤1023px — still 3 proportional columns; scale text + thumbnails ---------- */
@media (max-width: 1023px) {
  .home-middle {
    padding: 24px;
    gap: 48px;
  }
  #works .home-middle {
    gap: 28px;
  }

  /* Body / header / list text scales down slightly */
  .mid-label,
  .mid-body,
  .strength-p,
  .footer-link,
  .work-row,
  .work-year,
  .lead-row,
  .contact-row {
    font-size: 14px;
  }

  /* Works thumbnail shrinks but stays beside the info */
  .project-media {
    flex: 0 0 240px;
    width: 240px;
    height: 160px;
  }

  /* Chips → 2×2 */
  .service-chips {
    grid-template-columns: 1fr 1fr;
  }

  /* Skill / Favourite cards stay 3-up but tighter */
  .tri-card {
    padding: 16px 12px;
  }
  .tri-grid {
    gap: 8px;
  }

  /* Footer links closer together */
  .mid-footer-links {
    gap: 40px;
  }

  /* Contact expanded — reduced padding */
  .footer-default,
  .footer-expanded {
    padding: 28px;
  }
}

/* ---------- Tablet (≤768px) — left sidebar becomes a full-width header ---------- */
@media (max-width: 768px) {
  .layout {
    flex-direction: column;
    /* Stretch the (now stacked) columns to the viewport width. Without this the
       base `align-items: flex-start` lets .layout-main size to its content's
       min/max-content — and the horizontal scroll row (.testi-row) has a large
       min-content, which would otherwise widen .layout-main past the viewport
       and clip every full-width child (the cycle, etc.) under `overflow-x: clip`. */
    align-items: stretch;
  }

  /* How I Work cycle → vertical stack (initCycle() leaves transform/height
     cleared at this width). Cards read in cycle order; arrows hidden. */
  .cycle {
    position: static;
    width: 100% !important;
    height: auto !important;
    transform: none !important;
    display: flex;
    flex-direction: column;
    gap: 12px;
  }
  .cycle-card {
    position: static;
    width: 100%;
  }
  .cycle-lines {
    display: none;
  }

  /* Testimonials → full-width cards, one per view in the horizontal scroll row */
  .testi-card {
    width: 100%;
  }
  /* Left sidebar → static, full-width header row at the top of the page */
  .layout-left {
    width: 100%;
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 16px;
    padding: 24px;
  }
  .layout-right {
    display: none;
  }
  /* The shared left header stands in for the per-section identity here */
  .section-identity {
    display: none;
  }

  /* Works project cards stack: title → thumbnail → description → metadata.
     DOM order is media-first then info(desc, meta), so a plain column produces
     exactly that order. Thumbnail goes full width between title and description.
     Applies in both default and hover states (flex-direction is unaffected by
     hover). */
  .project-body {
    flex-direction: column;
  }
  .project-media {
    flex: none;
    width: 100%;
    height: auto;
    aspect-ratio: 343 / 240;
    border-radius: 12px;
  }

  /* Default footer → "Default/mobile" variant (Figma 800:15079): the card
     becomes a centered column — "CONTACT ME" on top, then the three links on a
     single row of equal, centered thirds spread across the full width. It flips
     here (not at 576) so it switches together with the single-column layout —
     no desktop-row-in-mobile-layout intermediate state. The base .mid-footer
     keeps its 24px/40px padding and 16px radius; .footer-row just stacks
     (gap 24px title→links) and .mid-footer-links spans full width so the
     repeat(3,1fr) thirds spread evenly. Applies to the plain footer (#home /
     #about) and the collapsed morph footer (#footer-cta) shared by onton.html. */
  .footer-row {
    flex-direction: column;
    gap: 24px;
  }
  .mid-footer-links {
    width: 100%;
    /* Figma's 80px artboard gap reduced so 3 links never overflow a phone;
       the 1fr thirds + justify-items:center keep the even spread. */
    gap: 16px;
  }
}

/* ---------- Mobile (≤576px) — sidebars gone, content full width ---------- */
@media (max-width: 576px) {
  /* Hide the header sidebar. The bio (name + role) renders ONCE, at the top of
     #home only — not repeated above #about / #works. Each section still carries
     its own .section-identity in the markup (base rule keeps them display:none);
     we reveal only #home's here so the bio doesn't reappear per section. */
  .layout-left {
    display: none;
  }
  #home .section-identity {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 8px;
  }

  /* Content full width with 24px padding */
  .home-middle {
    padding: 24px;
    gap: 40px;
  }
  #works .home-middle {
    gap: 24px;
  }

  /* Typography */
  .mid-label {
    font-size: 13px;
  }
  .mid-body,
  .strength-p {
    font-size: 14px;
  }

  /* (Project card stacking — title → thumbnail → description → metadata — is
     handled in the ≤768 block above and inherited here.) */

  /* Chips → 2×2, slightly smaller, comfortable tap target */
  .service-chips {
    grid-template-columns: 1fr 1fr;
    gap: 12px;
  }
  .chip {
    padding: 10px 12px;
    min-height: 44px;
  }

  /* Skill / Favourite cards stack full width */
  .tri-grid {
    grid-template-columns: 1fr;
  }

  /* Default footer mobile variant (column + single row of links) is handled in
     the ≤768 block above, so it flips with the single-column layout. Here we
     only tighten the collapsed morph card's padding for the narrowest phones. */
  .footer-default {
    padding: 24px 40px;
  }

  /* Contact (expanded) — compact padding, groups stack with 32px gap */
  .footer-expanded {
    padding: 24px;
  }
  .contact-groups {
    gap: 32px;
  }
  /* Long handles wrap instead of forcing horizontal overflow */
  .contact-value {
    white-space: normal;
    word-break: break-word;
    text-align: right;
  }
  .contact-leader {
    min-width: 16px;
  }
  .contact-bottom {
    flex-wrap: wrap;
    gap: 8px;
  }

  /* Floating nav — smaller icons + padding (stays centered, fixed bottom) */
  .floating-nav {
    gap: 8px;
  }
  .nav-item {
    width: 36px;
    height: 36px;
    padding: 8px;
  }

  /* Splash — compact padding, smaller title / lead / CTA */
  #splash {
    padding: 40px 24px 80px;
  }
  .splash-name-lg { font-size: 22px; }
  .splash-name-sm { font-size: 15px; }
  .splash-lead {
    font-size: 20px;
    max-width: 100%;
  }
  .splash-cta {
    padding: 16px 28px;
  }
}

/* ============================================================================
   SCROLL REVEAL — middle-column items fade up + slide in on first intersection.
   Opt-in via `.reveal-item`; the shared observer in reveal.js toggles
   `.revealed`. Initial → revealed is a 550ms transition; the observer applies a
   per-item staggered transition-delay (then clears it) so batches cascade.
   NOTE: scoped to in-flow middle-column content only — sidebars, floating nav,
   splash, and the footer/contact card are intentionally NOT tagged.
   ============================================================================ */
.reveal-item {
  opacity: 0;
  transform: translateY(10px);
  transition:
    opacity 550ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 550ms cubic-bezier(0.22, 1, 0.36, 1);
}

.reveal-item.revealed {
  opacity: 1;
  transform: translateY(0);
}

/* --- Preserve existing interactive transitions on tagged elements ---------- */
/* The base `.reveal-item` rule replaces these elements' own `transition`, so we
   re-declare their hover/dim transitions alongside the reveal ones. */

/* Project card: keep the 0.2s hover morph (border / padding / gap). It never
   animates opacity on hover, so there's no clash with the reveal opacity. */
.project.reveal-item {
  transition:
    opacity 550ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 550ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 0.2s ease,
    padding 0.2s ease,
    gap 0.2s ease;
}

/* List rows dim siblings via opacity on hover. While the dim state is active,
   restore the snappy 0.2s opacity transition (more specific than the reveal
   base, so it wins only during the hover-dim). */
.work-list:has(.work-row:hover) .work-row.reveal-item:not(:hover),
.lead-list:has(.lead-row:hover) .lead-row.reveal-item:not(:hover) {
  transition: opacity 0.2s ease;
}

/* --- Reduced motion: no animation, final state immediately. !important beats
   the per-element transition overrides above regardless of specificity. ------ */
@media (prefers-reduced-motion: reduce) {
  .reveal-item {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  /* Footer contact: keep the open/closed opacity toggle, drop the motion. */
  .footer-expanded .mid-header,
  .footer-expanded .contact-row,
  .footer-expanded .contact-bottom,
  .footer-default {
    transform: none !important;
    transition: none !important;
  }
  /* Stars hold their base opacity — no breathing (accent pulse is skipped in JS). */
  .splash-star--near {
    animation: none !important;
    opacity: var(--op);
  }
  /* CTA icon: no scroll-hint bob. (The spotlight is geometric, so it still works.) */
  .splash-cta-icon {
    animation: none !important;
  }
}
