/* ============================================================
   SAFFRON — OASIS / Palm Springs Kinetic Modernism
   ============================================================ */

/* ------------------------------------------------------------
   Display face — Archivo variable, width axis pinned to 125 (expanded).
   Google Fonts has no "Archivo Expanded" family; requesting one returns
   normal width silently. This is the real expanded instance, self-hosted.
   ------------------------------------------------------------ */
@font-face{
  font-family:"Archivo Text";
  src:url("../fonts/archivo-text.woff2") format("woff2");
  font-weight:400 700;
  font-style:normal;
  font-display:swap;
}

@font-face{
  font-family:"Archivo Expanded";
  src:url("../fonts/archivo-expanded.woff2") format("woff2");
  font-weight:600 900;
  font-style:normal;
  font-display:swap;
}

/* ------------------------------------------------------------
   Logo face — Cinzel SemiBold, the typeface of the owners' lockup.
   Subset to letters, ampersand and light punctuation: 6.6 KB.
   Cinzel has no lowercase; typing mixed case renders small caps, which is
   what gives "Restaurant & Lounge" its larger initial R and L.
   USE THIS FOR THE WORDMARK ONLY. It cannot set running text.
   ------------------------------------------------------------ */
@font-face{
  font-family:"Cinzel";
  src:url("../fonts/cinzel-600.woff2") format("woff2");
  font-weight:600;
  font-style:normal;
  font-display:swap;
}

:root{
  /* palette */
  --dusk: #221635;
  --night: #160F26;
  --magenta: #E8447A;
  --orange: #F59E2C;
  --apricot: #FFC97E;
  --turquoise: #37C3B8;
  --sand: #F1E4D0;
  --sand-ink: #2A1D12;

  --gold: #E8B84B;
  --burnt: #C87A2E;         /* on the dark sections: 5.6:1 */
  --burnt-ink: #8A4A12;     /* on the sand menu section: 5.5:1 */

  /* type */
  --display: "Archivo Expanded", "Archivo", system-ui, sans-serif;
  /* One superfamily, two widths: the paragraph is the wordmark at reading width.
     Replaces Inter, which carried no brand and shipped on every other site. */
  --body: "Archivo Text", system-ui, sans-serif;
  /* type scale — one step per role. Replaces 22 ad-hoc sizes, seven of which
     sat between 9.9px and 11.8px.
     Palm Springs skews 50+ and the old site drew the complaint "it's not easily
     readable", so every step carries 2-3px more than a default scale and the
     floor is 14px. Nothing smaller ships, at any breakpoint. */
  --fs-3xs:  .875rem;   /* 14px   eyebrows, tags, micro labels — the floor */
  --fs-2xs:  .9375rem;  /* 15px   nav, buttons, small meta */
  --fs-xs:   1rem;      /* 16px   secondary meta */
  --fs-sm:   1.0625rem; /* 17px   descriptions */
  --fs-base: 1.125rem;  /* 18px   body */
  --fs-md:   1.25rem;   /* 20px   dish names, prices, serif asides */
  --fs-lg:   1.4375rem; /* 23px */
  --fs-xl:   1.625rem;  /* 26px */
  --fs-2xl:  2.125rem;  /* 34px */

  --serif: "Cormorant Garamond", Georgia, serif;
  --logo: "Cinzel", Georgia, serif;   /* wordmark only, never running text */

  --maxw: 1280px;

  /* Clearance under the fixed header for any scroll that lands on a section.
     Read by main.js too, so the CSS path and the Lenis path agree. */
  --head-clear: 92px;
  --ease: cubic-bezier(.2,.8,.2,1);
}

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

html{ -webkit-text-size-adjust:100%; }

body{
  margin:0;
  background:var(--night);
  color:var(--sand);
  font-family:var(--body);
  /* body never declared a size, so every paragraph that did not name a token
     silently inherited the browser's 16px and sat outside the scale. */
  font-size:var(--fs-base);
  /* 500, not 400. The self-hosted Archivo Text is variable (400-700), so the
     extra stroke weight costs nothing to ship and is half of what "bolder"
     meant on the call — the other half is the scale above. */
  font-weight:500;
  line-height:1.6;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* Lenis smooth scroll */
html.lenis, html.lenis body{ height:auto; }
.lenis.lenis-smooth{ scroll-behavior:auto !important; }
.lenis.lenis-stopped{ overflow:hidden; }

img{ display:block; max-width:100%; height:auto; }

a{ color:inherit; text-decoration:none; }

h1,h2,h3,p,dl,dd,figure,ul{ margin:0; }
ul{ list-style:none; padding:0; }

/* focus states, styled and never removed.
   Double ring: turquoise reads on the dark sections, the night-violet halo
   reads on the sand menu section. One of the two always carries contrast
   against whatever sits behind it (WCAG 1.4.11). */
:focus-visible{
  outline:2px solid var(--turquoise);
  outline-offset:3px;
  box-shadow:0 0 0 5px var(--night);
  border-radius:2px;
}

.skip-link{
  position:fixed; top:8px; left:8px; z-index:200;
  background:var(--orange); color:var(--night);
  padding:.6rem 1rem; font-weight:800; border-radius:6px;
  transform:translateY(-160%); transition:transform .2s var(--ease);
}
.skip-link:focus{ transform:translateY(0); }

/* ---------- Film grain ---------- */
.grain{
  position:fixed; inset:-50%; z-index:150; pointer-events:none;
  opacity:.05; mix-blend-mode:overlay;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation:grain 8s steps(6) infinite;
}
@keyframes grain{
  0%{transform:translate(0,0)} 20%{transform:translate(-4%,3%)}
  40%{transform:translate(3%,-4%)} 60%{transform:translate(-3%,2%)}
  80%{transform:translate(4%,-2%)} 100%{transform:translate(0,0)}
}

/* ---------- Sticky side rail ---------- */
.rail{
  position:fixed; top:50%; left:16px; z-index:120;
  transform:translateY(-50%);
  display:flex; flex-direction:column; align-items:center; gap:14px;
  mix-blend-mode:difference;
}
.rail__label{
  writing-mode:vertical-rl; text-orientation:mixed;
  font-family:var(--display); font-weight:800;
  letter-spacing:.35em; text-transform:uppercase;
  font-size:var(--fs-3xs); color:var(--sand);
}
.rail__track{ width:2px; height:120px; background:rgba(241,228,208,.25); border-radius:2px; overflow:hidden; }
.rail__progress{ display:block; width:100%; height:0%; background:var(--orange); }
@media (max-width:820px){ .rail{ display:none; } }

/* ---------- Header ---------- */
.site-head{
  position:fixed; top:0; left:0; right:0; z-index:130;
  display:flex; align-items:center; justify-content:space-between;
  padding:16px clamp(16px,4vw,44px);
  transition:background .4s var(--ease), backdrop-filter .4s var(--ease), padding .4s var(--ease);
}
.site-head.is-stuck{
  background:rgba(22,15,38,.72);
  backdrop-filter:blur(12px);
  padding-top:10px; padding-bottom:10px;
  border-bottom:1px solid rgba(241,228,208,.08);
}
.site-head__brand{ display:flex; align-items:center; gap:10px; }
.site-head__mark{ width:34px; height:auto; }   /* the mark is 0.848:1, not square */
.site-head__word{
  /* The logo face. Cinzel is lighter than Archivo 800 at the same size, so this
     runs one step larger to hold the same presence in the bar. */
  font-family:var(--logo); font-weight:600; letter-spacing:.20em;
  text-transform:uppercase; font-size:var(--fs-md); color:var(--gold);
  /* trailing letter-space would push the word left of optical centre */
  margin-right:-.20em;
}
.site-head__nav{ display:flex; align-items:center; gap:clamp(14px,2.4vw,30px); }
.site-head__nav a{
  font-size:var(--fs-2xs); font-weight:600; letter-spacing:.08em; text-transform:uppercase;
  color:var(--sand); opacity:.82; transition:opacity .2s;
}
.site-head__nav a:hover{ opacity:1; }
.site-head__cta{
  padding:.5rem 1rem; border:1px solid var(--apricot); border-radius:100px;
  /* Solid ground from scroll 0, not only once .is-stuck lands: this chip used to
     sit over the sun's bright core and was the least legible thing in frame. */
  background:var(--night);
  color:var(--apricot) !important; opacity:1 !important;
}
.site-head__cta:hover{ background:var(--orange); color:var(--night) !important; }
/* Six items plus the wordmark need 644px of header, and the all-links band
   starts at 641px, so between roughly 641 and 860 the nav tightens rather than
   jamming itself against the logo. Adding a seventh item will not fit here:
   raise the 640px cutoff instead. */
@media (max-width:860px){
  .site-head__nav{ gap:10px; }
  .site-head__nav a:not(.site-head__cta){ letter-spacing:.04em; }
  .site-head__cta{ padding:.5rem .8rem; }
}
/* The bottom of that band is the tight part: at 641px the six items and the
   wordmark come to exactly the viewport width. 14px is the type floor for this
   site and nothing goes below it, so this is as far as the header stretches. */
@media (max-width:720px){
  .site-head__nav{ gap:8px; }
  .site-head__nav a:not(.site-head__cta){ font-size:var(--fs-3xs); }
  .site-head__cta{ padding:.45rem .7rem; font-size:var(--fs-3xs); }
}

/* ---------- Mobile navigation ----------
   Under 640px the header's top right holds navigation, not Reserve.
   Reserve moves to the dock, which is permanent, so the booking CTA is
   on screen at every scroll position on every page instead of only in
   the header. The panel is the same <nav>: one set of links, one
   aria-current, nothing duplicated to fall out of sync. */
.site-head__burger{
  display:none;
  width:44px; height:44px; padding:0;
  align-items:center; justify-content:center;
  background:var(--night); border:1px solid rgba(241,228,208,.24);
  border-radius:100px; color:var(--sand); cursor:pointer;
}
.site-head__burger:focus-visible{ outline:2px solid var(--turquoise); outline-offset:2px; }
.site-head__burger-box{ position:relative; display:block; width:18px; height:12px; }
.site-head__burger-box span{
  position:absolute; left:0; right:0; height:1.5px; border-radius:2px;
  background:currentColor;
  transition:transform .24s var(--ease), opacity .16s var(--ease);
}
.site-head__burger-box span:nth-child(1){ top:0; }
.site-head__burger-box span:nth-child(2){ top:5.25px; }
.site-head__burger-box span:nth-child(3){ bottom:0; }
.site-head__burger[aria-expanded="true"] .site-head__burger-box span:nth-child(1){
  transform:translateY(5.25px) rotate(45deg);
}
.site-head__burger[aria-expanded="true"] .site-head__burger-box span:nth-child(2){ opacity:0; }
.site-head__burger[aria-expanded="true"] .site-head__burger-box span:nth-child(3){
  transform:translateY(-5.25px) rotate(-45deg);
}

@media (max-width:640px){
  .site-head__burger{ display:inline-flex; }
  .site-head__cta{ display:none; }

  /* Anchored to the header, which is fixed, so the panel follows the
     padding change when .is-stuck lands rather than detaching from it. */
  .site-head__nav{
    position:absolute; top:100%; left:0; right:0;
    flex-direction:column; align-items:stretch; gap:0;
    padding:0 0 6px;
    background:rgba(22,15,38,.97);
    backdrop-filter:blur(16px);
    border-bottom:1px solid rgba(241,228,208,.14);
    max-height:calc(100vh - 150px);
    max-height:calc(100dvh - 150px);
    overflow-y:auto;
    opacity:0; visibility:hidden; transform:translateY(-6px);
    transition:opacity .22s var(--ease), transform .22s var(--ease),
               visibility 0s linear .22s;
  }
  .site-head__nav.is-open{
    opacity:1; visibility:visible; transform:none;
    transition:opacity .22s var(--ease), transform .22s var(--ease), visibility 0s;
  }
  .site-head__nav a:not(.site-head__cta){
    display:flex; align-items:center; min-height:52px;
    padding:0 clamp(16px,5vw,28px);
    font-size:var(--fs-2xs); letter-spacing:.08em; opacity:1;
    border-top:1px solid rgba(241,228,208,.08);
  }
  .site-head__nav a[aria-current="page"]{ color:var(--apricot); }
}
@media (prefers-reduced-motion:reduce){
  .site-head__nav, .site-head__burger-box span{ transition:none; }
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex; align-items:center; gap:.5rem;
  font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  font-size:var(--fs-2xs); padding:.95rem 1.7rem; border-radius:100px;
  transition:transform .25s var(--ease), box-shadow .25s var(--ease), background .25s, color .25s;
  will-change:transform;
}
.btn:hover{ transform:translateY(-3px); }
.btn--solid{
  background:linear-gradient(100deg,var(--magenta),var(--orange));
  color:var(--night);
  box-shadow:0 10px 34px -10px rgba(232,68,122,.7);
}
.btn--solid:hover{ box-shadow:0 16px 44px -10px rgba(245,158,44,.8); }
.btn--ghost{ border:1px solid rgba(241,228,208,.4); color:var(--sand); }
.btn--ghost:hover{ border-color:var(--sand); background:rgba(241,228,208,.06); }
.btn--dark{ background:var(--night); color:var(--apricot); }
.btn--dark:hover{ background:#0d0819; }
.btn--outline{ border:1px solid rgba(22,15,38,.5); color:var(--night); }
.btn--outline:hover{ background:rgba(22,15,38,.08); }

/* Visible, unmissable placeholder for anything not yet verified.
   Nothing uses it right now — the last two markers came off About on
   29 Aug — but it is kept deliberately. It exists so unfinished copy is
   impossible to miss, which only works if the class is already there the
   next time someone drafts against a fact they do not have yet. */
.needs-fact{
  display:inline-block;
  background:repeating-linear-gradient(135deg,rgba(55,195,184,.16) 0 8px,rgba(55,195,184,.06) 8px 16px);
  border:1px dashed var(--turquoise);
  color:var(--turquoise);
  border-radius:6px; padding:.06em .45em;
  font-family:var(--body); font-style:normal; font-weight:700;
  text-transform:none; letter-spacing:.01em;
  -webkit-text-fill-color:var(--turquoise);
}

/* section titles */
.section-title{
  font-family:var(--display); font-weight:900; line-height:.92;
  text-transform:uppercase; letter-spacing:-.01em;
  /* The floor has to fit the longest single word at 320px, where the content
     box is 288px. "QUESTIONS" on the About FAQ is the longest; at the old
     2.6rem it ran 21px past the viewport and only body's overflow-x:hidden
     kept that from becoming a sideways scroll. It stops overflowing at
     2.4rem, so 2.3rem leaves a little room. Only screens under about 460px
     see the floor at all; above that 8vw is already larger. */
  font-size:clamp(2.3rem,8vw,6rem);
}
[data-skew]{ transform-origin:left center; will-change:transform; }

/* Deep links, in-page clicks and load-time hash jumps all cleared the same
   amount; without this a shared /#visit put "Find Us" under the header. */
main > section[id]{ scroll-margin-top:var(--head-clear); }

/* ============================================================
   1. HERO
   ============================================================ */
.hero{
  position:relative; min-height:100svh;
  display:flex; flex-direction:column; justify-content:center;
  padding:clamp(92px,11vh,112px) clamp(16px,5vw,64px) clamp(44px,6vh,64px);
  overflow:hidden;
  isolation:isolate;
  background:
    radial-gradient(120% 80% at 50% 120%, rgba(232,68,122,.28), transparent 60%),
    linear-gradient(180deg,var(--night) 0%, var(--dusk) 60%, #2c1b3f 100%);
}
.hero__sun{
  /* The disc is anchored to the MARK, not to the hero, so it tracks the logo at
     every breakpoint: the eyebrow wraps on mobile and the mark sits lower there,
     and no hero-anchored position cleared the type on both. */
  /* It used to sit CONCENTRIC with the mark, which made a bullseye: a hard-edged
     bright disc with the logo inside it. The eye landed on the circle and then
     had to hunt for the crocus. The disc is now pushed out to the right and
     cropped by the frame, which is a poster device rather than a target, and it
     reads as an actual setting sun instead of a circle floating in mid-air.
     Lowering it instead was tried and failed: the disc slides onto SAFFRON and
     eats the wordmark's contrast. */
  position:absolute; z-index:-1; left:50%; top:50%;
  width:165%; aspect-ratio:1; border-radius:50%;
  /* Positioned with margins rather than a transform, because GSAP drives
     translateY for the rise and would otherwise cancel the offset. Percentage
     margins resolve against the container's width, and the disc is square, so
     -50% on an axis centres it there. */
  /* Far enough right that the frame crops it and it clears the nav, the wordmark
     and the mark all at once. -18% keeps its centre just above the mark's, so it
     sits on the ridge rather than in the sky. */
  /* The horizontal offset is in vw, NOT in percent. Percentage margins resolve
     against the mark, and the mark only shrinks 260px -> 158px between desktop
     and a 375px phone while the viewport shrinks 1440 -> 375. A mark-relative
     offset therefore cannot track the frame edge it is supposed to be cropped
     by: at 175% the disc landed 88px past the right edge on a phone and the sun
     vanished entirely. vw ties the offset to the frame instead. */
  margin-left:32vw; margin-top:-18%;
  transform:translateY(26%);
  background:radial-gradient(circle at 50% 45%,
     #FFE7B0 0%, var(--apricot) 22%, var(--orange) 45%, var(--magenta) 72%, rgba(232,68,122,0) 78%);
  filter:blur(1px);
  opacity:.88;
  will-change:transform;
}
/* With the sun moved off the mark, the mark needs its own light. The artwork is
   ~68% near-black maroon on a dark violet ground, so without this it goes muddy:
   moving the disc away and not replacing the light was the one variant where the
   crocus died. Sized and blurred to read as glow, never as a second disc. */
.hero__mark::before{
  content:""; position:absolute; z-index:0;
  left:50%; top:50%;
  width:230%; aspect-ratio:1;
  margin-left:-115%; margin-top:-115%;
  border-radius:50%;
  background:radial-gradient(circle at 50% 50%,
    rgba(255,231,176,.72) 0%, rgba(245,158,44,.46) 30%,
    rgba(232,68,122,.22) 55%, rgba(232,68,122,0) 72%);
  filter:blur(30px);
  pointer-events:none;
}
/* Generated dusk photo underlay, served from assets/opt/ (source of truth stays
   at assets/gen/oasis-dusk.jpg). If the file is absent the layer is simply
   transparent and the gradient background shows through. */
.hero__photo{
  position:absolute; inset:0; z-index:-3;
  background-image:url("../assets/opt/oasis-dusk-1376.jpg");
  background-image:image-set(
    url("../assets/opt/oasis-dusk-1376.avif") type("image/avif"),
    url("../assets/opt/oasis-dusk-1376.webp") type("image/webp"),
    url("../assets/opt/oasis-dusk-1376.jpg") type("image/jpeg"));
  background-size:cover; background-position:center 70%;
  opacity:.28; mix-blend-mode:soft-light;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 45%,#000 80%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 45%,#000 80%,transparent);
}
.hero__mesh{
  position:absolute; inset:0; z-index:-4; opacity:.55;
  background:
    radial-gradient(60% 50% at 15% 20%, rgba(55,195,184,.14), transparent 60%),
    radial-gradient(50% 50% at 85% 15%, rgba(232,68,122,.16), transparent 60%);
}
.hero__layers{ position:absolute; inset:0; z-index:-1; pointer-events:none; }
.hero__mountains{
  position:absolute; left:-5%; right:-5%; bottom:0; height:34%;
  background:linear-gradient(180deg,#1a1030,#0d0819);
  clip-path:polygon(0 62%,14% 40%,26% 58%,40% 28%,54% 55%,68% 32%,82% 56%,100% 38%,100% 100%,0 100%);
  opacity:.9;
}
.hero__palm-defs{ position:absolute; width:0; height:0; }
.hero__palm{
  position:absolute; bottom:-1%;
  height:clamp(320px,64vh,760px); width:auto;
  fill:#0b0616;
  will-change:transform;
}
/* Left crown, cropped by the left edge (classic poster device) */
.hero__palm--l{ left:-9%; transform-origin:bottom left; }
/* Right crown, mirrored, cropped by the right edge */
.hero__palm--r{ right:-9%; transform:scaleX(-1); transform-origin:bottom right; }
/* Far layer: small, pushed to one side, heavily blurred and faint so it reads
   as atmospheric depth rather than a subject behind the sun. */
.hero__palm--far{
  left:3%; bottom:5%;
  height:clamp(240px,42vh,520px);
  fill:#0a0518; opacity:.22; filter:blur(6px);
}
@media (max-width:640px){
  .hero__palm{ height:clamp(240px,42vh,420px); }
  .hero__palm--l{ left:-42%; }
  .hero__palm--r{ right:-42%; }
  .hero__palm--far{ display:none; }
}
/* Narrow frames: the disc is proportionally much larger against the viewport
   here (about 69vw versus 30vw on desktop), so the desktop offset would leave
   only a faint sliver of its outer ring showing. Pull it back in for a readable
   arc, and lift it so it clears the wordmark, which starts higher up the frame
   on a phone because the sub-line wraps to two lines. */
@media (max-width:760px){
  /* -113% rather than -88%: at -88% the disc's outer ring crossed the wordmark
     by 31px vertically and 45px horizontally, landing on the "RON". Lifting it
     clears the ink outright instead of relying on the type scrim to rescue it,
     and still leaves the disc well below the header. */
  .hero__sun{ margin-left:20vw; margin-top:-113%; }
}
/* The mark leads the lockup. Sized against the viewport's SHORT axis as well
   as its width: browser zoom shrinks the CSS viewport, so a logo sized in fixed
   px stays huge while the space around it collapses and pushes the CTAs off
   screen. That was the zoom complaint. min() with svh makes it shrink first. */
/* Shrink-wrapped to the logo, so the sun's percentage sizing is relative to
   the mark and not to the full-width text column. */
.hero__mark{
  display:block; width:fit-content;
  margin:0 auto clamp(10px,1.6vh,22px);
  position:relative; isolation:isolate;
}
.hero__mark img{
  width:max(64px, min(260px, 42vw, 30svh));
  height:auto; margin-inline:auto;
  /* The artwork is ~68% near-black maroon against a dark violet ground, so the
     mark needs light behind it rather than a recolour. That light is the sun. */
  filter:drop-shadow(0 6px 22px rgba(0,0,0,.55));
  position:relative; z-index:1;
}
.foot__mark{ width:56px; height:auto; }
.hero__type{
  position:relative; z-index:3; max-width:var(--maxw); margin:0 auto; width:100%;
  text-align:center;
}
/* Soft poster scrim so type separates from the sun disc at every point of its travel */
.hero__type::before{
  content:""; position:absolute; z-index:-1;
  left:-6%; right:-6%; top:-8%; bottom:-10%;
  background:radial-gradient(70% 80% at 50% 50%, rgba(11,6,22,.62), rgba(11,6,22,.28) 55%, transparent 78%);
  filter:blur(12px); pointer-events:none;
}
.hero__stack{
  /* The wordmark is the logo now, so it is set in the logo face and follows the
     lockup: SAFFRON in wide-tracked caps over Restaurant & Lounge in small caps.
     Casing is per line, because Cinzel renders lowercase as small caps and a
     blanket uppercase would flatten the second line's larger initial R and L. */
  font-family:var(--logo); font-weight:600; line-height:1.04; text-transform:none;
  text-shadow:0 2px 30px rgba(11,6,22,.55), 0 1px 3px rgba(11,6,22,.6);
}
.hero__line{ display:block; overflow:hidden; }
.hero__line--big{ margin-bottom:.10em; }
.hero__reveal{
  display:block; transform:translateY(110%);
}
.hero__line--big .hero__reveal{
  /* SAFFRON: caps, wide tracking, flat gold, exactly as the lockup sets it.
     Cinzel is narrower than Archivo Expanded but .24em of tracking gives most of
     the width back, so this sits under the old figure to keep Reserve above the
     fold on a 1280x900 laptop. */
  font-size:clamp(2rem,6.4vw,4.8rem); letter-spacing:.24em;
  text-transform:uppercase; color:var(--gold);
  margin-right:-.24em;   /* discount the trailing letter-space when centring */
}
.hero__line:not(.hero__line--big) .hero__reveal{
  /* Typed in mixed case on purpose: Cinzel maps lowercase to small caps, which is
     what gives the lockup its larger initial R and L. Never uppercase this.
     Burnt is the lockup colour, but it measures 3.30:1 against the lightest part
     of the hero (the magenta radial over #2c1b3f). That clears AA for large text
     and not for normal text, so the floor is 24px, not the old 17.6px. Do not
     lower it without changing the colour. */
  font-size:clamp(1.5rem,3.2vw,2.2rem); letter-spacing:.05em; color:var(--burnt);
  text-transform:none; margin-right:0;
}
/* At 375px the line measures 343px inside a 338px clip box, so overflow:hidden
   eats the end of LOUNGE. The floor cannot drop below 24px without failing
   contrast, so the tracking gives way instead. */
@media (max-width:480px){
  .hero__line:not(.hero__line--big) .hero__reveal{ letter-spacing:.01em; }
  .hero__line--big .hero__reveal{ letter-spacing:.18em; margin-right:-.18em; }
}
.hero__sub{
  margin-top:1.1rem; max-width:36ch; margin-inline:auto;
  font-size:clamp(1.125rem,1.6vw,1.4rem); color:var(--sand); opacity:.95;
  /* Without this the line broke as "...Uptown Design / District." and left the
     last word stranded on its own. Balance evens the two lines instead. */
  text-wrap:balance;
  text-shadow:0 1px 12px rgba(11,6,22,.85), 0 1px 2px rgba(11,6,22,.9);
}
.hero__cta{ margin-top:1.5rem; display:flex; justify-content:center; gap:1rem; flex-wrap:wrap; }

/* Browser zoom shrinks the CSS viewport rather than the layout, so at 200-400%
   a hero pinned to 100svh has no room left for the type and the buttons fall
   below the fold with a full-height section above them. Under a short viewport
   the hero stops filling the screen and simply flows. WCAG 1.4.10. */
@media (max-height:560px){
  .hero{ min-height:auto; padding-top:92px; }
}

/* ============================================================
   2. MARQUEES
   ============================================================ */
.marquees{ background:var(--night); padding:0; border-block:1px solid rgba(241,228,208,.08); }
/* Pause control for the scrolling strips (WCAG 2.2.2). */
.marquees{ position:relative; }
.motion-toggle{
  position:absolute; right:clamp(10px,3vw,22px); top:-42px; z-index:5;
  display:inline-flex; align-items:center; gap:.5rem;
  padding:.4rem .8rem; border-radius:100px;
  background:rgba(22,15,38,.72); backdrop-filter:blur(8px);
  border:1px solid rgba(241,228,208,.22);
  color:var(--sand); font-family:var(--body);
  font-size:var(--fs-3xs); font-weight:600; letter-spacing:.16em; text-transform:uppercase;
  cursor:pointer; opacity:.7; transition:opacity .25s var(--ease), border-color .25s var(--ease);
}
.motion-toggle:hover, .motion-toggle:focus-visible{ opacity:1; border-color:var(--turquoise); }
.motion-toggle__icon{
  width:8px; height:9px;
  border-left:3px solid currentColor; border-right:3px solid currentColor;
}
[data-motion="paused"] .motion-toggle__icon{
  width:0; height:0;
  border:5px solid transparent; border-left:8px solid currentColor; border-right:0;
}
@media (max-width:620px){ .motion-toggle__text{ display:none; } }

.marquee{ overflow:hidden; white-space:nowrap; padding:.7rem 0; }
.marquee--alt{ background:linear-gradient(90deg,var(--magenta),var(--orange)); }
.marquee__track{ display:inline-flex; will-change:transform; }
.marquee__track span{
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  font-size:clamp(1.1rem,2.4vw,1.9rem); letter-spacing:.02em;
  padding-right:.4em; color:var(--sand);
}
.marquee--alt .marquee__track span{ color:var(--night); }
.marquee--foot .marquee__track span{ color:var(--burnt); }

/* ============================================================
   3. MANIFESTO
   ============================================================ */
.manifesto{
  background:linear-gradient(180deg,#2c1b3f,var(--dusk));
  padding:clamp(80px,14vh,180px) clamp(16px,5vw,64px);
}
/* The room, as a band under the copy rather than behind it. A true
   background was measured and rejected: the bold words are gradient-filled
   to magenta (#E8447A, luminance .23), and any photo bright enough to read
   drops them to 1.67:1 against a 3:1 large-text minimum. Below the type
   there is nothing to contrast against, so the photo runs at full strength. */
.manifesto__room{
  position:relative; margin:clamp(48px,7vw,90px) calc(-1 * clamp(16px,5vw,64px)) calc(-1 * clamp(80px,14vh,180px));
  height:clamp(220px,34vh,380px); overflow:hidden;
}
.manifesto__room img{
  width:100%; height:100%; object-fit:cover; object-position:center 34%;
}
/* Feather the top edge so the band grows out of the section instead of
   sitting on it as a pasted-in rectangle. */
.manifesto__room::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(180deg,var(--dusk) 0%,rgba(34,22,53,.55) 22%,rgba(34,22,53,.08) 55%,rgba(34,22,53,.30) 100%);
}
.manifesto__inner{ max-width:1100px; margin:0 auto; }
.manifesto__kicker{
  font-family:var(--serif); font-style:italic; font-size:clamp(1.3rem,2.4vw,1.8rem);
  color:var(--apricot); margin-bottom:2rem;
}
.manifesto__big{
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  font-size:clamp(2rem,7vw,5.4rem); line-height:1.02; letter-spacing:-.01em;
}
.manifesto__big .mline{ display:block; overflow:hidden; }
.manifesto__big b{
  font-weight:900;
  background:linear-gradient(92deg,var(--orange),var(--magenta));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; color:transparent;
}
.manifesto__note{
  margin-top:2.4rem; max-width:56ch; font-size:clamp(1.125rem,1.4vw,1.25rem);
  color:var(--sand); opacity:.82;
}

/* ============================================================
   4. DISH RUNWAY
   ============================================================ */
.runway{ --runway-pad-top:clamp(60px,10vh,110px);
  background:var(--dusk); padding:var(--runway-pad-top) 0 0; overflow:hidden; }
.runway__head{
  display:flex; align-items:flex-end; justify-content:space-between; gap:1rem;
  padding:0 clamp(16px,5vw,64px); margin-bottom:2.4rem;
}
.runway__hint{ font-size:var(--fs-2xs); letter-spacing:.2em; text-transform:uppercase; color:var(--apricot); white-space:nowrap; }
/* Hide the hint without taking it out of the row. Removing it lets the heading
   claim the full width and unwrap from two lines to one, which changes the
   height the card is sized against — and the card's width is what decides
   whether the hint is needed at all, so the two chase each other and settle
   somewhere different on every load. visibility keeps the geometry still and
   still takes it out of the accessibility tree. */
.runway__hint[hidden]{ display:block; visibility:hidden; }
.runway__viewport{ width:100%; }
.runway__track{
  --runway-pad-bottom:clamp(60px,9vh,110px);
  display:flex; gap:clamp(20px,3vw,44px);
  padding:0 clamp(16px,5vw,64px) var(--runway-pad-bottom);
  will-change:transform;
}
.dish{
  position:relative; flex:0 0 auto;
  /* 4/5 closely matches the source portrait ratio, so photos crop minimally
     and each plated dish stays recognizable */
  width:clamp(280px,80vw,520px); aspect-ratio:4/5;
  border-radius:18px; overflow:hidden;
  background:#160f26;
}
/* ScrollTrigger pins .runway at "top top", so anything taller than the viewport
   sits below the fold for the whole pin — and the part that overflows is the
   bottom of the card, where the dish name and description live. A 650px card
   plus the heading and padding wants ~930px, which a 1440x900 display does not
   have once browser chrome is subtracted.

   So the card takes what the rest of the section leaves. Everything above and
   below it is subtracted exactly rather than guessed: the two vh paddings are
   repeated verbatim from .runway and .runway__track, and the heading reports
   its own height, because it is not a constant — "THE SIGNATURES" sits on one
   line at 1440 and wraps to two at 1280, so a hardcoded 88px clips the narrower
   window and a hardcoded 177px shrinks the wider one for nothing.

   --runway-head is published by buildRunway() in main.js, which re-runs once
   the webfont lands because the heading can wrap differently before and after.
   The fallback is the two-line height: nothing here can clip without JS, since
   the pin that causes the clipping is itself GSAP, but the safe direction is
   the larger number. The 8px is slack against sub-pixel rounding, which would
   otherwise land the card's bottom edge exactly on the fold.

   Carousel widths below 760px are untouched — nothing is pinned there. */
@media not all and (max-width:760px){
  .dish{
    height:min(650px, calc(100vh - var(--runway-head,177px) - 2.4rem - 8px
                           - var(--runway-pad-top) - var(--runway-pad-bottom)));
    height:min(650px, calc(100svh - var(--runway-head,177px) - 2.4rem - 8px
                           - var(--runway-pad-top) - var(--runway-pad-bottom)));
    width:auto;
  }
}
/* Sizing the card off the viewport alone costs the runway its horizontal
   scroll on a 1440x800 window: the card lands ~17px short of the width where
   three of them overflow, so the pin never engages and the most common laptop
   size loses the motion the section is built around. On short windows take
   that height out of the section's padding rather than the photograph. Taller
   viewports keep the padding they were designed with. */
@media not all and (max-width:760px) and (max-height:900px){
  .runway{ --runway-pad-top:clamp(40px,6vh,110px); }
  .runway__track{ --runway-pad-bottom:clamp(40px,5vh,110px); }
}

.dish--type{
  display:flex; flex-direction:column; justify-content:center;
  padding:clamp(24px,4vw,48px);
  background:
    radial-gradient(120% 90% at 20% 0%, rgba(245,158,44,.18), transparent 55%),
    linear-gradient(160deg,#2a1b40,#160f26);
  border:1px solid rgba(241,228,208,.1);
}
.dish__media{ position:absolute; inset:0; z-index:0; overflow:hidden; }
.dish__media::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(180deg,transparent 40%, rgba(22,15,38,.85));
  transition:opacity .4s var(--ease); opacity:.9;
}
.dish__media img{
  width:100%; height:100%; object-fit:cover;
  transition:transform .6s var(--ease), filter .6s var(--ease);
  will-change:transform;
}
.dish__meta{
  position:absolute; z-index:2; left:0; right:0; bottom:0;
  padding:clamp(18px,3vw,32px);
}
/* Name then description, no price. The runway is a taste of the kitchen, not a
   price list; the menu page carries the numbers and is one tap away. The
   .dish__price rule stays for the .dish--type variant, which nothing renders
   today but which a photoless fourth dish would need. */
.dish__meta{ display:flex; flex-direction:column; align-items:flex-start; }
.dish__meta h3{ font-family:var(--display); font-weight:800; text-transform:uppercase; font-size:clamp(1.3rem,2.6vw,2rem); line-height:1; }
.dish__meta .dish__price{ margin-top:.45rem; }
.dish__meta p{ margin-top:.5rem; font-size:var(--fs-sm); color:var(--apricot); opacity:.92; }
.dish--photo{ box-shadow:0 24px 60px -24px rgba(0,0,0,.7); transition:box-shadow .4s var(--ease), transform .4s var(--ease); }
.dish--photo:hover{ box-shadow:0 30px 80px -20px rgba(232,68,122,.55); }
.dish--photo:hover .dish__media img{ transform:scale(1.08) skewX(-1.4deg); filter:saturate(1.15); }
.dish--photo:hover .dish__media::after{ opacity:.7; background:linear-gradient(180deg,rgba(232,68,122,.28) 0%, rgba(22,15,38,.85)); }
.dish__typeface h3{ font-family:var(--display); font-weight:900; text-transform:uppercase; font-size:clamp(2rem,4.4vw,3.2rem); line-height:.94; }
.dish__desc{ margin-top:1.2rem; font-size:var(--fs-base); color:var(--sand); opacity:.82; }
.dish__price{ margin-top:1.6rem; display:inline-block; font-family:var(--display); font-weight:900; font-size:clamp(1.35rem,2.2vw,1.75rem); color:var(--orange); }

/* mobile carousel fallback */
.runway__viewport.is-carousel{ overflow-x:auto; -webkit-overflow-scrolling:touch; scroll-snap-type:x mandatory; }
.runway__viewport.is-carousel .runway__track{ transform:none !important; }
.runway__viewport.is-carousel .dish{ scroll-snap-align:center; }
/* hide the horizontal scrollbar on the carousel so no gray/white strip shows */
.runway__viewport.is-carousel,
.runway__viewport{ scrollbar-width:none; -ms-overflow-style:none; }
.runway__viewport::-webkit-scrollbar{ width:0; height:0; display:none; }

/* ============================================================
   5. FULL MENU
   ============================================================ */
.menu{
  background:var(--sand); color:var(--sand-ink);
  padding:clamp(70px,11vh,130px) clamp(16px,5vw,64px);
}
.menu__head{ max-width:var(--maxw); margin:0 auto 1.4rem; }
.menu__eyebrow{ font-family:var(--serif); font-style:italic; font-size:clamp(1.25rem,2vw,1.5rem); color:var(--burnt-ink); }
.menu__title{
  font-family:var(--display); font-weight:900; text-transform:uppercase;
  font-size:clamp(3rem,11vw,8rem); line-height:.9; letter-spacing:-.02em;
  color:var(--sand-ink);
}
/* Full-width opaque bar: menu text must never be readable through it. */
.menu__nav{
  position:sticky; top:66px; z-index:20;
  display:flex; flex-wrap:wrap; gap:.5rem; align-items:center;
  max-width:var(--maxw); margin:0 auto 2.6rem;
  padding:.7rem clamp(12px,2vw,20px);
  background:rgba(241,228,208,.96); backdrop-filter:blur(10px);
  border-bottom:1px solid rgba(42,29,18,.16);
  box-shadow:0 10px 22px -16px rgba(42,29,18,.55);
}
.menu__nav a{
  padding:.5rem 1.1rem; border-radius:100px; font-weight:700;
  font-size:var(--fs-2xs); letter-spacing:.06em; text-transform:uppercase;
  color:var(--sand-ink); transition:background .2s, color .2s;
}
.menu__nav a:hover{ background:rgba(42,29,18,.12); color:var(--sand-ink); }
.menu__nav a.is-active{ background:var(--sand-ink); color:var(--sand); }
.menu__nav a.is-active:hover{ background:var(--sand-ink); color:var(--sand); }
/* Poster bill: every category is a full-width block in vertical sequence;
   items flow in two balanced CSS columns within the block. */
.menu__body{ max-width:var(--maxw); margin:0 auto; }
.menu__group{ scroll-margin-top:144px; margin-bottom:clamp(48px,7vw,96px); }
.menu__group:last-child{ margin-bottom:0; }
.menu__list{ columns:2; column-gap:clamp(30px,4vw,70px); }
.menu-item{ break-inside:avoid; margin-bottom:1.6rem; }
/* Single column on small phones (375px must be one column; 562px keeps two). */
@media (max-width:540px){ .menu__list{ columns:1; } }
/* At 15px the four category chips no longer fit one phone row, and a wrapped
   sticky bar stacked on the header took ~180px of an 812px viewport. One row
   that scrolls sideways instead: the bar stays one line at every width. */
@media (max-width:640px){
  .menu__nav{
    flex-wrap:nowrap; overflow-x:auto; scroll-snap-type:x proximity;
    scrollbar-width:none; -ms-overflow-style:none;
  }
  .menu__nav::-webkit-scrollbar{ height:0; display:none; }
  .menu__nav a{ flex:0 0 auto; scroll-snap-align:start; }
  /* With the scrollbar hidden there was nothing to say the row scrolled, and
     at 375px "Desserts" began 21px from the edge — close enough that only its
     padding showed, so the fourth category read as absent rather than as
     off-screen. Tightening the chips buys back ~38px, enough for letters to
     be cut mid-word, which is what actually signals more to the right.
     Fitting all four instead would need 12px type: their combined labels run
     295px against 290px of room even with no padding at all, and this menu is
     read by guests who asked us to keep the type large. */
  .menu__nav a{ padding-left:.8rem; padding-right:.8rem; }
  /* Scrolling shadow. The cover layer is anchored to the content (local) and
     the shadow to the scrollport (scroll), so the cover slides over the
     shadow exactly as the last chip arrives: the edge fades only while there
     is something left to reach. The opaque base stays underneath — menu text
     must never be readable through this bar. */
  .menu__nav{
    background:
      linear-gradient(to left, rgba(241,228,208,1), rgba(241,228,208,0))
        right center / 46px 100% no-repeat local,
      linear-gradient(to left, rgba(42,29,18,.30), rgba(42,29,18,0))
        right center / 46px 100% no-repeat scroll,
      rgba(241,228,208,.96);
  }
}
.menu__cat{
  font-family:var(--display); font-weight:900; text-transform:uppercase;
  font-size:clamp(1.8rem,4vw,3rem); line-height:1;
  padding-bottom:.6rem; margin-bottom:1.4rem;
  border-bottom:2px solid rgba(42,29,18,.2);
}
.menu__catnote{ font-family:var(--serif); font-style:italic; font-size:var(--fs-md); color:var(--burnt-ink); margin:-.8rem 0 1.4rem; }
.menu-item__top{ display:flex; align-items:baseline; gap:.5rem; }
.menu-item__name{ font-weight:700; font-size:var(--fs-md); letter-spacing:.01em; }
.menu-item__dots{ flex:1; border-bottom:1.5px dotted rgba(42,29,18,.35); transform:translateY(-3px); }
.menu-item__price{ font-family:var(--display); font-weight:800; color:var(--burnt-ink); font-size:var(--fs-md); }
.menu-item__desc{ margin-top:.3rem; font-size:var(--fs-sm); color:rgba(42,29,18,.78); max-width:52ch; }
/* ---- homepage preview ------------------------------------------------------
   The homepage shows two plates per category; the full 28-item bill lives on
   /menu. With four short groups the full-page group rhythm leaves the section
   looking stretched, so tighten it here only. */
.menu__preface{
  font-family:var(--serif); font-style:italic;
  font-size:clamp(1.05rem,1.7vw,1.3rem); line-height:1.5;
  color:var(--burnt-ink); max-width:52ch; margin:.9rem 0 0;
}
.menu__preface a{ color:var(--burnt-ink); text-decoration:underline; text-underline-offset:3px; }
.menu__preface a:hover{ text-decoration-thickness:2px; }
.menu--preview .menu__group{ margin-bottom:clamp(30px,4vw,52px); }
.menu--preview .menu__cat{ font-size:clamp(1.5rem,3vw,2.2rem); margin-bottom:1.1rem; }

/* Sends the homepage section on to the full menu page, which is where the
   list is canonical and where menu searches should land. */
/* The preview's one way through to the full menu, so it is sized and placed
   as the section's closing action rather than as a link that happens to be
   last. Right-aligned at both breakpoints: it ends the section, and on a
   phone the right edge is where a thumb already is. The section's own bottom
   padding (11vh, 89px on a 812px phone) clears the 76px dock, so the button
   never comes to rest underneath it. */
.menu__more{
  max-width:var(--maxw); margin:clamp(36px,5vw,56px) auto 0;
  display:flex; justify-content:flex-end;
}
.menu__more .btn{
  font-size:var(--fs-xs);
  padding:1.15rem 2.4rem;
}
.tags{ display:inline-flex; flex-wrap:wrap; gap:.4rem; margin-top:.55rem; }
.tag{
  font-size:var(--fs-3xs); font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  padding:.22rem .6rem; border-radius:100px;
  background:rgba(138,74,18,.12); color:var(--burnt-ink); border:1px solid rgba(138,74,18,.35);
}

/* ============================================================
   6. NEON LOUNGE
   ============================================================ */
.lounge{
  background:linear-gradient(180deg,#0d0819,var(--night));
  padding:clamp(80px,13vh,160px) clamp(16px,5vw,64px);
  display:grid; gap:clamp(40px,6vw,80px);
  grid-template-columns:1fr; align-items:center;
}
@media(min-width:900px){ .lounge{ grid-template-columns:1fr 1.05fr; } }
.lounge__pre{ font-family:var(--serif); font-style:italic; font-size:var(--fs-xl); color:var(--apricot); }
.neon{
  font-family:var(--display); font-weight:900; text-transform:uppercase;
  font-size:clamp(2.8rem,9vw,6.5rem); line-height:.9; letter-spacing:.01em;
  color:#2a1b40;
  -webkit-text-stroke:2px var(--magenta);
  text-shadow:
    0 0 6px rgba(232,68,122,.9), 0 0 18px rgba(232,68,122,.7),
    0 0 40px rgba(245,158,44,.5), 0 0 80px rgba(245,158,44,.35);
  animation:flicker 5s infinite;
}
@keyframes flicker{
  0%,19%,21%,23%,80%,100%{ opacity:1; }
  20%,22%{ opacity:.55; }
  81%{ opacity:.7; }
}
.lounge__time{ margin-top:1.4rem; font-family:var(--display); font-weight:800; text-transform:uppercase; letter-spacing:.05em; font-size:clamp(1.15rem,2.2vw,1.5rem); color:var(--turquoise); }
.lounge__copy{ margin-top:1rem; max-width:42ch; color:var(--sand); opacity:.85; }
.lounge__link{ margin-top:1.1rem; }
.lounge__link a{
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  letter-spacing:.08em; font-size:var(--fs-2xs); color:var(--apricot);
  border-bottom:1px solid rgba(255,201,126,.45); padding-bottom:2px;
}
.lounge__link a:hover{ color:var(--sand); border-bottom-color:var(--sand); }
.lounge__arches{ display:grid; grid-template-columns:repeat(2,1fr); gap:clamp(14px,2.5vw,26px); }
/* Square, not arched. border-image does not follow border-radius, so the old
   1000px top radius clipped the photograph into an arch while the gold frame
   around it still drew as a rectangle: every shot lost its top corners to two
   dark wedges inside a square outline. The picture now fills the frame. */
.arch{
  position:relative; margin:0; overflow:hidden;
  aspect-ratio:3/4;
  border:3px solid;
  border-image:linear-gradient(160deg,#fce8c9,#c99a5f,#fce8c9) 1;
  background:#0d0819;
  box-shadow:0 18px 50px -18px rgba(232,68,122,.5);
}
.arch img{ width:100%; height:100%; object-fit:cover; transition:transform .6s var(--ease); }
.arch:hover img{ transform:scale(1.06); }
.arch figcaption{
  position:absolute; left:0; right:0; bottom:0; padding:1.4rem 1rem .9rem; text-align:center;
  font-family:var(--display); font-weight:800; text-transform:uppercase; letter-spacing:.08em;
  font-size:var(--fs-xs); color:var(--sand);
  background:linear-gradient(180deg,transparent,rgba(13,8,25,.85));
}

/* ============================================================
   7. THE SCENE
   ============================================================ */
.scene{
  position:relative; display:grid; grid-template-columns:1fr; align-items:stretch;
  background:var(--night);
}
@media(min-width:900px){ .scene{ grid-template-columns:1.2fr .8fr; } }
.scene__media{ position:relative; min-height:60vh; overflow:hidden; }
.scene__shot{ margin:0; }
.scene__shot--lead{ position:absolute; inset:0; }
/* Bias the crop up: centred, the frame cuts the canvas on the back wall in half. */
.scene__shot--lead img{ object-position:center 32%; }
.scene__media img{
  width:100%; height:100%; object-fit:cover;
  filter:grayscale(1) contrast(1.05) sepia(.5) hue-rotate(230deg) saturate(2.4) brightness(.7);
  transition:filter .8s var(--ease), transform .8s var(--ease);
  transform:scale(1.05);
}
.scene__media.is-live img,
.scene__media:hover img{ filter:none; transform:scale(1); }

.scene__media::after{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(200deg,rgba(232,68,122,.25),transparent 60%);
  mix-blend-mode:screen; pointer-events:none;
}
.scene__copy{
  display:flex; flex-direction:column; justify-content:center; gap:1.2rem;
  padding:clamp(48px,8vw,96px) clamp(16px,5vw,64px);
  background:linear-gradient(180deg,var(--dusk),#0d0819);
}
.scene__copy p{ max-width:44ch; color:var(--sand); opacity:.85; }
.scene__note{ font-family:var(--serif); font-style:italic; color:var(--apricot) !important; opacity:1 !important; font-size:var(--fs-md); }

/* ============================================================
   8. VISIT
   ============================================================ */
.visit{
  background:linear-gradient(180deg,#0d0819,var(--dusk));
  padding:clamp(70px,11vh,150px) clamp(16px,5vw,64px);
  display:grid; gap:clamp(36px,5vw,72px); grid-template-columns:1fr; align-items:start;
}
@media(min-width:900px){ .visit{ grid-template-columns:1.3fr .7fr; } }
.visit__title{
  font-family:var(--display); font-weight:900; text-transform:uppercase;
  font-size:clamp(3rem,10vw,7rem); line-height:.88; letter-spacing:-.02em;
  margin-bottom:2rem;
  background:linear-gradient(92deg,var(--apricot),var(--orange),var(--magenta));
  -webkit-background-clip:text; background-clip:text; -webkit-text-fill-color:transparent; color:transparent;
}
.visit__facts{ display:grid; grid-template-columns:repeat(2,1fr); gap:1.6rem 2rem; }
@media(max-width:520px){ .visit__facts{ grid-template-columns:1fr; } }
.fact dt{ font-family:var(--display); font-weight:800; text-transform:uppercase; letter-spacing:.1em; font-size:var(--fs-3xs); color:var(--turquoise); margin-bottom:.35rem; }
.fact dd{ font-size:var(--fs-base); color:var(--sand); line-height:1.5; }
/* Orientation under the address rather than a line of its own: the landmark
   is only useful at the moment you read the street number, and it is the
   passage an assistant quotes when asked where Saffron is. Set back from the
   address so the address still reads first. */
.fact__note{ display:inline-block; margin-top:.4rem; font-size:var(--fs-sm); color:var(--sand); opacity:.72; }
.fact a{ color:var(--apricot); border-bottom:1px solid rgba(255,201,126,.35); transition:border-color .2s, color .2s; }
.fact a:hover{ color:var(--sand); border-color:var(--sand); }
.visit__card{
  position:relative; overflow:hidden;
  padding:clamp(28px,4vw,44px); border-radius:22px;
  background:linear-gradient(150deg,var(--magenta),var(--orange) 65%,var(--apricot));
  color:var(--night);
  box-shadow:0 24px 70px -24px rgba(232,68,122,.6);
}
.visit__card-pre{ font-family:var(--display); font-weight:800; text-transform:uppercase; letter-spacing:.14em; font-size:var(--fs-3xs); }
.visit__card-line{ font-family:var(--serif); font-style:italic; font-size:clamp(1.5rem,3vw,2.1rem); line-height:1.15; margin:.8rem 0 1.6rem; }
.visit__card .btn{ width:100%; justify-content:center; margin-bottom:.7rem; }

/* ============================================================
   9. FOOTER
   ============================================================ */
.foot{ background:var(--night); border-top:1px solid rgba(241,228,208,.1); }
.marquee--foot{ padding:1.1rem 0; border-bottom:1px solid rgba(241,228,208,.08); }
.foot__inner{
  max-width:var(--maxw); margin:0 auto;
  display:flex; flex-wrap:wrap; gap:2rem; justify-content:space-between; align-items:center;
  padding:clamp(40px,6vw,72px) clamp(16px,5vw,64px);
}
.foot__brand{ display:flex; align-items:center; gap:16px; }
.foot__lock{ display:flex; flex-direction:column; line-height:1.1; }
/* The footer lockup is the logo, so all three lines take the logo face. The sub
   and cuisine lines stay in mixed case: Cinzel renders lowercase as small caps,
   which is what the lockup actually shows. Uppercasing them would flatten it. */
.foot__word{ font-family:var(--logo); font-weight:600; letter-spacing:.20em; text-transform:uppercase; color:var(--gold); font-size:var(--fs-lg); margin-right:-.20em; }
.foot__sub{ font-family:var(--logo); font-weight:600; text-transform:none; letter-spacing:.06em; color:var(--burnt); font-size:var(--fs-xs); margin-top:.24rem; }
/* Was opacity .8, which dropped orange to 6.1:1 against the footer and is wasted
   subtlety on a 14px line. Full strength, and one step up in size. */
.foot__cuisine{ font-family:var(--logo); font-weight:600; font-style:normal; letter-spacing:.09em; color:var(--orange); font-size:var(--fs-2xs); }
.foot__meta{ text-align:right; font-size:var(--fs-xs); color:var(--sand); opacity:.85; }
.foot__meta a{ color:var(--apricot); }
.foot__meta a:hover{ color:var(--sand); }
.foot__copy{ margin-top:.4rem; opacity:.6; font-size:var(--fs-2xs); }
@media(max-width:640px){ .foot__meta{ text-align:left; } }

/* ============================================================
   10. MOBILE DOCK + TOUCH TARGETS
   Under 640px the dock carries Reserve, which left the header free for
   navigation. It is shown by default and tucked away only while the
   hero's own Reserve button is on screen, so the two never stack up as
   competing calls to action. Default-shown rather than default-hidden
   is deliberate: without JS the dock is the only route to the menu,
   events and the phone once the header collapses to a toggle.
   ============================================================ */
.dock{
  position:fixed; z-index:60; left:0; right:0; bottom:0;
  display:none; gap:6px;
  padding:8px 10px calc(8px + env(safe-area-inset-bottom));
  background:rgba(22,15,38,.86); backdrop-filter:blur(14px);
  border-top:1px solid rgba(241,228,208,.14);
  transition:transform .32s var(--ease), visibility 0s;
}
/* visibility, not transform alone: a dock parked off-screen is still in
   the tab order, and a keyboard user should not fall into it. */
.dock.is-tucked{
  transform:translateY(110%); visibility:hidden;
  transition:transform .32s var(--ease), visibility 0s linear .32s;
}
.dock__item{
  flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  min-height:52px; padding:6px 4px; border-radius:12px;
  font-family:var(--body); font-size:var(--fs-3xs); font-weight:600;
  letter-spacing:.08em; text-transform:uppercase; color:var(--sand);
  border:1px solid transparent;
}
.dock__item .dock__icon svg{ width:20px; height:20px; display:block; }
.dock__item--go{ background:var(--sand); color:var(--sand-ink); }
.dock__item:active{ border-color:var(--turquoise); }
.dock__item:focus-visible{ outline:2px solid var(--turquoise); outline-offset:2px; }

/* Four items instead of three. "Reserve" and "Events" are the long labels and
   clear their boxes by about 2px at 320px, which is close enough that a font
   fallback would wrap the dock onto two rows. Easing the tracking buys ~4px
   back on the longest label without touching the 14px type floor. */
@media (max-width:400px){
  .dock__item{ letter-spacing:.04em; padding-inline:2px; }
}

@media (max-width:640px){
  .dock{ display:flex; }
  /* clear the dock so the footer's last line is never trapped behind it */
  body{ padding-bottom:calc(88px + env(safe-area-inset-bottom)); }

  /* 44px minimum on every touch target. The contact links were 18-21px tall —
     the ones someone taps standing on Palm Canyon Dr. */
  .fact a, .foot__meta a{ display:inline-flex; align-items:center; min-height:44px; }
  /* .site-head__cta is not in this list: under 640px Reserve is the dock's
     job, and re-declaring display here would override the rule that hides it. */
  .menu__nav a{ min-height:44px; display:inline-flex; align-items:center; }
  .site-head__brand{ min-height:44px; }
  .motion-toggle{ min-height:44px; min-width:44px; justify-content:center; }
}

/* the horizontal dish list is a scroll region: reachable and visible to keyboards */
.runway__viewport:focus-visible{
  outline:2px solid var(--turquoise); outline-offset:-4px; border-radius:12px;
}

/* At mobile the section title and the swipe cue no longer fit one row;
   without wrapping, the cue is pushed clean outside the viewport. */
@media (max-width:760px){
  .runway__head{ flex-wrap:wrap; align-items:flex-start; gap:.5rem; }
  .runway__hint{ white-space:normal; }
}

/* ============================================================
   Scroll reveal helpers
   ============================================================ */
[data-reveal-line]{ opacity:0; transform:translateY(40px); }
[data-reveal-line].is-in{ opacity:1; transform:none; transition:opacity .8s var(--ease), transform .8s var(--ease); }

/* ============================================================
   Reduced motion / no-JS fallbacks
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important; scroll-behavior:auto !important; }
  .grain{ display:none; }
  .hero__reveal{ transform:none !important; }
  .neon{ animation:none; }
  [data-reveal-line]{ opacity:1 !important; transform:none !important; }
  .runway__track{ transform:none !important; }
  .runway__viewport{ overflow-x:auto; scroll-snap-type:x mandatory; }
  .dish{ scroll-snap-align:center; }
  .scene__media img{ filter:none; transform:none; }
}

/* JS enables the hidden hero reveal; ensure visible without JS */
.no-js .hero__reveal{ transform:none; }
.no-js [data-reveal-line]{ opacity:1; transform:none; }

/* Assistive-only text: five links leave the site with no announcement. */
.vh{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

/* The Persian in the marquees is language, not ornament. Archivo carries no
   Arabic glyphs, so name faces that do rather than leaving it to the default. */
.marquee__track span [lang="fa"],
[lang="fa"]{
  font-family:"Vazirmatn","Noto Naskh Arabic","Geeza Pro","Tahoma",serif;
  font-weight:600;
}

/* ============================================================
   RESERVE DIALOG
   The OpenTable widget, summoned in place instead of handing the
   diner to opentable.com. Their iframe is a fixed 224px white card
   we cannot restyle, so it sits on a sand plinth inside our own
   panel: a light card floating over a dimmed page reads as a
   deliberate overlay, where the same card dropped into the page
   flow would read as a hole in the layout.
   Built and shown by main.js; without JS the links behave as before.
   ============================================================ */
.rsv{
  position:fixed; inset:0; z-index:160;   /* over the header (130), under the skip link (200) */
  display:grid; place-items:center; padding:20px;
  overscroll-behavior:contain;            /* a scroll that reaches the end stops here */
}
.rsv[hidden]{ display:none; }

.rsv__scrim{ position:absolute; inset:0; background:rgba(11,6,22,.80); opacity:0; transition:opacity .28s var(--ease); }
.rsv.is-open .rsv__scrim{ opacity:1; }
@supports (backdrop-filter:blur(3px)){ .rsv__scrim{ backdrop-filter:blur(3px); } }

.rsv__panel{
  position:relative; width:min(420px,100%);
  /* A column so the booking frame can take whatever height is left over
     after the bar and the footnote, rather than the stylesheet guessing at
     how tall those are and leaving the panel hanging off a short phone. */
  display:flex; flex-direction:column;
  height:min(760px, calc(100vh - 40px));
  height:min(760px, calc(100dvh - 40px));
  background:linear-gradient(168deg,var(--dusk),#180F2B);
  border:1px solid rgba(241,228,208,.16);
  border-radius:20px; padding:16px 16px 14px;
  box-shadow:0 30px 70px rgba(0,0,0,.6);
  opacity:0; transform:translateY(12px) scale(.985);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
.rsv.is-open .rsv__panel{ opacity:1; transform:none; }

.rsv__bar{ flex:none; display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:14px; }
.rsv__title{
  margin:0; font-family:var(--display); font-weight:700; text-transform:uppercase;
  font-size:var(--fs-3xs); letter-spacing:.14em; color:var(--gold);
}
/* 44px hit area, sized from the icon out so the glyph stays optically centred */
.rsv__close{
  flex:none; width:44px; height:44px; margin:-10px -10px -10px 0;
  display:grid; place-items:center;
  background:none; border:0; border-radius:50%; cursor:pointer;
  color:var(--sand); font-size:1.5rem; line-height:1;
}
.rsv__close:hover{ color:var(--apricot); }

/* The plinth the third-party page sits on. It carries the sand ground and
   the corner radius; the booking page brings its own padding, so this adds
   none of its own and clips the frame to the curve instead. */
.rsv__frame{
  position:relative; overflow:hidden;
  flex:1 1 auto; min-height:0;
  background:var(--sand); border-radius:12px;
}
/* Pinned to the plinth rather than given a height of its own, so it tracks
   the column without a second set of numbers to keep in sync. */
.rsv__frame iframe{
  position:absolute; inset:0;
  width:100%; height:100%; border:0; display:block;
}

/* Sits over the frame rather than above it, so the panel does not jolt
   taller at the moment the booking page paints. */
.rsv__loading{
  position:absolute; inset:0; margin:0; padding:16px;
  display:grid; place-items:center; text-align:center;
  font-size:var(--fs-3xs); color:var(--sand-ink);
}
.rsv__loading a{ color:inherit; text-decoration:underline; }

.rsv__foot{ flex:none; margin:12px 2px 0; font-size:var(--fs-3xs); color:rgba(241,228,208,.72); text-align:center; text-wrap:balance; }
.rsv__foot a{ color:var(--apricot); }

/* Focus wrap sentinels — the widget lives in a cross-origin iframe, so a
   keydown-based trap cannot see Tab presses made inside it. Two tabbable
   spans either side catch focus as it leaves the frame and send it back. */
.rsv__sentinel{ position:absolute; width:1px; height:1px; opacity:0; }

html.has-dialog, html.has-dialog body{ overflow:hidden; }

@media (prefers-reduced-motion:reduce){
  .rsv__scrim, .rsv__panel{ transition:none; }
}

/* ============================================================
   3b. PROOF BAR — the About press row's outlets, homepage cut.
   Logos reuse the About treatment: white-ink marks flattened
   with the same filter, same per-mark optical slot heights.
   ============================================================ */
/* The band's two edges stopped doing equal work when the signatures moved
   above the menu on 29 Aug. The top edge still has a tonal step under it —
   the manifesto sits on the darker body — but below the bar is now the
   runway, on the identical --dusk, so the bottom rule is the only thing
   marking where the proof ends and the food begins. At .08 it resolved to
   about 17 levels per channel against the ground and read as nothing. .2
   lands near 41 and reads as a hairline, which is what it was meant to be. */
.proof{ background:var(--dusk); border-block:1px solid rgba(241,228,208,.2); }
.proof__inner{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:1fr 1.08fr 1.08fr;
  gap:clamp(1.6rem,3vw,2.6rem);
  padding:clamp(30px,4.5vw,50px) clamp(16px,5vw,64px);
}
.proof__item{ display:flex; flex-direction:column; gap:.45rem; justify-content:center; }
.proof__item + .proof__item{
  border-left:1px solid rgba(241,228,208,.14);
  padding-left:clamp(1.6rem,3vw,2.6rem);
}
.proof__source{ display:flex; align-items:center; height:44px; margin-bottom:.55rem; }
.proof__logo{
  width:auto; max-width:min(100%, 210px); object-fit:contain;
  filter:brightness(0) invert(1); opacity:.88;
  transition:opacity .25s var(--ease);
}
.proof__item:hover .proof__logo{ opacity:1; }
.proof__logo--psl{  height:15px; }   /* 11.8:1 heavy sans wordmark */
.proof__logo--vgps{ height:42px; }   /* 2.4:1  stacked mark */
.proof__logo--pst{  height:34px; opacity:1; }  /* engraved script, hairlines need the weight */
.proof__label{
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  letter-spacing:.12em; font-size:var(--fs-3xs); color:var(--turquoise);
}
.proof__quote{
  font-family:var(--serif); font-style:italic;
  font-size:clamp(1.3rem,1.9vw,1.6rem); line-height:1.25; color:var(--apricot);
}
.proof__detail{ font-size:var(--fs-xs); color:var(--sand); opacity:.92; line-height:1.5; }
@media (max-width:860px){
  .proof__inner{ grid-template-columns:1fr; gap:1.35rem; }
  .proof__item + .proof__item{
    border-left:0; padding-left:0;
    border-top:1px solid rgba(241,228,208,.14); padding-top:1.35rem;
  }
}

/* Hero orientation strip: the tonight answers, under the CTAs. */
.hero__strip{
  margin-top:1.7rem;
  display:flex; justify-content:center; align-items:baseline;
  column-gap:.85rem; row-gap:.4rem; flex-wrap:wrap;
  font-family:var(--display); font-weight:700; text-transform:uppercase;
  letter-spacing:.11em; font-size:var(--fs-2xs); line-height:1.4;
  color:var(--sand);
  text-shadow:0 1px 12px rgba(11,6,22,.85), 0 1px 2px rgba(11,6,22,.9);
}
.hero__strip-sep{ color:var(--apricot); }
@media (max-height:560px){
  .hero__strip{ margin-top:1.1rem; }
}

/* The single row measures 972px and the hero's own padding takes the rest, so
   the three answers only fit on one line from about 1120px up. Below that the
   row wrapped and left its dots stranded at the line ends — "· WALK-INS
   WELCOME ·" — because a separator is a flex item like any other and stays put
   when the item after it moves down. Stack the three instead and drop the
   separators, which only ever divided a row. */
@media (max-width:1120px){
  .hero__strip{
    /* Wider than the line gap inside a wrapped answer, so the three facts still
       group as three even where the first one takes two lines. */
    flex-direction:column; align-items:center; row-gap:.55rem;
  }
  /* "OPEN FROM 4 PM, CLOSED TUESDAYS" is the one answer too wide for a phone,
     and it broke against the right edge and stranded TUESDAYS on its own line.
     Balance splits it evenly instead; the other two are short enough to be
     unaffected at every width. */
  .hero__strip > span{ text-wrap:balance; }
  .hero__strip-sep{ display:none; }
}

/* Phone heights. At 376x658 the lockup measured 674px against a 658px viewport,
   so the strip's last line sat on the fold and spilled into the marquee on
   anything shorter. The stack above returns most of it; the mark and the gaps
   above each block give up the rest. Measured after: 658px with 47px of slack,
   and 640px fits inside a 360x640 frame. */
@media (max-width:640px){
  .hero{ padding-top:clamp(84px,10vh,100px); padding-bottom:clamp(28px,4vh,40px); }
  /* Still the largest thing in the frame; it just stops eating the CTA's room. */
  .hero__mark img{ width:max(64px, min(200px, 38vw, 26svh)); }
  .hero__sub{ margin-top:.85rem; }
  .hero__cta{ margin-top:1.15rem; }
  .hero__strip{ margin-top:1.6rem; }
}

/* Award line at the point of decision, inside the reserve card. */
.visit__card-proof{
  margin-top:1.2rem; padding-top:1.15rem;
  border-top:1px solid rgba(22,15,38,.28);
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  letter-spacing:.1em; font-size:var(--fs-3xs); line-height:1.45;
  color:var(--night);
}

/* ---------------------------------------------------------------------------
   404. Served by Vercel for any unmatched path, so it has to stand on
   style.css alone — no page-specific stylesheet is loaded there.
   Centred, short, and every link goes somewhere real: a dead end that offers
   nothing is how people leave.
   --------------------------------------------------------------------------- */
.nf{
  min-height:72vh;
  display:grid; place-items:center;
  padding:clamp(120px,16vh,180px) clamp(16px,5vw,64px) clamp(64px,10vh,120px);
}
.nf__inner{ max-width:44rem; text-align:center; }
.nf__eyebrow{
  margin:0 0 .6em;
  font-family:var(--display); font-weight:800;
  font-size:var(--fs-3xs); letter-spacing:.24em; text-transform:uppercase;
  color:var(--burnt);
}
.nf__title{
  font-family:var(--display); font-weight:900; line-height:.98;
  text-transform:uppercase; letter-spacing:-.01em;
  /* Floor fits "TABLE", the longest word, inside a 288px content box. */
  font-size:clamp(2rem,6.4vw,3.6rem);
  margin:0 0 .5em;
}
.nf__lede{
  margin:0 auto 2.2em; max-width:34rem;
  font-size:var(--fs-md); color:var(--sand); opacity:.9;
}
.nf__links{
  list-style:none; margin:0 0 2.4em; padding:0;
  display:flex; flex-wrap:wrap; justify-content:center;
  gap:10px clamp(10px,2vw,16px);
}
.nf__links a{
  display:block;
  padding:.55em 1.15em;
  border:1px solid rgba(241,228,208,.28); border-radius:999px;
  font-size:var(--fs-2xs); font-weight:700;
  letter-spacing:.02em; text-decoration:none;
  color:var(--sand);
  transition:border-color .25s var(--ease), background-color .25s var(--ease);
}
.nf__links a:hover,
.nf__links a:focus-visible{
  border-color:var(--gold);
  background:rgba(232,184,75,.12);
}
.nf__call{ margin:0; font-size:var(--fs-sm); color:var(--sand); opacity:.85; }
/* The global `a` rule is colour:inherit / no underline, which is fine inside a
   list of links but leaves these two indistinguishable from the sentence around
   them. Underlined as well as recoloured: colour alone is WCAG 1.4.1. */
.nf__call a{
  color:var(--apricot);
  text-decoration:underline;
  text-underline-offset:.18em;
  text-decoration-thickness:from-font;
}

/* ---------------------------------------------------------------------------
   Analytics consent banner. Injected by js/consent.js only when there is a
   measurement ID, the host is production, GPC is absent, and no choice has
   been stored — so on most loads none of this is on the page at all.

   position:fixed, so it never shifts layout and costs nothing in CLS. It sits
   above the mobile dock rather than over it: the dock carries Reserve and Call,
   and covering those to ask about cookies would be a poor trade.
   --------------------------------------------------------------------------- */
.consent{
  position:fixed; z-index:70; left:0; right:0; bottom:0;
  background:rgba(22,15,38,.97);
  border-top:1px solid rgba(241,228,208,.18);
  padding:16px clamp(16px,5vw,40px) calc(16px + env(safe-area-inset-bottom));
  transform:translateY(100%);
  transition:transform .34s var(--ease);
}
.consent.is-up{ transform:translateY(0); }
@media (prefers-reduced-motion:reduce){
  .consent{ transition:none; transform:translateY(0); }
}
.consent__inner{
  max-width:var(--maxw); margin:0 auto;
  display:flex; align-items:center; justify-content:space-between;
  gap:clamp(14px,3vw,40px); flex-wrap:wrap;
}
.consent__title{
  margin:0 0 .25em;
  font-family:var(--display); font-weight:800; text-transform:uppercase;
  letter-spacing:.12em; font-size:var(--fs-3xs); color:var(--burnt);
}
.consent__text{
  margin:0; max-width:62ch;
  font-size:var(--fs-2xs); line-height:1.55; color:var(--sand);
}
/* Underlined as well as coloured: colour alone is WCAG 1.4.1. */
.consent__text a{
  color:var(--apricot); text-decoration:underline;
  text-underline-offset:.18em; text-decoration-thickness:from-font;
}
.consent__actions{ display:flex; gap:10px; flex-shrink:0; }
/* Both buttons share a size and weight. Only the fill differs, and Decline is
   not the quiet one. */
.consent__btn{
  min-height:44px; padding:.6em 1.5em;
  font-family:var(--body); font-size:var(--fs-2xs); font-weight:700;
  letter-spacing:.02em; cursor:pointer;
  border-radius:999px;
  border:1px solid rgba(241,228,208,.45);
  background:transparent; color:var(--sand);
  transition:border-color .2s var(--ease), background-color .2s var(--ease);
}
.consent__btn:hover{ border-color:var(--sand); }
.consent__btn:focus-visible{ outline:2px solid var(--turquoise); outline-offset:2px; }
.consent__btn--yes{ background:var(--sand); color:var(--sand-ink); border-color:var(--sand); }
.consent__btn--yes:hover{ background:#fff; }

@media (max-width:640px){
  /* clear the dock, which is 88px plus the safe area at this width */
  .consent{ bottom:calc(88px + env(safe-area-inset-bottom)); padding-bottom:16px; }
  .consent__inner{ flex-direction:column; align-items:flex-start; gap:14px; }
  .consent__actions{ width:100%; }
  .consent__btn{ flex:1; }
}
