/*
 * 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 rgba(70, 180, 80, 0.18);
}

.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: rgba(70, 180, 80, 0.12);
  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: rgba(70, 180, 80, 0.12);
  color: var(--c-ok);
}

.badge__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--c-ok);
  box-shadow: 0 0 0 4px rgba(70, 180, 80, 0.18);
}

/* --- 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;
  font-size: var(--sb-fs-meta);
  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;
}

.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);
}

.panel__eyebrow {
  margin: 0 0 6px;
  font-family: var(--sb-mono);
  font-size: var(--sb-fs-meta);
  font-weight: 400;
  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-meta);
  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):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) {
  padding-left: var(--sb-sp-5);
  padding-right: var(--sb-sp-5);
}

/* 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-meta);
  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: #178bc3;
  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; }

/* §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: 560px;
  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); }

.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); }

/* 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. */
/* 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; }
.g-req__subject { margin: 0 0 6px; font-weight: 700; color: var(--c-ink); line-height: 1.35; }
.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: 16px;
  padding: 16px 18px;
  box-shadow: 0 6px 18px rgba(20, 94, 168, 0.08);
}
.board-counter__num { display: block; font-size: 1.8rem; font-weight: 800; color: var(--c-blue-900); line-height: 1; }
.board-counter__label { font-size: 0.82rem; 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: 0.95rem;
  font-weight: 800;
  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; }

.req-card {
  background: var(--c-white);
  border: 1px solid var(--c-azure-100);
  border-radius: 16px;
  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: #eefaf0; box-shadow: 0 0 0 2px var(--sb-green); }
  to   { background: var(--c-white); box-shadow: 0 0 0 0 rgba(70, 180, 80, 0); }
}
.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: 800; color: var(--c-blue-900); }
.req-card__guest { font-size: 0.85rem; 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: 0.82rem; font-weight: 600; color: #178bc3;
}
.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: 0.8rem; font-weight: 600; color: var(--c-blue-700); }
.req-card__price { color: var(--sb-warn-fg, #92590F); }
.req-card__time { margin: 0 0 6px; font-size: 0.85rem; color: var(--c-blue-700); font-weight: 600; }
.req-card__meta { margin: 0 0 10px; font-size: 0.75rem; 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: 32px; height: 32px; padding: 0;
  border: 1px solid var(--sb-line); 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);
}

/* Storico richieste evase: giorno -> camera -> soggiorno -> item. */
/* Barra filtri dello storico: card dedicata sopra i giorni. */
.reqh-filters, .plan-filters, .rooms-filters, .mfilter-card { background: #fff; border: 1px solid var(--sb-line); border-radius: 16px; box-shadow: var(--sb-shadow); padding: 16px 20px; margin: 0 0 20px; }
.reqh-filter, .plan-filter, .rooms-filter { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; margin: 0; }
.reqh-filter__field, .plan-filter__field, .rooms-filter__field { display: flex; flex-direction: column; gap: 4px; font-size: 0.8rem; font-weight: 600; color: var(--c-ink-soft); }
/* Le azioni a destra finché c'è spazio; a capo con i campi quando manca. */
.reqh-filter__actions, .plan-filter__actions, .rooms-filter__actions { display: flex; align-items: flex-end; gap: 8px; margin-left: auto; }
/* 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. */
.reqh-filter .form__input, .plan-filter .form__input, .rooms-filter .form__input, .mfilter .form__input,
.reqh-filter .btn, .plan-filter .btn, .rooms-filter .btn, .mfilter .btn {
  height: 44px;
}
.reqh-day { background: #fff; border: 1px solid var(--sb-line); border-radius: 16px; 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); }
.reqh-card--cancelled { border-color: var(--sb-danger, #C62828); }
.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__state { flex: none; font-size: 0.65rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em; padding: 2px 8px; border-radius: 999px; }
.reqh-card--done .reqh-card__state { background: rgba(70, 180, 80, 0.14); color: var(--sb-green-fg, #147045); }
.reqh-card--cancelled .reqh-card__state { background: #fdecec; color: var(--sb-danger, #C62828); }
.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: 16px; 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);
}
.plan__bar {
  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: rgba(70, 180, 80, 0.18); 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: rgba(70, 180, 80, 0.18); 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; }

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


/* §E45.5b — The booking sheet on two columns: the bill on the left (rooms and
   price), the extras form on the right. You add a breakfast while looking at
   what has already been sold, which is why this used to mean scrolling up and
   down. The threshold is 1280 and not 1100: the left side carries a price table
   of four columns, and below that width splitting it in two makes the table
   unreadable before it makes the form convenient. Stacked, the bill comes
   first. */
/* Detached blocks: the sheet is not a stack of bands separated by a hairline,
   so each piece gets room around it. */
.mbook-sheet > .panel__section, .mbook-sheet > .mbook-money { margin-bottom: var(--sb-sp-5); }
.mbook-sheet > :last-child { margin-bottom: 0; }

.mbook-money { display: grid; grid-template-columns: 1fr; gap: var(--sb-sp-5); align-items: start; }
.mbook-money__main { min-width: 0; display: flex; flex-direction: column; gap: var(--sb-sp-4); }
/* Same column as its twin: the aside carries TWO cards — the extras and the
   rectification — and `.panel__section` has no margin of its own, so without
   this they touch and read as one. */
.mbook-money__aside { min-width: 0; display: flex; flex-direction: column; gap: var(--sb-sp-4); }
@media (min-width: 1280px) {
  .mbook-money { 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,
.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,
.mbook-money__main > .panel__section,
.mbook-money__aside > .panel__section,
.mbook-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); }
/* 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. */
.dash-card__link { display: inline-block; margin-top: var(--sb-sp-4); font-size: var(--sb-fs-body-sm); font-weight: 600; }

/* 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: #178bc3; 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: 12px; padding: 14px; background: #fff; }
.msvc-card:hover { border-color: #178bc3; box-shadow: var(--sb-shadow); }
.msvc-card__name { margin: 0; font-weight: 700; color: var(--c-ink); font-size: 0.95rem; }
.msvc-card__price { margin: 0; font-weight: 700; color: #178bc3; 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); border-radius: 9px;
  background: #fff; color: var(--c-ink-soft); cursor: pointer; text-decoration: none;
}
.icon-btn:hover { border-color: #178bc3; color: #178bc3; }
.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: 12px; 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: 0.9rem; }
.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: 0.9rem; }
/* §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; }

/* 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: #178bc3; }
.msvc-cat__drag:active, .msvc-card__drag:active { cursor: grabbing; color: #178bc3; }
.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 #178bc3; 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; }
.msvc-badge--on { background: rgba(70,180,80,0.14); color: var(--c-ok); }
.msvc-badge--off { background: #eef1f4; color: var(--c-ink-soft); }
.msvc-badge--ext { background: #efe6ff; color: #6b3fd0; }
/* 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; }

/* 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: 12px; padding: 14px; background: #fff; }
.mfaq-card:hover { border-color: #178bc3; 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: 9px; background: var(--sb-chip); color: #178bc3; 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: 12px; padding: 14px; background: #fff; }
.mmsg-card:hover { border-color: #178bc3; 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: 9px; background: var(--sb-chip); color: #178bc3; 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; }

/* 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: #178bc3; }
.mfaq-card__drag:active, .mdoc-card__drag:active, .mmsg-card__drag:active { cursor: grabbing; color: #178bc3; }
.mfaq-card.sortable-ghost, .mdoc-card.sortable-ghost, .mmsg-card.sortable-ghost {
  background: var(--sb-chip); border: 1px dashed #178bc3;
}
.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; }

/* 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: 12px; padding: 14px; background: #fff; }
.mdoc-card:hover { border-color: #178bc3; 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: 9px; background: var(--sb-chip); color: #178bc3; 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-family: var(--sb-mono); 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; }


/* ==========================================================================
   §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). */
.stat-bar__fill { display: block; height: 100%; 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: 14px; padding: 14px 16px; }
.stat-counter__num { display: block; font-size: 1.7rem; font-weight: 800; color: var(--c-blue-900); line-height: 1; }
.stat-counter__label { font-size: 0.82rem; color: var(--c-ink-soft); font-weight: 600; }

/* Le quattro fasi di una richiesta, con i colori che il pannello usa già altrove
   (`.mreq__status--*`): non si inventa un quinto vocabolario di stato. Sono
   colori RISERVATI, non tinte di serie — non compaiono mai nei grafici accanto.
   L'etichetta sotto il numero resta sempre: il colore non è mai l'unico segnale. */
.stat-counter--received    { background: var(--sb-chip); }
.stat-counter--in_progress { background: var(--sb-warn-bg); }
.stat-counter--done        { background: #E7F5EC; }
.stat-counter--cancelled   { background: #FDECEC; }
.stat-counter--received    .stat-counter__num { color: var(--sb-primary-2); }
.stat-counter--in_progress .stat-counter__num { color: var(--sb-warn-fg); }
.stat-counter--done        .stat-counter__num { color: var(--sb-green-fg); }
.stat-counter--cancelled   .stat-counter__num { color: var(--sb-danger); }

/* §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%; 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%; 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. */
.chip--locked {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--sb-bg); border-color: var(--sb-line);
  color: var(--sb-muted-2); cursor: not-allowed;
}
.chip--locked:hover { background: var(--sb-bg); }

/* --- 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 { 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-mono); font-size: var(--sb-fs-label); font-weight: 400;
  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: rgba(70, 180, 80, 0.14); border-color: rgba(70,180,80,0.3); 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: rgba(70,180,80,0.14); color: var(--sb-green-fg, #147045); }
.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);
}

/* --- 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;
  --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 */
  --sb-fs-label:   0.66rem;  /* 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 {
  height: 34px;
  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;
}
.mng-body .btn--sm { height: 28px; padding: 0 10px; font-size: var(--sb-fs-meta); }
/* 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; }

/* Il salvataggio di una form è verde: è l'unico primario che CONFERMA un
   cambiamento, e si distingue dai primari che portano altrove (crea, esporta).
   Scoped a `.form__actions`, che è l'unico posto dove quel pulsante sta.
   Il `.btn--locked` è escluso: un pulsante spento non si colora.

   È `--sb-green-fg` e NON `--sb-green` (#46b450), che è un verde da RIEMPIMENTO:
   con testo bianco quello dà 2,66:1, sotto il 4,5:1 che WCAG AA chiede per il
   testo di un controllo. È la ragione per cui la palette porta due verdi e
   chiama il secondo `-fg`.
   Il token è dichiarato DUE volte: #147045 più in basso vale per `.g-body`, il
   pannello prende il #0A5340 del suo `:root` — misurato nel browser, 9,04:1 su
   bianco. Il fallback del `var()` resta il valore chiaro, che è il peggiore dei
   due: se sparisse il token si tornerebbe comunque sopra soglia. */
.mng-body .form__actions .btn--primary:not(.btn--locked) {
  background: var(--sb-green-fg, #147045);
  border-color: var(--sb-green-fg, #147045);
  color: #fff;
}
.mng-body .form__actions .btn--primary:not(.btn--locked):hover {
  background: color-mix(in srgb, var(--sb-green-fg, #147045) 88%, #000);
  border-color: color-mix(in srgb, var(--sb-green-fg, #147045) 88%, #000);
}
/* 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.39: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); color: var(--sb-ink); }
.mng-body .btn--ghost:hover { background: var(--sb-bg); }
.mng-body .btn--danger { background: #fff; border-color: #E5B6B1; 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-2); 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; }
.chip:hover { background: #dcf0fb; }
.chip--active { 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-mono);
  font-size: var(--sb-fs-label);
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--sb-muted);
  margin-bottom: 5px;
}
.mng-body .form__input {
  height: var(--sb-ctl);
  min-height: var(--sb-ctl);
  padding: 0 8px;
  border: 1px solid var(--sb-line);
  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);
}
.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;
}
.mng-body .form__input--sm { height: var(--sb-ctl-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-family: var(--sb-mono); font-size: var(--sb-fs-body-sm); }
.mng-body .form__input[type="number"] { width: 140px; font-family: var(--sb-mono); text-align: right; }
/* 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); border-radius: var(--sb-r-btn);
  overflow: hidden; background: #fff;
}
.form__amount-unit {
  width: 28px; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: var(--sb-bg-2); border-right: 1px solid var(--sb-line-2);
  font-size: var(--sb-fs-body-sm); color: var(--sb-muted);
}
.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;
}
.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) -------------------------- */
.mng-body { background: var(--sb-bg); margin: 0; --mng-top-h: 0px; }
.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 {
  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 { width: 6px; }
.mng-side__nav::-webkit-scrollbar-track { background: transparent; }
.mng-side__nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,.28); border-radius: 3px; }
.mng-nav__item {
  display: flex; flex: none; align-items: center; gap: 11px; padding: 11px 12px; border-radius: 11px;
  color: rgba(255,255,255,.82); text-decoration: none; font-weight: 600; font-size: 14px;
}
.mng-nav__item:hover { background: #46b450; color: #fff; }
.mng-nav__item--active { background: #46b450; 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,.42); }
.mng-nav__label { flex: 1; }
/* §E46 — i gruppi con titolo sono <details>: apertura, chiusura, tastiera e stato
   esposto agli screen reader sono del browser, e con JavaScript spento il menu si
   piega comunque (dimentica soltanto). Le regole sono qualificate per ELEMENTO
   perché `admin` e `partners` rendono gli stessi .mng-nav__group come <div>/<p>:
   là ci sono 3-4 voci e non c'è niente da richiudere. Il selettore fratello
   .mng-nav__group + .mng-nav__group continua a valere fra div e details, perché
   aggancia la classe e non il tag. */
details.mng-nav__group { display: block; }
details.mng-nav__group > summary {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 12px 4px; border-radius: 9px;
  list-style: none; cursor: pointer; user-select: none;
}
details.mng-nav__group > summary::-webkit-details-marker { display: none; }
details.mng-nav__group > summary:hover { color: rgba(255,255,255,.7); }
details.mng-nav__group > summary:focus-visible { outline: 2px solid rgba(255,255,255,.85); outline-offset: -2px; box-shadow: none; }
/* Il gap fra le voci sta sul WRAPPER e non sul <details>: da Chrome 131 il
   contenuto di un <details> vive dentro ::details-content, quindi le voci non
   sono piu figlie flex dirette e un `gap` sull'elemento non le raggiungerebbe. */
.mng-nav__group-items { display: flex; flex-direction: column; gap: 4px; }
/* Caret PRIMA dell'etichetta, come la maniglia di un albero. */
.mng-nav__caret { flex: none; transform: rotate(-90deg); transition: transform .15s ease; }
details.mng-nav__group[open] > summary .mng-nav__caret { transform: rotate(0deg); }
/* Un gruppo richiuso non deve poter nascondere un contatore. Il pallino compare
   solo se dentro c'e un badge NON vuoto, e si accende e si spegne da se: e il
   badge stesso che il Turbo Stream mostra e nasconde con [hidden]. Nessun secondo
   nodo da tenere in sincrono, nessun JS. */
details.mng-nav__group:not([open]):has(.mng-nav__badge:not([hidden])) > summary::after {
  content: ""; margin-left: auto; flex: none;
  width: 6px; height: 6px; border-radius: 50%; background: var(--sb-accent);
}
/* 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: 20px; }
.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; }
/* §E49.4 — the dot on the "Notifiche" voice: notifications are OFF on this
   browser. Revealed by the `push` controller, so it needs its own [hidden] rule
   for the same reason as the badge above — an author declaration beats the
   user-agent one, and without it the dot would sit there for everyone, always.
   Deliberately not a .mng-nav__badge: that class also marks the folded group it
   sits in, and a reminder about one's own phone must not make "Operativita" look
   like it holds unread work. */
.mng-nav__dot {
  display: inline-block; margin-left: auto; flex: none;
  width: 6px; height: 6px; border-radius: 50%; background: var(--sb-accent);
}
.mng-nav__dot[hidden] { display: none; }
.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 30px;
  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: rgba(70,180,80,.12); color: #1f8b4c; font-size: 12px; font-weight: 600; padding: 5px 11px; border-radius: 20px; }
.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 è. */
.mng-top__identity { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; text-decoration: none; }
a.mng-top__identity:hover .mng-top__name { text-decoration: underline; }
/* 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: rgba(59,98,214,.1); border: 1px solid rgba(59,98,214,.3);
  color: var(--sb-primary); border-radius: 10px; padding: 7px 12px;
  font-weight: 600; font-size: 13px; text-decoration: none; white-space: nowrap;
}
.mng-top__switch:hover { background: rgba(59,98,214,.18); }
.mng-top__signout {
  display: inline-flex; align-items: center; gap: 7px; margin-left: 6px;
  background: var(--sb-bg); border: 1px solid var(--sb-line); color: var(--sb-muted);
  border-radius: 10px; padding: 7px 12px; font-family: var(--sb-sans);
  font-weight: 600; font-size: 13px; cursor: pointer;
}
.mng-top__signout:hover { background: #fff; border-color: var(--sb-primary); color: var(--sb-primary); }

/* 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 30px; 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: 30px; margin-right: 30px; }
.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; }
  details.mng-nav__group > summary { padding: 5px 12px 2px; }
}

/* 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;
    box-shadow: 0 0 40px rgba(0,0,0,.45);
  }
  .mng-shell--nav-open .mng-side { transform: translateX(0); }

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

  .mng-top { padding: 0 16px; }
  .mng-top__identity { display: none; }
  .mng-top__signout-label, .mng-top__switch-label { display: none; }
  .mng-top__signout, .mng-top__switch { padding: 7px 9px; }
}

/* 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, .mng-nav__caret { 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 20px 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 20px; }

/* 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: 14px; border: 1px solid var(--sb-line); box-shadow: none; }
.board-counter:first-child { border-left: 4px solid var(--sb-accent); }
.board-counter:first-child .board-counter__num { color: var(--sb-accent-strong); }
.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); }
.board-col__title--new::before { background: var(--sb-accent); }
.board-col__title--progress::before { background: var(--sb-primary); }
.board-col__title--done::before { background: var(--sb-green); }

.req-card { border-radius: 13px; 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; }

.price-row { display: flex; gap: 8px; align-items: center; }
.price-row .form__input--sm { max-width: 120px; }
.price-row 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;
}
.g-chat__log {
  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; }
.g-msg__bubble {
  padding: 10px 14px; border-radius: 16px; line-height: 1.45;
  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; }
.g-chat__input {
  flex: 1; padding: 12px 14px; border-radius: 999px;
  border: 1px solid var(--sb-line, #e6edf3); background: #fff; font-size: 1rem;
}
.g-chat__send { 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: 11px;
  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: 1.6rem; font-weight: 700; line-height: 1.1; color: var(--c-ink);
}
.kpi__label { margin: 2px 0 0; font-size: .78rem; 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-mono); font-size: var(--sb-fs-micro); font-weight: 400;
  letter-spacing: .08em; text-transform: uppercase; color: var(--sb-muted);
}
.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, dates and amounts in mono: that is what gets read character by
   character. */
.data-table .is-numeric, .data-table .is-code { font-family: var(--sb-mono); font-size: var(--sb-fs-body-sm); }
.data-table .is-numeric { text-align: right; }
/* 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-family: var(--sb-mono); font-size: 1.05rem; }
.data-table tfoot .dtable__total-label { font-weight: 600; color: var(--sb-muted); }
.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. */
.dtable__locked {
  padding: 6px 10px; font-size: .82rem; font-weight: 600;
  color: var(--c-ink-soft); opacity: .55; 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); }
.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__saving { margin: 0; font-size: .85rem; font-weight: 600; color: var(--sb-green-fg, #147045); }
.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. */
.billing-banner {
  display: flex; align-items: flex-start; justify-content: center; gap: 10px; margin-bottom: 16px;
  padding: 12px 14px; border-radius: 12px; font-size: .92rem;
  border: 1px solid; background: #fff; text-align: center;
}
/* 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%; 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 (una c'è solo con più strutture),
   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(--c-blue-300, #cbd5e1); border-radius: 12px; box-shadow: 0 10px 30px rgba(2, 32, 71, 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: 8px; cursor: pointer; font: inherit; color: var(--c-blue-900, #0b2447); }
.mng-top__menu-item:hover { background: var(--c-blue-100, #eff6ff); }
.mng-top__menu-item.is-current { font-weight: 700; }

/* §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 — and it is
   also the only way to the profile there, .mng-top__identity being hidden.
   The [hidden] rule is not optional: inline-flex would otherwise win over it. */
.mng-top__bell {
  position: relative; display: inline-flex; 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);
}
.mng-top__bell: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); }

/* 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(--c-blue-700, #1d4ed8); padding: 6px 8px; border-radius: 8px; }
.mng-top__lang > summary:hover { background: var(--c-blue-100, #eff6ff); }
/* 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». */
.rooms-filters { max-width: none; }
.rooms-filter__field--grow { flex: 1 1 240px; min-width: 0; }

/* 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: 10px; background: #fff;
  cursor: pointer;
}
.room-qr-thumb svg { width: 100%; height: 100%; display: block; }
button.room-qr-thumb:hover { border-color: #178bc3; }
.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. */
.btn--locked { opacity: 0.45; cursor: not-allowed; }
.mng-body .btn--primary.btn--locked:hover,
.mng-body .btn--primary.btn--locked:active {
  background: var(--md-primary); box-shadow: var(--md-elev-1);
}

.icon-btn--locked { opacity: 0.45; cursor: not-allowed; }
.icon-btn--locked:hover { border-color: var(--sb-line); color: var(--c-ink-soft); }

/* --- 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: 12px;
  box-shadow: 0 10px 30px rgba(2, 32, 71, 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 { opacity: 0.5; cursor: not-allowed; }
.dropdown__item--locked:hover { background: transparent; }
.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: 16px; 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(2, 32, 71, 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: 14px; 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: 16px; 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(2, 32, 71, 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; }
.modal--wide .modal__body { max-height: 70vh; overflow-y: auto; }

/* --- §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: 44px;
}

/* 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: 12px;
  box-shadow: 0 10px 30px rgba(2, 32, 71, .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;
  padding: 3px 10px; border-radius: var(--sb-r-btn);
  font-size: 0.7rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--sb-chip); color: var(--c-ink-soft);
}
.mreq__status--received { background: var(--c-azure-100); color: var(--c-blue-700); }
.mreq__status--in_progress { background: var(--sb-warn-bg, #FDF3DC); color: var(--sb-warn-fg, #92590F); }
.mreq__status--done { background: rgba(70, 180, 80, 0.14); color: var(--sb-green-fg, #147045); }
.mreq__status--cancelled { background: #fdecec; color: var(--sb-danger, #C62828); }

.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: 11px; border: 1px solid var(--sb-line); background: var(--sb-bg);
}
.form__rte lexxy-editor:focus-within {
  border-color: var(--sb-primary); box-shadow: 0 0 0 3px rgba(59,98,214,.25);
}
.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); }

/* 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 20px 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: 20px; right: 20px; 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 20px 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. */
.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 è `#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; }

/* 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; }

.g-sectionnav__list {
  display: flex; gap: 8px; margin: 0; padding: 12px 20px 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%; }

/* ==========================================================================
   §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 Nav di paginazione (elenchi di back-office) ---------------------
   Precedente / stato / successiva. Lo stato sta al centro anche quando manca una
   delle due frecce (prima e ultima pagina): senza `margin-inline: auto` il testo
   salterebbe da un lato all'altro sfogliando. */
.pagenav {
  display: flex;
  align-items: center;
  gap: var(--sb-sp-3);
  margin-top: var(--sb-sp-4);
  flex-wrap: wrap;
}

.pagenav__status {
  margin: 0 auto;
  color: var(--sb-muted);
  font-size: var(--sb-fs-label);
}

/* 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-mono); font-size: var(--sb-fs-micro);
    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;
  }
}
