/* ===========================================================================
   KALTRAUM
   Single stylesheet, no build step, no framework.
   Order: tokens, reset, primitives, layout, components, motion, responsive.
   Design system doc lives at /notes/02-design-system.md.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   1. TOKENS
   Light porcelain page, like the white-tiled counter. Anything laid over dark
   photography (image captions, the 404 backdrop) takes .on-dark,
   which swaps the same tokens back to the dark set, so components never need
   to know which surface they sit on.
   --------------------------------------------------------------------------- */
:root {
  color-scheme: light;
  /* the film rail's colours (assets/js/film-rail.js): the cold blue on the pale room */
  --rail-accent: var(--accent);
  --rail-glass: var(--bg);
  --rail-ink: var(--text);
  --rail-font: var(--mono);

  /* Colour. Ratios measured against --bg, worst case against --bg-deep. */
  --bg:           #F3F5F7;   /* page                                    */
  --bg-raised:    #FFFFFF;   /* raised surfaces, fields                 */
  --bg-deep:      #E6EAEE;   /* footer well, marquee band               */
  --text:         #111418;   /* primary text, primary button fill 16.9:1 */
  --text-2:       #4B545D;   /* secondary text                    7.1:1 */
  --text-3:       #5A636C;   /* recessive but still informational  5.1:1 */
  --decor:        #A7AFB7;   /* DECORATION ONLY. Fails AA by design. */
  --hairline:     rgba(17, 20, 24, .12);
  --hairline-str: rgba(17, 20, 24, .24);
  --accent:       #2442D6;   /* accent                            6.2:1 */
  --accent-deep:  #1B35B5;   /* accent fill under white           9.5:1 */
  --error:        #B3261E;   /*                                   5.4:1 */

  /* Type */
  --sans: 'Archivo', system-ui, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, monospace;

  --fs-display: clamp(2.75rem, 7vw, 5.5rem);
  --fs-h2:      clamp(2rem, 4.2vw, 3.25rem);
  --fs-h3:      clamp(1.25rem, 2vw, 1.5rem);
  --fs-body:    1.0625rem;
  --fs-small:   .875rem;
  --fs-label:   .75rem;

  /* Space. Only these values are permitted. */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;  --s5: 24px;
  --s6: 32px;  --s7: 48px;  --s8: 64px;  --s9: 96px;  --s10: 128px;

  --gutter:  clamp(1.25rem, 5vw, 4rem);
  --section: clamp(3rem, 6.5vw, 6rem);
  --maxw:    1280px;

  /* Shape. One radius for every rectangle on the site. */
  --r: 2px;

  /* Motion. Slow, weighted, one thing at a time. Nothing bounces. */
  --ease: cubic-bezier(.22, 1, .36, 1);
  --t-fast: 200ms;
  --t-mid:  450ms;
  --t-slow: 800ms;

  /* The nav must sit ABOVE the mobile panel, otherwise the full-screen panel
     covers its own close button and a touch user (who has no Escape key) can
     only dismiss it by tapping a link. */
  --z-grain: 60;
  --z-nav:   55;
  --z-panel: 50;
}

/* The original dark set, for surfaces laid over dark photography. */
.on-dark, .focus__tile figcaption {
  color-scheme: dark;
  color: var(--text);
  --bg:           #0D0F12;
  --bg-raised:    #14181D;
  --text:         #F2F4F6;
  --text-2:       #9AA3AC;
  --text-3:       #7E868E;
  --decor:        #5E666E;
  --hairline:     rgba(242, 244, 246, .12);
  --hairline-str: rgba(242, 244, 246, .22);
  --accent:       #6B86FF;
  --error:        #FF8D8D;
}

/* ---------------------------------------------------------------------------
   2. RESET
   --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-variation-settings: 'wdth' 100, 'wght' 400;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, picture, video { display: block; max-width: 100%; }
img { height: auto; background: var(--bg-raised); }

h1, h2, h3 { margin: 0; line-height: 1.08; text-wrap: balance; }
p  { margin: 0; }
ul, ol, dl, dd { margin: 0; padding: 0; list-style: none; }

a { color: inherit; text-decoration: none; }
button, input, textarea { font: inherit; color: inherit; }

/* One visible focus treatment everywhere. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r);
}

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

.skip-link {
  position: absolute; left: var(--s4); top: -100px; z-index: 100;
  background: var(--text); color: var(--bg);
  padding: var(--s3) var(--s5); border-radius: var(--r); font-weight: 600;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: var(--s4); }

/* ---------------------------------------------------------------------------
   3. PRIMITIVES
   --------------------------------------------------------------------------- */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.wrap--narrow { max-width: 640px; }

/* Every section shares one rhythm so spacing cannot drift between them. */
.section { padding-block: var(--section); }

/* The nav is fixed, so anchor jumps would otherwise land with the heading
   hidden underneath it. Reserve its height plus the gap above it. */
[id] { scroll-margin-top: 104px; }
#hero, #main, #top-sentinel { scroll-margin-top: 0; }

h2 {
  font-size: var(--fs-h2);
  font-variation-settings: 'wdth' 88, 'wght' 700;
  letter-spacing: -.02em;
  max-width: 18ch;
}

/* Rationed. Three on the page, none adjacent. */
.label {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--text-2);
  margin-bottom: var(--s5);
  font-variation-settings: 'wdth' 100, 'wght' 500;
}

.prose p { color: var(--text-2); max-width: 62ch; }
.prose p + p { margin-top: var(--s5); }

/* Mono is for numerals only, never prose. */
.num { font-family: var(--mono); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* Buttons. Squared like everything else. No pills. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s3);
  padding: var(--s4) var(--s6);
  border: 1px solid transparent; border-radius: var(--r);
  font-size: var(--fs-small); font-variation-settings: 'wdth' 100, 'wght' 600;
  letter-spacing: .01em; white-space: nowrap; cursor: pointer;
  transition: background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.btn--solid { background: var(--text); color: var(--bg); }
.btn--solid:hover { background: var(--accent-deep); color: #fff; }
.btn--ghost { border-color: var(--hairline-str); color: var(--text); }
.btn--ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn:active { transform: translateY(1px); }
.btn--block { width: 100%; }

.iconbtn {
  width: 44px; height: 44px; display: grid; place-items: center;
  background: none; border: 1px solid var(--hairline); border-radius: 50%;
  cursor: pointer; transition: border-color var(--t-fast) var(--ease),
                               background var(--t-fast) var(--ease);
}
.iconbtn:hover { border-color: var(--accent); background: rgba(36, 66, 214, .06); }
.iconbtn svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; }

/* The dotted leader, shared by the menu and the opening hours so they rhyme. */
.item__leader {
  flex: 1 1 auto; min-width: var(--s5); align-self: center; height: 1px;
  margin-inline: var(--s3);
  background-image: radial-gradient(circle, var(--decor) .8px, transparent .9px);
  background-size: 6px 1px; background-repeat: repeat-x;
  opacity: .7;
}

/* ---------------------------------------------------------------------------
   4. NAVIGATION
   --------------------------------------------------------------------------- */
#top-sentinel { position: absolute; top: 0; height: 1px; width: 1px; }

.nav {
  position: fixed; inset: var(--s4) 0 auto; z-index: var(--z-nav);
  padding-inline: var(--gutter);
}
.nav__inner {
  max-width: var(--maxw); margin-inline: auto; height: 64px;
  display: flex; align-items: center; gap: var(--s6);
  padding-inline: var(--s5);
  border: 1px solid var(--hairline); border-radius: var(--r);
  /* Always frosted: the hero footage has a black glove and steel exactly where
     the links sit, so a transparent nav is unreadable on some frames. */
  background: rgba(243, 245, 247, .84);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  transition: box-shadow var(--t-mid) var(--ease);
}
/* Set by script when the top sentinel leaves the viewport. */
.nav.is-stuck .nav__inner { box-shadow: 0 12px 32px rgba(17, 20, 24, .08); }

.nav__brand {
  font-variation-settings: 'wdth' 84, 'wght' 700;
  font-size: 1.0625rem; letter-spacing: .04em; text-transform: uppercase;
}
.nav__links { display: flex; gap: var(--s6); margin-left: auto; }
.nav__links a {
  font-size: var(--fs-small); color: var(--text-2);
  transition: color var(--t-fast) var(--ease);
}
.nav__links a:hover { color: var(--text); }
.nav__cta { padding: var(--s3) var(--s5); }

.nav__toggle {
  display: none; width: 44px; height: 44px; margin-left: auto;
  background: none; border: 0; cursor: pointer;
}
.nav__bars { display: block; position: relative; width: 22px; height: 12px; margin-inline: auto; }
.nav__bars i {
  position: absolute; left: 0; width: 100%; height: 1.5px;
  background: var(--text);
  transition: transform var(--t-mid) var(--ease);
}
.nav__bars i:first-child { top: 0; }
.nav__bars i:last-child  { bottom: 0; }
/* Two bars rotate about a shared centre to form the X. */
.is-open .nav__bars i:first-child { transform: translateY(5.25px) rotate(45deg); }
.is-open .nav__bars i:last-child  { transform: translateY(-5.25px) rotate(-45deg); }

.navpanel {
  position: fixed; inset: 0; z-index: var(--z-panel);
  background: rgba(243, 245, 247, .97);
  -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px);
  display: grid; place-items: center;
}
.navpanel[hidden] { display: none; }
.navpanel__links { display: grid; gap: var(--s5); text-align: center; }
.navpanel__links a {
  font-size: clamp(1.75rem, 8vw, 2.5rem);
  font-variation-settings: 'wdth' 88, 'wght' 700;
  letter-spacing: -.02em;
  opacity: 0; transform: translateY(18px);
  animation: panel-in var(--t-mid) var(--ease) forwards;
}
.navpanel__links a:nth-child(1) { animation-delay: 40ms; }
.navpanel__links a:nth-child(2) { animation-delay: 100ms; }
.navpanel__links a:nth-child(3) { animation-delay: 160ms; }
.navpanel__links a:nth-child(4) { animation-delay: 220ms; }
.navpanel__links a:nth-child(5) { animation-delay: 280ms; }
@keyframes panel-in { to { opacity: 1; transform: none; } }

/* ---------------------------------------------------------------------------
   5. HERO
   The stage owns its height, so the media never causes layout shift.
   --------------------------------------------------------------------------- */
.hero { position: relative; background: var(--bg); }
.hero__stage {
  position: relative;
  min-height: 100dvh;           /* never 100vh: iOS Safari jumps on it */
  display: flex; align-items: flex-end;
  padding-block: var(--s9) var(--s8);
  padding-top: min(var(--s9), 6rem);   /* hero top padding cap */
  overflow: hidden;             /* on the stage, not .hero: it would break sticky */
}
/* Scroll scrub, set by script only when the video will load. The section
   grows to give the scroll some distance and the stage pins while the video
   runs raw to cooked. --p is scroll progress 0..1, written by the script; the
   copy and scrim clear out in the first fifth so the cooking is unobstructed. */
.hero.is-scrub { height: 300vh; }
.hero.is-scrub .hero__stage { position: sticky; top: 0; height: 100dvh; }
.hero.is-scrub .hero__body,
.hero.is-scrub .hero__scrim { opacity: calc(1 - var(--p, 0) * 5); }
.hero.is-cooking .hero__body { visibility: hidden; }   /* no invisible click targets */

.hero__media { position: absolute; inset: 0; }
.hero__still, .hero__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 55%;
}
/* The video fades in over the still once its first frame is decoded. Frame 0
   is the still, so the fade is invisible when it works. */
.hero__video { opacity: 0; transition: opacity var(--t-slow) var(--ease); }
.hero__video.is-playing { opacity: 1; }

/* A gradient, never a flat tint. The footage is bright (white tiles, daylight),
   so the scrim fades the image into the page colour behind the copy rather
   than darkening it, and the copy is dark like the rest of the page. */
.hero__scrim {
  position: absolute; inset: 0;
  background:
    /* Anchored to the copy's own corner rather than to the viewport, so it
       follows the copy on short landscape viewports where the text sits high. */
    radial-gradient(120% 110% at 4% 96%,
      rgba(243, 245, 247, .95) 0%,
      rgba(243, 245, 247, .86) 32%,
      rgba(243, 245, 247, .40) 58%,
      rgba(243, 245, 247, 0) 78%),
    linear-gradient(to right,
      rgba(243, 245, 247, .90) 0%,
      rgba(243, 245, 247, .72) 30%,
      rgba(243, 245, 247, .20) 55%,
      rgba(243, 245, 247, 0) 70%);
}
/* Saves glyph edges over any stray dark detail. Invisible on the page colour. */
.hero__title, .hero__sub, .hero .label { text-shadow: 0 1px 16px rgba(243, 245, 247, .7); }

/* Anchored to the left gutter, not the centred 1280 column: at 1920 the
   centred column put the headline on top of the patty. */
.hero__body { position: relative; max-width: none; }
.hero__title {
  font-size: var(--fs-display);
  font-variation-settings: 'wdth' 86, 'wght' 700;
  letter-spacing: -.03em;
  max-width: 14ch;
  margin-bottom: var(--s5);
}
.hero__sub {
  color: var(--text); opacity: .92;
  max-width: 46ch; margin-bottom: var(--s7);
}
.hero__cta { display: flex; gap: var(--s4); flex-wrap: wrap; }

/* ---------------------------------------------------------------------------
   6. MARQUEE
   The mask is the whole point. Without it the band reads as broken overflow.
   --------------------------------------------------------------------------- */
.strip { border-block: 1px solid var(--hairline); background: var(--bg-deep); }
.marquee {
  overflow: hidden; padding-block: var(--s5);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 12%, #000 88%, transparent);
}
.marquee__track { display: flex; width: max-content; animation: marquee 44s linear infinite; }
.marquee:hover .marquee__track,
.marquee:focus-within .marquee__track { animation-play-state: paused; }
.marquee__list { display: flex; align-items: center; }
.marquee__list li {
  display: flex; align-items: center; white-space: nowrap;
  font-size: var(--fs-small); color: var(--text-2);
  padding-inline: var(--s6);
}
/* A hairline, not a decorative dot. */
.marquee__list li::after {
  content: ''; width: 1px; height: 14px; margin-left: var(--s6);
  background: var(--hairline-str);
}
@keyframes marquee { to { transform: translateX(-50%); } }

/* ---------------------------------------------------------------------------
   7. STORY
   --------------------------------------------------------------------------- */
.story__grid { display: grid; gap: var(--s8); }
.story__text .prose { margin-top: var(--s6); }
.story__figure img { width: 100%; border-radius: var(--r); }
.story__figure figcaption {
  margin-top: var(--s4); font-size: var(--fs-small); color: var(--text-3);
}
.facts {
  display: grid; gap: var(--s5);
  margin-top: var(--s8); padding-top: var(--s6);
  border-top: 1px solid var(--hairline);
}
.facts > div { display: flex; align-items: baseline; gap: var(--s4); }
.facts dt {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .14em;
  color: var(--text-3); min-width: 8ch;
}
.facts dd { font-size: var(--fs-h3); font-variation-settings: 'wdth' 92, 'wght' 500; }
/* Mono puts a full cell around the decimal point, so large figures need the
   tracking pulled in or "1.5" reads as "1 . 5". */
.facts .num { color: var(--accent); margin-right: var(--s1); letter-spacing: -.06em; }

/* ---------------------------------------------------------------------------
   8. MENU
   Leader lines, sparse grouping, no hairline under every row.
   --------------------------------------------------------------------------- */
/* Burgers run full width in two columns; sides and drinks pair beneath. An
   even 4 / 4+4 split left a tall void under the shorter column. */
.menu__burgers { margin-top: var(--s8); }
.menu__burgers .list { display: grid; gap: var(--s6); }
.menu__cols { display: grid; gap: var(--s9); margin-top: var(--s9); }
.menu__heading {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .18em;
  color: var(--text-3); padding-bottom: var(--s4);
  border-bottom: 1px solid var(--hairline); margin-bottom: var(--s6);
}
.menu__heading--gap { margin-top: var(--s9); }
.list { display: grid; gap: var(--s6); }
.item__row { display: flex; align-items: baseline; }
.item__name { font-size: var(--fs-h3); font-variation-settings: 'wdth' 94, 'wght' 500; }
.item__price { font-size: 1rem; color: var(--text); }
.item__desc { margin-top: var(--s2); font-size: var(--fs-small); color: var(--text-2); max-width: 54ch; }
/* One marked item on the page. More than two and the signal is gone. */
.item--mark .item__name { color: var(--accent); }
.item--mark .item__leader { background-image: radial-gradient(circle, var(--accent) .8px, transparent .9px); opacity: .5; }
.menu__note { margin-top: var(--s8); font-size: var(--fs-small); color: var(--text-3); }

/* ---------------------------------------------------------------------------
   9. FOCUS GALLERY
   Hovering one tile dims the others and pushes its own photograph in a little.
   The container carries the dim, so that half stays two rules.
   --------------------------------------------------------------------------- */
.focus { display: grid; gap: var(--s5); margin-top: var(--s8); }
.focus__tile { margin: 0; position: relative; border-radius: var(--r); overflow: hidden; }
.focus__tile img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }
.focus__tile figcaption {
  position: absolute; inset: auto 0 0; padding: var(--s6) var(--s5) var(--s5);
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s4);
  background: linear-gradient(to top, rgba(8, 10, 12, .92), rgba(8, 10, 12, 0));
}
.focus__name  { font-variation-settings: 'wdth' 94, 'wght' 600; font-size: 1rem; }
.focus__price { font-size: var(--fs-small); color: var(--text-2); }

/* Gated on hover capability: on touch there is no hover, and a stuck dim is a bug. */
@media (hover: hover) {
  /* These tiles are also .reveal, and section 16 gives .js .reveal a transition
     on opacity at the slow token. That selector outweighs .focus__tile, and
     transition is a shorthand, so a dim written as opacity here would quietly
     run at 800ms. Dimming through filter alone keeps the two off the same
     property, and the rule below restates the entrance unchanged while adding
     filter at the fast token, so the dim is quick in both directions and the
     reveal is untouched. */
  .focus__tile { transition: filter var(--t-fast) var(--ease); }
  .js .focus__tile.reveal {
    transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease),
                filter var(--t-fast) var(--ease);
  }
  .focus:hover .focus__tile:not(:hover) { filter: saturate(.6) opacity(.55); }
  .focus:focus-within .focus__tile:not(:focus-visible) { filter: saturate(.6) opacity(.55); }

  /* The tile clips and the caption is absolute, so only the photograph moves,
     and it stays inside the corner radius. */
  .focus__tile img { transition: transform var(--t-mid) var(--ease); }
  .focus__tile:hover img, .focus__tile:focus-visible img { transform: scale(1.06); }
}

/* ---------------------------------------------------------------------------
   10. MASONRY
   Varied aspect ratios are the point. Six images in four shapes fill a grid
   that would look thin with eight identical ones.
   --------------------------------------------------------------------------- */
/* Native multi-column rather than grid spans. A grid needs its cell count to
   divide by its column count or it leaves a hole; these seven images in four
   different aspect ratios do not divide by four. Columns just flow, so every
   image keeps its own shape and nothing is left empty. */
.masonry { column-count: 1; column-gap: var(--s5); margin-top: var(--s8); }
.tile {
  margin: 0 0 var(--s5); border-radius: var(--r); overflow: hidden;
  break-inside: avoid; -webkit-column-break-inside: avoid;
}
.tile img { width: 100%; height: auto; }

/* ---------------------------------------------------------------------------
   11. QUOTE DECK
   One card active, the rest stacked behind with decreasing scale.
   --------------------------------------------------------------------------- */
.deck { margin-top: var(--s8); max-width: 720px; }
.deck__stage { position: relative; min-height: 260px; }
/* The active card is opaque and sits on top; the others peek out above it.
   z-index is load-bearing here: the stacked cards are absolutely positioned
   and come after the in-flow active card, so without it they paint over it. */
.quote {
  position: absolute; inset: 0; margin: 0; z-index: 1;
  background: var(--bg-raised); border: 1px solid var(--hairline); border-radius: var(--r);
  padding: var(--s7);
  opacity: 0; pointer-events: none;
  transform: translateY(-14px) scale(.94);
  transition: opacity var(--t-mid) var(--ease), transform var(--t-mid) var(--ease);
}
.quote.is-active   { opacity: 1; transform: none; pointer-events: auto; position: relative; z-index: 4; }
.quote.is-behind-1 { opacity: .55; transform: translateY(-10px) scale(.965); z-index: 3; }
.quote.is-behind-2 { opacity: .28; transform: translateY(-20px) scale(.93);  z-index: 2; }
.quote blockquote {
  margin: 0; font-size: var(--fs-h3); line-height: 1.5;
  font-variation-settings: 'wdth' 98, 'wght' 400;
}
.quote figcaption {
  margin-top: var(--s6); padding-top: var(--s5);
  border-top: 1px solid var(--hairline);
  display: grid; gap: var(--s1);
}
.quote__name { font-size: var(--fs-small); font-variation-settings: 'wdth' 100, 'wght' 600; }
.quote__role { font-size: var(--fs-small); color: var(--text-3); }

.deck__controls { display: flex; align-items: center; gap: var(--s4); margin-top: var(--s6); }
.deck__dots { display: flex; align-items: center; gap: var(--s2); }
.deck__dots span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--text-3);
  transition: width var(--t-mid) var(--ease), background var(--t-mid) var(--ease);
}
.deck__dots span.is-on { width: 22px; border-radius: 3px; background: var(--accent); }

/* ---------------------------------------------------------------------------
   12. VISIT
   Inset panel with the headline crossing the image edge. That single overlap
   is most of the perceived production value.
   --------------------------------------------------------------------------- */
.panel { position: relative; }
.panel__media { margin: 0; border-radius: var(--r); overflow: hidden; position: relative; }
.panel__media img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
/* The photo is a night shot, so the dark headline crossing its edge needs the
   page colour under it: the bottom of the image dissolves into the page. */
.panel__media::after {
  content: ''; position: absolute; inset: auto 0 0; height: 32%;
  background: linear-gradient(to top, var(--bg) 18%, rgba(243, 245, 247, 0));
}
.panel__title {
  position: relative;
  margin-top: -.55em;          /* pulls the type up across the panel edge */
  margin-left: var(--s6);
  font-size: var(--fs-h2);
  font-variation-settings: 'wdth' 86, 'wght' 700;
  letter-spacing: -.02em;
  max-width: none;
}
.visit__cols { display: grid; gap: var(--s8); margin-top: var(--s9); }
.visit__sub {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .18em;
  color: var(--text-3); padding-bottom: var(--s4);
  border-bottom: 1px solid var(--hairline); margin-bottom: var(--s6);
}
.visit__address { font-style: normal; font-size: var(--fs-h3); font-variation-settings: 'wdth' 94, 'wght' 500; }
.visit__lines { margin-top: var(--s5); color: var(--text-2); }
.visit__lines a { border-bottom: 1px solid var(--hairline); transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); }
.visit__lines a:hover { color: var(--accent); border-color: var(--accent); }
.visit__note { margin-top: var(--s6); font-size: var(--fs-small); color: var(--text-3); max-width: 42ch; }

.hours > div { display: flex; align-items: baseline; padding-block: var(--s3); }
.hours dt { color: var(--text-2); font-size: var(--fs-small); }
.hours dd { font-size: var(--fs-small); color: var(--text); }
/* Marked by script. Without JS the row is simply unmarked, never blank. */
.hours > div.is-today dt,
.hours > div.is-today dd { color: var(--accent); }
.hours > div.is-today dt::after {
  content: ' (today)'; font-size: var(--fs-label); letter-spacing: .08em; text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   13. FORM
   Label above input, always. Never placeholder-as-label.
   --------------------------------------------------------------------------- */
.table__lede { margin-top: var(--s5); color: var(--text-2); max-width: 52ch; }
.form { margin-top: var(--s8); display: grid; gap: var(--s6); }
.field { display: grid; gap: var(--s2); }
.field label { font-size: var(--fs-small); font-variation-settings: 'wdth' 100, 'wght' 600; }
.field input, .field textarea {
  width: 100%; padding: var(--s4);
  background: var(--bg-raised);
  border: 1px solid var(--hairline-str); border-radius: var(--r);
  transition: border-color var(--t-fast) var(--ease);
}
.field input:hover, .field textarea:hover { border-color: var(--decor); }
.field textarea { resize: vertical; min-height: 96px; }
.field__help { font-size: var(--fs-small); color: var(--text-2); }
.field__err { font-size: var(--fs-small); color: var(--error); }
.field__err[hidden] { display: none; }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: var(--error); }
.field-row { display: grid; gap: var(--s6); }
.form__status { font-size: var(--fs-small); color: var(--accent); min-height: 1.5em; }

/* ---------------------------------------------------------------------------
   14. FOOTER
   Link columns, then the wordmark cropped by the bottom of the page.
   --------------------------------------------------------------------------- */
/* No margin-top: the preceding section already ends on a full section pad, and
   adding another put a ~400px void above the footer. */
.footer {
  background: var(--bg-deep); border-top: 1px solid var(--hairline);
  padding-top: var(--section);
  overflow: hidden;
}
.footer__grid { display: grid; gap: var(--s8); }
.footer__brand {
  font-size: var(--fs-h3); font-variation-settings: 'wdth' 84, 'wght' 700;
  text-transform: uppercase; letter-spacing: .06em; max-width: none;
}
.footer__line { margin-top: var(--s3); color: var(--text-2); font-size: var(--fs-small); max-width: 32ch; }
.footer__col h3 {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: .18em;
  color: var(--text-3); margin-bottom: var(--s5);
}
.footer__col ul { display: grid; gap: var(--s3); }
.footer__col a, .footer__col address, .footer__hours {
  font-size: var(--fs-small); color: var(--text-2); font-style: normal;
}
.footer__col a { transition: color var(--t-fast) var(--ease); }
.footer__col a:hover { color: var(--text); }
.footer__legal {
  display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--s4);
  margin-top: var(--s9); padding-top: var(--s5);
  border-top: 1px solid var(--hairline);
  font-size: var(--fs-small); color: var(--text-3);
}
.footer__legal a { transition: color var(--t-fast) var(--ease); }
.footer__legal a:hover { color: var(--text); }

/* The crop is the move. A wordmark that fits neatly reads as a logo; one that
   runs off the edge reads as a composition.
   The word lives in CSS rather than in the markup because it is pure
   decoration (WCAG 1.4.3 incidental text), which is also why its very low
   contrast is deliberate and correct here. */
.footer__mark::after { content: "Kaltraum"; }
.footer__mark {
  margin-top: var(--s7);
  font-size: clamp(4rem, 19vw, 17rem);
  line-height: .78;
  font-variation-settings: 'wdth' 82, 'wght' 800;
  letter-spacing: -.045em;
  text-transform: uppercase;
  color: var(--text);
  opacity: .06;
  text-align: center;
  user-select: none;
  margin-bottom: -.22em;   /* cropped by the bottom of the page */
}

/* ---------------------------------------------------------------------------
   15. GRAIN
   Fixed and non-interactive so it never repaints during scroll.
   --------------------------------------------------------------------------- */
.grain {
  position: fixed; inset: 0; z-index: var(--z-grain); pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------------------
   16. SCROLL REVEAL
   The hidden state is added by script, so if JS fails nothing is ever hidden.
   --------------------------------------------------------------------------- */
.js .reveal {
  opacity: 0; transform: translateY(22px);
  transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* ---------------------------------------------------------------------------
   17. RESPONSIVE
   Mobile first. Every multi-column layout declares its collapse.
   --------------------------------------------------------------------------- */
@media (min-width: 600px) {
  .field-row { grid-template-columns: 1fr 1fr; }
  .facts { grid-template-columns: repeat(3, auto); justify-content: start; gap: var(--s8); }
  .facts > div { display: block; }
  .facts dt { margin-bottom: var(--s2); }
  .masonry { column-count: 2; }
  .focus { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 900px) {
  .nav__toggle { display: none; }
  .story__grid { grid-template-columns: 7fr 5fr; gap: var(--s10); align-items: start; }
  .menu__burgers .list { grid-template-columns: 1fr 1fr; gap: var(--s7) var(--s10); }
  .menu__cols  { grid-template-columns: 1fr 1fr; gap: var(--s10); }
  .visit__cols { grid-template-columns: 5fr 7fr; gap: var(--s10); }
  .footer__grid { grid-template-columns: 2fr 1fr 1fr 1.2fr; }
  .masonry { column-count: 3; }
  .panel__media img { aspect-ratio: 21 / 9; }
  .panel__title { margin-left: var(--s9); }
}

@media (min-width: 1100px) {
  .focus { grid-template-columns: repeat(4, 1fr); }
}

/* Below 900px the links collapse into the full-screen panel. */
@media (max-width: 899px) {
  .nav__links, .nav__cta { display: none; }
  .nav__toggle { display: block; }
  .hero { align-items: flex-end; }
  .panel__title { margin-left: var(--s4); margin-top: -.4em; }
}

/* Portrait phones and tablets: the copy sits in the lower half, over the dark
   griddle, so the fade runs up from the bottom. It clears as the scrub starts.
   Landscape phones keep the corner-anchored base scrim. */
@media (max-width: 899px) and (orientation: portrait) {
  /* The press and the meat fill the middle of the portrait clip, which is
     exactly where the copy sits. While the copy is up, the footage is lifted so
     the subject clears it, then settles back to full-bleed as the copy fades.
     The band it uncovers at the bottom is page colour, same as the scrim. */
  .hero__media { transform: translateY(calc(var(--lift, 24%) * (min(1, var(--p, 0) * 5) - 1))); }
  .hero__scrim {
    background: linear-gradient(to top,
      rgba(243, 245, 247, 1) 0%,       /* solid, so the lifted edge is invisible */
      rgba(243, 245, 247, .92) 22%,
      rgba(243, 245, 247, .50) 32%,
      rgba(243, 245, 247, 0) 42%);
  }
}

/* Short landscape viewports, phones on their side. The stage is pinned at
   100dvh while scrubbing, so the copy has to fit inside it, below the nav. */
@media (max-height: 500px) {
  .hero__stage { padding-block: var(--s9) var(--s5); }
  .hero__title { font-size: 12vh; }
  .hero__sub { display: none; }
  /* The frame is so wide that the press reaches the end of the label. */
  .hero__scrim {
    background: linear-gradient(to right,
      rgba(243, 245, 247, .94) 0%,
      rgba(243, 245, 247, .86) 36%,
      rgba(243, 245, 247, .30) 54%,
      rgba(243, 245, 247, 0) 66%);
  }
}

/* Short portrait phones: with the subline the copy reaches up into the meat
   even with the footage lifted. The headline and buttons carry the hero. */
@media (max-width: 899px) and (orientation: portrait) and (max-height: 700px) {
  .hero__sub { display: none; }
}

/* Body scroll lock while the mobile panel is open. */
body.is-locked { overflow: hidden; }

/* ---------------------------------------------------------------------------
   18. REDUCED MOTION
   No pan, no marquee, no grain, no reveals, no video. The page stays fully
   usable and loses nothing but movement.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .marquee__track { animation: none; transform: none; }
  .marquee { overflow-x: auto; -webkit-mask-image: none; mask-image: none; }
  .grain { display: none; }
  .js .reveal { opacity: 1; transform: none; }
  .focus__tile:hover img, .focus__tile:focus-visible img { transform: none; }
  .hero__video { display: none; }
  .navpanel__links a { opacity: 1; transform: none; animation: none; }
}

/* ---------------------------------------------------------------------------
   19. PRINT
   People print menus. Give them something usable.
   --------------------------------------------------------------------------- */
@media print {
  .nav, .navpanel, .grain, .hero__media, .strip, .masonry, .focus,
  .deck__controls, .form, .footer__mark { display: none !important; }
  body { background: #fff; color: #000; }
  .section { padding-block: 1rem; }
  a { text-decoration: underline; }
}
