/* TEUF — one house, three rooms.
   Fluid throughout: clamp() for type and rhythm, one structural breakpoint. */

@import url('https://fonts.googleapis.com/css2?family=Archivo:wdth,wght@62..125,400..700&family=Caveat:wght@700&family=Prata&display=swap');

/* Bar Kutym's real typeface, licensed as a webfont from Fontspring on
   2026-09-18. Until then the desktop-only EULA forbade @font-face, which is why
   the wordmark is artwork and the lockup lines were set in Archivo.

   Self-hosted rather than imported: it is 28KB, it is the only face on the site
   that is not from Google Fonts, and it belongs to a licence rather than a CDN.

   swap, so the lockup is legible while it loads instead of invisible. */
@font-face {
  font-family: 'Tipton';
  src: url('../fonts/tipton-demi.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink: #17130E;
  --paper: #FAF8F3;
  --cream: #E8DCC6;
  --night: #0E0E0F;
  /* Teuf au Vin's ground. Rotated 60.5deg -> 50deg and lifted from C 0.076 to
     0.098 on 2026-09-18, because measurement showed it had drifted off the
     thing it is supposed to come from: the room's own hero photograph clusters
     at H 45 (54% of its chromatic pixels) and H 30 (23%), carrying C 0.082-0.085,
     while the page painted nothing outside H 60-86 and nothing above C 0.019
     except the accent. CLAUDE.md records the palette as "driven by the three
     venues' existing photographic worlds"; this executes that rather than
     overriding it. Lightness is unchanged, so contrast only improved:
     body 7.26 -> 7.38, accent 5.20 -> 5.29. */
  --oak: oklch(0.430 0.098 50);
  --light: #F4EFE4;

  --gutter: clamp(20px, 5vw, 88px);
  --section: clamp(48px, 7vw, 78px);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* ---- vertical rhythm ----
     Measured before this existed: every major section on a venue page
     rendered at exactly 78px top and bottom — events, feature, menu, about —
     so a dated one-off announcement arrived with the same ceremony as the
     menu, and nothing told the eye which was which. Three intervals instead
     of one: subdivisions inside a movement, ordinary sections, and the break
     between movements. --section keeps its existing value so nothing that
     already reads correctly moves. */
  --space-tight: clamp(26px, 3.4vw, 44px);
  --space-major: clamp(64px, 9vw, 116px);

  --eyebrow: clamp(9px, 0.7vw, 10px);
  --body: clamp(14px, 1.05vw, 15px);
  --lede: clamp(26px, 3.2vw, 38px);

  /* ---- action and state ----
     Not a new brand colour. Every token above sits on hue 60–92, and the three
     rooms' own photographs measure at 70° and 91° — so this is the palette's
     existing hue with the chroma it never used (the strongest token, --oak,
     reaches C 0.076; the photographs carry 0.14).

     Two lightness stops because no single value clears 4.5:1 on both the cream
     page and the near-black bands. Surfaces remap the role; they never restate
     the colour. */
  --accent-lo: oklch(0.47 0.13 66);   /* 6.6:1 on --paper */
  --accent-hi: oklch(0.85 0.13 84);   /* 11.6:1 on --ink, 5.2:1 on --oak */
  --warn-lo:   oklch(0.52 0.17 30);
  --warn-hi:   oklch(0.75 0.15 38);

  --accent: var(--accent-lo);
  --accent-on: var(--paper);          /* text on an accent fill */
  --warn: var(--warn-lo);
}

/* Every ground that is dark, or dark enough — the room whose page is dark,
   plus the bands that go dark inside the light ones.

   Teuf au Vin left this set on 2026-09-24 when its ground went from oak to
   cream. Leaving it here would have kept the light-on-dark accent on a light
   page, which is the wrong end of the accent scale and fails contrast. Its
   dark bands (.events, .latehour via --events-bg, .lightbox) are listed in
   their own right and still get it. */
.v-kutym,
.util, .hero, .events, .latehour, .onedish, .revealfoot, .navpanel, .lightbox {
  --accent: var(--accent-hi);
  --accent-on: var(--ink);
  --warn: var(--warn-hi);
}

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

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

  /* Stop the rubber-band. The document itself ends cleanly — measured 0px of
     slack below the footer at max scroll — so the bounce past the end was the
     browser's own overscroll, not stray layout. This also disables
     pull-to-refresh on Android Chrome, which is the trade. iOS Safari has only
     honoured this on the root since 16, so older iPhones will still bounce. */
  overscroll-behavior-y: none;
}

/* Smooth scrolling for anchors and for the scrollIntoView calls in js/.
   Declared as no-preference rather than blanket-on: someone who has asked
   their system for less motion gets the instant jump, which is the whole point
   of the setting. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  font-family: 'Archivo', system-ui, sans-serif;
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

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

/* Text selection shipped as the browser's blue on every page — a colour that
   belongs to no design system here. It now takes the room's own action colour,
   so it is correct on cream, on ink and on au Vin's brown without a per-room
   rule. */
::selection { background: var(--accent); color: var(--accent-on); }

a { color: inherit; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
/* On an accent-filled button the accent ring would be invisible, so the ring
   takes the button's own text colour instead. */
.btn:focus-visible { outline-color: currentColor; }

/* Visually hidden, still read aloud and still found by search. Used where a
   page genuinely has no text heading of its own — the home page is three
   photographs and three wordmarks. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Five pages had no <h1> at all: the room's name existed only as an <img alt>,
   so heading navigation landed on "Öppettider" and crawlers saw no subject.
   The wordmark now IS the heading, which costs nothing visually — but the
   browser's own h1 margin and weight would move things, so they are reset. */
.hero__h {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: 0;
}
h1.hero__tag {
  font-weight: inherit;
  margin-bottom: 0;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: var(--light); padding: 14px 20px;
  font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase; text-decoration: none;
}
.skip:focus { left: 0; }

/* ---------- shared atoms ---------- */

.eyebrow {
  font-size: var(--eyebrow);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  opacity: 0.55;
  margin: 0 0 clamp(16px, 2vw, 26px);
}

.label {
  font-size: clamp(10px, 0.9vw, 12px);
  letter-spacing: 0.32em;
  text-transform: uppercase;
}

.body-text {
  font-size: var(--body);
  line-height: 1.75;
  opacity: 0.7;
  margin: 0 0 15px;
  max-width: 62ch;
}

.btn {
  display: inline-block;
  font-size: clamp(11px, 0.9vw, 12px);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: clamp(15px, 1.6vw, 18px) clamp(24px, 2.6vw, 34px);
  text-decoration: none;
  border: 1px solid currentColor;
  background: transparent;
  cursor: pointer;
  transition: background 220ms var(--ease), color 220ms var(--ease);
}
/* Every button on this site is Boka bord, Boka chambré or Till kassan — one
   action family, so one treatment. --solid sits on light grounds and --ghost on
   dark ones, which is exactly the split the two accent stops already make.
   Both fill: an outline over a photograph borrows its contrast from whatever
   happens to be behind it, and that measured as low as 3.5:1. */
.btn--solid,
.btn--ghost { background: var(--accent); color: var(--accent-on); border-color: var(--accent); }

/* No booking URL yet, so js/booking.js routes these to email instead. They stay
   fully live and look unchanged — booking by mail is a real channel, not a
   degraded one, and a guest should not be able to tell the difference. The
   previous rule greyed them out in production only, which meant the build we
   reviewed was never the build guests got. */
.btn--solid:hover,
.btn--ghost:hover { background: transparent; color: var(--accent); border-color: var(--accent); }

/* ---------- the shop's buttons: one pair of tokens, flipped by ground ----------

   #0b090a on a light surface, and a light button on a dark one. Rather than
   writing that twice, --btn-bg / --btn-fg carry it and the dark grounds swap
   the pair. The rules below never change; only the tokens do.

   Set as its own tokens rather than by overriding --accent, which also drives
   the focus ring, ::selection and the open-now dot — redefining that here
   would quietly repaint all three. */
.shop-page {
  --btn-bg: #0b090a;
  --btn-fg: var(--paper);
}

/* Dark grounds inside the shop. .shophead carries --ink and holds the Varukorg
   button; .cart__panel is light and deliberately absent. */
.shop-page .shophead,
.shop-page .revealfoot,
.shop-page .navpanel {
  --btn-bg: var(--light);
  --btn-fg: #0b090a;
}

.shop-page .btn--solid {
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: 1px solid var(--btn-bg);
  /* matches .card's 4px, so a button inside a card shares its corner */
  border-radius: 4px;
}

.shop-page .btn--solid:hover {
  background: transparent;
  color: var(--btn-bg);
  border-color: var(--btn-bg);
}

/* .shophead__cart is declared with the rest of the shop header further down —
   it has to be, because that block would otherwise override this one on source
   order at equal specificity. It reads the same two tokens. */

.veil { position: absolute; inset: 0; }

/* ---------- home ---------- */

.home {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

.rooms {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.room {
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  text-decoration: none;
  transition: flex-grow 520ms var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.room img.bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Percentage of the room, not the viewport — the room is a third of the
   screen on desktop and the full width when stacked. */
/* ── The three marks, sized as one system ─────────────────────────────────
   Wherever the three wordmarks appear together, each surface sets `--mw` —
   TEUF's width — and the other two are derived from it. They are not three
   hand-tuned numbers per surface; they were, and they drifted.

   The multipliers come from measured ink, not from the files' boxes:

     TEUF        1024×567, ink fills the file.  Letterform height = 0.554 × w.
     Bar Kutym   4.68:1 vector, tight.          Letterform height = 0.214 × w.
     Teuf au Vin 361×374 after cropping.        Block height      = 1.036 × w.

   So equal letterform height puts Bar Kutym at 2.59 × TEUF's width. au Vin is
   a three-line stacked cluster rather than one line, so matching its total
   block to TEUF's single line would shrink its actual letters to a third. It
   is matched on ink instead — equal area of actual mark — which lands at 0.80.

   Two of these were wrong before rather than merely different. au Vin's PNG
   carried 42% transparent padding — asymmetric, 94px left against 166px right
   — so its CSS width described a box, not a mark: it rendered a third smaller
   than its number implied and sat off-centre in it. The file is now cropped to
   its ink, which is why au Vin's numbers here went *down* while the mark on
   screen got bigger. And Bar Kutym's multiplier was tuned when the mark was
   Corner Club's 7.8:1 serif lockup; the new one is 4.68:1, so the same width
   bought a third less height.

   Where a layout cannot afford 2.59 — the homepage gives each mark a third of
   the viewport — Bar Kutym is capped by the panel and does not reach parity.
   That is geometry, not a value to nudge: a 4.68:1 mark cannot be as tall as
   TEUF inside a 467px column without running past both edges. */
/* max-height is a guard, not a size: it binds only in the mobile bands, where
   the panel is short and wide. au Vin is nearly square, so at a width that
   matches the other two it ran into the role label beneath it. `contain` is
   what keeps that a scale-down rather than a squash — with an explicit width,
   a max-height alone distorts a replaced element rather than reducing both. */
.room__mark { position: relative; width: var(--mw); max-height: 72%; object-fit: contain; }
/* The padding is what stops the mark and the label colliding, not the guard.
   `.room__enter` is absolutely positioned at the panel's foot, so it is out of
   flow and the mark would otherwise centre itself in the whole panel —
   including the strip the label sits in. As the panel gets shorter that
   centred mark walks down into the label: measured 3px of overlap on all three
   rooms at a 459px-tall window, with the guard already binding. Reserving the
   label's strip makes the flex centring happen in what is actually free, so
   the clearance holds at any height rather than at the ones that were tested. */
.room { --mw: min(62%, 300px); padding-bottom: calc(clamp(14px, 3vw, 44px) + 16px); }
.room__enter {
  position: absolute; bottom: clamp(14px, 3vw, 44px);
  font-size: clamp(8px, 0.75vw, 10px); letter-spacing: 0.3em; text-transform: uppercase;
  opacity: 0.6;
}

.room--teuf { color: var(--light); }
.room--kutym { color: #F2F0EA; }
.room--vin  { color: #F7EFE2; }
.room--teuf .veil { background: radial-gradient(ellipse 85% 55% at 50% 50%, rgba(18,13,8,0.58), rgba(18,13,8,0.24) 100%); }
.room--kutym .veil { background: radial-gradient(ellipse 85% 55% at 50% 50%, rgba(8,8,9,0.46), rgba(8,8,9,0.2) 100%); }
.room--vin  .veil { background: radial-gradient(ellipse 85% 55% at 50% 50%, rgba(26,14,6,0.5), rgba(26,14,6,0.2) 100%); }

/* Panel-capped: 2.59 would be 160% of the column. 92% leaves a gutter. */
.room--kutym .room__mark { width: min(92%, calc(var(--mw) * 2.59)); }
.room--vin  .room__mark { width: calc(var(--mw) * 0.80); }

.util {
  flex: 0 0 auto;
  background: var(--ink);
  color: #EFE7D8;
  padding: clamp(16px, 2vw, 22px) var(--gutter);
  display: flex;
  flex-wrap: wrap;
  gap: clamp(10px, 2vw, 26px);
  align-items: center;
  justify-content: space-between;
  font-size: clamp(10px, 0.85vw, 12px);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.util a { text-decoration: none; border-bottom: 1px solid rgba(239,231,216,0.4); padding-bottom: 2px; }

/* Two addresses, because there are two. The single address and single room's
   open-now state that used to live here were wrong on a page whose entire
   premise is three rooms at two doors: on a Tuesday at 15.30 it read
   "Öppet nu" while Bar Kutym was shut for another half hour. Hours are
   per-room and now live on the room pages, where they can be right. */
.util__where { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; }
.util__room { opacity: 0.55; letter-spacing: 0.16em; }

@media (max-width: 599px) {
  /* Stacked, so neither address truncates the other at 10px. */
  .util { flex-direction: column; align-items: flex-start; }
  .util__where { flex-direction: column; gap: 6px; }
  .util__where > .util__room { margin-bottom: 4px; }
}
/* The one action on the home page. Same role, same colour as every Boka bord
   button on the room pages — it just has no button to sit in here. */
.util__book { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
.util nav { display: flex; gap: clamp(14px, 2vw, 26px); }

@media (min-width: 900px) {
  .rooms { flex-direction: row; }
  .room:hover { flex-grow: 1.18; }
  .room__enter { opacity: 0; transform: translateY(8px); transition: opacity 340ms var(--ease), transform 340ms var(--ease); }
  .room:hover .room__enter { opacity: 0.85; transform: translateY(0); }
}

/* ---------- venue pages ---------- */

/* The hero and the open-now strip together fill the viewport (2026-09-28).
   They share this box because the height has to be split between them: the
   hero takes whatever the strip does not, so the two total one screen instead
   of the strip pushing the fold past it.

   min-height, not height. On a short landscape phone the hero keeps its 460px
   floor and this box grows past a screen rather than clipping the mark, the
   label and the button — an exact 100svh there would cut them off.

   svh, not vh: vh on iOS is the LARGE viewport, so the strip would sit under
   the browser chrome until you scrolled. */
.top {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

.hero {
  position: relative;
  /* Fills what the strip leaves. The floor stays; the old 1000px ceiling is
     gone with it, so on a tall monitor the photograph now runs the full
     height — that is the trade this change makes. */
  flex: 1 1 auto;
  min-height: 460px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  padding: clamp(60px, 8vw, 90px) var(--gutter);
  text-align: center;
}
.hero img.bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Content sits above the photo and its scrim; the photo, the scrim and the
   back link stay pinned. Ordered so the specific rules win. */
.hero > * { position: relative; }
.hero > .bg,
.hero > .veil { position: absolute; inset: 0; }
.hero > .back { position: absolute; }
/* One shared width used to serve all three, which only worked while au Vin's
   padding was quietly shrinking it. Cropped, the same number would have made
   it the tallest thing on the site. Same system as the other three surfaces —
   but Bar Kutym is held off the edges here, because bleeding is TEUF's. */
.hero { --mw: clamp(220px, 48vw, 620px); }
.hero__mark { width: var(--mw); }
.v-kutym .hero__mark { width: min(80vw, calc(var(--mw) * 2.59)); }
/* au Vin's hero does NOT take the 0.80 the other surfaces do, and cannot.
   Its mark is 361px of real pixels after cropping, and the hero already drew
   them at 1:1 — every hero pixel was a source pixel, so it was soft on any
   retina display before anyone touched it. 0.582 holds it exactly where it
   rendered before; anything larger is upscaling. This is the 621px asset the
   brief has flagged since 2026-09-17, and only a vector fixes it. */
.v-vin   .hero__mark { width: calc(var(--mw) * 0.582); }
.hero__tag { margin-top: clamp(14px, 2vw, 22px); opacity: 0.85; }
.hero .btn { margin-top: clamp(26px, 3.4vw, 44px); }

.back {
  position: absolute; top: clamp(18px, 2.4vw, 34px); left: var(--gutter);
  font-size: clamp(9px, 0.8vw, 10px); letter-spacing: 0.26em; text-transform: uppercase;
  text-decoration: none; opacity: 0.8; z-index: 2;
}
.back:hover { opacity: 1; }

.section { padding: var(--section) var(--gutter); }

.about { display: grid; gap: clamp(28px, 4vw, 88px); align-items: start; }
.about__statement { font-size: var(--lede); line-height: 1.22; margin: 0 0 clamp(20px, 2.4vw, 28px); }
.about__img {
  width: 100%; aspect-ratio: 7 / 8; border: 1px solid currentColor; opacity: 0.35;
  display: flex; align-items: center; justify-content: center;
  font-size: 9px; letter-spacing: 0.24em; text-transform: uppercase;
}

/* ---------- About: eyebrow, contained portrait, statement, lead ----------

   Not full-bleed. The section sits on the room's own ground with real margin
   around it, and the photograph is contained rather than bleeding — the quiet
   counterpart to the menu split further down.

   Three columns: a label in the left margin, the portrait, then the text. The
   statement sits at the top of its column and the lead at the bottom, level
   with the foot of the photograph, with the space between them left empty.
   There is deliberately no call to action here; the onward link belongs to the
   menu, not to the room's own description. */

.about {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(20px, 3vw, 36px);
  padding: var(--space-major) var(--gutter);
}

.about__shot { margin: 0; }
.about__shot img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 4 / 5; object-fit: cover;
}

.about__body { display: flex; flex-direction: column; }

/* The first paragraph, set a step up from the rest so the column opens with
   some weight instead of starting flat. */
.about__lead {
  margin: clamp(22px, 3vw, 34px) 0 clamp(14px, 1.6vw, 20px);
  font-size: clamp(15px, 1.2vw, 17px);
  line-height: 1.7;
  opacity: 0.85;
}

/* One column, one measure. An earlier version put the lead here and the
   remaining paragraphs in a separate full-width section below, which read as
   two disconnected blocks of text with a void between them — the text started
   in the right column and resumed at the far left. All of the copy lives in
   this column now. */
.about__body > .body-text { max-width: none; opacity: 0.72; }
.about__body > .body-text:last-child { margin-bottom: 0; }

@media (min-width: 900px) {
  .about {
    grid-template-columns: minmax(70px, 6vw) minmax(0, 0.92fr) minmax(0, 1.08fr);
    gap: clamp(26px, 4vw, 70px);
    align-items: stretch;
  }
  /* The first column used to hold an "Om" label. It stays as margin now that
     the label is gone — it is what keeps the photograph inset from the gutter
     rather than flush against it. Both items are placed explicitly, because
     auto-placement would otherwise slide the photograph into that column and
     close the indent. */
  .about__shot { grid-column: 2; }
  .about__body { grid-column: 3; }
  /* The text column holds the whole About, so it is sized to a comfortable
     measure rather than stretched to the grid. */
  .about__body { max-width: 52ch; }
}

/* ---------- the split: photograph against a colour panel ----------

   The section directly under each hero. Full-bleed, edge to edge, no gutter:
   a photograph holding one half and a panel of the room's own deep tone
   holding the other.

   The panel colour is --cover-bg, which already exists per room and is derived
   from that room's photography — so the three read as three rooms rather than
   one template in three colours. Each also keeps its own display voice further
   down (Archivo wide / Prata / Caveat), for the same reason.

   Type sits top and bottom with the space between them left empty. That gap is
   the point: it is what makes the panel read as a held composition rather than
   a text box, and it is why the intro here is ~35 words and the rest of the
   copy lives in the section below. */

.split {
  display: grid;
  grid-template-columns: 1fr;
  background: var(--cover-bg);
  color: var(--cover-on);
}

.split__shot { margin: 0; position: relative; }
.split__shot img {
  display: block; width: 100%; height: 100%;
  min-height: 46svh;
  object-fit: cover;
}

.split__panel {
  display: flex; flex-direction: column;
  gap: clamp(28px, 4vw, 56px);
  padding: clamp(32px, 5vw, 76px) clamp(24px, 4vw, 64px);
}

.split__eyebrow {
  margin: 0;
  font-size: var(--eyebrow);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.62;
}

/* The statement, at display size. Per-room families are inherited from the
   .v-* rules further down, so this sets only what they share. */
.split__title {
  margin: 0;
  font-size: var(--lede);
  line-height: 1.12;
  text-wrap: balance;
}

/* Pushed to the bottom of the panel on desktop, under the empty middle. */
.split__foot { margin-top: auto; }

.split__lead {
  margin: 0 0 clamp(18px, 2vw, 26px);
  font-size: var(--body);
  line-height: 1.75;
  opacity: 0.78;
  max-width: 42ch;
}

/* The way into the menu, and the reason most people opened the page — so it is
   a filled button at .btn's metrics, not the eyebrow-sized label with a
   circled arrow it was until 2026-10-08. At `--eyebrow` it was smaller than
   the body copy above it and the same size as the PDF links below it, which
   left the section's primary action looking like a third footnote.

   It fills with the panel's own two colours, inverted: `--cover-on` on
   `--cover-bg`. That is deliberate rather than reaching for `--accent`.
   The yellow is the booking CTA's, and a second yellow button on the same page
   would have the menu competing with Boka bord instead of leading to it. The
   inverted pair also needs no new tokens and no per-room tuning — every room
   already defines these two as a contrast pair, so each panel gets its own
   maximum contrast for free (measured: TEUF 16.12:1, Bar Kutym 17.37:1,
   au Vin 7.54:1 — au Vin's is the lowest because its panel is a mid green
   rather than a near-black, and it still clears AA for body text).

   This is a <button>, because it opens the lightbox rather than navigating.
   The UA chrome is overridden here rather than in a separate `button.`
   override — the old one re-set `background`, `border` and `padding` after
   this rule and would now undo the fill. Note what is NOT reset:
   letter-spacing and text-transform are not imposed by the UA, and resetting
   them once overrode the uppercase and tracking declared here, which came out
   as sentence case. */
.split__more {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: inherit;
  font-size: clamp(11px, 0.9vw, 12px);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  padding: clamp(15px, 1.6vw, 18px) clamp(24px, 2.6vw, 34px);
  background: var(--cover-on);
  color: var(--cover-bg);
  border: 1px solid var(--cover-on);
  cursor: pointer;
  transition: background 220ms var(--ease), color 220ms var(--ease);
}
/* A plain arrow now. Inside a filled button the circle was drawing a second
   affordance around the first. */
.split__more span {
  display: inline-block;
  transition: transform 0.4s var(--ease);
}
.split__more:hover { background: transparent; color: var(--cover-on); }
.split__more:hover span { transform: translateX(4px); }

/* An award for the list, sitting directly above the way into the list — which
   is the whole reason it is here and not in the hero or the About. au Vin's
   Silver Star is for the wine list itself, so the claim and the thing it is
   claiming about are adjacent, and the Star Wine List link in .split__alt
   right below comes from the same source.

   The certificate and the typeset line do different jobs, which is why both
   are here. The scan is an A4 document whose words are set small, so at any
   size that fits a panel it is a picture of a certificate rather than a
   readable one — it carries the proof, not the information. The type beside it
   carries the information: legible at a glance, translatable for the English
   site, and what a screen reader gets. The whole lockup is one link to the
   full-size scan, so the claim and the evidence for it are the same target.

   Deliberately no redrawn logo. If the official badge is wanted it comes from
   Star Wine List's winner kit as an SVG.

   Generic rather than scoped to .v-vin, so the next room to win something
   needs markup and no CSS. The small lines run at 0.72, which is au Vin's
   floor for small text on that mid green — 0.62 measures 4.01:1 against a
   4.5 requirement. */
.split__award {
  display: flex; align-items: center;
  gap: clamp(14px, 1.8vw, 22px);
  margin: 0 0 clamp(22px, 2.6vw, 32px);
  text-decoration: none;
  color: inherit;
}
/* A white A4 document on a dark panel. The border and the shadow are what stop
   it reading as a pasted screenshot — they give it an edge and sit it on the
   panel as an object rather than a hole cut in it. */
.split__award-doc {
  flex: 0 0 auto;
  display: block;
  width: clamp(62px, 7vw, 84px);
  height: auto;
  background: #fff;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  box-shadow: 0 3px 14px rgba(0, 0, 0, 0.3);
  transition: transform 300ms var(--ease), box-shadow 300ms var(--ease);
}
.split__award:hover .split__award-doc {
  transform: translateY(-3px);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.38);
}
.split__award-text { display: grid; gap: 5px; }
/* Three lines in the order an award is actually cited: the honour, then the
   category, then the body and edition. Set as one line it wrapped mid-phrase
   ("BEST NEWCOMER / LIST") in the narrow column beside the thumbnail. */
.split__award-name {
  font-size: clamp(13px, 1.05vw, 15px);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.25;
}
.split__award-cat {
  font-size: clamp(10px, 0.82vw, 11px);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.3;
}
.split__award-src,
.split__award-see {
  font-size: var(--eyebrow);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.72;
  line-height: 1.4;
}
.split__award-see { text-decoration: underline; text-underline-offset: 3px; }
.split__award:hover .split__award-see { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .split__award-doc { transition: none; }
  .split__award:hover .split__award-doc { transform: none; }
}

/* au Vin has two doors, not one: the menu lightbox and the full list on Star
   Wine List. They are different destinations, so they are a matched pair at
   the same metrics rather than a button and a footnote — the wine list was an
   eyebrow-sized link under the button, which badly undersold the thing the bar
   just won a Silver Star for.

   One filled and one outlined, not two filled. Two solid cream blocks side by
   side on this green read as one wide smear and cancel each other out; equal
   size is what makes them equally visible, and the fill is what still says
   which one is the page's own. They trade states on hover, so the pair reads
   as one control rather than two unrelated boxes. */
.split__actions {
  display: flex; flex-wrap: wrap;
  gap: clamp(10px, 1.2vw, 14px);
}
.split__more--ghost {
  background: transparent;
  color: var(--cover-on);
  border-color: color-mix(in srgb, var(--cover-on) 58%, transparent);
}
.split__more--ghost:hover {
  background: var(--cover-on);
  color: var(--cover-bg);
  border-color: var(--cover-on);
}
/* It leaves the site, so its arrow goes the way the glyph points. */
.split__more--ghost:hover span { transform: translate(3px, -3px); }

/* The secondary ways into the menu — PDF, and au Vin's full wine list — sit
   under the button, on the panel rather than on the page. */
.split__alt {
  display: flex; flex-wrap: wrap; gap: 8px clamp(14px, 2vw, 26px);
  margin: clamp(18px, 2.2vw, 26px) 0 0;
  font-size: var(--eyebrow); letter-spacing: 0.12em; text-transform: uppercase;
}
.split__alt a { color: inherit; text-decoration: none; opacity: 0.62; }
.split__alt a:hover { opacity: 1; }

@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; min-height: 82svh; }
  .split__shot img { min-height: 0; position: absolute; inset: 0; }
  .split__panel { justify-content: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  .split__more span { transition: none; }
}

/* ---------- TEUF only: the one-dish About ---------- */

/* The house sells one dish. The section is built so the skeleton says that
   before a word is read: an opening spread, then the claim alone on the dark
   band the page already uses for its events, then the rest of the story in
   ordinary body copy. Everything here is scoped to .about--dish, so Corner
   Club and au Vin keep the plain two-column About untouched.

   No eyebrow: the old "Om" label named nothing the statement does not say. */

.about--dish { display: block; padding: 0; }

.about--dish .about__open {
  display: grid;
  gap: clamp(26px, 4vw, 64px);
  align-items: end;
  padding: var(--space-major) var(--gutter) var(--space-tight);
}

.about--dish .about__lead { min-width: 0; }
.about--dish .about__lead .body-text:last-child { margin-bottom: 0; }

.about--dish .about__shot { margin: 0; }
.about--dish .about__shot img {
  display: block; width: 100%; height: auto;
  /* Uncropped on desktop, where it sits beside the text. On a phone it is
     stacked, and at full height it pushed the claim 500px down the scroll —
     so cap it and hold the centre plate. */
  max-height: min(58svh, 440px);
  object-fit: cover;
  object-position: center 44%;
}

@media (min-width: 900px) {
  .about--dish .about__shot img { max-height: none; }
}

/* The statement runs at display scale on the width axis the wordmark lives on,
   rather than the shared --lede size, which is the smallest display step. */
.v-teuf .about--dish .about__statement {
  font-size: clamp(26px, 4.6vw, 62px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  font-stretch: 118%;
  font-weight: 500;
  max-width: 16ch;
  margin: 0 0 clamp(18px, 2.2vw, 26px);
}

/* The claim, alone. Same ink ground and hairlines as the events band, so this
   is the page's own device turned up rather than a new one. Deliberately not
   given [data-reveal]: its neighbours animate in, and this one is simply
   already there — the fixed thing they arrive at. */
.onedish {
  background: var(--ink);
  color: var(--light);
  border-top: 1px solid color-mix(in srgb, var(--light) 16%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--light) 16%, transparent);
  /* The ink ground already separates this; it does not need space as well.
     Tight against its neighbours so statement, photograph and claim read as
     one movement rather than three sections. */
  padding: var(--section) var(--gutter);
}
.onedish__line {
  margin: 0;
  font-size: clamp(37px, 6.2vw, 92px);
  line-height: 1.02;
  letter-spacing: -0.03em;
  font-stretch: 125%;
  font-weight: 600;
  max-width: 15ch;
}

.about--dish .about__rest {
  padding: var(--space-tight) var(--gutter) var(--space-major);
}
.about--dish .about__rest .body-text:last-child { margin-bottom: 0; }

@media (min-width: 900px) {
  .about--dish .about__open {
    grid-template-columns: minmax(0, 1fr) clamp(280px, 33vw, 460px);
    /* The photograph runs off the right edge, the way the wordmark runs off
       the hero. The gutter is re-added inside the text column only. */
    padding-right: 0;
  }
}

/* Hours and booking are why two of the three confirmed visitor situations are
   here at all, and under a squint test this band used to disappear entirely
   while the menu cover dominated. It now gets the page's major interval and a
   readable type size. Deliberately not a fourth dark band: mass comes from
   space and content size, so it does not compete with the events and one-dish
   grounds. */
.practical {
  padding: var(--space-major) var(--gutter);
  border-top: 1px solid currentColor; border-bottom: 1px solid currentColor;
  display: flex; flex-direction: column; gap: clamp(20px, 2.6vw, 30px);
}
.practical__title {
  font-size: clamp(16px, 1.7vw, 21px);
  font-weight: 600;
  line-height: 1.2;
  margin: 0 0 clamp(14px, 1.6vw, 18px);
}
.practical .hours__row { font-size: clamp(15px, 1.25vw, 17px); opacity: 0.78; }
.practical .today {
  font-size: clamp(12px, 1.05vw, 14px);
  letter-spacing: 0.16em;
  opacity: 1;
  margin: clamp(16px, 1.9vw, 22px) 0 0;
}
.practical__hours { font-size: var(--body); line-height: 1.7; }
.practical__note { font-size: clamp(12px, 1vw, 13px); line-height: 1.6; opacity: 0.66; }

/* A band that has to read as separate on three different page grounds, so it
   takes a ground per venue plus hairlines that carry it when contrast is low. */
.events {
  padding: var(--section) var(--gutter);
  background: var(--events-bg, var(--ink));
  color: var(--events-on, var(--light));
  border-top: 1px solid color-mix(in srgb, var(--events-on, var(--light)) 16%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--events-on, var(--light)) 16%, transparent);
}
.events[hidden] { display: none; }
.events__grid { display: grid; gap: clamp(26px, 4vw, 72px); align-items: center; }
.events__date { font-size: clamp(10px, 0.85vw, 11px); letter-spacing: 0.28em; text-transform: uppercase; opacity: 0.6; }
.events__name { font-size: clamp(32px, 4.6vw, 58px); font-weight: 500; line-height: 1.06; margin: 14px 0 0; }
.events__line { font-size: clamp(14px, 1.15vw, 16px); line-height: 1.65; opacity: 0.68; margin: 16px 0 0; max-width: 46ch; }
.events__act { margin-top: clamp(20px, 2.6vw, 34px); display: flex; flex-wrap: wrap; align-items: center; gap: clamp(14px, 2vw, 26px); }

.menu { display: flex; flex-direction: column; align-items: center; }
.menu .eyebrow { align-self: flex-start; }
.menu__cover {
  width: min(384px, 100%); aspect-ratio: 8 / 11;
  border: 1px solid transparent; padding: 0; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(28px, 4vw, 46px);
  transition: transform 320ms var(--ease), box-shadow 320ms var(--ease), border-color 320ms var(--ease);
}
.menu__cover:hover { transform: translateY(-6px); }
.menu__cover img { width: 58%; }
.menu__covertext { font-size: clamp(9px, 0.8vw, 10px); letter-spacing: 0.28em; text-transform: uppercase; opacity: 0.62; }
.menu__alt { margin-top: clamp(20px, 2.4vw, 30px); display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(18px, 2.6vw, 34px); }
.menu__alt a {
  font-size: clamp(11px, 0.9vw, 12px); letter-spacing: 0.16em; text-transform: uppercase;
  text-decoration: none; opacity: 0.66; border-bottom: 1px solid currentColor; padding-bottom: 2px;
}
.menu__alt a:hover { opacity: 1; }

.house { display: flex; flex-direction: column; }
.house__room {
  position: relative; overflow: hidden; text-decoration: none;
  min-height: clamp(160px, 22vw, 300px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; flex: 1 1 0;
  transition: flex-grow 420ms var(--ease);
}
.house__room img.bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.house__label { position: relative; font-size: clamp(8px, 0.7vw, 9px); letter-spacing: 0.28em; text-transform: uppercase; opacity: 0.6; }
/* Three equal flex columns, so Bar Kutym is column-capped like the homepage. */
.house { --mw: clamp(110px, 17vw, 200px); }
.house__mark { position: relative; width: var(--mw); }
.hm-kutym { width: min(88%, calc(var(--mw) * 2.59)); }
.hm-vin   { width: calc(var(--mw) * 0.80); }

.foot {
  background: var(--ink); color: #EFE7D8;
  padding: clamp(20px, 2.4vw, 30px) var(--gutter);
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between;
  font-size: clamp(10px, 0.85vw, 12px); letter-spacing: 0.2em; text-transform: uppercase;
}
.foot a { text-decoration: none; border-bottom: 1px solid rgba(239,231,216,0.4); padding-bottom: 2px; }

@media (min-width: 900px) {
  .practical { flex-direction: row; align-items: center; justify-content: space-between; gap: 40px; }
  .events__grid { grid-template-columns: minmax(0, 1fr) clamp(280px, 30vw, 380px); }
  .house { flex-direction: row; }
  .house__room:hover { flex-grow: 1.15; }
}

/* ---------- menu lightbox ---------- */

.lightbox {
  /* Above .navbtn (120). At 90 the nav button sat on top of the close button:
     12 of 17 points sampled across the close button resolved to .navbtn, so the
     menu could not be closed on a phone. */
  position: fixed; inset: 0; z-index: 130;
  background: rgba(10,8,6,0.985);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(18px, 2.4vw, 34px);
  padding: clamp(56px, 8vh, 90px) clamp(14px, 3vw, 40px) clamp(20px, 4vh, 40px);
}
.lightbox[hidden] { display: none; }
.lightbox__stage { display: flex; align-items: center; gap: clamp(14px, 3vw, 48px); max-width: 100%; }
.lightbox__page {
  background: #fff; width: auto; max-width: 100%;
  max-height: calc(100svh - clamp(150px, 26vh, 260px));
  box-shadow: 0 30px 90px rgba(0,0,0,0.55);
}
.lightbox__nav { display: flex; align-items: center; gap: clamp(14px, 2.4vw, 22px); }
.lightbox__counter { font-size: clamp(10px, 0.9vw, 11px); letter-spacing: 0.26em; text-transform: uppercase; color: var(--light); opacity: 0.6; min-width: 62px; text-align: center; }

.arrow {
  width: clamp(46px, 5vw, 56px); height: clamp(46px, 5vw, 56px);
  border-radius: 50%; border: 1px solid rgba(244,239,228,0.34);
  background: transparent; color: var(--light); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; padding: 0;
  transition: background 200ms var(--ease), color 200ms var(--ease);
}
.arrow:hover:enabled { background: var(--light); color: var(--ink); }
.arrow:disabled { opacity: 0.2; cursor: default; }

.lightbox__close {
  position: absolute; top: clamp(14px, 2vw, 40px); right: clamp(14px, 3vw, 48px);
  border: 0; background: transparent; color: var(--light); cursor: pointer;
  font-family: inherit; font-size: clamp(10px, 0.9vw, 11px);
  letter-spacing: 0.24em; text-transform: uppercase; opacity: 0.8; padding: 10px;
}
.lightbox__close:hover { opacity: 1; }

/* Beside the page on wide screens, beneath it on narrow ones. */
@media (max-width: 899px) {
  .lightbox__stage { flex-direction: column; }
  .lightbox__stage .arrow { display: none; }
}
@media (min-width: 900px) {
  .lightbox__nav .arrow { display: none; }
}

body.is-locked { overflow: hidden; }

/* ---------- per-venue themes ---------- */

/* Hero scrims are weighted toward the middle, where the mark and the button
   sit, so the photograph keeps its life at the edges. The TEUF flat-lay is a
   white tablecloth in hard sun and needs the most. */
.v-teuf {
  --ground: var(--paper); --on: var(--ink); --cover-bg: var(--ink); --cover-on: var(--light);
  --hero-veil: radial-gradient(ellipse 78% 66% at 50% 50%, rgba(14,10,6,0.66), rgba(14,10,6,0.26) 100%);
  --events-bg: var(--ink); --events-on: var(--light);
}
.v-kutym {
  --ground: #121213; --on: #F2F0EA; --cover-bg: #0A0A0B; --cover-on: #F2F0EA;
  --hero-veil: radial-gradient(ellipse 80% 70% at 50% 50%, rgba(6,6,7,0.5), rgba(6,6,7,0.2) 100%);
  --events-bg: #050506; --events-on: #F2F0EA;
}
.v-vin {
  /* Cream since 2026-09-24, replacing the oak ground — the room reads light
     now, with two dark moments punched into it: the green menu panel and the
     .latehour band, which still runs on --events-bg. The hero veil stays
     brown because it sits over a photograph, not over the page. */
  --ground: #FFFDF7; --on: var(--ink);
  --cover-bg: #32533D; --cover-on: #F7EFE2;
  --hero-veil: radial-gradient(ellipse 80% 82% at 50% 52%, rgba(26,12,3,0.76), rgba(26,12,3,0.3) 100%);
  /* The same green as the menu panel (2026-09-28), replacing the burnt brown.
     It drives both of this room's dark bands — .latehour and .events — so the
     cream page has one dark material rather than a green one and a brown one.
     .since is Bar Kutym's and is not affected. */
  --events-bg: #32533D; --events-on: #F7EFE2;
}

.venue { background: var(--ground); color: var(--on); }
.venue .hero { color: var(--light); }
.venue .hero .veil { background: var(--hero-veil); }
.venue .practical { border-color: color-mix(in srgb, currentColor 18%, transparent); }
.venue .about__img { border-color: color-mix(in srgb, currentColor 30%, transparent); }
.venue .menu__cover { background: var(--cover-bg); color: var(--cover-on); border-color: color-mix(in srgb, var(--cover-on) 26%, transparent); }

.v-kutym .about__statement { font-family: 'Prata', serif; font-size: clamp(25px, 3vw, 36px); }

/* Tipton, kept at the client's instruction through the Bar Kutym rename. Note
   what it no longer is: under Corner Club this was "the logo's own face", and
   the rule covered the lockup's other wording — the "a spirited establishment"
   line and the Est. 2013 band. Both were dropped with the rename, and the new
   mark is a different art-deco face that is not Tipton and has no font file.
   So this is now one descriptor line in a serif that matches nothing above it.
   Live with it or change it deliberately; don't read it as the mark's face.
   Tracking is 0.16em rather than .label's value, which compensated for Archivo
   and is too loose for a display serif. */
.v-kutym .hero__tag {
  font-family: 'Tipton', 'Prata', serif;
  font-weight: 600;
  letter-spacing: 0.16em;
}
.v-vin  .about__statement { font-family: 'Caveat', cursive; font-weight: 700; font-size: clamp(34px, 4vw, 46px); }

/* The menu panel's green (#32533D) is a mid tone, not a near-black like the
   other two rooms' panels, so the 0.62 opacity those small uppercase labels
   carry does not survive it: measured 4.01:1 against a 4.5 requirement at
   9.8px. 0.72 measures 4.64:1. The darker panels are unaffected — this is
   scoped because only this one fails. */
.v-vin .split__eyebrow,
.v-vin .split__alt a { opacity: 0.72; }
.v-teuf .about__statement { font-weight: 500; font-stretch: 112%; }

/* Uppercase for the two rooms whose display voice is built for it — TEUF's
   wide geometric sans and Bar Kutym's high-contrast serif. Teuf au Vin is
   deliberately excluded: Caveat is brush script, and setting a script face in
   caps reads as shouting rather than as display type. */
.v-teuf .about__statement,
.v-kutym .about__statement { text-transform: uppercase; letter-spacing: -0.01em; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: 1ms !important; animation-duration: 1ms !important; }
}

/* ---------- shop ---------- */

.shophead {
  position: relative;
  background: var(--ink);
  color: var(--light);
  padding: clamp(74px, 9vw, 110px) var(--gutter) clamp(34px, 4vw, 54px);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.shophead__mark { width: clamp(150px, 26vw, 260px); }
/* Same pair of tokens as the card's button. .shophead is dark, so the pair is
   flipped there and this comes out light with near-black text — the inverse of
   the add-to-cart on the cream cards, which is the point. */
.shophead__cart {
  position: absolute; top: clamp(16px, 2.2vw, 32px); right: var(--gutter);
  background: var(--btn-bg); color: var(--btn-fg);
  border: 1px solid var(--btn-bg); border-radius: 4px;
  font-family: inherit; font-size: clamp(10px, 0.85vw, 11px);
  letter-spacing: 0.22em; text-transform: uppercase;
  padding: 11px 16px; cursor: pointer;
  display: flex; align-items: center; gap: 9px;
  transition: background 200ms var(--ease), color 200ms var(--ease), border-color 200ms var(--ease);
}
.shophead__cart:hover {
  background: transparent; color: var(--btn-bg); border-color: var(--btn-bg);
}
/* A class-level display rule beats the UA's [hidden] rule, so say it again. */
.pill[hidden] { display: none; }
.pill {
  min-width: 19px; height: 19px; border-radius: 10px;
  background: var(--btn-fg, var(--light)); color: var(--btn-bg, var(--ink));
  font-size: 10px; letter-spacing: 0; display: flex; align-items: center; justify-content: center;
  padding: 0 5px;
}
.shophead__cart:hover .pill { background: var(--btn-bg); color: var(--btn-fg); }

.note {
  font-size: var(--body); line-height: 1.6; margin: 0 0 clamp(26px, 3vw, 40px);
  padding: 14px 18px; border-left: 1px solid currentColor; opacity: 0.75; max-width: 70ch;
}
/* The warning state used to be opacity 0.9 against 0.75 — invisible. */
.note--warn { color: var(--warn); opacity: 1; font-weight: 500; }

.grid {
  display: grid; gap: clamp(26px, 3.4vw, 52px);

  /* auto-FIT, not auto-fill. auto-fill kept the empty tracks it had room for:
     at 2000px it laid out six 260px columns, filled four, and left 625px of
     nothing on the right — the cards spanned 60% of the page while the grid
     spanned 91%. auto-fit collapses the empty tracks so the cards that exist
     share the full width and the gaps stay equal. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));

  /* The width itself comes from .section's gutter, which is the site's rhythm
     and already lands at 89–91% of the page from phone to 2000px. Centring is
     that gutter being symmetric, not a margin of its own. */

  /* A cap, because auto-fit has no upper bound: with one product left in the
     shop a lone card would otherwise stretch the whole 1824px. Centred so a
     short row sits in the middle rather than hugging the left gutter. */
  justify-content: center;
}
.grid > * { max-width: 520px; width: 100%; justify-self: stretch; }

/* The card as a bordered object, ported from the supplied component. Its
   shadow is deliberately quieter than the original's shadow-xl: this site's
   surfaces are flat and editorial, and a heavy drop shadow on a cream ground
   reads as a web template rather than as a shop. It lifts on hover instead. */
.card {
  display: flex; flex-direction: column;
  border: 1px solid color-mix(in srgb, currentColor 14%, transparent);
  border-radius: 4px;
  overflow: hidden;
  background: color-mix(in srgb, var(--ground) 100%, transparent);
  box-shadow: 0 1px 2px color-mix(in srgb, currentColor 6%, transparent);
  transition: box-shadow 320ms var(--ease), border-color 320ms var(--ease);
}
.card:hover {
  border-color: color-mix(in srgb, currentColor 26%, transparent);
  box-shadow: 0 6px 22px color-mix(in srgb, currentColor 12%, transparent);
}

/* The photograph bleeds to the border; the rest is inset. */
.card__body {
  display: flex; flex-direction: column; flex: 1;
  padding: clamp(13px, 1.5vw, 18px);
}
.card__img {
  /* One box, one shape, whatever the photograph is. The products are shot at
     different ratios — a flat t-shirt, a cap, two portrait posters — so cover
     was cropping them. contain shows each one whole and lets the box stay the
     same size across every card, which is what makes the row read as a row.
     max-height keeps that box from growing tall on a wide column. */
  width: 100%;
  aspect-ratio: 4 / 5;
  /* The cap exists so cards in a ROW match. On a phone there is one column
     and nothing to match, so the floor is generous — 240px there left the
     posters tiny for no benefit. */
  max-height: clamp(330px, 30vw, 380px);
  overflow: hidden;
  display: grid; place-items: center;
  padding: clamp(10px, 1.4vw, 18px);
  background: color-mix(in srgb, currentColor 5%, transparent);
  margin-bottom: 0;
}
.card__img img { width: 100%; height: 100%; object-fit: contain; }
.card__img--empty {
  display: flex; align-items: center; justify-content: center;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  font-size: 9px; letter-spacing: 0.24em; text-transform: uppercase; opacity: 0.45;
}
.card__title { font-size: clamp(16px, 1.35vw, 19px); font-weight: 500; margin: 0 0 5px; }
/* Two lines, then a toggle. A card whose description runs to a paragraph makes
   every other card in the row taller for no reason. shop.js only adds the
   button when the text actually overflows, so short descriptions get no
   "Läs mer" that expands nothing. */
.card__desc {
  font-size: clamp(13px, 1vw, 14px); line-height: 1.6; opacity: 0.66;
  margin: 0 0 4px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
.card__desc.is-open { -webkit-line-clamp: unset; overflow: visible; }

.card__more {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: inherit; font: inherit;
  font-size: clamp(11px, 0.9vw, 12px);
  letter-spacing: 0.12em; text-transform: uppercase;
  opacity: 0.7;
  margin: 0 0 12px;
  border-bottom: 1px solid currentColor;
}
.card__more:hover { opacity: 1; }
.card__price { font-size: var(--body); margin: 0 0 12px; letter-spacing: 0.04em; }
.card__add {
  align-self: stretch; margin-top: auto;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  /* "Lägg i varukorgen" is long for a 243px card. Without this it breaks to
     two lines and the button grows a row taller than its neighbours. */
  white-space: nowrap;
  font-size: clamp(10px, 0.78vw, 11px);
  letter-spacing: 0.1em;
  padding-inline: 12px;
}
.card__icon { width: 15px; height: 15px; flex: 0 0 auto; }
.card__icon--spin { animation: card-spin 760ms linear infinite; }
@keyframes card-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .card__icon--spin { animation: none; }
  .card { transition: none; }
}

/* ---------- product card: gallery + one row per option ----------

   Ported from a React/shadcn card rather than adopting that stack, per the
   rule in CLAUDE.md. What came across: the image gallery with dots, the
   per-option rows, colour swatches, and the add-to-cart state change.

   What deliberately did not: star ratings and review counts, "New" and "Best
   Seller" badges, a wishlist heart and a free-shipping line. None of those
   exist in the Shopify data, and inventing social proof or a shipping promise
   on a real shop is not a styling decision. */

.card__img { position: relative; }

.card__arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-items: center;
  border: 0; cursor: pointer;
  font: inherit; font-size: 15px; line-height: 1;
  background: color-mix(in srgb, var(--ground) 82%, transparent);
  color: var(--on);
  backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 260ms var(--ease), transform 260ms var(--ease);
}
.card__arrow--prev { left: 10px; }
.card__arrow--next { right: 10px; }
.card:hover .card__arrow,
.card__arrow:focus-visible { opacity: 1; }

/* Touch has no hover, so the arrows would never appear. The dots are the
   control there, and they are always visible. */
@media (hover: none) {
  .card__arrow { display: none; }
}

.card__dots {
  position: absolute; left: 0; right: 0; bottom: 10px;
  display: flex; justify-content: center; gap: 6px;
}
.card__dot {
  width: 6px; height: 6px; padding: 0; border: 0; border-radius: 999px;
  cursor: pointer;
  background: color-mix(in srgb, var(--ground) 55%, transparent);
  transition: width 260ms var(--ease), background 260ms var(--ease);
}
.card__dot[aria-current="true"] { width: 18px; background: var(--ground); }

/* ---- option rows ---- */

.opt { margin: 0 0 11px; }
.opt__label {
  display: block; margin: 0 0 7px;
  font-size: var(--eyebrow); letter-spacing: 0.18em; text-transform: uppercase;
  opacity: 0.6;
}
.opt__vals { display: flex; flex-wrap: wrap; gap: 8px; }

.swatch {
  width: 26px; height: 26px; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--sw);
  border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
  /* the ring sits outside the swatch so it never covers the colour */
  outline-offset: 3px;
  transition: outline-color 180ms var(--ease);
  outline: 2px solid transparent;
}
.swatch[aria-pressed="true"] { outline-color: currentColor; }
.swatch:disabled {
  cursor: default; opacity: 0.4;
  /* a diagonal bar, because a greyed-out swatch just looks like a grey colour */
  background-image: linear-gradient(to top right, transparent 46%, currentColor 46%, currentColor 54%, transparent 54%);
}

.sizes { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.size {
  min-width: 44px; min-height: 44px; padding: 0 12px;
  border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
  background: transparent; color: inherit; cursor: pointer;
  font-family: inherit; font-size: 12px; letter-spacing: 0.1em;
  transition: background 180ms var(--ease), color 180ms var(--ease), border-color 180ms var(--ease);
}
.size[aria-pressed="true"] { background: currentColor; border-color: currentColor; }
.size[aria-pressed="true"]::after { content: ''; }
.v-teuf .size[aria-pressed="true"] { color: var(--ink); background: var(--ink); }
.v-teuf .size[aria-pressed="true"] { color: var(--paper); }
.size:disabled { opacity: 0.45; cursor: default; text-decoration: line-through; }

/* Cart panel: a drawer on desktop, a sheet on a phone. */
/* Same tier as the menu lightbox (130). At 95 the nav button (120) and the cart
   button (115) both painted over this scrim and stayed clickable — the identical
   defect that was fixed for the lightbox and not generalised at the time. */
.cart { position: fixed; inset: 0; z-index: 130; background: rgba(10,8,6,0.6); display: flex; }
.cart[hidden] { display: none; }
.cart__panel {
  background: var(--paper); color: var(--ink);
  margin-left: auto; width: min(420px, 100%);
  display: flex; flex-direction: column;
  padding: clamp(22px, 3vw, 34px);
  box-shadow: -20px 0 60px rgba(0,0,0,0.3);
}
.cart__head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: clamp(18px, 2.4vw, 28px); }
.cart__title { font-size: clamp(18px, 1.6vw, 22px); font-weight: 500; margin: 0; }
.cart__close {
  border: 0; background: transparent; color: inherit; cursor: pointer; font-family: inherit;
  font-size: 11px; letter-spacing: 0.22em; text-transform: uppercase; opacity: 0.7; padding: 8px;
}
.cart__close:hover { opacity: 1; }
.cart__empty { font-size: var(--body); opacity: 0.6; margin: 0; }
.cart__lines { list-style: none; margin: 0; padding: 0; overflow-y: auto; flex: 1 1 auto; }
.line { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 15px 0; border-bottom: 1px solid rgba(23,19,14,0.12); }
.line__title { margin: 0; font-size: 15px; }
.line__meta { margin: 4px 0 0; font-size: 12.5px; opacity: 0.6; }
.qty { display: flex; align-items: center; gap: 4px; }
.qty__btn {
  width: 34px; height: 34px; border: 1px solid rgba(23,19,14,0.25);
  background: transparent; color: inherit; cursor: pointer; font-family: inherit; font-size: 15px;
}
.qty__btn:hover { background: var(--ink); color: var(--paper); }
.qty__n { min-width: 26px; text-align: center; font-size: 14px; }
.cart__foot { padding-top: clamp(18px, 2.4vw, 26px); }
.cart__note { font-size: 12px; line-height: 1.5; opacity: 0.6; margin: 0 0 14px; }
.cart__checkout { width: 100%; text-align: center; }

@media (max-width: 599px) {
  .cart { align-items: flex-end; }
  .cart__panel { margin: 0; width: 100%; max-height: 88svh; box-shadow: 0 -20px 60px rgba(0,0,0,0.35); }
}

/* Word-by-word scroll reveal. The .rv-on class only lands once the script has
   split the words, so unstyled text is never left dimmed. */
.rv-on .rv-word { transition: opacity 260ms linear; will-change: opacity; }
@media (prefers-reduced-motion: reduce) {
  .rv-on .rv-word { opacity: 1 !important; transition: none; }
}

/* Hero entrance — the one idea worth taking from the React hero component.
   Pure CSS: the content rises and fades in sequence on load. Two keyframes so
   each element settles at its own resting opacity instead of snapping to 1. */
@media (prefers-reduced-motion: no-preference) {
  .hero__mark,
  .hero__tag,
  .hero > .label:not(.hero__tag),
  .hero .btn {
    animation: rise 760ms var(--ease) backwards;
  }
  .hero__mark   { animation-delay: 100ms; }
  .hero__tag    { animation-delay: 260ms; animation-name: rise-soft; }
  .hero > .label:not(.hero__tag) { animation-delay: 340ms; animation-name: rise-faint; }
  .hero .btn    { animation-delay: 440ms; }
}
@keyframes rise       { from { opacity: 0;    transform: translateY(18px); } to { opacity: 1;    transform: none; } }
@keyframes rise-soft  { from { opacity: 0;    transform: translateY(18px); } to { opacity: 0.85; transform: none; } }
@keyframes rise-faint { from { opacity: 0;    transform: translateY(18px); } to { opacity: 0.55; transform: none; } }

/* Frosted button, also from that component. The blur has a job: it separates
   the button from whatever is behind it. At a 10% white tint it was not doing
   that job well enough — measured 3.5:1 over TEUF's bright flat-lay — so the
   frost is now tinted with the action colour instead of with white. Ten per
   cent of the photograph still moves behind it; the contrast no longer depends
   on which photograph that is. */
.btn--ghost {
  background: color-mix(in srgb, var(--accent) 90%, transparent);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
  border-color: var(--accent);
  color: var(--accent-on);
}
.btn--ghost:hover {
  background: transparent;
  color: var(--accent);
  border-color: var(--accent);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}

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

/* A restaurant that serves one dish, telling you a page is not on the menu.
   The joke is only available to TEUF, which is the point.

   Ink ground because this is an interstitial rather than a room — the rooms are
   what you are trying to reach, so none of their photography appears here. The
   only ornament is the number, set the way the wordmark is set: enormous, on
   Archivo's width axis, running off the left edge. */

.lost-page { background: var(--ink); color: var(--light); }
/* The dark ground needs the light accent stop, same as every other dark
   surface on the site. */
.lost-page { --accent: var(--accent-hi); --accent-on: var(--ink); }

.lost {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(56px, 9vh, 110px) var(--gutter);
  overflow: hidden;              /* the number bleeds; nothing else should */
  position: relative;
}

.lost__code {
  /* Pulled past the gutter so it genuinely runs off the left edge, which is
     how this house sets display type. .lost clips it. */
  /* clamp() takes (min, preferred, max). These are negative, so the larger
     magnitude is the MIN: written the other way round it silently resolved to
     -12px at every width and the fluid tightening never happened. */
  margin: 0 0 clamp(-28px, -1.4vw, -12px)
          calc(var(--gutter) * -1 - clamp(4px, 1.2vw, 26px));
  font-size: clamp(120px, 27vw, 380px);
  line-height: 0.82;
  letter-spacing: -0.05em;
  font-stretch: 125%;
  font-weight: 600;
  /* Ornament, and aria-hidden. Deliberately below the contrast floor: the
     error is stated in words at 16:1 in the heading, so nothing is carried by
     this alone. Raising it enough to pass 3:1 would put it in competition with
     the line that actually says something, which is the wrong trade. */
  color: color-mix(in srgb, var(--light) 13%, transparent);
  user-select: none;
}

.lost__line {
  margin: 0;
  font-size: clamp(30px, 5.4vw, 76px);
  line-height: 1.04;
  letter-spacing: -0.03em;
  font-stretch: 118%;
  font-weight: 500;
  max-width: 17ch;
}

.lost__sub {
  font-size: clamp(14px, 1.25vw, 17px);
  line-height: 1.65;
  opacity: 0.6;
  max-width: 44ch;
  margin: clamp(16px, 2vw, 22px) 0 0;
}

/* The three rooms as the menu this page does have. Hairlines rather than cards:
   a list of rooms in a building, not three products. */
.lost__rooms {
  margin: clamp(34px, 5vw, 60px) 0 0;
  border-top: 1px solid color-mix(in srgb, var(--light) 15%, transparent);
}

/* Stacked on a phone. Beside the name, the open-now line is greedy enough to
   break "Bar Kutym" over two lines and its note over three — the state is
   worth its own row rather than half the width of the room's name. */
.lostroom {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3px;
  padding: clamp(16px, 2.2vw, 24px) 0;
  border-bottom: 1px solid color-mix(in srgb, var(--light) 15%, transparent);
  text-decoration: none;
  transition: padding-left 320ms var(--ease), color 200ms var(--ease);
}
.lostroom__name {
  grid-column: 1;
  font-size: clamp(19px, 2.1vw, 27px);
  line-height: 1.2;
  font-stretch: 112%;
}
.lostroom__note {
  grid-column: 1;
  font-size: clamp(12px, 1.05vw, 14px);
  line-height: 1.5;
  opacity: 0.55;
}
.lostroom .today {
  font-size: clamp(10px, 0.9vw, 12px);
  letter-spacing: 0.2em;
  opacity: 0.8;
  margin-top: 7px;
}

@media (min-width: 620px) {
  .lostroom {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 4px clamp(16px, 3vw, 40px);
  }
  .lostroom__name { grid-column: 1; }
  .lostroom__note { grid-column: 1; }
  .lostroom .today {
    grid-column: 2; grid-row: 1 / span 2;
    margin-top: 0;
    letter-spacing: 0.26em;
    white-space: nowrap;
  }
}

/* The one hover idea: the room steps toward you, the way the home page panels
   grow. Nothing moves on touch, where there is no hover to reward. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .lostroom:hover { padding-left: clamp(10px, 1.4vw, 20px); color: var(--accent); }
  .lostroom:hover .lostroom__note { opacity: 0.75; }
}

.lost__actions {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(14px, 2vw, 26px);
  margin: clamp(30px, 4vw, 46px) 0 0;
}
.lost__home {
  font-size: clamp(11px, 0.95vw, 13px); letter-spacing: 0.16em; text-transform: uppercase;
  text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;
  opacity: 0.7;
}
.lost__home span { border-bottom: 1px solid currentColor; padding-bottom: 2px; }
.lost__home:hover { opacity: 1; }

.lost__addr {
  font-size: clamp(11px, 0.95vw, 13px); letter-spacing: 0.14em; text-transform: uppercase;
  opacity: 0.5;
  margin: clamp(30px, 4.5vw, 54px) 0 0;
}

/* One authored moment, borrowing the hero's own entrance rather than inventing
   a second motion vocabulary. The number arrives first and the rooms last. */
@media (prefers-reduced-motion: no-preference) {
  .lost__code, .lost__line, .lost__sub,
  .lost__rooms, .lost__actions, .lost__addr {
    animation: rise 760ms var(--ease) backwards;
  }
  .lost__code    { animation-delay: 60ms; }
  .lost__line    { animation-delay: 180ms; }
  .lost__sub     { animation-delay: 280ms; animation-name: rise-faint; }
  .lost__rooms   { animation-delay: 380ms; }
  .lost__actions { animation-delay: 500ms; }
  .lost__addr    { animation-delay: 580ms; animation-name: rise-faint; }
}

/* au Vin's ground is mid-lightness (L 0.43), not near-white like the dining room
   or near-black like Bar Kutym, so the shared .body-text opacity of 0.7 leaves
   far less contrast here than on the other two. Measured: TEUF 6.69:1, Corner
   Club 8.38:1, au Vin 4.49:1 — an actual AA failure, present before this pass
   and surfaced by measuring it. The room needs its own value, not a different
   colour. */
/* Everything on this page that dims itself toward the ground needs more room
   here than on cream or near-black. Same single cause, one correction. */
.v-vin .body-text,
.v-vin .practical__note { opacity: 0.84; }
.v-vin .menu__alt a,
.v-vin .merch__price { opacity: 0.78; }

/* Same cause: .eyebrow at 0.55 measured 3.41:1 here against 4.02:1 on cream.
   The size problem (9px, below the 11px floor) is a separate, site-wide issue
   and is not fixed by this pass. */
.v-vin .eyebrow { opacity: 0.74; }

/* ---------- Bar Kutym: the years ---------- */

/* The third room's own shape, and the third structurally different idea.
   TEUF's fact is a reduction, so one sentence alone on an empty ground.
   au Vin's is a time of night, so the hour itself is the object, paired.
   Bar Kutym's is a SPAN of years — it opened twelve years before the dining
   room — so this is neither a block nor a pairing but an inscription: one wide,
   thin line running the full measure and cropped by the viewport.

   That proportion is the room's own. Its logo lockup is a 7.8:1 horizontal
   strip, which is why every other treatment on the site has to work around it;
   here it is the point. Set in Prata, the serif this room already uses, on its
   own darkest ground rather than TEUF's ink.

   Copy is not new: the claim and the line under it are the two sentences of the
   About section's final paragraph, promoted rather than rewritten. */

.since {
  background: var(--events-bg);
  color: var(--events-on);
  border-top: 1px solid color-mix(in srgb, var(--events-on) 16%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--events-on) 16%, transparent);
  padding: clamp(40px, 6vw, 84px) var(--gutter);
  overflow: hidden;              /* the inscription is meant to run off */
}

.since__year {
  /* The logo's own face, licensed as a webfont 2026-09-18. Prata was the
     stand-in and remains the fallback while the file loads. */
  font-family: 'Tipton', 'Prata', serif;
  font-weight: 600;
  font-size: clamp(46px, 14.5vw, 210px);
  line-height: 1;
  letter-spacing: 0.02em;
  white-space: nowrap;
  margin: 0 0 clamp(20px, 3vw, 40px);
  /* Optical: Prata's cap height leaves a gap the band does not want. */
  margin-top: -0.06em;
}

.since__line {
  font-family: 'Prata', serif;
  font-size: clamp(18px, 2.1vw, 28px);
  line-height: 1.35;
  margin: 0 0 clamp(10px, 1.2vw, 14px);
  max-width: 30ch;
}

.since__after {
  font-size: var(--body);
  line-height: 1.7;
  opacity: 0.66;
  margin: 0;
  max-width: 46ch;
}

/* ---------- au Vin: the late hour ---------- */

/* Teuf au Vin's equivalent of the dining room's one-dish band, and deliberately
   not the same shape. TEUF's fact is about reduction, so one sentence alone on
   an empty ground is right for it. This fact is about a TIME — a charcuterie
   counter still open after 22.00 — so the hour itself is the object, and the
   claim sits beside it rather than under it.

   Its own ground too: --events-bg is au Vin's green, not TEUF's ink, so the
   room keeps its own voice at its loudest moment.

   Placed directly after the About so it lands beside the opening hours instead
   of three sections below them. For someone deciding at 21:45 this is the most
   useful sentence on the page; it used to be a quiet lead near the bottom. */

.latehour {
  background: var(--events-bg);
  color: var(--events-on);
  border-top: 1px solid color-mix(in srgb, var(--events-on) 16%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--events-on) 16%, transparent);
  padding: var(--section) var(--gutter);
  display: grid;
  gap: clamp(6px, 1.4vw, 28px) clamp(28px, 4.5vw, 72px);
  align-items: start;
}

/* The band used to lead with "22.00" set at 190px. It was removed 2026-10-09
   and the claim promoted into its place. The numeral carried aria-hidden, which
   is the tell: the largest thing on the band said nothing, and said it twice —
   once silently as decoration and once inside the claim, where it means
   something. It was also set in Archivo wide, which is TEUF's display voice on
   an au Vin band, and it spent --accent, which belongs to Boka bord. And a
   restaurant page showing a huge 22.00 reads as an opening time, which is the
   opposite of the claim: the counter is still open then. */

.latehour__body { min-width: 0; }

.latehour__claim {
  margin: 0 0 clamp(16px, 2vw, 24px);
  font-size: clamp(26px, 3.4vw, 46px);
  line-height: 1.12;
  letter-spacing: -0.015em;
  font-weight: 500;
  max-width: 17ch;
  text-wrap: balance;
}
/* Deliberately not Caveat. au Vin's script is the About statement's, and it is
   the page's one display moment; a second one here would flatten that. */

.latehour .body-text { opacity: 0.74; max-width: 54ch; }
.latehour .body-text:last-child { margin-bottom: 0; }

@media (min-width: 900px) {
  /* The hour takes its own column and the copy runs beside it — an asymmetric
     pairing rather than the single stacked column the dining room uses. */
  .latehour { grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); align-items: start; }
}

/* ---------- the answer, directly under the hero ---------- */

/* Two of the three confirmed visitor situations arrive wanting the same two
   facts: are you open, and which door. Before this band they were 3.6 and 5.9
   screens down on a phone, or behind an unlabelled hamburger. This is one line
   and it sits in the first screen after the hero.

   Deliberately quiet: a hairline and small caps, not a third ceremonial band.
   It answers a question; it is not trying to be looked at. */
.quickbar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px clamp(14px, 2.4vw, 28px);
  padding: 0 var(--gutter);
  /* never squeezed by the flex parent — the hero absorbs the remainder */
  flex: 0 0 auto;
  border-bottom: 1px solid color-mix(in srgb, currentColor 14%, transparent);
  font-size: clamp(11px, 0.95vw, 13px);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* The row is short; the tap areas are not. Padding carries both items past
   44px without changing how the line looks. */
.quickbar > * {
  display: inline-flex; align-items: center;
  min-height: 46px;
}
.quickbar .today { opacity: 0.9; }
.quickbar .maplink { text-decoration: none; opacity: 0.72; }
.quickbar .maplink:hover { opacity: 1; }

@media (max-width: 519px) {
  /* Genuinely stacked. flex-wrap alone was not enough — at 375px the two items
     just fit side by side, so with gap:0 they ran together as
     "STÄNGER 23.00MÄLARTORGET 15". Full-width basis forces the break. */
  .quickbar { gap: 0; }
  .quickbar > * { flex: 0 0 100%; min-height: 44px; }
  .quickbar .today { border-bottom: 1px solid color-mix(in srgb, currentColor 10%, transparent); }
}

/* ---------- press ---------- */

/* Read mode: a journalist is here to find a fact, a file and an address, in
   that order. Structure for scanning, not for atmosphere — no photography
   behind the header, because the photographs are the deliverable here rather
   than the decoration. */

/* The press page had no identity at all — no mark, no ground — on the one page
   where the house's own logotypes are the subject matter. It now wears the ink
   ground the shop header uses, but carries all three marks rather than one:
   that identifies the page and previews its deliverable in the same move. */
.presshead {
  position: relative;
  background: var(--ink);
  color: var(--light);
  --accent: var(--accent-hi);
  --accent-on: var(--ink);
  padding: clamp(96px, 13vh, 150px) var(--gutter) clamp(44px, 6vw, 72px);
  border-bottom: 0;
}

.presshead__marks {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: clamp(22px, 4vw, 54px);
  margin: 0 0 clamp(30px, 4.5vw, 54px);
  opacity: 0.85;
}
.presshead__mark { display: block; height: auto; }
/* A wrapping flex row with room to spare, so this is the one surface where
   Bar Kutym reaches full letterform parity with TEUF. */
.presshead__marks { --mw: clamp(84px, 12vw, 140px); }
.pm-teuf { width: var(--mw); }
.pm-club { width: calc(var(--mw) * 2.59); }
.pm-vin  { width: calc(var(--mw) * 0.80); }
.presshead__title {
  font-size: clamp(38px, 7vw, 96px);
  line-height: 1.02; letter-spacing: -0.03em;
  font-stretch: 118%; font-weight: 600;
  margin: 0;
}
.presshead__line {
  font-size: clamp(15px, 1.35vw, 19px); line-height: 1.6;
  opacity: 0.72; max-width: 46ch;
  margin: clamp(16px, 2vw, 24px) 0 clamp(24px, 3vw, 34px);
}
.presshead .back { position: absolute; }

.presssec {
  padding: var(--space-major) var(--gutter);
  border-bottom: 1px solid color-mix(in srgb, currentColor 12%, transparent);
}
.presssec:last-of-type { border-bottom: 0; }
.presssec[hidden] { display: none; }
.presssec__title {
  font-size: clamp(16px, 1.7vw, 21px); font-weight: 600; line-height: 1.2;
  margin: 0 0 clamp(24px, 3vw, 34px);
}
.presssec__sub {
  font-size: clamp(12px, 1vw, 13px); font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  opacity: 0.6;
  margin: clamp(28px, 3.4vw, 40px) 0 clamp(14px, 1.8vw, 20px);
}
.presssec__sub:first-of-type { margin-top: 0; }
.presssec__note {
  font-size: clamp(12px, 1vw, 13px); line-height: 1.6; opacity: 0.66;
  max-width: 58ch; margin: clamp(26px, 3vw, 36px) 0 0;
}

/* Coverage and listings share a row: source and date on one line, then the
   headline as the link. The outlet leads because that is what a reader scans
   for. */
.presslist { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(22px, 2.8vw, 34px); }
.presslist__item { max-width: 62ch; }
.presslist__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px clamp(12px, 1.6vw, 20px);
  margin-bottom: 6px;
}
.presslist__outlet {
  font-size: clamp(11px, 0.95vw, 13px); letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: 600;
}
.presslist__date { font-size: clamp(11px, 0.95vw, 13px); opacity: 0.6; }
.presslist__headline {
  display: inline-block;
  font-size: clamp(17px, 1.7vw, 23px); line-height: 1.32;
  margin: 0; text-decoration: none;
}
a.presslist__headline { border-bottom: 1px solid color-mix(in srgb, currentColor 30%, transparent); padding-bottom: 2px; }
a.presslist__headline:hover { border-bottom-color: currentColor; }
.presslist__quote {
  font-size: var(--body); line-height: 1.7; opacity: 0.72;
  margin: 10px 0 0; padding: 0; max-width: 56ch;
}

/* Facts read as a reference table, so the label column is fixed and the values
   align — a journalist copies from this. */
.facts { margin: 0; display: grid; gap: clamp(14px, 1.8vw, 20px) clamp(20px, 3vw, 40px); }
.facts__label {
  font-size: clamp(11px, 0.95vw, 13px); letter-spacing: 0.16em; text-transform: uppercase;
  font-weight: 600; opacity: 0.6; margin: 0;
}
.facts__value { font-size: var(--body); line-height: 1.7; margin: 0; max-width: 62ch; }

@media (min-width: 720px) {
  .facts { grid-template-columns: clamp(130px, 15vw, 180px) minmax(0, 1fr); align-items: baseline; }
  .facts__label { grid-column: 1; }
  .facts__value { grid-column: 2; }
}

/* Download tiles. The preview is the file itself, so what you see is what you
   get — no generic file icon standing in for the asset. */
.kit {
  display: grid; gap: clamp(16px, 2.2vw, 28px);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr));
}
.kit__item { display: block; text-decoration: none; }
.kit__shot {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 3 / 2; overflow: hidden; padding: 12%;
  margin: 0 0 clamp(10px, 1.2vw, 14px);
  background: color-mix(in srgb, currentColor 7%, transparent);
}
.kit__shot img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.kit__label { display: block; font-size: var(--body); line-height: 1.4; }
.kit__note { display: block; font-size: clamp(11px, 0.95vw, 12px); opacity: 0.6; margin-top: 3px; }
.kit__item:hover .kit__label { text-decoration: underline; text-underline-offset: 3px; }

/* Photographs are dark-ish and want no padding; logos are marks and want it. */
.kit--photos .kit__shot { padding: 0; }
.kit--photos .kit__shot img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- the Butik block on a venue page ---------- */

/* Tertiary by intent: it sits after the menu on the tight interval so merch
   never competes with the menu or with booking. No card chrome — the product
   photograph is the object, and a box around it would only add weight this
   block has not earned. */

.merch { padding: var(--space-tight) var(--gutter); }
.merch[hidden] { display: none; }

.merch__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: 10px clamp(16px, 2.4vw, 28px);
  margin: 0 0 clamp(20px, 2.6vw, 30px);
}
.merch__title { font-size: clamp(16px, 1.7vw, 21px); font-weight: 600; line-height: 1.2; margin: 0; }
/* The visible mark is a short underlined line; the tap area is not. Padding
   carries it to 44px without changing how it looks, and the negative margin
   keeps it optically aligned with the heading beside it. */
.merch__all {
  font-size: clamp(11px, 0.95vw, 13px); letter-spacing: 0.16em; text-transform: uppercase;
  text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;
  margin: -13px 0;
}
.merch__all span { border-bottom: 1px solid currentColor; padding-bottom: 2px; }

/* ---------- the Butik row: cards that scroll sideways ----------

   Each card is a photograph with the product name across the top and the price
   at the foot, and the row scrolls horizontally with snap. Cards are sized so
   the next one is always partly visible — that sliver is what tells a visitor
   there is more without needing an arrow to say so.

   The arrows and the progress bar are NOT decoration and are not always there:
   .merch__nav ships hidden and shop-teaser.js reveals it only when the track
   actually overflows its container. Three products on a wide desktop fit, so no
   controls appear; add a fourth and they do. */

.merch__track {
  --merch-gap: clamp(12px, 1.6vw, 22px);
  display: flex;
  gap: var(--merch-gap);
  overflow-x: auto;
  /* proximity, not mandatory. With mandatory the browser must land on a snap
     point, so a row overflowing by only a few dozen pixels jumped straight to
     its end on load and the first card opened half cut off. */
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  /* bleed to the page edges so a card can sit half-off the right gutter */
  margin: 0 calc(var(--gutter) * -1);
  padding: 0 var(--gutter);
  /* Snap aligns a card to the SNAPPORT, which by default is the scrollport and
     ignores the padding above. Without this, snapping card one into place
     scrolled the track by a whole gutter, so scrollLeft 0 was unreachable and
     the first card sat flush to the screen edge instead of on the page's
     left margin. */
  scroll-padding: 0 var(--gutter);
  /* justify-content:center on a scrolling flex row clips the first item in
     several browsers, so centring is applied by class only when the row
     genuinely fits. shop-teaser.js owns that class — it is already measuring
     overflow for the arrows. */
  /* room for the focus ring on a keyboard-tabbed card */
  padding-bottom: 4px;
  scrollbar-width: none;
}
.merch__track::-webkit-scrollbar { display: none; }
.merch__track--fits { justify-content: center; }

.merch__item {
  position: relative;
  /* One card plus a peek on a phone; exactly three across from the 900px
     breakpoint up (see below). */
  flex: 0 0 clamp(250px, 76vw, 330px);
  aspect-ratio: 4 / 5;
  overflow: hidden;
  text-decoration: none;
  scroll-snap-align: start;
  background: color-mix(in srgb, currentColor 8%, transparent);
}

.merch__shot { position: absolute; inset: 0; display: block; margin: 0; }
.merch__shot img {
  /* contain, not cover. The products are shot at different ratios — two
     portrait posters, a cap and a flat tee — and cover was cutting the cap's
     brim and the posters' edges off. Padding keeps the image off the card's
     corners so it reads as placed rather than as a failed crop. */
  width: 100%; height: 100%; object-fit: contain;
  padding: clamp(14px, 1.8vw, 26px);
  transition: transform 600ms var(--ease);
}
.merch__item:hover .merch__shot img { transform: scale(1.03); }
.merch__shot--empty { border: 1px solid color-mix(in srgb, currentColor 22%, transparent); }

/* The scrim. A product shot can be light or dark, and the label sits on top of
   it either way, so the legibility cannot depend on the photograph. */
.merch__item::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background:
    linear-gradient(to bottom, rgba(10,8,6,0.58) 0%, rgba(10,8,6,0) 30%),
    linear-gradient(to top,    rgba(10,8,6,0.72) 0%, rgba(10,8,6,0) 42%);
}

.merch__name,
.merch__foot { position: relative; z-index: 1; color: #F6F1E7; }

.merch__name {
  display: block; text-align: center;
  padding: clamp(16px, 2vw, 26px) clamp(12px, 1.6vw, 20px) 0;
  font-size: clamp(13px, 1.25vw, 17px);
  letter-spacing: 0.14em; text-transform: uppercase;
}

.merch__foot {
  position: absolute; left: 0; right: 0; bottom: clamp(16px, 2vw, 26px);
  display: grid; justify-items: center; gap: 10px;
  padding: 0 clamp(12px, 1.6vw, 20px);
  text-align: center;
}
.merch__price { font-size: clamp(12px, 1.05vw, 14px); opacity: 0.9; }
.merch__cta {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: var(--eyebrow); letter-spacing: 0.18em; text-transform: uppercase;
}
.merch__cta span {
  display: grid; place-items: center;
  width: 20px; height: 20px; border-radius: 50%;
  border: 1px solid currentColor;
  transition: transform 400ms var(--ease);
}
.merch__item:hover .merch__cta span { transform: translateX(3px); }

/* ---- the controls ---- */

.merch__nav {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(14px, 2vw, 24px);
  margin: clamp(20px, 2.6vw, 32px) auto 0;
  padding: 8px clamp(14px, 2vw, 22px);
  width: max-content;
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
  border-radius: 999px;
}
.merch__nav[hidden] { display: none; }

.merch__arrow {
  background: none; border: 0; cursor: pointer; color: inherit;
  font: inherit; line-height: 1;
  display: grid; place-items: center;
  width: 30px; height: 30px;
}
.merch__arrow:disabled { opacity: 0.28; cursor: default; }
.merch__arrow svg { width: 17px; height: 17px; }

.merch__bar {
  position: relative; display: block;
  width: clamp(72px, 12vw, 132px); height: 1px;
  background: color-mix(in srgb, currentColor 26%, transparent);
}
.merch__barfill {
  position: absolute; top: -1px; left: 0; height: 3px;
  background: currentColor;
  transition: transform 220ms linear, width 220ms linear;
}

/* Exactly three across, equal gaps. The percentage resolves against the
   track's content box, which is the viewport less its two gutters, so three
   cards and two gaps land flush inside them. No fourth card peeks any more —
   the arrows carry that job, and they appear because the track still overflows
   by a whole card. */
@media (min-width: 900px) {
  .merch__item {
    flex: 0 0 calc((100% - 2 * var(--merch-gap)) / 3);
  }
}

@media (prefers-reduced-motion: reduce) {
  .merch__track { scroll-behavior: auto; }
  .merch__shot img,
  .merch__cta span,
  .merch__barfill { transition: none; }
}

/* Belt and braces with the z-index: while a modal is open the page chrome is not
   just behind it, it is gone.

   .is-locked is set by both the lightbox and the cart, and neither owns the nav
   button, so suppressing it there is right for both. The nav panel does NOT set
   .is-locked — it uses .nav-open — so the nav keeps its own close button. */
body.is-locked .navbtn,
body.is-locked .shophead__cart { opacity: 0; pointer-events: none; }

/* ---------- opening hours ---------- */

.hours { display: flex; flex-direction: column; gap: 5px; }
.hours__row { display: flex; gap: clamp(14px, 2vw, 26px); font-size: var(--body); line-height: 1.6; opacity: 0.66; }
.hours__row--today { opacity: 1; font-weight: 600; }
.hours__day { min-width: 7ch; letter-spacing: 0.02em; }
.hours__time { font-variant-numeric: tabular-nums; }

.today { display: inline-flex; align-items: center; gap: 8px; }
/* Open and closed were separated by the opacity of an otherwise identical dot,
   which is to say the dot said nothing. Open is now lit; closed stays a faint
   neutral. The text beside it already names the state, so colour is never the
   only code. */
.today::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto;
  background: currentColor; opacity: 0.3;
}
.today.is-open::before { background: var(--accent); opacity: 1; }

/* A block for something that deserves more than a line inside About —
   au Vin's cheese and charcuterie counter, which is its own reason to visit. */
.feature {
  padding: var(--space-tight) var(--gutter);
  border-top: 1px solid color-mix(in srgb, currentColor 16%, transparent);
}
.feature__lead {
  font-size: clamp(22px, 2.6vw, 32px);
  line-height: 1.24;
  margin: 0 0 clamp(20px, 2.4vw, 28px);
  max-width: 24ch;
}
.v-vin .feature__lead { font-family: 'Caveat', cursive; font-weight: 700; font-size: clamp(28px, 3.4vw, 42px); }
.v-kutym .feature__lead { font-family: 'Prata', serif; }

/* ---------- full-screen navigation ---------- */

.navbtn {
  position: fixed; top: clamp(14px, 2vw, 28px); right: clamp(14px, 3vw, 44px);
  z-index: 120;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; width: 52px; height: 52px; padding: 0;
  border: 1px solid rgba(244,239,228,0.3); border-radius: 50%;
  background: rgba(20,15,10,0.34);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  cursor: pointer;
}
.navbtn__bar {
  display: block; width: 18px; height: 1.5px; background: var(--light);
  transition: transform 420ms var(--ease), opacity 220ms var(--ease);
}
body.nav-open .navbtn { background: transparent; border-color: rgba(244,239,228,0.4); }
body.nav-open .navbtn__bar:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
body.nav-open .navbtn__bar:nth-child(2) { opacity: 0; transform: scaleX(0); }
body.nav-open .navbtn__bar:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.navpanel {
  position: fixed; inset: 0; z-index: 110;
  /* The room's own pair, like .revealfoot__panel. The panel belongs to the
     page it opens over, so on au Vin it is that room's green and on Bar Kutym
     its colder black — not one warm near-black everywhere. Fallback for pages
     without a .v-* class. */
  background: var(--cover-bg, var(--ink)); color: var(--cover-on, var(--light));
  display: flex; flex-direction: column; justify-content: space-between;
  gap: clamp(28px, 5vh, 60px);
  padding: clamp(84px, 11vh, 130px) var(--gutter) clamp(28px, 5vh, 56px);
  overflow-y: auto;
  /* The wipe: closed is a zero-height sliver at the top. */
  clip-path: inset(0 0 100% 0);
  transition: clip-path 760ms var(--ease);
}
.navpanel[hidden] { display: none; }
body.nav-open .navpanel { clip-path: inset(0 0 0 0); }

.navpanel__rooms { display: flex; flex-direction: column; }
.navpanel__room {
  text-decoration: none; line-height: 1.08;
  align-items: center;
  padding: clamp(4px, 0.8vh, 10px) 0;
  display: flex; align-items: baseline; gap: clamp(10px, 1.6vw, 22px); flex-wrap: wrap;
  opacity: 0; transform: translateY(18px);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease), color 220ms var(--ease);
}
body.nav-open .navpanel__room { opacity: 1; transform: none; }
/* The delays used to be declared for rows 1-4 while five rows existed, so Press
   got 0ms and the stagger ran backwards — the last item arrived before the
   first. The base delay below means a row added later inherits a sane value
   instead of jumping the queue. */
body.nav-open .navpanel__room            { transition-delay: 300ms; }
body.nav-open .navpanel__room:nth-child(2) { transition-delay: 370ms; }
body.nav-open .navpanel__room:nth-child(3) { transition-delay: 440ms; }
body.nav-open .navpanel__room:nth-child(4) { transition-delay: 510ms; }
body.nav-open .navpanel__room:nth-child(5) { transition-delay: 580ms; }
body.nav-open .navpanel__room:nth-child(n+6) { transition-delay: 650ms; }
.navpanel__room:hover { color: #fff; }
body.nav-open .navpanel__room[aria-current="page"] { opacity: 0.4; }
.navpanel__room[aria-current="page"] { cursor: default; }

/* The rooms use their real wordmarks, not typeset substitutes. Two of the
   three cannot be set as type at all: the au Vin mark is drawn lettering, and
   Bar Kutym's Tipton Demi carries a desktop-only licence that explicitly
   forbids @font-face. The artwork is the only correct letterform.
   Sized by height, since the three have very different proportions. */
.navpanel__mark { display: block; height: auto; }
.navpanel { --mw: clamp(110px, 19vw, 232px); }
.nv-teuf .navpanel__mark { width: var(--mw); margin: -3% 0; }
.nv-kutym .navpanel__mark { width: min(86%, calc(var(--mw) * 2.59)); }
/* au Vin's crop removed the padding the -2% was cancelling out. */
.nv-vin  .navpanel__mark { width: calc(var(--mw) * 0.80); }
.navpanel__name { font-size: clamp(26px, 4.8vw, 58px); }
/* The homepage lost its util strip on 2026-10-09, and with it the only link to
   the privacy policy from the site's most-visited page. It lives here now, on
   every page, set as fine print rather than as a room. */
.navpanel__fine { font-size: var(--eyebrow); letter-spacing: 0.14em; text-transform: uppercase; opacity: 0.55; }
.navpanel__fine a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }
.nv-shop .navpanel__name,
.nv-press .navpanel__name { font-weight: 500; font-stretch: 112%; }
.navpanel__role {
  font-size: clamp(9px, 0.85vw, 11px); letter-spacing: 0.3em; text-transform: uppercase;
  opacity: 0.5;
}
/* Same mid-tone problem as the footer and the menu panel: 0.5 and 0.66 do not
   clear AA on au Vin's green. 0.72 is this pair's floor. Scoped — the two
   black panels are unaffected. */
.v-vin .navpanel__role,
.v-vin .navpanel__meta { opacity: 0.72; }

.navpanel__foot {
  display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
  gap: clamp(16px, 3vw, 40px);
  opacity: 0; transform: translateY(12px);
  transition: opacity 500ms var(--ease) 620ms, transform 500ms var(--ease) 620ms;
}
body.nav-open .navpanel__foot { opacity: 1; transform: none; }
.navpanel__meta { display: flex; flex-direction: column; gap: 7px; font-size: clamp(11px, 1vw, 13px); opacity: 0.66; }

body.nav-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .navpanel { transition: none; }
  .navpanel__room, .navpanel__foot { transition: none; opacity: 1; transform: none; }
}

.navpanel__actions { display: flex; align-items: center; gap: clamp(16px, 2.4vw, 28px); }
.social {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;           /* full tap target, icon stays 22px */
  color: var(--light); opacity: 0.66;
  transition: opacity 200ms var(--ease);
}
.social:hover { opacity: 1; }
.social svg { width: 22px; height: 22px; display: block; }

/* The address is a link everywhere it appears — on a phone it opens the maps
   app, which is what people actually want from it. */
.maplink {
  color: inherit; text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  opacity: 0.85;
  transition: opacity 180ms var(--ease);
}
.maplink:hover { opacity: 1; }
.util .maplink, .foot .maplink { border-bottom-color: rgba(239,231,216,0.4); }

/* ---------- revealed footer ---------- */
/* The page clips a fixed panel, so the footer reads as sitting behind the page
   and being uncovered as you reach the bottom. Ported from a React component;
   the mechanism is entirely CSS. clip-path clips fixed descendants without
   making them scroll, which is the whole trick. */

.revealfoot {
  /* Tall enough for the content it holds — measured, not guessed. The phone
     layout stacks three columns and needs more height than the desktop one. */
  /* svh, not vh: the hero already uses svh, and sizing the footer to the LARGE
     viewport meant its bottom row could sit under iOS browser chrome. */
  --foot-h: clamp(560px, 70svh, 620px);
  position: relative;
  height: var(--foot-h);
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
.revealfoot__panel {
  position: fixed; bottom: 0; left: 0;
  width: 100%; height: var(--foot-h);
  /* The room's own pair, not a hardcoded one. Until 2026-10-09 every page's
     footer was --ink, so Bar Kutym ended on a warm near-black that its page
     uses nowhere, and au Vin — a cream page whose only dark material is the
     green — ended on a third, browner dark. The fallback keeps any page
     without a .v-* class working. */
  background: var(--cover-bg, var(--ink)); color: var(--cover-on, #EFE7D8);
  display: flex; flex-direction: column;
  padding: clamp(30px, 5vh, 56px) var(--gutter) clamp(20px, 3vh, 30px);
  box-sizing: border-box;
  overflow-y: auto;
}
.revealfoot__cols {
  flex: 1 1 auto;
  display: grid; gap: clamp(24px, 4vw, 64px);
  grid-template-columns: 1fr;
}
.revealfoot .eyebrow { opacity: 0.45; margin-bottom: clamp(10px, 1.4vw, 16px); }
.revealfoot__addr { font-size: var(--body); line-height: 1.7; margin: 0 0 10px; }
.revealfoot .hours__row { opacity: 0.6; }
.revealfoot .hours__row--today { opacity: 1; }
.revealfoot__act { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(14px, 2vw, 22px); }
/* Instagram and booking sit at the lower corners of the footer, above the rule.
   The fine print goes below it. */
.revealfoot__actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(12px, 2vw, 24px);
  padding-bottom: clamp(18px, 2.6vh, 28px);
}
.revealfoot__actions .social { margin-left: -12px; }   /* optical align to the gutter */

.revealfoot__base {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  gap: 6px clamp(16px, 3vw, 32px);
  padding-top: clamp(14px, 2vh, 20px);
  border-top: 1px solid color-mix(in srgb, currentColor 16%, transparent);
}
.revealfoot__fine {
  font-size: clamp(10px, 0.85vw, 12px); letter-spacing: 0.14em; text-transform: uppercase;
  opacity: 0.5; line-height: 1.7;
}
.revealfoot__fine a { color: inherit; text-decoration: none; border-bottom: 1px solid color-mix(in srgb, currentColor 34%, transparent); padding-bottom: 1px; }
.revealfoot__fine a:hover { border-bottom-color: currentColor; }


.revealfoot .social { color: inherit; }

/* Same problem the menu panel already has, for the same reason: au Vin's
   green is a mid tone rather than a near-black, so the footer's quiet
   opacities stop clearing AA against it. 0.45 and 0.5 measure far under;
   0.72 is the floor that works on this pair, exactly as on .split__eyebrow.
   Scoped, because the two black footers are unaffected. */
.v-vin .revealfoot .eyebrow,
.v-vin .revealfoot__fine,
.v-vin .revealfoot .hours__row { opacity: 0.72; }
.v-vin .revealfoot .hours__row--today { opacity: 1; }

@media (min-width: 760px) {
  .revealfoot__cols { grid-template-columns: 1.2fr 1fr 1fr; align-items: start; }
}

/* If the reveal ever misbehaves — iOS Safari has historically been awkward
   with fixed inside a clipped ancestor — this turns it into an ordinary
   footer and loses nothing but the effect:
     .revealfoot { height: auto; clip-path: none; }
     .revealfoot__panel { position: static; height: auto; }
   Needs checking on real hardware before deciding. */

.contact { list-style: none; margin: 0 0 clamp(16px, 2.4vh, 24px); padding: 0; display: flex; flex-direction: column; gap: 7px; }
.contact a {
  font-size: var(--body); color: inherit; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, currentColor 34%, transparent); padding-bottom: 1px;
  transition: border-color 180ms var(--ease);
}
.contact a:hover { border-bottom-color: currentColor; }
.contact__tag {
  font-size: clamp(9px, 0.75vw, 10px); letter-spacing: 0.22em;
  text-transform: uppercase; opacity: 0.45; margin-left: 8px;
}

/* Butik already has a cart button top-right, so the menu toggle would land on
   top of it. Make the cart fixed too, sit it to the left of the toggle, and
   take it out of the way while the menu is open. */
/* Fixed, so it is over the dark header at the top of the page and over the cream
   product grid everywhere else. It used to inherit the header's light colour and
   measured 1.08:1 against the page the moment you scrolled — the only persistent
   route to the cart, invisible for the whole time you are shopping. It now
   carries its own ground so it reads on both. */
.shop-page .shophead__cart {
  position: fixed;
  top: clamp(14px, 2vw, 28px);
  right: calc(clamp(14px, 3vw, 44px) + 64px);
  z-index: 115;

  /* .navbtn sits beside this at the same top and is a hard 52px circle. Match
     its height and the two centres line up at every breakpoint — the label is
     already centred inside by the flex rules above. Nudging `top` instead
     would drift the moment either element's padding changed. */
  min-height: 52px;

  /* Default: over the cream product grid, which is where it spends almost all
     of its life. Dark button, light label. */
  --btn-bg: #0b090a;
  --btn-fg: var(--paper);

  background: var(--btn-bg);
  color: var(--btn-fg);
  border-color: var(--btn-bg);
  transition: opacity 260ms var(--ease), background 200ms var(--ease),
              color 200ms var(--ease), border-color 200ms var(--ease);
}

/* Over the dark header, the pair flips. A fixed element has no idea what is
   behind it, so js/shop.js sets this class by comparing the button's box with
   the header's on scroll — there is no CSS route to "what am I on top of". */
.shop-page .shophead__cart.is-on-dark {
  --btn-bg: var(--light);
  --btn-fg: #0b090a;
}

.shop-page .shophead__cart:hover {
  background: transparent;
  color: var(--btn-bg);
  border-color: var(--btn-bg);
}
.shop-page .shophead__cart:hover .pill { background: var(--btn-bg); color: var(--btn-fg); }
body.nav-open .shophead__cart { opacity: 0; pointer-events: none; }

/* The shop has no opening hours of its own, so its footer runs two columns. */
@media (min-width: 760px) {
  .revealfoot--shop .revealfoot__cols { grid-template-columns: 1.2fr 1fr; }
}


/* ── Integritetspolicy ────────────────────────────────────────────────────
   A legal document, so it is set for reading rather than for effect: one
   measure, generous leading, and no reveal animation — nobody should have to
   scroll past a word-by-word reveal to find out how their data is handled.
   The type is a step smaller than .body-text because the page is long and a
   smaller measure keeps the line length honest at 68ch. */
.policy { max-width: 74ch; margin-inline: auto; }
.policy__title {
  margin: clamp(10px, 1.2vw, 16px) 0 6px;
  font-size: clamp(30px, 4.4vw, 52px);
  font-weight: 500; font-stretch: 112%;
  line-height: 1.08;
}
.policy__updated {
  margin: 0 0 clamp(28px, 3.4vw, 44px);
  font-size: var(--eyebrow); letter-spacing: 0.18em; text-transform: uppercase;
  opacity: 0.6;
}
.policy h2 {
  margin: clamp(32px, 3.6vw, 48px) 0 clamp(10px, 1.2vw, 14px);
  font-size: clamp(17px, 1.5vw, 21px);
  font-weight: 600;
  line-height: 1.25;
}
.policy p,
.policy li {
  font-size: clamp(14px, 1.02vw, 15px);
  line-height: 1.8;
  opacity: 0.86;
}
.policy p { margin: 0 0 clamp(14px, 1.4vw, 18px); max-width: 68ch; }
.policy ul {
  margin: 0 0 clamp(16px, 1.6vw, 22px);
  padding-left: 1.1em;
  max-width: 68ch;
}
.policy li { margin-bottom: 10px; }
.policy li::marker { opacity: 0.4; }
.policy strong { font-weight: 600; opacity: 1; }
/* Links in a legal document must be unmistakably links. */
.policy a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.policy a:hover { text-decoration-thickness: 2px; }
.policy .back { position: static; display: inline-block; margin-bottom: clamp(18px, 2.2vw, 28px); }
