/* ==========================================================================
   LÚA — Styles
   ========================================================================== */

/* --- Fonts ---------------------------------------------------------------- */

@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-300-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-300-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: italic; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-300-italic-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal; font-weight: 600; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Cormorant Garamond";
  font-style: normal; font-weight: 600; font-display: swap;
  src: url("../assets/fonts/cormorant-garamond-600-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/montserrat-300-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 300; font-display: swap;
  src: url("../assets/fonts/montserrat-300-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/montserrat-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 400; font-display: swap;
  src: url("../assets/fonts/montserrat-400-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 500; font-display: swap;
  src: url("../assets/fonts/montserrat-500-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal; font-weight: 500; font-display: swap;
  src: url("../assets/fonts/montserrat-500-vietnamese.woff2") format("woff2");
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* --- Reset ---------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: var(--leading-body);
  min-height: 100svh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img, picture, svg, video { display: block; max-width: 100%; }
picture { display: contents; }
/* <source> is not in the HTML rendering spec's display:none list, so it is an
   ordinary inline box. Harmless inside a <picture> that generates one -- but
   `picture { display: contents }` lifts its children into the parent's layout,
   and in a grid parent three <source> elements become three grid items and
   therefore three rows. That is what pushed the home hero 81px down its own
   window: the mark was centred inside whatever space was left after three
   empty rows. */
source { display: none; }
img { height: auto; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }
a { color: var(--accent-bright); text-decoration: none; }
address { font-style: normal; }
p { text-wrap: pretty; }

/* --- Focus ---------------------------------------------------------------- */

:focus-visible {
  outline: 2px solid var(--gold-300);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

.skip-link {
  position: absolute;
  left: var(--space-sm);
  top: -100%;
  z-index: 999;
  padding: 0.75rem 1.25rem;
  background: var(--gold-500);
  color: var(--ink-900);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  transition: top var(--dur-fast) var(--ease-out);
}
.skip-link:focus { top: var(--space-sm); }

/* --- Typography ----------------------------------------------------------- */

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--leading-tight);
  color: var(--text-strong);
  text-wrap: balance;
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); line-height: var(--leading-snug); }

.eyebrow {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  font-weight: 300;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--gold-500);
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.7;
  color: var(--cream-300);
}

.muted { color: var(--text-muted); }

.gold-text {
  display: inline-block;
  color: var(--gold-300);
}
@supports (-webkit-background-clip: text) or (background-clip: text) {
  .gold-text {
    background: var(--gold-grad);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* --- Layout --------------------------------------------------------------- */

.shell { width: min(100% - 2.5rem, 78rem); margin-inline: auto; }

.section {
  padding-block: var(--space-3xl);
  text-align: center;
}
.section--ink { background: var(--ink-900); }

.section .eyebrow + h2 { margin-top: var(--space-2xs); }
.section .lead {
  margin-top: var(--space-md);
  max-width: 46ch;
  margin-inline: auto;
}
.section address {
  margin-top: var(--space-md);
  color: var(--cream-300);
}
.section .btn-row { margin-top: var(--space-lg); }

/* --- Divider -------------------------------------------------------------- */

.divider {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  width: min(100%, 16rem);
  margin: var(--space-md) auto;
  color: var(--gold-500);
}
.divider::before,
.divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hairline-strong));
}
.divider::after { background: linear-gradient(90deg, var(--hairline-strong), transparent); }
.divider__gem {
  width: 7px;
  height: 7px;
  flex: none;
  transform: rotate(45deg);
  border: 1px solid currentColor;
}

/* --- Buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.95rem 2rem;
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.btn:active { transform: translateY(1px); }

.btn--gold {
  background: var(--gold-grad-soft);
  color: var(--ink-900);
  font-weight: 600;
}
.btn--gold::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 -60%;
  width: 40%;
  z-index: -1;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.55), transparent);
  transition: transform var(--dur-slow) var(--ease-out);
}
.btn--gold:hover::after { transform: translateX(420%); }
.btn--gold:hover { color: var(--ink-900); }

.btn--ghost {
  border-color: var(--hairline-strong);
  color: var(--gold-200);
  background: transparent;
}
.btn--ghost:hover {
  border-color: var(--gold-400);
  color: var(--gold-100);
  background: rgba(200, 144, 64, 0.08);
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
  justify-content: center;
}

/* --- Header --------------------------------------------------------------- */

.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: var(--z-header);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-mid) var(--ease-out),
              border-color var(--dur-mid) var(--ease-out);
}
.site-header.is-scrolled {
  background: rgba(6, 6, 6, 0.96);
  border-bottom-color: var(--hairline-faint);
}
@media (min-width: 60em) {
  .site-header.is-scrolled {
    background: rgba(6, 6, 6, 0.88);
    backdrop-filter: blur(14px) saturate(1.1);
  }
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  min-height: var(--header-h);
}

.site-header__brand {
  display: flex;
  align-items: center;
  flex: none;
  padding-block: 0.5rem;
  margin-inline-end: auto;
}
.site-header__brand img {
  height: 36px;
  width: auto;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.site-header__brand:hover img { opacity: 0.82; }

.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.25rem);
}
.site-nav__link {
  position: relative;
  display: block;
  padding: 0.5rem 0;
  font-size: var(--fs-tiny);
  font-weight: 400;
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  color: var(--cream-300);
  transition: color var(--dur-fast) var(--ease-out);
}
.site-nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 100%; bottom: 0.15rem;
  height: 1px;
  background: var(--gold-400);
  transition: right var(--dur-mid) var(--ease-out);
}
.site-nav__link:hover { color: var(--gold-200); }
.site-nav__link:hover::after,
.site-nav__link.is-active::after { right: 0; }
.site-nav__link.is-active { color: var(--gold-300); }

/* Mobile toggle */
.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  padding: 0;
  place-items: center;
  color: var(--gold-300);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
}
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 17px;
  height: 1px;
  background: currentColor;
  transition: transform var(--dur-mid) var(--ease-out),
              opacity var(--dur-fast) var(--ease-out);
}
.nav-toggle__bars::before,
.nav-toggle__bars::after { content: ""; position: absolute; }
.nav-toggle__bars::before { transform: translateY(-5px); }
.nav-toggle__bars::after { transform: translateY(5px); }
[aria-expanded="true"] .nav-toggle__bars { transform: rotate(45deg); }
[aria-expanded="true"] .nav-toggle__bars::before { opacity: 0; }
[aria-expanded="true"] .nav-toggle__bars::after { transform: rotate(-90deg); }

/* Mobile drawer */
@media (max-width: 59.999em) {
  .nav-toggle { display: grid; position: relative; }

  /* Open, the header is the whole screen, and all of it is black. The drawer
     paints its own near-black, but the header's 72px row stays transparent
     until the page scrolls, and on a page whose title sits on film the video
     showed through behind the logo and the close button. The drawer's 0.97
     over this solid colour comes out as the same colour, so there is no seam
     between the two. */
  body.nav-open .site-header {
    height: 100vh;
    height: 100dvh;
    background: rgb(6, 6, 6);
    border-bottom-color: transparent;
  }

  .site-nav {
    position: absolute;
    inset: var(--header-h) 0 0 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    justify-content: safe center;
    background: rgba(6, 6, 6, 0.97);
    backdrop-filter: blur(18px);
    padding: clamp(var(--space-sm), 4vh, var(--space-xl)) var(--space-md);
    overflow-y: auto;
    overscroll-behavior: contain;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);
    transition: opacity var(--dur-mid) var(--ease-out),
                transform var(--dur-mid) var(--ease-out),
                visibility var(--dur-mid);
  }
  .site-nav.is-open { opacity: 1; visibility: visible; transform: none; }

  .site-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
    max-width: 24rem;
    margin-inline: auto;
  }
  .site-nav__list > li + li { border-top: 1px solid var(--hairline-faint); }

  .site-nav__link {
    padding: clamp(0.5rem, 1.6vh, 1.15rem) 0.25rem;
    font-family: var(--font-display);
    font-size: clamp(1.1rem, 3vh, 1.5rem);
    font-weight: 300;
    letter-spacing: 0.06em;
    text-transform: none;
    text-align: center;
  }
  .site-nav__link::after { display: none; }

  body.nav-open { overflow: hidden; }
}

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

.hero__logo {
  width: clamp(230px, 34vw, 400px);
  margin-inline: auto;
  filter: drop-shadow(0 0 40px rgba(200, 144, 64, 0.22));
}
.page-hero {
  position: relative;
  padding: var(--header-clear) 0 calc(var(--space-xl) * 0.75);
  text-align: center;
  background: var(--ink-900);
  border-bottom: 1px solid var(--hairline-faint);
}
.page-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse 60% 70% at 50% 0%,
      rgba(200, 144, 64, 0.1), transparent 65%);
}
.page-hero__inner { position: relative; }
.page-hero h1 { margin-top: var(--space-2xs); }
.page-hero .divider { margin-inline: auto; }
.page-hero .lead {
  max-width: 46ch;
  margin-inline: auto;
  margin-top: var(--space-sm);
}

/* --- Film behind a page title --------------------------------------------- */

/* The owner's footage of the patio, behind the Visit title. The film goes
   under everything else in its section, so the section stacks its own layers:
   film, then the page hero's gold bloom, then the words. The still and the
   video share one box and one crop, which is what lets the video take over
   from the still without a jump; the video only shows once it is actually
   playing (js/film.js has why). View Menu and Order Online carried it too
   until 2026-09-23. */
.has-film { position: relative; isolation: isolate; overflow: hidden; }
.has-film::before { z-index: 1; }
.has-film > .shell { position: relative; z-index: 2; }

.film {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* --film-focus is set per page, on the element. A wide screen shows barely
   half the frame's height, and which half is a choice about that footage. A
   phone shows the full height, so there the vertical part changes nothing. */
.film img,
.film video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--film-focus, 50% 50%);
}
.film video {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease-out);
}
.film.is-playing video { opacity: 1; }

/* The scrim is what keeps the title gold rather than yellow-on-yellow. Darkest
   at the top, where the header's links sit on it before any scrolling, and at
   the bottom, where the section meets the black of the next one. */
.film::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 58% 62% at 50% 58%, rgba(0, 0, 0, 0.5), transparent 78%),
    linear-gradient(to bottom,
      rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.38) 36%,
      rgba(0, 0, 0, 0.42) 68%, rgba(0, 0, 0, 0.9) 100%);
}

/* --- Intro ---------------------------------------------------------------- */

/* Arriving at LÚA opens dark and lights up, whichever page the visitor lands
   on: black holds for a beat, a gold bloom rises, the mark resolves inside it,
   and the veil lifts onto a page that was already sitting underneath. The page
   takes clicks from 1.8s and the veil is gone at 2.8s.

   Only opacity and transform animate. The bloom is its own element precisely so
   the mark never carries an animated filter -- a per-frame drop-shadow is the
   one thing here that would cost a cheap phone its frame rate.

   The veil is armed by an inline script in each page's head, before first
   paint, and only for an arrival: a link from another site, a typed address, a
   bookmark. Nothing is stored, so leaving the site and coming back is an
   arrival again. Three things are not:
     - a click from another LÚA page, told by a same-origin referrer. The
       site's strict-origin-when-cross-origin policy always sends one;
     - a reload;
     - back and forward. History hands a page the referrer it first loaded
       with, so back from the menu to a home page first reached from Google
       looks exactly like arriving from Google. Replaying on every trip back
       is the only alternative, and a page restored from the back-forward
       cache runs no script to replay it anyway. */

.lua-intro { display: none; }

.intro-armed .lua-intro {
  display: grid;
  place-items: center;
  position: fixed;
  inset: 0;
  z-index: 999;
  opacity: 1;
  background: var(--ink-900);
  will-change: opacity;
  animation: introVeil 1000ms 1800ms var(--ease-in-out) both;
}

.lua-intro__glow {
  position: absolute;
  width: min(150vw, 1150px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle,
      rgba(232, 176, 88, 0.30) 0%,
      rgba(200, 144, 64, 0.12) 38%,
      transparent 68%);
  will-change: opacity, transform;
  animation: introGlow 1650ms 150ms var(--ease-out) both;
}

/* Sits at the size and place the home page's own mark occupies, so lifting the
   veil reads as the room coming up around a mark that was always there. */
.lua-intro__mark {
  position: relative;
  width: var(--mark-w);
  height: auto;
  filter: drop-shadow(0 0 46px rgba(200, 144, 64, 0.30));
  will-change: opacity, transform;
  animation: introMark 1500ms 250ms var(--ease-out) both;
}

/* pointer-events rides along with the fade. The veil is fixed over the whole
   viewport and swallows anything aimed at the page beneath it, which is what a
   curtain should do while it is up. The moment it starts lifting it has to stop
   doing it, or the second of the fade is a stretch where the page looks ready
   and is not. `both` fill applies the from-keyframe through the 1800ms delay,
   so the veil blocks until the fade begins and passes everything through after
   that. This was the skip's job before; it is the only part of it worth keeping
   and it does not interrupt anything. */
@keyframes introVeil {
  from { opacity: 1; visibility: visible; pointer-events: auto; }
  1%   { pointer-events: none; }
  99%  { opacity: 0; visibility: visible; }
  to   { opacity: 0; visibility: hidden; pointer-events: none; }
}
@keyframes introGlow {
  from { opacity: 0; transform: scale(0.82); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes introMark {
  from { opacity: 0; transform: scale(1.06); }
  to   { opacity: 1; transform: scale(1); }
}

/* Every page but home has no mark of its own under the veil, so there the
   veil's copy flies into the header instead (flyVeilMark in js/site.js). The
   header's wordmark is the top of the same lockup: lua-wordmark is the first
   434 rows of lua-lockup at 30%, to within half a level of alpha, so the name
   lands on it exactly. The tagline has nowhere to land and fades on the way.

   Two boxes over one image, each clipped by overflow and each carrying the
   mark's drop-shadow. A box's shadow is cast by what the box shows and is not
   clipped by it, so the name keeps its whole halo in flight, and the two
   shadows add up to the single one on the veil's copy: the swap does not
   show. The cut at 76% is row 464, in the empty band between the name (ends
   at row 428) and the tagline (starts at 500). */
.lua-flight {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  transform-origin: 0 0;
  will-change: transform;
}
.lua-flight > div {
  position: absolute;
  overflow: hidden;
  filter: drop-shadow(0 0 46px rgba(200, 144, 64, 0.30));
  will-change: opacity;
}
.lua-flight__name    { inset: 0 0 24% 0; }
.lua-flight__tagline { inset: 76% 0 0 0; }
.lua-flight img {
  position: absolute;
  left: 0;
  width: 100%;
  height: auto;
}
.lua-flight__name img    { top: 0; }
.lua-flight__tagline img { bottom: 0; }

@media (prefers-reduced-motion: reduce) {
  .intro-armed .lua-intro { display: none; }
}

/* --- Home ----------------------------------------------------------------- */

/* One width for the mark, shared by the veil's copy and the page's own, so the
   two can never disagree about size the way they used to disagree about
   position. 0.382 is half the lockup's 611/800 aspect, which is what lets the
   layout centre the mark by its own height without hardcoding a pixel. */
:root { --mark-w: clamp(230px, 34vw, 420px); }

.home {
  position: relative;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  isolation: isolate;
  background: var(--ink-900);
}

.home__bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The scrim is what makes gold legible over photography. The radial pool sits
   under the wordmark; the wash along the bottom gives the information bar a
   ground to stand on instead of floating over a dish. */
.home::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 64% 68% at 50% 49%, rgba(0, 0, 0, 0.78), transparent 74%),
    linear-gradient(to bottom,
      rgba(0, 0, 0, 0.55) 0%, transparent 20%,
      transparent 58%, rgba(0, 0, 0, 0.93) 100%);
}

.home__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--space-xl) var(--space-md);
}

.home__mark {
  width: var(--mark-w);
  height: auto;
  filter: drop-shadow(0 0 46px rgba(200, 144, 64, 0.30));
}

.home__actions { margin-top: clamp(1.75rem, 4vw, 2.75rem); }
.home__actions .btn { min-width: 11rem; min-height: 44px; }

/* Wide screens: centre the MARK on the window, not the block.

   The intro veil puts its mark in the middle of the window, and the page put
   the middle of the whole block there instead, so the mark sat 34px lower and
   the veil lifting dropped the logo by that much. Centring the block can never
   fix it, because the buttons hang below the mark and drag the block's middle
   down with them.

   So the block spans every row of .home, the bar keeps its own row and paints
   over the bottom of the span (it can afford to: 546px of block in a 900px
   window), and inside the block the mark takes an auto row between two equal
   fractions. Equal fractions put the mark on the centre line exactly, whatever
   the buttons under it measure, with no number to keep in step. minmax(0, 1fr)
   rather than 1fr so the lower one cannot be inflated by its own content. */
@media (min-width: 48.0625em) {
  .home__inner {
    display: grid;
    /* Equal fractions above and below put the mark on the centre line of the
       space it has, whatever the buttons under it measure, with no number to
       keep in step. The lower one is a plain 1fr rather than minmax(0, 1fr):
       both take the same share when there is room, but only the plain one
       refuses to shrink under its own content, so on a short window the block
       grows and the page scrolls instead of the buttons landing on the
       information bar. A window 620px tall with a bar that has wrapped to three
       lines is where that showed. */
    grid-template-rows: minmax(0, 1fr) auto 1fr;
    justify-items: center;
  }
  .home__mark { grid-row: 2; }
  .home__actions { grid-row: 3; align-self: start; }
}

/* Only drawn where the information bar is below the fold. On a desktop screen
   everything already fits, and a chevron pointing at nothing is worse than no
   chevron at all. */
.home__scroll {
  display: none;
  margin-top: var(--space-lg);
  width: 44px;
  height: 44px;
  place-items: center;
  color: var(--gold-400);
  animation: scrollNudge 2.4s var(--ease-in-out) infinite;
}
.home__scroll svg { width: 22px; height: 22px; }

@keyframes scrollNudge {
  0%, 100% { transform: translateY(0); opacity: 0.75; }
  50%      { transform: translateY(6px); opacity: 1; }
}

@media (max-width: 48em) {
  .home { min-height: auto; }
  .home__inner {
    min-height: 100svh;
    padding-inline: var(--space-sm);
  }
  .home__scroll { display: grid; }

  /* The content block is centred, so the mark sits above the middle of the
     screen while the portrait plate keeps its dishes at the very top. A wider,
     stronger pool is what stops the wordmark landing on a bowl of pho. */
  .home::before {
    background:
      radial-gradient(ellipse 104% 48% at 50% 41%, rgba(0, 0, 0, 0.88), transparent 78%),
      linear-gradient(to bottom,
        rgba(0, 0, 0, 0.6) 0%, transparent 16%,
        transparent 58%, rgba(0, 0, 0, 0.94) 100%);
  }
}

/* The portrait plate carries its dishes in the top third and leaves the rest of
   the frame bare, so the content stands on stone instead of in the pho. The
   condition is the same one the <picture> element uses to choose that file, so
   the layout and the photograph can never disagree: a narrow window too short
   to qualify gets the landscape plate and keeps the centred treatment. */
@media (max-width: 48em) and (max-aspect-ratio: 2/3) {
  /* The photograph is exactly the first screenful. Left at height:100% it
     stretched over the information bar as well, which on a 390x844 phone made
     it 1256px tall and pushed the food band down over the wordmark: the plate's
     34% landed at 427px instead of 287px. Pinning it to 100svh means row 34% of
     the file is 34% down the screen whatever the bar below ends up measuring. */
  /* Raised, and taller than the screen by the amount it is raised, so its
     bottom still meets the information bar. The plate's food ends at 45.3% of
     the file; over-sizing by 14 and pulling up by 14 lands that at 37.6% of the
     screen instead of 45.3%, which is what makes room for the mark to sit on
     the centre line. The cost is a tighter side crop, 75px an edge instead of
     42 on a 390px phone, and the dishes were composed across the full width
     with that to spare. */
  .home__bg,
  .home::before {
    top: -14svh;
    bottom: auto;
    height: 114svh;
  }
  .home__inner {
    justify-content: flex-start;
    /* Puts the middle of the mark on the middle of the window, which is where
       the intro veil puts its copy, so lifting the veil does not move the logo.
       Subtracting half the mark's own height rather than a number keeps it true
       at every screen height and through the short-screen rule below, which
       changes --mark-w. */
    padding-block: calc(50svh - 0.382 * var(--mark-w)) var(--space-md);
  }
  /* No radial pool here: there is nothing to hide. The wash simply deepens
     below the food and closes to near-black at the bottom edge, which is also
     what hides the seam where the photograph meets the information bar. */
  .home::before {
    background: linear-gradient(to bottom,
      rgba(0, 0, 0, 0.55) 0%,
      rgba(0, 0, 0, 0.12) 16%,
      rgba(0, 0, 0, 0.34) 34%,
      rgba(0, 0, 0, 0.80) 54%,
      rgba(0, 0, 0, 0.92) 100%);
  }
}

/* Below the food the plate leaves 53% of the screen and the full block wants
   475px of it, which needs a 900px-tall phone. On anything shorter the chevron
   goes first: it is decoration, and the three actions are the page. */
@media (max-width: 48em) and (max-aspect-ratio: 2/3) and (max-height: 899px) {
  .home__scroll { display: none; }
}

/* Shorter still and the mark comes down a size too, rather than letting the
   buttons fall off the first screen. */
@media (max-width: 48em) and (max-aspect-ratio: 2/3) and (max-height: 740px) {
  :root { --mark-w: clamp(170px, 46vw, 230px); }
  .home__actions { margin-top: var(--space-md); }
  .home__inner { padding-bottom: var(--space-sm); }
}

/* Two 11rem buttons do not fit a 390px screen, and they were overflowing it
   rather than wrapping, which dragged the whole hero off centre. */
@media (max-width: 34em) {
  .home__actions {
    width: 100%;
    flex-direction: column;
    align-items: stretch;
  }
  .home__actions .btn { min-width: 0; width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .home__scroll { animation: none; }
}

/* --- Information bar ------------------------------------------------------ */

/* The facts the owner asked to keep in front of a visitor. Written into the
   markup rather than injected by script, so the address and the phone number
   survive a script failure and stay readable to a crawler, which matters more
   than usual on a site whose whole problem is that people cannot find the
   place. Home is the only page that carries it. */

.infobar {
  position: relative;
  z-index: 1;
  border-top: 1px solid var(--hairline-faint);
  background: rgba(6, 6, 6, 0.74);
}
@supports (backdrop-filter: blur(1px)) {
  .infobar { backdrop-filter: blur(10px) saturate(1.05); }
}

/* Each column is as wide as what it says, and the width left over is shared
   out equally, so a longer label or address never wraps while a column
   beside it sits half empty, as fixed fractions let happen. */
.infobar__list {
  display: grid;
  grid-template-columns: repeat(5, auto);
  margin: 0;
  padding: 0;
  list-style: none;
}

.infobar__item {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-sm) clamp(0.75rem, 1.6vw, 1.5rem);
  text-align: start;
  border-inline-start: 1px solid var(--hairline-faint);
}
.infobar__item:first-child { border-inline-start: 0; }

.infobar__icon {
  flex: none;
  width: 20px;
  height: 20px;
  color: var(--gold-400);
}

.infobar__label {
  display: block;
  white-space: nowrap;
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  color: var(--gold-300);
}
/* A time range never breaks at its dash, which a browser allows even with a
   no-break space either side. The address, a link, still wraps. */
span.infobar__value { white-space: nowrap; }
.infobar__value {
  display: block;
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--cream-300);
}
.infobar a {
  color: var(--cream-300);
  transition: color var(--dur-fast) var(--ease-out);
}
.infobar a:hover { color: var(--gold-200); }

@media (min-width: 60.0625em) {
  /* Clear of the chat button, which is fixed to this corner. */
  .infobar__item:last-child { padding-inline-end: 5.5rem; }
}

/* Three across, in two rows, from 961px until the full strip fits at 1024px.
   Five in a row there left each cell under 160px and broke the address and
   "Open daily" mid-line. */
@media (min-width: 60.0625em) and (max-width: 63.9375em) {
  .infobar__list { grid-template-columns: repeat(3, auto); }
  .infobar__item:nth-child(4) { border-inline-start: 0; }
  .infobar__item:nth-child(n + 4) { border-top: 1px solid var(--hairline-faint); }
  .infobar__item:last-child { grid-column: span 2; }
}

@media (max-width: 60em) {
  .infobar__list { grid-template-columns: repeat(2, 1fr); }
  .infobar__item:nth-child(odd) { border-inline-start: 0; }
  .infobar__item:nth-child(n + 3) { border-top: 1px solid var(--hairline-faint); }
  .infobar__item:last-child { grid-column: 1 / -1; }
}
@media (max-width: 34em) {
  .infobar__list { grid-template-columns: 1fr; }
  .infobar__item { border-inline-start: 0; }
  .infobar__item + .infobar__item { border-top: 1px solid var(--hairline-faint); }
}

/* --- Coming soon ---------------------------------------------------------- */

/* Menu and online ordering are written and waiting on the restaurant. A page
   that says so plainly beats a page of invented dishes. */
.soon {
  display: grid;
  place-items: center;
  text-align: center;
  /* --space-3xl alone is 75px at 390px wide, three pixels clear of a 72px fixed
     header, which is what made these two pages jump on a phone. */
  padding-block: var(--header-clear) var(--space-3xl);
}
.soon h1 {
  margin-top: var(--space-sm);
  /* A utility page does not need hero-scale type, and the longer of the two
     headings runs the full width of a desktop screen at --fs-h1. */
  font-size: var(--fs-h2);
}
.soon p {
  max-width: 44ch;
  /* auto, not 0: a max-width block inside a centred column still has to be
     told to centre itself. */
  margin: var(--space-md) auto 0;
  color: var(--cream-300);
}
/* Order Online: one card per way to order, side by side from tablet width
   and stacked on a phone. The cards are the info bar's materials -- elevated
   ink, a gold hairline -- and every button is the ghost style so neither way
   outranks the other. An unlinked button (no href, aria-disabled) is a
   placeholder for a delivery partner whose store page doesn't exist yet; it
   keeps the layout but is neither clickable nor a tab stop. */
.order__grid {
  display: grid;
  gap: var(--space-md);
  /* start, not stretch: a card is as tall as what it says, so the pickup
     card's one button sits under its copy instead of at the foot of a card
     stretched to match the taller delivery card. */
  align-items: start;
  width: min(100%, 52rem);
  margin: var(--space-xl) auto 0;
}
@media (min-width: 40em) {
  .order__grid { grid-template-columns: 1fr 1fr; }
}
.order__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg) var(--space-md);
  background: var(--bg-elevated);
  border: 1px solid var(--hairline);
}
.order__card h2 {
  margin: 0;
  font-size: var(--fs-h3);
}
/* Wins over `.soon p`, which centres a 44ch column; a card has its own width. */
.soon .order__card p {
  max-width: none;
  margin: 0;
}
.order__card-actions {
  display: grid;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}
.order__card-actions .btn { width: 100%; }
.soon .order__card p.order__card-note {
  font-size: var(--fs-tiny);
  color: var(--cream-500);
}
.btn[aria-disabled="true"] {
  opacity: 0.45;
  pointer-events: none;
}
.soon p.order__note {
  margin-top: var(--space-lg);
  font-size: var(--fs-small);
  color: var(--cream-500);
}
/* Wins over `.soon p` on specificity, which the class alone would not. */
.soon p.soon__badge {
  max-width: none;
  margin: 0;
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--gold-300);
}
.soon .btn-row { margin-top: var(--space-lg); }

/* --- Menu ----------------------------------------------------------------- */

/* The body of menu.html is generated: tools/make_menu.py bakes data/menu.json
   into it. Below the title it takes the printed menus' look (tools/
   menu_pdf.css): three sheets, each inside a printed card's double rule and
   laid out like its sheet of the owner's workbook, gold names with their
   prices in a column of their own, as the sheets have them, spaced capitals
   over gold rules for the section titles, and no dollar sign. A wide screen
   gets the sheets as printed; the columns fold to two, then one. */

:root { --menu-nav-h: 3.25rem; }

/* Cormorant's figures are old-style by default, so "Set for 2", "+12" and
   "Level 1" sat low beside the lining figures of the prices. One kind
   everywhere on the page. */
.page-hero--menu,
.menu { font-variant-numeric: lining-nums; }

/* The hours and the happy hour each hold together; a phone stacks them. */
.page-hero--menu .lead { max-width: none; }
.page-hero--menu .lead span { white-space: nowrap; }
@media (max-width: 47.999em) {
  .page-hero--menu .lead span { display: block; }
  .page-hero--menu .lead .menu-hero__dot { display: none; }
}
/* Under 352px the happy hour line (about 300px) no longer fits whole, and
   kept whole it widened a 320px page by 22px. */
@media (max-width: 21.9375em) {
  .page-hero--menu .lead span { white-space: normal; }
}

/* The section bar sticks under the header, on the layer the tokens reserve
   for it, and is set like the header's own links: capitals, the one being
   read in gold with the header's gold rule under it (js/menu.js). On a phone
   it scrolls sideways and fades at the right edge, so a cut-off word reads as
   "more this way". The auto margins centre the links when they fit and start
   them at the edge when they do not. */
.menu-nav {
  position: sticky;
  top: var(--header-h);
  z-index: var(--z-menubar);
  background: rgba(6, 6, 6, 0.96);
  border-bottom: 1px solid var(--hairline-faint);
}
@supports (backdrop-filter: blur(1px)) {
  .menu-nav {
    background: rgba(6, 6, 6, 0.86);
    backdrop-filter: blur(14px) saturate(1.1);
  }
}
.menu-nav__list {
  display: flex;
  padding-inline: 0.5rem 2.5rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.menu-nav__list::-webkit-scrollbar { display: none; }
.menu-nav__list > li { flex: none; }
.menu-nav__list > li:first-child { margin-inline-start: auto; }
.menu-nav__list > li:last-child { margin-inline-end: auto; }
.menu-nav a {
  position: relative;
  display: flex;
  align-items: center;
  min-height: var(--menu-nav-h);
  padding-inline: 0.85rem;
  font-size: var(--fs-tiny);
  font-weight: 500;
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--cream-300);
  transition: color var(--dur-fast) var(--ease-out);
}
.menu-nav a::after {
  content: "";
  position: absolute;
  left: 0.85rem;
  right: 0.85rem;
  bottom: 0.75rem;
  height: 1px;
  background: var(--gold-300);
  transform: scaleX(0);
  transition: transform var(--dur-mid) var(--ease-out);
}
.menu-nav a:hover { color: var(--gold-200); }
.menu-nav a[aria-current="true"] { color: var(--gold-300); }
.menu-nav a[aria-current="true"]::after { transform: scaleX(1); }
@media (max-width: 59.999em) {
  .menu-nav__list {
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
            mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
  }
}

/* A sheet: a printed card's double rule, inset from its edge. */
.menu {
  --menu-rule: rgba(200, 144, 64, 0.42);
  --menu-name: 1.25rem;
  --menu-desc: 0.9375rem;
  --menu-col: 2.5rem;
  display: grid;
  gap: clamp(2rem, 4vw, 3.5rem);
  width: min(100% - 2.5rem, 100rem);
  margin-inline: auto;
  padding-block: var(--space-2xl) var(--space-3xl);
  color: var(--cream-300);
}
.menu ul { list-style: none; margin: 0; padding: 0; }
.sheet {
  position: relative;
  padding: clamp(2rem, 4vw, 3.5rem) clamp(1.25rem, 4vw, 3.75rem);
  border: 1px solid rgba(200, 144, 64, 0.75);
  scroll-margin-top: var(--menu-nav-h);
}
.sheet::after {
  content: "";
  position: absolute;
  inset: 0.4rem;
  border: 1px solid rgba(200, 144, 64, 0.4);
  pointer-events: none;
}

/* Titles in spaced capitals: a sheet's with a rule running out after it, a
   list's over a rule, a centred one between two. */
.sheet__title,
.msec__title {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.1;
  text-transform: uppercase;
  color: var(--gold-300);
}
.sheet__title {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.25rem);
  font-size: clamp(1.75rem, 1.35rem + 1.3vw, 2.5rem);
  letter-spacing: 0.18em;
}
.msec__title {
  margin-bottom: 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--menu-rule);
  font-size: clamp(1.25rem, 1.05rem + 0.55vw, 1.5rem);
  letter-spacing: 0.15em;
}
h4.msec__title {
  margin-bottom: 0.75rem;
  padding-bottom: 0.45rem;
  font-size: clamp(1.05rem, 0.95rem + 0.3vw, 1.2rem);
  letter-spacing: 0.18em;
}
.msec__title--centred {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-bottom: 0;
  border-bottom: 0;
}
.sheet__title::after,
.sheet__title--centred::before,
.msec__title--centred::before,
.msec__title--centred::after {
  content: "";
  flex: 1;
  min-width: 1rem;
  border-top: 1px solid var(--menu-rule);
}
.msec,
.msub { scroll-margin-top: var(--menu-nav-h); }
.msec + .msec { margin-top: clamp(2rem, 3.5vw, 2.75rem); }
.msec__title + .msec { margin-top: 0; }

/* Wine's header row: the rule runs under the title and its price columns. */
.msec__head {
  display: flex;
  align-items: flex-end;
  gap: 0.75rem;
  margin-bottom: 1rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--menu-rule);
}
.msec__head:has(h4) { margin-bottom: 0.75rem; padding-bottom: 0.45rem; }
.msec__head .msec__title { flex: 1; margin: 0; padding: 0; border: 0; }
.cols,
.dish__price--cols {
  display: grid;
  grid-template-columns: repeat(3, var(--menu-col));
  text-align: right;
}
.cols {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cream-500);
}

/* Phở, Noodles & Rice and Bowls under MAIN: the logo's italic, in gold. */
.msub + .msub { margin-top: 2.25rem; }
.msub__title {
  margin-bottom: 0.35rem;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 1.25rem + 0.45vw, 1.65rem);
  font-style: italic;
  font-weight: 300;
  line-height: 1.15;
  color: var(--gold-200);
}
.msec__note,
.add,
.set__add {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-style: italic;
  font-weight: 300;
  line-height: 1.4;
  color: var(--cream-300);
}
.msec__note { margin-bottom: 0.5rem; }
.add--sec:last-of-type { margin-bottom: 1rem; }
.add__price {
  margin-inline-start: 0.35em;
  font-style: normal;
  font-weight: 600;
  color: var(--gold-300);
  white-space: nowrap;
}

/* A dish: name, tags and price on one line, the price flush right in the
   name's face; the description in the site's sans below. */
.dishes > li + li { margin-top: 1.1rem; }
.dish__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.9rem;
}
.dish__name,
.dish__price {
  font-family: var(--font-display);
  font-size: var(--menu-name);
  font-weight: 600;
  line-height: 1.25;
}
.dish__name { color: var(--gold-300); }
.dish__price {
  flex: none;
  color: var(--gold-200);
  white-space: nowrap;
  font-variant-numeric: lining-nums tabular-nums;
}
.dish__price i {
  font-size: 0.88em;
  font-weight: 300;
  color: var(--cream-300);
}
.dish__kind {
  font-family: var(--font-display);
  font-size: calc(var(--menu-name) * 0.9);
  font-style: italic;
  font-weight: 300;
  color: var(--cream-300);
}
.dish__kind::before { content: "· "; color: var(--gold-500); }
.tags {
  font-family: var(--font-ui);
  font-size: calc(var(--menu-name) * 0.62);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--gold-500);
  white-space: nowrap;
  vertical-align: 0.12em;
}
.tags abbr { text-decoration: none; }
.dish__desc {
  margin-top: 0.25rem;
  padding-inline-end: 1.5rem;
  font-size: var(--menu-desc);
  font-weight: 300;
  line-height: 1.55;
  text-wrap: pretty;
}
.dish .add { margin-top: 0.25rem; font-size: 1.0625rem; }
.sheet--drinks .dish__desc,
.sheet--hh .dish__desc { padding-inline-end: 0; }

/* Happy hour's title between two rules, its hours in spaced capitals. */
.hh-head { margin-bottom: clamp(1.75rem, 3vw, 2.5rem); text-align: center; }
.hh-head .sheet__title { margin-bottom: 0.75rem; }
.hh-head__when {
  font-family: var(--font-ui);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  line-height: 1.7;
  text-transform: uppercase;
  color: var(--gold-200);
}

/* Happy hour's food is centred, as its sheet sets it, the price after the name. */
.two__col--centred { text-align: center; }
.dish--centred .dish__head { display: block; }
.dish--centred .dish__price { margin-inline-start: 0.45em; }
.dish--centred .dish__desc { padding-inline: 0.75rem; }

/* The sharing sets: double-ruled panels, the price set large. */
.sets { display: grid; gap: 1.25rem; }
.set {
  padding: 1.75rem 1.25rem 1.5rem;
  text-align: center;
  border: 1px solid rgba(200, 144, 64, 0.75);
  outline: 1px solid rgba(200, 144, 64, 0.4);
  outline-offset: -0.45rem;
}
.set__name {
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  line-height: 1.25;
  text-transform: uppercase;
  color: var(--gold-300);
}
.set__price {
  margin: 0.4rem 0 0.8rem;
  font-family: var(--font-display);
  font-size: 2.6rem;
  font-weight: 600;
  line-height: 1;
  color: var(--gold-200);
}
.set__includes {
  font-size: 0.9375rem;
  line-height: 1.85;
  color: var(--cream-100);
}
.set__add { margin-top: 0.6rem; }

/* Food's footer, under the sharing sets: the key to V, VG, GF and P, the
   hours and the address. */
.menu-brand { margin-top: 2rem; text-align: center; }
.menu-key,
.menu-brand__facts {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  line-height: 1.9;
  text-transform: uppercase;
  color: var(--cream-500);
}
.menu-key span { white-space: nowrap; }
.menu-key span + span::before { content: "\00b7"; margin-inline: 0.6em; color: var(--gold-500); }
.menu-key .menu-key__break { display: none; }
.menu-key .menu-key__break::before,
.menu-key .menu-key__break + span::before { content: none; }
.menu-key abbr { font-weight: 600; text-decoration: none; color: var(--gold-500); }
.menu-key + .menu-brand__facts { margin-top: 0.85rem; }
.menu-brand__facts + .menu-brand__facts { margin-top: 0.5rem; }
/* Below the widest layout the hours and address are already in the title
   and the site's footer, and the logo in the header. */
.sheet--food .menu-brand__facts,
.food__logo { display: none; }

.food,
.two { display: grid; gap: 2.75rem; }
/* A long unbroken line inside a column must not hold the column open. */
.food__col,
.mains__col,
.two__col { min-width: 0; }

/* Stacked, the second half of MAIN needs the gap the columns gave it. */
.mains__col + .mains__col { margin-top: 2.25rem; }

/* A phone: slimmer gutters and a little smaller type, and the key as a
   two-by-two grid, since a wrapped line would start with a dot. */
@media (max-width: 39.999em) {
  .menu {
    width: calc(100% - 1.5rem);
    --menu-name: 1.125rem;
    --menu-col: 2.25rem;
  }
  .sheet { padding-inline: 1rem; }
  .menu-key {
    display: grid;
    grid-template-columns: repeat(2, auto);
    justify-content: center;
    column-gap: 1.25rem;
  }
  .menu-key span::before { content: none; }
}

@media (min-width: 40em) {
  .sets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* From 60em: Food keeps the sets across the top, then Appetizers over
   Baguettes and Dessert beside MAIN; Drinks and Happy Hour are the sheets'
   two columns. */
@media (min-width: 60em) {
  .food {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto auto 1fr;
    grid-template-areas: "sets sets" "app main" "bag main";
    column-gap: 0;
  }
  .food__col--sets { grid-area: sets; }
  .food__col--app { grid-area: app; padding-inline-end: 2.25rem; }
  .food__col--bag { grid-area: bag; padding-inline-end: 2.25rem; }
  .food__col--main {
    grid-area: main;
    padding-inline-start: 2.25rem;
    border-left: 1px solid var(--menu-rule);
  }
  .two { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); gap: 0; }
  .two--hh { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.2fr); }
  .two__col:first-child { padding-inline-end: 2.25rem; }
  .two__col + .two__col {
    padding-inline-start: 2.25rem;
    border-left: 1px solid var(--menu-rule);
  }
}

/* From 100em, Food is the printed page: five columns, the logo heading the
   first, MAIN spanning Phở and Noodles & Rice. The type scales with the
   screen, as it would on paper, sized so the longest names ("Garlic Chicken
   Wings (6 pc)", "Warm Brownie & Vanilla Gelato") stay on one line. Below
   100em five columns would need type too small to read. */
@media (min-width: 100em) {
  .menu { width: min(100% - 2.5rem, 110rem); }
  .sheet--food {
    --menu-name: clamp(1rem, 0.25rem + 0.75vw, 1.15rem);
    --menu-desc: clamp(0.8125rem, 0.2rem + 0.62vw, 0.9375rem);
  }
  .food {
    grid-template-columns: minmax(0, 1.35fr) minmax(0, 1.15fr) minmax(0, 2.3fr) minmax(0, 1.15fr);
    grid-template-rows: auto;
    grid-template-areas: "sets app main bag";
    row-gap: 0;
  }
  .food__col { padding-inline: 1.4rem; }
  .food__col--sets {
    display: flex;
    flex-direction: column;
    padding-inline-start: 0;
  }
  .food__col--bag { padding-inline-end: 0; }
  .food__col--app,
  .food__col--bag { border-left: 1px solid var(--menu-rule); }
  .food__logo {
    display: block;
    width: 86%;
    height: auto;
    margin: 0 auto 2rem;
  }
  .sets { grid-template-columns: 1fr; }
  .food__col--sets .menu-brand { margin-top: auto; padding-top: 2.5rem; }
  .sheet--food .menu-brand__facts { display: block; }
  .mains { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .mains__col + .mains__col { margin-top: 0; }
  .sheet--food .msub__title { font-size: calc(var(--menu-name) * 1.3); }
  /* The key in two lines under the sets, as on the printed page. */
  .menu-key .menu-key__break { display: block; }
  .mains__col:first-child { padding-inline-end: 1.4rem; }
  .mains__col + .mains__col {
    padding-inline-start: 1.4rem;
    border-left: 1px solid var(--menu-rule);
  }
}

/* --- Footer --------------------------------------------------------------- */

.site-footer {
  background: var(--ink-900);
  border-top: 1px solid var(--hairline-faint);
  padding-block: var(--space-2xl) var(--space-lg);
  text-align: center;
}
.site-footer .shell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}
.site-footer__logo { width: 170px; }
.site-footer address { color: var(--cream-300); }

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-xs) var(--space-md);
}
.footer-nav a {
  font-size: var(--fs-small);
  color: var(--cream-300);
  transition: color var(--dur-fast) var(--ease-out);
}
.footer-nav a:hover { color: var(--gold-200); }

.site-footer__fine {
  margin-inline-start: var(--space-xs);
  color: var(--cream-700);
}
.site-footer__fine:hover { color: var(--gold-300); }

.site-footer__copy {
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--hairline-faint);
  color: var(--cream-700);
  font-size: var(--fs-tiny);
  width: 100%;
}

/* --- Social --------------------------------------------------------------- */

.social-row { display: flex; gap: var(--space-2xs); justify-content: center; }
.social-row a {
  display: grid;
  place-items: center;
  width: 40px; height: 40px;
  border: 1px solid var(--hairline-faint);
  border-radius: 50%;
  color: var(--cream-300);
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.social-row a:hover {
  color: var(--gold-200);
  border-color: var(--hairline-strong);
  background: rgba(200, 144, 64, 0.07);
}
.social-row svg { width: 18px; height: 18px; }

/* --- 404 ------------------------------------------------------------------ */

.error-page {
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  place-items: center;
  text-align: center;
  padding: var(--space-2xl) 0;
}
.error-page__code {
  font-family: var(--font-display);
  font-size: clamp(5rem, 18vw, 11rem);
  line-height: 1;
  font-weight: 300;
  margin-top: var(--space-md);
}

/* --- Utilities ------------------------------------------------------------ */

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

[hidden] { display: none !important; }

/* ==========================================================================
   Chat widget
   ========================================================================== */

.chat-widget {
  --chat-z: 300;
  position: fixed;
  bottom: var(--space-md);
  right: var(--space-md);
  z-index: var(--chat-z);
  font-family: var(--font-ui);
}

/* Yielded to the home page's row of actions; see keepClearOfActions in
   js/chat.js. It fades rather than moves, deliberately: the script decides by
   comparing this button's box with the buttons' box, and a transform would
   shift the box it is measuring and set the two states fighting each other. */
.chat-widget.is-deferred {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-mid) var(--ease-out),
              visibility 0s linear var(--dur-mid);
}
.chat-widget { transition: opacity var(--dur-mid) var(--ease-out); }
@media (prefers-reduced-motion: reduce) {
  .chat-widget, .chat-widget.is-deferred { transition: none; }
}

/* --- Floating action button ----------------------------------------------- */

/* A ring, not a disc. Everywhere else on this site gold is a hairline or a
   piece of type, and a solid gold fill is reserved for the one primary button
   on a screen; a 56px gold disc floating over every page was the loudest thing
   in the design. The glow is what keeps it findable on a near-black page, and
   it is the same treatment the hero mark carries. */
.chat-fab {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: 1px solid var(--hairline-strong);
  background: var(--ink-700);
  color: var(--gold-300);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-lift), 0 0 24px -6px rgba(200, 144, 64, 0.45);
  transition: transform var(--dur-mid) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-mid) var(--ease-out);
}
.chat-fab:hover {
  transform: translateY(-3px);
  border-color: var(--gold-500);
  color: var(--gold-100);
  box-shadow: 0 30px 70px -24px rgba(0, 0, 0, 0.95),
              0 0 30px -4px rgba(200, 144, 64, 0.6);
}
.chat-fab__icon { width: 24px; height: 24px; }
.chat-fab__icon--close { display: none; }
.chat-widget.is-open .chat-fab__icon--chat { display: none; }
.chat-widget.is-open .chat-fab__icon--close { display: block; }

/* --- Popup ---------------------------------------------------------------- */

.chat-popup {
  position: absolute;
  bottom: calc(56px + var(--space-sm));
  right: 0;
  width: 380px;
  height: 520px;
  background: var(--ink-700);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* Inherits --font-ui. This used to hardcode Inter, which this site does not
     load, so the whole widget fell back to system sans while every other
     surface was Montserrat. That one line did most of the work of making the
     chat look like somebody else's product. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px) scale(0.96);
  transition: opacity var(--dur-mid) var(--ease-out),
              visibility var(--dur-mid),
              transform var(--dur-mid) var(--ease-out);
}
.chat-widget.is-open .chat-popup {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

/* --- Header --------------------------------------------------------------- */

.chat-popup__header {
  display: grid;
  justify-items: center;
  padding: var(--space-md) var(--space-xl) var(--space-sm);
  background: var(--ink-800);
  border-bottom: 1px solid var(--hairline);
  position: relative;
}
/* The lockup, given room. It already says "Vietnamese Kitchen + Bar", so the
   separate label underneath was repeating the mark; the greeting in the first
   message says what the assistant is. */
.chat-popup__brand img {
  width: auto;
  height: 62px;
}
.chat-popup__close {
  position: absolute;
  top: 50%;
  right: var(--space-2xs);
  transform: translateY(-50%);
  /* 44px, like every other control on the site. It used to be a bare times
     glyph with no padding. */
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  color: var(--cream-500);
  cursor: pointer;
  transition: color var(--dur-fast);
}
.chat-popup__close svg { width: 18px; height: 18px; }
.chat-popup__close:hover { color: var(--gold-200); }

/* --- Messages ------------------------------------------------------------- */

.chat-popup__messages {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.chat-msg {
  max-width: 82%;
  padding: 0.5rem 0.75rem;
  /* Even corners, no tail. The asymmetric bubble is a messaging-app convention
     and it fought the flat panels the rest of the site is built from. */
  border-radius: var(--radius-sm);
  /* 16px, matching the input rather than the site's small text. The transcript
     is the one place on this site someone reads a paragraph on a phone with a
     keyboard covering half the screen, and 14px was too small for that. Fixed
     rather than fluid: the popup is a fixed 380px, so type that scales with
     the viewport would have grown on the screen with the most room. */
  font-size: 1rem;
  /* Between --leading-snug and --leading-body: 1.3 is cramped once the type is
     16px, 1.75 makes a three-line answer look like an article. */
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}
.chat-msg--ai {
  align-self: flex-start;
  background: var(--ink-600);
  border: 1px solid var(--ink-500);
  color: var(--cream-300);
}
.chat-msg--ai a {
  color: var(--gold-300);
  text-underline-offset: 2px;
}
/* A gold line and a gold wash rather than a solid gold block, for the same
   reason the button is a ring. */
.chat-msg--user {
  align-self: flex-end;
  background: rgba(200, 144, 64, 0.10);
  border: 1px solid var(--hairline);
  color: var(--cream-100);
}

/* --- Typing indicator ----------------------------------------------------- */

.chat-typing {
  display: none;
  align-self: flex-start;
  background: var(--ink-600);
  border: 1px solid var(--ink-500);
  padding: 0.55rem 0.75rem;
  border-radius: var(--radius-sm);
  gap: 4px;
  align-items: center;
}
.chat-typing.is-visible { display: flex; }
.chat-typing span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--gold-400);
  opacity: 0.4;
  animation: chatDot 1.2s infinite;
}
.chat-typing span:nth-child(2) { animation-delay: 0.2s; }
.chat-typing span:nth-child(3) { animation-delay: 0.4s; }
@keyframes chatDot {
  0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
  30% { transform: translateY(-4px); opacity: 1; }
}

/* --- Input ---------------------------------------------------------------- */

.chat-popup__input {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2xs);
  padding: var(--space-xs);
  border-top: 1px solid var(--hairline);
  background: var(--ink-800);
}
.chat-input {
  flex: 1;
  background: var(--ink-600);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  color: var(--cream-300);
  font-family: var(--font-ui);
  /* 16px exactly. iOS Safari zooms the whole page when a focused input is any
     smaller, which on a site sold as mobile-friendly is a bug, not a detail. */
  font-size: 16px;
  padding: 0.6rem 0.7rem;
  resize: none;
  max-height: 100px;
  line-height: var(--leading-snug);
}
.chat-input::placeholder { color: var(--cream-700); }
.chat-input:focus {
  outline: none;
  border-color: var(--gold-500);
  box-shadow: 0 0 0 2px rgba(200, 144, 64, 0.2);
}

/* 44px, the site's own minimum, and the ghost-button treatment: a gold outline
   that fills only on hover. */
.chat-send {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline-strong);
  background: transparent;
  color: var(--gold-200);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.chat-send svg { width: 17px; height: 17px; }
.chat-send:hover {
  background: var(--gold-grad-soft);
  border-color: var(--gold-400);
  color: var(--ink-900);
}
.chat-send:disabled { opacity: 0.4; cursor: not-allowed; }

/* --- Mobile --------------------------------------------------------------- */

@media (max-width: 480px) {
  .chat-widget {
    bottom: calc(var(--space-sm) + env(safe-area-inset-bottom));
    right: calc(var(--space-sm) + env(safe-area-inset-right));
  }
  .chat-popup {
    position: fixed;
    inset: 0;
    width: 100%;
    /* dvh, not %, so the on-screen keyboard shortens the panel instead of
       pushing the input off the bottom of it. */
    height: 100dvh;
    border-radius: 0;
    border: none;
  }
  /* Full-bleed means the panel owns the notch and the home indicator. */
  .chat-popup__header { padding-top: calc(var(--space-sm) + env(safe-area-inset-top)); }
  .chat-popup__input { padding-bottom: calc(var(--space-xs) + env(safe-area-inset-bottom)); }
  .chat-popup__messages { padding-inline: calc(var(--space-sm) + env(safe-area-inset-left)); }
}

/* --- Visit: wayfinding ---------------------------------------------------- */

.lvl-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
  padding: 0.6rem 1.1rem;
  border: 1px solid var(--gold-600);
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--gold-300);
}
.lvl-badge svg { width: 15px; height: 15px; flex: none; }

/* the 3D model -- a pre-rendered Blender orbit, shipped as an image sequence */
.m3d {
  position: relative;
  width: 100%;
  aspect-ratio: 1000 / 625;
  max-width: 54rem;
  margin: var(--space-lg) auto 0;
  background: radial-gradient(ellipse 70% 60% at 50% 40%, #141210, var(--ink-900) 72%);
  border: 1px solid var(--ink-500);
  border-radius: var(--radius-sm);
  overflow: hidden;
  touch-action: pan-y;
  cursor: grab;
}
.m3d.is-drag { cursor: grabbing; }

/* Stated on the stage and the frame as well as on .m3d. The listeners are
   on the stage and the frame is a child of it, and leaving those at `auto`
   leaves which element's value applies up to the browser. This is the one
   declaration that decides whether a phone can scroll past the model. */
.m3d__stage { position: absolute; inset: 0; touch-action: pan-y; }
.m3d__stage:focus-visible {
  outline: 2px solid var(--gold-400);
  outline-offset: -3px;
}
.m3d__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
  touch-action: pan-y;
}

/* Labels ride over the render as real text, so they stay crisp at any size and
   a screen reader can read them out. */
.m3d__labels { position: absolute; inset: 0; pointer-events: none; }
.m3d__lbl {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 0.16rem 0.44rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: rgba(8, 8, 8, 0.78);
  font-family: var(--font-ui);
  font-size: clamp(0.46rem, 0.95vw, 0.6rem);
  font-weight: 400;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--cream-300);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.m3d__lbl--gold { color: var(--gold-300); border-color: var(--hairline-strong); }
/* On a phone the orientation labels cover more than they explain. */
@media (max-width: 34em) {
  .m3d__lbl[data-pri="2"] { display: none; }
}

/* The render covers the whole block. At 1.6:1 on a phone that leaves the stair
   and the restaurant a few dozen pixels across, so instead of shrinking the
   whole thing, go taller and let object-fit crop in on the part that matters.

   A cover crop on a portrait box shows the full height and a slice of the
   width, so object-position is the only control and both values below are
   measured off the render rather than guessed:

     4/3  window 1167px wide, x 61-1228 -- the whole block, Shoppers' sign at
          one end and TIME MARKET at the other
     4/5  window  700px wide, x 490-1190 -- the stair, LÚA and the marker; on a
          phone there is no room for Shoppers as well, and the photo walk
          below the model covers that end of the street anyway            */
@media (max-width: 48em) {
  .m3d { aspect-ratio: 4 / 3; }
  .m3d__img { object-position: 26% 50%; }
}
@media (max-width: 34em) {
  .m3d { aspect-ratio: 4 / 5; }
  .m3d__img { object-position: 70% 47%; }
}
.m3d.is-drag .m3d__lbl { transition: none; }

.m3d__hint {
  position: absolute;
  top: var(--space-xs);
  right: var(--space-xs);
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: rgba(11, 11, 11, 0.7);
  font-size: 0.6rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--cream-500);
  pointer-events: none;
  transition: opacity var(--dur-mid) var(--ease-out);
}
.m3d.is-touched .m3d__hint { opacity: 0; }

.m3d-note {
  margin-top: var(--space-sm);
  font-size: var(--fs-small);
  color: var(--text-muted);
}

/* Without JS the cutaway still explains the way in, so only the controls that
   need scripting are hidden. */
html:not(.js) .m3d-note,
html:not(.js) .m3d__hint { display: none; }
.m3d:not([data-m3d-ready]) { cursor: default; }

/* landmarks and the building-name warning */
.lm {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: var(--space-sm);
  margin: var(--space-lg) 0 0;
  padding: 0;
  list-style: none;
  text-align: left;
}
.lm li {
  padding: var(--space-sm) 0;
  border-top: 1px solid var(--hairline-faint);
}
.lm b { display: block; font-weight: 400; color: var(--text-strong); }
.lm span { font-size: var(--fs-small); color: var(--gold-500); }
.lm p { margin-top: var(--space-2xs); font-size: var(--fs-small); color: var(--cream-500); }

.note {
  max-width: 46ch;
  margin: var(--space-lg) auto 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 2px solid var(--gold-600);
  text-align: left;
  color: var(--cream-300);
  font-size: var(--fs-small);
}

/* --- Visit: landmark list ------------------------------------------------- */

/* --- Button trimmings ----------------------------------------------------- */

.btn__icon {
  width: 15px;
  height: 15px;
  margin-inline-start: 0.55rem;
  flex: none;
}
.btn-row__note {
  margin-top: var(--space-xs);
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  color: var(--cream-700);
}

/* --- Visit: the map ------------------------------------------------------- */

/* A real map, MapLibre over OpenFreeMap's dark style. The box is sized before
   any script runs so the map arriving shifts nothing, and the fallback sits
   inside that same box, which means a blocked tile server or no JS at all
   leaves the address exactly where the map would have been. */

.wmap {
  position: relative;
  margin: var(--space-lg) 0 0;
  padding: 0;
}
.wmap__canvas {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  min-height: 260px;
  border: 1px solid var(--hairline-faint);
  border-radius: var(--radius-sm);
  background: var(--ink-900);
  overflow: hidden;
}
@media (max-width: 48em) { .wmap__canvas { aspect-ratio: 4 / 3; } }
/* Taller, not squarer, as the screen narrows. The walk is 322 m north-south
   against 290 m east-west, and the labels need vertical room more than the
   route needs horizontal room. */
@media (max-width: 34em) { .wmap__canvas { aspect-ratio: 3 / 4; } }

.wmap__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  gap: var(--space-xs);
  padding: var(--space-lg);
  text-align: center;
}
.wmap__fallback p { max-width: 34ch; margin-inline: auto; color: var(--cream-300); }
.wmap__fallback b { color: var(--text-strong); }
.wmap__why { color: var(--gold-500); font-size: var(--fs-small); }

.wmap__legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-md);
  margin-top: var(--space-xs);
  font-family: var(--font-ui);
  font-size: var(--fs-tiny);
  color: var(--cream-500);
}
.wmap__key { display: inline-flex; align-items: center; gap: 0.5rem; }
.wmap__key::before {
  content: "";
  width: 22px;
  height: 0;
  flex: none;
  border-top: 2px dashed var(--gold-300);
}
/* The credit sits in the corner of the map and links to the full text on
   /credits, under the label "Sources". OpenStreetMap's attribution guideline
   accepts a clearly labelled link where links are the expected route to detail,
   which keeps the licence satisfied without a block of small print under the
   drawing. */
.wmap__credit {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 5px 9px;
  border-radius: var(--radius-sm) 0 0 0;
  background: rgba(11, 11, 11, 0.72);
  font-family: var(--font-ui);
  font-size: 0.5625rem;
  letter-spacing: var(--tracking-nav);
  text-transform: uppercase;
  /* Muted, but the licence asks for attribution that a visitor could actually
     notice, so not so muted that it disappears into the tiles. */
  color: var(--cream-500);
}
.wmap__credit:hover { color: var(--gold-300); }

/* Markers. The span is the dot and sits exactly on the coordinate; the label
   is absolutely positioned off it, on the side the route data specifies. Doing
   it the other way round, with the whole block anchored, pushed long labels off
   the edge of a narrow map and let neighbouring labels overlap. */
.wmap__pin {
  position: relative;
  display: block;
  width: 7px;
  height: 7px;
  border: 1px solid var(--cream-500);
  border-radius: 50%;
  background: var(--ink-900);
  pointer-events: none;
}
.wmap__pin b {
  position: absolute;
  display: block;
  width: max-content;
  max-width: 9rem;
  font-family: var(--font-ui);
  font-size: clamp(0.625rem, 1.4vw, 0.75rem);
  font-weight: 500;
  line-height: 1.25;
  color: var(--cream-100);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95), 0 0 10px rgba(0, 0, 0, 0.9);
}
.wmap__pin--top b    { bottom: 12px; left: 50%; transform: translateX(-50%); text-align: center; }
.wmap__pin--bottom b { top: 12px;    left: 50%; transform: translateX(-50%); text-align: center; }
.wmap__pin--right b  { left: 13px;   top: 50%;  transform: translateY(-50%); text-align: left; }
.wmap__pin--left b   { right: 13px;  top: 50%;  transform: translateY(-50%); text-align: right; }
/* Diagonals, for the three landmarks that sit within 90px of each other on a
   320px map. A centred label there reaches its neighbours whichever side of
   the dot it is on; these grow outward from the cluster instead of across it.
   tools/check_map_labels.py is what says whether a placement works. */
.wmap__pin--top-left b     { bottom: 10px; right: -3px; text-align: right; }
.wmap__pin--top-right b    { bottom: 10px; left: -3px;  text-align: left; }
.wmap__pin--bottom-left b  { top: 10px;    right: -3px; text-align: right; }
.wmap__pin--bottom-right b { top: 10px;    left: -3px;  text-align: left; }

/* On a 280px map the four dots span 90px, so a label at its desktop width
   reaches the frame edge and its neighbours. Wrapping them to two short lines
   is what buys the room; the names stay whole. */
@media (max-width: 30em) {
  .wmap__pin b { max-width: 5rem; }
}

.wmap__pin--lua {
  width: 11px;
  height: 11px;
  border-color: var(--gold-300);
  background: var(--gold-500);
  box-shadow: 0 0 0 4px rgba(200, 144, 64, 0.22);
}
/* No offset here. This rule used to carry `bottom: 16px` from when LÚA was
   always labelled above its dot; against a placement that sets `top`, the two
   resolved to a zero-height box, so the label rendered but measured as nothing
   and every collision test on it silently passed. Position belongs to the
   placement class alone. */
.wmap__pin--lua b {
  color: var(--gold-200);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
}

/* MapLibre ships a light-theme stylesheet, so its controls arrive as white
   pills on a near-black page. Overridden here rather than by editing the
   vendored file, which must stay byte-identical to the release. */
.maplibregl-ctrl-group {
  background: rgba(11, 11, 11, 0.9) !important;
  border: 1px solid var(--hairline-faint) !important;
  box-shadow: none !important;
}
.maplibregl-ctrl-group button + button { border-top-color: var(--ink-500) !important; }
.maplibregl-ctrl-group button span { filter: invert(1) opacity(0.75); }
.maplibregl-ctrl-group button:hover { background: rgba(200, 144, 64, 0.12) !important; }
.maplibregl-canvas:focus-visible { outline: 2px solid var(--gold-400); outline-offset: -2px; }
.maplibregl-desktop-message,
.maplibregl-mobile-message {
  background: rgba(11, 11, 11, 0.86) !important;
  color: var(--cream-200, var(--cream-300)) !important;
  font-family: var(--font-ui) !important;
}

/* --- Credits page --------------------------------------------------------- */

/* A legal page reads left to right, not centred like the rest of the site. */
.soon--legal { place-items: start center; }
.legal {
  max-width: 58ch;
  margin: var(--space-lg) auto 0;
  text-align: left;
}
.legal h2 {
  margin-top: var(--space-lg);
  font-size: var(--fs-h3);
  color: var(--gold-200);
}
.legal h2:first-child { margin-top: 0; }
.legal p {
  max-width: none;
  margin-top: var(--space-sm);
  font-size: var(--fs-small);
  color: var(--cream-300);
}
.legal a { text-underline-offset: 0.25em; text-decoration: underline; }
