/* =============================================================================
 * Fontana Hotels — design system
 * Clean, welcoming, warm, professional, with a little depth. One component set;
 * per-hotel colours are swapped as CSS variables by core/theme.js. The signature
 * is the coloured header block + the Cormorant wordmark. Mobile-first.
 * ========================================================================== */

/* ----------------------------------------------------------------------------
 * 1. Tokens  (the §6 [DECIDED] values are kept exactly; the rest are derived)
 * -------------------------------------------------------------------------- */
:root {
  color-scheme: light;

  /* geometry (§6 fixed) */
  --radius: 12px;
  --radius-sm: 8px;
  --space: 16px;
  --maxw: 520px;
  /* geometry (added) */
  --radius-lg: 20px;
  --radius-pill: 999px;
  --radius-btn: 7px; /* buttons: almost square, small soft corners */
  --radius-card: 9px; /* content surfaces (menu, cards, panels) — ONE shared corner so everything matches */

  /* neutrals — warm (§6 fixed) */
  --ink: #15211c;
  --bg: #f7f5ef;
  --surface: #fff;
  --muted: #6b6f6a;
  --line: rgba(0, 0, 0, .10);
  /* neutrals (added) */
  --ink-soft: #44504a;
  --surface-2: #fbfaf4;
  --line-strong: rgba(0, 0, 0, .16);

  /* brand — defaults; theme.js overrides per hotel (§6 fixed) */
  --c-primary: #1d6e86;
  --c-primary-dark: #0f2a38;
  --c-accent: #e0a55a;
  --c-tint: #e9f2f4;

  /* derived brand helpers */
  --on-primary: #fff;
  --on-accent: var(--ink);
  /* soft tinted surface from the active primary — adapts to light/dark */
  --tint-bg: color-mix(in srgb, var(--c-primary) 9%, var(--surface));
  --primary-hover: color-mix(in srgb, var(--c-primary) 86%, #000);

  /* feedback */
  --danger: #a8302f;
  /* one fixed red for every close / delete / closed-day cue — deliberately NOT re-declared in
     dark mode and never color-mixed with ink/accent, so it's identical in every theme (the Manager Reset red). */
  --danger-solid: #c0322b;
  --success: #2f6b4f;

  /* elevation */
  --shadow-sm: 0 1px 2px rgba(20, 30, 25, .06), 0 1px 3px rgba(20, 30, 25, .05);
  --shadow-md: 0 2px 8px rgba(20, 30, 25, .08), 0 12px 28px rgba(20, 30, 25, .08);

  /* type */
  --font-display: var(--font-ui); /* Fraunces dropped — Inter is the one sans for everything but the wordmark */
  --font-ui: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-logo: 'Cormorant', Georgia, 'Times New Roman', serif; /* the wordmark */
  --lh: 1.55;

  /* header block — white at brand level; per-hotel views override these */
  --header-bg: var(--surface);
  --header-fg: var(--ink);

  --focus: color-mix(in srgb, var(--c-primary) 60%, #000);
}

/* Dark mode — warm charcoal, not pure black. Brand fills (primary/accent) carry
 * over; the tinted surface re-derives against the dark surface automatically. */
/* Dark mode — one token set, swapped. Applied via data-theme on <html> by the
 * inline script in index.html (saved preference; "auto" follows the system). */
:root[data-theme="dark"] {
    color-scheme: dark;
    --ink: #eef1ea;
    --ink-soft: #c3ccc4;
    --bg: #12161a;
    --surface: #1a2024;
    --surface-2: #202730;
    --muted: #9aa49d;
    --line: rgba(255, 255, 255, .12);
    --line-strong: rgba(255, 255, 255, .20);
    --on-accent: #1a1206;
    --tint-bg: color-mix(in srgb, var(--c-primary) 24%, var(--surface));
    --primary-hover: color-mix(in srgb, var(--c-primary) 80%, #fff);
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow-md: 0 12px 30px rgba(0, 0, 0, .5);
    --focus: color-mix(in srgb, var(--c-primary) 55%, #fff);
}

/* ----------------------------------------------------------------------------
 * 2. Reset & base
 * -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: var(--lh);
  color: var(--ink);
  /* depth: a soft lighter pocket of the active colour up top, fading into bg */
  background:
    radial-gradient(135% 70% at 50% -8%, color-mix(in srgb, var(--c-primary) 13%, var(--bg)) 0%, transparent 58%),
    var(--bg);
  background-attachment: fixed;
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Savile Row pinstripe — a faint tailored texture behind the whole app. Fixed so
   it sits still while content scrolls; kept subtle, and fainter in light mode.
   Since 2026-07-17 it's a VARIABLE: the default stays the pinstripe, and a wizard client's
   chosen Sfondo (bg_01…bg_10, HQ picker) overrides it at boot via --app-bg on <html>. */
:root { --app-bg: url('../assets/img/savile-row-pinstripe.jpg'); }
/* The FADE below belongs to the pinstripe (the source file is too light — these low opacities
   ARE its treatment). A chosen Sfondo texture (bg_01…bg_10) is final art: the boot sets
   --app-bg-op:1 alongside --app-bg, and every rule here defers to it. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background: var(--app-bg) center center / cover no-repeat;
  opacity: var(--app-bg-op, .085); /* light-mode landing — kept very faint */
}
html[data-theme="dark"] body::before { opacity: var(--app-bg-op, .42); }
/* inside pages (a hotel, or the guest Private Area): lift the pinstripe to ~40% in both themes */
html[data-theme="dark"] body:has(.app--hotel)::before,
html[data-theme="dark"] body:has(.app--guest)::before { opacity: var(--app-bg-op, .4); }
html[data-theme="light"] body:has(.app--hotel)::before,
html[data-theme="light"] body:has(.app--guest)::before { opacity: var(--app-bg-op, .24); }

img, picture, svg { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--c-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

::selection { background: color-mix(in srgb, var(--c-accent) 45%, transparent); }

/* Portrait guard — the app is portrait-only. Android installed PWAs honour the manifest orientation
   lock; iOS ignores it, so we cover the app on a phone held in landscape. Scoped to phone-sized
   landscape (short viewport) so it NEVER triggers on a tablet or desktop (which stay tall). */
.rotate-guard { display: none; }
@media (orientation: landscape) and (max-height: 500px) {
  .rotate-guard {
    display: flex; position: fixed; inset: 0; z-index: 9999;
    align-items: center; justify-content: center; padding: 24px; text-align: center;
    background: var(--bg); color: var(--ink);
  }
  .rotate-guard__inner { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 320px; }
  .rotate-guard__inner svg { color: var(--c-primary); }
  .rotate-guard__txt { margin: 0; font-family: var(--font-ui); font-weight: 600; font-size: 16px; line-height: 1.35; color: var(--ink); }
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

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

/* ----------------------------------------------------------------------------
 * 3. Typography — Fraunces for display, Inter for UI. Sentence case throughout.
 * -------------------------------------------------------------------------- */
h1, h2, h3, .display, .title {
  font-family: var(--font-display);
  font-weight: 600;
  font-optical-sizing: auto;
  line-height: 1.15;
  margin: 0;
  letter-spacing: -.01em;
}
.display { font-size: calc(clamp(28px, 7vw, 40px) * var(--font-display-scale, 1)); }
h1, .h1 { font-size: calc(clamp(24px, 5.5vw, 30px) * var(--font-display-scale, 1)); }
h2, .h2 { font-size: calc(20px * var(--font-display-scale, 1)); }
h3, .h3 { font-size: calc(17px * var(--font-display-scale, 1)); }
p { margin: 0 0 .75em; }
p:last-child { margin-bottom: 0; }

.eyebrow {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 13px;
  color: var(--muted);
  margin: 0;
}
.lead { font-size: 17px; color: var(--ink-soft); }
.muted { margin: 0; color: var(--muted); }
.small { font-size: 14px; }

/* ----------------------------------------------------------------------------
 * 4. Layout
 * -------------------------------------------------------------------------- */
.app { min-height: 100dvh; }
/* guest app: full-height column so the bottom tab bar stays pinned to the bottom
   even when a screen's content is short (otherwise it floats up mid-screen) */
.app--guest { display: flex; flex-direction: column; }
.app--guest .screen { flex: 1 0 auto; padding-bottom: calc(84px + env(safe-area-inset-bottom)); } /* clear the fixed tab bar */
.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--space); }
.screen { padding-block: 20px calc(28px + env(safe-area-inset-bottom)); }
.stack { display: grid; gap: var(--gap, 14px); }
.cluster { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.section { margin-block: 26px; }
.section__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
/* ⚠️ the weight is the FACE's own, not a literal — see the note on .h1 below */
.section__title { font-family: var(--font-display); font-weight: var(--font-display-w, 600); font-size: calc(22px * var(--font-display-scale, 1)); }
.section__head--center { justify-content: center; }
.section__head--center .section__title { text-align: center; }
.section__logo { display: block; width: auto; height: 46px; max-width: 74%; object-fit: contain; }
.section__logo--trentino { height: 42px; }
/* the logos are white (for the dark theme); on the light theme flip them dark so they read */
html[data-theme="light"] .section__logo { filter: invert(1); }
/* big page headings (e.g. the hotel name under "Your stay") — Inter, bold */
/* ⭐ THE VENUE'S OWN FACE CARRIES THE PAGE TITLES TOO (Tony, 2026-08-07, looking at Cèsa
   Planber in Caveat): the content was in his handwriting face and every heading was still in
   the system font, so one app read as two stitched together. `h1, h2, h3` above already say
   `--font-display`; these two classes were putting it back, which is the only reason the
   titles stood apart. ⚠️ Both classes appear ONLY in the customer app (publicApp.js) — never
   in the Manager or HQ — so the working screens keep the UI font whatever a venue chooses.
   ⚠️ THIS IS ITS OWN COMMIT so `git revert` puts it back exactly: a handwriting face at 30px
   is charming on «Our Services» and may be less legible on a longer title. Judge it live.
   ⛔ AND ASK FOR THE WEIGHT THE FACE ACTUALLY HAS. Handing these two rules --font-display without
   also fixing the weight walked straight back into the bug fonts.js documents at length: the app
   ships ONE weight file per face (Caveat 600, Marcellus 400, Libre Baskerville 700), so a literal
   700 here is a weight Planber's file does not contain. --font-display-w carries the face's own
   number; the fallback is the value each rule had before, so any surface without a client face —
   the demo, Pane&Vino, the landing — does not move at all. */
.h1 { font-family: var(--font-display); font-weight: var(--font-display-w, 700); }
.section__link { font-size: 14px; font-weight: 600; }

/* thin inset separator between home blocks — fades out before the edges */
.rule {
  border: 0; height: 1px; width: 100%; margin: 18px 0;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--ink) 16%, transparent) 14%,
    color-mix(in srgb, var(--ink) 16%, transparent) 86%,
    transparent 100%);
}

/* ----------------------------------------------------------------------------
 * 5. The signature — the header block + wordmark
 *    A fixed block whose colour belongs to the active hotel (white at the brand
 *    level). The "Fontana Hotels" wordmark is the lockup.
 * -------------------------------------------------------------------------- */
.apphead {
  position: sticky; top: 0; z-index: 20;
  padding-top: env(safe-area-inset-top); /* clear the notch / status bar (esp. installed PWA) */
  background: var(--header-bg); color: var(--header-fg);
  border-bottom: 1px solid color-mix(in srgb, var(--header-fg) 12%, transparent);
  box-shadow: 0 4px 12px rgba(8, 16, 20, .24);
  transition: box-shadow .22s ease;
}
/* stronger lift once the page is scrolled */
html.is-scrolled .apphead { box-shadow: 0 12px 28px rgba(8, 16, 20, .46); }
.apphead__inner { display: flex; align-items: center; gap: 10px; min-height: 60px; }

/* brand level — white block, dark wordmark */
.apphead--brand { --header-bg: var(--brand-header-bg, var(--surface)); --header-fg: var(--brand-header-fg, var(--ink)); } /* a business may bring its own header colours (theme.headerBg/Fg) */

/* hotel level — the hotel's colour with a soft sheen, white wordmark */
.apphead--hotel {
  --header-fg: #fff;
  background:
    radial-gradient(120% 180% at 0% 0%, color-mix(in srgb, #fff 22%, var(--c-primary)) 0%, transparent 55%),
    linear-gradient(180deg, var(--c-primary) 0%, var(--primary-hover) 100%);
  border-bottom: 2px solid var(--c-primary-dark);
}

/* accent header — the guest "Private Area" (matches the gold banner you tapped) */
.apphead--accent {
  --header-fg: var(--on-accent);
  background:
    radial-gradient(120% 180% at 0% 0%, color-mix(in srgb, #fff 26%, var(--c-accent)) 0%, transparent 55%),
    linear-gradient(180deg, var(--c-accent) 0%, color-mix(in srgb, var(--c-accent) 86%, #000) 100%);
  border-bottom: 2px solid color-mix(in srgb, var(--c-accent) 55%, #000);
}
/* "PRIVATE AREA" header title — same look as the banner label (Inter, bold) */
.apphead__title { flex: 1; min-width: 0; text-align: center; font-family: var(--font-ui); font-weight: 800;
  font-size: 19.5px; letter-spacing: .06em; color: var(--header-fg); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.apphead__title--sm { font-size: clamp(15px, 4.5vw, 18px); letter-spacing: .04em; } /* longer titles that must fit (e.g. OPEN YOUR GUEST APP) */
/* guest header: "PRIVATE AREA" + the current screen name stacked underneath */
.apphead__titles { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; }
.apphead__titles .apphead__title { flex: none; max-width: 100%; }
.apphead__sub { font-family: var(--font-display); font-style: italic; font-size: calc(15px * var(--font-display-scale, 1)); line-height: 1.25;
  color: var(--on-accent); max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* hotel-name header — sized to fit the full "Hotel ..." name, with line-height
   that leaves room for descenders (g, p) so they aren't clipped by overflow */
/* One size for every hotel name, tuned so the longest ("Hotel Cesare Augusto")
   always fits between the back + menu icons with margin — never truncates. */
.apphead--hotel .wordmark--center { font-family: var(--font-ui); font-weight: 700; letter-spacing: -.01em; font-size: clamp(17px, 5.6vw, 24px); line-height: 1.12; }

.wordmark {
  font-family: var(--font-logo);
  font-weight: 600; font-size: 31px; line-height: 1; letter-spacing: .015em;
  color: var(--header-fg); white-space: nowrap;
}
/* "Hotels" in green italic, matching the wordmark on the unlock page */
.wordmark em { font-style: italic; font-weight: 600; color: #2f6b4f; opacity: 1; }
.apphead--hotel .wordmark em { color: inherit; } /* on a coloured header keep it white */
@media (prefers-color-scheme: dark) { .wordmark em { color: #6aa886; } }
.wordmark--sm { font-size: 22px; }
.wordmark--center { flex: 1; text-align: center; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* landing hero header — a cream "letterhead" block (fixed light in both modes) */
/* ⛔⛔ `position: relative` WAS HERE AND IT UNDID THE STICKY HEADER. Tony, 2026-08-09: *"THE HEADER
   SHOULD BE FIXED (I think it was and got lost in some upgrade) and the content should scroll below
   it."* He was right on both counts. `.apphead` (line ~254) sets `position: sticky; top: 0`; this
   rule has the SAME weight — one class each — and sits LATER in the file, so it silently won, and
   every customer header has scrolled away with the page ever since.
   ⚠️ THE `relative` WAS NOT POINTLESS: `.apphead__set` (the gear) is absolutely positioned and needs
   a positioned ancestor. ⭐ `sticky` IS a positioned ancestor, so removing this line costs the gear
   nothing — checked in a browser, not reasoned about.
   ⛔ THE LESSON, for the third time in this repo: two rules, one selector, and only a BROWSER can
   tell you which one won. `grep` for the property you set finds your rule and never its twin. */
.landing-head { background: var(--brand-header-bg, #f6f3ec); color: var(--brand-header-fg, #15211c); border-bottom: 1px solid #2f6b4f; } /* a business's own header colours win; the classic cream is the fallback. 1px divider (was 2px — too heavy) */
/* Settings gear, bottom-right of the cream branding bar — anchored to the BOTTOM so it always lands on
   the name row (compact) / tagline row (full) and never rides up into the notch / status-bar safe area.
   The header is ALWAYS cream (both themes), so the gear uses a FIXED dark colour — var(--ink) would go
   light in dark mode → invisible here. */
/* Settings gear — TOP-right corner (below the notch/status-bar via safe-area). Out of the centred logo +
   payoff column, so a payoff line of ANY length (Super-Admin editable) never runs under it. */
.apphead__set { position: absolute; right: 10px; top: calc(env(safe-area-inset-top) + 8px); z-index: 3; width: 40px; height: 40px; display: grid; place-items: center; color: color-mix(in srgb, var(--header-fg, #15211c) 55%, transparent); border-radius: 999px; text-decoration: none; -webkit-tap-highlight-color: transparent; }
/* NEVER underline the gear — the global `a:hover{text-decoration:underline}` (which iOS makes stick on
   tap) outranks the base rule above, so override it explicitly in every interactive state. */
.apphead__set:hover, .apphead__set:active, .apphead__set:focus, .apphead__set:visited { text-decoration: none; }
.apphead__set:active { color: color-mix(in srgb, var(--header-fg, #15211c) 78%, transparent); background: color-mix(in srgb, var(--header-fg, #15211c) 6%, transparent); }
.landing-head__inner { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 4px; padding: 2px 0 18px; } /* full (Home) header: logo higher; bottom pad (+3px → taller header, more empty space under the payoff) (Tony) */
.landing-head__logo + .landing-head__title { margin-top: -11px; } /* pull the payoff up toward the logo's ornament (Tony: up another 2px) */
/* ⭐⭐ THE OFFLINE STRIP (2026-08-13) — hidden unless <html> carries `is-offline`, which index.html
   sets from the `online`/`offline` events. Living in CSS rather than in a re-render is what keeps a
   connectivity change from repainting the header under somebody's finger.
   ⭐ IT WEARS THE HEADER'S OWN COLOURS. Everything is mixed from `--brand-header-fg`, so it is legible
   on Cesare Augusto's near-black header and on Pane&Vino's cream one with no per-client work and no
   second colour to keep in step. Same technique as `.apphead__set` and `.apphead__tabs` above. */
/* ⭐ RED, on Tony's word (2026-08-13): *"the sentence «You are offline…» should be on a red bg"*. It
   first shipped in the header's own colours, which read as decoration; being offline is a STATE the
   guest has to notice, and the app's own `--danger-solid` is the colour it already uses to say so.
   ⚠️ White on #c0322b, not a tint of the header — this one deliberately does NOT follow the brand,
   because on a near-black header a brand-tinted warning is invisible, which is the whole fault above.
   ⭐⭐ AND IT LIVES AT THE FOOT OF THE SCREEN, not under the header (Tony, same day, an hour later).
   The header is the one place the hotel's identity lives; an alarm colour immediately beneath the
   logo cuts the brand in half on every offline open. At the bottom it sits with the app's own
   furniture and — because both bars are fixed — stays visible the whole time he scrolls.
   ⛔ `pointer-events: none` is load-bearing, not tidiness: it sits a few pixels above the tab bar,
   and neither may it look tappable nor may it ever swallow a tap meant for a tab. */
.offline-strip { display: none; }
.is-offline .offline-strip {
  display: block; margin: 0; padding: 7px 14px 8px; text-align: center;
  font-size: 12.5px; line-height: 1.3; font-weight: 600; letter-spacing: .005em;
  color: #fff;
  background: var(--danger-solid, #c0322b);
  z-index: 21;                 /* the tab bar is 20 — above it, below every sheet and modal */
  pointer-events: none;
}
/* THE GUEST APP — it rides ON TOP of the fixed tab bar. Positioned against the BAR (`bottom: 100%`)
   and never with a number of its own: that bar's height moves with the safe-area inset and with the
   phone's text size, so any hard offset would be wrong on some device. */
.is-offline .tabbar > .offline-strip { position: absolute; left: 0; right: 0; bottom: 100%; }
/* …and the page grows the extra clearance so the strip never covers the last line of content.
   84px is what clears the bar alone (see .app--guest .screen above); the strip adds ~34px. */
.is-offline .app--guest .screen { padding-bottom: calc(118px + env(safe-area-inset-bottom)); }
/* THE MANAGER AND THE STAFF AREA have no tab bar at all, so there the strip pins to the foot of the
   screen itself — same place on screen, from the guest's or the owner's point of view.
   ⭐ AND THE LOGIN SCREEN (2026-08-14). Signing in is the one thing that genuinely cannot work with no
   signal — it is a live call to the database — so an owner who reaches this form offline is told
   BEFORE he types, instead of blaming his password for a bare refusal. */
.is-offline .apphead > .offline-strip,
.is-offline .mgrgate > .offline-strip {
  position: fixed; left: 0; right: 0; bottom: 0;
  padding-bottom: calc(8px + env(safe-area-inset-bottom));
}
.is-offline .app:not(.app--guest) .screen { padding-bottom: calc(72px + env(safe-area-inset-bottom)); }

/* ⛔⛔ THE COLOUR IS THE HEADER'S, NOT A FIXED DARK (2026-08-13). This said `color: #15211c`, which was
   correct for the original CREAM header and invisible on every client with a dark one. It was never
   seen because this text wordmark is the THIRD fallback in the lockup (publicApp.js:189) and only
   renders when `brand.logo` is absent — which online it never is.
   ⭐ OFFLINE IT IS ALWAYS ABSENT, and that is how it surfaced. Measured on the live hotel demo with
   DNS blackholed: the app renders fully from its saved copy, but `brand.logo` comes from the
   `public_client` call on Supabase — a DIFFERENT origin, which the service worker deliberately never
   caches — so the lockup falls to this line. Tony saw his hotel's name in near-black on a #1c1c24
   header, i.e. nothing at all. The brand colours themselves survive offline (they are injected into
   the HTML server-side, and that document IS cached), so the right colour was sitting there unused.
   ⚠️ The fallback stays `#15211c` for any context that does not set the variable, so nothing else moves. */
.wordmark--hero { font-size: clamp(40px, 14vw, 62px); line-height: 1.02; color: var(--brand-header-fg, #15211c); white-space: nowrap; }
.landing-head .wordmark--hero em { color: #2f6b4f; } /* green italic, fixed in both modes */
.landing-head__rule { width: 100%; max-width: 380px; height: 2px; border-radius: 2px; margin: 3px 0 5px;
  background: linear-gradient(90deg, transparent, #2f6b4f 12%, #2f6b4f 88%, transparent); }
.landing-head__title { font-family: var(--font-ui); font-weight: 600; font-size: 23px; color: var(--header-fg, #15211c); margin: 0; line-height: 1.2; }
.landing-head__sub { font-family: var(--font-display); font-style: italic; font-size: calc(15px * var(--font-display-scale, 1)); color: color-mix(in srgb, var(--header-fg, #15211c) 60%, transparent); margin: 0; line-height: 1.25; }
/* compact header — inner pages (Menu, Speciali, Impostazioni…): just the name/logo, a touch smaller,
   with no rule / payoff line / tagline. The full header stays on Home + Contatti. */
.landing-head--compact .landing-head__inner { padding: 2px 0 3px; gap: 0; } /* compact (inner pages): logo higher + shorter header (Tony) */
.landing-head--compact .wordmark--hero { font-size: clamp(26px, 8.5vw, 38px); }
.landing-head--compact .landing-head__logo { width: auto; height: calc(54px * var(--logo-scale, 1)); max-width: 78%;
  padding: 7px 0; box-sizing: border-box; object-fit: contain; } /* compact: size by HEIGHT; 7px top/bottom breathing space (Tony 2026-07-17 — a full-bleed PNG/SVG used to touch both edges; 5px → 7px same day) */

/* bare white icon button on a coloured header — no circle, like the banner arrow */
.iconbtn.iconbtn--on-color { color: #fff; background: transparent; }
.iconbtn.iconbtn--on-color:hover { background: rgba(255, 255, 255, .14); }
/* labeled icon button (the "Exit" button in the guest header) + a matching left spacer so the title stays centred */
.iconbtn--labeled { width: 48px; height: auto; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; padding: 4px 0; border-radius: 10px; }
.iconbtn__label { font-size: 10px; font-weight: 600; line-height: 1; letter-spacing: .03em; }
.apphead__spacer { flex: none; width: 48px; }
.iconbtn.iconbtn--on-accent { color: var(--on-accent); background: transparent; }
.iconbtn.iconbtn--on-accent:hover { background: rgba(0, 0, 0, .10); }

/* ----------------------------------------------------------------------------
 * 6. Buttons
 * -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-ui); font-weight: 600; font-size: 15px; line-height: 1;
  min-height: 46px; padding: 0 20px;
  border: 1px solid transparent; border-radius: var(--radius-btn);
  background: var(--surface-2); color: var(--ink); cursor: pointer;
  transition: background-color .18s ease, border-color .18s ease, transform .06s ease, box-shadow .18s ease;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: default; box-shadow: none; transform: none; }
/* primary — gradient + a soft side glow + glossy top edge for life */
.btn--primary {
  color: var(--on-primary);
  border-color: color-mix(in srgb, #000 16%, var(--c-primary)); /* thin crisp edge */
  background:
    linear-gradient(108deg, color-mix(in srgb, #fff 26%, var(--c-primary)) 0%, var(--c-primary) 42%, var(--primary-hover) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .28),
    0 6px 18px color-mix(in srgb, var(--c-primary) 38%, transparent);
}
.btn--primary:hover { filter: brightness(1.04); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .32), 0 8px 22px color-mix(in srgb, var(--c-primary) 46%, transparent); }
.btn--secondary { background: var(--surface); color: var(--ink); border-color: color-mix(in srgb, var(--ink) 18%, transparent); }
.btn--secondary:hover { background: var(--surface-2); border-color: var(--muted); }
.btn--ghost { background: transparent; color: var(--c-primary); }
.btn--ghost:hover { background: var(--tint-bg); }
.btn--accent {
  color: var(--on-accent);
  background: linear-gradient(108deg, color-mix(in srgb, #fff 32%, var(--c-accent)) 0%, var(--c-accent) 55%, color-mix(in srgb, var(--c-accent) 82%, #000) 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 6px 16px color-mix(in srgb, var(--c-accent) 40%, transparent);
}
.btn--accent:hover { filter: brightness(1.03); }
/* WhatsApp — the one true brand green (#25D366), so "message us" reads instantly. Matches the app's glossy finish. */
.btn--whatsapp {
  color: #fff;
  border-color: color-mix(in srgb, #000 16%, #25D366);
  background: linear-gradient(108deg, color-mix(in srgb, #fff 24%, #25D366) 0%, #25D366 46%, #1cb457 100%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30), 0 6px 16px color-mix(in srgb, #25D366 40%, transparent);
}
.btn--whatsapp:hover { filter: brightness(1.03); }
/* item 4 — customer splash: the owner's INLINE image paints on the first frame (no download); an opaque
   cover (= app bg) sits behind it so the app never flashes underneath; only the dismiss fades it out. */
.app-splash { position: fixed; inset: 0; z-index: 9000; background-color: var(--bg, #14181a);
  background-position: center; background-size: cover; background-repeat: no-repeat; transition: opacity .4s ease; }
.app-splash.is-out { opacity: 0; pointer-events: none; }
.btn--block { width: 100%; }
.btn--sm { min-height: 38px; padding: 0 14px; font-size: 14px; }
.btn[disabled], .btn:disabled { opacity: .5; cursor: not-allowed; }
.btn i { font-size: 18px; }

/* icon-only round button (app bar) */
.iconbtn {
  display: inline-grid; place-items: center; width: 42px; height: 42px;
  border-radius: var(--radius-pill); border: 1px solid transparent;
  background: transparent; color: var(--ink); cursor: pointer;
  transition: background-color .18s ease;
  -webkit-tap-highlight-color: transparent; /* no lingering colour flash on iOS tap */
}
/* hover tint only where a real pointer exists — avoids the sticky "brown circle" on touch */
@media (hover: hover) { .iconbtn:hover { background: var(--tint-bg); } }
.iconbtn i { font-size: 22px; }

/* ----------------------------------------------------------------------------
 * 7. Cards
 * -------------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.card__media { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }
.card__body { padding: 16px; }
.card__title { font-family: var(--font-display); font-weight: 600; font-size: calc(18px * var(--font-display-scale, 1)); margin: 0 0 4px; }

/* tinted "offer" card — the daily-offer money shot leans on this */
.card--tint { background: var(--tint-bg); border-color: color-mix(in srgb, var(--c-primary) 18%, transparent); }
.card--feature { box-shadow: var(--shadow-md); }
/* Specialità: keep the price pinned to the right and let a long description wrap to a 2nd line
   inside its own column — the price must never wrap onto a new row below the photo. */
.card--feature .card__body { flex-wrap: nowrap; align-items: center; }
.card--feature .card__body > .cluster { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; }
.card--feature .row__sub { white-space: normal; }
.card--feature .price { flex: none; white-space: nowrap; }

/* ── Manager panels take a wash of the client's HQ background colour (Tony, 2026-07-24 — "subtle,
   elegant, almost unnoticeable but felt"). Manager only: .app--guest is the customer app, and HQ
   sets neither var so the fallbacks keep it neutral there. The gold Passaparola (.pp-card) and the
   primary-tint (.card--tint) panels keep their own colour.
   SOURCE ORDER: --panel-tint-src is the NORMALISED colour theme.js computes (client hue, constant
   lightness) so every client feels the same strength — see the long note in core/theme.js for why a
   raw mix cannot do that. If the client's "Norm Col" switch is OFF the var is absent and this falls
   back to his colour mixed RAW, which is the pre-2026-07-24 behaviour. */
.app:not(.app--guest) main .card:not(.pp-card):not(.card--tint) {
  /* --panel-tint-amt is the HQ intensity slider (100% = this 12%); theme.js sets it only when it differs. */
  background: color-mix(in srgb, var(--panel-tint-src, var(--brand-header-bg, var(--surface))) var(--panel-tint-amt, 12%), var(--surface));
}

/* offer card — square photo flush to the left edge, copy left-aligned beside it */
.offer-card { position: relative; display: flex; align-items: stretch; overflow: hidden; text-decoration: none; color: inherit;
  background: var(--tint-bg); border: 1px solid color-mix(in srgb, var(--c-primary) 18%, transparent); border-radius: var(--radius-card); }
.offer-card:hover { text-decoration: none; }
.offer-card__img { width: 112px; flex: none; object-fit: cover; align-self: stretch; }
.offer-card__body { padding: 14px 42px 14px 16px; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
/* Offer titles: a touch larger, and never wrap — a JS pass (fitOfferTitles) shrinks any title that
   would spill so it always sits on a single row. overflow:hidden is the safety net if JS is off. */
.offer-card .card__title { font-size: 20px; white-space: nowrap; overflow: hidden; }

/* ☆ save-to-My-Extras control, top-right of the card; gold ★ when saved, × on the My Extras page */
.offer-star { position: absolute; top: 6px; right: 6px; z-index: 2; width: 36px; height: 36px;
  display: grid; place-items: center; cursor: pointer; color: var(--muted);
  transition: color .15s ease, transform .12s ease; }
.offer-star i { font-size: 23px; }
.offer-star .estar { width: 23px; height: 23px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
.offer-star.is-saved .estar { fill: currentColor; stroke: none; }
.offer-star:hover, .offer-star.is-saved { color: var(--c-accent); }
.offer-star:active { transform: scale(.85); }

/* ----------------------------------------------------------------------------
 * Speciali & Eventi — colour-coded feed cards (restaurant customer app). Each card
 * takes on its Tipo's identity: a soft pastel wash across the WHOLE card (bg, border,
 * badge, title, date, chevron) via the inline --tipo custom property (from
 * SPECIALI_TIPI). Independent of the app style, so the colour always leads.
 * -------------------------------------------------------------------------- */
.spec-feed { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.spec-card { --tipo: var(--c-primary);
  position: relative; display: block; min-width: 0; overflow: hidden;
  text-decoration: none; color: inherit; cursor: pointer;
  background: color-mix(in srgb, var(--tipo) 12%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--tipo) 30%, var(--line));
  border-radius: var(--radius-card);
  transition: border-color .15s ease, transform .08s ease;
}
.spec-card:active { transform: scale(.994); }
.spec-card__expiry { display: flex; align-items: center; justify-content: center; gap: 5px; padding: 7px 12px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .01em; color: color-mix(in srgb, var(--tipo) 55%, var(--muted));
  border-top: 1px solid color-mix(in srgb, var(--tipo) 18%, var(--line)); }
.spec-card__expiry i { font-size: 13px; }
/* left rail — the photo, or a tinted tile bearing the tipo icon. ABSOLUTELY positioned so its own
   height can never grow the card: it always fills the (text-driven) card height, cropped. This keeps
   every card the same height regardless of the uploaded photo's proportions, and 96px-pinned so it
   can never widen past the edge either. */
.spec-card__img, .spec-card__img--ph { position: absolute; left: 0; top: 0; bottom: 0; width: 96px; height: 100%; }
.spec-card__img { object-fit: cover; }
.spec-card__img--ph { display: grid; place-items: center;
  background: color-mix(in srgb, var(--tipo) 20%, var(--surface));
  color: color-mix(in srgb, var(--tipo) 80%, var(--ink)); }
.spec-card__body { margin-left: 96px; min-width: 0; overflow: hidden; padding: 12px 14px;
  display: flex; flex-direction: column; gap: 5px; }
/* the colour-coded Tipo badge: [icon] Label · €cost */
.spec-badge { align-self: flex-start; display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%;
  padding: 3px 11px; border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--tipo) 22%, var(--surface));
  color: color-mix(in srgb, var(--tipo) 82%, var(--ink));
  font-family: var(--font-ui); font-size: 12px; font-weight: 700; letter-spacing: .01em; white-space: nowrap; }
.spec-badge i { font-size: 15px; flex: none; }
.spec-badge__label { overflow: hidden; text-overflow: ellipsis; }
/* the price rides its own solid chip inside the badge — a "price tag", louder than the type label */
.spec-badge:has(.spec-badge__cost) { padding-right: 4px; }
.spec-badge__cost { flex: none; margin-left: 3px; padding: 2px 10px; font-weight: 800; font-size: 14.5px;
  background: var(--tipo); color: #fff; border-radius: var(--radius-pill); }
.spec-card__title { font-family: var(--font-display); font-weight: 600; font-size: calc(19px * var(--font-display-scale, 1)); line-height: 1.15; margin: 1px 0 0;
  color: color-mix(in srgb, var(--tipo) 22%, var(--ink)); overflow-wrap: anywhere;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* date UNDER the title, in the SAME colour as the title (not a brighter accent) */
.spec-card__date { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
  color: color-mix(in srgb, var(--tipo) 22%, var(--ink)); }
.spec-card__date i { font-size: 14px; }
.spec-card__more { display: flex; align-items: center; gap: 8px; margin-top: 1px; }
.spec-card__desc { flex: 1 1 auto; min-width: 0; margin: 0; overflow-wrap: anywhere;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 14px; line-height: 1.3; color: var(--muted); }
.spec-card__chev { flex: none; display: inline-flex; color: color-mix(in srgb, var(--tipo) 60%, var(--muted)); }
/* the date line inside the full-view card modal (kept under the title there) */
.spec-card__when { display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 600;
  color: color-mix(in srgb, var(--tipo) 60%, var(--ink)); }
.spec-card__when i { font-size: 14px; }

/* TRACK U — the head (photo + text) keeps its own positioning context + min height so the offer
   band can sit full-width BELOW it (inside the card); the absolute photo fills only the head. */
.spec-card__head { position: relative; min-height: 104px; }
/* "Usa l'offerta" band — a centred strip inside the card that matches the card's own tipo colour,
   with a ticket-notch edge, a big boxless present icon, and a softly glowing CTA. */
.spec-offer { position: relative; padding: 14px 14px 15px; display: flex; flex-direction: column; align-items: center; gap: 7px; text-align: center;
  border-top: 1px dashed color-mix(in srgb, var(--tipo) 48%, var(--line));
  background:
    radial-gradient(circle at 0 0, transparent 7px, var(--_ofc) 7.5px) left top / 51% 100% no-repeat,
    radial-gradient(circle at 100% 0, transparent 7px, var(--_ofc) 7.5px) right top / 51% 100% no-repeat;
  --_ofc: color-mix(in srgb, var(--tipo) 16%, var(--surface)); }
.spec-offer__gift { color: var(--tipo); line-height: 1; }
.spec-offer__gift i { font-size: 42px; }
.spec-offer__k { font-size: 10.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: color-mix(in srgb, var(--tipo) 82%, var(--ink)); }
.spec-offer__v { font-size: 22px; font-weight: 800; letter-spacing: -.2px; color: var(--ink); overflow-wrap: anywhere; margin-top: -3px; }
.spec-offer__btn { position: relative; width: 100%; margin-top: 4px; border: 0; border-radius: var(--radius-btn); background: var(--tipo); color: #fff;
  font-family: var(--font-ui); font-weight: 800; font-size: 15px; padding: 12px; cursor: pointer; text-transform: uppercase;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; -webkit-tap-highlight-color: transparent; }
.spec-offer__btn i { font-size: 18px; }
.spec-offer__btn:active { transform: translateY(1px); }
/* soft pulsing glow — on a pseudo-element so its colour is the card's --tipo: a normal declaration
   resolves var() per-element (unlike var() inside @keyframes, which Chromium resolves against the
   base). The keyframe animates only opacity, which can't break. */
.spec-offer__btn::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: 0 0 16px 0 color-mix(in srgb, var(--tipo) 55%, transparent);   /* spread 0 → glow sits only OUTSIDE the button */
  animation: spec-offer-glow 2.2s ease-in-out infinite; }  /* unique name — an offer-glow keyframe already exists (guest app) */
@keyframes spec-offer-glow { 0%, 100% { opacity: .2; } 50% { opacity: .9; } }
@media (prefers-reduced-motion: reduce) { .spec-offer__btn::after { animation: none; opacity: .45; } }
.spec-offer__hint { display: inline-flex; align-items: center; justify-content: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: color-mix(in srgb, var(--tipo) 82%, var(--ink)); }
.spec-offer__hint i { font-size: 14px; }
.spec-offer--used { background: color-mix(in srgb, var(--success) 10%, var(--surface)); align-items: center; }
.spec-offer__usedtag { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 700; color: var(--success); }
/* TRACK U Phase 2 — the automatic-offer banner (Benvenuto / Torna a trovarci) at the top of Home.
   A warm, tappable "gift from the venue" strip, tinted with its own --ao house colour (set inline). */
.auto-offer { position: relative; width: 100%; display: flex; align-items: center; gap: 14px; text-align: left;
  margin-bottom: 18px; padding: 14px; border-radius: var(--radius-card); cursor: pointer; font-family: inherit;
  -webkit-tap-highlight-color: transparent;
  border: 1px solid color-mix(in srgb, var(--ao) 42%, var(--line));
  background: linear-gradient(135deg, color-mix(in srgb, var(--ao) 15%, var(--surface)), color-mix(in srgb, var(--ao) 6%, var(--surface)));
  box-shadow: 0 7px 20px -12px color-mix(in srgb, var(--ao) 60%, transparent); }
.auto-offer:active { transform: translateY(1px); }
.auto-offer__glyph { flex: none; width: 48px; height: 48px; border-radius: 50%; display: grid; place-items: center;
  color: #fff; background: var(--ao); box-shadow: 0 3px 10px -3px color-mix(in srgb, var(--ao) 70%, transparent); }
.auto-offer__glyph i { font-size: 28px; }
.auto-offer__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.auto-offer__title { font-weight: 800; font-size: 21px; letter-spacing: -.4px; color: var(--ink); line-height: 1.1;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.auto-offer__title i { color: var(--ao); }
.auto-offer__deal { font-weight: 800; font-size: 15px; letter-spacing: -.2px; color: var(--ao); }
.auto-offer__body { font-size: 13px; color: var(--muted); line-height: 1.3; }
/* Carta Punti (TRACK U Phase 3) — customer loyalty screen */
.carta-hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 3px; padding: 22px 16px; border-radius: var(--radius-card);
  background: linear-gradient(135deg, color-mix(in srgb, var(--c-primary) 16%, var(--surface)), color-mix(in srgb, var(--c-primary) 5%, var(--surface)));
  border: 1px solid color-mix(in srgb, var(--c-primary) 30%, var(--line)); }
.carta-hero__k { font-size: 15px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--c-primary); }
.carta-hero__n { font-size: 54px; font-weight: 800; line-height: 1; color: var(--ink); font-variant-numeric: tabular-nums; display: inline-flex; align-items: baseline; gap: 9px; }
.carta-hero__u { font-size: 21px; font-weight: 700; color: var(--muted); }
.carta-hero__msg { font-size: 13.5px; color: var(--muted); margin: 10px 0 0; display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; justify-content: center; }
.carta-hero__msg i { color: var(--c-primary); }
.carta-stamps { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; align-items: center; margin-top: 12px; max-width: 238px; }  /* fits exactly 7 stamps per row (7×26 + 6×8) */
.carta-stamp { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; border: 1.5px dashed color-mix(in srgb, var(--c-primary) 40%, var(--line)); color: color-mix(in srgb, var(--c-primary) 45%, var(--muted)); }
.carta-stamp.is-on { background: var(--c-primary); border-color: var(--c-primary); color: var(--on-primary, #fff); }
/* the premio stamp (the 10/20/30 milestone, last of the row): GOLD and a bit bigger than the others */
.carta-stamp--premio { width: 32px; height: 32px; border: 2px dashed color-mix(in srgb, #d9a23c 70%, transparent); color: #d9a23c; }
.carta-stamp--premio.is-on { background: #d9a23c; border-color: #d9a23c; color: #fff; box-shadow: 0 0 12px color-mix(in srgb, #d9a23c 45%, transparent); }
.carta-bar { width: 82%; max-width: 300px; height: 10px; border-radius: 999px; margin-top: 14px; overflow: hidden; background: color-mix(in srgb, var(--c-primary) 16%, var(--surface)); }
.carta-bar__fill { display: block; height: 100%; border-radius: 999px; background: var(--c-primary); }
/* the "‹ tipi predefiniti" escape under the custom-tipo text field (Speciali form) */
.tipo-back { margin-top: 6px; background: none; border: 0; padding: 0 2px; font: inherit; font-size: 11.5px; color: var(--muted); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }

/* Home push-notification invite: one calm row, one tap = the iOS permission dialog */
/* ⭐ A LIGHT GLOW (Tony, 2026-08-08). This card is the one thing on the Home asking the guest for
   something, and it sat flat among panels that only inform. The glow lifts it half a step — gold,
   the same hue as the bell already inside it, so it reads as one object rather than a highlight
   somebody added. ⚠️ Deliberately faint: it is a shop window, not an alarm, and it lives above a
   hotel's own photographs. The inner hairline is what does most of the work; the outer bloom is
   barely there and is what makes it look lit rather than outlined. */
.notif-invite { display: flex; align-items: center; gap: 12px; padding: 12px 14px; margin-bottom: 16px; background: var(--surface); border: 1px solid color-mix(in srgb, #d9a23c 55%, var(--line)); border-radius: var(--radius-card);
  animation: notif-breathe 3.6s ease-in-out infinite; }
/* ⚠️ IT BREATHES, IT DOES NOT BLINK (Tony: "flashing calmly… but not a circus"). 3.6s each way, and
   the glow never leaves entirely — it swells and settles, so peripheral vision catches it once and
   is not nagged by it. A hard on/off, or anything under ~2s, reads as an error state.
   ⚠️ Only the SHADOW animates. Animating the border or the background would repaint the card and
   fight the photographs underneath it. */
@keyframes notif-breathe {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, #d9a23c 16%, transparent), 0 2px 16px color-mix(in srgb, #d9a23c 20%, transparent); }
  50%      { box-shadow: 0 0 0 1px color-mix(in srgb, #d9a23c 34%, transparent), 0 3px 30px color-mix(in srgb, #d9a23c 46%, transparent); }
}
/* ⛔ A guest who has asked his phone for less motion gets a steady card, not a pulsing one — the
   glow is decoration, and decoration is exactly what this setting exists to switch off. */
@media (prefers-reduced-motion: reduce) {
  .notif-invite { animation: none; box-shadow: 0 0 0 1px color-mix(in srgb, #d9a23c 22%, transparent), 0 2px 20px color-mix(in srgb, #d9a23c 30%, transparent); }
}
.notif-invite__icon { flex: none; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; background: color-mix(in srgb, #d9a23c 16%, transparent); color: #d9a23c; }
/* the bell gives a gentle ring every few seconds (gold, swinging from its crown) */
.notif-invite__icon i { animation: bell-ring 4.5s ease-in-out infinite; transform-origin: 50% 10%; }
@keyframes bell-ring {
  0%, 12%, 100% { transform: rotate(0); }
  2% { transform: rotate(14deg); } 4% { transform: rotate(-12deg); }
  6% { transform: rotate(9deg); } 8% { transform: rotate(-6deg); } 10% { transform: rotate(3deg); }
}
@media (prefers-reduced-motion: reduce) { .notif-invite__icon i { animation: none; } }
.notif-invite__t span i { color: var(--muted); vertical-align: -1px; }
.notif-invite__t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
/* ⭐ The review ask gets a ✕ — and it is the ONLY one of the three that does. The other two are setup
   steps whose exit is doing the thing; this one is a REQUEST, and a request you cannot decline is a
   nag. (The ✕ was taken OFF the notifications banner on 2026-08-08 for the opposite reason: guests
   were dismissing it by accident and losing notifications for the whole stay. Different control,
   different job.) */
.notif-invite__x { flex: none; width: 26px; height: 26px; margin-left: -4px; padding: 0;
  display: grid; place-items: center; border: 0; border-radius: 50%; background: transparent;
  color: var(--muted); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.notif-invite__x:active { background: color-mix(in srgb, var(--muted) 18%, transparent); }
.notif-invite--rev .notif-invite__icon i { animation: none; }   /* the bell rings; a star should not */
.notif-invite__t strong { font-size: 14.5px; }
.notif-invite__t span { font-size: 12px; color: var(--muted); line-height: 1.35; }
/* ⛔ `.notif-invite__x` REMOVED WITH ITS BUTTON (2026-08-08). The ✕ wrote a permanent "never ask
   again" and guests were tapping it by accident, losing the notifications for the rest of their stay
   with no way back but the Impostazioni screen. ⚠️ The row is `display:flex` with the text on
   `flex: 1`, so losing the last child changes nothing about the other three — the button simply had
   its own `flex: none` and no longer exists. */

/* print-materials picker (Condividi → Materiali da stampare): live mini-previews of the real pieces.
   ⭐ TWO TABS (Tony, 2026-07-31): a print shop and a home printer want DIFFERENT files — exact size
   with bleed for the first, several to an A4 sheet with cut lines and no bleed for the second. */
.pm-head { text-align: center; }
.pm-sep { height: 1px; background: var(--line); margin: 14px 0; border: 0; }
.pm-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; background: var(--surface); border: 1px solid var(--line); border-radius: 999px; padding: 4px; margin-bottom: 14px; }
.pm-tab { border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 650; color: var(--muted); padding: 9px 6px; border-radius: 999px; cursor: pointer; line-height: 1.2; }
.pm-tab.is-on { background: var(--accent, #c15c2c); color: #fff; }
.pm-note { font-size: 11.5px; color: var(--muted); text-align: center; line-height: 1.4; margin: 0 0 12px; }
.pm-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.pm-item { min-width: 0; display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 12px 8px 10px; background: var(--surface); border: 1px solid var(--line); border-radius: 12px; cursor: pointer; font: inherit; color: inherit; }
.pm-item:active { transform: scale(.97); }
.pm-thumb { position: relative; display: block; max-width: 100%; overflow: hidden; margin: 0 auto; }
.pm-thumb > .pm-scale { position: absolute; left: 0; top: 0; transform-origin: top left; }
.pm-thumb > svg { height: 86px; width: auto; max-width: 118px; display: block; border-radius: 3px; box-shadow: 0 4px 14px rgba(0, 0, 0, .28); }
.pm-label { font-size: 12.5px; font-weight: 650; text-align: center; line-height: 1.25; }

/* the card's own code (like the number printed on a physical loyalty card) + the recovery way back.
   Folded by default behind the quiet "Codice recupero carta" toggle. */
.carta-code-toggle { margin-top: 14px; background: none; border: 0; padding: 4px 6px; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
.carta-code-toggle i { transition: transform .18s ease; }
.carta-code-toggle[aria-expanded="true"] i { transform: rotate(180deg); }
.carta-code { margin-top: 10px; padding-top: 14px; border-top: 1px dashed var(--line); display: flex; flex-direction: column; align-items: center; gap: 2px; align-self: stretch; }
.carta-code[hidden] { display: none; }   /* the author display:flex would otherwise defeat the hidden attribute */
.carta-code__label { font-size: 10px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.carta-code__v { font-size: 19px; font-weight: 750; letter-spacing: .12em; color: var(--ink); font-variant-numeric: tabular-nums; }
.carta-code__note { font-size: 11px; line-height: 1.5; color: var(--muted); max-width: 36ch; text-align: center; margin: 5px 0 0; }
.carta-recover-link { margin-top: 10px; background: none; border: 0; padding: 4px 6px; font: inherit; font-size: 12.5px; color: var(--muted); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.carta-recover__help { font-size: 13.5px; color: var(--muted); text-align: center; margin: 0 0 12px; }
.carta-recover__input { width: 100%; padding: 13px; border: 1px solid var(--line); border-radius: 12px; font: inherit; font-size: 19px; font-weight: 650; letter-spacing: .16em; text-align: center; text-transform: uppercase; color: var(--ink); background: var(--surface); font-variant-numeric: tabular-nums; }
.carta-recover__input:focus { outline: none; border-color: #c98a2b; box-shadow: 0 0 0 3px color-mix(in srgb, #c98a2b 22%, transparent); }
.carta-recover__err { color: var(--danger, #c0392b); font-size: 12.5px; text-align: center; min-height: 1.1em; margin: 8px 0 10px; }
.carta-recover__foot { font-size: 11px; color: var(--muted); text-align: center; margin: 12px 0 0; }
/* ⛔⛔ min-width: 0 IS LOAD-BEARING (Tony, 2026-07-30, on Hotel Cesare Augusto: "the Carta Fedelta
   exceeds the allowed width and fucks it up... this does not happen in Pane & Vino, WHY HERE?").
   MEASURED: the row was 672px inside a 488px .stack. .stack is a GRID, and a grid item defaults to
   `min-width: auto` — it cannot shrink below its content's min-content width. The reward NAME is
   white-space: nowrap, so its min-content width is the whole sentence, and the row pushed the grid
   open and out of the screen.
   ⚠️ IT IS NOT A HOTEL BUG. Pane&Vino's rewards are called "Caffè" and "Dolce", short enough that
   the min-content width never exceeded the column. Same latent fault, never triggered. Cesare
   Augusto's "Sconto 5% sulla tua prossima prenotazione" is what finally reached it.
   ⭐ THE RULE, worth carrying: any flex/grid ITEM that contains nowrap text needs min-width: 0, or
   its parent silently grows to fit. The ellipsis on the name inside was already correct and could
   never fire, because the row never had to shrink. */
.carta-reward { display: flex; align-items: center; gap: 13px; padding: 12px 14px; border-radius: var(--radius-card); background: var(--surface); border: 1px solid var(--line); min-width: 0; }
.carta-reward--ok { border-color: color-mix(in srgb, #d99326 55%, var(--line)); box-shadow: 0 0 0 1px color-mix(in srgb, #d99326 35%, transparent), 0 4px 20px color-mix(in srgb, #d99326 22%, transparent); }
.carta-reward--claimed { opacity: .55; }
.carta-reward__badge { flex: none; width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; font-weight: 800; font-size: 16px; font-variant-numeric: tabular-nums; color: #fff; background: color-mix(in srgb, var(--muted) 50%, var(--surface)); }
.carta-reward--ok .carta-reward__badge { background: #d99326; }
.carta-reward__t { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.carta-reward__name { font-weight: 700; font-size: 16px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.carta-reward__sub { font-size: 12.5px; color: var(--muted); }
.carta-reward--ok .carta-reward__sub { color: #d99326; font-weight: 700; }
.carta-reward__lock, .carta-reward__done { flex: none; display: grid; place-items: center; color: var(--muted); }
.carta-reward__done { color: var(--success); }
/* icon-only tab (Impostazioni) — a slim, vertically-centred gear so the 6th tab fits */
.tab--icon { flex: 0 0 46px; align-content: center; }
.tab--icon i { font-size: 22px; }
/* a capped switch (e.g. "Mostra in Home" once 3 are chosen) reads as unavailable */
.switch.is-disabled { opacity: .4; }
/* Speciali page header: the title (left) + a Tipo filter dropdown (right, same row) */
.spec-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.spec-filter { display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 6px 8px 6px 11px; border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--muted); }
.spec-filter i { font-size: 15px; flex: none; }
.spec-filter__sel { border: 0; background: none; color: var(--ink); font-family: var(--font-ui);
  font-size: 13px; font-weight: 600; cursor: pointer; padding: 0 2px; max-width: 42vw;
  -webkit-appearance: none; appearance: none; }
.spec-filter__sel:focus { outline: none; }
/* "Vedi tutti" link on the Home teaser header */
.link-more { display: inline-flex; align-items: center; gap: 1px; font-size: 13px; font-weight: 600;
  color: var(--c-primary); text-decoration: none; white-space: nowrap; }
.link-more i { font-size: 15px; }
/* badge reused inside the full-view card modal */
.spec-badge--modal { align-self: flex-start; margin-bottom: 6px; }
.offer-star--remove { color: color-mix(in srgb, var(--muted) 85%, transparent); }
.offer-star--remove:hover { color: var(--danger-solid); }

/* soft pulsing glow for the daily-offer card — themed warm gold/yellow accent */
.offer-glow {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 45%, transparent), 0 0 14px 1px color-mix(in srgb, var(--c-accent) 38%, transparent);
  animation: offer-glow 2.6s ease-in-out infinite;
}
@keyframes offer-glow {
  0%, 100% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 38%, transparent), 0 0 8px 0 color-mix(in srgb, var(--c-accent) 30%, transparent); }
  50% { box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 60%, transparent), 0 0 22px 3px color-mix(in srgb, var(--c-accent) 52%, transparent); }
}

/* ---- Hotel photo card (landing centrepiece). Set --card-accent inline to the
 *      hotel's primary so the border + glow belong to that hotel. ---- */
.hotel-card {
  --card-accent: var(--c-primary);
  position: relative; display: block; color: #fff; text-decoration: none;
  border-radius: var(--radius-lg); overflow: hidden; aspect-ratio: 1 / 1; isolation: isolate;
  width: 72%; max-width: 340px; margin-inline: auto;
  border: 2px solid rgba(255, 255, 255, .35); /* same on every card, softened; green in light mode (below) */
  box-shadow:
    0 22px 46px rgba(0, 0, 0, .5),
    0 8px 18px rgba(0, 0, 0, .34);
  transition: box-shadow .25s ease, transform .25s ease;
}
html[data-theme="light"] .hotel-card {
  border-color: rgba(47, 107, 79, .45); /* the "Hotels" green, softened */
  box-shadow: 0 20px 40px rgba(20, 33, 28, .24), 0 7px 16px rgba(20, 33, 28, .16);
}
.hotel-card > img { /* the background photo only — not the logo inside __body */
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: -2; transition: transform .5s ease;
}
.hotel-card::after { /* semi-transparent dark panel over the bottom half (page-bg colour @ ~84%) */
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; top: 50%; z-index: -1;
  background: rgba(18, 22, 26, .84);
}
.hotel-card:hover { text-decoration: none; transform: translateY(-2px); box-shadow: 0 30px 60px rgba(0, 0, 0, .55), 0 12px 24px rgba(0, 0, 0, .38); }
.hotel-card:hover > img { transform: scale(1.045); }
.hotel-card__body { position: absolute; left: 0; right: 0; bottom: 0; top: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 10px 16px; }
.hotel-card__name { font-family: var(--font-display); font-weight: 600; font-size: calc(21px * var(--font-display-scale, 1)); line-height: 1.1; }
.hotel-card__meta { font-size: 13px; opacity: .92; margin-top: 3px; }
.hotel-card__logo { display: block; width: var(--logo-w, 56%); height: auto; max-width: 88%; max-height: 86%; object-fit: contain; }
.hotel-card__badge {
  position: absolute; top: 12px; left: 12px; z-index: 1;
  background: rgba(255, 255, 255, .94); color: #15211c; /* fixed dark: pill is always white */
  font-size: 12px; font-weight: 600; padding: 5px 10px; border-radius: var(--radius-pill);
}

/* ----------------------------------------------------------------------------
 * 8. List rows  (services, dashboard tiles)
 * -------------------------------------------------------------------------- */
.row {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
  width: 100%; text-align: left; color: inherit; cursor: pointer;
  transition: border-color .18s ease, background-color .18s ease;
}
a.row, button.row { font: inherit; }
.row:hover { border-color: var(--line-strong); text-decoration: none; }
/* a whole item that opens its editor on tap — press feedback confirms the gesture (the 3-dots still works) */
.row--tap { -webkit-tap-highlight-color: transparent; }
.row--tap:active { background: var(--surface-2); border-color: var(--line-strong); }
/* ⭐ An Offerte Automatiche card that holds a saved offer is tappable end to end (Tony, 2026-07-30).
   Same feedback as .row--tap and deliberately no extra border — the .fpanel already draws one, and a
   second would make the card look like a button instead of a card you can touch. */
.fpanel--tap { -webkit-tap-highlight-color: transparent; cursor: pointer; }
.fpanel--tap:active { background: var(--surface-2); }
.row__icon {
  flex: none; width: 42px; height: 42px; display: grid; place-items: center;
  border-radius: var(--radius-sm); background: var(--tint-bg); color: var(--c-primary);
}
.row__icon i { font-size: 22px; }
/* on the public hotel "website" pages, only the icon glyph takes the secondary
   (accent) colour; the square keeps its original (primary) tint */
.app--hotel .row__icon { color: var(--c-accent); }
/* same in the guest app — icons take the hotel's accent (the colourful "Your
   stay" icons set their own colour inline, so they override this) */
.app--guest .row__icon { color: var(--c-accent); }
.row__text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* ⭐ THE ROW TITLE WEARS THE FACE TOO (Tony, 2026-08-07, reading his own Manager): the section
   headings above it were in the venue's face and the rows underneath were not, so a settings screen
   read as two apps stitched together — "Your Property" in Caveat over "Password" in Inter.
   ⚠️ ONE class covers everything he pointed at: Password, Documentation & Help, and every sub-area
   row under I Servizi (General, Entertainment, Breakfast…) are all `.row__title`.
   ⚠️ font-size stays INHERITED — `1em` here IS the parent's size, which is exactly what this row
   renders at today — so the scale applies without inventing a size the row never had. */
.row__title { font-weight: 600; font-family: var(--font-display); font-size: calc(1em * var(--font-display-scale, 1)); }
.row__sub { color: var(--muted); font-size: 14px; line-height: 1.4; }
/* ⭐ «I Nostri Servizi» as a list of its areas (2026-08-10). The peek line says what is inside a row
   without opening it. ⛔ IT IS CLIPPED TO ONE LINE ON PURPOSE — Generali holds fourteen items on
   Cesare Augusto, and a row that grows with its contents makes the longest area the tallest thing on
   the page, which is exactly the upside-down hierarchy this list was built to fix. */
/* ⭐ TWO LINES, AND SMALLER THAN THE ROWS IT DESCRIBES (Tony, 2026-08-10). It was 14px — the same
   `.row__sub` the Manager uses for «7 voci», to the pixel — but beside a 21.5px title it wanted to
   be quieter, and one line was cutting a hotel's contents off after three words.
   ⛔ CLAMPED, not merely wrapped: two lines is a cap, not a suggestion. An area with fourteen items
   would otherwise grow its own card taller than every other one on the page. */
.shelf-list__peek { font-size: 13px; line-height: 1.35; max-width: 100%; white-space: normal;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
/* ⛔ AND THE ROW MUST BE ALLOWED TO SHRINK, or the peek does not clip — it pushes. A `nowrap` line
   gives its row a min-content width of the whole sentence, and a flex child does not go below that
   on its own: measured on the demo, the row came out 497px wide inside a 343px column and the whole
   card ran off the right of the screen. `.row` is shared, so this is scoped to the shelf list. */
/* ⭐ THE CARD WEARS ITS AREA'S COLOUR (Tony, 2026-08-10): "can we get the card background a 20% shade
   of the color the icon is using" — then, seeing seven of them at once, "bring it down to 10%".
   ⚠️⚠️ THE CARD AND THE SQUARE ARE ONE SETTING IN TWO PLACES, and the thing to preserve is the GAP
   between them, not either number. The square must stay clearly above the card it sits on: at 18% on
   a 20% card it vanished, which is what forced it to 45% when the card was 20%. The card came down
   25 points, so the square comes down 25 points with it. ⛔ Change one of these and you have changed
   the other, whether you meant to or not. */
.shelf-list .row { min-width: 0; max-width: 100%; overflow: hidden; gap: 14px; padding: 16px;
  background: color-mix(in srgb, var(--c) 5%, var(--surface));
  border-color: color-mix(in srgb, var(--c) 24%, transparent); }
.shelf-list .row__text { min-width: 0; gap: 4px; }
/* ⭐ THE SAME WEIGHT AS THE ITEMS INSIDE (Tony, 2026-08-10): "make the title and icon as big as those
   in screenshot 2 … the cards will become bigger obviously but that will make it look right."
   ⚠️ EVERY NUMBER HERE IS COPIED FROM `.hfeat` AND `.hfeat__ico`, the rows a guest meets one tap
   deeper — 56px tile, 12px radius, 21.5px display title. An area must not read as a smaller thing
   than the wi-fi inside it, and the two screens are read within seconds of each other, so a near-miss
   is worse than an obvious difference. ⛔ If those change, change these with them. */
.shelf-list .row__icon { width: 56px; height: 56px; border-radius: 12px; }
.shelf-list .row__title { font-size: calc(21.5px * var(--font-display-scale, 1));
  font-weight: var(--font-display-w, 600); line-height: 1.25; }
/* ⭐ THE AREA'S OWN COLOUR ON ITS TILE, the same treatment its items already wear inside and the same
   one the owner sees in his Manager — a column of identical grey squares says none of these is
   different from any other, which on this page is the opposite of true.
   ⛔ SPECIFICITY IS DELIBERATE. `.app--hotel .row__icon` further up paints every row icon with the
   accent, and it is exactly as specific as `.shelf-list .row__icon` — on a hotel, of all places, the
   later rule would have won and this would have looked like it simply did not work. */
.shelf-list .row__icon, .app--hotel .shelf-list .row__icon {
  color: var(--c); background: color-mix(in srgb, var(--c) 35%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c) 48%, transparent);
}
.shelf-back { margin: 0 0 14px; }
.row__chev { color: var(--muted); }
.row__chev i { font-size: 20px; }
/* "My Extras" feature row — the prominent first item in My Stay (accent-framed) */
.row--extras { border-color: color-mix(in srgb, var(--c-accent) 55%, var(--line));
  background: color-mix(in srgb, var(--c-accent) 9%, var(--surface)); }
.row--extras:hover { border-color: var(--c-accent); }
.row__icon--star .estar { width: 23px; height: 23px; fill: currentColor; }
/* × remove button on a My Extras stay row */
.row__x { flex: none; width: 36px; height: 36px; display: grid; place-items: center; border: 0; background: transparent;
  color: var(--muted); cursor: pointer; border-radius: 50%; transition: color .15s ease, background-color .15s ease; }
.row__x:hover { color: var(--danger-solid); background: color-mix(in srgb, var(--danger-solid) 12%, transparent); }
.row__x i { font-size: 20px; }

/* rooms accordion — tap a room to reveal its photo + details (single-open) */
.room { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); overflow: hidden; transition: border-color .18s ease; }
.room:hover, .room.is-open { border-color: var(--line-strong); }
.room__head { width: 100%; display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: none; border: 0; cursor: pointer; text-align: left; color: inherit; font: inherit; }
.room__chev { flex: none; display: grid; place-items: center; color: var(--c-accent); transition: transform .25s ease; }
.room__chev i { font-size: 20px; }
.room.is-open .room__chev { transform: rotate(180deg); }
.room__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s ease; }
.room.is-open .room__panel { grid-template-rows: 1fr; }
.room__panel-inner { overflow: hidden; min-height: 0; }
.room__img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; border-top: 1px solid var(--line); }
.room__desc { margin: 0; padding: 13px 16px 15px; color: var(--muted); font-size: 14px; line-height: 1.5; }

/* hero photo on a hotel page wears the same border as the landing cards (continuity) */
.hotel-hero { border: 2px solid rgba(255, 255, 255, .35); }
html[data-theme="light"] .hotel-hero { border-color: rgba(47, 107, 79, .45); }

/* ----------------------------------------------------------------------------
 * 9. Chips & badges
 * -------------------------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--radius-pill);
  background: var(--tint-bg); color: var(--c-primary);
  font-size: 13px; font-weight: 600;
}
.chip i { font-size: 15px; }
/* solid filled chip — white text on the active colour; high contrast in both modes */
.chip--solid { background: var(--c-primary); color: var(--on-primary, #fff); }
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 600;
  background: var(--surface-2); color: var(--ink-soft); border: 1px solid var(--line);
}
.badge--accent { background: var(--c-accent); color: var(--on-accent); border-color: transparent; }
.price { font-weight: 600; }
.price small { color: var(--muted); font-weight: 400; }

/* ----------------------------------------------------------------------------
 * 10. Forms
 * -------------------------------------------------------------------------- */
.field { display: grid; gap: 6px; }
.field > label { font-weight: 600; font-size: 14px; }
.input, .select, .textarea, select.input {
  width: 100%; min-height: 48px; padding: 12px 14px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.textarea { min-height: 96px; resize: vertical; }
select.input { height: 51px; } /* match the rendered text-input height (e.g. Offerte: Tipo = Titolo) */
.input::placeholder, .textarea::placeholder { color: var(--muted); opacity: .6; }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--c-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-primary) 22%, transparent);
}
/* ⭐⭐ DATE AND TIME BOXES: WE SIZE THEM, iOS DOES NOT (Tony, 2026-08-10, three devices on a bench).
   iOS draws these at a width IT computes from its own internal parts and ignores the one we give
   them. Inside the 420 stay sheet, where each box has 185 to live in, his OLD iPad drew 113 and his
   NEW one ~200 — too small and too big, one cause, opposite symptoms, and the big one spilled out
   past the sheet's own padding.
   ⭐ MEASURED, NOT REASONED (tools/datefield-harness.html, eight treatments, numbers printed on the
   device doing the drawing). `min-width:0` changed NOTHING. A wider sheet changed NOTHING. Taking
   the flex off the column — Safari is documented as sizing these properly only while they are not
   blockified — changed NOTHING either, on any device. Dropping the native appearance is the ONLY
   thing that made the box accept our width: 113 → 184 on the iPad, 114 → 176 on the iPhone, and
   already-correct desktop Safari and Chrome stayed correct, calendar button and all.
   ⚠️ THE VALUE MUST BE PINNED LEFT TOO, or WebKit centres it and the box reads unlike every other
   field in the app. That is the whole difference between treatment C and treatment D on the bench.
   ⛔ NEVER size a row around what one of these "naturally" wants. Apple documents that width
   nowhere — Safari 26's notes do not mention date fields at all — and it changed under us with an
   iOS release, silently, on a screen a paying hotel's guests use. */
.input[type="date"], .input[type="time"], .input[type="datetime-local"],
.input[type="month"], .input[type="week"] {
  -webkit-appearance: none; appearance: none; min-width: 0; max-width: 100%;
}
.input[type="date"]::-webkit-date-and-time-value,
.input[type="time"]::-webkit-date-and-time-value,
.input[type="datetime-local"]::-webkit-date-and-time-value { text-align: left; margin: 0; }
.input[type="date"]::-webkit-datetime-edit,
.input[type="time"]::-webkit-datetime-edit,
.input[type="datetime-local"]::-webkit-datetime-edit { padding: 0; }

/* ⭐ AND AN EMPTY ONE IS BLANK ON iOS — no «gg/mm/aaaa», nothing, just a grey rectangle nobody
   reads as a field. Apple's own developer forum carries the complaint and no clean answer. So the
   field wears OUR hint, and the platform's own is hidden while the box is empty and untouched, so
   that exactly one hint shows everywhere rather than two overlapping on the desktop.
   ⚠️ `:focus` puts the native one back the moment somebody starts typing on a desktop — hiding it
   there would mean typing a date blind. */
.datefield { position: relative; display: flex; }
.datefield .input { width: 100%; }
/* ⚠️ EVERY TEXT PROPERTY IS STATED, and that is not belt-and-braces. This span sits inside rows that
   dress their own captions — the stay sheet's `.stay-ask__row span` is uppercase, 700, letterspaced —
   and a descendant rule like that reaches straight into here: the hint shipped to the demo reading
   «CHOOSE THE DATE» in small caps, and only the screenshot showed it. A placeholder must look like a
   placeholder wherever this class is reused. */
.datefield__ph { position: absolute; left: 15px; top: 50%; transform: translateY(-50%);
  font: inherit; font-size: 16px; font-weight: 400; letter-spacing: normal; text-transform: none;
  color: var(--muted); opacity: .75; pointer-events: none; white-space: nowrap;
  max-width: calc(100% - 30px); overflow: hidden; text-overflow: ellipsis; }
.datefield.has-value .datefield__ph, .datefield .input:focus ~ .datefield__ph { display: none; }
.datefield:not(.has-value) .input:not(:focus)::-webkit-datetime-edit { opacity: 0; }
.field__hint { font-size: 13px; color: var(--muted); }
.field--error .input, .field--error .select, .field--error .textarea { border-color: var(--danger); }
/* The read-only account line in "Cambia password" — it exists so iOS/Safari can file the new
   password against the right login. Present and readable, but plainly not something you fill in. */
.input--acct { opacity: .6; pointer-events: none; }
.field__error { font-size: 13px; color: var(--danger); font-weight: 600; }

.check { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }
.check input { width: 20px; height: 20px; margin-top: 1px; accent-color: var(--c-primary); flex: none; }

/* ----------------------------------------------------------------------------
 * 11. App header (sticky) + horizon seam
 * -------------------------------------------------------------------------- */
.appbar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: 8px;
  padding: 10px 8px;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  backdrop-filter: saturate(180%) blur(12px);
}
.appbar__title { font-family: var(--font-display); font-weight: 600; font-size: calc(17px * var(--font-display-scale, 1)); flex: 1; text-align: center; }
.appbar__title--left { text-align: left; padding-left: 4px; }

/* ----------------------------------------------------------------------------
 * 12. Bottom tab bar (guest zone)
 * -------------------------------------------------------------------------- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  display: flex; gap: 2px;
  padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}
.tab {
  position: relative;
  flex: 1; display: grid; justify-items: center; gap: 3px;
  padding: 8px 4px; border-radius: var(--radius-sm);
  color: var(--muted); text-decoration: none; font-size: 11px; font-weight: 600;
}
.tab:hover { text-decoration: none; color: var(--ink); }
.tab i { font-size: 23px; }
.tab.is-active { color: var(--c-primary); }
/* "novità" dot on the Speciali tab — new specials the customer hasn't opened yet.
   ⭐⭐ FIXED RED, NEVER THE CLIENT'S COLOUR (Tony, 2026-08-06): *"it takes up the Signature color,
   which is not always right is it? what about if we make it a fixed red, so it works the same on
   all?"* He is right twice over. It is a NOTIFICATION MARK, not decoration — it means "something new
   is in here", which is a universal convention with a universal colour, and it is the same mark iOS
   puts on the app icon in red a centimetre away. Two different colours for one meaning, on one
   screen.
   ⛔ And on a brand whose accent is pale, grey or close to the bar's own background it could be
   nearly invisible — the one element in the app that must never be missed. A per-client colour makes
   its visibility a lottery decided by a logo.
   ⚠️ FIXED, deliberately outside the theme: this is the same reasoning as the gold Passaparola badge
   and the WhatsApp green — a few marks in this app mean something in the world, not in the brand. */
.tab-badge { position: absolute; top: 4px; left: 50%; margin-left: 5px; width: 11px; height: 11px;
  border-radius: 999px; background: #e5342a; box-shadow: 0 0 0 2px color-mix(in srgb, var(--surface) 92%, transparent); }

/* ----------------------------------------------------------------------------
 * 13. Empty state
 * -------------------------------------------------------------------------- */
.empty { text-align: center; padding: 40px 20px; color: var(--muted); }
.empty i { font-size: 32px; color: var(--line-strong); }
.empty__title { font-family: var(--font-display); font-weight: 600; font-size: calc(17px * var(--font-display-scale, 1)); color: var(--ink); margin: 10px 0 4px; }

/* ----------------------------------------------------------------------------
 * App footer — shared by the customer app + the Manager. Sits as the last child
 * of the page's content container (.container / .container--wide, both padded by
 * --space); the negative inline margin bleeds the hairline out to those edges
 * (= edge-to-edge on the phone). Two small centred lines: legal links + credits.
 * -------------------------------------------------------------------------- */
.app-footer {
  margin: 30px calc(var(--space) * -1) 0;
  padding: 15px var(--space) 12px;
  border-top: 1px solid var(--line);
  text-align: center; color: var(--muted);
  font-family: var(--font-ui); font-size: 11.5px; line-height: 1.5;
}
.app-footer__row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 4px 7px; margin: 0; }
.app-footer__credits { margin-top: 5px; }
/* the Telaio lockup under the credits — quiet brand sign-off, links to the product page */
.app-footer__brand { display: block; width: 92px; margin: 16px auto 0; color: var(--ink); opacity: .55; transition: opacity .15s ease; }
.app-footer__brand:hover, .app-footer__brand:active { opacity: .9; }
.app-footer__brand svg { display: block; width: 100%; height: auto; }
.app-footer__sep { color: var(--line-strong); }
.app-footer__link { color: var(--muted); text-decoration: none; background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
.app-footer__link:hover { color: var(--ink); text-decoration: underline; }
.app-footer__ver { color: var(--muted); opacity: .75; font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------------------------
 * 14. Toast (created by core/ui.js)
 * -------------------------------------------------------------------------- */
#fh-toasts {
  position: fixed; left: 0; right: 0; bottom: calc(16px + env(safe-area-inset-bottom));
  display: grid; justify-items: center; gap: 8px; pointer-events: none;
  /* ⚠️ ABOVE EVERY OVERLAY. This was 60 — under .cardmodal (85), .cropper (100), .ferie-modal (150)
     and .hq-modal (1000) — so any message raised while a dialog was open rendered BEHIND it and the
     owner saw nothing (found on Tony's iPhone: "le due password non coincidono" was painting under
     the Cambia-password modal). A toast is the answer to something you just did; it must outrank
     whatever you did it in. Only the rotate guard (9999) sits higher — by design, nothing should
     draw over "turn your phone upright". */
  z-index: 9500;
}
.toast {
  background: var(--c-primary-dark); color: #fff;
  padding: 11px 18px; border-radius: var(--radius-pill); font-size: 14px; font-weight: 600;
  box-shadow: var(--shadow-md); max-width: calc(var(--maxw) - 32px);
  opacity: 0; transform: translateY(8px); transition: opacity .2s ease, transform .2s ease;
}
.toast.is-visible { opacity: 1; transform: none;
  /* ⚠️⚠️ THE HOST IS `pointer-events: none` ON PURPOSE — a toast floats over the whole app at z-index
     9500 and must never swallow a tap meant for a button underneath. So tap-to-dismiss has to switch
     it back on for the BUBBLE ITSELF, and only while it is actually visible. Without this line the
     click handler in ui.js would be wired up and silently never fire — a dismiss that looks
     implemented and does nothing. */
  pointer-events: auto; cursor: pointer; }
.toast--success { background: var(--success); }
.toast--error { background: var(--danger); }

/* ----------------------------------------------------------------------------
 * 15. App structure components (Tasks 7–17)
 * -------------------------------------------------------------------------- */
/* website section menu (overlay sheet) */
.sheet { position: fixed; inset: 0; z-index: 40; display: flex; justify-content: flex-end; }
.sheet[hidden] { display: none; }
.sheet__backdrop { position: absolute; inset: 0; background: rgba(8, 16, 20, .45); }
.sheet__panel { position: relative; width: min(320px, 84vw); height: 100%;
  padding: calc(env(safe-area-inset-top) + 14px) 16px calc(env(safe-area-inset-bottom) + 16px) 16px;
  background: var(--surface); box-shadow: var(--shadow-md); display: flex; flex-direction: column; gap: 8px; overflow-y: auto; }
.sheet__head { display: flex; align-items: center; justify-content: space-between; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
.sheet__nav { display: flex; flex-direction: column; }
.sheet__link { display: block; text-align: left; background: none; border: 0; border-bottom: 1px solid var(--line);
  padding: 14px 6px; font: inherit; color: var(--ink); cursor: pointer; text-decoration: none; }
.sheet__link:hover { color: var(--c-primary); text-decoration: none; }

/* "GUEST PRIVATE AREA" — full-width footer bar pinned to the bottom; the page
   content scrolls underneath it. Accent colour, centred text. */
.appbanner-wrap { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; }
.appbanner { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  width: 100%; padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
  color: var(--on-accent); text-align: center; text-decoration: none;
  background: linear-gradient(180deg, color-mix(in srgb, #fff 16%, var(--c-accent)) 0%, var(--c-accent) 72%, color-mix(in srgb, var(--c-accent) 86%, #000) 100%);
  border-top: 1px solid color-mix(in srgb, #000 24%, var(--c-accent)); box-shadow: 0 -6px 18px rgba(8, 16, 20, .20); }
.appbanner:hover { text-decoration: none; filter: brightness(1.03); }
.appbanner:active { filter: brightness(.985); }
.appbanner__text { display: flex; flex-direction: column; align-items: center; line-height: 1.25; min-width: 0; }
.appbanner__text strong { font-size: 19.5px; letter-spacing: .06em; font-weight: 800; }
.appbanner__text small { opacity: .85; font-size: 13px; }

/* hotel-page badge chip — brighter, higher-contrast text so it reads clearly */
.app--hotel .chip { color: var(--ink); }

/* tab bar: a divider before the Settings tab */
.tab-sep { flex: none; align-self: center; width: 1px; height: 26px; margin: 0 3px; background: var(--line-strong); }

/* segmented control (Settings → Appearance) */
.seg-group { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 4px; }
.seg-group--2 { grid-template-columns: repeat(2, 1fr); } /* exact 50/50 split (e.g. Orari: Generale | Singole giornate) */
.seg-group--4 { grid-template-columns: repeat(4, 1fr); } /* Archivio: Piatti · Drink · Eventi · Menu all on one row */
.seg-group--4 .seg { padding: 10px 2px; font-size: 12.5px; }
.seg { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 10px 6px;
  border: 0; background: transparent; color: var(--muted); font: inherit; font-weight: 600; font-size: 13px;
  border-radius: var(--radius-sm); cursor: pointer; }
.seg.is-active { background: var(--surface); color: var(--c-primary); box-shadow: var(--shadow-sm); }
.seg i { font-size: 16px; }

/* toggle switch (Settings) */
.switch { flex: none; width: 46px; height: 28px; border-radius: var(--radius-pill);
  background: var(--line-strong); position: relative; cursor: pointer; transition: background-color .18s ease; }
/* ⭐ The ON state wears the client's colour — unless it cannot be told apart from the OFF grey, in
   which case theme.js hands over the project's gold instead. Hotel Cesare Augusto's brand IS a grey,
   so his switches looked identical on and off and the control said nothing at all, in his Manager and
   in his guests' app. The fallback is decided in one place (switchOnColor) and arrives as a token. */
.switch.is-on { background: var(--switch-on, var(--c-primary)); }
.switch__dot { position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3); transition: transform .18s ease; }
.switch.is-on .switch__dot { transform: translateX(18px); }

/* A CHECKMARK, not a switch — used only for "Mostra in Home" under the Menu Fisso formulas.
   Those formulas each carry a real switch, and a fourth identical switch directly beneath them read
   as a third formula (Tony, 2026-07-21). A different control says "this is a different KIND of
   thing" without a word of explanation. */
.chkbox { flex: none; width: 28px; height: 28px; border-radius: 9px; padding: 0; cursor: pointer;
  display: grid; place-items: center; background: transparent; color: transparent;
  border: 1.5px solid var(--line-strong); transition: background-color .15s ease, border-color .15s ease; }
.chkbox.is-on { background: var(--c-primary); border-color: var(--c-primary); color: var(--on-primary, #fff); }
.chkbox.is-disabled { opacity: .4; pointer-events: none; }   /* the Home cap is reached — same read as the old switch */

/* dashboard quick tiles + hotel services grid */
.tiles, .svc-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.tile { display: grid; justify-items: center; gap: 6px; text-align: center; padding: 14px 8px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); color: var(--ink); text-decoration: none; font-weight: 600; }
.tile:hover { border-color: var(--line-strong); text-decoration: none; }
.svc { display: grid; justify-items: center; gap: 6px; text-align: center; }

/* gallery — each cell is a button that opens the photo full-screen */
.g-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.g-cell { padding: 0; border: 0; background: none; cursor: zoom-in; border-radius: var(--radius-sm); overflow: hidden; display: block; }
.g-cell:hover .g-img { transform: scale(1.04); filter: brightness(1.03); }
.g-img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: var(--radius-sm); transition: transform .3s ease, filter .3s ease; }

/* "Il locale" gallery — a big hero photo + a thumbnail selector: tap a thumb to swap the hero (no modal) */
.loc-gallery { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.loc-gallery__hero { position: relative; width: 100%; aspect-ratio: 16 / 10; border-radius: var(--radius-card); overflow: hidden; background: #0c0c0c; cursor: zoom-in; }
.loc-gallery__blur { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(1.15); filter: blur(20px) brightness(.68) saturate(1.1); } /* fills the frame so there are no empty bars */
.loc-gallery__main { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; display: block; } /* the whole photo, sharp, uncropped */
.loc-gallery__expand { position: absolute; right: 8px; bottom: 8px; width: 30px; height: 30px; display: grid; place-items: center; border-radius: 999px; background: rgba(0, 0, 0, .5); color: #fff; -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); pointer-events: none; }
.loc-gallery__expand i { display: flex; align-items: center; justify-content: center; }
/* min-width:0 + overflow lets the strip scroll INSIDE the app width instead of forcing the page wider */
.loc-gallery__thumbs { display: flex; gap: 8px; overflow-x: auto; scrollbar-width: none; padding: 2px; margin: -2px; min-width: 0; max-width: 100%; }
.loc-gallery__thumbs::-webkit-scrollbar { display: none; }
.loc-gallery__thumb { flex: 0 0 auto; width: 64px; height: 64px; border-radius: var(--radius-sm); overflow: hidden; padding: 0; border: 2px solid transparent; background: var(--tint-bg); cursor: pointer; opacity: .55; transition: opacity .15s ease, border-color .15s ease; }
.loc-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.loc-gallery__thumb:hover { opacity: 1; }
.loc-gallery__thumb.is-active { opacity: 1; border-color: var(--c-primary); }

/* full-screen photo lightbox / carousel (created by app.js; swipe, arrows, ← → keys, Esc) */
/* ⛔⛔ 80 PUT IT UNDERNEATH THE CARD IT WAS OPENED FROM (fixed 2026-08-13, same day it shipped).
   Tony: *"what is the point of a full screen image opening below the card which is in a modal
   window? that is what is happening now"*. Exactly right. `.cardmodal` is 85, so full screen was
   BEHIND the room card — invisible, while every check said it had opened.
   ⚠️ It never showed from the Home gallery, because there is no modal there and 80 beats the page.
   The moment full screen could be reached from INSIDE a card, the number was wrong.
   ⭐ 88: above `.cardmodal` (85) and deliberately BELOW `.installsheet` / `.stay-ask` (90) and
   `.cropper` (100) — those are interruptions that should still be able to speak over a photograph.
   ⛔ THE RULE, and this repo has now learned it twice (see the toast): rank an overlay against the
   HIGHEST thing it can be opened on top of, never against the page. */
.lightbox { position: fixed; inset: 0; z-index: 88; display: grid; place-items: center;
  background: rgba(8, 12, 14, .92); padding: 16px; cursor: zoom-out; animation: lb-fade .15s ease; }
.lightbox__img { max-width: 100%; max-height: 100%; width: auto; height: auto; cursor: default;
  border-radius: var(--radius-sm); box-shadow: 0 24px 70px rgba(0, 0, 0, .6); }
/* bare glyphs, no circles (Tony 2026-07-15) — a drop-shadow keeps them readable over bright photos */
.lightbox__btn { position: absolute; z-index: 1; display: grid; place-items: center; cursor: pointer; color: #fff;
  background: none; border: 0; filter: drop-shadow(0 1px 5px rgba(0, 0, 0, .65)); transition: opacity .15s ease, transform .06s ease; }
.lightbox__btn i { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; line-height: 1; }
.lightbox__btn[hidden] { display: none; }   /* single-photo gallery: display:grid would defeat the hidden attribute → ghost arrows that "do nothing" */
.lightbox__btn:hover { opacity: .8; }
.lightbox__btn:active { transform: scale(.92); }
.lightbox__close { top: max(14px, env(safe-area-inset-top)); right: 14px; width: 42px; height: 42px; }
.lightbox__nav { top: 50%; transform: translateY(-50%); width: 48px; height: 48px; }
.lightbox__nav--prev { left: 12px; }
.lightbox__nav--next { right: 12px; }
.lightbox__nav:active { transform: translateY(-50%) scale(.92); }
.lightbox__count { position: absolute; left: 0; right: 0; bottom: max(16px, env(safe-area-inset-bottom)); text-align: center;
  color: rgba(255, 255, 255, .85); font: 600 14px var(--font-ui); letter-spacing: .05em; }
@keyframes lb-fade { from { opacity: 0; } to { opacity: 1; } }

/* "Add to Home Screen" — a bottom sheet that collapses to an always-visible PEEK
   bar you can tap to reopen (installPrompt.js). Inverted vs the theme so it stays
   visible: cream over the dark app, dark over the light app. */
.installsheet { position: fixed; inset: 0; z-index: 90; display: flex; align-items: flex-end; pointer-events: none; }
.installsheet__backdrop { position: absolute; inset: 0; background: rgba(8, 12, 14, .5); opacity: 0; transition: opacity .34s ease; pointer-events: none; }
.installsheet.is-in:not(.is-offroute):not(.is-collapsed) .installsheet__backdrop { opacity: 1; pointer-events: auto; }
.installsheet__panel { position: relative; width: 100%; max-width: 540px; margin-inline: auto; pointer-events: auto;
  border-radius: 22px 22px 0 0; box-shadow: 0 -22px 60px rgba(0, 0, 0, .42);
  padding: 0 22px env(safe-area-inset-bottom);
  transform: translateY(102%); transition: transform .42s cubic-bezier(.22, 1, .36, 1), margin-bottom .28s ease; }
.installsheet.is-in:not(.is-offroute) .installsheet__panel { transform: translateY(0); }
.installsheet.is-collapsed .installsheet__panel { margin-bottom: var(--peek-offset, 0); } /* peek sits above the tab bar / footer banner */
html[data-theme="dark"] .installsheet__panel { background: #f6f3ec; color: #15211c; }
html[data-theme="light"] .installsheet__panel { background: #1b211e; color: #f3f1ea; }

.ish__bar { display: flex; align-items: center; gap: 13px; width: 100%; padding: 12px 0; background: none; border: 0; cursor: pointer; text-align: left; color: inherit; font: inherit; }
.ish__icon { flex: none; border-radius: 10px; }
html[data-theme="dark"] .ish__icon { box-shadow: 0 0 0 1px rgba(21, 33, 28, .12); }
html[data-theme="light"] .ish__icon { box-shadow: 0 2px 10px rgba(0, 0, 0, .3); }
.ish__bartext { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ish__eyebrow { font-family: var(--font-ui); font-size: 11.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; opacity: .5; }
.ish__title { font-family: var(--font-ui); font-weight: 800; font-size: 18px; line-height: 1.15; }
/* Open, the bar's title just repeats the big "AGGIUNGI L'APP AL TELEFONO" two lines below it, so it
   goes (Tony, 2026-07-26 — "it is noise"). ⚠️ Hidden ONLY when open: COLLAPSED, this same bar is the
   peek strip and that title is the only thing inviting the tap — remove it there and the bar is a
   bare chevron. The button keeps its aria-label either way, so screen readers still announce it. */
.installsheet:not(.is-collapsed) .ish__title { display: none; }
.ish__chev { flex: none; width: 22px; height: 22px; opacity: .45; transition: transform .3s ease; }
.installsheet:not(.is-collapsed) .ish__chev { transform: rotate(180deg); }

.ish__body { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .36s cubic-bezier(.22, 1, .36, 1); }
.installsheet.is-collapsed .ish__body { grid-template-rows: 0fr; }
.ish__body-inner { overflow: hidden; min-height: 0; }
.ish__body-inner--centered { text-align: center; }
.ish__body-inner--centered .ish__steps { text-align: left; }
.ish__biglogo { display: block; margin: 6px auto 12px; width: 94px; height: 94px; border-radius: 21px; box-shadow: 0 12px 30px rgba(0,0,0,.16); }
.ish__bigtitle { margin: 0 0 6px; font-size: 19px; font-weight: 800; letter-spacing: .03em; text-transform: uppercase; color: inherit; }
.ish__add { margin: 8px 0 0; }
.ish__lead { margin: 0; opacity: .62; font-size: 14.5px; line-height: 1.4; }
.ish__steps { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.ish__step { display: flex; align-items: flex-start; gap: 13px; }
.ish__num { flex: none; width: 28px; height: 28px; border-radius: 50%; border: 2px solid; display: grid; place-items: center;
  font-family: var(--font-ui); font-weight: 700; font-size: 14px; margin-top: 1px; }
html[data-theme="dark"] .ish__num, html[data-theme="dark"] .ish__share { color: #2f6b4f; }
html[data-theme="light"] .ish__num, html[data-theme="light"] .ish__share { color: #6aa886; }
.ish__step p { margin: 0; font-size: 15.5px; line-height: 1.45; }
.ish__hint { opacity: .6; font-weight: 400; }
.ish__share { display: inline-block; width: 17px; height: 17px; vertical-align: -3px; margin: 0 1px; }
/* The ••• / chevron / ⊞ glyphs the steps name. Deliberately NOT the accent colour of .ish__share:
   they sit inside the dimmed hint line and must read as "this button on your screen", not as a
   highlight. They inherit the hint's colour + opacity, so they dim with it. */
.ish__gl { display: inline-block; width: 16px; height: 16px; vertical-align: -3px; margin: 0 1px; }
.ish__install { display: block; width: 100%; margin: 14px 0 2px; min-height: 50px; font-weight: 700; letter-spacing: .02em; }
.ish__rule { height: 1px; border: 0; background: currentColor; opacity: .14; margin: 20px 0 0; }
.ish__or { text-align: center; opacity: .55; font-size: 13px; margin: 12px 0 0; }
/* "Continua nel browser" demoted to a quiet text link (Tony 2026-07-23): the un-read tap must never
   land here — the only button-looking thing on the sheet stays the install path. */
.ish__browser { display: block; margin: 2px auto 0; padding: 8px 14px; border: 0; border-radius: var(--radius); font: inherit;
  font-size: 13px; font-weight: 600; cursor: pointer; background: none; color: inherit; opacity: .6;
  text-decoration: underline; text-underline-offset: 3px; transition: opacity .15s ease; }
.ish__browser:hover { opacity: .85; background: none; }
.ish__note { text-align: center; opacity: .5; font-size: 12px; line-height: 1.4; margin: 6px 4px 18px; }

/* unlock screen — bigger, full-strength lead (not muted grey) */
.unlock-lead { font-size: 16px; line-height: 1.5; color: var(--ink); margin: 0; }

/* loyalty — three colour-graded "earn points" actions (value → colour) */
.earn-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.earn-btn { display: grid; gap: 5px; justify-items: center; text-align: center;
  padding: 14px 8px; border-radius: var(--radius); border: 1px solid transparent;
  background: var(--surface); color: var(--ink); cursor: pointer; font: inherit;
  transition: filter .15s ease, transform .06s ease; }
.earn-btn:hover { filter: brightness(1.04); }
.earn-btn:active { transform: translateY(1px); }
.earn-btn__pts { font-family: var(--font-display); font-weight: 600; font-size: calc(22px * var(--font-display-scale, 1)); line-height: 1; }
.earn-btn__cap { font-size: 11px; line-height: 1.25; color: var(--muted); }
.earn-btn--s { background: color-mix(in srgb, #3b82f6 14%, var(--surface)); border-color: color-mix(in srgb, #3b82f6 30%, transparent); }
.earn-btn--s .earn-btn__pts { color: #3b82f6; }
.earn-btn--m { background: color-mix(in srgb, var(--c-accent) 18%, var(--surface)); border-color: color-mix(in srgb, var(--c-accent) 38%, transparent); }
.earn-btn--m .earn-btn__pts { color: color-mix(in srgb, var(--c-accent) 78%, var(--ink)); }
.earn-btn--l { background: color-mix(in srgb, var(--success) 16%, var(--surface)); border-color: color-mix(in srgb, var(--success) 36%, transparent); }
.earn-btn--l .earn-btn__pts { color: var(--success); }

/* current membership level highlight (wallet) */
.row--current { border-color: color-mix(in srgb, var(--c-accent) 55%, transparent); background: color-mix(in srgb, var(--c-accent) 9%, var(--surface)); }

/* beach spot picker */
.beach-grid { display: grid; gap: 6px; margin-top: 12px; }
.spot { aspect-ratio: 1; border: 1px solid var(--line-strong); border-radius: 6px; background: var(--surface);
  color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer; }
.spot:hover:not(:disabled) { border-color: var(--c-primary); color: var(--c-primary); }
.spot.is-taken { background: var(--surface-2); color: var(--line-strong); cursor: not-allowed; }
.spot.is-sel { background: var(--c-primary); color: var(--on-primary, #fff); border-color: var(--c-primary); }
.legend { display: inline-block; width: 12px; height: 12px; border-radius: 3px; vertical-align: -1px; border: 1px solid var(--line-strong); }
.legend--free { background: var(--surface); }
.legend--taken { background: var(--surface-2); }
.legend--sel { background: var(--c-primary); border-color: var(--c-primary); }

/* ----------------------------------------------------------------------------
 * 16. Motion preferences
 * -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .hotel-card:hover > img { transform: none; }
}

/* ── Super-admin — full-page control room ──────────────────────────────────── */
.container--wide { width: 100%; max-width: 1080px; margin-inline: auto; padding-inline: var(--space); }
.admin-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; align-items: start; }
@media (max-width: 760px) { .admin-cols { grid-template-columns: 1fr; } }
.su-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 16px; align-items: start; }
.su-card { display: flex; flex-direction: column; gap: 12px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);
  padding: 16px; box-shadow: 0 1px 3px rgba(20, 33, 28, .05); }
.su-card__head { display: flex; gap: 12px; align-items: center; }
/* The size lives HERE, not on the tag: the preset cards pass width/height attributes on an <img>, but
   a wizard client's face is a <span> wrapping its logo — with no size on the class, its inner "82%"
   had nothing to resolve against and the logo rendered at natural size, blowing the card apart. */
.su-card__icon { width: 46px; height: 46px; border-radius: 11px; border: 1px solid var(--line); flex: none; }
.su-card__name { font-weight: 700; display: block; line-height: 1.2; }
.su-card__vert { color: var(--muted); font-size: 12.5px; text-transform: capitalize; }
.su-card__mods { display: flex; flex-wrap: wrap; gap: 6px; }
.su-swatch { display: inline-block; width: 13px; height: 13px; border-radius: 4px; border: 1px solid rgba(0, 0, 0, .15); vertical-align: middle; }
.chip--xs { font-size: 11px; padding: 3px 9px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--muted); text-transform: none; }
.su-edit { border-top: 1px solid var(--line); padding-top: 12px; }
.su-field { display: flex; flex-direction: column; gap: 5px; }
.su-field > label { font-size: 12.5px; color: var(--muted); }
.su-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.su-edit input[type="color"] { width: 46px; height: 40px; padding: 2px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); cursor: pointer; }
.su-logo { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px; border: 1px dashed var(--line); border-radius: 12px; }
.su-logo.is-drop { border-color: var(--c-primary); border-style: solid; }
.su-logo__prev { height: 44px; max-width: 180px; object-fit: contain; }
@media (max-width: 480px) { .su-2col { grid-template-columns: 1fr; } }

/* ── Public (walk-in) app — Osteria / Bar / Tenuta ─────────────────────────── */
.menus-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.menus-2--single { grid-template-columns: 1fr; } /* one active formula → full width */
/* Every Menu Fisso card is a container so its contents scale WITH the card (which scales with the
   screen) — no overflow / off-centre on small phones, in any style. */
.setmenu { text-align: center; padding: 4px 2px; container-type: inline-size; min-width: 0; }
.setmenu--link { cursor: pointer; }
/* Force a simple centred flex column (the shared .stack is a grid, whose auto column drifts with the
   widest item). Now chip + price shrink-to-fit and centre; the description spans full width so its
   wrapped lines centre too. Nothing can drift off-centre regardless of price/name length. */
.setmenu .card__body { display: flex; flex-direction: column; align-items: center; }
.setmenu .card__body > * { max-width: 100%; }
.setmenu .card__body > .muted { align-self: stretch; }
.setmenu__name { font-weight: 700; font-size: clamp(13px, 12cqi, 16.5px); }
.setmenu__price { font-size: clamp(23px, 22cqi, 32px); font-weight: 800; color: var(--c-primary); line-height: 1.05; max-width: 100%; }
.dispo { display: flex; justify-content: space-between; align-items: center; padding: 9px 0; border-top: 1px solid var(--line); }
.dispo:first-child { border-top: 0; }
.dispo__turno { font-weight: 600; font-size: 15px; }
.dispo__posti { font-weight: 700; color: var(--c-primary); }
.dispo__posti.is-full { color: var(--muted); }

/* multi-day availability (TRACK M) — date header + tappable day chips (horizontal scroll) */
.day-head { font-size: 13.5px; font-weight: 700; color: var(--muted); letter-spacing: .01em; text-transform: capitalize; margin-top: -2px; }
/* "Orari e Disponibilità" (customer): a standalone card that INVERTS the theme so it stands out —
   light card in dark mode, dark card in light mode. Flipping the tokens flips every child with it. */
.dispo-panel { background: var(--surface); color: var(--ink); }
:root[data-theme="dark"] .dispo-panel { --surface: #f3ebd9; --surface-2: #eae1cb; --ink: #15211c; --muted: #6b6f6a; --line: rgba(0, 0, 0, .10); --line-strong: rgba(38, 32, 18, .22); }
:root[data-theme="light"] .dispo-panel { --surface: #242c33; --surface-2: #2c343d; --ink: #eef1ea; --muted: #9aa49d; --line: rgba(255, 255, 255, .12); --line-strong: rgba(255, 255, 255, .24); }
.dispo-panel__ico { display: inline-flex; align-items: center; color: var(--c-primary); }
.dispo-panel__sep { height: 1px; border: 0; margin: 0; background: var(--line-strong); }
.day-chips { display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none; margin: 0 -2px; padding: 2px; }
.day-chips::-webkit-scrollbar { display: none; }
.day-chip { flex: 0 0 auto; padding: 6px 12px; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--muted);
  font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; white-space: nowrap;
  transition: background .12s ease, color .12s ease, border-color .12s ease; }
.day-chip:hover { color: var(--ink); }
.day-chip.is-active { background: var(--c-primary); border-color: var(--c-primary); color: var(--on-primary, #fff); }
/* a closed day reads in RED at a glance (no need to tap): red text + red border + red strike */
.day-chip.is-closed:not(.is-active) {
  color: var(--danger-solid);
  border-color: color-mix(in srgb, var(--danger-solid) 55%, transparent);
  text-decoration: line-through; text-decoration-thickness: 1.5px;
  text-decoration-color: var(--danger-solid);
}
/* selecting a closed day: fill it solid red with white text (not the accent) so it still reads "chiuso" */
.day-chip.is-active.is-closed { background: var(--danger-solid); border-color: var(--danger-solid); color: #fff; text-decoration: none; }
/* weekday multi-select (Manager → Chiusure e ferie → Chiusura settimanale) */
.day-pick { flex: 0 0 auto; min-width: 46px; padding: 7px 12px; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid var(--line-strong); background: var(--surface); color: var(--muted);
  font-family: var(--font-ui); font-size: 13px; font-weight: 600; text-align: center;
  transition: background .12s ease, color .12s ease, border-color .12s ease; }
.day-pick:hover { color: var(--ink); }
.day-pick.is-on { background: var(--c-primary); border-color: var(--c-primary); color: var(--on-primary, #fff); }
/* a borderless label+switch row that breathes (no box) — forms + settings panels */
.togglerow { display: flex; align-items: center; justify-content: space-between; gap: 14px; width: 100%; }
/* a soft inner panel (e.g. Chiusura settimanale / Ferie inside "Chiusure e ferie") */
.subpanel { border: 1px solid var(--line); border-radius: var(--radius-card); background: var(--surface); padding: 15px 16px; }
/* one ferie period inside the Ferie subpanel */
.ferie-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--ink) 5%, var(--surface)); }
.ferie-item .row__text { flex: 1; min-width: 0; }
/* closed-day body — a CENTERED red "Chiuso" badge + the closure message */
.dispo-closed { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 10px 0 4px; text-align: center; }
.dispo-closed__tag { display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 14px; border-radius: var(--radius-pill); font-size: 13.5px; font-weight: 700;
  background: color-mix(in srgb, var(--danger-solid) 14%, transparent);
  color: var(--danger-solid); }
.dispo-closed__tag i { color: var(--danger-solid); }
.dispo-closed__msg { margin: 0; font-size: 14px; line-height: 1.4; color: var(--muted); max-width: 36ch; }
/* Home ferie banner — unmissable when today is inside a holiday period */
.ferie-banner { display: flex; align-items: center; gap: 10px; margin-bottom: 18px;
  padding: 12px 15px; border-radius: var(--radius-card); font-size: 14px; font-weight: 600; line-height: 1.35;
  background: color-mix(in srgb, var(--c-accent) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c-accent) 34%, transparent); color: var(--ink); }
.ferie-banner i { flex: none; color: var(--c-accent); }
/* "Chiuso per ferie" — a big centred card (same shape as the offer block), warm ferie colours. */
.ferie-card { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 5px; margin-bottom: 18px;
  padding: 20px 16px; border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--c-accent) 14%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--c-accent) 34%, transparent); }
.ferie-card__icon { color: var(--c-accent); line-height: 1; }
.ferie-card__icon i { font-size: 36px; }
.ferie-card__title { font-weight: 800; font-size: 19px; letter-spacing: -.2px; color: var(--ink); }
.ferie-card__period { font-size: 14px; color: var(--muted); text-transform: capitalize; }
/* "Chiuso per ferie" pop-up — an on-open alert modal. Reuses the .cardmodal backdrop; lifted above every
   other overlay (install sheet 90, lightbox 80…) so a closure is the first thing seen. */
/* .cardmodal.ferie-modal (not just .ferie-modal) so these beat the shared .cardmodal rule regardless of
   source order. Darker backdrop than the default card modal — the app stays faintly visible behind, but
   the closure clearly takes over the screen. */
.cardmodal.ferie-modal { z-index: 150; background: rgba(0, 0, 0, .88); }
.ferie-modal__panel { width: min(360px, 100%); padding: 30px 24px calc(26px + env(safe-area-inset-bottom)); text-align: center; display: flex; flex-direction: column; align-items: center; gap: 8px; border: 1px solid rgba(255, 255, 255, .2); animation: feriePop .22s ease-out; }
.ferie-modal__icon { color: var(--c-accent); line-height: 1; }
.ferie-modal__icon i { font-size: 46px; }
.ferie-modal__title { margin: 8px 0 0; font-size: 22px; font-weight: 800; letter-spacing: -.2px; color: var(--ink); }
.ferie-modal__period { margin: 0; font-size: 15px; color: var(--muted); text-transform: capitalize; }
.ferie-modal__bye { margin: 6px 0 16px; font-size: 15px; color: var(--ink); }
@keyframes feriePop { from { opacity: 0; transform: translateY(12px) scale(.97); } to { opacity: 1; transform: none; } }
/* The header logo is an INLINE <svg> (see publicApp header + panevino-logo.js). Sized by WIDTH with
   height:auto from the viewBox → the box is reserved synchronously on first paint (no collapse-then-grow
   jump) and it never fetches (no flash / no broken-image "?"). --logo-scale is the Super-Admin size knob
   (65%×1.2 ≈ the approved 78% width). max-width caps it on the narrowest phones. */
.landing-head__logo { display: block; margin-inline: auto; width: calc(68% * var(--logo-scale, 1)); height: auto; max-width: 92%; }

/* restaurant admin — compact table inventory + tap-to-book tiles */
.tbl-inv-row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.tbl-inv { display: flex; flex-direction: column; gap: 3px; }
.tbl-inv label { font-size: 11px; color: var(--muted); }
.tbl-inv .input { width: 52px; padding: 8px 6px; text-align: center; }
.tbl-inv-row .btn { min-height: 48px; } /* match the da-8/da-6 input height */
.tbl { min-width: 38px; height: 38px; padding: 0 9px; border-radius: 9px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); font-weight: 700; font-size: 16px; cursor: pointer; transition: transform .05s ease; }
.tbl:active { transform: scale(.93); }
.tbl.is-booked { background: #8a2024; border-color: #8a2024; color: #fff; position: relative; overflow: hidden; }
/* booked = crossed out (unavailable): a thin 1px diagonal line across the red tile */
.tbl.is-booked::after { content: ""; position: absolute; inset: 0; background:
  linear-gradient(to top right, transparent calc(50% - .5px), rgba(255, 255, 255, .92) calc(50% - .5px),
  rgba(255, 255, 255, .92) calc(50% + .5px), transparent calc(50% + .5px)); }

/* "add" zones in Gestione — a light dashed panel: heading, full-width fields, action on its own row */
.addzone { background: var(--tint-bg); border: 1px dashed color-mix(in srgb, var(--c-primary) 30%, var(--line)); border-radius: 12px; padding: 14px; }
.addzone__title { display: block; font-weight: 700; color: var(--c-primary); font-size: 12px; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 10px; }
.addzone__fields { display: flex; flex-direction: column; gap: 10px; }
.addzone__row { display: flex; gap: 10px; align-items: flex-end; }
.addzone .su-field { width: 100%; }
.addzone .su-field .input, .addzone .su-field select.input { width: 100%; max-width: none; }
/* same-row action button matches the input height (so they line up) */
.addzone__row { align-items: stretch; }
.addzone__row .su-field { display: flex; flex-direction: column; }
.addzone__row .su-field .input-money, .addzone__row .su-field .input { margin-top: auto; }

/* per-field owner-content translation: the language chips + the "Traduci in …" modal */
.langchips { gap: 7px; }
.langchips__label { font-size: 12.5px; color: var(--muted); }
.langchips__row { display: flex; flex-wrap: wrap; gap: 6px; }
.langchip {
  position: relative; min-width: 38px; padding: 6px 9px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--ink);
  font-size: 12px; font-weight: 700; letter-spacing: .04em; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.langchip:hover { border-color: var(--muted); }
.langchip--it { cursor: default; opacity: .85; }
.langchip.is-active { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.langchip__dot {
  position: absolute; top: -3px; right: -3px; width: 8px; height: 8px; border-radius: 50%;
  background: #2e9c5a; border: 1.5px solid var(--surface);
}
.langtr__orig {
  font-size: 13px; color: var(--muted); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px; min-height: 20px;
}
.langtr__g { align-self: flex-start; gap: 6px; }

/* titled field panel inside the Speciale form (Avvisa i clienti = switch reveals its body; Offerta = always open) */
.fpanel { border: 1px solid var(--line); border-radius: 12px; background: var(--surface-2); overflow: hidden; }
/* ⭐ ONE HUE PER AUTOMATIC MESSAGE (Tony, 2026-08-13) — five identical dark panels read as one wall,
   and the owner has to read every heading to find the one he wants.
   ⛔ THE COLOUR IS BUILT HERE, FROM A HUE, and never passed in as hex. Lightness and saturation are
   FIXED, so all five sit exactly the same distance from the surface and only the colour changes.
   Hand-picked hexes would land at five different strengths — some shouting, some invisible — with the
   code looking perfectly even. This app learned that once already, on the per-client wash.
   ⚠️ `--fp-amt` is the single dial — 12% (Tony, 2026-08-13). Change this one number if he ever wants
   it stronger or quieter; nothing else needs touching.
   ⭐ The edge carries it too, at double: a border is one pixel against the page and needs the help. */
.fpanel[style*="--fp-h"] { --fp-amt: 12%;
  background: color-mix(in srgb, hsl(var(--fp-h) 42% 52%) var(--fp-amt), var(--surface-2));
  border-color: color-mix(in srgb, hsl(var(--fp-h) 42% 52%) calc(var(--fp-amt) * 2), var(--line)); }
.fpanel[style*="--fp-h"].fpanel--tap:active {
  background: color-mix(in srgb, hsl(var(--fp-h) 42% 52%) calc(var(--fp-amt) * 1.7), var(--surface-2)); }
/* The heading icon takes the same hue at full strength — the one place it is allowed to be seen
   rather than felt, so a glance at the icons alone tells the five rows apart. */
.fpanel[style*="--fp-h"] .fpanel__title .ti { color: hsl(var(--fp-h) 48% 62%); }
/* the flagship "Offerta per i clienti" panel: light gold border + a soft outer glow (Tony 2026-07-15) */
.fpanel--gold { border-color: color-mix(in srgb, #d9a23c 45%, var(--line)); box-shadow: 0 0 14px color-mix(in srgb, #d9a23c 16%, transparent); }
.fpanel__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 14px; }
.fpanel__title { display: inline-flex; align-items: center; gap: 8px; font-weight: 600; font-size: 15px; color: var(--ink); }
.fpanel__note { font-size: 12.5px; font-weight: 600; color: var(--c-primary); }   /* Mostra in Home: the orange "Te ne rimangono N" notice */
.fpanel__body { padding: 0 14px 14px; display: flex; flex-direction: column; gap: 11px; }
/* the "how to write a good offer" tip inside the Offerta panel */
.fpanel__tip { display: flex; gap: 7px; align-items: flex-start; margin: 0; font-size: 12px; line-height: 1.45;
  color: color-mix(in srgb, var(--c-primary) 82%, var(--ink));
  background: color-mix(in srgb, var(--c-primary) 9%, var(--surface)); border-radius: 9px; padding: 9px 11px; }
.fpanel__tip i { flex: none; margin-top: 1px; font-size: 15px; color: var(--c-primary); }
.addzone__row .btn { align-self: flex-end; min-height: 51px; }
/* money field: "Costo" label, € shown inside the field */
.input-money { position: relative; }
.input-money__sym { position: absolute; left: 13px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; line-height: 1; }
.input-money .input { padding-left: 28px; }
/* "Immagine" field in the add/edit modal: big preview + Carica / Cancella stacked beside it */
.imgfield { display: flex; gap: 12px; align-items: center; }
.imgfield__preview { width: 92px; height: 92px; border-radius: 12px; object-fit: cover; flex: 0 0 auto; background: var(--tint-bg); cursor: pointer; }
.imgfield__preview--empty { display: inline-flex; align-items: center; justify-content: center; color: var(--muted); }
/* ⚠️ DEAD, and left alone deliberately: icon() writes an inline font-size, which beats this. The
   placeholder has always drawn at its call size. Changing it here changes nothing — change the call. */
.imgfield__preview--empty i { font-size: 30px; }
.imgfield__btns { display: flex; flex-direction: column; gap: 8px; min-width: 120px; }

/* #68 · THE ITEM ICON PICKER (Manager). ⛔ Its own classes, NOT `.hq-iconpick`: that one is painted in
   the `--hq-*` tokens, which exist only on the HQ surface — reusing it would draw an invisible grid
   inside a client's Manager. Same shape, this app's colours. */
.icongrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 8px; }
.iconpick { aspect-ratio: 1; border-radius: 10px; display: grid; place-items: center; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink-soft); }
.iconpick.is-on { color: var(--on-primary); background: var(--c-primary); border-color: var(--c-primary); }
/* "Nessuna icona" reads as a choice, not as a missing tile — so it is drawn, muted, and sits first. */
.iconpick__none { display: grid; place-items: center; color: var(--muted); }
.iconpick.is-on .iconpick__none { color: var(--on-primary); }
/* ⭐ THE NAME OF THE ONE UNDER THE FINGER. One fixed line above the grid rather than a bubble beside
   the icon: on a phone the finger covers the tile it is pressing, so a tooltip drawn there is the one
   place the answer cannot be read. `min-height` holds the row open so the grid never jumps when the
   text swaps between the hint and a name. ⚠️ Reserves TWO lines' worth — the longest labels
   («Campanello · Servizio in camera») wrap on a narrow phone, and a one-line box would still jump. */
.iconpick__cap { margin: 0 0 10px; min-height: 2.6em; font-size: 13px; line-height: 1.3; color: var(--muted); }
.iconpick__cap.is-named { color: var(--ink); font-weight: 600; }

/* image framing/crop modal (openImageCropper) — pick which part of the photo fills the card */
.cropper { position: fixed; inset: 0; z-index: 100; background: rgba(0, 0, 0, .72); display: grid; place-items: center; padding: 16px; }
.cropper__panel { background: var(--surface); border-radius: var(--radius-card); padding: 16px; width: min(440px, 94vw); max-height: 92vh; display: flex; flex-direction: column; gap: 12px; box-shadow: var(--shadow-md); }
.cropper__head { font-family: var(--font-display); font-weight: 600; font-size: calc(18px * var(--font-display-scale, 1)); }
.cropper__stage { position: relative; width: 100%; height: 56vh; max-height: 460px; background: #0c0c0c; border-radius: var(--radius-sm); overflow: hidden; touch-action: none; user-select: none; }
.cropper__img { position: absolute; user-select: none; -webkit-user-drag: none; pointer-events: none;
  /* Checkerboard under the image (design-tool standard): a transparent/dark file was invisible
     against the dark stage, so the crop box seemed "stuck" when it had simply reached the
     image's real edge. Now the file's true bounds are always visible. */
  background: repeating-conic-gradient(#2e2e2e 0% 25%, #232323 0% 50%) 0 0 / 18px 18px;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .18); }
.cropper__box { position: absolute; box-shadow: 0 0 0 9999px rgba(0, 0, 0, .5); border: 2px solid #fff; cursor: move; touch-action: none; }
.cropper__grip { position: absolute; right: -12px; bottom: -12px; width: 24px; height: 24px; border-radius: 999px; background: #fff; border: 2px solid var(--c-primary); cursor: nwse-resize; touch-action: none; box-shadow: 0 1px 4px rgba(0, 0, 0, .4); }
/* Thumb-sized invisible hit area (Apple minimum 44px): missing the 24px dot by a hair used to
   MOVE the box instead of resizing — which read as "the crop can only shrink" on the phone. */
.cropper__grip::after { content: ''; position: absolute; inset: -18px; border-radius: 999px; }
/* The second handle, freeform only (logos): top-left, so the two corners between them reach all
   four edges. Identical size/target to the first — nothing here should feel like the lesser grip. */
.cropper__grip--nw { right: auto; bottom: auto; left: -12px; top: -12px; }
.cropper__hint { margin: 0; font-size: 12.5px; color: var(--muted); text-align: center; }
.cropper__btns { display: flex; gap: 10px; }
.cropper__btns .btn { flex: 1; }

/* swipe-to-reveal rows (menu dishes): drag left → Archivia / Elimina (desktop: hover reveals) */
.swipe { position: relative; overflow: hidden; border-radius: var(--radius); }
.swipe__actions { position: absolute; top: 0; right: 0; bottom: 0; display: flex; }
.swipe__act { width: 84px; border: 0; color: #fff; font-weight: 700; font-size: 12px; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; }
.swipe__act i { font-size: 19px; }
.swipe__act--archive { background: #b4730b; }
.swipe__act--delete { background: var(--danger-solid); }
.swipe__content { position: relative; transition: transform .22s ease; will-change: transform; touch-action: pan-y; }
.swipe.is-open .swipe__content { transform: translateX(-168px); }
@media (hover: hover) { .swipe:hover .swipe__content { transform: translateX(-168px); } }

/* owner content thumbnails (resized photos on specialità / dishes / offers) */
.ra-thumb { width: 46px; height: 46px; border-radius: 9px; object-fit: cover; flex: 0 0 auto; background: var(--tint-bg); border: 1px solid var(--line); }
.ra-thumb--empty { display: inline-flex; align-items: center; justify-content: center; color: var(--muted); }
.ra-thumb--empty i { font-size: 20px; }
/* #68b — the chosen icon in the Manager's LIST row (Tony: "if we selected an Icon this should show
   also in the list"). It must read as a SET value, not as a placeholder: the generic photo glyph
   stays `--muted` (an empty slot), a real choice is `--ink-soft`.
   ⛔⛔ IT MUST SIT AFTER `.ra-thumb--empty`, and that is the whole reason this line is here rather
   than up beside the picker's own styles. Both selectors are one class, so the LAST one wins — put it
   above and `--empty`'s `--muted` silently takes the colour back. Written above first, caught by
   reading the file rather than by any check: the twin gate sees duplicate selectors, not this.
   ⚠️ NO font-size here. `icon()` writes one inline and the box size already differs between the
   restaurant's 46px row and the hotel's 92px one — a rule here would be dead either way. */
.ra-thumb--ico { color: var(--ink-soft); }

/* Photo reveal: content photos start hidden over a soft neutral placeholder and are shown by JS only
   once decoded (revealPhoto). Cached photos show instantly (no flash on repeat navigation); uncached
   ones fade in gently instead of hard-popping. */
img.notepad__thumb, img.offer-card__img, img.spec-card__img, img.pub-thumb {
  opacity: 0;
  background-color: color-mix(in srgb, var(--ink) 8%, var(--surface));
}
img.notepad__thumb.fade, img.offer-card__img.fade, img.spec-card__img.fade, img.pub-thumb.fade { transition: opacity .35s ease; }
img.notepad__thumb.is-ready, img.offer-card__img.is-ready, img.spec-card__img.is-ready, img.pub-thumb.is-ready { opacity: 1; }

/* "Il locale" gallery editor (Impostazioni App) — thumbnails with a remove button + an add tile */
.ra-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(76px, 1fr)); gap: 8px; }
.ra-gallery__cell { position: relative; aspect-ratio: 1; border-radius: 9px; overflow: hidden; border: 1px solid var(--line); background: var(--tint-bg); }
.ra-gallery__cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ra-gallery__rm { position: absolute; top: 4px; right: 4px; width: 22px; height: 22px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 999px; background: rgba(0, 0, 0, .6); color: #fff; cursor: pointer; }

/* #11 — "put this one on the cover". ⛔ BOTTOM-LEFT, diagonally opposite the delete button: the two
   sit on a thumbnail barely wider than a thumb, and the one that destroys a photo must never be
   where the hand lands reaching for the one that does not. Gold rather than the brand colour — it is
   the same gold the Passaparola and the loyalty stamp already use for "chosen", and it stays legible
   on a photograph of any colour, which a per-client accent cannot promise. */
/* #11ter — the introduction photo's editor. Deliberately the same shape as the item Immagine field
   the owner already knows: a square of picture, and the buttons beside it. */
.ra-introshot { display: flex; gap: 12px; align-items: flex-start; margin-top: 10px; }
/* ⚠️ SCOPED, so it beats `.ra-thumb` whatever the order in this file. Both are single classes, so
   with a bare `.ra-introshot__thumb` the winner would be whichever happens to come last — the exact
   silent trap this project has already been bitten by three times in one session. `.ra-introshot
   .ra-thumb` is (0,2,0) and cannot lose. ⭐ 16:9 because that is the shape the guest sees it in. */
.ra-introshot .ra-thumb { width: 96px; height: 54px; flex: none; object-fit: cover; border-radius: 10px; }
.ra-introshot__btns { display: grid; gap: 6px; flex: 1; min-width: 0; }

/* ── #3 · THE SIX DIGITS THAT OPEN ONE AREA ─────────────────────────────────────────────────────
   ⛔⛔ THIS EXISTS BECAUSE IT DID NOT, and no gate could have told me. The panel was written using
   `.ra-code` and `.ra-code__url` as if they were real; they were not, so the six digits — the one
   thing on that panel that matters — would have rendered as ordinary small body text. Valid markup,
   no error, nothing red. It is the same shape as every other bug in this file's history: a control
   that renders and is quietly wrong. Checked by reading the stylesheet, not by trusting the markup.
   ⭐ BIG, SPACED AND SELECTABLE. It gets read down a phone line and typed into another phone, so it
   is sized to be read across a desk and `user-select` is left alone so it can be copied. Tabular
   figures keep the two groups from shifting width as the digits change. */
.ra-code {
  margin: 4px 0 12px;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: .16em;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  color: var(--ink);
}
/* ── THE RESPONSABILE'S LOG — A WINDOW, NOT A WALL ──────────────────────────────────────────────
   Tony, 2026-08-06: "this log window will become super long, so lets give it a fixed size (show 7)
   and add a scroll". One line per save and the app saves as you work, so a busy area writes dozens
   in an evening — unbounded, it pushes everything below it off the screen for ever.
   ⚠️ `overscroll-behavior: contain` is not optional on iOS: without it, reaching the end of this
   list hands the gesture to the page behind and the whole panel jumps. Same rule as every other
   inner scroller in this app.
   ⚠️ Sized in `em` rather than pixels so it still shows seven rows when the phone's text is scaled
   up — a px height would show three.
   ⚠️⚠️ THE NUMBER IS MEASURED, NOT ESTIMATED. A row here is 73px, not the ~40 it looks like in the
   markup: `.row` carries its own padding and the two lines of text. The first attempt at "seven
   rows" was 21em and showed FOUR. 7 rows (511px) + 6 gaps of 6px (36px) = 547, plus a sliver so the
   eighth peeks over the edge and the list reads as scrollable before anyone touches it. */
.ra-log__rows { max-height: 35em; overflow-y: auto; overscroll-behavior: contain; }
/* ⭐ THE GAP BETWEEN THE TWO GROUPS OF THREE, drawn instead of typed (Tony, 2026-08-05). It used to
   be a literal space, which travelled with the copy and had to be deleted by hand at the other end —
   or worse, was not, and the paste was refused. An empty element with a width reads the same across
   a desk and contributes no character, so a selection copies exactly six digits. */
.ra-code__gap { display: inline-block; width: .45em; }
/* ⚠️ An address has no spaces to break at, so without this it pushes the whole panel sideways on a
   phone — the one thing the layout rules here forbid. */
.ra-code__url { overflow-wrap: anywhere; word-break: break-all; }
.ra-gallery__cover { position: absolute; bottom: 4px; left: 4px; width: 22px; height: 22px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 999px; background: rgba(0, 0, 0, .6); color: #fff; cursor: pointer; }
/* ⛔⛔ HOVER IS A LIE ON A PHONE, and it cost a real confusion on 2026-08-03. Tony tapped the ⭐ on a
   room photo and one star stayed GOLD — iOS keeps :hover on the last thing tapped until you tap
   somewhere else. The gold sat on the CELL POSITION, not on a photograph, so after the swap it was
   sitting on the OLD cover: the app appeared to be marking the wrong picture as the cover while
   nothing was tracking a "which is the cover" state at all. His words: "which makes no sense."
   ⭐ `@media (hover: hover)` is the whole fix — a device with a real pointer keeps the feedback, a
   touch screen never gets a state it cannot clear. `:active` gives the tap its own answer instead.
   ⚠️ The delete button had exactly the same fault and had had it for weeks; it is fixed here too,
   because a ✕ that stays red reads as "this photo is about to go". */
@media (hover: hover) {
  .ra-gallery__rm:hover { background: #c0322b; }
  .ra-gallery__cover:hover { background: #c9982f; color: #1a1a1a; }
}
.ra-gallery__rm:active { background: #c0322b; }
.ra-gallery__cover:active { background: #c9982f; color: #1a1a1a; }
.ra-gallery__add { aspect-ratio: 1; display: grid; place-items: center; align-content: center; gap: 2px; border: 1.5px dashed var(--line-strong); border-radius: 9px; color: var(--muted); cursor: pointer; font-size: 12px; font-weight: 600; text-align: center; }
.ra-gallery__add:hover { border-color: var(--c-primary); color: var(--c-primary); }

/* the thumbnail itself is the upload/replace button in Gestione */
.ra-thumb-btn { display: inline-flex; padding: 0; border: 0; background: none; cursor: pointer; flex: 0 0 auto; -webkit-tap-highlight-color: transparent; }
.ra-thumb-btn .ra-thumb { transition: filter .15s ease; }
@media (hover: hover) { .ra-thumb-btn:hover .ra-thumb { filter: brightness(.88); } }

/* item ② — customer-facing photo thumbnails + tap-to-open card */
.pub-thumb { width: 54px; height: 54px; border-radius: 10px; object-fit: cover; flex: 0 0 auto; background: var(--tint-bg); border: 1px solid color-mix(in srgb, var(--c-primary) 35%, transparent); }
.pub-item { cursor: pointer; transition: transform .06s ease; }
.pub-item:active { transform: scale(.99); }
img.offer-card__img { object-fit: cover; }
.notepad__cam { color: var(--c-primary); opacity: .75; vertical-align: middle; }
/* notepad dish with a photo: small thumb on the left, as tall as the dish's rows */
.notepad__dish--photo { display: flex; gap: 10px; align-items: center; }
.notepad__dish--photo .notepad__dish-main { flex: 1 1 auto; min-width: 0; }
.notepad__thumb { width: 46px; height: 46px; flex: 0 0 auto; object-fit: cover; border-radius: 8px; background: var(--tint-bg); border: 1px solid color-mix(in srgb, var(--c-primary) 35%, transparent); }

/* the tap-to-open content card (built in app.js openCard) */
/* safe-area breathing room top & bottom so the modal never tucks under the notch / home bar (Tony 2026-07-18) */
.cardmodal { position: fixed; inset: 0; z-index: 85; display: grid; place-items: center;
  padding: calc(env(safe-area-inset-top) + 16px) 16px calc(env(safe-area-inset-bottom) + 16px);
  background: rgba(0, 0, 0, .9); }
.cardmodal__panel { position: relative; width: min(420px, 100%); max-height: 88vh; max-height: 88svh; overflow: auto; overscroll-behavior: contain;
  background: var(--surface); color: var(--ink); border-radius: 18px; box-shadow: 0 20px 60px rgba(0, 0, 0, .45);
  border: 1px solid color-mix(in srgb, var(--tipo, var(--c-primary)) 30%, var(--line)); }  /* the same light edge as the offer cards */
/* edit / editor modals: fixed × header + fixed footer, only the middle scrolls — the action buttons stay
   put (no scrolling to find them) AND this dodges the iOS repaint bug where the bottom of a long scroll
   sometimes wouldn't paint until you nudged it. */
/* max-height in svh (small-viewport) not %/vh/dvh — on iOS %/vh size to the LAYOUT viewport (behind the
   toolbar/home-indicator) pushing the footer off-screen, and dvh RESIZES live as browser chrome moves →
   the footer visibly jumps mid-gesture. svh = the smallest visible viewport: stable, footer always on
   screen. NOTE: NO -webkit-overflow-scrolling:touch on the body scroller — that legacy property has
   documented WebKit bugs (content inside fails to paint / stops scrolling after a resize, breaks fixed
   descendants); modern iOS momentum-scrolls natively without it. */
.cardmodal--form .cardmodal__panel, .cardmodal--editor .cardmodal__panel { display: flex; flex-direction: column; max-height: 88vh; max-height: 88svh; overflow: hidden; width: min(600px, 100%); }  /* wider on tablet so the editors match the Manager's own width — not a skinny 420 strip */
.cardmodal--form .cardmodal__body, .cardmodal--editor .cardmodal__body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.cardmodal__footer { flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 12px 16px 14px; border-top: 1px solid var(--line); background: var(--surface); box-shadow: 0 -7px 18px rgba(0, 0, 0, .3); z-index: 1; }
.cardmodal__footer .btn { width: 100%; margin: 0; min-height: 46px; font-size: 14px; padding: 0 8px; gap: 6px; }
.cardmodal__footer .btn i { font-size: 16px; }
/* ⭐ A LONE «Fatto» SPANS THE WHOLE ROW (Tony, 2026-08-02: "just make the Fatto button full width").
   The footer is a 2-column grid, so a single button filled half of it and left a hole. With Archivia
   and Elimina present it takes the row above them, which reads correctly too: the way out first, the
   two destructive ones under it. ⚠️ Checked for an existing `.btn--wide` before adding this — two
   rules on one selector let the LAST one win silently, and this file has been bitten by that. */
.cardmodal__footer .btn--wide { grid-column: 1 / -1; }
/* show the FULL photo (the owner's crop), never a re-cropped slice: the wrap sizes to the image,
   which is shown at its natural aspect ratio, bounded to the modal width and a sensible max height. */
/* ⭐ `position: relative` + `cursor: zoom-in` added 2026-08-13 for the full-screen badge a room's own
   gallery now carries (app.js openCard). The badge is `.loc-gallery__expand`, which is absolutely
   positioned — without a positioned parent here it would pin itself to the whole viewport.
   ⛔ EDITED IN PLACE rather than added as a second `.cardmodal__imgwrap` rule further down: two rules
   on one selector is how this stylesheet has been bitten three times, the later one winning silently. */
.cardmodal__imgwrap { position: relative; cursor: zoom-in; width: 100%; background: #0c0c0c; overflow: hidden; border-radius: 18px 18px 0 0; display: grid; place-items: center; }
.cardmodal__img { max-width: 100%; max-height: 62vh; object-fit: contain; display: block; }
.cardmodal__body { padding: 16px 18px 20px; }
/* The ✕ is absolutely placed top-right (40px box, 8px inset → it owns the first 48px). Text that starts at
   the very top of a modal must wrap BEFORE it, or the first line runs under the button (Tony spotted it on
   the "Esci" confirm). Only a leading heading/paragraph is nudged — images and forms keep the full width. */
.cardmodal__body > p:first-child,
.cardmodal__body > h3:first-child { padding-right: 36px; }
.cardmodal__top { display: flex; gap: 12px; justify-content: space-between; align-items: baseline; }
.cardmodal__name { margin: 0; font-size: 20px; }
.cardmodal__price { font-weight: 800; color: var(--c-primary); white-space: nowrap; }
.cardmodal__desc { margin: 8px 0 0; color: var(--muted); }
/* ⭐ THE OPENING PARAGRAPH OF AN ITEM CARD, in the venue's own face (Tony, 2026-08-08). It is the
   card's half of what `.area-intro__lead` already does one screen up, and it is deliberately the SAME
   three properties — the face, the display scale, the ink — so a hotel that changes its font in HQ
   moves both together and neither can drift away from the other.
   ⚠️ Sized one step down (16px against the intro's 17px): this sits inside a modal under a 20px
   title, where the intro sits at the top of a page under nothing. ⛔ The paragraphs after it keep
   `.cardmodal__desc` exactly as it is — the whole point is the contrast between the two. */
.cardmodal__lead { margin: 10px 0 0; font-family: var(--font-display);
  font-size: calc(16px * var(--font-display-scale, 1)); font-weight: 500;
  line-height: 1.45; letter-spacing: .005em; color: var(--ink); }
.cardmodal__lead + .cardmodal__desc { margin-top: 10px; }
/* PIECE 38 — the extra photos of one item, as a scrolling strip of thumbnails under the big picture.
   Tapping one swaps the picture above. The strip scrolls sideways INSIDE the card, so eight room
   photos never make the card itself wider than the phone. */
.cardmodal__shots { display: flex; gap: 7px; overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; margin: 12px 0 2px; padding-bottom: 2px; }
.cardmodal__shots::-webkit-scrollbar { display: none; }
.cardmodal__shot { flex: 0 0 62px; height: 46px; padding: 0; border-radius: 8px; overflow: hidden;
  cursor: pointer; background: var(--c-surface-2, rgba(0, 0, 0, .05));
  border: 2px solid transparent; opacity: .62; transition: opacity .15s, border-color .15s; }
.cardmodal__shot.is-active { opacity: 1; border-color: var(--c-primary); }
.cardmodal__shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* PIECES 3 + 16 — Prenota / Più informazioni, stacked full-width under the description. */
.cardmodal__acts { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.cardmodal__acts .btn { gap: 8px; text-decoration: none; }
.cardmodal__close { position: absolute; top: 8px; right: 8px; width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--muted); cursor: pointer; z-index: 1; line-height: 1; }
.cardmodal__close i { font-size: 22px; display: block; }

/* ⭐⭐ THE × ALWAYS HAS SOMETHING BEHIND IT (Tony, 2026-08-09): *"it is frequently very difficult to
   see. Can we put a circle behind it in the Background colour that was set up in HQ?"* He was looking
   at a bedroom photograph filling the top of a card with a grey × lost in it.
   ⛔⛔ SCOPED TO THE GUEST'S APP, and that scoping is the whole care of this rule. `.cardmodal__close`
   is SHARED with the owner's Manager (restaurantAdmin.js builds two of its own), and Tony asked for
   the customer app only — *"did not have those issues on the other apps"*. The modal is appended to
   `body`, outside the app root, so a descendant selector cannot reach it; `:root:has(.app--guest)`
   can, and `app--guest` is emitted in exactly ONE place in the whole engine (publicApp.js) while the
   Manager mounts a plain `.app`. ⚠️ `:has()` is already relied on 14 times in this file.
   ⭐ THE COLOURS ARE THE CLIENT'S OWN, and the foreground is not guessed: `--brand-header-fg` is the
   readable ink the app already derives from that very background for the header (business.js
   headerFgFor). It is deliberately UNSET for a dark background — which is every client alive — so the
   fallback here must be light, and white is right for exactly that reason. Measured across all eight
   live venues: white on their backgrounds runs 7.2:1 to 21:1, every one far past the 4.5:1 floor.
   ⚠️ The shadow is not decoration: on a panel whose own colour happens to match the header the circle
   would otherwise vanish, and the × would look like it was floating. */
:root:has(.app--guest) .cardmodal__close,
.stay-ask__x {
  border-radius: 50%;
  background: var(--brand-header-bg, rgba(20, 22, 26, .62));
  color: var(--brand-header-fg, #fff);
  box-shadow: 0 1px 6px rgba(0, 0, 0, .3);
}

/* TRACK U — redeem action sheet: a bottom-sheet variant of the card modal (matches the mock). */
.cardmodal--sheet { place-items: end center; padding: 0; --tipo: var(--c-primary); }   /* --tipo is set inline per offer → the sheet carries the offer's colour (reds stay for the confirm/warning) */
.cardmodal--sheet .cardmodal__panel { width: 100%; max-width: 480px; max-height: 92vh; border-radius: 20px 20px 0 0;
  animation: sheet-up .26s cubic-bezier(.2, .8, .2, 1); }
.cardmodal--sheet .btn--primary { background: var(--tipo); border-color: color-mix(in srgb, #000 16%, var(--tipo)); box-shadow: 0 3px 14px color-mix(in srgb, var(--tipo) 34%, transparent); }
.cardmodal--sheet :focus-visible { outline-color: var(--tipo); }   /* the focus ring matches the offer, not the app orange */
@keyframes sheet-up { from { transform: translateY(28px); opacity: .6; } to { transform: none; opacity: 1; } }
.btn--danger { background: var(--danger-solid); color: #fff; }
.btn--danger:hover { filter: brightness(1.05); }
.redeem { padding: 20px 18px calc(20px + env(safe-area-inset-bottom)); }
/* centred, boxless header — matches the "Usa l'offerta" band on the card, then a separator */
.redeem__band { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; margin: 2px 0 0; }
.redeem__gift { color: var(--tipo); line-height: 1; }
.redeem__gift i { font-size: 44px; }
.redeem__band--warn .redeem__gift { color: var(--danger-solid); }
.redeem__sep { border: 0; border-top: 1px solid var(--line); margin: 14px 0; }
.redeem__k { font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: color-mix(in srgb, var(--tipo) 80%, var(--ink)); }
.redeem__k--warn { color: var(--danger-solid); }
.redeem__v { font-size: 22px; font-weight: 800; letter-spacing: -.3px; line-height: 1.15; color: var(--ink); overflow-wrap: anywhere; }
.redeem__sub { margin: 0 0 8px; font-size: 15px; font-weight: 700; color: var(--ink); text-align: center; }
.redeem__desc { margin: 0 0 14px; font-size: 14px; color: var(--muted); line-height: 1.5; text-align: center; }
.redeem__note { display: flex; gap: 12px; align-items: center; border-radius: 12px; padding: 13px 15px; margin: 0 0 16px;
  background: color-mix(in srgb, var(--tipo) 10%, var(--surface)); }
.redeem__note i { flex: none; line-height: 1; display: block; color: color-mix(in srgb, var(--tipo) 82%, var(--ink)); }
.redeem__note p { margin: 0; font-size: 15px; font-weight: 600; line-height: 1.45; color: color-mix(in srgb, var(--tipo) 82%, var(--ink)); }
/* the sheet CTAs speak in CAPS like PRENOTA — standard button size, the voice does the work
   (the quiet "più tardi" / "Annulla" stay lowercase); line-height covers a rare 2-line wrap */
.redeem .btn--primary, .redeem .btn--danger { text-transform: uppercase; line-height: 1.2; }
.redeem__note--warn { background: color-mix(in srgb, var(--danger) 12%, var(--surface)); }
.redeem__note--warn i, .redeem__note--warn p { color: var(--danger-solid); }

/* notepad menu — the à-la-carte courses written on lined paper */
.notepad {
  position: relative;
  margin-top: 4px;
  padding: 22px 20px 30px 29px;
  border-radius: var(--radius-card);
  border: 1px solid rgba(53, 49, 43, .13);
  background-color: #f7f2e4;
  background-image: repeating-linear-gradient(#f7f2e4 0 31px, #ccdae6 31px 32px);
  box-shadow: 0 18px 40px -20px rgba(20, 20, 20, .55), 0 1px 0 rgba(0, 0, 0, .05);
  color: #34312b;
  overflow: hidden;
}
.notepad::before { /* the red margin rule */
  content: ''; position: absolute; top: 0; bottom: 0; left: 15px; width: 2px;
  background: rgba(192, 75, 68, .5);
}
.notepad__title {
  font-family: 'Caveat', cursive; font-weight: 700;
  font-size: 42px; line-height: 1; text-align: center; margin-bottom: 6px;
  color: var(--c-primary);
}
.notepad__course { margin-top: 18px; }
.notepad__course:first-of-type { margin-top: 8px; }
.notepad__course-name {
  font-family: 'Caveat', cursive; font-weight: 700;
  font-size: 28px; line-height: 1.1; margin: 0 0 2px;
  color: var(--c-primary);
}
.notepad__dish { margin: 7px 0; }
.notepad__line { display: flex; align-items: baseline; gap: 8px; }
.notepad__dish-name { font-family: 'Caveat', cursive; font-weight: 600; font-size: 25px; line-height: 1.1; }
.notepad__leader { flex: 1 1 auto; border-bottom: 2px dotted rgba(80, 95, 110, .42); transform: translateY(-0.32em); }
.notepad__price { font-family: 'Caveat', cursive; font-weight: 700; font-size: 25px; white-space: nowrap; color: #34312b; }
.notepad__desc { font-family: 'Caveat', cursive; font-weight: 500; font-size: 21px; line-height: 1.05; margin: 1px 0 0; color: #837a69; }

/* Menu style: MODERN — clean, sans-serif, no paper texture */
.notepad--modern { padding: 20px; background-color: var(--surface); background-image: none; border: 1px solid var(--line); border-radius: var(--radius-card); box-shadow: var(--shadow-sm); color: var(--ink); }
/* subtle on-brand glow from the top so Modern doesn't read as empty (falls back to flat surface) */
@supports (background: color-mix(in srgb, red, blue)) {
  .notepad--modern { background-image:
    repeating-linear-gradient(45deg, color-mix(in srgb, var(--ink) 5%, transparent) 0 1px, transparent 1px 13px),
    radial-gradient(120% 70% at 50% -10%, color-mix(in srgb, var(--c-primary) 9%, transparent), transparent 60%); }
}
.notepad--modern::before { display: none; }
.notepad--modern .notepad__title { font-family: var(--font-ui); font-weight: 800; font-size: 22px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink); }
.notepad--modern .notepad__course-name { font-family: var(--font-ui); font-weight: 700; font-size: 18px; letter-spacing: .06em; text-transform: uppercase; color: var(--c-primary); }
.notepad--modern .notepad__dish-name { font-family: var(--font-ui); font-weight: 600; font-size: 17px; }
.notepad--modern .notepad__price { font-family: var(--font-ui); font-weight: 700; font-size: 17px; color: var(--c-primary); }
.notepad--modern .notepad__desc { font-family: var(--font-ui); font-weight: 400; font-size: 14px; color: var(--muted); }
.notepad--modern .notepad__leader { border: 0; }
/* Moderno only: a hairline after the last dish/drink of each category (not the last category) */
.notepad--modern .notepad__course:not(:last-child) { border-bottom: 1px solid var(--line); padding-bottom: 16px; }

/* Menu style: ELEGANT — refined serif on ivory */
.notepad--elegant { padding: 26px 28px 30px; background: #faf6ee; background-image: none; border: 1px solid rgba(53, 49, 43, .13); box-shadow: var(--shadow-sm); color: #34312b; }
.notepad--elegant::before { display: none; }
.notepad--elegant .notepad__title { position: relative; padding-bottom: 20px; font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 34px; letter-spacing: .01em; color: var(--c-primary); }
/* elegant separator under the title: a faded hairline with a small diamond on it */
.notepad--elegant .notepad__title::before { content: ''; position: absolute; left: 50%; bottom: 9px; transform: translateX(-50%); width: 132px; height: 1px; background: linear-gradient(to right, transparent, rgba(140, 110, 60, .5) 20%, rgba(140, 110, 60, .5) 80%, transparent); }
.notepad--elegant .notepad__title::after { content: ''; position: absolute; left: 50%; bottom: 6px; transform: translateX(-50%) rotate(45deg); width: 7px; height: 7px; background: var(--c-primary); }
.notepad--elegant .notepad__course-name { font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 28px; letter-spacing: .02em; color: var(--c-primary); text-align: center; }
.notepad--elegant .notepad__dish-name { font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 21px; color: #34312b; }
.notepad--elegant .notepad__price { font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 21px; color: #34312b; }
.notepad--elegant .notepad__desc { font-family: var(--font-ui); font-style: italic; font-size: 14px; color: #8a8170; }
.notepad--elegant .notepad__leader { border-bottom: 1px dotted rgba(80, 95, 110, .4); transform: translateY(-0.3em); }

/* Menu-style picker: a mini live mockup of each style (Gestione → Menu) */
.mstyle-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.mstyle { display: flex; flex-direction: column; gap: 6px; padding: 0; background: none; border: 0; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.mstyle__prev { position: relative; display: block; height: 66px; padding: 7px 8px; border-radius: 9px; border: 1px solid var(--line); overflow: hidden; }
.mstyle.is-active .mstyle__prev { border-color: transparent; outline: 2px solid var(--c-primary); outline-offset: 1px; }
.mstyle__label { text-align: center; font-size: 12px; font-weight: 600; color: var(--muted); }
.mstyle.is-active .mstyle__label { color: var(--c-primary); }
.mstyle__course { display: block; line-height: 1.05; }
.mstyle__dish { display: flex; justify-content: space-between; align-items: baseline; gap: 4px; margin-top: 4px; }
.mstyle__name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.mstyle__price { white-space: nowrap; }
/* notepad mini: lined cream paper + red margin + handwritten */
.mstyle__prev--notepad { background-color: #f7f2e4; background-image: repeating-linear-gradient(#f7f2e4 0 11px, #ccdae6 11px 12px); }
.mstyle__prev--notepad::before { content: ''; position: absolute; top: 0; bottom: 0; left: 5px; width: 1px; background: rgba(192, 75, 68, .55); }
.mstyle__prev--notepad .mstyle__course { font-family: 'Caveat', cursive; font-weight: 700; font-size: 14px; color: var(--c-primary); padding-left: 4px; }
.mstyle__prev--notepad .mstyle__dish { padding-left: 4px; }
.mstyle__prev--notepad .mstyle__name, .mstyle__prev--notepad .mstyle__price { font-family: 'Caveat', cursive; font-weight: 600; font-size: 12px; color: #34312b; }
/* modern mini: clean dark card + sans */
.mstyle__prev--modern { background-color: var(--surface); background-image: repeating-linear-gradient(45deg, color-mix(in srgb, var(--ink) 10%, transparent) 0 1px, transparent 1px 7px); }
.mstyle__prev--modern .mstyle__course { font-family: var(--font-ui); font-weight: 700; font-size: 8px; letter-spacing: .06em; text-transform: uppercase; color: var(--c-primary); }
.mstyle__prev--modern .mstyle__name { font-family: var(--font-ui); font-weight: 600; font-size: 10px; color: var(--ink); }
.mstyle__prev--modern .mstyle__price { font-family: var(--font-ui); font-weight: 700; font-size: 10px; color: var(--c-primary); }
/* elegant mini: ivory + serif */
.mstyle__prev--elegant { background: #faf6ee; }
.mstyle__prev--elegant .mstyle__course { font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 14px; color: var(--c-primary); }
.mstyle__prev--elegant .mstyle__name, .mstyle__prev--elegant .mstyle__price { font-family: 'Cormorant', Georgia, serif; font-weight: 600; font-size: 12px; color: #34312b; }

/* Condividi share sheet: white QR card + link */
.share-qr { width: 220px; max-width: 72vw; aspect-ratio: 1 / 1; margin: 0 auto; background: #fff; border-radius: 14px; padding: 12px; box-sizing: border-box; }
.share-qr svg { width: 100%; height: 100%; display: block; }
.share-url { margin: 14px 0 0; text-align: center; font-size: 18px; font-weight: 600; line-height: 1.4; color: var(--ink); word-break: break-all; }

/* Menu disponibili — the menu library thumbs (2 per row, card-like) */
.menu-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
/* square cards, 2 per row — tap opens the menu/list editor modal (Tony 2026-07-18); no selected-ring anymore.
   ⭐ "COPERTINA" (Tony, 2026-07-24 — his pick from 6 mocked options, "less is more"): the NAME is the whole
   design, like the cover of a menu. No icon, no "N piatti" count (the panel header already totals them).
   The section colour arrives per-card as --pc (inline, e.g. #c15c2c Ristorante / #6c5ce7 Bar) and does two
   jobs: a 10% wash on the card and the small rule under the name. Content is CENTRED as one stack —
   the old `space-between` left 32.7px above and 15px below (measured), which read as "pushed down". */
.menu-thumb { position: relative; display: flex; flex-direction: column; gap: 11px; align-items: center; text-align: center; justify-content: center; height: 172px; overflow: hidden; padding: 14px 10px; background: color-mix(in srgb, var(--pc, var(--c-primary)) 10%, var(--surface-2)); border: 1px solid color-mix(in srgb, var(--pc, var(--c-primary)) 26%, var(--line)); border-radius: 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: border-color .15s ease, background-color .15s ease; }
.menu-thumb:active { background: color-mix(in srgb, var(--pc, var(--c-primary)) 18%, var(--surface-2)); border-color: color-mix(in srgb, var(--pc, var(--c-primary)) 40%, var(--line)); }
.menu-thumb__act { position: absolute; top: 6px; right: 6px; }
.menu-thumb__main { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; flex: 0 0 auto; min-height: 0; overflow: hidden; }
/* the rule under the name — the only other mark on the card */
.menu-thumb__main::after { content: ''; display: block; width: 34px; height: 1.5px; background: var(--pc, var(--c-primary)); opacity: .75; margin: 9px auto 0; }
/* 22px, not 23: MEASURED — the card's content box is 148px and "Menu Degustazione" (a realistic name)
   renders "Degustazione" at 153.5px at 23px, so the last letter dropped alone onto a third line. 22px = 147px,
   fits. `break-word` (not `anywhere`) breaks only a word that genuinely cannot fit; `balance` evens the lines.
   ⚠️ Deliberately NOT `hyphens: auto` — the document is lang="en", so it would hyphenate Italian by English rules. */
/* ⚠️ min-height = EXACTLY two lines (2 × 1.1em), and it is what keeps the SWITCHES ALIGNED across a row.
   Centring the stack (above) made the switch float with the text, so a 1-line name ("Menu Fisso") and a
   2-line one ("Menu à la Carta") put their two identical switches at different heights, side by side —
   caught on Tony's own screenshot 2026-07-24. Reserving two lines makes every card identical inside, so
   the name still sits centred AND the switches return to one line. A 1-line name centres in the reserved box. */
.menu-thumb__name { flex: none; display: flex; align-items: center; justify-content: center; min-height: 2.2em; font-family: var(--font-display); font-weight: 600; font-size: calc(22px * var(--font-display-scale, 1)); line-height: 1.1; letter-spacing: .01em; color: var(--ink); overflow-wrap: break-word; text-wrap: balance; }
.menu-thumb__show { flex: none; }
.menu-thumb__show { display: inline-flex; align-items: center; gap: 7px; }
/* the editable menu/list name at the top of the editor modal (replaces the old pencil-rename) */
.listedit__title { text-align: center; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.menu-thumb__showlbl { font-size: 11.5px; color: var(--muted); }

/* customer Menu page: tabs over the active menus */
.menu-tabs { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; }
/* squared buttons (matches the Gestione admin tabs) + a food/drink icon; 1px smaller than before */
.menu-tab { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px; padding: 9px 13px; border-radius: 10px; border: 1px solid var(--line); background: transparent; color: var(--muted); font-weight: 600; font-size: 13px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.menu-tab i { flex: 0 0 auto; font-size: 16px; }
/* §C step 3 — I SERVIZI's shelf strip: FIXED and WRAPPING, never sliding (§17).
   A hotel's shelf can hold six or seven tabs and the guest has to SEE that they exist; a row that
   scrolls sideways hides half of them behind an edge nobody thinks to drag. ⛔ Hotel only — the
   restaurant's menu strip keeps sliding, which is right for two or three menus. */
/* ⚠️ CENTRED, on Tony's instruction (2026-07-28) — a wrapping strip left-aligned leaves a ragged gap
   on the last row that reads as a mistake rather than as a layout. ⛔ Hotel only: this class exists on
   no restaurant or bar page, where the tabs are a single sliding row that must stay left-aligned. */
/* ⚠️ UNUSED SINCE 2026-07-30 and kept deliberately, not by accident. The hotel's I Servizi strip used
   to wrap to two or three rows; Tony replaced it with the sliding strip Pane&Vino already uses. The
   rules stay because the class costs nothing and a future vertical with three short areas may want
   them — but NOTHING emits menu-tabs--wrap today. Delete both lines the day that is still true. */
.menu-tabs--wrap { flex-wrap: wrap; overflow-x: visible; row-gap: 8px; justify-content: center; }
.menu-tabs--wrap .menu-tab { flex: 0 1 auto; }
/* §C step 3 — a SUB-AREA row in Impostazioni › Aree dell'App. Indented, with a hairline running down
   the gap, so the eye reads "these belong to the one above" without any extra words. The switch stays
   at the right edge, in line with its parent's, because they are the same kind of control. */
/* ⚠️ `width: auto` is the whole fix. `.row` carries a hard `width: 100%`, so a left margin pushed the
   box OUT of the panel instead of narrowing it — the indented rows ended up 20px wider than their
   parent and their switches no longer lined up. Auto lets the grid item shrink by its own margin, so
   every RIGHT edge stays flush however deep the nesting goes.
   ⚠️ Two levels, because the nesting is two levels deep in meaning: I Servizi holds Il Ristorante,
   and Il Ristorante holds Specialità del Giorno (§17.7). */
/* §C step 3 — the way back out of a sub-area's own screen. Reads as a step backwards, not a button:
   quiet, left-aligned, the arrow doing the work. */
/* §C step 4 — the room-tag chips. `.tagrow`/`.tagchip` are what a GUEST sees on a card; the picker
   in the Manager reuses `.fchip`, the allergen chip, on purpose (same gesture, same proven styling). */
.tagrow { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.tagchip { display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 650; line-height: 1.2; color: var(--muted);
  border: 1px solid var(--line); background: color-mix(in srgb, var(--c-primary) 6%, transparent); }
.tagchip--more { font-style: italic; }
.tagico { display: inline-flex; width: var(--s, 15px); height: var(--s, 15px); flex: 0 0 auto; }
.tagico svg { width: 100%; height: 100%; }
.mgr-back { display: inline-flex; align-items: center; gap: 8px; padding: 8px 2px; margin: -4px 0 2px; background: none; border: 0; color: var(--muted); font: inherit; font-weight: 600; font-size: 14px; cursor: pointer; }
.mgr-back:hover { color: var(--ink); }
.row--sub { margin-left: 20px; width: auto; position: relative; }
.row--sub2 { margin-left: 40px; width: auto; position: relative; }
.row--sub::before, .row--sub2::before { content: ''; position: absolute; left: -12px; top: 6px; bottom: 6px; width: 2px; border-radius: 2px; background: color-mix(in srgb, var(--c-primary) 30%, transparent); }
.menu-tab.is-active { background: var(--c-primary); border-color: transparent; color: var(--on-primary, #fff); }
/* the menu editor connects to "Menu disponibili" as one unit (.menu-pair) — no orange ring; it uses the normal card border. */
/* "Menu disponibili" + the selected-menu panel read as ONE connected unit (TRACK Q): they touch, facing corners flattened. */
.menu-pair { display: flex; flex-direction: column; }
.menu-pair > .card:first-child { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.menu-pair > .card:last-child { border-top-left-radius: 0; border-top-right-radius: 0; }
.menu-pair--folded > .card:first-child { border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); } /* rounded again when the pair is folded */
/* "Nascosta ai clienti" pill — stamped on a Gestione panel whose customer-facing section is toggled off (TRACK Q). */
/* "Nascosti ai clienti" — a full-width red warning row that bleeds to the card edges (cancels the 16px
   card padding). Sits under the panel header: a red band when the panel is open, a footer band when folded. */
.hidden-tag { display: block; margin: 3px -16px 0; padding: 4px 16px; background: #c0322b; color: #fff; text-align: center; font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.card.is-collapsed .hidden-tag { margin-bottom: -16px; border-radius: 0 0 var(--radius-card) var(--radius-card); }
/* A section switched OFF (its .hidden-tag band is present) reads as INACTIVE: icon/title/content greyed +
   the shadow removed (flattened). The red banner AND the on/off switch stay full so it's clearly re-enableable. */
.card:has(.hidden-tag) { box-shadow: none; }
.card:has(.hidden-tag) .panel-ico,
.card:has(.hidden-tag) .section__title,
.card:has(.hidden-tag) .help-btn,
.card:has(.hidden-tag) .card__body > :not(.panel-head):not(.hidden-tag) { opacity: .42; }
.card:has(.hidden-tag) .panel-head::before { opacity: .42; }   /* the ">" collapse chevron */
/* Non-blocking save indicator (Manager): bottom-centre pill; NEVER blocks input (pointer-events:none). TRACK Q. */
.savepill { position: fixed; left: 50%; bottom: 22px; transform: translate(-50%, 16px); z-index: 200; display: inline-flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: 999px; font-size: 13px; font-weight: 600; color: #fff; box-shadow: 0 8px 26px rgba(0,0,0,.20); opacity: 0; pointer-events: none; transition: opacity .22s ease, transform .22s ease; }
.savepill.is-shown { opacity: 1; transform: translate(-50%, 0); }
.savepill--saving { background: #3a3a3c; }
.savepill--saved { background: #2f8a4e; }
.savepill--error { background: #c0322b; }
/* #89 — the ONLY state that may be tapped. The pill is pointer-events:none everywhere else so it can
   never swallow a tap meant for the screen behind it; the failed one has something to offer. */
.savepill--retry { pointer-events: auto; cursor: pointer; }
.savepill__spin { width: 13px; height: 13px; border: 2px solid rgba(255,255,255,.35); border-top-color: #fff; border-radius: 50%; animation: savespin .7s linear infinite; }
@keyframes savespin { to { transform: rotate(360deg); } }
/* admin header: "< App"  ·  centered "Osteria del Borgo | Admin"  ·  balance */
.apphead__inner--admin { display: flex; align-items: center; justify-content: space-between; gap: 8px; position: relative; }
.admin-head__title { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); white-space: nowrap; font-size: 24px; font-weight: 600; color: var(--header-fg); pointer-events: none; }
.admin-head__biz { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.admin-head__suffix { flex: none; opacity: .5; font-weight: 600; }
.apphead__bal { flex: 0 0 36px; }
/* Manager primary navigation — the two areas as a bold, full-width segmented control (TRACK Q header). */
/* The Manager tabs are a SECOND BAND of the header: full-bleed, square, its own light tint, a white hairline
   at the foot. They live inside .apphead, so the sticky behaviour and the header's shadow come for free —
   always reachable, so you can jump tabs from anywhere without scrolling back up. */
.apphead__tabs { border-bottom: 1px solid color-mix(in srgb, var(--header-fg) 15%, transparent); } /* no background of its own → it IS the header's colour; soft hairline at the foot */
.apphead:has(.apphead__tabs) { border-bottom: 0; } /* the band carries the line — never draw two */
.admin-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; padding: 6px 0; }
.admin-tab2 { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 13px 5px; border: 0; border-radius: 10px; background: transparent; color: var(--muted); font-weight: 700; font-size: 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .12s ease, color .12s ease; }
.admin-tab2 i { flex: 0 0 auto; }
.admin-tab2 span { white-space: nowrap; }
.admin-tab2.is-active { background: var(--c-primary); color: var(--on-primary, #fff); box-shadow: var(--shadow-sm); }
.admin-tabs--3 { grid-template-columns: 1fr 1fr 1fr; }
.admin-tabs--3 .admin-tab2 { font-size: 13px; padding: 12px 3px; gap: 5px; }
/* UNDERLINE TABS (only inside the header band). The floating pill is dropped: the active tab is marked by a
   rule in the signature colour sitting ON the band's bottom hairline, which ties the tab to the content below
   it. Nothing has a shape of its own, so nothing can look like it's floating. Placed after the rules above so
   it wins on source order without a specificity fight. */
.apphead__tabs .admin-tabs { gap: 0; padding: 0; }
.apphead__tabs .admin-tab2 {
  position: relative; border-radius: 0; background: none; box-shadow: none;
  padding: 15px 4px 14px; color: color-mix(in srgb, var(--header-fg) 55%, transparent);
}
.apphead__tabs .admin-tab2.is-active { background: none; box-shadow: none; color: var(--c-primary); }
.apphead__tabs .admin-tab2.is-active::after {
  content: ''; position: absolute; left: 12%; right: 12%; bottom: -1px; height: 3px;
  background: var(--c-primary); border-radius: 3px 3px 0 0;
}
/* Big centred group headings that split the Gestione list into I Menu / Il Tuo Marketing / Extra —
   same size as the "OB Manager" header title so the two read as one hierarchy. */
.mgr-group { display: flex; align-items: center; justify-content: center; gap: 9px; text-align: center; font-size: 24px; font-weight: 700; color: var(--ink); margin: 16px 0 0; letter-spacing: -.01em; line-height: 1.15; }
.mgr-group .ti { flex: none; } /* the page-title icon inherits the title colour — same monochrome style as the Statistiche icon */
/* Statistiche screen */
.statcard { background: color-mix(in srgb, var(--c-primary) 10%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-primary) 26%, var(--line)); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center; }
.statcard__n { font-size: 34px; font-weight: 800; color: var(--c-primary); line-height: 1; font-variant-numeric: tabular-nums; }
.statcard__l { font-size: 12.5px; color: var(--muted); }
.statrow { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; }
.statrow__t { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.statrow__title { font-weight: 700; font-size: 15px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.statrow__sub { font-size: 12px; color: var(--muted); }
.statrow__n { flex: none; font-size: 20px; font-weight: 800; color: var(--c-primary); font-variant-numeric: tabular-nums; }
/* tappable stat rows (accordion, one open at a time) + the offer thumb + the detail panel */
.statrow--btn { width: 100%; font: inherit; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: border-color .15s ease; }
.statrow__thumb { width: 44px; height: 44px; flex: 0 0 auto; border-radius: 10px; object-fit: cover; background: var(--tint-bg); border: 1px solid color-mix(in srgb, var(--c-primary) 30%, transparent); }
.statrow__thumb--ph { display: grid; place-items: center; color: var(--c-primary); }
.statrow__thumb--gold { color: #d9a23c; border-color: color-mix(in srgb, #d9a23c 45%, transparent); background: color-mix(in srgb, #d9a23c 12%, var(--surface)); }
.statrow__chev { flex: none; display: inline-flex; color: var(--muted); transition: transform .2s ease; }
.statitem.is-open .statrow { border-color: color-mix(in srgb, var(--c-primary) 45%, var(--line)); border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.statitem.is-open .statrow__chev { transform: rotate(180deg); }
.statrow__detail { padding: 14px; background: color-mix(in srgb, var(--c-primary) 4%, var(--surface)); border: 1px solid color-mix(in srgb, var(--c-primary) 45%, var(--line)); border-top: 0; border-radius: 0 0 10px 10px; }
.statmini-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.statmini { display: flex; flex-direction: column; gap: 1px; padding: 10px 12px; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; }
.statmini__n { font-size: 17px; font-weight: 800; color: var(--c-primary); font-variant-numeric: tabular-nums; }
.statmini__l { font-size: 11.5px; color: var(--muted); }
.statnote { margin: 10px 0 0; font-size: 12px; color: var(--muted); line-height: 1.45; }
/* Statistiche charts — hand-drawn bars, no libraries: the 30-day rhythm + the weekday view */
.statchart { margin-top: 12px; padding: 14px 16px 12px; background: var(--surface); border: 1px solid var(--line); border-radius: 12px; }
.statchart__head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 12px; }
.statchart__title { font-weight: 700; font-size: 14.5px; color: var(--ink); }
.statchart__range { font-size: 12px; color: var(--muted); }
.statchart__range strong { color: #d9a23c; }
.statchart__bars { display: flex; align-items: flex-end; gap: 3px; height: 84px; }
.statchart__bar { flex: 1 1 0; min-width: 0; height: calc(var(--h, 0) * 0.84px); min-height: 3px; border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--c-primary) 55%, var(--surface)); }  /* --h is 0–100 (unitless) */
.statchart__bar[style*="--h:0"] { background: color-mix(in srgb, var(--ink) 10%, var(--surface)); }  /* zero days: a quiet stub */
.statchart__bar.is-today { background: var(--c-primary); }
.statchart__axis { display: flex; justify-content: space-between; margin-top: 6px; font-size: 10.5px; color: var(--muted); }
.statweek { display: flex; gap: 8px; }
.statweek__col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 4px; }
.statweek__n { font-size: 11px; font-weight: 650; color: var(--muted); font-variant-numeric: tabular-nums; line-height: 1; }
.statweek__bar { width: 100%; max-width: 34px; height: calc(var(--h, 0) * 0.64px); min-height: 3px; border-radius: 4px 4px 0 0;
  background: color-mix(in srgb, var(--c-primary) 55%, var(--surface)); }
.statweek__bar[style*="--h:0"] { background: color-mix(in srgb, var(--ink) 10%, var(--surface)); }
.statweek__d { font-size: 10.5px; color: var(--muted); line-height: 1; }
.statweek__col.is-best .statweek__bar { background: #d9a23c; }
.statweek__col.is-best .statweek__n { color: #d9a23c; font-weight: 800; }
.statweek__col.is-best .statweek__d { color: var(--ink); font-weight: 700; }
/* Collapsible Manager panels (TRACK Q): tap a panel header to fold it; chevron shows state; the "+" hides when folded. */
.panel-head { position: relative; min-height: 42px; display: flex; align-items: center; padding-left: 15px; cursor: pointer; -webkit-tap-highlight-color: transparent; } /* fixed height so the title never shifts when the "+" hides */
.panel-head::before { content: ''; position: absolute; left: -4px; top: 50%; width: 7px; height: 7px; border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted); transform: translateY(-55%) rotate(45deg); transition: transform .18s ease; } /* the ">" sits ~12px from the panel edge, matching the chevron→icon gap (same in Gestione + Impostazioni) */
.card.is-collapsed .panel-head::before { transform: translateY(-50%) rotate(-45deg); }
.card.is-collapsed .card__body > :not(.panel-head):not(.panel-keep) { display: none; }
.app main .card { scroll-margin-top: calc(125px + env(safe-area-inset-top)); } /* when a panel is scrolled open, clear the sticky header AND the sticky tabs */
/* revealIntoView() anchors (Tony's "move to the top" rule): the tapped control scrolls to the top of
   the view with its opened content below. scroll-margin-top clears each shell's sticky header — the
   Manager stat row sits under header+tabs (125px), the customer carta toggle under the app header
   (~76px); HQ has no sticky header so a small gap is enough. */
.app main .statrow--btn { scroll-margin-top: calc(125px + env(safe-area-inset-top)); }
.carta-code-toggle { scroll-margin-top: calc(76px + env(safe-area-inset-top)); }
[data-app-edit], [data-cv-edit] { scroll-margin-top: 14px; }
.card.is-collapsed .panel-head button:not(.help-btn) { display: none; } /* the "?" help stays visible next to the name even when folded */
/* "Lista raggruppata" look (restaurantAdmin.js GESTIONE_GROUPED): a group's sector cards merge into ONE panel
   with hairline dividers, a slightly bigger icon badge, and a detail line under each title. Uses the same
   surface/line/radius tokens as .card, so colours are identical. The class is only present when the flag is on. */
.gest-group { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-card); box-shadow: var(--shadow-sm); overflow: hidden; }
.gest-group > .card { border: 0; border-radius: 0; box-shadow: none; background: transparent; overflow: visible; }
.gest-group > .card + .card { border-top: 1px solid var(--line); }
.gest-group > .card > .card__body { padding-top: 20px; padding-bottom: 20px; } /* a touch taller rows (was 16px) */
.gest-group .panel-ico { width: 41px; height: 41px; border-radius: 12px; } /* ~1.2× the base grouped icon */
.gest-group .panel-ico i { font-size: 22px !important; } /* bump the glyph inside the bigger grouped badge (overrides icon()'s inline 15px); Disponibilità, ungrouped, stays 15px */
/* Pointer at the foot of Gestione when a section is switched off, so a hidden section is never "lost".
   Outlined in the business's signature colour (--c-primary) with an attention icon, so it actually gets seen. */
.gest-hidden-note {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  margin: 2px 0 0; padding: 12px 14px;
  border: 1.5px solid color-mix(in srgb, var(--c-primary) 60%, transparent);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--c-primary) 8%, transparent);
  color: var(--c-primary); font-size: 13px; font-weight: 600; text-align: center;
}
.gest-hidden-note .ti { flex: none; }
.section__sub { color: var(--muted); opacity: .5; font-size: 12.5px; line-height: 1.3; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* One FIXED size for every Manager panel title that has an icon badge — both the direct-sibling layout
   (panelHead: Archivio, menu/drink editors) AND the nested layout (sectionHead: Il Ristorante, Il Bar,
   Specialità, Eventi & Offerte, Offerte Automatiche, Carta Fedeltà, Disponibilità e Turni). Customer
   .section__title (no .panel-ico anywhere) stays 22px. */
/* Every Manager section-panel title: one fixed size, a touch dimmed (75%), so the full-white group titles
   lead the hierarchy. Manager-scoped via .container--wide (the customer app uses .container, unaffected);
   the panel-ico/.menupanel selectors also cover any Manager title rendered outside .container--wide. */
.container--wide .section__title,
.panel-ico ~ .section__title,
.cluster:has(.panel-ico) .section__title,
.menupanel .section__title { font-size: 18px; color: color-mix(in srgb, var(--ink) 75%, var(--surface)); } /* names at 75%, not full white */
/* per-panel identity badge (Gestione): a small tinted square, its colour set inline via --pi.
   Stays visible when the panel is collapsed, so each panel keeps its identity at a glance. */
/* ⭐ THE "?" STAYS ON THE TITLE'S ROW (Tony, 2026-07-30). .cluster wraps by design — correct nearly
   everywhere, wrong here: "Regala un mese, ricevi un mese" is long enough that on a phone the help
   button had nowhere to go but the next line, which read as a stray control belonging to nothing.
   ⚠️ SCOPED TO .pp-head. Un-wrapping .cluster globally would reach dozens of panels that rely on it.
   ⚠️ min-width: 0 on the inner cluster is what actually lets the title give way — a flex item's
   default min-width is auto, so without it the title refuses to shrink and pushes the "?" out
   anyway, nowrap or not. Same rule that caused the Carta Fedeltà overflow earlier this week.
   ⭐ 18 → 16px, which Tony sanctioned ("if you need reduce the size of the title so that it fits").
   At 16px the longest of the two readings fits a 390px screen with the icon and the "?" beside it. */
/* ⛔⛔ BOTH clusters, and the first version only did the outer one — which fixed the "?" and broke
   something else instead: the INNER cluster (icon + title) wraps too, and once min-width:0 let it
   shrink, the title dropped BELOW the gift icon. Tony sent it back with the right reference point:
   "the icon and title and ? on the fucking same row as Area Riservata below."
   ⭐ Area riservata's markup is IDENTICAL to this one — the only difference is that "Area riservata"
   is 14 characters and "Regala un mese, ricevi un mese" is 30. So this is not a structural fix, it
   is purely about making a long title behave in a row that was only ever tested with a short one.
   ⚠️ No ellipsis on purpose: if the title ever cannot fit it wraps to two lines beside the icon,
   which is ugly but readable. A truncated "Regala un mese, ricevi un…" would read as a bug. */
.pp-head, .pp-head > .cluster { flex-wrap: nowrap; }
.pp-head > .cluster { min-width: 0; flex: 1 1 auto; }
/* ⛔⛔ `.pp-head .section__title` (0,2,0) SILENTLY LOST to `.cluster:has(.panel-ico) .section__title`
   (0,3,0) a few rules above — the declaration was right there in the file and the title kept
   rendering at 18px and wrapping to two lines. Measured: 56px row against Area riservata's 29px.
   ⚠️ This is the two-rules-one-selector trap wearing its other face, and the one smoke-css-twins.js
   explicitly CANNOT see: not a duplicate selector, a DIFFERENT selector that outranks. The gate can
   only prove the line exists; only the browser proves it wins. Rendering the real markup against the
   deployed stylesheet is what caught it.
   ⭐ .pp-head.cluster is 0,3,0 — a tie, and this block sits later, so it takes it. */
.pp-head.cluster .section__title { font-size: 16px; }
.panel-ico { flex: 0 0 auto; width: 29px; height: 29px; display: grid; place-items: center; border-radius: 9px; color: var(--pi, var(--c-primary)); background: color-mix(in srgb, var(--pi, var(--c-primary)) 15%, var(--surface)); }
/* per-panel "?" help — sits in the freed header corner; muted so it stays secondary to the identity badge */
.help-btn { flex: 0 0 auto; display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; background: none; color: var(--muted); border-radius: 50%; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color .15s ease, background .15s ease; }
.help-btn:active { color: var(--c-primary); background: var(--tint-bg); }
@media (hover: hover) { .help-btn:hover { color: var(--c-primary); background: var(--tint-bg); } }
.help-body { color: var(--ink); font-size: 14.5px; line-height: 1.55; }
.help-body p { margin: 0 0 10px; }
.help-body p:last-child { margin-bottom: 0; }
/* "Guida passo passo" (2026-07-26): every task panel's help card carries an explanation, a hairline,
   then numbered steps. The rule is Tony's: describe the ACTION, not the button label — the instances
   refactor will rename things and a guide pinned to today's labels would lie the day it lands. */
.help-body hr { border: 0; border-top: 1px solid var(--line); margin: 16px 0 14px; }
.help-body h4 { margin: 0 0 10px; font-size: 12.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--c-accent); }
.help-body ol { margin: 0 0 10px; padding-left: 0; list-style: none; counter-reset: hstep; }
.help-body ol li { counter-increment: hstep; position: relative; padding-left: 30px; margin-bottom: 9px; }
.help-body ol li::before { content: counter(hstep); position: absolute; left: 0; top: 1px;
  width: 21px; height: 21px; border-radius: 50%; display: grid; place-items: center;
  font-size: 11.5px; font-weight: 700; color: var(--on-accent, #1a1206);
  background: var(--c-accent); }
.help-body ol li:last-child { margin-bottom: 0; }
.help-body ol + h4 { margin-top: 16px; }
/* Each part of the guide is a COLLAPSED <details> (Tony 2026-07-26): the Eventi card runs to 16 steps
   across four parts, and a card that opens on a 16-step scroll is not a card anyone consults during
   service. Everything stays in ONE place — it just opens small and you expand the part you need.
   Native <details>: no JS, keyboard- and screen-reader-friendly for free. */
.help-body details { border-top: 1px solid var(--line); }
.help-body details:last-of-type { border-bottom: 1px solid var(--line); }
.help-body details > summary {
  list-style: none; cursor: pointer; padding: 11px 26px 11px 0; position: relative;
  font-weight: 700; font-size: 14px; color: var(--ink); -webkit-tap-highlight-color: transparent;
}
.help-body details > summary::-webkit-details-marker { display: none; }
.help-body details > summary::after {
  content: ''; position: absolute; right: 6px; top: 50%; width: 7px; height: 7px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: translateY(-70%) rotate(45deg); transition: transform .18s ease;
}
.help-body details[open] > summary::after { transform: translateY(-30%) rotate(-135deg); }
.help-body details > *:not(summary) { margin-bottom: 12px; }
.help-body details > ol { padding-top: 2px; }
/* the accordion scrolls the opened part to the top — leave it a little air off the modal edge */
.help-body details { scroll-margin-top: 10px; }
/* per-turno "lista principale" button — full width, squared corners (deliberately not a pill) */
.turno-main { display: flex; align-items: center; gap: 8px; width: 100%; margin-top: 2px; padding: 10px 12px; border-radius: 8px; border: 1px dashed var(--line); background: transparent; color: var(--muted); font-family: var(--font-ui); font-weight: 600; font-size: 13.5px; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.turno-main.is-set { border-style: solid; border-color: color-mix(in srgb, var(--c-primary) 40%, var(--line)); color: var(--ink); background: color-mix(in srgb, var(--c-primary) 6%, var(--surface)); }
.turno-main > span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* the ONE "Aggiungi…" button — full width, squared, subtle (replaces every header "+") */
.add-btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 11px 12px; border-radius: 8px; border: 1px dashed color-mix(in srgb, var(--muted) 42%, var(--line)); background: transparent; color: var(--ink); font-family: var(--font-ui); font-weight: 600; font-size: 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.add-btn:active { background: color-mix(in srgb, var(--muted) 9%, var(--surface)); }
.add-btn--danger { border-color: color-mix(in srgb, var(--danger-solid) 48%, var(--line)); color: var(--danger-solid); }
.add-btn--danger:active { background: color-mix(in srgb, var(--danger-solid) 9%, var(--surface)); }
/* the selected menu/drink EDITOR sits a shade lighter than its picker above, so the picker reads as a
   section header and the two separate visually (menu-pair = picker card + this editor card). */
/* (Session 30: the editor's lighter shade removed — one uniform dark background, Tony's call.) */
/* The selected menu/list NAME is the zone's real headline — same scale as the group titles (.mgr-group). */
.menupanel__name { font-size: 24px; font-weight: 700; color: var(--ink); letter-spacing: -.01em; line-height: 1.15; }
/* Item rows read as CARDS — the same surface recipe as the Offerte Automatiche panels. Applies in the
   menu/drink editors and in every .card--rows panel (Specialità · Eventi & Offerte · Carta Fedeltà · Archivio). */
.menupanel .row, .card--rows .row { background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px; padding: 11px 12px; }
.is-hidden { display: none !important; }
.ge-day { flex: 0 0 82px; font-size: 13px; color: var(--muted); }
.ge-day + .input { flex: 1; width: auto; min-width: 0; }

/* ============================================================================
   Manager app — PIN lock screen (managerApp.js). A clean app-style passcode pad.
   --mgr-accent is set inline from the business brand colour.
   ============================================================================ */
.mgrgate { min-height: 100svh; display: grid; place-items: safe center; padding: 28px 24px; background: var(--brand-header-bg, var(--tint-bg)); overflow-y: auto; } /* a business with a dark brand header (Pane&Vino = olive) uses it as the login bg too; others keep the mattone tint */
/* The transient boot splash ("Un momento…", app.js adminGate): NO solid ground — the brand panel
   belongs to the real login screen only. Painted on the boot splash it flashed brand-colour between
   two dark frames on every open (Tony 2026-07-24). Transparent rides the already-painted texture. */
.mgrgate--quiet { background: none; }
/* Pane&Vino: warm the login's dark surfaces (inputs / Crea account / social / PRESTO pills) into the
   olive family so nothing reads brown against the olive bg. Scoped by data-property → other clients keep
   their charcoal surfaces. The Accedi button (mattone) + lock/MANAGER (gold) are untouched. */
:root[data-property="osteria-main"] .mgrgate { --surface: #272d1c; --tint-bg: #313823; }
.mgrgate__card { width: 100%; max-width: 330px; text-align: center; }
.mgrgate__lock { width: 66px; height: 66px; margin: 0 auto 16px; display: grid; place-items: center; border-radius: 50%; color: var(--on-accent, #fff); background: var(--mgr-accent); box-shadow: 0 12px 30px rgba(0,0,0,.18); }
.mgrgate__brand { font-size: 21px; font-weight: 800; letter-spacing: -.01em; margin: 0; color: var(--ink); }
.mgrgate__tag { font-size: 11.5px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; color: var(--mgr-accent); margin: 3px 0 0; }
.mgrgate__sub { color: var(--muted); font-size: 14.5px; margin: 18px 0 0; }
.mgrgate__dots { display: flex; gap: 15px; justify-content: center; margin: 16px 0 0; }
.mgrdot { width: 13px; height: 13px; border-radius: 50%; border: 1.5px solid var(--mgr-accent); transition: background .12s ease; }
.mgrdot.is-on { background: var(--mgr-accent); }
.mgrgate__err { color: var(--danger, #c0322b); font-size: 13px; margin: 11px 0 0; min-height: 1.1em; }
.mgrgate__pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 12px auto 0; max-width: 272px; }
.mgrkey { width: 72px; height: 72px; justify-self: center; border-radius: 50%; border: 1px solid var(--line); background: var(--surface); font-size: 25px; font-weight: 500; color: var(--ink); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background .1s, transform .05s; }
.mgrkey:active { background: var(--tint-bg); transform: scale(.95); }
.mgrkey--ghost { border: 0; background: none; pointer-events: none; }
.mgrkey--del { font-size: 21px; border: 0; background: none; }
.mgrgate__note { color: var(--muted); font-size: 11.5px; margin: 22px 0 0; line-height: 1.4; }
.mgrgate__link { display: inline-block; margin: 13px 0 0; font-size: 13.5px; color: var(--mgr-accent); text-decoration: none; font-weight: 600; }
/* ⭐ THE STAFF WAY IN, on the owner's login screen. Deliberately QUIET — it is the door for one
   person in a hundred who opens this page, and it must never compete with Sign in. Styled off
   `.mgrgate__link` rather than as a button for exactly that reason. */
.mgrgate__staff { display: inline-block; margin: 14px 0 0; padding: 0; background: none; border: 0;
  font: inherit; font-size: 13px; color: var(--muted); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer; }
.mgrgate__staff:hover { color: var(--mgr-accent); }
.mgrgate .shake { animation: mgrshake .4s ease; }
@keyframes mgrshake { 10%,90%{transform:translateX(-1px)} 20%,80%{transform:translateX(2px)} 30%,50%,70%{transform:translateX(-5px)} 40%,60%{transform:translateX(5px)} }
/* entrance + unlock-spring */
.mgrgate__card { animation: mgrIn .42s ease both; }
@keyframes mgrIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
.mgrgate.is-unlocked .mgrgate__lock { animation: mgrPop .5s ease; }
@keyframes mgrPop { 0% { transform: scale(1); } 45% { transform: scale(1.14); } 100% { transform: scale(1); } }
.mgrgate.is-unlocked .mgrdot { background: var(--success); border-color: var(--success); transition: background .2s, border-color .2s; }
.mgrgate.is-unlocked .mgrgate__card { animation: mgrOut .5s ease forwards; animation-delay: .14s; }
@keyframes mgrOut { to { opacity: 0; transform: scale(.97); } }
@media (prefers-reduced-motion: reduce) {
  .mgrgate__card, .mgrgate.is-unlocked .mgrgate__lock, .mgrgate.is-unlocked .mgrgate__card { animation: none; }
}
/* the PIN dots + keypad live inside a modal here (not the full-screen .mgrgate): give the modal the
   accent the dots colour themselves with, and let them shake on a wrong code. */
.cardmodal { --mgr-accent: var(--c-primary, #c15c2c); }
.cardmodal .shake { animation: mgrshake .4s ease; }
.cardmodal .mgrgate__dots, .cardmodal .mgrgate__pad { justify-content: center; }

/* Touch devices: kill accidental double-tap-to-zoom (and the legacy 300ms tap delay) app-wide.
   `manipulation` still allows scrolling AND pinch-to-zoom, so accessibility is preserved. */
html, body { touch-action: manipulation; }
/* …but NEVER on form fields: `touch-action: manipulation` on the page root blocks the on-screen
   keyboard in an installed iOS PWA (Safari is fine — standalone-only). Exempt inputs so they type. */
input, textarea, select, [contenteditable="true"] { touch-action: auto; -webkit-user-select: text; user-select: text; }

/* ── Area Riservata (TRACK R) ─────────────────────────────────────────────────────────── */
/* Customer "temporarily off" screen — calm, branded, themed to the business colour. */
.pausescreen { min-height: 100svh; display: grid; place-items: safe center; padding: 32px 26px; background: var(--tint-bg); }
.pausescreen__card { width: 100%; max-width: 360px; text-align: center; background: var(--surface); border: 1px solid var(--line); border-radius: 20px; padding: 40px 30px; box-shadow: 0 20px 50px rgba(0,0,0,.08); animation: mgrIn .42s ease both; }
.pausescreen__badge { width: 70px; height: 70px; margin: 0 auto 20px; display: grid; place-items: center; border-radius: 50%; color: var(--pause-accent); background: color-mix(in srgb, var(--pause-accent) 14%, var(--surface)); }
.pausescreen__brand { font-size: 22px; font-weight: 800; letter-spacing: -.01em; margin: 0; color: var(--ink); }
.pausescreen__title { font-size: 16px; font-weight: 700; color: var(--pause-accent); margin: 14px 0 0; }
.pausescreen__body { font-size: 14.5px; color: var(--muted); line-height: 1.5; margin: 8px 0 0; }
.pausescreen__body--lead { margin-top: 16px; font-size: 15.5px; } /* the message stands alone (closed screen, no accent title above) */
.pausescreen__photo { margin: 20px 0 0; border-radius: 14px; overflow: hidden; aspect-ratio: 16 / 10; background: color-mix(in srgb, var(--pause-accent) 8%, var(--surface)); }
.pausescreen__photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Manager "Area riservata" panel — a subtle red frame + red identity badge. */
/* ---- Il Passaparola (Manager → Impostazioni, 2026-07-23) --------------------------------------
   The owner's AMICO code, big enough to read aloud across a bar counter. Dashed border = "this is
   a coupon". ⭐ FIXED GOLD, never the client's accent (Tony): the coupon is a Telaio thing, and it
   must look like money in every branded app. Same rule for the WhatsApp button: brand green, like
   any real-world logo button. */
.pp-lead { margin: -4px 0 0; font-size: 15px; line-height: 1.55; color: var(--ink); }
.pp-codebox { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px;
  border: 1.5px dashed rgba(201, 152, 47, .65); border-radius: 12px; background: rgba(201, 152, 47, .09); }
.pp-code { font-weight: 800; font-size: 19px; letter-spacing: .06em; color: #c9982f; word-break: break-all; }
.btn--wa { background: #25d366; border-color: #25d366; color: #fff; }
.btn--wa:hover, .btn--wa:active { background: #1fb658; border-color: #1fb658; color: #fff; }

.area-card { border-color: color-mix(in srgb, #c0322b 30%, var(--line)); }
.area-ico { --pi: #c0322b; }
/* Il Passaparola panel — same treatment as the red Area riservata, in the Passaparola's FIXED gold
   (#c9982f, the codebox gold — never the client accent). Gold reads weaker than red on the dark
   ground, so the mix leans harder (45% vs 30%) to land at the same visual weight. */
.pp-card { border-color: color-mix(in srgb, #c9982f 45%, var(--line)); }
.pp-ico { --pi: #c9982f; }
.area-grouplabel { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
/* Filled action buttons (Tony) — white text on a solid fill so they carry weight in this area.
   Amber = caution, red = danger; the safe actions use the copper .btn--primary. Mirrors its gradient finish. */
.area-btn--warn, .area-btn--danger, .area-btn--go { color: #fff; border-color: color-mix(in srgb, #000 18%, var(--_ab)); background: linear-gradient(108deg, color-mix(in srgb, #fff 22%, var(--_ab)) 0%, var(--_ab) 46%, color-mix(in srgb, var(--_ab) 84%, #000) 100%); box-shadow: inset 0 1px 0 rgba(255,255,255,.24), 0 6px 18px color-mix(in srgb, var(--_ab) 30%, transparent); }
.area-btn--warn { --_ab: #45647c; }   /* caution — cool steel-blue, clearly distinct from the warm copper */
/* "Take your data with you" — the amber block inside the close-the-account confirm. Amber on
   purpose: it is neither a danger (the red button below it is) nor a note to skim. It is the last
   moment his content is reachable, and it carries the button rather than pointing at one. */
.area-takeaway { border: 1px solid rgba(243, 177, 75, .42); background: rgba(243, 177, 75, .09);
  border-radius: 12px; padding: 12px 13px; margin: 0 0 14px; }
.area-takeaway p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.45; color: #f0cf9a; }
.area-takeaway .btn { width: 100%; background: rgba(243, 177, 75, .16); border-color: rgba(243, 177, 75, .5); color: #f6dfb4; }
.area-takeaway__when { display: block; margin: 7px 2px 0; font-size: 11.5px; color: var(--muted); }
/* "on but invisible" — the Carta is switched on with an empty reward ladder, so the customer tab
   is (correctly) hidden. Amber, inline, and only while that is true: it is an unfinished setup,
   not an error. */
.carta-warn { display: flex; gap: 8px; align-items: flex-start; margin: 0; padding: 10px 12px;
  border: 1px solid rgba(243, 177, 75, .4); background: rgba(243, 177, 75, .09);
  border-radius: 11px; font-size: 13px; line-height: 1.45; color: #f0cf9a; }
.carta-warn svg { flex: none; margin-top: 1px; }
.area-btn--danger { --_ab: #c0322b; } /* danger — red */
.area-btn--go { --_ab: #2f8a4e; }     /* reactivate — green "go" */
.area-btn--warn:hover, .area-btn--danger:hover, .area-btn--go:hover { filter: brightness(1.05); }

/* "App in pausa" banner (Manager) — the owner keeps working while the customer app is off, so keep it unmissable.
   Centered content + full-width button, a lifted shadow + a 2px border so it clearly floats above the scrolling content. */
.mgr-pausebanner { position: sticky; top: calc(env(safe-area-inset-top) + 110px); /* clears the header + its tabs band */ z-index: 15; display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; padding: 12px 14px; border-radius: 12px; background: color-mix(in srgb, #d97706 16%, var(--surface)); border: 2px solid color-mix(in srgb, #d97706 55%, transparent); box-shadow: 0 10px 28px rgba(0,0,0,.32); }
.mgr-pausebanner__txt { display: inline-flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 8px; font-size: 13.5px; color: var(--ink); }
.mgr-pausebanner__txt i { color: #d97706; }
.mgr-pausebanner__btn { width: 100%; background: #2f8a4e; color: #fff; border-color: #2f8a4e; }
.mgr-pausebanner__btn:hover { filter: brightness(1.05); }
/* §B the 5-day trial — the once-a-day reminder card (trialGate.js).
   Deliberately plain: it rides the existing .cardmodal panel, so it inherits the modal's own
   scrolling, safe-area and iOS behaviour rather than inventing a second set of rules. */
.trialnote__day { margin: 2px 0 8px; font-size: 19px; font-weight: 700; color: var(--ink); line-height: 1.25; }
.trialnote__day--last { color: #d97706; }
.trialnote__body { margin: 0; font-size: 15px; line-height: 1.5; color: var(--ink); }
.trialnote__hint { margin: 10px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--muted); }
.area-note { display: flex; gap: 7px; align-items: flex-start; font-size: 11.5px; color: var(--muted); line-height: 1.45; margin: 2px 0 0; }
.area-note i { margin-top: 1px; flex: 0 0 auto; }
/* live feedback under the "type the name" field in the close-account confirm */
.area-match { color: var(--muted); }
.area-match.is-ok { color: #2f8a4e; font-weight: 600; }

/* Manager login (managerAuth.js) — email/password form + coming-soon social buttons */
.mgrauth { max-width: 340px; }
.mgrauth__form { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; text-align: left; }
.mgrauth__form .input { width: 100%; }
.mgrauth__form .mgrgate__err { text-align: center; margin: 2px 0 0; }
.mgrauth__switchtext { font-size: 13.5px; color: var(--muted); margin: 18px 0 8px; text-align: center; }
.mgrauth__switchbtn { margin: 0; }
.mgrauth__or { display: flex; align-items: center; gap: 12px; margin: 20px 0 14px; color: var(--muted); font-size: 12px; letter-spacing: .04em; }
.mgrauth__or::before, .mgrauth__or::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.mgrauth__social { display: grid; gap: 8px; }
.mgrsocial { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius, 12px); background: var(--surface); color: var(--ink); font-size: 14.5px; font-weight: 500; cursor: not-allowed; opacity: .72; }
.mgrsocial span { flex: 1; text-align: left; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mgrsocial em { flex: none; font-style: normal; font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; color: var(--muted); background: var(--tint-bg); padding: 2px 8px; border-radius: 999px; }
.mgrsocial .ti { font-size: 20px; color: var(--ink); }

/* ============ Stile dell'App — Classico / Elegante / Moderno ============ */
/* Elegante = the current default look, so it needs no overrides. */

/* Menu Fisso cards + Offerte (Classico) = a warm dark "wooden plaque" done in PURE CSS — no image, so
   it can never flash or re-load a background. Gold border + carved text keep the character. */
.setmenu--notepad, .offer-card--notepad {
  position: relative; overflow: hidden;
  background-color: #2a1d10;
  background-image: linear-gradient(157deg, rgba(122, 86, 46, .34) 0%, rgba(48, 33, 18, .12) 44%, rgba(16, 11, 5, .34) 100%);
  border: 2px solid #4f3719; border-radius: var(--radius-card); box-shadow: var(--shadow-md);
}
/* Menu Fisso (set-menu) cards in Classic: the subtle Elegant/base-card shadow, not the heavier wood
   drop (Tony 2026-07-23). Scoped to the set-menu card only — the offer cards on the wood above keep
   their --shadow-md. */
.setmenu--notepad { box-shadow: var(--shadow-sm); }
.setmenu--notepad .card__body { position: relative; z-index: 1; padding: clamp(11px, 8cqi, 16px); }
.offer-card--notepad .offer-card__img { position: relative; z-index: 1; } /* product photo above the plaque */
/* handwriting font + a soft "engraved into the wood" treatment: dark depth shadow + a faint warm top light */
/* handwriting price + description, carved deeper into the wood */
.setmenu--notepad .setmenu__price, .setmenu--notepad .muted,
.offer-card--notepad .card__title {
  font-family: 'Caveat', cursive;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .72), 0 2px 3px rgba(0, 0, 0, .45), 0 -1px 1px rgba(255, 240, 215, .30);
}
/* Title = a dark rounded chip (like the "Aperitivo 8€" pill): centered, UI font, icon in front. */
.setmenu--notepad .setmenu__name {
  align-self: center; max-width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: .4em; white-space: nowrap;
  font-family: var(--font-ui); font-weight: 600; font-size: clamp(9px, 7.5cqi, 13.5px); letter-spacing: .01em;
  color: #fbe7d0; background: rgba(0, 0, 0, .46); border: 1px solid rgba(255, 236, 208, .14);
  padding: clamp(3px, 3cqi, 5px) clamp(7px, 7cqi, 12px); border-radius: var(--radius-card); box-shadow: 0 2px 6px rgba(0, 0, 0, .35);
}
.setmenu--notepad .setmenu__name .ti { font-size: 1.15em; color: #ffdca6; flex: none; }
.setmenu--notepad .setmenu__price {
  font-size: clamp(24px, 22cqi, 40px); font-weight: 700; color: #ffdca6; /* sized so a 2-digit price fits fully inside the card (stays centred) */
  -webkit-text-stroke: 0.6px #ffdca6; /* extra weight — Caveat itself maxes out at 700 */
}
/* tight line-height so a 2-line description reads as one block */
.setmenu--notepad .muted { font-size: clamp(15px, 14cqi, 22px); font-weight: 400; color: #f1e4d0 !important; line-height: 1.12; }

/* Offerte on the wood: a scrim + light text over the body; the product photo stays clear on the left. */
.offer-card--notepad .offer-card__body { position: relative; z-index: 1; background: linear-gradient(180deg, rgba(16, 11, 5, .16), rgba(16, 11, 5, .40)); }
.offer-card--notepad .card__title { font-size: 28px; font-weight: 700; color: #fbe7d0; }
.offer-card--notepad .offer-card__body .muted, .offer-card--notepad .offer-card__body .small { font-family: var(--font-ui); font-size: 15px; font-weight: 400; line-height: 1.45; color: #f1e4d0 !important; text-shadow: 0 1px 2px rgba(0, 0, 0, .55); }
.offer-card--notepad .chip { background: rgba(0, 0, 0, .34); color: #fbe7d0; border: 0; }

/* Moderno cards (fisso + offerte) = the menu's diagonal hatch over the existing dark background. */
.setmenu--modern, .offer-card--modern {
  background-image: repeating-linear-gradient(45deg, color-mix(in srgb, var(--ink) 7%, transparent) 0 1px, transparent 1px 13px);
}

/* Specialità del giorno (n.2), Classico = the menu's notepad lined-paper look. */
.card--feature--notepad {
  background-color: #f7f2e4;
  background-image: repeating-linear-gradient(#f7f2e4 0 31px, #ccdae6 31px 32px);
  border: 1px solid rgba(53, 49, 43, .16); color: #34312b;
}
/* Specialità text = the Menu's handwriting (Caveat), same as the notepad dishes */
.card--feature--notepad .row__title, .card--feature--notepad .price { color: #34312b; font-family: 'Caveat', cursive; }
.card--feature--notepad .row__title { font-weight: 600; font-size: 23px; line-height: 1.1; }
.card--feature--notepad .price { font-weight: 700; font-size: 23px; }
.card--feature--notepad .row__sub { color: #837a69 !important; font-family: 'Caveat', cursive; font-weight: 500; font-size: 20px; line-height: 1.05; }
.card--feature--elegant { background: #faf6ee; border: 1px solid rgba(140, 110, 60, .24); }
/* same ink as the Elegante menu — the title/price were inheriting white and vanishing on the cream card */
.card--feature--elegant .row__title, .card--feature--elegant .price { color: #34312b; }
.card--feature--elegant .row__sub { color: #8a8170 !important; }
.card--feature--modern {
  background-color: var(--surface);
  background-image: repeating-linear-gradient(45deg, color-mix(in srgb, var(--ink) 6%, transparent) 0 1px, transparent 1px 13px);
  border: 1px solid var(--line);
}

/* ============================================================================
   TRACK V — Allergeni & Diete: the 14-allergen + veg icon set, the per-item icon
   rows, the two customer filter buttons/modals, and the dish-card big list.
   Allergens = amber, diets = green (semantic, separate from the business accent).
   ============================================================================ */
:root {
  --alg: #b3781f; --alg-fill: #faf2e4; --alg-line: #e6cf9f;
  --veg: #3f9d4f; --veg-fill: #eaf5ec; --veg-line: #bfe2c6; --veg-ink: #2f7d3c;
  --alg-off: #c0392b;
}
:root[data-theme="dark"] {
  --alg: #dcab54; --alg-fill: color-mix(in srgb, #dcab54 15%, var(--surface)); --alg-line: color-mix(in srgb, #dcab54 42%, var(--surface));
  --veg: #6cc47a; --veg-fill: color-mix(in srgb, #6cc47a 15%, var(--surface)); --veg-line: color-mix(in srgb, #6cc47a 40%, var(--surface)); --veg-ink: #8fd79a;
}

/* one circular OUTLINE chip — hollow ring + glyph (no fill), so it's light and identical everywhere */
.alg { --c: var(--alg);
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; vertical-align: middle;
  width: var(--s, 22px); height: var(--s, 22px); border-radius: 999px; background: transparent; border: 1px solid var(--c); color: var(--c); }
.alg svg { width: 66%; height: 66%; display: block; }
.alg--veg { --c: var(--veg); }
.alg--off { --c: #fff; background: var(--alg-off); border-color: var(--alg-off); }

.iconrow { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; align-items: center; }

/* the two customer filter buttons — no box, just icon + label; pinned top-right INSIDE the paper menu */
.paper-filters { display: flex; justify-content: flex-end; gap: 18px; margin: 2px 2px 12px; }
.pfilter { display: flex; flex-direction: column; align-items: center; gap: 5px; border: 0; background: transparent; padding: 2px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.pfilter__ic { position: relative; display: inline-flex; }
.pfilter__lbl { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; color: var(--muted); }
.pfilter.is-on .pfilter__lbl { color: var(--c-primary); }
.fbadge { position: absolute; top: -6px; right: -8px; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--c-primary); color: var(--on-primary, #fff); font-size: 11px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; box-shadow: var(--shadow-sm); }
/* Elegante: centre the two filters (like its centred course names), sit a touch higher + closer to the first course */
.notepad--elegant .paper-filters { justify-content: center; margin: -10px 2px 2px; }
/* Classico + Moderno: pull the filters (and the courses below) up to compress the empty space at the paper's top */
.notepad--modern .paper-filters,
.notepad:not(.notepad--elegant):not(.notepad--modern) .paper-filters { margin-top: -12px; margin-bottom: 4px; }
/* Classico (ruled paper): fill the icon circles white so the ruled lines don't show through them (this style only) */
.notepad .alg { background: #fff; }
.notepad--elegant .alg, .notepad--modern .alg { background: transparent; }

/* the two filter modals (built on the shared .cardmodal overlay) */
.algfilter__lede { margin: 8px 0 4px; text-align: center; color: var(--muted); font-size: 13.5px; line-height: 1.45; }
.veg-opts { display: flex; flex-direction: column; gap: 10px; margin: 18px 0 6px; }
.veg-opt { display: flex; align-items: center; gap: 13px; border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px;
  font-weight: 700; font-size: 15px; color: var(--muted); background: var(--surface); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.veg-opt.is-on { border-color: var(--veg-line); background: var(--veg-fill); color: var(--veg-ink); }
.algmodal { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin: 18px 0 8px; }
.algrow { display: flex; align-items: center; gap: 10px; border: 1px solid var(--line); border-radius: 11px; padding: 9px 10px;
  font-size: 13px; font-weight: 600; color: var(--ink); background: var(--surface); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.algrow.is-on { border-color: var(--alg-line); background: var(--alg-fill); }
.alg-disclaim { display: flex; gap: 9px; align-items: flex-start; margin-top: 14px; padding: 11px 12px; border-radius: 10px;
  background: color-mix(in srgb, var(--c-primary) 10%, var(--surface)); color: var(--ink); font-size: 12px; line-height: 1.45; }
.alg-disclaim i { flex: 0 0 auto; font-size: 16px; margin-top: 1px; color: var(--c-primary); }
.alg-empty { text-align: center; color: var(--muted); padding: 26px 12px; font-size: 14px; }

/* the dish card's big labelled list (Contiene · Diete), after the description */
.card__alg { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.card__alg h4 { margin: 0 0 11px; font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.biglist { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 10px; }
.bigitem { display: flex; align-items: center; gap: 10px; font-size: 13.5px; font-weight: 600; color: var(--ink); }

/* the owner "Filtri" block — a quiet bordered panel (like the Descrizione box) that groups the
   contents; the "Filtri" label sits OUTSIDE it, matching the other field labels. */
.filtri { display: flex; flex-direction: column; gap: 2px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface-2); padding: 10px 13px 13px; }
.filtri__lbl { font-size: 12px; font-weight: 600; color: var(--muted); margin: 10px 0 8px; }
.filtri__lbl:first-child { margin-top: 2px; }
.ftoggles { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 15px; }
.ftoggle { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line); border-radius: 999px; padding: 7px 13px 7px 8px;
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--muted); background: var(--surface); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.ftoggle.is-on { border-color: var(--veg-line); color: var(--veg-ink); background: var(--veg-fill); }
.fchips { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.fchip { display: flex; align-items: center; gap: 9px; border: 1px solid var(--line); border-radius: 10px; padding: 7px 9px;
  font-family: inherit; font-size: 12.5px; font-weight: 500; color: var(--ink); background: var(--surface); cursor: pointer; text-align: left; -webkit-tap-highlight-color: transparent; }
.fchip span { min-width: 0; }
.fchip.is-on { border-color: var(--alg-line); background: var(--alg-fill); }

/* ============================================================================
   TELAIO HQ — Tony's control room (#/hq). Its OWN identity: the Telaio landing
   palette (dark charcoal + teal/green/gold), deliberately NOT the business theme.
   Scoped under .hq so nothing leaks into the customer/Manager apps.
   ========================================================================== */
.hq {
  --hq-bg1: #161719; --hq-bg2: #0e0f10;
  --hq-panel: rgba(32, 34, 32, .72); --hq-panel2: rgba(44, 49, 45, .7);
  --hq-ink: #e5e5e5; --hq-soft: #9aa39d; --hq-bright: #f2f7f4;
  --hq-line: #262b28; --hq-line2: #39403b;
  --hq-teal: #2aa595; --hq-green: #1e6b4d; --hq-gold: #f3b14b; --hq-good: #4bb98a;
  --hq-cream: #f4f1e8;
  min-height: 100vh; min-height: 100dvh; color: var(--hq-ink);
  background:
    radial-gradient(900px 500px at 15% -10%, rgba(20, 145, 134, .16), transparent 60%),
    radial-gradient(800px 480px at 90% 0%, rgba(30, 107, 77, .15), transparent 55%),
    linear-gradient(var(--hq-bg1), var(--hq-bg2));
}
.hq .input { background: var(--hq-panel); border: 1px solid var(--hq-line2); color: var(--hq-bright); }
.hq .input:focus { border-color: var(--hq-teal); outline: none; }
.hq .input::placeholder { color: var(--hq-soft); }
.hq-btn-primary { background: linear-gradient(150deg, var(--hq-green), var(--hq-teal)); color: #fff; border: none; }
.hq-btn-primary:hover { filter: brightness(1.08); }

/* ---- the gate (login / not-allowed / splash) ---- */
.hqgate { min-height: 100vh; min-height: 100dvh; display: grid; place-items: center;
  padding: calc(24px + env(safe-area-inset-top, 0px)) 24px calc(24px + env(safe-area-inset-bottom, 0px)); }
.hqgate__card { width: 100%; max-width: 360px; background: var(--hq-panel); border: 1px solid var(--hq-line2);
  border-radius: 20px; padding: 30px 26px 24px; display: flex; flex-direction: column; gap: 10px;
  box-shadow: 0 40px 80px rgba(0, 0, 0, .5), 0 0 0 1px rgba(255, 255, 255, .05) inset; }
.hqgate__lock { width: 54px; height: 54px; border-radius: 15px; display: grid; place-items: center; margin: 0 auto 2px;
  background: rgba(42, 165, 149, .12); color: var(--hq-teal); }
.hqgate__brand { margin: 0; text-align: center; font-size: 24px; font-weight: 800; letter-spacing: -.01em; color: var(--hq-bright); }
.hqgate__brand em { font-style: normal; color: var(--hq-teal); }
.hqgate__sub { margin: 0 0 6px; text-align: center; font-size: 13px; color: var(--hq-soft); line-height: 1.5; }
.hqgate__form { display: flex; flex-direction: column; gap: 10px; }
.hqgate__err { margin: 0; min-height: 1em; text-align: center; font-size: 12.5px; color: #e08578; }
.hqgate__note { margin: 8px 0 0; text-align: center; font-size: 12px; color: var(--hq-soft); }
.hqgate__note a { color: var(--hq-teal); text-decoration: none; }

/* ---- Home ---- */
.hq-home { max-width: 1100px; margin: 0 auto; padding: 0 16px calc(40px + env(safe-area-inset-bottom, 0px)); }
.hq-head { display: flex; align-items: center; gap: 10px; border-bottom: 1px solid var(--hq-line);
  padding: calc(16px + env(safe-area-inset-top, 0px)) 2px 13px; }
.hq-head__mark { font-size: 18px; font-weight: 800; letter-spacing: -.01em; color: var(--hq-bright); }
.hq-head__mark em { font-style: normal; color: var(--hq-teal); }
.hq-head__who { margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--hq-cream);
  background: var(--hq-panel2); border: 1px solid var(--hq-line2); border-radius: 999px; padding: 4px 11px;
  max-width: 46vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hq-head__out { border: 1px solid var(--hq-line2); background: var(--hq-panel); color: var(--hq-soft);
  border-radius: 10px; width: 34px; height: 30px; display: grid; place-items: center; cursor: pointer; }
.hq-head__out:hover { color: var(--hq-bright); border-color: var(--hq-teal); }
.hq-main { display: flex; flex-direction: column; gap: 14px; padding-top: 16px; }
.hq-stats { display: flex; gap: 8px; }
.hq-stat { flex: 1; background: var(--hq-panel); border: 1px solid var(--hq-line); border-radius: 12px; padding: 10px 12px; }
.hq-stat b { display: block; font-size: 22px; font-weight: 800; color: var(--hq-bright); font-variant-numeric: tabular-nums; line-height: 1.15; }
.hq-stat b i { font-style: normal; font-size: 13px; color: var(--hq-soft); }
.hq-stat span { font-size: 11.5px; color: var(--hq-soft); letter-spacing: .02em; }
.hq-stat--gold b { color: var(--hq-gold); }
.hq-stat--teal b { color: var(--hq-teal); }
.hq-stat--good b { color: var(--hq-good); }
.hq-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.hq-tile { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 9px; min-height: 118px;
  background: var(--hq-panel); border: 1px solid var(--hq-line2); border-radius: 16px; padding: 15px 14px 13px;
  font-family: inherit; text-align: left; color: inherit; cursor: pointer; transition: border-color .2s, transform .2s;
  -webkit-tap-highlight-color: transparent; }
.hq-tile:hover { border-color: var(--hq-teal); transform: translateY(-2px); }
.hq-tile__ic { width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center;
  background: rgba(42, 165, 149, .12); color: var(--hq-teal); }
.hq-tile--gold .hq-tile__ic { background: rgba(243, 177, 75, .12); color: var(--hq-gold); }
.hq-tile__tx { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.hq-tile__tx b { font-size: 15.5px; font-weight: 700; color: var(--hq-bright); line-height: 1.2; }
.hq-tile__tx span { font-size: 12px; color: var(--hq-soft); line-height: 1.35; }
.hq-tile--new { grid-column: 1 / -1; flex-direction: row; align-items: center; min-height: 0; padding: 14px 16px;
  background: linear-gradient(150deg, rgba(30, 107, 77, .5), rgba(42, 165, 149, .34)); border-color: rgba(42, 165, 149, .55); }
.hq-tile--new .hq-tile__ic { background: rgba(255, 255, 255, .14); color: #fff; }
.hq-tile--new .hq-tile__tx b { font-size: 16.5px; }
.hq-tile--new .hq-tile__tx span { color: rgba(244, 241, 232, .78); }
.hq-foot { margin: 8px 2px 0; font-size: 11.5px; color: var(--hq-soft); }
/* The update block at the foot of HQ Home (#3-e, Tony's design): separator · version line · button */
.hq-update { margin: 18px 2px 0; padding-top: 14px; border-top: 1px solid var(--hq-line); display: flex; flex-direction: column; align-items: center; gap: 10px; }
.hq-update .hq-foot { margin: 0; }
.hq-update__btn { font-size: 13px; padding: 9px 16px; }
@media (min-width: 760px) { /* iPad/desktop: the wide tile joins the grid as a taller twin-width card */
  .hq-home { padding: 0 24px 48px; }
  .hq-grid { grid-template-columns: repeat(3, 1fr); }
  .hq-tile--new { grid-column: span 3; }
}
@media (prefers-reduced-motion: reduce) { .hq-tile { transition: none; } .hq-tile:hover { transform: none; } }

/* ---- "Le mie app" inside HQ: reskin the old .su-* pieces to the HQ palette ---- */
.hq-head__back { border: 1px solid var(--hq-line2); background: var(--hq-panel); color: var(--hq-soft);
  border-radius: 10px; width: 34px; height: 30px; display: grid; place-items: center; flex: none; }
.hq-head__back:hover { color: var(--hq-bright); border-color: var(--hq-teal); }
.hq-sub { margin: 0; font-size: 13px; color: var(--hq-soft); line-height: 1.5; max-width: 640px; }
/* ⭐ The two words Tony types for a search engine, inside «Su Google». ⛔ A GRID, and that is the
   whole point: `.hq-lbl` and `.hq-sub` set no `display`, so as <label> and <span> they are inline —
   the first build ran a hint, the next label and the Save button together on one line. Rows here
   cannot collapse into each other whatever tag they wear. */
/* ⭐ Where the review request sends the guest — two brand buttons at half a row each, then the
   address field once one is chosen. ⛔ Brand NAME and COLOUR, no logo files: as recognisable in a
   private back office, and nothing third-party to go stale or breach a spacing rule. */
.revpick { display: flex; gap: 8px; }
/* ⛔ THE BRAND'S OWN GROUND, not a tint of it. Google's mark is only ever placed on white;
   Tripadvisor's is white and made for their green. A «16% of the brand colour» background — the
   pattern used everywhere else in this app — would breach both guidelines at once. */
.revpick__btn { flex: 1 1 0; min-width: 0; min-height: 52px; border-radius: var(--radius-sm);
  display: grid; place-items: center; padding: 8px 10px;
  border: 1px solid rgba(255,255,255,.18); background: var(--rv);
  cursor: pointer; -webkit-tap-highlight-color: transparent; }
.revpick__btn:active { filter: brightness(.92); }
/* ⭐ CENTRED IN ITS OWN BUTTON (Tony, 2026-08-12). `place-items` centres the grid cell, and
   `margin:0 auto` centres the image inside a cell it does not fill — both are needed, because the
   two marks have very different aspect ratios and only one of them ever fills the width. */
.revpick__btn img, .revchosen__logo { max-width: 100%; object-fit: contain; display: block; margin: 0 auto; }
/* The chosen row keeps the mark centred too, with the ✕ floated over the right edge so it cannot
   push the logo off-centre. */
.revchosen { position: relative; display: grid; place-items: center; padding: 12px 44px;
  border-radius: var(--radius-sm); border: 1px solid rgba(255,255,255,.18); background: var(--rv); }
.revchosen__x { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); }
.revchosen__x { display: grid; place-items: center; width: 30px; height: 30px;
  border: 0; border-radius: 50%; background: rgba(0,0,0,.15); cursor: pointer; }
/* ⛔ DON'T "FIX" THIS WITH `line-height:1; display:block` — measured on the live app 2026-08-12 and it
   changes NOTHING. A grid/flex container BLOCKIFIES an inline child, so `display:block` is already in
   force; half-leading is symmetric, so line-height cannot decentre a glyph either. The ✕ is an SVG
   now precisely so the question cannot come back: a stroke drawn at 6→18 in a 24 viewBox is centred
   by arithmetic, on WebKit as on Blink, at any size and any pixel density. */
.revchosen__svg { display: block; }
/* ⭐ "Where do I find this link?" — folded away, because the owner needs it exactly once. `<details>`
   and not a modal: it opens in place, under the field it explains, and the form keeps its state. */
.revhow { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 8px; }
.revhow > summary { font-size: 12.5px; color: var(--c-primary); cursor: pointer; list-style: none;
  -webkit-tap-highlight-color: transparent; }
.revhow > summary::-webkit-details-marker { display: none; }
/* ⛔ `content: ' ›'` DOESN'T WORK — an inline-BLOCK collapses the space at its own leading edge, so
   the arrow sat welded to the question mark. The gap has to be a margin. */
.revhow > summary::after { content: '›'; display: inline-block; margin-left: 5px;
  transition: transform .15s ease; }
.revhow[open] > summary::after { transform: rotate(90deg); }
.revhow ol { margin: 8px 0 0; padding-left: 18px; }
.revhow li, .revhow p { font-size: 12.5px; line-height: 1.55; color: var(--muted); }
.revhow li { margin-bottom: 4px; }
.revhow p { margin: 8px 0 0; }
/* ⭐ "Provalo su di te" — set apart from Salva/Chiudi by a rule, because it is not a third way to
   finish the form: it changes nothing and saves nothing, it just makes the phone buzz. */
.notiftry { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.notiftry .field__hint { display: block; margin-top: 6px; text-align: center; }
/* ⭐ The hotel's classification, under its name on the guest's Home. Centred with the title above it,
   and quiet: it is a fact about the hotel, not a headline. ⛔ Drawn only when the owner has set it. */
/* ⛔ THE CHARACTER, NOT THE ICON FONT. `ti-star` is the only star the font ships and it is an
   OUTLINE — five hollow shapes under a hotel's name read as "not yet rated", which is the opposite
   of what three stars mean. U+2605 is filled everywhere and needs no font at all. */
.hstars { display: flex; justify-content: center; align-items: center; gap: 2px;
  margin: -2px 0 0; color: var(--c-accent, var(--c-primary)); line-height: 1;
  font-size: 15px; letter-spacing: 2px; }
.hstars__sup { margin-left: 4px; font-family: var(--font-display); font-weight: 700; font-size: 13px;
  letter-spacing: .04em; }
/* The picker in the Manager — five stars and an S, tapped straight on. */
.starpick { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.starpick__s, .starpick__sup { display: grid; place-items: center; width: 42px; height: 42px;
  font-size: 22px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--surface);
  color: var(--muted); cursor: pointer; -webkit-tap-highlight-color: transparent; font: inherit; }
.starpick__sup { font-weight: 800; font-size: 16px; }
.starpick__s.is-on, .starpick__sup.is-on {
  color: var(--c-primary); border-color: color-mix(in srgb, var(--c-primary) 45%, transparent);
  background: color-mix(in srgb, var(--c-primary) 14%, var(--surface)); }
/* ⭐⭐ THE TWO MANUAL STEPS, shown ONLY once a venue is switched on — and dressed as a WARNING, in
   gold, because Tony asked for it after seeing the first version: *"make the panel more evident and
   stand out as a warning… maybe in an orange/yellow background"*. It earns that: until he does these
   two things Google may never come and look, and a grey box saying so reads as one more note.
   ⚠️ THE SAME AMBER AS THE FOUNDER OFFER (`.hq-off__deal.is-on` above) — the 10% gold wash and the
   55% gold border, both mixed from `--hq-gold`. Copying the recipe rather than picking a new orange
   keeps HQ speaking one language, and it follows the token if the brand gold ever moves.
   ⚠️ A grid so every line is its own row whatever tag it wears — `.hq-sub` carries no display and
   three of them in a row ran together on the first build. */
.hq-seo__next { display: grid; gap: 4px; margin: 12px 0 4px; padding: 11px 13px; border-radius: 12px;
  background: color-mix(in srgb, var(--hq-gold) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--hq-gold) 55%, transparent); }
/* ⚠️ The heading takes the gold; the instructions stay readable rather than joining in. Gold on gold
   at 13px is a decoration nobody can read, which is the opposite of a warning. */
.hq-seo__next .hq-lbl { color: var(--hq-gold); }
.hq-seo__next .hq-sub { color: var(--hq-bright); word-break: break-word; }
/* the closing «until you do…» line is the reason, not the instruction — it steps back down */
.hq-seo__next .hq-sub:last-child { color: var(--hq-soft); }
.hq-seo__words { display: grid; gap: 6px; margin: 12px 0 4px; }
.hq-seo__words .input { width: 100%; }
.hq-seo__words .hq-lbl { margin: 8px 2px 0; }
/* The button leads its row and the sentence sits beside it — an action reads first, its footnote after. */
.hq-seo__saverow { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 8px; }
.hq-seo__saverow .btn { flex: none; }
.hq-btn-ghost { background: var(--hq-panel); border: 1px solid var(--hq-line2); color: var(--hq-bright); }
.hq-btn-ghost:hover { border-color: var(--hq-teal); }
/* the irreversible actions (Elimina cliente): quiet by default — red text on a hairline, never a
   big red slab — so the destructive button can't be mistaken for the primary one. */
.hq-btn-danger { background: transparent; border: 1px solid rgba(196, 71, 58, .45); color: #d4574a; }
.hq-btn-danger:hover { background: rgba(196, 71, 58, .1); border-color: #c4473a; }
.hq-btn-danger:disabled { opacity: .45; }
.hq-modal__panel [data-typed] { width: 100%; margin: -4px 0 14px; }
.hq .su-card { background: var(--hq-panel); border: 1px solid var(--hq-line2); border-radius: 16px; }
.hq .su-card__icon { border-color: var(--hq-line2); }
.hq .su-card__name { color: var(--hq-bright); }
.hq .su-card__vert, .hq .muted, .hq .mono { color: var(--hq-soft); }
.hq .chip { background: var(--hq-panel2); border: 1px solid var(--hq-line2); color: var(--hq-soft); }
.hq .su-edit { border-top-color: var(--hq-line2); }
.hq .su-field > label { color: var(--hq-soft); }
.hq .su-logo { border-color: var(--hq-line2); }
.hq .su-logo.is-drop { border-color: var(--hq-teal); }
.hq .su-edit input[type="color"] { border-color: var(--hq-line2); background: var(--hq-panel); }

/* ---- HQ language pill (EN default | IT), mirroring the marketing pages' langseg ---- */
.hq-langseg { display: inline-flex; background: var(--hq-panel); border: 1px solid var(--hq-line2);
  border-radius: 999px; overflow: hidden; flex: none; }
.hq-langseg button { background: transparent; border: none; color: var(--hq-soft); font: inherit;
  font-size: 11px; font-weight: 700; padding: 5px 10px; cursor: pointer; letter-spacing: .04em; }
.hq-langseg button.on { background: var(--hq-teal); color: #fff; }
.hqgate__lang { display: flex; justify-content: center; margin-bottom: 4px; }

/* ---- HQ real brand (Tony, 2026-07-16): the actual Telaio icon + Sora wordmark everywhere the
   name appears. "HQ" is set in real Sora (a 716-byte self-hosted subset of just H+Q — same
   self-host+no-CDN rule as every other font here). ---- */
@font-face {
  font-family: 'Sora HQ';
  src: url('../assets/fonts/sora-700-hq.woff2') format('woff2');
  font-weight: 700; font-style: normal; font-display: block;
}
.hq-brandmark { height: 72px; width: auto; display: block; margin: 0 auto 2px; }
.hq-brandline { display: flex; align-items: center; justify-content: center; gap: 5px; margin: 0; }
.hq-brandline img { height: 24px; width: auto; display: block; }
.hq-hq { font-family: 'Sora HQ', 'Avenir Next', -apple-system, sans-serif; font-weight: 700;
  font-size: 29px; line-height: 1; color: var(--hq-teal); letter-spacing: .01em; }
.hq-head__brand { display: flex; align-items: center; gap: 4px; }
.hq-head__brand img { height: 30px; width: auto; display: block; }
.hq-head__brand .hq-hq { font-size: 20px; }

/* Narrow phones: keep the HQ header on ONE line (brand + pill + email + esci all shrink politely) */
@media (max-width: 480px) {
  .hq-head { gap: 7px; }
  .hq-head__brand img { height: 24px; }
  .hq-head__brand .hq-hq { font-size: 18px; }
  .hq-head__who { max-width: 28vw; font-size: 10.5px; }
}

/* ---- HQ · Clienti & Visite + Codici Fondatori (2026-07-16) ---- */
.hq-chiprow { display: flex; gap: 7px; overflow-x: auto; padding-bottom: 2px; scrollbar-width: none; }
.hq-chiprow::-webkit-scrollbar { display: none; }
.hq-chip { flex: none; border: 1px solid var(--hq-line2); background: var(--hq-panel); color: var(--hq-soft);
  font: inherit; font-size: 12px; font-weight: 650; border-radius: 999px; padding: 6px 12px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-chip.on { background: var(--hq-teal); border-color: var(--hq-teal); color: #fff; }
.hq-list { display: flex; flex-direction: column; gap: 8px; }
.hq-row { display: flex; align-items: center; gap: 11px; background: var(--hq-panel); border: 1px solid var(--hq-line);
  border-radius: 13px; padding: 10px 12px; text-decoration: none; color: inherit; }
.hq-row__tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.hq-row__tx b { font-size: 15px; font-weight: 700; color: var(--hq-bright); line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.hq-row, .hq-row a, .hq-row b { -webkit-tap-highlight-color: transparent; }
.hq-row, .hq-row:visited, .hq-row:active, .hq-row:hover,
.hq-row a, .hq-row a:visited, .hq-row a:active, .hq-row a:hover { text-decoration: none !important; }
.hq-row__tx a { display: block; min-width: 0; max-width: 100%; }
.hq-row__tx b { max-width: 100%; }
.hq-row__tx a { color: inherit; text-decoration: none; }
.hq-row__tx > span { font-size: 12px; color: var(--hq-soft); }
.hq-ph { width: 46px; height: 46px; border-radius: 11px; object-fit: cover; flex: none; border: 1px solid var(--hq-line2); }
.hq-ph--empty { display: grid; place-items: center; color: var(--hq-soft); background: var(--hq-panel2); }
.hq-ph--big { width: 76px; height: 76px; border-radius: 15px; cursor: zoom-in; }
.hq-stage { flex: none; font-size: 10.5px; font-weight: 700; border-radius: 999px; padding: 4px 9px; white-space: nowrap;
  color: var(--hq-soft); border: 1px solid var(--hq-line2); }
.hq-stage--attivato { color: #14110a; background: var(--hq-gold); border-color: var(--hq-gold); }
.hq-stage--cliente { color: var(--hq-good); border-color: rgba(75, 185, 138, .4); background: rgba(75, 185, 138, .08); }
.hq-stage--codice_consegnato { color: var(--hq-teal); border-color: rgba(42, 165, 149, .45); background: rgba(42, 165, 149, .09); }
.hq-stage--perso { color: #c4473a; border-color: rgba(196, 71, 58, .45); background: rgba(196, 71, 58, .08); }

/* ---- client status chip: WHERE a venue stands, in one glance ----------------------------------
   Four states along one journey, so the colour PROGRESSES: grey (nothing yet) → amber (built, not
   live) → teal (live) → green (live AND the owner runs it = finished). Same shape/size as .hq-stage
   so the two lists stay one family. The legend below the list spells each one out. */
.hq-cst { flex: none; font-size: 10.5px; font-weight: 700; border-radius: 999px; padding: 4px 9px; white-space: nowrap;
  color: var(--hq-soft); border: 1px solid var(--hq-line2); background: transparent; }
.hq-cst--bozza { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 45%, transparent); background: color-mix(in srgb, var(--hq-gold) 9%, transparent); }
.hq-cst--online { color: var(--hq-teal); border-color: rgba(42, 165, 149, .45); background: rgba(42, 165, 149, .09); }
.hq-cst--attivo { color: var(--hq-good); border-color: rgba(75, 185, 138, .45); background: rgba(75, 185, 138, .1); }
/* The two closed-account states. They break the journey's colour progression on purpose: this is
   not a further step along the same path, it is the path ending — and 'dacanc' is the only chip in
   HQ that means "a legal obligation is waiting on you". */
.hq-cst--chiuso { color: var(--hq-soft); border-color: var(--hq-line2); background: rgba(255, 255, 255, .04); }
.hq-cst--dacanc { color: #f0a49d; border-color: rgba(224, 122, 114, .5); background: rgba(224, 122, 114, .12); }
/* 5b — the pause states: 'prova' (trial expired) red like dacanc — it wants a decision;
   'pausa' (deliberate) quiet gold — everything is fine, just off by choice. */
.hq-cst--prova { color: #f0a49d; border-color: rgba(224, 122, 114, .5); background: rgba(224, 122, 114, .12); }
.hq-cst--pausa { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 45%, transparent); background: color-mix(in srgb, var(--hq-gold) 9%, transparent); }
/* §B — the trial. Gold while it RUNS (a clock, not a problem: gold is the colour HQ already uses
   for "money is doing its work"), red once it is over. ⛔ 'nonpagato' is the loudest chip in the
   list on purpose — it is the only state where the app is dead and only Tony can end the story. */
.hq-cst--inprova { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 45%, transparent); background: color-mix(in srgb, var(--hq-gold) 9%, transparent); }
.hq-cst--provax { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 60%, transparent); background: color-mix(in srgb, var(--hq-gold) 14%, transparent); }
.hq-cst--nonpagato { color: #fff; border-color: rgba(224, 122, 114, .85); background: rgba(224, 122, 114, .5); }
/* the trial block inside the Abbonamento card: one line of state, the dates under it, then the
   date field + "Sposta" side by side so the correction is one gesture. */
.hq-trial { display: flex; flex-direction: column; gap: 7px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--hq-line2); }
.hq-trialline { margin: 0; font-size: 14px; font-weight: 700; color: var(--hq-ink, #eee); line-height: 1.4; }
.hq-trialset { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; }
.hq-trialset .input { margin: 0; }

/* ---- §3c-bis · THE OFFERTA BLOCK (2026-08-06) ------------------------------------------------
   The block Tony reads out standing in front of the owner, so it is built to be read at arm's
   length on a phone: the two entry deals as one pair of buttons (the chosen one lit), the add-ons
   as full rows with the price hard right, then THE line — bigger and brighter than anything else
   on the card, because it is the contract.
   ⚠️ Fresh class names on purpose. `.hq-check` next door has NO rule at all in this file, and the
   twin-selector trap (two rules, one selector, the last silently winning) has cost this project
   three sessions — `hq-off*` appears nowhere else. */
.hq-off__deals { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.hq-off__deal { appearance: none; cursor: pointer; text-align: left; padding: 10px 12px; border-radius: 12px;
  font: inherit; font-size: 13px; font-weight: 650; line-height: 1.35; color: var(--hq-soft);
  border: 1px solid var(--hq-line2); background: transparent; }
.hq-off__deal.is-on { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 55%, transparent);
  background: color-mix(in srgb, var(--hq-gold) 10%, transparent); }
.hq-off__deal[disabled] { cursor: default; opacity: .5; }
.hq-off__rows { display: flex; flex-direction: column; gap: 2px; }
/* ⚠️ EVERY CELL PLACED BY HAND. The hours row has THREE children (name · quantity · price) and the
   quantity spans the full width, so with automatic placement the price fell through to a third row
   and left a 40px hole under the field. Measured on the bench, not guessed. */
.hq-off__row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 10px; padding: 7px 0; }
.hq-off__pick { grid-row: 1; grid-column: 1; display: flex; align-items: flex-start; gap: 9px; cursor: pointer; min-width: 0; }
.hq-off__pick input { flex: none; width: 17px; height: 17px; margin: 1px 0 0; accent-color: var(--hq-teal); }
.hq-off__pick span { font-size: 13px; line-height: 1.4; color: var(--hq-ink); }
/* "solo oggi" is a condition of the sale, not decoration — it stays legible, just quieter. */
.hq-off__pick i { font-style: normal; font-size: 11.5px; font-weight: 700; color: var(--hq-gold); white-space: nowrap; }
.hq-off__row > b { grid-row: 1; grid-column: 2; font-size: 14px; font-weight: 800; color: var(--hq-ink);
  font-variant-numeric: tabular-nums; text-align: right; }
/* ⚠️ A QUANTITY, never a total. Narrow on purpose: a box this size cannot be mistaken for a field
   where you type 240. It drops to its own line under the name so the price stays hard right. */
.hq-off__qty { grid-row: 2; grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; padding-left: 26px; }
.hq-off__qty .input { margin: 0; width: 72px; text-align: center; font-variant-numeric: tabular-nums; }
.hq-off__qty em { font-style: normal; font-size: 12px; color: var(--hq-soft); }
/* THE LINE. Everything above exists to produce it. */
.hq-off__sum { margin: 0 0 8px; font-size: 16px; font-weight: 800; line-height: 1.35;
  color: var(--hq-bright); font-variant-numeric: tabular-nums; }
.hq-off__det { list-style: none; margin: 0 0 4px; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.hq-off__det li { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-size: 12.5px; line-height: 1.4; color: var(--hq-soft); }
.hq-off__det b { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---- the payment mirror chip (hq_payments via stripe-webhook, 2026-07-23) ---------------------
   Same family as .hq-cst. Gold while Tony's €70 is doing the work (paid/trialing), green once the
   monthly invoices flow, salmon when Stripe is retrying — the one chip that asks for attention. */
/* Tony's layout: the badge takes the whole row (as tall as it needs), the money line sits
   centered underneath, a step up from a sub. */
.hq-paychip { display: block; width: 100%; text-align: center; padding: 10px 12px; border-radius: 12px;
  font-size: 14px; font-weight: 800; letter-spacing: .01em; border: 1px solid; }
.hq-payline { margin: 0; text-align: center; font-size: 14.5px; font-weight: 600; color: var(--hq-soft); font-variant-numeric: tabular-nums; }
.hq-pay--paid_activation, .hq-pay--trialing { color: var(--hq-gold); border-color: color-mix(in srgb, var(--hq-gold) 45%, transparent); background: color-mix(in srgb, var(--hq-gold) 9%, transparent); }
.hq-pay--active { color: var(--hq-good); border-color: rgba(75, 185, 138, .45); background: rgba(75, 185, 138, .1); }
.hq-pay--past_due { color: #f0a49d; border-color: rgba(224, 122, 114, .5); background: rgba(224, 122, 114, .12); }
.hq-pay--canceled, .hq-pay--incomplete { color: var(--hq-soft); border-color: var(--hq-line2); background: rgba(255, 255, 255, .04); }

/* "Segnalato da": one quiet line + a small "cambia" (the fallback should LOOK like a fallback —
   the automatic AMICO attribution is the main road). ⭐ The <select> is an INVISIBLE overlay on
   the whole row: a tap anywhere on it is a tap on the native control, so the phone opens ITS OWN
   picker directly — never a second styled list on screen (the 2026-07-23 double-picker bug). */
.hq-refrow { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 0; }
.hq-refval { font-size: 14px; font-weight: 600; color: var(--hq-ink, #eee); }
.hq-refval .hq-refauto { font-style: normal; font-size: 12px; font-weight: 700; color: var(--hq-good); }
.hq-reflink { font-size: 12.5px; font-weight: 700; color: var(--hq-teal); }
.hq-refsel { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; border: 0; margin: 0; padding: 0;
  -webkit-appearance: none; appearance: none; cursor: pointer; }

/* No legend: the labels say what they mean ("Bozza App", "Manager Attivo"). A longer label needs the
   row's TEXT to give way, not the chip — so the text column shrinks/ellipsises and the chip stays whole. */
.hq-row .hq-cst { max-width: 45%; overflow: hidden; text-overflow: ellipsis; }
.hq-stars { display: inline-flex; gap: 2px; }
.hq-stars svg { stroke: var(--hq-gold); }
.hq-stars svg.on { fill: var(--hq-gold); }
.hq-stars--edit { gap: 7px; margin-top: 3px; }
.hq-stars--edit svg { cursor: pointer; }
.hq-scorelbl { font-size: 11px; color: var(--hq-soft); margin-top: 7px; }
.hq-idrow { display: flex; gap: 13px; align-items: flex-start; }
.hq-idtx { display: flex; flex-direction: column; min-width: 0; }
.hq-idtx > b { font-size: 21px; font-weight: 800; color: var(--hq-bright); letter-spacing: -.01em; line-height: 1.15; }
.hq-idtx > span { font-size: 12.5px; color: var(--hq-soft); display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 3px; }
.hq-banner { background: var(--hq-panel); border: 1px solid var(--hq-line2); border-radius: 13px; padding: 12px 14px;
  font-size: 14px; color: var(--hq-ink); }
.hq-banner b { color: var(--hq-bright); font-variant-numeric: tabular-nums; }
.hq-banner--gold { background: linear-gradient(150deg, rgba(243, 177, 75, .16), rgba(243, 177, 75, .06));
  border-color: rgba(243, 177, 75, .4); color: var(--hq-cream); }
.hq-banner--gold b { color: var(--hq-gold); }
.hq-banner--good { border-color: rgba(75, 185, 138, .4); background: rgba(75, 185, 138, .08); color: var(--hq-good); }
/* The account the owner CLOSED and whose 30 days are up — his photos and his login are still alive
   and only a human can remove them. The one banner in HQ that means "act now", so it is the only
   one that reads as an alarm rather than a note. */
.hq-banner--danger { border-color: rgba(224, 122, 114, .5); background: rgba(224, 122, 114, .1); color: #f0a49d; }
.hq-actrow { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.hq-actrow .btn:only-child { grid-column: 1 / -1; }
.hq-card { background: var(--hq-panel); border: 1px solid var(--hq-line); border-radius: 13px; padding: 8px 13px 11px;
  display: flex; flex-direction: column; gap: 2px; }
.hq-crow { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--hq-ink); padding: 6px 0;
  border-bottom: 1px solid var(--hq-line); }
.hq-crow:last-of-type { border-bottom: none; }
.hq-crow svg { color: var(--hq-teal); flex: none; }
.hq-crow a { color: var(--hq-bright); text-decoration: none; }
.hq-crow__wa { margin-left: auto; flex: none; }
.hq-crow__wa svg { color: inherit; }
.hq-missing { color: var(--hq-soft); font-style: italic; }
.hq-tag { margin-left: auto; font-size: 10px; font-weight: 700; color: var(--hq-gold);
  border: 1px solid rgba(243, 177, 75, .4); border-radius: 999px; padding: 3px 8px; white-space: nowrap; }
.hq-card .btn { align-self: stretch; margin-top: 10px; }
.hq-form { display: flex; flex-direction: column; gap: 10px; }
.hq-photo-add { display: flex; align-items: center; gap: 11px; font-size: 13.5px; color: var(--hq-soft); cursor: pointer; }
.hq-tl { display: flex; flex-direction: column; padding: 2px 4px; }
.hq-ev { display: flex; gap: 10px; font-size: 12.5px; color: var(--hq-soft); padding: 3px 0; }
.hq-ev span { color: var(--hq-gold); font-variant-numeric: tabular-nums; width: 52px; flex: none; }
.hq-ev b { color: var(--hq-ink); font-weight: 650; }
.hq-ev i { font-style: normal; color: var(--hq-soft); }
.hq-del { align-self: stretch; width: 100%; border: 1px solid rgba(196, 71, 58, .5); border-radius: 11px;
  background: rgba(196, 71, 58, .08); color: #e07a72; font: inherit; font-size: 14.5px; font-weight: 700;
  padding: 12px 10px; cursor: pointer; margin-top: 6px; -webkit-tap-highlight-color: transparent; }
.hq-del:hover { background: rgba(196, 71, 58, .16); }
/* replaces "Elimina locale" while the venue still has an app — a NOTICE, not a dead button: gold text
   + the house "read this before you act" triangle, on a gold-tinted hairline. Deliberately not a
   filled slab — it sits on every client card that has an app, permanently, so it has to stay quiet
   enough to live there without shouting, while still catching the eye when you go looking to delete. */
.hq-del-blocked { display: flex; align-items: flex-start; gap: 9px; margin: 0; padding: 11px 13px;
  border: 1px dashed color-mix(in srgb, var(--hq-gold) 55%, transparent); border-radius: 11px;
  background: color-mix(in srgb, var(--hq-gold) 7%, transparent);
  font-size: 12.5px; line-height: 1.45; color: var(--hq-gold); text-align: left; }
.hq-del-blocked i { flex: none; font-size: 17px; line-height: 1.25; }
/* ⚠️⚠️ THE HQ PALETTE IS REPEATED HERE ON PURPOSE (2026-07-27). Those variables are declared on `.hq`,
   and every HQ modal is appended to document.body — OUTSIDE that element — so `var(--hq-gold)` and
   friends resolve to NOTHING inside a dialog. The older dialogs survived on luck: their panel
   background is a literal colour and an undefined `color` just inherits. The area form did not: its
   SELECTED icon is dark text on `var(--hq-gold)`, so with no gold it rendered as a black square on a
   black square and read as "this icon is unavailable" (Tony, on device). Declaring them on the modal
   fixes that and hardens every existing dialog against the same trap. */
.hq-modal { position: fixed; inset: 0; z-index: 1000; background: rgba(0, 0, 0, .6); display: grid; place-items: center; padding: 24px;
  --hq-bg1: #161719; --hq-bg2: #0e0f10;
  --hq-panel: rgba(32, 34, 32, .72); --hq-panel2: rgba(44, 49, 45, .7);
  --hq-ink: #e5e5e5; --hq-soft: #9aa39d; --hq-bright: #f2f7f4;
  --hq-line: #262b28; --hq-line2: #39403b;
  --hq-teal: #2aa595; --hq-green: #1e6b4d; --hq-gold: #f3b14b; --hq-good: #4bb98a;
  --hq-cream: #f4f1e8; }
/* ⚠️⚠️ AND THE PANEL SCROLLS ITSELF. Without a height limit a tall dialog simply overflowed the
   screen: the page behind it moved instead, and Tony was stuck on a form he could not reach the
   bottom of. The house rules for this, learned the hard way on the customer app's modals:
     · `svh` and NOT `vh` — iOS `vh` counts a browser chrome that is not there, so the panel is cut off
     · `overscroll-behavior: contain` — the scroll stops at the panel instead of dragging the page
     · ⛔ NEVER `position: fixed` on the body to "lock" it: iOS then stops painting tall modals
     · ⛔ NEVER `-webkit-overflow-scrolling: touch` — banned in this codebase, it breaks tall panels */
/* ⚠️ WIDENED 2026-07-28, and it was TWO problems, not one (Tony asked twice — "why is this modal so
   much thinner than the others"). On a big screen the 340px cap was simply too tight for a form with
   an icon grid and a colour row. On a PHONE the cap was never reached: the overlay's own 24px padding
   on both sides was eating 48px of a 375px screen, so the panel could not fill the width whatever the
   cap said. Both are fixed here — the cap for desktop, the padding for the phone. */
.hq-modal__panel { width: 100%; max-width: 560px; background: #1a1d1b; border: 1px solid var(--hq-line2); border-radius: 16px;
  padding: 20px 18px 16px; color: var(--hq-ink); box-shadow: 0 30px 70px rgba(0, 0, 0, .55);
  max-height: calc(100svh - 48px); overflow-y: auto; overscroll-behavior: contain; }
@media (max-width: 520px) { .hq-modal { padding: 12px; } .hq-modal__panel { max-height: calc(100svh - 24px); } }
/* ⭐ A switch row on an HQ form: label + explanation on the left, the toggle hard right. Same
   `.switch` control the Manager uses, so "is this on?" looks identical everywhere in the product. */
.hq-swrow { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--hq-line2); }
.hq-swrow:last-of-type { border-bottom: 0; }
.hq-swtx { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.hq-swtx b { font-size: 14px; font-weight: 650; color: var(--hq-ink); }
.hq-swtx em { font-style: normal; font-size: 12.5px; line-height: 1.45; color: var(--hq-soft); }
.hq-modal__panel p { margin: 0 0 16px; font-size: 14px; line-height: 1.5; }
.hq-modal__row { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.hq-row--code .hq-code { font-weight: 800; font-size: 14.5px; color: var(--hq-cream); font-variant-numeric: tabular-nums; min-width: 62px; flex: none; }
.hq-code--dead { text-decoration: line-through; color: var(--hq-soft); }
.hq-fchip { flex: none; font-size: 11px; font-weight: 700; border-radius: 999px; padding: 4px 9px; white-space: nowrap;
  color: var(--hq-soft); border: 1px solid var(--hq-line2); font-variant-numeric: tabular-nums; }
.hq-fchip--gold { color: #14110a; background: var(--hq-gold); border-color: var(--hq-gold); }
.hq-fchip--good { color: var(--hq-good); border-color: rgba(75, 185, 138, .4); background: rgba(75, 185, 138, .08); }
.hq-fchip--dead { color: #c4473a; border-color: rgba(196, 71, 58, .45); background: rgba(196, 71, 58, .08); }
.hq-wabtn { border: 1px solid var(--hq-line2); background: #1c201d; color: var(--hq-teal); border-radius: 10px;
  width: 36px; height: 36px; display: grid; place-items: center; cursor: pointer; flex: none; padding: 0; }
/* (its `line-height:1` icon rule is gone: it is a grid, so its <i> was already blockified — see the
   measurement note by `.revchosen__x`. Removing it changed nothing on screen.) */
/* The recovery-saga lesson, applied to HQ: display:flex/grid on a class DEFEATS the hidden
   attribute — make [hidden] win everywhere inside .hq. */
.hq [hidden] { display: none !important; }
.hq-btn-gold { background: var(--hq-gold); color: #14110a; border: none; font-weight: 700; }
.hq-btn-gold:hover { filter: brightness(1.06); }
.hq-remswitch { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.hq-remswitch > span { display: flex; align-items: center; gap: 8px; color: var(--hq-bright); font-weight: 650; }
.hq-remswitch svg { color: var(--hq-teal); }
.hq-row--due { border-color: rgba(243, 177, 75, .45); }
.hq-tile__dot { position: absolute; top: 11px; right: 11px; min-width: 22px; height: 22px; border-radius: 999px;
  background: #e0453a; color: #fff; font-size: 12px; font-weight: 800; display: grid; place-items: center;
  padding: 0 6px; font-variant-numeric: tabular-nums; box-shadow: 0 2px 8px rgba(0, 0, 0, .4); }

/* ---- HQ · Nuovo Cliente wizard (block 1, 2026-07-16) ---- */
.hq-steps { display: flex; gap: 6px; flex-wrap: wrap; }
.hq-step { font-size: 11.5px; font-weight: 700; color: var(--hq-soft); background: var(--hq-panel);
  border: 1px solid var(--hq-line); border-radius: 999px; padding: 5px 11px; }
.hq-step.on { color: #14110a; background: var(--hq-gold); border-color: var(--hq-gold); }
.hq-step.done { color: var(--hq-teal); border-color: rgba(42, 165, 149, .45); }
.hq-lbl { margin: 4px 2px -4px; font-size: 11px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--hq-soft); }
.hq-nw-note { font-style: normal; text-transform: none; letter-spacing: 0; font-weight: 500; opacity: .8; }
.hq-tpl { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
.hq-tpl__t { position: relative; display: flex; flex-direction: column; gap: 3px; text-align: left;
  background: var(--hq-panel); border: 1px solid var(--hq-line2); border-radius: 13px; padding: 13px 12px;
  font-family: inherit; color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-tpl__t b { font-size: 15px; font-weight: 700; color: var(--hq-bright); }
.hq-tpl__t span { font-size: 11.5px; color: var(--hq-soft); line-height: 1.35; }
.hq-tpl__t.sel { border-color: var(--hq-teal); background: rgba(42, 165, 149, .1); }
.hq-tpl__t.sel::after { content: "✓"; position: absolute; top: 10px; right: 12px; color: var(--hq-teal); font-weight: 800; }
.hq-tpl__t.soon { opacity: .45; cursor: default; }
.hq-tpl__t.soon i { position: absolute; top: 9px; right: 9px; font-style: normal; font-size: 10px;
  font-weight: 700; color: var(--hq-soft); border: 1px solid var(--hq-line2); border-radius: 999px; padding: 2px 7px; }
.hq-nw-slugrow { display: flex; align-items: center; gap: 8px; }
.hq-nw-slugrow .input { flex: 1; min-width: 0; }
.hq-nw-slugrow > span { font-size: 13px; color: var(--hq-soft); white-space: nowrap; }
.hq-nw-slugmsg { margin: 5px 2px 0; font-size: 12px; color: var(--hq-soft); min-height: 1em; }
.hq-nw-slugmsg.ok { color: var(--hq-good); }
.hq-nw-slugmsg.bad { color: #e08578; }
.brand-rowx { display: flex; align-items: center; gap: 12px; background: var(--hq-panel);
  border: 1px solid var(--hq-line); border-radius: 13px; padding: 11px 13px; }
.brand-rowx input[type="color"] { width: 44px; height: 40px; padding: 2px; border: 1px solid var(--hq-line2);
  border-radius: 10px; background: var(--hq-panel2); cursor: pointer; flex: none; }
.brand-rowx b { font-size: 13px; color: var(--hq-bright); }
.hq-nw-hex { max-width: 120px; margin-top: 4px; padding: 6px 9px; font-size: 13px; }
.hq-wardrobe { display: flex; flex-direction: column; gap: 8px; }
.hq-wardrobe__f { display: flex; flex-direction: column; gap: 2px; text-align: left; background: var(--hq-panel);
  border: 1px solid var(--hq-line2); border-radius: 13px; padding: 11px 13px; font-family: inherit;
  color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-wardrobe__f b { font-size: 20px; font-weight: 600; color: var(--hq-bright); line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hq-wardrobe__f span { font-size: 11px; color: var(--hq-soft); }
.hq-wardrobe__f.sel { border-color: var(--hq-teal); background: rgba(42, 165, 149, .1); }
.hq-nw-preview { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  background: var(--hq-panel); border: 1px solid var(--hq-line2); border-radius: 16px; padding: 18px 14px; }
.hq-nw-preview img { max-height: 64px; max-width: 70%; object-fit: contain; }
.hq-nw-preview > b { font-size: 24px; color: var(--hq-bright); }
.hq-nw-preview > span { font-size: 12px; color: var(--hq-soft); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center; }
.hq-nw-sw { display: inline-block; width: 13px; height: 13px; border-radius: 4px; background: var(--c); border: 1px solid rgba(255,255,255,.2); }
.hq-nw-addr { font-size: 15px; font-weight: 800; color: var(--hq-teal); font-variant-numeric: tabular-nums; }
.hq-nw-addr i { font-style: normal; color: var(--hq-soft); font-weight: 600; }
.hq-nw-wdlink { font-size: 12px; color: var(--hq-soft); text-decoration: none; align-self: flex-start; padding: 2px; }
.hq-nw-wdlink:active { color: var(--hq-teal); }

/* The app-icon preview: the logo exactly as it will sit in the icon square (signature colour bg) */
.hq-iconrow { display: flex; align-items: center; gap: 14px; }
.hq-iconprev { width: 84px; height: 84px; flex: none; border-radius: 19px; background: var(--c);
  border: 1px solid rgba(255, 255, 255, .12); display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .35); }
/* 90% content (Tony, 2026-07-17: 66% read too small) — matches the shipped iOS/any icons;
   Android circle masks get their own separately-baked 62% variant, so nothing ever clips. */
.hq-iconprev img { max-width: 90%; max-height: 90%; object-fit: contain; }
/* No logo → the monogram the icon factory actually bakes (white, serif, same 44%-of-the-square
   proportion as makeIconSet) — the preview must not flatter what the phone will really show. */
.hq-iconprev__mono { font-family: Georgia, serif; font-weight: 600; font-size: 37px; color: #fff; line-height: 1; }
.hq-iconwrap { display: flex; flex-direction: column; align-items: center; gap: 6px; flex: none; }
.hq-iconprev__lbl { font-size: 11px; color: var(--hq-bright); max-width: 88px; text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-height: 1em; }
.hq-iconrow__tx { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.hq-iconrow__tx b { font-size: 13px; color: var(--hq-bright); }
.hq-iconrow__tx > span { font-size: 11.5px; color: var(--hq-soft); line-height: 1.35; }
.hq-iconrow__crop { align-self: flex-start; margin-top: 5px; padding: 7px 16px; font-size: 13px; }
/* "Ritaglia" on a logo row — same weight as the one that used to sit under the Icona App, so
   moving it reads as the same button in a better place, not a new one. */
.hq-logotile__crop { align-self: flex-start; margin-top: 6px; padding: 6px 15px; font-size: 12.5px; }
/* The client's "face" once his app exists: the icon itself (logo on the signature colour) */
/* A label+input+note group inside a form: restores the form's own rhythm (the bare wrapper
   div was collapsing it, leaving the label glued to its field). */
.hq-fieldblock { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
/* Two colour pickers on one row: Colore firma | Colore background (Tony, 2026-07-17) */
.hq-colrow { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.hq-colrow .brand-rowx { min-width: 0; }
.hq-colrow .hq-nw-hex { max-width: 100%; }
.hq-sep { border: none; border-top: 1px solid var(--hq-line2); margin: 2px 0; }

/* Logo TILES (Tony 2026-07-17): each logo judged like the Icona App — a square mini-header
   preview: Colore background ground, the logo whole, the Colore firma rule at the bottom. */
.hq-logotile { display: flex; align-items: center; gap: 14px; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-logotile__sq { position: relative; width: 84px; height: 84px; flex: none; border-radius: 19px;
  background: var(--bg); border: 1px solid rgba(255, 255, 255, .12); display: flex; align-items: center;
  justify-content: center; overflow: hidden; box-shadow: 0 4px 14px rgba(0, 0, 0, .35); }
.hq-logotile__sq img { max-width: 74%; max-height: 58%; object-fit: contain; margin-bottom: 6px; }
/* Icona App preview (Tony 2026-07-24): a photo FILLS the icon square (cover, no colour band); a
   logo/vector keeps the fit-whole look. No header rule line — this is the icon, not a header. */
[data-tile-icon] .hq-logotile__rule { display: none; }
[data-tile-icon] .hq-logotile__sq.hq-iconfill img { max-width: 100%; max-height: 100%; width: 100%; height: 100%; object-fit: cover; margin: 0; border-radius: inherit; }
.hq-logotile__empty { color: rgba(255, 255, 255, .45); margin-bottom: 6px; }
.hq-logotile__rule { position: absolute; left: 50%; transform: translateX(-50%); bottom: 11px;
  width: 62%; height: 2px; border-radius: 2px; background: var(--fx); }
/* The ✕ that clears a logo. INSIDE the square — the tile is overflow:hidden, so a badge hanging off
   the corner would be sliced. Deliberately modest (26px): it sits on top of a tile whose whole
   surface opens the file picker, so an oversized target would make an accidental wipe likelier than
   a deliberate one. Dark disc + hairline so it reads on a logo of any colour. */
.hq-logotile__x { position: absolute; top: 5px; right: 5px; width: 26px; height: 26px; padding: 0;
  display: grid; place-items: center; border-radius: 50%; cursor: pointer;
  background: rgba(0, 0, 0, .62); border: 1px solid rgba(255, 255, 255, .3); color: #fff;
  -webkit-tap-highlight-color: transparent; }
.hq-logotile__x:active { background: rgba(0, 0, 0, .85); transform: scale(.92); }
.hq-logotile__tx { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.hq-logotile__tx b { font-size: 13px; color: var(--hq-bright); }
.hq-logotile__tx > span { font-size: 11.5px; color: var(--hq-soft); line-height: 1.35; }

/* The Sfondo picker: samples of each shipped texture in a horizontal shelf; ring = chosen. */
.hq-bgrow { display: flex; gap: 10px; overflow-x: auto; padding: 2px 2px 8px; scrollbar-width: none; }
.hq-bgrow::-webkit-scrollbar { display: none; }
.hq-bgtile { flex: none; width: 62px; height: 96px; border-radius: 12px; overflow: hidden; padding: 0;
  border: 2px solid var(--hq-line2); background: var(--hq-panel2); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-bgtile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hq-bgtile.sel { border-color: var(--hq-gold); box-shadow: 0 0 0 2px rgba(243, 177, 75, .35); }

/* Sfondo Intensità: full art at 100%, softer toward the dark ground as it drops */
/* "Norm Col" — the per-client escape hatch for the Manager's panel wash (Tony 2026-07-24).
   A plain checkbox on purpose: HQ has no switch component, and inventing one for a single
   cosmetic toggle is the over-build Tony called out. */
.hq-normcol { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; margin: 2px 0 4px; }
.hq-normcol input { flex: none; width: 18px; height: 18px; margin-top: 1px; accent-color: var(--c-primary); }
.hq-normcol > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hq-normcol b { font-size: 13px; }
.hq-bgop { display: flex; align-items: center; gap: 12px; }
.hq-bgop > span { font-size: 12px; color: var(--hq-soft); flex: none; }
.hq-bgop input[type="range"] { flex: 1; accent-color: var(--hq-teal); min-width: 0; }
.hq-bgop b { font-size: 13px; color: var(--hq-bright); font-variant-numeric: tabular-nums; flex: none; width: 44px; text-align: right; }

/* The wardrobe as a DROPDOWN: closed = only the chosen face + a chevron; tap to open. */
.hq-wardrobe--closed .hq-wardrobe__f:not(.sel) { display: none; }
.hq-wardrobe--closed .hq-wardrobe__f.sel { position: relative; }
.hq-wardrobe--closed .hq-wardrobe__f.sel::after { content: '⌄'; position: absolute; right: 14px; top: 50%;
  transform: translateY(-60%); font-size: 18px; color: var(--hq-soft); }

/* the living dots of the "sto lavorando" overlay */
.hq-dots i { font-style: normal; animation: hq-dot 1.2s infinite; }
.hq-dots i:nth-child(2) { animation-delay: .2s; }
.hq-dots i:nth-child(3) { animation-delay: .4s; }
@keyframes hq-dot { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }
.hq-icontile { background: var(--c); display: flex; align-items: center; justify-content: center; }
.hq-icontile img { max-width: 90%; max-height: 90%; object-fit: contain; }

/* Clienti list rows: the app address line under the name */
.hq-row__app { font-size: 12px; font-weight: 700; color: var(--hq-teal); }
.hq-row__app i { font-style: normal; color: var(--hq-soft); font-weight: 600; }
.hq-row__app.none { color: var(--hq-soft); font-weight: 500; font-style: italic; }

/* "La sua app" on the client card */
.hq-appcard { gap: 10px; }
.hq-appcard__id { display: flex; flex-direction: column; align-items: center; gap: 5px; text-align: center; padding: 6px 0 2px; }
.hq-appcard__id img { max-height: 56px; max-width: 65%; object-fit: contain; }
.hq-appcard__id > b { font-size: 22px; color: var(--hq-bright); }
.hq-appcard__id > span { font-size: 12px; color: var(--hq-soft); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: center; }
/* The ADDRESS is not one of the little detail lines above, but the `> span` rule caught it too:
   the 6px flex gap opened a space in the middle of the address ("testlab2 .studiocorocher.com")
   and the 12px grey washed out the one line on this card that is the actual product. It keeps its
   own identity here, and it's a real link once the app is live. */
.hq-appcard__id > .hq-nw-addr { display: block; font-size: 15px; color: var(--hq-teal); text-decoration: none; }
.hq-appst { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: 99px; }
.hq-appst--bozza { color: var(--hq-gold); background: rgba(212, 160, 55, .12); border: 1px solid rgba(212, 160, 55, .35); }
.hq-appst--provisioning { color: var(--hq-soft); background: rgba(255, 255, 255, .06); border: 1px solid var(--hq-line2); }
/* double class = outranks the .hq-appcard__id > span colour/size (it was washing the text grey) */
.hq-appst.hq-appst--live { color: #14110a; background: var(--hq-gold); border: 1px solid var(--hq-gold);
  font-size: 14px; font-weight: 800; padding: 7px 18px; letter-spacing: .02em; }
/* the whole panel celebrates: an ONLINE app is the product, delivered */
.hq-appcard--live { border-color: rgba(243, 177, 75, .5); background: rgba(243, 177, 75, .05);
  box-shadow: 0 0 0 1px rgba(243, 177, 75, .12), 0 6px 22px rgba(0, 0, 0, .25); }

/* The MANAGER chapter — "his login" block on the live card, + the credentials result sheet */
.hq-mgr { display: flex; flex-direction: column; gap: 8px; padding-top: 11px; margin-top: 3px; border-top: 1px solid var(--hq-line2); }
.hq-mgr .hq-lbl { margin: 0; }
.hq-mgr-done { display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 700; color: var(--hq-good); }
.hq-mgr-done span { color: var(--hq-soft); font-weight: 500; word-break: break-all; }
/* "Reimposta password" — the quiet rescue under the green line. Deliberately NOT gold (gold is the
   create action) and NOT red (red is delete): a small, calm, self-sized button, left-aligned. */
.hq-mgr-reset { align-self: flex-start; display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; padding: 7px 12px; }
.hq-modal__panel .hq-mgr-title { font-size: 17px; font-weight: 800; color: var(--hq-bright); text-align: center; margin: 0 0 14px; }
.hq-sub--warn { color: var(--hq-bright); background: rgba(243, 177, 75, .1);
  border: 1px solid rgba(243, 177, 75, .35); border-radius: 9px; padding: 8px 10px; margin-top: 10px; }
.hq-cred { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--hq-line2); }
.hq-cred span { font-size: 12px; color: var(--hq-soft); flex-shrink: 0; }
.hq-cred b { font-size: 14px; color: var(--hq-bright); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-align: right; word-break: break-all; }
.hq-cred:last-of-type { border-bottom: none; }

/* Font wardrobe showcase (#/hq/font) */
.hq-wd { display: flex; flex-direction: column; gap: 10px; }
/* The group heading inside a font list (Serif · Slab · Sans · Corsivo · Scritto a mano). Faces sit
   next to their own kind so the comparison is fair — 2026-07-28, Tony's call. */
.hq-wd__g { display: flex; align-items: baseline; gap: 8px; margin: 14px 0 -2px; font-size: 11px;
  font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--hq-soft); }
.hq-wd__g:first-child { margin-top: 0; }
.hq-wd__g span { font-weight: 600; letter-spacing: 0; opacity: .6; }
.hq-wardrobe .hq-wd__g { grid-column: 1 / -1; }
.hq-wd__f { position: relative; display: flex; flex-direction: column; gap: 3px; text-align: left; background: var(--hq-panel);
  border: 1px solid var(--hq-line2); border-radius: 15px; padding: 16px 15px 13px; font-family: inherit;
  color: inherit; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.hq-wd__f b { font-size: 27px; font-weight: 600; color: var(--hq-bright); line-height: 1.15;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hq-wd__f span { font-size: 11px; color: var(--hq-soft); }
.hq-wd__f i { font-style: normal; font-size: 11px; font-weight: 700; color: var(--hq-soft); margin-top: 4px; }
.hq-wd__f.on { border-color: var(--hq-gold); background: rgba(212, 160, 55, .07); }
.hq-wd__f.on i { color: var(--hq-gold); }

/* ---- Legal documents in the footer modal (2026-07-23, #5 pre-launch) ----
   /termini + /privacy are fetched into the cardmodal (app.js openLegal); their own page styles are
   inline in their <head> and do NOT ride along with <main id="doc"> — these rules restyle that
   content inside the modal. Scoped under .legal-doc so nothing leaks into other dialogs. */
.legal-doc { margin-top: 4px; font-size: 14.5px; line-height: 1.6; color: var(--ink); text-align: left; }
.legal-doc__updated { margin: 0 0 6px; color: var(--muted); font-size: 12.5px; }
.legal-doc h2 { font-size: 16px; font-weight: 700; margin: 20px 0 6px; }
.legal-doc h2 .n { color: var(--c-primary, #149186); margin-right: 5px; }
.legal-doc h3 { font-size: 14.5px; font-weight: 700; margin: 12px 0 4px; }
.legal-doc p { margin: 7px 0; }
.legal-doc ul { margin: 7px 0 7px 18px; padding: 0; }
.legal-doc li { margin: 4px 0; }
.legal-doc .muted { color: var(--muted); }
.legal-doc .callout { background: rgba(255, 255, 255, .04); border: 1px solid rgba(255, 255, 255, .1);
  border-left: 3px solid var(--c-primary, #149186); border-radius: 10px; padding: 10px 12px; margin: 10px 0; }
.legal-doc table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 13px; }
.legal-doc th, .legal-doc td { text-align: left; padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, .1); vertical-align: top; }
.legal-doc th { color: var(--muted); font-weight: 600; }
.legal-doc code { font-size: 12.5px; background: rgba(255, 255, 255, .07); border-radius: 4px; padding: 1px 5px; }
.legal-doc a { color: var(--c-primary, #149186); text-decoration: underline; }

/* ── HQ · Aree dell'App (hotels wave 2, piece 22) ──────────────────────────────────────────────
   One row per area of a client's app: its icon in its own colour, its name, the machine id under it,
   then move-up / move-down / edit / delete. Built from the HQ variables already in this file so it
   sits in the console rather than beside it. */
.hq-arearow { display: flex; align-items: center; gap: 11px; padding: 9px 11px;
  border: 1px solid var(--hq-line2); border-radius: 12px; background: var(--hq-panel); }
.hq-areaico { flex: none; width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  color: var(--c); background: color-mix(in srgb, var(--c) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--c) 35%, transparent); }
.hq-areatxt { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.hq-areatxt b { font-size: 14px; color: var(--hq-bright); overflow-wrap: anywhere; }
/* The machine id is deliberately quiet: Tony needs to see it, an owner never does. */
.hq-areatxt em { font-size: 11px; font-style: normal; color: var(--hq-soft); overflow-wrap: anywhere; }
.hq-areaacts { flex: none; display: flex; gap: 4px; }
.hq-areabtn { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--hq-line2); color: var(--hq-soft); cursor: pointer; }
.hq-areabtn:hover:not(:disabled) { color: var(--hq-bright); border-color: var(--hq-soft); }
/* ⚠️ A disabled arrow (first row can't go up) must LOOK unavailable, or it reads as a broken button. */
.hq-areabtn:disabled { opacity: .3; cursor: default; }
.hq-areabtn--danger:hover { color: #e08578; border-color: rgba(196, 71, 58, .5); }

/* The icon + colour pickers in the area form. A short curated set on purpose — 20 icons is a better
   decision than 300, and every one of these already ships in the sprite. */
.hq-icongrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); gap: 6px; margin: 0 0 14px; }
.hq-iconpick { aspect-ratio: 1; border-radius: 9px; display: grid; place-items: center; cursor: pointer;
  background: var(--hq-panel); border: 1px solid var(--hq-line2); color: var(--hq-soft); }
.hq-iconpick.is-on { color: #14110a; background: var(--hq-gold); border-color: var(--hq-gold); }
.hq-colorpick { aspect-ratio: 1; border-radius: 9px; cursor: pointer; background: var(--c);
  border: 2px solid transparent; }
.hq-colorpick.is-on { border-color: var(--hq-bright); }
/* The form's own labels sit tighter than the card's, so the modal doesn't scroll on a phone. */
.hq-modal__panel .hq-lbl { margin: 0 0 5px; }
.hq-modal__panel .input { margin: 0 0 4px; }

/* HQ · the SETS rows in the area form (piece 39) — one input per list Tony defines. */
.hq-setrows { display: flex; flex-direction: column; gap: 6px; margin: 0 0 6px; }
.hq-setrows .input { margin: 0; }
.hq-setadd { width: 100%; margin: 0 0 14px; }

/* HQ · the per-area FIELD SWITCHES (§5) — what an item in this area carries. Same pill as the icon
   grid so the form reads as one thing, but text-width instead of square. */
.hq-pillrow { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 14px; }
.hq-pill { padding: 7px 13px; border-radius: 9px; cursor: pointer; font-size: 13px; font-weight: 600;
  background: var(--hq-panel); border: 1px solid var(--hq-line2); color: var(--hq-soft); }
.hq-pill.is-on { color: #14110a; background: var(--hq-gold); border-color: var(--hq-gold); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   §C STEP 4 · THE THREE HOTEL LOOKS — Classico · Elegante · Moderno (2026-07-28)
   ⛔ HOTEL ONLY. Every selector below starts at .hrooms, which exists on no restaurant or bar page.
   Nothing here can move Pane&Vino, and nothing here borrows from the menu paper or the Specialità
   card — that is the rule the whole hotel UI is built under.
   ⭐ Each look degrades on its own terms when a hotel has sent no photos yet (the normal day-one
   state): the photo-less rules sit beside each look rather than in one shared "no image" block, so a
   missing picture reads as a design choice instead of a hole.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */
.hrooms { display: flex; flex-direction: column; }
.hroom { position: relative; text-align: left; }
.hroom[data-card] { cursor: pointer; }
.hroom__name { font-family: var(--font-display); margin: 0; }
.hroom__text { margin: 0; }
/* ⭐ §E item 2 — the room's price, printed only when the owner typed one.
   ⭐ ON THE TITLE'S OWN ROW, pushed right (Tony, 2026-07-31, after seeing it on its own line): "we
   lose vertical space… all the way to the right like you did in the opened card. Looks cleaner (in
   all Hotel styles)." A card is a scarce screenful, and a price is one short token — it costs a whole
   line to say four characters, and the name's line has room to spare.
   ⚠️ BASELINE, not centre: the name is 27px and the price 16px, so centring them makes the price
   float and the pair stops reading as one heading. `min-width:0` on the name lets a long room name
   take the space it needs without ever shoving the price off the edge.
   ⛔ NEVER var(--c-primary) — see .hfeat__price. And the UI face, not the display face: a display
   font is chosen for a room NAME, and several of the eighteen render digits badly at small sizes. */
.hroom__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.hroom__head .hroom__name { min-width: 0; }
.hroom__price { flex: none; font-family: var(--font-ui); font-variant-numeric: tabular-nums;
  white-space: nowrap; color: var(--ink); }
.hroom .tagrow { margin-top: 11px; }

/* ── CLASSICO · the printed brochure ──────────────────────────────────────────────────────────── */
.hrooms--classico { gap: 16px; }
.hroom--classico { background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-card); overflow: hidden; box-shadow: var(--shadow-sm); }
.hroom--classico .hroom__cover { display: block; width: 100%; height: 214px; object-fit: cover; }
.hroom--classico .hroom__body { padding: 18px 20px 20px; }
/* ⭐⭐ ASK FOR THE WEIGHT THE FACE ACTUALLY HAS (2026-07-30). --font-display-w carries the client
   font's own single weight (js/data/fonts.js). Seventeen of the twenty client faces ship exactly ONE
   weight, so a rule naming 700 or 750 asked for something the file does not contain.
   ⚠️ MEASURED: on Chrome the engine matches DOWN to the real weight rather than faking a heavier one
   (Outfit at 600 and at 750 render identical advance widths), so the "faux bold" I first claimed here
   is UNCONFIRMED — and iOS Safari, the engine that matters, could not be tested. The change stands on
   a different reason: the wardrobe's faces genuinely differ (Outfit 600, Baskerville 700, Marcellus
   400), and only asking for the face's OWN weight renders each one as it was drawn.
   ⛔ HOTEL-ONLY ON PURPOSE. .card__title, .spec-card__title and .menu-thumb__name also name
   --font-display, but they are SHARED with the restaurant: changing them would move Pane&Vino, and no
   gate compares CSS. They keep their literal 600 until that is a decision Tony makes. */
.hroom--classico .hroom__name { font-weight: var(--font-display-w, 600); font-size: calc(27px * var(--font-display-scale, 1)); line-height: 1.1; color: var(--ink); }
/* The brochure look: the price is quiet beside a big name, not a competing headline. */
.hroom--classico .hroom__price { font-size: 16px; font-weight: 700; }
/* The little rule under the name is the brochure's one flourish, and it wears the venue's colour. */
.hroom--classico .hroom__rule { width: 46px; height: 2px; margin: 12px 0 13px; border-radius: 2px;
  background: var(--c-accent); }
/* ⭐ THREE LINES, THEN "…" (Tony, 2026-07-31). ⚠️ This REVERSES the brochure's founding idea — the
   header of this file still describes Classico as showing "the FULL text. Nothing to tap to know
   everything", and that is no longer true: he asked for 3 lines here and 2 on Elegante, so a long
   room now has to be opened. Written down because it looks like a bug to anyone reading that comment.
   ⚠️ Clamped BY LINE, not by characters, so the cut lands correctly in Italian and in German alike. */
.hroom--classico .hroom__text { font-size: 14.5px; line-height: 1.62; color: var(--ink-soft);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.hroom--classico .hroom__more { margin-top: 15px; display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; letter-spacing: .09em; text-transform: uppercase; font-weight: 700; color: var(--c-primary); }
/* No photo → the brochure keeps its shape and puts the venue's colour where the picture was, as a
   thin band above the name. Not a grey box, not a placeholder icon. */
/* ⚠️ NO BAR WHERE THE PHOTO WAS. A 3px rule in the venue's colour was meant to stand in for the
   picture; on a client whose colour is a grey (Cesare Augusto's is) it read as a stray divider, and
   the card already has one piece of colour — the flourish under the name. One is the right number. */
.hroom--classico:not(.hroom--shot) .hroom__body { padding-top: 22px; }

/* ── ELEGANTE · restrained, horizontal, quiet ─────────────────────────────────────────────────── */
.hrooms--elegante { gap: 0; }
.hroom--elegante { display: grid; grid-template-columns: 112px 1fr; gap: 15px; align-items: center;
  padding: 15px 0; border-bottom: 1px solid var(--line); }
.hroom--elegante:last-child { border-bottom: 0; }
.hroom--elegante .hroom__sq { display: block; width: 112px; height: 112px; object-fit: cover; border-radius: 3px; }
.hroom--elegante .hroom__name { font-size: 12.5px; letter-spacing: .18em; text-transform: uppercase;
  font-weight: var(--font-display-w, 600); color: var(--ink); }
/* Elegante's name is a small spaced-out label, so the price must NOT out-shout it. 13px against the
   name's 12.5px reads as its equal on the row rather than as a louder second thing. */
.hroom--elegante .hroom__price { font-size: 13px; font-weight: 700; letter-spacing: .02em; }
/* ⭐ TWO LINES (Tony, 2026-07-31, was three). Elegante is the look that has to scan — ten rooms in one
   thumb-flick — and two lines is what keeps a row a row. */
.hroom--elegante .hroom__text { margin-top: 8px; font-size: 13px; line-height: 1.6; color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.hroom--elegante .hroom__more { margin-top: 10px; font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--c-primary); font-weight: 700; }
/* No photo → the text simply takes the whole width. The row stays a row; nothing is left empty. */
.hroom--elegante:not(.hroom--shot) { grid-template-columns: 1fr; gap: 0; }

/* ── MODERNO · the photo IS the card ──────────────────────────────────────────────────────────── */
.hrooms--moderno { gap: 14px; }
.hroom--moderno { border-radius: var(--radius-lg); overflow: hidden; aspect-ratio: 4 / 3.05; }
.hroom--moderno .hroom__full { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* ⚠️ THE VEIL IS STRONGER THAN THE MOCK-UP'S, and it had to be. The drawing used photographs that
   happened to be dark at the bottom; Cesare Augusto's real rooms are white walls and pale wood, and
   over those the name and the two lines were barely legible — a caption you have to squint at is a
   caption nobody reads. It now reaches higher and lands darker, and is checked against his brightest
   photograph rather than against a favourable one. */
.hroom--moderno .hroom__veil { position: absolute; inset: 0; background: linear-gradient(to top,
  rgba(8, 11, 14, .96) 0%, rgba(8, 11, 14, .84) 26%, rgba(8, 11, 14, .48) 52%, rgba(8, 11, 14, 0) 82%); }
.hroom--moderno .hroom__body { position: absolute; left: 0; right: 0; bottom: 0; padding: 17px 17px 16px; }
.hroom--moderno .hroom__name { font-size: calc(21px * var(--font-display-scale, 1)); font-weight: var(--font-display-w, 600); letter-spacing: -.015em; color: #fff; }
/* ⛔ WHITE, not var(--ink). Moderno's body sits ON the photograph — --ink is near-white and would
   usually pass, but "usually" is not good enough over an image whose brightness is the owner's. */
.hroom--moderno .hroom__price { font-size: 16px; font-weight: 700; color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .45); }
.hroom--moderno .hroom__text { margin-top: 6px; font-size: 13px; line-height: 1.5; color: rgba(255, 255, 255, .82);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.hroom--moderno .hroom__count { position: absolute; top: 13px; right: 13px; z-index: 2; font-size: 11px;
  font-weight: 650; color: #fff; background: rgba(8, 11, 14, .55); backdrop-filter: blur(6px);
  border-radius: var(--radius-pill); padding: 5px 10px; }
/* The chips float on the photograph, so they are glass rather than the app's normal chip. */
.hroom--moderno .tagrow .tagchip { color: #fff; background: rgba(255, 255, 255, .17);
  border-color: rgba(255, 255, 255, .22); backdrop-filter: blur(6px); }
.hroom--moderno .tagrow .tagico { color: #fff; }
/* No photo → the card becomes a panel in the venue's own colour, keeping its height and its shape,
   so a hotel mid-way through sending pictures does not get a page of holes. */
/* ⚠️ AND WITHOUT A PHOTO IT STOPS PRETENDING TO BE ONE. Keeping the 4:3 frame left two thirds of the
   card empty above the text — a hole in the venue's own colour is still a hole. With nothing to show,
   the card simply becomes a coloured panel the size of its words. */
.hroom--moderno:not(.hroom--shot) { aspect-ratio: auto; background: linear-gradient(160deg,
  color-mix(in srgb, var(--c-primary) 88%, #000), color-mix(in srgb, var(--c-primary-dark) 92%, #000)); }
.hroom--moderno:not(.hroom--shot) .hroom__veil { display: none; }
.hroom--moderno:not(.hroom--shot) .hroom__body { position: static; padding: 18px 17px 17px; }

/* The look picker in HQ's area form (§C step 4). Each option carries a tiny abstract drawing of the
   look rather than only its name — "Elegante" means nothing until you have seen it, and Tony is
   choosing between three shapes, not three words. */
/* Three, not four: there is no "menu paper" option for a hotel — it was never a choice a hotel had. */
.hq-looks { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 4px 0 14px; }
.hq-look { display: flex; flex-direction: column; align-items: center; gap: 7px; padding: 10px 4px 9px;
  background: var(--hq-panel); border: 1px solid var(--hq-line); border-radius: 10px; cursor: pointer; }
.hq-look b { font-size: 10.5px; font-weight: 700; color: var(--hq-soft); letter-spacing: .01em; }
.hq-look.is-on { border-color: var(--hq-teal); background: rgba(42, 165, 149, .1); }
.hq-look.is-on b { color: var(--hq-bright); }
.hq-look__art { display: flex; flex-direction: column; gap: 2px; width: 34px; height: 30px; justify-content: center; }
.hq-look__art i { display: block; background: var(--hq-soft); border-radius: 1.5px; opacity: .55; }
.hq-look.is-on .hq-look__art i { background: var(--hq-teal); opacity: 1; }
/* classico: a big photo band over two text lines */
.hq-look__art--classico i:first-child { height: 13px; width: 100%; border-radius: 2px; }
.hq-look__art--classico i:nth-child(2) { height: 2px; width: 62%; margin-top: 2px; }
.hq-look__art--classico i:last-child { height: 2px; width: 88%; }
/* elegante: a small square beside the text — drawn as a row */
.hq-look__art--elegante { flex-direction: row; align-items: center; gap: 4px; }
.hq-look__art--elegante i:first-child { width: 12px; height: 12px; border-radius: 2px; flex: none; }
.hq-look__art--elegante i:nth-child(2), .hq-look__art--elegante i:last-child { display: none; }
.hq-look__art--elegante::after { content: ''; flex: 1; height: 2px; background: currentColor;
  color: var(--hq-soft); opacity: .55; border-radius: 1.5px; }
.hq-look.is-on .hq-look__art--elegante::after { color: var(--hq-teal); opacity: 1; }
/* moderno: one filled block — the photo IS the card */
.hq-look__art--moderno i:first-child { height: 26px; width: 100%; border-radius: 4px; }
.hq-look__art--moderno i:nth-child(2), .hq-look__art--moderno i:last-child { display: none; }

/* ── THE MANAGER'S ITEM ROW ON A HOTEL (Tony, 2026-07-29) ──────────────────────────────────────
   Rooms carry four or five lines of description, so two of them filled the owner's screen and the
   one thing he scans for — the name — was the smallest element on the row. Bigger picture, dominant
   name, two lines of text and then "…".
   ⛔⛔ MANAGER ONLY, AND HOTELS ONLY. `.row--bigitem` is put on by restaurantAdmin.js and by nothing
   else, so the guest's room list and Pane&Vino's dish list are both untouched. The two lists do
   different jobs: the owner is finding a row to edit, the guest is choosing a room.
   ⚠️ CLAMPED BY LINES, NOT CHARACTERS. Two lines of Italian and two of German are very different
   character counts, and a character cut chops mid-word in one language and not the other. line-clamp
   measures what is actually on screen, and the "…" comes free — in every language.
   ⚠️ The clamp lives on the row's own sub-line only, never on `.row__sub` generally, which the whole
   Manager uses for one-line captions. */
.row--bigitem .ra-thumb { width: 92px; height: 92px; border-radius: 10px; }
.row--bigitem .ra-thumb--empty i { font-size: 30px; }
.row--bigitem .row__title { font-size: 17px; font-weight: 700; line-height: 1.2; color: var(--ink); }
.row--bigitem .row__sub { margin-top: 4px; font-size: 13px; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* The WHOLE row opens the editor, the ellipsis included — an ellipsis that is its own tappable thing
   is a target half the taps miss on a phone. Nothing inside may swallow the tap. */
.row--bigitem .row__text { pointer-events: none; }

/* ── THE AREA'S OWN INTRODUCTION (Tony, 2026-07-29) ────────────────────────────────────────────
   It opens the area, so it has to READ like an opening — his words: "right now it looks like a bunch
   of text dropped there for the sake of it". ⛔ Not a word of the hotel's text is changed; the whole
   difference is typographic, and it uses THEIR OWN paragraph breaks:
     · the FIRST paragraph as the lead — larger, in the venue's own face, in full ink;
     · anything after it as quieter body copy, so the eye knows where the opening ends;
     · a hairline below, so the list underneath starts as a new thing rather than as more of this.
   ⚠️ A hotel that wrote one paragraph gets a lead and nothing else, which is right — there is
   nothing to demote. */
.area-intro { margin: 2px 2px 20px; padding-bottom: 18px; border-bottom: 1px solid var(--line); }
/* #11ter — the introduction's photograph. FULL WIDTH, above the words, and 16:9 because the crop is
   fixed at 16:9 in the Manager: declaring the ratio here means the space is reserved before the image
   arrives, so the paragraph underneath does not jump when it lands. ⚠️ `object-fit: cover` in case an
   older photo predates the crop. */
.area-intro__shot { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-md, 12px); margin: 0 0 14px; background: var(--surface-2); }
/* An area whose introduction is ONLY a photograph: no words to separate from, so no rule under it. */
.area-intro--shotonly { padding-bottom: 0; border-bottom: 0; }
/* ⭐ 17px, DOWN FROM 18.5 (Tony, 2026-08-03, looking at Fontana: "it reads bold").
   ⛔⛔ AND THE REASON IT IS THE SIZE AND NOT THE WEIGHT, which is worth knowing before anyone tries
   the obvious fix again: EVERY DISPLAY FACE SHIPS EXACTLY ONE WEIGHT. `js/data/fonts.js` says so in
   its own header — "one weight per face, and the weight differs because the faces do" — and the
   folder proves it: lora-600, outfit-600, marcellus-400, playfair-600. So `font-weight` on this
   line is a number the font cannot honour. Fontana asked for 400 and drew Lora 600, because 600 is
   the only Lora there is; measured in the live app, not reasoned about. Only Cormorant, Caveat,
   Inter and Sora ship more than one cut.
   ⚠️ CONSEQUENCE: making the lead lighter means SHIPPING a 400 cut of that face (self-host + preload,
   the procedure the project already follows) — or making it smaller, which works on every face at
   once and costs nothing. This is the second.
   ⚠️ The weight stays 500: it is what the rule has always said, it changes nothing on a
   single-weight face, and on the four faces that DO have several cuts it keeps them where they were. */
.area-intro__lead { margin: 0; font-family: var(--font-display); font-size: calc(17px * var(--font-display-scale, 1)); font-weight: 500;
  line-height: 1.45; letter-spacing: .005em; color: var(--ink); }
.area-intro__body { margin: 12px 0 0; font-size: 13.5px; line-height: 1.65; color: var(--muted); }
/* ⭐ TWO LINES, THEN A CHEVRON (Tony, 2026-07-30). Cesare Augusto's introduction is eight lines of
   room specification above his actual rooms — true, useful, and not what a guest opening Le Camere
   came for. ⛔ THE LEAD IS NEVER CLAMPED: the bold opening sentence stays whole in every look. Only
   the paragraphs under it fold.
   ⚠️ The clamp is on the WRAPPER, not on each paragraph — clamping each one would give two lines per
   paragraph and defeat the point. line-clamp on a container works because the paragraphs inside are
   normal flow content.
   ⚠️ In EVERY style, at his request. This is the one place the three looks agree, because the
   introduction is not part of the look — it is the page's opening. */
/* ⛔⛔ max-height, NOT -webkit-line-clamp. My first version used line-clamp and it did NOTHING —
   measured on the live page: eight lines, and the toggle changed the height by zero. line-clamp
   counts INLINE lines inside a -webkit-box, and this wrapper holds <p> BLOCKS, so each paragraph
   became one box child and nothing was ever clipped. The comment I wrote claiming otherwise was
   simply wrong, and only opening the page showed it.
   ⭐ A height limit does not care what is inside it. 45px = two lines of 13.5px at 1.65 line-height,
   with the paragraph's own top margin moved onto the wrapper so it is not counted twice. */
.area-intro__more { max-height: 45px; overflow: hidden; margin-top: 12px; transition: max-height .2s ease; }
.area-intro__more.is-open { max-height: none; }

/* ⭐ THE VENUE'S DESCRIPTION ON HOME — seven lines, then a chevron (Tony, 2026-08-07). Planber's runs
   to about twenty and pushed everything else off the screen.
   ⭐ `-webkit-line-clamp` IS CORRECT HERE, even though the note above says it did nothing on
   `.area-intro__more`. Both are true and the difference is what is inside: line-clamp counts INLINE
   lines within a -webkit-box, so it cannot clip a wrapper full of <p> BLOCKS (each becomes one box
   child) and it clips a single <p> of running text exactly as intended. ⛔ Do not "correct" this to a
   max-height — a pixel height would cut a line in half the moment the venue's font or size changes,
   and every venue here has its own face.
   ⚠️ No transition: animating to `none` is not animatable, and a half-opened clamp shows a clipped
   line of somebody's welcome. It opens at once, like the chevron promises. */
.about__txt { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 7; overflow: hidden; }
.about__txt.is-open { -webkit-line-clamp: unset; overflow: visible; }

/* ⭐⭐ §G / #4 — THE STAY DATES SHEET. It stands between the guest tapping «Attiva» and the phone's
   own permission dialog, which is asked ONCE ever: this screen is what gives him a reason first.
   ⚠️ `100svh`, never `vh` — on iOS `vh` counts the address bar that is not there, so the panel is
   pushed under the fold and the Conferma button sits off-screen exactly when it matters most.
   ⛔ AND THE BODY IS NEVER PINNED. `position:fixed` on <body> stops iOS painting tall overlays at
   all; the app has that written down from an earlier modal and it cost a session to find. */
.stay-ask { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center;
  padding: 18px; background: rgba(8, 16, 20, .62); overscroll-behavior: contain; }
.stay-ask__panel { position: relative; width: min(420px, 100%); max-height: 100svh; overflow: auto;
  border-radius: 18px; padding: 22px 20px 18px; background: var(--surface, #1b2224);
  border: 1px solid var(--line, rgba(255, 255, 255, .12)); box-shadow: 0 18px 50px rgba(0, 0, 0, .45); }
.stay-ask__x { position: absolute; top: 8px; right: 8px; width: 34px; height: 34px; display: grid;
  place-items: center; border: 0; border-radius: 50%; background: transparent; color: var(--muted); cursor: pointer; }
.stay-ask__t { margin: 0 26px 6px 0; font-size: 18px; font-weight: 800; line-height: 1.25; color: var(--ink); }
.stay-ask__sub { margin: 0 0 16px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
/* Two dates side by side: they are read together, and stacking them makes a two-field form look
   like a booking. On a narrow phone they wrap by themselves rather than squeezing. */
.stay-ask__row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.stay-ask__row label { flex: 1 1 140px; display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* ⛔ THE CAPTION ONLY — `> label > span:first-child`, never every span in the row. The row now holds a
   second span (the field wrapper, which carries the empty-box hint), and a blanket rule painted it
   uppercase and letterspaced too. Worse than the hint: `text-transform` and `letter-spacing` INHERIT
   into a form control even though `font` does not, so on iOS — where the value reads «10 ago 2026»
   rather than digits — the date itself would have come out as «10 AGO 2026». */
.stay-ask__row > label > span:first-child { font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.stay-ask__row .input { margin: 0; width: 100%; }
.stay-ask__err { margin: -8px 0 12px; font-size: 12.5px; font-weight: 600; color: var(--c-danger, #e0715f); }
.stay-ask__panel .btn + .btn { margin-top: 8px; }
.area-intro__more .area-intro__body { margin-top: 0; }
.area-intro__more .area-intro__body + .area-intro__body { margin-top: 12px; }
.area-intro__toggle { display: inline-flex; align-items: center; gap: 5px; margin-top: 8px; padding: 4px 0;
  background: none; border: 0; cursor: pointer; font-family: var(--font-ui); font-size: 12px;
  font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--c-primary);
  -webkit-tap-highlight-color: transparent; }
.area-intro__toggle i, .area-intro__toggle svg { transition: transform .18s ease; }
.area-intro__toggle.is-open i, .area-intro__toggle.is-open svg { transform: rotate(180deg); }
/* ⚠️ On the cream Moderno carta the accent would be the venue's colour on paper — but this block is
   never inside the carta, so it keeps the app's own accent everywhere. */

/* ── A HOTEL'S SERVICES: a list of features, not a wall of cards (Tony, 2026-07-29) ────────────
   "Smaller title, put an icon in front of each like in the rest of the Apps (coloured square +
   icon) + description. No need for the separator. Make it less prominent."
   ⭐ A room is a thing a guest CHOOSES between and earns a photograph; a service is a fact he CHECKS.
   Same weight for both is what made Generali read as shouting. ⛔ Hotel-only markup — `.hfeat`
   exists on no restaurant or bar page. */
.hfeats { display: flex; flex-direction: column; gap: 2px; }
.hfeat { display: flex; align-items: center; gap: 14px; padding: 14px 2px;
  border-bottom: 1px solid var(--line); }
.hfeat:last-child { border-bottom: 0; }
/* The coloured square + icon the rest of the app uses for a section — one colour per service, so the
   list reads as a set of distinct things rather than as one thing repeated. */
/* ⭐ DOUBLE SIZE (Tony, 2026-07-29): "the icon and the image should be x2 the size it is now." At 42px
   a photograph of the pool was a stamp; at 84 it is a picture, and the icon squares keep the same
   rhythm beside it so a list of mixed rows still lines up. */
/* ⭐ ÷1.5 (Tony, 2026-07-30): "in Generali reduce the square with the icon by x1.5". 84 → 56px.
   ⚠️ The icon INSIDE is drawn at a fixed 48px by the renderer, which would leave 4px of breathing
   room in a 56px box — so the glyph is scaled with the box rather than left to fill it. */
.hfeat__ico { flex: none; width: 56px; height: 56px; border-radius: 12px; display: grid; place-items: center; font-size: 32px;
  color: var(--c); background: color-mix(in srgb, var(--c) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--c) 30%, transparent); }
/* #68 — "nessuna icona" in a list where the OTHER rows carry a square: the space is held so the
   column of titles stays straight, and nothing at all is drawn in it. ⚠️ Where NO row in the list has
   an icon, featuresHTML emits no box at all and the list is a clean column of names — this class is
   never reached in that case. */
.hfeat__ico--none { background: transparent; border-color: transparent; }
/* ⛔⛔ THE SIZE IS SET AT THE CALL, NOT HERE, and a rule written here is DEAD CODE. An app icon is a
   WEBFONT glyph — `<i class="ti ti-wifi" style="font-size:NNpx">` — so `.hfeat__ico svg { width }`
   matched nothing, and even a correct `font-size` here would lose to the inline style anyway.
   I shipped exactly that rule, told Tony the icons were 2.3× bigger, and nothing had moved.
   ⭐ The size lives in featuresHTML: tIcon(…, { size: 48 }). If it needs changing, change it there. */
/* ⚠️ `flex: 1` ADDED 2026-07-31, and it is the description's doing. This block used to be sized by its
   text, which was WIDE, so it filled the row and pushed the price and the chevron to the right edge
   on its own. With the description gone the block shrank to the width of a title and the chevron came
   with it — a ">" floating in the middle of the row, three rows in three different places. Nothing in
   the gates can see this; it took opening the page. */
.hfeat__txt { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
/* ⭐ THE TITLE CARRIES THE ROW ALONE (Tony, 2026-07-31): "only the Title x1.3 bigger and no
   description". 16.5 × 1.3 = 21.5. The description moved into the card behind the ">", which is what
   makes every row worth tapping and the list consistent — see featuresHTML for the reasoning.
   🧹 The old `.hfeat__txt span` rule (a one-line clamp on the description) went with it: featuresHTML
   is the only user of this block, and it no longer emits that span at all. */
.hfeat__txt b { font-family: var(--font-display); font-size: calc(21.5px * var(--font-display-scale, 1)); font-weight: var(--font-display-w, 600); line-height: 1.25; color: var(--ink); }
/* The owner's own photograph takes the square the moment he uploads one. */
.hfeat__shot { flex: none; width: 56px; height: 56px; border-radius: 12px; object-fit: cover;
  border: 1px solid var(--line); }
/* A service that has something behind it says so — and only then. Wi-Fi and check-in carry nothing
   the row does not already print, so they stay flat and untappable (Tony, 2026-07-29). */
.hfeat--open { cursor: pointer; }
.hfeat__chev { flex: none; align-self: center; color: var(--muted); opacity: .7; }
/* ⭐ §E item 2 — the price the owner typed, on a service row. `margin-left:auto` pins it to the right
   whatever the text does, so the column holds even when one row has a price and its neighbour does
   not. ⛔ NEVER var(--c-primary): Cesare Augusto's brand grey computes to 2.5:1 on this ground and
   fails AA at every size — the brand colour is for micro-labels, never for a number a guest reads. */
.hfeat__price { flex: none; margin-left: auto; align-self: center; font-family: var(--font-ui);
  font-size: 15px; font-weight: 700; line-height: 1.2; white-space: nowrap;
  font-variant-numeric: tabular-nums; color: var(--ink); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   LA CARTA — the hotel's own menu and drink card (§D1.c, 2026-07-30)
   ═══════════════════════════════════════════════════════════════════════════════════════════════
   Tony saw the restaurant's paper on his hotel and sent it back: "The menus/drink cards for the
   Hotels get their own style… using the colors, font and general style we used for Le Camere, so we
   get consistency in the look." Drawn as three mock-ups on his real menu (assets/mock-carta.html),
   then decided by him in two rounds:
     ROUND 1 → "no clamp"  ·  "remove the card option on Classico and Moderno too… never cards, just
               lists"  ·  "una per tutti" (one dish card, not one per look)
   ⛔ SO THERE ARE NO PANELS ANYWHERE, and that cost the three looks their easiest difference — the
   box WAS the distinction. Each now earns its identity from three things only:
     photo   64px/4px corner  ·  72px/3px  ·  84px/14px
     title   accent flourish  ·  centred .18em caps + hairline  ·  tight display type
     rows    air, no rule     ·  a hairline under each          ·  a fainter hairline
     price   plain            ·  quiet                          ·  in a pill
   ⭐ EACH LOOK STILL INHERITS ITS VOICE FROM ITS OWN ROOM CARD above, so a guest moving from
   Le Camere to Il Ristorante stays in one app: .hcarta--classico borrows .hroom--classico's 27px
   display name and its 46×2 flourish; --elegante borrows the .18em uppercase title and the hairline
   rows; --moderno borrows .hroom__count's pill, which now holds the price.
   ⛔ NOT ONE DECLARATION IS SHARED WITH THE RESTAURANT'S PAPER. `.notepad` is untouched and frozen
   byte-for-byte by tools/smoke-notepad-freeze.js.
   ⚠️ ALL THREE SIT DIRECTLY ON THE PAGE TEXTURE now that the panels are gone (the pinstripe at 40%,
   :121-133) — which is why the mock was judged with that texture on rather than on flat black.
   ⚠️ THE PRICE IS NEVER var(--c-primary). Cesare Augusto's brand is #5c5c5c, which on this page
   computes to 2.5:1 — below the 4.5:1 floor and below even the 3:1 large-text one. Brand colour is
   for micro-labels (as .hroom__more uses it); a number a guest must read gets --ink. */
.hcarta { display: flex; flex-direction: column; }
.hcarta__title { margin: 0; font-family: var(--font-display); font-weight: var(--font-display-w, 600); color: var(--ink); }
/* ⭐ THE PHOTO IS A BLOCK, NOT A THUMBNAIL (Tony, 2026-07-30): "make sure the photo square is as
   big as the 3 rows of the content on the right (including the Allergeni icons) so that it looks
   like a block — right now the height of the photo gets to half of the allergeni icons."
   ⚠️ So the row STRETCHES and the image takes its height from the text beside it, with aspect-ratio
   keeping it square — which means the COLUMN must be auto, not a fixed px, or the width could never
   follow the height. Each look keeps its own min-width (64/72/84) as the floor for a short row.
   ⚠️ A MAX IS DELIBERATE. "No clamp" means a description can run to five lines, and without a ceiling
   the square would grow with it until one dish owned the screen. 132px is roughly a three-line row —
   the shape he asked for — and taller rows simply stop growing rather than distorting. */
.hcarta__dish { display: grid; align-items: stretch; }
.hcarta__name { margin: 0; font-family: var(--font-display); font-weight: var(--font-display-w, 600);
  color: var(--ink); overflow-wrap: break-word; text-wrap: balance; }
/* ⚠️ align-self, not the row's alignment. Elegante and Moderno centre the row so the square photo
   sits level with the text block (exactly as the room card does) — and centring dragged the price
   down beside the DESCRIPTION, which reads as a price for the wrong line and makes the column
   ragged. Caught by opening the mock, not by reading it. */
.hcarta__price { font-family: var(--font-ui); white-space: nowrap; font-variant-numeric: tabular-nums;
  color: var(--ink); align-self: start; }
/* ⚠️ The TEXT column must not stretch with the row, or its own margins would be pushed apart. Only
   the photograph stretches; the words keep their natural height at the top of the row. */
.hcarta__dish > div { align-self: start; }
/* ⭐ NO CLAMP, ANYWHERE (Tony, 2026-07-30). A room can be teased; on a menu the description IS the
   dish, so it is never cut. */
.hcarta__desc { margin: 0; }
/* ⭐ THE ONE GAP THAT IS HALF (Tony, 2026-07-30): "the space Veggie/Allergeni → title, reduce it by
   half." So the flex gap becomes 14 — which IS that one distance — and every OTHER gap adds 14 back
   with a margin, returning to 28. ⚠️ Done with a POSITIVE margin on the courses rather than a
   negative one on the head: a negative margin is a number nobody can read the intent of, and the
   next person to change the gap would silently move the title instead of the courses. This way the
   gap IS the small distance and the margin IS the top-up. */
.hcarta__course { margin-top: 14px; }
/* ⛔⛔ NOT aspect-ratio. The first attempt asked for a SQUARE whose side followed the text height —
   `align-self: stretch` + `aspect-ratio: 1` + `width: auto` in an `auto` column — and that is
   CIRCULAR: the column's width depends on the image's width, which depends on its height, which
   depends on the row's height, which depends on the column. The browser broke the loop by taking the
   max-width (132px) as the width and then making the row 132px tall to match — so the photograph
   DROVE the row instead of following it, and got bigger than the text rather than level with it.
   MEASURED on the live demo: photo 132px, text 98px.
   ⭐ So the column keeps a fixed width and only the HEIGHT follows. The image fills the row top to
   bottom — which is what Tony actually asked for: "as big as the 3 rows of the content on the right
   (including the Allergeni icons), so that it looks like a block". It is a block; on a row taller
   than it is wide it is a portrait block rather than a square, and no CSS can have both. */
/* ⛔⛔ `height: 0; min-height: 100%` — NOT `height: 100%`. Tony: "why in specialità del giorno the
   photo is not a square?" Because that dish's photograph is PORTRAIT: an <img> still contributes its
   INTRINSIC height when sizing the grid row, so at 78px wide a tall photo asked for ~147px and the
   row grew to fit it, while a landscape one at the same width asked for less than the text and
   behaved. Same circularity as before, wearing a different hat — and invisible until a client
   uploads a portrait photo, which is what a phone camera produces by default.
   ⭐ height:0 removes the intrinsic contribution entirely, so the TEXT alone sizes the row;
   min-height:100% then fills it. object-fit: cover crops rather than distorts. */
.hcarta__shot { display: block; object-fit: cover; background: rgba(255, 255, 255, .05);
  align-self: stretch; width: 100%; height: 0; min-height: 100%; }
/* A dish with no photograph gives its width to the text rather than keeping an empty square. */
.hcarta__dish--nophoto { grid-template-columns: 1fr auto !important; }
/* The two filter buttons, CENTRED and BELOW the title (the paper pins them top-right). ⛔ Its own
   class, never `.paper-filters` — that one is shared with Pane&Vino and must not move. */
/* ⚠️ It now sits ABOVE the title (the renderer emits filters → title → courses), so this bottom
   margin is the gap between the buttons and the menu's name rather than between name and courses. */
/* ⛔ NO BOTTOM MARGIN HERE. The carta is a flex column with its own `gap`, so a margin ADDS to it:
   22px + a 32px gap read as 54px on the live page, and the head's 18px + 32 read as 50 — which is
   why Tony saw the title "lost floating around". MEASURED, then removed: the gap is now the ONE
   thing that spaces this page, so the three distances cannot drift apart again.
   ⭐ ITS VALUE IS HIS: "the distance from the top separator to the start of the VEGGIE/ALLERGENI
   should be used as the correct distance also between the VEGGIE/ALLERGENI and the Title, and from
   the Title to the Antipasti." That distance measured 28px. */
/* ⛔ NO BOX (Tony, 2026-07-30): "remove the box around the 2 Veggie and Allergeni". Just the icon
   and its word — which is what the restaurant's own filters have always been. The box was mine, and
   on the cream carta it read as two empty buttons rather than as a filter. */
.hcarta__filters { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 18px; margin: 0; }
.hcarta__filters .pfilter { min-width: 0; padding: 2px; border: 0; }
/* ⭐ CLASSICO: the bar rides the first course's heading, hard right, sharing its baseline. */
.hcarta__course-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 14px; }
.hcarta__course-head .hcarta__filters { justify-content: flex-end; flex: none; gap: 0 16px; margin-bottom: 2px; }

/* ── CLASSICO · a list, generous. Air does the work. ─────────────────────────────────────────── */
.hcarta--classico { gap: 14px; }
/* ⭐ CENTRED, IN EVERY LOOK (Tony, 2026-07-30): "it should be centered". Elegante already was;
   Classico and Moderno follow it, so the menu's name reads the same way whichever look is chosen.
   The flourish under it centres with it. */
.hcarta--classico .hcarta__head { margin: 0; text-align: center; }
/* ⭐ ×1.5 (Tony, 2026-07-30: "make the menu Title x1.5 bigger"). Each look scales its OWN size, so
   the three keep their relationship to each other: 27→40.5, 12.5→18.75, 24→36. The menu's name is
   now the largest thing on the page, which is what a printed carta does too. */
.hcarta--classico .hcarta__title { font-size: 40.5px; line-height: 1.1; letter-spacing: -.005em; }
/* ⚠️ `margin: 12px auto 0`, in THIS rule. A separate `margin-left/right: auto` was added above and
   did nothing: the same selector appears again here, later in the file, and its shorthand reset both
   sides back to 0 — so the flourish stayed hard left under a centred title. Two rules for one
   selector is how a fix disappears without anything looking wrong. */
.hcarta--classico .hcarta__rule { width: 46px; height: 2px; margin: 12px auto 0; border-radius: 2px; background: var(--c-accent); }
/* ⚠️ THE RULE MOVES TO THE ROW. With the filters beside the heading, a border on the heading alone
   would underline the words and stop short of the buttons — a line that looks broken. The row owns
   it, so it runs the full width under both. */
.hcarta--classico .hcarta__course-name { margin: 0; font-family: var(--font-display);
  font-weight: var(--font-display-w, 600); font-size: 21px; line-height: 1.15; color: var(--ink); }
.hcarta--classico .hcarta__course-head { margin: 0 0 16px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.hcarta--classico .hcarta__dish { grid-template-columns: 78px 1fr auto; gap: 14px; }
.hcarta--classico .hcarta__dish + .hcarta__dish { margin-top: 22px; }
/* ⭐ A SQUARE FLOOR. The row takes its height from the TEXT, so a short item (a name, four words,
   no allergen row) produced a row shorter than the column is wide and the photo came out a
   squashed strip — Tony saw it on Espresso. min-height = the column width, so the picture can grow
   with a tall row but never drop below square. One number per look, matching its own column. */
.hcarta--classico .hcarta__shot { min-height: 78px; border-radius: 4px; }
.hcarta--classico .hcarta__name { font-size: 16.5px; line-height: 1.3; }
.hcarta--classico .hcarta__price { font-size: 15.5px; font-weight: 700; line-height: 1.3; }
.hcarta--classico .hcarta__desc { margin-top: 5px; font-size: 13.5px; line-height: 1.55; color: var(--muted); }
.hcarta--classico .iconrow { margin-top: 8px; }

/* ── ELEGANTE · a list, quiet. Hairlines do the work. ────────────────────────────────────────── */
.hcarta--elegante { gap: 14px; }  /* the one rhythm — see .hcarta__filters */
.hcarta--elegante .hcarta__head { text-align: center; margin: 0; }
.hcarta--elegante .hcarta__title { font-size: 18.75px; letter-spacing: .18em; text-transform: uppercase; font-weight: 700; }
.hcarta--elegante .hcarta__rule { height: 1px; background: var(--line); margin: 11px 0 0; }
.hcarta--elegante .hcarta__course-name { margin: 0 0 4px; font-family: var(--font-ui); font-size: 11px;
  letter-spacing: .2em; text-transform: uppercase; font-weight: 700; color: var(--muted); }
.hcarta--elegante .hcarta__dish { grid-template-columns: 84px 1fr auto; gap: 15px; padding: 15px 0;
  border-bottom: 1px solid var(--line); }
.hcarta--elegante .hcarta__dish:last-child { border-bottom: 0; }
.hcarta--elegante .hcarta__shot { min-height: 84px; border-radius: 3px; }
.hcarta--elegante .hcarta__name { font-size: 15px; line-height: 1.3; }
.hcarta--elegante .hcarta__price { font-size: 14px; font-weight: 700; line-height: 1.3; color: var(--ink-soft); }
.hcarta--elegante .hcarta__desc { margin-top: 7px; font-size: 12.5px; line-height: 1.6; color: var(--muted); }
.hcarta--elegante .iconrow { margin-top: 9px; }

/* ── MODERNO · the parts that stay whatever the ground ──────────────────────────────────────── */
/* ⚠️ "The photo IS the card" cannot be translated literally to a menu — twenty 4:3 photographs is
   twenty screenfuls. Its honest translation is the dominant 84px square and the pill. */
.hcarta--moderno { gap: 14px; }
.hcarta--moderno .hcarta__head { margin: 0; text-align: center; }
.hcarta--moderno .hcarta__title { font-size: 36px; letter-spacing: -.015em; line-height: 1.15; }
/* ⭐ HALVED (Tony, 2026-07-30): "reduce the gap from the line below it to the first piatto by half."
   28px = this 14px margin + the dish's own 14px padding-top. The dish padding is shared with the
   bottom of every row, so halving THAT would squash the whole carta; the heading's margin is the
   piece that belongs to this gap alone. 0 + 14 = 14px, exactly half. */
.hcarta--moderno .hcarta__course-name { margin: 0; font-family: var(--font-display);
  font-weight: var(--font-display-w, 600); font-size: 18px; letter-spacing: -.01em; color: var(--ink); }
/* ⭐ THE ROW IS CLASSICO'S ROW NOW (Tony, 2026-07-30): "make the photo as big as the one in the
   Classico style and make the title/description and icons fit like in Classico." 96 → 78px.
   ⚠️ WHY IT WAS 96: the Moderno look descends from the rooms, where "the photo IS the card". On a
   menu that reasoning does not survive contact — a 96px column stole 18px from the text on a 390px
   phone, so "Salumi, pecorino, miele e focaccia calda" broke over two lines and every row grew.
   ⛔ The 14px corner radius STAYS. That, not the size, is what makes this look modern; matching
   Classico's 4px would have merged the two looks, which is the opposite of the point. */
.hcarta--moderno .hcarta__dish { grid-template-columns: 68px 1fr auto; gap: 14px; padding: 14px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .07); }
.hcarta--moderno .hcarta__dish:last-child { border-bottom: 0; }
/* ⭐ THE CARTA TIGHTENS (Tony, 2026-07-30): "reduce the gap between the Title and Antipasti by 1/3
   … reduce the gap between the last piatto and the next portata by 1/3 (same for the others below)."
   ⚠️ Both gaps are built from .hcarta__course's margin-top PLUS the carta's 14px flex gap, so one
   number cannot serve both — the first course also has the title above it and no dish padding.
     · between courses : 14 flex + 0 + 14 (the last dish's own bottom padding) = 28px, was 42px
     · under the title : 14 flex + 5                                           = 19px, was 28px
   ⛔ Scoped to --moderno: Classico's and Elegante's rhythm is deliberate and stays as it is. */
.hcarta--moderno .hcarta__course { margin-top: 0; }
.hcarta--moderno .hcarta__course:first-of-type { margin-top: 5px; }
.hcarta--moderno .hcarta__shot { min-height: 68px; border-radius: 14px; }
.hcarta--moderno .hcarta__name { font-size: 16.5px; line-height: 1.3; letter-spacing: -.01em; }
/* ⛔ THE GLASS PILL IS GONE, and it had to go with the cream. It was the mark that tied this look to
   .hroom__count on the rooms — but a translucent WHITE pill on cream paper is invisible, and a
   blurred backdrop over a flat colour blurs nothing. On a printed carta the price is simply the
   number, in the ink of the page, and that is what this is now.
   ⚠️ WRITTEN HERE RATHER THAN OVERRIDDEN ABOVE. My first attempt added a second
   `.hcarta--moderno .hcarta__price` rule in the cream block; this one sits LATER in the file, same
   specificity, so it won and the pill survived — measured on the live page as
   rgba(255,255,255,0.1). Exactly the trap the accent flourish hit an hour earlier: two rules for one
   selector is how a fix disappears with nothing looking wrong. One rule, one place. */
.hcarta--moderno .hcarta__price { font-size: 15.5px; font-weight: 700; line-height: 1.3;
  color: #34312b; }
.hcarta--moderno .hcarta__desc { margin-top: 5px; font-size: 13.5px; line-height: 1.55; color: var(--muted); }
.hcarta--moderno .iconrow { margin-top: 8px; }

/* ⚠️⚠️ THIS BLOCK SITS HERE, AFTER the dark Moderno rules, and the position is load-bearing.
   It was first written ABOVE them and SIX of its selectors were silently overridden by their later
   twins — .hcarta--moderno itself, the title, the course name (three rules deep), the dish name, the
   description and the dish row. The cream appeared, so it looked as though it had worked; the type
   colours never changed, and "Antipasti" rendered in the DARK theme's near-white on cream paper —
   invisible. That is the same trap that hid the accent flourish and the price pill earlier the same
   day: TWO RULES FOR ONE SELECTOR, and CSS quietly takes the last one.
   ⭐ Rather than merge six pairs by hand, the whole block moved. Reverting is still one deletion.
   ⚠️ If you ever add a rule to the dark Moderno section above, check here for a twin. */
/* ══════════════════════════════════════════════════════════════════════════════════════════════
   ── MODERNO · THE LIGHT ONE. A printed carta on cream paper. ─────────────────────────────────
   ══════════════════════════════════════════════════════════════════════════════════════════════
   Tony, 2026-07-30, after seeing all three live: "can we make the Moderno (just for the Menus) that
   is inside a container with a cream bg… or even better that is the same as the Elegante in Pane e
   Vino, but placed in the Moderno in the Hotels? so this way we actually have 3 different styles and
   one is on a light bg?"

   ⭐ HE IS RIGHT, AND IT FIXES SOMETHING THE PANELS USED TO HIDE. Once "never cards, just lists"
   removed the boxes, all three looks were dark type on the same dark page and separated only by size
   and rhythm — close cousins. A light ground is a genuinely different look, so the three now read as
   three: dark brochure · dark quiet list · light printed carta.

   ⛔⛔ BUT IT IS NOT PANE&VINO'S PAPER, AND MUST NEVER BE. Two reasons, both hard:
     1. Tony's own governing rule for the hotel: "Do not use the menu format for this, create its own."
     2. PRACTICAL, and the one that would actually bite: notepadHTML's output is frozen byte-for-byte
        by tools/smoke-notepad-freeze.js. Sharing it would mean any future change to the hotel's carta
        moves Pane&Vino's menu — the exact coupling this whole effort exists to prevent.
   So this is the hotel's own cream surface, wearing the hotel's own classes. Same feeling on screen,
   no shared line of code.

   ⭐ IT WEARS THE CLIENT'S OWN FACE, which Pane&Vino's paper cannot: that one hardcodes Cormorant.
   Here the headings stay on --font-display, so a hotel on Outfit gets a cream carta in Outfit.

   ⚠️ REVERTING IS ONE COMMIT. Everything below is scoped to .hcarta--moderno and lives in this one
   contiguous block; deleting it returns Moderno to the dark list it was. Nothing else reads it.
   ⚠️ The colours are LITERALS, not tokens, on purpose. --ink and --muted are the dark theme's, and on
   cream they would be invisible. These four are the paper's own, and they are the only place in the
   hotel CSS where that is true. */
/* ⭐ CREAMIER (Tony, 2026-07-30): #faf6ee read as off-white rather than paper. #f7efdd carries more
   yellow and less grey — the difference between "unbleached" and "cream" — while staying light
   enough for #34312b ink to sit at well over 10:1. */
/* ⭐ LESS AIR AT THE TOP (Tony, 2026-07-30): "move them closer to the Title and less space above them
   to the border — we need to recover some lost space." 26px of padding above a bar that then had its
   own gap below it pushed the menu's name a third of the way down the screen. 16px, and the bar sits
   close to the title it belongs to. */
.hcarta--moderno { background: #f7efdd; border: 1px solid rgba(53, 49, 43, .13); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm); padding: 16px 20px 26px; color: #34312b; }
/* the gap the filter bar leaves under itself, only on the light carta where space is scarcest */
/* ⭐ −1/3 AGAIN (Tony, 2026-07-30): the flex gap is 14px, so -4px left 10px of air between the two
   icons and "Menu à la Carta". -7px leaves 7px — the bar now reads as part of the title's block. */
.hcarta--moderno .hcarta__filters { margin-bottom: -7px; }
/* ⛔⛔ THE COURSE HEADINGS ARE INK, NOT THE BRAND — and this was caught by looking, not by thinking.
   Pane&Vino's cream paper puts its course names in --c-primary and they sing, because HIS brand is a
   terracotta. Cesare Augusto's is #5f656d, a mid grey, and on #faf6ee "Antipasti" came out so faint
   it read as a disabled label — I could barely see it in the screenshot.
   ⭐ THE RULE, now hit for the third time today: a brand colour is safe on SMALL LABELS and on LARGE
   type, and unsafe everywhere in between. The title survives because it is 36px; a 18px heading does
   not. So headings take the paper's own ink and the brand keeps the title alone — which also gives
   the page a hierarchy it did not have when both were the same colour.
   ⚠️ This is why the carta could never simply BE Pane&Vino's paper: his is designed around one brand
   that happens to be strong. A hotel's could be anything, including a grey. */
.hcarta--moderno .hcarta__title { color: var(--c-primary); }
.hcarta--moderno .hcarta__course-name { color: #34312b; text-align: center; }
/* the flourish under a course heading, so a centred label is not left floating on the paper */
/* ⭐ ×1.5 (Tony, 2026-07-30) — 18 → 27px. On cream the course heading is the page's main rhythm, and
   at 18px it read as a label rather than a division of the menu. */
/* ⭐ ÷1.3 (Tony, 2026-07-30): "make Antipasti x1.3 less big". 27 → 20.8px. The ×1.5 that took it to
   27px was right when the heading had to carry the page alone; now that the carta is compact it was
   competing with the 36px title instead of sitting under it. 36 · 20.8 · 16.5 is a real hierarchy. */
.hcarta--moderno .hcarta__course-name { font-size: 24px; padding-bottom: 10px;
  border-bottom: 1px solid rgba(53, 49, 43, .14); }
/* ⭐ THE PHOTO TAKES THE VENUE'S OWN EDGE (Tony: "make sure the photo thumb have a color border,
   1px, in the bg color we set up in HQ"). --brand-header-bg is exactly that colour — the one he
   picks on the client card — and on cream a 1px line of it reads as a printed keyline round each
   plate, which is what a real carta does.
   ⚠️ box-sizing is stated explicitly: the image is sized width:100% inside a fixed column, so a
   border added on top of that would push it 2px wider than its column and misalign every row.
   ⚠️ It falls back to the paper's own ink, so a venue whose header colour never resolved gets a
   sensible keyline rather than no border at all. */
.hcarta--moderno .hcarta__shot { box-sizing: border-box;
  border: 1px solid var(--brand-header-bg, #34312b); }
.hcarta--moderno .hcarta__name { color: #34312b; }
.hcarta--moderno .hcarta__desc { color: #8a8170; font-style: italic; }
.hcarta--moderno .hcarta__dish { border-bottom: 1px solid rgba(53, 49, 43, .10); }
/* The filter buttons need the paper's own border and its own muted, or they vanish into the cream. */
.hcarta--moderno .hcarta__filters .pfilter { border-color: rgba(53, 49, 43, .18); }
.hcarta--moderno .hcarta__filters .pfilter__lbl { color: #8a8170; }
/* ⚠️ The allergen rings are drawn with the app's amber/green, which are tuned for the dark page. On
   cream they need darkening or they read as highlighter. Same hues, lower lightness. */
.hcarta--moderno .alg { --alg: #b07d14; --veg: #3f8b52; }

/* ⭐ SPECIALITÀ DEL GIORNO WEARS THE TITLE'S TREATMENT (Tony, 2026-07-30): "the Specialità del
   giorno title should be centered and x1.5 what it is now — can it have the same style as Menu a
   la Carta, with the line below?"
   ⚠️ ×1.5 OF ITSELF, not the menu title's size. It is still a heading INSIDE the page, under the
   menu it belongs to, so matching the menu's own 40.5px would make the two compete. 21 → 31.5px
   (Classico), 18 → 27px (Moderno), and Elegante's tiny label → 16.5px, each ×1.5 of its own.
   ⭐ Centred with the rule beneath, which is what he means by "the same style": the block reads as
   a second carta rather than as one more course of the first.
   ⚠️ It is the COURSE NAME that is dressed, not a title — the block has no title of its own, so
   restyling the course heading is what puts the words in the right place without a second renderer.
   ⛔ Scoped to .hcarta--spec, so a real course heading in the menu above is untouched. */
/* ⭐ CENTRED FOR REAL (Tony, 2026-07-30, after seeing it still sitting left). text-align on the
   heading was never going to work: it is a shrink-to-fit item in a space-between flex row, so its box
   IS its text. Centring the ROW is what moves it. The text-align stays for the wrapped case. */
.hcarta--spec .hcarta__course-head { justify-content: center; }
.hcarta--spec .hcarta__course-name { text-align: center; }
.hcarta--classico.hcarta--spec .hcarta__course-name { font-size: 31.5px; }
/* ⭐ ÷1.5 AND IN THE BRAND (Tony, 2026-07-30): "make Specialità del Giorno x1.5 smaller and the same
   title color as Menu à la Carta". 40.5 → 27px, and --c-primary, the colour the carta's own title
   wears — so the two headings on the page finally read as the same rank of thing.
   ⚠️ THIS IS THE ONE PLACE THE BRAND IS SAFE ON A MID-SIZE HEADING, and only because 27px is large
   enough. The COURSE names above stay ink (#34312b) for exactly the reason recorded there: on a grey
   brand like Cesare Augusto's, a 20.8px heading in --c-primary reads as a disabled label. Do not
   "make it consistent" by moving the course names to the brand too. */
/* ⚠️ NO RULE UNDER IT (Tony, 2026-07-30): "centered and no underline". This REVERSES his own earlier
   "with the line below" — on the cream carta the rule read as a course divider and made the day's
   special look like one more portata. Overriding both halves explicitly, because the inherited
   .hcarta--moderno .hcarta__course-name sets padding-bottom AND a border. */
.hcarta--moderno.hcarta--spec .hcarta__course-name { font-size: 27px; color: var(--c-primary);
  padding-bottom: 0; border-bottom: 0; }
.hcarta--elegante.hcarta--spec .hcarta__course-name { font-size: 16.5px; padding-bottom: 10px;
  border-bottom: 1px solid var(--line); }

/* A carta filtered down to nothing. Its own class so a restaurant CSS edit can never move it. */
.hcarta__empty { color: var(--muted); font-size: 14px; line-height: 1.6; text-align: center; padding: 24px 0; }

/* ⛔ THE DISH CARD, hotel only. The modal itself is SHARED with Pane&Vino, so this scopes by the
   venue's own class rather than editing .cardmodal__price — which is var(--c-primary) and therefore
   2.5:1 on Cesare Augusto's grey. Tony: "una per tutti" — one card for all three looks. */
.cardmodal--hotel .cardmodal__price { color: var(--ink); }

/* #66 · HQ — THE AREAS SHOWN AS TWO GROUPS (2026-08-01)
   ⚠️ HQ-only classes, painted in the `--hq-*` tokens like everything else on that surface. They do
   not exist on a client app and must not: this is Tony's screen. */
.hq-grouphd { margin: 16px 0 6px; font-size: 12px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--hq-soft); }
.hq-grouphd:first-of-type { margin-top: 10px; }
/* ⚠️ NO CAP HERE. `.hq-sub` carries `max-width: 640px`, which is right for a paragraph in a narrow
   column and wrong for this one: the card is far wider on the screen Tony actually works on, so the
   line broke after "but you can" with half the row empty beside it. Tony, 2026-08-04: *"make it stay
   on 1 row… no point in 2."* ⛔ It is the CAP that is lifted, not the wrapping — a genuinely narrow
   window still wraps, because the alternative is a line running off the side of the card. */
.hq-areahelp { margin: 6px 0 2px; line-height: 1.45; max-width: none; }

/* ── THE TWO PANELS AT THE FOOT OF A CLIENT (2026-08-04) ────────────────────────────────────────
   ⛔⛔ NEITHER OF THESE HAD A RULE AT ALL. `.hq-areas__head` and `.hq-areas__note` were written into
   the markup and never styled, so the headings rendered at whatever the card inherited and read as
   body text rather than as sections. Tony: *"make them in capital letters and x1.5 bigger so they
   actually look like areas."*
   ⭐ Modelled on `.hq-grouphd`, which is what already labels a group on this screen — uppercase and
   letter-spaced, so the two panels announce themselves the same way the rest of HQ does rather than
   inventing a third heading style. */
.hq-areas__head { display: flex; align-items: center; gap: 8px; margin: 4px 0 6px; }
.hq-areas__head b { font-size: 18px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--hq-bright); }
/* ⚠️ The icon must not grow with the word — 1.5× on a glyph beside uppercase text reads as a
   mistake. It keeps the size it is drawn at and simply sits centred against the taller line. */
.hq-areas__head .ti { flex: none; color: var(--hq-soft); }
/* Tony: "make the description smaller and in the same style as [the areas help line]." Same size,
   same colour, same rhythm — and the same absence of a cap, for the same reason. */
.hq-areas__note { margin: 0 0 8px; font-size: 13px; line-height: 1.45; color: var(--hq-soft); max-width: none; }
/* A built-in row is greyed so "you cannot rename or delete this" reads as a rule, not a fault.
   ⛔ NOT `opacity` on the whole row — that would fade the arrows too, and the arrows are the point:
   they are what lets Tony move his own areas above and below these. */
.hq-arearow--builtin .hq-areatxt b { color: var(--hq-soft); font-weight: 600; }
.hq-arearow--builtin .hq-areaico { opacity: .55; }
/* The add button sits INSIDE the group it adds to, so it needs its own breathing room and a rule
   under the whole block — everything below it acts on the WHOLE app, not on the areas. */
/* ⭐ Full width, like every other button in the card (Tony, 2026-08-01: "should be full width as the
   others"). It sits in a list of full-width actions — a button that stops short reads as a different
   kind of thing, and it is not one. */
.hq-areaadd { margin-top: 10px; width: 100%; }
.hq-sep--areas { margin: 18px 0 4px; }

/* ── #12bis(a) · SU GOOGLE — the one panel on this card that has a FRAME ────────────────────────
   ⛔⛔ Tony, 2026-08-06: *"please make it stand out in its own panel inside the HQ… it is getting
   very crowded and difficult to read."* Everything else on a client card is a heading with a rule
   above it — `.hq-mgr` is literally a border-top and nothing more — so the way to make one block
   read as its own thing is to give it the one property none of the others has: a box.
   ⭐ TEAL, not gold and not red. Gold on this screen means "the action that makes money" (send the
   payment link, put it online) and red means "you cannot undo this". This is neither: it is a report
   about whether the app is finished, and it must not shout in a colour that already means something. */
.hq-seo { margin-top: 14px; padding: 13px 14px 14px; border-radius: 14px;
  background: linear-gradient(160deg, rgba(42, 165, 149, .10), rgba(42, 165, 149, .03));
  border: 1px solid rgba(42, 165, 149, .34);
  display: flex; flex-direction: column; gap: 8px; }
.hq-seo__head { display: flex; align-items: center; gap: 8px; margin: 0 0 2px; }
.hq-seo__head b { font-size: 18px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--hq-bright); }
/* Same rule as the areas heading: the glyph keeps its own size against the taller line. */
.hq-seo__ico { display: inline-flex; flex: none; color: var(--hq-teal); }
/* The state, readable without opening anything. ⚠️ It is `margin-left:auto` rather than a flex
   spacer — the heading has three children and a spacer would be a fourth thing to keep in step. */
/* ⛔ AN EMPTY PILL IS NOT A PILL. Seen on the bench: the heading is drawn before the venue has been
   read, and for that second the state showed as a small bordered capsule with nothing in it —
   which looks like a control that failed to load rather than an answer that has not arrived. */
.hq-seo__pill:empty { display: none; }
.hq-seo__pill { margin-left: auto; flex: none; font-size: 11px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--hq-line2); color: var(--hq-soft); }
.hq-seo__pill.is-on { color: var(--hq-good); border-color: rgba(75, 185, 138, .5); background: rgba(75, 185, 138, .12); }
.hq-seo__pill.is-ready { color: var(--hq-teal); border-color: rgba(42, 165, 149, .5); background: rgba(42, 165, 149, .12); }
.hq-seo__pill.is-not { color: var(--hq-gold); border-color: rgba(243, 177, 75, .45); background: rgba(243, 177, 75, .1); }
.hq-seo__state { margin: 0; font-size: 14px; font-weight: 600; color: var(--hq-bright); }
/* The count line IS the button that opens the list — one target, and the count stays on screen
   while the list is shut, or nobody ever opens it. */
.hq-seo__more { background: none; border: 0; padding: 2px 0; margin: 0; text-align: left;
  font: inherit; font-size: 13px; color: var(--hq-soft); cursor: pointer; align-self: flex-start; }
.hq-seo__more:hover { color: var(--hq-bright); }
.hq-seo__chev { display: inline-block; width: 12px; color: var(--hq-teal); }
.hq-seo__list { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 2px;
  padding: 10px 0 2px; border-top: 1px solid rgba(42, 165, 149, .22); }
/* ⚠️ `align-items: baseline` would drop the mark half a line on a row that wraps onto two. The mark
   belongs to the FIRST line of its sentence, which is what `flex-start` plus the line-height does. */
.hq-seo__row { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; line-height: 1.45; }
.hq-seo__row > span:first-child { flex: none; width: 13px; text-align: center; }
.hq-seo__row.is-ok { color: var(--hq-soft); }
.hq-seo__row.is-ok > span:first-child { color: var(--hq-good); }
.hq-seo__row.is-bad { color: var(--hq-bright); }
.hq-seo__row.is-bad > span:first-child { color: #e07a72; }
/* ⛔ THE ADVISORY ROW MUST NOT LOOK LIKE THE BLOCKING ONES. A missing photograph is worth showing
   and is not worth refusing over, and a third colour is what says so without a sentence. */
.hq-seo__row.is-warn { color: var(--hq-ink); }
.hq-seo__row.is-warn > span:first-child { color: var(--hq-gold); }
/* WHICH ones — the names travel with the count, so the line is an instruction rather than a report. */
.hq-seo__row em { font-style: normal; color: var(--hq-soft); }
.hq-seo__row i { font-style: normal; font-size: 11.5px; color: var(--hq-soft); opacity: .85; }
.hq-seo__btn { width: 100%; margin-top: 2px; }
.hq-seo__btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ---- #12 · DOMINIO PROPRIO — the panel under «Su Google» -------------------------------------
   ⛔ IT HAD ITS OWN COLOUR TAKEN AWAY ONCE AND TONY PUT IT BACK (2026-08-14). The first version was
   deliberately neutral, on the argument that «Su Google» is opened daily and this once per venue,
   so it should not compete. He looked at it and asked for a real panel with a real colour. He is
   right and the reasoning was wrong: this is the one panel that carries a PAID service, and a grey
   frame reads as an afterthought bolted onto the card rather than something worth €100.
   ⭐ BLUE, and specifically not the other three. Teal already means «Su Google»; gold means a
   warning everywhere in HQ; red means broken. Blue is the only free hue on this card, and it stays
   free for exactly one thing — the venue's own address.
   ⚠️ Same radius, padding and geometry as .hq-seo, on purpose: a different SHAPE would say it is a
   different kind of thing, and it is not — it is the second half of the same conversation. */
.hq { --hq-blue: #5f92dd; }
.hq-dom { margin-top: 12px; padding: 13px 14px 14px; border-radius: 14px;
  background: linear-gradient(160deg, rgba(95, 146, 221, .12), rgba(95, 146, 221, .035));
  border: 1px solid rgba(95, 146, 221, .34);
  display: flex; flex-direction: column; gap: 8px; }
.hq-dom__head { display: flex; align-items: center; gap: 8px; margin: 0 0 2px; }
.hq-dom__head b { font-size: 18px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--hq-bright); }
.hq-dom__ico { display: inline-flex; flex: none; color: var(--hq-blue); }
/* ⛔ AN EMPTY PILL IS NOT A PILL — the same lesson the «Su Google» pill records. The heading paints
   before Cloudflare has answered, and for that second an empty bordered capsule looks like a
   control that failed to load rather than an answer that has not arrived yet. */
.hq-dom__pill:empty { display: none; }
.hq-dom__pill { margin-left: auto; flex: none; font-size: 11px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--hq-line2); color: var(--hq-soft); }
.hq-dom__pill.is-on { color: var(--hq-good); border-color: rgba(75, 185, 138, .5); background: rgba(75, 185, 138, .12); }
.hq-dom__pill.is-wait { color: var(--hq-gold); border-color: rgba(243, 177, 75, .45); background: rgba(243, 177, 75, .1); }
.hq-dom__pill.is-not { color: #e07a72; border-color: rgba(224, 122, 114, .45); background: rgba(224, 122, 114, .1); }
/* The field and its button on one line, and the field takes what is left: a hostname is long and
   the button's label is two syllables. ⚠️ It wraps rather than shrinking the input below readable
   width — HQ is used on a phone and a squeezed address field is where typos come from. */
.hq-dom__form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.hq-dom__form .input { flex: 1 1 220px; min-width: 0; }
.hq-dom__form .btn { flex: none; }
/* ⚠️ THE PRIMARY BUTTON WEARS THE PANEL'S COLOUR, not the app's green. Seen on the bench the moment
   the blue arrived: a green «Crea» inside a blue frame reads as a control borrowed from somewhere
   else. Scoped to .hq-dom, so the green primary everywhere else in HQ is untouched. */
.hq-dom .hq-btn-primary { background: linear-gradient(150deg, #3a6cb0, var(--hq-blue)); }
.hq-dom .input:focus { border-color: var(--hq-blue); }
.hq-dom__addr { margin: 2px 0 0; font-size: 15px; font-weight: 700; word-break: break-all; }
.hq-dom__addr a { color: var(--hq-bright); text-decoration: none; border-bottom: 1px solid rgba(95, 146, 221, .45); }
.hq-dom__addr a:hover { color: var(--hq-blue); border-bottom-color: var(--hq-blue); }
/* The two steps. Same row grammar as the readiness checks above, because they answer the same
   shape of question and Tony has already learned to read it. */
.hq-dom__row { display: flex; align-items: flex-start; gap: 8px; font-size: 13px; line-height: 1.45; }
.hq-dom__row > span:first-child { flex: none; width: 13px; text-align: center; }
.hq-dom__row.is-ok { color: var(--hq-soft); }
.hq-dom__row.is-ok > span:first-child { color: var(--hq-good); }
.hq-dom__row.is-warn { color: var(--hq-ink); }
.hq-dom__row.is-warn > span:first-child { color: var(--hq-gold); }
/* The record, laid out the way a domain technician reads one — label left, value right, monospace
   so a hyphen and a dot are not a guess. ⚠️ It has to survive being read off a phone screen and
   typed into somebody else's control panel. */
.hq-dom__cname { display: grid; gap: 6px; margin: 4px 0 2px; padding: 11px 12px;
  border-radius: 12px; background: var(--hq-panel); border: 1px solid var(--hq-line2); }
.hq-dom__cname .hq-lbl { margin: 0 0 2px; color: var(--hq-soft); }
.hq-dom__cname .btn { margin-top: 4px; }
.hq-dom__kv { display: flex; align-items: baseline; gap: 10px; font-size: 13px; }
.hq-dom__kv > span { flex: none; min-width: 92px; color: var(--hq-soft); }
.hq-dom__kv > b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px;
  color: var(--hq-bright); word-break: break-all; }
.hq-dom__acts { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
.hq-dom__acts .btn { flex: 1 1 auto; }
/* ⛔⛔ THE COUNT — Tony: «I will not remember». By NUMBER, never a percentage, and on screen every
   time the panel opens rather than only once it is high: a warning that appears for the first time
   at 80 is a warning nobody has learned to look for. */
.hq-dom__use { margin: 6px 0 0; font-size: 12.5px; color: var(--hq-soft); }
.hq-dom__use.is-warn { color: var(--hq-gold); font-weight: 600; }
/* "Fino a N persone" in the opened room card. Sits under the description, above the tag chips —
   ⛔ it is a FACT about the room, so it reads at body weight, not as a chip: chips are the features
   a guest scans, and burying the one thing he looks for first among them would lose it. */
.cardmodal__guests { display: flex; align-items: center; gap: 7px; margin: 10px 0 0;
  color: var(--ink-soft); font-family: var(--font-ui); font-size: 15px; }
/* #69 — the "what goes in this button?" picker. A row, not a tile: Tony reads names, and a grid of
   equal squares would make «Eventi & Offerte» and «Le Camere» look interchangeable when they are not. */
.hq-navpick { width: 100%; text-align: left; cursor: pointer; background: var(--hq-panel);
  border: 1px solid var(--hq-line2); }
.hq-navpick:hover { border-color: var(--hq-soft); }
/* Already in the bar: shown, not hidden — picking it is how Tony MOVES a button, and hiding it would
   make the list change shape depending on what he happened to have chosen. */
.hq-navpick.is-in { opacity: .5; }

/* #83 — the snapshot picker. A row says WHEN and WHAT, because most copies are nightly ones of the
   same content and the one worth choosing is the one somebody took on purpose. */
.hq-snaplist { display: flex; flex-direction: column; gap: 6px; max-height: 46svh; overflow-y: auto; overscroll-behavior: contain; margin: 10px 0 4px; }
.hq-snaprow { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; padding: 10px 12px; border: 1px solid var(--hq-line, rgba(255,255,255,.14)); border-radius: 10px; background: transparent; color: inherit; text-align: left; cursor: pointer; }
.hq-snaprow:hover { background: rgba(255,255,255,.05); }
.hq-snaprow__when { font-weight: 600; font-size: 14px; }
.hq-snaprow__what { font-size: 12px; opacity: .6; }
