/* ============================================================
   G8 — Signature Collection
   ============================================================ */

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

/* Design 2.0 (see G8/08 in the Obsidian vault, "12.09.2026" entry, for the
   full before/after reasoning): the ground shifted from a neutral warm
   near-black to a true navy-black — the product itself is navy cotton
   twill, and the old --bg read as generic "dark luxury" rather than
   specifically THIS brand's own color. --gold/--gold-bright/--gold-dim are
   UNCHANGED on purpose — that's the real gold thread and brass seal on the
   physical cap, an authentic brand color, not a generic accent to swap out
   for novelty. Token names are identical to before, so nothing downstream
   (every rule below, main.js's color reads where it has any) needed to
   change to pick this up. */
:root {
  --bg: #0a0e1a;
  --bg-raised: #101a2c;
  --gold: #bb9755;
  --gold-bright: #ddb978;
  --gold-dim: rgba(187, 151, 85, 0.34);
  --gold-faint: rgba(187, 151, 85, 0.15);
  --text: #eef0f2;
  --text-dim: #8b8d92;
  --text-faint: #75767b;
  --line: rgba(187, 151, 85, 0.17);
  --serif: 'Bodoni Moda', Georgia, 'Times New Roman', serif;
  --sans: 'Jost', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Every other animation on this site runs through GSAP, which was
     deliberately told to ignore this OS setting (confirmed decision — see
     main.js). The identity photo carousel is the one plain-CSS transition
     on the page, so without this exception it alone would collapse to
     instant here while everything else keeps animating — which is exactly
     the "why is this one thing suddenly a hard cut" bug this rule fixes.
     A single transition-duration value applies to EVERY property in the
     transition list — .is-active transitions two properties (opacity 2.2s,
     transform 8s) at different speeds on purpose (the Ken Burns zoom needs
     to still be moving when the slide cuts), so it needs its own full
     restatement here, not just the base selector's single duration, or
     this would quietly also crush the zoom down to 2.2s. */
  .identity-slide {
    transition-duration: 2.2s !important;
  }
  .identity-slide.is-active {
    transition: opacity 2.2s ease-in-out, transform 8s linear !important;
  }
  /* Same exception, single-property case — one duration value is enough
     here since .field::after only ever transitions transform. */
  .field::after {
    transition-duration: 0.45s !important;
  }
}

html {
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-weight: 300;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

::selection {
  background: var(--gold-dim);
  color: var(--text);
}

a { color: inherit; text-decoration: none; }
img, svg { display: block; max-width: 100%; }

.container {
  width: 100%;
  max-width: 720px;
  margin: 0 auto;
  padding: 0 24px;
}

.container--wide {
  max-width: 960px;
}

/* ---------- film grain + vignette (fixed, whole page) ---------- */

.grain {
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  opacity: 0.045;
  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.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: 998;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 40%, transparent 45%, rgba(0,0,0,0.55) 100%);
}

/* ---------- small caps label utility ---------- */

.label {
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold);
}

.label--dim {
  color: var(--text-dim);
}

/* ---------- nav ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 22px 32px;
  transition: background-color 0.4s var(--ease), border-color 0.4s var(--ease), padding 0.4s var(--ease);
  border-bottom: 1px solid transparent;
}

.nav.is-scrolled {
  background: rgba(10, 14, 26, 0.82);
  backdrop-filter: blur(10px);
  border-bottom-color: var(--line);
  padding: 16px 32px;
}

.nav-mark {
  display: flex;
  align-items: center;
  gap: 10px;
}

.nav-mark svg { width: 26px; height: 26px; }

.nav-mark span {
  font-family: var(--serif);
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--text);
}

.nav-link {
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--text-dim);
  border: 1px solid var(--line);
  padding: 10px 20px;
  border-radius: 1px;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}

.nav-link:hover {
  color: var(--bg);
  background: var(--gold);
  border-color: var(--gold);
}

/* ---------- hero ---------- */

.hero {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  position: relative;
  padding: 140px 24px 80px;
}

.hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(ellipse 70% 50% at 50% 20%, rgba(201,168,76,0.07), transparent 60%),
    linear-gradient(180deg, #0a0e1a 0%, #0d1424 55%, #0a0e1a 100%);
}

.hero-frame {
  position: absolute;
  inset: 20px;
  border: 1px solid var(--line);
  pointer-events: none;
}

/* Faint, slowly drifting edition numbers — texture, not copy meant to be
   read. Populated and animated from js/main.js (positions/values are
   randomised per load). Fixed to the viewport, same as .grain/.vignette
   above — this is one site-wide layer, not something that belongs to any
   one section, so it stays put and visible no matter how far down the page
   you've scrolled. z-index sits deliberately low (behind every section's
   own content, which has no explicit z-index of its own and so stacks
   above this by default) — unlike .grain/.vignette, which sit in FRONT of
   everything at z-index 998/999, this is meant to feel like it's behind
   the page, not printed over it. */
.site-numerals {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.site-numeral {
  position: absolute;
  font-family: var(--serif);
  font-weight: 500;
  color: var(--gold);
  opacity: 0.055;
  white-space: nowrap;
  user-select: none;
}

.corner { position: absolute; width: 18px; height: 18px; border-color: var(--gold); opacity: 0.55; }
.corner.tl { top: -1px; left: -1px; border-style: solid; border-width: 1px 0 0 1px; transform-origin: top left; }
.corner.tr { top: -1px; right: -1px; border-style: solid; border-width: 1px 1px 0 0; transform-origin: top right; }
.corner.bl { bottom: -1px; left: -1px; border-style: solid; border-width: 0 0 1px 1px; transform-origin: bottom left; }
.corner.br { bottom: -1px; right: -1px; border-style: solid; border-width: 0 1px 1px 0; transform-origin: bottom right; }

/* Only the hero's own corners grow in, following the seal — .corner is
   shared with .identity-media's corner accents (see rule near the photo
   carousel), which are untouched and stay exactly as they always were. */
.hero-frame .corner {
  transform: scale(0);
}

.hero-content { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; }

.hero-seal { width: 108px; height: 108px; margin-bottom: 40px; opacity: 0; }

.hero-eyebrow { margin-bottom: 28px; opacity: 0; }

.hero-title {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(2.4rem, 6.4vw, 4.6rem);
  line-height: 1.12;
  letter-spacing: -0.01em;
  max-width: 14ch;
  color: var(--text);
  opacity: 0;
}

.hero-title em { font-style: italic; color: var(--gold-bright); }

.hero-sub {
  margin-top: 26px;
  max-width: 42ch;
  font-size: 15.5px;
  font-weight: 300;
  color: var(--text-dim);
  opacity: 0;
}

.hero-cta {
  margin-top: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  opacity: 0;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  padding: 18px 40px;
  border: 1px solid var(--gold);
  color: var(--bg);
  background: var(--gold);
  border-radius: 1px;
  cursor: pointer;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease), background-color 0.35s var(--ease);
}

.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 32px -8px rgba(201, 168, 76, 0.45);
}

.btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--line);
}

.btn--ghost:hover {
  border-color: var(--gold);
  box-shadow: none;
  transform: none;
  color: var(--gold);
}

.hero-meta {
  font-size: 11.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.hero-scroll {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  width: 1px;
  height: 46px;
  background: linear-gradient(180deg, transparent, var(--gold-dim) 40%, var(--gold-dim) 100%);
  opacity: 0;
}

.hero-scroll::after {
  content: '';
  position: absolute;
  top: 0; left: -2px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--gold);
  animation: scrollDot 2.6s ease-in-out infinite;
}

@keyframes scrollDot {
  0% { top: 0; opacity: 0; }
  20% { opacity: 1; }
  80% { opacity: 1; }
  100% { top: 42px; opacity: 0; }
}

/* ---------- section rhythm ---------- */

section { position: relative; z-index: 1; }

.section {
  padding: 140px 0;
  border-top: 1px solid var(--line);
}

.section-head {
  text-align: center;
  margin-bottom: 64px;
}

.section-head .label { display: block; margin-bottom: 20px; }

.section-head h2 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(1.7rem, 3.6vw, 2.5rem);
  letter-spacing: -0.01em;
}

.section-lede {
  max-width: 56ch;
  margin: 20px auto 0;
  color: var(--text-dim);
  font-size: 15px;
  text-align: center;
}

/* ---------- reusable photo slot (graceful fallback if file is missing) ---------- */

.photo-slot {
  position: relative;
  overflow: hidden;
  background: linear-gradient(160deg, #101a2c, #0a0e1a);
}

.photo-slot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.photo-slot-fallback {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 10.5px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 20px;
}

.photo-slot.is-missing img { display: none; }
.photo-slot.is-missing .photo-slot-fallback { display: flex; }

.photo-slot.is-missing {
  border: 1px solid var(--line);
}

/* ---------- identity / photo moment ---------- */

.identity {
  position: relative;
  min-height: 92vh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  border-top: 1px solid var(--line);
}

.identity-media {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    linear-gradient(180deg, #0c1220 0%, #101a2c 45%, #0a0e1a 100%);
}

/* Three stacked slides crossfading in place — see "identity photo carousel"
   in js/main.js, which toggles .is-active on a timer. Each slide is its own
   full-bleed layer (position:absolute, inset:0); width/height/object-fit
   come from the shared .photo-slot img rule above, only object-position
   varies per slide since each source photo frames its subject differently. */
.identity-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  z-index: 0;
  /* Symmetric ease, not var(--ease) — that curve is front-loaded (built for
     things sliding/rising into view), which reads as a hard snap through
     the middle of a pure opacity fade rather than a smooth blend. Same
     lesson as the counter's crossfade fix: symmetric in/out, no overshoot.
     transform has NO transition here on purpose — the moment a slide goes
     inactive it must snap back to scale(1)/translate(0,0) instantly
     (invisible, since opacity is also leaving), so the next time it
     becomes active the zoom+drift always starts fresh from the same point
     instead of continuing from wherever it stopped last time. */
  transition: opacity 2.2s ease-in-out;
  object-position: 62% 56%; /* marina-dusk default */
  transform: scale(1) translate(0, 0);
}

.identity-slide.is-active {
  opacity: 1;
  /* Ken Burns — a slow, continuous zoom-and-drift for as long as the slide
     stays on screen (matches the hold in js/main.js, with room to spare so
     it never visibly completes and holds still). Linear, not eased: an
     eased move has a perceptible start/stop, which reads as a deliberate
     "effect" rather than ambient drift. Direction varies per slide (see
     the modifier classes below) so three photos in a row don't all drift
     the same way. */
  transform: scale(1.08) translate(-1.5%, -1%);
  transition: opacity 2.2s ease-in-out, transform 8s linear;
  /* Whichever slide is becoming active always stacks above the others, so
     the incoming photo visibly fades in over the outgoing one every time —
     without this, the slide *before* index 0 in source order would sit
     underneath on the wrap-around transition instead of on top of it. */
  z-index: 1;
}

.identity-slide--pool {
  object-position: 35% center;
}

.identity-slide--pool.is-active {
  transform: scale(1.08) translate(1.5%, 1%);
}

.identity-slide--tennis {
  object-position: center 15%;
}

.identity-slide--tennis.is-active {
  transform: scale(1.08) translate(-1%, 1.5%);
}

.identity-media .corner { opacity: 0.3; z-index: 2; }

/* The source photo (704×1072, ~0.66 ratio — cropped 03.09.2026 to remove a
   letterboxed watermark, was taller before) is shaped closer to a phone
   screen than to a landscape rectangle. On a wide desktop window the
   section becomes landscape-shaped instead, and object-fit:cover has to
   crop it down to fill that mismatched shape. Above 900px, stop stretching
   the photo edge to edge and instead show it as a centered column at its
   own aspect ratio — .identity has no background of its own, so the
   near-black page background (--bg) shows through on either side, which
   already matches this section's gradient tones closely enough that it
   reads as intentional, not like an empty gap. */
@media (min-width: 900px) {
  .identity-media {
    inset: 0 auto 0 50%;
    width: auto;
    aspect-ratio: 704 / 1072;
    transform: translateX(-50%);
  }
}

.identity-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(0deg, rgba(10,14,26,0.96) 0%, rgba(10,14,26,0.55) 42%, transparent 75%);
}

.identity-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: 90px 0 96px;
  text-align: center;
}

.identity-line {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(2.1rem, 5.4vw, 3.6rem);
  line-height: 1.14;
  margin-top: 18px;
  color: var(--text);
}

.identity-line em {
  font-style: italic;
  color: var(--gold-bright);
}

.identity-sub {
  max-width: 44ch;
  margin: 24px auto 0;
  font-size: 14.5px;
  color: var(--text-dim);
  line-height: 1.7;
}

/* ---------- detail strip (compact, one line — deliberately not a card grid) ---------- */

/* ---------- product plates (the object itself, not lifestyle) ----------
   Real product photography (background-replaced via AI, product pixels
   preserved from the physical sample — see G8/08 in the Obsidian vault),
   presented like specimens in a case rather than a raw e-commerce grid:
   each light studio photo sits inside a dark matted card so it reads as
   deliberate against the rest of the near-black site, not a stray white
   box. Grid auto-fits, so this scales cleanly from 2 photos up to 6. */

.plates { padding: 100px 0 40px; }

/* Design 2.0: this section used to carry only the small-caps ".label"
   eyebrow ("THE PIECE") with no real heading under it — dropping the
   eyebrow pattern here (see the reveal above in the identity section)
   would have left the section with no title at all, so this promotes it
   to an actual (modest) heading in the display serif instead. */
.plates-heading {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(1.4rem, 2.8vw, 1.9rem);
  text-align: center;
  color: var(--text);
}

.plates-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 28px;
  margin-top: 44px;
}

.plate {
  position: relative;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  padding: 22px;
}

.plate-frame { overflow: hidden; }

.plate-frame img { display: block; width: 100%; height: auto; }

.plate-caption {
  margin-top: 16px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-align: center;
}

.details-strip {
  padding: 88px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.details {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 40px;
  text-align: center;
}

.detail-label {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.detail-value {
  margin-top: 8px;
  font-family: var(--serif);
  font-size: 1.15rem;
  color: var(--gold);
}

.vsep {
  width: 1px;
  height: 32px;
  background: var(--gold);
  opacity: 0.5;
  transform: scale(0);
  will-change: transform;
}

@media (max-width: 560px) {
  .details { gap: 20px; }
  .details-strip { padding: 64px 0; }
}

@media (max-width: 420px) {
  .details { flex-direction: column; gap: 24px; }
  .vsep { width: 32px; height: 1px; }
}

/* ---------- edition / counter ---------- */

.edition { text-align: center; }

.counter {
  max-width: 480px;
  margin: 56px auto 0;
}

.counter-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 18px;
}

.counter-figure { text-align: left; }
.counter-figure.right { text-align: right; }

.counter-value {
  font-family: var(--serif);
  font-size: clamp(2.2rem, 5vw, 3rem);
  color: var(--text);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  display: inline-block;
}

.counter-value.gold { color: var(--gold-bright); }

.counter-caption {
  margin-top: 8px;
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.counter-note {
  margin-top: 28px;
  font-size: 12px;
  color: var(--text-faint);
  font-style: italic;
}

/* urgency tier line */
.counter-urgency {
  margin-top: 22px;
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: color 0.5s var(--ease);
}

.counter-urgency.tier-warm { color: var(--gold); }
.counter-urgency.tier-hot { color: var(--gold-bright); }
.counter-urgency.tier-closed { color: #d98a5a; }

/* recent-activity ticker */
.ticker {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 20px;
}

.ticker-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--gold);
  flex-shrink: 0;
  animation: tickerPulse 2.2s ease-in-out infinite;
}

.ticker-text {
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}

.ticker-text.is-swapping {
  opacity: 0;
  transform: translateY(4px);
}

@keyframes tickerPulse {
  0%, 100% { opacity: 0.4; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.4); }
}

/* demo badge */
.demo-badge {
  position: fixed;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 600;
  background: var(--bg-raised);
  border: 1px solid var(--gold-dim);
  color: var(--gold);
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  padding: 10px 18px;
  border-radius: 2px;
  box-shadow: 0 8px 24px -8px rgba(0,0,0,0.6);
}

/* ---------- reservation ---------- */

.reserve { text-align: center; }

.form-card {
  max-width: 440px;
  margin: 0 auto;
  border: 1px solid var(--line);
  padding: 48px 36px;
  background: linear-gradient(180deg, rgba(201,168,76,0.03), transparent 40%);
}

.form-price {
  font-family: var(--serif);
  font-size: 2.4rem;
  color: var(--text);
  margin-bottom: 6px;
}

.form-price span { color: var(--gold); }

.form-price-note { font-size: 12px; color: var(--text-faint); letter-spacing: 0.1em; margin-bottom: 36px; }

.field {
  text-align: left;
  margin-bottom: 20px;
  position: relative;
}

.field label {
  display: block;
  font-size: 11px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 10px;
}

.field input {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--line);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 300;
  padding: 10px 2px;
}

.field input::placeholder { color: var(--text-faint); }
.field input:focus { outline: none; }

/* Gold line draws in from the left on focus, over the always-visible faint
   .field input border-bottom, rather than the underline just swapping color
   instantly — the same "traced, not switched on" language as the seal and
   the detail-strip dividers elsewhere on the page. :focus-within (on the
   parent) rather than input:focus, since the pseudo-element lives on
   .field, one level up from the input it's tracking. */
.field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease);
  pointer-events: none;
}

.field:focus-within::after {
  transform: scaleX(1);
}

.form-card .btn { width: 100%; margin-top: 12px; }

.form-legal {
  margin-top: 20px;
  font-size: 11.5px;
  color: var(--text-faint);
  letter-spacing: 0.02em;
}

.form-error {
  min-height: 1px;
  margin-top: 14px;
  font-size: 12px;
  color: #d98a5a;
}

.form-success {
  text-align: center;
}

.form-success h3 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 1.4rem;
  color: var(--gold-bright);
  margin-bottom: 12px;
}

.form-success p {
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.6;
}

/* ---------- footer ---------- */

.footer {
  padding: 64px 0 40px;
  border-top: 1px solid var(--line);
  text-align: center;
}

.footer-seal { width: 40px; height: 40px; margin: 0 auto 24px; opacity: 0.7; }

.footer-links {
  display: flex;
  justify-content: center;
  gap: 28px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}

.footer-links a {
  font-size: 11.5px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
  transition: color 0.3s var(--ease);
}

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

.footer-copy {
  font-size: 11.5px;
  color: var(--text-faint);
  letter-spacing: 0.05em;
}

/* ---------- scroll reveal ---------- */

/* Initial hidden state for GSAP-driven reveals (see js/main.js).
   Kept in CSS, not just JS, so there's no flash of unanimated content
   before the script runs. A <noscript> rule further down covers the
   case where JS never runs at all. */
.reveal,
.stagger-item {
  opacity: 0;
  transform: translateY(16px);
  will-change: transform, opacity;
}

.parallax-layer {
  will-change: transform;
}

/* ---------- interactive layer: custom cursor + magnetic buttons ----------
   JS-driven (js/main.js), gated behind (hover:hover) and (pointer:fine) so
   touch devices never see or pay for this. The native cursor is never
   hidden — this rides alongside it as a supplementary accent, so a JS
   hiccup can't leave a visitor without any cursor feedback.
*/
.custom-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid var(--gold);
  border-radius: 50%;
  pointer-events: none;
  z-index: 999;
  opacity: 0;
  transition: width 0.3s var(--ease), height 0.3s var(--ease), margin 0.3s var(--ease),
    opacity 0.3s var(--ease), border-color 0.3s var(--ease);
}

.custom-cursor.is-visible { opacity: 0.65; }

.custom-cursor.is-active {
  width: 46px;
  height: 46px;
  margin: -23px 0 0 -23px;
  opacity: 1;
  border-color: var(--gold-bright);
}

.btn { will-change: transform; }

/* ---------- responsive ---------- */

@media (max-width: 640px) {
  .nav { padding: 18px 20px; }
  .nav-link { padding: 9px 16px; font-size: 10.5px; letter-spacing: 0.2em; }
  .hero { padding-top: 120px; }
  .section { padding: 96px 0; }
  .object-grid { grid-template-columns: 1fr 1fr; }
  .spec { padding: 26px 18px; }
  .counter-row { flex-direction: row; }
  .form-card { padding: 36px 24px; }
}

@media (max-width: 420px) {
  .object-grid { grid-template-columns: 1fr; }
}
