/* ============================================================
   LE RIZIÈRE — STYLES
   Design tokens come first: edit colors / fonts / spacing here
   and the whole site follows.
   ============================================================ */

:root {
  /* —— Color —— */
  /* VI palette: FAEBDC (cream) / black / white, 70/30 rule (sage is
     not used in the page gradient — VI green is reserved for accents) */
  --vi-cream: #FAEBDC;
  --vi-sage: #BEC3A5;            /* kept as token in case Cursor uses it for an accent */

  /* hero mask — 50% per the latest revision. Contrast against the actual
     foraging photo: 4.9–5.3:1 globally (AA normal text), 8.6–9:1 inside
     the localized text scrims (AAA). See README for the contrast check. */
  --hero-mask: rgba(151, 103, 55, 0.50);   /* #976737 @ 50% */

  /* —— Page gradient: sunrise (amber) -> cream -> near-white ——
     Per the latest direction: grad-1 matches the hero mask base color
     (#976737) so the hero -> constellation transition reads as ONE
     continuous warming, not two ambers fighting. ONE continuous track
     painted on .gradient-track; sections stay transparent. */
  --grad-1: #976737;             /* matches the hero mask base for clean handoff */
  --grad-2: var(--vi-cream);     /* FAEBDC — cream daylight arrives */
  --grad-3: #F6EBDD;             /* cream brightening */
  --grad-4: #FBF6EE;             /* near-white, still warm */
  --grad-5: #FDFBF6;             /* footer-adjacent: soft near-white */

  --paper: #FAF7F2;               /* flat light pages (experience/menu/past/contact) */
  --ink: #1F1B16;                 /* dark text */
  --ink-soft: rgba(31, 27, 22, 0.66);
  --line: rgba(31, 27, 22, 0.35);
  --on-light: var(--ink);
  --on-dark: #FFFCF7;             /* warm white — used on hero & burger overlay */

  /* —— Type —— */
  --display: "Lora", serif;       /* display / artistic — headings, dish names, hero */
  --body: "Jost", sans-serif;     /* editorial body — paragraphs, captions, intro copy */
  --ui: "Jost", sans-serif;       /* interaction / control layer — labels, buttons, inputs,
                                     chips, selects, nav items. Separate token so a future
                                     swap (e.g. to a monospaced or geometric sans) only
                                     touches this one line. Currently same face as --body
                                     but distinguished by weight 500 & tracking in usage. */
  --hanzi: "Noto Serif SC", serif;/* Chinese — ExtraLight 200 display / Light 300 body */

  /* —— Grid (1440 reference: 12 col / 120 margin / 20 gutter) —— */
  --margin: clamp(20px, 8.33vw, 120px);
  --gutter: 20px;
}

/* The continuous landing-page gradient. Painted on a wrapper behind
   hero + constellation + experience-teaser + desmond + subnav, so the
   color is one unbroken track instead of stitched section backgrounds.
   Percentages are tuned to this page's section heights — if you add or
   resize a section in Cursor, nudge these stops to match. */
.gradient-track {
  overflow: hidden;
  background: linear-gradient(
    to bottom,
    /* Hero (0-20%): photo + amber mask dominate visually. Gradient holds
       amber so when the photo ends the background is the same colour —
       no flash of a different hue at the cut.
       Amber transitions to cream quickly (20%→40%) so the constellation
       section arrives on a readable warm cream ground, not mid-amber.
       This is the key legibility fix: dark ink nodes on cream = AA+;
       dark ink on #976737 = ~2:1, which fails. */
    var(--grad-1) 0%,
    var(--grad-1) 20%,
    var(--grad-2) 40%,
    var(--grad-3) 65%,
    var(--grad-4) 82%,
    var(--grad-5) 100%
  );
  position: relative;
}
/* fine grain over the gradient — kills 8-bit banding in the long sage->white run */
.gradient-track::after {
  content: "";
  position: absolute; inset: 0; pointer-events: none; opacity: 0.05; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
}

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

html { scroll-behavior: smooth; overflow-x: hidden; }
body { overflow-x: hidden; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

body {
  font-family: var(--body);
  font-weight: 300;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
/* cover images (hero, gallery frames) are position:absolute filling their container —
   the global max-width:100% caps them at their natural pixel width, leaving gaps
   at wide viewports. Override to let object-fit:cover scale them up freely. */
.hero-photo,
.gallery-frame img,
.gallery-frame-inline img { max-width: none; }
a { color: inherit; }

.wrap { padding-left: var(--margin); padding-right: var(--margin); }

/* ============ HEADER ============ */
.site-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 10;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  padding: 8px var(--margin) 4px;
  transition: background 0.35s ease, backdrop-filter 0.35s ease, border-color 0.35s ease;
  border-bottom: 1px solid transparent;
}
/* scrolled / inner-page state — semi-transparent, content shows through */
.site-header.header-scrolled {
  background: rgba(253, 251, 246, 0.40);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom-color: rgba(31, 27, 22, 0.07);
}
.brand { grid-column: 2; text-align: center; text-decoration: none; }
.brand .mark { height: 56px; width: auto; display: inline-block; transition: opacity 0.2s; }

.burger {
  grid-column: 3; justify-self: end;
  background: none; border: 0; cursor: pointer; padding: 8px;
  color: inherit;  /* essential: buttons don't inherit color by default */
  display: flex; flex-direction: column; gap: 5px;
}
.burger span { display: block; width: 22px; height: 1.5px; background: currentColor; transition: background 0.25s; }
.burger:focus-visible { outline: 1.5px solid currentColor; outline-offset: 4px; }

/* default: header sits on a light/paper background -> dark text/icon.
   .header-light is added by main.js while the header overlaps the
   hero photo, and removed once scrolled past it (logo image swaps too). */
.site-header { color: var(--ink); transition: color 0.25s; }
.header-light { color: var(--on-dark); }

/* ============ OVERLAY NAV (hamburger) ============ */
.nav-overlay {
  position: fixed; inset: 0; z-index: 50;
  background: var(--on-dark); color: var(--ink);
  display: none; flex-direction: column; justify-content: center;
  padding: 0 var(--margin);
}
.nav-overlay.open { display: flex; }
.nav-overlay .close {
  position: absolute; top: 28px; right: var(--margin);
  background: none; border: 0; color: inherit; font-size: 28px; cursor: pointer;
  font-family: var(--body); font-weight: 300;
}
.nav-overlay a { text-decoration: none; display: inline-block; }
.nav-primary a {
  font-family: var(--display); font-size: clamp(30px, 4.5vw, 52px);
  line-height: 1.5; color: var(--ink); opacity: 0.85;
}
.nav-primary a:hover, .nav-overlay a:focus-visible { opacity: 1; text-decoration: underline; text-underline-offset: 8px; }
.nav-primary .zh { font-family: var(--hanzi); font-weight: 200; font-size: 0.45em; margin-left: 14px; letter-spacing: 0.3em; opacity: 0.7; }
.nav-secondary { margin-top: 40px; display: flex; gap: 28px; flex-wrap: wrap; }
.nav-secondary a {
  font-family: var(--ui); font-weight: 500; font-size: 13px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink); opacity: 0.6;
  transition: opacity 0.18s ease;
}
.nav-secondary a:hover {
  opacity: 1;
  text-decoration: underline;
  text-underline-offset: 6px;
}
.nav-overlay .nav-term { margin-top: 56px; font-size: 13px; letter-spacing: 0.12em; opacity: 0.55; }
/* decoration logo — ghosted wordmark in bottom-right of overlay panel */
.nav-overlay-logo {
  position: absolute; bottom: 48px; right: var(--margin);
  width: 240px; height: auto;
  opacity: 0.38; mix-blend-mode: multiply;
  pointer-events: none; user-select: none;
}

/* ============ HERO ============
   Layout-led crop: text always sits in the upper-left and lower-left
   safe zones, regardless of what any future hero photo looks like.
   object-position is the ONE thing to retune per new photo — find
   the calmest tonal patch in the new image and aim it at roughly
   25% / 40% (x/y), where these zones read it. */
.hero {
  position: relative; min-height: 100vh; overflow: hidden;
  display: flex; flex-direction: column;
  color: var(--on-dark);
}
.hero-photo {
  position: absolute; top: 0; left: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center 40%; z-index: 0;
}

/* ============ HERO SLIDESHOW ============
   Crossfade carousel inspired by Gagosian's hero. Each slide is
   full-bleed; only one is .is-active at a time. The active slide
   fades in and runs a slow Ken Burns pan/zoom on its <img>. Inactive
   slides reset to scale(1) so the animation restarts cleanly on
   re-activation. Direction alternates by nth-child for variety.
   Per-slide --pos sets object-position so each photo can be aimed
   at its calmest tonal patch (matters under the amber mask). */
.hero-slideshow {
  position: absolute; inset: 0; z-index: 0; overflow: hidden;
}
.hero-slide {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 1.2s ease-in-out;
  will-change: opacity;
}
.hero-slide.is-active { opacity: 1; }
.hero-slide img.hero-photo {
  object-position: var(--pos, center center);
  transform: scale(1);
  transform-origin: center center;
  /* Transition fires when .is-active is removed and the CSS animation
     stops controlling transform. Without this the image snaps instantly
     from mid-animation scale (e.g. 1.06) back to 1.0 during the
     crossfade, causing the visible "shrink" jump. CSS animations suppress
     transitions while active, so this doesn't affect Ken Burns startup. */
  transition: transform 1.2s ease-in-out;
}
/* Ken Burns runs only while a slide is active. Two variants alternate
   so consecutive slides drift in different directions. Duration is
   ~8s — slower than the 6s slide interval so the motion never feels
   like it lands; it always seems to be still moving when we crossfade. */
.hero-slide:nth-child(odd).is-active img.hero-photo  { animation: kenburnsA 8.5s ease-out forwards; }
.hero-slide:nth-child(even).is-active img.hero-photo { animation: kenburnsB 8.5s ease-out forwards; }
@keyframes kenburnsA {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.08) translate3d(-1.4%, -0.6%, 0); }
}
@keyframes kenburnsB {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.08) translate3d(1.4%, 0.6%, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-slide { transition: opacity 0.4s ease; }
  .hero-slide.is-active img.hero-photo,
  .hero-slide:nth-child(odd).is-active img.hero-photo,
  .hero-slide:nth-child(even).is-active img.hero-photo { animation: none; transform: none; }
}

/* ============ HERO SCROLL HINT ============
   Centred above the indicator bar. Inner wrapper handles the bounce so
   the outer translateX(-50%) isn't overridden by the animation. */
.hero-scroll-hint {
  position: absolute; bottom: 28px; left: 50%; transform: translateX(-50%);
  z-index: 3; display: flex; flex-direction: column; align-items: center;
  gap: 6px; color: var(--on-dark); user-select: none; pointer-events: none;
}
.scroll-label {
  font-family: var(--ui); font-size: 9px; font-weight: 500;
  letter-spacing: 0.25em; text-transform: uppercase; opacity: 0.5;
}
.scroll-inner {
  display: flex; align-items: center;
  animation: scrollBounce 1.9s ease-in-out infinite;
  opacity: 0.55;
}
.scroll-chevron { width: 18px; height: 11px; display: block; }
@keyframes scrollBounce {
  0%, 100% { transform: translateY(0);   opacity: 0.55; }
  50%       { transform: translateY(5px); opacity: 0.8;  }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-inner { animation: none; }
}

/* ============ HERO INDICATORS (full-width progress bar) ============
   Spans the full hero width flush at the very bottom edge — like Gagosian's
   bottom-of-carousel bar. "Wide but invisible": each segment is a near-
   transparent track; only the active fill is slightly opaque.
   1px gaps between segments give the impression of distinct slides
   without being noisy. Tap/click anywhere on a segment to jump.
   JS stamps one <button.hero-dot> per slide and sets --slide-duration. */
.hero-indicators {
  position: absolute; bottom: 0; left: 0; right: 0;
  z-index: 3; display: flex; gap: 1px; height: 20px; /* tall tap area */
  align-items: flex-end; /* track sits at very bottom */
}
.hero-dot {
  flex: 1; height: 100%;  /* fill the tap-area row */
  background: none; border: 0; padding: 0; cursor: pointer;
  display: flex; align-items: flex-end;
}
.hero-dot-track {
  width: 100%; height: 3px; /* visual strip */
  background: rgba(255, 252, 247, 0.18); /* near-invisible at rest */
  position: relative; overflow: hidden;
}
.hero-dot-fill {
  position: absolute; inset: 0;
  background: rgba(255, 252, 247, 0.72);
  transform-origin: left center;
  transform: scaleX(0);
}
.hero-dot.is-active .hero-dot-fill {
  animation: dotFill var(--slide-duration, 4s) linear forwards;
}
@keyframes dotFill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
  .hero-dot.is-active .hero-dot-fill { animation: none; transform: scaleX(1); }
}
.hero::before {
  /* flat amber mask, measured to hold 4.5–5.5:1 contrast for white text
     across this photo's text zones — see contrast notes in README */
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: var(--hero-mask);
}
/* localized scrims — sit only behind text, not over the whole photo,
   so a future bright/busy photo still stays readable without
   darkening the image globally */
.hero-inner::before {
  content: ""; position: absolute; top: 0; left: 0;
  width: min(55%, 640px); height: 48%;
  background: radial-gradient(ellipse at top left, rgba(20,14,6,0.38), transparent 72%);
  z-index: 0; pointer-events: none;
}
.hero-bottom::before {
  content: ""; position: absolute; bottom: 0; left: 0;
  width: min(60%, 600px); height: 220px;
  background: radial-gradient(ellipse at bottom left, rgba(20,14,6,0.36), transparent 72%);
  z-index: 0; pointer-events: none;
}
.hero-inner, .hero-bottom { position: relative; z-index: 2; }
.hero-inner { flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 140px var(--margin) 60px; }

/* Chinese display copy uses Noto Serif SC ExtraLight (200) per the
   updated direction — calmer, more literary at large sizes. Small
   labels stay at Light (300) so they don't disappear at body size. */
.term-display .term-zh {
  font-family: var(--hanzi); font-weight: 200;
  font-size: clamp(72px, 12vw, 168px); line-height: 1.05; letter-spacing: 0.06em;
}
.term-display .pentad-zh {
  font-family: var(--hanzi); font-weight: 300;
  font-size: clamp(28px, 3.4vw, 48px); margin-top: 10px; letter-spacing: 0.1em;
}
.term-display .term-en {
  font-family: var(--body); font-weight: 400; font-size: 15px;
  letter-spacing: 0.22em; text-transform: uppercase; margin-top: 18px; opacity: 1;
}

.hero-bottom {
  display: flex; justify-content: space-between; align-items: flex-end; gap: 40px;
  padding: 0 var(--margin) 56px;
}
/* ── Hero tagline ─────────────────────────────────────────────────────── */
.hero-tagline { max-width: 400px; margin-left: auto; text-align: left; position: relative; z-index: 1; }
.hero-tagline .tagline-headline {
  font-family: var(--display); font-weight: 400;
  font-size: clamp(21px, 2vw, 26px);
  line-height: 1.45;
  text-shadow: 0 1px 6px rgba(0,0,0,0.45);
}
.hero-tagline .tagline-body {
  font-family: var(--ui); font-weight: 400;
  font-size: clamp(16px, 1.4vw, 18px);
  line-height: 1.75;
  margin-top: 9px;
  text-shadow: 0 1px 5px rgba(0,0,0,0.4);
}
/* Bottom-right scrim — mirrors the existing bottom-left scrim so the
   tagline on the right side has the same dark backing as the CTA. */
.hero-bottom::after {
  content: ""; position: absolute; bottom: 0; right: 0;
  width: min(55%, 560px); height: 220px;
  background: radial-gradient(ellipse at bottom right, rgba(20,14,6,0.40), transparent 70%);
  z-index: 0; pointer-events: none;
}

/* ── Logo ghost behind term display ──────────────────────────────────── */
.term-display { position: relative; }
.term-mark-ghost {
  position: absolute;
  top: calc(2% + 128px);              /* 128 px below original anchor */
  left: clamp(65px, 10vw, 145px);
  width: clamp(156px, 21.6vw, 234px); /* ×1.2 vs previous */
  height: auto;
  opacity: 0.15;
  pointer-events: none;
  z-index: 0;
  filter: brightness(10);
}

/* dynamic CTA — content & href set by js/main.js */
.cta {
  font-family: var(--ui); font-weight: 500; font-size: 15px; letter-spacing: 0.05em;
  text-decoration: none; display: inline-flex; align-items: baseline; gap: 10px;
  border-bottom: 1px solid transparent; padding-bottom: 2px;
  transition: border-bottom-color 0.2s ease;
}
.cta .arrow { font-family: var(--ui); transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1); }
.cta:hover .arrow, .cta:focus-visible .arrow { transform: translateX(6px); }
.cta:hover, .cta:focus-visible { border-bottom-color: currentColor; }

/* ---- section navigation button (pill, centred, before footer) ---- */
.section-nav {
  display: flex; justify-content: center;
  padding: 64px var(--margin) 96px;
}
.btn-nav {
  font-family: var(--ui); font-weight: 500; font-size: 12px;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  display: inline-flex; align-items: center;
  border: 1px solid var(--ink);
  padding: 14px 32px;
  background: transparent;
  transition: background 0.2s, color 0.2s;
}
.btn-nav:hover, .btn-nav:focus-visible {
  background: var(--ink); color: var(--paper);
}

/* ============ SECTION 2 — CONSTELLATION ============
   background is painted by .gradient-track on the wrapper; this
   section stays transparent so the one continuous gradient shows through */
.constellation-section {
  position: relative;
  padding: 120px 0 140px;
  overflow: hidden;
}
/* Thin top-fade: softens the hard photo edge at the hero/constellation join.
   Uses a short gradient from the hero mask base colour (solid amber at top,
   fading to transparent over ~120px). Sits below the constellation nodes
   (z-index 0) so it never touches readability. The gradient track holds
   amber underneath anyway — this just smooths the photo cut-off edge.
   Keep it short: 120px max. Any taller and it intrudes on the node text. */
.constellation-section::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0;
  height: 120px; z-index: 0; pointer-events: none;
  background: linear-gradient(to bottom, var(--grad-1), transparent);
}

/* ── Jacquard pattern — constellation section ::after ─────────────────
   CSS background-image tiles the culture motif at Jacquard scale (small
   repeated units). The SVG fill is brand cream #e5cfb5 on transparent, so
   on the amber gradient ground this reads as an embossed textile watermark.
   10% opacity keeps it strictly decorative — nodes and text stay AA+. */
.constellation-section::after {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: url('../img/culture-pattern.svg');
  background-repeat: repeat;
  background-size: 196px 196px;  /* 64px per motif unit × 3.07 units/side = 196px */
  opacity: 0.05;
}

.constellation {
  position: relative;
  max-width: 1100px; margin: 0 auto;
  height: 560px;
  z-index: 1;
}
.constellation svg.lines { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.constellation svg.lines line { stroke: var(--line); stroke-width: 1; stroke-dasharray: 4 6; }

.node {
  position: absolute;
  --px: 0px; --py: 0px; --scroll-y: 0px;
  transform: translate(calc(-50% + var(--px)), calc(-50% + var(--py) + var(--scroll-y)));
  transition: transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  will-change: transform;
  background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--display); color: var(--ink);
  text-decoration: none;
}
.node .name { font-size: clamp(19px, 2vw, 26px); line-height: 1.3; }
.node .zh { font-family: var(--hanzi); font-weight: 300; display: block; font-size: 0.78em; margin-top: 4px; letter-spacing: 0.14em; }
.node.center .name { font-size: clamp(26px, 2.8vw, 38px); }

/* orbit node dots — upgraded: solid disc + subtle outer ring for "orbit" feel */
.node::before {
  content: ""; display: block; width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink); margin-bottom: 12px; opacity: 0.75;
  box-shadow: 0 0 0 3px rgba(31, 27, 22, 0.10), 0 0 0 5px rgba(31, 27, 22, 0.04);
}
/* center node: culture emblem replaces the plain dot */
.node.center::before { display: none; }
.node-emblem {
  display: block;
  width: 48px; height: 48px; object-fit: contain;
  opacity: 0.22;
  margin: 0 auto 10px;
  filter: none; /* ink color — SVG fill is already black */
  transition: opacity 0.3s ease;
}
.node.center:hover .node-emblem,
.node.center:focus-visible .node-emblem { opacity: 0.4; }

.node:hover .name, .node:focus-visible .name, .node[aria-expanded="true"] .name { text-decoration: underline; text-underline-offset: 6px; }
.node:focus-visible { outline: 1.5px solid var(--ink); outline-offset: 8px; }
/* centre node: clickable label only, no hover card — jumps straight to
   the Experience landing tab, since it's the umbrella, not an offering */
.node.center { cursor: pointer; text-align: center; }
.node.center .name { text-decoration-style: dotted; text-underline-offset: 8px; text-decoration-line: underline; opacity: 0.9; }
.node.center:hover .name, .node.center:focus-visible .name { opacity: 1; text-decoration-style: solid; }

/* hover / tap card — orbit nodes only */
.node-card {
  position: absolute; z-index: 5; width: min(360px, 80vw);
  background: var(--paper); color: var(--ink);
  padding: 26px 28px; border: 1px solid var(--line);
  opacity: 0; pointer-events: none; transform: translateY(8px);
  transition: opacity 0.25s, transform 0.25s;
}
.node-card.show { opacity: 1; pointer-events: auto; transform: translateY(0); }
.node-card h3 { font-family: var(--display); font-weight: 500; font-size: 19px; }
.node-card h3 .zh { font-family: var(--hanzi); font-weight: 300; font-size: 14px; margin-left: 10px; letter-spacing: 0.14em; }
.node-card p { font-size: 14.5px; margin-top: 12px; line-height: 1.65; }
.node-card p.zh { font-family: var(--hanzi); font-weight: 300; font-size: 13px; color: var(--ink-soft); }
.node-card .card-links { margin-top: 18px; display: flex; flex-direction: column; gap: 8px; }
.node-card .card-links a { font-size: 14px; text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 1px; width: fit-content; }
.node-card .card-links a:hover { border-bottom-color: var(--ink); }
.node-card .card-links a.invite { font-style: italic; opacity: 0.75; }
.node-card .card-links a.invite:hover { opacity: 1; }

/* mobile: constellation becomes a vertical list, tap to open */
/* Constellation mobile: handled in the MOTION SYSTEM block at bottom (max-width: 860px) */

/* ============ SECTION 3 — DESMOND ============
   This is the text-color pivot point: backgrounds before this section
   sit in the warm-amber/cream zone (dark text already works fine since
   --ink was used throughout); .profile-section is where the gradient
   visually settles toward light, and everything from here on commits
   to var(--ink) body text. No literal color flip needed in CSS — --ink
   was the body color sitewide already — this comment just documents
   the design intent for whoever edits this later. */
.profile-section { position: relative; padding: 120px var(--margin); }
.profile { display: grid; grid-template-columns: minmax(280px, 420px) 1fr; gap: 60px; max-width: 1100px; margin: 0 auto; align-items: start; }
.profile-photo { aspect-ratio: 4 / 5; overflow: hidden; background: var(--ink); }
.profile-photo img { width: 100%; height: 100%; object-fit: cover; object-position: center 15%; }
.profile h2 { font-family: var(--display); font-weight: 500; font-size: clamp(28px, 3.4vw, 42px); }
.profile .bio { font-size: 15.5px; max-width: 60ch; margin-top: 20px; }
.profile .bio p + p { margin-top: 16px; }

/* ============ SECTION 3.5 — TESTIMONIAL ============ */
.testimonial-section { position: relative; padding: 0 var(--margin) 120px; text-align: center; }
.testimonial-label {
  font-family: var(--ui); font-weight: 500; font-size: 11px;
  letter-spacing: 0.25em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 28px;
}
.testimonial-quote {
  font-family: var(--display); font-style: italic; font-weight: 400;
  font-size: clamp(24px, 3.2vw, 38px); line-height: 1.5;
  max-width: 820px; margin: 0 auto; color: var(--ink);
}
.testimonial-attrib { margin-top: 32px; }
.testimonial-attrib .name {
  display: block; font-style: normal;
  font-family: var(--ui); font-weight: 500; font-size: 13px;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink);
}
.testimonial-attrib .role {
  display: block; font-family: var(--ui); font-weight: 400; font-size: 11px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-soft);
  margin-top: 6px;
}

/* ============ SECTION 4 — SUB NAV ============ */
.subnav-section { position: relative; padding: 140px var(--margin) 120px; }
.subnav { display: flex; justify-content: space-between; gap: var(--gutter); max-width: 1100px; margin: 0 auto; flex-wrap: wrap; }
.subnav a { text-decoration: none; text-align: center; }
.subnav .en { font-family: var(--display); font-weight: 500; font-size: clamp(26px, 3vw, 38px); }
.subnav .zh { font-family: var(--hanzi); font-weight: 300; display: block; font-size: 13px; margin-top: 8px; letter-spacing: 0.3em; }
.subnav a:hover .en, .subnav a:focus-visible .en { text-decoration: underline; text-underline-offset: 8px; }
.subnav-cta { max-width: 1100px; margin: 90px auto 0; }

/* Subnav links hidden from first paint — revealed on scroll, never on load.
   Transition is set via inline style by JS (more reliable than parent-class
   selectors for triggering transitions on child elements). */
.subnav a,
.subnav-cta .cta {
  opacity: 0;
  transform: translateY(24px);
}
.subnav a.subnav-visible,
.subnav-cta .cta.subnav-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ============ FOOTER ============ */
.site-footer {
  background: var(--grad-5); color: var(--ink);
  padding: 64px var(--margin) 40px;
}
.footer-main {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: 48px; flex-wrap: wrap;
}
.footer-brand { display: flex; flex-direction: column; gap: 20px; }
.footer-logo {
  max-width: 300px; height: auto;
  mix-blend-mode: multiply; opacity: 0.82;
}
.footer-term { font-size: 13px; letter-spacing: 0.12em; opacity: 0.6; max-width: 320px; }
.footer-term .zh { font-family: var(--hanzi); font-weight: 300; letter-spacing: 0.2em; }
.footer-links { display: flex; flex-direction: column; gap: 18px; text-align: right; }
.footer-links nav { display: flex; gap: 22px; justify-content: flex-end; flex-wrap: wrap; }
.footer-links nav a {
  font-family: var(--ui); font-weight: 500; font-size: 11px;
  letter-spacing: 0.1em; text-transform: uppercase;
  text-decoration: none; opacity: 0.65; color: var(--ink);
}
.footer-links nav a:hover { opacity: 1; text-decoration: underline; }
/* Instagram row — icon replaces the arrow */
.footer-social { display: flex; justify-content: flex-end; }
.footer-social a {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--ui); font-weight: 400; font-size: 12.5px;
  letter-spacing: 0.03em;
  color: var(--ink); opacity: 0.65; text-decoration: none;
}
.footer-social a:hover { opacity: 1; text-decoration: underline; }
.ig-icon { width: 16px; height: 16px; flex-shrink: 0; }
.footer-contact {
  font-family: var(--ui); font-weight: 300;
  font-size: 12.5px; opacity: 0.55; letter-spacing: 0.01em;
  text-align: right;
}
.footer-legal {
  margin-top: 40px; padding-top: 22px;
  border-top: 1px solid var(--line);
  font-family: var(--ui); font-weight: 300;
  font-size: 11px; opacity: 0.45; letter-spacing: 0.06em;
}

/* ============ INNER PAGES — shared ============ */
.page { background: var(--paper); min-height: 100vh; padding-top: 130px; }
.page-title { text-align: center; }
.page-title h1 { font-family: var(--display); font-weight: 600; font-size: clamp(36px, 5vw, 56px); }
.page-title .zh { font-family: var(--hanzi); font-weight: 300; display: block; font-size: 15px; letter-spacing: 0.4em; margin-top: 10px; }
.page-subtitle {
  text-align: center; font-size: 13px; letter-spacing: 0.18em; color: var(--ink-soft);
  margin-top: 16px; text-transform: uppercase;
}
.page-subtitle .zh, .page-subtitle [data-term="zh"] { font-family: var(--hanzi); font-weight: 300; letter-spacing: 0.16em; text-transform: none; }
.page-title .term-line { font-size: 13px; letter-spacing: 0.18em; margin-top: 18px; color: var(--ink-soft); }

/* tag chips — one style sitewide */
.chip {
  display: inline-block; font-family: var(--ui); font-weight: 500; font-size: 11px;
  letter-spacing: 0.07em; text-transform: uppercase; padding: 4px 10px;
  border: 1px solid var(--line);
  background: var(--paper); text-decoration: none; white-space: nowrap;
}
.chip:hover, .chip:focus-visible { border-color: var(--ink); }
.chip.solid { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* ============ EXPERIENCE PAGE ============ */
.experience-tabs { display: flex; gap: 36px; justify-content: center; flex-wrap: wrap; margin: 56px var(--margin) 0; }
.experience-tabs a {
  font-family: var(--body); font-weight: 400; font-size: 16px; letter-spacing: 0.03em;
  text-decoration: none; color: var(--ink-soft); padding-bottom: 6px; border-bottom: 1px solid transparent;
}
.experience-tabs a.active { color: var(--ink); border-bottom-color: var(--ink); font-weight: 500; }
.experience-panel { display: none; margin: 0 0 0; }
.experience-panel.active { display: block; }

/* —— text panel (umbrella tab: Le Rizière Experience) —— */
.experience-panel .text-block { max-width: 720px; margin: 70px auto 0; padding: 0 var(--margin); }
.experience-panel .text-block h2 { font-family: var(--display); font-weight: 500; font-size: clamp(26px, 3vw, 36px); text-align: center; }
.experience-panel .text-block h2 .zh { font-family: var(--hanzi); font-weight: 200; display: block; font-size: 0.55em; margin-top: 10px; letter-spacing: 0.3em; }
.experience-panel .text-block h3 { font-family: var(--body); font-weight: 500; font-size: 13px; letter-spacing: 0.24em; text-transform: uppercase; margin: 48px 0 14px; }
.experience-panel .text-block p { font-size: 15.5px; margin-top: 14px; }
.experience-panel .text-block p.zh { font-family: var(--hanzi); font-weight: 300; font-size: 14px; color: var(--ink-soft); }
/* .needs-input styles removed — all placeholder content converted to HTML comments.
   Search for "NEEDS INPUT" in experience.html to find content gaps. */
.experience-panel .panel-cta { margin: 48px auto 110px; max-width: 720px; padding: 0 var(--margin); display: flex; gap: 28px; flex-wrap: wrap; }

/* —— scroll gallery (Quince-style): full-bleed image sections,
   each ~90vh, with a short caption that fades in. "Easy to expand":
   adding a 6th image is one more .gallery-frame block, nothing else
   changes. Frames default to LANDSCAPE (full-bleed). For PORTRAIT
   photography, add data-orientation="portrait" to the frame and
   the image becomes centered with breathing room on the sides. —— */
.gallery-head { max-width: 720px; margin: 70px auto 0; padding: 0 var(--margin); text-align: center; }
.gallery-head h2 { font-family: var(--display); font-weight: 500; font-size: clamp(26px, 3vw, 36px); }
.gallery-head h2 .zh { font-family: var(--hanzi); font-weight: 200; display: block; font-size: 0.55em; margin-top: 10px; letter-spacing: 0.3em; }
.gallery-head p { font-size: 15.5px; margin-top: 18px; }
.gallery-head p.zh { font-family: var(--hanzi); font-weight: 300; font-size: 14px; color: var(--ink-soft); margin-top: 8px; }

/* —— structured offer details (price + guests block) —— */
.offer-meta {
  display: grid; grid-template-columns: auto 1fr;
  gap: 10px 24px; list-style: none;
  margin: 32px auto 0; padding: 24px 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  text-align: left; max-width: 480px;
}
.offer-meta dt {
  font-family: var(--body); font-size: 11px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--ink-soft);
  padding-top: 2px; white-space: nowrap; align-self: start;
}
.offer-meta dd { font-size: 14.5px; margin: 0; line-height: 1.6; }
.offer-note {
  margin-top: 20px; font-size: 13.5px;
  color: var(--ink-soft); line-height: 1.65;
  font-style: italic;
}

.gallery-frame {
  position: relative; min-height: 90vh; display: flex; align-items: flex-end;
  margin-top: 64px; overflow: hidden; background: var(--vi-cream);
}
/* default = landscape: image fills the frame */
.gallery-frame img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* portrait: image is centered, has max-width, with cream space on the sides
   — feels like a printed image plate rather than a wall projection */
.gallery-frame[data-orientation="portrait"] { background: var(--vi-cream); align-items: center; padding: 60px var(--margin); }
.gallery-frame[data-orientation="portrait"] img { position: relative; inset: auto; width: auto; max-width: 480px; max-height: 80vh; margin: 0 auto; object-fit: contain; }
.gallery-frame[data-orientation="portrait"] .caption { background: none; color: var(--ink); position: absolute; bottom: 30px; left: 0; right: 0; padding: 0 var(--margin); text-align: center; }

.gallery-frame .caption {
  position: relative; z-index: 1; color: var(--on-dark);
  padding: 36px var(--margin); width: 100%;
  background: linear-gradient(to top, rgba(20,14,6,0.55), transparent);
}
.gallery-frame .caption .en { font-family: var(--display); font-size: 18px; }
.gallery-frame .caption .zh { font-family: var(--hanzi); font-weight: 300; font-size: 13px; margin-top: 6px; opacity: 0.9; letter-spacing: 0.08em; }
.gallery-frame.reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.7s ease, transform 0.7s ease; }
.gallery-frame.reveal.in { opacity: 1; transform: translateY(0); }
.gallery-frame-inline.reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.7s ease, transform 0.7s ease; }
.gallery-frame-inline.reveal.in { opacity: 1; transform: translateY(0); }
.gallery-pair-inline.reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.75s ease, transform 0.75s ease; }
.gallery-pair-inline.reveal.in { opacity: 1; transform: translateY(0); }

/* inline gallery frames inside the umbrella text tab —
   shorter than the full scroll galleries (60vh vs 90vh),
   full-bleed edge-to-edge, with the same reveal animation */
.gallery-frame-inline {
  position: relative; min-height: 60vh; overflow: hidden;
  margin-top: 72px; background: var(--vi-cream);
  display: flex; align-items: flex-end;
}
.gallery-frame-inline img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
/* portrait inline: centered image, cream sides — same logic as portrait gallery */
.gallery-frame-inline[data-orientation="portrait"] {
  background: var(--vi-cream); align-items: center;
  justify-content: center; padding: 60px var(--margin);
  min-height: 70vh;
}
.gallery-frame-inline[data-orientation="portrait"] img {
  position: relative; inset: auto; width: auto;
  max-width: 440px; max-height: 72vh; margin: 0 auto; object-fit: contain;
}
.gallery-frame-inline .caption {
  position: relative; z-index: 1; color: var(--on-dark);
  padding: 28px var(--margin); width: 100%;
  background: linear-gradient(to top, rgba(20,14,6,0.50), transparent);
}
.gallery-frame-inline[data-orientation="portrait"] .caption {
  position: absolute; bottom: 0; left: 0; right: 0;
  background: none; color: var(--ink); text-align: center; padding: 0 var(--margin) 28px;
}
.gallery-frame-inline .caption .en { font-family: var(--display); font-size: 17px; }
.gallery-frame-inline .caption .zh { font-family: var(--hanzi); font-weight: 300; font-size: 13px; margin-top: 5px; opacity: 0.88; letter-spacing: 0.08em; }
/* two-up side-by-side inline pair */
.gallery-pair-inline {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  margin-top: 72px;
}
.gallery-pair-inline .gallery-frame-inline { margin-top: 0; }
@media (max-width: 760px) {
  .gallery-pair-inline { grid-template-columns: 1fr; }
}

.gallery-cross { max-width: 720px; margin: 56px auto 0; padding: 0 var(--margin); text-align: center; }
.gallery-cross a { font-family: var(--body); font-size: 15px; text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 2px; }
.gallery-cross a:hover { border-bottom-color: var(--ink); }

/* full-height side-by-side pair for commercial gallery tabs (90vh, same as .gallery-frame) */
.gallery-pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  margin-top: 64px;
}
.gallery-pair .gallery-frame { margin-top: 0; }
.gallery-pair.reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.75s ease, transform 0.75s ease; }
.gallery-pair.reveal.in { opacity: 1; transform: translateY(0); }
@media (max-width: 760px) {
  .gallery-pair { grid-template-columns: 1fr; }
}

/* ============ MENU PAGE — hanging paper strips ============
   Reference: the wall of paper menu slips in classic Cantonese
   restaurants in SF. Each .dish is a vertical strip with a dish
   photo behind a dark mask, white vertical title on top.
   States:
     default      darkened photo + vertical zh/en title (top-aligned)
     hover        mask fades, photo reveals, title fades
     .open        strip expands to ~640px; siblings compress to ~half
                  to hint at horizontal continuation. Detail panel
                  shows: vertical zh/en title + dish photo + desc.
   Close: × button (top-right) or click outside any strip.
============================================================== */
.menu-board {
  display: flex; justify-content: center; align-items: stretch;
  gap: 14px;
  margin: 60px var(--margin) 120px;
  height: 580px;
  /* keep strips visible on smaller desktops if they get compressed */
}

.dish {
  position: relative; overflow: hidden;
  flex: 1 1 160px;
  min-width: 80px; max-width: 200px;
  cursor: pointer;
  background: var(--ink);
  transition: flex-basis 0.55s cubic-bezier(0.22, 0.61, 0.36, 1),
              max-width 0.55s cubic-bezier(0.22, 0.61, 0.36, 1),
              min-width 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
  outline: none;
}
.dish:focus-visible { box-shadow: inset 0 0 0 2px var(--paper); }

/* Background photo layer */
.dish-bg {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  transition: transform 0.6s ease, filter 0.5s ease;
  filter: saturate(0.95);
}

/* Dark mask sits on top of the photo (matches .past-cover-mask register) */
.dish-mask {
  position: absolute; inset: 0;
  background: rgba(20, 16, 12, 0.62);
  transition: background 0.45s ease, opacity 0.45s ease;
  pointer-events: none;
}

/* Default vertical title — zh pinned LEFT as a flex-column of individual
   chars (Chromium doesn't advance CJK glyphs in writing-mode:vertical-rl
   without a valid vertical font metric, so per-char spans is the fix).
   en pinned RIGHT using writing-mode:vertical-rl, which works fine for Latin. */
.dish-title {
  position: absolute; inset: 22px 12px;
  color: var(--on-dark);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.65);
  transition: opacity 0.4s ease;
  pointer-events: none;
  z-index: 2;
}
/* zh: absolutely-positioned flex column, chars stacked top-down */
.dish-title .zh {
  position: absolute; top: 0; left: 0; bottom: 0;
  display: flex; flex-direction: column; align-items: center;
  width: 36px;
}
.dish-title .zh span {
  font-family: var(--hanzi); font-weight: 400;
  font-size: clamp(18px, 1.6vw, 24px);
  line-height: 1.3;
  display: block;
  text-align: center;
  width: 100%;
}
/* en: vertical-rl works correctly for Latin */
.dish-title .en {
  position: absolute; top: 0; right: 0; bottom: 0;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-family: var(--display); font-style: italic;
  font-size: 12px; letter-spacing: 0.03em;
  color: rgba(255, 252, 247, 0.9);
  white-space: nowrap;
  max-height: 100%;
  overflow: hidden;
}

/* Hover state (only when this dish is NOT open and nothing else is open) */
.menu-board:not(.has-open) .dish:hover .dish-mask,
.menu-board:not(.has-open) .dish:focus-visible .dish-mask {
  background: rgba(20, 16, 12, 0);
}
.menu-board:not(.has-open) .dish:hover .dish-title,
.menu-board:not(.has-open) .dish:focus-visible .dish-title {
  opacity: 0;
}
.menu-board:not(.has-open) .dish:hover .dish-bg,
.menu-board:not(.has-open) .dish:focus-visible .dish-bg {
  transform: scale(1.03);
}

/* When one is open: siblings compress to a sliver, the open one expands.
   This creates the wireframe-04 effect of adjacent panels "pushed aside" */
.menu-board.has-open .dish {
  flex: 0 0 70px;
  min-width: 70px;
  max-width: 70px;
  cursor: pointer;
}
.menu-board.has-open .dish .dish-title .en {
  /* en label too tight to read at 70px — hide it; zh keeps the identity */
  opacity: 0;
}

.menu-board.has-open .dish.open {
  flex: 1 1 680px;
  min-width: 480px; max-width: 760px;
  cursor: default;
  background: var(--paper);
}
.dish.open .dish-bg,
.dish.open .dish-mask,
.dish.open .dish-title {
  opacity: 0;
}

/* Expanded detail panel — three columns:
   [vertical title strip] [photo] [english description] */
.dish-detail {
  position: absolute; inset: 0;
  display: grid;
  grid-template-columns: 56px minmax(180px, 0.9fr) minmax(200px, 1.1fr);
  gap: 20px;
  padding: 28px 52px 28px 20px;
  background: var(--paper);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s ease 0.15s;
}
.dish.open .dish-detail {
  opacity: 1;
  pointer-events: auto;
}

/* Vertical title inside expanded panel — same per-char approach as default state */
.dish-detail-title {
  display: flex; flex-direction: row; gap: 8px;
  align-items: flex-start;
  color: var(--ink);
  padding-top: 4px;
  overflow: hidden;
  height: 100%;
}
.dish-detail-title .zh {
  display: flex; flex-direction: column; align-items: center;
  flex-shrink: 0;
  width: 28px;
  overflow: hidden;
}
.dish-detail-title .zh span {
  font-family: var(--hanzi); font-weight: 400;
  font-size: clamp(16px, 1.5vw, 21px);
  line-height: 1.25;
  display: block;
  text-align: center;
  width: 100%;
}
.dish-detail-title .en {
  writing-mode: vertical-rl;
  text-orientation: mixed;
  font-family: var(--display); font-style: italic;
  font-size: 12px; letter-spacing: 0.03em;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  align-self: flex-start;
  max-height: 100%;
}

.dish-detail-photo {
  position: relative; overflow: hidden;
  align-self: stretch;
  background: rgba(31, 27, 22, 0.06);
}
.dish-detail-photo img {
  width: 100%; height: 100%; object-fit: cover; max-width: none;
  display: block;
}

.dish-detail-body {
  display: flex; flex-direction: column;
  align-self: center;
  padding-right: 8px;
}
.dish-detail-body p {
  font-family: var(--body);
  font-size: 14.5px; line-height: 1.65;
  color: var(--ink);
  margin: 0;
}
.dish-detail-body .chips {
  margin-top: 22px;
  display: flex; gap: 8px; flex-wrap: wrap;
}
.dish-detail-body .chip {
  font-family: var(--body); font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--ink); text-decoration: none;
  border: 1px solid var(--line);
  padding: 5px 10px;
  transition: background 0.2s ease, color 0.2s ease;
}
.dish-detail-body .chip:hover {
  background: var(--ink); color: var(--paper);
}

/* Close × — visible only on the open dish */
.close-dish {
  position: absolute; top: 14px; right: 16px;
  background: none; border: 0;
  font-family: var(--body); font-size: 26px; line-height: 1;
  color: var(--ink); cursor: pointer;
  padding: 4px 8px;
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s ease 0.2s;
  z-index: 3;
}
.dish.open .close-dish { opacity: 1; pointer-events: auto; }
.close-dish:hover { color: var(--grad-1); }

@media (max-width: 860px) {
  /* Mobile: stack vertically. Strips become horizontal bars with the
     dish photo + a horizontal title overlay. Open state expands the bar
     downward to reveal the detail content stacked. */
  .menu-board {
    flex-direction: column;
    height: auto;
    gap: 12px;
    margin: 40px var(--margin) 80px;
  }
  .dish {
    flex: none;
    min-width: 0; max-width: none;
    width: 100%;
    height: 120px;
  }
  .dish-title {
    top: auto; bottom: 0;
    padding: 16px 18px;
    inset: auto 0 0 0;
  }
  .dish-title .zh {
    position: static; flex-direction: row; align-items: baseline; gap: 2px;
  }
  .dish-title .zh span { font-size: 24px; line-height: 1; }
  .dish-title .en {
    position: static;
    writing-mode: horizontal-tb; font-size: 12px;
    display: inline; margin-left: 10px;
  }
  .dish-detail-title {
    flex-direction: row; writing-mode: horizontal-tb;
    align-items: baseline; gap: 14px;
  }
  .dish-detail-title .zh { flex-direction: row; }
  .dish-detail-title .zh span { font-size: 22px; }
  .dish-detail-title .en { writing-mode: horizontal-tb; margin-left: 0; }

  .menu-board.has-open .dish {
    flex: none; min-width: 0; max-width: none; width: 100%;
    height: 70px;
  }
  .menu-board.has-open .dish .dish-title .en { opacity: 0.86; } /* keep readable */
  .menu-board.has-open .dish.open {
    height: auto; min-width: 0; max-width: none;
  }
  .dish-detail {
    position: relative;
    grid-template-columns: 1fr;
    gap: 18px;
    padding: 22px 22px 28px;
  }
  .dish-detail-title {
    writing-mode: horizontal-tb;
    align-items: baseline;
    gap: 14px;
  }
  .dish-detail-photo { aspect-ratio: 4 / 3; }
  .close-dish { top: 8px; right: 10px; font-size: 24px; }
}

/* ============ PAST PAGE — timeline ============
   Layout:
   - centered dashed vertical line via .past-timeline::before
   - .past-row holds a .past-cover (image tile) + .past-detail (description card)
     side-by-side; class .past-row-left puts the cover on the left, .past-row-right
     puts it on the right. The empty opposite slot reveals .past-detail on hover.
   - default state: cover image is darkened by .past-cover-mask (40% black)
   - hover state: mask fades to 0%, image returns to real tone; .past-detail
     fades in on the opposite side (was invisible/transparent before).
   Wireframe references: 05 (default), 06 (hover).
*/

/* Header row — centered title with CTA pinned right (wireframe 05) */
.past-page-head {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 24px;
  max-width: 1200px; margin: 16px auto 0;
  padding: 0 var(--margin);
}
.past-title-center { grid-column: 2; text-align: center; }
.past-head-cta {
  grid-column: 3; justify-self: end; align-self: end;
  padding-bottom: 6px;            /* visually align with title baseline */
}
@media (max-width: 760px) {
  .past-page-head { grid-template-columns: 1fr; }
  .past-title-center, .past-head-cta { grid-column: 1; justify-self: center; }
  .past-head-cta { margin-top: 18px; }
}

.past-head {
  display: flex; justify-content: flex-end;
  max-width: 1200px; margin: 24px auto 0; padding: 0 var(--margin);
}

.past-timeline {
  position: relative; max-width: 1200px;
  margin: 60px auto 120px; padding: 0 var(--margin);
}
.past-timeline::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  border-left: 1.5px dashed var(--ink);
  opacity: 0.5;
}

.past-row {
  display: grid; grid-template-columns: 1fr 1fr;
  grid-auto-flow: dense;          /* lets the detail back-fill col 1 when cover is in col 2 */
  gap: 56px;                      /* gap straddles the dashed line */
  margin-bottom: 88px;
  align-items: stretch;           /* detail cell stretches to match cover height */
}
.past-row:last-child { margin-bottom: 0; }

/* Cover image tile — clickable, navigates to event detail page */
.past-cover {
  position: relative; display: block;
  aspect-ratio: 16 / 11;
  overflow: hidden;
  text-decoration: none; color: var(--paper);
  background: var(--field-top);
}
.past-cover img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 0.6s ease;
}
/* default-state dark filter sits on top of the photo */
.past-cover-mask {
  position: absolute; inset: 0;
  background: rgba(20, 16, 12, 0.42);
  transition: background 0.4s ease;
  pointer-events: none;
}

/* Centered event-name pill — 25% transparent white fill, no border, white text */
.past-cover-name {
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(255, 255, 255, 0.25);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--paper);
  padding: 9px 22px;
  font-family: var(--body); font-weight: 500;
  font-size: 14px; letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  max-width: 86%;
  overflow: hidden; text-overflow: ellipsis;
  transition: opacity 0.35s ease;
  z-index: 1;
}

/* Chip strip at bottom-right of cover tile */
.past-cover-chips {
  position: absolute; right: 14px; bottom: 14px;
  display: flex; gap: 6px; flex-wrap: wrap;
  justify-content: flex-end;
  max-width: calc(100% - 28px);
  transition: opacity 0.35s ease;
  z-index: 1;
}

/* Chips on the cover — 25% transparent fill, no border, white text */
.past-cover-chips .ev-chip {
  background: rgba(255, 255, 255, 0.25);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: var(--paper);
  border: 0;
  padding: 4px 9px;
  font-family: var(--body); font-weight: 400;
  font-size: 10.5px; letter-spacing: 0.04em;
  white-space: nowrap;
  line-height: 1.3;
}
.past-cover-chips .ev-chip.ev-chip-strong { font-weight: 600; }
.past-cover-chips .ev-chip .zh {
  font-family: var(--hanzi); font-weight: 300;
  letter-spacing: 0.05em; margin-left: 2px;
}

/* Chips on the description card (right side of row) — keep dark-on-paper readable */
.past-detail-chips .ev-chip {
  background: transparent; color: var(--ink);
  border: 1px solid var(--ink);
  padding: 4px 9px;
  font-family: var(--body); font-weight: 400;
  font-size: 10.5px; letter-spacing: 0.04em;
  white-space: nowrap; line-height: 1.3;
}
.past-detail-chips .ev-chip.ev-chip-strong { font-weight: 600; }
.past-detail-chips .ev-chip .zh {
  font-family: var(--hanzi); font-weight: 300;
  letter-spacing: 0.05em; margin-left: 2px;
}

/* Hover: drop the mask, reveal real photo, slight zoom, fade out overlay text */
.past-cover:hover .past-cover-mask,
.past-cover:focus-visible .past-cover-mask {
  background: rgba(20, 16, 12, 0);
}
.past-cover:hover img,
.past-cover:focus-visible img {
  transform: scale(1.03);
}
/* On hover, the name pill and chips on the photo fade away so only the
   description card (on the opposite side) communicates the event info. */
.past-cover:hover .past-cover-name,
.past-cover:hover .past-cover-chips,
.past-cover:focus-visible .past-cover-name,
.past-cover:focus-visible .past-cover-chips {
  opacity: 0;
}

/* Description detail card — sits opposite the cover, invisible by default.
   Stretches to match the cover's height (row alignment is `stretch`), and
   uses flexbox to vertically center its content within that frame so the
   text reads inside the same horizontal band as the photo. */
.past-detail {
  display: flex; flex-direction: column; justify-content: center;
  padding: 6px 4px;
  opacity: 0;
  transition: opacity 0.4s ease 0.05s;
  pointer-events: none;
}
.past-detail-name {
  font-family: var(--body); font-weight: 500;
  font-size: 13px; letter-spacing: 0.18em;
  text-transform: uppercase; margin: 0 0 18px;
  color: var(--ink);
}
.past-detail-desc {
  font-size: 14.5px; line-height: 1.75;
  color: var(--ink); margin: 0 0 28px;
  max-width: 460px;
}
.past-detail-chips {
  display: flex; gap: 6px; flex-wrap: wrap;
}

/* When the user hovers/focuses the cover in the same row, fade the
   detail card in — :has() lets us scope this without JS */
.past-row:has(.past-cover:hover) .past-detail,
.past-row:has(.past-cover:focus-visible) .past-detail {
  opacity: 1;
}

/* Side placement: alternate cover position via grid order */
.past-row-left  .past-cover  { grid-column: 1; }
.past-row-left  .past-detail { grid-column: 2; }
.past-row-right .past-cover  { grid-column: 2; }
.past-row-right .past-detail { grid-column: 1; text-align: left; }

/* Mobile: stack — single column, dashed line out, detail always visible below cover */
@media (max-width: 760px) {
  .past-timeline { margin-top: 32px; margin-bottom: 80px; }
  .past-timeline::before { display: none; }
  .past-row, .past-row-left, .past-row-right {
    grid-template-columns: 1fr;
    gap: 20px;
    margin-bottom: 60px;
  }
  .past-row-left  .past-cover,
  .past-row-right .past-cover,
  .past-row-left  .past-detail,
  .past-row-right .past-detail { grid-column: 1; }
  .past-detail { opacity: 1; }
  /* On mobile keep the mask darker so the overlay text reads against the photo;
     no hover state to drop it, so the chips/name must stay legible at all times. */
  .past-cover-mask { background: rgba(20, 16, 12, 0.50); }
}

/* ============ PAST EVENT — DETAIL PAGE ============
   Atomix-style: full-bleed hero, title overlay, sparse text with tags,
   loose grid of supporting images. Not a strict gallery — uses CSS
   grid with intentional irregular spans for rhythm. */
.event-hero {
  position: relative; width: 100%; height: 80vh;
  min-height: 520px; max-height: 760px;
  overflow: hidden;
  margin-top: -130px;            /* cancel .page padding-top so hero is true-edge */
}
.event-hero img {
  width: 100%; height: 100%; object-fit: cover;
  display: block;
}
.event-hero::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(20,16,12,0.30) 0%, rgba(20,16,12,0.55) 65%, rgba(20,16,12,0.75) 100%);
  pointer-events: none;
}
.event-hero-meta {
  position: absolute; left: var(--margin); bottom: 56px; right: var(--margin);
  color: var(--paper);
  z-index: 1;
}
.event-hero-date {
  font-family: var(--body); font-weight: 400;
  font-size: 12px; letter-spacing: 0.3em;
  text-transform: uppercase;
  opacity: 0.85; margin-bottom: 18px;
}
.event-hero-title {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(34px, 5vw, 64px); line-height: 1.1;
  margin: 0; max-width: 880px;
}

/* Body block under the hero */
.event-body {
  max-width: 760px; margin: 96px auto 0;
  padding: 0 var(--margin);
}
/* Back-to-Past navigation — small text link above the description.
   Positioned in body block so it doesn't compete with the immersive hero. */
.event-back {
  display: inline-flex; align-items: center; gap: 8px;
  margin-bottom: 36px;
  font-family: var(--body); font-size: 13px;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-soft); text-decoration: none;
  transition: color 0.2s;
}
.event-back .arrow {
  display: inline-block;
  transition: transform 0.25s ease;
}
.event-back:hover, .event-back:focus-visible {
  color: var(--ink);
}
.event-back:hover .arrow, .event-back:focus-visible .arrow {
  transform: translateX(-4px);
}
.event-desc {
  font-size: 17px; line-height: 1.8; color: var(--ink);
  margin: 0 0 40px;
}
.event-tag-row {
  display: flex; gap: 10px; flex-wrap: wrap;
  margin-bottom: 24px;
}
.event-tag-row .tag-label {
  font-family: var(--body); font-size: 11px;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-soft);
  padding: 7px 0 7px;
  margin-right: 8px;
}
.event-tag {
  background: transparent; color: var(--ink);
  border: 1px solid var(--ink);
  padding: 7px 14px;
  font-family: var(--body); font-size: 12px;
  letter-spacing: 0.06em;
  text-decoration: none;
  transition: background 0.2s, color 0.2s;
}
.event-tag:hover, .event-tag:focus-visible {
  background: var(--ink); color: var(--paper);
}

/* Menu listing (table events only) */
.event-menu {
  max-width: 760px; margin: 64px auto 0;
  padding: 0 var(--margin);
}
.event-menu-title {
  font-family: var(--body); font-size: 11px;
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--ink-soft);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  margin: 0 0 8px;
}
.event-menu-list {
  list-style: none; margin: 0; padding: 0;
}
.event-menu-item {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
  gap: 24px;
}
.event-menu-item .course-num {
  font-family: var(--body); font-size: 11px;
  letter-spacing: 0.22em; color: var(--ink-soft);
  flex-shrink: 0; min-width: 36px;
}
.event-menu-item .course-name {
  font-family: var(--display); font-size: 18px;
  flex: 1;
}
.event-menu-item .course-note {
  font-family: var(--body); font-size: 13px;
  color: var(--ink-soft); font-style: italic;
  text-align: right;
}

/* Loose gallery grid for supporting images — Atomix style.
   Not strict pairs: uses irregular spans to create rhythm.
   Default tile is 1 column / 1 row of a 12-col grid.
   `data-span="wide"` -> 2 cols, `data-span="tall"` -> 1 col 2 rows. */
.event-gallery {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 16px;
  max-width: 1400px;
  margin: 96px auto 120px;
  padding: 0 var(--margin);
}
.event-gallery figure {
  position: relative; margin: 0;
  grid-column: span 6;            /* default = 6/12 = half row */
  aspect-ratio: 4 / 5;             /* portrait default */
  overflow: hidden;
  background: var(--field-top);
}
.event-gallery figure[data-span="wide"]   { grid-column: span 8; aspect-ratio: 3 / 2; }
.event-gallery figure[data-span="full"]   { grid-column: span 12; aspect-ratio: 21 / 9; }
.event-gallery figure[data-span="narrow"] { grid-column: span 4; aspect-ratio: 4 / 5; }
.event-gallery figure[data-span="square"] { grid-column: span 6; aspect-ratio: 1 / 1; }
.event-gallery figure img {
  width: 100%; height: 100%; object-fit: cover;
  display: block;
}

@media (max-width: 760px) {
  .event-hero { height: 70vh; min-height: 440px; margin-top: -100px; }
  .event-hero-meta { bottom: 36px; }
  .event-body, .event-menu { margin-top: 56px; }
  .event-gallery {
    grid-template-columns: 1fr;
    margin-top: 60px; margin-bottom: 80px;
  }
  .event-gallery figure,
  .event-gallery figure[data-span="wide"],
  .event-gallery figure[data-span="full"],
  .event-gallery figure[data-span="narrow"],
  .event-gallery figure[data-span="square"] {
    grid-column: 1; aspect-ratio: 4 / 5;
  }
}

/* Back-to-top button — sits after the gallery, before the footer.
   Minimal circular outline with an up arrow; click scrolls to top smoothly. */
.event-top-anchor {
  display: flex; justify-content: center;
  padding: 0 var(--margin) 96px;
}
.btn-top {
  display: inline-flex; flex-direction: column; align-items: center; gap: 12px;
  background: none; border: 0; cursor: pointer;
  color: var(--ink-soft);
  font-family: var(--body); font-size: 11px;
  letter-spacing: 0.26em; text-transform: uppercase;
  padding: 0;
  transition: color 0.2s;
}
.btn-top-circle {
  width: 56px; height: 56px;
  border: 1px solid var(--ink);
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.2s, color 0.2s, transform 0.25s ease;
  background: transparent; color: var(--ink);
}
.btn-top-circle svg { width: 18px; height: 18px; display: block; }
.btn-top:hover .btn-top-circle,
.btn-top:focus-visible .btn-top-circle {
  background: var(--ink); color: var(--paper);
  transform: translateY(-4px);
}
.btn-top:hover, .btn-top:focus-visible { color: var(--ink); }

/* ============ CONTACT PAGE ============
   Type hierarchy:
   • intro body copy → --body weight 300 (editorial tone, matches other pages)
   • form labels     → --ui  weight 500, uppercase, tracked (control layer)
   • inputs/textarea → --ui  weight 300 (entry feels neutral, distinct from labels)
   • send button     → --ui  weight 500, uppercase (same register as labels + btn-nav)
   • placeholder     → --ui  weight 300, reduced opacity
============================================================ */
.contact-wrap { max-width: 600px; margin: 60px auto 120px; padding: 0 var(--margin); }
.contact-intro p {
  font-family: var(--body); font-weight: 300;
  font-size: 15.5px; line-height: 1.7; margin-top: 14px;
}
.contact-intro a { color: var(--ink); text-underline-offset: 3px; }
.contact-form { margin-top: 56px; }

/* ---- field ---- */
.field { margin-bottom: 36px; }
.field label {
  display: block;
  font-family: var(--ui); font-weight: 500;
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink); margin-bottom: 10px;
}
.field label .count {
  float: right;
  font-family: var(--ui); font-weight: 400; font-size: 11px;
  letter-spacing: 0.04em; text-transform: none;
  color: var(--ink-soft);
}
.field input, .field select, .field textarea {
  width: 100%; border: 0; border-bottom: 1px solid var(--line);
  background: transparent; padding: 10px 2px;
  font-family: var(--ui); font-weight: 300;
  font-size: 14.5px; color: var(--ink); border-radius: 0;
  appearance: none;
}
.field select {
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='8' viewBox='0 0 14 8' fill='none'%3E%3Cpath d='M1 1L7 7L13 1' stroke='%231F1B16' stroke-opacity='0.5' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 4px center;
  padding-right: 26px;
}
.field textarea { min-height: 150px; resize: vertical; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-bottom-color: var(--ink);
}
.field ::placeholder {
  font-family: var(--ui); font-weight: 300;
  color: rgba(31, 27, 22, 0.38); font-size: 13.5px;
}

/* ---- send button — --ui font, send icon (paper plane) ---- */
.send-btn {
  margin-left: auto; display: flex; align-items: center; gap: 10px;
  background: none; border: 1px solid var(--ink); padding: 13px 28px;
  font-family: var(--ui); font-weight: 500;
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer; color: var(--ink);
  transition: background 0.2s, color 0.2s;
}
.send-btn:hover, .send-btn:focus-visible {
  background: var(--ink); color: var(--paper);
}
.send-btn:hover .send-icon path,
.send-btn:focus-visible .send-icon path { stroke: var(--paper); }
.send-icon {
  width: 18px; height: 18px; flex-shrink: 0;
  transition: transform 0.25s;
}
.send-icon path { transition: stroke 0.2s; }
.send-btn:hover .send-icon, .send-btn:focus-visible .send-icon {
  transform: translateX(3px) translateY(-2px);
}

/* ── Contact form: error / loading / success states ───────────────────────
   Design principles:
   • Error colour only on borders + micro-copy — never page-level red alerts
   • Tone: calm, solution-oriented ("Please enter…" not "Field required")
   • Loading: disable button, swap label — no spinner needed for a simple form
   • Success: replace form block entirely so there's no double-submit risk
   • API error: keep form visible, show fallback email link inline
   All transitions mirror the existing 0.2s easing of the send-btn. */

/* Error colour token — not in global tokens because it's form-only */
:root { --c-error: #A63228; }   /* desaturated brick-red — readable on white */

/* ---- field error state ---- */
.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-bottom-color: var(--c-error);
}
/* Error message: hidden by default, slides in when .has-error is present */
.field-msg {
  display: block;
  font-family: var(--ui); font-weight: 400;
  font-size: 11.5px; letter-spacing: 0.02em;
  color: var(--c-error);
  margin-top: 6px;
  min-height: 0;            /* collapses to nothing when empty */
  opacity: 0;
  transform: translateY(-3px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}
.field.has-error .field-msg {
  opacity: 1;
  transform: translateY(0);
}

/* ---- send button loading state ---- */
.send-btn[data-state="loading"] {
  opacity: 0.55;
  pointer-events: none;
  cursor: default;
}
.send-btn[data-state="loading"] .send-label::after {
  content: "…";   /* "Sending…" — appended via CSS to avoid JS string mutation */
}

/* ---- form-level success state ---- */
.form-status-success {
  display: none;
  padding: 48px 0 24px;
  opacity: 0;
  transition: opacity 0.4s ease;
}
.form-status-success.show {
  display: block;
  opacity: 1;
}
.form-status-success .status-title {
  font-family: var(--display); font-size: clamp(20px, 2.4vw, 28px);
  line-height: 1.3; margin-bottom: 16px;
}
.form-status-success .status-body {
  font-family: var(--ui); font-weight: 300;
  font-size: 15px; line-height: 1.75; color: var(--ink-soft);
}
.form-status-success .status-body a {
  color: var(--ink); text-underline-offset: 3px;
}

/* ---- inline API / network error ---- */
.form-api-error {
  display: none;
  margin-top: 16px;
  font-family: var(--ui); font-weight: 400;
  font-size: 13px; line-height: 1.65;
  color: var(--c-error);
  border-left: 2px solid var(--c-error);
  padding-left: 14px;
}
.form-api-error.show { display: block; }
.form-api-error a { color: var(--c-error); text-underline-offset: 3px; }

/* small screens */
@media (max-width: 760px) {
  .profile { grid-template-columns: 1fr; }
  .hero-bottom { flex-direction: column; align-items: flex-start; }
  .hero-tagline { margin-left: 0; }
  .hero-tagline .tagline-headline { font-size: 19px; }
  .hero-tagline .tagline-body { font-size: 15px; }
  .subnav { gap: 36px; justify-content: flex-start; }
}

/* ============ FAQ PAGE ============ */
.faq-wrap { max-width: 740px; margin: 64px auto 140px; padding: 0 var(--margin); }

.faq-section { margin-top: 72px; }
.faq-section:first-child { margin-top: 0; }
.faq-section-title {
  font-family: var(--body); font-size: 11px; font-weight: 500;
  letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--ink-soft); padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 0;
}

.faq-item {
  border-bottom: 1px solid var(--line);
}
.faq-item summary {
  list-style: none;
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 24px; padding: 22px 0;
  font-family: var(--body); font-size: 16px; font-weight: 400;
  color: var(--ink); cursor: pointer;
  user-select: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: '+';
  font-family: var(--body); font-size: 18px; font-weight: 300;
  opacity: 0.5; flex-shrink: 0;
  transition: transform 0.2s, opacity 0.2s;
}
.faq-item[open] summary::after {
  content: '−';
  opacity: 0.7;
}
.faq-item summary:hover { opacity: 0.75; }
.faq-answer {
  padding: 0 0 26px;
  font-size: 15px; line-height: 1.75; color: var(--ink);
  max-width: 620px;
}
.faq-answer p { margin-top: 0; }
.faq-answer p + p { margin-top: 12px; }


/* ============================================================
   MOTION SYSTEM v2
   One easing curve, one rhythm — applied everywhere.

   Easing: cubic-bezier(0.16, 1, 0.3, 1)  — expo-out:
     starts brisk, decelerates to a silky stop. Feels elegant
     at any duration because the slow tail reads as "settling".

   Duration ladder (all use the same easing):
     hover   200ms   button fills, link underlines, opacity flips
     appear  1500ms  scroll reveals, profile entrance
     page    1400ms  inner-page title / content entrance
     hero    1600ms  hero text items (longest — full viewport entrance)
     const   1800ms  constellation node entrance (most unhurried)

   Stagger: 280ms between sequential siblings (relaxed, not rushed)
   Rise distance: 36px — visible drift, not a pop

   Scroll parallax: triggered by IntersectionObserver + scroll,
   NOT on first paint — so the user only sees it while scrolling.

   prefers-reduced-motion: hard no-op at bottom of file.
============================================================ */

/* -------- EASING VARIABLE -------- */
:root {
  /* Gentle ease-out: starts slowly, settles softly.
     Compared to the previous expo-out (0.16,1,0.3,1) this curve
     begins more gradually — the element feels pulled into place
     rather than launched. */
  --ease: cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* -------- A. HOVER AUDIT — all interactive elements -------- */

/* Inline text links (editorial, body copy) */
a:not(.cta):not(.btn-nav):not(.node):not(.chip):not([class*="footer"]):not(.send-btn):not(.brand):not(.nav-overlay a):not(.subnav a):not([class*="btn"]) {
  transition: opacity 0.2s ease;
}
a:not([class]):hover, .contact-intro a:hover { opacity: 0.65; }

/* CTA arrow links */
.cta { transition: border-bottom-color 0.2s ease; }
.cta .arrow { display: inline-block; transition: transform 0.3s var(--ease); }

/* Outlined pill buttons */
.btn-nav { transition: background 0.2s ease, color 0.2s ease; }

/* Chips */
.chip { transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; }

/* Subnav hover — quick fade once visible */
.subnav a.subnav-visible:hover { opacity: 0.6; }

/* Footer */
.footer-links nav a, .footer-social a { transition: opacity 0.2s ease; }

/* Past thumbnails */
.past-cover { transition: transform 0.5s var(--ease); }
.past-cover:hover { transform: scale(1.018); }
.past-cover-mask { transition: background 0.45s ease; }
.past-cover:hover .past-cover-mask { background: rgba(20, 16, 12, 0); }

/* Constellation cards */
.node-card .card-links a { transition: border-bottom-color 0.2s ease, opacity 0.2s ease; }

/* FAQ */
.faq-item summary { transition: opacity 0.2s ease; cursor: pointer; }
.faq-item summary:hover { opacity: 1; }

/* -------- B. SCROLL REVEAL (.sr) --------
   All reveals share one class, one curve, one duration.
   JS sets transition-delay inline for stagger.
   Pattern: element starts invisible (opacity 0, translateY 36px)
   Observer adds .sr-visible → transition fires to resting state. */
.sr {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity   1.3s var(--ease),
    transform 1.3s var(--ease);
}
.sr.sr-visible {
  opacity: 1;
  transform: translateY(0);
}

/* -------- C. HERO ENTRANCE --------
   Items fade up in sequence on first page load.
   JS sets animation-delay per element.
   Same curve, same duration — just staggered by delay offset. */
@keyframes heroItemIn {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}
.hero-enter-item {
  opacity: 0;
  animation: heroItemIn 1.4s var(--ease) both;
}

/* -------- D. INNER-PAGE ENTRANCE --------
   Titles + content fade in on .page load.
   Staggered by animation-delay in the rule itself. */
.page .page-title {
  animation: heroItemIn 1.2s var(--ease) 0.2s both;
}
.page .page-subtitle {
  animation: heroItemIn 1.2s var(--ease) 0.45s both;
}
.page .contact-wrap,
.page .menu-board-wrap {
  animation: heroItemIn 1.2s var(--ease) 0.6s both;
}

/* -------- E. CONSTELLATION --------
   Desktop: nodes float relative to base position using CSS vars
   (--px, --py from mousemove; --scroll-y from scroll handler).
   Mobile: position:static flex list — no transforms, no parallax.

   Node entrance: fade + scale from center outward.
   Line entrance: stroke-dashoffset draw-on.
   Center dot: quiet pulse.
   Orbit dots: staggered pulse, slightly faster. */

/* The base .node transform already handles vars (defined above) */
.node {
  transition: transform 0.7s var(--ease);
  will-change: transform;
}

/* Node entrance */
@keyframes nodeIn {
  from {
    opacity: 0;
    transform: translate(
      calc(-50% + var(--px, 0px)),
      calc(-50% + var(--py, 0px) + var(--scroll-y, 0px))
    ) scale(0.92);
  }
  to {
    opacity: 1;
    transform: translate(
      calc(-50% + var(--px, 0px)),
      calc(-50% + var(--py, 0px) + var(--scroll-y, 0px))
    ) scale(1);
  }
}
.node-enter {
  animation: nodeIn 1.5s var(--ease) both;
}

/* Line draw */
@keyframes lineIn {
  from { stroke-dashoffset: 300; opacity: 0; }
  to   { stroke-dashoffset: 0;   opacity: 1; }
}
.line-enter {
  animation: lineIn 1.3s var(--ease) both;
  opacity: 0;
}

/* Center dot pulse — slow, quiet */
@keyframes dotPulse {
  0%, 100% { transform: scale(1);   opacity: 0.85; }
  50%       { transform: scale(1.7); opacity: 0.45; }
}
.node.center::before {
  animation: dotPulse 4s ease-in-out infinite;
}

/* Orbit dots — slightly different period, offset by index */
.node:not(.center)::before {
  animation: dotPulse 3.4s ease-in-out infinite;
}
.node:nth-child(3)::before { animation-delay: 1.1s; }
.node:nth-child(5)::before { animation-delay: 2.2s; }

/* Hover: name underlines, no extra transform (parallax already active) */
.node:not(.center):hover .name { text-decoration: underline; text-underline-offset: 6px; }

/* -------- E2. CONSTELLATION — MOBILE RESPONSIVE --------
   On small screens: hide SVG lines, lay nodes out as a plain
   vertical stack. No absolute positioning, no parallax transforms.
   Tap to expand card (existing JS still works — just uses
   click/touch instead of hover). */
@media (max-width: 860px) {
  .constellation-section { overflow: visible; }
  .constellation {
    height: auto !important;
    display: flex !important;
    flex-direction: column !important;
    gap: 0 !important;
    padding: 0 var(--margin) !important;
    max-width: none !important;
  }
  .constellation svg.lines { display: none !important; }

  /* Override absolute positioning and ALL transforms — !important needed
     because inline style="left:…" + base rule with will-change fight back */
  .node {
    position: relative !important;
    left: auto !important; top: auto !important;
    transform: none !important;
    transition: none !important;
    will-change: auto !important;
    display: flex !important;
    flex-direction: column !important;
    padding: 28px 0;
    border-bottom: 1px solid var(--line);
  }
  .node:last-of-type { border-bottom: none; }

  /* Remove the dot — redundant in list context */
  .node::before { display: none !important; }

  .node .name { font-size: clamp(22px, 5vw, 30px); }
  .node.center .name { font-size: clamp(24px, 5.5vw, 34px); }
  .node.center { order: -1; } /* Keep center at top */

  .node-card {
    position: static !important;
    width: 100% !important;
    opacity: 1 !important;
    transform: none !important;
    margin-top: 16px;
    display: none;
    border: 1px solid var(--line);
  }
  .node-card.show { display: block !important; }

  /* No scroll parallax on mobile */
  .node-enter { animation: none !important; opacity: 1 !important; }
  .line-enter  { animation: none !important; opacity: 1 !important; }
}

/* -------- F. PROFILE ENTRANCE -------- */
.profile-photo.sr  { transition: opacity 1.3s var(--ease), transform 1.3s var(--ease); }
.profile > div:last-child.sr { transition: opacity 1.3s var(--ease), transform 1.3s var(--ease); }

/* -------- G. REDUCED MOTION — full kill switch -------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .sr { opacity: 1 !important; transform: none !important; }
  .node { transition: none !important; will-change: auto !important; }
  .subnav a, .subnav-cta .cta {
    opacity: 1 !important; transform: none !important;
  }
}
