/* ============================================================
   Components — header, menu, footer, hero pieces, cards.
   ============================================================ */

/* ----------------------------------------------------------------
   Hero background — fixed video + readability gradient
   ---------------------------------------------------------------- */

/* Hero background layer — fixed-position container for the video
   AND a fallback poster background-image. Sits at z-index 0
   (NOT negative — see tokens.css for why). Content above is
   z-index 1+ and either transparent (the .hero section) or
   opaque (everything below) so it scrolls over cleanly. */
.hero-bg {
  position: fixed;
  inset: 0;
  z-index: var(--z-bg);
  pointer-events: none;
  background-color: var(--color-bg);
  background-image: url("../assets/videos/hero-poster.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* translate3d alone promotes this to its own GPU layer without
     the persistent memory cost of will-change. */
  transform: translate3d(0, 0, 0);
}

/* Mobile gets its own portrait-orientation poster — much smaller
   file (368KB vs 1.1MB) and properly proportioned for vertical
   phone screens. Browsers respect media-scoped background-images
   and only fetch the matching one. */
@media (max-width: 768px) {
  .hero-bg {
    background-image: url("../assets/videos/hero-mobile-poster.jpg");
  }
}

/* Two stacked <video> elements, both fill .hero-bg. The intro
   sits on top with .is-active; the loop sits underneath at
   opacity 0. When the intro nears its end, JS starts the loop
   playing then instantly swaps z-index + opacity on `ended` —
   gapless, no fade, no black frame. */
.hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  z-index: 1;
  -webkit-transform: translate3d(0, 0, 0);
  transform: translate3d(0, 0, 0);
  /* Disable any pointer interaction on the video so user taps go to
     the page (e.g. menu toggle, scroll) rather than to the video's
     native play overlay. */
  pointer-events: none;
}

/* (Browser play-overlay suppression — every <video> on the site —
   handled by the universal `video::-webkit-media-controls *` rule
   at the bottom of this file. iOS Safari, Android WebView and older
   Chromium variants all covered by that single block.) */

.hero-video.is-active {
  opacity: 1;
  z-index: 2;
}

.hero-overlay {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(180deg,
      rgba(10,10,10,0.20) 0%,
      transparent 40%,
      transparent 60%,
      rgba(10,10,10,0.65) 100%
    );
  pointer-events: none;
}

/* (Tap-to-play overlay removed — autoplay attributes + global
    play() retry on first user gesture make it unnecessary.) */

/* On internal pages, no video — solid bg only */
body:not(.page-home) .hero-bg { background: var(--color-bg); }
body:not(.page-home) .hero-video,
body:not(.page-home) .hero-overlay { display: none; }

/* ----------------------------------------------------------------
   Animated hero logo (homepage only).
   Fixed at viewport center; GSAP animates transform from
   center scale 1 → top-bar position scale ~0.32.
   ---------------------------------------------------------------- */

.hero-logo {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: var(--z-overlay);
  width: 22vw;
  max-width: 380px;
  min-width: 180px;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.94);
  will-change: transform, opacity;
}

.hero-logo img {
  width: 100%;
  height: auto;
  display: block;
}

body:not(.page-home) .hero-logo { display: none; }

/* ----------------------------------------------------------------
   Site header (top bar)
   ---------------------------------------------------------------- */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-h);
  z-index: var(--z-header);
  background: transparent;
  transition:
    background-color var(--dur-short) var(--ease-out),
    backdrop-filter  var(--dur-short) var(--ease-out);
}

.site-header.is-visible {
  background: var(--color-overlay);
  /* Lighter blur (10px not 14px) and no saturate boost — backdrop-filter
     is the most expensive thing in the viewport during scroll, and the
     visual difference between 10/14px blur is invisible. */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid rgba(255,255,255,0.04);
}

/* No-backdrop-filter fallback: rgba(10,10,10,0.75) is too dark over
   the hero video on home (the blur is what makes 75% read as glass).
   Drop opacity to ~0.40 + a soft gradient bottom so the header still
   has presence but the video underneath stays visible. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.is-visible {
    background: linear-gradient(to bottom, rgba(10,10,10,0.55), rgba(10,10,10,0.35));
  }
}
html[data-perf="low"] .site-header.is-visible {
  background: linear-gradient(to bottom, rgba(10,10,10,0.55), rgba(10,10,10,0.40));
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.site-header__inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: 100%;
  /* viewport-fit=cover means the page extends under iPhone notch /
     Dynamic Island. max() keeps the existing gutter on flat-edge
     devices and only widens when the inset is larger. */
  padding-inline:
    max(var(--gutter-x), env(safe-area-inset-left))
    max(var(--gutter-x), env(safe-area-inset-right));
  max-width: var(--content-max);
  margin-inline: auto;
}

/* On homepage, top-bar elements start hidden — JS fades them in.
   Gated on html.js so a no-JS visitor still gets header controls. */
.js .page-home .site-header .lang-toggle,
.js .page-home .site-header .header-logo,
.js .page-home .site-header .menu-toggle {
  opacity: 0;
}

@media (max-width: 768px) {
  .site-header { height: var(--header-h-mobile); }
}

/* ----------------------------------------------------------------
   Header logo (static, in top bar)
   ---------------------------------------------------------------- */

.header-logo {
  display: block;
  justify-self: center;
  height: 38px;
  line-height: 0;
}

.header-logo img {
  height: 100%;
  width: auto;
  display: block;
}

@media (max-width: 768px) {
  .header-logo { height: 32px; }
}

/* ----------------------------------------------------------------
   Language toggle (left of header)
   ---------------------------------------------------------------- */

.lang-toggle {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: 1px solid rgba(240, 237, 230, 0.12);
  border-radius: 999px;
  background: rgba(10, 10, 10, 0.4);
  justify-self: start;
}

.lang-toggle__btn {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
  /* Tap target — Apple HIG says 44px min. We lift the button to that
     hit area without inflating its visual size. */
  min-height: 32px;
  min-width: 32px;
  transition:
    color var(--dur-micro) var(--ease-micro),
    background-color var(--dur-micro) var(--ease-micro);
}

@media (hover: none) and (pointer: coarse) {
  .lang-toggle__btn { min-height: 36px; min-width: 36px; padding: 9px 14px; }
}

.lang-toggle__btn:hover {
  color: var(--color-text);
}

.lang-toggle__btn[aria-pressed="true"] {
  color: var(--color-text);
  background: rgba(240, 237, 230, 0.08);
}

@media (max-width: 480px) {
  .lang-toggle { padding: 3px; }
  .lang-toggle__btn { padding: 4px 9px; font-size: 11px; }
}

/* ----------------------------------------------------------------
   Menu toggle (hamburger, right of header)
   ---------------------------------------------------------------- */

.menu-toggle {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: background-color var(--dur-micro) var(--ease-micro);
}

/* When the menu overlay is open, lift the entire header above it
   so the hamburger (now an X) stays visible and clickable. The class
   `body.menu-is-open` is set by js/nav.js openMenu/closeMenu so the
   header z-index switches in lockstep with the overlay. */
body.menu-is-open .site-header {
  z-index: calc(var(--z-modal) + 1);
}
body.menu-is-open .site-header.is-visible {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom: none;
}

.menu-toggle:hover {
  background: rgba(240, 237, 230, 0.06);
}

/* Two screen-reader labels stacked inside the toggle. CSS swaps which
   is visible to AT based on aria-expanded so screen readers always
   read the correct action ("Open menu" / "Close menu") even though
   the visible icon also morphs from ≡ to X. */
.menu-toggle__label--close { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle__label--open { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle__label--close { display: inline; }

.menu-toggle__icon {
  position: relative;
  display: block;
  width: 22px;
  height: 14px;
}

/* Both bars are centered vertically; spacing comes from transform only.
   Animating a single property (transform) prevents the jitter that
   shows up when top/bottom and transform animate concurrently. */
.menu-toggle__icon span {
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: var(--color-text);
  transform-origin: center;
  transition: transform 320ms cubic-bezier(0.65, 0, 0.35, 1);
}

/* Closed: spread apart by ±5px from center */
.menu-toggle__icon span:first-child { transform: translateY(calc(-50% - 5px)); }
.menu-toggle__icon span:last-child  { transform: translateY(calc(-50% + 5px)); }

/* Open: collapse to center and rotate into an X */
.menu-toggle[aria-expanded="true"] .menu-toggle__icon span:first-child {
  transform: translateY(-50%) rotate(45deg);
}
.menu-toggle[aria-expanded="true"] .menu-toggle__icon span:last-child {
  transform: translateY(-50%) rotate(-45deg);
}

/* ----------------------------------------------------------------
   Full-screen menu overlay — opaque, centred, premium.
   Solid #0A0A0A covers everything (video included). The header
   stays above with class swap on body, so the X close icon is
   always visible.
   ---------------------------------------------------------------- */

.menu-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  /* The overlay itself is just a positioning container. Visual
     layers (video, glass, content) stack inside it. */
  background: var(--color-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 300ms var(--ease-out);
  overflow: hidden;
}

/* Video layer — sits at the bottom of the stack inside the menu.
   Two stacked videos cycle intro→loop forever. */
.menu-bg-video {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
}

.menu-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  z-index: 1;
}

.menu-video.is-active {
  opacity: 1;
  z-index: 2;
}

/* Glass tint layer — blurs the video behind it. Same matte feel
   as before, just moved off the parent so the video CAN be behind. */
.menu-glass {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: rgba(10, 10, 10, 0.50);
  backdrop-filter: blur(24px) saturate(0.95) brightness(0.85);
  -webkit-backdrop-filter: blur(24px) saturate(0.95) brightness(0.85);
  pointer-events: none;
}

/* Content (links + footer) sits above the glass */
.menu-overlay__inner {
  position: relative;
  z-index: 3;
}

/* No-backdrop-filter fallback (old Firefox, old Chromium, embedded
   WebViews without the API): the glass blur is unavailable, but the
   VIDEO is still playing behind. Use a LIGHT tint so the video
   remains clearly visible — the previous near-opaque 0.96 fill was
   blocking the video entirely. Legibility of menu links over the
   moving footage is preserved by the text's own contrast + the
   subtle radial gradient option below. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .menu-glass {
    background:
      radial-gradient(ellipse at center, rgba(10, 10, 10, 0.55), rgba(10, 10, 10, 0.30) 70%),
      rgba(10, 10, 10, 0.30);
  }
}

/* Low-perf tier: skip the expensive blur but keep the video readable
   behind a soft tint. */
html[data-perf="low"] .menu-glass {
  background:
    radial-gradient(ellipse at center, rgba(10, 10, 10, 0.55), rgba(10, 10, 10, 0.35) 70%),
    rgba(10, 10, 10, 0.35);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

.menu-overlay[hidden] { display: none; }

.menu-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.menu-overlay__inner {
  max-width: 600px;
  width: 100%;
  padding-inline: var(--gutter-x);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-6);
  text-align: center;
}

.menu-list {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
}

.menu-list li { width: 100%; }

.menu-list a {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6vw, 4rem);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: var(--tr-display);
  color: var(--color-text);
  display: inline-block;
  position: relative;
  padding: 0;
  border: none;
  background: none;
  text-decoration: none;
  outline: none;        /* No box outline — focus is color only */
  opacity: 0;
  transform: translateY(20px);
  transition: color 200ms var(--ease-micro);
}

/* Focus uses colour, not an outline box. Keeps the menu clean
   while still accessible — keyboard users see the current focus
   in the same brand red used for hover and active page. */
.menu-list a:focus-visible {
  color: var(--color-brand);
  outline: none;
}

/* Open animation — stagger each link in */
.menu-overlay.is-open .menu-list a {
  opacity: 1;
  transform: translateY(0);
  transition:
    opacity 600ms var(--ease-out),
    transform 600ms var(--ease-out),
    color 200ms var(--ease-micro);
}
.menu-overlay.is-open .menu-list li:nth-child(1) a { transition-delay: 0ms,   0ms,   0ms; }
.menu-overlay.is-open .menu-list li:nth-child(2) a { transition-delay: 80ms,  80ms,  0ms; }
.menu-overlay.is-open .menu-list li:nth-child(3) a { transition-delay: 160ms, 160ms, 0ms; }
.menu-overlay.is-open .menu-list li:nth-child(4) a { transition-delay: 240ms, 240ms, 0ms; }
.menu-overlay.is-open .menu-list li:nth-child(5) a { transition-delay: 320ms, 320ms, 0ms; }

/* Hover — desktop only — pure colour change, no boxes/borders */
@media (hover: hover) and (pointer: fine) {
  .menu-list a:hover {
    color: var(--color-brand);
  }
}

/* Active page — red colour + thin underline below.
   No square prefix, no box outline. */
.menu-list a[aria-current="page"] {
  color: var(--color-brand);
}
.menu-list a[aria-current="page"]::after {
  content: "";
  display: block;
  width: 64px;
  height: 1px;
  background: var(--color-brand);
  margin: 12px auto 0;
}

/* Footer — thin red rule + email block */
.menu-overlay__footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
  opacity: 0;
  transition: opacity 400ms var(--ease-out);
}
.menu-overlay.is-open .menu-overlay__footer {
  opacity: 1;
  transition-delay: 600ms;
}

.menu-overlay__rule {
  display: block;
  width: 48px;
  height: 1px;
  background: var(--color-divider);
  border: none;
  margin: 0;
}

.menu-overlay__footer a {
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color var(--dur-micro) var(--ease-micro);
}

@media (hover: hover) and (pointer: fine) {
  .menu-overlay__footer a:hover { color: var(--color-text); }
}

/* Mobile — left-aligned, smaller link size. The whole menu inner
   pulls to the page's left gutter so it reads like the rest of
   the site's editorial column. Footer (rule + email) follows. */
@media (max-width: 768px) {
  .menu-overlay {
    justify-content: flex-start;
  }
  .menu-overlay__inner {
    align-items: flex-start;
    text-align: left;
    max-width: none;
    width: 100%;
  }
  .menu-list {
    align-items: flex-start;
  }
  .menu-list a {
    font-size: clamp(2rem, 8vw, 2.75rem);
    text-align: left;
  }
  /* Active-page underline aligns to the left edge of the text */
  .menu-list a[aria-current="page"]::after {
    margin: 12px 0 0;
  }
  .menu-overlay__footer {
    align-items: flex-start;
    margin-top: var(--space-6);
  }
  .menu-overlay__rule {
    margin: 0;
  }
}

/* ----------------------------------------------------------------
   CTA buttons (mailto cards) — used on home + contact
   ---------------------------------------------------------------- */

.cta-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
  /* align-items: start so a hovered button can grow downward
     without stretching its sibling to match its new height. */
  align-items: start;
}

/* Grid layout: row 1 holds the label + arrow (always visible),
   row 2 holds the email reveal (collapsed to 0 by default). The
   box height is determined by row 1 only — when row 2 expands on
   hover, the box grows downward. */
.cta-btn {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto;
  column-gap: var(--space-3);
  row-gap: 0;
  align-items: center;
  padding: 14px var(--space-3);
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-divider);
  color: var(--color-text);
  text-decoration: none;
  overflow: hidden;
  transition:
    border-color var(--dur-micro) var(--ease-micro),
    background-color var(--dur-short) var(--ease-out);
}

.cta-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--color-brand);
  transform: scaleY(0);
  transform-origin: bottom;
  transition: transform var(--dur-short) var(--ease-out);
  z-index: 0;
}

.cta-btn > * { position: relative; z-index: 1; }

.cta-btn__label {
  grid-column: 1;
  grid-row: 1;
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--color-text);
  /* Block element with line-height 1.2 — pairs symmetrically
     with the arrow's centred line height for true vertical center. */
  line-height: 1.2;
  min-width: 0;
}

.cta-btn__arrow {
  grid-column: 2;
  grid-row: 1;
  font-size: var(--fs-xl);
  line-height: 1;
  color: var(--color-brand);
  /* Match the label's optical centre exactly */
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  transition:
    transform var(--dur-short) var(--ease-out),
    color var(--dur-micro) var(--ease-micro);
}

/* Email reveal — second grid row. Collapsed to zero height by
   default; the box reads as a clean rectangle with just label+arrow.
   On hover/focus the row expands and the email fades in. */
.cta-btn__reveal {
  grid-column: 1 / -1;
  grid-row: 2;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: 400;
  color: var(--color-text-muted);
  letter-spacing: 0.01em;
  word-break: break-word;
  overflow-wrap: anywhere;
  min-width: 0;
  opacity: 0;
  max-height: 0;
  margin-top: 0;
  overflow: hidden;
  transition:
    opacity var(--dur-micro) var(--ease-micro),
    max-height var(--dur-short) var(--ease-out),
    margin-top var(--dur-short) var(--ease-out);
}

.cta-btn:hover .cta-btn__reveal,
.cta-btn:focus-visible .cta-btn__reveal,
.cta-btn:focus-within .cta-btn__reveal {
  opacity: 1;
  max-height: 2em;
  margin-top: 8px;
  transition:
    opacity 200ms var(--ease-micro),
    max-height var(--dur-short) var(--ease-out),
    margin-top var(--dur-short) var(--ease-out);
}

@media (hover: hover) {
  .cta-btn:hover {
    border-color: var(--color-brand);
  }
  .cta-btn:hover::before { transform: scaleY(1); }
  .cta-btn:hover .cta-btn__arrow {
    color: var(--color-text);
    transform: translateX(6px);
  }
}

@media (max-width: 640px) {
  .cta-row { grid-template-columns: 1fr; }
}

/* ----------------------------------------------------------------
   Toast — bottom-center notification when mailto fallback fires
   (clipboard copy etc). Subtle, 2s lifetime, fade in/out.
   ---------------------------------------------------------------- */
.cta-toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translate(-50%, 12px);
  background: rgba(20, 20, 20, 0.96);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0;
  padding: 12px 20px;
  border: 1px solid rgba(240, 237, 230, 0.12);
  z-index: var(--z-modal);
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 200ms var(--ease-micro),
    transform 200ms var(--ease-out);
}

.cta-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* ----------------------------------------------------------------
   Custom cursor (hidden on touch devices)
   ---------------------------------------------------------------- */

.cursor-dot,
.cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: var(--z-modal);
  mix-blend-mode: difference;
}

.cursor-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-brand);
  transform: translate(-50%, -50%) translate3d(0, 0, 0);
  transition: opacity var(--dur-micro) var(--ease-micro);
  opacity: 0;
}

.cursor-ring {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid rgba(240, 237, 230, 0.55);
  transform: translate(-50%, -50%) translate3d(0, 0, 0);
  transition:
    width var(--dur-short) var(--ease-out),
    height var(--dur-short) var(--ease-out),
    border-color var(--dur-micro) var(--ease-micro),
    opacity var(--dur-micro) var(--ease-micro),
    background-color var(--dur-short) var(--ease-out);
  opacity: 0;
}

.cursor-ready .cursor-dot,
.cursor-ready .cursor-ring { opacity: 1; }

/* Hover state — ring grows, dot fades */
.cursor-hover .cursor-dot { opacity: 0; }
.cursor-hover .cursor-ring {
  width: 64px;
  height: 64px;
  border-color: var(--color-brand);
  background: rgba(194, 0, 14, 0.08);
}

/* Hide native cursor on devices with fine pointer */
@media (hover: hover) and (pointer: fine) {
  html.cursor-ready,
  html.cursor-ready *,
  html.cursor-ready a,
  html.cursor-ready button {
    cursor: none !important;
  }
}

/* Touch devices — hide entirely */
@media (hover: none), (pointer: coarse) {
  .cursor-dot, .cursor-ring { display: none; }
}

/* Reduced motion — hide custom cursor, restore native */
@media (prefers-reduced-motion: reduce) {
  .cursor-dot, .cursor-ring { display: none; }
  html.cursor-ready, html.cursor-ready * { cursor: auto !important; }
}

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

.site-footer {
  position: relative;
  z-index: var(--z-content);
  /* Background painted by the video + glass layers below; the
     fallback solid color shows on no-JS or before videos load. */
  background: var(--color-bg);
  padding-block: var(--space-12) var(--space-6);
  border-top: 1px solid var(--color-divider);
  overflow: hidden; /* clips the video edges to the footer rect */
  isolation: isolate; /* establish a stacking context for z-indexed children */
}

/* Video layer — same intro+loop cycle pattern as the menu/hero.
   Lazy-loaded by js/footer.js when the footer enters viewport. */
.site-footer__bg-video {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.site-footer__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  z-index: 1;
  pointer-events: none;
}

.site-footer__video.is-active {
  opacity: 1;
  z-index: 2;
}

/* (Browser play-overlay suppression — handled by the universal
   `video::-webkit-media-controls *` rule at the bottom of this file.) */

/* Matte-glass tint layer — same blur + tint as the menu so footer
   reads as a peer surface. */
.site-footer__glass {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: rgba(10, 10, 10, 0.50);
  backdrop-filter: blur(24px) saturate(0.95) brightness(0.85);
  -webkit-backdrop-filter: blur(24px) saturate(0.95) brightness(0.85);
  pointer-events: none;
}

/* No-backdrop-filter fallback: same approach as .menu-glass — keep
   the video visible behind a light radial tint rather than a solid
   black panel. The previous 0.96 fill hid the video entirely on
   old browsers, which is the opposite of what the surface should do. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-footer__glass {
    background:
      radial-gradient(ellipse at center, rgba(10, 10, 10, 0.55), rgba(10, 10, 10, 0.30) 70%),
      rgba(10, 10, 10, 0.30);
  }
}

/* Low-perf tier: skip blur, keep video readable. */
html[data-perf="low"] .site-footer__glass {
  background:
    radial-gradient(ellipse at center, rgba(10, 10, 10, 0.55), rgba(10, 10, 10, 0.35) 70%),
    rgba(10, 10, 10, 0.35);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Content sits above the glass + video. */
.site-footer__content {
  position: relative;
  z-index: 3;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--gutter-x);
}

/* Legacy selector kept for any older markup that still scopes via
   .site-footer .container (none after this update, but harmless). */
.site-footer .container {
  max-width: var(--content-max);
}

/* Three columns on desktop: brand | address | email
   Address and email are siblings of equal width, so their
   labels share a baseline and their content aligns vertically. */
.site-footer__grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  column-gap: var(--space-8);
  row-gap: var(--space-8);
  padding-bottom: var(--space-10);
  align-items: start;
}

.site-footer__brand {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
}

/* Logo above the tagline — anchored to the same left edge as
   the tagline text. Wrapped in a link to the home page so the
   footer mark behaves the same as the header logo. */
.site-footer__mark-link {
  display: inline-block;
  line-height: 0;
  margin: 0 0 var(--space-1) 0;
  padding: 0;
  border: none;
  transition: opacity var(--dur-micro) var(--ease-micro);
}

.site-footer__mark-link:hover { opacity: 0.85; }

.site-footer__mark {
  display: block;
  width: 96px;
  height: auto;
  margin: 0;
  padding: 0;
}

@media (max-width: 640px) {
  .site-footer__mark { width: 80px; }
}

.site-footer__tagline {
  font-size: var(--fs-xl);
  line-height: 1.15;
  max-width: 18ch;
  margin: 0;
}

.site-footer__col {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.site-footer__col .label {
  font-size: var(--fs-xs);
  margin: 0 0 var(--space-1) 0;
  line-height: 1.2;
}

.site-footer__col > p,
.site-footer__col > a {
  font-size: var(--fs-sm);
  line-height: 1.55;
  max-width: none;
  margin: 0;
}

.site-footer__col a {
  display: inline-block;
  align-self: flex-start;
  border-bottom: 1px solid transparent;
  padding-bottom: 1px;
  width: fit-content;
  transition:
    color var(--dur-micro) var(--ease-micro),
    border-color var(--dur-micro) var(--ease-micro);
}
.site-footer__col a:hover {
  color: var(--color-brand-hover);
  border-color: var(--color-brand-hover);
}

.site-footer .divider { margin-block: var(--space-4); }

.site-footer__legal {
  font-size: var(--fs-xs);
  letter-spacing: 0.05em;
  text-align: left;
  margin: 0;
}

/* Tablet: brand on top, address+email side-by-side below */
@media (max-width: 1024px) {
  .site-footer__grid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas:
      "brand brand"
      "addr  email";
    column-gap: var(--space-6);
    row-gap: var(--space-6);
  }
  .site-footer__brand   { grid-area: brand; }
  .site-footer__col--address { grid-area: addr; }
  .site-footer__col--contact { grid-area: email; }
}

/* Mobile: everything stacks */
@media (max-width: 640px) {
  .site-footer__grid {
    grid-template-columns: 1fr;
    grid-template-areas:
      "brand"
      "addr"
      "email";
    row-gap: var(--space-5);
  }
}




/* ============================================================
   Hong Kong live clock — Aston-Martin-dashboard restraint.

   Mono tabular digits (so they don't reflow as numbers tick),
   small uppercase label, brand-red blinking colon between minutes
   and seconds as the live indicator. Two surfaces:

   - .hk-clock              — contact page, under the Office heading
   - .hk-clock--footer      — bottom row of every footer

   Format updated by js/clock.js via Intl.DateTimeFormat with
   timeZone: 'Asia/Hong_Kong'.
   ============================================================ */
.hk-clock {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-3);
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-feature-settings: "tnum";
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
  line-height: 1;
}

.hk-clock__label,
.hk-clock__zone {
  font-size: var(--fs-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  line-height: 1;
}

.hk-clock__time {
  font-size: var(--fs-md);
  letter-spacing: 0.05em;
  line-height: 1;
  color: var(--color-text);
}

.hk-clock__sep {
  color: var(--color-text-muted);
  margin-inline: 0.05em;
}

/* The center colon (between minutes and seconds) blinks once per
   second as the "live" indicator — same idea as a tower-clock pulse. */
/* Blinking separator colon — driven by JS class toggle (on the
   [data-hk-clock] root) so its on/off flip is perfectly in sync
   with the seconds-digit tick. CSS keyframe removed because animation
   start time depends on parse order, drifting out of sync with the
   setInterval-driven digit update. */
.hk-clock__sep--blink {
  color: var(--color-brand);
  opacity: 0.3;
  transition: opacity 80ms ease-out;
}
[data-hk-clock].is-tick-on .hk-clock__sep--blink {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .hk-clock__sep--blink {
    opacity: 1 !important;
    transition: none;
  }
}

/* Footer variant — slightly smaller time, sits opposite the legal
   line in the bottom row. All digits stay white; only the blinking
   colon and the HKT zone label carry the brand red. */
.hk-clock--footer .hk-clock__time {
  font-size: var(--fs-sm);
  letter-spacing: 0.06em;
}

/* HKT zone label in brand red — quiet but unmistakable timezone
   signal pinned to the right of the digits. */
.hk-clock--footer .hk-clock__zone {
  color: var(--color-brand);
}

/* Footer bottom row: legal copyright on the left, clock on the right.
   Stacks on mobile with gap. */
.site-footer__bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

@media (max-width: 640px) {
  .site-footer__bottom {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ============================================================
   Hong Kong clock — special variant for the Contact page.

   Editorial-dateline treatment, more presence than the footer
   variant. Three lines stacked tight:

     [pulse] HONG KONG  ·  GMT+8         ← mono uppercase eyebrow
     16:42                      · 18      ← display serif HH:MM, mono small ·SS
     Sunday, 10 May 2026                  ← italic display serif date

   The pulse dot breathes once per 2s as the live indicator. The
   colon between HH and MM blinks once per second.
   ============================================================ */
/* ============================================================
   .hk-clock--digital — refined digital clock for the Contact page.

   Composition:
     ┌─ .hk-clock__eyebrow      HONG KONG · GMT+8 + radar pulse
     ├─ .hk-clock__display      time row
     │   ├─ .hk-clock__hm        HH:MM (big display serif)
     │   │   └─ .hk-clock__colon--blink (brand-red, 1Hz blink)
     │   └─ .hk-clock__side      vertical mono stack:
     │       ├─ .hk-clock__period   AM/PM (brand-red small caps)
     │       └─ .hk-clock__seconds  SS s (muted)
     ├─ .hk-clock__rule         hairline brand-red 40px divider
     └─ .hk-clock__date         day + date in italic serif

   Live cadences:
     - Radar pulse on the eyebrow waxes once per 1.8s (analog feel).
     - Colon between HH and MM blinks on/off each second (digital feel).
     - Both run independently — the eye reads them as distinct
       indicators rather than a competing pair.
   ============================================================ */
.hk-clock--digital {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.hk-clock--digital > * { margin: 0; }

/* ----- Eyebrow row + radar pulse ----- */
.hk-clock--digital .hk-clock__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: var(--fs-xs);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  line-height: 1;
}
.hk-clock--digital .hk-clock__sep-dot {
  color: var(--color-text-muted);
  opacity: 0.6;
}

.hk-clock--digital .hk-clock__pulse {
  position: relative;
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: rgba(240, 237, 230, 0.55);
  box-shadow:
    0 0 6px  rgba(194, 0, 14, 0.85),
    0 0 14px rgba(194, 0, 14, 0.45);
  animation: hk-clock-pulse-core 1.8s ease-in-out infinite;
}
.hk-clock--digital .hk-clock__pulse::before,
.hk-clock--digital .hk-clock__pulse::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1.5px solid var(--color-brand);
  background: transparent;
  box-sizing: border-box;
  animation: hk-clock-pulse-ring 1.8s cubic-bezier(0, 0, 0.3, 1) infinite;
  pointer-events: none;
}
.hk-clock--digital .hk-clock__pulse::after { animation-delay: 0.9s; }

@keyframes hk-clock-pulse-core {
  0%, 100% {
    transform: scale(1);
    background: rgba(240, 237, 230, 0.55);
    box-shadow:
      0 0 6px  rgba(194, 0, 14, 0.85),
      0 0 14px rgba(194, 0, 14, 0.45);
  }
  50% {
    transform: scale(1.13);
    background: rgba(255, 230, 232, 0.85);
    box-shadow:
      0 0 10px rgba(194, 0, 14, 1),
      0 0 22px rgba(194, 0, 14, 0.7);
  }
}
@keyframes hk-clock-pulse-ring {
  0%   { transform: scale(1);   opacity: 0.6; border-width: 1.5px; }
  75%  {                        opacity: 0;   border-width: 1px;   }
  100% { transform: scale(3);   opacity: 0;   border-width: 1px;   }
}

/* ----- Time row: HH:MM + side stack -----
   `align-items: stretch` on the parent makes the side-stack column
   span the full line-box of the HH:MM. `justify-content:
   space-between` inside the side-stack then pins AM/PM to the
   cap-height and the seconds to the baseline. Result: the AM lines
   up with the top of the serif digits and the seconds line up with
   their bottom, perfectly, at every clamp() font-size. */
.hk-clock--digital .hk-clock__display {
  display: inline-flex;
  align-items: stretch;
  gap: 0.75em;
  line-height: 0.92;
  margin-top: var(--space-1);
}

/* HH:MM — primary time. Uses Fraunces (var(--font-numeric)) rather
   than Cormorant Garamond: Fraunces is a variable serif designed
   for display sizes, and its numerals are visibly more elegant —
   the "0" is a soft oval, the "1" has a confident diagonal flag,
   the "8" is a properly stacked figure-eight. `opsz: 144` selects
   the display-optimised cut of the font. Tabular lining figures
   keep the digit grid stable as the value changes.
   - tight line-height so cap-height ≈ line-box → the side-stack
     pin points are reliable.
   - subtle text-shadow: a faint warm halo, as if lit from within.
     Never explicit; only felt as the digits read slightly softer
     against the matte-black field. */
.hk-clock--digital .hk-clock__hm {
  font-family: var(--font-numeric);
  font-weight: 400;
  font-size: clamp(3rem, 5.5vw, 4.75rem);
  font-variation-settings: "opsz" 144;
  font-feature-settings: "tnum", "lnum";
  font-variant-numeric: tabular-nums lining-nums;
  line-height: 0.85;
  letter-spacing: -0.022em;
  color: var(--color-text);
  white-space: nowrap;
  text-shadow: 0 0 30px rgba(240, 237, 230, 0.08);
}

/* Colon — brand-red, blinks once per second via a JS class toggle
   on the [data-hk-clock] root so the on/off transition is in lockstep
   with each digit change. Dim state is opacity 0.45 (not invisible)
   so the colon reads as a steady glow that pulses brighter, never
   "disappears" mid-cycle. */
.hk-clock--digital .hk-clock__colon {
  margin-inline: 0.04em;
  color: var(--color-text-muted);
}
.hk-clock--digital .hk-clock__colon--blink {
  color: var(--color-brand);
  opacity: 0.45;
  transition: opacity 100ms ease-out;
}
[data-hk-clock].is-tick-on .hk-clock--digital .hk-clock__colon--blink {
  opacity: 1;
}

/* Side stack — AM/PM at the cap-height, seconds at the baseline.
   space-between handles the distribution; the small padding-block
   creates a tiny inset so the items don't sit flush against the
   absolute edges of the digits. */
.hk-clock--digital .hk-clock__side {
  display: inline-flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  padding-block: 0.1em 0.18em;
  line-height: 1;
}

/* AM/PM — refined mono small-caps in cream. Same chromatic family
   as the time digits (only weight differentiates it), so the only
   moments of brand-red on the time line are the blinking colon and
   the hairline beneath. Visual restraint reads as luxury. */
.hk-clock--digital .hk-clock__period {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(240, 237, 230, 0.9);
  font-variant-numeric: tabular-nums;
}

/* Seconds — same family + size as AM/PM but lighter weight and
   muted colour. The weight + tone delta is the hierarchy: AM/PM is
   primary information, seconds is the live-tick that confirms the
   clock is breathing. */
.hk-clock--digital .hk-clock__seconds {
  font-family: var(--font-mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* ----- Brand-red hairline — refined punctuation between the time
   block and the date. 60px wide; the linear-gradient form has
   subtle taper at each end (alpha 0 → 1 → 0) so the line reads as
   etched into the surface rather than slapped on top. ----- */
.hk-clock--digital .hk-clock__rule {
  width: 60px;
  height: 1px;
  background: linear-gradient(90deg,
    rgba(194, 0, 14, 0)   0%,
    var(--color-brand)   18%,
    var(--color-brand)   82%,
    rgba(194, 0, 14, 0) 100%);
  border: 0;
  margin: var(--space-2) 0 0 0;
}

/* ----- Date readout — italic display serif. Day name in brand
   red; the bullet separator sits in a subtler colour than the body
   so the punctuation is felt without being seen. ----- */
.hk-clock--digital .hk-clock__date {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: var(--fs-md);
  line-height: 1.3;
  color: var(--color-text-muted);
  letter-spacing: 0.005em;
  margin-top: var(--space-1);
}
.hk-clock--digital .hk-clock__date [data-day] {
  color: var(--color-brand);
}
.hk-clock--digital .hk-clock__date-sep {
  color: var(--color-text-subtle);
  font-style: normal;
}

/* CN swap — CN families have no italic; render upright. */
body.lang-cn .hk-clock--digital .hk-clock__date {
  font-family: var(--font-cn);
  font-style: normal;
}

/* Reduced motion — disable pulse + colon blink. The time digits
   themselves still update each second (the seconds digit changing
   is itself the live indicator under reduced motion). */
@media (prefers-reduced-motion: reduce) {
  .hk-clock--digital .hk-clock__pulse,
  .hk-clock--digital .hk-clock__pulse::before,
  .hk-clock--digital .hk-clock__pulse::after,
  .hk-clock--digital .hk-clock__colon--blink {
    animation: none;
    opacity: 1;
  }
  .hk-clock--digital .hk-clock__pulse::before,
  .hk-clock--digital .hk-clock__pulse::after {
    display: none;
  }
}


/* ============================================================
   UNIVERSAL native-media-control suppression.

   Belt-and-braces shadow-DOM nuke: every <video> on the site,
   regardless of class, has its native controls forced off. Some
   iOS Safari + WebView builds expose play overlays through
   pseudo-elements not explicitly listed in the per-class rules
   above; the wildcard descendant selector below covers everything
   inside ::-webkit-media-controls.

   Combined with the JS-side `controls=false` + `controlsList`
   suppression, the user can never trigger or see a "tap to play"
   overlay. Even when autoplay is rejected, the poster remains
   visible until the silent-gesture fallback kicks in on first
   user interaction.
   ============================================================ */
video::-webkit-media-controls,
video::-webkit-media-controls *,
video::-webkit-media-controls-panel,
video::-webkit-media-controls-overlay-enclosure,
video::-webkit-media-controls-enclosure,
video::-webkit-media-controls-overlay-play-button,
video::-webkit-media-controls-start-playback-button,
video::-webkit-media-controls-play-button,
video::-webkit-media-controls-current-time-display,
video::-webkit-media-controls-time-remaining-display,
video::-webkit-media-controls-mute-button,
video::-webkit-media-controls-volume-slider,
video::-webkit-media-controls-fullscreen-button,
video::-webkit-media-controls-toggle-closed-captions-button {
  display: none !important;
  visibility: hidden !important;
  opacity: 0 !important;
  -webkit-appearance: none !important;
  appearance: none !important;
  pointer-events: none !important;
  width: 0 !important;
  height: 0 !important;
}

video {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
