/* ============================================================
   Concierge kiosk — design system (linked, no inline fork).
   Portrait canvas 1080 × 1920, scaled to fit any viewport.

   Themeable colours come ONLY from the six shared branding custom
   properties (--bg, --ink, --accent, --accent-strong, --accent-soft,
   --border); branding.js overrides these via the CSSOM. Everything else
   is a fixed internal token.
   ============================================================ */

:root {
  /* --- Branding whitelist (tenant-overridable). Neutral greyscale baseline: an
     unbranded device must look generic, never like some other tenant's look. --- */
  --bg: #F4F5F6;            /* content surface (neutral light grey) */
  --ink: #1d1d1b;           /* primary text */
  --accent: #5B6675;        /* brand primary (neutral slate) */
  --accent-strong: #3E4652; /* brand shade */
  --accent-soft: #E6E9ED;   /* brand tint */
  --border: #DCDFE3;        /* hairline borders */

  /* --- Fixed internal design tokens (not themeable) --- */
  --surface: #ffffff;
  --cream: #F0F1F3;
  --yellow: #FFD200;
  --green: #2E7D32;
  --green-light: #43A047;
  --blue: #1565C0;
  --gray: #9aa0a6;
  --gray-soft: #efe9e2;

  --font-body: "Segoe UI", -apple-system, Roboto, Arial, sans-serif;
  --font-display: "Segoe UI Black", "Arial Black", "Segoe UI", sans-serif;

  --shadow-tile: 0 16px 30px rgba(0, 0, 0, 0.30);
  --shadow-card: 0 10px 24px rgba(29, 29, 27, 0.10);
  --radius-card: 26px;

  /* Home grid: 3 columns × 4 rows by default; the Layout doc can override
     --grid-cols so 8 or 16 tiles reflow without code changes. */
  --grid-cols: 3;

  /* 1:1 — the canvas dimensions ARE the kiosk panel's, so this is exact on real
     hardware from the very first paint (the old 0.5 rendered every cold boot at
     half size until JS caught up). rescaleStage() overwrites it with an inline
     value on <html> for any other viewport. Must stay a bare number: scale()
     rejects lengths, and calc(100vw / 1080) is a length, not a ratio — that
     would invalidate the whole transform, translate included. */
  --stage-scale: 1;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  /* The stage is a fixed-aspect canvas scaled to FIT, so any viewport that is
     not exactly 9:16 / 16:9 shows margins around it. Painting them with the
     stage's own edge colour (the radial gradient ends at --accent-strong)
     makes the canvas read as full-screen instead of a card floating on black.
     Pre-branding this is the neutral slate default, and the boot veil covers
     it anyway. */
  background: #14100F;
  overflow: hidden;
  font-family: var(--font-body);
  color: var(--ink);
  user-select: none;
}

/* ---------- Boot veil ----------
   Styled in the render-blocking stylesheet (and marked up in index.html) so it
   is fully painted on the FIRST frame, before any module executes. It sits above
   every other layer, including the orientation guard, and main.js takes it down
   only once real config + branding have landed.

   Intentionally brand-free: a plain dark neutral and a grey spinner — no logo,
   no product name, no text at all. A device that never receives a config simply
   keeps this on screen forever, so "awaiting provisioning" is visually identical
   to "still starting up". Colours are literals, NOT branding vars: this must not
   change appearance when a tenant's palette arrives. */

.boot {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: #101215;
  transition: opacity 0.35s ease;
}
.boot[hidden] { display: none; }
.boot.is-done { opacity: 0; pointer-events: none; }

.boot-spinner {
  width: 96px; height: 96px; border-radius: 50%;
  border: 8px solid rgba(138, 144, 153, 0.22);
  border-top-color: #8a9099;
  animation: boot-spin 0.9s linear infinite;
}
@keyframes boot-spin { to { transform: rotate(360deg); } }

/* Reduced motion: a gentle opacity pulse instead of a rotating ring. */
@media (prefers-reduced-motion: reduce) {
  .boot-spinner {
    animation: boot-pulse 1.8s ease-in-out infinite;
    border-color: #8a9099;
  }
  @keyframes boot-pulse { 0%, 100% { opacity: 0.25; } 50% { opacity: 0.8; } }
}

/* ---------- Boot veil: "no content for this screen" diagnostic ----------
   Mounted at RUNTIME by js/boot-status.js (never in index.html — the shipped
   markup stays text-free) once /concierge/config has answered 404 three times
   running. Same rules as the veil itself: literal colours only, no branding
   vars, no z-index (it nests inside .boot, which already outranks everything)
   and no animation of its own — the spinner stays, shrunk, as the visible proof
   the kiosk is still trying. The veil is position:fixed, i.e. OUTSIDE the
   --stage-scale canvas, so these are raw viewport pixels.

   EVERY size here is fluid, because this card is the one kiosk surface that is
   also read on a phone: what.app opens the kiosk URL in an in-app WebView and
   in a browser, so 390x844 is a primary viewport, not an exotic one. Fixed
   44/30/26 px type made the card 920 px tall there — inside a fixed,
   unscrollable veil under body{overflow:hidden}, which clipped the title, put
   the spinner off-screen entirely and ran the Retry button past the bottom
   edge. A diagnostic whose "press Retry" affordance cannot be pressed is worse
   than the silent spinner it replaced.

   --diag-u is the card's scale unit: min() of a width and a HEIGHT term, so it
   shrinks on a 1280x600 desktop window (height-bound) as well as on a phone
   (width-bound, where wrapped text costs height quadratically). The px floors
   keep it legible; the ceilings keep the 1920x1080 TV and the 1080x1920 kiosk
   looking exactly as designed. */

.boot.is-diagnostic {
  flex-direction: column;
  /* `safe center` centres the card when it fits but falls back to top-aligned
     when it does not, instead of pushing the title past the top edge where no
     scroll can reach it. The plain `center` above it is the fallback for
     engines without the safe keyword. */
  justify-content: center;
  justify-content: safe center;
  gap: clamp(12px, 2.4vh, 44px);
  padding: clamp(14px, 3vh, 48px) clamp(14px, 4vw, 56px);
  /* Reachability backstop for window sizes below the fleet's own (every
     viewport in .doc/ARCHITECTURE.md fits with no scrolling — measured). The
     bare veil keeps the kiosk no-scroll rule; only the diagnostic state, which
     no customer ever sees, may scroll rather than hide its own button. */
  overflow-y: auto;
  --diag-u: min(1.05vw, 1.2vh);
}
.boot.is-diagnostic .boot-spinner {
  flex: none;
  width: clamp(22px, calc(var(--diag-u) * 5), 56px);
  height: clamp(22px, calc(var(--diag-u) * 5), 56px);
  border-width: clamp(3px, calc(var(--diag-u) * 0.45), 5px);
}

.boot-diag {
  flex: none;
  max-width: min(820px, 88vw);
  text-align: center;
  color: #e7eaee;
  font-family: var(--font-body);
}
.boot-diag-title {
  margin: 0 0 clamp(8px, 1.6vh, 18px);
  font-size: clamp(26px, calc(var(--diag-u) * 4.4), 44px);
  line-height: 1.15; font-weight: 700;
  color: #e7eaee;
}
.boot-diag-body {
  margin: 0 0 clamp(12px, 2.6vh, 34px);
  font-size: clamp(15px, calc(var(--diag-u) * 2.6), 26px);
  line-height: 1.4;
  color: #8a9099;
}
.boot-diag-ids {
  margin: 0 0 clamp(12px, 2.6vh, 34px);
  padding: clamp(12px, 2vh, 24px) clamp(14px, 2.4vw, 28px);
  background: #171a1f;
  border: 1px solid #262b32;
  border-radius: clamp(10px, 1.6vh, 18px);
  text-align: left;
}
.boot-diag-key {
  margin: 0;
  font-size: clamp(12px, calc(var(--diag-u) * 2), 20px);
  letter-spacing: 0.04em; text-transform: uppercase;
  color: #8a9099;
}
.boot-diag-value {
  /* The one string a human must copy off the screen and compare with the What
     app, so it is monospace, unabbreviated and selectable even though the body
     sets user-select:none. Its floor is the highest of the card's: this is the
     line the whole diagnostic exists to deliver. */
  margin: clamp(3px, 0.6vh, 6px) 0 clamp(10px, 2vh, 22px);
  font-family: "Cascadia Mono", Consolas, "Courier New", monospace;
  font-size: clamp(18px, calc(var(--diag-u) * 3), 30px);
  line-height: 1.3;
  color: #e7eaee;
  word-break: break-all;
  user-select: text;
}
.boot-diag-value:last-child { margin-bottom: 0; }
.boot-diag-value.is-missing { font-style: italic; color: #8a9099; }
.boot-diag-retry {
  /* Never below the 44 px touch target: the min-height floor holds even when
     the font floor has bottomed out. */
  min-height: 44px;
  padding: clamp(10px, 1.8vh, 18px) clamp(22px, 4vw, 42px);
  font-family: inherit; font-weight: 600;
  font-size: clamp(17px, calc(var(--diag-u) * 2.6), 26px);
  color: #e7eaee;
  background: #262b32;
  border: 1px solid #8a9099;
  border-radius: 999px;
  cursor: pointer;
}
/* Pressed: the button says "trying now" for a moment (js/boot-status.js). The
   press must be visible on a wall-mounted screen from standing distance, so it
   is a filled state, not a subtle one — and the geometry is untouched (same
   min-height, same padding) so nothing under the finger shifts. Deliberately
   NOT :disabled — a further tap must always be possible. */
.boot-diag-retry.is-busy {
  color: #10141a;
  background: #cdd3da;
  border-color: #cdd3da;
}
.boot-diag-note {
  margin: clamp(10px, 2vh, 22px) 0 0;
  font-size: clamp(12px, calc(var(--diag-u) * 2), 20px);
  color: #8a9099;
}

/* ---------- Stage: fixed 1080×1920 canvas, scaled by JS ---------- */

.stage {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Aspect-adaptive canvas: main.js rescaleStage() sets --stage-w/--stage-h
     (short axis stretched toward the viewport aspect, clamped 4:3 — see
     js/canvas.js) alongside --stage-scale. The fallbacks are the portrait
     design base for the first pre-JS paint, which the boot veil covers. */
  width: var(--stage-w, 1080px);
  height: var(--stage-h, 1920px);
  transform: translate(-50%, -50%) scale(var(--stage-scale));
  /* The ground is a near-black neutral warmed by the TENANT's accent, not the
     accent itself: a full-bleed tone-40 fill is the loudest thing a kiosk can
     do, and it left every surface on top fighting it. The glow keeps the brand
     present at the top, where the logo is, and lets white type and light cards
     carry the rest. color-mix keeps it tenant-driven — red for MENY, blue for
     an arena — with no new custom property. */
  background:
    radial-gradient(120% 62% at 50% -8%,
      color-mix(in srgb, var(--accent) 62%, #14100F) 0%,
      color-mix(in srgb, var(--accent) 18%, #14100F) 48%,
      #14100F 100%);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  /* No radius/shadow: the body behind is painted --accent-strong (the
     gradient's own edge colour), so the canvas blends seamlessly into any
     letterbox margin — a rounded, shadowed card here read as "not full
     screen" the moment the viewport wasn't exactly 16:9. */
}

/* Browsers without color-mix fall back to a flat accent gradient. */
@supports not (background: color-mix(in srgb, red, blue)) {
  .stage { background: #14100F; }
}

/* ---------- Header ---------- */

.kiosk-header {
  position: relative;
  flex: 0 0 auto;
  min-height: 300px;
  padding: 34px 48px 6px;
  text-align: center;
}

.brand { min-height: 150px; display: flex; align-items: center; justify-content: center; }
.brand-logo { max-width: 560px; max-height: 180px; display: block; }

.store-line {
  margin-top: 10px;
  font-size: 30px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.94);
  letter-spacing: 0.5px;
}
.store-sep { margin: 0 14px; opacity: 0.65; }

.clock {
  position: absolute;
  top: 34px;
  right: 44px;
  text-align: right;
  color: #fff;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 20px;
  padding: 12px 22px 14px;
}
.clock-time { display: block; font-family: var(--font-display); font-size: 46px; line-height: 1; letter-spacing: 1px; }
.clock-date { display: block; margin-top: 6px; font-size: 20px; opacity: 0.92; }

/* ---------- Content area ---------- */

.content {
  flex: 1 1 auto;
  min-height: 0;
  /* Self-service kiosk: content NEVER scrolls. A page either paginates (menus)
     or is scaled to fit by main.js (fitPage). We clip so an oversized page can't
     bleed past the fixed stage while it's being measured/scaled. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 8px 44px 24px;
  transition: transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* The scaler shrinks an oversized page around its top so nothing is clipped. */
.content > .page { transform-origin: top center; }

/* ---------- Accessibility / reach mode ---------- */
/* Shrink toward the bottom edge so the whole UI lands within ~1000px of
   reach; origin at the bottom so the top isn't clipped past the stage. */
.stage.reach .content { transform: scale(0.72); transform-origin: 50% 100%; }
/* Dim the brand mark, not the information: at 0.35 the store status and clock
   composited to ~1.7:1 on the stage, unreadable for exactly the users reach
   mode serves. 0.75 keeps them ≥3.5:1 on a Material tone-40 accent. */
.stage.reach .brand-logo { opacity: 0.45; }
.stage.reach .store-line, .stage.reach .clock { opacity: 0.75; }

/* ---------- HOME ---------- */

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


/* Every home paradigm draws this heading straight onto .stage, i.e. onto the
   --accent → --accent-strong gradient, so it is on-ACCENT text, not on-surface
   text. var(--ink) is the near-black M3 neutral meant for var(--bg): on the
   MENY seed it landed #201A19 on #BC160E — 2.8:1, under AA, and visibly muddy.
   A literal white matches the two elements already sitting on the same
   gradient (.store-line, .clock) and holds for ANY seed, because the gradient
   is built from M3 tones 40/30, which are dark by construction. */
.home-title {
  /* A quiet label over the grid, not the loudest thing on the screen: today's
     offer is the headline now (see .home-banner). */
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 28px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  color: rgba(255, 255, 255, 0.62);
  margin: 4px 6px 18px;
}

/* The grid bleeds 16px into .content's 44px gutter — every pixel of card width
   is label width. --tile-h is set by renderer/home-circular.js from the height
   actually left on the stage: square tiles simply did not fit once a layout had
   more rows than three (the Jar arena's 3x4 ran 170px past the bottom edge and
   the fourth row was clipped), and a kiosk may never hide a destination. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-cols, 3), minmax(0, 1fr));
  grid-auto-rows: var(--tile-h, 240px);
  gap: var(--tile-gap, 22px) 20px;
  padding: 0;
  margin: auto -16px;
}

/* Circular touch targets — comfortably above the 120px minimum at 3 cols. */
/* A card, not a disc. A circle wastes its corners, crops its own label to a
   chord barely two-thirds of its width, and reads as 2015; the same footprint
   as a rounded card gives the label the full width and a row more room. The
   surface is a light veil over the dark ground rather than opaque white, so
   twelve of them read as one calm field instead of twelve spotlights. */
.tile {
  min-width: 120px;
  min-height: 120px;
  border-radius: 34px;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.13);
  box-shadow: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  text-decoration: none;
  color: #fff;
  position: relative;
  overflow: hidden;
  transition: transform 0.14s ease, background-color 0.14s ease;
  animation: tile-pop 0.55s cubic-bezier(0.34, 1.4, 0.64, 1) backwards;
}
.tile:active { background: rgba(255, 255, 255, 0.14); }
.tile:active { transform: scale(0.93); }

.tile-grid .tile:nth-child(1)  { animation-delay: 0.02s; }
.tile-grid .tile:nth-child(2)  { animation-delay: 0.06s; }
.tile-grid .tile:nth-child(3)  { animation-delay: 0.10s; }
.tile-grid .tile:nth-child(4)  { animation-delay: 0.14s; }
.tile-grid .tile:nth-child(5)  { animation-delay: 0.18s; }
.tile-grid .tile:nth-child(6)  { animation-delay: 0.22s; }
.tile-grid .tile:nth-child(7)  { animation-delay: 0.26s; }
.tile-grid .tile:nth-child(8)  { animation-delay: 0.30s; }
.tile-grid .tile:nth-child(9)  { animation-delay: 0.34s; }
.tile-grid .tile:nth-child(10) { animation-delay: 0.38s; }
.tile-grid .tile:nth-child(11) { animation-delay: 0.42s; }
.tile-grid .tile:nth-child(12) { animation-delay: 0.46s; }

@keyframes tile-pop {
  from { opacity: 0; transform: scale(0.55); }
  to   { opacity: 1; transform: scale(1); }
}

/* 76 px, portrait and landscape alike (landscape used to trim 92 → 76): the
   icon is the one thing on a disc that can give the label room without the
   whole grid's type shrinking to fit the longest name. */
/* The icon keeps the white disc the tile itself gave up — it is the card's
   focal point and the vendored glyphs are drawn for a light ground. */
.tile-icon {
  width: var(--tile-icon, 104px); height: var(--tile-icon, 104px);
  padding: calc(var(--tile-icon, 104px) * 0.24);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.32);
  flex: 0 0 auto;
}
.tile-icon svg { width: 100%; height: 100%; display: block; }

/* font-size here is the CEILING, not the final size: renderer/home-circular.js
   measures every label against its disc's chord after layout and steps this
   down — to ONE size shared by the whole grid — until the longest label clears
   the circular clip (see its "Label fitting" note). The 12px side padding is
   the gap it leaves between the glyphs and that clip, so the two numbers move
   together.

   No engine hyphenation: Chromium's and WebView2's Norwegian dictionaries
   broke "TOALETTARTIKLER" at different (both wrong) points. A label that needs
   a break carries it in the data — a soft hyphen (U+00AD) in the pack's label
   renders "TOALETT-/ARTIKLER" only when the disc is too narrow, and pre-line
   honours an explicit newline. */
.tile-label {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 30px;
  line-height: 1.12;
  letter-spacing: 0.02em;
  text-align: center;
  text-wrap: balance;
  color: #fff;
  white-space: pre-line;
  padding: 0 14px;
  hyphens: manual; /* soft hyphens only — see above */
}

.ripple {
  position: absolute;
  border-radius: 50%;
  background: var(--accent-soft);
  transform: scale(0);
  animation: ripple 0.55s ease-out forwards;
  pointer-events: none;
}
@keyframes ripple { to { transform: scale(3.2); opacity: 0; } }

/* Home bottom banner — today's headline offer.
   Mounted by renderer/home-circular.js ONLY when the Layout authors `banner`
   (opt-in, like body.map:'builtin' — see buildBanner). `margin-top: auto`
   against .tile-grid's own `margin: auto 0` splits the column's slack, which is
   what pins the strip low without the discs moving when it is absent.
   Colour is entirely branding vars: the kicker and the pill are var(--accent),
   i.e. the tenant's seed colour — #D52B1E on the MENY demo, slate unbranded. */
.home-banner {
  /* The offer LEADS the home now: `order: -1` lifts it above the grid, where
     the eye lands first, and it is the one saturated object on the screen. It
     was a white strip pinned to the bottom edge, under everything, competing
     with nothing. */
  order: -1;
  margin: 0 -16px 26px;
  display: flex;
  align-items: center;
  gap: 26px;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-strong) 100%);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 34px;
  padding: 26px 34px;
  box-shadow: 0 26px 54px rgba(0, 0, 0, 0.42);
  text-decoration: none;
  color: #fff;
  /* It is a <button> (the kiosk navigates through its state machine, not hrefs)
     and there is no global button font/alignment reset, so both are restated
     here — without them the 31px headline renders in the UA's default face,
     centred. */
  font-family: var(--font-body);
  text-align: left;
  animation: tile-pop 0.55s 0.55s cubic-bezier(0.34, 1.4, 0.64, 1) backwards;
}
.home-banner:active { transform: scale(0.985); }
.home-banner-emoji { font-size: 74px; line-height: 1; flex: none; }
.home-banner-text { flex: 1; min-width: 0; }
/* display:block — the PoC carried this as an inline style on the title span;
   inline styles are not an option here (branding owns presentation). */
/* On the accent gradient now, so both lines are on-accent white. */
.home-banner-kicker { display: block; font-family: var(--font-body); font-weight: 700; color: rgba(255, 255, 255, 0.78); font-size: 22px; letter-spacing: 0.16em; text-transform: uppercase; }
.home-banner-title { display: block; font-size: 36px; font-weight: 700; margin-top: 4px; color: #fff; }
.home-banner-cta {
  flex: none;
  font-size: 25px; font-weight: 700; color: var(--accent); background: #fff;
  border-radius: 999px; padding: 16px 28px; white-space: nowrap;
}
/* A flourish, not information — a reduced-motion kiosk gets it instantly. */
@media (prefers-reduced-motion: reduce) {
  .home-banner { animation: none; }
  .home-banner:active { transform: none; }
}

/* ---------- Generic page card ---------- */

.page {
  background: var(--bg);
  border-radius: 34px;
  padding: 38px 42px 54px;
  margin: 4px 0 20px;
  box-shadow: 0 26px 64px rgba(0, 0, 0, 0.45);
  animation: page-in 0.34s cubic-bezier(0.2, 0.8, 0.2, 1);
}
@keyframes page-in {
  from { opacity: 0; transform: translateY(46px); }
  to   { opacity: 1; transform: translateY(0); }
}

.page-head { display: flex; align-items: center; gap: 28px; margin-bottom: 26px; }

.back-btn {
  flex: 0 0 auto;
  width: 96px; height: 96px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
  box-shadow: 0 10px 20px rgba(176, 35, 24, 0.45);
  transition: transform 0.14s ease;
}
.back-btn:active { transform: scale(0.9); }
.back-btn svg { width: 46px; height: 46px; }

.page-kicker { font-family: var(--font-display); color: var(--accent); font-size: 24px; letter-spacing: 4px; }
.page-title { font-family: var(--font-display); font-size: 58px; line-height: 1.02; color: var(--ink); margin-top: 2px; }

.section-title {
  font-family: var(--font-display);
  font-size: 36px; color: var(--ink);
  margin: 40px 0 20px;
  display: flex; align-items: center; gap: 14px;
}
.section-title .sicon { width: 36px; height: 36px; color: var(--accent); display: inline-flex; }
.section-title .sicon svg { width: 100%; height: 100%; }

/* ---------- Department page + floor map ---------- */

.dept-lead { font-family: var(--font-display); font-size: 38px; color: var(--accent); margin: 0 0 14px; line-height: 1.1; }

.map-card { background: var(--surface); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: 24px; line-height: 0; }
.map-card svg { width: 100%; height: auto; }

/* QR handoff reserves its space while the link loads, so fitPage measures it. */
.navigation-handoff { display: flex; flex: 0 0 auto; align-items: center; gap: 24px; margin-top: 20px; padding: 18px 24px; border: 2px solid var(--border); border-radius: 24px; background: var(--surface); }
.navigation-qr { flex: 0 0 184px; width: 184px; height: 184px; background: white; border-radius: 8px; overflow: hidden; }
.navigation-qr svg { display: block; width: 100%; height: 100%; }
.navigation-handoff h2 { font-size: 28px; line-height: 1.15; margin: 0 0 8px; color: var(--ink); }
.navigation-handoff p { font-size: 22px; line-height: 1.3; margin: 4px 0; }
.navigation-handoff .navigation-status { font-size: 18px; }
.navigation-handoff a, .navigation-handoff button { color: var(--accent-strong); font: inherit; font-size: 20px; text-underline-offset: 4px; }
.navigation-handoff button { border: 1px solid var(--border); background: var(--bg); border-radius: 10px; padding: 6px 12px; }
.navigation-handoff [hidden] { display: none; }
.map-fill { flex: 1 1 auto; min-height: 0; display: flex; background: transparent; box-shadow: none; padding: 0; }
.map-fill svg { width: 100%; height: 100%; }

.map-caption { font-size: 23px; color: #6b6b68; margin-top: 16px; display: flex; align-items: center; gap: 12px; line-height: 1.3; }
.map-caption .micon { width: 26px; height: 26px; color: var(--accent); flex: 0 0 auto; }
.map-caption .micon svg { width: 100%; height: 100%; }

.mini-strip { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 18px; }
.mini { background: var(--surface); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: 18px 14px 16px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; }
.mini-emoji { font-size: 44px; line-height: 1.15; }
.mini-image { width: 72px; height: 72px; object-fit: contain; }
.mini-name { font-weight: 700; font-size: 23px; line-height: 1.15; min-height: 54px; display: flex; align-items: center; text-wrap: balance; }
.mini-price { font-family: var(--font-display); font-size: 30px; background: var(--yellow); border-radius: 10px; padding: 2px 16px 6px; transform: rotate(-2deg); }
.mini-price small { font-size: 18px; font-weight: 400; margin-left: 3px; }

/* Price tag (shared by offers/departments) */
.price { display: inline-flex; align-items: baseline; background: var(--yellow); color: var(--ink); font-family: var(--font-display); border-radius: 12px; padding: 6px 16px 10px; transform: rotate(-3deg); box-shadow: 0 6px 12px rgba(29, 29, 27, 0.18); }
.price-main { font-size: 44px; line-height: 1; }
.price-dec { font-size: 24px; margin-left: 2px; }
.price-unit { font-family: var(--font-body); font-size: 20px; font-weight: 600; margin-left: 6px; }

/* "Where in the store" chip */
/* offers.js emits a <button> when the place can be navigated to and a <span>
   otherwise; the two must look identical apart from the CTA pill, so the UA
   button chrome (grey box, Arial, centred text, 2px padding) is reset here in
   full. The row wraps rather than forcing its min-content width — an unwrappable
   chip inflated the 1fr grid track and pushed the right column off the card. */
.place-chip {
  appearance: none; -webkit-appearance: none;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  min-height: 52px; margin: 4px 0 0; padding: 0; border: 0; background: none;
  font: inherit; font-size: 21px; font-weight: 600; line-height: 1.25; text-align: left;
  color: var(--accent-strong); text-decoration: none; cursor: default;
}
button.place-chip { cursor: pointer; }
.place-chip .micon { width: 22px; height: 22px; color: var(--accent); flex: 0 0 auto; display: inline-flex; }
/* The pill is the tappable affordance — sized for a finger, not for the text. */
.place-cta { font-weight: 700; color: var(--accent); background: var(--accent-soft); border: 2px solid var(--accent); border-radius: 999px; padding: 8px 18px 10px; white-space: nowrap; }
button.place-chip:active .place-cta { background: var(--accent); color: #fff; }

/* ---------- Offers page ---------- */

.offer-intro { font-size: 27px; color: #3c3c3a; margin: 0 0 26px; }
/* minmax(0, 1fr), not 1fr: a bare 1fr track's minimum is its content's
   min-content width, so one card with an unbreakable chip grew its column and
   pushed the right-hand cards 36px past the page card edge. */
.cards-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
.offers-list { align-content: start; }
/* The starburst badge (.splash) deliberately overhangs the card's top-right by
   design; the pager viewport must clip rows for pagination, so it gets a bleed
   that keeps the overhang inside the clip box. Padding counts in both
   clientHeight and scrollHeight, so paginate.js measures exactly as before. */
.pager-viewport.cards-2 { padding: 24px 28px 0; margin: -24px -28px 0; }
/* Text starts at the same y in both columns (top-aligned body, disc centred),
   so names line up across a row instead of floating with each card's height. */
.ocard { background: var(--surface); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: 24px; display: flex; gap: 20px; align-items: flex-start; position: relative; overflow: visible; }
.ocard-emoji { flex: 0 0 auto; align-self: center; width: 104px; height: 104px; border-radius: 50%; background: var(--cream); display: flex; align-items: center; justify-content: center; font-size: 56px; }
/* `anywhere`: a Norwegian compound can be one 36-character word, and a product
   name comes straight from a flyer. Without it the word ran out of its column,
   across the card edge and under the neighbouring card. min-width: 0 alone does
   not help — it lets the column shrink, not the word. */
.ocard-body { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.ocard-img { width: 100%; height: 100%; object-fit: contain; border-radius: 12px; }
.ocard-emoji:has(.ocard-img) { background: #fff; border-radius: 12px; }
.ocard-description { margin: 8px 0; font-size: 20px; line-height: 1.25; }
.ocard .price { flex-wrap: wrap; max-width: 100%; }
/* Room for the badge is reserved vertically, not by narrowing every name to a
   190px column: only a card that HAS a badge starts its text below it. */
.ocard:has(> .splash) .ocard-body { padding-top: 44px; }
.ocard-name { font-weight: 700; font-size: 26px; line-height: 1.15; text-wrap: balance; }
.ocard-pricerow { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px; margin-top: 12px; }
/* #6b6b68 is the stylesheet's secondary-text grey (map-caption, menu-row-desc):
   5.3:1 on white, where --gray's #9aa0a6 sat at 2.6:1 — the "Før" price was the
   one line on the card a shopper could not read. */
.ocard-old { font-size: 21px; color: #6b6b68; text-decoration: line-through; text-decoration-thickness: 2px; }
.ocard-bestbefore { margin-top: 8px; font-size: 20px; font-weight: 600; color: var(--accent-strong); }

.splash {
  position: absolute; top: -16px; right: -8px;
  background: var(--yellow); color: var(--ink);
  font-family: var(--font-display); font-size: 21px;
  padding: 12px 14px; transform: rotate(8deg);
  clip-path: polygon(50% 0%, 61% 12%, 76% 5%, 79% 21%, 95% 21%, 89% 36%, 100% 50%, 89% 64%, 95% 79%, 79% 79%, 76% 95%, 61% 88%, 50% 100%, 39% 88%, 24% 95%, 21% 79%, 5% 79%, 11% 64%, 0% 50%, 11% 36%, 5% 21%, 21% 21%, 24% 5%, 39% 12%);
  min-width: 96px; min-height: 96px;
  display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1.05;
}

/* ---------- Expiry (Redd maten) ---------- */

.eco-banner { display: flex; align-items: center; gap: 26px; background: linear-gradient(120deg, var(--green), var(--green-light)); color: #fff; border-radius: var(--radius-card); padding: 30px 34px; box-shadow: var(--shadow-card); }
.eco-banner-emoji { font-size: 64px; }
.eco-banner-title { font-family: var(--font-display); font-size: 40px; }
.eco-banner-text { font-size: 25px; margin-top: 6px; opacity: 0.96; line-height: 1.35; }

.xrows { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
.xrow { display: flex; align-items: center; gap: 22px; background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-card); padding: 18px 26px; }
.xrow-emoji { font-size: 46px; width: 64px; text-align: center; }
.xrow-name { flex: 1; font-weight: 700; font-size: 27px; }
.xrow-date { font-size: 21px; font-weight: 700; border-radius: 999px; padding: 8px 18px; background: var(--gray-soft); color: #5a5a57; white-space: nowrap; }
.xrow-date.urgent { background: var(--accent); color: #fff; }
.xrow-price { font-family: var(--font-display); font-size: 34px; white-space: nowrap; }
.xrow-disc { font-family: var(--font-display); font-size: 22px; color: #fff; background: var(--green); border-radius: 12px; padding: 8px 14px; white-space: nowrap; }

/* ---------- Coupons ---------- */

.coupon-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px; }
.coupon { background: var(--surface); border-radius: 22px; box-shadow: var(--shadow-card); padding: 26px 24px 22px; text-align: center; position: relative; }
/* Ticket notches read as cut-outs only when they are the colour of the ground
   BEHIND the card (var(--bg), the page card) and sit on the card's midline;
   in --cream at 58% the two facing notches merged into a grey pill floating
   in the gutter. */
.coupon::before, .coupon::after { content: ""; position: absolute; width: 36px; height: 36px; border-radius: 50%; background: var(--bg); top: 50%; transform: translateY(-50%); }
.coupon::before { left: -17px; }
.coupon::after { right: -17px; }
.coupon-emoji { font-size: 52px; }
.coupon-title { font-family: var(--font-display); font-size: 28px; margin-top: 8px; line-height: 1.1; }
.coupon-qr { margin: 18px auto 0; width: 190px; height: 190px; padding: 10px; background: var(--surface); border: 3px dashed #d8cfc6; border-radius: 16px; display: flex; align-items: center; justify-content: center; }
.coupon-qr svg { width: 100%; height: 100%; }
.coupon-valid { font-size: 20px; color: #6b6b68; margin-top: 12px; }
.coupon-note { flex: 0 0 auto; margin-top: 26px; text-align: center; font-size: 23px; font-weight: 600; color: #5a5a57; }
.coupon-status { font-size: 18px; margin: 12px 0 6px; }
.coupon button, .coupon a { font: inherit; font-size: 18px; color: var(--ink); }
.coupon button { padding: 8px 16px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); cursor: pointer; }

/* ---------- News / embed ---------- */

.news-disclaimer { display: inline-flex; align-items: center; gap: 10px; font-size: 21px; font-weight: 600; color: #5a5a57; background: var(--gray-soft); padding: 10px 20px; border-radius: 999px; margin-bottom: 22px; }
.news-list { display: flex; flex-direction: column; gap: 14px; }
.news-item { background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-card); padding: 24px 28px; }
.news-meta { display: flex; align-items: center; gap: 14px; margin-bottom: 8px; }
.news-cat { font-size: 18px; font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; color: #fff; background: var(--blue); border-radius: 8px; padding: 5px 12px; }
.news-time { font-size: 20px; color: var(--gray); }
.news-title { font-size: 30px; font-weight: 700; line-height: 1.2; }
.news-summary { font-size: 23px; color: #5a5a57; margin-top: 8px; line-height: 1.4; }

.embed-frame { width: 100%; height: 100%; border: 0; border-radius: var(--radius-card); background: var(--surface); }

/* ---------- Markdown ---------- */

.markdown-body { font-size: 27px; line-height: 1.5; color: #3c3c3a; }
.markdown-body h1, .markdown-body h2, .markdown-body h3 { font-family: var(--font-display); color: var(--ink); }
.markdown-body a { color: var(--accent); }

/* ---------- Assist ---------- */

.assist-hero { text-align: center; padding-top: 20px; }
.assist-headline { font-family: var(--font-display); font-size: 54px; }
.assist-sub { font-size: 28px; color: #5a5a57; margin-top: 10px; }
.assist-btn {
  margin: 44px auto 10px; width: 340px; height: 340px; padding: 0 46px;
  border-radius: 50%; border: none; cursor: pointer;
  background: radial-gradient(circle at 50% 32%, color-mix(in srgb, var(--accent) 80%, #fff), var(--accent) 70%);
  color: #fff; font-family: var(--font-display); font-size: 29px; line-height: 1.18;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  box-shadow: 0 20px 44px rgba(176, 35, 24, 0.5); position: relative; transition: transform 0.14s ease;
}
.assist-btn:active { transform: scale(0.92); }
.assist-btn::after { content: ""; position: absolute; inset: -18px; border-radius: 50%; border: 5px solid var(--accent-soft); animation: pulse-ring 1.8s ease-out infinite; }
@keyframes pulse-ring { 0% { transform: scale(0.92); opacity: 1; } 100% { transform: scale(1.16); opacity: 0; } }
.assist-btn .bicon { width: 76px; height: 76px; }
.assist-btn .bicon svg { width: 100%; height: 100%; }

.svc-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; }
.svc { background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-card); padding: 24px; display: flex; gap: 18px; align-items: flex-start; }
.svc-big { flex-direction: column; align-items: center; text-align: center; gap: 12px; padding: 22px 16px; }
/* font-size for the emoji case: without it the glyph inherited the 16px body
   size and sat as a speck inside the 64px box. */
.svc-icon { flex: 0 0 auto; width: 64px; height: 64px; border-radius: 16px; background: var(--cream); color: var(--accent); font-size: 34px; line-height: 1; display: flex; align-items: center; justify-content: center; }
.svc-icon svg { width: 36px; height: 36px; }
.svc-label { font-weight: 700; font-size: 26px; }

/* Assist confirmation overlay */
.assist-confirm { position: absolute; inset: 0; background: rgba(29, 29, 27, 0.55); display: flex; align-items: center; justify-content: center; z-index: 40; animation: fade-in 0.25s ease; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.assist-confirm-card { background: var(--surface); border-radius: 32px; padding: 56px 64px; text-align: center; max-width: 720px; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4); animation: tile-pop 0.45s cubic-bezier(0.34, 1.4, 0.64, 1); }
.assist-confirm-title { font-family: var(--font-display); font-size: 46px; }
.assist-confirm-text { font-size: 27px; color: #5a5a57; margin-top: 14px; line-height: 1.4; }
/* The only button on the confirmation card was an unstyled UA <button>. */
.assist-confirm-close {
  appearance: none; -webkit-appearance: none;
  margin-top: 34px; min-height: 68px; padding: 16px 44px;
  border: 0; border-radius: 999px; background: var(--accent); color: #fff;
  font: inherit; font-size: 26px; font-weight: 700; cursor: pointer;
  transition: transform 0.14s ease;
}
.assist-confirm-close:active { transform: scale(0.95); background: var(--accent-strong); }

/* ---------- Footer ---------- */

.kiosk-footer { flex: 0 0 auto; min-height: 220px; display: flex; align-items: center; gap: 22px; padding: 14px 44px 34px; position: relative; }
.lang-chips { display: flex; gap: 10px; }
.chip { border: 2px solid rgba(255, 255, 255, 0.34); background: rgba(255, 255, 255, 0.07); color: #fff; font-family: var(--font-display); font-size: 24px; border-radius: 999px; padding: 12px 24px; cursor: pointer; }
.chip.is-active { background: #fff; color: var(--accent); }

.footer-btn { display: inline-flex; align-items: center; gap: 14px; border: 2px solid rgba(255, 255, 255, 0.34); background: rgba(255, 255, 255, 0.07); color: #fff; font-family: var(--font-body); font-weight: 700; font-size: 25px; border-radius: 999px; padding: 14px 28px; cursor: pointer; text-decoration: none; transition: transform 0.14s ease; }
.footer-btn:active { transform: scale(0.94); }
.footer-btn.is-active { background: #fff; color: var(--accent); }
.footer-btn-icon { width: 32px; height: 32px; display: inline-flex; }
.footer-btn-icon svg { width: 100%; height: 100%; }

.offline-chip { position: absolute; right: 44px; bottom: 18px; display: inline-flex; align-items: center; gap: 8px; font-size: 20px; font-weight: 600; color: #fff; background: rgba(0, 0, 0, 0.28); border-radius: 999px; padding: 8px 18px; }
/* Load-bearing: the display rule above outranks the UA's `[hidden] { display:
   none }` (same specificity, later origin wins for author rules), so without
   this the chip is visible from first paint no matter what main.js toggles —
   the kiosk showed a permanent false "Frakoblet" in every interactive session. */
.offline-chip[hidden] { display: none; }

/* ---------- Attract mode overlay ---------- */

.attract {
  position: absolute; inset: 0; z-index: 60;
  background: radial-gradient(120% 80% at 50% 0%, var(--accent) 0%, var(--accent-strong) 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; color: #fff; overflow: hidden;
}
.attract[hidden] { display: none; }
/* Idle-screen composition: logo + greeting + name + slogan as one centred group
   (attract.js renders them as flow children of a centred flex column). */
.attract-logo { margin-bottom: 46px; }
.attract-logo-img { display: block; max-width: 520px; max-height: 170px; }
.attract-kicker { font-size: 40px; font-weight: 600; letter-spacing: 0.5px; opacity: 0.92; margin-bottom: 12px; }
.attract-cta {
  position: absolute; bottom: 150px; left: 50%; transform: translateX(-50%);
  background: #fff; color: var(--accent); font-family: var(--font-display); font-size: 32px;
  border-radius: 999px; padding: 26px 54px; white-space: nowrap;
  animation: cta-pulse 1.6s ease-in-out infinite; box-shadow: 0 18px 40px rgba(0, 0, 0, 0.35);
}
@keyframes cta-pulse { 0%, 100% { transform: translateX(-50%) scale(1); } 50% { transform: translateX(-50%) scale(1.06); } }

/* ---------- Closed-store notice ---------- */

.closed-card { background: var(--bg); border-radius: 34px; padding: 60px 48px; margin: 40px 0; text-align: center; box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35); }
.closed-title { font-family: var(--font-display); font-size: 56px; color: var(--ink); }
.closed-body { font-size: 30px; color: #5a5a57; margin-top: 16px; }
.closed-hours { display: grid; grid-template-columns: auto auto; gap: 8px 40px; justify-content: center; margin-top: 34px; font-size: 28px; }
.closed-hours dt { font-weight: 700; text-align: right; }
.closed-hours dd { margin: 0; text-align: left; }

/* ---------- Toast ---------- */

.toast {
  /* Centred with insets, not left:50% + translate: a shrink-to-fit box placed at
     the midline only gets the RIGHT half of the stage to lay out in, so a
     60-character reach-mode toast wrapped into two ragged lines at ~470px
     despite the 860px cap. */
  position: absolute; left: 0; right: 0; bottom: 260px;
  margin-inline: auto; width: max-content; max-width: 860px;
  background: var(--ink); color: #fff; font-size: 25px; font-weight: 600;
  border-radius: 999px; padding: 18px 36px; z-index: 70;
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.4); text-align: center;
}
.toast[hidden] { display: none; }

/* ---------- Render fallback ---------- */

.render-fallback { background: var(--bg); border-radius: 34px; padding: 60px 48px; margin: 40px 0; text-align: center; box-shadow: var(--shadow-card); }
.render-fallback-title { font-family: var(--font-display); font-size: 48px; color: var(--ink); }
.render-fallback-body { font-size: 27px; color: #5a5a57; margin-top: 14px; }

/* ---------- Orientation guard (portrait enforced) ---------- */

.orientation-guard {
  position: fixed; inset: 0; z-index: 200;
  background: var(--accent-strong); color: #fff;
  display: flex; align-items: center; justify-content: center; text-align: center;
}
.orientation-guard[hidden] { display: none; }
.orientation-inner { max-width: 70vw; }
.orientation-glyph { font-size: 22vmin; line-height: 1; animation: rotate-hint 2.4s ease-in-out infinite; }
@keyframes rotate-hint { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(90deg); } }
.orientation-guard h2 { font-family: var(--font-display); font-size: 6vmin; margin: 24px 0 8px; }
.orientation-guard p { font-size: 3.4vmin; opacity: 0.92; }

/* ---------- Landscape orientation (1920×1080 templates) ---------- */
/* The stage swaps to a landscape canvas when the manifest orientation is
   'landscape' (main.js adds .landscape). The shell chrome (header/footer/clock)
   is retuned for the wide, short canvas; each home paradigm owns its own
   landscape rules in its css/home-*.css. Colours stay on the branding vars. */

.stage.landscape { width: var(--stage-w, 1920px); height: var(--stage-h, 1080px); }

.stage.landscape .kiosk-header { min-height: 150px; padding: 22px 56px 4px; }
.stage.landscape .brand { min-height: 96px; }
.stage.landscape .brand-logo { max-height: 120px; max-width: 460px; }
.stage.landscape .store-line { font-size: 26px; margin-top: 6px; }
.stage.landscape .clock { top: 24px; right: 40px; padding: 10px 18px 12px; }
.stage.landscape .clock-time { font-size: 38px; }
.stage.landscape .clock-date { font-size: 17px; }
.stage.landscape .content { padding: 6px 64px 16px; }
.stage.landscape .kiosk-footer { min-height: 130px; padding: 10px 56px 22px; }

/* Circular home in landscape: renderer spreads tiles across ~2 rows, so trim
   the title, label ceiling and gaps a touch to keep the ≥120px targets
   comfortable. (The icon is already 76px in both orientations.) */
.stage.landscape .home-title { font-size: 60px; margin: 12px 0 14px; }
.stage.landscape .tile-grid { gap: 30px 30px; }
.stage.landscape .tile-label { font-size: 26px; }

/* Respect reduced-motion for kiosk users sensitive to animation.

   The boot spinner is EXCLUDED from the blanket rule. Under reduced motion it
   swaps its rotation for a slow opacity pulse (see the boot-veil block above),
   and that pulse is the only thing on screen telling a customer the device is
   working rather than hung — squashing it to a single 0.001s iteration, as
   `*` with !important would, freezes it solid and reads as a dead kiosk. It is
   a gentle fade, not motion, so it is safe to keep here. Excluding it in the
   SELECTOR (rather than fighting back with more !important) is what makes the
   earlier rule win: the blanket declaration simply never matches it.

   The wayfinding ROUTE is excluded for the same reason. A tenant floor plan
   (department `body.floorplan_svg`, e.g. MENY Indre Havn) draws its walking
   route as `#route-dots` circles whose staggered opacity fade travels from
   "Du er her" to the destination — the only cue that says which way to walk
   (SMIL is stripped from tenant SVGs, so this inline CSS animation is all a
   tenant map can use). It is an opacity fade, not movement. And on a kiosk
   the media query reports how the DEVICE was set up (Windows "Animation
   effects" off, "best performance", a remote session), not a shopper's
   preference: with the route frozen solid, every map on such a panel looked
   static. The pin ripples (scale) stay damped. */
@media (prefers-reduced-motion: reduce) {
  *:not(.boot-spinner):not(.map-card #route-dots circle) { animation-duration: 0.001s !important; animation-iteration-count: 1 !important; transition-duration: 0.001s !important; }
}

/* ── Drilldown breadcrumb (Home › … › current) ────────────────────────────
   Lives between the header and #content; shown by main.js only in PAGE state. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 2px;
  /* 44px matches .content's gutter so the trail's text edge sits on the page
     card's edge below it (the crumb's own 14px side padding is visual only). */
  padding: 6px 30px 8px;
  min-height: 68px;
}
.breadcrumb[hidden] { display: none; }
/* The trail sits on .stage — the accent gradient — so it is on-ACCENT chrome
   like .store-line and .clock, never on-surface: painted in var(--accent) and
   var(--ink) it rendered "Hjem" at 1.0:1 (invisible) and the current crumb at
   2.7:1 on every drilldown. White at these alphas clears 4.5:1 on any Material
   tone-40 accent, so no tenant var is needed. Each crumb is also a real finger
   target now (≥56px tall, side padding), not a 39px text run. */
.crumb {
  display: inline-flex;
  align-items: center;
  min-height: 56px;
  background: none;
  border: none;
  border-radius: 999px;
  margin: 0;
  padding: 6px 14px;
  font-family: inherit;
  font-size: 28px;
  line-height: 1.1;
  color: rgba(255, 255, 255, 0.82);
  cursor: pointer;
  max-width: 360px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: background-color 0.14s ease;
}
.crumb:not(.crumb-current):active { background: rgba(255, 255, 255, 0.16); }
.crumb-current { color: #fff; font-weight: 700; cursor: default; }
.crumb-sep { font-size: 28px; color: rgba(255, 255, 255, 0.55); }

/* ── No-scroll pagination (menus, offers, coupons) ─────────────────────────────
   A list that overflows the screen is split into pages that each FIT, paged with
   big prev/next controls + dots. A self-service kiosk NEVER scrolls. The page is a
   flex column: a clipping viewport that fills it + a fixed-height pager (and, for
   coupons, a trailing note). `fitPage` (main.js) skips `.paginated` pages. */
.paginated { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* The list area fills the page and CLIPS (a full page's rows always fit it — the
   renderer paginates against this box's height). Inner layout (list vs grid) comes
   from a co-class: .menu-viewport, .cards-2, .coupon-grid. */
.pager-viewport { flex: 1 1 auto; min-height: 0; overflow: hidden; }
/* Pager reserves its own space so pagination measures the real available height.
   The reserve must EQUAL the filled pager: an 84px button + 18px of padding-top is
   102px. At 96px the viewport was measured 6px taller than it ends up once the
   controls land, so a row that fit by less than that had its bottom edge (rounded
   corners, shadow) clipped on the page it was paginated onto. */
.pager { flex: 0 0 auto; min-height: 102px; display: flex; align-items: center; justify-content: center; gap: 34px; padding-top: 18px; }
/* mountPager marks a one-page list `pager-single` AFTER measuring: the reserve
   then only left a 114px void under the last row, and collapsing it can only
   give the viewport more room, never less. (Keying this off :empty was wrong —
   the pager is empty DURING measurement too, so multi-page lists measured
   against a taller viewport and lost their last row when the controls landed.) */
.pager.pager-single { min-height: 0; padding: 0; }
/* The RSS page's swap region and feed wrapper pass the flex column down to
   the viewport so pagination measures a real height. */
.rss-region, .embed-feed { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.pager-btn {
  width: 84px; height: 84px; border-radius: 50%; flex: 0 0 auto;
  border: 2px solid var(--border); background: var(--card, #fff);
  color: var(--accent); font-size: 52px; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
.pager-btn:disabled { opacity: 0.32; cursor: default; }
.pager-dots { display: flex; align-items: center; gap: 16px; }
.pager-dot {
  width: 20px; height: 20px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 2px solid var(--accent); background: transparent;
}
.pager-dot.is-active { background: var(--accent); transform: scale(1.25); }

/* ── Menu (drilldown navigation node) ─────────────────────────────────────── */
.menu-intro { flex: 0 0 auto; font-size: 30px; color: var(--ink); opacity: 0.9; margin: 0 0 22px; line-height: 1.3; }
.menu-viewport { display: flex; flex-direction: column; gap: 16px; }
.menu-row {
  display: flex; align-items: center; gap: 26px;
  width: 100%; text-align: left; box-sizing: border-box;
  padding: 26px 30px; border-radius: 18px;
  background: var(--card, #fff); border: 2px solid var(--border);
  color: var(--ink); font-family: inherit; cursor: pointer;
}
.menu-row--static { cursor: default; }
.menu-row-icon { flex: 0 0 auto; width: 72px; height: 72px; display: flex; align-items: center; justify-content: center; font-size: 52px; color: var(--accent); }
.menu-row-icon svg { width: 62px; height: 62px; }
.menu-row-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.menu-row-label { font-family: var(--font-display); font-size: 38px; line-height: 1.05; }
.menu-row-desc { font-size: 25px; color: #6b6b68; line-height: 1.25; }
.menu-row-chev { flex: 0 0 auto; width: 40px; height: 40px; color: var(--accent); }
.menu-empty { font-size: 28px; color: #6b6b68; }

/* ── Wayfinding (animated map + arrows) ────────────────────────────────────── */
.way-caption { display: flex; align-items: center; gap: 14px; font-size: 34px; font-weight: 700; color: var(--ink); margin-bottom: 18px; flex-wrap: wrap; }
.way-caption-arrow { color: var(--accent); font-size: 40px; line-height: 1; }
.way-caption-dest { font-family: var(--font-display); }
.way-caption-sep { color: var(--border); }
.way-caption-floor { font-weight: 400; color: #6b6b68; }
.way-map { width: 100%; border-radius: 20px; overflow: hidden; background: #fff; border: 2px solid var(--border); }
.way-map svg { display: block; width: 100%; height: auto; }
.way-note { font-size: 26px; color: #6b6b68; padding: 16px 24px; margin: 0; }
.way-steps { margin: 24px 0 0; padding-left: 1.2em; display: flex; flex-direction: column; gap: 12px; }
.way-step { font-size: 30px; color: var(--ink); line-height: 1.3; }
.way-directions { font-size: 30px; color: var(--ink); line-height: 1.35; margin-top: 22px; }

/* ── RSS item detail (one more level inside a feed) ────────────────────────── */
.rss-item-title { font-family: var(--font-display); font-size: 52px; line-height: 1.05; color: var(--ink); margin: 6px 0 12px; }
.rss-item-meta { font-size: 24px; color: #6b6b68; margin-bottom: 20px; }
.rss-item-img { display: block; width: 100%; max-height: 620px; object-fit: cover; border-radius: 18px; margin-bottom: 24px; }
.rss-item-summary { font-size: 32px; line-height: 1.4; color: var(--ink); margin: 0 0 24px; }
.rss-item-link { font-size: 24px; color: #6b6b68; word-break: break-word; }
.news-item--tap { display: block; width: 100%; text-align: left; font-family: inherit; cursor: pointer; border: none; }
.news-more { display: inline-block; margin-top: 10px; font-size: 24px; font-weight: 700; color: var(--accent); }
