/* ==========================================================================
   OZE'S RESTAURANT — main.css
   Implements brand/ART-DIRECTION.md against css/tokens.css.

   Load order is fixed:  tokens.css  →  main.css
   Nothing here re-declares a value that already exists in tokens.css.

   CONTENTS
     00  Locally-defined tokens (the few AD asks for that tokens.css lacks)
     01  Reset + document base
     02  Layout primitives            .container .layout .bleed
     03  Type primitives              .section__title .prose .u-eyebrow--arch
     04  Buttons
     05  Header + primary nav
     06  Mobile drawer
     07  Leader rows                  (shared with menu.html)
     08  Footer
     09  Motion system                (reveal / sweep / draw / stagger)
     ------------------------------------------------------------------
     10  HOME PAGE ONLY — hero, eat, drink, enjoy, teaser, gallery, visit

   Sections 00–09 are the SHARED layer. menu.html inherits all of it and
   should add its own page block below section 10, not edit anything above.
   ========================================================================== */


/* ==========================================================================
   00. LOCALLY-DEFINED TOKENS
   The three stagger intervals that used to live here have MOVED to tokens.css
   (phase 2). Do not re-declare them here: tokens.css zeroes them inside its
   `prefers-reduced-motion` block, and because main.css loads afterwards, a
   duplicate :root declaration here would win the cascade and silently defeat
   reduced motion for every staggered reveal.
   ========================================================================== */
:root {
  /* ASSETS.md note 4: mark.svg / arch.svg render the "Öze's" script and the
     RESTAURANT letterforms with `fill: var(--oze-ink, #013236)`. That default
     is invisible on green. Repoint it to headline gold, document-wide. */
  --oze-ink: var(--gold-200);

  /* AD §6.5 — micro-arch bullet, 12×10, used as a mask so the fill stays a
     token rather than a hard-coded hex inside a data URI. */
  --micro-arch: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10'%3E%3Cpath d='M1,9.5 V5 A5,5 0 0 1 11,5 V9.5' fill='none' stroke='%23000' stroke-width='1.1'/%3E%3C/svg%3E");
}

/* The one place the ink flips: a light band. Nothing currently puts a lockup
   on paper, but menu.html might. */
.theme-light,
[data-theme="light"] { --oze-ink: var(--green-950); }


/* ==========================================================================
   01. RESET + DOCUMENT BASE
   ========================================================================== */
/* Cross-document crossfade: the page fades out, the next fades in.
   Progressive enhancement — ignored by browsers without support. */
@view-transition { navigation: auto; }

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

html {
  /* `clip` (not `hidden`) so position: sticky/fixed keeps working. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  overflow-x: clip;
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--ff-ui);
  font-size: var(--fs-body);
  font-weight: var(--fw-light);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body.is-locked { overflow: hidden; }

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

img, svg, picture, video { max-width: 100%; }
img { display: block; height: auto; }

a { color: inherit; }

address { font-style: normal; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

hr { border: 0; }

::selection { background: var(--c-accent); color: var(--c-on-accent); }

/* Sticky header is fixed, so anchored sections need their own clearance. */
:target,
[id] { scroll-margin-top: calc(var(--header-h) + var(--sp-5)); }


/* ==========================================================================
   02. LAYOUT PRIMITIVES
   ========================================================================== */
.container {
  width: 100%;
  max-width: calc(var(--w-content) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.container--wide { max-width: calc(var(--w-wide) + var(--gutter) * 2); }

/* 12-column grid, resolved down to 4 / 8 / 12 per AD §4. */
.layout {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: var(--grid-gap);
  row-gap: var(--sp-stack);
}
@media (min-width: 768px)  { .layout { grid-template-columns: repeat(8, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .layout { grid-template-columns: repeat(12, minmax(0, 1fr)); } }

.section {
  position: relative;
  padding-block: var(--sp-section);
}
.section--lg { padding-block: var(--sp-section-lg); }


/* ==========================================================================
   03. TYPE PRIMITIVES
   ========================================================================== */
.section__title {
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: var(--fs-h1);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h1);
  text-indent: -0.04em;          /* optical: hang the first stem */
  color: var(--c-heading);
  text-wrap: balance;
}

.prose {
  font-family: var(--ff-ui);
  font-weight: var(--fw-light);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text);
}

/* Eyebrow with the micro-arch bullet (AD §6.5). */
.u-eyebrow--arch {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.u-eyebrow--arch::before {
  content: "";
  flex: none;
  width: 12px;
  height: 10px;
  background-color: var(--c-accent);
  -webkit-mask-image: var(--micro-arch);
          mask-image: var(--micro-arch);
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
}

/* Sub-heading used by visit columns and (later) menu categories. */
.col-title,
.visit__col-title {
  font-family: var(--ff-display);
  font-weight: var(--fw-mid);
  font-size: var(--fs-h3);
  line-height: var(--lh-heading);
  color: var(--c-heading);
}


/* ==========================================================================
   04. BUTTONS — gold is an outline. Only :hover fills, and then the label
   flips to --c-on-accent (AD §8).
   ========================================================================== */
.btn {
  --btn-py: var(--sp-3);
  --btn-px: var(--sp-6);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: var(--btn-py) var(--btn-px);
  font-family: var(--ff-ui);
  font-weight: var(--fw-book);
  font-size: var(--fs-nav);
  letter-spacing: var(--ls-ui);
  line-height: var(--lh-ui);
  color: var(--c-accent);
  text-decoration: none;
  border: var(--rule-strong);
  background: transparent;
  transition:
    color            var(--dur-base) var(--ease-brand),
    background-color var(--dur-base) var(--ease-brand),
    border-color     var(--dur-base) var(--ease-brand),
    box-shadow       var(--dur-slow) var(--ease-brand);
}
.btn--pill { border-radius: var(--r-pill); white-space: nowrap; }
.btn--sm   { --btn-py: var(--sp-2); --btn-px: var(--sp-5); }
.btn[data-magnetic] {
  transition:
    color            var(--dur-base)  var(--ease-brand),
    background-color var(--dur-base)  var(--ease-brand),
    border-color     var(--dur-base)  var(--ease-brand),
    box-shadow       var(--dur-slow)  var(--ease-brand),
    transform        360ms            cubic-bezier(0.22, 0.61, 0.36, 1);
}

.btn:hover,
.btn:focus-visible {
  color: var(--c-on-accent);
  background-color: var(--c-accent);
  border-color: var(--c-accent);
  box-shadow: var(--glow-gold);
}
.btn:focus-visible { border-radius: var(--r-pill); }

.btn:active {
  transform: scale(0.97);
  transition-duration: var(--dur-instant);
}


/* ==========================================================================
   05. HEADER + PRIMARY NAV  (AD §5.1)
   ========================================================================== */
.site-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-sticky);
  background-color: transparent;
  /* Transparent over the hero, but the hero arch bleeds to the top edge and
     the neon sign sits directly under the nav. A scrim that fades to nothing
     keeps the links legible without reading as a bar. */
  background-image: linear-gradient(to bottom,
      rgb(4 26 22 / 0.62) 0%, rgb(4 26 22 / 0.24) 58%, rgb(4 26 22 / 0) 100%);
  transition:
    background-color  var(--dur-slow) var(--ease-brand),
    backdrop-filter   var(--dur-slow) var(--ease-brand);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  height: var(--header-h);
  width: 100%;
  max-width: calc(var(--w-content) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  transition: height var(--dur-slow) var(--ease-brand);
}

/* The bar's own horizon: a 1px rule that draws outward from the centre. */
.site-header__rule {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: var(--hairline);
  background: var(--c-border);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-slow) var(--ease-brand);
}

.site-header.is-condensed {
  background-image: none;
  background-color: rgb(4 26 22 / 0.86);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
}
.site-header.is-condensed .site-header__inner { height: var(--header-h-slim); }
.site-header.is-condensed .site-header__rule  { transform: scaleX(1); }

/* --- Slim-bar lockup: the horizontal cafe logo (arch + Cafe in one mark).
       The vertical lockup is NEVER squashed into this bar. ---------------- */
.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-right: auto;
  text-decoration: none;
  color: var(--c-heading);
}
.brand__cafe {
  flex: none;
  height: 60px;                 /* arch reads at the old 66px mark's scale */
  width: auto;                  /* cafe-logo-horizontal intrinsic AR — never stretched */
}
.brand:hover .brand__cafe { opacity: 0.75; }

/* --- Nav ------------------------------------------------------------- */
.site-nav { display: none; }
@media (min-width: 768px) { .site-nav { display: block; } }

.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(var(--sp-5), 2.2vw, var(--sp-7));
}
.site-nav__link {
  position: relative;
  display: inline-block;
  padding-block: var(--sp-2);
  font-size: var(--fs-nav);
  font-weight: var(--fw-book);
  letter-spacing: var(--ls-ui);
  color: var(--c-text);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-brand);
}
.site-nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: var(--hairline);
  background-image: var(--grad-foil);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slow) var(--ease-brand);
}
.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--c-accent-hover); }
/* CRAFT-FLOOR §D5: every hover TRANSFORM is pointer-gated. Touch fires a
   false hover on tap and leaves the rule drawn under a link nobody hovered.
   focus-visible is ungated — a keyboard user has a real focus, not a ghost. */
.site-nav__link:focus-visible::after { transform: scaleX(1); }
@media (hover: hover) and (pointer: fine) {
  .site-nav__link:hover::after { transform: scaleX(1); }
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* --- Drawer toggle (below 768px only) --------------------------------- */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: var(--rule);
  border-radius: var(--r-pill);
  color: var(--c-accent);
  transition: border-color var(--dur-base) var(--ease-brand);
}
.nav-toggle:hover { border-color: var(--c-border-strong); }
@media (min-width: 768px) { .nav-toggle { display: none; } }

.nav-toggle__bars {
  display: block;
  width: 16px;
}
.nav-toggle__bars span {
  display: block;
  height: var(--hairline);
  background: currentColor;
  transition: transform var(--dur-base) var(--ease-brand),
              opacity   var(--dur-fast) var(--ease-brand);
}
.nav-toggle__bars span:first-child { margin-bottom: 5px; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:first-child { transform: translateY(3px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars span:last-child  { transform: translateY(-3px) rotate(-45deg); }

/* The header's CTA is the site's one real destination — it never hides. */
@media (max-width: 400px) {
  .site-header__actions .btn { --btn-px: var(--sp-4); }
}


/* ==========================================================================
   06. MOBILE DRAWER — full screen, focus-trapped, Escape closes.
   ========================================================================== */
.drawer {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--c-bg-deep);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-brand),
              visibility 0s linear var(--dur-base);
}
.drawer[hidden] { display: none; }
.drawer.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-base) var(--ease-brand),
              visibility 0s linear 0s;
}

.drawer__list {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  text-align: center;
}
.drawer__link {
  display: inline-block;
  padding: var(--sp-2) var(--sp-4);
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: var(--fs-h2);
  line-height: var(--lh-heading);
  color: var(--c-heading);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-brand);
}
.drawer__link:hover { color: var(--c-accent-hover); }
.drawer__link--cta {
  margin-top: var(--sp-4);
  font-family: var(--ff-ui);
  font-size: var(--fs-nav);
  letter-spacing: var(--ls-ui);
  color: var(--c-accent);
  border: var(--rule-strong);
  border-radius: var(--r-pill);
  padding: var(--sp-3) var(--sp-6);
}
.drawer__link--cta:hover {
  color: var(--c-on-accent);
  background: var(--c-accent);
}

.drawer__close {
  position: absolute;
  top: var(--sp-4);
  right: var(--gutter);
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--c-accent);
  border: var(--rule);
  border-radius: var(--r-pill);
}


/* ==========================================================================
   07. LEADER ROWS — item · dotted gold leader · price.
   Shared with menu.html. (AD §5.6, §7 #8)
   ========================================================================== */
.leaders {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.leader {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
}
.leader__name {
  font-family: var(--ff-display);
  font-weight: var(--fw-mid);
  font-size: var(--fs-h3);
  line-height: var(--lh-heading);
  color: var(--c-heading);
}
.leader__rule {
  flex: 1 1 auto;
  min-width: var(--sp-6);
  height: var(--hairline);
  align-self: flex-end;
  margin-bottom: 0.35em;
  /* Square 1.5px dots on a 5px pitch: a `circle closest-side` radial in a
     1px-tall tile antialiases itself into nothing at some device ratios. */
  color: var(--c-accent);
  background-image: linear-gradient(to right,
      currentColor 0, currentColor 1.5px, transparent 1.5px, transparent 100%);
  background-size: 5px var(--hairline);
  background-repeat: repeat-x;
  background-position: left center;
  opacity: 0.75;
}
.leader__price {
  flex: none;
  font-family: var(--ff-deco);
  font-size: var(--fs-price);
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  color: var(--c-accent);
}

/* Hover: name lifts to brightest gold, leader rule brightens. Pointer-gated. */
.leader__name { transition: color var(--dur-base) var(--ease-brand); }
@media (hover: hover) and (pointer: fine) {
  .leader:hover .leader__name { color: var(--c-accent-hover); }
  .leader:hover .leader__rule { opacity: 1; }
}


/* ==========================================================================
   08. FOOTER  (AD §5.9)
   ========================================================================== */
.site-footer {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  /* Ground and glow copied from the reference footer (ozes.starfallwebdesign.ca):
     a near-black teal (#001414, vs green-950 #041A16 before) with a gold-200
     radial bloom at the top centre, behind the content. The ::before sits at
     z-index -1 inside the isolate context, so it reads as light in the room,
     above the ground and below every element. */
  padding-block: var(--sp-8) var(--sp-6);   /* was section-lg / sp-8 — trimmed */
  background: #001414;
  text-align: center;
}
.site-footer::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Percentage-size ellipses scale with the footer box — a tall footer made
     the bloom enormous. Pinned to fixed pixels so it reads the same at any
     footer height. */
  background: radial-gradient(480px 340px at 50% 0%,
    rgb(236 213 168 / 0.1) 0%,
    transparent 60%);
}

/* Top border: the same full-width 1px gold hairline the condensed
   navigation draws at its own bottom edge — and, per plan 002's spirit,
   the same entrance: it draws outward from the centre when the footer
   enters the viewport (the nav draws out on condense; here on scroll). */
.site-footer__rule {
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: var(--hairline);
  background: var(--c-border);
  transform: scaleX(1);
}
.js .site-footer__rule[data-reveal="rule"] {
  transform: scaleX(0);
  transform-origin: center;
  transition: transform var(--dur-draw) var(--ease-brand);
}
.js .site-footer__rule[data-reveal="rule"].is-revealed {
  transform: scaleX(1);
}
@media (prefers-reduced-motion: reduce) {
  .js .site-footer__rule[data-reveal="rule"] {
    transition: none;
    transform: scaleX(1);
  }
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding-top: 0;   /* was --sp-9; the section padding above is the only air */
}

/* Second and last appearance of the full vertical lockup — the cafe logo
   (arch + Cafe), sized by height; cafe-logo.svg intrinsic AR — never stretched. */
.site-footer__mark {
  height: clamp(150px, 17vw, 200px);
  width: auto;
  margin-bottom: var(--sp-4);
}

/* The Turkish farewell — the same display voice as the section titles,
   a third of their size, opening the footer above the lockup. */
.site-footer__wish {
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: var(--fs-h3);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h1);
  margin-bottom: var(--sp-2);
}

.site-footer__line {
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--c-text-muted);
}
.site-footer__line a {
  color: var(--c-accent);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-brand);
}
.site-footer__line a:hover { color: var(--c-accent-hover); }
.site-footer__dot { padding-inline: var(--sp-2); opacity: 0.5; }

.site-footer__copy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-ui);
  color: var(--c-text-muted);
}
/* Two hairline olive sprigs, 12px, flanking the copyright line. */
.sprig {
  flex: none;
  width: 24px;
  height: 12px;
  color: var(--c-accent);
  opacity: 0.65;
}

/* The candle ambience arrives here and settles. */
.u-glow--settled { opacity: 0.55; animation-duration: calc(var(--dur-ambient) * 1.6); }

/* ==========================================================================
   09. MOTION SYSTEM  (AD §7)
   Every hidden start-state is scoped to `html.js` so a no-JS document —
   or one where motion.js fails to load — renders fully visible.
   ========================================================================== */

/* --- #4 Stagger rise -------------------------------------------------- */
.js [data-reveal="rise"] {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity   var(--dur-slower) var(--ease-brand),
    transform var(--dur-slower) var(--ease-brand);
}
.js [data-reveal="rise"].is-revealed {
  opacity: 1;
  transform: none;
}

.js [data-reveal="stagger"] > * {
  opacity: 0;
  transform: translateY(24px);
  transition:
    opacity   var(--dur-slower) var(--ease-brand) calc(var(--stagger) * var(--i, 0)),
    transform var(--dur-slower) var(--ease-brand) calc(var(--stagger) * var(--i, 0));
}
.js [data-reveal="stagger"].is-revealed > * {
  opacity: 1;
  transform: none;
}

/* --- #2 Arch reveal — unmask from the foot upward --------------------- */
.js [data-reveal="arch"] {
  opacity: 0;
  clip-path: inset(100% 0 0 0);
  transition:
    opacity   var(--dur-reveal) var(--ease-brand) calc(var(--stagger-arch) * var(--i, 0)),
    clip-path var(--dur-reveal) var(--ease-brand) calc(var(--stagger-arch) * var(--i, 0));
}
.js [data-reveal="arch"].is-revealed {
  opacity: 1;
  clip-path: inset(0 0 0 0);
}
/* .u-arch--svg already owns clip-path for its shape — animate opacity only. */
.js [data-reveal="arch"]:has(> .u-arch--svg) { clip-path: none; }

/* --- Quiet settle (CRAFT-FLOOR §A1) ------------------------------------
   The default for everything that is NOT the page's authored moment. No
   translate, no scale: the element is already where it belongs, it simply
   comes into focus. blur() stays smooth because it is composited on its own
   layer and the radius is small. Most sections use nothing at all; this is
   for the two or three that would otherwise arrive with no ceremony.
   ---------------------------------------------------------------------- */
.js [data-reveal="settle"] {
  opacity: 0;
  filter: blur(6px);
  transition:
    opacity var(--dur-reveal) var(--ease-brand),
    filter  var(--dur-reveal) var(--ease-brand);
}
.js [data-reveal="settle"].is-revealed {
  opacity: 1;
  filter: blur(0);
}
/* A .oz-plate carries its own --plate-filter; never clobber it with blur. */
.js .oz-plate[data-reveal="settle"],
.js .oz-shot[data-reveal="settle"] { filter: none; }
.js .oz-plate[data-reveal="settle"] > picture > img,
.js .oz-shot[data-reveal="settle"] > picture > img {
  filter: var(--plate-filter) blur(6px);
  transition: filter var(--dur-reveal) var(--ease-brand),
              scale var(--dur-slow) var(--ease-brand);
}
.js .oz-plate[data-reveal="settle"].is-revealed > picture > img,
.js .oz-shot[data-reveal="settle"].is-revealed > picture > img {
  filter: var(--plate-filter) blur(0);
}

/* --- #3 Line draw ----------------------------------------------------- */
/* --draw-length is measured by motion.js (pixel space) for generated frames;
   1000 is the fallback for paths that declare pathLength="1000". */
.js [data-reveal="draw"] path {
  stroke-dasharray: var(--draw-length, 1000);
  stroke-dashoffset: var(--draw-length, 1000);
  transition: stroke-dashoffset var(--dur-draw) var(--ease-brand);
}
.js [data-reveal="draw"].is-revealed path { stroke-dashoffset: 0; }

/* --- #8 Leader draw --------------------------------------------------- */
.js [data-reveal="leaders"] .leader__rule {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-slower) var(--ease-brand) calc(var(--stagger-leader) * var(--i, 0));
}
.js [data-reveal="leaders"] .leader__name {
  opacity: 0;
  transition: opacity var(--dur-slower) var(--ease-brand) calc(var(--stagger-leader) * var(--i, 0));
}
.js [data-reveal="leaders"] .leader__note {
  opacity: 0;
  transition: opacity var(--dur-slower) var(--ease-brand)
              calc(var(--stagger-leader) * var(--i, 0) + var(--dur-slower) * 0.3);
}
.js [data-reveal="leaders"] .leader__price {
  opacity: 0;
  /* price fades up only after its own leader lands: +60% of the duration */
  transition: opacity var(--dur-slower) var(--ease-brand)
              calc(var(--stagger-leader) * var(--i, 0) + var(--dur-slower) * 0.6);
}
.js [data-reveal="leaders"].is-revealed .leader__rule  { transform: scaleX(1); }
.js [data-reveal="leaders"].is-revealed .leader__name,
.js [data-reveal="leaders"].is-revealed .leader__note,
.js [data-reveal="leaders"].is-revealed .leader__price { opacity: 1; }

/* --- #10 Arch, from the top -------------------------------------------
   The drink band's curtain: the arch unveils DOWNWARD from its crown, the
   inverse of #2. Same contract as #2 — the start state is a clip on the
   element itself, so motion.js proxies the observation to the parent
   (proxyFor) and the child elements (.leader__note pattern) stay untouched.
   ---------------------------------------------------------------------- */
.js [data-reveal="arch-top"] {
  opacity: 0;
  clip-path: inset(0 0 100% 0);
  transition:
    opacity   var(--dur-reveal) var(--ease-brand) calc(var(--stagger-arch) * var(--i, 0)),
    clip-path var(--dur-reveal) var(--ease-brand) calc(var(--stagger-arch) * var(--i, 0));
}
.js [data-reveal="arch-top"].is-revealed {
  opacity: 1;
  clip-path: inset(0 0 0 0);
}

/* --- #11 Shutter open ---------------------------------------------------
   A wide band reveals SIDEWAYS — two leaves parting. The clip lives on the
   img (a child), the observed figure only fades, so the IntersectionObserver
   always sees real geometry and no proxy is needed.
   ---------------------------------------------------------------------- */
.js [data-reveal="open"] {
  opacity: 0;
  transition: opacity var(--dur-reveal) var(--ease-brand);
}
.js [data-reveal="open"].is-revealed { opacity: 1; }
.js [data-reveal="open"] > picture > img {
  clip-path: inset(0 50% 0 50%);
  transition:
    clip-path var(--dur-reveal) var(--ease-brand),
    scale     var(--dur-slow)   var(--ease-brand);
}
.js [data-reveal="open"].is-revealed > picture > img { clip-path: inset(0 0 0 0); }

/* Arch images: zoom lives on the individual `scale` property so the parallax
   loop can own `transform` outright. Identity unless a slot opts in. */
.u-arch > picture > img,
.u-arch > img {
  scale: var(--img-scale, 1);
  transition: scale var(--dur-slower) var(--ease-brand);
}

/* --- #9 Arch parallax — >=1200px only, wired from JS ------------------ */
@media (min-width: 1200px) {
  .u-arch[data-parallax] { --parallax-amp: 3; }
  .u-arch[data-parallax] > picture {
    position: absolute;
    inset: 0;
    display: block;
  }
  .u-arch[data-parallax] > picture > img {
    position: absolute;
    top: -7%;
    left: 0;
    height: 114%;
    transform: translate3d(0, var(--py, 0px), 0);
  }
  .u-arch[data-parallax].is-parallax > picture > img { will-change: transform; }
}

/* Reduced motion: JS never attaches the loop, but neutralise the offset too. */
@media (prefers-reduced-motion: reduce) {
  .u-arch[data-parallax] > picture > img { transform: none; }
}




/* ==========================================================================
   ==========================================================================
   10. SHARED PAGE FURNITURE  (phase 2)
   Written on index.html but authored for all four pages: the header and the
   footer are copied byte-identically, so their styling cannot live in a page
   block. Only aria-current="page" differs between pages.
   ==========================================================================
   ========================================================================== */

/* --- Current page, marked THREE ways (V2 §2.3, WCAG 1.4.1) ---------------
   1. aria-current="page" on the anchor
   2. colour lifts to --c-heading
   3. the 12x10 hairline micro-arch above the label — the brand's line is an
      arch, and an underline is the one straight rule that would say
      "generic nav". Never colour alone.
   -------------------------------------------------------------------------- */
.site-nav__link[aria-current="page"] { color: var(--c-heading); }
.site-nav__link[aria-current="page"]::after {
  transform: scaleX(1) !important;  /* keep the foil underline always on */
}
/* Active page: the foil underline (::after) is always shown at full width —
   no additional ornament; the colour lift + permanent bar is the indicator. */
/* Stacked vertically, an above-label mark is ambiguous — it moves to the
   left of the label at the same size. */
.drawer__link[aria-current="page"] {
  position: relative;
  color: var(--c-heading);
}
.drawer__link[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -22px;
  top: 50%;
  width: 12px;
  height: 10px;
  transform: translateY(-50%);
  background-color: var(--c-accent);
  -webkit-mask-image: var(--micro-arch);
          mask-image: var(--micro-arch);
  -webkit-mask-size: contain;      mask-size: contain;
  -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
  -webkit-mask-position: center;   mask-position: center;
}

/* The phone number in the bar. It is a real action, not decoration, so it
   gets the tabular figures the rest of the site's numbers get. */
.site-header__tel {
  display: none;
  font-size: var(--fs-nav);
  font-weight: var(--fw-book);
  letter-spacing: var(--ls-ui);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--c-text);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-brand);
}
.site-header__tel:hover,
.site-header__tel:focus-visible { color: var(--c-accent-hover); }
@media (min-width: 1000px) { .site-header__tel { display: inline-block; } }

.drawer__link--tel { font-variant-numeric: tabular-nums lining-nums; }

/* --- Footer nav ---------------------------------------------------------- */
.site-footer__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-4) var(--sp-6);
  margin-block: var(--sp-5);
}
.site-footer__nav a {
  font-size: var(--fs-sm);
  letter-spacing: var(--ls-ui);
  color: var(--c-text);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-brand);
}
.site-footer__nav a:hover,
.site-footer__nav a:focus-visible { color: var(--c-accent-hover); }


/* ==========================================================================
   ==========================================================================
   11. HOME PAGE
   index.html only. Running order: hero, marquee, story teaser, eat/drink/
   enjoy, Specialties, breakfast feature, gallery, reviews, Instagram, visit
   teaser. menu.html adds its own block; it does not edit anything above.
   ==========================================================================
   ========================================================================== */


/* --------------------------------------------------------------------------
   11.1 HERO — the page's one authored motion moment.
   The sign switches on: five hairline strokes draw, the filled lockup
   cross-fades in behind them as the last stroke closes, and the headline's
   foil sweep fires on that landing. Once per session. Everything else on
   this page settles quietly or does not move at all.
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-top: calc(var(--header-h) + var(--sp-6));
  padding-bottom: var(--sp-8);
  background: var(--c-bg-deep);
}

/* The stage: the storefront photograph fills the first viewport edge to
   edge. No card, no arch mask — the facade IS the layout. */
.hero__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--c-bg-deep);
}
/* Non-slider fallback: picture direct child of stage */
.hero__stage > picture { display: block; height: 100%; }
.hero__stage > picture > img,
.hero__slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 58%;
}

/* --- Slider slides -------------------------------------------------------
   Three slides stack absolutely in the stage. Only the active one is
   visible; all others are opacity: 0. The crossfade is 1.4 s to match the
   dusk-to-lit transition the entrance animation already uses. */
.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1400ms var(--ease-brand);
}
.hero__slide.is-active { opacity: 1; }
.hero__slide > picture { display: block; height: 100%; }

/* Ken Burns breathes on each active slide — a slow push-in from 1.04 to 1.0
   over 10 s. The JS resets the animation on the departing slide so the next
   active slide always starts fresh from scale 1.04. */
.hero__slide img {
  transform-origin: 50% 58%;
}
.hero__slide.is-active img {
  animation: hero-ken-burns 10s var(--ease-brand) forwards;
}
@keyframes hero-ken-burns {
  from { scale: 1.04; }
  to   { scale: 1.0;  }
}

/* --- Dot navigation -------------------------------------------------------
   Centred at the bottom of the hero, above the scroll cue. Small circles;
   the active one brightens and grows slightly. */
.hero__dots {
  position: absolute;
  bottom: var(--sp-7);
  left: 50%;
  transform: translateX(-50%);
  z-index: calc(var(--z-raised) + 2);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  pointer-events: auto;
}
.hero__dot {
  position: relative;    /* stacking context for ::before fill track */
  overflow: hidden;      /* clip fill to the circle's own border-radius */
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: rgb(239 218 172 / 0.2);
  border: 1px solid rgb(239 218 172 / 0.5);
  transition:
    background-color var(--dur-base) var(--ease-brand),
    border-color     var(--dur-base) var(--ease-brand),
    transform        var(--dur-base) var(--ease-brand);
}

/* Fill track: scaleX(0) at rest, no transition so reset is instantaneous. */
.hero__dot::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--c-heading);
  border-radius: inherit;
  transform: scaleX(0);
  transform-origin: left;
}

/* Active state: gold border signals "current" instantly; fill sweeps the
   gold interior over the full 6 s interval. The ::before animation starts
   after the dot finishes expanding (var(--dur-base) delay). */
.hero__dot.is-active {
  border-color: var(--c-heading);
  transform: scale(1.375);
}
.hero__dot.is-active::before {
  animation: hero-dot-fill 6000ms linear forwards;
  animation-delay: var(--dur-base);
}
@keyframes hero-dot-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@media (hover: hover) and (pointer: fine) {
  .hero__dot:hover { background-color: rgb(239 218 172 / 0.45); }
}
@media (prefers-reduced-motion: reduce) {
  .hero__dots { display: none; }
  .hero__dot::before { display: none; }
  .hero__slide { opacity: 0 !important; }
  .hero__slide.is-active { opacity: 1 !important; animation: none; }
  .hero__slide.is-active img { animation: none; }
}

/* Layered veil, darkest where the copy sits (foot + left) and at the very
   top under the fixed header scrim. The sign's glow stays untouched. */
.hero__veil {
  position: absolute;
  inset: 0;
  z-index: var(--z-raised);
  pointer-events: none;
  background-image:
    linear-gradient(to top,
      rgb(4 26 22 / 0.92) 0%,
      rgb(4 26 22 / 0.55) 30%,
      rgb(4 26 22 / 0.10) 62%,
      rgb(4 26 22 / 0.38) 100%),
    linear-gradient(to right,
      rgb(4 26 22 / 0.66) 0%,
      rgb(4 26 22 / 0.30) 40%,
      rgb(4 26 22 / 0) 72%);
}

.hero__intro {
  position: relative;
  z-index: calc(var(--z-raised) + 1);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* The full vertical lockup: arch + script + RESTAURANT, one file, never
   squashed. Clear space >= 25% of the mark's width on all sides. */
.hero__lockup {
  position: relative;
  width: clamp(64px, 8vw, 100px);
  aspect-ratio: 278.13 / 522.84;   /* cafe-logo.svg intrinsic AR — never stretched */
  margin: 0 0 var(--sp-5);
  padding: 0;
}
.hero__lockup::after {          /* the 25% clear-space guard, non-visual */
  content: "";
  position: absolute;
  inset: -25%;
  pointer-events: none;
}
.hero__mark,
.hero__line {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.hero__line { pointer-events: none; }

/* The authored moment. --dur-draw (1200ms) is the ONE duration on this site
   allowed past 300ms, because this is the thing people are meant to watch.
   Over the photograph it reads as the neon sign switching on. */
.js .hero__mark {
  opacity: 0;
  transition: opacity var(--dur-slower) var(--ease-brand) var(--dur-draw);
}
.js .hero__line path {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  transition: stroke-dashoffset var(--dur-draw) var(--ease-brand);
}
.js .hero__line {
  opacity: 1;
  transition: opacity var(--dur-slow) var(--ease-brand) var(--dur-draw);
}
.js .hero__line path:nth-child(1) { transition-delay: calc(var(--stagger-draw) * 0); }
.js .hero__line path:nth-child(2) { transition-delay: calc(var(--stagger-draw) * 1); }
.js .hero__line path:nth-child(3) { transition-delay: calc(var(--stagger-draw) * 2); }
.js .hero__line path:nth-child(4) { transition-delay: calc(var(--stagger-draw) * 3); }
.js .hero__line path:nth-child(5) { transition-delay: calc(var(--stagger-draw) * 4); }

.hero__lockup.is-drawn .hero__mark      { opacity: 1; }
.hero__lockup.is-drawn .hero__line      { opacity: 0; }
.hero__lockup.is-drawn .hero__line path { stroke-dashoffset: 0; }

/* Already seen this session — no draw, straight to the end state. */
.hero__lockup.is-instant .hero__mark,
.hero__lockup.is-instant .hero__line,
.hero__lockup.is-instant .hero__line path { transition: none; }

/* The headline and the CTA are NEVER hidden at rest. Legibility above the
   fold is not gated on an animation (CRAFT-FLOOR §A1). */
.hero__copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
}
.hero__headline {
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: clamp(2.5rem, 4.2vw + 1rem, 4.5rem);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  text-indent: -0.04em;
  text-wrap: balance;
  max-width: 16ch;
  color: var(--c-heading);
}
.hero__headline em {
  font-style: italic;
  font-weight: 300;
}
.hero__lead {
  max-width: 44ch;
  font-size: var(--fs-lead);
  color: var(--c-text);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-5);
  margin-top: var(--sp-2);
}

/* "established 2021" is real client copy, so it stays — but it is a FACT,
   not a kicker, so it sits under the call to action beside the live hours
   rather than above the headline (CRAFT-FLOOR §A2). */
.hero__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4) var(--sp-5);
  margin-top: var(--sp-5);
}
.hero__since {
  font-family: var(--ff-deco);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--c-text-muted);          /* 6.69:1 on --c-bg-deep */
}

/* Parallax rides the stage photo on desktop; the image is oversized 114%
   and translated by --py (set by motion.js). Reduced motion kills it. */
@media (min-width: 1200px) {
  .hero__stage[data-parallax] { --parallax-amp: 6; }
  .hero__stage[data-parallax] > picture { position: absolute; inset: 0; }
  .hero__stage[data-parallax] > picture > img {
    position: absolute;
    top: -7%;
    left: 0;
    width: 100%;
    height: 114%;
    transform: translate3d(0, var(--py, 0px), 0);
  }
  .hero__stage[data-parallax].is-parallax > picture > img { will-change: transform; }
}

/* --- The hero entrance, staged as a scene ------------------------------
   Dusk falls, then the room lights up. One timeline, keyed off a single
   class (motion.js adds .is-live to .hero on its first painted frame):

     0ms      the photograph brightens out of dusk and settles from a
              1.05 push-in (1400/1600ms, ease-out — eyes adjusting)
     0ms      the sign's five strokes begin drawing over it (existing)
     1200ms   the drawn mark fills (existing) and, in answer,
     1350ms   the neon in the PHOTOGRAPH ignites: the glow bloom fades in
              and begins its slow candle-breathing
     1300ms+  the copy arrives last: headline, lead, actions, facts,
              each rising 18px out of a 4px blur, 160ms apart
     2200ms   the scroll cue fades in with its falling spark

   The zoom rides the `scale` PROPERTY so the parallax loop keeps `transform`
   (the same split the plates use). Choreography offsets are authored values;
   every duration and curve is a token.
   ---------------------------------------------------------------------- */
.js .hero__stage > picture > img,
.js .hero__slide.is-active img {
  filter: brightness(0.55) saturate(0.85);
  transition:
    filter  1400ms var(--ease-brand);
}
.js .hero.is-live .hero__stage > picture > img,
.js .hero.is-live .hero__slide.is-active img {
  filter: none;
}

/* The neon catching. mix-blend-mode: screen = light added to the photograph,
   never a painted blob; the breathe is ambient (like --glow-candle) and dies
   under reduced motion. */
.hero__glow {
  position: absolute;
  /* Anchored to the sign in the anchor photograph: the concept storefront's
     neon arch sits just left of centre, its crown at ~5% height. */
  left: 49%;
  top: 18%;
  width: 46%;
  aspect-ratio: 2.2 / 1;
  transform: translate(-50%, -50%);
  z-index: calc(var(--z-raised) + 1);
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 50%,
    rgb(239 218 172 / 0.42) 0%,
    rgb(209 164 121 / 0.16) 42%,
    transparent 72%);
  mix-blend-mode: screen;
  opacity: 0;
}
.js .hero.is-live .hero__glow {
  opacity: 1;
  transition: opacity 1100ms var(--ease-brand) 1350ms;
  animation: hero-glow-breathe 7s var(--ease-brand) 2600ms infinite alternate;
}
@keyframes hero-glow-breathe {
  from { filter: brightness(1); }
  to   { filter: brightness(1.3); }
}

/* The copy lands last, closest to the reader, quickest to read. */
.js .hero__copy > * {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(4px);
  transition:
    opacity   700ms var(--ease-brand),
    transform 700ms var(--ease-brand),
    filter    700ms var(--ease-brand);
}
.js .hero__copy > *:nth-child(1) { transition-delay: 1150ms; }
.js .hero__copy > *:nth-child(2) { transition-delay: 1310ms; }
.js .hero__copy > *:nth-child(3) { transition-delay: 1470ms; }
.js .hero__copy > *:nth-child(4) { transition-delay: 1630ms; }
.js .hero.is-live .hero__copy > * {
  opacity: 1;
  transform: none;
  filter: none;
}

/* The invitation down. Decorative, non-interactive, late. */
.hero__cue {
  position: absolute;
  right: clamp(1.25rem, 3vw, 2.75rem);
  bottom: 0;
  width: var(--hairline);
  height: clamp(56px, 9vh, 104px);
  background-image: linear-gradient(to bottom,
    rgb(239 218 172 / 0) 0%,
    rgb(239 218 172 / 0.55) 100%);
  overflow: hidden;
  opacity: 0;
}
.js .hero.is-live .hero__cue {
  opacity: 1;
  transition: opacity 900ms var(--ease-brand) 2200ms;
}
.hero__cue::after {
  content: "";
  position: absolute;
  top: -22%;
  left: 0;
  width: 100%;
  height: 22%;
  background-color: var(--c-accent);
  /* constant motion -> linear (the drift is ambient, not a UI response) */
  animation: hero-cue-drift 2.6s linear infinite;
}
@keyframes hero-cue-drift {
  to { top: 122%; }
}

/* Second visit this session: the scene is already lit. Straight to the end
   state, no transitions. */
.hero.is-instant .hero__stage > picture > img,
.hero.is-instant .hero__slide img,
.hero.is-instant .hero__glow,
.hero.is-instant .hero__copy > *,
.hero.is-instant .hero__cue { transition: none; }
.hero.is-instant .hero__glow { animation: none; }
.hero.is-instant .hero__slide.is-active img { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .js .hero__stage > picture > img,
  .js .hero__slide img { filter: none; transition: none; }
  .js .hero__copy > * { transform: none; filter: none; }
  .js .hero.is-live .hero__glow { animation: none; }
  .hero__cue::after { animation: none; top: 50%; }
  .hero__cue { opacity: 1; }
}


/* --------------------------------------------------------------------------
   11.2 TEASER — story (-> this-is-us.html). The visit teaser was removed
   from the homepage 2026-09-07; visit.html carries the address and hours.
   No eyebrow: the heading already names the section, and a label that only
   repeats a heading is the decorative kicker the floor bans.
   -------------------------------------------------------------------------- */
.oz-teaser { padding-block: var(--sp-section); }
.oz-teaser--story { background: var(--c-bg); }

.oz-teaser__inner {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
}
.oz-teaser__cta { margin-top: var(--sp-3); }

/* The story teaser is set as an editorial spread: heading left, a foil
   hairline between the columns, copy right. The rule draws itself downward
   as the section reveals (child transition keyed off the parent's
   .is-revealed, so the observer never sees a zero-height target). */
@media (min-width: 768px) {
  .oz-teaser--story .oz-teaser__inner {
    display: grid;
    grid-template-columns: minmax(0, 0.8fr) auto minmax(0, 1.2fr);
    column-gap: clamp(var(--sp-6), 4vw, var(--sp-9));
    row-gap: var(--sp-4);
    align-items: start;
  }
  .oz-teaser--story .section__title { grid-column: 1; margin: 0; }
  .oz-teaser--story .oz-teaser__rule {
    grid-column: 2;
    grid-row: 1 / span 2;
    width: var(--hairline);
    align-self: stretch;
    min-height: 140px;
    background-image: var(--grad-foil);
    opacity: 0.45;
  }
  .oz-teaser--story .oz-teaser__body { grid-column: 3; display: flex; flex-direction: column; gap: var(--sp-3); }
  .oz-teaser--story .oz-teaser__cta  { margin-top: 0; }
}
@media (max-width: 767.98px) {
  /* On phones the rule becomes a horizontal hairline under the heading. */
  .oz-teaser--story .oz-teaser__rule {
    display: block;
    width: 64px;
    height: var(--hairline);
    background-image: var(--grad-foil);
    opacity: 0.45;
  }
}
.js .oz-teaser--story .oz-teaser__rule {
  transform: scaleY(0);
  transform-origin: top;
  transition: transform var(--dur-slower) var(--ease-brand) calc(var(--stagger) * 3);
}
.js .oz-teaser--story .oz-teaser__inner.is-revealed .oz-teaser__rule { transform: none; }

/* --------------------------------------------------------------------------
   11.3 EAT / DRINK / ENJOY
   The client's own three section words. This is the ONE surviving deco-caps
   label on the page, and it survives structurally: the word IS the heading
   and the client's phrase is the sub-line beneath it. Nothing is stacked
   above a heading (CRAFT-FLOOR §A2).
   -------------------------------------------------------------------------- */
.oz-trip {
  position: relative;
  padding-block: var(--sp-section);
  background: var(--c-bg);
  border-top: var(--rule);
}
.oz-trip--enjoy {
  /* Owned here so both the centre column and the plate can read it. */
  --enjoy-pad-x: clamp(var(--sp-5), 4vw, var(--sp-8));
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-block: var(--sp-section-lg);
  background: var(--c-bg-deep);
  text-align: center;
}

/* --- ENJOY: the dimmed room + the frame that draws itself (AD §5.5) -------
   The poster photograph is pushed almost all the way back — 22% over a veil
   that is darkest at the edges — so it reads as a wall behind the words, not
   as a photograph competing with them. Gold is a light source here, so the
   glow layer sits between the veil and the copy.
   -------------------------------------------------------------------------- */
.enjoy__bg,
.enjoy__veil {
  position: absolute;
  inset: 0;
  z-index: var(--z-below);
  pointer-events: none;
}
.enjoy__bg { display: block; }
/* The class sits ON the element now (the video replaced the picture), so the
   cover rules must match the element itself — a descendant selector never
   would, and the video would render at its intrinsic 1920x1080. */
.enjoy__bg img,
video.enjoy__bg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 45%;
  opacity: 0.22;
  filter: saturate(0.7) contrast(1.04);
}
/* The video runs a touch more present than the still poster did — it is the
   living room, and the veil above it keeps the words in charge. At 0.28 under
   the veil it was technically playing but read as "no video" — 0.55 keeps the
   copy in charge while the room is unmistakably there and moving. */
video.enjoy__bg { opacity: 0.55; }
/* Darkest at the rim, lightest behind the copy: the room falls away. */
.enjoy__veil {
  background: radial-gradient(120% 92% at 50% 45%,
    rgb(4 26 22 / 0.42) 0%,
    rgb(4 26 22 / 0.78) 62%,
    rgb(4 26 22 / 0.96) 100%);
}

.enjoy__plate {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  /* The frame's padding sits INSIDE this box, so capping it at --measure-tight
     left the paragraph about 25ch wide and ten lines tall. Size the plate so
     the copy keeps its measure and the frame stands off it. */
  /* The parent is a centred flex column, so without this the plate shrinks to
     its content. The measure itself is enforced on the copy, not here — the
     frame's padding has to sit OUTSIDE the 46ch, or it eats into it. */
  width: 100%;
  align-self: stretch;
  margin-inline: auto;
  padding: clamp(var(--sp-6), 4.5vw, var(--sp-9))
           var(--enjoy-pad-x)
           clamp(var(--sp-7), 5vw, var(--sp-9));
}

/* An OPEN rectangle: up the left side, across two arched top corners, down the
   right. The bottom stays broken, exactly like the mark's own rail. The `d` is
   generated in pixel space by motion.js initFrames(), which is why there is no
   viewBox here. */
.enjoy__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  color: var(--c-accent);
  pointer-events: none;
}
.enjoy__frame path {
  stroke: currentColor;
  opacity: 0.6;
}

.oz-trip__row { align-items: center; }
.oz-trip__text {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
}
/* More space above a heading than below it. */
.oz-trip__word {
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: var(--fs-h1);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h1);
  text-indent: -0.04em;
  color: var(--c-heading);
}
.oz-trip__line {
  margin-top: calc(var(--sp-2) * -1);
  font-family: var(--ff-ui);
  font-weight: var(--fw-light);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text);
}
.oz-trip__text .prose { margin-top: var(--sp-2); }
.oz-trip .lexicon { margin-top: var(--sp-5); width: 100%; }

/* Lexicon rail — four words lifted verbatim from the paragraph above it, on
   1px rules. Not a card, not a chip set: a row of engraved words. */
.lexicon {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  border-top: var(--rule);
  border-bottom: var(--rule);
}
.lexicon__item {
  flex: 1 1 auto;
  padding: var(--sp-4);
  font-family: var(--ff-deco);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  color: var(--c-accent);
}
.lexicon__item + .lexicon__item { border-left: var(--rule); }

/* Hours rows. Shared with visit.html, which owns the full panel; here they
   are two summary lines under the teaser's address. Monday is muted, never
   hidden — a closed day is information. */
.hours__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
  font-size: var(--fs-sm);
}
.hours__row + .hours__row { border-top: var(--rule); }
.hours__row dt {
  font-family: var(--ff-ui);
  font-weight: var(--fw-book);
  letter-spacing: var(--ls-ui);
  color: var(--c-text);
}
.hours__row dd { color: var(--c-text); }
.hours__row--closed dt,
.hours__row--closed dd { color: var(--c-text-muted); }

/* The one place this page allows a filled surface; visit.html leans on it. */
.hours-panel {
  margin-top: var(--sp-5);
  padding: var(--sp-5) var(--sp-6);
  background: var(--c-surface);
  box-shadow: var(--inset-hairline);
}

.oz-trip__media {
  grid-column: 1 / -1;
  width: 100%;
  max-width: 460px;   /* never wider than the source can carry at 2x */
  margin-inline: auto;
}

.oz-trip__centre {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  max-width: var(--measure-tight);
}
/* Enjoy widens its column by exactly the frame's padding, so the copy keeps its
   46ch measure and the drawn frame stands clear of it rather than squeezing it
   to ~25ch and ten lines. */
.oz-trip--enjoy .oz-trip__centre {
  max-width: min(100%, calc(var(--measure-tight) + var(--enjoy-pad-x) * 2));
}

.oz-trip__centre .oz-trip__line { color: var(--c-text-muted); }
/* The only italic on the site — this is the family's own voice. */
.oz-trip__lead {
  margin-top: var(--sp-3);
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text);
  text-wrap: pretty;
}

@media (min-width: 768px) {
  .oz-trip__text  { grid-column: 1 / span 5; }
  .oz-trip__media { grid-column: 6 / -1; margin-inline: 0; }
}
@media (min-width: 1200px) {
  .oz-trip__text  { grid-column: 1 / span 6; }
  .oz-trip__media { grid-column: 8 / span 5; }
}

/* --- DRINK: the one light band on the page --------------------------------
   Cream paper under a domed top edge (an awning over the page's mid-point),
   green ink, and the coffee photograph as a TRUE arch — a window onto a
   darker room. theme-light flips every token underneath it, so the word,
   the sub-line and the prose re-ink themselves; u-foil collapses to solid
   green (gold text on cream is banned by the token contract).
   Row order mirrors eat (photo left, copy right) on desktop.
   -------------------------------------------------------------------------- */
.oz-trip--drink {
  background: var(--c-bg);              /* theme-light -> cream */
  border-top: 0;
  position: relative;
}
.oz-trip--drink .oz-trip__line { color: var(--c-text-muted); }

.oz-trip__media--arch {
  border-radius: var(--r-arch);
}
/* The plate clips its own window; the hairline arch must stand OUTSIDE it,
   so the rounding is applied per-layer instead of as figure overflow. */
.oz-trip__media--arch > picture {
  border-radius: var(--r-arch);
  overflow: hidden;
}
.oz-trip__media--arch::before,
.oz-trip__media--arch::after {
  border-radius: var(--r-arch);
}
/* A hairline arch floating just INSIDE the photo's arch — the bleed clips
   its own overflow, so the drawn line lives in the window (the gallery
   ring's mechanic, singular here). */
.oz-archline {
  position: absolute;
  inset: 12px 12px auto 12px;
  height: calc(100% - 24px);
  border: var(--rule-strong);
  border-bottom: 0;
  border-radius: var(--r-arch);
  pointer-events: none;
  z-index: calc(var(--z-raised) + 1);
}
@media (min-width: 768px) {
  .oz-trip--drink .oz-trip__media { grid-column: 1 / span 5; }
  .oz-trip--drink .oz-trip__text  { grid-column: 6 / -1; }
}
@media (min-width: 1200px) {
  .oz-trip--drink .oz-trip__media { grid-column: 1 / span 6; }
  .oz-trip--drink .oz-trip__text  { grid-column: 8 / span 5; }
}


/* --------------------------------------------------------------------------
   11.4 SPECIALTIES — the signature-dish grid.
   Six is the cap. No cards, no nesting, no badge marking an item "popular":
   the heading is the client's own menu section name and it carries the
   framing; the items state a name and a price and nothing else.
   -------------------------------------------------------------------------- */
.section--dishes { background: var(--c-bg); }
.section--dishes .section__title { margin-bottom: var(--sp-stack); }
.section__after { margin-top: var(--sp-stack); }

/* The split: one tall plate ("today's sign") against the printed menu card.
   Asymmetric on purpose — 6/5 columns, the plate reading large on the left,
   the leader list holding a 34rem measure on the right. Phones stack them.
   Double class = wins over components.css's .oz-dishes breakpoints, which
   load later in the cascade. */
.oz-dishes.oz-dishes--split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-stack);
}
.oz-dishes__hero {
  max-width: 460px;
  margin-inline: auto;
}
.oz-dishes__caption {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.oz-dishes__caption .oz-dish__name { margin-top: 0; }
.oz-dishes__caption .leader__rule { flex: 1 1 auto; align-self: center; }
.oz-dishes__list { max-width: 34rem; }
.leader__note {
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: var(--c-text-muted);
}
@media (min-width: 768px) {
  .oz-dishes.oz-dishes--split {
    grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
    align-items: center;
    column-gap: var(--grid-gap);
  }
  .oz-dishes__hero { max-width: 520px; margin-inline: 0; }
}
@media (min-width: 1200px) {
  .oz-dishes__hero { max-width: 560px; }
  /* A plate, not a hero: half amplitude, like the breakfast feature. */
  .oz-dishes__hero [data-parallax] { --parallax-amp: 3%; }
}

/* --- Specialties hover polish -------------------------------------------
   Gold archline inside the hero dish plate fades in on hover, echoing the
   gallery ring mechanic. Leader rows shift subtly right on hover — a quiet
   gesture that says "this is interactive", not an ad. */
.oz-dishes__hero .oz-archline {
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-brand);
}
@media (hover: hover) and (pointer: fine) {
  .oz-dishes__hero:hover .oz-archline { opacity: 1; }
  .oz-dishes__list .leader {
    transition: transform var(--dur-base) var(--ease-brand);
  }
  .oz-dishes__list .leader:hover { transform: translateX(5px); }
}


/* --------------------------------------------------------------------------
   11.5 BREAKFAST FEATURE
   The photograph is a full-bleed layer owned by components.css (C8); this
   section adds no padding of its own — .container already carries the
   gutters, and the photo must reach both viewport edges untouched.
   -------------------------------------------------------------------------- */

/* The eyebrow became a voice line: the house's own italic, set BELOW the
   heading it belongs to (CRAFT-FLOOR §A2 bans the label above the heading,
   not the phrase itself). */
.oz-feature__sub {
  margin-top: var(--sp-2);
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text-muted);
}

/* The phase-1 parallax rule is scoped to .u-arch. A plate is the same idea in
   a different frame, so it gets the same plumbing: the image is oversized by
   14% and rides --py, which motion.js writes at the slot's own amplitude
   (3% here — half, per V2 §3.5). The ARCH never moves; only what is inside
   it does. >=1200px only, and neutralised under reduced motion. */
@media (min-width: 1200px) {
  .oz-plate[data-parallax] { --parallax-amp: 3; }
  .oz-plate[data-parallax] > picture {
    position: absolute;
    inset: 0;
    display: block;
  }
  .oz-plate[data-parallax] > picture > img {
    position: absolute;
    top: -7%;
    left: 0;
    height: 114%;
    transform: translate3d(0, var(--py, 0px), 0);
  }
  .oz-plate[data-parallax].is-parallax > picture > img { will-change: transform; }
}
@media (prefers-reduced-motion: reduce) {
  .oz-plate[data-parallax] > picture > img { transform: none; }
}


/* --------------------------------------------------------------------------
   11.6 GALLERY — three lit windows, no captions and no plaque.
   The interiors are NOT here: room-display / room-wide / room-neon belong to
   this-is-us.html and to the Instagram grid, and room-display beside
   room-wide would read as a duplicate frame (CRAFT-FLOOR §E).
   -------------------------------------------------------------------------- */
.gallery {
  padding-block: var(--sp-section);
  background: var(--c-bg);
}

/* The section's anchor: one display heading, aligned to the rail beneath it.
   More space below it than a paragraph would take — the skyline needs air. */
.gallery__title {
  max-width: calc(var(--w-wide) + var(--gutter) * 2);
  margin-inline: auto;
  margin-bottom: var(--sp-7);
  padding-inline: var(--gutter);
  font-family: var(--ff-display);
  font-weight: var(--fw-book);
  font-size: clamp(1.9rem, 1.3rem + 2.6vw, 3.2rem);
  line-height: var(--lh-heading);
  letter-spacing: var(--ls-h2);
  text-align: center;
  color: var(--c-heading);
}

.gallery__rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 78vw;
  gap: var(--grid-gap);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  padding-inline: var(--gutter);
  scrollbar-width: none;
}
.gallery__rail::-webkit-scrollbar { display: none; }
.gallery__cell { scroll-snap-align: center; }

/* The ring rides on top of the plate's own hairline and carries the sweep. */
.gallery__ring {
  position: absolute;
  inset: 0;
  z-index: var(--z-raised);
  pointer-events: none;
  border: var(--rule);
  border-radius: var(--r-arch);
  transition: border-color var(--dur-slow) var(--ease-brand);
}
/* The wide elliptical band's ring follows the ellipse, not the arch. */
.gallery__ring--band { border-radius: var(--r-band-arch); }

/* A skyline, not a queue: three different heights on one baseline — a tall
   arch (3/4), a square arch (1/1), and a wide elliptical band (the band's
   own 16/10). The rail bottom-aligns its cells so the feet meet like
   shopfronts on a street. */
@media (min-width: 768px) {
  .gallery__rail { align-items: end; }
  .gallery__cell:nth-child(1) .gallery__arch { --ar-plate: 3 / 4; }
  .gallery__cell:nth-child(2) .gallery__arch { --ar-plate: 1 / 1; }
}

/* Hover: the image inside moves 4%, the ring brightens, the arch itself
   NEVER moves. Pointer-gated — a tap on a touch screen fires a false hover
   and would leave a tile stuck zoomed (CRAFT-FLOOR §D5). */
@media (hover: hover) and (pointer: fine) {
  .gallery__arch:hover,
  .gallery__arch:focus-within { --img-scale: 1.06; }
  .gallery__arch:hover .gallery__ring,
  .gallery__arch:focus-within .gallery__ring { border-color: var(--c-border-strong); }
  .gallery__cell--cat:hover .gallery__cat-rule,
  .gallery__cell--cat:focus-within .gallery__cat-rule { transform: scaleX(1.6); }
}

/* --- Menu category overlay ------------------------------------------------
   A text label lives below the arch's curved crown — the bottom corners are
   square so the overlay sits naturally inside the visual bounds of the plate.
   The cell is the position parent; label and link both ride on top of the
   figure without being clipped by the plate's overflow:hidden. */
.gallery__cell--cat { position: relative; }

.gallery__cat {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: var(--sp-8) var(--sp-5) var(--sp-5);
  background: linear-gradient(to top, rgb(4 26 22 / 0.88) 0%, rgb(4 26 22 / 0.3) 55%, transparent 100%);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  pointer-events: none;
  z-index: calc(var(--z-raised) + 1);
  transition: background var(--dur-slow) var(--ease-brand);
}
.gallery__cat--wide {
  /* The wide band uses a shorter plate — compress the gradient accordingly. */
  padding-top: var(--sp-6);
}
.gallery__cat-name {
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: clamp(1.8rem, 1.2rem + 2.2vw, 2.9rem);
  line-height: var(--lh-heading);
  color: var(--c-heading);
}
.gallery__cat-rule {
  width: 44px;
  height: var(--hairline);
  margin-block: var(--sp-2) var(--sp-1);
  background-image: var(--grad-foil);
  transform-origin: left center;
  transition: transform var(--dur-slow) var(--ease-brand);
}
.gallery__cat-sub {
  font-size: var(--fs-sm);
  letter-spacing: var(--ls-ui);
  color: var(--c-accent);
  opacity: 0.9;
}
.gallery__cat-link {
  position: absolute;
  inset: 0;
  z-index: calc(var(--z-raised) + 2);
  border-radius: inherit;
}
@media (hover: hover) and (pointer: fine) {
  .gallery__cell--cat:hover .gallery__cat-name { color: var(--c-accent-hover); }
  .gallery__cell--cat:hover .gallery__cat {
    background: linear-gradient(to top, rgb(4 26 22 / 0.92) 0%, rgb(4 26 22 / 0.4) 60%, transparent 100%);
    transform: translateY(-4px);
  }
}
.gallery__cat {
  /* ... existing declaration is earlier; add transition here for the lift */
  transition: background var(--dur-base) var(--ease-brand),
              transform   var(--dur-base) var(--ease-brand);
}

@media (min-width: 768px) {
  .gallery__rail {
    grid-auto-columns: 42vw;
    scroll-snap-type: none;
  }
}
@media (min-width: 1200px) {
  .gallery__rail {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-auto-columns: auto;
    overflow: visible;
    max-width: calc(var(--w-wide) + var(--gutter) * 2);
    margin-inline: auto;
  }
}


/* --------------------------------------------------------------------------
   11.7 REVIEWS + INSTAGRAM grounds.
   Both sections are rendered by their own module into a mount point; all
   this file owns is the ground they stand on and the rhythm around them.
   -------------------------------------------------------------------------- */
/* The photo layer lives in C9 (.oz-reviews__bg); this owns the stacking:
   the figure sits at z-index -1 against the section's own ground, so the
   plaque and the quote cards never have to know it is there. */
.section--reviews {
  position: relative;
  isolation: isolate;
  background-color: var(--c-bg-deep);
  padding-block: 0;
}
.section--instagram { background: var(--c-bg); padding-block: var(--sp-section); }


/* --------------------------------------------------------------------------
   11.8 LQIP BLUR-UP  (ASSETS.md note 3)
   Each 88-166 B placeholder is inlined as a background on the slot itself and
   the real <img> paints over it. No JS, no library, no extra request. The
   background-position mirrors each slot's object-position so the blur does
   not jump when the full image lands.
   -------------------------------------------------------------------------- */
.hero__stage {
  background-image: url(data:image/webp;base64,UklGRmwAAABXRUJQVlA4IGAAAACwAwCdASoYAA0APt1cp0yopSOiMAgBEBuJQBOnKAAbMB4p9jQZQAD4osenDRG3a+/+/fyqngRXHpZKwQdPIRiuXf2Fqn8RCsn8V1VizdFr/kapd3AoyRsH0cERLqPAAAA=);
  background-size: cover;
  background-position: 50% 58%;
  background-repeat: no-repeat;
}


/* --------------------------------------------------------------------------
   11.9 REDUCED MOTION — "fewer and gentler", never "zero".
   tokens.css already shortens every duration inside its own
   prefers-reduced-motion block. What is left to remove here is MOVEMENT:
   translation, parallax offset and the ambient candle drift. Opacity and
   colour survive at the shortened durations, because they are what carries
   comprehension. Every final state stays reachable.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal="rise"],
  .js [data-reveal="stagger"] > * { transform: none; }
  .js [data-reveal="settle"] { filter: none; }
  .js .oz-plate[data-reveal="settle"] > picture > img,
  .js .oz-shot[data-reveal="settle"] > picture > img { filter: var(--plate-filter); }
  .js [data-reveal="arch-top"] { clip-path: none; }
  .js [data-reveal="open"] > picture > img { clip-path: none; }
  .js .oz-teaser--story .oz-teaser__rule { transform: none; }
  .u-glow { animation: none; transform: none; }
  .hero__stage[data-parallax] > picture > img { transform: none; }
  .hero__slide img { transform: none; }
}


/* ==========================================================================
   12. INTERIOR PAGES — this-is-us.html and visit.html
   APPENDED BLOCK. Nothing above this line is touched: sections 00–11 are the
   shared layer index.html depends on, and they are owned elsewhere.

   Both pages open on a compact masthead, never a 100svh hero (V2 §2.2), and
   each carries exactly ONE authored motion moment (CRAFT-FLOOR §A1):
     this-is-us  the shared-table photograph opening from its centre
     visit       the map panel opening (already authored in components.css)
   Everything else settles quietly or does not move at all.

   Note on the cascade: components.css loads AFTER this file, so every rule
   below that re-points a .oz-plate / .oz-bleed / [data-shot] value is written
   at a specificity that wins on its own rather than on source order.
   ========================================================================== */

/* --------------------------------------------------------------------------
   12.1 COMPACT MASTHEAD — the interior-page opening. Content is above the
   fold on every interior page, always. The header is fixed, so the top pad
   clears it exactly the way menu.css does.
   -------------------------------------------------------------------------- */
.page-head {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding-top: calc(var(--header-h) + var(--sp-9));
  padding-bottom: var(--sp-section);
  background: var(--c-bg);
}
.page-head__inner > * + * { margin-top: var(--sp-4); }

.page-head__lead {
  font-family: var(--ff-ui);
  font-weight: var(--fw-light);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-text);
  max-width: var(--measure);
  text-wrap: pretty;
}
.page-head .oz-status   { margin-top: var(--sp-6); }
.page-head .u-foil-rule { margin-top: var(--sp-7); }


/* --------------------------------------------------------------------------
   12.2 THE DAYLIGHT BAND  (this-is-us.html) — full-viewport parallax moment.

   shared-table fills 100svh with object-fit cover. A deep forest-green
   gradient scrim runs up from the bottom so the family-voice copy sits
   legibly over the photograph. On >=1200px the image is oversized to 114%
   and rides --py for a scroll parallax (same plumbing as the hero).
   -------------------------------------------------------------------------- */
.story-band {
  background: var(--c-bg);
}
/* The frame is the full-viewport image block. */
.story-band__frame {
  position: relative;
  height: 100svh;
  overflow: hidden;
}
/* High-specificity override so this beats components.css's oz-shot rules. */
.story-band .story-band__media.oz-plate[data-shot="shared-table"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  aspect-ratio: unset;
  border-radius: 0;
}
.story-band .story-band__media > picture {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.story-band .story-band__media > picture > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 25%;
}

/* Copy: flows below the image on the cream ground. */
.story-band__copy {
  margin: 0;
  padding: var(--sp-8) var(--gutter) var(--sp-10);
  font-family: var(--ff-display);
  font-style: italic;
  font-weight: var(--fw-light);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-heading);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* Reveal: gentle fade + scale-down (clip-path is for contained plates,
   not for a viewport-filling image). */
.js .story-band .story-band__media[data-reveal="settle"] > picture > img {
  opacity: 0;
  scale: 1.04;
  transition:
    opacity var(--dur-reveal) var(--ease-brand),
    scale   var(--dur-slow)   var(--ease-brand);
}
.js .story-band .story-band__media[data-reveal="settle"].is-revealed > picture > img {
  opacity: 1;
  scale: 1;
}
/* Copy reveal is handled by its own data-reveal="rise" observer. */

/* Parallax: oversized 200% (25% amp), translates with --py (motion.js).
   top: -50% and height: 200% are the exact values needed so the img
   covers the frame at both extremes of the ±25% translate.
   >=1200px only, never under reduced-motion. */
@media (min-width: 1200px) {
  .story-band .story-band__media.oz-plate[data-parallax] {
    --parallax-amp: 80;
  }
  .story-band .story-band__media.oz-plate[data-parallax] > picture > img {
    position: absolute;
    top: -80%;
    left: 0;
    width: 100%;
    height: 260%;
    transform: translate3d(0, var(--py, 0px), 0);
  }
  .story-band .story-band__media.oz-plate[data-parallax].is-parallax > picture > img {
    will-change: transform;
  }
}
@media (prefers-reduced-motion: reduce) {
  .story-band .story-band__media.oz-plate[data-parallax] > picture > img {
    top: 0;
    height: 100%;
    transform: none;
  }
}


/* --------------------------------------------------------------------------
   12.3 THE CENTRED STATEMENT  (this-is-us.html)
   Typography only, --w-narrow, between two foil rules. Built so that deleting
   the three flagged fragments leaves no hole: the rules and the verified
   paragraph keep their own rhythm without them.
   -------------------------------------------------------------------------- */
.statement { background: var(--c-bg); }
.statement__inner {
  max-width: var(--w-narrow);
  margin-inline: auto;
  text-align: center;
}
.statement__rule {
  width: 200px;
  max-width: 60%;
  margin-inline: auto;
}
.statement__lead {
  margin-block: var(--sp-7);
  font-family: var(--ff-display);
  font-weight: var(--fw-light);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--c-heading);
  text-wrap: pretty;
}
.statement__flags {
  list-style: none;
  margin: 0 0 var(--sp-7);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.statement__flag {
  font-family: var(--ff-ui);
  font-weight: var(--fw-light);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--c-text-muted);      /* 6.69:1 on --c-bg */
}


/* --------------------------------------------------------------------------
   12.4 THE ROOM  (this-is-us.html) — three dark bleeds at 3:4, no arches, no
   captions. Three-across above 768px, a scroll-snap rail below it. Every cell
   stays under the ~540px CSS box these ~1080px files can carry (§E).
   -------------------------------------------------------------------------- */
.room { background: var(--c-bg); padding-block: var(--sp-section); }
.room__rail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--grid-gap);
}
.room__cell { min-width: 0; }

@media (max-width: 767px) {
  .room__rail {
    grid-auto-flow: column;
    grid-auto-columns: 78vw;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    padding-inline: var(--gutter);
    scrollbar-width: none;
  }
  .room__rail::-webkit-scrollbar { display: none; }
  .room__cell { scroll-snap-align: center; }
}
@media (min-width: 768px) {
  .room__rail {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    max-width: calc(var(--w-wide) + var(--gutter) * 2);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }
}


/* --------------------------------------------------------------------------
   12.5 FIND US / HOURS  (visit.html) — the phase-1 two-column block, kept.
   -------------------------------------------------------------------------- */
.visit-find { background: var(--c-bg); }
.visit-find__row { row-gap: var(--sp-section); }
.visit-find__col { grid-column: 1 / -1; min-width: 0; }
@media (min-width: 768px) {
  .visit-find__col:first-child { grid-column: 1 / span 4; }
  .visit-find__col:last-child  { grid-column: 5 / -1; }
}
@media (min-width: 1200px) {
  .visit-find__col:first-child { grid-column: 1 / span 5; }
  .visit-find__col:last-child  { grid-column: 7 / span 6; }
}

.visit-find__address {
  margin-top: var(--sp-5);
  font-family: var(--ff-display);
  font-style: normal;
  font-weight: var(--fw-light);
  font-size: var(--fs-h3);
  line-height: var(--lh-heading);
  color: var(--c-text);
}
/* The address itself is a Google Maps directions link (same treatment as
   the footer's address line). Covers the map-panel address too. */
.visit-find__address a,
.oz-map__address a {
  color: var(--c-accent);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-brand);
}
.visit-find__address a:hover,
.visit-find__address a:focus-visible,
.oz-map__address a:hover,
.oz-map__address a:focus-visible { color: var(--c-accent-hover); }
.visit-find__contact {
  list-style: none;
  margin: var(--sp-6) 0 0;
  padding: 0;
}
.visit-find__contact-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
  border-top: var(--rule);
  font-size: var(--fs-sm);
}
.visit-find__contact-row:last-child { border-bottom: var(--rule); }
.visit-find__contact-label {
  font-family: var(--ff-ui);
  letter-spacing: var(--ls-ui);
  color: var(--c-text-muted);
}
.visit-find__contact-row a {
  font-family: var(--ff-ui);
  color: var(--c-accent);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-brand);
}
.visit-find__contact-row a:hover,
.visit-find__contact-row a:focus-visible { color: var(--c-accent-hover); }

.visit-hours__rule { margin-top: var(--sp-5); }
/* These two sentences were set as "engraving" — Poiret One, 11px, uppercase,
   0.25em tracking. That is 40+ characters of all-caps carrying the two most
   operationally important facts on the page: when breakfast stops, and that
   there are no reservations. All-caps removes word-shape, and wide tracking
   at 11px removes what is left; someone scanning for "do they take a booking"
   should not have to decode it. The engraved feel now comes from the foil rule
   above and the deco marker, and the sentence itself is simply readable. */
.visit-hours__note {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-ui);
  line-height: var(--lh-body);
  color: var(--c-text);            /* 11.95:1 — these are facts, not fine print */
}
/* The deco register survives as a hairline marker rather than as the words. */
.visit-hours__note::before {
  content: "";
  flex: none;
  width: 12px;
  height: 10px;
  transform: translateY(-0.15em);
  background-color: var(--c-accent);
  -webkit-mask-image: var(--micro-arch);
          mask-image: var(--micro-arch);
  -webkit-mask-size: contain;  mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
}


/* --------------------------------------------------------------------------
   12.6 THE MAP GROUND  (visit.html). The panel itself, its arched top edge
   and the opening transition all live in components.css — this only supplies
   the ground and the candle layer behind it.
   -------------------------------------------------------------------------- */
.visit-map { background: var(--c-bg); }


/* --------------------------------------------------------------------------
   12.7 THE ONE IMAGE  (visit.html)
   room-wide is a 1080px file, so CRAFT-FLOOR §E forbids a CSS box wider than
   ~540px — it never becomes the full-bleed band the art direction sketched,
   because §E overrides. The tall frame is also what crops the blue neon out:
   the neon appears exactly ONCE on the site, on this-is-us.
   -------------------------------------------------------------------------- */
/* This shipped flush against the left viewport edge with two thirds of the row
   empty beside it, because components.css loads later and its .oz-bleed margin
   beat `margin-inline: auto`. It was also cropped 1:2 — a vertical sliver of a
   4:5 photograph, which threw away most of the room it was meant to show. Match
   the file's own ratio, and win the cascade on specificity rather than
   !important. §E still caps a 1080px-wide source at a ~540px box. */
/* The map runs edge to edge and mounts on load. Full-bleed is done by dropping
   the container's constraints rather than with `width: 100vw`, which counts the
   scrollbar and would put a horizontal scroll on the page. The section keeps
   its own vertical rhythm; only the map escapes horizontally. */
.visit-map > .container {
  max-width: none;
  padding-inline: 0;
}
.visit-map .oz-map {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  aspect-ratio: auto;          /* height is driven, not the ratio */
  max-height: none;
  height: clamp(24rem, 72vh, 46rem);
  border-radius: 0;            /* an arch cap would clip Google's own controls */
}
/* The arched top edge survives as a 1px rule drawn OVER the map (map.js adds
   it), so the brand still frames the tool without degrading it. */

/* 12.7 was "the one image" — the visit-room photograph after the map. The
   client asked for it to be removed (2026-09-07): the map now ends the page's
   photographic run, and instagram follows directly. The §12.7 rules and the
   markup left with it. */


/* --------------------------------------------------------------------------
   12.8 REDUCED MOTION for this block — movement out, opacity kept gentle.
   The final state is always reachable; the band's clip simply is not animated.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .js .story-band .story-band__media[data-reveal="settle"] > picture > img {
    clip-path: none;
  }
}
