/* ==========================================================================
   Zampa — Design tokens & base styles
   Warm, trustworthy, friendly-but-clean. Soft rounded cards, generous
   whitespace, consistent service-type color coding.
   ========================================================================== */

:root {
  /* ======================================================================
     PRIMITIVE SCALES — raw values, one definition each.
     Nothing outside this block should reference these directly; components
     use the semantic tokens below, so a hue change never touches a component.
     ====================================================================== */

  /* Navy — carries structure, hierarchy and trust. */
  --navy-50:  #F1F4F9;
  --navy-100: #DFE6F0;
  --navy-200: #C0CEE1;
  --navy-300: #92A9C9;
  --navy-400: #5D7CA8;
  --navy-500: #3A5B8A;
  --navy-600: #294670;   /* default action — 9.53:1 with white */
  --navy-700: #1E3557;   /* hover */
  --navy-800: #16283F;   /* active */
  --navy-900: #0E1B2C;

  /* Coral — warmth and emphasis. Used sparingly, never as the structure.
     500 is the brand hue but only clears 3.9:1, so it is a NON-TEXT accent;
     anything carrying text or white-on-fill uses 600 or darker. */
  --coral-50:  #FEF3EF;
  --coral-100: #FCE0D6;
  --coral-200: #F8BFAC;
  --coral-300: #F29677;
  --coral-400: #EA7350;
  --coral-500: #DC5533;   /* non-text accent only (3.90:1) */
  --coral-600: #BE4425;   /* text + filled buttons (5.19:1) */
  --coral-700: #9A3620;

  /* Warm neutrals — the single biggest lever on perceived warmth.
     Every one is warm-tinted; none is a pure grey. */
  --warm-0:   #FFFFFF;
  --warm-50:  #FAF8F5;
  --warm-100: #F4F1EC;
  --warm-200: #EBE6DF;
  --warm-300: #E5DFD6;
  --warm-400: #D2C9BC;
  --warm-500: #8F8371;   /* 3.71:1 — the lightest border allowed to carry meaning */
  --warm-600: #6E675E;   /* 4.95:1 on the worst surface */
  --warm-700: #544E47;
  --warm-900: #1F1D1A;

  /* ======================================================================
     SEMANTIC TOKENS — what components actually use.
     ====================================================================== */

  --color-bg: var(--warm-50);
  --color-surface: var(--warm-0);
  --color-surface-2: var(--warm-100);
  --color-surface-3: var(--warm-200);

  --color-primary: var(--navy-600);
  --color-primary-dark: var(--navy-700);
  --color-primary-active: var(--navy-800);
  --color-primary-light: var(--navy-100);
  --color-primary-subtle: var(--navy-50);
  --color-primary-disabled: var(--navy-300);
  --color-on-primary: var(--warm-0);

  --color-accent: var(--coral-500);          /* non-text accent */
  --color-accent-strong: var(--coral-600);   /* text / filled */
  --color-accent-dark: var(--coral-700);
  --color-accent-light: var(--coral-100);
  --color-accent-subtle: var(--coral-50);
  --color-on-accent: var(--warm-0);

  --color-text: var(--warm-900);
  --color-text-secondary: var(--warm-700);
  --color-text-muted: var(--warm-600);
  --color-text-faint: var(--warm-600);   /* was lighter than AA; folded into muted */
  --color-text-on-dark: var(--warm-0);

  --color-border: var(--warm-300);
  --color-border-strong: var(--warm-400);
  --color-border-interactive: var(--warm-500);   /* inputs & controls, meets 3:1 */

  /* Semantic — harmonised with the palette, not browser defaults.
     Each has a base (fills/dots), a -bg tint, and a -fg that is legible on it. */
  --color-success: #2E7D5B;
  --color-success-bg: #E3F2EA;
  --color-success-fg: #1F5A41;
  --color-success-light: var(--color-success-bg);
  --color-warning: #B5741A;
  --color-warning-bg: #FBF0DC;
  --color-warning-fg: #8A5712;
  --color-warning-light: var(--color-warning-bg);
  /* Crimson rather than the orange-red it was: coral sits at hue 12 deg and the
     old danger at 7 deg, so "in ritardo" and "annullata" were the same colour.
     This is 23 deg away and reads unmistakably as an error. */
  --color-danger: #B02640;
  --color-danger-bg: #FAE4E9;
  --color-danger-fg: #8E1E33;
  --color-danger-light: var(--color-danger-bg);
  --color-info: #2A5C8F;
  --color-info-bg: #E4ECF6;
  --color-info-fg: #234E79;

  /* Service types — one muted family. The old bright purple/pink pair
     (#A855F7 / #EC4899) clashed with everything; these sit inside the palette.
     -fg is the text colour to use ON the matching -bg. */
  --svc-vet: #2A5C8F;       --svc-vet-bg: #E4ECF6;       --svc-vet-fg: #234E79;
  --svc-grooming: #8A5A7A;  --svc-grooming-bg: #F5EAF1;  --svc-grooming-fg: #743F63;
  --svc-walking: #3F7A5E;   --svc-walking-bg: #E4F1EA;   --svc-walking-fg: #2F6249;
  --svc-sitting: #956425;   --svc-sitting-bg: #FAEFDF;   --svc-sitting-fg: #84591F;
  --svc-training: #3E6E78;  --svc-training-bg: #E4F0F2;  --svc-training-fg: #2F565E;
  --svc-boarding: #A85742;  --svc-boarding-bg: #FAEAE5;  --svc-boarding-fg: #8A4433;

  /* Booking status. Colour never carries the meaning on its own — every
     status renders its Italian label too (see StatusPill). */
  --status-confirmed: var(--color-success);
  --status-confirmed-bg: var(--color-success-bg);
  --status-confirmed-fg: var(--color-success-fg);
  --status-pending: var(--color-warning);
  --status-pending-bg: var(--color-warning-bg);
  --status-pending-fg: var(--color-warning-fg);
  --status-completed: var(--navy-500);
  --status-completed-bg: #E9EEF6;
  --status-completed-fg: var(--navy-600);
  --status-cancelled: var(--color-danger);
  --status-cancelled-bg: var(--color-danger-bg);
  --status-cancelled-fg: var(--color-danger-fg);

  /* Per-pet accent, drawn from the same family. */
  --pet-1: var(--navy-500);
  --pet-2: var(--svc-sitting);
  --pet-3: var(--svc-walking);
  --pet-4: var(--svc-grooming);
  --pet-5: var(--coral-600);

  /* Overlays and scrims, warm-tinted to match. */
  --color-scrim: rgba(31, 29, 26, 0.48);
  --color-scrim-strong: rgba(31, 29, 26, 0.72);
  --color-overlay-light: rgba(255, 255, 255, 0.92);
  --color-viewer-bg: #14120F;
  --color-header-wash: rgba(250, 248, 245, 0.92);
  --color-on-dark-subtle: rgba(255, 255, 255, 0.14);
  --color-on-dark-subtle-hover: rgba(255, 255, 255, 0.26);
  /* La pillola in basso: un'ombra ampia e bassa che la stacca dal contenuto che
     le scorre sotto, senza l'alone scuro di --shadow-lg (pensata per i modali,
     che hanno uno scrim dietro). Tinta sul navy, non su un grigio neutro. */
  --shadow-nav: 0 6px 22px rgba(31, 29, 26, 0.10), 0 1px 3px rgba(31, 29, 26, 0.06);
  --shadow-primary: 0 4px 14px rgba(41, 70, 112, 0.28);
  --shadow-accent: 0 4px 14px rgba(190, 68, 37, 0.30);

  /* ======================================================================
     MOTION — one place to tune how the whole app feels.

     Calm and confident, never bouncy: this app takes payments and books vet
     visits. Personality lives in waiting moments (loading, empty states),
     restraint in transactional ones (booking, payment).

     Every duration here is also the reason there are no spring easings: an
     overshoot reads as playful, which is wrong on a confirmation screen.
     ====================================================================== */
  --duration-instant: 90ms;    /* press feedback, must feel immediate */
  --duration-fast: 140ms;      /* the default: hovers, colour and border changes */
  --duration-medium: 200ms;    /* entrances, state changes with travel */
  --duration-slow: 320ms;      /* full-surface transitions — modals, sheets */
  --duration-deliberate: 620ms;/* success moments, seen once and worth watching */

  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);      /* leaving: fast then settling */
  --ease-in-out: cubic-bezier(0.45, 0.05, 0.55, 0.95);/* moving between two states */
  --ease-decel: cubic-bezier(0.16, 1, 0.3, 1);        /* arriving, strongly decelerated */

  /* How far a pressed control sinks. A single value keeps every press
     consistent; buttons and cards differ in size, not in behaviour. */
  --press-scale: 0.972;

  --radius-sm: 10px;
  --radius-md: 16px;
  --radius-lg: 20px;
  --radius-xl: 24px;
  --radius-btn: 14px;
  --radius-input: 14px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.04);
  --shadow-md: 0 10px 30px rgba(15, 23, 42, 0.08);
  --shadow-lg: 0 20px 50px rgba(15, 23, 42, 0.14);

  /* Single source of truth for typography. --font-display / --font-body are
     kept as aliases so existing rules keep working; both resolve here. */
  --font-base: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
  --font-display: var(--font-base);
  --font-body: var(--font-base);

  --header-h: 68px;
  /* La barra in basso è una pillola staccata, quindi "quanto spazio occupa" non
     coincide più con la sua altezza: --bottomnav-space è la somma che ogni
     pagina deve lasciare libera in fondo, ed è l'unico valore che gli altri
     fogli citano. Cambiare l'altezza qui (per esempio a 68px per rimettere le
     didascalie) sposta insieme padding, brindisi, guscio della chat e barra
     della prenotazione.
     L'inserto di sistema entra nell'OFFSET, non nel padding interno: è il punto
     esatto in cui la barra si era rotta in standalone — box-sizing: border-box
     sottraeva i 34px dell'inserto dall'altezza dichiarata e schiacciava le
     icone. Una pillola staccata non ha modo di ricascarci. */
  --bottomnav-h: 58px;
  --bottomnav-gap: 12px;
  --bottomnav-space: calc(var(--bottomnav-h) + var(--bottomnav-gap) + var(--safe-bottom));
  /* OS safe areas. Two declarations on purpose: constant() is the iOS 11.0–11.2
     spelling, env() is everything since — the later valid one wins, and both
     fall back to 0px, so nothing changes on desktop or in a normal mobile
     browser where the insets are zero. */
  --safe-bottom: constant(safe-area-inset-bottom, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-top: constant(safe-area-inset-top, 0px);
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-left: constant(safe-area-inset-left, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: constant(safe-area-inset-right, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --sidebar-w: 240px;

  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  scroll-behavior: smooth;
}

body {
  min-height: 100vh;
  font-size: 16px;
  line-height: 1.55;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 6px;
  letter-spacing: -0.02em;
}

h1 { font-size: 32px; font-weight: 800; }
h2 { font-size: 24px; font-weight: 700; }
h3 { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; }

p { margin: 0 0 10px; color: var(--color-text-muted); }

a { color: inherit; text-decoration: none; }

button { font-family: inherit; }

img { max-width: 100%; display: block; }

ul { margin: 0; padding: 0; list-style: none; }

input, select, textarea {
  font-family: inherit;
  font-size: 16px;
  color: var(--color-text);
}

::selection { background: var(--color-primary-light); }

/* Scrollbars */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-thumb { background: var(--color-border-strong); border-radius: 8px; }
::-webkit-scrollbar-track { background: transparent; }

/* ==========================================================================
   Layout shell
   ========================================================================== */

#app { min-height: 100vh; }

.app-shell {
  display: flex;
  min-height: 100vh;
}

.sidebar {
  width: var(--sidebar-w);
  flex-shrink: 0;
  background: var(--color-surface);
  border-right: 1px solid var(--color-border);
  padding: 22px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
}

.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px 22px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 21px;
}

/* Il marchio ora è un SVG inline a fondo trasparente (Logo() in
   components.js), quindi questa piastrella bianca non nasconde più il quadrato
   opaco della vecchia PNG: lo disegna, ed è una scelta. Il padding in
   percentuale riporta il segno al ~72% della piastrella — la stessa presenza
   ottica della PNG che sostituisce — e resta proporzionato a ogni misura,
   perché il marchio nel suo viewBox ne occupa il 91%. */
.logo-mark {
  width: 36px; height: 36px;
  border-radius: 11px;
  background: var(--color-surface);
  display: grid;
  place-items: center;
  box-shadow: var(--shadow-sm);
  flex-shrink: 0;
}
.logo-mark-sm { width: 30px; height: 30px; border-radius: 9px; }

/* 79% del riquadro: il marchio occupa il 91% del proprio viewBox, quindi
   0.79 x 0.91 = 72% della piastrella, la presenza ottica esatta della PNG che
   sostituisce. Una sola regola copre nav, auth e landing. */
.logo-mark > svg,
.auth-logo > svg { width: 79%; height: 79%; display: block; }

.nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-weight: 600;
  font-size: 14.5px;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
  position: relative;
}

.nav-link .icon { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.nav-link.active .icon-svg { stroke-width: 2.3; }

.nav-link:hover { background: var(--color-surface-2); color: var(--color-text); }

.nav-link.active {
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
}

/* Il pallino con il conteggio. L'aspetto sta qui, slegato da .nav-link: lo
   usano sia la barra laterale sia la pillola in basso, che non sono annidate
   una nell'altra. Navy e non corallo — il corallo resta scarso. */
.badge-dot {
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: 11px;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
}
.nav-link .badge-dot { margin-left: auto; }
/* `display: flex` qui sopra è una regola d'autore e batte il
   `[hidden] { display: none }` del browser, che è di user agent — quindi il
   pallino della barra, che sta sempre nel markup e si accende con `hidden`,
   restava visibile su tutte le schede come un puntino navy. Stessa trappola di
   .bottom-nav:not([hidden]). */
.badge-dot[hidden] { display: none; }

.sidebar-footer {
  margin-top: auto;
  border-top: 1px solid var(--color-border);
  padding-top: 14px;
}

.role-switch {
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--color-text-faint);
  padding: 8px 10px;
}
.role-switch button {
  width: 100%;
  border: 1px dashed var(--color-border-strong);
  background: transparent;
  border-radius: var(--radius-sm);
  padding: 9px 10px;
  font-weight: 600;
  font-size: 13px;
  color: var(--color-text-muted);
  cursor: pointer;
  margin-top: 6px;
}
.role-switch button:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }

.main-col {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.topbar {
  height: var(--header-h);
  display: none;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  background: var(--color-header-wash);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 30;
}

.main-content {
  flex: 1;
  padding: 28px 32px 60px;
  max-width: 1180px;
  width: 100%;
  margin: 0 auto;
}

/* ==========================================================================
   Barra in basso — pillola staccata, sole icone, cursore che scivola

   Il markup vive fuori da #app (vedi index.html e src/layout.js): il nodo non
   viene mai sostituito, quindi il cursore è già promosso e già dipinto quando
   deve muoversi. Qui sotto c'è solo la forma.
   ========================================================================== */

/* Nascosta di suo: fuori da #app significa che sta nel documento anche mentre
   si è sul login o sulla schermata di attesa. `:not([hidden])` sotto è
   obbligatorio e non è pedanteria — una regola d'autore batte comunque il
   `[hidden] { display: none }` del browser, che è una regola di user agent. */
.bottom-nav { display: none; }

@media (max-width: 900px) {
  .sidebar { display: none; }
  .topbar { display: flex; }
  /* Lo spazio che la pillola occupa, più aria: nessuna pagina deve finire
     sotto di lei. Vale per ogni rotta, perché ogni rotta rende dentro qui. */
  .main-content { padding: 18px 14px calc(var(--bottomnav-space) + 18px); }

  .bottom-nav:not([hidden]) {
    display: flex;
    position: fixed;
    z-index: 40;
    /* Staccata da tutti e quattro i lati: l'inserto di sistema si somma
       all'offset, mai al padding interno (vedi il commento sui token). */
    bottom: calc(var(--bottomnav-gap) + var(--safe-bottom));
    left: calc(12px + var(--safe-left));
    right: calc(12px + var(--safe-right));
    height: var(--bottomnav-h);
    padding: 0 6px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
    box-shadow: var(--shadow-nav);
    /* NIENTE backdrop-filter. Una sfocatura su un elemento fisso sopra
       contenuto che scorre si ripaga a ogni fotogramma, e qui non comprerebbe
       niente: la pillola è opaca, e a staccarla dal contenuto ci pensa l'ombra.
       Così non c'è nemmeno da chiedersi se sia viva mentre il cursore scivola. */
  }
}

.bnav-item {
  position: relative;
  z-index: 1;                 /* l'icona sta sopra il cursore */
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-muted);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--duration-fast) var(--ease-out);
}
/* Navy, non corallo. Il corallo resta scarso — CTA principali e cose in
   ritardo — e qui non serve: a dire dove sei è il cursore, che si sposta. */
.bnav-item.is-active { color: var(--color-primary-dark); }
.bnav-item:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -3px;
  border-radius: var(--radius-btn);
}
.bnav-icon { position: relative; display: flex; line-height: 0; }
.bnav-item:active .bnav-icon { transform: scale(var(--press-scale)); }
/* Contorno contro pieno, prima ancora che per tinta: senza didascalie il
   colore da solo non può portare "sei qui". La sagoma si riempie, i tratti
   interni restano tratti. */
.nav-icon .ni-body {
  fill: currentColor;
  fill-opacity: 0;
  transition: fill-opacity var(--duration-fast) var(--ease-out);
}
.bnav-item.is-active .ni-body,
.nav-link.active .ni-body { fill-opacity: 0.16; }
.bnav-item.is-active .icon-svg { stroke-width: 2.2; }

/* Il cursore. left/width li scrive placeNavIndicator(); l'unica cosa che si
   anima è `transform`, così le schede potrebbero anche avere larghezze diverse
   senza tirare dentro una proprietà di layout. --ease-in-out è il token per
   spostarsi fra due stati, che è esattamente questo. */
.bnav-ind {
  position: absolute;
  left: 0; top: 7px; bottom: 7px; width: 0;
  z-index: 0; opacity: 0;
  border-radius: var(--radius-btn);
  background: var(--color-primary-light);
  transform-origin: left center;
  transition: transform var(--duration-medium) var(--ease-in-out);
  /* Tiene il livello promosso ANCHE fra un'animazione e l'altra. È il punto di
     tutta la faccenda: promuovere all'inizio della transizione vorrebbe dire
     dipingere il livello nuovo proprio nel fotogramma che sta già montando la
     pagina. Un livello solo, grande quanto una scheda. */
  will-change: transform;
  pointer-events: none;
}
.bnav-ind.is-ready { opacity: 1; }
/* Finché il cursore non è stato misurato e messo, la scheda attiva si disegna
   la propria pillola: altrimenti per un fotogramma sembrerebbe spenta, e lo
   resterebbe se lo script non arrivasse mai. */
.bottom-nav:not(:has(.bnav-ind.is-ready)) .bnav-item.is-active::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 7px; bottom: 7px;
  z-index: -1;
  border-radius: var(--radius-btn);
  background: var(--color-primary-light);
}

/* Il pallino: ancorato al riquadro dell'icona, non alla voce, così resta al
   suo posto anche con le didascalie rimesse. */
.bnav-badge {
  position: absolute;
  top: -7px; left: 13px;
  margin: 0;
  border: 2px solid var(--color-surface);
  font-size: 10.5px;
  min-width: 19px; height: 19px;
  pointer-events: none;
}

/* --------------------------------------------------------------------------
   Didascalie rimesse. Due mosse, non un rifacimento: BOTTOM_NAV_LABELS = true
   in src/layout.js, e --bottomnav-h da 58px a 68px nei token. Tutto il resto
   — spazio in fondo alle pagine, brindisi, guscio della chat, altezza del
   cursore — deriva da quel token e segue da solo.
   -------------------------------------------------------------------------- */
.bottom-nav.has-labels .bnav-item { flex-direction: column; gap: 3px; }
.bottom-nav.has-labels .bnav-label {
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.1;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  /* La regola globale porta già ogni durata a 1ms; queste tolgono il viaggio,
     così per chi ha chiesto di non muovere niente il cursore appare al posto
     giusto invece di scivolarci. */
  .bnav-ind { transition: none; }
  .nav-icon .ni-body { transition: none; }
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 600;
  font-size: 14px;
  padding: 11px 20px;
  border-radius: var(--radius-btn);
  border: none;
  cursor: pointer;
  transition: transform var(--duration-instant) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), opacity var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
  white-space: nowrap;
}
.btn:active { transform: scale(0.97); }
.btn-primary { background: var(--color-primary); color: var(--color-on-primary); box-shadow: var(--shadow-primary); }
.btn-primary:hover { background: var(--color-primary-dark); }

/* The one action that matters most on a screen. Separate from .btn-primary on
   purpose: that class appears 51 times, and coral on all of them would stop
   meaning anything. At most one .btn-cta should be visible at a time. */
.btn-cta { background: var(--color-accent-strong); color: var(--color-on-accent); box-shadow: var(--shadow-accent); }
.btn-cta:hover { background: var(--color-accent-dark); }
.btn-secondary { background: var(--color-surface); color: var(--color-text); border: 1.5px solid var(--color-border-strong); }
.btn-secondary:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }
.btn-ghost { background: transparent; color: var(--color-text-muted); }
.btn-ghost:hover { background: var(--color-surface-2); color: var(--color-text); }
.btn-danger { background: var(--color-danger-bg); color: var(--color-danger-fg); }
.btn-danger:hover { background: var(--color-danger); color: var(--color-on-primary); }   /* 5.10:1 */
.btn-sm { padding: 7px 14px; font-size: 12.5px; }
.btn-block { width: 100%; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-icon {
  /* 44px e non 38: questo è il controllo a icona più diffuso dell'app e porta
     anche azioni distruttive (elimina animale, elimina servizio, rimuovi
     carta). Il segno dentro non cambia misura. */
  width: 44px; height: 44px; padding: 0; border-radius: 50%;
  background: var(--color-surface); border: 1.5px solid var(--color-border-strong);
  color: var(--color-text-muted); font-size: 16px;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.btn-icon:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }

/* ==========================================================================
   Cards & surfaces
   ========================================================================== */

.card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-sm);
}

.card-pad { padding: 20px; }

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 14px;
  gap: 12px;
}
.section-head h2 { margin: 0; }
.section-head .link { font-weight: 700; font-size: 13.5px; color: var(--color-primary-dark); cursor: pointer; text-decoration: none; }

/* Navigazione dentro il testo: informativa cookie, termini in registrazione,
   privacy nel profilo, riga legale in accesso. Tutti usavano già class="link"
   mentre l'unica regola definita era quella dentro .section-head, quindi
   comparivano come testo normale, senza colore né cursore. */
.link {
  color: var(--color-primary-dark);
  font-weight: 700;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.link:hover { color: var(--color-primary-active); }
/* Fuoco visibile per i controlli che non ne avevano uno. :where() tiene la
   specificità a quella del solo :focus-visible, così ogni regola esistente per
   una classe — .pet-chip, .bk-card, .ap-card… — continua a vincere. */
:where([data-action][role="button"], [role="tab"], a.link, .nav-link, .ti-open, .card-link):focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}

/* Badges / pills */
.pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 700;
}

.tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 600;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

.svc-pill { border: none; }
/* The -fg step, not the base hue: a base tuned to read as a dot on white only
   manages ~4.3:1 on its own pale tint, which misses AA for the pill label. */
.svc-walking-color { background: var(--svc-walking-bg); color: var(--svc-walking-fg); }
.svc-grooming-color { background: var(--svc-grooming-bg); color: var(--svc-grooming-fg); }
.svc-vet-color { background: var(--svc-vet-bg); color: var(--svc-vet-fg); }
.svc-sitting-color { background: var(--svc-sitting-bg); color: var(--svc-sitting-fg); }
.svc-training-color { background: var(--svc-training-bg); color: var(--svc-training-fg); }
.svc-boarding-color { background: var(--svc-boarding-bg); color: var(--svc-boarding-fg); }

/* Colour never carries the state on its own — each pill prints its label too. */
.status-confirmed { background: var(--status-confirmed-bg); color: var(--status-confirmed-fg); }
.status-pending { background: var(--status-pending-bg); color: var(--status-pending-fg); }
.status-completed { background: var(--status-completed-bg); color: var(--status-completed-fg); }
.status-cancelled { background: var(--status-cancelled-bg); color: var(--status-cancelled-fg); }
.status-declined { background: var(--status-cancelled-bg); color: var(--status-cancelled-fg); }

/* ==========================================================================
   Forms
   ========================================================================== */

.field { margin-bottom: 16px; }
.field label {
  display: block;
  font-weight: 700;
  font-size: 13px;
  margin-bottom: 7px;
  color: var(--color-text);
}
.field .hint { font-size: 12px; color: var(--color-text-faint); margin-top: 5px; }
.input, .select, .textarea {
  width: 100%;
  padding: 12px 14px;
  border-radius: var(--radius-input);
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 4px var(--color-primary-light);
}
/* Un campo che si legge ma non si scrive — l'email del profilo, H3. Il testo
   resta a pieno contrasto; cambia il fondo, un gradino sotto, e sparisce il
   cursore da testo, così non sembra un campo in attesa. */
.input[readonly] { background: var(--color-surface-2); cursor: default; }
/* Il cambio d'email in attesa di conferma, sotto la riga dell'indirizzo. */
.email-pending {
  margin-top: 10px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: 14px;
}
/* A business whose owner deleted their account: history stays, actions go. */
.closed-notice {
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
}
.chat-closed { margin: 0; justify-content: center; text-align: center; }
.textarea { resize: vertical; min-height: 90px; }
.checkbox-row { display: flex; align-items: center; gap: 9px; cursor: pointer; }
.checkbox-row input { width: 18px; height: 18px; accent-color: var(--color-primary); }

/* ==========================================================================
   Avatars
   ========================================================================== */

.avatar {
  border-radius: 50%;
  object-fit: cover;
  background: var(--color-surface-2);
  flex-shrink: 0;
}
.avatar-fallback {
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-weight: 700;
  color: var(--color-on-primary);
  flex-shrink: 0;
}

/* ==========================================================================
   Empty states
   ========================================================================== */

.empty-state {
  text-align: center;
  padding: 50px 20px;
  color: var(--color-text-muted);
}
.empty-state .emoji { font-size: 44px; margin-bottom: 12px; }
.empty-state h3 { margin-bottom: 6px; color: var(--color-text); }
.empty-state p { max-width: 380px; margin: 0 auto 18px; }

/* ==========================================================================
   Toasts
   ========================================================================== */

.toast-root {
  position: fixed;
  top: 18px;
  right: 18px;
  z-index: 999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 340px;
}
@media (max-width: 900px) {
  /* Sopra la pillola, non dietro: --bottomnav-space contiene già l'inserto. */
  .toast-root { top: auto; bottom: calc(var(--bottomnav-space) + 12px); right: 12px; left: 12px; max-width: none; }
}
.toast {
  background: var(--color-text);
  color: var(--color-on-primary);
  padding: 13px 16px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  font-size: 13.5px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  animation: toast-in 0.25s ease;
}
/* accent-strong, not accent: a toast is white text on this fill, and coral-500
   is documented as a non-text accent (3.90:1). */
.toast.success { background: var(--color-accent-strong); }
.toast.error { background: var(--color-danger); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ==========================================================================
   Modal
   ========================================================================== */

.modal-backdrop {
  position: fixed; inset: 0;
  background: var(--color-scrim);
  backdrop-filter: blur(2px);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  animation: fade-in 0.15s ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.modal {
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  max-width: 520px;
  width: 100%;
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
  animation: modal-in 0.2s cubic-bezier(.2,.8,.3,1);
}
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
.modal-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px 20px;
  border-bottom: 1px solid var(--color-border);
  position: sticky; top: 0; background: var(--color-surface); z-index: 2;
}
.modal-body { padding: 20px; }
.modal-footer {
  padding: 16px 20px; border-top: 1px solid var(--color-border);
  display: flex; gap: 10px; justify-content: flex-end;
  position: sticky; bottom: 0; background: var(--color-surface);
}
.modal-wide { max-width: 760px; }

/* --------------------------------------------------------------------------
   Action sheet — bottom sheet on phones, centred card on desktop
   -------------------------------------------------------------------------- */

.sheet-backdrop {
  position: fixed; inset: 0;
  background: var(--color-scrim);
  backdrop-filter: blur(2px);
  z-index: 210;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  animation: fade-in 0.15s ease;
}
.action-sheet {
  background: var(--color-surface);
  border-radius: var(--radius-xl);
  width: 100%; max-width: 380px;
  padding: 10px;
  box-shadow: var(--shadow-lg);
  animation: modal-in 0.2s cubic-bezier(.2,.8,.3,1);
}
.sheet-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-faint); padding: 10px 12px 8px;
}
.sheet-actions { display: flex; flex-direction: column; }
.sheet-action {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 14px 12px;
  border: none; background: none; cursor: pointer;
  font-size: 15px; font-weight: 600; color: var(--color-text);
  border-radius: var(--radius-sm); text-align: left;
}
.sheet-action:hover { background: var(--color-surface-2); }
.sheet-action:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }
.sheet-action .sa-icon { color: var(--color-text-muted); line-height: 0; flex-shrink: 0; }
.sheet-action.danger { color: var(--color-danger); }
.sheet-action.danger .sa-icon { color: var(--color-danger); }
.sheet-cancel { margin-top: 6px; }

@media (max-width: 700px) {
  /* Slide up from the bottom edge, thumb-reachable. */
  .sheet-backdrop { align-items: flex-end; padding: 0; }
  .action-sheet {
    max-width: none;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    padding: 8px 12px calc(12px + var(--safe-bottom));
    animation: sheet-up 0.22s cubic-bezier(.2,.8,.3,1);
  }
  .sheet-action { padding: 16px 12px; }
}
@keyframes sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }

/* --------------------------------------------------------------------------
   Full-screen image viewer
   -------------------------------------------------------------------------- */

.viewer-backdrop {
  position: fixed; inset: 0; z-index: 220;
  /* Opaque: a photo viewer should not compete with the page behind it. */
  background: var(--color-viewer-bg);
  display: flex; flex-direction: column;
  animation: fade-in 0.15s ease;
}
.viewer-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: calc(10px + var(--safe-top)) 14px 10px;
  color: var(--color-on-primary); flex-shrink: 0;
}
.viewer-caption { font-weight: 700; font-size: 15px; }
.viewer-close {
  width: 40px; height: 40px; border-radius: 50%;
  border: none; background: var(--color-on-dark-subtle); color: var(--color-on-primary);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  flex-shrink: 0;
}
.viewer-close:hover { background: var(--color-on-dark-subtle-hover); }
.viewer-close:focus-visible { outline: 2px solid var(--color-surface); outline-offset: 2px; }
.viewer-stage {
  flex: 1; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  touch-action: none;          /* gestures are handled in JS */
  cursor: zoom-in;
}
.viewer-stage.is-zoomed { cursor: grab; }
.viewer-img {
  max-width: 100%; max-height: 100%;
  object-fit: contain;
  transform-origin: center center;
  user-select: none; -webkit-user-drag: none;
  will-change: transform;
}

/* ==========================================================================
   Dashboard
   ========================================================================== */

.hero-banner {
  background: linear-gradient(120deg, var(--navy-50) 0%, var(--color-bg) 60%);
  border-radius: var(--radius-lg);
  padding: 26px 26px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 22px;
  border: 1px solid var(--color-border);
  overflow: hidden;
  position: relative;
}
.hero-banner .greet-eyebrow { color: var(--color-primary-dark); font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; }
.hero-banner h1 { margin-bottom: 4px; }

.grid-2 { display: grid; grid-template-columns: 1.5fr 1fr; gap: 20px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 900px) {
  .grid-2 { grid-template-columns: 1fr; }
  .grid-3 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .grid-3 { grid-template-columns: 1fr; }
}

.next-appt-card {
  display: flex;
  gap: 16px;
  padding: 18px;
}
.next-appt-date {
  flex-shrink: 0;
  width: 62px;
  height: 62px;
  border-radius: var(--radius-md);
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 800;
}
.next-appt-date .mon { font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; }
.next-appt-date .day { font-size: 21px; line-height: 1.1; }

.stat-card {
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.stat-card .stat-value { font-family: var(--font-display); font-size: 26px; font-weight: 700; }
.stat-card .stat-label { font-size: 12.5px; color: var(--color-text-muted); font-weight: 600; }

.reminder-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--color-border);
}
.reminder-item:last-child { border-bottom: none; }
.reminder-icon {
  width: 38px; height: 38px; border-radius: 11px;
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; flex-shrink: 0;
}

/* week strip preview */
.week-strip { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.week-strip-day {
  border-radius: var(--radius-sm);
  padding: 8px 4px;
  text-align: center;
  background: var(--color-surface-2);
  min-height: 74px;
  cursor: pointer;
  border: 1.5px solid transparent;
}
.week-strip-day.today { border-color: var(--color-primary); }
.week-strip-day .wd { font-size: 10.5px; font-weight: 700; color: var(--color-text-faint); text-transform: uppercase; }
.week-strip-day .dn { font-weight: 700; font-size: 15px; margin: 3px 0 6px; }
.week-strip-dots { display: flex; gap: 3px; justify-content: center; flex-wrap: wrap; }
.week-strip-dots span { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }

/* ==========================================================================
   Provider card / Discover
   ========================================================================== */

.filter-bar {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 18px;
}
.search-input-wrap {
  position: relative;
  flex: 1;
  min-width: 220px;
}
.search-input-wrap .input { padding-left: 40px; }
.search-input-wrap .search-icon {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--color-text-faint);
}
.chip {
  padding: 9px 15px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.chip:hover { border-color: var(--color-primary); }
.chip.active { background: var(--color-text); border-color: var(--color-text); color: var(--color-on-primary); }
.chip select { border: none; background: transparent; font: inherit; color: inherit; cursor: pointer; outline: none; }
/* outline:none senza sostituto lasciava questi due select SENZA alcun segno di
   fuoco. L'anello va sul .chip, che è il rettangolo che si vede. */
.chip:focus-within { border-color: var(--color-primary); box-shadow: 0 0 0 4px var(--color-primary-light); }

.provider-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 18px;
}

.provider-card {
  overflow: hidden;
  cursor: pointer;
  transition: transform var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
  display: flex;
  flex-direction: column;
}
.provider-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-md); }
.provider-card .photo-wrap {
  position: relative;
  height: 148px;
  background: var(--color-surface-2);
}
.provider-card .photo-wrap img { width: 100%; height: 100%; object-fit: cover; }
.provider-card .fav-btn {
  position: absolute; top: 10px; right: 10px;
  width: 44px; height: 44px; border-radius: 50%;
  background: var(--color-overlay-light);
  display: flex; align-items: center; justify-content: center;
  border: none; cursor: pointer; color: var(--color-text-muted); box-shadow: var(--shadow-sm);
  transition: color var(--duration-fast) var(--ease-out);
}
.provider-card .fav-btn.is-fav, .btn-icon.is-fav { color: var(--color-danger); }
.provider-card .body { padding: 15px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.provider-card .name-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.provider-card .name-row h3 { margin: 0; }
.provider-card .card-link { color: inherit; text-decoration: none; }
.provider-card .rating { display: flex; align-items: center; gap: 4px; font-weight: 700; font-size: 13px; white-space: nowrap; }
.provider-card .meta-row { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; color: var(--color-text-muted); }
.provider-card .tags-row { display: flex; gap: 6px; flex-wrap: wrap; }
.provider-card .footer-row {
  margin-top: auto;
  display: flex; align-items: center; justify-content: space-between;
  padding-top: 10px; border-top: 1px solid var(--color-border);
}
.provider-card .price { font-weight: 800; font-family: var(--font-display); font-size: 16px; }
.provider-card .next-slot { font-size: 11.5px; color: var(--color-accent); font-weight: 700; }

/* map placeholder */
.map-panel {
  border-radius: var(--radius-lg);
  overflow: hidden;
  height: 100%;
  min-height: 420px;
  position: relative;
  background:
    radial-gradient(circle at 20% 30%, var(--navy-100) 0, transparent 40%),
    radial-gradient(circle at 80% 70%, var(--coral-100) 0, transparent 45%),
    var(--navy-50);
  border: 1px solid var(--color-border);
}
.map-pin {
  position: absolute;
  transform: translate(-50%, -100%);
  cursor: pointer;
}
.map-pin .pin-dot {
  width: 34px; height: 34px; border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  display: flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-md);
  border: 2px solid var(--color-surface);
}
.map-pin .pin-emoji { transform: rotate(45deg); color: var(--color-on-primary); display: flex; }
.map-pin.active .pin-dot { transform: rotate(-45deg) scale(1.2); }

/* ==========================================================================
   Provider detail
   ========================================================================== */

.provider-hero {
  border-radius: var(--radius-lg);
  overflow: hidden;
  margin-bottom: 20px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.provider-hero .cover { height: 200px; background: var(--color-surface-2); position: relative; }
.provider-hero .cover img { width: 100%; height: 100%; object-fit: cover; }
/* The avatar is meant to sit partly over the cover — but the pull belongs on the
   avatar, not on the row. With `margin-top: -46px` on .info-row plus
   `align-items: flex-end`, the whole row rode up onto the cover, and once the
   text column grew taller than the 84px avatar (stars + service tags) bottom
   alignment pushed the avatar down clear of the cover and lifted the NAME into
   it instead — the overlap inverted. flex-start plus a pull on the avatar alone
   keeps the intended look and leaves the text below the cover edge. */
.provider-hero .info-row {
  display: flex; gap: 16px; padding: 18px 22px; align-items: flex-start; position: relative;
}
.provider-hero .info-row .avatar,
.provider-hero .info-row .avatar-fallback,
.provider-hero .info-row .pp-avatar-button {
  margin-top: -46px;
  flex-shrink: 0;
}
.provider-hero .info-row .avatar,
.provider-hero .info-row .avatar-fallback { width: 84px; height: 84px; border: 4px solid var(--color-surface); }
.provider-hero .info-main { flex: 1; min-width: 0; }
.provider-hero .info-actions { display: flex; gap: 8px; padding: 0 22px 18px; flex-wrap: wrap; }
/* Below ~560px the 84px avatar leaves too little room beside it: the business
   name wraps to three lines and the tags stack one per row, with dead space
   under the avatar. Stacking gives the text the full width; the avatar keeps
   its pull onto the cover, so the look is unchanged. */
@media (max-width: 560px) {
  .provider-hero .info-row { flex-direction: column; gap: 10px; }
}

.tab-bar { display: flex; gap: 6px; border-bottom: 1px solid var(--color-border); margin-bottom: 20px; overflow-x: auto; }
.tab-btn {
  padding: 11px 16px;
  font-weight: 700;
  font-size: 13.5px;
  color: var(--color-text-muted);
  border-bottom: 2.5px solid transparent;
  cursor: pointer;
  white-space: nowrap;
}
.tab-btn.active { color: var(--color-primary-dark); border-color: var(--color-primary); }
/* Le schede ora sono <button> veri: via sfondo, bordi e font che il browser dà
   ai pulsanti, resta solo il filo sotto. */
.tab-bar .tab-btn { background: none; border: 0; border-bottom: 2.5px solid transparent; font-family: inherit; line-height: inherit; }
.tab-bar .tab-btn.active { border-bottom-color: var(--color-primary); }

.service-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 15px 16px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border);
  margin-bottom: 10px;
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
  gap: 10px;
}
.service-row:hover { border-color: var(--color-primary); background: var(--color-primary-light); }
.service-row.selected { border-color: var(--color-primary); background: var(--color-primary-light); }

.review-item { padding: 15px 0; border-bottom: 1px solid var(--color-border); }
.review-item:last-child { border-bottom: none; }
.review-item .rh { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.stars { color: var(--color-warning); letter-spacing: 1px; font-size: 13px; }

.gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.gallery-grid img { border-radius: var(--radius-sm); aspect-ratio: 1; object-fit: cover; }

.gallery-tile { position: relative; }
.gallery-open {
  display: block; width: 100%; padding: 0; border: none; background: none; cursor: pointer;
  border-radius: var(--radius-sm);
}
.gallery-open img { width: 100%; display: block; }
.gallery-open:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.gallery-remove {
  position: absolute; top: 6px; right: 6px;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: none; cursor: pointer; color: var(--color-on-primary);
  background: var(--color-scrim);
}
.gallery-remove:hover { background: var(--color-danger); }
.gallery-add {
  aspect-ratio: 1; width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border: 2px dashed var(--color-border-strong); border-radius: var(--radius-sm);
  background: none; cursor: pointer; color: var(--color-text-muted);
  font-size: 12px; font-weight: 600; text-align: center; padding: 8px; position: relative;
}
.gallery-add:hover:not(:disabled) { border-color: var(--color-primary); color: var(--color-primary-dark); }
.gallery-add:disabled { cursor: progress; }

/* A label with a secondary action opposite it (e.g. "Password dimenticata?"). */
.label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.label-row label { margin-bottom: 0; }
.link-button {
  border: none; background: none; padding: 0; cursor: pointer;
  font-size: 12.5px; font-weight: 600; color: var(--color-primary);
}
.link-button:hover { color: var(--color-primary-dark); text-decoration: underline; }
.link-button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; border-radius: 4px; }

/* Cancellation policy, shown under the service list. */
.policy-row {
  display: flex; gap: 10px; align-items: flex-start;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px dashed var(--color-border);
  color: var(--color-text-muted);
}
.policy-row svg { flex-shrink: 0; margin-top: 2px; }
.policy-row.is-editable label { display: block; }
.policy-row .select { width: 100%; max-width: 380px; }

/* Author-entered prose: keeps the typed line breaks and paragraphs without any
   markup being generated from user input, and breaks long unspaced strings
   instead of widening the card. */
.long-text { white-space: pre-wrap; overflow-wrap: anywhere; }
.char-count { display: block; text-align: right; }
.char-count.is-over { color: var(--color-danger); font-weight: 700; }

.hours-row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 13.5px; border-bottom: 1px dashed var(--color-border); }
.hours-row:last-child { border-bottom: none; }
.hours-row .day { font-weight: 700; }
.hours-row.closed { color: var(--color-text-faint); }
.hours-row.today-row { color: var(--color-primary-dark); font-weight: 700; }

/* ==========================================================================
   Booking flow
   ========================================================================== */

.stepper {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 26px;
  overflow-x: auto;
}
.stepper-step {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--color-text-faint);
  white-space: nowrap;
}
.stepper-step .dot {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--color-surface-2);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px;
  border: 1.5px solid var(--color-border-strong);
}
.stepper-step.active { color: var(--color-text); }
.stepper-step.active .dot { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); }
.stepper-step.done .dot { background: var(--color-success); border-color: var(--color-success); color: var(--color-on-primary); }
.stepper-line { width: 22px; height: 2px; background: var(--color-border-strong); flex-shrink: 0; }

.pet-select-card {
  display: flex; align-items: center; gap: 12px;
  padding: 13px 15px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border);
  margin-bottom: 9px;
  cursor: pointer;
}
.pet-select-card.selected { border-color: var(--color-primary); background: var(--color-primary-light); }
.pet-select-card .check {
  margin-left: auto;
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--color-border-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: transparent;
}
.pet-select-card.selected .check { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); }

.slot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.slot-btn {
  padding: 10px 6px;
  border-radius: var(--radius-sm);
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  font-weight: 700;
  font-size: 13px;
  cursor: pointer;
  text-align: center;
}
.slot-btn:hover { border-color: var(--color-primary); }
.slot-btn.selected { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-on-primary); }
.slot-btn:disabled { opacity: 0.35; cursor: not-allowed; text-decoration: line-through; }

.date-strip { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 16px; }
.date-chip {
  flex-shrink: 0;
  width: 58px;
  padding: 9px 4px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border-strong);
  text-align: center;
  cursor: pointer;
}
.date-chip.selected { background: var(--color-text); border-color: var(--color-text); color: var(--color-on-primary); }
.date-chip .wd { font-size: 10.5px; font-weight: 700; text-transform: uppercase; opacity: 0.7; }
.date-chip .dn { font-weight: 800; font-size: 16px; margin-top: 2px; }

.recur-options { display: flex; gap: 8px; flex-wrap: wrap; }

.summary-row { display: flex; justify-content: space-between; padding: 8px 0; font-size: 13.5px; }
.summary-row.total { font-weight: 800; font-size: 15.5px; border-top: 1px solid var(--color-border); margin-top: 6px; padding-top: 12px; }

/* `bottom` non è 0: appiccicata al bordo della finestra finirebbe sotto la
   pillola, che è fissa lì sopra. Su desktop la barra non c'è e il token vale
   comunque, perché --safe-bottom è 0 e la pagina ha spazio di suo — ma per non
   lasciare un vuoto gratuito lì sotto, l'offset è solo nella media query. */
.booking-footer-bar {
  position: sticky;
  bottom: 0;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
  padding: 14px 0;
  margin-top: 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
@media (max-width: 900px) {
  .booking-footer-bar { bottom: var(--bottomnav-space); }
}

.confirm-check {
  text-align: center;
  padding: 30px 10px 10px;
}
.confirm-check .big-emoji {
  width: 84px; height: 84px; border-radius: 50%;
  background: var(--color-success-light); color: var(--color-success);
  display: flex; align-items: center; justify-content: center;
  font-size: 40px; margin: 0 auto 18px;
}

/* ==========================================================================
   Unified Calendar — hero screen
   ========================================================================== */

.calendar-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.calendar-toolbar .nav-cluster { display: flex; align-items: center; gap: 8px; }

.calendar-filters {
  display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px;
  padding: 14px 16px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.filter-group { display: flex; flex-direction: column; gap: 7px; }
.filter-group .fg-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-text-faint); }
.filter-group .fg-opts { display: flex; gap: 6px; flex-wrap: wrap; }
.legend-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px 5px 7px;
  border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 700;
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  cursor: pointer;
  opacity: 1;
  transition: opacity var(--duration-fast) var(--ease-out);
}
/* Spento. Era opacity 0.35 — 2.16:1, sotto ogni soglia, e per di più su un
   controllo attivo: lo stato era portato SOLO dalla dissolvenza, senza testo
   né aria. Ora lo portano il tratto sul pallino e aria-pressed, che
   providerCalendar.js scrive accanto alla classe. */
.legend-chip.off { color: var(--color-text-muted); border-style: dashed; }
.legend-chip.off .dot { background: transparent !important; box-shadow: inset 0 0 0 2px var(--color-border-strong); }
.legend-chip .dot { width: 9px; height: 9px; border-radius: 50%; }

/* Month grid */
.month-weekday-head {
  padding: 9px 6px;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--color-text-faint);
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface-2);
}

@media (max-width: 700px) {
  .month-weekday-head { font-size: 9.5px; padding: 6px 2px; }
}

/* Week / day grid.
   --hour-h and --day-col-min are the single source of truth for the grid's
   sizing: the gutter, the hour lines and the JS-positioned events all derive
   from them, so a breakpoint can resize the whole grid in one place. */
.time-grid-wrap {
  --hour-h: 48px;
  --day-col-min: 132px;
  display: flex; border: 1px solid var(--color-border); border-radius: var(--radius-md);
  overflow: hidden; background: var(--color-surface);
}
.time-grid-scroll { flex: 1; overflow-x: auto; }
/* Heads and day columns share one sizing rule, so a column and its heading can
   never disagree on width. flex-basis 0 makes the 7 columns exactly even. */
.time-grid-cols { min-width: 100%; }
.time-cols { display: flex; }
/* The heading row stays visible while the grid scrolls vertically — without it
   you lose track of which column is which the moment you scroll past 10:00. */
.time-head-row {
  position: sticky; top: 0; z-index: 4;
  background: var(--color-surface);
  box-shadow: 0 1px 0 var(--color-border);
}
.time-cols > * { flex: 1 1 0; min-width: var(--day-col-min); }
/* The wrapper already draws the outer edge; the 7th column would double it. */
.time-cols > *:last-child { border-right: none; }
.time-grid {
  display: grid;
  position: relative;
}
.time-col-head {
  position: sticky; top: 0; z-index: 5;
  display: grid;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}
.time-col-head-cell {
  text-align: center;
  padding: 10px 4px;
  border-right: 1px solid var(--color-border);
  font-weight: 700;
}
.time-col-head-cell .wd { font-size: 10.5px; color: var(--color-text-faint); text-transform: uppercase; }
.time-col-head-cell .dn { font-size: 17px; margin-top: 2px; }
.time-col-head-cell.today .dn { color: var(--color-primary); }

.time-gutter { width: 54px; flex-shrink: 0; border-right: 1px solid var(--color-border); }
.time-gutter-slot { height: var(--hour-h); text-align: right; padding-right: 8px; font-size: 10.5px; color: var(--color-text-faint); position: relative; top: -6px; }

.time-day-col { position: relative; border-right: 1px solid var(--color-border); }

/* Current time. Sits above the tinting and the slot buttons but below the
   appointments, so it never intercepts a tap. */
.time-now { position: absolute; left: 0; right: 0; height: 0; z-index: 2; pointer-events: none; border-top: 2px solid var(--color-accent); }
.time-now::before {
  content: ''; position: absolute; left: -1px; top: -4px;
  width: 7px; height: 7px; border-radius: 50%; background: var(--color-accent);
}
.time-slot-line { height: var(--hour-h); border-bottom: 1px solid var(--color-border); display: flex; flex-direction: column; }
/* Half-hour click targets. Buttons, not divs: they are real controls, so they
   are reachable by keyboard and announced with their own label. */
.time-slot-hit {
  flex: 1 1 0; border: 0; padding: 0; background: none; cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out);
}
.time-slot-hit:hover { background: color-mix(in srgb, var(--color-primary) 8%, transparent); }
.time-slot-hit:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }

/* Availability hint, from the provider's own opening hours. Mixed down from the
   existing status tokens so the tint stays far behind the text — a background
   cue, not a state colour. The header answers "is this day open at all?", while
   each hour row answers "am I working at this time?". */
.time-slot-line.is-open,
.time-col-head-cell.is-open { background: color-mix(in srgb, var(--color-success-light) 40%, var(--color-surface)); }
.time-slot-line.is-closed,
.time-col-head-cell.is-closed { background: color-mix(in srgb, var(--color-danger-light) 40%, var(--color-surface)); }
/* Today's highlight must still read on top of either tint. */
.time-col-head-cell.today { box-shadow: inset 0 -2px 0 var(--color-primary); }

/* The filter panel is always visible on desktop, so its toggle is hidden. */
.filters-toggle { display: none; }
.filters-count { padding: 1px 7px; font-size: 11px; background: var(--color-primary); color: var(--color-on-primary); }

/* Phones: shorter rows and narrower columns so the default week view shows
   about three days at once instead of two, without a taller page — and the
   ~300px filter panel folds away so the grid starts near the top. */
@media (max-width: 700px) {
  /* MOBILE DECISION: keep all seven columns and scroll horizontally, with the
     hour gutter pinned outside the scroller so it never leaves. Seven columns
     squeezed into 375px gives ~45px each — too narrow to read a service name or
     hit reliably. At 112px about three days are visible at once, which matches
     how a provider actually reads a week: today and what is coming.

     Rows get TALLER on mobile, not shorter. Vertical space is cheap because the
     grid scrolls anyway, and it is what makes a half-hour block tappable. */
  .time-grid-wrap { --hour-h: 60px; --day-col-min: 112px; }
  .time-gutter { width: 44px; }
  .time-col-head-cell { padding: 8px 3px; }
  .time-col-head-cell .dn { font-size: 15px; }
  .filters-toggle { display: inline-flex; margin-bottom: 12px; }
  .calendar-filters.collapsed { display: none; }
}
.time-event {
  position: absolute;
  /* left/width come from the lane layout inline — overlapping appointments
     share the column instead of stacking on top of one another. */
  border-radius: 8px;
  padding: 4px 7px;
  font-size: 11px;
  font-weight: 700;
  overflow: hidden;
  cursor: pointer;
  text-align: left;
  border: 0;
  border-left: 3px solid transparent;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  line-height: 1.25;
  z-index: 3;
  transition: box-shadow var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.time-event:active { transform: scale(0.985); }
.time-event:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }

.time-event > span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.time-event .te-svc { font-weight: 800; }
.time-event .te-time { font-weight: 600; opacity: 0.85; font-size: 10px; }
.time-event .te-title { font-weight: 600; opacity: 0.9; font-size: 10px; }

/* Content degrades by duration rather than overflowing: a half-hour block is
   about one line tall, so it keeps the service name and drops the rest. */
.time-event.is-tiny .te-time,
.time-event.is-tiny .te-title { display: none; }
.time-event.is-short .te-title { display: none; }
.time-event.is-short { flex-direction: row; align-items: baseline; gap: 5px; }
.time-event.is-short .te-time { order: -1; flex-shrink: 0; }

/* Status reads as a shape, not only a colour — the accessible name carries the
   word as well, so nothing depends on seeing the difference. */
.time-event.is-pending { border-left-style: dashed; }
.time-event.is-cancelled,
.time-event.is-declined { opacity: 0.55; }
.time-event.is-cancelled .te-svc,
.time-event.is-declined .te-svc { text-decoration: line-through; }


/* Event detail modal */
.event-detail-head { display: flex; align-items: center; gap: 12px; }
/* Durata modificabile dentro la riga di dettaglio: il campo resta stretto, così
   la riga continua a leggersi come le altre invece di diventare un modulo. */
.evt-duration-edit { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Azione distruttiva in fondo alla scheda, non fra i pulsanti in basso: là
   starebbe accanto ad "Accetta" su una prenotazione da confermare, e quattro
   pulsanti non ci stanno comunque su un telefono. */
.evt-danger-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--color-border);
}
.evt-danger-title { font-weight: 700; font-size: 13.5px; color: var(--color-text); }
@media (max-width: 560px) {
  .evt-danger-row { flex-direction: column; align-items: stretch; gap: 10px; }
}
.evt-duration-edit .input { width: 84px; flex: 0 0 auto; }
.event-detail-svc-icon { width: 44px; height: 44px; border-radius: 13px; display: flex; align-items: center; justify-content: center; font-size: 21px; flex-shrink: 0; }
.detail-row { display: flex; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--color-border); font-size: 13.5px; }
.detail-row:last-child { border-bottom: none; }
.detail-row .drk { width: 90px; flex-shrink: 0; font-weight: 700; color: var(--color-text-faint); font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; }
/* Narrow screens can't afford a 90px label gutter: side by side it wraps the
   label over 3 lines and squeezes the value into ~150px. Stack instead. */
@media (max-width: 560px) {
  .detail-row { flex-direction: column; gap: 2px; padding: 9px 0; }
  .detail-row .drk { width: auto; }
}

/* ==========================================================================
   Pet profile
   ========================================================================== */

/* Cover banner. aspect-ratio reserves the height before the image decodes, so
   nothing below it shifts once it loads. */
.pet-cover {
  position: relative;
  aspect-ratio: 1000 / 260;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: linear-gradient(120deg, var(--color-primary-light) 0%, var(--navy-50) 45%, var(--coral-50) 100%);
  border: 1px solid var(--color-border);
  cursor: pointer;
}
.pet-cover-img { width: 100%; height: 100%; object-fit: cover; }
.pet-cover-edit {
  position: absolute; right: 12px; top: 12px;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 12px; border-radius: var(--radius-pill);
  background: var(--color-overlay-light);
  color: var(--color-text); font-size: 12.5px; font-weight: 700;
  box-shadow: var(--shadow-sm); backdrop-filter: blur(6px);
}
.pet-cover:hover .pet-cover-edit { color: var(--color-primary-dark); }
.pet-cover:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }

/* flex-start matters: with flex-end the avatar would be pinned to the bottom of
   the row and its negative margin could never lift it over the cover. */
.pet-header {
  display: flex; gap: 18px; align-items: flex-start; margin-bottom: 22px; flex-wrap: wrap;
}
/* Only the avatar rides up over the cover — pulling the whole header up would
   drag the name and buttons onto the image with it. */
.pet-photo-button { margin-top: -46px; }
.pet-header-actions { margin-left: auto; }
.pet-header .pet-avatar {
  box-shadow: var(--shadow-md);
  border: 4px solid var(--color-surface);
  background-clip: padding-box;
}
.pet-identity { flex: 1; min-width: 0; }
.pet-identity h1 { margin-bottom: 2px; overflow-wrap: anywhere; }
.pet-subtitle { overflow-wrap: anywhere; }
.pet-identity-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.pet-header-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* The avatar is the control for changing the photo; the badge is what makes
   that discoverable now that there is no separate "Foto" button. */
.pet-photo-button { position: relative; }
.pet-photo-button .photo-badge {
  position: absolute; right: 0; bottom: 0;
  width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-surface); color: var(--color-text-muted);
  border: 1.5px solid var(--color-border-strong); box-shadow: var(--shadow-sm);
}
.pet-photo-button:hover .photo-badge { border-color: var(--color-primary); color: var(--color-primary-dark); }

.pet-tabs-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
/* Column layout + auto top margin keeps the values on one line across the row
   even when a label needs two lines to fit. */
.info-tile { padding: 14px 16px; display: flex; flex-direction: column; }
.info-tile .lbl { font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--color-text-faint); letter-spacing: 0.03em; margin-bottom: 4px; }
.info-tile .val { font-weight: 700; font-size: 15px; margin-top: auto; }

/* --- Scheda animale: identità e dettagli --------------------------------- */

/* Specie · razza · sesso · età su una riga sola. La pagina si apriva con una
   scheda di campi etichettati che ripeteva quanto già diceva il titolo: questi
   sono gli stessi fatti letti come una frase, così viene prima l'animale. */
.pet-idline {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  margin-top: 2px;
  font-size: 14px; color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

/* Affordance informativa: né un link né un'azione. Il tratteggio dice "qui c'è
   dell'altro" senza promettere una navigazione. */
.pet-age-note {
  font: inherit; color: inherit;
  background: none; border: none; padding: 0;
  cursor: pointer;
  border-bottom: 1px dashed var(--color-border-interactive);
  transition: color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}
.pet-age-note:hover,
.pet-age-note[aria-expanded="true"] {
  color: var(--color-text); border-bottom-color: var(--color-text);
}
.pet-age-note:focus-visible {
  outline: 2px solid var(--color-primary); outline-offset: 3px; border-radius: 3px;
}

.pet-birth-note {
  display: inline-flex; align-items: baseline; gap: 8px;
  margin-top: 8px; padding: 8px 12px;
  background: var(--color-surface-2);
  border-radius: var(--radius-sm);
  font-size: 13px;
  animation: pet-birth-in var(--duration-medium) var(--ease-decel);
}
/* display on the block above would otherwise beat the hidden attribute. */
.pet-birth-note[hidden] { display: none; }
.pbn-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.03em; color: var(--color-text-faint);
}
.pbn-value { font-weight: 700; }

@keyframes pet-birth-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}

/* Il conteggio in ritardo è l'unico corallo della schermata. */
.pet-health-badge { margin-left: 7px; padding: 1px 7px; font-size: 11px; }
/* accent-strong, not accent: this pill carries text, and coral-500 is
   documented as a non-text accent (3.90:1). */
.pet-health-badge.is-overdue { background: var(--color-accent-strong); color: var(--color-on-accent); }
.pet-health-badge.is-unknown { background: var(--color-surface-3); color: var(--color-text-muted); padding: 1px 5px; }
.pet-health-badge svg { display: block; }

/* Azione distruttiva in fondo alla pagina, come nella scheda appuntamento
   (`.evt-danger-row`) e nel profilo (`.privacy-row.is-danger`): l'unica azione
   irreversibile della scheda non sta accanto a Salute e Modifica. Riquadro
   completo e non solo un bordo superiore: qui la riga non chiude il corpo di
   una scheda, sta da sola in fondo a una colonna di schede. */
.pet-danger-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
}
.pet-danger-title { font-weight: 700; font-size: 13.5px; color: var(--color-text); }
.pet-danger-row .btn { flex-shrink: 0; }
.pet-danger-row > div { min-width: 0; }
@media (max-width: 560px) {
  .pet-danger-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .pet-danger-row .btn { width: 100%; }
}

/* Volutamente più leggero di una scheda: sono cose che si consultano, non su
   cui si agisce, e tre schede di pari peso erano ciò che rendeva la pagina
   piatta. */
.pet-reference {
  background: var(--color-surface-2);
  border-radius: var(--radius-lg);
  padding: 20px;
}
.pet-ref-title { font-size: 15px; margin: 0 0 2px; color: var(--color-text-muted); }
.pet-ref-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0 28px;
}
/* Full contrast, unlike the muted field labels below it. Both were uppercase
   at the same size and colour, so "ASPETTO" and "PESO" read as siblings and the
   grouping did no work — most visibly on mobile, where the columns collapse and
   the subheadings are the only thing separating one group from the next. */
.pet-ref-h3 {
  margin: 22px 0 2px; font-size: 12.5px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--color-text);
}
.pet-ref-group:first-child .pet-ref-h3 { margin-top: 10px; }
/* Label above value, not beside it. The shared .detail-row gives the label a
   fixed 90px column, which a word like "COMPORTAMENTO" overruns — in a card as
   wide as the old ones that merely looked tight, but in these narrower columns
   the label ran straight into its own value. Stacking also gives the value the
   full column width, which is what lets three columns fit at all. */
.pet-reference .detail-row { flex-direction: column; gap: 2px; }
.pet-reference .detail-row .drk { width: auto; }

.pet-ref-note { margin-top: 4px; }
.pet-ref-note p { font-size: 13.5px; }
.pet-ref-foot {
  margin: 18px 0 0; padding-top: 12px;
  border-top: 1px solid var(--color-border);
  font-size: 12px; color: var(--color-text-faint);
}

/* --- Pagina Salute ------------------------------------------------------- */
.subpage-head { margin-bottom: 14px; }
.pet-health-head {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 18px;
}
.pet-health-head h1 { margin: 0; }
.pet-health-head-text { min-width: 0; }
.pet-health-foot { margin: 2px 0 0; padding: 0 2px; }

/* Phones: stack the identity block under the avatar instead of squeezing a
   32px title into the ~180px left over beside it, and keep the summary tiles
   on one row rather than collapsing to four full-width cards. */
@media (max-width: 700px) {
  /* Taller ratio so the banner still reads as a banner on a narrow screen. */
  .pet-cover { aspect-ratio: 5 / 2; border-radius: var(--radius-md); }
  .pet-cover-edit { right: 10px; top: 10px; padding: 8px; }
  .pet-photo-button { margin-top: -38px; }
  .pet-header { gap: 14px; margin-bottom: 18px; }
  .pet-identity { flex-basis: 100%; }
  .pet-identity h1 { font-size: 26px; }
  /* Two buttons, one row: Salute and Modifica split the full-bleed width.
     Delete used to make it three and wrap onto a second row; it now lives at
     the foot of the page. */
  .pet-header-actions { flex-basis: 100%; }
  .pet-header-actions .btn { flex: 1; }
  .pet-tabs-grid { grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 16px; }
  .info-tile { padding: 12px 10px; }
  .info-tile .lbl { font-size: 10px; }
  .info-tile .val { font-size: 14px; }
}

.pet-selector-strip { display: flex; gap: 10px; margin-bottom: 22px; overflow-x: auto; padding-bottom: 4px; }
.pet-chip {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 14px 7px 7px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  cursor: pointer;
  flex-shrink: 0;
  font-weight: 700;
  font-size: 13px;
}
.pet-chip.active { border-color: var(--color-primary); background: var(--color-primary-light); color: var(--color-primary-dark); }
.pet-chip .avatar, .pet-chip .avatar-fallback { width: 26px; height: 26px; font-size: 11px; }


/* ==========================================================================
   Bookings list
   ========================================================================== */

.booking-row {
  display: flex; align-items: center; gap: 14px;
  padding: 15px 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--color-border);
  margin-bottom: 10px;
  background: var(--color-surface);
}
.booking-row .svc-dot { width: 10px; height: 60px; border-radius: 6px; flex-shrink: 0; }
/* A fixed 60px bar sets the floor for the whole row; let it track the content
   instead, so a one-line booking stays a one-line row. Only the bar stretches —
   the row stays centre-aligned so the status pill keeps its natural height. */
@media (max-width: 700px) {
  .booking-row { gap: 11px; padding: 13px 12px; }
  .booking-row .svc-dot { height: auto; align-self: stretch; min-height: 38px; }
}
.booking-row .brow-main { flex: 1; min-width: 0; }
.booking-row .brow-title { font-weight: 700; font-size: 14.5px; margin-bottom: 2px; }
.booking-row .brow-meta { font-size: 12.5px; color: var(--color-text-muted); display: flex; gap: 10px; flex-wrap: wrap; }
.meta-icon-txt { display: inline-flex; align-items: center; gap: 4px; }
.booking-row .brow-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* ==========================================================================
   Messaging
   ========================================================================== */

/* The shell is the only thing bounded to the viewport; everything inside it
   scrolls internally. dvh follows the mobile URL bar, with vh as the fallback
   for browsers that lack it. */
.messages-shell {
  display: grid; grid-template-columns: 300px 1fr;
  height: calc(100vh - var(--header-h) - 40px);
  height: calc(100dvh - var(--header-h) - 40px);
  min-height: 0;
  border: 1px solid var(--color-border); border-radius: var(--radius-lg);
  overflow: hidden; background: var(--color-surface);
}
@media (max-width: 900px) {
  .messages-shell {
    grid-template-columns: 1fr;
    /* L'unica pagina che non scorre: il guscio è tagliato sull'altezza della
       finestra e dentro scorrono la lista e la conversazione. Quindi il
       padding in fondo a .main-content qui non protegge niente, e lo spazio
       della pillola va sottratto a mano — o la casella di scrittura, che è
       l'ultima cosa dentro al guscio, finisce sotto la barra.
       Il 40 è la somma vera dei margini di .main-content qui sopra (18 in cima
       + 18 di aria sotto lo spazio della barra) più 4px di gioco: con 32,
       misurato, il documento risultava 4px più alto della finestra e la
       schermata della chat — l'unica che non dovrebbe scorrere affatto —
       rimbalzava. */
    height: calc(100vh - var(--header-h) - var(--bottomnav-space) - 40px);
    height: calc(100dvh - var(--header-h) - var(--bottomnav-space) - 40px);
  }
}

.thread-list { border-right: 1px solid var(--color-border); overflow-y: auto; min-height: 0; }
.thread-item { display: flex; gap: 11px; padding: 13px 14px; cursor: pointer; border-bottom: 1px solid var(--color-border); }
.thread-item:hover, .thread-item.active { background: var(--color-surface-2); }
.thread-item .ti-name { font-weight: 700; font-size: 13.5px; display: flex; justify-content: space-between; gap: 6px; }
.thread-item .ti-preview { font-size: 12.5px; color: var(--color-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thread-item .ti-time { font-size: 10.5px; color: var(--color-text-faint); font-weight: 600; white-space: nowrap; }
/* Il nome è il pulsante "apri" della conversazione: la riga contiene fissa ed
   elimina, e un controllo dentro un controllo non si può annunciare. Nessun
   aspetto da pulsante — resta il nome, in grassetto, e va a capo come faceva
   lo <span> di prima invece di troncarsi. */
.thread-item .ti-open {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  white-space: normal;
}
.unread-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--color-primary); flex-shrink: 0; }

.thread-item { align-items: flex-start; }
.thread-item.pinned { background: var(--color-primary-light); }
.thread-item.pinned:hover, .thread-item.pinned.active { background: var(--color-primary-light); }
.ti-side { display: flex; flex-direction: column; align-items: center; gap: 6px; flex-shrink: 0; }
/* I margini negativi riassorbono l'altezza in eccesso, come in .zm-fb-btn:
   il bersaglio è 44px per il pollice, il segno resta piccolo com'è giusto per
   un controllo secondario, e la riga non cresce di venti pixel per due icone.
   Prima erano 20x20 misurati sul telefono — e uno dei due elimina la
   conversazione, dentro una riga la cui intera superficie significa "apri". */
.ti-actions { display: flex; gap: 2px; margin: -12px -6px; }
.ti-actions button {
  border: none; background: none; cursor: pointer; border-radius: 8px; line-height: 0;
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--color-text-faint); opacity: 0.55; transition: opacity var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.thread-item:hover .ti-actions button { opacity: 1; }
.ti-actions button:hover { background: var(--color-surface); color: var(--color-text); }
.ti-actions button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -4px; }
.ti-actions button.is-pinned { color: var(--color-primary); opacity: 1; }
.ti-actions button.ti-del:hover { color: var(--color-danger); }
/* Touch devices have no hover, so keep the controls permanently legible. */
@media (hover: none) { .ti-actions button { opacity: 0.8; } }

/* min-height:0 on both the panel and the scroller is what actually fixes the
   bug: a flex/grid child defaults to min-height:auto, so the message list grew
   to fit every message and pushed the input bar out of the clipped shell. */
/* The grid cell holding the chat. Without min-height:0 a grid item defaults to
   min-height:auto, so it grew to fit every message and carried the composer
   below the clipped edge of the shell — that is the bug. */
.chat-pane { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.chat-panel { display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1; }
.chat-header { display: flex; align-items: center; gap: 10px; padding: 13px 16px; border-bottom: 1px solid var(--color-border); }
.chat-body {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 16px; display: flex; flex-direction: column; gap: 10px;
}
.chat-msg { max-width: 72%; padding: 10px 14px; border-radius: 16px; font-size: 13.5px; }
.chat-msg.mine { align-self: flex-end; background: var(--color-primary); color: var(--color-on-primary); border-bottom-right-radius: 4px; }
.chat-msg.theirs { align-self: flex-start; background: var(--color-surface-2); border-bottom-left-radius: 4px; }
.chat-msg .ct { display: block; font-size: 10px; margin-top: 4px; opacity: 0.65; }
.chat-msg.report-card { background: var(--color-surface); border: 1.5px solid var(--color-border); align-self: flex-start; max-width: 82%; }
.chat-msg.report-card .rc-photos { display: flex; gap: 6px; margin-top: 8px; }
.chat-msg.report-card .rc-photos img { width: 68px; height: 68px; border-radius: 8px; object-fit: cover; }
/* ==========================================================================
   Zampa nella lista dei messaggi

   Deve leggersi al primo colpo d'occhio come "non è una persona". Il marchio
   al posto della foto fa quasi tutto il lavoro; il resto lo fa l'assenza della
   casella per scrivere, che è anche la risposta a "e se qualcuno risponde".
   Non si usa il coral: il tetto di due elementi per schermata è già occupato
   dal resto dell'interfaccia, e questa riga non è una cosa da fare.
   ========================================================================== */
.zampa-avatar {
  width: 42px; height: 42px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
}
.zampa-avatar .logo-mark { display: flex; }
.chat-header.is-zampa .zampa-avatar { width: 36px; height: 36px; }

.thread-item.is-zampa { align-items: center; }
/* Il bordo a sinistra la separa dai fornitori senza spostarla né colorarla
   tutta: resta una riga della stessa lista, ma non la stessa cosa. */
.thread-item.is-zampa { box-shadow: inset 3px 0 0 var(--color-primary); }

/* Titolo e corpo su due righe: sono note brevi, non battute di chat. Più
   larghe delle bolle normali perché contengono due o tre frasi intere. */
.chat-msg.is-zampa-msg {
  max-width: 88%;
  background: var(--color-surface);
  border: 1.5px solid var(--color-border);
  box-shadow: var(--shadow-sm);
}
.chat-msg.is-zampa-msg .zm-title { display: block; font-weight: 700; margin-bottom: 4px; }
.chat-msg.is-zampa-msg .zm-body { display: block; }

/* I pollici stanno sulla riga dell'ora, non sotto: così il riscontro non
   aggiunge una riga di altezza a ogni consiglio, e una conversazione di
   trenta note resta lunga come prima. L'ora si prende lo spazio e li spinge
   a destra. */
.chat-msg.is-zampa-msg .zm-foot { display: flex; align-items: center; gap: 8px; }
.chat-msg.is-zampa-msg .zm-foot .ct { flex: 1; margin-top: 0; }
.zm-fb { display: flex; gap: 2px; margin: -10px -8px -10px 0; }

/* 44px di area sensibile attorno a un'icona da 15: il bersaglio è quello che
   serve al pollice, il segno resta piccolo com'è giusto per un controllo
   secondario. I margini negativi qui sopra riassorbono l'altezza in eccesso,
   altrimenti la bolla crescerebbe di venti pixel per due glifi. */
/* Il grigio è quello del token, a piena opacità. Un `opacity` che smorza il
   muted sembra la via ovvia per renderlo discreto e invece lo porta sotto il
   3:1 che la 1.4.11 chiede a un comando: a 0.55 misurava 2.27 su bianco, e
   sarebbe passato inosservato perché il controllo si vedeva benissimo a occhio
   nudo. La discrezione la fa la misura del segno, 15px, non la trasparenza. */
.zm-fb-btn {
  display: flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0;
  border: none; background: none; cursor: pointer;
  color: var(--color-text-muted);
  border-radius: var(--radius-sm); line-height: 0;
  transition: color var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.zm-fb-btn:hover { color: var(--color-text); }
.zm-fb-btn:active { transform: scale(0.88); }
.zm-fb-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -4px; }
/* Votato. Il pieno contro il contorno è ciò che porta lo stato; il navy è di
   contorno, non il portatore, e resta il primario perché il coral è scarso e
   un pollice non è una cosa da fare. */
.zm-fb-btn.is-on { opacity: 1; color: var(--color-primary); }

/* Al posto della casella per scrivere. Stessa posizione, stesso bordo
   superiore, così l'occhio ci arriva dove si aspetta la risposta e trova
   invece la spiegazione del perché non c'è. */
.zampa-note {
  flex-shrink: 0; margin: 0;
  padding: 12px 14px;
  border-top: 1px solid var(--color-border);
  background: var(--color-surface-2);
  font-size: 12.5px; color: var(--color-text-muted);
}
.zampa-empty {
  align-self: center; max-width: 40ch; margin: auto 0;
  text-align: center; font-size: 13.5px; color: var(--color-text-muted);
}

/* flex-shrink:0 keeps the composer at its natural height no matter how long
   the conversation is. */
.chat-input-row {
  display: flex; gap: 8px; padding: 12px; flex-shrink: 0;
  border-top: 1px solid var(--color-border); background: var(--color-surface);
}
.chat-input-row input { flex: 1; }

/* ==========================================================================
   Provider dashboard
   ========================================================================== */

.pd-tabs { display: flex; gap: 6px; margin-bottom: 20px; flex-wrap: wrap; }
.request-card { display: flex; gap: 14px; padding: 16px; border-radius: var(--radius-md); border: 1px solid var(--color-border); margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
.request-card .rc-main { flex: 1; min-width: 180px; }

.hours-editor-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--color-border); }
.hours-editor-row .day-lbl { width: 90px; font-weight: 700; font-size: 13px; }
.hours-editor-row input[type=time] { padding: 7px 9px; border-radius: 8px; border: 1.5px solid var(--color-border-strong); }

/* Availability page on phones.
   The opening-hours row is the actual culprit behind the "overlapping dates"
   in Chiusure programmate: a 90px day label plus two non-shrinking time inputs
   need ~386px in a ~305px column, and with no flex-wrap it stretched the whole
   document past the viewport. Everything below then laid out against that wider
   document and collided with the screen edge. Letting the row wrap and the
   inputs shrink keeps the page at the phone's width. */
@media (max-width: 700px) {
  .hours-editor-row { flex-wrap: wrap; row-gap: 8px; }
  .hours-editor-row .day-lbl { width: auto; min-width: 42px; }
  /* Takes the rest of the first line, so the two times share the second one
     instead of stranding "alle" beside a single input. */
  .hours-editor-row .checkbox-row { margin-right: auto !important; }
  .hours-editor-row input[type=time] { flex: 1 1 0; min-width: 88px; }
}

/* Closures form: one field per row on small screens, so the date cells always
   have space instead of relying on the date input's intrinsic width. */
@media (max-width: 560px) {
  #block-form .field { flex: 1 1 100%; min-width: 0; }
  #block-form .input { width: 100%; }
  #block-form button[type="submit"] { width: 100%; }
}


/* ==========================================================================
   Pet avatars, species picker, pet forms
   ========================================================================== */

.pet-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; overflow: hidden; flex-shrink: 0;
  background: color-mix(in srgb, var(--pet-tint) 16%, var(--color-surface));
  border: 2px solid color-mix(in srgb, var(--pet-tint) 32%, var(--color-surface));
  line-height: 1;
}
.pet-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pet-avatar.is-emoji { user-select: none; }

/* An avatar that is itself a button — must size to its content, unlike .btn-icon */
.avatar-button {
  padding: 0; border: none; background: none; cursor: pointer;
  border-radius: 50%; flex-shrink: 0; line-height: 0;
  /* Area sensibile da 44px anche quando l'immagine dentro è da 32 (la barra in
     alto): il segno resta della misura che ha, il bersaglio no. Sui pulsanti
     più grandi — foto dell'animale, avatar del profilo — il minimo non morde. */
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
.avatar-button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }

/* Species picker */
.species-search-wrap { position: relative; margin-bottom: 14px; }
.species-group { margin-bottom: 18px; }
.species-group-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-faint); margin-bottom: 8px;
}
.species-grid-lg { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.species-card {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 14px 8px; border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border-strong); background: var(--color-surface);
  cursor: pointer; font-weight: 600; font-size: 12.5px; text-align: center;
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out);
}
.species-card:hover { border-color: var(--color-primary); }
.species-card:active { transform: scale(0.97); }
.species-card.selected { border-color: var(--color-primary); background: var(--color-primary-light); color: var(--color-primary-dark); }
.species-card .sc-emoji { font-size: 30px; line-height: 1; }
.species-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.category-chip-row { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 14px; }

/* Derived category badge — shown, never chosen */
.derived-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: var(--radius-pill);
  background: var(--color-surface-2); color: var(--color-text-muted);
  font-size: 12px; font-weight: 600;
}

/* Form sections in the pet wizard / editor */
.form-section { margin-bottom: 22px; }
.form-section > h3 {
  font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--color-text-faint); margin: 0 0 10px;
}
.field-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.field-error { color: var(--color-danger); font-size: 12px; font-weight: 600; margin-top: 5px; }
.input.has-error, .textarea.has-error { border-color: var(--color-danger); }

/* A value computed from another field — visibly not typeable, but still legible */
.input.is-derived {
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  cursor: default;
}
.input.is-derived:focus { border-color: var(--color-border-strong); box-shadow: none; }

/* Avatar editor */
.avatar-editor { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.avatar-dropzone {
  border: 2px dashed var(--color-border-strong); border-radius: var(--radius-md);
  padding: 18px; text-align: center; cursor: pointer; flex: 1; min-width: 200px;
  color: var(--color-text-muted); font-size: 13px; transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.avatar-dropzone:hover, .avatar-dropzone.dragover { border-color: var(--color-primary); background: var(--color-primary-light); }
.cropper-stage {
  position: relative; width: 100%; max-width: 320px; aspect-ratio: 1; margin: 0 auto;
  overflow: hidden; border-radius: var(--radius-md); background: var(--color-text);
  touch-action: none; cursor: grab;
}
/* Cover crops are wide, so they get the full dialog width to frame in. */
.cropper-stage.is-wide { max-width: 100%; }
.cropper-stage canvas { display: block; width: 100%; height: 100%; }
/* Avatars render as circles everywhere, so the square crop is previewed with
   the circle punched out of a dim overlay — the corners stay visible because
   they are still part of the exported square. */
.cropper-mask {
  position: absolute; inset: 0; border-radius: 50%; pointer-events: none;
  box-shadow: 0 0 0 9999px var(--color-scrim);
}
.cropper-hint { text-align: center; font-size: 12px; color: var(--color-text-faint); margin-top: 8px; }

/* Pet list cards */
.pet-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.pet-card {
  display: flex; align-items: center; gap: 13px; padding: 15px;
  cursor: pointer; transition: transform var(--duration-instant) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
}
.pet-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.pet-card-add {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 92px; border-style: dashed; cursor: pointer;
  color: var(--color-text-muted); font-weight: 700; font-size: 13.5px;
}
.pet-card-add:hover { border-color: var(--color-primary); color: var(--color-primary-dark); }

@media (prefers-reduced-motion: reduce) {
  .pet-card, .species-card { transition: none; }
  .pet-card:hover { transform: none; }
}

/* ==========================================================================
   Provider profile (public + owner edit share these)
   ========================================================================== */

.provider-hero .cover.is-editable { cursor: pointer; position: relative; }
.provider-hero .cover .cover-edit {
  position: absolute; right: 12px; top: 12px;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 12px; border-radius: var(--radius-pill);
  background: var(--color-overlay-light); color: var(--color-text);
  font-size: 12.5px; font-weight: 700; box-shadow: var(--shadow-sm);
}
.pp-avatar-button { position: relative; }
.pp-avatar-button .photo-badge {
  position: absolute; right: -2px; bottom: -2px;
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-surface); color: var(--color-text-muted);
  border: 1.5px solid var(--color-border-strong); box-shadow: var(--shadow-sm);
}
.pp-avatar-button:hover .photo-badge { border-color: var(--color-primary); color: var(--color-primary-dark); }

/* Provider's own service list: management row + add/edit form */
.service-manage-row {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 0; border-bottom: 1px solid var(--color-border);
}
.service-manage-row:last-child { border-bottom: none; }
/* Non attivo: lo dice già la pillola "Non attivo" accanto al nome. L'opacity
   a 0.62 portava la riga dei dettagli a 2.57:1, quindi si toglie e resta il
   token. */
.service-manage-row.is-inactive .text-faint { color: var(--color-text-muted); }
.service-manage-row.is-inactive .fw-700 { color: var(--color-text-secondary); }
.svc-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.switch-inline { display: inline-flex; align-items: center; }
.service-form {
  border: 1.5px solid var(--color-border-strong);
  border-radius: var(--radius-md);
  padding: 16px; margin-bottom: 16px;
  background: var(--color-surface-2);
}
.service-form h3 { margin-bottom: 12px; }
@media (max-width: 560px) {
  .service-manage-row { flex-direction: column; }
  .svc-actions { align-self: flex-end; }
}

/* Tappable animal row on an appointment — reads as a control, not as text. */
.pet-chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
.pet-mini-row {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 8px 4px 4px;
  border-radius: var(--radius-pill);
  border: 1.5px solid var(--color-border-strong);
  background: var(--color-surface);
  cursor: pointer; font: inherit; font-size: 12.5px; font-weight: 700;
  color: var(--color-text);
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.pet-mini-row:hover { border-color: var(--color-primary); color: var(--color-primary-dark); background: var(--color-primary-light); }
.pet-mini-row:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
.pet-mini-row .pmr-chevron { color: var(--color-text-faint); line-height: 0; }
.pet-mini-row:hover .pmr-chevron { color: var(--color-primary-dark); }
.pet-mini-row .pmr-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 140px; }
@media (prefers-reduced-motion: reduce) { .pet-mini-row { transition: none; } }

.next-slot-banner {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; margin-bottom: 14px;
  border-radius: var(--radius-md);
  background: var(--color-primary-light); color: var(--color-primary-dark);
  font-size: 13.5px;
}
.next-slot-banner strong { font-weight: 700; }

/* ==========================================================================
   Customer profile
   ========================================================================== */

.profile-page { display: flex; flex-direction: column; gap: 16px; max-width: 720px; }

.profile-header { display: flex; align-items: center; gap: 18px; }
.profile-identity { min-width: 0; flex: 1; }
.profile-identity h1 { font-size: 24px; margin-bottom: 2px; overflow-wrap: anywhere; }

.profile-avatar-button { position: relative; }
.profile-avatar-button .photo-badge {
  position: absolute; right: -2px; bottom: -2px;
  width: 28px; height: 28px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-surface); color: var(--color-text-muted);
  border: 1.5px solid var(--color-border-strong); box-shadow: var(--shadow-sm);
}
.profile-avatar-button:hover .photo-badge { border-color: var(--color-primary); color: var(--color-primary-dark); }
.profile-avatar-button.is-busy { cursor: progress; }
.profile-avatar-button.is-busy .avatar, .profile-avatar-button.is-busy .avatar-fallback { opacity: 0.45; }
.avatar-spinner {
  position: absolute; inset: 0; margin: auto;
  width: 26px; height: 26px; border-radius: 50%;
  border: 3px solid var(--color-primary-light); border-top-color: var(--color-primary);
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Pets + payment rows share a divider rhythm with .detail-row */
.profile-pet-row, .payment-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--color-border);
}
.profile-pet-row:last-child, .payment-row:last-child { border-bottom: none; }
.ppr-main { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; cursor: pointer; border-radius: var(--radius-sm); }
.ppr-main:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.ppr-actions { display: flex; gap: 6px; flex-shrink: 0; }
.btn-icon.danger { color: var(--color-danger); }
.btn-icon.danger:hover { border-color: var(--color-danger); color: var(--color-danger); }

.payment-chip {
  width: 42px; height: 42px; border-radius: var(--radius-sm); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--color-surface-2); color: var(--color-text-muted);
}
.payment-form { border-top: 1px solid var(--color-border); padding-top: 16px; }

.switch-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 13px 0; border-bottom: 1px solid var(--color-border); cursor: pointer;
}
.switch-row .switch-label { display: block; font-weight: 600; font-size: 14px; }
input.switch {
  appearance: none; -webkit-appearance: none;
  width: 46px; height: 27px; border-radius: 999px; flex-shrink: 0;
  background: var(--color-border-strong); position: relative; cursor: pointer;
  transition: background var(--duration-medium) var(--ease-in-out); margin: 0;
}
input.switch::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 21px; height: 21px; border-radius: 50%; background: var(--color-surface);
  box-shadow: var(--shadow-sm); transition: transform var(--duration-medium) var(--ease-in-out);
}
input.switch:checked { background: var(--color-primary); }
input.switch:checked::after { transform: translateX(19px); }
input.switch:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* Sidebar footer entry point to the profile */
.sidebar-user {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: none; background: none; cursor: pointer;
  border-radius: var(--radius-sm); color: inherit; text-align: left;
}
.sidebar-user:hover { background: var(--color-surface-2); }
.sidebar-user.active { background: var(--color-primary-light); color: var(--color-primary-dark); }
.sidebar-user:focus-visible { outline: 2px solid var(--color-primary); outline-offset: -2px; }

@media (max-width: 700px) {
  .profile-header { gap: 14px; }
  .profile-identity h1 { font-size: 21px; }
  .switch-row { gap: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .avatar-spinner { animation-duration: 2s; }
  input.switch, input.switch::after { transition: none; }
}

/* ==========================================================================
   Auth (login / signup)
   ========================================================================== */

.auth-shell { min-height: 100vh; background: var(--color-surface-2); }
.auth-card { max-width: 400px; margin: 0 auto; padding: 60px 20px; min-height: 100vh; display: flex; flex-direction: column; justify-content: center; }

/* Brand lockup. Stessa piastrella della nav, stesso motivo: il marchio è un
   SVG trasparente e il piano bianco è deliberato, non un rimedio ai bordi
   opachi della vecchia PNG. Il padding vale in percentuale, così .landing-brand
   può rimpicciolire il riquadro senza ricalcolare nulla. */
.auth-brand { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 28px; }
.auth-brand strong { font-family: var(--font-display); font-size: 22px; }
.auth-logo {
  width: 88px; height: 88px; border-radius: var(--radius-lg);
  background: var(--color-surface); box-shadow: var(--shadow-sm);
  display: grid; place-items: center;
}
.auth-switch { text-align: center; margin-top: 20px; font-size: 13px; color: var(--color-text-faint); }
.auth-switch button { color: var(--color-primary); font-weight: 700; background: none; border: none; cursor: pointer; padding: 0; font-size: inherit; }

/* ==========================================================================
   Landing — la schermata di accesso per chi non ha ancora una sessione.

   Un'unica griglia serve due letture. Sul telefono l'ordine del DOM comanda:
   intestazione, modulo, motivi — chi torna trova il campo email senza
   scorrere. Da 900px in su le stesse tre parti si dispongono su due colonne
   con grid-template-areas, il racconto a sinistra su due righe e l'accesso a
   destra che le attraversa, senza duplicare marcatura per il desktop.
   ========================================================================== */

.landing {
  display: grid;
  gap: 26px;
  max-width: 1060px;
  margin: 0 auto;
  padding: calc(36px + var(--safe-top)) calc(20px + var(--safe-right))
           calc(40px + var(--safe-bottom)) calc(20px + var(--safe-left));
}

/* .auth-brand impila logo e nome al centro; qui stanno affiancati, più piccoli:
   sopra il titolo la firma deve esserci, non prendere la scena. */
.landing-brand {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-bottom: 20px;
}
.landing-brand .auth-logo { width: 52px; height: 52px; }

.landing-title {
  font-size: clamp(26px, 5.4vw, 38px);
  line-height: 1.15;
  margin: 0 0 12px;
  text-align: center;
}
.landing-sub {
  max-width: 46ch;
  margin: 0 auto;
  font-size: 15.5px;
  line-height: 1.55;
  text-align: center;
}
.il.landing-art { margin-top: 24px; }

/* .auth-card centra il modulo in un'intera schermata: dentro la griglia quella
   altezza forzata spingerebbe i motivi fuori dal telefono. */
.auth-card.landing-card {
  min-height: 0;
  max-width: 420px;
  padding: 0;
  justify-content: flex-start;
}
.landing-card-title { font-size: 20px; text-align: center; margin-bottom: 4px; }
.landing-card-sub { text-align: center; margin-bottom: 18px; }

.landing-points { display: grid; gap: 18px; }
.landing-point { display: flex; align-items: flex-start; gap: 12px; }
.landing-point-icon {
  flex-shrink: 0;
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--color-primary-light);
  color: var(--color-primary-dark);
}
.landing-point-title { font-weight: 800; font-size: 14.5px; margin-bottom: 2px; }
.landing-point-body { margin: 0; font-size: 13.5px; line-height: 1.5; }

.landing-cta { max-width: 420px; margin-top: 26px; }
.landing-legal {
  margin: 24px 0 0; text-align: center;
  font-size: 12.5px; color: var(--color-text-faint);
}

/* Sul telefono il racconto sta sopra il modulo, quindi ogni pixel speso qui
   allontana il campo email di chi voleva solo entrare. Il disegno resta — è
   quello che dà carattere alla schermata — ma più piccolo, e le distanze si
   stringono, così su uno schermo corto restano visibili anche Accedi. */
@media (max-width: 899px) {
  .landing { gap: 20px; }
  .landing-brand { margin-bottom: 14px; }
  .il.landing-art { width: 136px; height: 109px; margin-top: 14px; }
}

@media (min-width: 900px) {
  .landing {
    grid-template-columns: 1.05fr 0.95fr;
    grid-template-areas: "intro auth" "value auth";
    align-items: start;
    column-gap: 56px;
    row-gap: 34px;
    padding-top: calc(60px + var(--safe-top));
  }
  .landing-intro { grid-area: intro; }
  /* Ancorata in alto, non centrata: centrandola il campo email finiva a metà
     schermo e il vuoto si spostava sopra, cioè davanti agli occhi di chi torna
     solo per entrare. */
  .landing-auth { grid-area: auth; }
  .landing-value { grid-area: value; }

  /* A due colonne il testo è una colonna vera e si allinea a sinistra; centrato
     lascerebbe margini a zig-zag contro il bordo della scheda accanto. */
  .landing-brand { justify-content: flex-start; }
  .landing-title,
  .landing-sub,
  .landing-legal { text-align: left; }
  .landing-sub { margin-left: 0; }
  .il.landing-art { margin-left: 0; }
}
.role-toggle-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
.role-toggle-opt {
  padding: 16px 10px; text-align: center; border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border-strong); cursor: pointer; font-weight: 700; font-size: 13px;
}
.role-toggle-opt.selected { border-color: var(--color-primary); background: var(--color-primary-light); color: var(--color-primary-dark); }

/* ==========================================================================
   Onboarding
   ========================================================================== */

.onboarding-wrap {
  max-width: 520px;
  margin: 0 auto;
  padding: 40px 20px 60px;
  min-height: 100vh;
}
.onboarding-progress { display: flex; gap: 6px; margin-bottom: 30px; }
.onboarding-progress span { flex: 1; height: 4px; border-radius: 4px; background: var(--color-border); }
.onboarding-progress span.done { background: var(--color-primary); }
.species-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.species-opt {
  padding: 16px 6px; text-align: center; border-radius: var(--radius-md);
  border: 1.5px solid var(--color-border-strong); cursor: pointer; font-weight: 700; font-size: 13px;
}
.species-opt .se { font-size: 26px; display: block; margin-bottom: 6px; }
.species-opt.selected { border-color: var(--color-primary); background: var(--color-primary-light); color: var(--color-primary-dark); }
.temper-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.temper-opt {
  padding: 8px 14px; border-radius: var(--radius-pill); border: 1.5px solid var(--color-border-strong);
  cursor: pointer; font-weight: 600; font-size: 12.5px;
}
.temper-opt.selected { background: var(--color-text); border-color: var(--color-text); color: var(--color-on-primary); }

/* Utility */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-6 { gap: 6px; }
.gap-8 { gap: 8px; }
.gap-10 { gap: 10px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; }
.mt-20 { margin-top: 20px; }
.mb-0 { margin-bottom: 0; }
.text-muted { color: var(--color-text-muted); }
.text-faint { color: var(--color-text-faint); }
.text-sm { font-size: 13.5px; }
.text-center { text-align: center; }
.fw-700 { font-weight: 700; }
.w-full { width: 100%; }
.hide-mobile { }
@media (max-width: 700px) { .hide-mobile { display: none !important; } }
.hide-desktop { display: none !important; }
@media (max-width: 700px) { .hide-desktop { display: revert !important; } }

/* ==========================================================================
   Reviews
   ========================================================================== */

.star-input { display: flex; align-items: center; gap: 4px; }
.star-btn {
  border: none; background: none; cursor: pointer; padding: 0 2px;
  font-size: 28px; line-height: 1; color: var(--color-border-strong);
  transition: color var(--duration-fast) var(--ease-out), transform var(--duration-instant) var(--ease-out);
}
.star-btn.on { color: var(--color-warning); }
.star-btn:hover { transform: scale(1.12); }
.star-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; border-radius: 4px; }
.star-value { margin-left: 8px; font-size: 13px; color: var(--color-text-faint); font-weight: 600; }

.review-photos { display: flex; flex-wrap: wrap; gap: 8px; }
.review-photo {
  position: relative; width: 76px; height: 76px; flex-shrink: 0;
  border-radius: var(--radius-sm); overflow: hidden; padding: 0;
  border: 1px solid var(--color-border); background: var(--color-surface-2); cursor: pointer;
}
.review-photo img { width: 100%; height: 100%; object-fit: cover; }
.review-photo-remove {
  position: absolute; top: 3px; right: 3px;
  width: 22px; height: 22px; border-radius: 50%;
  border: none; cursor: pointer; line-height: 1; font-size: 12px;
  background: var(--color-scrim-strong); color: var(--color-on-primary);
}
.review-photo-add {
  width: 76px; height: 76px; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  border: 2px dashed var(--color-border-strong); border-radius: var(--radius-sm);
  background: none; cursor: pointer; color: var(--color-text-muted);
  font-size: 11px; font-weight: 600; position: relative;
}
.review-photo-add:hover:not(:disabled) { border-color: var(--color-primary); color: var(--color-primary-dark); }
.review-photo-add:disabled { cursor: progress; }

.review-item.is-mine { background: var(--color-primary-light); border-radius: var(--radius-md); padding: 12px; margin-bottom: 12px; }
@media (prefers-reduced-motion: reduce) { .star-btn { transition: none; } }

/* Notification panel rows */
.ntf-row { cursor: pointer; border-radius: var(--radius-sm); }
/* Letto = più quieto, ma NON con opacity. A 0.6 il corpo del messaggio
   scendeva a 2.48:1 su bianco, cioè illeggibile, ed era il testo principale
   del pannello. L'opacity non si vede in un controllo sui colori calcolati:
   va composta a mano. Qui il grado di quiete lo porta un token già verificato
   e il pallino che sparisce. */
.ntf-row.is-read .text-sm { color: var(--color-text-muted); }
.ntf-row.is-read .fw-700 { font-weight: 600; }
.ntf-row.is-read .reminder-icon { background: var(--color-surface-3) !important; color: var(--color-text-muted) !important; }
.ntf-row:hover { background: var(--color-surface-2); }
.ntf-row:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* .section-head is nowrap by default, which pushed a long CTA past the card
   edge on narrow screens. Let it wrap rather than overflow. */
@media (max-width: 560px) {
  .section-head { flex-wrap: wrap; }
}

/* Inline edit/delete on your own review row */
.review-actions { display: flex; gap: 6px; flex-shrink: 0; margin-left: auto; }
@media (max-width: 480px) {
  /* Keep the controls off the name/stars on narrow screens. */
  .review-item .rh { flex-wrap: wrap; }
  .review-actions { margin-left: 0; width: 100%; justify-content: flex-end; }
}

/* ==========================================================================
   Consent banner + legal pages
   ========================================================================== */
.consent-root {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 12px 12px calc(12px + var(--safe-bottom));
  display: flex; justify-content: center; pointer-events: none;
}
/* Il banner sta sopra la barra (z 60 contro 40) e deve restarci: va deciso
   prima di andare avanti. Ma con una pillola staccata sotto, appoggiarcisi
   sopra sembra un ritaglio sbagliato invece di una sovrapposizione voluta,
   quindi qui si alza — e solo dove la barra c'è davvero. Il banner esce anche
   sulle pagine legali e sul login, dove barra non ce n'è: `:has()` su
   `[hidden]` è ciò che distingue i due casi, visto che il nodo è nel documento
   comunque. */
@media (max-width: 900px) {
  body:has(#bottom-nav:not([hidden])) .consent-root {
    padding-bottom: calc(var(--bottomnav-space) + 10px);
  }
}
.consent-card {
  pointer-events: auto; width: 100%; max-width: 560px;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: 18px; max-height: 80vh; overflow-y: auto;
}
.consent-card h2 { margin: 0 0 6px; font-size: 17px; }
.consent-options { margin: 14px 0 4px; border-top: 1px solid var(--color-border); }
.consent-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 12px 0; border-bottom: 1px solid var(--color-border);
}
.consent-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.consent-actions .btn { flex: 1; min-width: 120px; }
/* The signup checkbox has to stay tappable next to its links. */
.consent-check { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.45; }
.consent-check input { margin-top: 2px; flex-shrink: 0; }

.legal-page { max-width: 760px; }
.legal-page h2 { font-size: 17px; margin: 22px 0 8px; }
.legal-page ul { padding-left: 20px; }
.legal-page li { margin-bottom: 6px; }
.legal-notice {
  border-left: 3px solid var(--color-warning);
  background: var(--color-surface-2); border-radius: var(--radius-sm);
  padding: 12px 14px; margin: 14px 0 18px; font-size: 13.5px;
}
.legal-todo { color: var(--color-text-faint); font-size: 13.5px; }
@media (max-width: 560px) { .consent-actions .btn { flex-basis: 100%; } }

/* Logo standing alone above a heading (signup, provider onboarding), where the
   auth pages use .auth-brand instead. Centred with the same rhythm the emoji
   it replaced had. */
.auth-logo-inline { margin: 0 auto 10px; }

/* ==========================================================================
   Typography + selection behaviour
   ========================================================================== */

/* Form controls do not inherit the page font in any browser, and a few UA
   stylesheets reset them again per-element. Naming them all here means the
   font is set in exactly one place: --font-base. */
button, input, select, textarea, optgroup {
  font-family: var(--font-base);
}
::placeholder { font-family: var(--font-base); }

/* Chrome — the app's own furniture. Selecting a nav label or a button caption
   is never intentional; it just produces a blue smear on a long-press. Content
   is deliberately absent from this list (see below). */
.no-select,
.sidebar-brand,
.auth-brand,
.nav-link,
.bottom-nav,
.btn,
.tab-btn,
.tag,
.stepper,
.badge,
.drk,
.section-head h2,
.stat-card .stat-label,
.consent-actions,
.date-chip,
.slot-btn {
  -webkit-user-select: none;
  user-select: none;
}

/* The wordmark is a brand element, not text: no selection, no I-beam, and no
   drag-off as an image. The sidebar brand carries no click handler, so it also
   must not suggest one. */
.sidebar-brand,
.auth-brand,
.auth-brand strong {
  -webkit-user-select: none;
  user-select: none;
  cursor: default;
  -webkit-user-drag: none;
}

/* Deliberately still selectable, because people copy these: chat messages,
   "Chi siamo" and other long text, addresses, prices, review bodies, and every
   form value. Listed explicitly so a future blanket rule cannot silently take
   selection away from them. */
.chat-msg,
.long-text,
.review-item p,
.detail-row > div:last-child,
input, textarea, select {
  -webkit-user-select: text;
  user-select: text;
}

/* Empty hour rows are the second way to create an appointment. The hover tint
   is deliberately faint so it reads as "you may click here" without competing
   with the open/closed availability colouring underneath. */
.time-slot-line.is-clickable { cursor: pointer; }
.time-slot-line.is-clickable:hover { background: var(--color-primary-light); }

/* A provider-created appointment whose pet link failed: the appointment is
   real, the pet is not attached, and both facts need to be visible. */
.time-event .te-warn { display: inline-flex; align-items: center; margin-left: 4px; opacity: 0.85; }
.pet-link-warning {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  color: var(--color-danger); font-size: 13px;
}
.pet-link-warning .btn { margin-left: auto; }

/* ==========================================================================
   404
   ========================================================================== */
.not-found {
  max-width: 460px;
  margin: 0 auto;
  padding: 32px 20px calc(40px + var(--safe-bottom));
  text-align: center;
}
/* The trail walks off to the right and fades; currentColor ties it to the
   primary so it recolours with the theme rather than being a fixed grey. */
.not-found .nf-trail {
  display: block;
  width: 100%;
  max-width: 320px;
  margin: 0 auto 4px;
  color: var(--color-primary);
}
.not-found .nf-code {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.14em;
  color: var(--color-text-faint);
}
.not-found h1 {
  margin: 6px 0 10px;
  font-size: 24px;
  line-height: 1.25;
}
.not-found .nf-sub {
  margin: 0 auto 24px;
  max-width: 380px;
  color: var(--color-text-muted);
  font-size: 15px;
  line-height: 1.55;
}
.not-found .nf-actions { display: flex; flex-direction: column; align-items: center; gap: 16px; }
.not-found .nf-actions .btn { min-width: 220px; }
.nf-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 20px; }

@media (max-width: 560px) {
  .not-found { padding-top: 16px; }
  .not-found h1 { font-size: 21px; }
  .not-found .nf-actions .btn { width: 100%; min-width: 0; }
}

/* ==========================================================================
   Prenotazioni (owner)
   ========================================================================== */
.bk-tabs {
  display: flex; gap: 4px; padding: 4px;
  background: var(--color-surface-2); border-radius: var(--radius-pill);
  margin: 16px 0 20px; width: fit-content; max-width: 100%;
  position: relative;   /* the indicator is placed against this box */
}
.bk-tab {
  border: none; background: none; cursor: pointer;
  padding: 8px 18px; border-radius: var(--radius-pill);
  font-size: 13.5px; font-weight: 700; color: var(--color-text-muted);
  position: relative; z-index: 1;   /* the label stays above the pill */
  transition: color var(--duration-fast) var(--ease-out);
}
.bk-tab.active { color: var(--color-text); }
.bk-tab:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

/* The pill that slides between segments. Its size and offset are written by
   placeIndicator(); only `transform` is ever animated, so the segments can be
   different widths without a layout property taking part in the motion.
   --ease-in-out is the token for moving between two states, which is precisely
   what this is. */
.bk-tab-ind {
  position: absolute; left: 0; top: 4px; bottom: 4px; width: 0;
  z-index: 0; opacity: 0;
  border-radius: var(--radius-pill);
  background: var(--color-surface); box-shadow: var(--shadow-sm);
  transform-origin: left center;
  transition: transform var(--duration-medium) var(--ease-in-out);
}
.bk-tab-ind.is-ready { opacity: 1; }
/* Until the indicator has been measured and placed, the active segment paints
   its own pill — otherwise the selected tab would read as unselected for a
   frame, and would stay that way if scripting never got to it. */
.bk-tabs:not(:has(.bk-tab-ind.is-ready)) .bk-tab.active {
  background: var(--color-surface); box-shadow: var(--shadow-sm);
}

/* The incoming list arrives from the side of the tab that was tapped. Quick and
   small: enough to say which way we moved, never enough to wait for. */
@keyframes bk-list-enter {
  from { opacity: 0; transform: translateX(var(--bk-enter-x, 0)); }
  to   { opacity: 1; transform: none; }
}
#bookings-list.is-entering { animation: bk-list-enter var(--duration-medium) var(--ease-decel); }

/* The global reduced-motion rule already collapses both to 1ms; these drop the
   travel itself, so nothing slides for someone who asked it not to. */
@media (prefers-reduced-motion: reduce) {
  .bk-tab-ind { transition: none; }
  #bookings-list.is-entering { animation: none; }
}

/* The date is stated once per group. Sticky under the mobile topbar, which is
   itself sticky at --header-h; on desktop that bar is hidden so 0 is right. */
.bk-day { margin-bottom: 26px; }
.bk-day-head {
  position: sticky; top: 0; z-index: 5;
  margin: 0 0 10px; padding: 6px 0;
  font-size: 13px; font-weight: 800; letter-spacing: 0.01em;
  color: var(--color-text-muted);
  background: var(--color-bg);
}
.bk-day-items { display: flex; flex-direction: column; gap: 10px; }

/* Quiet surface: a hairline border and a whisper of shadow rather than rules
   between rows. */
.bk-card {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 68px 1fr auto; align-items: center; gap: 14px;
  padding: 16px 16px 16px 20px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.bk-card:hover { border-color: var(--color-border-strong); }
.bk-card:active { transform: scale(0.995); }
.bk-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
/* Service colour as a hairline down the edge — the only colour the card needs. */
.bk-accent { position: absolute; left: 0; top: 0; bottom: 0; width: 4px; }

.bk-time { display: flex; flex-direction: column; gap: 2px; }
.bk-hour { font-size: 20px; font-weight: 800; line-height: 1.1; letter-spacing: -0.01em; }
.bk-dur { font-size: 12px; color: var(--color-text-faint); font-weight: 600; }

.bk-main { min-width: 0; }
.bk-service {
  margin: 0 0 2px; font-size: 15px; font-weight: 700; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bk-provider {
  margin: 0; font-size: 13.5px; color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bk-pet {
  margin: 4px 0 0; font-size: 12.5px; color: var(--color-text-faint);
  display: flex; align-items: center; gap: 5px;
}
.bk-pet svg { flex-shrink: 0; }

.bk-side { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
.bk-side .avatar, .bk-side .avatar-fallback { width: 32px; height: 32px; }

/* History reads as history: the same card, dialled down. */
.bk-card.is-past { box-shadow: none; background: var(--color-surface); }
.bk-card.is-past .bk-hour { color: var(--color-text-muted); font-weight: 700; }
.bk-card.is-past .bk-service { font-weight: 700; color: var(--color-text-muted); }
/* .bk-side conteneva la pillola di stato, che a 0.72 scendeva a 4.08:1.
   L'avatar può sbiadire, la pillola no: è testo e porta significato. */
.bk-card.is-past .bk-side .avatar,
.bk-card.is-past .bk-side .avatar-fallback { opacity: 0.72; }
.bk-card.is-past .bk-accent { opacity: 0.4; }

/* Skeletons: same geometry as a real card, so nothing jumps when data lands. */
.bk-card.is-skeleton { cursor: default; box-shadow: none; }
.sk { display: block; border-radius: 6px; background: var(--color-surface-2); animation: sk-pulse 1.4s ease-in-out infinite; }
.sk-hour { width: 46px; height: 18px; }
.sk-dur { width: 32px; height: 11px; margin-top: 6px; }
.sk-line-lg { width: 60%; height: 14px; }
.sk-line-md { width: 42%; height: 12px; margin-top: 8px; }
.sk-line-sm { width: 28%; height: 11px; margin-top: 8px; }
.sk-avatar { width: 32px; height: 32px; border-radius: 50%; }
.sk-day-head { width: 96px; height: 12px; margin-bottom: 12px; }
@keyframes sk-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) { .sk { animation: none; } }

@media (max-width: 900px) {
  .bk-day-head { top: var(--header-h); }
}
@media (max-width: 560px) {
  .bk-tabs { width: 100%; justify-content: space-between; }
  .bk-tab { flex: 1; padding: 8px 10px; }
  .bk-card { grid-template-columns: 56px 1fr auto; gap: 12px; padding: 14px 12px 14px 16px; }
  .bk-hour { font-size: 18px; }
  /* Truncating the service name hides the most important line on the narrowest
     screen. Let it wrap to two lines instead, then clamp. */
  .bk-service {
    white-space: normal;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  }
  .bk-side .avatar, .bk-side .avatar-fallback { display: none; }
}

/* ==========================================================================
   Password show/hide
   ========================================================================== */
.pw-wrap { position: relative; display: block; }
/* Right padding keeps a long password from running under the icon. */
.pw-input { padding-right: 46px; }
.pw-toggle {
  position: absolute;
  /* Pinned to the input itself, not the field, so a validation message
     appearing beneath never drags the icon off centre. */
  top: 0;
  right: 0;
  height: 100%;
  width: 44px;              /* comfortable tap target on mobile */
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; cursor: pointer; padding: 0;
  color: var(--color-text-faint);
}
.pw-toggle:hover { color: var(--color-text-muted); }
.pw-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.pw-toggle svg { display: block; }

/* ==========================================================================
   Home (owner)
   ========================================================================== */
.home-greet {
  margin: 4px 0 14px;
  font-size: 17px; font-weight: 800; letter-spacing: -0.01em;
}
.home-cta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 48px; margin-bottom: 26px;
}
.home-section { margin-bottom: 26px; }
.home-h2 { margin: 0 0 10px; font-size: 13px; font-weight: 800; color: var(--color-text-muted); }
.home-section .section-head { margin-bottom: 10px; }

/* Appointment card. The time is the loudest thing on it; the chips are the
   quietest — the inverse of the previous layout. */
.ap-card {
  display: grid; grid-template-columns: 84px 1fr auto; align-items: center; gap: 14px;
  padding: 16px; cursor: pointer;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.ap-card:hover { border-color: var(--color-border-strong); }
.ap-card:active { transform: scale(0.995); }
.ap-card:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.ap-when { display: flex; flex-direction: column; gap: 1px; }
.ap-rel { font-size: 11.5px; font-weight: 800; letter-spacing: 0.04em; color: var(--color-text-muted); }
.ap-time { font-size: 26px; font-weight: 800; line-height: 1.05; letter-spacing: -0.02em; }
.ap-date { font-size: 11.5px; color: var(--color-text-faint); }

.ap-body { min-width: 0; }
.ap-service { margin: 0 0 2px; font-size: 16px; font-weight: 700; line-height: 1.25; }
.ap-who {
  margin: 0 0 8px; font-size: 13px; color: var(--color-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Chips on one quiet line rather than two stacked blocks. */
.ap-badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ap-badges .pill, .ap-badges .service-pill { font-size: 11px; padding: 3px 9px; }

.ap-msg {
  display: flex; align-items: center; gap: 6px;
  min-width: 44px; min-height: 44px; justify-content: center;
}

/* Waiting on the provider: the one thing on this page the user cannot resolve
   themselves, so it gets a visible marker rather than only a pill. */
.ap-card.is-pending { border-left: 3px solid var(--color-warning); }

.ap-card.is-compact { grid-template-columns: 76px 1fr; padding: 13px 14px; box-shadow: none; }
.ap-card.is-compact .ap-time { font-size: 21px; }
.ap-card.is-compact .ap-service { font-size: 14.5px; }
.ap-card.is-skeleton { cursor: default; box-shadow: none; }

/* Pets: a horizontal row that scrolls rather than wrapping into a grid. */
.pet-row {
  display: flex; gap: 14px; overflow-x: auto; padding: 2px 2px 6px;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
}
.pet-row::-webkit-scrollbar { display: none; }
.pet-chip {
  flex: 0 0 auto; scroll-snap-align: start;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  /* Wide enough for the larger avatar plus a name that is not immediately
     clipped; the row scrolls rather than shrinking the chips. */
  width: 80px; padding: 0; border: none; background: none; cursor: pointer;
}
.pet-chip-name {
  font-size: 12.5px; font-weight: 700; color: var(--color-text);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pet-chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; border-radius: var(--radius-md); }

@media (max-width: 560px) {
  .ap-card { grid-template-columns: 72px 1fr auto; gap: 10px; padding: 14px 12px; }
  .ap-time { font-size: 23px; }
  .ap-msg span { display: none; }
}

/* Privacy & data rights (profilo) */
.privacy-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 0; border-top: 1px solid var(--color-border);
}
.privacy-row:first-of-type { margin-top: 8px; }
.privacy-row > div { min-width: 0; }
.privacy-row .btn { flex-shrink: 0; }
/* Deletion is separated and marked, so it can never be hit by mistake. */
.privacy-row.is-danger { border-top: 1px solid var(--color-border); margin-top: 4px; }
@media (max-width: 560px) {
  .privacy-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .privacy-row .btn { width: 100%; }
}

/* ==========================================================================
   Salute (scheda animale)
   ========================================================================== */
.hr-h3 { margin: 20px 0 8px; font-size: 12.5px; font-weight: 800; color: var(--color-text-muted); }
.hr-h3:first-of-type { margin-top: 12px; }

.hr-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-bottom: 1px solid var(--color-border);
}
.hr-row:last-child { border-bottom: none; }
.hr-main { flex: 1; min-width: 0; }
.hr-label { font-weight: 700; font-size: 14px; }
.hr-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 12.5px; color: var(--color-text-faint); margin-top: 3px; }
.hr-note { font-size: 12.5px; color: var(--color-text-muted); margin-top: 3px; line-height: 1.45; }
.hr-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }

/* Urgency is carried by the badge text; colour only reinforces it. */
.hr-badge { font-weight: 700; }
.hr-row.is-overdue .hr-badge { color: var(--color-accent-strong); }
.hr-row.is-soon .hr-badge { color: var(--color-warning); }
.hr-row.is-scheduled .hr-badge { color: var(--color-text-muted); }
.hr-row.is-overdue { border-left: 3px solid var(--color-accent); padding-left: 10px; margin-left: -10px; }

.hr-suggestions {
  background: var(--color-surface-2); border-radius: var(--radius-md);
  padding: 12px 14px; margin-bottom: 8px;
}
.hr-suggestions .hr-row { border-bottom-color: var(--color-border); }
.hr-disclaimer {
  margin: 0 0 8px; font-size: 12px; line-height: 1.45; color: var(--color-text-muted);
}
.hr-hint {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 12.5px; color: var(--color-text-muted); margin: 0 0 12px;
}
.hr-hint svg { flex-shrink: 0; margin-top: 1px; }

.hr-event { display: flex; align-items: flex-start; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--color-border); }
.hr-event:last-child { border-bottom: none; }
.hr-event-date { font-size: 12px; font-weight: 700; color: var(--color-text-faint); width: 96px; flex-shrink: 0; }

/* The onboarding step it sits on is centred; the rows inside are not. */
.hr-card-left, .hr-card-left * { text-align: left; }

@media (max-width: 560px) {
  .hr-row { flex-wrap: wrap; }
  .hr-actions { width: 100%; justify-content: flex-end; }
  .hr-event-date { width: 100%; }
  .hr-event { flex-wrap: wrap; }
}

/* ==========================================================================
   Micro-interactions
   Press feedback that makes a control feel physical. Transform and opacity
   only — nothing here triggers layout — and every duration comes from the
   motion tokens so the whole app can be retuned in one place.
   ========================================================================== */

/* Buttons sink slightly and lose their lift. `transform` is already in the
   .btn transition, so this only needs the pressed end-state. */
.btn:active:not(:disabled) {
  transform: scale(var(--press-scale));
  box-shadow: none;
}
.btn-primary:active:not(:disabled),
.btn-cta:active:not(:disabled) {
  box-shadow: 0 1px 3px rgba(31, 29, 26, 0.16);
}

/* Cards that do something when tapped. Deliberately weaker than a button:
   a card is a surface, not a control, and should not feel clicky. */
.card-tappable,
.pet-card, .species-card, .bk-card, .service-row, .provider-card,
.pet-mini-row, .thread-row, .reminder-item, .hr-row, .date-chip, .slot-btn {
  transition: transform var(--duration-instant) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.card-tappable:active,
.pet-card:active, .species-card:active, .bk-card:not(.is-skeleton):active,
.service-row:active, .provider-card:active, .pet-mini-row:active,
.thread-row:active, .reminder-item:active, .date-chip:active, .slot-btn:active {
  transform: scale(0.988);
}

/* Tabs and chips move between states rather than snapping. */
.tab-btn, .chip, .filter-chip, .temper-opt, .seg-btn {
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.tab-btn:active, .chip:active, .filter-chip:active, .temper-opt:active { transform: scale(0.97); }

/* Inputs: the ring grows from the border rather than appearing on top of it. */
.input, .textarea, .select {
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}
.input:focus, .textarea:focus, .select:focus,
.input:focus-visible, .textarea:focus-visible, .select:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}

/* ==========================================================================
   Reduced motion
   A single global stop, on top of the specific rules already in this file.
   Not "nice to have": vestibular disorders make large motion genuinely
   unpleasant, and the OS switch is the user telling us so.

   Durations collapse to 1ms rather than 0 so transitionend/animationend still
   fire — several places rely on those events to clean up, and killing them
   outright would leave elements stuck mid-state.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* Press feedback is a position change, not travel — but with no transition
     it reads as a glitch, so it is dropped entirely. */
  .btn:active:not(:disabled),
  .card-tappable:active, .pet-card:active, .species-card:active,
  .bk-card:not(.is-skeleton):active, .service-row:active, .provider-card:active,
  .pet-mini-row:active, .thread-row:active, .reminder-item:active,
  .date-chip:active, .slot-btn:active,
  .tab-btn:active, .chip:active, .filter-chip:active, .temper-opt:active {
    transform: none;
  }
}

/* ==========================================================================
   Schermata di caricamento con consigli
   Invisibile finché non passa la soglia: `is-visible` arriva da petLoader.js
   dopo ~350ms, così i caricamenti veloci non fanno lampeggiare nulla.
   ========================================================================== */
.pet-loader {
  min-height: 60vh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; padding: 32px 24px; text-align: center;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.pet-loader.is-visible { opacity: 1; }

.pl-trail { display: flex; align-items: center; gap: 10px; }
.pl-paw {
  display: block; color: var(--color-primary); opacity: 0.22;
  transform: rotate(-18deg);
  animation: pl-step 1.5s var(--ease-in-out) infinite;
  animation-delay: calc(var(--i) * 0.18s);
}
/* Le impronte si alternano su due file, come passi veri. */
.pl-paw:nth-child(even) { transform: rotate(-18deg) translateY(7px); }

@keyframes pl-step {
  0%, 70%, 100% { opacity: 0.22; }
  22%           { opacity: 1; }
}

.pl-label { margin: 0; font-weight: 800; font-size: 15px; color: var(--color-text); }
.pl-tip {
  margin: 0; max-width: 34ch; font-size: 13.5px; line-height: 1.5;
  color: var(--color-text-muted); min-height: 3em;
  opacity: 0;
  transition: opacity var(--duration-slow) var(--ease-out);
}
.pl-tip.is-in { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  /* Le impronte restano, ferme e tutte visibili: l'informazione ("sto
     caricando") non dipende dal movimento. */
  .pl-paw { animation: none; opacity: 0.5; }
}

/* ==========================================================================
   Momenti di conferma
   Una prenotazione inviata e una recensione pubblicata sono momenti di
   fiducia, non feste: il segno di spunta si disegna una volta e si ferma.
   Solo transform, opacity e stroke-dashoffset — nessun reflow.
   ========================================================================== */
.success-check { margin: 0 auto 18px; display: block; }
.success-check .sc-ring {
  stroke: var(--color-success);
  opacity: 0.28;
  transform-origin: center;
  animation: sc-ring-in var(--duration-deliberate) var(--ease-decel) both;
}
.success-check .sc-tick {
  stroke: var(--color-success);
  stroke-dasharray: 44;
  stroke-dashoffset: 44;
  animation: sc-tick-draw var(--duration-deliberate) var(--ease-decel) 120ms forwards;
}
@keyframes sc-ring-in {
  from { transform: scale(0.82); opacity: 0; }
  to   { transform: scale(1);    opacity: 0.28; }
}
@keyframes sc-tick-draw { to { stroke-dashoffset: 0; } }

/* The card itself rises gently under the mark. */
.confirm-check { animation: confirm-rise var(--duration-slow) var(--ease-decel) both; }
@keyframes confirm-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  /* The check still needs to be a check — it is drawn instantly, not animated. */
  .success-check .sc-ring { animation: none; opacity: 0.28; }
  .success-check .sc-tick { animation: none; stroke-dashoffset: 0; }
  .confirm-check { animation: none; }
}

/* ==========================================================================
   Pull-to-refresh
   L'indicatore vive fuori dal flusso e si muove solo con transform, quindi
   tirare non provoca un reflow della pagina sotto.
   ========================================================================== */
.ptr {
  position: fixed;
  top: calc(var(--safe-top, 0px) + 6px);
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  box-shadow: var(--shadow-md);
  color: var(--color-text-muted);
  opacity: 0;
  z-index: 35;                 /* sotto i modali (40+), sopra il contenuto */
  pointer-events: none;        /* non deve mai rubare un tap */
}
/* La transizione esiste solo al rilascio: durante il trascinamento il
   movimento deve seguire il dito senza ritardo. */
.ptr.is-animating {
  transition: transform var(--duration-medium) var(--ease-out),
              opacity var(--duration-medium) var(--ease-out);
}
.ptr.is-ready { color: var(--color-accent-strong); border-color: var(--color-accent); }
.ptr-paw { display: flex; transition: transform var(--duration-instant) var(--ease-out); }
.ptr.is-busy .ptr-paw { animation: ptr-spin 900ms linear infinite; }
@keyframes ptr-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  /* Il gesto resta identico: cambia solo che la zampa non gira. La soglia è
     comunicata dal colore, non dal movimento. */
  .ptr-paw { transition: none; }
  .ptr.is-busy .ptr-paw { animation: none; }
}

/* ==========================================================================
   Illustrazioni
   Tratto navy, un solo accento corallo per disegno. Nessun colore è scritto
   nell'SVG: arrivano tutti da qui, quindi seguono la palette da soli.
   ========================================================================== */
.il {
  display: block;
  margin: 0 auto;
  color: var(--color-primary);
  opacity: 0.9;
  /* Non deve mai spingere l'azione fuori dallo schermo: su telefoni bassi
     l'illustrazione cede spazio prima del pulsante. */
  max-width: 100%;
  height: auto;
}
.il .il-accent, .nf-trail .il-accent { color: var(--color-accent); }
.il .il-accent { stroke: currentColor; }
.nf-trail .il-accent { fill: currentColor; }

/* Sostituisce l'emoji nelle schermate vuote. */
.empty-state .il { margin-bottom: 14px; }
.empty-state-wrap { background: var(--color-surface); border-radius: var(--radius-md); }

@media (max-width: 560px) {
  .empty-state { padding: 34px 18px; }
  .empty-state .il { width: 96px; }
}
/* Telefoni bassi (o in orizzontale): il disegno si riduce ancora perché
   quello che conta è che il pulsante resti visibile senza scorrere. */
@media (max-height: 620px) {
  .empty-state { padding: 24px 18px; }
  .empty-state .il { width: 76px; margin-bottom: 8px; }
}

/* ==========================================================================
   Trascinamento sulla griglia
   Il movimento è solo transform su un elemento: la griglia sotto non si
   ridisegna mentre il dito è giù.
   ========================================================================== */

/* Il blocco trascinato si stacca dagli altri e li sorvola. */
.time-event.is-dragging {
  z-index: 20;
  box-shadow: var(--shadow-lg);
  opacity: 0.95;
  cursor: grabbing;
  transition: none;               /* segue il dito senza ritardo */
  /* SERVE ALLO SPOSTAMENTO, non è un resto del ridimensionamento: .time-event
     ritaglia il proprio contenuto per via degli angoli arrotondati, ma
     l'etichetta con l'orario di arrivo è figlia del blocco e sta a top:-10px,
     quindi veniva tranciata a metà proprio mentre serviva leggerla. Le righe di
     testo hanno già il loro overflow, qui non sborda nient'altro. */
  overflow: visible;
}

/* La griglia non è testo da copiare: sono etichette dentro bersagli che si
   trascinano. Vale SEMPRE, non solo a gesto armato — il tocco lungo che arma lo
   spostamento è esattamente il gesto con cui iOS inizia a selezionare, e su
   desktop il trascinamento evidenziava le ore. Niente menu contestuale del
   tocco lungo per lo stesso motivo. Distinto da touch-action, che governa lo
   scorrimento e non la selezione. */
.time-grid-wrap,
.time-grid-wrap *,
.time-gutter,
.time-gutter * {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.is-drag-active .time-slot-hit { pointer-events: none; }

/* Etichetta che segue il blocco: l'orario di arrivo è coperto dal blocco
   stesso, quindi va detto qui. */
.cal-drag-badge {
  position: absolute; top: -10px; left: 50%; transform: translateX(-50%);
  background: var(--color-text); color: var(--color-text-on-dark);
  font-size: 10px; font-weight: 800; letter-spacing: 0.01em;
  padding: 2px 7px; border-radius: var(--radius-pill);
  white-space: nowrap; pointer-events: none; z-index: 21;
  box-shadow: var(--shadow-sm);
}

@media (prefers-reduced-motion: reduce) {
  /* Il trascinamento resta: è un'interazione diretta, non una decorazione.
     Sparisce solo l'ombra che "solleva" il blocco. */
  .time-event.is-dragging { box-shadow: var(--shadow-sm); }
}
