/* =====================================================================
   nv-menu — the list page: menu, catalogue and services
   =====================================================================

   REQUIRES app/css/nv-base.css, loaded before this file.

   This is the page a visitor reaches by pressing the call to action on the
   card, so it is built from the same tokens and the same surfaces. Same
   palette, same radius, same motion, the same `.nv-sheet` for anything that
   opens over it. It should read as the card opening out, not as arriving
   somewhere else.

   SIZE
   ----
   On a phone the sheet is the viewport, exactly as the card is. Once there
   is room it widens — a menu is a long list, and holding a 416px column on
   a 1280px screen would be continuity bought with the visitor's patience.
   The sheet grows to --nv-menu-max and the items go to two columns.

   ONE PAGE, THREE LAYOUTS
   -----------------------
   `data-layout` on .nv-menu is `menu`, `catalog` or `services`. Everything
   below is shared unless it is under a [data-layout] selector. The three
   templates this replaces duplicated their entire cart and checkout, and
   the copies had already drifted apart.

   STRUCTURE
   ---------
   .nv-menu                      the sheet, and the Alpine root
     .nv-menu__head              logo, title, open/closed, cart, back
     .nv-menu__filters           list chips, then sub-category chips
     .nv-menu__body              the only thing that scrolls
       .nv-item                  a product
     .nv-menu__bar               the cart summary, when there is one
   ===================================================================== */

:root {
  --nv-menu-max: 49rem;          /* 784px — two comfortable columns */
  --nv-menu-gap: .6rem;
  /* Detail chips sit on --nv-surface-hi, one step darker than the card
     surfaces --nv-back-muted was chosen against. At 10.5px that dropped the
     ratio to 4.48:1, just under AA for small text, so the chips get their
     own foreground rather than nudging a token the card also uses. */
  --nv-tag-fg: #4b5563;
}
html[data-theme="dark"], html.dark { --nv-tag-fg: #a7b4c6; }
@media (prefers-color-scheme: dark) {
  html:not([data-theme="light"]):not(.light) { --nv-tag-fg: #a7b4c6; }
}

/* ── the sheet ──────────────────────────────────────────────────────── */

.nv-menu-page {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  padding: max(env(safe-area-inset-top), 10px) 10px max(env(safe-area-inset-bottom), 10px);
  box-sizing: border-box;
  overflow: hidden;
  isolation: isolate;
}
.nv-menu-page::before {
  content: "";
  position: absolute;
  inset: -20%;
  z-index: -1;
  background:
    radial-gradient(55% 50% at 30% 18%, var(--nv-stage-glow), transparent 72%),
    radial-gradient(50% 55% at 74% 82%, var(--nv-stage-glow), transparent 72%);
  pointer-events: none;
}

.nv-menu {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--nv-max-w);
  height: 100%;
  min-height: 0;
  box-sizing: border-box;
  border-radius: var(--nv-radius);
  overflow: hidden;
  background: var(--nv-back-bg);
  color: var(--nv-back-fg);
  box-shadow: var(--nv-shadow);
}

@media (min-width: 600px) {
  .nv-menu { max-width: var(--nv-menu-max); }
}

/* ── header ─────────────────────────────────────────────────────────── */

.nv-menu__head {
  flex: none;
  display: flex;
  align-items: center;
  gap: .7rem;
  padding: .8rem var(--nv-pad);
  background: var(--nv-back-bg-2);
  border-bottom: 1px solid var(--nv-line);
}

.nv-menu__logo {
  width: 40px; height: 40px;
  flex: none;
  border-radius: 12px;
  object-fit: cover;
  border: 1px solid var(--nv-line);
}

.nv-menu__titles { flex: 1; min-width: 0; }
.nv-menu__title {
  margin: 0;
  font-size: 1rem;
  font-weight: 800;
  line-height: 1.5;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The open/closed line, same shape and the same tokens as the caption on
   the card, so the two pages state it identically. */
.nv-menu__status {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: .72rem;
  color: var(--nv-back-muted);
}
.nv-menu__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
.nv-menu__status.is-open   { color: var(--nv-open); }
.nv-menu__status.is-closed { color: var(--nv-closed); }

.nv-mbtn {
  appearance: none;
  position: relative;
  width: 38px; height: 38px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--nv-line);
  border-radius: 11px;
  background: var(--nv-surface);
  color: var(--nv-back-fg);
  font: inherit;
  font-size: 14px;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .2s var(--nv-ease-soft), transform .12s var(--nv-ease-soft);
}
.nv-mbtn:hover { background: var(--nv-surface-hi); }
.nv-mbtn:active { transform: scale(.92); }
.nv-mbtn svg { width: 16px; height: 16px; }

.nv-mbtn__count {
  position: absolute;
  top: -5px; inset-inline-end: -5px;
  min-width: 18px; height: 18px;
  padding: 0 4px;
  box-sizing: border-box;
  border-radius: 999px;
  background: var(--nv-accent);
  color: var(--nv-accent-fg);
  font-size: .62rem;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
  animation: nv-pop .28s var(--nv-ease);
}
@keyframes nv-pop {
  0%   { transform: scale(.4); opacity: 0; }
  60%  { transform: scale(1.18); opacity: 1; }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) { .nv-mbtn__count { animation: none; } }

/* ── search ─────────────────────────────────────────────────────────── */

.nv-menu__search {
  flex: none;
  /* A real bottom gap, and NO `overflow: hidden`. The old rule had neither:
     the focus ring and the input's rounded corners were clipped at the box
     edge, and with zero bottom padding the field sat directly on the chip
     rail, so a focused input's outline collided with the first chip.
     The gap below is deliberately larger than the one above — search and
     filters are two different controls and should not read as one block. */
  padding: .6rem var(--nv-pad) .9rem;
  /* A hairline that says where searching ends and filtering begins. */
  border-bottom: 1px solid var(--nv-line);
  margin-bottom: .1rem;
}
.nv-search {
  position: relative;
  display: flex;
  align-items: center;
}
.nv-search input {
  box-sizing: border-box;
  width: 100%;
  min-height: 40px;
  padding-block: .5rem;
  padding-inline: .8rem 34px;
  border: 1px solid var(--nv-line);
  border-radius: 12px;
  background: var(--nv-surface);
  color: var(--nv-back-fg);
  font: inherit;
  font-size: .88rem;
}
.nv-search input::placeholder { color: var(--nv-back-muted); }
.nv-search__ico {
  position: absolute;
  inset-inline-end: 11px;
  width: 15px; height: 15px;
  color: var(--nv-back-muted);
  pointer-events: none;
}
.nv-search__clear {
  position: absolute;
  inset-inline-end: 8px;
  width: 24px; height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 8px;
  background: var(--nv-surface-hi);
  color: var(--nv-back-muted);
  font-size: 11px;
  cursor: pointer;
}

/* ── filter chips ───────────────────────────────────────────────────── */

/* .nv-menu__filters and its is-compact state are defined with the list
   tiles below, where the folding behaviour is explained. */

.nv-chips {
  display: flex;
  /* stretch, so every chip in a rail is exactly as tall as the tallest —
     a chip carrying a thumbnail no longer stands a few pixels prouder than
     its text-only neighbours. */
  align-items: stretch;
  gap: .4rem;
  padding: .5rem var(--nv-pad);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* The chip row scrolls sideways; the page must not. */
  touch-action: pan-x;
  /* pickList() calls scrollIntoView on the selected chip. Without a scroll
     padding the "centred" chip is centred against the raw box, so the first
     and last chips look off — this reserves the same breathing room the
     padding gives. */
  scroll-padding-inline: var(--nv-pad);
}
.nv-chips::-webkit-scrollbar { display: none; }
.nv-chips--sub { padding-top: 0; }

.nv-chip {
  appearance: none;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  /* Uniform metrics: one height, one floor for the width. The width is a
     MINIMUM rather than a fixed size on purpose — pinning every chip to the
     same width would truncate the longer Persian category names, which is a
     worse trade than a rail whose chips grow with their label. */
  min-height: 36px;
  min-width: 5.25rem;
  max-width: 60vw;
  box-sizing: border-box;
  padding: .4rem .75rem;
  border: 1px solid var(--nv-line);
  border-radius: 999px;
  background: var(--nv-surface);
  color: var(--nv-back-fg);
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .2s var(--nv-ease-soft),
              color .2s var(--nv-ease-soft),
              border-color .2s var(--nv-ease-soft);
}
.nv-chip:hover { background: var(--nv-surface-hi); }
.nv-chip.is-on {
  background: var(--nv-accent);
  border-color: transparent;
  color: var(--nv-accent-fg);
}
.nv-chip__img {
  width: 20px; height: 20px;
  border-radius: 6px;
  object-fit: cover;
  flex: none;
}
.nv-chip__off {
  font-size: .68rem;
  font-weight: 800;
  padding: .05rem .3rem;
  border-radius: 5px;
  background: var(--nv-closed);
  color: #fff;
}
.nv-chip.is-on .nv-chip__off { background: var(--nv-accent-fg); color: var(--nv-accent); }

/* ── list tiles ─────────────────────────────────────────────────────── */
/* The list rail is square picture tiles, all one size. A list is a place —
   "drinks", "main courses" — and a picture identifies a place faster than a
   word does. Every tile is exactly --nv-lchip-w wide with a 1:1 image and a
   single-line name, so the rail is a row of identical cards rather than a
   ragged run of pills that grow with their label.

   --nv-lchip-w is the one number to change to resize the whole rail. */

.nv-chips--lists {
  --nv-lchip-w: 4.25rem;      /* 68px — a selector, not a content card */
  align-items: flex-start;
  gap: .5rem;
  padding-block: .45rem;
}

.nv-lchip {
  appearance: none;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: .35rem;
  /* flex-basis, min-width and max-width all pinned to the same token.
     `width` alone was not enough: a flex item's automatic minimum size is
     its min-content width, and a nowrap name makes that the whole string —
     so adding padding to the name when the rail folded pushed each tile from
     68px to 80px, the rail's scrollWidth from 1096 to 1293, and a rail
     scrolled to its far end lurched sideways as the browser re-clamped
     scrollLeft. min-width: 0 is what actually holds the width still. */
  flex: 0 0 var(--nv-lchip-w);
  width: var(--nv-lchip-w);
  min-width: 0;
  max-width: var(--nv-lchip-w);
  box-sizing: border-box;
  padding: 0;
  border: 0;
  background: none;
  /* --nv-tag-fg, not --nv-back-muted: at .74rem the muted grey measured 4.68
     against the surface — over AA, but only just, and this label is the only
     way to tell one list from another when the pictures are similar. */
  color: var(--nv-tag-fg);
  font: inherit;
  font-size: .74rem;
  font-weight: 700;
  text-align: center;
  cursor: pointer;
  transition: color .2s var(--nv-ease-soft);
}
.nv-lchip__img {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1;          /* the 1:1 the uploader already enforces */
  /* A definite cap at BOTH ends, which is what makes the fold animatable:
     `height: auto` cannot be transitioned, and animating `width` reflows the
     whole rail on every frame. See the fold section below. */
  max-height: var(--nv-lchip-w, 4.25rem);
  border-radius: 14px;
  overflow: hidden;
  background: var(--nv-surface-hi);
  border: 2px solid transparent;
  box-sizing: border-box;
  transition: max-height .24s var(--nv-ease-soft),
              opacity .18s var(--nv-ease-soft),
              border-color .2s var(--nv-ease-soft),
              transform .2s var(--nv-ease-soft),
              box-shadow .2s var(--nv-ease-soft);
}
.nv-lchip__img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nv-lchip__initial {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sized from the tile, not hard-coded: the folded rail shrinks the box to
     18px, and a fixed 1.35rem letter spilled straight out of it. */
  font-size: calc(var(--nv-lchip-w, 4.25rem) * .32);
  line-height: 1;
  font-weight: 800;
  color: var(--nv-back-muted);
  overflow: hidden;
}
.nv-lchip__name {
  /* Up to TWO lines, centred under the picture. One line cut every longer
     Persian name to «نوشیدنی…», and the name is the only way to tell two
     lists apart when their pictures are alike. The tile's width is still
     pinned (see .nv-lchip), so wrapping cannot reflow the rail sideways —
     a long name only makes its own tile taller, and the rail's
     align-items: flex-start keeps every picture on the same top line.
     Anything past two lines is clamped; the button's title has the rest. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: break-word;   /* only splits a single word wider than the tile */
  line-height: 1.45;
}
/* Unfolded, the name's pill padding is invisible (no fill, no border) and
   only steals width from the text. So the name drops it and reaches a
   quarter-rem into the empty gap on each side (the rail's gap is .5rem, so
   two neighbouring names can never meet). The TILE stays exactly
   --nv-lchip-w wide — this is overflow of a child, not a wider flex item —
   so the rail's scroll geometry is untouched. 2px keeps the first glyph's
   ink from being shaved off by overflow: hidden. The folded rail below,
   where the pill is drawn, goes back to the tile's width and its padding. */
.nv-menu__filters:not(.is-compact) .nv-lchip__name {
  margin-inline: -.25rem;
  padding-inline: 2px;
}
.nv-lchip__off {
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  padding: .05rem .3rem;
  border-radius: 5px;
  background: var(--nv-closed);
  color: #fff;
  font-size: .62rem;
  font-weight: 800;
}
.nv-lchip:hover .nv-lchip__img { transform: translateY(-1px); }
.nv-lchip.is-on { color: var(--nv-back-fg); }
.nv-lchip.is-on .nv-lchip__img {
  border-color: var(--nv-accent);
  box-shadow: 0 4px 14px -6px var(--nv-accent);
}

/* ── the folded rail ────────────────────────────────────────────────── */
/* Scrolling down rolls the pictures away and leaves the names; scrolling up
   brings them back. A fifth of a phone screen handed back to the thing the
   visitor is actually reading. Only `is-compact` is toggled — it is set by
   nvMenu.watchScroll().

   NOTHING HERE MAY CHANGE A TILE'S WIDTH, and that is the whole design.

   The first attempt morphed each tile into a horizontal pill: flex-direction
   flipped, the width went from 68px to auto, the image shrank to 18px. Every
   one of those reflows the rail, so mid-fold the browser was re-laying out
   fourteen tiles a frame — and worse, the rail's scrollWidth changed under a
   scrollLeft that was already near the end, so the whole strip lurched
   sideways. That lurch is the glitch.

   Collapsing max-height instead keeps the column direction, the 68px width
   and therefore the scroll geometry exactly as they were. One property
   animates, the rail cannot jump, and the names never move sideways. */

.nv-menu__filters { flex: none; }

.nv-menu__filters.is-compact .nv-lchip__img {
  max-height: 0;
  opacity: 0;
  border-width: 0;
  box-shadow: none;
}
/* The gap belongs to the picture; with the picture gone it is dead space. */
.nv-menu__filters.is-compact .nv-lchip { gap: 0; }
.nv-menu__filters.is-compact .nv-chips--lists { padding-block: .4rem; }

/* With no picture to carry the selection, the name has to. */
.nv-lchip__name {
  padding: .18rem .3rem;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: background-color .2s var(--nv-ease-soft),
              border-color .2s var(--nv-ease-soft),
              color .2s var(--nv-ease-soft);
}
.nv-menu__filters.is-compact .nv-lchip__name {
  background: var(--nv-surface);
  border-color: var(--nv-line);
  /* The folded rail is a single row of pills: back to one line here, as it
     always was. The height change at the flip is absorbed by watchScroll()'s
     hold(), which corrects for any change in the rail's height per frame. */
  display: block;
  white-space: nowrap;
  text-overflow: ellipsis;
  line-height: 1.6;
}
.nv-menu__filters.is-compact .nv-lchip.is-on .nv-lchip__name {
  background: var(--nv-accent);
  border-color: transparent;
  color: var(--nv-accent-fg);
}

/* The sub rail rolls away on the same curve rather than vanishing on one
   frame. max-height, not display:none, so the block's height animates with
   the pictures instead of stepping. */
.nv-chips--sub {
  max-height: 4rem;
  transition: max-height .24s var(--nv-ease-soft),
              opacity .18s var(--nv-ease-soft),
              padding-block .24s var(--nv-ease-soft);
}
.nv-menu__filters.is-compact .nv-chips--sub {
  max-height: 0;
  opacity: 0;
  padding-block: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .nv-lchip, .nv-lchip__img, .nv-lchip__name, .nv-chips--sub { transition: none; }
}

/* ── the scrolling body ─────────────────────────────────────────────── */

.nv-menu__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: .3rem var(--nv-pad) 1rem;
  scrollbar-width: thin;
  scrollbar-color: var(--nv-line) transparent;
  scroll-behavior: smooth;
}
.nv-menu__body::-webkit-scrollbar { width: 6px; }
.nv-menu__body::-webkit-scrollbar-thumb {
  background: var(--nv-line);
  border-radius: 999px;
}
@media (prefers-reduced-motion: reduce) { .nv-menu__body { scroll-behavior: auto; } }

.nv-group__head {
  position: sticky;
  top: -.3rem;
  z-index: 2;
  margin: .5rem 0 .45rem;
  padding: .35rem 0;
  background: var(--nv-back-bg);
  font-size: .78rem;
  font-weight: 800;
  color: var(--nv-back-muted);
  letter-spacing: .01em;
}

/* ── groups ──────────────────────────────────────────────────────────
   Each sub-category is a <section class="nv-group">, spaced from the one
   above by its heading's margin. The items that belong straight to the
   list (no sub-category) form a group with NO heading — so it had no
   spacing at all, and its first card sat flush against (and, with the
   catalogue's deep shadows, visually over) the last card of the group
   above. This puts back a clear break, a little wider than the gap
   between cards so it still reads as a new group. Only `~`, never the
   first group: a list with no sub-categories starts exactly as before. */
.nv-group ~ .nv-group--bare {
  margin-top: calc(var(--nv-menu-gap) * 2);
}

/* Tabular: every cell in a row is the same height, and `.nv-item` fills its
   cell. Without `grid-auto-rows: 1fr` a two-column row is as tall as its
   taller card and the shorter one floats with a ragged gap under it, which is
   what made the list look thrown together rather than laid out. */
.nv-grid {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: 1fr;
  gap: var(--nv-menu-gap);
}
@media (min-width: 600px) {
  .nv-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ── an item ────────────────────────────────────────────────────────── */

.nv-item {
  position: relative;
  display: flex;
  /* flex-start, not stretch: a stretched flex item ignores aspect-ratio for
     its height, so the square thumbnail grew to whatever the text beside it
     needed and the row became twice as tall as it should be. */
  align-items: flex-start;
  gap: .6rem;
  /* Fill the grid cell. Combined with grid-auto-rows: 1fr above, this is
     what makes neighbouring cards share an edge instead of each shrinking to
     its own content. */
  height: 100%;
  box-sizing: border-box;
  padding: .5rem;
  border: 1px solid var(--nv-line);
  border-radius: 16px;
  background: var(--nv-surface);
  text-align: start;
  cursor: pointer;
  /* Only opacity and transform animate; both are composited, so a list of
     forty of these still scrolls at frame rate on a cheap phone. */
  transition: border-color .2s var(--nv-ease-soft), transform .12s var(--nv-ease-soft);
  /* Each item is its own paint boundary: an image decoding or a stepper
     re-rendering cannot invalidate the whole list. */
  contain: layout paint;
}
.nv-item:hover { border-color: color-mix(in srgb, var(--nv-accent) 40%, var(--nv-line)); }
.nv-item:active { transform: scale(.99); }

.nv-item.is-out { opacity: .55; }
.nv-item.is-out .nv-item__thumb { filter: grayscale(1); }

/* 1:1 throughout — that is the only shape the uploader accepts, so no layout
   may assume otherwise. Only the SIZE changes per layout, from this one
   token, and the menu's is deliberately modest: the row is scanned for a
   name and a price, and an 84px picture was pushing every row past 140px
   tall for no gain. */
.nv-item__thumb {
  position: relative;
  width: var(--nv-thumb, 64px);
  flex: none;
  align-self: flex-start;
  aspect-ratio: 1;
  border-radius: 12px;
  overflow: hidden;
  background: var(--nv-surface-hi);
}
.nv-item__thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

.nv-item__body {
  flex: 1;
  min-width: 0;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: .1rem;
}
/* One line for the name in the scan layouts: a wrapped name is what makes
   one row taller than its neighbour, and a menu item that needs two lines of
   title is better read in the sheet. */
.nv-item__name {
  font-size: .88rem;
  font-weight: 700;
  line-height: 1.5;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nv-item__desc {
  font-size: .72rem;
  line-height: 1.55;
  color: var(--nv-back-muted);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* ── badges: the model data the page never used to show ─────────────── */

.nv-tags {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  margin-top: .1rem;
}
.nv-tag {
  display: inline-flex;
  align-items: center;
  gap: .22rem;
  padding: .1rem .4rem;
  border-radius: 6px;
  background: var(--nv-surface-hi);
  color: var(--nv-tag-fg);
  font-size: .66rem;
  font-weight: 600;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
.nv-tag b { color: var(--nv-back-fg); font-weight: 700; }
.nv-tag--low   { background: var(--nv-gold-soft);   color: var(--nv-gold); }
.nv-tag--out   { background: color-mix(in srgb, var(--nv-closed) 14%, transparent);
                 color: var(--nv-closed); }
.nv-tag--off   { background: var(--nv-accent);      color: var(--nv-accent-fg); }

/* ── price ──────────────────────────────────────────────────────────── */

.nv-price {
  display: flex;
  align-items: baseline;
  gap: .4rem;
  flex-wrap: wrap;
  margin-top: auto;
  padding-top: .15rem;
}
.nv-price__now {
  font-size: .92rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.nv-price__was {
  font-size: .72rem;
  color: var(--nv-back-muted);
  text-decoration: line-through;
  font-variant-numeric: tabular-nums;
}
.nv-price__unit { font-size: .68rem; color: var(--nv-back-muted); font-weight: 600; }

/* ── stepper ────────────────────────────────────────────────────────── */

.nv-step {
  display: inline-flex;
  align-items: center;
  gap: .1rem;
  flex: none;
  align-self: center;
  padding: 2px;
  border-radius: 11px;
  background: var(--nv-surface-hi);
  border: 1px solid var(--nv-line);
}
.nv-step__b {
  appearance: none;
  width: 28px; height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--nv-back-fg);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
  transition: background-color .15s var(--nv-ease-soft);
}
/* :not(--add) so the add button is never a candidate for this rule.
   Source order and specificity were keeping the two apart by luck, and in
   the catalogue the luck ran out: the layout's hover override scores higher
   than its own --add rule, so after a tap the sticky :hover phones keep on
   the last-tapped element painted the + grey — and the moment it hit the
   limit and went :disabled, the hover rule stopped matching and the colour
   came back. Grey when live, coloured when dead, exactly backwards. */
.nv-step__b:not(.nv-step__b--add):hover:not(:disabled) { background: var(--nv-surface); }
.nv-step__b:disabled { opacity: .35; cursor: not-allowed; }
.nv-step__b--add {
  background: var(--nv-accent);
  color: var(--nv-accent-fg);
}
.nv-step__b--add:hover:not(:disabled) { background: var(--nv-accent); filter: brightness(1.08); }
.nv-step__n {
  min-width: 22px;
  text-align: center;
  font-size: .82rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

/* ── cart bar ───────────────────────────────────────────────────────── */

.nv-menu__bar {
  flex: none;
  padding: .6rem var(--nv-pad);
  padding-bottom: max(.6rem, env(safe-area-inset-bottom));
  background: var(--nv-back-bg-2);
  border-top: 1px solid var(--nv-line);
}
/* Slides up from the edge the first time something lands in the cart, and
   is simply absent otherwise — no empty bar taking a row of the screen. */
.nv-menu__bar {
  animation: nv-bar-in .26s var(--nv-ease);
}
@keyframes nv-bar-in {
  from { transform: translateY(100%); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .nv-menu__bar { animation: none; } }

.nv-cartbtn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  width: 100%;
  box-sizing: border-box;
  min-height: 50px;
  padding: 0 1rem;
  border: 0;
  border-radius: 14px;
  background: var(--nv-accent);
  color: var(--nv-accent-fg);
  font: inherit;
  font-size: .95rem;
  font-weight: 800;
  cursor: pointer;
  transition: filter .2s var(--nv-ease-soft), transform .12s var(--nv-ease-soft);
}
.nv-cartbtn:hover { filter: brightness(1.07); }
.nv-cartbtn:active { transform: scale(.985); }
.nv-cartbtn__total { font-variant-numeric: tabular-nums; }

/* ── rows inside the cart and item sheets ───────────────────────────── */

.nv-line {
  display: flex;
  align-items: center;
  gap: .65rem;
  padding: .55rem 0;
}
.nv-line + .nv-line { border-top: 1px solid var(--nv-line); }
.nv-line__img {
  width: 46px; height: 46px;
  flex: none;
  border-radius: 10px;
  object-fit: cover;
  background: var(--nv-surface-hi);
}
.nv-line__body { flex: 1; min-width: 0; }
.nv-line__name { font-size: .85rem; font-weight: 700; }
.nv-line__meta { font-size: .72rem; color: var(--nv-back-muted); font-variant-numeric: tabular-nums; }

.nv-sum {
  display: flex;
  justify-content: space-between;
  padding: .35rem 0;
  font-size: .84rem;
  color: var(--nv-back-muted);
  font-variant-numeric: tabular-nums;
}
.nv-sum--total {
  margin-top: .35rem;
  padding-top: .6rem;
  border-top: 1px solid var(--nv-line);
  font-size: .98rem;
  font-weight: 800;
  color: var(--nv-back-fg);
}
.nv-sum--save { color: var(--nv-open); }

/* ── forms ──────────────────────────────────────────────────────────── */

.nv-field { margin-bottom: .6rem; }
.nv-field__label {
  display: block;
  font-size: .74rem;
  font-weight: 600;
  color: var(--nv-back-muted);
  margin-bottom: .25rem;
}
.nv-menu input[type="text"],
.nv-menu input[type="tel"],
.nv-menu textarea,
.nv-sheet input[type="text"],
.nv-sheet input[type="tel"],
.nv-sheet textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: 42px;
  padding: .55rem .7rem;
  border: 1px solid var(--nv-line);
  border-radius: 11px;
  background: var(--nv-surface);
  color: var(--nv-back-fg);
  font: inherit;
  font-size: .88rem;
}
.nv-sheet textarea { min-height: 68px; resize: vertical; }
.nv-menu input::placeholder,
.nv-sheet input::placeholder,
.nv-sheet textarea::placeholder { color: var(--nv-back-muted); }
.nv-err { color: var(--nv-closed); font-size: .74rem; margin-top: .2rem; }

/* ── the item sheet ─────────────────────────────────────────────────── */

.nv-detail__hero {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: var(--nv-surface-hi);
  overflow: hidden;
  flex: none;
}
/* contain, not cover: the sheet is where a visitor goes to look at the
   thing, and cropping it there defeats the trip. The blurred copy behind
   fills whatever the letterbox leaves, so a tall or wide photo is framed
   rather than sitting in two grey bars. */
.nv-detail__hero { display: block; cursor: zoom-in; text-decoration: none; }
.nv-detail__blur {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  filter: blur(22px) saturate(1.15) brightness(.72);
  transform: scale(1.15);        /* hides the soft edge blur leaves */
  pointer-events: none;
}
.nv-detail__shot {
  position: relative;
  z-index: 1;
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
.nv-detail__zoom {
  position: absolute;
  z-index: 2;
  inset-block-end: .6rem;
  inset-inline-end: .6rem;
  width: 34px; height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: rgba(10, 13, 20, .58);
  color: #fff;
  font-size: 14px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background-color .2s var(--nv-ease-soft), transform .15s var(--nv-ease-soft);
}
.nv-detail__hero:hover .nv-detail__zoom { background: rgba(10, 13, 20, .8); transform: scale(1.06); }
.nv-detail__hero:active .nv-detail__zoom { transform: scale(.94); }
@media (prefers-reduced-motion: reduce) { .nv-detail__zoom { transition: none; } }

/* ── several photos: a swipeable strip with a dot tracker ───────────────
   Native scroll-snap does the gesture (touch, trackpad, RTL, momentum), so
   there is no drag code to get wrong; nv-menu.js only reads which slide
   landed. With one photo none of the chrome renders and the strip is just
   the hero it always was. */
.nv-detail__gallery {
  position: relative;
  flex: none;
}
.nv-detail__track {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.nv-detail__track::-webkit-scrollbar { display: none; }
.nv-detail__track > .nv-detail__hero {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
}

.nv-detail__dots {
  position: absolute;
  z-index: 3;
  inset-block-end: .7rem;
  inset-inline: 0;
  margin-inline: auto;
  width: max-content;
  max-width: calc(100% - 7rem);   /* clear of the zoom button */
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 6px;
  padding: 6px 9px;
  border-radius: 999px;
  background: rgba(10, 13, 20, .42);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.nv-detail__dot {
  width: 7px; height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .5);
  cursor: pointer;
  transition: width .28s var(--nv-ease-soft), background-color .2s var(--nv-ease-soft);
}
.nv-detail__dot:hover { background: rgba(255, 255, 255, .8); }
.nv-detail__dot.is-on {
  width: 20px;
  background: var(--nv-accent, #fff);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .35);
}
.nv-detail__dot:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

.nv-detail__nav {
  position: absolute;
  z-index: 3;
  inset-block-start: 50%;
  translate: 0 -50%;
  width: 34px; height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 999px;
  background: rgba(10, 13, 20, .5);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  opacity: 0;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: opacity .2s var(--nv-ease-soft), background-color .2s var(--nv-ease-soft);
}
/* the page is RTL: "previous" sits on the right, "next" on the left */
.nv-detail__nav--prev { inset-inline-start: .6rem; }
.nv-detail__nav--next { inset-inline-end: .6rem; }
.nv-detail__gallery:hover .nv-detail__nav:not(:disabled),
.nv-detail__nav:focus-visible { opacity: 1; }
.nv-detail__nav:hover { background: rgba(10, 13, 20, .78); }
.nv-detail__nav:disabled { cursor: default; }
/* touch screens swipe; the arrows are for a mouse */
@media (hover: none) { .nv-detail__nav { display: none; } }
@media (prefers-reduced-motion: reduce) {
  .nv-detail__dot, .nv-detail__nav { transition: none; }
}

.nv-detail__pad { padding: .9rem var(--nv-pad); }
.nv-detail__name { margin: 0 0 .3rem; font-size: 1.1rem; font-weight: 800; line-height: 1.5; }
.nv-detail__desc { font-size: .86rem; line-height: 2; color: var(--nv-back-muted); white-space: pre-line; }

/* ═══════════════════════════════════════════════════════════════════════
   THE THREE LAYOUTS
   ═══════════════════════════════════════════════════════════════════════
   One template, one set of markup, three genuinely different pages — the
   whole difference is below this line. They are not decorative variants:
   a menu is SCANNED (dense text rows, price and stepper close together), a
   catalogue is BROWSED (the picture is the product, so it leads), and a
   services list is BOOKED (no cart at all, one action per row).

   `menu` is the base above and has no block of its own.
   ═════════════════════════════════════════════════════════════════════ */

/* ── catalogue: an editorial lookbook ───────────────────────────────── */
/*
   A catalogue is browsed the way a printed one is: one thing at a time, the
   photograph doing the selling, the words getting out of its way. So the
   card IS the photograph — full bleed, edge to edge, one per row on a phone
   — and the name and price sit on it under a gradient, the way a caption
   sits on a plate in a printed catalogue.

   The previous version was a four-across thumbnail grid with a border, a
   description and a stepper: efficient, and it read as a stock list. Nothing
   about it said the goods were worth the price.

   Everything inside the tile sits on the same dark scrim in both themes, so
   the type is white and the accent gold in both — no per-theme branching,
   and the contrast is the same figure day and night.

   1:1, because that is the only shape the uploader accepts. Never crop to a
   different ratio here: it would silently cut the edges off every photo the
   owner composed. */

.nv-menu[data-layout="catalog"] {
  --nv-lux: #d8b45a;              /* the one accent — a thin gold rule */
  --nv-lux-ink: #f6f2e9;
}

.nv-menu[data-layout="catalog"] .nv-grid {
  grid-template-columns: 1fr;
  /* Not 1fr rows: the tiles are square, so equal width already means equal
     height, and forcing a row height would fight the aspect ratio. */
  grid-auto-rows: auto;
  gap: 1.1rem;
}
@media (min-width: 680px) {
  .nv-menu[data-layout="catalog"] .nv-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* The catalogue's cards sit 1.1rem apart and cast a deep shadow, so the
   break before the un-headed group has to be wider than that to read as
   one — and to keep that shadow off the first tile below it. */
.nv-menu[data-layout="catalog"] .nv-group ~ .nv-group--bare { margin-top: 1.75rem; }

.nv-menu[data-layout="catalog"] .nv-item {
  display: block;
  position: relative;
  height: auto;
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 20px;
  overflow: hidden;
  background: #0b0e14;
  box-shadow: 0 22px 45px -26px rgba(0, 0, 0, .7),
              0 2px 6px -2px rgba(0, 0, 0, .35);
  transition: transform .45s var(--nv-ease-soft), box-shadow .45s var(--nv-ease-soft);
}
.nv-menu[data-layout="catalog"] .nv-item:hover {
  transform: translateY(-3px);
  box-shadow: 0 30px 60px -26px rgba(0, 0, 0, .78);
}
.nv-menu[data-layout="catalog"] .nv-item:active { transform: translateY(-1px); }

.nv-menu[data-layout="catalog"] .nv-item__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  aspect-ratio: auto;
  border-radius: 0;
  background: none;
}
/* A slow, small push-in on press. Slow is the point: a fast zoom reads as a
   button, a slow one reads as a page turning. */
.nv-menu[data-layout="catalog"] .nv-item__thumb img {
  transition: transform 1.1s cubic-bezier(.2, .7, .2, 1);
}
.nv-menu[data-layout="catalog"] .nv-item:hover .nv-item__thumb img,
.nv-menu[data-layout="catalog"] .nv-item:active .nv-item__thumb img {
  transform: scale(1.045);
}

.nv-menu[data-layout="catalog"] .nv-item__body {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 2;
  gap: .1rem;
  padding: 4.5rem 1.05rem 1rem;
  color: var(--nv-lux-ink);
  /* Three stops, not two: a straight linear fade leaves a visible band
     where the gradient meets the photo. */
  background: linear-gradient(to top,
              rgba(5, 7, 12, .94) 0%,
              rgba(5, 7, 12, .78) 32%,
              rgba(5, 7, 12, .30) 62%,
              rgba(5, 7, 12, 0) 100%);
}
.nv-menu[data-layout="catalog"] .nv-item__name {
  font-size: 1.06rem;
  font-weight: 800;
  letter-spacing: -.01em;
  line-height: 1.6;
  color: #fff;
}
.nv-menu[data-layout="catalog"] .nv-item__desc {
  font-size: .76rem;
  line-height: 1.75;
  -webkit-line-clamp: 1;
  color: rgba(255, 255, 255, .74);
}

/* The gold hairline. One rule, short, above the price — the whole accent. */
.nv-menu[data-layout="catalog"] .nv-item .nv-price {
  position: relative;
  margin-top: .5rem;
  padding-top: .62rem;
  gap: .5rem;
}
.nv-menu[data-layout="catalog"] .nv-item .nv-price::before {
  content: '';
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 34px;
  height: 1px;
  background: var(--nv-lux);
}
.nv-menu[data-layout="catalog"] .nv-item .nv-price__now {
  font-size: 1.22rem;
  font-weight: 800;
  letter-spacing: .01em;
  color: #fff;
}
.nv-menu[data-layout="catalog"] .nv-item .nv-price__was { color: rgba(255, 255, 255, .5); }
.nv-menu[data-layout="catalog"] .nv-item .nv-price__unit { color: var(--nv-lux); font-weight: 700; }

/* Badges float top-start, clear of the caption. */
.nv-menu[data-layout="catalog"] .nv-item .nv-tags {
  position: absolute;
  inset-block-start: .7rem;
  inset-inline-start: .7rem;
  z-index: 3;
  gap: .3rem;
}
.nv-menu[data-layout="catalog"] .nv-item .nv-tag {
  font-size: .66rem;
  padding: .22rem .5rem;
  border-radius: 8px;
  background: rgba(8, 10, 16, .62);
  color: #fff;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  white-space: nowrap;
}
.nv-menu[data-layout="catalog"] .nv-item .nv-tag--off {
  background: var(--nv-lux);
  color: #1a1206;
  font-weight: 800;
}

/* The stepper floats over the photo at the opposite corner from the caption
   text, on frosted glass so it reads against any picture. */
.nv-menu[data-layout="catalog"] .nv-item .nv-step {
  position: absolute;
  z-index: 3;
  inset-block-end: 1rem;
  inset-inline-end: 1.05rem;
  align-self: auto;
  padding: 3px;
  border-radius: 13px;
  border-color: rgba(255, 255, 255, .22);
  background: rgba(12, 15, 22, .55);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.nv-menu[data-layout="catalog"] .nv-item .nv-step__b { color: #fff; width: 32px; height: 32px; }
.nv-menu[data-layout="catalog"] .nv-item .nv-step__b:not(.nv-step__b--add):hover:not(:disabled) {
  background: rgba(255, 255, 255, .14);
}
.nv-menu[data-layout="catalog"] .nv-item .nv-step__b--add:hover:not(:disabled) {
  background: var(--nv-lux);
  filter: brightness(1.06);
}
.nv-menu[data-layout="catalog"] .nv-item .nv-step__b--add {
  background: var(--nv-lux);
  color: #1a1206;
}
.nv-menu[data-layout="catalog"] .nv-item .nv-step__n { color: #fff; }

.nv-menu[data-layout="catalog"] .nv-item.is-out { opacity: 1; }
.nv-menu[data-layout="catalog"] .nv-item.is-out .nv-item__thumb img {
  filter: grayscale(1) brightness(.72);
}

@media (prefers-reduced-motion: reduce) {
  .nv-menu[data-layout="catalog"] .nv-item,
  .nv-menu[data-layout="catalog"] .nv-item__thumb img { transition: none; }
}

/* ── services: avatar cards ─────────────────────────────────────────── */
/* No cart, no stepper — a service is booked, not bought by the unit. The
   image becomes a round avatar (and services often have none, which is why
   the placeholder has to look deliberate rather than like a broken photo),
   and the single action spans the card so there is never any doubt what to
   press. */

/*
   A doctor's page, not a menu with a booking button bolted on. The question
   a visitor arrives with is "when can I be seen", so the answer is on the
   card: the next free times, as chips you can tap straight into the booking
   form. See watchSlots() in nv-menu.js for how they are fetched.

   The restraint is deliberate — a clinic that looks like a takeaway is not
   reassuring. One accent, a calm surface, an accent bar on the leading edge
   the way a record card is tabbed, and no discount shouting. */

.nv-menu[data-layout="services"] {
  --nv-svc-line: color-mix(in srgb, var(--nv-accent) 55%, transparent);
}

.nv-menu[data-layout="services"] .nv-grid { gap: .7rem; }

.nv-menu[data-layout="services"] .nv-item {
  align-items: center;
  /* The slot strip and the book button are flex children pushed onto their
     own lines by flex-basis: 100%. Wrapping is what puts them there. */
  flex-wrap: wrap;
  /* Cards in a row share a height (grid-auto-rows: 1fr); this pins the
     button to the bottom of every one of them so a two-line service name
     does not leave its neighbour's button floating mid-card. */
  align-content: space-between;
  row-gap: .5rem;
  padding: .75rem .8rem;
  border-radius: 18px;
  overflow: hidden;
  box-shadow: 0 10px 26px -22px rgba(0, 0, 0, .55);
}
/* The tab on the leading edge. Present but quiet; it is what makes a run of
   these read as records rather than as list items. */
.nv-menu[data-layout="services"] .nv-item::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  width: 3px;
  background: var(--nv-svc-line);
  opacity: .0;
  transition: opacity .25s var(--nv-ease-soft);
}
.nv-menu[data-layout="services"] .nv-item:hover::before { opacity: 1; }

/* A service description is a line, not a paragraph — the full text is in the
   sheet. Two lines here bought nothing and cost a third of every card. */
.nv-menu[data-layout="services"] .nv-item__desc { -webkit-line-clamp: 1; }
.nv-menu[data-layout="services"] .nv-item__thumb {
  --nv-thumb: 46px;
  align-self: center;
  border-radius: 999px;
  box-shadow: 0 0 0 2px var(--nv-surface), 0 0 0 3px var(--nv-line);
}
.nv-menu[data-layout="services"] .nv-item__body { align-self: center; }
.nv-menu[data-layout="services"] .nv-item__name {
  font-size: .96rem;
  font-weight: 800;
  letter-spacing: -.005em;
}
.nv-menu[data-layout="services"] .nv-item .nv-price__now { font-size: 1rem; }

/* ── the next free times ────────────────────────────────────────────── */

.nv-slots {
  flex: 0 0 100%;
  width: 100%;
  min-height: 34px;
  display: flex;
  align-items: center;
  padding-top: .5rem;
  border-top: 1px dashed var(--nv-line);
}
.nv-slots__row {
  display: flex;
  align-items: center;
  gap: .45rem;
  width: 100%;
  min-width: 0;
}
.nv-slots__when {
  flex: none;
  font-size: .68rem;
  font-weight: 700;
  color: var(--nv-tag-fg);
  white-space: nowrap;
}
.nv-slots__times {
  display: flex;
  gap: .35rem;
  overflow-x: auto;
  scrollbar-width: none;
  min-width: 0;
}
.nv-slots__times::-webkit-scrollbar { display: none; }

.nv-slot {
  appearance: none;
  flex: none;
  min-height: 28px;
  padding: .2rem .6rem;
  border: 1px solid var(--nv-svc-line);
  border-radius: 9px;
  background: color-mix(in srgb, var(--nv-accent) 10%, transparent);
  color: var(--nv-accent);
  font: inherit;
  font-size: .76rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background-color .18s var(--nv-ease-soft),
              color .18s var(--nv-ease-soft),
              transform .12s var(--nv-ease-soft);
}
.nv-slot:hover { background: var(--nv-accent); color: var(--nv-accent-fg); }
.nv-slot:active { transform: scale(.94); }

.nv-slots__empty { font-size: .7rem; color: var(--nv-back-muted); }

/* Three dots while the answer is in flight. Deliberately tiny: this is a
   nicety loading behind the real button, not the page loading. */
.nv-slots__wait { display: flex; gap: .25rem; align-items: center; }
.nv-slots__dot {
  width: 5px; height: 5px;
  border-radius: 999px;
  background: var(--nv-back-muted);
  opacity: .35;
  animation: nv-slot-pulse 1.1s ease-in-out infinite;
}
.nv-slots__dot:nth-child(2) { animation-delay: .15s; }
.nv-slots__dot:nth-child(3) { animation-delay: .3s; }
@keyframes nv-slot-pulse {
  0%, 100% { opacity: .25; transform: translateY(0); }
  50%      { opacity: .8;  transform: translateY(-2px); }
}
@media (prefers-reduced-motion: reduce) {
  .nv-slots__dot { animation: none; opacity: .45; }
  .nv-menu[data-layout="services"] .nv-item::before { transition: none; }
}

.nv-book {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  /* Its own line, full width. */
  flex: 0 0 100%;
  width: 100%;
  box-sizing: border-box;
  min-height: 36px;
  margin-top: auto;
  padding: .45rem .85rem;
  border: 0;
  border-radius: 12px;
  background: var(--nv-accent);
  color: var(--nv-accent-fg);
  font: inherit;
  font-size: .84rem;
  font-weight: 700;
  cursor: pointer;
  transition: filter .2s var(--nv-ease-soft), transform .12s var(--nv-ease-soft);
}
.nv-book:hover { filter: brightness(1.08); }
.nv-book:active { transform: scale(.98); }

/* ── entrance ───────────────────────────────────────────────────────── */
/* Items fade up as the list first paints. Capped at a handful of steps so a
   long menu does not spend two seconds staggering — after the tenth item
   they all arrive together. */

.nv-item {
  animation: nv-rise .34s var(--nv-ease) backwards;
  animation-delay: calc(var(--i, 0) * 28ms);
}
@keyframes nv-rise {
  from { opacity: 0; transform: translateY(9px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .nv-item { animation: none; }
}

/* ── misc ───────────────────────────────────────────────────────────── */

.nv-menu :focus-visible {
  outline: 2px solid var(--nv-accent);
  outline-offset: 2px;
  border-radius: 8px;
}

.nv-menu__empty {
  text-align: center;
  color: var(--nv-back-muted);
  font-size: .85rem;
  padding: 3rem 1rem;
}

.nv-credit {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  padding: .55rem;
  padding-bottom: max(.55rem, env(safe-area-inset-bottom));
  background: var(--nv-back-bg-2);
  border-top: 1px solid var(--nv-line);
  color: var(--nv-back-muted);
  font-size: .72rem;
  text-decoration: none;
}
.nv-credit b { color: var(--nv-back-fg); font-weight: 800; }
.nv-credit:hover, .nv-credit:hover b { color: var(--nv-accent); }

@media (max-height: 640px) {
  .nv-menu { --nv-pad: .9rem; }
  /* Set the token, not the width: a layout that overrides --nv-thumb (the
     services avatar) must keep winning on a short screen too. */
  .nv-menu { --nv-thumb: 54px; }
  .nv-chips--lists { --nv-lchip-w: 4.25rem; }
}

/* ── the rate note ──────────────────────────────────────────────────────
   Only rendered for a business that prices against a currency or gold, so
   this costs every other menu nothing but the bytes of these rules.

   It sits on the same --nv-back-* surface as .nv-credit directly below it,
   because the two are one footer strip as far as the eye is concerned and
   a second background colour there reads as a banner the visitor has to
   dismiss. Muted, small, above the credit line — present if you look for
   it, not competing with the prices it is explaining. */
.nv-ratenote {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  padding: .5rem .75rem;
  background: var(--nv-back-bg-2);
  border-top: 1px solid var(--nv-line);
  color: var(--nv-back-muted);
  font-size: .72rem;
  line-height: 1.7;
  text-align: center;
}
.nv-ratenote b { color: var(--nv-back-fg); font-weight: 800; }

/* Stale is a warning, not an error: the number is still the best one we
   have and the shop is still open. Amber rather than the accent, so it
   does not read as something to tap. */
.nv-ratenote__stale { color: #d97706; font-weight: 700; }

/* ══════════════════════════════════════════════════════════════════════
   NORIVA'S RECOMMENDATION — mid-list and end-of-list
   ══════════════════════════════════════════════════════════════════════
   Filled by nv-reco.js (see spotlight/). Built from the item card's own
   tokens — border, radius, surface, the same rise-in as the items around it
   — and slimmer than an item, so it never outweighs the business's own
   things. The three layouts restyle it below exactly as they restyle
   .nv-item. */

.nv-reco {
  margin-top: var(--nv-menu-gap);
  animation: nv-rise .34s var(--nv-ease) backwards;
}
.nv-reco[hidden] { display: none; }
.nv-reco--end { margin-top: 1rem; }
@media (prefers-reduced-motion: reduce) { .nv-reco { animation: none; } }

/* ── the row ────────────────────────────────────────────────────────── */

.nv-reco__row {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .45rem .5rem;
  border: 1px solid var(--nv-line);
  border-radius: 16px;
  background: var(--nv-surface);
  transition: border-color .2s var(--nv-ease-soft);
}
.nv-reco__row:hover { border-color: color-mix(in srgb, var(--nv-accent) 40%, var(--nv-line)); }

.nv-reco__main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .6rem;
  color: inherit;
  text-decoration: none;
}

.nv-reco__ico {
  width: 44px;
  height: 44px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  overflow: hidden;
  background: var(--nv-surface-hi);
}
.nv-reco__ico img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nv-reco__ico.is-letter { color: var(--nv-accent); font-weight: 800; font-size: 1rem; }

.nv-reco__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .05rem;
}
.nv-reco__label {
  font-size: .66rem;
  line-height: 1.5;
  color: var(--nv-tag-fg);
}
.nv-reco__title {
  font-size: .86rem;
  font-weight: 700;
  line-height: 1.5;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.nv-reco__text {
  font-size: .72rem;
  line-height: 1.55;
  color: var(--nv-back-muted);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.nv-reco__act {
  flex: none;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--nv-line);
  border-radius: 10px;
  background: var(--nv-surface-hi);
  color: var(--nv-accent);
  transition: transform .12s var(--nv-ease-soft);
}
.nv-reco__act:active { transform: scale(.92); }
.nv-reco__act svg { width: 15px; height: 15px; }

/* ── the wide card (mid-list, when the ad has a banner) ─────────────── */

.nv-reco__wide {
  display: block;
  overflow: hidden;
  border: 1px solid var(--nv-line);
  border-radius: 16px;
  background: var(--nv-surface);
  color: inherit;
  text-decoration: none;
  transition: border-color .2s var(--nv-ease-soft), transform .12s var(--nv-ease-soft);
}
.nv-reco__wide:hover { border-color: color-mix(in srgb, var(--nv-accent) 40%, var(--nv-line)); }
.nv-reco__wide:active { transform: scale(.99); }

/* 2:1 reserved before the image arrives, so nothing below it moves. */
.nv-reco__media {
  display: block;
  aspect-ratio: 2 / 1;
  background: var(--nv-surface-hi);
}
.nv-reco__media img { width: 100%; height: 100%; object-fit: cover; display: block; }

.nv-reco__foot {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .55rem .7rem .6rem;
}
.nv-reco__cta {
  flex: none;
  padding: .3rem .75rem;
  border-radius: 999px;
  background: var(--nv-accent-soft);
  color: var(--nv-accent);
  font-size: .72rem;
  font-weight: 700;
  white-space: nowrap;
}

/* Two columns of items on a wide screen: a full-width 2:1 banner there
   would be taller than three rows of menu. Picture beside the text instead,
   at the height of a couple of items. */
@media (min-width: 600px) {
  .nv-reco__wide { display: flex; align-items: stretch; }
  .nv-reco__media { flex: none; width: 42%; }
  .nv-reco__foot { flex: 1; min-width: 0; padding: .8rem 1rem; }
}

/* ── catalog: the dark, gold-ruled tiles ───────────────────────────── */

.nv-menu[data-layout="catalog"] .nv-reco__row,
.nv-menu[data-layout="catalog"] .nv-reco__wide {
  border: 0;
  border-radius: 20px;
  background: #0b0e14;
  color: var(--nv-lux-ink);
  box-shadow: 0 22px 45px -26px rgba(0, 0, 0, .7),
              0 2px 6px -2px rgba(0, 0, 0, .35);
}
.nv-menu[data-layout="catalog"] .nv-reco__label { color: var(--nv-lux); }
.nv-menu[data-layout="catalog"] .nv-reco__title { color: #fff; }
.nv-menu[data-layout="catalog"] .nv-reco__text { color: rgba(255, 255, 255, .7); }
.nv-menu[data-layout="catalog"] .nv-reco__ico,
.nv-menu[data-layout="catalog"] .nv-reco__media { background: #151a23; }
.nv-menu[data-layout="catalog"] .nv-reco__ico.is-letter { color: var(--nv-lux); }
.nv-menu[data-layout="catalog"] .nv-reco__act {
  border-color: rgba(216, 180, 90, .45);
  background: transparent;
  color: var(--nv-lux);
}
.nv-menu[data-layout="catalog"] .nv-reco__cta {
  background: transparent;
  border: 1px solid var(--nv-lux);
  color: var(--nv-lux);
}
.nv-menu[data-layout="catalog"] .nv-reco { margin-top: 1.1rem; }

/* ── services: the item's accent edge ───────────────────────────────── */

.nv-menu[data-layout="services"] .nv-reco { margin-top: .7rem; }
