/*
 * Stili base ConSergio — CSS a mano (nessun build step, Propshaft).
 * Palette azzurro/blu, card arrotondate, look pulito. Mobile-first.
 * Le componenti reali (Guest::/Manage::/Admin::) arriveranno nelle epiche successive.
 */

:root {
  --c-blue-900: #0b3a66;
  --c-blue-700: #145ea8;
  --c-blue-500: #1f7ae0;
  --c-blue-300: #7ec1ff;
  --c-azure-100: #e8f3ff;
  --c-azure-50: #f4f9ff;
  --c-white: #ffffff;
  --c-ink: #1a2733;
  --c-ink-soft: #4a5b6b;
  --c-ok: #46b450;

  --radius: 20px;
  --radius-sm: 999px;
  --shadow: 0 10px 30px rgba(20, 94, 168, 0.15);
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
}

body {
  font-family: var(--font);
  color: var(--c-ink);
  background: linear-gradient(160deg, var(--c-azure-50) 0%, var(--c-azure-100) 100%);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

/* --- Hero / landing ------------------------------------------------------- */
.hero {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.hero__card {
  background: var(--c-white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px 28px;
  max-width: 520px;
  width: 100%;
  border: 1px solid var(--c-azure-100);
  /* La testata è a tutta larghezza: senza ritaglio sborderebbe dagli angoli. */
  overflow: hidden;
}

.hero__brand {
  display: inline-block;
  margin: 0 0 16px;
  padding: 6px 14px;
  font-weight: 700;
  letter-spacing: 0.3px;
  color: var(--c-blue-700);
  background: var(--c-azure-100);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
}

.hero__title {
  margin: 0 0 12px;
  font-size: 1.9rem;
  line-height: 1.15;
  color: var(--c-blue-900);
}

.hero__subtitle {
  margin: 0 0 24px;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--c-ink-soft);
}

.hero__status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 24px;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  background: rgba(70, 180, 80, 0.1);
  color: var(--c-ok);
  font-weight: 600;
  font-size: 0.95rem;
}

.hero__dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--c-ok);
  box-shadow: 0 0 0 4px var(--sb-chip-line);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0 0 24px;
}

.hero__langs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  border-top: 1px solid var(--c-azure-100);
  padding-top: 20px;
}

.hero__langs-label {
  color: var(--c-ink-soft);
  font-size: 0.9rem;
  margin-right: 4px;
}

.lang-chip {
  display: inline-block;
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  background: var(--c-azure-50);
  border: 1px solid var(--c-azure-100);
  color: var(--c-blue-700);
  font-size: 0.8rem;
  font-weight: 600;
}

.lang-chip--active {
  background: var(--c-blue-500);
  border-color: var(--c-blue-500);
  color: var(--c-white);
}

/* --- Flash messages ------------------------------------------------------- */
/* §E50.1 — a flash is a row now: icon, text, close button. The icon is not
   decoration: state is never told by colour alone (app/views/CLAUDE.md §5), and
   the two bands were the last place in the app still doing that. */
.flash {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0;
  padding: 12px 20px;
  text-align: center;
  font-weight: 600;
  font-size: 0.95rem;
}

.flash__icon, .g-flash__icon { flex: 0 0 auto; }
.flash__text, .g-flash__text { flex: 1 1 auto; }

/* A naked button, so the only thing it adds to the band is the cross. It keeps
   `color: inherit` because each variant tints its own text and the cross has to
   follow it rather than pick a colour of its own. */
.flash__close, .g-flash__close {
  flex: 0 0 auto;
  background: none;
  border: 0;
  padding: 0;
  line-height: 0;
  color: inherit;
  cursor: pointer;
  opacity: 0.7;
}

.flash__close:hover, .g-flash__close:hover { opacity: 1; }

.flash--notice {
  background: var(--sb-accent-tint);
  color: var(--c-ok);
}

.flash--alert {
  background: #fdecec;
  color: var(--sb-danger, #C62828);
}

/* The two levels that had no class at all: before §E50.1 a `flash[:warning]` or
   `flash[:info]` was dropped on the floor without an error anywhere. Same palette
   as .billing-banner--warn / --info, so the panel speaks with one voice. */
.flash--warn {
  background: #fdf6ea;
  color: #7d5210;
}

.flash--info {
  background: #eef7fd;
  color: #0f5c85;
}

/* In the panel a flash is a left-aligned notice with a coloured rule, not a
   centred band: it reads where the text starts. Scoped, because `.flash` shows
   up in the guest app too.
   `.panel__note` is deliberately NOT here: the direction's table files it under
   alerts, but in this code it is a PARAGRAPH of explanation inside a section
   (see the comment on its own definition), and framing it would turn a hundred
   explanations into as many green banners. */
.mng-body .flash {
  border-radius: 0;
  border-left: 3px solid var(--sb-accent);
  background: #F1FAF6;
  color: var(--sb-green-fg);
  padding: 10px 12px;
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  font-weight: 400;
  text-align: left;
}
.mng-body .flash--alert { border-left-color: var(--sb-danger); background: #FEF6F5; color: #8A2019; }
.mng-body .flash--warn { border-left-color: var(--sb-warn-fg); background: var(--sb-warn-bg); color: var(--sb-warn-fg); }
.mng-body .flash--info { border-left-color: #0f5c85; background: #EEF7FD; color: #0f5c85; }
.mng-body .panel__note--warn { color: var(--sb-warn-fg); }

/* §E50.1 — a flash and a .billing-banner are two notices in the SAME slot of the
   panel layout, one after the other. The flash used to be full-bleed (margin: 0,
   centred text) while `.mng-main > .billing-banner` respected the gutter, so two
   consecutive messages started at two different x. They start at the same one now.
   Scoped to the direct child so an in-page `.flash` — if one ever appears — is not
   pulled out of its own column. */
.mng-main > .flash {
  margin: var(--sb-sp-4) var(--sb-sp-gutter);
  border-radius: 10px;
  border-left-width: 3px;
}

/* --- Bottoni --------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 18px;
  border-radius: 12px;
  border: 1px solid transparent;
  font-family: inherit;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  line-height: 1;
  transition: filter 0.15s ease, background 0.15s ease;
}

.btn--sm {
  padding: 7px 12px;
  font-size: 0.85rem;
}

.btn--primary {
  background: var(--c-blue-500);
  border-color: var(--c-blue-500);
  color: var(--c-white);
}

.btn--primary:hover {
  filter: brightness(0.95);
}

.btn--ghost {
  background: var(--c-white);
  border-color: var(--c-azure-100);
  color: var(--c-blue-700);
}

.btn--ghost:hover {
  background: var(--c-azure-50);
}

.btn--danger {
  background: var(--c-white);
  border-color: #f3c9c9;
  color: var(--sb-danger, #C62828);
}

.btn--danger:hover {
  background: #fdecec;
}

.chip {
  display: inline-block;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  background: var(--c-azure-50);
  border: 1px solid var(--c-azure-100);
  color: var(--c-blue-700);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
}

.chip:hover {
  background: var(--c-azure-100);
}

/* --- Badge ----------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  font-size: 0.82rem;
  font-weight: 700;
}

.badge--ok {
  background: var(--sb-accent-tint);
  color: var(--c-ok);
}

.badge__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c-ok);
  box-shadow: 0 0 0 4px var(--sb-chip-line);
}

/* --- Form generico --------------------------------------------------------- */
.form__section {
  border: 1px solid var(--c-azure-100);
  border-radius: var(--radius);
  padding: 20px;
  margin: 0 0 20px;
}

.form__legend {
  padding: 0 8px;
  font-weight: 700;
  color: var(--c-blue-700);
  font-size: 0.95rem;
}

.form__row {
  margin: 0 0 14px;
}

.form__row:last-child {
  margin-bottom: 0;
}

.form__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
}

.form__label {
  display: block;
  margin: 0 0 6px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--c-ink-soft);
}

.form__input {
  width: 100%;
  padding: 11px 14px;
  border-radius: 12px;
  border: 1px solid var(--c-azure-100);
  background: var(--c-white);
  font-family: inherit;
  font-size: 1rem;
  color: var(--c-ink);
}

.form__input:focus {
  outline: none;
  border-color: var(--c-blue-300);
  box-shadow: 0 0 0 3px rgba(126, 193, 255, 0.35);
}

.form__input--sm {
  max-width: 120px;
}

.form__hint {
  margin: 6px 0 0;
  font-size: 0.8rem;
  color: var(--c-ink-soft);
}

/* Explanatory paragraph inside a card — NOT a field note. Distinct from
   .form__hint because that one is defined by its POSITION (6px above, zero
   below: it only works glued under a control, inside a .form__row that carries
   the bottom margin). Lending it to a standalone paragraph has already
   produced two patches: the `.form__cards .panel__section > .form__hint` rule
   further down, whose own comment says exactly this, and the
   `.form__hint.dtable__note` pair used on three admin pages purely to cancel
   the wrong margin-top. Margins are zero here: the spacing is the section's
   job (.panel__section--flow), not the paragraph's. */
.panel__note {
  margin: 0;
  /* The paragraph that explains money, privacy and deadlines was the smallest
     and palest text of the page, under a 20.8px title. Inter, like the other
     prose slots: it used to fall back on Archivo. */
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  color: var(--sb-muted);
  line-height: 1.5;
}

.panel__note--warn { color: var(--sb-warn-fg); }

.form__actions {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* Separatore tra blocchi di un form. */
.form__sep {
  margin: 18px 0;
  border: 0;
  border-top: 1px solid var(--c-azure-100);
}

/* Sezione facoltativa richiudibile (<details>): chiusa di default, si apre solo
   quando serve davvero. Marker nativo nascosto, chevron ruotato all'apertura. */
.form__collapse {
  margin-bottom: 14px;
}

.form__collapse-summary {
  display: flex;
  align-items: center;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--c-ink-soft);
}

.form__collapse-summary::-webkit-details-marker {
  display: none;
}

.form__collapse-label {
  flex: 1;
}

.form__collapse-chevron {
  transition: transform 0.15s ease;
}

.form__collapse[open] .form__collapse-chevron {
  transform: rotate(180deg);
}

.form__collapse-body {
  margin-top: 14px;
}

.form__link {
  color: var(--c-blue-700);
  font-weight: 600;
  font-size: 0.9rem;
  text-decoration: none;
}
/* In the panel a link has to look like one at rest: `.form__link` is ink with
   no underline (it is the "Annulla" of fifteen pages), and a bare <a> in a
   hint fell back on the browser's blue and purple. */
.mng-body .form__link { text-decoration: underline; text-underline-offset: 2px; }
.mng-body :is(.form__hint, .panel__note, .panel__stat, .panel__section-desc) a:not([class]) {
  color: var(--sb-accent); text-decoration: underline; text-underline-offset: 2px;
}

.form__errors {
  background: #fdecec;
  border: 1px solid #f3c9c9;
  border-radius: 12px;
  padding: 14px 16px;
  margin: 0 0 20px;
  color: var(--sb-danger, #C62828);
}

.form__errors-title {
  margin: 0 0 8px;
  font-weight: 700;
}

.form__errors ul {
  margin: 0;
  padding-left: 18px;
}

/* --- Registrazione self-service ------------------------------------------- */
.signup {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 32px 20px;
}

/* `margin: auto` e non `align-items: center` sul contenitore: centra la card
   nella pagina ma, quando il form è più alto della finestra (registrazione),
   non ne taglia la testa fuori dallo scroll. Serve anche a spegnere lo stretch
   di default del flex, che allungava la card per tutta l'altezza dello schermo. */
.signup__card {
  background: var(--c-white);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 32px 26px;
  max-width: 640px;
  width: 100%;
  margin: auto;
  border: 1px solid var(--c-azure-100);
  /* La testata è a tutta larghezza: senza ritaglio sborderebbe dagli angoli. */
  overflow: hidden;
}

/* Testata della card: il logo su fascia colorata, come la banda delle categorie
   nel pannello. Esce dal padding della card con margini negativi, così tocca i
   bordi invece di galleggiare, e separa il marchio dal titolo di pagina. I
   margini negativi sono per card, perché home e signup hanno padding diverso. */
.signup__head, .hero__head {
  background: var(--sb-chip, var(--c-azure-100));
  border-bottom: 1px solid var(--sb-line, var(--c-azure-100));
}
.signup__head { margin: -32px -26px 22px; padding: 20px 26px; }
.hero__head { margin: -32px -28px 22px; padding: 20px 28px; }
.signup__head .app-logo--hero, .hero__head .app-logo--hero { margin-bottom: 0; }

/* Accesso, recupero password e accettazione invito: form corti, card stretta
   come quella della home. */
.signup__card--auth { max-width: 460px; }

/* Rimando all'altra strada (accesso ↔ registrazione), staccato dal form. */
.signup__alt {
  margin: 20px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--c-azure-100);
  font-size: 0.9rem;
}

.signup__brand {
  display: inline-block;
  margin: 0 0 16px;
  padding: 6px 14px;
  font-weight: 700;
  color: var(--c-blue-700);
  background: var(--c-azure-100);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
}

.signup__title {
  margin: 0 0 8px;
  font-size: 1.8rem;
  color: var(--c-blue-900);
}

.signup__subtitle {
  margin: 0 0 24px;
  color: var(--c-ink-soft);
  line-height: 1.5;
}

/* --- Pannello camere (Manage::) ------------------------------------------- */
.panel {
  max-width: 1120px;
  margin: 0 auto;
  padding: 28px 24px 56px;
}

.panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sb-sp-5);
  background: #fff;
  border: none;
  border-bottom: 1px solid var(--sb-line-2);
  border-radius: 0;
  box-shadow: none;
  padding: var(--sb-sp-4) var(--sb-sp-5);
  margin: 0 0 var(--sb-sp-5);
}

/* Archivo and not mono (UX review 2026-10): the eyebrow, the labels and the
   table heads are the anchors the eye reads most, and mono is for what is read
   character by character — a code. The small caps and the spacing are what set
   them apart, not the face. */
.panel__eyebrow {
  margin: 0 0 6px;
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-meta);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--sb-muted);
}

.panel__title {
  margin: 0;
  font-family: var(--sb-display);
  font-size: var(--sb-fs-title);
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--sb-ink);
}

.panel__meta {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* The one subtitle of a page header, and the ONLY rule that governs it anywhere
   in the panel — no Material, theme or shell layer touches it.
   The `margin` is the half that mattered: without it a <p> carried the browser's
   own `margin-block: 1em`, computed against its own size, so every header was
   26px taller than it looks and the page below started that much lower. And the
   same class is used as a <span> inside .panel__meta, where a margin is inert —
   so one class was rendering two different ways. */
.panel__stat {
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  font-weight: 400;
  line-height: 1.5;
  color: var(--sb-muted);
  margin: 5px 0 0;
}
/* In the meta row the span is a FLEX ITEM, where that top margin does apply and
   would drop the text 5px below the badges beside it. */
.panel__meta .panel__stat { margin: 0; }

/* Context row: under the title, ONE fact about the state of the page. A service
   band, so it is grey — the third place the solid fill is allowed, with the
   toolbar and the action bar. */
.panel__context {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-3);
  flex-wrap: wrap;
  min-height: 44px;
  padding: 0 var(--sb-sp-5);
  background: var(--sb-bg);
  border-bottom: 1px solid var(--sb-line-2);
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  color: var(--sb-muted);
}

/* Two tiers of action, and the header says which is which. The topline carries
   the general verbs of a page — back, edit, delete, the sibling pages — demoted
   to `btn--sm`; the row beside the title is for what THIS page does. One flat
   row put "Indietro" next to "Invia la conferma" at the same weight. */
.panel__topline {
  flex: 1 0 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sb-sp-3);
  flex-wrap: wrap;
}
/* The eyebrow's bottom margin is vertical rhythm inside the heading; as a flex
   item on the topline it shifts the row's own baseline instead. Same trap that
   `.panel__meta .panel__stat` already documents. */
.panel__topline .panel__eyebrow { margin: 0; }

.panel__nav {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-2);
  flex-wrap: wrap;
}

.panel__actions {
  display: flex;
  gap: var(--sb-sp-2);
  flex: none;
  flex-wrap: wrap;
}

/* Two surfaces only, so never a card inside a card: a section is a band of the
   page, white on the grey, separated from the next by a hairline. */
.panel__section {
  display: block;
  margin: 0;
  /* sp-5 and not sp-4: between two stacked bands the breathing room is the sum
     of the two paddings, and at 16+16 a section heading started almost against
     the table of the section before — which, with its solid border, already
     reads as a closing line. */
  padding: var(--sb-sp-5);
  background: #fff;
  border: none;
  border-bottom: 1px solid var(--sb-line-2);
  border-radius: 0;
  box-shadow: none;
}

/* The two-column ROW — title and description on the left, fields on the right —
   is for FORMS, and only for them. A section whose content is a table or a list
   stays full width: there the 240px column is white space, and it takes it out
   of the very thing that needed room (the dashboard queue lost a third of the
   table to it, and the request column went to ellipsis).
   `:has()` and not a modifier class because the redesign does not rewrite the
   views, and because the condition is exactly the right one: what makes a
   section a form row is that it holds form rows. */
.panel__section:has(> .form__row),
.panel__section:has(> .form__grid),
/* A block of repeatable rows is form content as much as a single row is. Without
   it the booking's "Camere" section — whose rows live inside `.svc-avail` — was
   the one card of that form left full width, and the services availability card
   only qualified by accident, through an unrelated `.form__grid` beside it. */
.panel__section:has(> .svc-avail) {
  display: grid;
  grid-template-columns: var(--sb-section-label) minmax(0, 1fr);
  gap: 0;
  padding: 0;
}

/* The dividing rule between the two columns is painted ONCE, by the section, as
   a 1px background column at the boundary. Drawn the obvious way — a
   `border-left` on each field block — it came out DASHED: every margin between
   two blocks is a hole in the line, and so is every child that is not a field
   block (the "add a row" button, a bare paragraph). A background knows nothing
   about the children, so it cannot be interrupted by them.
   It runs the full height of the card: the action bar, which spans both columns,
   covers the last stretch with its own opaque background. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) {
  background-image: linear-gradient(var(--sb-line-2), var(--sb-line-2));
  background-repeat: no-repeat;
  background-position: var(--sb-section-label) 0;
  background-size: 1px 100%;
}

.panel__section > * { grid-column: 2; }
.panel__section > .panel__section-title,
.panel__section > .panel__section-desc { grid-column: 1; }
.panel__section > .form__row:first-of-type,
.panel__section > .form__grid:first-of-type,
.panel__section > .svc-avail:first-of-type { padding-top: var(--sb-sp-4); }
/* `.form__hint` is in the list because a section often ENDS with the sentence
   that explains the last checkbox, and without it that line sits flush on the
   bottom border of the card. */
.panel__section > :is(.form__row, .form__grid, .svc-avail, .form__hint, .panel__note):last-child {
  padding-bottom: var(--sb-sp-4);
}
/* The action bar is the FOOTER of the card it acts on, so it lives inside the
   section and spans both columns — left out at `grid-column: 2` it would sit
   indented by the width of the title column. It keeps its own padding and top
   hairline, and rounds off the bottom corners of the card.
   No `overflow: hidden` on the section to clip them: an ancestor with an
   overflow other than `visible` becomes the scroll container, and the bar would
   stop sticking to the window. */
.panel__section > .form__actions {
  grid-column: 1 / -1;
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
}

/* A bare control dropped straight into a form section is a GRID ITEM, and a grid
   item stretches to its column: the "add a row" buttons came out full width,
   which reads as a band and not as a button. It goes to the END of the field
   column — where the row it adds will appear — and carries the column's own
   right gutter, since the padding rule below only dresses boxes. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) > .btn {
  justify-self: end;
  margin-right: var(--sb-sp-5);
}
/* Closing a card with that button is the one case the padding rule below cannot
   cover: on a `.btn` a `padding-bottom` would grow the button itself, not the
   space under it. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail))
  > .btn:last-child {
  margin-bottom: var(--sb-sp-4);
}

/* The gutter of the field column. The guard is the point: this used to run
   unconditionally, so a `.form__hint` or an `<hr class="form__sep">` sitting
   directly in a section that is NOT a two-column form was indented off nothing,
   which is what showed under the booking form's room rows. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail))
  > :is(.form__row, .form__grid, .svc-avail, .form__collapse, .form__hint, .form__sep, .panel__note) {
  padding-left: var(--sb-sp-5);
  padding-right: var(--sb-sp-5);
}
/* A banner is a box with its own border and fill, so it takes the gutter as a
   MARGIN: padding would only make the box wider. Without this a warning dropped
   into a form card (the widget's "every origin is allowed") sat flush on the
   dividing rule and on the card's edge. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) > .billing-banner {
  margin: 0 var(--sb-sp-5) var(--sb-sp-4);
}

/* The two columns need room for BOTH: 240px of title plus a field column worth
   having. Below that the section stacks, and the test is on the SECTION'S OWN
   BOX and not the window — the same card is half a page at 1280 and nearly a
   whole one at 1600, so a viewport query would be right for one and wrong for
   the other. This is also what keeps a form in a 340px rail stacked without
   anyone having to remember the rule. */
@container (max-width: 640px) {
  /* Every selector here repeats the trigger's own shape, and that is not
     verbosity: a container query adds NO specificity, so a plain
     `.panel__section` (0-1-0) loses to the `:has()` rule (0-2-0) and only the
     declarations that happen to tie would land. Half-applied, it put every field
     into the 240px title column and left the other half of the card empty. */
  .panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) {
    grid-template-columns: minmax(0, 1fr);
  }
  .panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) > * {
    grid-column: 1;
  }
  .panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) {
    background-image: none;
  }
  /* Back to the start of the row it belongs to: there is no second column left
     to sit at the end of. It takes the gutter on the LEFT now, or it would be
     the one control in the card standing on its edge. */
  .panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail)) > .btn {
    justify-self: start;
    margin-left: var(--sb-sp-5);
  }
}

/* Opt-in vertical rhythm inside a card. The owl selector alone is NOT enough:
   it would ADD to the margins the children already carry (.table-card mt 16,
   .plan-grid mt 14, .form__row mb 14, .msvc-list mt 8), so a block would land
   at double the spacing of its neighbour. Zeroing both margins on the direct
   child makes the rhythm the ONLY source of spacing in the block, and removes
   the need for :first-child/:last-child.
   It is opt-in because .panel__section is rendered ~45 times in admin/partner
   and ~150 in manage: a global owl would need ~20 child classes zeroed first,
   and each of those would then lose its rhythm everywhere else.
   Do NOT use it on a section whose content is a .form — the form has its own
   rhythm (.form__row) and this would flatten it to a uniform, wrong 16px.
   It matches DIRECT children only: a turbo-frame between the section and its
   blocks shifts the rhythm down a level, where it goes back to zero. */
.panel__section--flow > * { margin-top: 0; margin-bottom: 0; }
.panel__section--flow > * + * { margin-top: var(--sb-sp-4); }

.panel__section-title {
  align-self: start;
  margin: 0 0 var(--sb-sp-3);
  padding: 0;
  font-family: var(--sb-display);
  font-size: var(--sb-fs-head);
  font-weight: 600;
  color: var(--sb-ink);
}
/* In a form row the title and the description carry the vertical padding of
   their own column, because the section has none of its own there. The RIGHT one
   keeps the text off the dividing rule.
   The left one is a variable, because it depends on what the section is. A band
   inside the page column is already inset by its container, and a gutter of its
   own would put the section heading on one axis and the page heading on another.
   A section that is a CARD — two-column form, dashboard rail — has no container
   to inset it, so it carries the gutter itself, or the text sits on the border. */
.panel__section { --sb-section-gutter: 0px; }
/* The list has to match the trigger above, `.svc-avail` included: a section that
   qualifies only through its block of repeatable rows would otherwise fall back
   to the plain title rule and sit flush on the card's own edge. */
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail))
  > .panel__section-title {
  margin: 0;
  padding: var(--sb-sp-4) var(--sb-sp-5) 0 var(--sb-section-gutter);
}
.panel__section:is(:has(> .form__row), :has(> .form__grid), :has(> .svc-avail))
  > .panel__section-desc {
  margin: 5px 0 0;
  padding: 0 var(--sb-sp-5) var(--sb-sp-4) var(--sb-section-gutter);
}

/* The long explanation lives HERE, not under the fields, where only a one-line
   hint is left. Rule 5 of the direction, and also what keeps the right column
   readable. */
.panel__section-desc {
  margin: 5px 0 var(--sb-sp-3);
  padding: 0;
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  line-height: 1.5;
  color: var(--sb-muted);
}

/* Tab di pagina (es. Statistiche: Attività ospiti | Assistente AI). Stile a
   sottolineatura, distinto dalle chip pillola del filtro periodo. */
.panel__tabs {
  display: flex;
  gap: 4px;
  margin: 0 0 18px;
  border-bottom: 1px solid var(--sb-line, var(--c-azure-100));
}
.panel__tab {
  padding: 10px 16px;
  font-weight: 600;
  color: var(--c-ink-soft);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.panel__tab:hover { color: var(--sb-primary, var(--c-blue-500)); }
.panel__tab--active {
  color: var(--sb-primary, var(--c-blue-500));
  border-bottom-color: var(--sb-primary, var(--c-blue-500));
}

/* Overlay di caricamento a schermo intero (operazioni lunghe, es. traduzione AI). */
.app-loader {
  position: fixed; inset: 0; z-index: 9999;
  display: grid; place-items: center;
  background: rgba(15, 23, 42, 0.45);
}
.app-loader__box {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  background: #fff; border-radius: 16px; padding: 28px 40px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
}
.app-loader__spinner {
  width: 38px; height: 38px; border-radius: 50%;
  border: 3px solid var(--c-azure-100, #dbeafe); border-top-color: var(--sb-accent);
  animation: app-loader-spin 0.7s linear infinite;
}
.app-loader__text { font-weight: 600; color: var(--c-blue-900, #0f2a4a); }
@keyframes app-loader-spin { to { transform: rotate(360deg); } }

/* Anagrafica di una pagina di dettaglio: etichetta sopra, valore sotto, in
   colonne che si adattano. Semanticamente è una <dl>; la griglia tiene le
   coppie incolonnate invece di farle correre come testo in linea. */
.panel__facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: var(--sb-sp-4) var(--sb-sp-5); margin: 0;
}
.panel__fact { min-width: 0; }
.panel__fact-label {
  font-size: var(--sb-fs-micro); font-weight: 700; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--sb-muted);
}
.panel__fact-value { margin: var(--sb-sp-1) 0 0; color: var(--sb-ink); font-weight: 600; word-break: break-word; }
/* The `margin: 0` above comes after `.panel__section--flow > * + *` at the same
   weight, so in a flow section the facts lost their rhythm and sat glued to
   whatever came before — a note (the pre check-in card), a table (the service
   sheet). After a title nothing changes: the title's own margin already holds. */
.panel__section--flow > :not(.panel__section-title) + .panel__facts { margin-top: var(--sb-sp-4); }

/* §E60.2 — The notes of a booking, on its own sheet and in the stay sheet's
   booking card.

   A request from the widget reuses `.panel__facts` on purpose: its summary IS
   label and value, and a second way of drawing a pair on the same page would
   only be a second thing to keep aligned. What is new here is the quote — the
   visitor's own words are the one text on the sheet that nobody on the staff
   wrote, and until now they sat in the same grey paragraph as our labels.
   `__group` and `__label` are shared with a note typed at the desk, which has no
   shape to read back and only needs saying what it is.

   The message keeps its shape because `simple_format` gives it one: it arrives
   from a textarea with CRLF, so its blank lines become paragraphs and its single
   breaks `<br>`. Hence the two paragraph rules below and no `white-space` here —
   the breaks are markup, not preserved whitespace. */
.bnote { display: grid; gap: var(--sb-sp-4); }

.bnote__title {
  margin: 0;
  font-size: var(--sb-fs-label);
  font-weight: 700;
  color: var(--sb-ink);
}

.bnote__group { display: grid; gap: var(--sb-sp-2); }

/* Same mark as a fact label: what follows is a value, and the eye should read
   the pair the same way it reads the ones above. */
.bnote__label {
  margin: 0;
  font-size: var(--sb-fs-micro);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sb-muted);
}

.bnote__quote {
  margin: 0;
  padding: var(--sb-sp-3) var(--sb-sp-4);
  border-left: 3px solid var(--sb-accent);
  border-radius: 0 var(--sb-r-card) var(--sb-r-card) 0;
  background: var(--sb-bg-2);
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-body-sm);
  color: var(--sb-ink);
  overflow-wrap: anywhere;
}

.bnote__quote p { margin: 0 0 var(--sb-sp-2); }
.bnote__quote p:last-child { margin-bottom: 0; }

/* §E43 — Valore da consegnare a qualcun altro (codice referral, link di
   registrazione) con il suo bottone di copia. Flex e non flusso inline: un URL
   di registrazione va a capo più volte in una cella di .panel__facts, e in
   flusso normale il bottone finirebbe in coda all'ultimo frammento invece di
   restare accanto al valore. `min-width: 0` sul testo è ciò che gli permette di
   restringersi dentro il flex invece di sfondare la cella. */
.copy-value { display: flex; align-items: center; gap: var(--sb-sp-2); }
.copy-value__text { min-width: 0; overflow-wrap: anywhere; font-size: var(--sb-fs-label); }

/* Occhiello come varco verso la pagina padre: è la briciola di pane delle
   pagine di dettaglio, e toglie un bottone "Indietro" dalla riga delle azioni. */
.panel__eyebrow a { color: inherit; text-decoration: none; }
.panel__eyebrow a:hover { text-decoration: underline; }

.panel__empty {
  background: #fff;
  border: 1px dashed var(--sb-line);
  border-radius: var(--sb-r-btn);
  box-shadow: none;
  padding: var(--sb-sp-5) var(--sb-sp-4);
  text-align: center;
  color: var(--sb-muted);
}

/* I Turbo Frame sono elementi custom: senza display esplicito resterebbero
   inline e romperebbero i layout che li contengono. */
turbo-frame {
  display: block;
}

/* --- Desktop -------------------------------------------------------------- */
@media (min-width: 640px) {
  .hero__card {
    padding: 44px 40px;
  }

  .hero__title {
    font-size: 2.3rem;
  }

  .form__grid {
    grid-template-columns: 1fr 1fr;
  }
}

/* ==========================================================================
   App ospite (Guest::) — mobile-first, ispirazione StayBell.
   Card arrotondate, badge "In evidenza"/"Più scelto", footer neutro.
   ========================================================================== */
.g-body {
  background: linear-gradient(180deg, var(--c-azure-50) 0%, var(--c-azure-100) 100%);
}

.g-app {
  max-width: var(--g-app-width); /* §E67 — 560px, 960px from 768px */
  margin: 0 auto;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  padding: 0 16px calc(24px + env(safe-area-inset-bottom));
}

/* --- Top bar --------------------------------------------------------------- */
.g-topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 4px;
  background: rgba(244, 249, 255, 0.9);
  backdrop-filter: blur(8px);
}

.g-topbar__brand {
  font-weight: 800;
  font-size: 1.05rem;
  color: var(--c-blue-900);
  text-decoration: none;
  letter-spacing: 0.2px;
}

/* --- Selettore lingua (details nativo) ------------------------------------- */
.g-lang {
  position: relative;
}

.g-lang__current {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  color: var(--c-blue-700);
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

.g-lang__current::-webkit-details-marker { display: none; }

.g-lang__menu {
  position: absolute;
  right: 0;
  margin: 6px 0 0;
  padding: 6px;
  list-style: none;
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: 14px;
  box-shadow: var(--shadow);
  min-width: 150px;
  z-index: 20;
}

.g-lang__item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 10px;
  text-decoration: none;
  color: var(--c-ink);
  font-size: 0.9rem;
}

.g-lang__item:hover { background: var(--c-azure-50); }
.g-lang__item--active { background: var(--c-azure-100); font-weight: 700; }
.g-lang__code { font-weight: 700; color: var(--c-blue-700); font-size: 0.78rem; }

/* --- Flash ----------------------------------------------------------------- */
/* §E50.1 — same partial as the panel (`shared/_flash`, prefix "g-flash"), so the
   row of icon + text + close is the same; the box around it stays the guest one,
   and the four guest themes keep overriding only the margin as they did. */
.g-flash {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 8px 0 0;
  padding: 10px 14px;
  border-radius: 12px;
  font-weight: 600;
  font-size: 0.9rem;
}
.g-flash--notice { background: rgba(70, 180, 80, 0.12); color: var(--c-ok); }
.g-flash--alert { background: #fdecec; color: var(--sb-danger, #C62828); }
.g-flash--warn { background: #fdf6ea; color: #7d5210; }
.g-flash--info { background: #eef7fd; color: #0f5c85; }

/* --- Demo (§E35) ----------------------------------------------------------
   La striscia in cima all'app ospite della struttura vetrina, e la riga di
   pulsanti della schermata dei limiti.

   La striscia sta FUORI da `.g-app` (vedi il commento nel layout): è ConSergio
   che parla, non la struttura, e soprattutto `.g-app` è il contesto di
   posizionamento della testata, che su `classic` con copertina galleggia sulla
   foto in `position: absolute`. Da qui è a tutta larghezza, quindi il contenuto
   si allinea da sé alla colonna dell'app.

   Costruita sui token --sb-*, che i tre fogli di tema rimappano (ADR-0018): la
   striscia segue l'aspetto scelto invece di restare azzurra su un tema
   notturno. Il fondo scuro è però suo e non del tema: dev'essere leggibile
   sopra qualunque cosa cominci sotto, copertina compresa. */
.g-demo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 2px 10px;
  /* Più larga della colonna dell'app (560px): a quella misura, su desktop, la
     frase andava a capo lasciando due terzi di barra vuoti ai lati. Qui sta su
     una riga sola dove c'è spazio, e va a capo solo sui telefoni. */
  max-width: 820px;
  margin: 0 auto;
  padding: 7px 20px;
  background: var(--sb-dark);
  color: #fff;
  font-size: 0.78rem;
  line-height: 1.35;
  text-align: center;
}
.g-demo__label {
  flex: none;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--sb-accent);
  color: var(--sb-dark);
  font-weight: 700;
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
/* Prende lo spazio che resta, così il link finisce a capo sui telefoni stretti
   invece di comprimere il testo a due parole per riga. */
.g-demo__text { flex: 1 1 12rem; color: rgba(255, 255, 255, .88); }
.g-demo__link {
  flex: none;
  color: #fff;
  font-weight: 700;
  text-decoration: underline;
  white-space: nowrap;
}

/* I due pulsanti della schermata dei limiti: affiancati, a capo se non ci stanno. */
.g-demo__actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }

/* Il fondo deve correre da bordo a bordo anche quando il contenuto è fermo a
   560px: su desktop una barra larga quanto la colonna sembrerebbe un pezzo
   dell'app della struttura, e questa non lo è. */
.g-demo-bleed { background: var(--sb-dark); }

/* --- §E56 — invito alle notifiche push ------------------------------------- */
/* Sta FUORI da `.g-app`, accanto alla striscia della demo e per la stessa
   ragione: `.g-app` è il contesto di posizionamento di una topbar che su
   `classic` galleggia sopra la copertina, e una striscia dentro il contenitore
   verrebbe disegnata sotto la foto.

   Tonalità chiara e non `--sb-dark`: la demo parla a nome di ConSergio, questa
   parla a nome della struttura ed è dentro la sua app. Usa i token `--c-*`, così
   i quattro fogli `guest_template_*.css` la ridipingono da soli senza che qui
   serva un override per template. */
.g-push-bleed { background: var(--c-azure-50); }

.g-push {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  max-width: 560px;
  margin: 0 auto;
  padding: 12px 20px;
}

/* NON opzionale: `.g-push` è `display: flex`, e una dichiarazione d'autore batte
   la regola [hidden] dello user agent. Senza questa riga la striscia sarebbe
   visibile sempre, a tutti, anche senza JavaScript — cioè proprio a chi non ha
   modo di attivare niente. Stessa lezione di .billing-banner[hidden] e
   .mng-nav__badge[hidden]. */
.g-push[hidden] { display: none; }

/* Prende lo spazio che resta, così i pulsanti vanno a capo sui telefoni stretti
   invece di comprimere il testo a due parole per riga. */
.g-push__body { flex: 1 1 14rem; }
.g-push__title {
  margin: 0;
  font-weight: 700;
  font-size: 0.86rem;
  color: var(--c-ink);
}
.g-push__text {
  margin: 2px 0 0;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--c-muted);
}

.g-push__actions { display: flex; flex: none; align-items: center; gap: 10px; }

.g-push__cta {
  flex: none;
  padding: 8px 16px;
  border: 0;
  border-radius: 999px;
  background: var(--c-blue-500);
  color: var(--c-white);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
}
.g-push__later {
  flex: none;
  padding: 8px 4px;
  border: 0;
  background: none;
  color: var(--c-muted);
  font: inherit;
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
}

/* L'invito nella conferma di richiesta sta dentro la colonna, non a tutta
   larghezza: è in contesto, accanto allo stato di cui parla. */
.g-push--invite {
  margin: 18px 0 0;
  padding: 14px 16px;
  border: 1px solid var(--c-azure-100);
  border-radius: var(--radius);
  background: var(--c-azure-50);
  text-align: left;
}

/* Pagina delle notifiche: lo stato che il controller riempie, e la nota privacy. */
.g-push-status {
  margin: 0 0 14px;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--c-ink);
}
.g-push-note {
  margin: 14px 0 0;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--c-muted);
}

/* L'interruttore è attivabile solo in due dei dieci stati, quindi nasce nascosto
   e lo rivela il controller: `.btn` è inline-flex e batterebbe [hidden]. Idem
   per la campanella in topbar. */
.g-body .btn[hidden],
.g-topbar__action[hidden] { display: none; }

/* §E56.1 — over a cover photo the header floats with no background of its own, and
   a white pill at 16% on a bright photo is invisible. The bell is the way back for
   whoever did not switch notifications on straight away, so it has to stay legible
   there too: a DARK backing, because the photo can be any colour, and white ink is
   already what these two templates use up there.

   `classic` and `green` only, and deliberately so: `warm` and `night` float as well
   but repaint the actions in their own dark ink, where a dark pill would be worse
   than the problem. `utility` keeps an opaque header and needs nothing. */
.g-body[data-guest-hero="cover"]:is([data-guest-template="classic"],
                                    [data-guest-template="green"]) .g-topbar__action {
  background: rgba(15, 23, 42, 0.42);
}
.g-body[data-guest-hero="cover"]:is([data-guest-template="classic"],
                                    [data-guest-template="green"]) .g-topbar__action:hover {
  background: rgba(15, 23, 42, 0.58);
}

/* --- §E56.1 The activation modal of the guest app --------------------------- */

/* A strip is not read on a phone held in one hand: the offer asks the way an app
   asks, with a sheet that rises from the bottom and waits for an answer.
   `showModal()` puts it in the TOP LAYER, which is also what frees it from the
   stacking trouble that keeps the demo banner outside `.g-app`.

   It opens by itself — the permission prompt does NOT. requestPermission() stays
   inside the click on the button in here, which is the difference between a
   pre-permission screen and burning a permission nobody can grant back. */
.g-sheet {
  border: 0;
  padding: 0;
  /* `auto auto 0` pins it to the bottom: the UA gives a modal dialog inset 0, so
     the automatic margins centre it and a zero at the bottom drops it down. */
  margin: auto auto 0;
  width: 100%;
  max-width: 440px;
  background: #fff;
  color: var(--c-ink);
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -12px 40px rgba(2, 32, 71, 0.22);
}
.g-sheet::backdrop { background: rgba(15, 23, 42, 0.45); }

/* No `overflow` here, and it is a decision: `overflow-y: auto` forces the computed
   `overflow-x` to `auto` as well, and the focus ring of the button — 2px plus 1px
   of offset — would then scroll the sheet sideways. The content is three lines. */
.g-sheet__box { padding: 10px 20px calc(20px + env(safe-area-inset-bottom, 0px)); }

/* The grab handle of a native sheet. Decorative: nothing drags here. */
.g-sheet__grab {
  width: 38px;
  height: 4px;
  margin: 0 auto 16px;
  border-radius: 999px;
  background: var(--c-azure-100);
}

/* Three bodies, one shown at a time by the controller. The `[hidden]` rule is not
   optional: `display: flex` is an author declaration and beats the user agent, so
   without it all three would show at once. */
.g-sheet__body { display: flex; flex-direction: column; gap: 8px; }
.g-sheet__body[hidden] { display: none; }

.g-sheet__title { margin: 0; font-size: 1.02rem; font-weight: 700; color: var(--c-ink); }
.g-sheet__text { margin: 0; font-size: 0.86rem; line-height: 1.45; color: var(--c-muted); }

/* Stacked and full width, with the thumb in mind: this is the bottom of a phone. */
.g-sheet__actions { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; }
.g-sheet__cta {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 0 18px;
  border: 0; border-radius: 999px;
  background: var(--c-blue-500); color: var(--c-white);
  font-family: inherit; font-size: 0.92rem; font-weight: 700;
  cursor: pointer;
}
.g-sheet__later {
  min-height: 44px;
  border: 0; background: none;
  color: var(--c-muted);
  font-family: inherit; font-size: 0.85rem; font-weight: 600;
  cursor: pointer;
}

/* Above phone width it stops being a sheet and becomes a centred dialog. */
@media (min-width: 30rem) {
  .g-sheet { margin: auto; border-radius: 18px; }
  .g-sheet__grab { display: none; }
  .g-sheet__box { padding: 22px 24px 24px; }
}

@media (prefers-reduced-motion: no-preference) {
  .g-sheet[open] { animation: g-sheet-rise 0.22s ease-out; }
  @keyframes g-sheet-rise {
    from { transform: translateY(14px); opacity: 0; }
    to { transform: none; opacity: 1; }
  }
}

.g-main {
  flex: 1;
  padding-top: 8px;
}

/* --- Saluto ---------------------------------------------------------------- */
/* Copertina della struttura in cima alla home. Rapporto fisso: la foto la
   sceglie la struttura e non deve poter allungare la pagina a piacere. */
.g-cover { margin: 12px 0 0; }
.g-cover__img {
  display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: 18px; background: var(--c-azure-50);
  box-shadow: 0 8px 22px rgba(20, 94, 168, 0.12);
}
@media (min-width: 640px) {
  .g-cover__img { aspect-ratio: 21 / 9; }
}

.g-roombadge {
  display: inline-block;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  background: var(--c-blue-500);
  color: var(--c-white);
  font-size: 0.8rem;
  font-weight: 700;
  margin-bottom: 12px;
}

/* --- Card assistente (disabilitata MVP) ------------------------------------ */
.g-assistant {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 18px 0;
  padding: 16px;
  border-radius: var(--radius);
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  box-shadow: var(--shadow);
}

.g-assistant__icon {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: var(--c-azure-100);
  color: var(--c-blue-700);
  font-size: 1.3rem;
}

.g-assistant__title { margin: 0 0 2px; font-size: 1.05rem; color: var(--c-blue-900); }
.g-assistant__sub { margin: 0; font-size: 0.85rem; color: var(--c-ink-soft); }

/* --- Sezioni --------------------------------------------------------------- */
.g-section { margin: 22px 0; }

.g-section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.g-section__title {
  margin: 0 0 12px;
  font-size: 1.15rem;
  color: var(--c-blue-900);
}

.g-section__head .g-section__title { margin-bottom: 0; }

.g-link {
  color: var(--c-blue-700);
  font-weight: 600;
  font-size: 0.85rem;
  text-decoration: none;
  white-space: nowrap;
}

/* --- Griglia servizi ------------------------------------------------------- */
.g-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}

.g-service {
  display: flex;
  flex-direction: column;
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: 18px;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  box-shadow: 0 6px 18px rgba(20, 94, 168, 0.08);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.g-service:active { transform: scale(0.99); }

.g-service__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--c-azure-100);
  /* §E28 — il rapporto lo decide il RIQUADRO, mai l'immagine. Le varianti degli
     allegati arrivano già ritagliate su misura, le foto della galleria proposta
     no: sono asset statici con la LORO proporzione. Senza questi due vincoli
     l'altezza intrinseca della foto gonfia il riquadro (`min-height: auto` del
     flex item) e la card esce più alta delle vicine.
     Vale per tutti i template: quelli che ridefiniscono il riquadro cambiano
     misura e proporzione, non questa regola. */
  min-height: 0;
  overflow: hidden;
}

.g-service__img { width: 100%; height: 100%; object-fit: cover; display: block; }

.g-service__placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 2rem;
  font-weight: 800;
  color: var(--c-blue-300);
}

.g-service__badges {
  position: absolute;
  top: 8px;
  left: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.g-service__body { padding: 10px 12px 14px; }
.g-service__cat {
  margin: 0 0 2px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--c-blue-500);
  font-weight: 700;
}
.g-service__name { margin: 0 0 8px; font-size: 0.98rem; color: var(--c-ink); line-height: 1.2; }
.g-service__meta { display: flex; flex-direction: column; gap: 2px; }
.g-service__hours { font-size: 0.8rem; color: var(--c-ink-soft); }
.g-service__price { font-size: 0.9rem; font-weight: 700; color: var(--c-blue-700); }

/* --- Badge ----------------------------------------------------------------- */
.g-badge {
  display: inline-block;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.3px;
}
.g-badge--featured { background: var(--c-blue-500); color: var(--c-white); }
.g-badge--popular { background: #ffb020; color: #3a2600; }

/* --- CTA contenuti --------------------------------------------------------- */
.g-cta {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px;
  border-radius: var(--radius);
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  box-shadow: var(--shadow);
  text-decoration: none;
  color: inherit;
}
.g-cta__icon {
  width: 44px; height: 44px; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--c-azure-100); color: var(--c-blue-700); font-size: 1.2rem;
}
.g-cta__body { display: flex; flex-direction: column; flex: 1; }
.g-cta__title { font-weight: 700; color: var(--c-blue-900); }
.g-cta__sub { font-size: 0.85rem; color: var(--c-ink-soft); }
.g-cta__arrow { color: var(--c-blue-500); font-size: 1.2rem; }

/* --- Liste / contenuti ----------------------------------------------------- */
.g-page__title { margin: 8px 0 4px; font-size: 1.5rem; color: var(--c-blue-900); }
.g-page__sub { margin: 0 0 16px; color: var(--c-ink-soft); }

.g-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.g-list__item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px; border-radius: 16px; background: var(--c-white);
  border: 1px solid var(--c-azure-100); box-shadow: 0 6px 18px rgba(20, 94, 168, 0.08);
  text-decoration: none; color: var(--c-ink); font-weight: 600;
}
.g-list__arrow { color: var(--c-blue-500); }

/* Allegato scaricabile nel dettaglio di un documento: icona, nome su più righe
   se serve (i nomi dei file sono lunghi e lo schermo è stretto), peso sotto. */
.g-file__icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: 11px;
  background: var(--c-azure-50); color: var(--c-blue-700);
}
.g-file__body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 auto; }
.g-file__name { overflow-wrap: anywhere; }
.g-file__meta { font-size: 0.8rem; font-weight: 500; color: var(--c-ink-soft); }

.g-back {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 10px 0 4px; color: var(--c-blue-700);
  text-decoration: none; font-weight: 600; font-size: 0.9rem;
}

/* §E27 — messaggi programmati dalla struttura (benvenuto, offerta del giorno).
   Prefisso `g-announce` e NON `g-msg`, che è già la bolla della chat: due cose
   diverse con lo stesso nome, in un CSS scritto a mano, si rompono a vicenda.

   Base: la stessa card di .g-cta (fondo bianco, bordo azzurro, ombra). Il non
   letto si distingue con un bordo a sinistra E l'etichetta "Nuovo": il colore da
   solo non comunica mai uno stato. */
.g-announces { display: flex; flex-direction: column; gap: 12px; }
.g-announces__intro { margin: 0 0 12px; font-size: 0.9rem; color: var(--c-ink-soft); }
.g-announce {
  display: flex; flex-direction: column; gap: 10px; padding: 16px;
  border-radius: var(--radius); background: var(--c-white);
  border: 1px solid var(--c-azure-100); box-shadow: var(--shadow);
}
.g-announce--unread { border-left: 3px solid var(--c-blue-500); }
.g-announce__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.g-announce__icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: 11px;
  background: var(--c-azure-100); color: var(--c-blue-700);
}
.g-announce__title { margin: 0; flex: 1 1 auto; min-width: 0; font-size: 1.02rem; color: var(--c-blue-900); overflow-wrap: anywhere; }
.g-announce__flag {
  flex: none; padding: 3px 9px; border-radius: var(--radius-sm);
  background: var(--c-blue-500); color: #fff;
  font-size: 0.68rem; font-weight: 800; letter-spacing: 0.3px; text-transform: uppercase;
}
.g-announce__body { font-size: 0.9rem; color: var(--c-ink); overflow-wrap: anywhere; }
.g-announce__body p { margin: 0 0 8px; }
.g-announce__body p:last-child { margin-bottom: 0; }
.g-announce__actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.g-announce__cta {
  display: inline-flex; align-items: center; min-height: 40px; padding: 0 16px;
  border-radius: var(--sb-r-btn); background: var(--c-blue-700); color: #fff;
  font-size: 0.88rem; font-weight: 700; text-decoration: none;
}
.g-announce__link { color: var(--c-blue-700); font-weight: 600; font-size: 0.85rem; text-decoration: none; }
/* "Non mostrare più" è un button_to: il form non deve aggiungere margine, e il
   bottone non deve sembrare l'azione principale della card. */
.g-announce__dismiss {
  margin-left: auto; padding: 0; min-height: 40px;
  background: none; border: 0; cursor: pointer;
  color: var(--c-ink-soft); font-size: 0.8rem; font-weight: 600;
}
.g-announce__dismiss:hover { color: var(--c-blue-700); text-decoration: underline; }
.g-announce__actions form { margin: 0; margin-left: auto; }
.g-announce__actions form .g-announce__dismiss { margin-left: 0; }
.g-announce__time { margin: 10px 0 0; font-size: 0.78rem; color: var(--c-ink-soft); }

/* Storico: già letti o messi via. Una riga, non una card: sono consultazione. */
.g-announces__past-title {
  margin: 24px 0 8px; font-size: 0.78rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.03em; color: var(--c-blue-700);
}
.g-announces__past { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.g-announce-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 44px; padding: 8px 14px;
  border-radius: var(--radius-sm); background: var(--c-white);
  border: 1px solid var(--c-azure-100); text-decoration: none; color: inherit;
}
.g-announce-row__title { font-size: 0.88rem; font-weight: 600; color: var(--c-blue-900); overflow-wrap: anywhere; }
.g-announce-row__time { flex: none; font-size: 0.75rem; color: var(--c-ink-soft); }

/* Contatore dei non letti sulla voce di navigazione (chip e tab bar). */
.g-navbadge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--sb-accent-strong); color: #fff;
  font-size: 0.68rem; font-weight: 800; line-height: 1;
}
.g-sectionnav__item--active .g-navbadge { background: #fff; color: var(--sb-primary); }

/* --- §E29 Sondaggio di fine soggiorno --------------------------------------
   Real radio inputs under every control: they work with no JavaScript, they are
   reachable from the keyboard and a screen reader names them for free. Stars and
   the 0-10 scale are CSS painted on top of those inputs, never a custom widget.

   Prefix `g-survey` and not `g-form`: the guest form classes (`form__*`) are
   shared with the panel, and repainting them here would follow the staff around
   the whole app. */
/* Introduzione: era una riga grigia piccola sopra la prima domanda e si perdeva
   del tutto. È il testo che spiega PERCHÉ stiamo chiedendo, cioè quello che
   decide se l'ospite comincia: ha bisogno di una superficie sua. */
.g-survey__intro {
  margin: 0 0 18px; padding: 14px 16px;
  border-radius: var(--radius); border-left: 3px solid var(--c-blue-500);
  background: var(--c-white); box-shadow: var(--shadow);
  font-size: 1rem; line-height: 1.5; color: var(--c-ink);
}
.g-survey__intro p { margin: 0 0 8px; }
.g-survey__intro p:last-child { margin-bottom: 0; }
.g-survey__questions { display: flex; flex-direction: column; gap: 12px; }

/* A fieldset and not a div: the legend is what ties the question to its group of
   radios, which is the whole accessible name of the control. */
/* `overflow: hidden` fa due lavori: arrotonda la fascia del titolo insieme alla
   card e contiene il float della legend qui sotto. */
.g-survey__q {
  margin: 0; padding: 0; border: 1px solid var(--c-azure-100);
  border-radius: var(--radius); background: var(--c-white); box-shadow: var(--shadow);
  overflow: hidden;
}
/* Il titolo resta un <legend> — è ciò che dà il nome accessibile al gruppo di
   radio, e un <div> lo perderebbe — ma `float: left; width: 100%` lo toglie
   dalla linea del bordo, dove il browser lo incastona di suo tagliando il testo
   a metà. Diventa così una piccola intestazione a tutta larghezza. */
.g-survey__q-title {
  float: left; width: 100%; box-sizing: border-box;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 12px 16px; margin: 0;
  background: var(--c-azure-100); border-bottom: 1px solid var(--c-azure-100);
  font-size: 1rem; font-weight: 700; color: var(--c-blue-900); line-height: 1.3;
}
.g-survey__q-body { clear: both; padding: 16px; }
.g-survey__q-num {
  flex: none; display: inline-grid; place-items: center;
  width: 22px; height: 22px; border-radius: 999px;
  background: var(--c-azure-100); color: var(--c-blue-700);
  font-size: 0.72rem; font-weight: 800;
}
.g-survey__q-req {
  flex: none; padding: 2px 8px; border-radius: var(--radius-sm);
  background: var(--c-azure-100); color: var(--c-blue-700);
  font-size: 0.66rem; font-weight: 800; letter-spacing: 0.3px; text-transform: uppercase;
}
.g-survey__q-help { margin: 0 0 10px; font-size: 0.82rem; color: var(--c-ink-soft); }

/* §E29.5 — the optional "why" under a score. It sits below the control, so the
   spacing is its own top margin: the controls above carry no bottom one. */
.g-survey__q-comment { margin-top: 14px; }
.g-survey__q-comment-label {
  display: block; margin-bottom: 6px;
  font-size: 0.82rem; color: var(--c-ink-soft);
}

/* Stars. DOM order stays 1..5 — reversing it to make the sibling combinator work
   would reverse the tab order too — so the fill uses `:has()` on the container.
   The CSS `fill` property beats the `fill="none"` presentation attribute the
   icon helper writes, which is what turns the outline into a solid star. */
.g-stars { display: flex; gap: 4px; }
.g-stars__input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.g-stars__label {
  display: inline-flex; padding: 4px; cursor: pointer;
  color: var(--c-blue-300); transition: color 0.12s ease;
}
.g-stars__input:focus-visible + .g-stars__label {
  outline: 2px solid var(--sb-primary-500); outline-offset: 2px; border-radius: 6px;
}
.g-stars:has(.g-stars__input[value="1"]:checked) .g-stars__label:nth-of-type(-n+1),
.g-stars:has(.g-stars__input[value="2"]:checked) .g-stars__label:nth-of-type(-n+2),
.g-stars:has(.g-stars__input[value="3"]:checked) .g-stars__label:nth-of-type(-n+3),
.g-stars:has(.g-stars__input[value="4"]:checked) .g-stars__label:nth-of-type(-n+4),
.g-stars:has(.g-stars__input[value="5"]:checked) .g-stars__label:nth-of-type(-n+5) {
  color: var(--sb-accent);
}
.g-stars__label svg { fill: currentColor; }
/* Fallback where `:has()` is missing: the chosen star at least paints itself, so
   the answer is never invisible. */
.g-stars__input:checked + .g-stars__label { color: var(--sb-accent); }

/* Scala 0-10 su UNA riga sola. Una griglia a 11 colonne uguali e non un flex che
   va a capo: mandando a capo, la scala si legge come due gruppi separati e il 10
   finisce da solo sotto lo zero, cioè accanto al suo opposto.
   Le celle si stringono quanto serve; l'altezza resta di 44px, così il bersaglio
   per il dito non si perde anche quando sono strette. */
.g-nps { display: grid; grid-template-columns: repeat(11, minmax(0, 1fr)); gap: 4px; }
.g-nps__input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.g-nps__label {
  display: grid; place-items: center; min-width: 0; height: 44px;
  padding: 0 2px; border-radius: var(--sb-r-btn);
  border: 1px solid var(--c-azure-100); background: var(--c-white);
  color: var(--c-blue-900); font-weight: 700; font-size: 0.82rem; cursor: pointer;
}
.g-nps__input:checked + .g-nps__label {
  background: var(--c-blue-700); border-color: var(--c-blue-700); color: #fff;
}
.g-nps__input:focus-visible + .g-nps__label {
  outline: 2px solid var(--sb-primary-500); outline-offset: -2px;
}
.g-nps__scale {
  display: flex; justify-content: space-between; gap: 12px;
  margin-top: 6px; font-size: 0.74rem; color: var(--c-ink-soft);
}

/* Yes / no: the same pill as the scale, wide enough for a thumb. */
.g-choice { display: flex; gap: 8px; }
.g-choice__input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.g-choice__label {
  display: inline-grid; place-items: center; min-width: 92px; height: 44px;
  padding: 0 18px; border-radius: var(--sb-r-btn);
  border: 1px solid var(--c-azure-100); background: var(--c-white);
  color: var(--c-blue-900); font-weight: 700; font-size: 0.9rem; cursor: pointer;
}
.g-choice__input:checked + .g-choice__label {
  background: var(--c-blue-700); border-color: var(--c-blue-700); color: #fff;
}
.g-choice__input:focus-visible + .g-choice__label {
  outline: 2px solid var(--sb-primary-500); outline-offset: 2px;
}

/* Foot: the autosave note on the left, the send button on the right. */
.g-survey__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 18px;
}
.g-survey__status {
  display: inline-flex; align-items: center; gap: 6px; min-height: 20px;
  font-size: 0.8rem; color: var(--c-ink-soft);
}
.g-survey__send { min-width: 160px; }

/* Closing screen. The review links are there for everyone: the score only moves
   the emphasis (see ADR-0022). */
.g-survey__thanks { display: flex; flex-direction: column; gap: 18px; }
.g-survey__thanks-lead { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--c-blue-900); }
.g-survey__thanks-note { margin: 0; font-size: 0.9rem; color: var(--c-ink-soft); }
.g-survey__thanks-back { margin: 0; }
.g-survey__recovery {
  padding: 16px; border-radius: var(--radius); background: var(--c-white);
  border: 1px solid var(--c-azure-100); box-shadow: var(--shadow);
}
.g-survey__recovery-title { margin: 0 0 4px; font-size: 1rem; color: var(--c-blue-900); }
.g-survey__reviews {
  padding: 16px; border-radius: var(--radius);
  background: var(--c-azure-100); border: 1px solid var(--c-blue-300);
}
/* Below the threshold the links stay, quietly: first we listen, then we ask. */
.g-survey__reviews--muted { background: var(--c-white); border-color: var(--c-azure-100); }
.g-survey__reviews-title { margin: 0 0 10px; font-size: 0.98rem; color: var(--c-blue-900); }
.g-survey__reviews-links { display: flex; flex-wrap: wrap; gap: 8px; }
.g-survey__review-link {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding: 0 16px; border-radius: var(--sb-r-btn);
  border: 1px solid var(--c-blue-300); background: var(--c-white);
  color: var(--c-blue-700); font-size: 0.88rem; font-weight: 700; text-decoration: none;
}
.g-survey__review-link--primary {
  background: var(--c-blue-700); border-color: var(--c-blue-700); color: #fff;
}

/* Home card: the accent border is the only thing that sets it apart from the
   assistant card next to it — an ordinary card would go unnoticed on the one day
   it matters. */
.g-cta--survey { border-left: 3px solid var(--sb-accent); }

.g-empty {
  margin: 18px 0;
  padding: 28px;
  text-align: center;
  color: var(--c-ink-soft);
  background: var(--c-white);
  border: 1px dashed var(--c-blue-300);
  border-radius: var(--radius);
}

/* --- Dettaglio ------------------------------------------------------------- */
.g-detail { padding: 6px 0 8px; }
.g-detail__media {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius);
  overflow: hidden;
  min-height: 0; /* vedi .g-service__media */
  background: var(--c-azure-100);
  margin-bottom: 14px;
}
.g-detail__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.g-detail__placeholder { position: absolute; inset: 0; display: grid; place-items: center; font-size: 3rem; font-weight: 800; color: var(--c-blue-300); }
.g-detail__badge { position: absolute; top: 12px; left: 12px; }
.g-detail__cat { margin: 0 0 2px; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.4px; color: var(--c-blue-500); font-weight: 700; }
.g-detail__title { margin: 0 0 14px; font-size: 1.6rem; color: var(--c-blue-900); line-height: 1.15; }

/* §E23/UX — orari e prezzo: una scheda sola a righe piene.
   Prima erano due riquadri affiancati (`flex: 1 1 120px`) da ~170px in una
   colonna da 350px: il valore, a 1rem in grassetto, non ci stava e andava a
   capo su ogni riga. Ora la riga è larga quanto la colonna e giorni/orari si
   incolonnano in due tracce, come una tabella di apertura. */
/* La superficie sta su un modificatore, NON su `.g-facts`: il riepilogo del
   modulo usa le stesse righe dentro una card che ha già la sua, e i tre temi
   ridipingono le superfici con selettori più specifici — un reset sulla
   variante compatta perderebbe comunque. Meglio non darle mai lo stile card. */
.g-facts--card {
  margin-bottom: 16px;
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: 14px;
  overflow: hidden;
}
.g-fact { display: flex; gap: 12px; padding: 12px 14px; }
/* Il divisorio separa le righe SENZA chiudere la prima: un bordo in cima a
   ognuna tranne quella iniziale. */
.g-fact + .g-fact { border-top: 1px solid var(--sb-line, var(--c-azure-100)); }
.g-fact__icon { flex: none; margin-top: 1px; color: var(--c-blue-500); }
.g-fact__body { min-width: 0; flex: 1; }
.g-fact__label { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.4px; color: var(--c-ink-soft); font-weight: 700; margin-bottom: 3px; }

/* Due tracce: la prima si adatta al giorno più lungo ("Tutti i giorni"), la
   seconda prende il resto. Allineando gli orari su una colonna sola la lista
   si scorre in verticale invece di rileggerla riga per riga. */
.g-fact__rows { display: grid; grid-template-columns: auto 1fr; gap: 2px 14px; align-items: baseline; }
.g-fact__key { color: var(--c-ink-soft); font-size: 0.92rem; font-weight: 500; }
/* `tabular-nums`: le cifre degli orari hanno tutte la stessa larghezza, quindi
   18:00–20:00 e 19:00–21:00 si incolonnano davvero. */
.g-fact__value {
  color: var(--c-blue-900); font-size: 0.92rem; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.g-fact__value--wide { grid-column: 1 / -1; }
.g-fact__note { color: var(--c-ink-soft); font-size: 0.92rem; }

/* Riepilogo dentro il modulo di richiesta: niente etichette, che le icone
   bastano, e righe strette. La superficie non c'è perché non la si aggiunge
   (vedi `.g-facts--card`), non perché la si toglie. */
.g-facts--compact .g-fact { padding: 2px 0; }
.g-facts--compact .g-fact + .g-fact { border-top: 0; }
/* Nel riepilogo le due righe sono blocchi separati, quindi la seconda colonna
   non può allinearsi fra loro: meglio due frasi compatte che due mezze tabelle
   disallineate. */
.g-facts--compact .g-fact__rows { grid-template-columns: auto auto; justify-content: start; gap: 2px 8px; }

.g-detail__desc { color: var(--c-ink); line-height: 1.6; }
.g-detail__desc p { margin: 0 0 12px; }

/* --- Footer ---------------------------------------------------------------- */
.g-footer {
  margin-top: 28px;
  padding: 20px 0 8px;
  border-top: 1px solid var(--c-azure-100);
  text-align: center;
}
.g-footer__note { margin: 0; font-size: 0.78rem; color: var(--c-ink-soft); line-height: 1.6; }
.g-footer__contact { margin: 0 0 12px; }
.g-footer__name { margin: 0 0 2px; font-weight: 700; color: var(--c-ink); }
.g-footer__line { margin: 0; font-size: 0.85rem; color: var(--c-ink-soft); line-height: 1.5; }
.g-footer__link { color: var(--c-blue-700); text-decoration: none; }
.g-footer__link:hover { text-decoration: underline; }

/* --- Schermi più larghi ---------------------------------------------------- */
/* §E23/UX — la griglia NON passa più a 3 colonne su viewport ≥600px.
   `.g-app` è largo al massimo 560px: la colonna resta 520px comunque, e tre
   colonne lì dentro davano card da 165px, cioè PIÙ STRETTE dei 169px che si
   ottengono a 390px. Il layout peggiorava allargando lo schermo.
   Restando a due colonne la card cresce col contenitore: 169px a 390px, 254px
   a 760px. Il breakpoint era sul viewport, che qui non c'entra nulla.
   §E67 — the three columns are back on a tablet, keyed to the COLUMN's width
   (a container query) now that the column itself widens to 960px. */
/* Sotto i 360px due colonne danno card da ~134px: il nome del servizio andava
   su tre righe e "35,00 € a persona" si spezzava. Meglio una colonna piena. */
@media (max-width: 359px) {
  .g-grid { grid-template-columns: 1fr; }
}

/* ==========================================================================
   §E4 — Richieste (ospite) e bacheca (Manage::)
   ========================================================================== */

/* --- Bottoni extra --------------------------------------------------------- */
.btn--block { display: flex; width: 100%; }
.chip--active { background: var(--c-blue-500); border-color: var(--c-blue-500); color: var(--c-white); }

/* --- Ospite: dettaglio CTA / servizio richiesto ---------------------------- */
.g-detail__cta { margin-top: 20px; }
.g-request-service {
  margin: 4px 0 16px;
  font-weight: 700;
  color: var(--c-blue-900);
  font-size: 1.1rem;
}

/* --- Ospite: schermata codice ---------------------------------------------- */
.g-code { text-align: center; }
.g-code__icon { font-size: 2.4rem; margin-bottom: 8px; }
.g-code__field {
  text-align: center;
  letter-spacing: 0.4em;
  text-transform: uppercase;
  font-size: 1.4rem;
  font-weight: 800;
}
.g-code__error { text-align: left; }

/* --- Ospite: conferma + stato ---------------------------------------------- */
.g-confirm { text-align: center; }
.g-confirm__check {
  width: 64px; height: 64px; margin: 0 auto 12px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(70, 180, 80, 0.14);
  color: var(--c-ok);
  font-size: 2rem; font-weight: 800;
}
.g-status {
  display: inline-flex; align-items: center; gap: 9px;
  margin: 16px auto;
  padding: 9px 16px; border-radius: var(--radius-sm);
  font-weight: 700;
  background: var(--c-azure-100); color: var(--c-blue-700);
}
.g-status__dot { width: 10px; height: 10px; border-radius: 50%; background: currentColor; }
.g-status--received { background: var(--c-azure-100); color: var(--c-blue-700); }
.g-status--in_progress { background: #fff3d6; color: #8a5a00; }
.g-status--done { background: rgba(70, 180, 80, 0.14); color: var(--c-ok); }
.g-status--cancelled { background: #fdecec; color: var(--sb-danger, #C62828); }

/* --- "Le mie richieste" (Guest::) ------------------------------------------ */
.g-reqs { display: flex; flex-direction: column; gap: 12px; }
/* §E23/UX — i messaggi della reception vanno a capo su una riga tutta loro.
   `row-gap: 0` di proposito: il contenitore dei messaggi si rende SEMPRE (è il
   bersaglio del broadcast) e contiene spazi, quindi `:empty` non lo prende;
   con un gap di riga ogni richiesta senza messaggi si porterebbe dietro 12px
   di vuoto. Lo stacco quando servono lo dà il margine del titolo. */
.g-req {
  display: flex; flex-wrap: wrap; column-gap: 12px; row-gap: 0;
  justify-content: space-between; align-items: flex-start;
  padding: 14px 16px; border-radius: var(--radius-md, 14px);
  background: #fff; border: 1px solid var(--c-line, #e6edf3);
}
.g-req-notes { flex: 1 1 100%; min-width: 0; }
.g-req__main { min-width: 0; flex: 1; }
/* Without a subject the row shows the guest's own words, and a pasted link has
   no place to break: it widened the whole page. */
.g-req__subject { margin: 0 0 6px; font-weight: 700; color: var(--c-ink); line-height: 1.35; overflow-wrap: anywhere; }
.g-req__pricing { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 6px; }
.g-req__people,
.g-req__price {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 0.8rem; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  background: var(--c-azure-100); color: var(--c-blue-700);
}
.g-req__price { background: var(--sb-warn-bg, #FDF0DC); color: var(--sb-warn-fg, #92590F); }
.g-req__meta { margin: 0; font-size: 0.75rem; color: var(--c-ink-soft); }
.g-req__side { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; flex-shrink: 0; }
.g-req__side .g-status { margin: 0; }
.g-req__cancel { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }

/* --- Bacheca (Manage::) ---------------------------------------------------- */
.board-counters {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  margin: 0 0 20px;
}
.board-counter {
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: var(--sb-r-card);
  padding: 16px 18px;
  box-shadow: 0 6px 18px rgba(20, 94, 168, 0.08);
}
.board-counter__num { display: block; font-size: var(--sb-fs-metric); font-weight: 700; color: var(--c-blue-900); line-height: 1; }
.board-counter__label { font-size: var(--sb-fs-body-sm); color: var(--c-ink-soft); font-weight: 600; }
.board-counter--time .board-counter__num { color: var(--c-blue-500); }

.board-filter { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 18px; }

.board {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
}
.board-col {
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid var(--c-azure-100);
  border-radius: var(--radius);
  padding: 14px;
}
.board-col__title {
  margin: 0 0 12px;
  font-size: var(--sb-fs-head);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding-left: 10px;
  border-left: 4px solid var(--c-blue-300);
}
.board-col__title--new { border-color: var(--c-blue-500); }
.board-col__title--progress { border-color: #ffb020; }
.board-col__title--done { border-color: var(--c-ok); }
.board-col__body { display: flex; flex-direction: column; gap: 12px; min-height: 40px; }
/* The empty column's sentence: always in the page, hidden as soon as a card is
   in the column (cards come and go by Turbo Stream). Ink and not the muted
   grey: on the column's --sb-bg-2 the grey is 4.42:1. */
.board-col__empty { padding: 0; color: var(--sb-ink); font-size: var(--sb-fs-body-sm); }
.board-col__body:has(.req-card) + .board-col__empty { display: none; }
.board-col__body:not(:has(.req-card)) { min-height: 0; }

.req-card {
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: var(--sb-r-card);
  padding: 14px;
  box-shadow: 0 6px 18px rgba(20, 94, 168, 0.08);
}
/* Card arrivata in tempo reale: l'animazione parte da sola perché Turbo
   inserisce un nodo nuovo. */
@keyframes req-card-fresh {
  from { background: var(--sb-accent-tint); box-shadow: 0 0 0 2px var(--sb-green); }
  to   { background: var(--c-white); box-shadow: 0 0 0 0 transparent; }
}
.req-card--fresh { animation: req-card-fresh 1.6s ease-out 1; }
.req-card__head {
  display: flex; justify-content: space-between; align-items: flex-start; gap: 8px;
  margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--sb-line, var(--c-azure-100));
}
.req-card__ident { display: flex; flex-direction: column; gap: 2px; }
.req-card__room { font-weight: 700; color: var(--c-blue-900); }
.req-card__guest { font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--c-ink-soft); }
.req-card__badges { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; }
.req-badge { padding: 2px 8px; border-radius: var(--radius-sm); font-size: 0.68rem; font-weight: 800; letter-spacing: 0.3px; }
.req-badge--lang { background: var(--c-azure-100); color: var(--c-blue-700); }
.req-badge--dep { background: #eef1f4; color: var(--c-ink-soft); text-transform: capitalize; }
.req-badge--ai { background: #efe6ff; color: #6b3fd0; }
.req-card__subject { margin: 0 0 2px; font-weight: 700; color: var(--c-ink); }
.req-card__body {
  margin: 0 0 4px; color: var(--c-ink); line-height: 1.4;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 4; line-clamp: 4; overflow: hidden;
}
.req-card__body--expanded { -webkit-line-clamp: unset; line-clamp: unset; overflow: visible; }
.req-card__more {
  margin: 0 0 8px; padding: 0; background: none; border: none; cursor: pointer;
  font: inherit; font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--sb-accent);
}
.req-card__more:hover { text-decoration: underline; }
.req-card__pricing { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; margin: 0 0 8px; text-align: right; }
.req-card__people,
.req-card__price { font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--c-blue-700); }
.req-card__price { color: var(--sb-warn-fg, #92590F); }
.req-card__time { display: flex; align-items: center; gap: var(--sb-sp-1); margin: 0 0 6px; font-size: var(--sb-fs-body-sm); color: var(--c-blue-700); font-weight: 600; }
.req-card__meta { margin: 0 0 10px; font-size: var(--sb-fs-meta); color: var(--c-ink-soft); }
.req-card__actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; align-items: center; }
.req-card__actions form { margin: 0; }
/* Annulla e Note restano a sinistra, le azioni principali a destra. Lo spazio
   elastico sta su Note perché è l'ultimo elemento del gruppo di sinistra ed è
   SEMPRE presente: l'annulla sparisce sulle richieste già annullate (§E20). */
.req-card__actions .req-card__notes { margin-right: auto; }
/* Annulla discreto: solo icona cestino, diventa rosso all'hover. Bordo, raggio e
   altezza sono quelli dei .btn--ghost che gli stanno accanto (§E20): senza, fra
   due pulsanti bordati sembrava un elemento non finito. Resta neutro a riposo —
   è un'azione distruttiva, ma secondaria: il rosso arriva quando ci si avvicina. */
.req-card__cancel-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--sb-ctl); height: var(--sb-ctl); padding: 0;
  border: 1px solid var(--sb-line-ctl); border-radius: var(--sb-r-btn);
  background: #fff; color: var(--c-ink-soft); cursor: pointer;
}
.req-card__cancel-btn:hover {
  background: #fdecec; border-color: #F1C7C7; color: var(--sb-danger, #C62828);
}

/* The filter card above a list. The request history and the planning had a
   family of their own (`reqh-filter*`, `plan-filter*`) with the same rules,
   written twice; both now use `mfilter*`, whose row is defined further down. */
.mfilter-card { background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); box-shadow: var(--sb-shadow); padding: 16px 20px; margin: 0 0 20px; }
/* Campi e pulsanti di una card filtri hanno la STESSA altezza: un "Azzera" più
   basso del campo che azzera si legge come un elemento di seconda classe.
   Il selettore a due classi batte l'altezza di .btn/.btn--sm dei layer tema. */
/* Under `.mng-body` and on --sb-ctl: at two classes these rules lost to
   `.mng-body .form__input` and `.mng-body .btn--sm`, so a card showed a 36px
   field, a 28px "Azzera filtri" and a 44px checkbox on one line. */
.mng-body .mfilter :is(.form__input, .btn) {
  height: var(--sb-ctl);
  min-height: var(--sb-ctl);
}
.reqh-day { background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); box-shadow: var(--sb-shadow); padding: 18px 20px; margin: 0 0 20px; }
.reqh-day__title { margin: 0 0 14px; font-size: 1.05rem; color: var(--c-blue-900); font-family: var(--sb-display); text-transform: capitalize; }
/* Una RIGA per camera: etichetta a sinistra, card della giornata affiancate a
   destra (vanno a capo quando la riga è piena). */
.reqh-room { display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: 16px; align-items: start; padding: 14px 0; border-top: 1px solid var(--sb-line-2); }
.reqh-room:first-of-type { border-top: 0; padding-top: 0; }
.reqh-room:last-child { padding-bottom: 0; }
.reqh-room__title { margin: 0; padding-top: 6px; font-size: 0.95rem; font-weight: 800; color: var(--c-ink); overflow-wrap: anywhere; }
.reqh-cards { display: flex; flex-wrap: wrap; gap: 12px; }

/* Card quadrata: il bordo colorato è lo stato (verde evasa / rosso annullata).
   L'altezza è fissata dall'aspect-ratio, quindi ogni blocco di testo va troncato
   o il contenuto sborderebbe. */
.reqh-card {
  width: 200px; aspect-ratio: 1 / 1; display: flex; flex-direction: column; gap: 6px;
  padding: 12px; border-radius: 14px; background: #fff;
  border: 2px solid var(--sb-line); box-shadow: 0 4px 14px rgba(20, 94, 168, 0.07);
  overflow: hidden;
}
.reqh-card--done { border-color: var(--sb-green); }
/* Grey like its badge (request_status_badge): a cancelled request is closed,
   not an error. */
.reqh-card--cancelled { border-color: var(--sb-line-ctl); }
.reqh-card__head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.reqh-card__time { font-size: 0.9rem; font-weight: 800; color: var(--c-ink); }
.reqh-card__head > .msvc-badge { flex: none; }
.reqh-card__name { margin: 0; font-size: 0.82rem; font-weight: 700; color: var(--c-ink); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
.reqh-card--cancelled .reqh-card__name { text-decoration: line-through; color: var(--c-ink-soft); }
.reqh-card__text { margin: 0; flex: 1; min-height: 0; font-size: 0.78rem; color: var(--c-ink-soft); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden; }
.reqh-card__foot { margin-top: auto; padding-top: 8px; border-top: 1px solid var(--sb-line-2); display: flex; flex-direction: column; gap: 2px; }
.reqh-card__guest { font-size: 0.75rem; font-weight: 700; color: var(--sb-primary, #178bc3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.reqh-card__guest--none { font-weight: 600; color: var(--c-ink-soft); }
.reqh-card__staff { font-size: 0.7rem; font-weight: 600; color: var(--c-ink-soft); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }
/* Il pulsante note si stacca dalle due righe di testo del piede (§E20). */
.reqh-card__notes { align-self: flex-end; margin-top: 6px; }

/* Sotto i 720px l'etichetta della camera passa sopra le card. */
@media (max-width: 720px) {
  .reqh-room { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .reqh-room__title { padding-top: 0; }
}

/* Planning soggiorni (§E5): camere in riga, giorni in colonna, una barra per
   soggiorno. Un solo contenitore scorre entrambi gli assi — `overflow-x: auto`
   calcola comunque `overflow-y: auto`, quindi la testata dei giorni può essere
   sticky solo rispetto a questo elemento, che va limitato in altezza. */
/* `margin-bottom` come una .panel__section: la card del reticolo ne è una a
   tutti gli effetti, e senza il margine si incollava alla sezione successiva —
   l'elenco dei soggiorni senza date sul planning, la modifica in blocco sulla
   griglia prezzi. */
.plan { background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); box-shadow: var(--sb-shadow); padding: 12px; margin: 0 0 var(--sb-sp-5); }
.plan__scroll {
  --plan-day-w: 40px; --plan-room-w: 110px;
  position: relative; overflow: auto; max-height: calc(100vh - 300px);
}
.plan__head {
  display: grid; grid-template-columns: var(--plan-room-w) repeat(var(--plan-days), var(--plan-day-w));
  position: sticky; top: 0; z-index: 3; background: #fff; border-bottom: 1px solid var(--sb-line);
}
.plan__corner {
  position: sticky; left: 0; z-index: 4; background: #fff;
  display: flex; align-items: flex-end; padding: 4px 8px 8px;
  font-size: 0.72rem; font-weight: 700; color: var(--c-ink-soft);
}
.plan__day {
  display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 6px 0 8px;
  font-size: 0.7rem; color: var(--c-ink-soft); border-left: 1px solid var(--sb-line-2);
}
.plan__day--weekend { background: var(--sb-bg-2); }
.plan__day--today { color: var(--sb-warn-fg); font-weight: 800; }
.plan__day__dow { text-transform: uppercase; letter-spacing: 0.02em; }
.plan__day__num { font-size: 0.85rem; font-weight: 700; color: var(--c-ink); }
.plan__day--today .plan__day__num { color: var(--sb-warn-fg); }

/* La riga ha la stessa larghezza totale della testata: le colonne si allineano
   senza subgrid. */
.plan__row {
  display: grid; grid-template-columns: var(--plan-room-w) calc(var(--plan-days) * var(--plan-day-w));
  width: calc(var(--plan-room-w) + var(--plan-days) * var(--plan-day-w));
  border-bottom: 1px solid var(--sb-line-2);
}
.plan__room {
  position: sticky; left: 0; z-index: 2; background: #fff;
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  padding: 6px 8px;
  font-weight: 700; font-size: 0.85rem; color: var(--c-ink); overflow-wrap: anywhere;
}
/* §E45.29 — la tipologia sotto il numero di camera. Maiuscoletto spaziato
   perché --sb-fs-micro lo RICHIEDE: a quel corpo, in minuscolo, non si legge.
   La colonna resta un flex con un figlio solo dove la tipologia non c'è (e
   nella riga "occupazione"), quindi non cambia niente là. */
.plan__room__type {
  font-size: var(--sb-fs-micro); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--sb-muted);
}
.plan__lanes { display: flex; flex-direction: column; gap: 5px; padding: 7px 0; min-width: 0; }
/* Le linee dei giorni sono un gradiente, non celle: 30 camere × 92 giorni
   sarebbero quasi 3.000 nodi vuoti. */
.plan__lane {
  display: grid; grid-template-columns: repeat(var(--plan-days), var(--plan-day-w));
  height: 38px;
  background-image: repeating-linear-gradient(to right,
    transparent 0 calc(var(--plan-day-w) - 1px),
    var(--sb-line-2) calc(var(--plan-day-w) - 1px) var(--plan-day-w));
}

/* Barra e tessera della legenda condividono i colori: due elenchi di selettori
   invece di due palette che prima o poi divergono. */
.plan__bar, .plan__key__swatch {
  background: var(--sb-chip); color: var(--sb-primary-2);
  border-left: 3px solid var(--sb-primary);
}
/* The columns arrive as two variables from the view (§6). */
.plan__bar {
  grid-column: var(--col-start) / var(--col-end);
  display: flex; align-items: center; min-width: 0; height: 100%;
  padding: 0 8px; border-radius: 8px; text-decoration: none;
  font-size: 0.78rem; font-weight: 700;
}
.plan__bar:hover { filter: brightness(0.97); }
.plan__bar__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Mai testo bianco su --sb-green (2.8:1): tinta chiara + testo scuro. */
.plan__bar--checked_in, .plan__key__swatch--checked_in { background: var(--sb-chip-line); color: var(--sb-green-fg); border-left-color: var(--sb-green); }
.plan__bar--checked_out, .plan__key__swatch--checked_out { background: var(--sb-bg-2); color: var(--sb-muted); border-left-color: var(--sb-line); }
.plan__bar--overdue, .plan__key__swatch--overdue { background: var(--sb-warn-bg); color: var(--sb-warn-fg); border-left-color: var(--sb-accent); }
/* §E45.2 — An option holds the room only until its deadline, an inquiry does not
   hold it at all: both are drawn hollow, so a sold night stays the only solid
   bar on the grid. Dashed for the option, faded for the inquiry — the difference
   has to survive being read at a glance, and colour alone never carries state
   here (§5 of the view conventions). */
.plan__bar--option, .plan__key__swatch--option { background: #fff; color: var(--sb-primary-2); border: 1px dashed var(--sb-primary-300); border-left: 3px dashed var(--sb-primary); }
/* §E45.3 — Room out of service: hatched, and not a colour of its own. It is not
   a state of a booking — there is no guest and nothing to open — so it must not
   read as one more shade in the same scale as sold, option and inquiry. */
.plan__bar--blocked, .plan__key__swatch--blocked {
  color: var(--sb-muted); border: 1px solid var(--sb-line); border-left: 3px solid var(--sb-muted-2);
  background: repeating-linear-gradient(135deg, var(--sb-bg-2) 0 5px, var(--sb-chip) 5px 10px);
}
.plan__bar--inquiry, .plan__key__swatch--inquiry { background: var(--sb-bg-2); color: var(--sb-muted); border: 1px dotted var(--sb-line); border-left: 3px dotted var(--sb-muted-2); }
/* §E45.3.2 — Annullata: le notti sono tornate in vendita, quindi la striscia di
   occupazione sopra dà la camera LIBERA e una barra piena la smentiva. Non
   sparisce — il planning è anche storia, e «mi ricordavo una prenotazione qui»
   deve avere risposta — ma si tira indietro. Il segno che la distingue è il nome
   BARRATO, non una tinta: tratteggiato è già l'opzione, puntinato la richiesta, e
   il colore da solo non porta mai lo stato qui. Un NO-SHOW resta pieno: quella
   notte è stata venduta. */
.plan__bar--cancelled, .plan__key__swatch--cancelled {
  background: transparent; color: var(--sb-muted-2);
  border: 1px solid var(--sb-line-2); border-left: 3px solid var(--sb-line);
}
.plan__bar--cancelled .plan__bar__name { text-decoration: line-through; }

/* Legenda dei colori: sopra la griglia, allineata a destra. */
.plan__key { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 16px; margin: 0 2px 10px; font-size: 0.78rem; color: var(--c-ink-soft); }
.plan__key__item { display: inline-flex; align-items: center; gap: 6px; }
.plan__key__swatch { width: 18px; height: 14px; border-radius: 5px; flex: none; }
/* Tagliata dal bordo del periodo (il soggiorno continua fuori) vs aperta perché
   la data non è indicata: due fatti diversi, due segni diversi. */
.plan__bar--clip-start { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-style: double; }
.plan__bar--clip-end { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.plan__bar--open-start { border-left-style: dashed; }
.plan__bar--open-end { border-right: 2px dashed currentColor; }

/* Colonna di oggi: un solo overlay assoluto, non un marcatore per riga. */
.plan__today {
  position: absolute; top: 0; bottom: 0; pointer-events: none; z-index: 1;
  left: calc(var(--plan-room-w) + var(--plan-day-w) * var(--plan-today));
  width: var(--plan-day-w);
  background: rgba(242, 140, 0, 0.10);
  border-inline: 1px solid var(--sb-accent);
}
.plan__legend { margin: 10px 2px 0; font-size: 0.8rem; color: var(--c-ink-soft); }

/* §E45.1 — Occupancy strip: one cell per day, and here the cells are affordable.
   On the rooms grid they would be 30 × 92 empty nodes (see .plan__lane, where the
   day lines are a gradient for exactly that reason); on ONE row they are at most
   92, and they carry each day's count and `title`. */
.plan__occupancy {
  display: grid; grid-template-columns: var(--plan-room-w) repeat(var(--plan-days), var(--plan-day-w));
  width: calc(var(--plan-room-w) + var(--plan-days) * var(--plan-day-w));
  border-bottom: 1px solid var(--sb-line);
}
.plan__cell {
  display: flex; align-items: center; justify-content: center;
  padding: var(--sb-sp-1) 0;
  font-size: var(--sb-fs-meta); font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--sb-ink); border-left: 1px solid var(--sb-line-2);
}
/* No room taken: dimmed, so the eye falls on what is full. The 400 here cancels
   the 600 of the rule above, which is its only permitted use. */
.plan__cell--free { color: var(--sb-muted-2); font-weight: 400; }
/* Sold out: green, like a stay in house. For a property "full" is the good news,
   not a warning — which is why this is not `--sb-warn-*`. */
.plan__cell--full { background: var(--sb-chip-line); color: var(--sb-green-fg); font-weight: 700; }
/* §E45.4b — The price grid reuses the planning's reticle: one container of cells
   per room row, aligned to the same day columns. Here the cells CARRY a number,
   so they are not the empty nodes .plan__lane avoids — but they are why this page
   caps its window at two months instead of three. */
.plan__cells {
  display: grid; grid-template-columns: repeat(var(--plan-days), var(--plan-day-w));
  height: 34px;
}
/* No rate: not a zero and not a closure — a hole in the configuration, and the
   only cell state worth pulling the eye. */
.plan__cell--missing { color: var(--sb-danger); font-weight: 700; }
/* Minimum nights or a closed-to flag on the rate that won: an underline, because
   the price is still the thing to read. */
.plan__cell--restricted { text-decoration: underline dotted var(--sb-accent-strong); text-underline-offset: 3px; }

/* §E45.3 — Closed: the property is out of season, so there is no number to show.
   Hatched and not merely grey, because "closed" and "empty" are opposite
   answers to the same question and colour alone never carries state here. */
.plan__cell--closed {
  color: var(--sb-muted-2); font-weight: 400;
  background: repeating-linear-gradient(135deg, transparent 0 4px, var(--sb-line-2) 4px 8px);
}

/* §E45.9/ADR-0066 — Il planning in miniatura, dentro la card di un conflitto.
   Riusa il reticolo invece di rifarne uno: le due parti di un conflitto sono
   notti occupate su una camera, cioè esattamente quello che `.plan__bar` sa già
   disegnare, e una seconda palette per la stessa cosa divergerebbe. Qui però
   non c'è niente da impilare (una barra per riga, nessun packing su corsie) e
   non c'è niente da scorrere in verticale: cade la card, cade il tetto di
   altezza, e le colonne si stringono perché il riquadro vive dentro un'altra
   card. */
.plan--compact { background: none; border: 0; border-radius: 0; box-shadow: none; padding: 0; margin: 0; }
.plan--compact .plan__scroll { --plan-day-w: 30px; --plan-room-w: 88px; max-height: none; }
.plan--compact .plan__head { position: static; }
.plan--compact .plan__row:last-child { border-bottom: 0; }
/* La corsia sta dentro la riga senza `.plan__lanes`: quel livello esiste per
   impacchettare più soggiorni sulla stessa camera, e qui le righe sono una per
   parte. Restano i 4px di aria che il padding di `.plan__lanes` dava. */
.plan--compact .plan__lane { height: 26px; margin: 4px 0; }

/* Il CANALE: la parte che non possiamo toccare, e va distinta da una nostra
   prenotazione a colpo d'occhio. Accento caldo, come ciò che chiede attenzione
   altrove — e mai il solo colore a portarlo: la barra ha il suo nome dentro e
   il `title`, e sotto al disegno l'elenco ripete tutto a parole. */
.plan__bar--channel {
  background: var(--sb-warn-bg); color: var(--sb-warn-fg); border-left: 3px solid var(--sb-accent);
}

@media (max-width: 720px) {
  .plan__scroll { --plan-day-w: 28px; --plan-room-w: 64px; max-height: none; }
  .plan__bar__name { display: none; }
  /* A 64px la tipologia non ci sta: resta nel `title` della cella. */
  .plan__room__type { display: none; }
}

@media (min-width: 720px) {
  .board-counters { grid-template-columns: repeat(4, 1fr); }
  .board { grid-template-columns: repeat(3, 1fr); align-items: start; }
}

/* ==========================================================================
   §E5 — CRUD gestione (Manage::)
   ========================================================================== */

.form__row--check { display: flex; align-items: center; gap: 10px; }
.form__row--check .form__label { margin: 0; }
/* Whatever Stimulus reveals needs its own [hidden] rule: `display: flex` above is
   an author declaration and beats the user-agent one, so without this the comment
   checkbox of §E29.5 would show on every question kind, including the ones where
   the model then clears it. Same lesson as .billing-banner[hidden] and
   .mng-nav__badge[hidden]. */
.form__row--check[hidden] { display: none; }
/* §E58 — same lesson one line down: `.form__grid` is `display: grid`, and the
   request deadline block is revealed by the `nested-form` controller. Without
   this it would be on screen for every service, JavaScript or not. */
.form__grid[hidden] { display: none; }

.chip--danger { background: #fdecec; border-color: #f3c9c9; color: var(--sb-danger, #C62828); }
.chip--danger:hover { background: #f9dada; }


/* §E45.5b, rinominata in §E57.1 — The DETAIL SHEET on two columns: what the page
   is about on the left, what is looked up beside it on the right. Born on the
   booking sheet (bill left, extras form right: you add a breakfast while looking
   at what has already been sold, which used to mean scrolling up and down) and
   now shared with the stay sheet, so the names lost the `mbook` prefix.

   The threshold is 1280 and not 1100 because of what the left column carries in
   both pages: a table of four or five columns, which below that width becomes
   unreadable before the split becomes convenient. Stacked, the left column comes
   first. */
/* Detached blocks: the sheet is not a stack of bands separated by a hairline,
   so each piece gets room around it. */
.sheet > .panel__section, .sheet > .sheet-cols { margin-bottom: var(--sb-sp-5); }
.sheet > :last-child { margin-bottom: 0; }

.sheet-cols { display: grid; grid-template-columns: 1fr; gap: var(--sb-sp-5); align-items: start; }
.sheet-cols__main { min-width: 0; display: flex; flex-direction: column; gap: var(--sb-sp-4); }
/* Same column as its twin: each side carries MORE THAN ONE card, and
   `.panel__section` has no margin of its own, so without this they touch and
   read as one. */
.sheet-cols__aside { min-width: 0; display: flex; flex-direction: column; gap: var(--sb-sp-4); }
@media (min-width: 1280px) {
  .sheet-cols { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); }
}
/* The aside used to be sticky, to keep the extras form in view while a long bill
   scrolled. It no longer is, and the reason is worth keeping: it now carries TWO
   cards, so it is regularly taller than the window — and a sticky box taller
   than the window pins with its own lower half, the submit included, out of
   reach. Capping it with `overflow-y: auto` is worse, not better: the scroll box
   becomes the anchor for every `position: sticky` inside it, so the two
   `.form__actions` bars would park at the bottom of the RAIL instead of the
   window, floating over the card below — the very thing that made the
   rectification button appear above its own fields. */

/* §E45.24 — "Il giorno" on two columns: on the left who arrives, who leaves and
   who stays — three lists read together and the bulk of the page; on the right
   what there is to DO today, which is short and carries a clock.
   The split only happens when there IS something to do: with no tasks the three
   lists take the whole width rather than leaving a column of air. Below 1100px
   it stacks, and there the markup order rules — the clock first, the roll call
   after. */
/* §E45.24 — Una cosa fatta resta leggibile ma smette di chiedere attenzione:
   scende in fondo alla lista (lo fa DaySheet) e qui si smorza. Non barrata: il
   testo dice ancora che cosa portare a chi, e serve fino a fine servizio. */
.mstay-row--done .mstay-row__main { opacity: .55; }

.mstay-day { display: grid; grid-template-columns: 1fr; gap: var(--sb-sp-5); align-items: start; }
.mstay-day__people { display: flex; flex-direction: column; gap: var(--sb-sp-5); min-width: 0; }
@media (min-width: 1100px) {
  .mstay-day:has(.mstay-day__tasks) { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }
  .mstay-day:has(.mstay-day__tasks) .mstay-day__people { grid-column: 1; grid-row: 1; }
  .mstay-day__tasks { grid-column: 2; grid-row: 1; }
}
.mstay-day .panel__section { margin: 0; }

/* §E47 — Dashboard: colonna sinistra (coda richieste) + rail destro (AI e
   confronto). 1.7fr/1fr come da design; sotto i 1100px si impila, perché una
   tabella a cinque colonne e una card affiancate non ci stanno. */
.dash-body { display: grid; grid-template-columns: 1fr; gap: var(--sb-sp-5); align-items: start; }
@media (min-width: 1100px) {
  .dash-body { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }
}
.dash-rail { display: flex; flex-direction: column; gap: var(--sb-sp-5); min-width: 0; }
.dash-rail .panel__section { margin: 0; }
/* Where sections sit SIDE BY SIDE as blocks on the grey — rather than stacked as
   bands of one page — they close on all four sides. With the bottom hairline
   alone they read as cut in half. Three layouts do this: the dashboard, the day
   sheet, any two-column form and the translation editor. */
.dash-body > .panel__section,
.dash-rail > .panel__section,
.mstay-day .panel__section,
/* The day sheet's blocks that live OUTSIDE `.mstay-day`: the overdue check-outs
   above it, the Alloggiati file below it. Without this they were the only flat
   bands on a page of cards. */
.panel__section.mstay-card,
.form__cards-col > .panel__section,
/* A section standing on its own in a form, beside the `.form__cards` grid rather
   than inside it — a block that needs the full width, like the booking's room
   rows. Same treatment, or it would read as a band cut out of a page of cards. */
.form > .panel__section,
.tr-layout__form > .panel__section,
.sheet-cols__main > .panel__section,
.sheet-cols__aside > .panel__section,
.sheet > .panel__section {
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-card);
  /* A card is its own container: the left column takes its gutter from here. */
  --sb-section-gutter: var(--sb-sp-5);
}
/* Precisazione sotto l'etichetta di una tile («2 oltre 30 min»). Prende il
   colore del tono, che su una tile in ritardo è l'ambra. */
.kpi__note { margin: 2px 0 0; font-size: var(--sb-fs-meta); font-weight: 600; color: var(--kpi-color); }

/* The request queue. The narrow columns take exactly what they need and the
   request text takes ALL the rest: before, the columns shared the width evenly,
   so "Camera" — three digits — took a third of the table while the request went
   to ellipsis after four words.
   `width: 1%` on a <td> in table-layout auto means "the least you need"; the
   100% on the other one then claims what is left. */
.dash-queue .data-table th,
.dash-queue .data-table td { width: 1%; }
.dash-queue .data-table th.dqueue__text-col,
.dash-queue .dqueue__text { width: 100%; }
/* `max-width: 0` is the idiom that makes text-overflow work inside a <td>. */
.dqueue__text { max-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dqueue__late { font-weight: 700; color: var(--sb-warn-fg); }
.dqueue__who { display: block; font-size: var(--sb-fs-meta); }
/* A late row is NOT tinted. The state is the word — "in attesa 5h 26m", in
   amber, in the column that says so — and a solid fill on every row took the
   table's two levels away from it: it erased the hairline between rows, hid the
   hover, and made an ordinary queue look like a fault. The solid fill stays with
   the service bands. */

/* Card del rail: metrica grande, righe di fatti, link in fondo. */
.dash-card__metric { margin: 0; font-size: var(--sb-fs-metric); }
.dash-card__facts { margin: var(--sb-sp-4) 0 0; display: flex; flex-direction: column; gap: var(--sb-sp-2); }
.dash-card__facts > div { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sb-sp-3); }
.dash-card__facts dt { color: var(--sb-muted); font-size: var(--sb-fs-body-sm); }
.dash-card__facts dd { margin: 0; font-weight: 600; }
/* The card's footer link was at --sb-fs-label (0.66rem): that size is for
   small-caps mono labels, not for something you are meant to click. */
/* The panel's link colour, the one a link in a note has: without it the card
   footer fell back on the browser's blue, the only one left on the dashboard. */
.dash-card__link { display: inline-block; margin-top: var(--sb-sp-4); font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--sb-accent); text-underline-offset: 2px; }

/* Confronto settimanale: etichetta a sinistra, delta a destra. */
.dash-compare { margin: 0; display: flex; flex-direction: column; gap: var(--sb-sp-2); }
.dash-compare__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sb-sp-3); }
.dash-compare__row dd { margin: 0; }
.dash-compare__label { color: var(--sb-muted); font-size: var(--sb-fs-body-sm); }
/* Un delta si legge per DIREZIONE e non per segno: un tempo di risposta che
   sale è un «+» che peggiora, e va ambra. Ambra e non rosso: una settimana più
   lenta è un segnale, non un guasto. Il segno resta testo, quindi il senso non
   dipende dal colore. */
.ddelta { font-weight: 700; font-size: var(--sb-fs-body-sm); }
.ddelta--good { color: var(--sb-green-fg); }
.ddelta--bad { color: var(--sb-warn-fg); }
.ddelta--flat, .ddelta--none { color: var(--sb-muted); font-weight: 600; }

/* Liste servizi / documenti / stay */
/* Lista servizi: categoria come banda + servizi come griglia di card. */
.msvc-cat-card { background: #fff; border: 1px solid var(--sb-line); border-radius: 16px; box-shadow: var(--sb-shadow); overflow: hidden; margin: 0 0 20px; }
.msvc-cat { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 13px 18px; background: var(--sb-chip); border-bottom: 1px solid var(--sb-line); }
.msvc-cat__title { display: flex; align-items: center; gap: 10px; min-width: 0; }
.msvc-cat__icon { display: inline-flex; width: 34px; height: 34px; border-radius: 9px; background: var(--sb-accent); color: #fff; align-items: center; justify-content: center; flex: none; }
.msvc-cat__name { margin: 0; font-size: 1.1rem; color: var(--c-blue-900); font-family: var(--sb-display); }
.msvc-cat__count { font-size: 0.8rem; font-weight: 600; color: var(--c-ink-soft); }
.msvc-cat__actions { display: flex; gap: 8px; flex-wrap: wrap; }
.msvc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 12px; padding: 16px 18px; }
.msvc-card { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); padding: 14px; background: #fff; }
.msvc-card:hover { border-color: var(--sb-accent); box-shadow: var(--sb-shadow); }
.msvc-card__name { margin: 0; font-weight: 700; color: var(--c-ink); font-size: 0.95rem; }
.msvc-card__link { color: inherit; text-decoration: none; }
.msvc-card__link:hover, .msvc-card__link:focus-visible { text-decoration: underline; text-underline-offset: 2px; }
.msvc-card__price { margin: 0; font-weight: 700; color: var(--sb-accent); font-size: 0.9rem; }
.msvc-card__badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.msvc-card__actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--sb-line); }
.msvc-cat__actions form, .msvc-card__actions form { margin: 0; }

/* Pulsante-icona per azioni compatte (traduzioni / modifica / elimina). */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 32px; height: 32px; padding: 0; border: 1px solid var(--sb-line-ctl); border-radius: var(--sb-r-btn);
  background: #fff; color: var(--c-ink-soft); cursor: pointer; text-decoration: none;
}
.icon-btn:hover { border-color: var(--sb-accent); color: var(--sb-accent); }
/* Red at rest, not only under the mouse: on a touch screen it never turned red,
   and at rest "delete" looked like the pencil beside it. */
.icon-btn--danger { color: var(--sb-danger, #C62828); }
.icon-btn--danger:hover { border-color: var(--sb-danger, #C62828); color: var(--sb-danger, #C62828); background: #fdecec; }
.icon-btn--warn { border-color: #f0dcb8; color: var(--sb-warn-fg, #92590F); background: var(--sb-warn-bg, #FDF0DC); }
.icon-btn--warn:hover { border-color: #e6c98a; }
.msvc-list, .mstay-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.msvc-row, .mstay-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-radius: var(--sb-r-card); background: var(--c-azure-50);
  border: 1px solid var(--c-azure-100);
}
.msvc-row__main, .mstay-row__main { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.msvc-row__name, .mstay-row__room { font-weight: 700; color: var(--c-ink); }
/* `align-items: center`: senza, un badge accanto a due pulsanti si stira e il
   suo testo resta in alto, mezzo centimetro sopra le etichette dei pulsanti. */
.msvc-row__actions { display: flex; align-items: center; gap: 6px; }
.msvc-empty { color: var(--c-ink-soft); margin: 0; padding: 16px 18px; font-size: var(--sb-fs-body-sm); }
.msvc-thumb { border-radius: 10px; margin: 6px 0; display: block; }
.mstay-row__guest { color: var(--c-ink); }
.mstay-row__period { color: var(--c-ink-soft); font-size: var(--sb-fs-body-sm); }
/* §E45.9/ADR-0066 — Un conflitto non è una riga ma un blocco: intestazione,
   timeline, e una riga per parte. `justify-content: space-between` della riga
   normale spingerebbe la timeline contro il bordo, e `align-items: center` la
   centrerebbe lasciando le righe di testo corte in mezzo al vuoto. */
.mstay-row--stacked { flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 6px; }
.mstay-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* The same air between cards as the `.mstay-day` grid puts between its own. The
   compound selector is what beats `.panel__section { margin: 0 }`. */
.panel__section.mstay-card { margin: var(--sb-sp-5) 0; }
/* §E45.35 — a checkbox that picks a row: box and room number are ONE target, and
   the number keeps the look it has in every other row of the sheet. */
.mstay-row__pick { display: inline-flex; align-items: center; gap: var(--sb-sp-3); cursor: pointer; }
.mstay-row__pick input { margin: 0; }
/* The bulk action under a list closes the card, at the end where the eye lands
   after the last row (why not `.form__actions`: see _overdue_checkouts). */
.mstay-bulk { display: flex; justify-content: flex-end; margin-top: var(--sb-sp-4); }

/* Riordino drag & drop (Sortable.js) di categorie e servizi. Le maniglie hanno
   classi diverse perché le liste sono annidate: una classe sola farebbe partire
   il drag della categoria dalla maniglia di una card. */
.msvc-cat__drag, .msvc-card__drag {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  color: #999; cursor: grab; touch-action: none; user-select: none;
}
.msvc-cat__drag { width: 24px; height: 24px; }
/* Sulla card servizio la maniglia sta accanto al titolo: più piccola, non deve
   competere con il nome. */
.msvc-card__drag { width: 18px; height: 18px; }
/* I sei punti a stroke 2 sparivano del tutto: ingrossati restano leggibili
   anche in grigio chiaro e senza cornice. */
.msvc-cat__drag .icon, .msvc-card__drag .icon { stroke-width: 3.4; }
.msvc-cat__drag:hover, .msvc-card__drag:hover { color: var(--sb-accent); }
.msvc-cat__drag:active, .msvc-card__drag:active { cursor: grabbing; color: var(--sb-accent); }
.msvc-card__head { display: flex; align-items: flex-start; gap: 6px; }
.msvc-card__head .msvc-card__name { flex: 1 1 auto; min-width: 0; }

/* Segnaposto lasciato dall'elemento in volo. */
.msvc-cat-card.sortable-ghost, .msvc-card.sortable-ghost {
  background: var(--sb-chip); border: 1px dashed var(--sb-accent); box-shadow: none;
}
.msvc-cat-card.sortable-ghost > *, .msvc-card.sortable-ghost > * { visibility: hidden; }
.sortable-chosen { cursor: grabbing; }
.msvc-cat-card.sortable-drag, .msvc-card.sortable-drag { box-shadow: 0 12px 28px rgba(11, 58, 102, 0.22); }

/* Una categoria senza servizi deve restare un bersaglio di drop: la griglia
   c'è sempre, con il segnaposto dentro. */
.msvc-grid { min-height: 56px; }
.msvc-grid > .msvc-empty { grid-column: 1 / -1; padding: 6px 0; }

.msvc-badge { padding: 2px 9px; border-radius: var(--radius-sm); font-size: 0.7rem; font-weight: 800; }

/* §E29.5 — the guest's comment under a score, INSIDE the answer cell. Not
   `.dtable__note`, which is the note of the whole table and carries a max-width
   of its own. */
.msvc-answer-note {
  margin: 4px 0 0; font-size: var(--sb-fs-meta); color: var(--sb-muted);
  overflow-wrap: anywhere;
}
.msvc-badge--on { background: var(--sb-accent-tint); color: var(--c-ok); }
/* A light fill and a hairline instead of #eef1f4: grey on that fill was 4.26:1,
   on --sb-bg it is 4.67. */
.msvc-badge--off { background: var(--sb-bg); box-shadow: inset 0 0 0 1px var(--sb-line); color: var(--sb-muted); }
.msvc-badge--ext { background: #efe6ff; color: #6b3fd0; }
/* A category with no state of its own — a role, the department a user sees,
   the service a message talks about, "predefined". Every other modifier says
   something (on, off, external, for the AI): borrowing one of them for a label
   made "Reception" read as switched off. Ink on the service grey: 16.32:1. */
.msvc-badge--neutral { background: var(--sb-bg-2); color: var(--sb-ink); }
/* Indicizzato: c'è, ma solo per l'assistente — né il verde del pubblicato né il
   grigio della bozza. */
.msvc-badge--ai { background: rgba(23,139,195,0.14); color: #0f6b98; }

/* §E5 — The service sheet (manage/services/show), presentation card.
   The photo runs to the side edges of the card, like a table inside a section
   (`.table-card` above): an inset frame inside a framed card would be the card
   in a card the direction forbids. Only the INLINE margins are set, because
   the block ones belong to `--flow` and a `margin` shorthand here, being later
   at the same weight, would wipe its spacing out. */
.svc-show__media {
  position: relative;
  margin-inline: calc(var(--sb-sp-5) * -1);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--sb-bg-2);
}
.svc-show__img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Where the picture comes from, written ON it: the gallery photo is not the
   property's own, and from the image alone nobody can tell. Ink at 80% keeps
   white text above 4.5:1 whatever the photo is under it. */
.svc-show__caption {
  position: absolute; left: var(--sb-sp-3); bottom: var(--sb-sp-3);
  padding: 2px var(--sb-sp-2); border-radius: var(--sb-r-pill);
  background: rgba(20, 24, 28, 0.8); color: #fff;
  font-size: var(--sb-fs-meta); font-weight: 600;
}
/* No photo: a short band that says so and where to add one, not a 16:9 hole. */
.svc-show__media--empty {
  aspect-ratio: auto;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sb-sp-2);
  padding: var(--sb-sp-5);
  background: var(--sb-bg);
  border-block: 1px solid var(--sb-line-2);
  color: var(--sb-muted);
  text-align: center;
}
.svc-show__media--empty .svc-show__caption {
  position: static; padding: 0; background: none; color: var(--sb-muted);
  font-size: var(--sb-fs-body-sm); font-weight: 400;
}
/* The price the guest reads, at the size of a figure: it is the one number on
   the page. In Archivo like every amount — mono is for codes. */
.svc-show__price {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--sb-sp-2);
  font-size: var(--sb-fs-metric); font-weight: 700; color: var(--sb-ink); font-variant-numeric: tabular-nums;
}
.svc-show__unit { font-family: var(--sb-sans); font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--sb-muted); }
.svc-show .rich-text {
  max-width: 68ch;
  font-family: var(--sb-prose); font-size: var(--sb-fs-body); line-height: 1.6; color: var(--sb-ink);
}
.svc-show__rooms { display: flex; flex-wrap: wrap; gap: var(--sb-sp-2); padding: 0; list-style: none; }

/* A label and its state, one per row: the publication switches and the guest
   languages on the service sheet. A <dl> like `.panel__facts`, but in rows and
   not in a grid: here the eye runs DOWN a column of yes and no, and a grid would
   make it zig-zag. The state is the badge's word, never its colour alone. */
.state-list { margin: 0; }
.state-list__row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sb-sp-3);
  padding: var(--sb-sp-2) 0;
  border-bottom: 1px solid var(--sb-line-2);
}
.state-list__row:first-child { padding-top: 0; }
.state-list__row:last-child { padding-bottom: 0; border-bottom: none; }
.state-list__label { min-width: 0; margin: 0; font-size: var(--sb-fs-body-sm); color: var(--sb-ink); }
.state-list__value { flex: none; margin: 0; }
/* A row that is not a state — the source language. A grey pill would read the
   same as "to translate" beside it. */
.state-list__note { font-size: var(--sb-fs-body-sm); color: var(--sb-muted); }

/* Card FAQ: stessa anatomia delle card servizio (testa, corpo, badge in basso,
   azioni sotto una riga di separazione), senza il contenitore categoria — le FAQ
   sono una lista piatta. Card più larghe dei servizi: domanda e risposta sono
   testo esteso, non un'etichetta. */
.mfaq-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-top: 8px; }
.mfaq-card { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); padding: 14px; background: #fff; }
.mfaq-card:hover { border-color: var(--sb-accent); box-shadow: var(--sb-shadow); }
.mfaq-card__head { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.mfaq-card__icon { display: inline-flex; width: 30px; height: 30px; border-radius: var(--sb-r-card); background: var(--sb-chip); color: var(--sb-accent); align-items: center; justify-content: center; flex: none; }
.mfaq-card__question { margin: 0; font-weight: 700; color: var(--c-ink); font-size: 0.95rem; overflow-wrap: anywhere; }
.mfaq-card__answer { margin: 0; font-size: 0.85rem; color: var(--c-ink-soft); overflow-wrap: anywhere; }
.mfaq-card__badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.mfaq-card__actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--sb-line); }
.mfaq-card__actions form { margin: 0; }
/* §E27 — messaggi programmati all'ospite. Stessa card di FAQ e documenti, con
   due righe in più: quando scatta e a chi arriva. Sono l'informazione che conta
   in questa lista (il titolo dice cosa, la programmazione dice quando), quindi
   stanno nel corpo della card e non fra i badge. */
.mmsg-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 12px; margin-top: 8px; }
.mmsg-card { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); padding: 14px; background: #fff; }
.mmsg-card:hover { border-color: var(--sb-accent); box-shadow: var(--sb-shadow); }
.mmsg-card__head { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.mmsg-card__icon { display: inline-flex; width: 30px; height: 30px; border-radius: var(--sb-r-card); background: var(--sb-chip); color: var(--sb-accent); align-items: center; justify-content: center; flex: none; }
.mmsg-card__title { margin: 0; font-weight: 700; color: var(--c-ink); font-size: 0.95rem; overflow-wrap: anywhere; }
.mmsg-card__body { margin: 0; font-size: 0.85rem; color: var(--c-ink-soft); overflow-wrap: anywhere; }
.mmsg-card__schedule, .mmsg-card__audience {
  display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: 0.8rem; font-weight: 600; color: var(--sb-primary-2); overflow-wrap: anywhere;
}
.mmsg-card__audience { color: var(--c-ink-soft); font-weight: 500; }
.mmsg-card__schedule .icon, .mmsg-card__audience .icon { flex: none; }
.mmsg-card__badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.mmsg-card__actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--sb-line); }
.mmsg-card__actions form { margin: 0; }
/* §E27.1 — "Comunicazioni" tab: a list and not a grid, because each row says
   when it went out and who has seen it. Title, excerpt and meta lines reuse the
   .mmsg-card__* pieces so the two tabs read as the same family. */
.mann-list { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.mann-row { display: flex; gap: 12px; align-items: flex-start; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); padding: 14px; background: #fff; }
.mann-row:hover { border-color: var(--sb-accent); box-shadow: var(--sb-shadow); }
.mann-row__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.mann-row__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.mann-row__meta { display: flex; gap: 6px 18px; flex-wrap: wrap; }
.mann-row__actions { display: flex; gap: 6px; flex: none; }
.mann-row__actions form { margin: 0; }
.mann-reads { list-style: none; margin: 0; padding: 0; display: flex; gap: 6px; flex-wrap: wrap; }
.mann-read {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 9px; border-radius: 999px;
  font-size: 0.78rem; border: 1px solid var(--sb-line); color: var(--c-ink-soft); background: #fff;
}
.mann-read .icon { flex: none; }
.mann-read strong { color: var(--c-ink); }
.mann-read--read { background: #e8f6ee; border-color: #b9e2c9; color: #1d6b3f; }
.mann-read--seen { background: var(--sb-chip); border-color: #bcdcf0; color: #0f5f8a; }
.mann-read-note { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 0.8rem; color: var(--c-ink-soft); }
@media (max-width: 640px) {
  .mann-row { flex-direction: column; }
}

/* Maniglia delle card riordinabili (FAQ, documenti, messaggi): stessa resa di
   quella dei servizi (vedi .msvc-card__drag). */
.mfaq-card__drag, .mdoc-card__drag, .mmsg-card__drag {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex: none; margin-top: 6px;
  color: var(--c-ink-soft); cursor: grab; touch-action: none;
}
.mfaq-card__drag .icon, .mdoc-card__drag .icon, .mmsg-card__drag .icon { stroke-width: 3.4; }
.mfaq-card__drag:hover, .mdoc-card__drag:hover, .mmsg-card__drag:hover { color: var(--sb-accent); }
.mfaq-card__drag:active, .mdoc-card__drag:active, .mmsg-card__drag:active { cursor: grabbing; color: var(--sb-accent); }
.mfaq-card.sortable-ghost, .mdoc-card.sortable-ghost, .mmsg-card.sortable-ghost {
  background: var(--sb-chip); border: 1px dashed var(--sb-accent);
}
.mfaq-card.sortable-ghost > *, .mdoc-card.sortable-ghost > *, .mmsg-card.sortable-ghost > * { visibility: hidden; }
.mfaq-card.sortable-drag, .mdoc-card.sortable-drag, .mmsg-card.sortable-drag { box-shadow: 0 12px 28px rgba(11, 58, 102, 0.22); }
/* Il `margin-top` serve alla testa FAQ, allineata in alto perché la domanda va
   a capo; quella del documento è centrata e non lo vuole. */
.mdoc-card__drag { margin-top: 0; }

/* The same handle in a table row (departments): it sits in line before the
   name, so the cell stays a table cell and not a flex box, which would break
   the row height. The ghost row keeps its height and loses its content, like
   the cards above. */
.dtable__drag {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-right: var(--sb-sp-2); vertical-align: middle;
  color: var(--c-ink-soft); cursor: grab; touch-action: none;
}
.dtable__drag .icon { stroke-width: 3.4; }
.dtable__drag:hover, .dtable__drag:active { color: var(--sb-accent); }
.dtable__drag:active { cursor: grabbing; }
.data-table tr.sortable-ghost > td { background: var(--sb-chip); color: transparent; }
.data-table tr.sortable-ghost > td > * { visibility: hidden; }

/* Liste con form nel rail destro (documenti, FAQ): card a sinistra, form per
   aggiungere a destra (stesso schema del rail AI in dashboard). Sotto i 1000px
   si impila, con le card sempre per prime. */
.mlist-layout { display: grid; grid-template-columns: 1fr; gap: 16px; }
.mlist-layout > * { min-width: 0; }
.mlist-layout > .panel__section { margin: 0; }
/* Colonna sinistra: card filtri sopra la card dell'elenco (come .rooms-main). */
.mlist-main > .panel__section { margin: 0; }
@media (min-width: 1000px) {
  .mlist-layout { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
}

/* Card documento: stessa anatomia delle card servizio (testa, corpo, badge in
   basso, azioni sotto una riga di separazione), riordinabili trascinando come
   le FAQ. La colonna è stretta per via del rail, quindi le card partono da
   230px: sotto quella soglia il titolo andava a capo a ogni parola. */
.mdoc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; margin-top: 8px; }
.mdoc-card { display: flex; flex-direction: column; gap: 8px; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); padding: 14px; background: #fff; }
.mdoc-card:hover { border-color: var(--sb-accent); box-shadow: var(--sb-shadow); }
.mdoc-card__head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.mdoc-card__icon { display: inline-flex; width: 30px; height: 30px; border-radius: var(--sb-r-card); background: var(--sb-chip); color: var(--sb-accent); align-items: center; justify-content: center; flex: none; }
.mdoc-card__name { margin: 0; font-weight: 700; color: var(--c-ink); font-size: 0.95rem; overflow-wrap: anywhere; }
.mdoc-card__body { margin: 0; font-size: 0.85rem; color: var(--c-ink-soft); }
.mdoc-card__files { display: flex; flex-wrap: wrap; gap: 6px; }
.mdoc-card__files .chip { max-width: 100%; overflow-wrap: anywhere; }
.mdoc-card__badges { display: flex; gap: 6px; flex-wrap: wrap; margin-top: auto; }
.mdoc-card__actions { display: flex; gap: 6px; flex-wrap: wrap; padding-top: 8px; border-top: 1px solid var(--sb-line); }
.mdoc-card__actions form { margin: 0; }

/* Filtri di una lista (documenti, FAQ, servizi): ricerca + stato su una riga
   sola finché c'è spazio, in una CARD sopra l'elenco — come nella pagina camere.
   Vedi manage/shared/_filter_bar. */
.mfilter { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin: 0; }
.mfilter__field { display: flex; flex-direction: column; gap: 4px; font-size: 0.8rem; font-weight: 600; color: var(--c-ink-soft); }
.mfilter__field--grow { flex: 1 1 220px; min-width: 0; }
/* Le azioni a destra finché c'è spazio, come nelle altre card filtri. */
.mfilter__actions { display: flex; align-items: flex-end; gap: 8px; margin-left: auto; }

/* What is on, and how much is left: the active filters on the left, each one a
   chip you take off, the result count on the right of the same bar. The whole
   strip disappears when neither is there — an empty rule under the fields would
   read as a broken layout. */
.mfilter__summary {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sb-sp-3); flex-wrap: wrap;
  margin-top: var(--sb-sp-3); padding-top: var(--sb-sp-3);
  border-top: 1px solid var(--sb-line-2);
}
.mfilter__chips { display: flex; align-items: center; gap: var(--sb-sp-2); flex-wrap: wrap; min-width: 0; }
.chip--filter { display: inline-flex; align-items: center; gap: 5px; text-decoration: none; }
.chip--filter .icon { flex: none; color: var(--sb-muted); }
.chip--filter:hover .icon { color: var(--sb-ink); }
/* Mono, like every other count in the panel: it changes as you type, and a
   proportional font makes the whole line shuffle at each keystroke. */
.mfilter__count { margin: 0 0 0 auto; font-size: var(--sb-fs-meta); color: var(--sb-muted); }

/* File allegati nel form di modifica: nome scaricabile + spunta di rimozione. */
.mdoc-files { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.mdoc-files__item { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.mdoc-files__remove { display: flex; align-items: center; gap: 6px; font-size: 0.85rem; color: var(--c-ink-soft); }

/* Dropzone documenti */
.dropzone {
  border: 2px dashed var(--c-blue-300); border-radius: 14px;
  padding: 20px; text-align: center; background: var(--c-azure-50);
}
.dropzone__hint { margin: 8px 0 0; font-size: 0.82rem; color: var(--c-ink-soft); }

/* Codice accesso */
.access-panel { border-color: var(--c-blue-300); }
.access-code {
  background: var(--c-blue-900); color: var(--c-white);
  border-radius: 16px; padding: 20px; text-align: center; margin-bottom: 14px;
}
.access-code__label { margin: 0 0 6px; font-size: 0.8rem; opacity: 0.8; text-transform: uppercase; letter-spacing: 0.5px; }
.access-code__value {
  margin: 0; font-size: 2.4rem; font-weight: 800; letter-spacing: 0.35em;
  font-family: "SF Mono", ui-monospace, monospace;
}
.access-code__until { margin: 8px 0 0; font-size: 0.9rem; opacity: 0.9; }
.access-code__note { margin: 10px 0 0; font-size: 0.8rem; opacity: 0.75; }
.access-status { font-weight: 600; color: var(--c-blue-900); margin: 0 0 6px; }

/* §E68.1 — the recovery codes, shown once. Mono because they are codes, two to a
   row so ten of them read as a sheet to copy out and not as a long list. */
.recovery-codes {
  display: grid; grid-template-columns: repeat(2, minmax(0, max-content)); gap: var(--sb-sp-2) var(--sb-sp-5);
  margin: 0; padding: 0; list-style: none;
  font-family: var(--sb-mono); font-size: 1rem; letter-spacing: 0.04em;
}


/* ==========================================================================
   §E6 — Statistiche (Manage::)
   ========================================================================== */
.stats-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
.stat-card {
  background: var(--c-white); border: 1px solid var(--c-azure-100);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px 22px;
}
.stat-card--soon { opacity: 0.75; }
.stat-card__title { margin: 0 0 16px; font-size: 1.05rem; color: var(--c-blue-900); }
.stat-empty { color: var(--c-ink-soft); margin: 0; font-size: 0.9rem; }

.stat-bars { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.stat-bar { display: grid; grid-template-columns: 120px 1fr 40px; align-items: center; gap: 10px; }
.stat-bar__label { font-size: 0.88rem; color: var(--c-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-bar__track { height: 10px; background: var(--c-azure-100); border-radius: var(--radius-sm); overflow: hidden; }
/* Il colore arriva per `currentColor` dalla classe tono della barra
   (`.stat-seg--N`, la categoria del servizio). */
/* The measure arrives as a variable from the view (§6: no real property in a
   `style=`), and the stylesheet decides which dimension it drives. */
.stat-bar__fill { display: block; height: 100%; width: var(--fill, 0%); background: currentColor; border-radius: var(--radius-sm); }
.stat-bar__value { text-align: right; font-weight: 700; color: var(--c-blue-700); font-size: 0.9rem; }

.stat-counters { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.stat-counter { background: var(--c-azure-50); border-radius: var(--sb-r-card); padding: 14px 16px; }
.stat-counter__num { display: block; font-size: var(--sb-fs-metric); font-weight: 700; color: var(--c-blue-900); line-height: 1; }
.stat-counter__label { font-size: var(--sb-fs-body-sm); color: var(--c-ink-soft); font-weight: 600; }

/* The four stages of a request, in the tones of the panel's one status badge
   (Manage::RequestsHelper#request_status_badge): no second vocabulary of state.
   Amber for received (nobody has it yet), neutral for in progress, green for
   done, grey for cancelled. RESERVED colours, not series tints — they never
   appear in the charts next to them. The label under the number always stays:
   colour is never the only signal. */
.stat-counter--received    { background: var(--sb-warn-bg); }
.stat-counter--in_progress { background: var(--sb-bg-2); }
.stat-counter--done        { background: #E7F5EC; }
.stat-counter--cancelled   { background: var(--sb-bg); box-shadow: inset 0 0 0 1px var(--sb-line); }
.stat-counter--received    .stat-counter__num { color: var(--sb-warn-fg); }
.stat-counter--in_progress .stat-counter__num { color: var(--sb-ink); }
.stat-counter--done        .stat-counter__num { color: var(--sb-green-fg); }
.stat-counter--cancelled   .stat-counter__num { color: var(--sb-muted); }

/* §E45.7 — Una tacca che porta un IMPORTO e non un conteggio: su schermo largo
   le tacche stanno a quattro per riga, e "1.240,00 €" a 1.7rem esce dalla cella.
   Stessa medicina di `.kpi--compact`, per la stessa ragione — il valore è testo,
   non una cifra. */
.stat-counter--money .stat-counter__num { font-size: 1.15rem; line-height: 1.2; }

.stat-hours { display: flex; align-items: flex-end; gap: 3px; height: 120px; padding-top: 8px; }
.stat-hours__col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; position: relative; }
/* Come le barre: il colore viene dalla fascia oraria (`.stat-band--*`). */
.stat-hours__bar { width: 100%; height: var(--fill, 0%); min-height: 2px; background: currentColor; border-radius: 4px 4px 0 0; }
.stat-hours__tick { position: absolute; bottom: -18px; font-size: 0.65rem; color: var(--c-ink-soft); }

/* --- Andamento della media dei feedback (§E29.4) ------------------------------
   Non riusa `.stat-hours*` benché la forma sia la stessa: lì l'altezza è un
   conteggio rapportato al massimo della serie, qui è già una percentuale su un
   asse FISSO 0-100. Stesso vocabolario visivo, unità diversa — e con il nome
   dell'altro grafico la differenza sparirebbe dalla vista. */
.stat-trend { display: flex; gap: 10px; padding: 8px 0 22px; }
.stat-trend__scale {
  list-style: none; margin: 0; padding: 0; flex: 0 0 auto;
  display: flex; flex-direction: column; justify-content: space-between;
  height: 140px; font-size: 0.65rem; color: var(--c-ink-soft);
  font-variant-numeric: tabular-nums;
}
/* La riga a metà altezza è il 50 dell'asse: un'altezza che È una percentuale,
   senza un riferimento a metà scala, si legge a occhio e male. */
.stat-trend__plot {
  flex: 1; min-width: 0; display: flex; align-items: flex-end; gap: 3px; height: 140px;
  border-bottom: 1px solid var(--sb-line);
  background: linear-gradient(var(--sb-line-2), var(--sb-line-2)) no-repeat center / 100% 1px;
}
.stat-trend__col {
  flex: 1; min-width: 0; height: 100%; position: relative;
  display: flex; flex-direction: column; justify-content: flex-end;
}
/* Come le altre barre: il colore arriva per `currentColor` dalla classe tono. */
.stat-trend__bar { width: 100%; height: var(--fill, 0%); min-height: 2px; background: currentColor; border-radius: 4px 4px 0 0; }
/* Ancorata a sinistra e non centrata: sulle colonne strette un'etichetta
   centrata sulla prima uscirebbe dal grafico. */
.stat-trend__tick {
  position: absolute; bottom: -20px; left: 0;
  font-size: 0.65rem; color: var(--c-ink-soft); white-space: nowrap;
}

/* Riga KPI dell'attività ospiti: piena larghezza come la griglia sottostante e
   con lo stacco che .kpi-grid non ha (stessa scelta di .mconv-kpis). */
.stats-kpis { max-width: none; margin: 0 0 16px; }

/* Chip di periodo dentro la card: i 18px di .board-filter sono pensati per una
   barra a sé e qui staccherebbero troppo dai campi data. */
.mfilter-card > .board-filter { margin: 0 0 12px; }

/* Preset di periodo fuori dal piano: reso e spento, con la spiegazione nel
   title (views/CLAUDE.md §7). Non è un link: non c'è niente da aprire. */
/* Two classes, and it matters: as `.chip--locked` it tied with `.chip` further
   down and lost, so a period outside the plan looked selectable and turned
   azure under the mouse. */
.chip.chip--locked, .chip.chip--locked:hover {
  display: inline-flex; align-items: center; gap: 5px;
  background: #F1F3F5; border-color: #D8DBE0;
  color: var(--sb-muted); cursor: not-allowed;
}

/* --- Barra a segmenti delle lingue -------------------------------------------
   Nessuna libreria di grafici (importmap, niente Node): il markup è la barra e i
   colori stanno qui. Il colore arriva per `currentColor` dalla classe di passo,
   così la stessa classe tinge il segmento e il pallino della legenda senza
   duplicare la rampa. */
.stat-stack-wrap { margin: 4px 0 0; }
.stat-stack {
  display: flex; height: 14px; border-radius: 999px; overflow: hidden;
  background: var(--sb-line-2);
}
/* Il bordo bianco separa i passi contigui senza rubare spazio al totale
   (box-sizing: border-box), quindi le larghezze continuano a sommare a 100%. */
.stat-stack__seg { width: var(--fill, 0%); min-width: 3px; box-sizing: border-box; background: currentColor; border-right: 2px solid #fff; }
.stat-stack__seg:last-child { border-right: none; }

.stat-legend {
  list-style: none; margin: 0; padding: 0; flex: 1 1 180px; min-width: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.stat-legend__item { display: flex; align-items: center; gap: 8px; font-size: 0.86rem; }
.stat-legend__dot { width: 10px; height: 10px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.stat-legend__label { color: var(--sb-ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat-legend__value {
  margin-left: auto; font-weight: 700; color: var(--sb-primary-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Legenda su una riga: sotto la barra delle lingue, sopra gli altri due grafici. */
.stat-legend--inline { flex-direction: row; flex-wrap: wrap; gap: 6px 16px; margin: 0 0 14px; }
.stat-legend--inline .stat-legend__item { font-size: 0.8rem; }
.stat-stack-wrap .stat-legend--inline { margin: 12px 0 0; }
/* La sigla della lingua non è un'abbreviazione da sottolineare: il nome per
   esteso resta nel title, ma senza il tratteggio del browser. */
.stat-legend__label abbr { text-decoration: none; font-weight: 700; }

/* --- Tinte categoriche (barre dei servizi) -----------------------------------
   Quattro tinte da token esistenti. L'ordine non è a gusto: è il migliore fra
   tutte le combinazioni dei token del progetto, scelto massimizzando la distanza
   minima fra tinte (skill `dataviz`, scripts/validate_palette.js).

   Il test è quello ALL-PAIRS, non solo sulle coppie adiacenti: in un elenco due
   categorie QUALSIASI possono finire una sopra l'altra. Con le due sole famiglie
   del marchio — indaco e arancio — nessun insieme di cinque lo supera; questi
   quattro sì, con un peggior accoppiamento di ΔE 15.5 in protanopia.

   Il colore non porta MAI l'informazione da solo: ogni barra ha nome e numero
   accanto, e la legenda ripete le categorie. */
.stat-seg--1 { color: var(--sb-primary-600); }
.stat-seg--2 { color: var(--sb-primary-300); }
.stat-seg--3 { color: var(--sb-accent); }
.stat-seg--4 { color: var(--sb-accent-strong); }
/* Aggregato "Altre": grigio di proposito, fuori dalla palette delle serie. */
.stat-seg--other { color: var(--sb-muted-2); }

/* --- Rampa ordinale a quattro passi ------------------------------------------
   Una sola tinta, dal più scuro al più chiaro. La usano i dati che hanno un
   ORDINE, dove la sequenza va letta nel colore invece che nella legenda: le
   quote delle lingue (`.stat-ramp--N`, dal segmento più grande al più piccolo) e
   le fasce della giornata (`.stat-band--*`, che la percorre al contrario perché
   la giornata va dal chiaro allo scuro).
   Verificata come rampa ordinale: luminosità monotona, salti ΔL ≥ 0.06, estremo
   chiaro ancora leggibile sul bianco. Quattro è il massimo: un quinto passo
   starebbe a ΔL 0.054 dal vicino e sarebbe lo stesso blu. */
.stat-ramp--1 { color: var(--sb-primary); }
.stat-ramp--2 { color: var(--sb-primary-600); }
.stat-ramp--3 { color: var(--sb-primary-500); }
.stat-ramp--4 { color: var(--sb-primary-300); }

.stat-band--night     { color: var(--sb-primary-300); }
.stat-band--morning   { color: var(--sb-primary-500); }
.stat-band--afternoon { color: var(--sb-primary-600); }
.stat-band--evening   { color: var(--sb-primary); }

@media (min-width: 720px) {
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
  .stat-counters { grid-template-columns: repeat(4, 1fr); }
  /* Card che occupa tutta la riga della griglia: la usano i grafici dei feedback
     (§E29.4), dove trenta colonne o un'etichetta come "Rapporto qualità-prezzo"
     in mezza larghezza diventano illeggibili. */
  .stat-card--wide { grid-column: 1 / -1; }
}

/* ==========================================================================
   §E7 — Traduzioni (Manage::)
   ========================================================================== */
.chip--warn { background: #fff3d6; border-color: #ffe0a3; color: #8a5a00; }
.chip--warn:hover { background: #ffecbf; }
/* §E21 — Tradurre è confrontare, quindi le due cose stanno affiancate: a
   sinistra le lingue da scrivere, a destra l'originale. E l'originale è
   AGGANCIATO: su un corpo lungo scorreva via proprio mentre lo si traduceva, e
   la traduzione si scriveva a memoria. Sotto i 1100px si impila, con l'originale
   in fondo — dove si legge una cosa alla volta, prima si guarda cosa c'è da
   riempire. */
.tr-layout { display: grid; grid-template-columns: 1fr; gap: var(--sb-sp-5); align-items: start; }
.tr-layout__form { min-width: 0; display: flex; flex-direction: column; gap: var(--sb-sp-4); }
.tr-layout__source {
  min-width: 0;
  background: #fff;
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-card);
  padding: var(--sb-sp-4) var(--sb-sp-5);
}
@media (min-width: 1100px) {
  .tr-layout { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); }
  /* Below the two sticky bands, like the panel's anchors. It only holds because
     no ancestor has an `overflow` other than `visible`. */
  .tr-layout__source { position: sticky; top: calc(var(--mng-top-h) + 110px); }
}

/* The source text is the REFERENCE, not a field. A box with a border says
   "write here", which is exactly what this one is not: it is the sentence being
   translated, beside the boxes that are. Field name on top like a label, text
   underneath — the anatomy of a field, minus the frame.
   `.tr-source` is deliberately OUT of the two card groups above: that is where
   the border and the white ground came from, and a rule here that only set a
   margin left them both standing. */
.tr-source { margin: 0 0 var(--sb-sp-4); background: none; border: none; }
.tr-source:last-child { margin-bottom: 0; }
.tr-source__lang {
  display: block; margin-bottom: 5px;
  font-family: var(--sb-sans); font-size: var(--sb-fs-label); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--sb-muted);
}
.tr-source__text { color: var(--sb-ink); font-size: var(--sb-fs-body); line-height: 1.5; }
.tr-source__text > :first-child { margin-top: 0; }
.tr-source__text > :last-child { margin-bottom: 0; }

/* Feedback "Copiato!" sul chip copia-link (§E2 QR page) */
.chip--copied { background: var(--sb-accent-tint); border-color: var(--sb-chip-line); color: var(--c-ok); }

/* ==========================================================================
   §E7.4 — Utenti staff (Manage::)
   ========================================================================== */
.muser-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.muser-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-radius: 12px; background: var(--c-azure-50);
  border: 1px solid var(--c-azure-100); flex-wrap: wrap;
}
.muser-row__main { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.muser-row__name { font-weight: 700; color: var(--c-ink); }
.muser-row__email { color: var(--c-ink-soft); font-size: 0.9rem; }
.muser-row__actions { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.muser-status--active { background: var(--sb-accent-tint); color: var(--sb-green-fg); }
.muser-status--invited { background: var(--sb-warn-bg, #FDF0DC); color: var(--sb-warn-fg, #92590F); }
.muser-status--disabled { background: #FDECEC; color: var(--sb-danger, #C62828); }

/* ==========================================================================
   MATERIAL DESIGN 3 LAYER — token, elevazioni, forme, state layer, componenti.
   Seed primario = azzurro del brand. Cascata: sovrascrive gli stili precedenti.
   ========================================================================== */
:root {
  /* Ruoli colore (M3) — seed brand blue */
  --md-primary: #1f7ae0;
  --md-on-primary: #ffffff;
  --md-primary-container: #d6e8ff;
  --md-on-primary-container: #06305c;
  --md-secondary-container: #e6edf5;
  --md-on-secondary-container: #33424f;
  --md-surface: #ffffff;
  --md-surface-container-low: #f6f9fe;
  --md-surface-container: #eef3fb;
  --md-surface-container-high: #e7eef8;
  --md-on-surface: #1a1c1e;
  --md-on-surface-variant: #43474e;
  --md-outline: #c3c7cf;
  --md-outline-variant: #e0e5ee;
  --md-error: #b3261e;
  --md-error-container: #f9dedc;

  /* Elevazioni (tinta brand, leggere per superfici chiare) */
  --md-elev-0: none;
  --md-elev-1: 0 1px 2px rgba(16,42,74,.10), 0 1px 3px 1px rgba(16,42,74,.05);
  --md-elev-2: 0 1px 2px rgba(16,42,74,.12), 0 2px 6px 2px rgba(16,42,74,.07);
  --md-elev-3: 0 2px 6px rgba(16,42,74,.10), 0 6px 14px 4px rgba(16,42,74,.09);

  /* Forma (M3 shape scale) */
  --md-shape-xs: 8px;
  --md-shape-s: 12px;
  --md-shape-m: 16px;
  --md-shape-l: 24px;
  --md-shape-full: 999px;

  --md-font: "Roboto", "Helvetica Neue", system-ui, -apple-system, "Segoe UI", Arial, sans-serif;

  /* Remap dei token legacy sulle nuove scale */
  --radius: var(--md-shape-m);
  --radius-sm: var(--md-shape-full);
  --shadow: var(--md-elev-1);
  --c-white: var(--md-surface);
  --c-azure-50: var(--md-surface-container-low);
  --c-azure-100: var(--md-surface-container);
  --font: var(--md-font);
}

body {
  background: var(--md-surface-container-low);
  color: var(--md-on-surface);
  font-family: var(--md-font);
  letter-spacing: 0.1px;
}

/* --- Tipografia (M3 type scale, sintetica) -------------------------------- */
.hero__title, .panel__title, .g-detail__title, .signup__title {
  letter-spacing: 0; font-weight: 600;
}
.panel__eyebrow, .g-service__cat, .g-detail__cat, .board-col__title,
.stat-card__title, .panel__eyebrow { letter-spacing: 0.4px; }

/* --- Icone ----------------------------------------------------------------- */
.icon { display: inline-block; vertical-align: middle; flex: 0 0 auto; }
.btn .icon { width: 18px; height: 18px; }
.chip .icon { width: 16px; height: 16px; }

/* --- Bottoni (M3: filled / tonal / outlined / text) ----------------------- */
.btn {
  height: 40px; padding: 0 22px; border-radius: var(--md-shape-full);
  font-family: var(--md-font); font-weight: 500; font-size: 0.9rem; letter-spacing: 0.1px;
  gap: 8px; border: none; transition: box-shadow .18s ease, background-color .18s ease;
}
.btn--sm { height: 32px; padding: 0 16px; font-size: 0.82rem; }
.btn--block { display: flex; width: 100%; }

.btn--primary { background: var(--md-primary); color: var(--md-on-primary); box-shadow: var(--md-elev-1); }
.btn--primary:hover { background: color-mix(in srgb, var(--md-primary) 92%, #000); box-shadow: var(--md-elev-2); }
.btn--primary:active { background: color-mix(in srgb, var(--md-primary) 86%, #000); box-shadow: var(--md-elev-1); }

/* "ghost" -> pulsante tonale */
.btn--ghost { background: var(--md-secondary-container); color: var(--md-on-secondary-container); border: none; }
.btn--ghost:hover { background: color-mix(in srgb, var(--md-on-secondary-container) 8%, var(--md-secondary-container)); }

.btn--danger { background: var(--md-error-container); color: var(--md-error); border: none; }
.btn--danger:hover { background: color-mix(in srgb, var(--md-error) 12%, var(--md-error-container)); }

/* --- Chip (assist/filter) -------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 14px;
  border-radius: var(--md-shape-xs); border: 1px solid var(--md-outline-variant);
  background: var(--md-surface); color: var(--md-on-surface-variant);
  font-weight: 500; font-size: 0.82rem; transition: background-color .15s ease;
}
.chip:hover { background: color-mix(in srgb, var(--md-primary) 8%, var(--md-surface)); }
.chip--active { background: var(--md-primary); border-color: var(--md-primary); color: var(--md-on-primary); }
.chip--danger { background: var(--md-surface); border-color: var(--md-outline-variant); color: var(--md-error); }
.chip--danger:hover { background: color-mix(in srgb, var(--md-error) 8%, var(--md-surface)); }
.chip--warn { background: #fff4d6; border-color: #ffe19c; color: #7a5300; }

/* --- Superfici / card (elevazione + forma M3) ----------------------------- */
.hero__card, .signup__card,
.stat-card, .req-card, .board-counter, .g-service,
.g-cta, .g-assistant, .g-list__item, .g-wifi, .g-contacts, .access-panel {
  border-radius: var(--md-shape-m);
  border: 1px solid var(--md-outline-variant);
  box-shadow: var(--md-elev-1);
}
.board-col { border-radius: var(--md-shape-m); border-color: var(--md-outline-variant); background: var(--md-surface-container-low); }
.msvc-row, .mstay-row, .muser-row, .stat-counter, .g-facts--card, .g-roombadge {
  border-radius: var(--md-shape-s);
}
/* The panel's rows and counters are cards of the panel, at its radius: the 12px of
   the Material layer above stays with the two guest blocks that share the rule. */
.msvc-row, .mstay-row, .muser-row, .stat-counter { border-radius: var(--sb-r-card); }

/* --- Campi di testo (M3 outlined) ----------------------------------------- */
.form__input {
  border-radius: var(--md-shape-xs); border: 1px solid var(--md-outline-variant);
  background: var(--md-surface); transition: border-color .15s ease, box-shadow .15s ease;
}
.form__input:hover { border-color: var(--md-outline); }
.form__input:focus { border-color: var(--md-primary); box-shadow: 0 0 0 1px var(--md-primary); outline: none; }
.form__label { color: var(--md-on-surface-variant); }


/* --- App ospite: app bar + accenti ---------------------------------------- */
.g-topbar { box-shadow: var(--md-elev-1); border-radius: 0 0 var(--md-shape-m) var(--md-shape-m); margin-bottom: 8px; }
.g-service { box-shadow: var(--md-elev-1); border-radius: var(--md-shape-m); }
.g-service:hover { box-shadow: var(--md-elev-2); }
.g-assistant__icon, .g-cta__icon { border-radius: var(--md-shape-s); }
.g-lang__current { border-radius: var(--md-shape-full); }
.g-back { display: inline-flex; align-items: center; gap: 4px; }
.g-back .icon { width: 20px; height: 20px; }

/* --- Badge (M3) ------------------------------------------------------------ */
.badge, .msvc-badge, .req-badge, .g-badge { border-radius: var(--md-shape-full); font-weight: 600; }

/* --- Flash / snackbar ------------------------------------------------------ */
.flash, .g-flash { border-radius: var(--md-shape-xs); }

/* Badge camera (home ospite) + camera nell'app bar */
.g-roombadge { display: inline-flex; align-items: center; gap: 6px; }
.g-roombadge .icon { width: 15px; height: 15px; }
.g-topbar__id { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; min-width: 0; }
.g-topbar__room {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: var(--md-shape-full);
  background: var(--md-primary-container); color: var(--md-on-primary-container);
  font-size: 0.78rem; font-weight: 700;
}
.g-topbar__room .icon { width: 14px; height: 14px; }

/* ==========================================================================
   STAYBELL THEME — direction B (dense and operational). Last layer: it wins.
   Fonts: Archivo (interface and headings), Inter (help text), JetBrains Mono
   (codes, dates, amounts, labels). One accent, green. Action in ink.
   The values that were here before are frozen on `.g-body` below — the guest
   app does NOT follow this direction.
   ========================================================================== */
:root {
  /* Action and structure. Direction B has one accent and it is green, so the
     indigo ramp becomes an ink one: the primary fill is near-black and carries
     white, the -500 step is the accent itself (focus ring, active nav on the
     dark sidebar) and -300 is a grey for placeholders and decorative marks.
     The gradients are gone — a flat tint takes their place, and the two tokens
     survive only so the rules still referring to them keep resolving. */
  --sb-primary: #14181C;      /* button fill, nav, links */
  --sb-primary-2: #14181C;    /* hover/pressed, text on tints */
  --sb-primary-500: #0E6B4F;  /* focus ring, active nav on dark */
  --sb-primary-300: #9AA3AF;  /* decorative, placeholders */
  --sb-grad: #14181C;
  --sb-grad-nav: #1E252B;
  /* The one token direction B never respecified, left at its indigo value on
     purpose rather than guessed. It feeds the chart palette below
     (.stat-seg--1, .stat-ramp--2, .stat-band--afternoon), whose ordinal ramp —
     primary, -600, -500, -300 — no longer runs monotonically now that its ends
     are ink and grey. Picking four new steps means re-running the all-pairs
     check the comment on .stat-seg documents, not swapping a hex. */
  --sb-primary-600: #1A44B0;

  /* Accent. One family, green, and it is the only colour that means "this is
     live": links, selection, confirmed state, focus. The -strong step is the
     one that carries white; the tint is the ground of a positive chip. */
  --sb-accent: #0E6B4F;
  --sb-accent-strong: #0A5340;
  --sb-accent-tint: #E4F6EE;
  --sb-highlight: #B54708;
  --sb-highlight-tint: #FDF3E7;

  /* Neutrals — a plain grey ramp, no cast. Two surfaces only: --sb-page is the
     page, white is a section. --sb-bg is left for service bands (toolbar,
     action bar, totals), --sb-line is the border of a control and --sb-line-2
     the hairline that separates two rows. */
  --sb-dark: #14181C;
  --sb-ink: #14181C;
  --sb-muted: #6B7280;
  --sb-muted-2: #9AA3AF;
  --sb-line: #C9CED6;
  /* The outline of a CONTROL (field, ghost button, icon button), not a hairline:
     WCAG 1.4.11 wants 3:1 against the surface, and --sb-line gave 1.58:1, so an
     empty field was a white box on a white card. #8B929C measures 3.14:1. */
  --sb-line-ctl: #8B929C;
  --sb-line-2: #E4E7EB;
  --sb-bg: #FAFBFC;
  --sb-bg-2: #F3F5F7;
  --sb-page: #EDEEF0;
  --sb-chip: #E4F6EE;
  --sb-chip-line: #C7E8DA;

  /* Semantic. Green doubles as the accent here — a confirmed booking and a
     link are the same colour on purpose, because there is only one. */
  --sb-green: #0E6B4F;
  --sb-green-fg: #0A5340;
  --sb-danger: #B42318;
  --sb-warn-bg: #FDF3E7;
  --sb-warn-fg: #B54708;

  /* Corners are nearly square, and nothing casts a shadow except what floats
     above the page. --sb-shadow and --sb-shadow-h stay declared because rules
     across the sheet still ask for them; they now answer `none`. */
  --sb-r-card: 6px;
  --sb-r-btn: 3px;
  --sb-r-pill: 3px;
  --sb-shadow: none;
  --sb-shadow-h: none;
  --sb-shadow-overlay: 0 8px 24px rgba(20, 24, 28, .18);

  /* Three families, three jobs: Archivo runs the interface, Inter is kept for
     help text and running prose, and the mono carries anything you read
     character by character — a code, a date, an amount, a small-caps label. */
  --sb-sans: 'Archivo', system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --sb-display: 'Archivo', system-ui, sans-serif;
  --sb-prose: 'Inter', system-ui, sans-serif;
  --sb-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;

  /* Spacing: a 4px ramp, plus two measures that get a name of their own.
     Seven steps and not five: a 4/8/12/16/24/32 ramp would swallow 20, 22 and
     26 into 24 and rewrite the padding of every card in the app, and it would
     push the gutter from 30 to 32px, moving the sticky header with it. Those
     two sit off the ramp on purpose, so they are named instead of rounded. */
  --sb-sp-1: 4px;       /* label↔control, chip↔chip inside one cluster */
  --sb-sp-2: 8px;       /* inline gap between siblings on a row */
  --sb-sp-3: 12px;      /* row of controls, icon↔text */
  --sb-sp-4: 16px;      /* vertical rhythm INSIDE a card */
  --sb-sp-5: 24px;      /* vertical rhythm BETWEEN cards */
  --sb-sp-card: 20px;   /* inner padding of a content card */
  --sb-sp-gutter: 30px; /* horizontal inset of the column in .mng-main */

  /* Control heights are pointer targets, not rhythm — hence not on the ramp.
     36px is a desktop measure and only that: the mobile block at the foot of
     this sheet puts --sb-ctl back to 44px below 900px, where a finger points
     instead of a mouse. --sb-row is the height of a table row. */
  --sb-ctl: 36px;
  --sb-ctl-sm: 28px;
  --sb-row: 42px;
  --sb-section-label: 240px; /* title column of a .panel__section */

  /* Type: sizes by ROLE, not by component. Two steps smaller across the board
     than the previous theme — the panel is read at a desk, by someone who
     wants more rows on screen, not a landing page. */
  --sb-fs-title:   1.3rem;   /* page h1 */
  --sb-fs-metric:  1.5rem;   /* numeric value of a KPI tile */
  --sb-fs-head:    0.94rem;  /* section h2 */
  --sb-fs-body:    0.85rem;  /* running text, value inside an input */
  --sb-fs-body-sm: 0.82rem;  /* cells, banners, empty states, .btn */
  /* 0.66rem (10.56px) at weight 400 made the form labels the hardest text of the
     panel to read, and they are what a form is read by. Same size as meta now:
     mono small caps still tell the two roles apart. */
  --sb-fs-label:   0.72rem;  /* control label, .btn--sm */
  --sb-fs-meta:    0.72rem;  /* note, chip, badge, tag */
  --sb-fs-micro:   0.63rem;  /* ONLY letter-spaced small caps: th, fact-label */

  /* rimappa i token legacy/Material sul tema ConSergio */
  --font: var(--sb-sans);
  --md-primary: var(--sb-primary);
  --md-on-primary: #ffffff;
  --md-primary-container: var(--sb-chip);
  --md-on-primary-container: var(--sb-primary-2);
  --md-surface: #ffffff;
  --md-outline-variant: var(--sb-line);
  --md-on-surface: var(--sb-ink);
  --md-on-surface-variant: var(--sb-muted);
  /* These six carried cold-blue literals from the indigo theme. On the token
     now, so they follow the palette instead of pulling against it. */
  --md-secondary-container: var(--sb-bg-2);
  --md-on-secondary-container: var(--sb-ink);
  --md-surface-container-low: var(--sb-bg);
  --md-surface-container: var(--sb-bg-2);
  --md-surface-container-high: var(--sb-line-2);
  --md-outline: var(--sb-line);
  --c-blue-900: var(--sb-ink);
  --c-blue-700: var(--sb-primary-2);
  --c-blue-500: var(--sb-primary);
  --c-blue-300: var(--sb-primary-300);
  --c-ink: var(--sb-ink);
  --c-ink-soft: var(--sb-muted);
  --c-azure-50: var(--sb-bg);
  --c-azure-100: var(--sb-line);
  --c-white: #ffffff;
  --c-ok: var(--sb-green);
  --radius: var(--sb-r-card);
  --radius-sm: var(--sb-r-pill);
  --shadow: var(--sb-shadow);
}

/* --------------------------------------------------------------------------
   GUEST PORTAL: the token values above, frozen.

   The :root block right above now carries the PANEL palette ("direction B"):
   near-black actions, one green accent, 3px radii, no shadow, and type two
   steps smaller. The guest app must not follow it there, and it would: the
   four themed sheets remap about twenty tokens onto their own --gt-* values,
   but they INHERIT the rest from :root — the accent family, the control
   heights and all nine type sizes. Left alone, `classic` would change palette
   outright and the other four would lose a third of their type size, on a
   portal read on a phone.

   So the guest body re-declares the whole set at the values it had before the
   redesign. It sits on `.g-body` and not on `.g-body[data-guest-template=
   "classic"]` precisely because of that inheritance: the themed sheets load
   after this one and win on what they remap (class + attribute, 0-2-0), so
   this block only ever supplies what they leave alone.

   The derived remaps below (--md-*, --c-*, --radius, --shadow, --font) are
   repeated verbatim on purpose: a custom property resolves its var() on the
   element that declares it, so the ones declared on :root would have computed
   against the NEW --sb-* and carried the panel palette down here regardless.

   This block is what a guest redesign would delete. Until then, every token
   added to :root belongs here too, or the guest app quietly drifts with it.
   -------------------------------------------------------------------------- */
.g-body {
  --sb-primary: #002F91;
  --sb-primary-2: #17008E;
  --sb-primary-600: #1A44B0;
  --sb-primary-500: #3B62D6;
  --sb-primary-300: #93A6EC;
  --sb-grad: linear-gradient(150deg, #1A44B0 0%, #17008E 100%);
  --sb-grad-nav: linear-gradient(150deg, #3B62D6 0%, #2438B8 100%);
  --sb-accent: #F28C00;
  --sb-accent-strong: #B85E00;
  --sb-accent-tint: #FFF0DB;
  --sb-highlight: #FFCE00;
  --sb-highlight-tint: #FFF6CC;
  --sb-dark: #0D0B33;
  --sb-ink: #191B33;
  --sb-muted: #5A5F80;
  --sb-muted-2: #8B90AD;
  --sb-line: #E2E4F0;
  --sb-line-2: #EFF0F7;
  --sb-bg: #F6F7FC;
  --sb-bg-2: #EFF0F7;
  --sb-page: #E9EBF5;
  --sb-chip: #E7EAFA;
  --sb-chip-line: #CBD3F2;
  --sb-green: #46b450;
  --sb-green-fg: #147045;
  --sb-danger: #C62828;
  --sb-warn-bg: #FDF0DC;
  --sb-warn-fg: #92590F;
  --sb-r-card: 16px;
  --sb-r-btn: 12px;
  --sb-r-pill: 20px;
  --sb-shadow: 0 6px 18px rgba(23,0,142,.07);
  --sb-shadow-h: 0 8px 24px rgba(23,0,142,.12);
  --sb-sans: 'Inter', system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
  --sb-display: 'Sora', 'Inter', system-ui, sans-serif;
  --sb-sp-1: 4px;
  --sb-sp-2: 8px;
  --sb-sp-3: 12px;
  --sb-sp-4: 16px;
  --sb-sp-5: 24px;
  --sb-sp-card: 20px;
  --sb-sp-gutter: 30px;
  --sb-ctl: 44px;
  --sb-ctl-sm: 32px;
  --sb-fs-title: 1.8rem;
  --sb-fs-metric: 1.6rem;
  --sb-fs-head: 1.05rem;
  --sb-fs-body: 1rem;
  --sb-fs-body-sm: 0.9rem;
  --sb-fs-label: 0.85rem;
  --sb-fs-meta: 0.8rem;
  --sb-fs-micro: 0.72rem;
  --font: var(--sb-sans);
  --md-primary: var(--sb-primary);
  --md-on-primary: #ffffff;
  --md-primary-container: var(--sb-chip);
  --md-on-primary-container: var(--sb-primary-2);
  --md-surface: #ffffff;
  --md-outline-variant: var(--sb-line);
  --md-on-surface: var(--sb-ink);
  --md-on-surface-variant: var(--sb-muted);
  --md-secondary-container: #E9EBF5;
  --md-on-secondary-container: #3A3F5C;
  --md-surface-container-low: #F6F7FC;
  --md-surface-container: #EFF0F7;
  --md-surface-container-high: #E7E9F3;
  --md-outline: #C5C8DA;
  --c-blue-900: var(--sb-ink);
  --c-blue-700: var(--sb-primary-2);
  --c-blue-500: var(--sb-primary);
  --c-blue-300: var(--sb-primary-300);
  --c-ink: var(--sb-ink);
  --c-ink-soft: var(--sb-muted);
  --c-azure-50: var(--sb-bg);
  --c-azure-100: var(--sb-line);
  --c-white: #ffffff;
  --c-ok: var(--sb-green);
  --radius: var(--sb-r-card);
  --radius-sm: var(--sb-r-pill);
  --shadow: var(--sb-shadow);
}

body { font-family: var(--sb-sans); color: var(--sb-ink); background: var(--sb-page); }
h1,h2,h3,.hero__title,.panel__title,.g-detail__title,.signup__title,
.stat-card__title,.board-col__title,.g-service__name {
  font-family: var(--sb-display);
}

/* --- Buttons: direction B, and ONLY in the panel ---------------------------
   Scoped to `.mng-body` because these same classes dress the guest app (29
   occurrences across its views) and the public pages: 34px is a mouse measure,
   and on a portal read on a phone it would be a target under 44px. Outside of
   here the base rules at the top of the sheet apply — the guest app resolves
   them against its frozen tokens, `auth.css` dresses them on its own. */
.mng-body .btn {
  /* The height of a field: a 34px button beside a 36px input sat 1px off on
     both sides in every row of controls. */
  height: var(--sb-ctl);
  padding: 0 14px;
  /* The SHORTHAND, and it matters: the Material layer above sets `border: none`
     on .btn, so a variant that names only `border-color` leaves the width at
     zero — which is how every ghost and every danger button in the panel ended
     up with no outline at all, reading as plain text on a white header. The
     colour is then the variants' business. */
  border: 1px solid transparent;
  border-radius: var(--sb-r-btn);
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-body-sm);
  font-weight: 600;
  box-shadow: none;
  letter-spacing: 0;
  transition: background-color .14s, border-color .14s, color .14s;
}
/* One height for every control in the panel, the small button included: the
   guest app's `.btn--sm { min-height: 36px }` already lifted it there, so the 28
   written here was a measure nobody saw. What sets it apart is the label and
   the padding. The icon buttons follow (`.mng-body .icon-btn` below). */
.mng-body .btn--sm { height: var(--sb-ctl); padding: 0 10px; font-size: var(--sb-fs-meta); }
.mng-body .icon-btn { width: var(--sb-ctl); height: var(--sb-ctl); border-radius: var(--sb-r-btn); }
/* The primary is ink, not green: the accent means "this is live", not "press
   here", and with one primary per page there is nothing to shout about. */
.mng-body .btn--primary { background: var(--sb-ink); border-color: var(--sb-ink); color: #fff; box-shadow: none; }
.mng-body .btn--primary:hover { background: #262D34; border-color: #262D34; filter: none; }

/* One primary, and it is ink everywhere, the Save at the bottom of a form
   included. That Save used to be green (--sb-green-fg) to tell "confirm a change"
   from "go elsewhere", but next to the ink primary (1.97:1) and the accent
   (1.39:1) nobody could see the difference, and it read as inconsistency, not
   meaning. The label says what a button does; green is left to `btn--accent`,
   the button that closes a piece of work. Decided 2026-10-01. */
/* The one coloured variant, and it carries the accent: this is the button that
   CLOSES a piece of work — "Segna come evasa" — and in this system green already
   means done, confirmed. Not a second primary: the ink primary stays the one
   that moves things on (save, take charge), this is the one that ends them.
   White on #0E6B4F measures 6.49:1, past the 4.5 required. */
.mng-body .btn--accent { background: var(--sb-accent); border-color: var(--sb-accent); color: #fff; }
.mng-body .btn--accent:hover { background: var(--sb-accent-strong); border-color: var(--sb-accent-strong); filter: none; }
.mng-body .btn--ghost { background: #fff; border-color: var(--sb-line-ctl); color: var(--sb-ink); }
.mng-body .btn--ghost:hover { background: var(--sb-bg); }
.mng-body .btn--danger { background: #fff; border-color: var(--sb-danger); color: var(--sb-danger); }
.mng-body .btn--danger:hover { background: #FEF3F2; }
.mng-body .btn:disabled, .mng-body .btn[aria-disabled="true"] {
  background: #F1F3F5; border-color: #D8DBE0; color: var(--sb-muted); cursor: not-allowed;
}
.mng-body .btn:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: 1px; }
/* An icon inside a control is 18px, the same as inside .icon-btn: the helper's
   24px default is a measure for a card plate, and on a 34px button it takes up
   more room than the word it is meant to accompany. */
.mng-body .btn .icon { width: 18px; height: 18px; }

/* --- Chip / pill ----------------------------------------------------------- */
.chip { border-radius: var(--sb-r-pill); background: var(--sb-chip); border: 1px solid var(--sb-chip-line); color: var(--sb-primary-2); font-weight: 600; }
/* Hover darkens the chip's own tint instead of the old azure #dcf0fb, which no
   longer belongs to the palette. And `.chip:hover` (0-2-0) outranks
   `.chip--active` (0-1-0): without the second active rule a selected period
   turned into white on a pale fill under the mouse, 1.17:1. */
.chip:hover { background: var(--sb-chip-line); }
.chip--active, .chip--active:hover { background: var(--sb-primary); border-color: var(--sb-primary); color: #fff; }
.chip--danger { background: #fff; border-color: var(--sb-line); color: var(--sb-danger); }
.chip--warn { background: var(--sb-warn-bg); border-color: #F0DCB8; color: var(--sb-warn-fg); }

/* --- Card / superfici ------------------------------------------------------ */
/* .panel__header, .panel__section and .panel__empty are NOT here any more:
   direction B wants them without a frame (a band, a row, a dashed box), and from
   inside this group the border and the radius would come back over their own
   rules. */
.hero__card, .signup__card,
.stat-card, .req-card, .board-counter, .g-service,
.g-cta, .g-assistant, .g-list__item, .g-wifi, .g-contacts, .access-panel,
.g-facts--card, .g-form-card, .g-recap {
  border-radius: var(--sb-r-card); border: 1px solid var(--sb-line); box-shadow: var(--sb-shadow); background: #fff;
}
.board-col { background: var(--sb-bg-2); border: none; border-radius: var(--sb-r-card); }
.badge, .msvc-badge, .req-badge, .g-badge { border-radius: var(--sb-r-pill); font-weight: 700; }
/* Chips and badges in the panel: the geometry of a control, and the state is
   the WORD, not the dot next to it. */
.mng-body .chip, .mng-body .badge {
  border-radius: var(--sb-r-btn);
  padding: 3px 8px;
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-meta);
  font-weight: 600;
}
.mng-body .badge--ok { background: var(--sb-accent-tint); color: var(--sb-accent); }
.mng-body .badge__dot { display: none; }

/* --- Fields: low and plain, and ONLY in the panel --------------------------
   Same reason as the buttons above: `.form__input` and `.form__label` are in the
   guest views too, where 36px and a small-caps mono label would be out of place.
   Widths are sized to the datum — a date does not take the whole column — and
   the focus ring is a real `outline`: it survives forced colours, where a
   `box-shadow` disappears. */
.mng-body .form__label {
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-label);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sb-muted);
  margin-bottom: 5px;
}
/* …except beside a checkbox, where those 5px are not spacing but a MISALIGNMENT.
   `.form__row--check` centers its items, and a flex item is centered by its
   MARGIN box: a label 5px taller at the bottom has its text sitting 2.5px above
   the box it labels. The rule that zeroes it (`.form__row--check .form__label`)
   ties on specificity with the one above and comes EARLIER in this file, so it
   loses — hence this one, scoped to the panel and placed after. Same lesson as
   the `.form__row` margin inside a horizontal row: a vertical-rhythm margin is
   nothing in a row, and `align-items: center` turns it into a visible offset. */
.mng-body .form__row--check .form__label { margin: 0; }
/* A checkbox label is a sentence, not a field name: in small-caps mono a whole
   "Show prices on the widget" read as one shouted line. It goes back to running
   text, at the size of the value it switches. */
.mng-body .form__row--check .form__label {
  font-family: var(--sb-sans); font-size: var(--sb-fs-body);
  letter-spacing: normal; text-transform: none; color: var(--sb-ink);
}
.mng-body .form__input {
  height: var(--sb-ctl);
  min-height: var(--sb-ctl);
  padding: 0 8px;
  border: 1px solid var(--sb-line-ctl);
  border-radius: var(--sb-r-btn);
  background: #fff;
  box-shadow: none;
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-body);
  color: var(--sb-ink);
}
/* Without a rule the browser picks: 4.61:1 in Chrome, 3.86 in Firefox, 2.35 in
   Safari. Not --sb-muted-2, which would give 2.55. */
.mng-body ::placeholder { color: var(--sb-muted); opacity: 1; }
.mng-body textarea.form__input { height: auto; min-height: 72px; padding: 8px; }
.mng-body .form__input:focus,
.mng-body .form__input:focus-visible {
  border-color: var(--sb-accent);
  outline: 2px solid var(--sb-accent);
  outline-offset: 1px;
  box-shadow: none;
}
.mng-body .form__input:disabled {
  background: #F1F3F5; color: var(--sb-muted-2); border-color: var(--sb-line-2);
}
.mng-body .form__input[aria-invalid="true"], .mng-body .form__input.is-invalid {
  border-color: var(--sb-danger); background: #FEF6F5;
}
/* Small means NARROW, not short: the height lost to the `min-height` above
   anyway, and a field lower than the button beside it is what one height for
   every control (UX review 2026-10) closes. */
.mng-body .form__input--sm { font-size: var(--sb-fs-body-sm); }
/* 150 and not the 112 of the spec: that is the width of a date as TEXT, but the
   native control also puts a calendar icon beside it, and at 112 the year fell
   out of the field ("11 / 09 / 2("). Better a little wide than clipped. */
.mng-body .form__input[type="date"] { width: 150px; font-size: var(--sb-fs-body-sm); font-variant-numeric: tabular-nums; }
.mng-body .form__input[type="number"] { width: 140px; text-align: right; font-variant-numeric: tabular-nums; }
/* Inside .form__amount the frame decides the width, not the field: the rule
   above is more specific than `.form__amount .form__input`, so it handed the
   field its full 140px inside a 140px box that already spends 28 on the prefix —
   the rest went under the `overflow: hidden`, taking the digits with it. */
.mng-body .form__amount .form__input[type="number"] { width: 100%; }
.mng-body .form__hint {
  font-family: var(--sb-prose);
  font-size: var(--sb-fs-meta);
  line-height: 1.5;
  color: var(--sb-muted);
  margin: 5px 0 0;
}
.mng-body .form__grid { gap: var(--sb-sp-3) var(--sb-sp-4); }

/* --- Action bar: one fixed place, at the bottom ----------------------------
   Destructive on the left, apart from the other two; secondary and primary on
   the right, primary last. It only holds if no ancestor has an `overflow` other
   than `visible`: that ancestor would become the scroll container, and the bar
   would stop sticking. */
.mng-body .form__actions {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sb-sp-2);
  flex-wrap: wrap;
  margin: 0;
  padding: var(--sb-sp-3) var(--sb-sp-5);
  background: var(--sb-bg);
  border-top: 1px solid var(--sb-line-2);
}
.mng-body .form__actions .btn--danger { margin-right: auto; }

/* Amount with a unit prefix: digits to the right, fixed width, so a column of
   prices lines up. */
.form__amount {
  display: flex; align-items: center;
  width: 140px; height: var(--sb-ctl);
  border: 1px solid var(--sb-line-ctl); border-radius: var(--sb-r-btn);
  overflow: hidden; background: #fff;
}
/* A minimum and not a width: "CHF" is three letters, and at 28px fixed it
   overflowed the box. */
.form__amount-unit {
  min-width: 28px; padding: 0 var(--sb-sp-1); flex: none; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: var(--sb-bg-2); border-right: 1px solid var(--sb-line-2);
  /* Ink, not muted: grey on --sb-bg-2 is 4.42:1, under AA. */
  font-size: var(--sb-fs-body-sm); color: var(--sb-ink);
}
.form__amount .form__input { border: none; height: 100%; width: 100%; text-align: right; }
.form__amount:focus-within { outline: 2px solid var(--sb-accent); outline-offset: 1px; }

/* A date range as one field: two inputs and an arrow, a single label. */
.form__range { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.form__range-sep { color: var(--sb-muted-2); }

/* Toggleable chips (weekdays, filters): today these are checkboxes in a row. */
.chip-group { display: flex; gap: 4px; flex-wrap: wrap; }
.chip-group__item {
  height: 32px; min-width: 34px; padding: 0 8px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--sb-line); border-radius: var(--sb-r-btn);
  background: #fff; color: var(--sb-muted);
  font-family: var(--sb-sans); font-size: var(--sb-fs-body-sm);
  cursor: pointer;
  transition: background-color .14s, border-color .14s, color .14s;
}
/* Also a link, where the choice is a page (billing cadence and module). */
a.chip-group__item { text-decoration: none; }
.chip-group__item[aria-pressed="true"],
.chip-group__item.is-on,
.chip-group__item:has(input:checked) {
  background: var(--sb-accent); border-color: var(--sb-accent); color: #fff; font-weight: 600;
}
/* The chip IS the label of a real checkbox, hidden but not removed: the group
   keeps working with the keyboard, submits like any other field and needs no
   JavaScript at all. `appearance: none` and not `display: none`, because a
   display-none control takes no focus. */
.chip-group__item input {
  position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none;
}
.chip-group__item:has(input:focus-visible) { outline: 2px solid var(--sb-accent); outline-offset: 1px; }
.chip-group__item:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: 1px; }

/* §E41 — Campo password con il pulsante che la mostra in chiaro.
   Il markup lo scrive `shared/_password_field`, che e' lo stesso su tutte le
   pagine che impostano una password: auth.css lo veste per quelle pubbliche
   (scoped su .auth-page), queste regole per il cambio password dentro al
   pannello, dove auth.css non e' caricato.
   Il padding a destra dell'input non e' decorativo: senza, una password lunga
   scorre sotto al pulsante e non si legge proprio mentre la si sta rileggendo. */
.form__password { position: relative; }
.form__password .form__input { padding-right: 2.9rem; }

.form__password-toggle {
  position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0; border: 0; border-radius: 9px;
  background: transparent; color: var(--sb-muted-2); cursor: pointer;
  transition: color .15s ease, background .15s ease;
}
.form__password-toggle:hover { color: var(--sb-primary); background: var(--sb-chip); }

/* Stesso anello del focus dei campi: il pulsante sta dentro l'input, e due
   trattamenti diversi a un centimetro di distanza si notano. */
.form__password-toggle:focus-visible {
  color: var(--sb-accent); outline: 2px solid var(--sb-accent); outline-offset: 1px; box-shadow: none;
}

/* --- Pannello: shell con sidebar scura (StayBell) -------------------------- */
/* The body declares the running size (UX review 2026-10). Without it the text
   that inherits came out at the browser's 16px — a guest's request on the board,
   the rows of the day sheet, the checkbox groups, the user menu — next to tables
   at 13 and notes at 13: five sizes for one role. */
.mng-body { background: var(--sb-bg); margin: 0; --mng-top-h: 0px; font-size: var(--sb-fs-body); }
.mng-body--topbar { --mng-top-h: 58px; }
.mng-shell { display: flex; min-height: 100vh; align-items: stretch; }
.mng-side {
  width: 238px; flex: none; background: #000000db; color: #fff;
  display: flex; flex-direction: column; padding: 22px 16px;
  position: sticky; top: 0; height: 100vh; min-height: 0;
}
.mng-side__brand { display: flex; flex: none; align-items: center; gap: 10px; padding: 6px 4px 24px; }
.mng-side__plate { display: flex; flex: 1; align-items: center; justify-content: center; padding: 6px 4px; }
/* Logo orizzontale (~7.85:1): larghezza piena della targa, altezza proporzionale. */
.mng-side__logo-img { display: block; width: 100%; height: auto; }
.mng-side__logo { width: 34px; height: 34px; border-radius: 10px; background: var(--sb-grad); display: flex; align-items: center; justify-content: center; }
.mng-side__name { font-family: var(--sb-display); font-weight: 700; font-size: 18px; }
.mng-side__dot { color: var(--sb-primary); }

/* --- Logo applicativo ConSergio + logo struttura --------------------------- */
.app-logo { display: block; height: auto; max-width: 100%; }
.app-logo--hero { width: 260px; max-width: 80%; margin: 0 auto 18px; }
.signup .app-logo--hero, .hero .app-logo--hero { margin-left: 0; margin-right: auto; }
.g-topbar__plate { display: inline-flex; align-items: center; background: #fff; border-radius: 10px; padding: 5px 10px; box-shadow: 0 4px 12px rgba(13,11,51,.25); }
.g-topbar__logo { display: block; height: 30px; width: auto; }
.settings-logo { display: flex; align-items: center; gap: 14px; margin-bottom: 10px; }
.settings-logo__img { max-height: 64px; width: auto; border-radius: 10px; border: 1px solid var(--sb-line, #e6edf3); padding: 6px 10px; background: #fff; }
.settings-logo__remove { display: inline-flex; align-items: center; gap: 6px; font-size: 0.85rem; color: var(--c-ink-soft); }
.settings-cover { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin-bottom: 10px; }
.settings-cover__img { max-width: 100%; border-radius: 12px; border: 1px solid var(--sb-line, #e6edf3); display: block; }

/* --- Form lunghe: campi raggruppati in card su due colonne ------------------
   Two column wrappers instead of grid auto-placement, so each column stacks its
   own cards at free height (same reason as .mconv-rail). */
/* A container, so the two-column sections below can measure the box they are
   actually in rather than the window. Cheap: a block form's width never depended
   on its contents anyway. */
.form { container-type: inline-size; }
.form__cards { display: grid; grid-template-columns: 1fr; gap: 16px; margin-bottom: 24px; }
.form__cards-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; container-type: inline-size; }
.form__cards-col > .panel__section { margin: 0; }
/* A section standing beside the grid rather than inside it carries the gap that
   `.form__cards` leaves below itself — without it the card butts straight onto
   the action bar. */
.form > .panel__section { margin-bottom: var(--sb-sp-5); }
@media (min-width: 1100px) {
  .form__cards { grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: start; }
}
/* .form__grid splits at 640px of VIEWPORT width: inside a half-page card the two
   inputs would be cramped, so pair them only when the card is really wide enough. */
@container (max-width: 520px) {
  .form__cards-col .form__grid { grid-template-columns: 1fr; }
}
/* Card-level intro notes: .form__hint has no bottom margin (it normally sits
   last inside a .form__row), so as a direct child of the card it would stick to
   the label of the next field. */
.form__cards .panel__section > .form__hint:not(:last-child) { margin-bottom: 14px; }
.form__checks { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.form__check-inline { display: inline-flex; align-items: center; gap: 6px; }
.form__static { margin: 2px 0; }
/* La nav prende lo spazio residuo e scrolla: con molte voci su schermi bassi
   l'ultima voce restava fuori viewport. */
.mng-side__nav, .mng-side__groups {
  display: flex; flex-direction: column; gap: 4px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  margin: 0 -6px; padding: 0 6px;
  scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.28) transparent;
}
.mng-side__nav::-webkit-scrollbar, .mng-side__groups::-webkit-scrollbar { width: 6px; }
.mng-side__nav::-webkit-scrollbar-track, .mng-side__groups::-webkit-scrollbar-track { background: transparent; }
.mng-side__nav::-webkit-scrollbar-thumb, .mng-side__groups::-webkit-scrollbar-thumb { background: rgba(255,255,255,.28); border-radius: 3px; }
/* §E70 — the panel sidebar has a fixed bottom (the Settings link), so the
   scrolling moves from the nav to the groups above it. Admin and partners keep
   the nav itself as the scroller. */
.mng-side__nav--split { overflow: visible; margin: 0; padding: 0; gap: 0; }
.mng-side__bottom {
  flex: none; display: flex; flex-direction: column;
  margin-top: var(--sb-sp-2); padding-top: var(--sb-sp-2); border-top: 1px solid rgba(255,255,255,.1);
}
/* The Settings sidebar replaces the panel one: a way back, then its title. */
.mng-side__back {
  display: flex; flex: none; align-items: center; gap: 11px; padding: 9px 12px;
  margin-bottom: var(--sb-sp-2); border-radius: var(--sb-r-card);
  color: rgba(255,255,255,.82); text-decoration: none; font-weight: 600; font-size: 14px;
}
.mng-side__back:hover { background: rgba(255,255,255,.08); color: #fff; }
.mng-side__title {
  flex: none; margin: 0 0 var(--sb-sp-4); padding: var(--sb-sp-3) 12px 0;
  border-top: 1px solid rgba(255,255,255,.1);
  font-family: var(--sb-display); font-size: var(--sb-fs-head); font-weight: 700; color: #fff;
}
.mng-nav__item {
  display: flex; flex: none; align-items: center; gap: 11px; padding: 11px 12px; border-radius: var(--sb-r-card);
  color: rgba(255,255,255,.82); text-decoration: none; font-weight: 600; font-size: 14px;
}
/* White on #46b450 was 2.66:1, under AA on every page, and hover painted the same
   fill as the active voice: moving the mouse across the menu made it stop saying
   where you were. Active takes the accent (6.49:1 with white); hover is a light
   wash that can never be mistaken for it. */
.mng-nav__item:hover { background: rgba(255,255,255,.08); color: #fff; }
.mng-nav__item--active, .mng-nav__item--active:hover { background: var(--sb-accent); color: #fff; }
/* Raggruppamento del menu per area, con separatore fra i gruppi. */
.mng-nav__group { display: flex; flex-direction: column; gap: 4px; }
.mng-nav__group + .mng-nav__group { margin-top: 4px; padding-top: 8px; border-top: 1px solid rgba(255,255,255,.1); }
.mng-nav__group-label { margin: 0; padding: 6px 12px 2px; font-size: 11px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: rgba(255,255,255,.6); }
.mng-nav__label { flex: 1; }
/* Il badge resta nel DOM anche a 0 (per il Turbo Stream) e si nasconde con
   l'attributo hidden: serve il display esplicito qui sotto per la transform
   della pulsazione, e quindi anche la regola [hidden] — una dichiarazione
   d'autore batte il display:none dello user-agent stylesheet. */
.mng-nav__badge { display: inline-block; background: #fff; color: var(--sb-accent); font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: var(--sb-r-pill); }
.mng-nav__badge[hidden] { display: none; }
@keyframes nav-badge-pulse { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.3); } }
.mng-nav__badge--pulse { animation: nav-badge-pulse .5s ease-out 2; }
.mng-side__pulse { width: 7px; height: 7px; border-radius: 50%; background: var(--sb-green); }
.mng-main { flex: 1; min-width: 0; background: var(--sb-page); }

/* --- Fascia superiore: struttura + utente + logout ------------------------- */
.mng-top {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  height: var(--mng-top-h); padding: 0 var(--sb-sp-gutter);
  background: #fff; border-bottom: 1px solid var(--sb-line);
}
.mng-top__property { display: flex; align-items: center; gap: 10px; min-width: 0; margin-right: auto; }
/* Hamburger: nascosto su desktop, compare solo quando la sidebar va off-canvas. */
.mng-top__burger {
  display: none; flex: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 10px;
  background: var(--sb-bg); border: 1px solid var(--sb-line); color: var(--sb-dark); cursor: pointer;
}
.mng-top__burger:hover { border-color: var(--sb-primary); color: var(--sb-primary); }
/* Backdrop del drawer: inattivo su desktop, attivo solo sotto il breakpoint. */
.mng-side__backdrop { display: none; }
.mng-top__property-name {
  font-family: var(--sb-display); font-weight: 700; font-size: 15px; color: var(--sb-dark);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mng-top__status { display: inline-flex; flex: none; align-items: center; gap: 7px; background: var(--sb-accent-tint); color: var(--sb-green-fg); font-size: var(--sb-fs-meta); font-weight: 600; padding: 5px 11px; border-radius: var(--sb-r-pill); }
.mng-top__user { display: flex; flex: none; align-items: center; gap: 10px; }
.mng-top__avatar {
  display: flex; flex: none; width: 32px; height: 32px; border-radius: 50%;
  align-items: center; justify-content: center; background: var(--sb-grad);
  color: #fff; font-weight: 700; font-size: 13px;
}
/* §E17 — nel pannello l'identità è un link al proprio profilo: deve restare
   identica all'etichetta del back-office, che link non è. The link wraps the
   avatar too, so that the avatar alone can carry it on a phone. */
.mng-top__me-menu { position: relative; display: inline-flex; flex: none; min-width: 0; }
.mng-top__me {
  display: flex; align-items: center; gap: 10px; min-width: 0; color: inherit;
  list-style: none; cursor: pointer; padding: 4px 6px; border-radius: var(--sb-r-btn);
}
.mng-top__me::-webkit-details-marker { display: none; }
.mng-top__me:hover, .mng-top__me-menu[open] > .mng-top__me { background: var(--sb-bg-2); }
.mng-top__me:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: 1px; }
.mng-top__me .mng-top__caret { color: var(--sb-muted); flex: none; }
.mng-top__identity { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
/* Positioned by dropdown_controller (fixed, clamped to the viewport), like the
   alert centre: no `right: 0`, which would fight the inline `left`.
   TWO classes, and it is the whole rule: the base `.mng-top__menu` further down
   sets `position: absolute` at the same weight and won, so the controller's
   viewport coordinates were applied relative to the avatar and the menu opened
   off the right edge of the screen — it looked as if it did not open at all. */
.mng-top__menu.mng-top__menu--fixed { position: fixed; top: 0; left: 0; margin: 0; max-width: calc(100vw - 16px); }
.mng-top__menu.mng-top__menu--user { position: fixed; top: 0; left: 0; margin: 0; min-width: 240px; max-width: calc(100vw - 16px); }
.mng-top__menu-sep { height: 0; margin: 6px 4px; border-top: 1px solid var(--sb-line-2); }
.mng-top__menu-head {
  padding: 6px 10px 2px; font-family: var(--sb-sans); font-size: var(--sb-fs-micro); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--sb-muted);
}
a.mng-top__menu-item { text-decoration: none; color: var(--sb-ink); }
.mng-top__menu-item.mng-top__signout { display: flex; align-items: center; gap: 8px; }
/* max-width: senza nome/cognome full_name ricade sull'email, che sfonderebbe. */
.mng-top__name { font-weight: 600; font-size: 13px; color: var(--sb-dark); max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mng-top__role { font-size: 11px; color: var(--sb-muted); }
.mng-top__switch {
  display: inline-flex; align-items: center; gap: 7px; margin-left: 6px;
  background: var(--sb-bg-2); border: 1px solid var(--sb-line-ctl);
  color: var(--sb-ink); border-radius: var(--sb-r-btn); padding: 7px 12px;
  font-weight: 600; font-size: 13px; text-decoration: none; white-space: nowrap;
}
.mng-top__switch:hover { background: var(--sb-line-2); }
/* Sign-out is the last entry of the user menu now, not a button of its own in
   the bar: it takes the look of the other entries (.mng-top__menu-item). */

/* content: la .panel diventa l'area contenuto a tutta larghezza */
.mng-main .panel { max-width: none; margin: 0; padding: 0 0 40px; }
/* Nel pannello la colonna contenuto è già la pagina: un secondo tetto a 1120px
   lasciava mezzo schermo vuoto a destra di ogni elenco, e le colonne strette
   dentro. Il cap resta fuori di qui, dove `.panel` è una colonna centrata. */
.mng-main .table-card, .mng-main .dtable__note { max-width: none; }
.mng-main .panel__header {
  background: #fff; border: none; border-bottom: 1px solid var(--sb-line); border-radius: 0;
  box-shadow: none; padding: 22px var(--sb-sp-gutter); margin: 0 0 24px; position: sticky; top: var(--mng-top-h); z-index: 10;
}
/* Le <dialog> sono figlie della .panel ma vivono nel top layer: i margini di
   contenuto qui sotto sovrascriverebbero il `margin: auto` con cui il browser
   le centra, incollandole a sinistra. */
.mng-main .panel > *:not(.panel__header):not(dialog) { margin-left: var(--sb-sp-gutter); margin-right: var(--sb-sp-gutter); }
.widget-toc { margin-bottom: var(--sb-sp-4); }
.mng-main .panel__eyebrow { color: var(--sb-muted); }
/* A header with a topline is TWO rows, and the 24px `gap` is the one meant for
   two blocks side by side — as vertical rhythm it reads as a gulf. Scoped under
   `.mng-main` on purpose: at equal specificity the override above would win on
   source order. */
.mng-main .panel__header:has(.panel__topline) { row-gap: var(--sb-sp-3); }

/* An anchor inside the panel lands under TWO sticky bands — the property bar
   (--mng-top-h) and the page header — and without this the target stops exactly
   behind them: you jump to a section and it looks as though nothing happened.
   Generous on purpose: a few pixels of air above the target go unnoticed, a
   covered target does not. */
.mng-main .panel [id] { scroll-margin-top: calc(var(--mng-top-h) + 110px); }
/* The topline adds a row to that sticky header, so the offset has to follow it
   or the target lands behind the very band this rule exists to clear. */
.mng-main .panel:has(.panel__topline) [id] {
  scroll-margin-top: calc(var(--mng-top-h) + 150px);
}

/* Schermi bassi (13"/notebook con molte toolbar): comprime il capo della sidebar
   così la nav resta quasi tutta visibile senza scrollare. */
@media (max-height: 800px) {
  .mng-side { padding: 14px 16px; }
  .mng-side__brand { padding: 2px 4px 14px; }
  .mng-side__plate { padding: 9px 12px; }
  .mng-nav__item { padding: 9px 12px; }
}

/* Sotto gli 860px (mobile / tablet in verticale) la sidebar diventa un drawer
   off-canvas: nascosto di default, apribile con l'hamburger nella topbar. */
@media (max-width: 860px) {
  .mng-top__burger { display: inline-flex; }

  .mng-side {
    position: fixed; top: 0; left: 0; z-index: 60;
    width: 264px; max-width: 84vw; height: 100vh; height: 100dvh;
    transform: translateX(-100%); transition: transform .25s ease, visibility .25s ease;
    box-shadow: 0 0 40px rgba(0,0,0,.45);
    /* Off screen is not gone: without this, Tab walked through the links of a
       closed menu. `visibility` waits for the slide to end before hiding. */
    visibility: hidden;
  }
  .mng-shell--nav-open .mng-side { transform: translateX(0); visibility: visible; }

  .mng-side__backdrop {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(20, 24, 28, .55);
    opacity: 0; visibility: hidden; transition: opacity .25s ease, visibility .25s ease;
  }
  .mng-shell--nav-open .mng-side__backdrop { opacity: 1; visibility: visible; }

  /* On a phone the topbar, a sticky page header and the sticky action bar took
     more than half the screen, and the column kept 30px a side while the topbar
     had 16: two edges that did not line up. One gutter for topbar, header,
     banners and content, and the page header scrolls away with the page — the
     topbar and the save bar are what has to stay in reach. */
  .mng-body { --sb-sp-gutter: var(--sb-sp-4); }
  .mng-main .panel__header { position: static; padding-block: 18px; }
  /* Name and role leave the screen but not the link's accessible name, and the
     avatar becomes a 40px thumb target: it is the way to profile, password and
     e-mail on a phone. */
  .mng-top__identity {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
  .mng-top__me { justify-content: center; min-width: 40px; min-height: 40px; padding: 0; }
  .mng-top__me .mng-top__caret { display: none; }
  .mng-top__switch-label { display: none; }
  .mng-top__switch { padding: 7px 9px; }
}

/* In forced colours (Windows High Contrast) every fill is replaced, and the
   active voice, the selected chip and the current tab were told by a fill
   alone: they became identical to the others. An outline survives. */
@media (forced-colors: active) {
  .mng-nav__item--active, .chip--active, .chip-group__item.is-on,
  .panel__tab--active, .mng-top__menu-item.is-current {
    outline: 2px solid CanvasText; outline-offset: -2px;
  }
}

/* Blocca lo scroll della pagina mentre il drawer è aperto. */
.mng-body--nav-open { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .mng-side, .mng-side__backdrop { transition: none; }
  .mng-nav__badge--pulse, .req-card--fresh { animation: none; }
}

/* --- App ospite StayBell --------------------------------------------------- */
.g-body { background: var(--sb-bg); }
.g-app { padding: 0 0 24px; background: var(--sb-bg); }

/* app bar + hero gradiente (home) */
.g-topbar {
  background: var(--sb-grad); color: #fff; border-radius: 0; box-shadow: none;
  padding: 16px var(--g-gutter) 14px; margin: 0; position: sticky; top: 0; z-index: 30;
}

/* La topbar qui sopra è `sticky`, quindi un'ancora atterra ESATTAMENTE sotto la
   barra e il titolo della sezione resta coperto: è il caso di "Wi-Fi" toccato
   dalla home, che porta a /contents#wifi. `--g-topbar-h` è la barra nella sua
   versione più alta (con il badge camera); i template che la ridisegnano la
   ricalibrano nel proprio foglio. */
.g-body { --g-topbar-h: 104px; }
.g-main .g-section[id] { scroll-margin-top: calc(var(--g-topbar-h) + 12px); }
.g-topbar__brand { color: #fff; font-family: var(--sb-display); font-weight: 700; }
.g-topbar__room { background: rgba(255,255,255,.16); color: #fff; }
.g-lang__current { background: rgba(255,255,255,.16); border: none; color: #fff; }
/* .g-lang__code hardcodes the brand blue, and the same class serves both the
   dropdown items (white background, fine) and the pill on the gradient topbar,
   where an indigo label is unreadable. Scope the override to the pill. */
.g-lang__current .g-lang__code { color: inherit; }

.g-roombadge {
  background: var(--sb-dark); color: #fff; box-shadow: 0 8px 20px rgba(13,11,51,.4);
  border-radius: 11px; padding: 8px 14px; font-weight: 600;
}

/* le sezioni della home tornano su sfondo chiaro con padding */
.g-main { padding: 0 var(--g-gutter); }

/* CTA assistente */
.g-assistant { border-radius: var(--sb-r-card); box-shadow: var(--sb-shadow); }
.g-assistant__icon { background: var(--sb-grad); color: #fff; border-radius: 14px; }
.g-assistant__title { font-family: var(--sb-display); }

/* card CTA (richiesta/contenuti) */
.g-cta__icon { background: var(--sb-chip); color: var(--sb-primary-2); }
.g-cta__title { font-family: var(--sb-display); }
.g-cta__arrow { color: var(--sb-primary); }

/* sezioni / titoli */
.g-section__title { font-family: var(--sb-display); color: var(--sb-ink); }
.g-link { color: var(--sb-primary-2); font-weight: 600; }

/* card servizio */
.g-service { border-radius: 14px; border: 1px solid var(--sb-line); box-shadow: var(--sb-shadow); }
.g-service:hover { border-color: var(--sb-primary); box-shadow: var(--sb-shadow-h); transform: translateY(-2px); }
.g-service__media { background: var(--sb-chip); }
.g-service__placeholder { color: var(--sb-primary); }
.g-service__cat { color: var(--sb-primary-2); }
.g-service__name { font-family: var(--sb-display); }
.g-service__price { color: var(--sb-primary-2); }
/* These two sit on top of the card media. A brand-blue plate on the now deep
   indigo placeholder disappears; a white plate reads over any image beneath. */
.g-badge--featured { background: rgba(255,255,255,.94); color: var(--sb-primary-2); }
.g-badge--popular { background: var(--sb-chip); color: var(--sb-primary-2); }

/* dettaglio + footer */
.g-detail__title { font-family: var(--sb-display); }
.g-detail__media { background: repeating-linear-gradient(135deg,#E7EAFA,#E7EAFA 11px,#DCE1F7 11px,#DCE1F7 22px); }
.g-footer__note { color: var(--sb-muted-2); }

/* status richiesta / conferma */
.g-confirm__check { background: #E7F5EC; color: var(--sb-green-fg); }
.g-status--received { background: var(--sb-chip); color: var(--sb-primary-2); }
.g-status--done { background: #E7F5EC; color: var(--sb-green-fg); }

/* --- Pannello: rifiniture StayBell (KPI, board, card richiesta) ------------ */
.board-counter { border-radius: var(--sb-r-card); border: 1px solid var(--sb-line); box-shadow: none; }
/* The "new" counter in the status badge's amber, like its column mark. */
.board-counter:first-child { border-left: 4px solid var(--sb-warn-fg); }
.board-counter:first-child .board-counter__num { color: var(--sb-warn-fg); }
.board-counter:nth-child(3) { border-left: 4px solid var(--sb-green); }
.board-counter:nth-child(3) .board-counter__num { color: var(--sb-green); }
.board-counter--time .board-counter__num { color: var(--sb-ink); }
.board-counter__num { font-family: var(--sb-display); }

.board-col { padding: 14px; }
.board-col__title { font-family: var(--sb-display); text-transform: none; letter-spacing: 0; border-left: none; padding-left: 0; display: flex; align-items: center; gap: 9px; }
.board-col__title::before { content: ""; width: 9px; height: 9px; border-radius: 50%; background: var(--sb-primary); }
/* The column marks speak the status badge's tones too: amber for the new ones. */
.board-col__title--new::before { background: var(--sb-warn-fg); }
.board-col__title--progress::before { background: var(--sb-primary); }
.board-col__title--done::before { background: var(--sb-green); }

.req-card { border-radius: var(--sb-r-card); box-shadow: none; }
.req-card__room { font-family: var(--sb-display); }
.req-badge--ai { background: var(--sb-warn-bg); color: var(--sb-warn-fg); }

/* KPI/statistiche numeri in Sora */
.stat-counter__num, .panel__title, .panel__section-title { font-family: var(--sb-display); }
/* Il riempimento delle barre NON è più il gradiente unico: ogni barra porta la
   tinta della sua categoria e ogni colonna quella della sua fascia oraria
   (blocco §E6, `.stat-seg--*` / `.stat-band--*`). */


/* liste gestione: bordo/hover StayBell */
.msvc-row, .mstay-row, .muser-row { background: #fff; }
.msvc-row:hover, .mstay-row:hover, .muser-row:hover { background: var(--sb-bg); }
.access-code { background: var(--sb-dark); }
.dropzone { border-color: #C9CDE2; background: #FAFBFE; border-radius: 14px; }

/* --- Categorie ospite + prezzo persone (StayBell) -------------------------- */
.g-cat {
  display: flex; flex-direction: column; background: #fff;
  border: 1px solid var(--sb-line); border-radius: 14px; overflow: hidden;
  text-decoration: none; color: inherit; box-shadow: var(--sb-shadow);
  transition: transform .12s ease, box-shadow .12s ease;
}
.g-cat:hover { transform: translateY(-2px); box-shadow: var(--sb-shadow-h); border-color: var(--sb-primary); }
/* min-height/overflow: vedi .g-service__media — il riquadro impone il ritaglio. */
.g-cat__media { aspect-ratio: 5 / 3; background: var(--sb-chip); min-height: 0; overflow: hidden; }
.g-cat__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.g-cat__body { padding: 12px 14px 14px; }
.g-cat__name { font-family: var(--sb-display); margin: 0; font-size: 1rem; color: var(--sb-ink); }
.g-cat__desc { margin: 4px 0 0; font-size: 0.82rem; color: var(--sb-muted); line-height: 1.35; }
.g-cat__count { display: inline-block; margin-top: 8px; font-size: 0.75rem; font-weight: 700; color: var(--sb-primary-2); }

.g-cat-head { margin-bottom: 16px; }
.g-cat-head__media { aspect-ratio: 16 / 8; border-radius: var(--sb-r-card); overflow: hidden; min-height: 0; margin-bottom: 14px; background: var(--sb-chip); }
.g-cat-head__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* §E58 — .svc-cutoff is the same composition as .price-row (a control plus its
   qualifier on one line), so it extends the rule instead of repeating it.
   `flex-wrap` is new for both: inside .form__cards-col the container query
   stacks the columns, and a time input beside "di 2 giorni prima" does not fit
   on one line down there. */
.price-row, .svc-cutoff { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.price-row .form__input--sm, .svc-cutoff .form__input--sm { max-width: 120px; }
.price-row select.form__input, .svc-cutoff select.form__input { width: auto; }

.mstay-total {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 12px; padding: 12px 14px; border-radius: 12px;
  background: var(--sb-bg); border: 1px solid var(--sb-line);
  font-family: var(--sb-display); font-weight: 700; color: var(--sb-ink);
}
.mstay-total strong { color: var(--sb-primary-2); font-size: 1.1rem; }
.mstay-row__cost { color: var(--sb-primary-2); }

/* --- §E9 Chat assistente (Guest::) ----------------------------------------- */
.g-chat { display: flex; flex-direction: column; gap: 12px; }
.g-chat__head { padding: 4px 0; }
.g-chat__title { margin: 0; font-family: var(--sb-display); font-size: 1.35rem; color: var(--c-ink); }
.g-chat__sub { margin: 2px 0 0; font-size: 0.85rem; color: var(--c-ink-soft); }
/* Avviso AI: deve leggersi prima di scrivere, non dopo. Sta sopra il log e non
   dentro una bolla — una bolla lo farebbe passare per un messaggio, cioè per
   qualcosa che scorre via al primo scroll. */
.g-chat__notice {
  margin: 8px 0 0; padding: 8px 10px;
  font-size: 0.78rem; line-height: 1.35; color: var(--c-ink-soft);
  background: var(--sb-bg-2, #f4f9ff); border-left: 3px solid var(--sb-line, #e2e4f0);
  border-radius: 8px;
}
/* A scroll container on both axes (`overflow-y: auto` computes `overflow-x` to
   auto as well), so it is a containing block like every guest scroller — see
   .g-sectionnav__list. */
.g-chat__log {
  position: relative;
  display: flex; flex-direction: column; gap: 10px;
  max-height: 58vh; overflow-y: auto; padding: 12px;
  background: var(--sb-bg-2, #f4f9ff); border-radius: var(--sb-r-card, 16px);
}
.g-msg { display: flex; flex-direction: column; max-width: 86%; }
.g-msg--user { align-self: flex-end; align-items: flex-end; }
.g-msg--assistant { align-self: flex-start; align-items: flex-start; }
/* `overflow-wrap`: a link in an answer, or one the guest pastes, would
   otherwise scroll the log sideways on a narrow phone. */
.g-msg__bubble {
  padding: 10px 14px; border-radius: 16px; line-height: 1.45; overflow-wrap: anywhere;
  background: #fff; color: var(--c-ink); box-shadow: var(--sb-shadow, 0 2px 8px rgba(14,27,42,.08));
}
.g-msg__bubble p { margin: 0 0 6px; }
.g-msg__bubble p:last-child { margin-bottom: 0; }
.g-msg--user .g-msg__bubble { background: var(--sb-grad, #17008E); color: #fff; }
.g-msg__bubble--error { background: #FDECEC; color: var(--sb-danger); }
.g-msg__meta { margin: 3px 4px 0; font-size: 0.7rem; color: var(--c-ink-soft); }
.g-msg__flag { color: var(--sb-warn-fg); font-weight: 600; }
.g-typing__bubble { display: inline-flex; gap: 4px; align-items: center; padding: 12px 14px; }
.g-typing__dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--c-ink-soft);
  animation: g-typing 1.2s infinite ease-in-out;
}
.g-typing__dot:nth-child(2) { animation-delay: .15s; }
.g-typing__dot:nth-child(3) { animation-delay: .3s; }
@keyframes g-typing { 0%, 60%, 100% { opacity: .3; transform: translateY(0); } 30% { opacity: 1; transform: translateY(-3px); } }
@media (prefers-reduced-motion: reduce) { .g-typing__dot { animation: none; opacity: .6; } }
.g-chat__form { display: flex; gap: 8px; align-items: center; }
/* `min-width: 0` is what lets the field shrink. A flex item never goes below
   its automatic minimum, and an <input>'s is its intrinsic width (`size`, 20
   characters): on a column narrower than ~330px the field held its width and
   pushed the send button past the edge, where nobody could press it. */
.g-chat__input {
  flex: 1; min-width: 0; padding: 12px 14px; border-radius: 999px;
  border: 1px solid var(--sb-line, #e6edf3); background: #fff; font-size: 1rem;
}
.g-chat__send { flex: none; border-radius: 999px; white-space: nowrap; }

/* Variante d'attenzione del badge base (usata dai gap di contenuto in
   Statistiche). Le regole del thread conversazioni che stavano qui sono state
   riscritte in fondo al file — vedi §E9.5. */
.badge--warn { background: var(--sb-warn-bg); color: var(--sb-warn-fg); }

/* --- §E10 Formattazione risposte chat + fonti ------------------------------ */
.g-msg__bubble ul, .g-msg__bubble ol { margin: 6px 0; padding-left: 20px; }
.g-msg__list li { margin: 3px 0; line-height: 1.45; }
/* §E23/UX — niente sottolineato nelle risposte dell'assistente: su ogni
   servizio proposto spezzava la lettura. Al suo posto il grassetto, perché
   serve un segnale che NON sia il solo colore — link e testo corrente sono due
   blu scuri a 1.15:1 l'uno dall'altro (WCAG 1.4.1). Torna su hover e focus.
   `.g-offer` è una CARD, non un link di testo: dichiarava già
   `text-decoration: none`, ma questa regola — più specifica — glielo
   sovrascriveva, ed è per questo che nome, prezzo e "Richiedi" risultavano
   sottolineati tutti insieme. */
.g-msg__bubble a { color: var(--c-blue-700); text-decoration: none; }
.g-msg__bubble a:not(.g-offer) { font-weight: 600; }
.g-msg__bubble a:not(.g-offer):hover,
.g-msg__bubble a:not(.g-offer):focus-visible { text-decoration: underline; }
.g-msg--user .g-msg__bubble a { color: #fff; }
.g-msg__bubble strong { font-weight: 700; }
.g-msg__sources { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--sb-line, #e6edf3); }
.g-msg__sources-title {
  margin: 0 0 5px; font-size: 0.7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--c-ink-soft);
}
.g-msg__source-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.g-msg__source {
  display: inline-block; max-width: 100%; padding: 4px 10px; border-radius: 999px;
  background: var(--sb-bg-2, #f4f9ff); color: var(--c-blue-700);
  font-size: 0.78rem; text-decoration: none; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.g-msg__source:hover { text-decoration: underline; }

/* --- Azione secondaria nella topbar ospite --------------------------------- */
.g-topbar__actions { display: flex; align-items: center; gap: 8px; }
.g-topbar__action {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .16); color: #fff;
  font-size: .8rem; font-weight: 600; text-decoration: none; white-space: nowrap;
}
.g-topbar__action:hover { background: rgba(255, 255, 255, .26); }
/* Su schermi stretti resta la sola icona: l'azione non deve competere col brand. */
@media (max-width: 30rem) { .g-topbar__action-label { display: none; } }

/* --- FAQ nella sezione informazioni ---------------------------------------- */
.g-faqs { display: flex; flex-direction: column; gap: 8px; }
.g-faq {
  background: #fff; border: 1px solid var(--sb-line, #e6edf3);
  border-radius: var(--sb-r-card); padding: 12px 14px;
}
.g-faq__q { cursor: pointer; font-weight: 700; color: var(--c-ink); list-style: none; }
.g-faq__q::-webkit-details-marker { display: none; }
.g-faq__q::after { content: "+"; float: right; color: var(--c-ink-soft); font-weight: 400; }
.g-faq[open] .g-faq__q::after { content: "−"; }
.g-faq__a { margin-top: 8px; color: var(--c-ink-soft); line-height: 1.5; }
.g-faq__a p { margin: 0 0 6px; }
.g-faq__a p:last-child { margin-bottom: 0; }

/* --- Wi-Fi nella sezione informazioni --------------------------------------
   Una card per rete (camera e/o struttura). Il valore è in monospace: si copia
   col pulsante, ma va anche letto e digitato a mano dal dispositivo. */
.g-wifi { padding: 12px 14px; }
.g-wifi + .g-wifi { margin-top: 10px; }
.g-wifi__label {
  margin: 0 0 8px; font-size: .82rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--c-ink-soft);
}
.g-wifi__row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--sb-line, #e6edf3);
}
.g-wifi__row:first-of-type { border-top: none; padding-top: 0; }
.g-wifi__field { color: var(--c-ink-soft); min-width: 5.5rem; }
/* Il valore prende lo spazio residuo e va a capo: SSID e password possono
   essere lunghi e non devono far scorrere la pagina in orizzontale. */
.g-wifi__value {
  flex: 1 1 8rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700; color: var(--c-ink); word-break: break-all;
}
.g-wifi__copy { flex: none; }

/* --- Contatti diretti nella sezione informazioni ---------------------------
   UNA card: il telefono come riga cliccabile, WhatsApp come pulsante sotto.
   Il numero WhatsApp non si mostra: all'ospite serve aprire la chat, non
   leggerlo. */
.g-section__sub { margin: -6px 0 10px; color: var(--c-ink-soft); font-size: 0.9rem; }
.g-contacts { display: flex; flex-direction: column; gap: 12px; padding: 14px; }
/* Telefono: icona sopra, numero sotto, tutto centrato e tutto cliccabile. */
.g-contact {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 6px 0 2px; text-decoration: none; color: inherit; text-align: center;
}
.g-contact__icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: 12px;
  background: var(--sb-chip); color: var(--c-blue-500);
}
.g-contact__value {
  font-weight: 700; font-size: 1.1rem; color: var(--c-ink); overflow-wrap: anywhere;
}

/* Pulsante WhatsApp: verde del canale, l'unico elemento fuori palette dell'app
   ospite — è il segnale che lo rende riconoscibile senza leggere. Il verde è
   quello scuro del marchio: su #25D366 il testo bianco non sarebbe leggibile.
   Nessun separatore sopra: lo darebbe dentro l'area verde, basta il gap. */
.g-wa-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 46px; padding: 12px 16px; border-radius: 12px;
  background: #128C4A; color: #fff; font-weight: 700; text-decoration: none;
}
.g-wa-btn:hover { background: #0f7a40; }

/* --- §E11 KPI tile (Insight / dashboard) -----------------------------------
   Il colore sta sull'ICONA, non sul numero: il valore resta in inchiostro
   (leggibilità e coerenza tipografica). Ogni tile ha icona + etichetta, quindi
   il significato non è mai affidato al solo colore. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sb-sp-4);
  /* Oltre questa soglia le tile diventerebbero enormi e illeggibili. */
  max-width: 1120px;
  /* Deliberately no margin: .mconv-kpis and .dash-kpis rely on its
     absence. Inside a card the gap above and below comes from
     .panel__section--flow, which is where that decision belongs. */
}
@media (min-width: 1100px) { .kpi-grid { grid-template-columns: repeat(4, 1fr); } }
/* §E47 — le CINQUE mattonelle della dashboard. Deve stare DOPO .kpi-grid: la
   specificità è la stessa (una classe), quindi decide l'ordine — messa prima,
   .kpi-grid vinceva con `repeat(4,1fr)` e `max-width`, e la quinta andava a capo
   da sola. Il margine sotto sta qui perché .kpi-grid non ne ha per scelta, ed è
   lo stesso ritmo fra le card di .dash-body.
   Le soglie sono sul VIEWPORT ma la griglia vive nella colonna contenuto, che è
   il viewport meno la sidebar (238px) e i gutter (2×30px): cinque tile da 190px
   chiedono ~1310px di finestra, non 1100. Sotto quella soglia tre e due, che
   legge come una scelta, invece di quattro più una spaiata. */
.dash-kpis { max-width: none; margin-bottom: var(--sb-sp-5); }
@media (min-width: 900px) { .dash-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1310px) { .dash-kpis { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

.kpi {
  display: flex; align-items: center; gap: 12px;
  padding: 16px; background: #fff;
  border: 1px solid var(--sb-line, #e2eaf0);
  border-radius: var(--sb-r-card);
  box-shadow: var(--sb-shadow, 0 2px 8px rgba(14, 27, 42, .06));
  border-left: 4px solid var(--kpi-color, var(--sb-primary, #002F91));
}
.kpi__icon {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: var(--sb-r-card);
  background: var(--kpi-tint, var(--sb-chip, #E7EAFA)); color: var(--kpi-color, var(--sb-primary-2, #17008E));
}
.kpi__body { min-width: 0; }
.kpi__value {
  margin: 0; font-family: var(--sb-display, inherit);
  font-size: var(--sb-fs-metric); font-weight: 700; line-height: 1.1; color: var(--c-ink);
}
.kpi__label { margin: 2px 0 0; font-size: var(--sb-fs-body-sm); color: var(--c-ink-soft); line-height: 1.3; }

/* Stato: blu = informativo, verde = va bene, ambra = richiede attenzione. */
.kpi--info { --kpi-color: var(--sb-primary-2); --kpi-tint: var(--sb-chip); }
.kpi--ok   { --kpi-color: var(--sb-green-fg); --kpi-tint: #E7F6EE; }
.kpi--warn { --kpi-color: var(--sb-warn-fg); --kpi-tint: var(--sb-warn-bg); }

/* --- §E11 Extra proposti in chat: CTA di richiesta -------------------------- */
.g-msg__offers { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.g-offer {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 12px; border-radius: 12px; text-decoration: none;
  background: var(--sb-bg-2, #eef3f7);
  border: 1px solid var(--sb-line, #e2eaf0);
}
.g-offer:hover { border-color: var(--sb-primary, #002F91); }
.g-offer__body { display: flex; flex-direction: column; min-width: 0; }
.g-offer__name { font-weight: 700; color: var(--c-ink); line-height: 1.3; }
.g-offer__price { font-size: .8rem; color: var(--c-ink-soft); }
.g-offer__cta {
  flex: 0 0 auto; padding: 6px 12px; border-radius: 999px;
  background: var(--sb-grad, #17008E); color: #fff;
  font-size: .8rem; font-weight: 700; white-space: nowrap;
}

/* --- Tabella dati (Manage::) ------------------------------------------------
   Mancava del tutto: le tabelle risultavano testo nudo. Il wrapper permette lo
   scorrimento orizzontale su schermi stretti senza far scrollare la pagina. */
.table-card {
  max-width: 1120px; margin-top: 16px;
  background: #fff; border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-card); box-shadow: none;
  /* auto and not hidden: a table wider than its column has to scroll, and
     `hidden` would clip the last columns with no way to reach them. */
  overflow-x: auto;
}
.data-table { width: 100%; border-collapse: collapse; font-size: var(--sb-fs-body); }
.data-table th, .data-table td { padding: 0 var(--sb-sp-3); text-align: left; white-space: nowrap; }
/* The head is not a grey band: the hairline is enough to detach it, and the
   solid fill stays with the service bands. */
.data-table thead th {
  height: 30px;
  background: #fff; border-bottom: 1px solid var(--sb-line-2);
  font-family: var(--sb-sans); font-size: var(--sb-fs-micro); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--sb-muted);
  /* A head wraps: kept on one line, «Importo per persona a notte» set the width
     of its column and pushed the menu out of the card. Wrapped heads sit on the
     same baseline, the one closest to the rows. */
  white-space: normal; vertical-align: bottom; line-height: 1.3; padding-block: 8px;
}
.data-table tbody td {
  height: var(--sb-row); color: var(--sb-ink);
  /* --sb-line-2 e non un grigio più chiaro: su una riga in tinta (in ritardo, in
     modifica) un filetto più tenue sparisce, e le righe si incollano. */
  border-top: 1px solid var(--sb-line-2); vertical-align: middle;
}
.data-table tbody tr:first-child td { border-top: none; }
.data-table tbody tr:hover td { background: var(--sb-bg); }
/* Only the rows that really open something get the pointer: on all of them it
   would promise a click that does not happen. The selector is the controller's
   own value attribute, so the promise and the behaviour come from the same
   markup and cannot drift apart. */
.data-table tbody tr[data-row-link-url-value] { cursor: pointer; }

/* Inside a white section a .table-card is NOT a card — that would be a card in
   a card, which is the one thing the direction forbids. It stays the scroll
   container and nothing else, and the table runs to the edges of the section:
   the rows then take the whole width and their hairlines separate the whole
   row, instead of a framed block floating inside a frame. */
.panel__section:not(:has(> .form__row)):not(:has(> .form__grid)) > .table-card {
  margin: 0 calc(var(--sb-sp-5) * -1);
  width: auto;
  max-width: none;
  border: none;
  border-radius: 0;
  background: transparent;
}
/* The gutter the wrapper used to provide now belongs to the outer cells, so the
   text still lines up with the title above it. */
.panel__section > .table-card .data-table th:first-child,
.panel__section > .table-card .data-table td:first-child { padding-left: var(--sb-sp-5); }
.panel__section > .table-card .data-table th:last-child,
.panel__section > .table-card .data-table td:last-child { padding-right: var(--sb-sp-5); }
/* A table that IS the section keeps its frame — there it is the white surface,
   not something sitting on one. That is `.mng-main .panel > .table-card`, inset
   by the content gutter, and it is deliberately left alone. */

/* Codes in mono: that is what gets read character by character. A number cell
   is `.dtable__num`, the one class every table uses (the bookings list had an
   `.is-numeric` of its own, in mono, unlike the other forty). */
.data-table .is-code { font-family: var(--sb-mono); font-size: var(--sb-fs-body-sm); }
/* A code never breaks: "2026-\n0038" is two codes to whoever reads it. As the first
   cell it would otherwise wrap like a name, and take the 40% of `.dtable__name`. */
.data-table tbody td.is-code { white-space: nowrap; width: auto; }
/* Free text and lists (a guest, the rooms of a booking, a row of restriction
   chips) wrap. Every other cell is `nowrap`, so a long one pushed the last
   columns out of the card at 1440px — and the menu with them. The min-width
   keeps the wrap at the phrase, not at every word. */
.data-table td.dtable__wrap { white-space: normal; min-width: 6rem; }
/* Prima colonna = etichetta della riga; le altre sono numeri, allineati a destra. */
.data-table td:not(:first-child), .data-table th:not(:first-child) { text-align: right; }
.data-table td:first-child { font-weight: 600; white-space: normal; }

/* --- §E18 Reparti: tabella con editing inline ------------------------------ */
/* Variante non numerica di .data-table: qui le colonne sono etichette e
   controlli, non cifre, quindi si torna all'allineamento a sinistra. */
.data-table--form td:not(:first-child), .data-table--form th:not(:first-child) { text-align: left; }
.data-table--form td { vertical-align: middle; }
.dtable__name { width: 40%; }
/* Cella numerica dentro una tabella di testo: `data-table--form` riporta tutto
   a sinistra, ma le cifre si confrontano solo se incolonnate a destra. */
.dtable__num { text-align: right !important; font-variant-numeric: tabular-nums; }

/* §E45.4.2 — Riga del totale. Sta in un `tfoot` e non in una nota sotto la card
   per una ragione sola: così il numero cade SOTTO la colonna degli importi
   invece che accanto alla prima. Un totale che si deve cercare è un totale che
   nessuno verifica. Filo più marcato sopra e fondo pieno: si legge prima delle
   righe, che è l'ordine in cui lo si guarda. */
/* A full-bleed table that CLOSES its section leaves no white strip underneath:
   its foot — which carries a solid ground — already ends the block, and the
   section's padding added a 24px band of nothing below it. */
.panel__section:has(> .table-card:last-child) { padding-bottom: 0; }

/* The total is the figure you go looking for: it gets a row of its own, taller
   than a data row, and a larger size. With no height it took only the cells'
   horizontal padding and sat glued to the last row and to the card's edge. */
.data-table tfoot td {
  height: 54px;
  border-top: 2px solid var(--sb-line); background: var(--sb-bg-2);
  font-size: var(--sb-fs-head); font-weight: 700; color: var(--sb-ink);
}
.data-table tfoot .dtable__num { font-size: 1.05rem; font-weight: 700; }
.data-table tfoot .dtable__total-label { font-weight: 600; color: var(--sb-ink); }
.dtable__muted { color: var(--c-ink-soft); }
.dtable__empty { color: var(--c-ink-soft); font-weight: 400 !important; text-align: center !important; padding: 22px 16px; }
/* button_to emette un <form>: senza flex i pulsanti andrebbero a capo. Il flex
   sta su un wrapper INTERNO e non sul <td>: un table-cell con display non
   tabellare esce dal contesto della tabella e il browser gli costruisce attorno
   una cella anonima trasparente, che lascia una striscia bianca sotto la riga. */
.dtable__actions { width: 1%; } /* si stringe sui pulsanti: lo spazio va al nome */
.dtable__actions-inner { display: flex; gap: 6px; align-items: center; justify-content: flex-end; }
.dtable__actions-inner form { margin: 0; }
/* Elimina non disponibile (servizi collegati): resta leggibile e col motivo nel
   title, invece di sparire senza spiegazione. */
/* No opacity: it is TEXT that says why (2.15:1 at .55). The lock icon and the
   colour say it is off, like every other unavailable control. */
.dtable__locked {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 6px 10px; font-size: var(--sb-fs-body-sm); font-weight: 600;
  color: var(--sb-muted); cursor: not-allowed;
}
.dtable__note { margin-top: 10px; max-width: 1120px; } /* stessa larghezza di .table-card */
.dtable__row--editing td { background: var(--sb-chip, #eaf6fd); }
/* Muted on the green tint is 4.31:1: the dash and the notes take the ink. */
.dtable__row--editing .dtable__muted { color: var(--sb-ink); }
.dtable__row--editing .form__input { width: 100%; }
.dtable__error { margin: 6px 0 0; font-size: .8rem; font-weight: 500; color: var(--sb-danger, #c0392b); }

/* Etichetta per i soli screen reader (intestazioni e campi senza testo visibile). */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* --- §E12 Abbonamento: listino e avviso di stato --------------------------- */
.panel__section-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}

/* §E51 — the bookings-module chooser above the plan grid. It decides WHICH
   three plans are on screen, so it cannot read like the cadence toggle beside
   the title: a bare pair of buttons labelled "Senza / Con" left the reader
   guessing what it switched.

   Shaped after .structure-alert (icon, body, actions pushed right) so the two
   read as siblings, but on --sb-accent-tint: this box advertises the module,
   and the accent is the colour the product says yes with. NOT --sb-warn-bg,
   which is the same family as the amber banners and would read as a caution;
   and --sb-accent-tint rather than --sb-chip, which holds the identical value
   but means "chip". It is the tint "Piano attuale" also wears, and that is
   fine: a full-width band with a heading and buttons is not mistaken for a
   pill inside one card, and a second green would be a second accent.

   The white ghost buttons are what makes the unselected side legible on it. */
.plan-option {
  display: flex; align-items: center; gap: var(--sb-sp-3); flex-wrap: wrap;
  margin-top: var(--sb-sp-4); max-width: 1120px;
  padding: var(--sb-sp-3) var(--sb-sp-card);
  background: var(--sb-accent-tint); border: 1px solid var(--sb-chip-line);
  border-radius: var(--sb-r-card);
}
.plan-option__icon { flex: 0 0 auto; line-height: 0; color: var(--sb-accent-strong); }
.plan-option__body { flex: 1 1 280px; }
.plan-option__title {
  margin: 0; font-size: var(--sb-fs-head); font-weight: 700; color: var(--sb-accent-strong);
}
/* --sb-ink and NOT --sb-muted: muted grey is 4.83:1 on white, which is already
   the floor, and any tinted band drops it under 4.5:1 (4.31:1 here). What keeps
   this line secondary to the title is the size and the weight, not a paler
   grey — measured, not assumed. */
.plan-option__text {
  margin: var(--sb-sp-1) 0 0; font-size: var(--sb-fs-body-sm); color: var(--sb-ink);
}
/* `margin-left: auto` e non `justify-content`: il corpo cresce, i bottoni no. */
.plan-option__choice { flex: 0 0 auto; margin-left: auto; display: flex; gap: var(--sb-sp-2); }

.plan-grid {
  display: grid; gap: 16px; margin-top: 14px; max-width: 1120px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.plan-card {
  display: flex; flex-direction: column; gap: 8px; padding: 20px;
  background: #fff; border: 1px solid var(--sb-line, #e2eaf0);
  border-radius: var(--sb-r-card); box-shadow: var(--sb-shadow, 0 2px 8px rgba(14,27,42,.06));
}
/* Il piano attivo si riconosce dal bordo E dall'etichetta: mai dal solo colore. */
.plan-card--current { border-color: var(--sb-primary, #002F91); border-width: 2px; }
.plan-card__name { margin: 0; font-size: 1.05rem; font-weight: 700; color: var(--c-ink, #1a2733); }
.plan-card__price { margin: 0; font-size: 1.8rem; font-weight: 700; color: var(--c-ink, #1a2733); }
.plan-card__period { font-size: .9rem; font-weight: 500; color: var(--c-ink-soft, #4a5b6b); }
.plan-card__features { margin: 6px 0 auto; padding-left: 18px; color: var(--c-ink-soft, #4a5b6b); font-size: .9rem; }
.plan-card__features li { margin-bottom: 4px; }
.plan-card__badge {
  margin: 10px 0 0; padding: 8px; text-align: center; font-size: .85rem; font-weight: 600;
  color: var(--sb-primary-2, #17008E); background: var(--sb-chip, #E7EAFA); border-radius: 10px;
}
/* Piano non attivabile (troppe camere per il suo tetto): il motivo prende il
   posto del pulsante, così non si scopre il blocco dopo il click. */
.plan-card__note {
  margin: 10px 0 0; padding: 8px; text-align: center; font-size: .85rem;
  color: var(--c-ink-soft, #4a5b6b); background: var(--sb-bg, #f4f7fa); border-radius: 10px;
}
.btn--block { width: 100%; }

/* Avviso di stato abbonamento: icona + testo, non il solo colore. */
/* Left-aligned and not centred (UX review 2026-10): a centred banner of two
   lines starts each line in a different place, and the eye loses the second. */
.billing-banner {
  display: flex; align-items: flex-start; justify-content: flex-start; gap: 10px; margin-bottom: 16px;
  padding: 12px 14px; border-radius: var(--sb-r-card); font-size: var(--sb-fs-body-sm);
  border: 1px solid; background: #fff; text-align: left;
}
/* In the layouts the banner is a direct child of .mng-main, which carries no
   padding of its own — the 30px gutter comes from
   `.mng-main .panel > *`, and the banner sits outside the .panel. Without this
   a bordered, rounded box runs flush to both edges of the viewport. .flash in
   the same slot is full-bleed on purpose (margin: 0, centred text); this is
   not — it has a border and a radius, so it has to respect the gutter. */
.mng-main > .billing-banner { margin: var(--sb-sp-4) var(--sb-sp-gutter); }

/* §E49.3 — the strip of the discovery banner is rendered hidden and revealed by
   Stimulus, and .billing-banner above is `display: flex`: an author declaration
   beats the user-agent [hidden] rule, so without this the strip would be visible to
   everyone, always, JavaScript or not. Same lesson as .mng-nav__badge[hidden] and
   .wtour__step[hidden]. */
.billing-banner[hidden] { display: none; }

.billing-banner__text { margin: 0; }
.billing-banner__link { font-weight: 600; color: inherit; }
.billing-banner--info  { border-color: #bcdcf5; background: #eef7fd; color: #0f5c85; }
.billing-banner--warn  { border-color: #f0dcb8; background: #fdf6ea; color: #7d5210; }
.billing-banner--error { border-color: #f0c4c1; background: #fdeeed; color: #8c2b25; }
/* §E17 — il rinvio della mail di conferma è un POST, quindi un form: va reso
   inline come gli altri link del banner. */
.billing-banner__form { display: inline; }

/* §E49.3 — discovery strip: the only banner with two buttons, so it needs a row of
   actions of its own. Text left, actions right, stacked when there is no room. */
.push-strip { flex-wrap: wrap; justify-content: space-between; text-align: left; }
.push-strip__body { flex: 1 1 260px; }
.push-strip__title { margin: 0; font-weight: 600; }
.push-strip__note { margin: var(--sb-sp-1) 0 0; font-size: var(--sb-fs-body-sm); opacity: .85; }
.push-strip__actions { display: flex; flex: 0 0 auto; align-items: center; gap: var(--sb-sp-3); }
.billing-banner__link--button {
  font-weight: 600; color: inherit; background: none; border: 0; padding: 0;
  font-size: inherit; font-family: inherit; text-decoration: underline; cursor: pointer;
}

/* §E16 — avviso in evidenza sulla dashboard quando la struttura non è attiva:
   icona + testo + CTA per attivare/riattivare l'abbonamento. */
.structure-alert {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin: 0 0 22px; padding: 16px 20px; border-radius: 16px; border: 1px solid;
}
.structure-alert__icon { flex: 0 0 auto; line-height: 0; }
.structure-alert__body { flex: 1 1 260px; }
.structure-alert__title { margin: 0 0 4px; font-size: 1.05rem; font-weight: 700; }
.structure-alert__text { margin: 0; font-size: .93rem; }
.structure-alert__note { margin: 0; font-size: .88rem; font-weight: 600; }
.structure-alert__actions { flex: 0 0 auto; margin-left: auto; }
.structure-alert--warn   { border-color: #f0dcb8; background: #fdf6ea; color: #7d5210; }
.structure-alert--danger { border-color: #f0c4c1; background: #fdeeed; color: #8c2b25; }

/* --- §E13 Contatore di consumo del ciclo ---------------------------------- */
.usage-bar { max-width: 1120px; margin: 18px 0 6px; }
.usage-bar__head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; margin-bottom: 6px; font-size: .9rem;
}
.usage-bar__label { font-weight: 600; color: var(--c-ink, #1a2733); }
.usage-bar__figures { color: var(--c-ink-soft, #4a5b6b); font-variant-numeric: tabular-nums; }
.usage-bar__track {
  height: 10px; border-radius: 999px; overflow: hidden;
  background: var(--sb-bg-2, #eef3f7); border: 1px solid var(--sb-line, #e2eaf0);
}
.usage-bar__fill { height: 100%; width: var(--fill, 0%); background: var(--sb-primary, #002F91); }
/* Oltre la quota inclusa il riempimento cambia tono: la cifra accanto resta
   comunque scritta, il colore non è mai l'unico segnale. */
.usage-bar__fill--over { background: var(--sb-warn-fg, #92590F); }

/* --- §E14 Back-office di piattaforma --------------------------------------- */
.adm-badge {
  display: inline-block; padding: var(--sb-sp-2) var(--sb-sp-3); border-radius: 999px;
  background: var(--sb-primary-2, #17008E); color: #fff;
  font-size: var(--sb-fs-micro); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
/* The back-office brand plate stacks the product logo over the DevInterface
   badge: the logo says WHICH product, the badge says you are behind it. The
   base block is a row (logo beside name), hence the direction override — and
   the plate must stay stretched, or the logo's width: 100% collapses. */
.mng-side__brand--admin { flex-direction: column; align-items: stretch; gap: 10px; }
.mng-side__brand--admin .adm-badge { align-self: center; }
/* Etichette di stato: sempre accompagnate dal testo, mai solo colore.
   No margin-left here, and that is the point: a component never carries the
   gap its container owns. That margin was adding to .panel__meta's 12px gap
   (18px between an h2 and its qualifier) and, in a <td> where the tag is the
   only child, it was insetting the cell. Where a tag really does follow inline
   text the HTML newline already supplies a word space.
   font-family is explicit because a tag can sit inside an h2, which inherits
   Sora from the heading rule — a status label must stay in the text face. */
.tag {
  display: inline-block; padding: 2px 8px;
  border-radius: 999px; font-family: var(--sb-sans);
  font-size: var(--sb-fs-meta); font-weight: 600; white-space: nowrap;
}
.tag--ok    { background: #e4f4ec; color: var(--sb-green-fg, #147045); }
.tag--warn  { background: var(--sb-warn-bg, #FDF0DC); color: var(--sb-warn-fg, #92590F); }
.tag--loss  { background: #fdeeed; color: #8c2b25; }
.tag--info  { background: var(--sb-chip, #E7EAFA); color: var(--sb-primary-2, #17008E); }

/* Blocco di testo preformattato del back-office (il purge_report di un account,
   che è JSON su una riga sola). overflow-x è il punto della regola, non una
   rifinitura: .panel__section non ha overflow, quindi senza questo il <pre>
   sfonda la card invece di scorrere dentro di sé. */
.code-block {
  margin: 0;
  padding: var(--sb-sp-3);
  border-radius: var(--sb-r-btn);
  background: var(--sb-bg-2);
  border: 1px solid var(--sb-line);
  font-size: var(--sb-fs-meta);
  color: var(--sb-ink);
  overflow-x: auto;
}

/* Form in linea del back-office: campi affiancati su desktop, impilati sotto. */
.form--inline { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sb-sp-4); }
.form--inline .form__row { margin: 0; }

/* §E16 — menu a tendina della topbar: selettore di struttura e selettore di lingua.
   Classi distinte perché sono due cose diverse, stessa meccanica di apertura. */
.mng-top__selector,
.mng-top__lang { position: relative; }
.mng-top__selector > summary,
.mng-top__lang > summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 6px; }
.mng-top__selector > summary::-webkit-details-marker,
.mng-top__lang > summary::-webkit-details-marker { display: none; }
.mng-top__caret { font-size: 0.7em; opacity: 0.7; }
.mng-top__menu { position: absolute; z-index: 30; margin: 8px 0 0; padding: 6px; min-width: 220px; list-style: none;
  background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); box-shadow: 0 10px 30px rgba(20, 24, 28, 0.14); }
.mng-top__menu li { margin: 0; }
.mng-top__menu-item { display: block; width: 100%; text-align: left; padding: 8px 10px; border: 0; background: transparent;
  border-radius: var(--sb-r-btn); cursor: pointer; font: inherit; color: var(--c-blue-900, #0b2447); }
.mng-top__menu-item:hover { background: var(--sb-bg-2); }
.mng-top__menu-item.is-current { font-weight: 700; }
/* §E70 — the current property is text, not a button: a tick says it, together
   with the weight (never the colour alone), and it does not light on hover as if
   it could be pressed. */
.mng-top__menu--properties .mng-top__menu-item.is-current {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sb-sp-2);
  cursor: default; background: transparent;
}
.mng-top__menu--properties .mng-top__menu-item.is-current .icon { flex: none; color: var(--sb-accent); }

/* §E49.3 — notifications bell in the topbar. A link to one's own profile, never a
   switch: it must not be able to fire a permission prompt by accident.
   40px like .mng-top__burger, because below 860px this is a thumb target.
   The [hidden] rule is not optional: inline-flex would otherwise win over it. */
/* §E62 — the geometry is shared with the alert centre's button, so the two sit in
   the bar as a pair by construction rather than by two lists of numbers kept in
   step by hand. What is push-specific (the [hidden], the [data-push-state] rules)
   stays on .mng-top__bell alone. */
.mng-top__bell,
.mng-top__alerts-btn {
  position: relative; display: inline-flex; flex: none; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--sb-r-btn);
  background: var(--sb-bg); border: 1px solid var(--sb-line-ctl); color: var(--sb-dark);
}
.mng-top__bell:hover,
.mng-top__alerts-btn:hover { border-color: var(--sb-primary); color: var(--sb-primary); }
.mng-top__bell[hidden] { display: none; }
/* The dot says "not active yet" without relying on colour alone: it is there only
   while the bell means something to do, and the label says which. */
.mng-top__bell-dot {
  position: absolute; top: 7px; right: 8px; width: 7px; height: 7px;
  border-radius: 50%; background: var(--sb-accent);
}
[data-push-state="active"] .mng-top__bell-dot { display: none; }
[data-push-state="active"] .mng-top__bell { color: var(--sb-green-fg); }

/* §E62 — the alert centre. The panel is driven by dropdown_controller, which is
   what closes it on an outside click and on Esc — the language selector next door
   does neither, and this one had to. That controller places a POSITION: FIXED
   panel by hand and clamps it to the viewport, so the menu overrides the
   `position: absolute` of .mng-top__menu and does NOT take --right: `right: 0`
   would fight the inline `left` the controller writes.
   The block is rendered only when there IS an alert, so there is no empty state
   and no [hidden] rule to get wrong. */
.mng-top__alerts { position: relative; display: inline-flex; flex: none; }
.mng-top__alerts > summary { list-style: none; cursor: pointer; }
.mng-top__alerts > summary::-webkit-details-marker { display: none; }

/* The count pill sits where the bell's dot sits, but grows with the number.
   White text only on the two tokens that carry it: --sb-accent (#F28C00) gives
   1.9:1 against white, which is why --sb-accent-strong exists. */
.mng-top__alerts-count {
  position: absolute; top: -4px; right: -4px; min-width: 17px; height: 17px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 9px; border: 2px solid #fff;
  font-family: var(--sb-sans); font-size: 0.63rem; font-weight: 700; line-height: 1; color: #fff;
}
.mng-top__alerts-count--info { background: var(--sb-primary); }
.mng-top__alerts-count--warn { background: var(--sb-accent-strong); }
.mng-top__alerts-count--error { background: var(--sb-danger, #C62828); }

/* §E62 — with alerts the button is LIT, and it is not decoration: it is rendered
   only when there IS something, so a neutral one would make a panel with work
   waiting look exactly like a quiet one until somebody spotted the pill.

   The tint is the level's own — the same pair the strip it replaced used — so the
   count pill is never the only thing carrying the state (§5). The :hover rules
   are repeated per level because the shared .mng-top__alerts-btn:hover is a class
   PLUS a pseudo-class, and would otherwise win over these and grey them out again
   under the pointer. */
.mng-top__alerts-btn--info { background: #eef7fd; border-color: #b6ddf2; color: #0f5c85; }
.mng-top__alerts-btn--info:hover { border-color: #0f5c85; color: #0f5c85; }
.mng-top__alerts-btn--warn {
  background: var(--sb-warn-bg); border-color: var(--sb-accent); color: var(--sb-warn-fg);
}
.mng-top__alerts-btn--warn:hover { border-color: var(--sb-warn-fg); color: var(--sb-warn-fg); }
.mng-top__alerts-btn--error { background: #fdeeed; border-color: #f0bdb9; color: #8c2b25; }
.mng-top__alerts-btn--error:hover { border-color: #8c2b25; color: #8c2b25; }

/* Wide enough that a two-line alert does not become a six-line column: at 320px
   "Conflitti con i canali" wrapped onto two lines on its own. The max-width is
   what keeps it on a phone — the controller clamps the LEFT edge, it does not
   shrink the panel. */
.mng-top__menu--alerts {
  position: fixed; top: 0; left: 0; margin: 0; padding: 0;
  min-width: 380px; max-width: calc(100vw - 16px);
}
.mng-top__alert { display: flex; gap: var(--sb-sp-3); padding: var(--sb-sp-3); }
.mng-top__alert + .mng-top__alert { border-top: 1px solid var(--sb-line-2, #eef3f7); }
.mng-top__alert .icon { flex: none; }
/* Each row keeps its icon: state is never told by colour alone (§5). The colour
   is what tells the rows apart at a glance once the icon has said which is which. */
.mng-top__alert--info .icon { color: var(--sb-primary); }
.mng-top__alert--warn .icon { color: var(--sb-warn-fg); }
.mng-top__alert--error .icon { color: var(--sb-danger, #C62828); }
.mng-top__alert-text { margin: 0 0 4px; font-size: var(--sb-fs-body-sm); color: var(--sb-ink); }
.mng-top__alert-link { font-size: var(--sb-fs-body-sm); font-weight: 600; color: var(--sb-primary); }

/* Selettore di lingua: stessa meccanica, ma sta in fondo alla fascia, quindi il
   pannello si apre allineato a destra per non uscire dal viewport. */
.mng-top__menu--right { right: 0; min-width: 160px; }
.mng-top__lang > summary { color: var(--sb-ink); padding: 6px 8px; border-radius: var(--sb-r-btn); }
.mng-top__lang > summary:hover { background: var(--sb-bg-2); }
/* Non tokenizzata di proposito: sta in shared/_topbar_user, quindi è CONDIVISA
   con Manage. Il suo 0.82rem va a --sb-fs-meta insieme agli altri selettori
   condivisi, non da sola (§6.1). */
.mng-top__lang-code { font-size: 0.82rem; font-weight: 600; letter-spacing: 0.04em; }

/* --- §E2 Camere: tabella, miniatura QR, menu download, modal ---------------
   La griglia di card non regge un hotel: con 40+ camere serviva un elenco
   compatto. Si riusa .table-card/.data-table--form dei reparti; qui sotto solo
   ciò che la tabella camere aggiunge. */
/* Il rail di aggiunta non c'è più: la form sta sopra l'elenco, a piena
   larghezza (§E48). Nel rail da 340px la tabella scendeva a 626px e in modifica
   ne chiedeva 908, quindi si scorreva di lato per arrivare a «Salva». */

/* La colonna del QR si stringe sulla miniatura: lo spazio va a numero e nome. */
.rooms-table__qr { width: 1%; }
.rooms-table td:first-child { font-weight: 400; }
.rooms-table .dtable__name { width: auto; font-weight: 700; }

/* Colonna Wi-Fi: sola lettura anche in modifica (si cambia dalla modal). */
.rooms-table__wifi { display: table-cell; min-width: 8rem; }

/* Miniatura: riconoscibile come QR, non leggibile — serve ad aprire la modal. */
.room-qr-thumb {
  display: inline-flex; width: 44px; height: 44px; padding: 3px;
  border: 1px solid var(--sb-line); border-radius: var(--sb-r-card); background: #fff;
  cursor: pointer;
}
.room-qr-thumb svg { width: 100%; height: 100%; display: block; }
button.room-qr-thumb:hover { border-color: var(--sb-accent); }
.room-qr-thumb--off { filter: grayscale(1); opacity: 0.5; cursor: default; }

/* Camera disattivata: riga in secondo piano, resta leggibile. */
.rooms-table__row--off td { color: var(--c-ink-soft); }

/* Segnaposto di un'azione non disponibile (riattivare a piano pieno): stesso
   ingombro di un .icon-btn, ma spento e non cliccabile. */
/* A button denied by the plan: same shape, visibly out of order, and the reason
   in `title` — §7 asks for disabled and explained, never hidden. The hover has
   to be neutralised at a weight above `.mng-body .btn--primary:hover`, or it
   would light up under a pointer that cannot press it. It is a <span>, so there
   is nothing to click either way. */
/* The look comes from `.btn[aria-disabled="true"]`, which every locked button
   carries: the opacity on top of it dimmed them twice, and the hover rule put
   back a Material shadow in a system without shadows. */
.btn--locked { cursor: not-allowed; }

.icon-btn--locked, .icon-btn--locked:hover {
  background: #F1F3F5; border-color: #D8DBE0; color: var(--sb-muted); cursor: not-allowed;
}

/* --- Menu a tendina generico (<details>) -----------------------------------
   Stessa meccanica dei menu della topbar, ma il pannello è in position:fixed:
   dentro .table-card (overflow-x: auto) un position:absolute verrebbe
   ritagliato. Le coordinate le calcola dropdown_controller. */
.dropdown { position: relative; display: inline-flex; }
.dropdown > summary { list-style: none; cursor: pointer; }
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown__menu {
  position: fixed; z-index: 40; top: 0; left: 0; min-width: 160px; padding: 6px;
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card);
  box-shadow: 0 10px 30px rgba(20, 24, 28, 0.14);
}
.dropdown__item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left; padding: 8px 10px;
  border: 0; background: transparent; border-radius: 8px; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--sb-ink); text-decoration: none;
  white-space: nowrap;
}
.dropdown__item:hover { background: var(--sb-chip); }
.dropdown__item .icon { flex: none; color: var(--sb-muted); }
/* button_to emette un <form>: qui il pulsante deve restare una voce di menu. */
.dropdown__menu form { margin: 0; }
.dropdown__item--danger { color: var(--sb-danger, #C62828); }
.dropdown__item--danger:hover { background: #fdecec; }
.dropdown__item--danger .icon { color: inherit; }
/* Azione non disponibile (riattivare a piano pieno): resta leggibile, col
   motivo nel title, invece di sparire senza spiegazione. */
.dropdown__item--locked, .dropdown__item--locked:hover {
  background: transparent; color: var(--sb-muted); cursor: not-allowed;
}
.dropdown__sep { margin: 5px 4px; border: 0; border-top: 1px solid var(--sb-line-2, #eef3f7); }
/* Un separatore in testa o in coda al menu non separa niente. */
.dropdown__menu > .dropdown__sep:first-child,
.dropdown__menu > .dropdown__sep:last-child { display: none; }

/* --- Modal del QR ---------------------------------------------------------- */
.qr-dialog {
  border: 0; border-radius: var(--sb-r-card); padding: 0; max-width: 420px; width: calc(100% - 32px);
  margin: auto; /* centratura: è il margine, non left/top, a farla */
  background: #fff; color: var(--sb-ink); box-shadow: 0 24px 70px rgba(20, 24, 28, 0.28);
}
.qr-dialog::backdrop { background: rgba(15, 23, 42, 0.45); }
.qr-dialog__box { padding: 22px 24px 24px; }
.qr-dialog__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.qr-dialog__title { margin: 0; font-size: 1.15rem; color: var(--c-blue-900); }
.qr-dialog__sub { margin: 2px 0 0; font-size: 0.9rem; color: var(--c-ink-soft); }
.qr-dialog__qr {
  margin: 16px 0 12px; padding: 14px; border-radius: var(--sb-r-card); background: var(--c-azure-50);
}
.qr-dialog__qr svg { width: 100%; max-width: 280px; height: auto; display: block; margin: 0 auto; }
.qr-dialog__hint { margin: 0 0 6px; font-size: 0.85rem; color: var(--c-ink-soft); }
.qr-dialog__url { margin: 0 0 14px; font-size: 0.78rem; color: var(--c-ink-soft); word-break: break-all; }
.qr-dialog__actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.qr-dialog__label { font-size: 0.82rem; font-weight: 600; color: var(--c-ink-soft); }

/* --- Modal generica (contenuto caricato in un Turbo Frame) ------------------ */
.modal {
  border: 0; border-radius: var(--sb-r-card); padding: 0; max-width: 440px; width: calc(100% - 32px);
  margin: auto; /* centratura: è il margine, non left/top, a farla */
  background: #fff; color: var(--sb-ink); box-shadow: 0 24px 70px rgba(20, 24, 28, 0.28);
}
.modal::backdrop { background: rgba(15, 23, 42, 0.45); }
.modal__box { padding: 22px 24px 24px; }
/* A hairline separates the title from the content, and it runs edge to edge:
   the negative margins cancel the padding of .modal__box, or the line would stop
   24px short of the sides and read as an underline of the title. Same band as
   the page header. */
.modal__head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: 0 calc(var(--sb-sp-5) * -1);
  padding: 0 var(--sb-sp-5) var(--sb-sp-4);
  border-bottom: 1px solid var(--sb-line-2);
}
.modal__title { margin: 0; font-size: 1.15rem; color: var(--c-blue-900); }
.modal__sub { margin: 0 0 14px; font-size: 0.9rem; color: var(--c-ink-soft); }
.modal__body { display: block; margin-top: 14px; }
.modal__loading { margin: 0; font-size: 0.9rem; color: var(--c-ink-soft); }
/* Dettaglio richiesta (§E20): 440px sono troppo stretti per un contenuto lungo.
   max-height + overflow perché il thread delle note cresce senza limite. */
/* The button row of a dialog: way out on the left, action on the right, like
   the action bar of a form. */
.modal__actions { display: flex; align-items: center; justify-content: flex-end; gap: var(--sb-sp-2); flex-wrap: wrap; margin-top: var(--sb-sp-5); }

/* The destructive confirmation is narrow: a question and two buttons, and at
   full width the text would drift away from the button that carries it out. */
.modal--confirm { max-width: 420px; }
.modal--confirm .modal__sub { margin: var(--sb-sp-3) 0 0; }

.modal--wide { max-width: 720px; }
/* A focus ring sits 3px outside the border box (2px outline + 1px offset), and
   `overflow-y: auto` forces the computed `overflow-x` to `auto` too: the ring of
   any control flush with the edge — the action row is `justify-content: flex-end`,
   the note textarea is `width: 100%` — overflowed by exactly those 3px and put a
   horizontal scrollbar under the detail. The gutter gives the ring room INSIDE
   the scroll box, and the negative margin hands the 3px back to the padding of
   .modal__box, so nothing moves on screen. Clipping the axis instead would cut
   the ring, which is the only sign of where the tab key has got to. */
.modal--wide .modal__body {
  max-height: 70vh;
  overflow-y: auto;
  padding-inline: 3px;
  margin-inline: -3px;
}

/* --- §E9.5 Conversazioni (Manage::) -----------------------------------------
   Elenco come tabella dati (stesso impianto di camere e reparti) e dettaglio
   come thread di chat: bolle a destra per l'ospite, a sinistra per
   l'assistente, con i card di contesto nel rail.

   IL BLOCCO STA IN FONDO di proposito: `.mconv-table td:first-child` ha la
   STESSA specificità di `.data-table td:first-child` (riga ~2446), quindi a
   decidere è l'ordine di sorgente. Piazzato più in alto, il grassetto della
   prima colonna vincerebbe in silenzio — stesso motivo per cui `.rooms-table`
   sta dove sta. */

/* Pillola d'attenzione: completa la famiglia .msvc-badge, così stato e "senza
   risposta" nella stessa cella hanno lo stesso ingombro. */
.msvc-badge--warn { background: var(--sb-warn-bg); color: var(--sb-warn-fg); }

/* La tabella occupa tutta la larghezza del pannello: il tetto di 1120px di
   .table-card serve alle tabelle strette (fatturazione, reparti), qui lascerebbe
   una fascia vuota a destra sotto la card dei filtri, che è già a piena
   larghezza. Il margine superiore lo porta già la card dei filtri. */
.mconv-card { max-width: none; margin-top: 0; }
/* Checkbox in una card filtri: etichetta ACCANTO al controllo, non sopra, e
   stessa altezza dei campi. Doppia classe per battere `.mfilter__field`. */
.mfilter__field.mfilter__field--check {
  flex-direction: row; align-items: center; gap: 8px; height: var(--sb-ctl);
}

/* Tabella conversazioni. La prima colonna è una data, non l'etichetta della
   riga: niente grassetto (come nella tabella camere). */
.mconv-table td:first-child { font-weight: 400; }
.mconv-table__when { color: var(--sb-muted); }
/* Il nome dell'ospite può essere lungo: va a capo invece di allargare la tabella. */
.mconv-table__guest { white-space: normal; min-width: 12rem; }
.mconv-table__sub {
  display: block; margin-top: 2px; font-size: 0.78rem; font-weight: 400; color: var(--sb-muted);
}
.mconv-table__count { font-variant-numeric: tabular-nums; }
.mconv-table__status .msvc-badge + .msvc-badge { margin-left: 6px; }

/* Valore non numerico in una tile KPI (una data, una lingua, uno stato): a
   1.6rem andrebbe a capo dentro i 190px minimi della griglia. */
.kpi--compact .kpi__value { font-size: 1.05rem; line-height: 1.25; }
/* Le .panel__section hanno lo stacco di serie, la .kpi-grid no. E come la
   tabella, la riga di tile va a piena larghezza: deve allinearsi al contenuto
   sottostante invece di fermarsi ai 1120px di .kpi-grid. */
.mconv-kpis { max-width: none; margin-bottom: 24px; }

/* Rail del dettaglio: i card devono stare in UN SOLO figlio della .mlist-layout,
   altrimenti diventano due celle della griglia e il secondo va a capo sotto il
   thread. La regola `.mlist-layout > .panel__section` colpisce i figli diretti,
   quindi il margine va tolto anche qui. */
.mconv-rail { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
.mconv-rail > .panel__section { margin: 0; }

/* --- Thread dei messaggi ---------------------------------------------------- */
.mconv { display: flex; flex-direction: column; gap: 14px; margin-top: 14px; }
.mconv__msg { display: flex; align-items: flex-start; gap: 10px; max-width: 82%; }
/* Ospite a destra con l'avatar dal lato suo; assistente e sistema a sinistra. */
.mconv__msg--user { flex-direction: row-reverse; margin-left: auto; }
.mconv__msg--assistant, .mconv__msg--system { margin-right: auto; }

.mconv__avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--sb-bg-2); color: var(--sb-muted); border: 1px solid var(--sb-line);
}
.mconv__msg--user .mconv__avatar {
  background: var(--sb-chip); color: var(--sb-primary-2); border-color: var(--sb-chip-line);
}
/* Avatar del marchio: l'immagine è già un cerchio bianco con il suo bordo, il
   contenitore ne disegnerebbe un secondo. Vale anche per la card assistente
   della home ospite, che nel tema ha un fondo in gradiente. */
.mconv__avatar--brand, .g-assistant__icon--brand {
  background: none; border: 0; padding: 0; overflow: hidden; border-radius: 50%;
}
.mconv__avatar--brand img, .g-assistant__icon--brand img {
  width: 100%; height: 100%; display: block; object-fit: contain;
}

/* --- Card assistente della home ospite -------------------------------------
   La card è un <a> ma non aveva `text-decoration: none` (a differenza di
   .g-cta): il testo usciva sottolineato. */
.g-assistant { text-decoration: none; color: inherit; }

/* Titolo e sottotitolo su righe separate. Nella variante attiva sono due
   <span> — inline, quindi scorrevano di seguito e i loro margini verticali non
   avevano effetto; in quella disabilitata sono <h2>/<p> e si impilavano già.
   Stessa colonna flex di .g-cta__body, che ha lo stesso markup. */
.g-assistant__body { display: flex; flex-direction: column; flex: 1; min-width: 0; }

/* Chiedere all'assistente è L'azione della home, non una delle tante: la
   versione attiva prende il gradiente del marchio, mentre quella disabilitata
   (senza sessione da QR) resta bianca — la differenza si legge a colpo d'occhio
   invece di affidarsi alla sola opacità. Il logo è un cerchio bianco: sul
   gradiente stacca da solo. */
a.g-assistant {
  background: var(--sb-grad); border-color: transparent; color: #fff;
  box-shadow: var(--sb-shadow-h);
}
a.g-assistant:hover { filter: brightness(1.08); }
a.g-assistant .g-assistant__title { color: #fff; }
a.g-assistant .g-assistant__sub { color: rgba(255, 255, 255, 0.85); }
a.g-assistant .g-cta__arrow { color: #fff; }

.mconv__bubble {
  min-width: 0; padding: 10px 14px; border-radius: 14px; line-height: 1.45;
  background: #fff; border: 1px solid var(--sb-line); color: var(--sb-ink);
}
.mconv__msg--user .mconv__bubble {
  background: var(--sb-grad); border-color: transparent; color: #fff;
}

.mconv__head {
  margin: 0 0 6px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.7rem;
}
.mconv__role { font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--sb-muted); }
.mconv__time { color: var(--sb-muted-2); }
.mconv__msg--user .mconv__role, .mconv__msg--user .mconv__time { color: rgba(255, 255, 255, 0.8); }

/* Lingua del messaggio: pillola neutra, che dentro la bolla in gradiente si
   ribalta in chiaro (.msvc-badge non saprebbe farlo). */
.mconv__lang {
  padding: 1px 7px; border-radius: var(--sb-r-pill);
  background: var(--sb-bg-2); color: var(--sb-muted); font-weight: 700; letter-spacing: 0.04em;
}
.mconv__msg--user .mconv__lang { background: rgba(255, 255, 255, 0.2); color: #fff; }
/* "Senza risposta": tinta propria, leggibile su entrambe le bolle. */
.mconv__flag {
  padding: 1px 7px; border-radius: var(--sb-r-pill);
  background: var(--sb-warn-bg); color: var(--sb-warn-fg); font-weight: 700;
}

/* Il testo passa da chat_format: paragrafi, elenchi e link sono nostri. */
.mconv__text { overflow-wrap: anywhere; }
.mconv__text p { margin: 0 0 6px; }
.mconv__text p:last-child { margin-bottom: 0; }
.mconv__text ul, .mconv__text ol { margin: 6px 0; padding-left: 20px; }
.mconv__text li { margin: 3px 0; }
.mconv__text a { color: var(--sb-primary-2); text-decoration: underline; }
.mconv__msg--user .mconv__text a { color: #fff; }

/* --- Card di contesto nel rail ---------------------------------------------- */
.mconv-guest {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 12px; margin-bottom: 4px; border-bottom: 1px solid var(--sb-line-2);
}
.mconv-guest__avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--sb-chip); color: var(--sb-primary-2);
  font-family: var(--sb-display); font-size: 1rem; font-weight: 700;
}
.mconv-guest__name {
  margin: 0; font-family: var(--sb-display); font-size: 1rem; font-weight: 700;
  color: var(--sb-ink); overflow-wrap: anywhere;
}
.mconv-guest__sub { margin: 2px 0 0; font-size: 0.82rem; color: var(--sb-muted); }

.mconv-facts { margin: 0; }
.mconv-fact {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 9px 0; border-top: 1px solid var(--sb-line-2);
}
.mconv-fact:first-child { border-top: 0; }
.mconv-fact:last-child { padding-bottom: 0; }
.mconv-fact__label { flex: none; font-size: 0.8rem; color: var(--sb-muted); }
.mconv-fact__value {
  margin: 0; min-width: 0; text-align: right; font-weight: 600;
  color: var(--sb-ink); overflow-wrap: anywhere;
}

/* Badge "riservato a N camere": completa la famiglia .msvc-badge nell'elenco
   servizi (§E19). */
.msvc-badge--rooms { background: var(--sb-chip); color: var(--sb-primary-2); }

/* ==========================================================================
   §E19 — Selettore a tag (servizio ↔ camere)
   Autocomplete client-side: chip rimovibili + menu dei risultati. Il menu può
   stare in position: absolute perché .panel__section non ha overflow (è il
   vincolo che aveva costretto .dropdown al position: fixed dentro .table-card).
   ========================================================================== */
.tagsel { position: relative; }

.tagsel__field {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  min-height: 44px; padding: 6px 8px; cursor: text;
  background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-btn);
}
.tagsel__field:focus-within {
  border-color: var(--sb-accent);
  outline: 2px solid var(--sb-accent);
  outline-offset: 1px;
  box-shadow: none;
}

.tagsel__input {
  flex: 1 1 140px; min-width: 140px; padding: 4px 2px;
  border: 0; outline: none; background: transparent; font: inherit; color: var(--sb-ink);
}

.tagsel__chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 5px 4px 12px; border-radius: var(--sb-r-pill);
  background: var(--sb-chip); border: 1px solid var(--sb-chip-line);
  color: var(--sb-primary-2); font-size: .82rem; font-weight: 600; white-space: nowrap;
}
.tagsel__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0; border: 0; border-radius: 50%;
  background: transparent; color: inherit; cursor: pointer; line-height: 1;
}
.tagsel__remove:hover { background: rgba(0, 0, 0, .09); }
.tagsel__remove .icon { width: 14px; height: 14px; }

.tagsel__menu {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; right: 0;
  max-height: 240px; margin: 0; padding: 6px; overflow-y: auto; list-style: none;
  background: #fff; border: 1px solid var(--sb-line); border-radius: var(--sb-r-card);
  box-shadow: 0 10px 30px rgba(20, 24, 28, .14);
}
.tagsel__option {
  padding: 8px 10px; border-radius: 8px; cursor: pointer;
  font-weight: 600; color: var(--sb-ink);
}
.tagsel__option:hover, .tagsel__option.is-active { background: var(--sb-chip); }
.tagsel__hint { padding: 8px 10px; color: var(--sb-muted); font-size: .85rem; }

/* --- §E20 Pulsante "Note" sulle card (bacheca e storico) --------------------
   È un btn--ghost btn--sm: prende dai pulsanti tutta la resa, qui restano solo
   l'allineamento dell'icona e la pastiglia del contatore. */
.req-card__notes,
.reqh-card__notes {
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
}
.req-card__notes .icon,
.reqh-card__notes .icon { flex: none; }
/* Pastiglia piena solo quando le note ci sono davvero: un "0" occuperebbe spazio
   per dire che non c'è niente da leggere. */
.req-card__notes-count {
  min-width: 18px; padding: 0 5px; border-radius: 999px; text-align: center;
  background: var(--sb-primary); color: #fff; font-size: 0.7rem; font-weight: 800;
}

/* --- §E20 Dettaglio richiesta e note di lavorazione (Manage::) ---------------
   Il dettaglio vive dentro .modal--wide: niente card/ombre proprie, il riquadro
   ce l'ha già la modal. */
.mreq__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.mreq__ident { display: flex; flex-direction: column; gap: 2px; }
.mreq__room { font-size: 1rem; font-weight: 800; color: var(--c-blue-900); }
.mreq__guest { font-size: 0.82rem; font-weight: 600; color: var(--sb-primary, #178bc3); }
.mreq__badges { display: flex; gap: 6px; flex-wrap: wrap; }

/* The state sits on the right, in line with the language and department badges
   above it: it is the same kind of information, and on the left it was stranded
   between the guest name and the request title. `width: fit-content` plus
   `margin-left: auto` because the pill has to keep its own width, not stretch. */
.mreq__status { display: block; width: fit-content; margin: 12px 0 0 auto; }

.mreq__subject { margin: 12px 0 4px; font-weight: 700; color: var(--c-ink); }
.mreq__body { margin: 0; color: var(--c-ink); }
.mreq__body p { margin: 0 0 6px; }

.mreq__facts {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 12px 0 0; padding: 0;
  list-style: none; font-size: 0.8rem; color: var(--c-ink-soft);
}
.mreq__staff { margin: 6px 0 0; font-size: 0.78rem; color: var(--c-ink-soft); }

/* Right-aligned like every action bar in the panel: the action is always in the
   same place, and this was the one row that kept it on the left. */
.mreq__actions {
  display: flex; gap: var(--sb-sp-2); flex-wrap: wrap; align-items: center;
  justify-content: flex-end;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--sb-line-2);
}
.mreq__actions form { margin: 0; }

.mreq__notes { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--sb-line-2); }
.mreq__section-title { margin: 0 0 10px; font-size: 0.95rem; color: var(--c-blue-900); }
.mreq__note-readonly { margin: 10px 0 0; font-size: 0.85rem; color: var(--sb-warn-fg, #92590F); }
.mreq__back { margin: 18px 0 0; }

.mreq-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.mreq-notes__empty { font-size: 0.85rem; color: var(--c-ink-soft); }
/* Le due visibilità si distinguono per LUMINOSITÀ e saturazione, non solo per
   tinta: due azzurri chiari vicini (era chip vs azure-100) si leggevano uguali.
   Interna = quasi bianca e desaturata, sta sullo sfondo. Per l'ospite = azzurro
   pieno e bordo di brand, perché quel testo è USCITO dalla struttura. */
.mreq-note {
  padding: 10px 12px; border-radius: 12px;
  background: #F4F5F9; border-left: 3px solid var(--sb-muted-2);
}
.mreq-note--guest { background: #CFE0FF; border-left: 4px solid var(--sb-primary); }
.mreq-note__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 0.72rem; color: var(--c-ink-soft); }
.mreq-note__badge { font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em; color: var(--sb-muted); }
/* Pillola piena: la differenza regge anche a colpo d'occhio, in scala di grigi
   e per chi non distingue bene i colori. */
.mreq-note--guest .mreq-note__badge {
  padding: 1px 8px; border-radius: 999px;
  background: var(--sb-primary); color: #fff;
}
.mreq-note--guest .mreq-note__head { color: var(--sb-primary-2); }
.mreq-note__author { font-weight: 700; }
.mreq-note__time { margin-left: auto; }
.mreq-note__delete { margin: 0; }
.mreq-note__delete-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: none; border-radius: 8px;
  background: none; color: var(--c-ink-soft); cursor: pointer;
}
.mreq-note__delete-btn:hover { background: #fdecec; color: var(--sb-danger, #C62828); }
.mreq-note__body { margin-top: 4px; font-size: 0.88rem; color: var(--c-ink); }
.mreq-note__body p { margin: 0 0 4px; }

.mreq-note-form { margin-top: 14px; }
.mreq-note-form__visibility { border: 0; padding: 0; margin: 0 0 12px; }
.mreq-note-form__choice { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; align-items: center; margin-top: 6px; cursor: pointer; }
.mreq-note-form__choice-label { font-weight: 600; color: var(--c-ink); }
.mreq-note-form__choice-hint { grid-column: 2; font-size: 0.75rem; color: var(--c-ink-soft); }

/* Note della reception nella schermata ospite (mobile-first). */
.g-req-notes:empty { display: none; }
.g-req-notes__title { margin: 10px 0 4px; font-size: 0.72rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em; color: var(--c-blue-700); }
.g-req-notes__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* §E23/UX — non più il grigio neutro delle superfici: è un messaggio di
   qualcuno, e nella palette della struttura si legge come tale. L'angolo in
   alto a sinistra resta squadrato — il verso da cui "arriva" — così non si
   confonde con la pillola di stato, che ha lo stesso fondo ma forma diversa.
   Il token è `--sb-chip`, che ogni template ridefinisce nella propria palette. */
.g-req-note {
  padding: 9px 12px; border-radius: 4px 14px 14px 14px;
  background: var(--sb-chip, #E7EAFA);
}
.g-req-note__body { font-size: 0.85rem; color: var(--c-ink); }
.g-req-note__body p { margin: 0 0 4px; }
.g-req-note__time { margin: 2px 0 0; font-size: 0.7rem; color: var(--c-ink-soft); }

/* ==========================================================================
   §E21 — Editor rich text (Lexxy) sui campi descrizione
   Toolbar volutamente minima: link, grassetto, corsivo, sottolineato, elenco
   puntato, elenco numerato (+ annulla/ripristina). Lexxy non espone la
   configurazione per singolo bottone — il preset di default ha solo
   `toolbar.upload` — quindi il resto si nasconde qui. È solo l'aspetto: il
   confine vero è l'allowlist di RichTextFields, che al salvataggio scarta ciò
   che le hotkey native di Lexical riuscissero comunque a produrre.
   ========================================================================== */
.form__rte lexxy-toolbar button[name="strikethrough"],
.form__rte lexxy-toolbar button[name="quote"],
.form__rte lexxy-toolbar button[name="code"],
.form__rte lexxy-toolbar button[name="table"],
.form__rte lexxy-toolbar button[name="divider"],
.form__rte lexxy-toolbar .lexxy-editor__toolbar-dropdown--highlight,
.form__rte lexxy-toolbar lexxy-toolbar-dropdown:has(button[name="format"]) { display: none; }

/* Il campo deve leggersi come gli altri .form__input della scheda. */
.form__rte lexxy-editor {
  border-radius: var(--sb-r-btn); border: 1px solid var(--sb-line); background: var(--sb-bg);
}
.form__rte lexxy-editor:focus-within {
  /* The ring every field has (`.mng-body .form__input:focus`), not the indigo
     halo of before: the editor is a field like the others. */
  border-color: var(--sb-accent); outline: 2px solid var(--sb-accent); outline-offset: 1px; box-shadow: none;
}
.form__rte .lexxy-editor__content { min-height: 7rem; }

/* --- Resa dei contenuti rich text (ospite + pannello) ---------------------- */
.rich-text > :first-child { margin-top: 0; }
.rich-text > :last-child { margin-bottom: 0; }
.rich-text p { margin: 0 0 0.6em; }
.rich-text ul, .rich-text ol { margin: 0 0 0.6em; padding-inline-start: 1.35em; }
.rich-text li { margin-bottom: 0.2em; }
.rich-text a { color: var(--sb-primary); text-decoration: underline; overflow-wrap: anywhere; }

/* ==========================================================================
   §E22 — Fasce di disponibilità dei servizi (pannello) e selettore slot (ospite)
   Righe ripetibili: giorni a checkbox + orario di apertura/chiusura. I giorni
   occupano tutta la larghezza (flex-basis 100%) perché sette caselle non stanno
   accanto a due campi orario dentro una colonna a metà pagina.
   ========================================================================== */
.svc-avail { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }

.svc-avail__row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--sb-line); border-radius: var(--sb-r-btn);
}
/* A row that failed validation: red edge plus its own message, which takes the
   whole width like the day checkboxes do. Reset the margin — the row spaces its
   children with `gap`, and a component never carries its container's spacing. */
.svc-avail__row--invalid { border-color: var(--sb-danger); }
.svc-avail__row > .dtable__error { flex: 1 1 100%; margin: 0; }

.svc-avail__days { flex: 1 1 100%; }
/* Wrapping is not cosmetic: the group is three labelled fields, ~420px of
   intrinsic width, and the row now lives in a column narrowed by the section's
   title column. Without it the last fields were simply clipped by the card. */
.svc-avail__times { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.svc-avail__times .form__input--sm { max-width: 130px; }
.svc-avail__sep { color: var(--sb-muted); }

/* A `.form__row` in one of these rows must NOT keep its 14px bottom margin: here
   the row runs HORIZONTALLY, so that margin is not vertical rhythm. And because
   `.form__row:last-child` zeroes it, the last field had a margin box 14px shorter
   than its siblings', which `align-items: center` then centred — pushing the
   expected arrival and the exemptions 7px below the fields next to them. Spacing
   between these children is the row's `gap`; a component never carries its
   container's spacing (see app/views/CLAUDE.md §6.1). The service availability
   windows are untouched: that row has no `.form__row` at all. */
.svc-avail__row > .form__row,
.svc-avail__times > .form__row { margin-bottom: 0; }

/* Where the group is made of LABELLED rows, what has to line up is the labels,
   so the alignment is on the top edge and not on the centre. Centring lined the
   fields up by their middles, and `<input type="time">` is 2.8px taller than
   `<input type="date">` in Chrome — enough to leave the expected arrival 1.4px
   off its neighbours whatever the margins do. The `:has()` matters: a group of
   BARE inputs (the service availability windows) keeps centring, which is what
   puts its «–» separator level with the two times. */
.svc-avail__times:has(> .form__row) { align-items: flex-start; }
/* Same reason one level up, because the room picker sits OUTSIDE the groups: a
   group holding the time field is now 2.8px taller than one that does not, and
   centring pushed the picker's label off the others by half of that. The button
   keeps its own centring — it is a control, not a labelled field, and pinning it
   to the top would leave it hanging above the row it belongs to. Untouched again
   is the availability row, whose only direct child is `.svc-avail__days`. */
.svc-avail__row:has(> .form__row) { align-items: flex-start; }

.svc-avail__remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; margin-left: auto; padding: 0;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--sb-muted); cursor: pointer; line-height: 1;
  align-self: center;
}
.svc-avail__remove:hover { background: rgba(0, 0, 0, .07); color: var(--sb-danger); }
.svc-avail__remove .icon { width: 14px; height: 14px; }

.form__hint--warn { color: var(--sb-warn-fg, #92590F); }
/* §E59.4 — `.form__hint` è `display: block` per dichiarazione d'autore, che batte
   lo `[hidden] { display: none }` dello user-agent: senza questa riga l'avviso
   di contrasto sarebbe visibile sempre, su ogni colore, anche quando la coppia
   si legge benissimo. Stessa lezione di `.billing-banner[hidden]`. */
.form__hint[hidden] { display: none; }

/* Selettore data + orario dell'ospite: senza JS resta un solo <select> con gli
   orari raggruppati per data, quindi il campo delle date parte nascosto. */
.g-slot { display: flex; flex-direction: column; gap: 10px; }

/* ==========================================================================
   §E23/UX — Anteprima del template dell'app ospite (pannello struttura)
   Sostituisce le vecchie radio-card `.tplpick*`: erano miniature disegnate in
   CSS, quindi mostravano un layout astratto e mai il logo, la copertina e i
   servizi DI QUESTA struttura. Qui la dropdown sceglie e l'iframe rende la home
   ospite vera.

   L'iframe si rende alla larghezza logica reale del telefono (390px) e si
   rimpicciolisce con `transform`: restringerlo davvero farebbe scattare le
   @media dell'app ospite e mostrerebbe un layout che nessun ospite vede.
   `pointer-events: none` è il secondo strato dopo `sandbox`: la cornice è una
   fotografia, non un'app da usare (per quella c'è il link sotto).
   ========================================================================== */
.tplprev { --tplprev-w: 390px; --tplprev-h: 760px; --tplprev-scale: .70; margin: 4px 0 12px; }

.tplprev__phone {
  width: max-content; margin: 0 auto; padding: 8px;
  border-radius: 30px; background: #14162a; box-shadow: var(--sb-shadow);
}

.tplprev__stage {
  width: calc(var(--tplprev-w) * var(--tplprev-scale));
  height: calc(var(--tplprev-h) * var(--tplprev-scale));
  border-radius: 22px; overflow: hidden; background: #fff;
}

.tplprev__frame {
  display: block; width: var(--tplprev-w); height: var(--tplprev-h);
  border: 0; pointer-events: none;
  transform: scale(var(--tplprev-scale)); transform-origin: top left;
  transition: opacity .15s;
}
/* Fra il cambio di `src` e il `load` l'iframe mostra ancora il template
   precedente: attenuarlo dice che la scelta è stata registrata. */
.tplprev__frame--busy { opacity: .35; }

/* `.form__cards-col` dichiara già container-type: inline-size. Sul telefono la
   colonna scende sotto i 380px e il telaio a scala piena traboccherebbe. */
@container (max-width: 380px) {
  .tplprev { --tplprev-scale: .56; }
}

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

/* ==========================================================================
   §E23/UX — Apertura della home del template "classic"
   Prima erano tre blocchi impilati: testata (101px) + copertina (197px) +
   saluto su gradiente (206px) = 504px di intestazione su 844, con la prima
   azione a 533px. Ora l'apertura è una sola e porta il saluto dentro di sé.

   Tutto scoped su `classic`: `.gt-hero*` esiste anche nei tre template nuovi,
   che lo ridefiniscono nei propri fogli. Senza questo scoping il gradiente
   indigo colerebbe dentro `night`, `warm` e `utility`.
   ========================================================================== */
.g-body[data-guest-template="classic"] .g-main { padding: 6px var(--g-gutter) 0; }

/* Con la copertina la testata galleggia sulla foto: 100px di intestazione in
   meno, e la foto torna a essere l'apertura invece di un inserto fra due bande. */
.g-body[data-guest-template="classic"][data-guest-hero="cover"] .g-topbar {
  position: absolute; top: 0; left: 0; right: 0; z-index: 30;
  background: transparent; box-shadow: none;
}
.g-body[data-guest-template="classic"] .g-app { position: relative; }

/* Senza copertina la testata è in tinta piatta e la fascia del saluto riparte
   dallo STESSO colore: prima i due gradienti si toccavano ripartendo entrambi
   da capo e lasciavano una giunzione visibile. */
.g-body[data-guest-template="classic"][data-guest-hero="plain"] .g-topbar {
  background: var(--sb-primary-600);
}

.g-body[data-guest-template="classic"] .gt-hero--cover {
  position: relative; height: 260px; overflow: hidden; background: var(--sb-chip);
}
.g-body[data-guest-template="classic"] .gt-hero--cover .gt-hero__img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.g-body[data-guest-template="classic"] .gt-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(13, 11, 51, .60) 0%, rgba(13, 11, 51, .06) 26%,
    rgba(13, 11, 51, .80) 58%, rgba(13, 11, 51, .97) 100%);
}
.g-body[data-guest-template="classic"] .gt-hero--cover .gt-hero__body {
  position: absolute; left: var(--g-gutter); right: var(--g-gutter); bottom: 18px;
}

.g-body[data-guest-template="classic"] .gt-hero--plain {
  background: linear-gradient(180deg, var(--sb-primary-600) 0%, var(--sb-primary-2) 100%);
  padding: 8px var(--g-gutter) 26px;
}

.g-body[data-guest-template="classic"] .gt-hero__title {
  margin: 0; font-family: var(--sb-display); font-weight: 600;
  font-size: 1.7rem; line-height: 1.15; color: #fff;
}
.g-body[data-guest-template="classic"] .gt-hero__sub {
  margin: 6px 0 0; line-height: 1.5; color: rgba(255, 255, 255, .85);
}
/* Sulla foto il badge camera ha bisogno del proprio fondo per restare leggibile
   qualunque cosa ci sia sotto. */
.g-body[data-guest-template="classic"] .gt-hero__badge {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 10px; padding: 6px 13px; border-radius: var(--sb-r-pill);
  background: rgba(13, 11, 51, .62); backdrop-filter: blur(6px);
  color: #fff; font-size: 0.8rem; font-weight: 700;
}

/* ==========================================================================
   §E23/UX — Card del catalogo
   ========================================================================== */

/* Il nome del servizio era 0.98rem: PIÙ PICCOLO del testo corrente, con sopra
   una eyebrow maiuscola che gli rubava l'attenzione. È l'elemento che l'ospite
   sta cercando, quindi sale e si prende due righe piene. */
.g-service__name {
  font-size: 1.02rem; line-height: 1.25;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.g-cat__name { font-size: 1.02rem; line-height: 1.25; }

/* --- Card alte uguali e testo troncato ------------------------------------
   Due categorie affiancate si sfalsavano per la sola lunghezza della
   descrizione (misurato in home: 216px e 233px). `grid-auto-rows: 1fr` pareggia
   TUTTE le righe, non solo quelle affiancate, così la griglia resta regolare
   anche scorrendo.
   `night` e `utility` disegnano le categorie come righe di elenco a una
   colonna: lì pareggiare vorrebbe dire allungare ogni riga fino alla più
   lunga, quindi si tirano fuori nei propri fogli. */
/* Le righe si dimensionano sul contenuto e le card si allungano fino al bordo
   della PROPRIA riga: due categorie affiancate diventano uguali, che è dove lo
   sfalsamento si vede. Pareggiare anche fra righe diverse (`grid-auto-rows: 1fr`)
   costringerebbe le card senza foto — molto più basse — a riempire un'altezza
   che non hanno. */
.g-grid--even > :not(.g-cat--flat) { align-self: stretch; }

/* Lo spazio in più deve finire in un posto solo e prevedibile: il conteggio
   scende in fondo alla card invece di lasciare un vuoto sotto di sé. */
.g-cat__body { display: flex; flex-direction: column; flex: 1; }
.g-cat__count { margin-top: auto; align-self: start; }

/* Senza immagine la card si adatta al contenuto: non c'è un riquadro foto da
   pareggiare, e allungarla fino a una con foto (114px contro 233) vorrebbe dire
   riempire di vuoto. Il conteggio segue il testo invece di essere spinto in
   fondo: senza spazio da distribuire, `margin-top: auto` varrebbe zero e la
   riga si appiccicherebbe alla descrizione. */
.g-cat--flat .g-cat__count { margin-top: 8px; }

/* Il testo si tronca: un nome o una descrizione lunghi non devono decidere
   l'altezza dell'intera griglia. `.g-cat__desc` è già accorciato a 70 caratteri
   lato server, ma in una card da 169px quei 70 caratteri sono tre righe. */
.g-cat__name, .g-cat__desc, .g-service__hours {
  display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
}
/* Il `max-height` NON è ridondante: questi tre sono elementi di un contenitore
   flex, quindi `-webkit-box` viene blockificato e il vecchio WebKit smette di
   applicare il clamp. Dove il clamp funziona si vedono i puntini; dove non
   funziona il testo si taglia comunque, e l'altezza resta quella prevista —
   che è l'unica cosa da cui dipende la griglia. */
.g-cat__name { -webkit-line-clamp: 2; line-clamp: 2; max-height: 2.5em; }   /* 2 × 1.25 */
.g-cat__desc { -webkit-line-clamp: 2; line-clamp: 2; max-height: 2.7em; }   /* 2 × 1.35 */
/* Gli orari sono una riga sola per costruzione, ma una fascia con più
   intervalli ("09:00–12:00, 15:00–18:00") ne occupa due. */
.g-service__hours { -webkit-line-clamp: 1; line-clamp: 1; max-height: 1.5em; }

/* Card senza foto: non c'è riquadro da riempire, quindi il corpo respira come
   una card a sé invece di sembrare monco. */
.g-service--flat .g-service__body,
.g-cat--flat .g-cat__body { padding: 14px; }

/* La griglia è a righe: una card con foto allunga la propria riga e quella
   accanto restava orfana in cima, con la successiva mandata a capo nell'altra
   colonna (misurato in "In evidenza": 393px, ~121 di spazio morto).
   Una card con foto è alta all'incirca quanto DUE senza (251px contro 130):
   facendogliene occupare due, le card basse le si impilano accanto — 272px.
   La classe c'è già sulla card, quindi niente `:has()` e nessun dubbio sul
   supporto nei WebKit più vecchi. Dove la griglia ha una colonna sola
   (`night`, `utility`) o è un rail (`warm`, in evidenza) non ha effetto.
   §E67 — on a tablet `night` and `utility` put their rows in two columns, and
   there it WOULD take effect: their sheets reset it with `grid-row: auto`. */
.g-grid > .g-service:not(.g-service--flat) { grid-row: span 2; align-self: stretch; }

/* Lo spazio che avanza fra l'altezza naturale della card con foto e quella
   delle due che le stanno accanto lo prende la FOTO, non il corpo: il testo
   resta allineato a quello delle card vicine invece di avere sotto un margine
   che le altre non hanno. `flex-grow` e basta — con `flex: 1` la base andrebbe
   a zero e l'immagine collasserebbe dove non c'è spazio da distribuire. */
.g-service__media { flex-grow: 1; }

.g-service__badges--inline {
  position: static; margin: 0 0 8px; display: flex; flex-wrap: wrap; gap: 6px;
}

/* Prezzo e orari: due righe distinte, non due frammenti che si accavallano. */
.g-service__meta { gap: 3px; }

/* ==========================================================================
   §E23/UX — Aree toccabili dell'app ospite
   Su ~22 elementi interattivi solo 7 arrivavano a 44px, e uno solo per scelta
   (`.g-wa-btn`). I tre peggiori erano azioni frequenti: il link "indietro"
   (~20px, navigazione primaria di 9 viste su 12), il titolo di una FAQ (~20px,
   il modo per leggerle) e l'email nel footer (~19px).
   ========================================================================== */
/* Il margine negativo compensa il padding: l'area toccabile cresce, il testo
   resta allineato al resto della pagina. */
.g-back { min-height: 44px; padding: 0 10px; margin: 0 0 2px -10px; }
.g-faq__q { padding: 12px 0; min-height: 44px; display: flex; align-items: center; }
.g-faq__q::after { margin-left: auto; float: none; }
.g-faq { padding: 0 14px; }
.g-faq__a { padding-bottom: 12px; }
.g-footer__link { display: inline-block; min-height: 44px; line-height: 44px; }

/* Chip e bottoni piccoli: 32px è sotto la soglia di qualunque linea guida. */
.g-wifi__copy, .btn--sm { min-height: 36px; height: auto; }
/* L'area toccabile arriva a 44px senza spostare la grafica. */
.g-wifi__copy::after, .btn--sm::after {
  content: ""; position: absolute; inset: -4px; border-radius: inherit;
}
.g-wifi__copy, .btn--sm { position: relative; }

/* Selettore lingua: era 30px. */
.g-lang__current { min-height: 40px; }
.g-lang__item { min-height: 44px; }

/* Il bottone primario era `#178bc3`, un azzurro che non sta in nessun token del
   tema: nell'app ospite stona con l'indigo di testata, link e badge. Corretto
   SOLO lato ospite — nel pannello è una scelta di marca, non un difetto. */
.g-body .btn--primary {
  background: var(--sb-primary-600); box-shadow: 0 6px 16px rgba(23, 0, 142, .18);
}
.g-body .btn--primary:hover { background: var(--sb-primary-2); filter: none; }

/* Da quando le card senza foto non disegnano il riquadro, una riga può mettere
   accanto una card alta (con foto) e una bassa: senza questo, la bassa si
   stirava all'altezza dell'altra e restava con un buco bianco dentro. Ora ogni
   card è alta quanto il suo contenuto e lo spazio avanza SOTTO, dove non si
   legge come un errore. */
.g-grid { align-items: start; }

/* ==========================================================================
   §E23/UX — Moduli dell'app ospite.

   Il form era l'UNICO blocco interattivo senza superficie: `.g-service`,
   `.g-wifi`, `.g-contacts` e `.g-list__item` sono card bianche, il modulo
   galleggiava sulla pagina. E i campi non staccavano perché erano LO STESSO
   COLORE della pagina: `.form__input` prende `background: var(--sb-bg)` e
   `.g-body` prende lo stesso token (#F6F7FC). L'unico confine era il bordo
   `--sb-line`, che contro la pagina misura 1.18:1 — WCAG 1.4.11 ne chiede 3.
   ========================================================================== */
.g-form-card { padding: 18px 16px; margin-bottom: 4px; }

/* Il campo si INCASSA nella card: fondo smorzato e contorno a tono medio, che
   sul bianco della card misura 3.14:1. Il solo riempimento non basterebbe
   (#EFF0F7 su bianco è 1.14:1): è il contorno a portare il contrasto, ed è per
   questo che serve la card sotto. Scopato a `.g-body` — nel pannello i campi
   stanno già dentro `.panel__section` bianche e leggono correttamente. */
.g-body .form__input {
  background-color: var(--sb-bg-2);
  border: 1px solid var(--sb-muted-2);
  padding: 12px 14px;
}
/* Always `background-color` here, never the `background` shorthand: it also
   resets image, repeat and position, and it outranks the `select` chevron rule
   below, which then tiles across the whole field (or vanishes) on focus. */
.g-body .form__input:focus {
  background-color: #fff;
  border-color: var(--sb-primary);
  box-shadow: 0 0 0 3px rgba(59, 98, 214, .25);
}
.g-body .form__label { color: var(--sb-ink); }

/* Un `select` non deve somigliare a un campo di testo: il selettore di data e
   quello di orario erano indistinguibili da una casella scrivibile. */
.g-body select.form__input {
  appearance: none; -webkit-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%235A5F80' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* Il selettore data e quello orario sono due righe simmetriche: prima la label
   era figlia diretta di `.g-slot`, accanto al blocco della data, e le due
   etichette si accavallavano finché Stimulus non entrava in gioco. */
.g-slot__date, .g-slot__time { display: block; }
/* An author `display` beats the user agent's `[hidden]`: without this the date
   row the controller reveals was on screen before it, and with no JavaScript
   at all. */
.g-slot__date[hidden] { display: none; }

/* L'azione principale della pagina era il bersaglio più piccolo: bottone
   inline da 40px, dopo che la CTA che ci ha portato qui era a tutta larghezza. */
.g-form-card .form__actions { margin-top: 18px; }
.g-form-card .form__actions .btn { width: 100%; height: 48px; }

/* --- Riepilogo del servizio richiesto --------------------------------------
   Sostituisce la coppia eyebrow + nome: l'ospite vede orari e prezzo MENTRE
   sceglie, invece di doverli ricordare dalla pagina precedente. */
.g-recap { display: flex; gap: 12px; padding: 14px; margin-bottom: 12px; }
.g-recap__media { flex: none; width: 68px; height: 68px; border-radius: 12px; overflow: hidden; }
.g-recap__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.g-recap__body { flex: 1; min-width: 0; }
.g-recap__cat {
  margin: 0; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.4px;
  color: var(--c-blue-500); font-weight: 700;
}
.g-recap__name { margin: 1px 0 6px; font-size: 1.05rem; font-weight: 700; color: var(--c-blue-900); line-height: 1.25; }

/* --- Contatore persone -----------------------------------------------------
   Un campo numerico su mobile è un bersaglio scomodo e le frecce native non
   esistono. I bottoni li aggiunge Stimulus: senza JS resta il campo, che
   funziona come prima. */
.g-stepper { display: flex; align-items: center; gap: 10px; }
.g-stepper__btn {
  flex: none; width: 44px; height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--sb-muted-2); border-radius: 12px;
  background: #fff; color: var(--sb-ink);
  font-size: 1.3rem; font-weight: 600; line-height: 1; cursor: pointer;
}
.g-stepper__btn:hover:not(:disabled) { background: var(--sb-bg-2); }
.g-stepper__btn:disabled { opacity: .45; cursor: default; }
.g-body .g-stepper .form__input {
  width: 72px; max-width: none; text-align: center;
  font-weight: 600; font-variant-numeric: tabular-nums;
}
.g-total { margin: 8px 0 0; font-size: 0.92rem; font-weight: 600; color: var(--c-blue-900); }

/* ==========================================================================
   §E23/UX — Navigazione fra le sezioni (tutti i template tranne `utility`,
   che ha la tab bar in fondo). Sta sotto l'apertura e sopra il contenuto, su
   OGNI pagina: prima, fuori dalla home, l'unica navigazione era il link
   "indietro" e il catalogo non si raggiungeva da nessun'altra parte.
   Scorre in orizzontale se le voci non ci stanno: meglio che mandarle a capo
   e far ballare l'altezza della testata da una pagina all'altra.
   ========================================================================== */
.g-sectionnav { margin: 0; }

/* `position: relative` makes the scroller the containing block of what is
   absolutely positioned inside it — the `.visually-hidden` sentence next to an
   unread badge. Without it that span is placed against the page, escapes the
   scroller's clip and widens the PAGE to wherever the item sits in the
   unscrolled list (~332px): a sideways scroll on a 320px phone and inside the
   showcase frame, whose screen is narrower than that on most laptops. */
.g-sectionnav__list {
  position: relative;
  display: flex; gap: 8px; margin: 0; padding: 12px var(--g-gutter) 6px;
  list-style: none; overflow-x: auto; scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.g-sectionnav__list::-webkit-scrollbar { display: none; }

.g-sectionnav__item {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 0 14px; white-space: nowrap;
  border-radius: var(--sb-r-pill); border: 1px solid var(--sb-line);
  background: #fff; color: var(--sb-muted);
  font-size: 0.85rem; font-weight: 600; text-decoration: none;
}
.g-sectionnav__item .icon { width: 16px; height: 16px; flex: none; }
.g-sectionnav__item:hover { border-color: var(--sb-primary-300); color: var(--sb-ink); }

/* La voce corrente non si affida al solo colore: cambia anche fondo e bordo. */
.g-sectionnav__item--active {
  background: var(--sb-primary); border-color: var(--sb-primary); color: #fff;
}
.g-sectionnav__item--active:hover { background: var(--sb-primary-2); color: #fff; }

/* §E23/UX — "Le mie richieste" vive nella testata, non fra le sezioni: segna
   comunque la pagina corrente, con fondo pieno oltre che col colore. Il minimo
   di 40px porta il bersaglio a misura di dito (era ~28px). */
.g-topbar__action { min-height: 40px; }
.g-topbar__action--active { background: #fff; color: var(--sb-primary); }

/* ============================================================================
   §E28 — Selettore immagine: nessuna / galleria proposta / foto propria.
   Usato dal form categoria e dal form servizio (manage/shared/_photo_picker).
   ========================================================================= */

/* Il fieldset serve per legend + gruppo di radio: la cornice del browser no. */
.imgpick__fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.imgpick__fieldset > .form__label { padding: 0; }

.imgpick__modes { margin-top: 6px; }

.imgpick__preview { margin: 6px 0 10px; }
.imgpick__current {
  display: block; width: 120px; height: 80px; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--sb-line);
}

.imgpick__panel { margin-top: 10px; }
.imgpick__panel[hidden] { display: none; }

/* La galleria scorre dentro il suo riquadro: trenta foto nella colonna stretta
   del form servizio spingerebbero il pulsante Salva fuori da due schermate. */
.imgpick__panel--gallery {
  max-height: 380px; overflow-y: auto; padding: 10px;
  border: 1px solid var(--sb-line); border-radius: var(--sb-r-card);
  background: var(--sb-bg);
}

.imgpick__group {
  margin: 12px 0 6px; font-size: 0.8rem; font-weight: 600;
  color: var(--sb-muted); text-transform: uppercase; letter-spacing: 0.04em;
}
.imgpick__group:first-of-type { margin-top: 0; }

.imgpick__grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}

.imgpick__tile { display: block; cursor: pointer; }
.imgpick__tile-img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--sb-r-btn); border: 2px solid var(--sb-line);
}
.imgpick__tile:hover .imgpick__tile-img { border-color: var(--sb-primary-300); }

/* Selezione: bordo pieno PIÙ anello esterno. Il solo cambio di colore del bordo
   si perde su una foto scura quanto il blu del tema. */
.imgpick__tile input:checked + .imgpick__tile-img {
  border-color: var(--sb-primary);
  box-shadow: 0 0 0 3px var(--sb-chip);
}
/* Il radio è visually-hidden ma resta raggiungibile da tastiera: il focus deve
   comunque vedersi, e l'unica cosa visibile è l'immagine accanto. */
.imgpick__tile input:focus-visible + .imgpick__tile-img {
  outline: 2px solid var(--sb-primary-500); outline-offset: 2px;
}

.imgpick__file { max-width: 100%; }

/* §E59.7 — gli avatar del widget sono QUADRATI e si mostrano tondi, e le tile di
   §E28 sono tarate su foto 4:3: dentro quel ritaglio una faccia perde la fronte
   e il mento. Un modificatore e non una seconda griglia — selezione, focus e
   stato scelto restano quelli, che è tutto il lavoro vero di questo blocco.
   Le caselle sono anche più piccole: quattordici facce a 120px sono un elenco da
   scorrere, non una tavolozza da guardare. */
.imgpick--avatar .imgpick__grid {
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
}
.imgpick--avatar .imgpick__tile-img { aspect-ratio: 1; border-radius: 50%; }
.imgpick--avatar .imgpick__current {
  width: 72px; height: 72px; border-radius: 50%;
}

/* ==========================================================================
   §E30 — Documenti legali di piattaforma
   Pagina pubblica (prosa lunga) + sorgente markdown in back-office.
   .rich-text non basta qui: copre solo paragrafi, elenchi e link, mentre un
   testo legale ha titoli, tabelle e citazioni.
   ========================================================================== */
.legal {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  padding: 32px 20px;
}

.legal__card {
  background: var(--c-white);
  border-radius: var(--sb-r-card);
  box-shadow: var(--sb-shadow);
  /* 72ch: la misura oltre cui l'occhio perde la riga tornando a capo, ed è
     esattamente quello che si fa leggendo venti paragrafi di contratto. */
  max-width: 72ch;
  width: 100%;
  margin: auto;
  padding: 32px 34px;
}

.legal__brand { display: inline-block; margin-bottom: 18px; }

.legal__head { border-bottom: 1px solid var(--sb-line); padding-bottom: 18px; margin-bottom: 24px; }

.legal__title { font-family: var(--sb-display); margin: 0 0 8px; color: var(--sb-ink); }

.legal__meta { margin: 0; color: var(--sb-muted); font-size: 0.9rem; }

/* Avviso "stai leggendo una versione superata": icona + testo, mai il solo colore. */
.legal__notice {
  display: flex; gap: 10px; align-items: flex-start;
  margin: 16px 0 0; padding: 12px 14px;
  border-radius: var(--sb-r-btn);
  background: var(--sb-warn-bg); color: var(--sb-warn-fg);
  font-size: 0.9rem;
}
.legal__notice a { color: inherit; font-weight: 600; }

/* --- Prosa del documento --------------------------------------------------- */
.legal__body { color: var(--sb-ink); line-height: 1.65; overflow-wrap: anywhere; }
.legal__body > :first-child { margin-top: 0; }
.legal__body > :last-child { margin-bottom: 0; }
.legal__body h1, .legal__body h2, .legal__body h3,
.legal__body h4, .legal__body h5, .legal__body h6 {
  font-family: var(--sb-display); color: var(--sb-ink); line-height: 1.25;
  margin: 1.8em 0 0.6em;
}
.legal__body h1 { font-size: 1.5rem; }
.legal__body h2 { font-size: 1.25rem; }
.legal__body h3 { font-size: 1.05rem; }
.legal__body h4, .legal__body h5, .legal__body h6 { font-size: 1rem; }
.legal__body p { margin: 0 0 1em; }
.legal__body ul, .legal__body ol { margin: 0 0 1em; padding-inline-start: 1.4em; }
.legal__body li { margin-bottom: 0.35em; }
.legal__body dt { font-weight: 600; margin-top: 0.6em; }
.legal__body dd { margin: 0 0 0.4em 1.4em; }
.legal__body a { color: var(--sb-primary); text-decoration: underline; }
.legal__body hr { border: 0; border-top: 1px solid var(--sb-line); margin: 2em 0; }
.legal__body blockquote {
  margin: 0 0 1em; padding: 2px 0 2px 16px;
  border-inline-start: 3px solid var(--sb-line);
  color: var(--sb-muted);
}
.legal__body code {
  background: var(--sb-page); border-radius: 6px; padding: 1px 5px; font-size: 0.9em;
}

/* Una tabella di trattamento dati è larga: scorre da sola invece di allargare
   la pagina e mandare in orizzontale tutto il resto del testo. */
.legal__body table {
  display: block; overflow-x: auto; max-width: 100%;
  border-collapse: collapse; margin: 0 0 1.2em; font-size: 0.92rem;
}
.legal__body th, .legal__body td {
  border: 1px solid var(--sb-line); padding: 8px 10px; text-align: start; vertical-align: top;
}
.legal__body th { background: var(--sb-page); font-weight: 600; }

/* --- Piede: lingua e altre versioni ---------------------------------------- */
.legal__foot { border-top: 1px solid var(--sb-line); padding-top: 18px; margin-top: 28px; }
.legal__langs, .legal__versions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px;
}
.legal__langs-label { color: var(--sb-muted); font-size: 0.85rem; }
.legal__version {
  padding: 3px 9px; border-radius: var(--sb-r-pill);
  border: 1px solid var(--sb-line); color: var(--sb-muted);
  font-size: 0.8rem; text-decoration: none;
}
.legal__version--active { border-color: var(--sb-primary); color: var(--sb-primary); font-weight: 600; }

/* --- Sorgente markdown in back-office --------------------------------------
   È un SORGENTE, non un campo rich text: a spaziatura proporzionale una tabella
   markdown diventa illeggibile e il rientro degli elenchi non si vede. */
.form__input--code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88rem;
  line-height: 1.55;
  min-height: 24rem;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

/* --- §E30.1 Pagination nav. §E54: with the numbered series -------------------
   Previous / numbers / next, and the total on a second line.

   The CENTRE belongs to the numbered series (`margin-inline: auto`), so it stays
   put when one of the two arrows is missing (first and last page) instead of
   jumping from side to side while paging. Before §E54 the centre was the status
   line: the two cannot both have it, and the numbers are what you aim at. */
.pagenav {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-3);
  margin-top: var(--sb-sp-4);
  flex-wrap: wrap;
}

/* Full row of its own: the total is a summary, not a control, and putting it
   between the arrows would push the numbers off centre. */
.pagenav__status {
  flex-basis: 100%;
  margin: 0;
  text-align: center;
  color: var(--sb-muted);
  font-size: var(--sb-fs-label);
}

.pagenav__pages {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-1);
  margin-inline: auto;
  padding: 0;
  list-style: none;
  flex-wrap: wrap;
}

/* Square targets: a page number is one or two characters, and without a floor
   on the width the boxes would be of different sizes along the row. */
.pagenav__page {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--sb-ctl-sm);
  height: var(--sb-ctl-sm);
  padding: 0 var(--sb-sp-2);
  border-radius: var(--sb-r-btn);
  font-size: var(--sb-fs-body-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--sb-ink);
  text-decoration: none;
}

a.pagenav__page:hover { background: var(--sb-chip); }

/* Filled and not just bold: where you are has to be readable at a glance, and
   weight alone does not carry across a row of numbers. */
.pagenav__page--current {
  background: var(--sb-primary);
  color: #fff;
}

.pagenav__gap {
  padding: 0 var(--sb-sp-1);
  color: var(--sb-muted-2);
  font-size: var(--sb-fs-body-sm);
}

/* Le frecce hanno icona + testo: l'icona da sola non dice quale delle due è,
   a chi sfoglia con lo screen reader come a chi guarda di fretta. */
.pagenav .btn { display: inline-flex; align-items: center; gap: var(--sb-sp-2); }

/* --- §E38 Tour di benvenuto (struttura non ancora attivata) ------------------
   Riusa la modal generica (.modal*): qui c'è solo quello che il tour aggiunge,
   cioè l'illustrazione a tutta larghezza, i passi, i pallini e la navigazione.

   Colonna flex con il solo corpo scorrevole: l'illustrazione è quello che si deve
   vedere per primo, e se scorresse via su uno schermo basso resterebbe fuori
   proprio dove serve. Il cap sull'altezza sostituisce quello dello user agent. */
.modal--tour {
  max-width: 520px;
  max-height: calc(100vh - 32px);
  flex-direction: column;
}
/* The `display` MUST stay behind [open]: an author rule beats the user agent's
   `dialog:not([open]) { display: none }`, and an unconditional one would leave
   the closed dialog in the flow — in absolute position with no offsets, i.e. at
   the foot of the page, backdrop-less. Visible on every page where the tour is
   only reopenable (dashboard button) instead of opening by itself. */
.modal--tour[open] { display: flex; }
.modal--tour .modal__box { margin-top: 0; overflow-y: auto; }

/* L'illustrazione va a filo dei bordi, quindi sta FUORI da .modal__box (che ha i
   suoi 24px di padding) e si arrotonda da sé sui due angoli alti della dialog. */
.wtour__hero {
  position: relative;
  flex: 0 0 auto;
  aspect-ratio: 16 / 9;
  border-radius: 16px 16px 0 0;
  overflow: hidden;
  background: var(--sb-bg-2);
}
/* Le cinque illustrazioni sono impilate e si alternano: sovrapposte in assoluto,
   il riquadro non si affloscia nel momento in cui si cambia passo. */
.wtour__art {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.wtour__art[hidden] { display: none; }

/* La chiusura sta sull'illustrazione, non su un'intestazione che qui non c'è.
   Pastiglia bianca piena: le illustrazioni sono chiare e un'icona nuda ci
   sparirebbe sopra. */
.wtour__close {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--sb-line); border-radius: 50%;
  background: #fff; color: var(--sb-primary-2); cursor: pointer;
  box-shadow: 0 2px 8px rgba(23, 0, 142, .12);
}
.wtour__close:hover { background: var(--sb-chip); }

/* Schermi bassi (portatili 13", finestre affiancate): l'illustrazione cede altezza
   prima di tutto il resto — meglio una fascia più stretta che i bottoni fuori
   schermo o un corpo che parte già scorso. */
@media (max-height: 760px) { .wtour__hero { aspect-ratio: auto; height: 200px; } }
@media (max-height: 620px) { .wtour__hero { height: 140px; } }

/* I pallini sono decorativi (aria-hidden): il passo lo annuncia la sua intestazione.
   Quello attivo si allunga oltre a cambiare colore — la posizione non deve
   dipendere dal solo colore, qui come nei badge. */
.wtour__dots {
  display: flex; justify-content: center; gap: 6px;
  margin: 0 0 18px; padding: 0; list-style: none;
}
.wtour__dot {
  width: 7px; height: 7px; border-radius: var(--sb-r-pill);
  background: var(--sb-chip-line); transition: width .18s ease, background-color .18s ease;
}
.wtour__dot--on { width: 20px; background: var(--sb-primary); }

/* min-height: i passi hanno testi di lunghezza diversa e senza un fondo comune la
   modal si alzerebbe e abbasserebbe a ogni "Avanti", spostando i bottoni sotto il
   dito di chi li sta premendo. */
.wtour__step { text-align: center; min-height: 168px; }
.wtour__step[hidden] { display: none; } /* .btn e le regole di flex battono l'attributo */

.wtour__heading { margin: 0 0 8px; font-size: 1.1rem; color: var(--sb-primary-2); }
.wtour__lead {
  margin: -2px 0 8px; font-size: 0.9rem; font-weight: 600; color: var(--sb-primary);
}
.wtour__text { margin: 0; font-size: 0.93rem; line-height: 1.55; color: var(--sb-muted); }

.wtour__link {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 12px; font-size: 0.9rem; font-weight: 600;
  color: var(--sb-primary); text-decoration: none;
}
.wtour__link:hover { color: var(--sb-primary-2); text-decoration: underline; }

.wtour__cta { margin-top: 14px; }
.wtour__note { margin: 14px 0 0; font-size: 0.88rem; font-weight: 600; color: var(--sb-muted); }

.wtour__actions {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--sb-line-2);
}
.wtour__nav { display: flex; gap: 8px; }
.wtour__nav .btn[hidden] { display: none; }
.wtour__nav .btn[disabled] { opacity: .45; cursor: default; }

/* ==========================================================================
   Required fields — the asterisk on the label
   The view marks the CONTROL with `required`, and the rule below decorates the
   label from it: one source of truth instead of two to keep in sync, and every
   future form inherits the marker without anyone remembering to add it.
   Two agents cannot carry `required`, so each brings its own class instead:
   `.form__rte--required` for rich text (the HTML5 constraint is not reliable on
   a custom element) and `.form__label--required` for a group of checkboxes,
   which has no single control to point at.
   Scoped to `.mng-body` — the guest app is deliberately out (short forms, its
   own CSS); `.auth-page` carries the same rule in auth.css with its own token.
   The child combinator matters: without it the nested label of a
   `form__check-inline` would get an asterisk of its own alongside the group's.
   Hidden inputs never match `:required`, so the empty field a checkbox group
   always submits cannot trip the rule.
   ========================================================================== */
.mng-body .form__row:has(:required) > .form__label::after,
.mng-body .form__row:has(.form__rte--required) > .form__label::after,
.mng-body .form__label--required::after {
  content: " *";
  color: var(--sb-danger);
}

/* ==========================================================================
   Focus: 2px, visible, on EVERYTHING that takes it
   The last word of the sheet, and that is what makes it a net: a rule that drops
   the outline without replacing it, written further up and of equal specificity,
   loses to this one. Rules with a ring of their OWN (.mng-body .btn:focus-visible,
   .mng-body .form__input:focus) are more specific and keep it.
   An outline and not a box-shadow: under forced colours the shadow disappears,
   and with it the only sign of where the tab key has got to.
   ========================================================================== */
.mng-body :focus-visible { outline: 2px solid var(--sb-accent); outline-offset: 1px; }
/* On the dark sidebar green does not separate: there the ring is white. */
.mng-side :focus-visible { outline-color: rgba(255, 255, 255, .85); }
/* The one exception, and it is a substitution rather than a removal: the ring
   is drawn by `.tagsel__field:focus-within` around the whole field. Without this
   line the net above would draw a second one around the caret alone. */
.mng-body .tagsel__input:focus-visible { outline: none; }

/* ==========================================================================
   Direction B — below 900px
   The columns stack, the action bar goes full width and every control returns to
   44px, which is the measure of a finger and not of a pointer.
   Scoped to `.mng-body` like the rules it corrects: without that, `.form__actions`
   below (0-1-0) would lose to `.mng-body .form__actions` (0-2-0) and the whole
   block would do exactly nothing.
   ========================================================================== */
@media (max-width: 900px) {
  .mng-body { --sb-ctl: 44px; --sb-ctl-sm: 36px; }

  .mng-body .panel__section { grid-template-columns: minmax(0, 1fr); }
  .mng-body .panel__section > * { grid-column: 1; }
  /* `.svc-avail` joins the list because it is now one of the things that draws
     the rule above; left out, a block of repeatable rows would keep a divider
     with nothing on the other side of it. */
  .mng-body .panel__section > :is(.form__row, .form__grid, .svc-avail, .form__collapse, .form__hint, .form__sep) {
    border-left: none;
  }

  .mng-body .form__actions { flex-direction: column-reverse; align-items: stretch; }
  .mng-body .form__actions .btn { height: 44px; width: 100%; font-size: var(--sb-fs-body); }
  .mng-body .form__actions .btn--danger { margin-right: 0; }

  .mng-body .btn { height: 44px; }
  .mng-body .btn--sm { height: 36px; }

  .mng-body .form__input[type="date"],
  .mng-body .form__input[type="number"],
  .mng-body .form__amount { width: 100%; }
  .mng-body .form__range { flex-direction: column; align-items: stretch; }
  .mng-body .form__range-sep { display: none; }

  /* Every control reaches the 44px a finger needs, not just the buttons: the
     icon ones are the smallest thing on the page, and they carry delete. */
  .mng-body .icon-btn { width: 44px; height: 44px; }
  .mng-body .dropdown__item { min-height: 44px; }
  .mng-body .chip--filter { min-height: 44px; }
  .mng-body .chip-group__item { height: 44px; }
  .mng-body .form__check-inline { min-height: 44px; }

  /* A table becomes a list of cards. Opt-in, and it has to be: the cell label
     comes from `data-label` on the <td>, so a table whose rows do not carry one
     would turn into unlabelled runs of text. Without the class a table keeps
     scrolling sideways inside .table-card, which is what they all do today. */
  .mng-body .data-table--cards,
  .mng-body .data-table--cards tbody,
  .mng-body .data-table--cards tr,
  .mng-body .data-table--cards td { display: block; width: auto; }
  .mng-body .data-table--cards thead { display: none; }
  .mng-body .data-table--cards tbody tr,
  .mng-body .data-table--cards tbody tr:hover td {
    background: #fff;
  }
  .mng-body .data-table--cards tbody tr {
    border: 1px solid var(--sb-line);
    border-radius: var(--sb-r-card);
    padding: var(--sb-sp-3);
    margin-bottom: var(--sb-sp-3);
  }
  .mng-body .data-table--cards tbody td {
    height: auto; padding: 4px 0;
    border: none; white-space: normal;
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--sb-sp-3);
    text-align: right;
  }
  .mng-body .data-table--cards tbody td::before {
    content: attr(data-label);
    flex: none; text-align: left;
    font-family: var(--sb-sans); font-size: var(--sb-fs-micro); font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase; color: var(--sb-muted);
  }
  /* The actions cell has no label to show, and an empty one would leave a gap
     where the reader expects a word. */
  .mng-body .data-table--cards tbody td:not([data-label])::before { content: none; }
  .mng-body .data-table--cards tbody td:not([data-label]) { justify-content: flex-end; }
  /* The rows are the cards now, so the wrapper stops being one. */
  .mng-body .table-card:has(.data-table--cards) {
    border: none; background: transparent; overflow-x: visible;
  }
}

/* =========================================================================
   §E60 — Widget: booking request form, rendered inside an iframe.

   §E59.2 CORRECTS WHAT THIS COMMENT USED TO SAY. It read "framed by somebody
   else's design, so it must read as part of THEIR page" — a correct intention
   for a context that is not the one this page is seen in. `.cns-frame` is
   `position:absolute; inset:0` INSIDE the widget panel, laid over the chat: the
   customer's page is behind the panel, never around this form. Rendered plain
   it was a white card with slate ink inside a widget a property may have set to
   black, and that was a defect, not restraint.

   So it wears the widget's own surface. The variables are declared on `:root`
   by `widget_surface_css` in the layout — custom properties do not cross an
   iframe boundary, so nothing is inherited from the panel around it.

   THE ONE-TEXT-COLOUR-PER-BACKGROUND RULE HOLDS HERE TOO, and it is the reason
   the secondary text below is `inherit` with an opacity rather than a muted
   colour borrowed from another surface: `--cnsFooterText` is authored against
   `--cnsFooterBg`, and there is no footer on this page. Borrowing it is exactly
   the bug widget_themes.yml exists to prevent.

   It still keeps the panel's form vocabulary (`.form__*`), so a change to a
   field there reaches here too — but repainted, below, in the widget's colours.
   ========================================================================= */
.wgt-body {
  margin: 0;
  background: transparent;
  font-family: var(--cnsFontFamily, var(--font-sans, system-ui, sans-serif));
  color: var(--cnsText, #0f172a);
}

.wgt-main { padding: var(--cnsPadLog, 16px); }

/* THE FRAME IS THE CARD. `.cns-frame` already draws the widget's surface, its
   border and its radius, and this page fills it edge to edge: a second bordered
   box inside it reads as a double border and costs ~36px of padding on a panel
   380px wide. The card keeps only its measure, for the case where this page
   stands on its own. */
.wgt-card {
  max-width: 560px;
  margin: 0 auto;
}

.wgt-card__title { margin: 0 0 6px; font-size: 18px; font-weight: 600; }
.wgt-card__lead { margin: 0 0 16px; font-size: 14px; line-height: 1.5; color: inherit; opacity: .75; }
.wgt-card__body { margin: 12px 0 0; font-size: 14px; line-height: 1.5; color: inherit; }
.wgt-card__note { margin: 12px 0 0; font-size: 13px; line-height: 1.5; color: inherit; opacity: .75; }

/* §E60.1 — the way out at the END of the reading, not only at the top. Full
   width and on its own row, like the form's submit two screens earlier: it is
   the only thing left to do on this page. */
.wgt-card__actions { display: grid; margin: 20px 0 0; }

/* §E61.2 — the panel's preview opens this very page, and here it says so. It
   takes the accent PAIR rather than tinting the widget's own surface: an
   advisory that borrowed a background without its authored ink is exactly the
   "one text colour per background" rule this file keeps everywhere else. */
.wgt-preview {
  margin: 0 0 16px;
  padding: 8px 12px;
  font-size: 13px;
  line-height: 1.4;
  border-radius: var(--cnsRadiusChip, 8px);
  background: var(--cnsAccent, #c8894a);
  color: var(--cnsAccentText, #1d2126);
}

/* The recap sits on the bot bubble's surface, so it takes the bot bubble's ink:
   that pair is authored to contrast, and it is the only secondary surface this
   page has. */
.wgt-summary {
  background: var(--cnsBotBg, #f8fafc);
  color: var(--cnsBotText, inherit);
  border: var(--cnsBorderWidth, 1px) solid var(--cnsBorder, #e2e8f0);
  border-radius: var(--cnsRadiusCard, 10px);
  padding: 12px 14px;
  margin: 0 0 16px;
}

.wgt-summary__type { margin: 0 0 2px; font-weight: 600; font-size: 15px; }
.wgt-summary__dates { margin: 0; font-size: 13px; color: inherit; opacity: .75; }
.wgt-summary__total { margin: 8px 0 0; font-size: 15px; font-weight: 600; }
.wgt-summary__note { margin: 4px 0 0; font-size: 12px; color: inherit; opacity: .75; }

/* The panel's form vocabulary, repainted. Scoped to `.wgt-body` and nowhere
   else: these same classes dress the staff panel, and the widget palette has no
   business there. */
.wgt-body .form__label { color: inherit; }
.wgt-body .form__hint { color: inherit; opacity: .7; }

/* ONE rhythm, in one place. In the panel these rows sit inside a
   `.panel__section`, which supplies the spacing; this page has no section, so
   the two spacings meant for different containers stacked: `.form__row`'s own
   14px bottom margin PLUS `.form__grid`'s 14px gap made 28px between two fields
   of the same grid, while `:last-child` zeroed the margin and left 0 between one
   grid and the next. Wide gap, no gap, wide gap — down the whole form.
   A single grid gap replaces both, and it is the right tool here because the
   hidden fields (`display:none`) and the absolutely positioned honeypot are not
   grid items at all, so neither costs a row of space. */
.wgt-body .form { display: grid; gap: 14px; }
.wgt-body .form__row { margin: 0; }

/* One control, full width — the same shape the widget's own `.cns-search__go`
   and `.cns-book` already have two centimetres away, in the same panel. */
.wgt-body .form__actions { display: grid; }

.wgt-body .form__input {
  background: var(--cnsBg, #fff);
  color: var(--cnsText, #0f172a);
  border: var(--cnsBorderWidth, 1px) solid var(--cnsBorder, #e2e8f0);
  border-radius: var(--cnsRadiusField, 12px);
}

.wgt-body .form__input:focus {
  border-color: var(--cnsBrand, #1f7ae0);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cnsBrand, #1f7ae0) 30%, transparent);
}

/* §E59.4 — the BUTTON pair, not the brand colour with white written into it.
   This was the last hardwired `#fff` left over from the sweep that closed the
   same defect inside widget.js: white on a pale blue reads at 1.92:1, and
   nobody had authored it to contrast. It only became visible when §E61.2 let
   the panel's preview open this form. */
.wgt-body .btn--primary {
  background: var(--cnsButton, #1c6fcf);
  border-color: var(--cnsButton, #1c6fcf);
  border-radius: var(--cnsRadiusButton, 12px);
  color: var(--cnsButtonText, #fff);
}

/* Disabled only ever happens in the preview, where the request is not sent. */
.wgt-body .btn--primary:disabled { opacity: .55; cursor: not-allowed; }

/* The honeypot. Hidden from EYES and from assistive technology alike: left
   merely off-screen, a screen reader would announce it and somebody would
   dutifully fill it in — turning an anti-spam field into a trap for the very
   visitors it exists to let through. */
.wgt-hp {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* =========================================================================
   §E59.1 — Widget configuration page: snippet block and live preview.

   §E59.3 — it is now an IFRAME running the real widget.js, so there is no
   second implementation of anything here. The style tokens and the twelve
   colours are painted live onto the HOST element inside that iframe by
   widget_preview_controller — same origin, so `contentDocument` is readable,
   and custom properties on the host cross into the shadow root. That is the
   very contract we document to customers for re-theming from their own
   stylesheet, so the preview proves it works.
   ========================================================================= */

/* The copy button sits ABOVE the snippet, not floating over it: the snippet
   wraps onto several lines, and an overlaid button would cover the last one —
   which is the line carrying the key. */
/* §E59.5 — la rigenerazione della chiave, accanto alla chiave. Staccata di un
   filetto dal resto della riga perché è un'AZIONE e non un altro campo: senza,
   il pulsante si legge come parte del blocco «copia questo valore». */
.wsnip__regen {
  margin-top: var(--sb-sp-3);
  padding-top: var(--sb-sp-3);
  border-top: 1px solid var(--sb-line-2);
}

.wsnip__regen .form__hint { margin-top: var(--sb-sp-1); }

.wsnip { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.wsnip__copy { align-self: flex-start; }
.wsnip__code {
  width: 100%;
  margin: 0;
  padding: 12px 14px;
  background: var(--sb-surface-2, #f8fafc);
  border: 1px solid var(--sb-line);
  border-radius: var(--sb-r-input, 10px);
  font-size: 12px;
  line-height: 1.6;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-all;
}

/* A colour field is a SQUARE swatch with the hex code beside it, not a
   full-width colour bar: twelve of those filled the column and pushed the
   preview off screen, which is the one thing this page needs on screen while
   the colours are chosen. The swatch is a real `input[type=color]`, shrunk —
   the browser's own picker is the only one that works on every platform — and
   `appearance: none` plus the two vendor pseudo-elements are what let it be
   square at all. The hex box carries NO name: the swatch is the field that gets
   submitted (see color_input_controller). */
.colorpick {
  display: flex;
  align-items: center;
  gap: 8px;
}

.colorpick__swatch {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--md-outline-variant, #dbeafe);
  border-radius: var(--md-shape-xs, 8px);
  background: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

.colorpick__swatch::-webkit-color-swatch-wrapper { padding: 3px; }
.colorpick__swatch::-webkit-color-swatch { border: none; border-radius: 5px; }
.colorpick__swatch::-moz-color-swatch { border: none; border-radius: 5px; }

.colorpick__swatch:hover { border-color: var(--md-outline, #94a3b8); }

.colorpick__swatch:focus-visible {
  outline: none;
  border-color: var(--md-primary, #2563eb);
  box-shadow: 0 0 0 2px var(--md-primary, #2563eb);
}

/* `flex-basis` and not `width: 100%`: the box inherits `.form__input`, whose
   full width would fight the swatch for the row instead of sitting beside it. */
.colorpick__hex {
  flex: 0 1 132px;
  min-width: 0;
  padding: 9px 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* The right-hand column follows the reader down the left one: twelve colours
   are only really chosen if the thing they colour is on screen while you pick
   them. Sticky only where there are two columns — stacked, it would pin the
   preview over the fields it is meant to illustrate.
   It parks below the TWO sticky bands above it, with the same 110px the panel's
   own scroll anchors use for the page header: the shell's topbar is a variable,
   the header's height is not measurable in CSS. Pinned at 20px from the window
   instead, the preview scrolled UNDER the page header, and the first thing to go
   was the widget's own header — which is what half of these colours paint. */
@media (min-width: 1100px) {
  .form__cards-col--sticky {
    position: sticky;
    top: calc(var(--mng-top-h, 0px) + 110px + var(--sb-sp-5, 20px));
  }

  /* §E59.8 — appuntata, la cornice prende lo SPAZIO DISPONIBILE invece di
     un'altezza fissa: più alta dove c'è posto, e mai tanto da finire sotto la
     barra azioni — che è `sticky; bottom: 0` con fondo opaco e coprirebbe
     l'angolo in cui il widget mette il pulsante. Finché il launcher era nascosto
     laggiù non c'era niente, e il difetto non poteva vedersi.
     Il vincolo sta sulla SEZIONE e non sull'iframe, così l'intestazione (titolo,
     nota, interruttore) si misura da sé e non resta un numero da tenere
     allineato: l'unico sottraendo è l'altezza della barra azioni. */
  .wpv {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - var(--mng-top-h, 0px) - 110px - var(--sb-sp-5, 20px) - 92px);
  }

  .wpv__frame {
    height: auto;
    flex: 1 1 auto;
    /* Il pavimento vale più del vincolo: sotto questa misura la disposizione più
       alta si vedrebbe mozzata proprio dove la si sta giudicando. */
    min-height: 460px;
    max-height: 820px;
  }
}

/* §E59.2 — one theme is ONE block: its switch, its name, its twelve colours.
   The wrapper carries the field column's own gutter because the section's
   padding rule only dresses the blocks it knows by name, and a `div` dropped
   between them would be indented off nothing — which is why "Chiaro" and
   "Scuro" sat hard against the dividing rule while every field beside them was
   inset. */
/* §E65.1 — the widget upgrade page (Essential + Reservations). */
.wup__points { list-style: none; margin: 0 0 var(--sb-sp-4); padding: 0;
  display: grid; gap: var(--sb-sp-3); grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.wup__point { display: flex; gap: var(--sb-sp-3); align-items: flex-start; padding: var(--sb-sp-3);
  border: 1px solid var(--sb-line); border-radius: 12px; }
.wup__icon { flex: none; display: grid; place-items: center; width: 40px; height: 40px;
  border-radius: 10px; background: var(--sb-chip); color: var(--sb-primary-2); }
.wup__point-title { margin: 0 0 4px; font-size: 1rem; font-weight: 600; color: var(--sb-ink); }
.wup__point-text { margin: 0; font-size: .9rem; line-height: 1.45; color: var(--sb-muted); }
/* The SAME columns as `.wup__points` below, so each screenshot sits centred over
   its point; capped at 300px because at full column width they pushed the
   explanation below the fold. */
.wup__shots { display: grid; gap: var(--sb-sp-3); margin: 0 0 var(--sb-sp-4);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
.wup__shot { margin: 0; width: 100%; max-width: 300px; justify-self: center; }
.wup__shot img { display: block; width: 100%; height: auto; border-radius: 12px;
  border: 1px solid var(--sb-line); background: var(--sb-chip); }
.wup__shot figcaption { margin-top: 6px; font-size: .85rem; color: var(--sb-muted); text-align: center; }
.wup__plan { margin: 0 0 var(--sb-sp-3); color: var(--sb-ink); }

/* §E65 — the site-scan section: the page list and "scan now" side by side. */
.wsc__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

.wthm {
  padding: var(--sb-sp-4) var(--sb-sp-5);
}

.wthm + .wthm {
  padding-top: var(--sb-sp-5);
  border-top: 1px solid var(--sb-line-2);
}

/* The name of the theme, not a section heading: it labels the twelve fields
   under it, so it is close to them and further from what came before. Left with
   the browser's own `h3` margins it was `1em` of a bigger font on both sides —
   the same gulf above and below, which read as a heading belonging to neither
   block. */
.wthm__title {
  margin: var(--sb-sp-4) 0 var(--sb-sp-3);
  font-family: var(--sb-display);
  font-size: var(--sb-fs-head);
  font-weight: 600;
  color: var(--sb-ink);
}

/* §E59.5 — il nome di un gruppo di colori, dentro il blocco di un tema.

   Sans, scuro e con un filetto che corre fino al bordo: deve leggersi come UN
   LIVELLO SOPRA le etichette dei campi, che sono mono, maiuscole, minute e
   grigie. La prima versione usava lo stesso trattamento delle etichette e
   spariva fra di esse — un titolo che somiglia a ciò che titola non titola
   niente. Il filetto fa il lavoro che in una tabella farebbe una riga vuota,
   senza costare spazio verticale. */
.wthm__group {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-3);
  margin: var(--sb-sp-5) 0 var(--sb-sp-3);
  font-family: var(--sb-sans);
  font-size: var(--sb-fs-body-sm);
  font-weight: 700;
  color: var(--sb-ink);
}

.wthm__group::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--sb-line);
}

.wthm__group:first-of-type { margin-top: 0; }

/* Trentadue selettori di colore stanno su una pagina solo se ogni riga costa il
   minimo: il ritmo verticale lo dà il `gap` della griglia, quindi il margine
   che ogni `form__row` si porta dietro qui sarebbe sommato a quello e
   raddoppierebbe ogni intervallo. */
.wthm__colors {
  gap: var(--sb-sp-3) var(--sb-sp-4);
}

.wthm__colors .form__row { margin-bottom: 0; }
.wthm__colors .form__hint { margin-top: var(--sb-sp-1); }

/* Un tema alla volta. Quello spento resta chiuso: sedici caselle di un tema che
   la struttura non ha acceso sono sedici caselle fra chi configura e quello che
   sta configurando. Un `<details>` e non dei tab, perché non serve JavaScript e
   perché due pannelli si possono anche voler aprire insieme, per confrontarli. */
.wthm__box {
  border-top: 1px solid var(--sb-line-2);
  margin-top: var(--sb-sp-3);
}

.wthm__summary {
  cursor: pointer;
  padding: var(--sb-sp-3) 0;
}

.wthm__summary:focus-visible {
  outline: 2px solid var(--sb-primary-500);
  outline-offset: 2px;
  border-radius: var(--sb-r-input, 10px);
}

.wthm__summary .wthm__title {
  display: inline;
  margin: 0;
}

/* §E59.3 — l'anteprima è un IFRAME che monta widget.js vero, non più un disegno
   a mano. Quello che qui c'era — `.wpv__stage`, `.wpv__panel`, `.wpv__msg` e il
   resto — riproduceva la geometria del widget una seconda volta, e con più di un
   layout sarebbe diventata una seconda geometria PER LAYOUT. Ora non c'è niente
   da tenere allineato: quel che si vede è il widget.

   Resta di nostro solo la cornice, e lo sfondo a scacchi sta dentro l'iframe
   (`layouts/widget_preview`) perché è parte di ciò che si guarda: un widget
   bianco su un sito bianco è il difetto che si viene a cercare qui. */
.wpv__frame {
  display: block;
  width: 100%;
  /* Alta quanto la disposizione più alta che il widget sa disegnare (`home`,
     660px) più il respiro che si lascia ai bordi: una cornice più corta
     mozzerebbe proprio la disposizione che si sta giudicando. */
  height: 700px;
  border: 0;
  border-radius: var(--sb-r-input, 10px);
  background: #eef2f7;
}

/* ==========================================================================
   Showcase phone frame (Demo::DevicesController).
   A wide screen sees the guest app inside a phone; the app itself runs in an
   iframe, so nothing in here reaches into it — this is only the bezel.
   The screen is 390×844 (a current phone), scaled down to the window height.
   ========================================================================== */
.g-device-body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  background:
    radial-gradient(1200px 600px at 20% 0%, var(--c-blue-300) 0%, transparent 60%),
    linear-gradient(160deg, var(--c-blue-700) 0%, var(--c-blue-900) 100%);
}
.g-device {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
  box-sizing: border-box;
}
.g-device__phone {
  position: relative;
  box-sizing: border-box;
  height: min(844px + 28px, calc(100dvh - 48px));
  aspect-ratio: 418 / 872;
  padding: 14px;
  border-radius: 56px;
  background: #0f172a;
  box-shadow:
    0 0 0 2px #334155,
    0 30px 80px rgba(2, 12, 27, .55);
}
.g-device__screen {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 42px;
  background: var(--c-azure-50);
}

/* §E66.2 — the pre check-in form. It lives on the guest skeleton (.g-body,
   .g-form-card, .form__*) and adds only what that skeleton lacks: the guest
   list, a real checkbox, the place search menu and the photo warning. Every
   element Stimulus hides has its [hidden] rule — an author `display` beats the
   user agent's, and without it the element would stay on screen. */
.pck-warning {
  display: flex; gap: 10px; align-items: flex-start; margin: 0 0 16px; padding: 12px 14px;
  border-radius: 12px; background: var(--c-azure-50); color: var(--c-ink); font-size: .95rem;
}
.pck-warning svg { flex: none; margin-top: 2px; color: var(--c-blue-500); }
.pck-guests { margin: 0 0 14px; }
.pck-guest { align-items: flex-start; }
.pck-guest__name { margin: 0 0 4px; }
.pck-guest__meta { margin: 0; font-weight: 400; color: var(--c-ink-soft); font-size: .92rem; }
.pck-guest__actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.pck-guest__actions form { margin: 0; }
.pck-state { display: inline-flex; gap: 4px; align-items: center; }
.pck-state--done { color: var(--c-blue-700); }
.pck-state--todo { color: var(--c-ink-soft); }
.pck-fieldset { border: 0; margin: 0 0 18px; padding: 0; min-width: 0; }
.pck-fieldset[hidden] { display: none; }
.pck-fieldset__title { font-size: 1.05rem; margin-bottom: 8px; }
/* The sentence under the document legend: `.form__hint` is built to sit UNDER a
   control (6px above, nothing below), so here it touched the next label. */
.pck-fieldset > .form__hint { margin: 0 0 12px; }
.pck-choices { display: flex; gap: 10px; flex-wrap: wrap; }
.pck-choice { display: inline-flex; gap: 8px; align-items: center; min-height: 44px; }
.pck-consent { border: 0; margin: 0 0 12px; padding: 0; min-width: 0; }
.pck-consent .form__row[hidden] { display: none; }
.pck-check { display: flex; gap: 10px; align-items: flex-start; margin: 6px 0 12px; }
.pck-check__input { flex: none; width: 22px; height: 22px; margin-top: 2px; }
.pck-check__label { font-weight: 400; }
.pck-place { position: relative; }
.pck-place__menu {
  position: absolute; left: 0; right: 0; z-index: 5; margin: 4px 0 0; padding: 4px; list-style: none;
  max-height: 260px; overflow-y: auto; background: var(--c-white); border: 1px solid var(--c-azure-100);
  border-radius: 12px; box-shadow: 0 10px 24px rgba(20, 94, 168, 0.14);
}
.pck-place__menu[hidden] { display: none; }
.pck-place__option, .pck-place__empty { padding: 10px 12px; border-radius: 8px; }
.pck-place__option { cursor: pointer; }
.pck-place__option:hover, .pck-place__option[aria-selected="true"] { background: var(--c-azure-100); }
.pck-place__empty { color: var(--c-ink-soft); }
.pck-notice { margin: 0 0 18px; }
.pck-choice { cursor: pointer; }

/* §E66.2.1 — the pre check-in on a tablet or a computer. Unlike the QR app the
   link arrives by email, days before arrival, and is often opened at a desk;
   the form is long, and copied from a document held in the other hand.
   The frame around the column (page colour, hairline, width) is the guest
   app's, §E67 below; what stays here is the form's own: its column is 680px
   and not 960 — about 640 of content, two fields of ~300px.

   Pairs of fields (`.form__grid`, 1fr 1fr from 640px). Outside the panel the
   two spacings stack — `.form__row`'s 14px margin PLUS the grid gap, while
   `:last-child` zeroes the second cell — so a pair got 28px inside it on a
   phone and none after it. One rhythm: the rows drop their margin, the grid
   carries it. Labels line up because grid cells stretch and start at the top. */
.pck-body .form__grid { margin: 0 0 14px; }
.pck-body .form__grid:last-child { margin-bottom: 0; }
.pck-body .form__grid > .form__row { margin: 0; }

@media (min-width: 768px) {
  /* The notice keeps the line length it has in the phone column. */
  .pck-body .pck-notice { max-width: 70ch; }

  /* Buttons at their own width, in a row. The DOM stays [primary, back] — the
     primary on top on a phone — and `row-reverse` sets it on the right, where
     the panel puts it too. */
  .pck-body .g-form-card .form__actions { flex-direction: row-reverse; justify-content: flex-start; }
  .pck-body .g-form-card .form__actions .btn { width: auto; min-width: 200px; }
}

/* ==========================================================================
   §E67 — The guest app on a tablet (ADR-0089).

   One column, as on a phone, that from 768px widens to 960px and stands out
   from the page around it; inside it the content rearranges. Two mechanisms,
   and keeping them apart is the point:
   - the FRAME (column width, page, gutter) follows the WINDOW: a media query;
   - the CONTENT follows the COLUMN: a container query on `.g-app`. The lesson
     is written above `.g-grid`'s narrow-screen rule — a breakpoint on the
     viewport once sent the grid to three columns while the column was still
     560px, and the cards came out narrower than on a phone.

   768px is also `Demo::Config::DEVICE_MIN_WIDTH`, and 768/960 are repeated in
   `GuestHelper::TABLET_*` for the images' `sizes`: CSS cannot read either, so
   `test/assets/guest_tablet_css_test.rb` keeps them together.

   The themed sheets write their descendants at 0-3-0 and load AFTER this one:
   a tablet override of a property a theme owns (hero height, grid columns,
   media sizes) goes in the "§E67 — tablet" section at the end of that sheet,
   with the same selector. Only what no theme touches lives here.
   ========================================================================== */
.g-body {
  --g-app-width: 560px;
  /* The side padding the content keeps from the column edge. Each themed sheet
     sets its own; every rule that used to write it as a literal reads this,
     negative margins of the full-bleed bands included. */
  --g-gutter: 20px;
  /* The reading width: forms, chat and text pages stay this wide on a tablet. */
  --g-measure: 720px;
  /* The page around the column. Each themed sheet remaps it next to its own
     `--gt-*`. */
  --g-backdrop: var(--sb-page);
  --g-frame-line: rgba(15, 23, 42, .08);
}

/* Named, always: `.form` is a container too, and an unnamed query would bind to
   the nearest container of any kind. The pre check-in is not one — its column
   stops at 680px and its layout rules are its own, above.
   `inline-size` applies no layout containment: the topbar that floats over a
   cover photo still positions against `.g-app`, and the tab bar and the push
   dialog live outside it anyway. */
.g-body:not(.pck-body) .g-app { container: guest / inline-size; }

/* --- Narrow by default, collections widen -----------------------------------
   A page of text, a form or the chat at 900px is a line nobody reads to the
   end: on a tablet the page keeps the reading width and centres it. A page
   that lays out a COLLECTION takes the whole column back. Pure CSS, no hook in
   the views — so the list below is the contract: a new collection class
   belongs in it, or its page renders as a narrow column with empty sides.
   A variable and not a rule on `.g-main`, because the section nav above the
   page has to line up with it, and the list must exist once. It is declared on
   the column itself, outside the container query — a container cannot be
   styled by its own query — and only read inside it. */
.g-body:not(.pck-body) .g-app { --g-page-width: calc(var(--g-measure) + 2 * var(--g-gutter)); }
.g-body:not(.pck-body) .g-app:has(.g-main :is(.g-grid, .g-reqs, .g-announces, .g-detail__media)) {
  --g-page-width: none;
}

@media (min-width: 768px) {
  .g-body { --g-app-width: 960px; }
  .g-body.pck-body { --g-app-width: 680px; }

  /* `:root` lifts both to 0-3-0 and more: each themed sheet declares its tokens
     and paints the body at 0-2-0, after this one. A phone in landscape is wider
     than 768px too, hence the notch in the gutter (`viewport-fit=cover`). */
  :root .g-body[data-guest-template] { background: var(--g-backdrop); }
  :root .g-body[data-guest-template]:not(.pck-body) {
    --g-gutter: max(32px, env(safe-area-inset-left), env(safe-area-inset-right));
  }

  /* The column was a strip with no edge, on a page of the SAME colour. A
     full-height column and not a card with margins and rounded corners: the
     sticky topbar has to meet the top of the window. */
  .g-app {
    box-shadow: 0 0 0 1px var(--g-frame-line), 0 16px 48px rgba(15, 23, 42, .08);
  }
}

@container guest (min-width: 720px) {
  /* The page width decided above, for the page and the section nav. */
  .g-main,
  .g-sectionnav__list {
    width: 100%;
    max-width: var(--g-page-width);
    margin-inline: auto;
  }

  /* `classic` and `green` let the flash run to the column edge, which at
     960px is a band and not a message. The other themes already keep it in. */
  .g-flash { margin-inline: var(--g-gutter); }

  /* --- Catalogue -----------------------------------------------------------
     Three columns now that the COLUMN is wide: ~280px cards at 960, a little
     wider than the 254 a phone's largest column gives. The photo card keeps
     spanning two rows. `night` and `utility` draw rows, not cards, and decide
     in their own sheets. */
  .g-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }

  /* Two scheduled messages sit side by side; a single one spans the row. */
  .g-announces {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr));
  }
  .g-announces__past {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
  }

  /* --- Category header: the photo beside the title, not above it ---------- */
  .g-cat-head:has(> .g-cat-head__media) {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    grid-template-rows: 1fr auto auto 1fr;
    column-gap: 28px;
    margin-bottom: 24px;
  }
  .g-cat-head:has(> .g-cat-head__media) > * { grid-column: 2; }
  .g-cat-head:has(> .g-cat-head__media) > .g-cat-head__media {
    grid-column: 1; grid-row: 1 / -1; aspect-ratio: 16 / 10; margin: 0;
  }
  .g-cat-head:has(> .g-cat-head__media) > .g-page__title { grid-row: 2; }
  .g-cat-head:has(> .g-cat-head__media) > .g-page__sub { grid-row: 3; margin-bottom: 0; }
  .g-page__title { font-size: 1.85rem; }

  /* --- Service detail: photo | text ----------------------------------------
     Only with a photo — without one the page is text and reads at the reading
     width. No markup change: the photo is the first child, and the last row
     takes whatever the photo is taller than the text. Sticky, so a long
     description scrolls beside it instead of leaving it behind. */
  .g-detail:has(> .g-detail__media) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: repeat(5, auto) 1fr;
    column-gap: 36px;
    align-items: start;
  }
  .g-detail:has(> .g-detail__media) > * { grid-column: 2; }
  .g-detail:has(> .g-detail__media) > .g-detail__media {
    grid-column: 1; grid-row: 1 / -1; margin-bottom: 0;
    position: sticky; top: calc(var(--g-topbar-h) + 16px);
  }
  .g-detail__title { font-size: 2rem; }
  /* Without a photo the page is text at the reading width, and a full-width
     button under it was a 720px bar: it takes its own width, like the form's. */
  .g-detail:not(:has(> .g-detail__media)) .g-detail__cta .btn--block {
    display: inline-flex; width: auto; min-width: 260px;
  }

  /* --- Request form ---------------------------------------------------------
     The button at its own width, on the right where the panel puts it: full
     width it was a 650px bar. Day and time side by side. */
  .g-form-card .form__actions { justify-content: flex-end; }
  .g-form-card .form__actions .btn { width: auto; min-width: 220px; }
  .g-slot { flex-direction: row; align-items: flex-start; }
  .g-slot > * { flex: 1 1 0; min-width: 0; }

  /* The access code is six digits: a card the width of the page around them
     would be mostly empty. The button goes back to the card's width. */
  .g-code .g-form-card { max-width: 420px; margin-inline: auto; }
  .g-code .g-form-card .form__actions .btn { width: 100%; }

  /* --- My requests, information, chat --------------------------------------- */
  .g-reqs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    align-items: start;
  }
  .g-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
  }
  /* Phone and WhatsApp side by side when there are both. */
  .g-contacts:has(> .g-contact):has(> .g-wa-btn) {
    display: grid; grid-template-columns: 1fr 1fr; align-items: center;
  }
  .g-msg { max-width: 78%; }

  /* --- Opening of the `classic` home ---------------------------------------
     Taller with the column, but capped by the window — and never shorter than
     on a phone: a phone in landscape is wider than 768px and only ~390px tall,
     and below the phone's height the floating topbar covers the room badge. */
  .g-body[data-guest-template="classic"] .gt-hero--cover { height: clamp(260px, min(40cqi, 46svh), 380px); }
  .g-body[data-guest-template="classic"] .gt-hero--cover .gt-hero__body { bottom: 28px; }
  .g-body[data-guest-template="classic"] .gt-hero__title { font-size: 2.3rem; }
}

/* §E66.4 — the document section of the desk's guest form is a target the
   fields controller hides for family and group members. A section holding form
   rows is `display: grid` by an author rule (the two-column form, §3.1), which
   beats the user agent's `[hidden]`: without this the block would stay on
   screen for everyone. */
.mng-body .panel__section[hidden] { display: none; }

/* §E66.4 — the guests of an arrival on the day sheet: a line of their own under
   the room (the row is a wrapping flex), each name with its state pill. */
.mstay-row__guests {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sb-sp-2) var(--sb-sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--sb-fs-body-sm);
}
.mstay-row__guests > li { display: inline-flex; align-items: center; gap: var(--sb-sp-2); }

/* §E66.4 — a cell note right under a status pill (who checked a guest, and
   when): the pill's tinted box needs more air than plain text above it, or the
   note reads as glued to it. */
.data-table .msvc-badge + .msvc-answer-note { margin-top: var(--sb-sp-2); }
