/* FILE GENERATO: non modificarlo. Le sorgenti sono in css/src/*.css;
   dopo una modifica eseguire `php tools/minify_css.php`. */
/* ==============================================================================
   InvoicePlane CI4 — custom.css (file unico)
   -----------------------------------------------------------------------------
   Unificazione di modern-theme.css + custom.css + saas.css in un solo file,
   nello stesso ordine di cascata di prima (i tre erano già pensati come layer
   successivi sullo stesso :root). Nessuna regola è stata cambiata nel merge.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   I caratteri del design system, serviti da qui
   -----------------------------------------------------------------------------
   Prima arrivavano da fonts.googleapis.com, con i file su un secondo dominio
   (fonts.gstatic.com): due connessioni esterne nel percorso critico di ogni
   pagina, un foglio CSS bloccante che nessuna cache dell'app poteva coprire, e
   due origini da dichiarare nella CSP. Questi sono gli stessi file "latin" che
   il browser scaricava da li', ora sotto assets/core/fonts: il service worker
   li mette in cache insieme agli altri asset, e il ?v= lo aggiunge CssBundle
   con l'impronta del file (sostituire un font non lascia in giro la copia
   vecchia, che l'Expires di un anno terrebbe per mesi).

   Il sottoinsieme e' quello latino di Google — U+0000-00FF piu' punteggiatura
   tipografica ed euro, cioe' tutto l'italiano e l'inglese: esattamente cio' che
   il browser scaricava prima. Un carattere fuori da li' (greco, cirillico,
   latino esteso: una ragione sociale polacca, per esempio) cade sul fallback di
   sistema dichiarato nei token.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/figtree-latin.woff2?v=392aed3e") format("woff2");
}

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 500 700;
  font-display: swap;
  src: url("../fonts/fraunces-latin.woff2?v=5d283517") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-500-latin.woff2?v=094fe534") format("woff2");
}

@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/ibm-plex-mono-600-latin.woff2?v=a6b3444c") format("woff2");
}

/* -----------------------------------------------------------------------------
   Base del documento (ex layer "modern-theme")
   -----------------------------------------------------------------------------
   Questo tratto teneva un secondo set di token --bg-main, --primary,
   --card-shadow, --transition... che rimappava sui --ip-*: 25 alias per 32 usi,
   quattro dei quali (--secondary, --success, --success-bg, --success-text)
   senza alcun consumatore. Gli usi sono stati portati sui token --ip-* e il
   layer rimosso: ora il foglio ha una sola famiglia di token, e chi legge non
   deve più capire quale delle due vince.
   .modern-card è stato rimosso con lo stesso criterio: nessuna view lo emette
   (0 occorrenze, contro 412 di .card). Le classi .card-label/.card-value/
   .card-subtext restano perché sono usate dalle KPI card.
   -------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   Scale del sistema (uguali nei due temi)
   -----------------------------------------------------------------------------
   Tipografia, spaziatura, elevazioni e altezze dei controlli. Prima esistevano
   solo i token di colore: le misure erano valori sciolti (35 dimensioni di
   testo, 140 padding, 17 z-index), quindi due componenti simili finivano per
   differire di mezzo pixel senza che nessuno lo decidesse. Questi gradini sono
   la scala su cui vanno mappate le eccezioni.
   -------------------------------------------------------------------------- */
:root {
  /* Tipografia — 11 gradini da 10 a 32px, il corpo è --ip-text-md (14px).
     I gradini bassi sono fitti (1px) perché l'interfaccia è densa; sopra i 16px
     si allargano. Ogni titolo e ogni cifra grande deve cadere su uno di questi,
     che sia un <h3> o un KPI: era il motivo per cui due card simili finivano a
     15.5 e 16px senza che nessuno lo avesse deciso. */
  --ip-text-2xs:  10px;   /* badge, timbri, note minute */
  --ip-text-xs:   11px;   /* etichette compatte */
  --ip-text-sm:   12px;   /* metadati, intestazioni di tabella */
  --ip-text-base: 13px;   /* testo secondario, celle, h6 */
  --ip-text-md:   14px;   /* corpo, h5 */
  --ip-text-lg:   16px;   /* sottotitoli, h4 */
  --ip-text-xl:   18px;   /* titoli di sezione minori */
  --ip-text-2xl:  20px;   /* h3, titolo di modale */
  --ip-text-3xl:  22px;   /* h2, titoli di card */
  --ip-text-4xl:  28px;   /* h1, titolo di pagina */
  --ip-text-5xl:  32px;   /* cifre KPI (.card-value, .count) */

  /* Spaziatura — passo 2px, fino a 24. Usata da gap/padding/margin. */
  --ip-space-1: 2px;
  --ip-space-2: 4px;
  --ip-space-3: 6px;
  --ip-space-4: 8px;
  --ip-space-5: 10px;
  --ip-space-6: 12px;
  --ip-space-7: 16px;
  --ip-space-8: 20px;
  --ip-space-9: 24px;

  /* Strati di sovrapposizione: l'ordine è quello di Bootstrap, scritto una
     volta. I valori locali (0/1/2/3 e 1015/1024/1025) restano sciolti perché
     sono contestuali, non strati globali. */
  --ip-z-base: 1;
  --ip-z-raised: 5;
  --ip-z-sticky: 50;
  --ip-z-header: 1020;
  --ip-z-dropdown: 1030;
  --ip-z-popover: 1035;
  --ip-z-overlay: 1050;
  --ip-z-modal: 1060;
  --ip-z-toast: 1080;
  --ip-z-sheet: 1085;
  --ip-z-top: 2000;

  /* Altezze dei controlli: due taglie, non sette. */
  --ip-control-h: 36px;
  --ip-control-h-sm: 30px;
}
html {
    font-size: var(--ip-text-lg);
}
body {
    font-family: var(--ip-font);
    background-color: var(--ip-bg);
    color: var(--ip-text);
    font-size: var(--ip-text-md);
}
h1, h2, h3, h4, h5, h6 {
    font-weight: 600;
    color: var(--ip-text);
    font-family: var(--ip-font);
}
h1 {
    font-size: var(--ip-text-4xl);
    margin-bottom: 1.5rem;
    letter-spacing: -0.02em;
    /* Fraunces (--ip-display) per i titoli di pagina: il contrasto serif/sans
       con Figtree da' gerarchia immediata senza usare il colore, che resta
       riservato alla semantica di stato. */
    font-family: var(--ip-display);
}
/* I livelli sono i gradini della scala (28/22/20/18/16/13), non rem sciolti:
   così un cambio di scala si propaga ai titoli invece di lasciarli indietro. */
h2 { font-size: var(--ip-text-3xl); }
h3 { font-size: var(--ip-text-2xl); }
h4 { font-size: var(--ip-text-xl); }
h5 { font-size: var(--ip-text-lg); }
h6 { font-size: var(--ip-text-base); }
/* I titoli dei modali sono <h5>: senza questa riga scenderebbero da 20 a 16px,
   perché la scala sopra uniforma i livelli e il modale non è un sottotitolo. */
.modal-title { font-size: var(--ip-text-2xl); }
/* Etichetta e valore delle KPI card (dashboard, scheda cliente, ospite). */
.card-label {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ip-text-muted);
    font-weight: 600;
}
.card-value {
    font-size: var(--ip-text-5xl);
    font-weight: 700;
    margin: 0.5rem 0;
    color: var(--ip-primary);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}
.card-value.card-value-danger {
    color: var(--ip-danger-text);
}
.card-value.card-value-warning {
    color: var(--ip-warning-text);
}
/* Stat card del guest dashboard — coerente con .card-value del KPI. */
.count {
    font-size: var(--ip-text-5xl);
    font-weight: 700;
    margin: 0.5rem 0;
    color: var(--ip-primary);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
}
.card-subtext {
    color: var(--ip-text-muted);
    font-size: 0.8125rem;
}
/* KPI cards dashboard — 6 card, quindi le colonne devono essere un divisore di
   6 (3, 2, 1): con auto-fit si ottenevano righe da 5 + 1 orfana. E ogni card e'
   una riga flex [icona | contenuto | trend], che sotto ~200px di contenuto
   tronca l'importo con l'ellissi (il tasso di incasso diventava "1..."):
   percio' niente 4/5/6 colonne, si va da 3 a 2 a 1. */
/* Uniform Form Styling */
.card-header,
.card-title {
    font-size: var(--ip-text-md);
    font-family: var(--ip-font);
}
/* Le etichette e i campi hanno dimensioni differenziate più sotto
   (.form-group > label 13px, .ip-doc-label / .form-badge 12.5px, ricerca
   live 13.5px): prima questo blocco usava !important anche su label e
   .form-control, e ogni raffinamento successivo era silenziosamente
   annullato. La misura base resta 14px ma senza !important. .btn è
   escluso: ha dimensioni differenziate (.btn-sm 12px, .btn 14px). */
label,
.control-label,
.form-label {
    font-size: var(--ip-text-md);
    font-family: var(--ip-font);
}
/* Color Corrections */
.form-control,
.control-label,
.form-label,
label,
.card > .card-header {
    color: var(--ip-text);
}
a {
    color: var(--ip-primary);
    transition: var(--ip-transition);
}
a:hover {
    color: var(--ip-primary-hover);
    text-decoration: none;
}
/* ---- ex custom.css ---- */
/* =============================================================================
   InvoicePlane CI4 — Custom UI/UX overrides
   -----------------------------------------------------------------------------
   Layer finale di raffinamento interposto tra style.css (Bootstrap 3 + tema IP)
   e modern-theme.css (look moderno). Aggiunge coerenza visiva, micro-stati,
   e un punto di vista più calmo/professionale per un'app di fatturazione.
   ========================================================================== */

/* I font (Figtree / Fraunces / IBM Plex Mono) arrivano da <link> nel <head>,
   partial layout/fonts.php. Qui c'era un @import: dopo la fusione dei tre
   fogli non era più in testa al file, quindi il browser lo ignorava e la
   tipografia dell'app cadeva tutta sui fallback di sistema. */

/* -----------------------------------------------------------------------------
   1. Design tokens — "Casa verde postale"
   -----------------------------------------------------------------------------
   La cartoleria finanziaria: carta calda ma non crema, inchiostro verde-nero,
   verde postale come colore del denaro (primary/paid), vermiglio come colore
   del timbro (scaduto/attenzione), ambra per le situazioni intermedie.
   Mappa i token BS5 ai nostri per allineare .text-bg-*, .link-*, .alert-* .
   -------------------------------------------------------------------------- */
:root {
  /* Palette IP — neutra, professionale (grigio/bianco + accento slate scuro;
     colore riservato solo alla semantica di stato: pagato/scaduto/attenzione) */
  --ip-bg:            #f6f6f7;     /* canvas neutro */
  --ip-bg-card:       #ffffff;
  --ip-border:        #e4e4e7;
  --ip-border-soft:   #ececef;
  --ip-border-strong: #d4d4d8;      /* bordo .btn-secondary (leggibile come pulsante) */
  --ip-border-strong-hover: #a1a1aa;/* hover/active dello stesso bordo */
  /* Hover dei campi: deve essere piu' scuro del bordo a riposo, non piu'
     chiaro. Quando --ip-border-field e' salito a 3:1 questo token (#cbd5e1,
     1.5:1) e' finito sotto il valore di riposo e l'hover schiariva il bordo. */
  --ip-border-hover:  #6b7280;      /* bordo campi su hover (input, IPSelect) */
  --ip-text:          #18181b;     /* quasi nero neutro */
  --ip-text-strong:   #18181b;     /* testo "inchiostro" per cifre evidenziate */
  /* Scurito il 2026-10-24: #71717a stava a 4.47:1 sul canvas (sotto 4.5 per il
     testo da 12-14px, e il canvas è il fondo più frequente). Ora 4.89:1 sul
     canvas e 5.28:1 sulla card. */
  --ip-text-muted:    #6b6b73;
  --ip-primary:       #18181b;     /* slate/nero — accento primario neutro */
  --ip-primary-hover: #27272a;
  --ip-primary-dark:  #09090b;     /* (active) */
  /* Tinte derivate invece che scritte in rgba(): un rgba() con i numeri del
     tema chiaro resta identico quando il tema si ribalta, quindi la tinta
     "soft" di un accento chiaro diventa un velo scuro su fondo scuro. Con
     color-mix la tinta segue il token e i due temi non possono divergere. */
  --ip-primary-soft:  color-mix(in srgb, var(--ip-primary) 6%, transparent);
  --ip-on-primary:    #ffffff;     /* testo su sfondo --ip-primary (chiaro: primary scuro) */
  /* Testo sui chip pieni neutri (--ip-neutral-solid). In chiaro il grigio
     pieno è scuro e vuole bianco; in tema scuro lo stesso token diventa
     chiaro e vuole il canvas. Tenere #fff nei due temi faceva 2.60:1 sui
     conteggi. Un solo token, ribaltato insieme al fondo. */
  --ip-on-neutral:    #ffffff;
  /* Tinte di servizio che erano riferite senza essere definite. */
  --ip-bg-subtle:     #f2f4f6;   /* riga in trascinamento */
  --ip-warning-border:#f0ad4e;   /* barra del feed calendario senza token */
  --ip-shadow-focus-danger: 0 0 0 3px color-mix(in srgb, var(--ip-danger) 15%, transparent);
  --ip-level:         0;         /* rientro dei sottoconti, sovrascritto inline */
  --ip-bulk-bar-h:    0px;       /* altezza della barra azioni, misurata da JS */

  /* Skeleton di caricamento: base e riflesso dello shimmer. La base e'
     volutamente più scura di un bordo leggero (1.55:1 sulla card bianca,
     1.44:1 sul canvas) così i placeholder restano percepibili anche a bassa
     acuità; le varianti prefers-contrast e forced-colors stanno in fondo al
     file. */
  --ip-skeleton-bg:   #cfcfd6;
  --ip-skeleton-shine:#e9e9ec;

/* Colori di stato ripresi dal tema originale InvoicePlane
      (html/assets/invoiceplane/sass/_ip_variables.scss): sono la parte piu'
      riconoscibile dell'identita' dell'app, perché i badge di stato compaiono
      in ogni lista di fatture e preventivi. Il resto della cromia resta neutro. */
  --ip-success:       #468847;     /* $color_green — paid / approved */
  --ip-success-bg:    #eef5ee;
  --ip-warning:       #F89406;     /* $color_yellow — viewed */
  --ip-warning-bg:    #fdf4e6;
  --ip-danger:        #B94A48;     /* $color_red — overdue / rejected / delete */
  --ip-danger-bg:     #f9eded;
  --ip-danger-soft:   #fee2e2;      /* bg hover delle righe con stato pericolo */
  --ip-info:          #3A87AD;     /* $color_blue — sent */
  --ip-info-bg:       #edf3f7;
  --ip-violet:        #6d5dd3;     /* costi / money-out: inchiostro distinto da rosso e blu */
  --ip-violet-bg:     #f1eefe;

  --ip-neutral:       #999999;     /* $color_grey — draft / stato neutro */
  --ip-neutral-solid: #6E6E6E;     /* grigio pieno: serve più scuro per reggere testo bianco */
  --ip-canceled:      #333333;     /* $color_status_canceled */

  /* Varianti "testo su fondo chiaro" delle tinte di stato. I badge sono
      outline (bordo + testo colorati su bianco) e a 10px il testo deve stare
      sopra 4.5:1: l'arancione originale #F89406 su bianco da' solo 2.28, il blu
      4.00. Il bordo mantiene la tinta originale, così il chip resta
      riconoscibile; il testo usa questa versione scurita. */
  /* Scurite il 2026-10-24: sul proprio fondo tinto i valori precedenti
     stavano a 4.14 / 4.16 / 4.44 / 4.02, sotto 4.5 per il testo da 11px dei
     badge --soft. Ora 4.67 / 4.97 / 5.02 / 4.80 sul fondo e 5.18 / 5.42 /
     5.74 / 5.38 sul bianco: stesso tono, un gradino più profondo. */
  /* Regola d'uso: i colori di stato pieni (--ip-success, --ip-warning…) sono
     per fili, icone, fondi e barre; il testo usa sempre la variante -text.
     L'ambra piena su bianco o crema sta intorno a 2:1 e il verde/blu pieni
     poco sotto 4,5:1: avvisi, KPI e riepiloghi grafici che li usavano come
     colore del testo erano i punti meno leggibili del tema chiaro. */
  --ip-success-text:  #3d7a3e;
  --ip-warning-text:  #9a5b03;
  --ip-danger-text:   #ad4341;
  --ip-info-text:     #2e7194;
  --ip-hover:         #f4f4f5;     /* bg hover bottoni neutri/righe */
  --ip-success-hover: #2f6330;     /* verde scuro per hover .btn-success */
  --ip-danger-hover:  #9d3d3b;     /* rosso scuro per hover .btn-danger  */

  /* Bordo dedicato ai campi input: 3:1 su card per WCAG 1.4.11.
     --ip-border resta per separatori decorativi (tabelle, card).
     Il valore precedente (#cbd5e1) dichiarava 3.2:1 ma ne misurava 1.48 su
     card bianca: la soglia va verificata su ENTRAMBI i fondi su cui il campo
     puo' stare, card (#fff) e canvas (#f6f6f7), ed e' il secondo il caso
     peggiore. */
  --ip-border-field:  #82899a;     /* 3.5:1 su card, 3.24:1 su canvas (era #888f9c: 3.01:1 sul canvas, a un soffio dalla soglia 3:1) */

  /* Superfici piene dei pulsanti. Le tinte di stato (--ip-success, --ip-info)
     sono nate per badge e testo, dove il fondo e' chiaro: usate come fondo di
     un pulsante con testo bianco restano sotto 4.5:1 (4.31 e 4.00). I pulsanti
     hanno quindi una versione piu' profonda, mentre badge e stati continuano a
     usare la tinta originale — che e' la parte riconoscibile dell'identita'. */
  --ip-success-solid: #3d7a3e;     /* 5.18:1 con testo bianco */
  --ip-info-solid:    #31708f;     /* 5.46:1 con testo bianco */

  /* Inchiostro per i fondi chiari e saturi (ambra): il bianco su #F89406 da'
     2.28:1. In tema scuro diventa il canvas scuro, vedi blocco dark. */
  --ip-on-bright:     #18181b;

  /* Altezza navbar: fallback statico usato solo prima che lo script inline in
     layout/base.php misuri l'altezza reale (font Figtree, avatar, eventuale
     wrap su viewport strette) e sovrascriva questa variabile via JS. Vedi
     sezione 2 qui sotto. */
  --ip-navbar-height: 56px;
  /* Altezza headerbar: fallback statico, come per la navbar, per ancorare
     l'eventuale thead sticky (vedi .table--sticky-head) subito sotto la
     headerbar stessa invece che sotto la navbar. Sovrascritta via JS in
     layout/base.php con l'altezza misurata. */
  --ip-headerbar-height: 56px;

  /* Barra superiore scura, ripresa da $navbar-inverse-bg del tema originale
     (#333c4a): e' la firma visiva più riconoscibile di InvoicePlane. */
  --ip-navbar-bg:     #333c4a;
  --ip-navbar-border: #29313c;
  --ip-navbar-text:   rgba(255, 255, 255, 0.78);
  --ip-navbar-hover:  rgba(255, 255, 255, 0.10);

  --ip-radius:        6px;
  --ip-radius-sm:     4px;
  /* Raggio più morbido per superfici "fluttuanti" grandi (modali, popover,
     toast): stacca dal raggio di default dei campi/card senza introdurre
     una nuova famiglia di forme. */
  --ip-radius-lg:     10px;
  /* Geometria delle superfici a card — una misura sola per intestazione,
     corpo e piè di card. Prima erano tre ritmi: il footer stava a 16px di
     rientro mentre header e body stavano a 18 (il testo del piè di card non
     cadeva sotto quello del corpo), e le sezioni documento rispondevano a
     13/16 e 18/16 mentre le card degli altri form a 14/18 e 16/18 — due
     famiglie per lo stesso ruolo, con l'intestazione alta 49 o 59px secondo
     la pagina. --ip-card-head-y e' l'unico valore che imposta l'altezza
     dell'intestazione: il contenuto (icona 30px o pallino 22px) ci sta
     dentro con lo stesso respiro. */
  --ip-card-pad-x:   18px;
  --ip-card-head-y:  14px;
  --ip-card-body-y:  16px;
  --ip-card-foot-y:  10px;
  --ip-sidebar-width: 236px;
  --ip-shadow-sm:     0 1px 2px 0 rgba(24, 24, 27, 0.05);
  --ip-shadow-focus:  0 0 0 3px rgba(24, 24, 27, 0.14);
  /* Scala di elevazione: --ip-shadow-md per pannelli fluttuanti (modali,
     dropdown, IPSelect/IPDatepicker gia' usano la propria), --ip-shadow-lg per
     overlay/drag. Prima l'unica ombra era --ip-shadow-sm: aprendo più livelli
     (modale dentro card) tutto poggiava sulla stessa profondita' piatta. */
  --ip-shadow-md:     0 4px 12px -2px rgba(24, 24, 27, 0.10), 0 2px 4px rgba(24, 24, 27, 0.04);
  --ip-shadow-lg:     0 18px 48px -12px rgba(24, 24, 27, 0.22), 0 6px 16px rgba(24, 24, 27, 0.08);
  --ip-preview-bg:    #52525b;      /* fondo "carta" dell'anteprima .ip-preview__sheet */
  --ip-transition:    150ms ease;
  --ip-font:          "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ip-display:       "Fraunces", Georgia, "Times New Roman", serif;
  /* Carattere del solo nome accanto al logo. Ha un token suo, e non riusa
     --ip-display, perché il nome è un marchio e non un titolo: così cambiare
     il serif dei titoli non ritocca il marchio, e viceversa. */
  --ip-brand-font:    "Sora", "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --ip-mono:          "IBM Plex Mono", "SF Mono", "Menlo", "Consolas", "Liberation Mono", monospace;
  /* Bootstrap dà a <code>, <kbd> e <pre> il proprio stack (SFMono-Regular):
     identificatori di serie e chiavi MCP uscivano in un monospazio diverso
     dalle cifre del resto dell'app, che sono tutte in IBM Plex Mono. */
  --bs-font-monospace: var(--ip-mono);

  /* Mapping BS5 → palette IP (per .text-bg-*, .link-primary, .alert-* etc.) */
  --bs-primary:        var(--ip-primary);
  --bs-primary-rgb:    24, 24, 27;
  --bs-success:        var(--ip-success);
  --bs-success-rgb:    70, 136, 71;
  --bs-warning:        var(--ip-warning);
  --bs-warning-rgb:    248, 148, 6;
  --bs-danger:         var(--ip-danger);
  --bs-danger-rgb:     185, 74, 72;
  --bs-info:           var(--ip-info);
  --bs-info-rgb:       58, 135, 173;
  --bs-link-color:     var(--ip-primary);
  --bs-link-color-rgb: 24, 24, 27;
  --bs-link-hover-color: var(--ip-primary-hover);

  /* Bordi: senza questa mappatura Bootstrap continua a disegnare con il proprio
     default #dee2e6 (tabelle, card, input-group, hr, dropdown). Sulla stessa
     pagina convivevano quattro grigi di bordo diversi, di cui il più diffuso
     non era un nostro token: le linee risultavano leggermente disallineate di
     tono fra tabella e card. */
  --bs-border-color: var(--ip-border);
  --bs-border-color-translucent: var(--ip-border);
  --bs-table-border-color: var(--ip-border);
}
/* -----------------------------------------------------------------------------
   1bis. Tema scuro — stesso sistema di token, valori per fondo scuro
   ----------------------------------------------------------------------------
   html[data-theme="dark"] viene impostato da layout/theme_head.php (script
   inline nel <head>, prima dei CSS: niente lampeggio del tema sbagliato).
   La preferenza sta in localStorage (ip-theme), altrimenti segue
   prefers-color-scheme. data-bs-theme (stesso valore) attiva il tema scuro
   nativo di Bootstrap 5.3 per le superfici dei componenti; qui si ribaltano
   solo i token --ip-* — tutto il resto (card, tabelle, sidebar, form,
   dropdown, modali) li consuma e si ribalta da solo.

   Specificità html[data-theme="dark"] > :root: i token scuri vincono sul
   blocco chiaro anche quando ridefiniscono le stesse variabili.
   -------------------------------------------------------------------------- */
html[data-theme="dark"] {
  --ip-bg:                #151518;    /* canvas */
  --ip-bg-card:           #1e1e23;    /* superfici (card, sidebar, pannelli) */
  --ip-border:            #33333b;
  --ip-border-soft:       #2a2a31;
  --ip-border-strong:     #4b4b55;
  --ip-border-strong-hover: #63636e;
  --ip-text:              #e7e7ea;
  --ip-text-strong:       #f4f4f5;
  --ip-text-muted:        #9b9ba5;
  --ip-primary:           #e7e7ea;    /* accento invertito: link/testo chiari */
  --ip-primary-hover:     #f4f4f5;
  --ip-primary-dark:      #c9c9d0;    /* (active) */
  --ip-primary-soft:      color-mix(in srgb, var(--ip-primary) 9%, transparent);
  --ip-on-primary:        var(--ip-bg);    /* testo su sfondo --ip-primary (scuro: primary chiaro) */
  --ip-on-neutral:        var(--ip-bg);    /* chip neutri: in scuro il grigio pieno è chiaro */
  --ip-bg-subtle:         #26262d;
  --ip-warning-border:    #7a5520;
  --ip-shadow-focus-danger: 0 0 0 3px color-mix(in srgb, var(--ip-danger) 30%, transparent);

  /* Tinte di stato adattate: il vermiglio/verde/ambra originali su fondo
     scuro non reggevano il contrasto, qui le varianti più chiare. */
  --ip-success:           #57a859;
  --ip-success-bg:        #1c271d;
  --ip-success-text:      #7ec780;
  --ip-success-hover:     #4d9650;
  --ip-warning:           #e89c2f;
  --ip-warning-bg:        #2e2414;
  --ip-warning-text:      #e8a13d;
  --ip-danger:            #d0716e;
  --ip-danger-bg:         #2d1c1c;
  --ip-danger-soft:       #3a2323;
  --ip-danger-text:       #da8f8c;
  --ip-danger-hover:      #b95a57;
  --ip-info:              #5d9fbe;
  --ip-info-bg:           #192430;
  --ip-info-text:         #7ab4d4;
  --ip-violet:            #9b8ce8;    /* costi, adattato al fondo scuro */
  --ip-violet-bg:         #211d33;
  --ip-neutral:           #8a8a92;
  --ip-neutral-solid:     #a0a0a8;
--ip-canceled:          #4a4a52;

   --ip-border-field:      #6e6e7a;    /* 3.3:1 su card scura (#1e1e23) */
   --ip-border-hover:      #8a8a97;    /* hover campi: piu' chiaro del riposo */
   /* In scuro i fondi dei pulsanti sono gia' chiari e il testo e' scuro
      (vedi html[data-theme="dark"] .btn-success): la tinta di stato va bene
      cosi' com'e'. */
   --ip-success-solid:     var(--ip-success);
   --ip-info-solid:        var(--ip-info);
   --ip-on-bright:         var(--ip-bg);

   --ip-hover:             #26262d;    /* hover bottoni neutri/righe */
  /* Barra superiore. I due tentativi precedenti hanno fissato altrettanti
     vincoli: #262b36 era troppo vicino per luminosita' a card/sidebar
     (#1e1e23, ~12/255 di luminanza percepita) e la barra si perdeva nello
     sfondo; il navy del tema chiaro (#333c4a) risolveva la distinzione ma
     restava l'unica superficie satura dell'intero tema scuro — su canvas
     #151518 e card #1e1e23 leggeva come una fascia azzurra estranea alla
     palette, e per giunta la più chiara dello schermo.
     Nel tema chiaro la topbar e' una fascia molto più scura del contenuto:
     tradotta su fondo scuro, la stessa relazione vuole una fascia piu'
     chiara delle superfici. Quello che va tolto non e' la luminosita' ma la
     saturazione. #3b3b45 tiene la stessa luminanza percepita del navy
     (~59/255: resta identico lo stacco netto da card e canvas che serviva a
     far leggere la fascia) e la riporta sulla rampa neutra della palette,
     che ha gia' la stessa lieve dominante bluastra (#33333b = 51,51,59).
     Il testo (bianco a 0.78) resta a ~7.6:1, invariato. Il pannello brand
     del login usa lo stesso token, così le due superfici restano coerenti. */
  --ip-navbar-bg:         #3b3b45;
  --ip-navbar-border:     #2a2a31;
  --ip-navbar-text:       rgba(255, 255, 255, 0.78);
  --ip-navbar-hover:      rgba(255, 255, 255, 0.10);

  --ip-shadow-sm:         0 1px 2px 0 rgba(0, 0, 0, 0.45);
  --ip-shadow-focus:      0 0 0 3px color-mix(in srgb, var(--ip-primary) 18%, transparent);
  --ip-shadow-md:         0 6px 16px -4px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.30);
  --ip-shadow-lg:         0 22px 56px -14px rgba(0, 0, 0, 0.70), 0 8px 20px rgba(0, 0, 0, 0.45);
  --ip-preview-bg:        #0f0f12;    /* fondo "carta" dell'anteprima */
  --ip-skeleton-bg:       #33333b;
  --ip-skeleton-shine:    #26262d;

  /* Mapping BS5 -> palette scura: gli rgb servono alle varianti rgba() */
  --bs-body-bg:           var(--ip-bg);
  --bs-body-color:        var(--ip-text);
  --bs-primary-rgb:       231, 231, 234;
  --bs-link-color-rgb:    231, 231, 234;
  --bs-success-rgb:       87, 168, 89;
  --bs-warning-rgb:       232, 156, 47;
  --bs-danger-rgb:        208, 113, 110;
  --bs-info-rgb:          93, 159, 190;
}
/* Superfici BS5 che non ereditano i token --ip-*: dropdown, modali, liste,
   accordion restano sui grigi di default di Bootstrap (--bs-*-bg) anche con
   i token scuri. Le riallineamo alle nostre superfici. */
html[data-theme="dark"] .dropdown-menu {
  --bs-dropdown-color: var(--ip-text);
  --bs-dropdown-bg: var(--ip-bg-card);
  --bs-dropdown-border-color: var(--ip-border);
  --bs-dropdown-link-color: var(--ip-text);
  --bs-dropdown-link-hover-color: var(--ip-text);
  --bs-dropdown-link-hover-bg: var(--ip-hover);
  --bs-dropdown-link-active-color: var(--ip-bg);
  --bs-dropdown-link-active-bg: var(--ip-primary);
}
html[data-theme="dark"] .modal-content {
  --bs-modal-color: var(--ip-text);
  --bs-modal-bg: var(--ip-bg-card);
  --bs-modal-border-color: var(--ip-border);
}
html[data-theme="dark"] .form-control,
html[data-theme="dark"] .form-select {
  background-color: var(--ip-bg-card);
  color: var(--ip-text);
}
html[data-theme="dark"] .input-group-text {
  background-color: var(--ip-hover);
  color: var(--ip-text);
}
/* Spunta su fondo scuro: l'accento in dark e' quasi bianco (--ip-primary
   #e7e7ea), quindi il segno bianco spariva dentro la casella selezionata.
   Stessa regola dei pulsanti primari, che in dark scrivono in --ip-bg.
   Il colore sta dentro l'SVG e non puo' venire da una variabile: serve la
   coppia di regole, una per tema. */
html[data-theme="dark"] .form-check-input:checked[type="checkbox"],
html[data-theme="dark"] input[type="checkbox"]:checked,
html[data-theme="dark"] .form-check-input[type="checkbox"]:indeterminate,
html[data-theme="dark"] input[type="checkbox"]:indeterminate {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23151518' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.2' d='m4 8.2 2.6 2.6L12 5.4'/%3e%3c/svg%3e");
}
html[data-theme="dark"] .form-check-input[type="checkbox"]:indeterminate,
html[data-theme="dark"] input[type="checkbox"]:indeterminate {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23151518' stroke-linecap='round' stroke-width='2.2' d='M4.5 8h7'/%3e%3c/svg%3e");
}
html[data-theme="dark"] .form-check-input:checked[type="radio"],
html[data-theme="dark"] input[type="radio"]:checked {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3ccircle cx='8' cy='8' r='3.2' fill='%23151518'/%3e%3c/svg%3e");
}
html[data-theme="dark"] .list-group-item {
  background-color: var(--ip-bg-card);
  color: var(--ip-text);
}
html[data-theme="dark"] .list-group-item.active {
  background-color: var(--ip-primary);
  border-color: var(--ip-primary);
  color: var(--ip-bg);
}
html[data-theme="dark"] .accordion-button {
  background-color: var(--ip-bg-card);
  color: var(--ip-text);
}
html[data-theme="dark"] .accordion-button:not(.collapsed) {
  background-color: var(--ip-hover);
  color: var(--ip-text);
}
/* Badge .text-bg-light (contatori dashboard): su fondo scuro un badge
    chiarissimo con testo scuro fisso di Bootstrap è illeggibile. */
html[data-theme="dark"] .text-bg-light {
  color: var(--ip-text-muted) !important;
  background-color: var(--ip-hover) !important;
}
/* In scuro il fondo del conteggio è --ip-hover (#3d3d43), più chiaro del
   canvas: il testo "muted" su quella superficie dava 3.0:1. Il tono spento
   resta, ma sul gradino di testo pieno (8.6:1). In chiaro la stessa scelta
   non serve: --ip-text-muted su --ip-bg sta già a 4.89:1. */
html[data-theme="dark"] .ip-count-badge {
  color: var(--ip-text) !important;
  background-color: var(--ip-hover) !important;
  border: 1px solid var(--ip-border-strong);
}
/* Testo bianco sui fondi --ip-primary: in tema scuro il primary è chiaro,
    il testo va ribaltato sul canvas scuro. */
html[data-theme="dark"] .btn-primary {
  --bs-btn-color: var(--ip-bg);
  --bs-btn-hover-color: var(--ip-bg);
  --bs-btn-active-color: var(--ip-bg);
  --bs-btn-focus-shadow-rgb: 231, 231, 234;
}
/* btn-success e btn-danger: in tema scuro i fondi sono chiari (#57a859, #d0716e)
   ma il testo era bianco fisso -> contrasto insufficiente. Usano testo scuro
   come .btn-primary, così superano 4.5:1 in entrambi i temi. */
html[data-theme="dark"] .btn-success {
  --bs-btn-color: var(--ip-bg);
  --bs-btn-hover-color: var(--ip-bg);
  --bs-btn-active-color: var(--ip-bg);
  --bs-btn-focus-shadow-rgb: 87, 168, 89;
}
html[data-theme="dark"] .btn-danger {
  --bs-btn-color: var(--ip-bg);
  --bs-btn-hover-color: var(--ip-bg);
  --bs-btn-active-color: var(--ip-bg);
  --bs-btn-focus-shadow-rgb: 208, 113, 110;
}
/* btn-info e btn-warning in tema scuro: fondi chiari, testo scuro per contrasto */
html[data-theme="dark"] .btn-info {
  --bs-btn-color: var(--ip-bg);
  --bs-btn-hover-color: var(--ip-bg);
  --bs-btn-active-color: var(--ip-bg);
  --bs-btn-focus-shadow-rgb: 93, 159, 190;
}
html[data-theme="dark"] .btn-warning {
  --bs-btn-color: var(--ip-bg);
  --bs-btn-hover-color: var(--ip-bg);
  --bs-btn-active-color: var(--ip-bg);
  --bs-btn-focus-shadow-rgb: 232, 156, 47;
}
html[data-theme="dark"] .ip-dp__day.is-selected,
html[data-theme="dark"] .ip-dp__month.is-selected { color: var(--ip-bg); }
/* IPSelect (pannello dropdown azienda/anno/anagrafiche con ricerca): la voce
   corrente usa background: var(--ip-primary) + color: #fff fisso. In tema
   chiaro --ip-primary e' un blu scuro e il bianco regge; in tema scuro
   --ip-primary si ribalta in quasi-bianco (#e7e7ea) ma il colore testo
   restava #fff fisso, quindi la voce selezionata diventava bianco su bianco,
   illeggibile. Stesso ribaltamento gia' applicato sopra a .btn-primary e
   .ip-dp__day.is-selected. */
html[data-theme="dark"] .ip-select-option.is-selected { color: var(--ip-bg); }
html[data-theme="dark"] .btn-group.index-options .btn.btn-primary,
html[data-theme="dark"] .btn-group.index-options .btn.active { color: var(--ip-bg); }
html[data-theme="dark"] .ip-topbar .ip-topbar-link:hover .ip-nav-avatar { color: var(--ip-bg); }
html[data-theme="dark"] .ip-tabs .nav-link.active,
html[data-theme="dark"] .ip-tabs .nav-link.active:focus-visible { color: var(--ip-on-primary); }
html[data-theme="dark"] .ip-tabs .nav-link.active .ip-tab-badge {
  background: rgba(0, 0, 0, 0.14);
  color: var(--ip-on-primary);
}
html[data-theme="dark"] .ip-period-month.is-active {
  color: var(--ip-on-primary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 1px 2px rgba(0, 0, 0, .2);
}

/* -----------------------------------------------------------------------------
   1ter. Font globale — applica il token --ip-font (Figtree) a tutta l'app.
   Prima era definito ma mai collegato a body/html: l'app rendeva ancora con
   il fallback di sistema nonostante il token fosse pronto.
   -------------------------------------------------------------------------- */
html, body {
  font-family: var(--ip-font);
}
/* -----------------------------------------------------------------------------
   1quater. Scrollbar sottile on-brand
   -----------------------------------------------------------------------------
   Il default di sistema e' rumoroso e sfasa rispetto alla palette. scrollbar-
   color vale per Firefox; la pseudo-scrollbar copre WebKit (Chrome/Safari/
   Edge). Su fondo chiaro e scuro eredita i token --ip-border / --ip-bg, quindi
   si ribalta da sola col tema. */
html {
  scrollbar-color: var(--ip-border) transparent;
  scrollbar-width: thin;
}
html::-webkit-scrollbar { width: 10px; height: 10px; }
html::-webkit-scrollbar-track { background: transparent; }
html::-webkit-scrollbar-thumb {
  background: var(--ip-border);
  border: 3px solid var(--ip-bg);
  border-radius: 999px;
}
html::-webkit-scrollbar-thumb:hover { background: var(--ip-border-strong); }
/* Selezione del testo on-brand: invece del blu di sistema, usa il soft del
   primary, così anche evidenziare resta dentro la palette neutra. */
::selection {
  background: var(--ip-primary-soft);
  color: var(--ip-text);
}
/* Rispetto di prefers-reduced-motion: chi lo chiede non vuole movimento.
   Spengniamo shimmer, transizioni e animazioni di entrata mantenendo il
   layout identico. Coerente con l'attenzione all'accessibilita' gia' presente
   nel foglio (skip-link, focus-ring, contrasto degli scheletri). */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
/* Entrata dolce del contenuto a ogni caricamento pagina: evita il "pop" rigido
   del DOM pronto, senza ritardare la lettura (200ms). La guardia reduced-motion
   la neutralizza.
   Solo opacita', niente translateY: un'animazione su transform con fill "both"
   resta applicata anche dopo la fine, e in Chromium un antenato con
   animazione transform attiva diventa containing block dei discendenti
   position:fixed — la barra delle azioni di gruppo (ip-bulk-bar, fixed) si
   sarebbe ancorata a #content invece che al viewport, comparendo in mezzo
   alla tabella. */
@keyframes ip-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
#main-content #content {
  animation: ip-fade-in 200ms ease both;
}
/* Transizione di tema morbida: abilitata solo durante il toggle (la classe
   .ip-theme-animating e' messa/levata da layout/base.php), così il cambio
   chiaro<->scuro non e' brusco ma il caricamento iniziale resta istantaneo.
   La guardia prefers-reduced-motion la neutralizza comunque. */
html.ip-theme-animating,
html.ip-theme-animating *,
html.ip-theme-animating *::before,
html.ip-theme-animating *::after {
  transition: background-color 240ms ease, color 240ms ease, border-color 240ms ease !important;
}
/* -----------------------------------------------------------------------------
   1sexies. Primitive di dettaglio — sticky head, zebra, icon-button
   --------------------------------------------------------------------------
   Tutte opt-in via classe, per non alterare le tabelle/azioni gia' definite. */
/* Header di tabella ancorato allo scroll: resta leggibile sulle liste lunghe
   senza introdurre colore (sfondo neutro ereditato dal thead). Si ancora
   sotto navbar + headerbar grazie ai token misurati via JS, così non finisce
   mai dietro la headerbar fissa. Le tabelle dentro card con overflow:hidden
   (dashboard) restano fuori da questo comportamento. */
.table--sticky-head > thead > tr > th {
  position: sticky;
  top: calc(var(--ip-navbar-height) + var(--ip-headerbar-height));
  z-index: var(--ip-z-raised);
}
/* Zebratura leggera per tabelle dati fitte (liste a molte righe): aiuta la
   scansione orizzontale restando sul grigio neutro, senza colore. L'hover
   resta prevalente. */
.table--zebra > tbody > tr:nth-child(even) > td {
  background: var(--ip-bg);
}
.table--zebra.table-hover > tbody > tr:hover > td {
  background: var(--ip-primary-soft);
}
/* Pulsante solo-icona: cerchio con hover soft, riutilizzabile su azioni
   compatte (es. toggle). La variante in topbar usa l'hover della barra. */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.btn-icon:hover { background: var(--ip-hover); color: var(--ip-text); }
/* -----------------------------------------------------------------------------
   2. Layout / Spacing globale — allineamento coerente headerbar ↔ content
   --------------------------------------------------------------------------

   Problema risolto: modern-theme.css setta #main-content { padding: 15px },
   che si sommava al padding interno dell'headerbar (12px 20px) producendo
   un doppio padding (27px sui lati) e disallineamento orizzontale fra il
   titolo (a 35px dal bordo) e il content (a 15px).

   Soluzione: #main-content padding 0; l'headerbar gestisce il proprio
   padding a tutta larghezza (full-bleed); #content usa lo stesso padding
   orizzontale (20px) dell'headerbar così titolo e corpo si allineano. Le
   pagine tabella (.table-content) mantengono padding orizzontale 20px ma
   azzerano il verticale così la tabella aderisce alla headerbar.

   Inoltre: il contenitore #main-area e' reso flexbox (row) così .sidebar
   sta a sinistra e #main-content a destra, affiancati. Senza questo, la
   sidebar si accatasta SOPRA il content. */

/* body.padding-top (il CSS legacy la fissava a 51px statici) va sostituito
   dalla misura reale della navbar: con l'avatar utente, il font Figtree e le
   voci di menu, l'altezza effettiva è quasi sempre > 51px, e la differenza
   faceva scivolare #main-area (quindi #headerbar e il suo titolo) sotto la
   navbar fixed, "coprendo" il titolo pagina invece di lasciarlo mancante.
   --ip-navbar-height viene ri-scritta via JS in layout/base.php non appena
   la navbar è nel DOM (vedi commento token in sezione 1). */
/* Scoped a :has(.ip-topbar): le pagine senza topbar fissa (login, password
   reset, guest senza header, ecc.) non devono ereditare lo spazio riservato
   alla navbar, altrimenti resta una fascia bianca vuota in alto. */
body:has(.ip-topbar) {
  padding-top: var(--ip-navbar-height);
}
/* Contenitore principale: flexbox row, la sidebar è ora fixed (sezione 20,
   selettore #ip-sidebar ~ #main-area per il margine) */
#main-area {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  min-height: calc(100vh - var(--ip-navbar-height));
}
/* Content area: flex-grow, si espande al resto della larghezza */
#main-content {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
}
/* Content area: padding orizzontale coerente con headerbar (20px).
   Su mobile ridotto a 16px per più area utile.
   Scoped a #main-content per non applicarsi a pagine standalone (.container > #content)
   come welcome.php che hanno il proprio container Bootstrap. */
#main-content #content {
  padding: 16px 20px;
}
@media (max-width: 575.98px) {
  #main-content #content { padding: 14px 16px; }
  #headerbar { padding: 12px 16px; }
}
/* Pagine tabella: padding orizzontale allineato, verticale azzerato.
   La tabella aderisce al bordo inferiore dell'headerbar senza gap. */
#main-content #content.table-content {
  padding: 0 20px;
}
@media (max-width: 575.98px) {
  #main-content #content.table-content { padding: 0 16px; }
}
/* Rispetto di .no-padding quando usato esplicitamente (tabs pages, partial-item):
   ha la precedenza sul padding di default per #content */
#main-content #content.no-padding,
#main-content #content.tabbable,
#main-content #content .tabbable {
  padding: 0;
}
/* Il padding tolto a #content va restituito al singolo pannello del tab,
   altrimenti il contenuto finisce a filo viewport: le .row hanno gutter
   negativi (-.75rem) e senza padding del contenitore sporgono oltre il bordo.
   .tab-rich-content = pannello con card (stesso padding di #content);
   .tab-pane.table-content = pannello con tabella (solo padding orizzontale,
   come #content.table-content, così la card tabella si allinea alle liste).
   Il top lo lascia alla barra schede: .ip-tabs ha il margine inferiore (18px),
   sommato qui darebbe quasi il doppio del respiro standard. */
.tab-pane.tab-rich-content {
  padding: 0 20px 16px;
}
.tab-pane.table-content {
  padding: 0 20px 20px;
}
@media (max-width: 575.98px) {
  .tab-pane.tab-rich-content { padding: 0 16px 14px; }
  .tab-pane.table-content { padding: 0 16px 16px; }
}
/* La pagina impostazioni tiene #content col padding di default: come per i
   pannelli delle schede, il top lo gestisce la barra .ip-tabs (margine
   inferiore 18px), altrimenti col suo padding si arriva quasi al doppio. */
#form-settings #content { padding-top: 0; }
/* -----------------------------------------------------------------------------
   2bis. Headerbar
   -------------------------------------------------------------------------- */

/* L'headerbar è la 'toccata' visiva di ogni schermata: tipografia più decisa,
   separazione netta, spazio coerente. Non va gridato: 1.2rem + peso 600.
   Layout a 3 zone: titolo | filtri (desktop) | azioni. Su mobile le azioni
   vanno a capo sotto il titolo per evitare affollamento, resta solo il
   bottone primario + il toggle filtri in linea. */
/* position: sticky — l'headerbar resta incollata sotto la navbar fixed
   durante lo scroll. top: var(--ip-navbar-height) la fa combaciare col
   bordo inferiore della navbar (la variabile è misurata a runtime dallo
   script in layout/base.php, quindi si adatta a navbar più alte su mobile
   o quando l'hamburger menu è aperto). z-index 1020 la mantiene sopra il
   contenuto (navbar è 1030) senza coprire dropdown/filtri. */
#headerbar {
  position: sticky;
  top: var(--ip-navbar-height);
  z-index: 1020;
  min-height: 56px;
  padding: 12px 20px;
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
}
#headerbar .headerbar-title {
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--ip-text);
  letter-spacing: -0.01em;
  margin: 0;
  line-height: 1.4;
  /* Stessa famiglia display dei titoli di pagina: la "toccata" di ogni
     schermata parla la stessa lingua tipografica. */
  font-family: var(--ip-display);
}
/* Slot destinato a breadcrumb/metadata sotto il titolo (opt-in via .headerbar-meta) */
#headerbar .headerbar-meta {
  width: 100%;
  margin: -4px 0 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
/* Quando c'e' meta, il titolo tiene un minimo di respiro sopra */
#headerbar:has(.headerbar-meta) { padding-top: 10px; padding-bottom: 14px; }
#headerbar:has(.headerbar-meta) .headerbar-title { margin-bottom: 2px; }
/* Zone right: gruppo singolo (BS3 .pull-right o BS5 .float-end). Unifica i
   selettori morti in uno coerente per tutte le view legacy e nuove. */
#headerbar .headerbar-item,
#headerbar .headerbar-item.pull-right,
#headerbar .headerbar-item.float-end {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-4);
  align-items: center;
  margin-left: auto;
}
/* Filtri e azioni formano un solo gruppo a destra. Con due margin-left:auto
   lo spazio si distribuiva fra i due e la ricerca di Impostazioni restava
   sospesa a metà pagina, con un vuoto prima dei pulsanti. */
#headerbar .headerbar-item + .headerbar-item { margin-left: 0; }
/* Keep inline filter e primary action visually adjacent */
#headerbar .headerbar-item > .btn-group.index-options + .btn,
#headerbar .headerbar-item > .btn + .btn-group.index-options { margin-left: 4px; }
/* Su schermi piccoli l'headerbar item va full-width per evitare che le
   azioni escano fuori dal viewport (xs <= 575px). */
@media (max-width: 575.98px) {
  #headerbar .headerbar-item,
  #headerbar .headerbar-item.pull-right,
  #headerbar .headerbar-item.float-end {
    width: 100%;
    margin-left: 0;
    justify-content: flex-start;
  }
  #headerbar .headerbar-item .btn-group.index-options { flex: 1 1 auto; }
}

/* Totale del documento nell'headerbar (fatture, preventivi, importazioni,
   RiBa, esportazioni): stessa resa di quella che prima stava nella barra di
   salvataggio fissa in fondo alla pagina, ora accanto ai pulsanti azione
   dentro .headerbar-item, così il flex row li allinea sulla stessa riga. */
.headerbar-total {
  display: flex;
  align-items: baseline;
  gap: var(--ip-space-4);
  margin-right: 4px;
}
.headerbar-total__label {
  font-size: var(--ip-text-sm);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.headerbar-total__amount {
  font-size: var(--ip-text-xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}
@media (max-width: 575.98px) {
  .headerbar-total { width: 100%; margin-right: 0; }
}

/* Gruppo filtri stato nei listati: separato, non invadente */
.btn-group.index-options {
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  overflow: hidden;
}
.btn-group.index-options .btn {
  border: none;
  border-radius: 0;
  border-right: 1px solid var(--ip-border);
  background: var(--ip-bg-card);
  color: var(--ip-text-muted);
  padding: 5px 12px;
  font-size: var(--ip-text-base);
  font-weight: 500;
}
.btn-group.index-options .btn:last-child { border-right: 0; }
.btn-group.index-options .btn:hover {
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
}
.btn-group.index-options .btn.btn-primary,
.btn-group.index-options .btn.active {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  box-shadow: none;
}
/* -----------------------------------------------------------------------------
   3. Pannelli (Card BS5) — bordi soft, lieve ombra, padding coerente
   -------------------------------------------------------------------------- */

.card {
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
  background: var(--ip-bg-card);
  /* Distanza fra card = gradino 16 della scala spaziature. Era 18px, valore
     fuori scala che conviveva con i 16 di .ip-doc-section e con il mb-3 di
     Bootstrap: la stessa pila di pannelli aveva due passi diversi secondo la
     pagina. */
  margin-bottom: var(--ip-space-7);
}
.card > .card-header {
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border-soft);
  color: var(--ip-text);
  font-weight: 600;
  font-size: var(--ip-text-md);
  /* Ritmo di respiro condiviso con .ip-table-card / .ip-doc-section__head
     (14px 18px): lo stesso header su card diverse non cambia mai altezza. */
  padding: var(--ip-card-head-y) var(--ip-card-pad-x);
}
.card-body { padding: var(--ip-card-body-y) var(--ip-card-pad-x); }
/* L'ultimo elemento di un corpo non porta con sé il proprio margine: il
   respiro di chiusura lo dà il padding della card. Senza questo, un campo
   (margin-bottom 16) dentro un corpo con padding 16 lasciava 32px sotto e 16
   sopra, e il contenuto sembrava salito. Vale anche per le colonne: un form
   largo mette i campi in .row e il margine dell'ultima colonna si sommava
   allo stesso modo. */
.card-body > :last-child,
.ip-doc-section__body > :last-child,
.ip-window-body > :last-child,
.card-body > .row:last-child > [class*="col-"] > :last-child,
.ip-doc-section__body > .row:last-child > [class*="col-"] > :last-child {
  margin-bottom: 0;
}
/* Il footer di default BS5 usa un grigio esterno al tema (rgba(0,0,0,.03)):
   lo portiamo sul sistema — sfondo canvas, filetto soft, testo muto. */
.card > .card-footer {
  background: var(--ip-bg);
  border-top: 1px solid var(--ip-border-soft);
  /* Stesso rientro orizzontale di header e body: il piè di card chiude il
     contenuto sulla stessa colonna (era 16px contro 18px, e il pager
     rientrava di 2px rispetto alle celle della tabella sopra). */
  padding: var(--ip-card-foot-y) var(--ip-card-pad-x);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
/* Variante compatta: card annidate (es. note del cliente) — senza ombra,
   margine ridotto, padding del corpo allineato al resto. */
.card.small {
  margin-bottom: 10px;
  box-shadow: none;
}
.card.small .card-body { padding: 12px 14px; }
/* La variante compatta rientra a 14px anche nel piè di card: le note del
   cliente hanno corpo e data che devono chiudersi sulla stessa colonna. */
.card.small > .card-footer { padding: var(--ip-card-foot-y) 14px; }
/* Filetto orizzontale (es. sezione SMTP): opacita' default BS5 spenta,
   allineato ai bordi hairline del tema. */
hr {
  border: 0;
  border-top: 1px solid var(--ip-border-soft);
  opacity: 1;
}
/* -----------------------------------------------------------------------------
   4. Tabelle — hover più morbido, righe compatte, allineamento importi
   -------------------------------------------------------------------------- */

.table {
  font-size: var(--ip-text-base);
  margin-bottom: 0;
}
.table > thead > tr > th {
  font-weight: 600;
  color: var(--ip-text-muted);
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--ip-text-sm);
  border-bottom: 1px solid var(--ip-border);
  background: var(--ip-bg);
}
.table > tbody > tr > td {
  border-top: 1px solid var(--ip-border-soft);
  vertical-align: middle;
}
.table-hover > tbody > tr {
  transition: background-color var(--ip-transition);
}
/* Il colore va sulla td, non sulla tr: una tr non rispetta in modo affidabile
   il proprio border-radius (ne' quello ereditato), quindi uno sfondo dipinto
   a livello di riga sfora sempre gli angoli arrotondati della card sotto
   l'ultima riga. Dipingendo la td invece della tr, l'arrotondamento messo
   sulle celle d'angolo (vedi sezione "ultima riga senza footer" qui sotto)
   ritaglia correttamente lo sfondo hover. Stessa convenzione gia' in uso per
   le righe scadute (.ip-row-overdue) qualche riga sotto. */
.table-hover > tbody > tr:hover > td {
  background: var(--ip-primary-soft);
}
/* Ultima riga di una tabella che chiude la card (nessun card-footer/pager
   sotto): senza questo, l'hover della riga (sopra) resta un rettangolo
   squadrato che sfora visibilmente gli angoli arrotondati della card. Le due
   celle d'angolo prendono lo stesso raggio della card (1px in meno per
   restare dentro al bordo di 1px), così lo sfondo hover — ora dipinto sulla
   td — viene ritagliato alla stessa curva. Guardia :has() perché se c'e' un
   footer/pager sotto, l'ultima riga non e' più il bordo visivo della card e
   arrotondarla creerebbe un incavo innaturale sopra il footer. */
/* Selettore su "table" (elemento), non ".table" (classe Bootstrap): alcune
   tabelle del tema (es. .ip-items-table del prospetto finanziario e delle
   righe documento) non portano la classe .table e restavano fuori da questa
   regola, quadrando l'angolo in hover. Stesso motivo per cui includiamo
   .ip-doc-section fra i contenitori: e' lo stesso guscio "a card" (bg,
   bordo, border-radius) usato nelle pagine documento e nel prospetto
   finanziario, ma senza la classe .card. Il ramo ":has(tfoot)" copre le
   tabelle con riga Totale in tfoot (es. finance/index.php): li' l'ultima
   riga "reale" e' quella del tfoot, non dell'ultima <tr> del tbody, e la sua
   prima cella e' spesso un <th scope="row"> anziche' un <td> — per questo
   si usa ":first-child"/":last-child" invece di "td:first-child". */
.card:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :first-child,
.ip-table-card:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :first-child,
.ip-doc-section:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :first-child,
.card:not(:has(.card-footer)) table > tfoot > tr:last-child > :first-child,
.ip-table-card:not(:has(.card-footer)) table > tfoot > tr:last-child > :first-child,
.ip-doc-section:not(:has(.card-footer)) table > tfoot > tr:last-child > :first-child {
  border-bottom-left-radius: calc(var(--ip-radius) - 1px);
}
.card:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :last-child,
.ip-table-card:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :last-child,
.ip-doc-section:not(:has(.card-footer)) table:not(:has(tfoot)) > tbody > tr:last-child > :last-child,
.card:not(:has(.card-footer)) table > tfoot > tr:last-child > :last-child,
.ip-table-card:not(:has(.card-footer)) table > tfoot > tr:last-child > :last-child,
.ip-doc-section:not(:has(.card-footer)) table > tfoot > tr:last-child > :last-child {
  border-bottom-right-radius: calc(var(--ip-radius) - 1px);
}
/* Importi allineati a destra con numeri tabulari per scansionare cifre */
.table .amount,
.amount {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Tabella dentro card senza padding doppio */
.card .table-responsive,
.table-responsive { margin-bottom: 0; }
/* Ricerca live sulle liste (clienti/fatture/preventivi) — usa il backend
   filter/filter_* gia' esistente (FilterAjax), prima non collegato a nessuna UI. */
.ip-live-search {
  position: relative;
  max-width: 360px;
  margin-bottom: 14px;
}
.ip-live-search .fa-search {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  pointer-events: none;
}
.ip-live-search input.form-control {
  padding-left: 34px;
}
@media (max-width: 575.98px) {
  .ip-live-search { max-width: 100%; }
}
/* -----------------------------------------------------------------------------
   5. Status badge (BS5 .badge / .text-bg-*) — coerenti e leggibili
   -------------------------------------------------------------------------- */

/* Ossatura unica di ogni etichetta compatta (timbri di stato, conteggi,
   badge di import/export, tab, aging, XML). Prima ogni famiglia aveva le
   proprie misure: tre raggi (3px, 4px, 999px), due corpi (10 e 11px), due pesi
   e padding in em — che su corpi diversi davano 3,08px invece di 3. Stesso
   significato, aspetto diverso a seconda della pagina: era il difetto che si
   vedeva di più. Ora le misure stanno qui e nei token, e i componenti portano
   solo il colore e la forma (bordo, pieno, pillola).
   Il padding in px, non in em: in em dipende dal corpo e produce le differenze
   di un pixel che rendevano due timbri affiancati diversi in altezza. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: var(--ip-badge-padding-y) var(--ip-badge-padding-x);
  font-size: var(--ip-badge-font-size);
  font-weight: var(--ip-badge-font-weight);
  line-height: var(--ip-badge-line-height);
  letter-spacing: var(--ip-badge-letter-spacing);
  /* Il bordo trasparente non è decorazione: occupa spazio, quindi una pillola
     piena e una con bordo hanno la stessa altezza. Senza, "Bozza" e "Pagata"
     nella stessa colonna differivano di 2px. */
  border: 1px solid transparent;
  border-radius: var(--ip-badge-radius);
  white-space: nowrap;
  vertical-align: baseline;
}
/* Didascalia secondaria accanto a un badge in cella tabella (es. il codice
   Natura IVA + descrizione estesa in tax_rates/index.php): stessa idea del
   sottotitolo sotto un valore, ma in linea invece che a capo, perché un
   badge di poche lettere (N6.3) accanto a una riga intera vuota sembrerebbe
   sproporzionato. */
.ip-table-cell-note {
  display: inline-block;
  margin-left: 8px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
/* Prima colonna di liste dove il testo e' scelto dall'utente e puo' quindi
   essere arbitrariamente lungo (es. tax_rates/index.php: "Nome imposta").
   Senza un limite la cella spinge in larghezza le colonne accanto (importo,
   Natura) finche' non restano schiacciate contro il bordo della card. Il
   troncamento con ellissi rende visibile che il testo continua; il title
   sull'elemento (stesso valore, non escapizzato due volte) recupera il nome
   intero al passaggio del mouse senza dover aprire il form. */
.ip-table-cell-truncate {
  max-width: 260px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 767.98px) {
  .ip-table-cell-truncate { max-width: 160px; }
}
/* -----------------------------------------------------------------------------
   6. Bottoni — micro-feedback controllato, niente decorazione esuberante
   -------------------------------------------------------------------------- */

.btn {
  border-radius: var(--ip-radius-sm);
  font-weight: 500;
  transition: all var(--ip-transition);
  border: 1px solid transparent;
}
.btn:focus { outline: none; }
/* Feedback tattile al click: la leggera compressione segnala l'azione senza
   ombra ne' colore aggiunti — resta coerente col linguaggio sobrio dell'app. */
.btn:active { transform: scale(0.985); }
/* Varianti outline sui token dell'app. Non erano mai state tematizzate:
   restavano i colori di Bootstrap (#6c757d, che sul fondo scuro fa 3,5:1, e
   il blu #0d6efd, che non compare in nessun altro punto dell'interfaccia), e
   il bordo spariva perché .btn qui sopra lo dichiara trasparente con lo
   shorthand. Le variabili --bs-btn-* tengono coerenti hover, active e
   disabled senza riscrivere ogni stato. */
.btn-outline-primary,
.btn-outline-secondary,
.btn-outline-success,
.btn-outline-danger,
.btn-outline-warning {
  --bs-btn-bg: transparent;
  --bs-btn-disabled-bg: transparent;
  color: var(--bs-btn-color);
  border-color: var(--bs-btn-border-color);
}
.btn-outline-primary {
  --bs-btn-color: var(--ip-primary);
  --bs-btn-border-color: var(--ip-primary);
  --bs-btn-hover-color: var(--ip-on-primary);
  --bs-btn-hover-bg: var(--ip-primary);
  --bs-btn-hover-border-color: var(--ip-primary);
  --bs-btn-active-color: var(--ip-on-primary);
  --bs-btn-active-bg: var(--ip-primary-hover);
  --bs-btn-active-border-color: var(--ip-primary-hover);
}
/* La secondaria è la più usata (calendario, "Valida"): testo pieno e bordo
   forte, come btn-secondary ma senza fondo, non grigio su grigio. */
.btn-outline-secondary {
  --bs-btn-color: var(--ip-text);
  --bs-btn-border-color: var(--ip-border-strong);
  --bs-btn-hover-color: var(--ip-text);
  --bs-btn-hover-bg: var(--ip-hover);
  --bs-btn-hover-border-color: var(--ip-border-strong-hover);
  --bs-btn-active-color: var(--ip-text);
  --bs-btn-active-bg: var(--ip-hover);
  --bs-btn-active-border-color: var(--ip-border-strong-hover);
}
.btn-outline-success {
  --bs-btn-color: var(--ip-success-text);
  --bs-btn-border-color: var(--ip-success);
  --bs-btn-hover-color: var(--ip-success-text);
  --bs-btn-hover-bg: var(--ip-success-bg);
  --bs-btn-hover-border-color: var(--ip-success);
  --bs-btn-active-color: var(--ip-success-text);
  --bs-btn-active-bg: var(--ip-success-bg);
  --bs-btn-active-border-color: var(--ip-success);
}
.btn-outline-danger {
  --bs-btn-color: var(--ip-danger-text);
  --bs-btn-border-color: var(--ip-danger);
  --bs-btn-hover-color: var(--ip-danger-text);
  --bs-btn-hover-bg: var(--ip-danger-bg);
  --bs-btn-hover-border-color: var(--ip-danger);
  --bs-btn-active-color: var(--ip-danger-text);
  --bs-btn-active-bg: var(--ip-danger-bg);
  --bs-btn-active-border-color: var(--ip-danger);
}
.btn-outline-warning {
  --bs-btn-color: var(--ip-warning-text);
  --bs-btn-border-color: var(--ip-warning-border);
  --bs-btn-hover-color: var(--ip-warning-text);
  --bs-btn-hover-bg: var(--ip-warning-bg);
  --bs-btn-hover-border-color: var(--ip-warning);
  --bs-btn-active-color: var(--ip-warning-text);
  --bs-btn-active-bg: var(--ip-warning-bg);
  --bs-btn-active-border-color: var(--ip-warning);
}
.btn-outline-primary:hover, .btn-outline-secondary:hover, .btn-outline-success:hover,
.btn-outline-danger:hover, .btn-outline-warning:hover {
  color: var(--bs-btn-hover-color);
  background-color: var(--bs-btn-hover-bg);
  border-color: var(--bs-btn-hover-border-color);
}
/* Anello focus da tastiera coerente su tutta l'app: link, bottoni e i nostri
   controlli custom condividono lo stesso --ip-shadow-focus delle input, cosi'
   la navigazione da tastiera resta sempre leggibile. */
a:focus-visible,
.btn:focus-visible,
.ip-select__control:focus-visible,
.ip-nav-link:focus-visible,
.ip-nav-sub-link:focus-visible {
  outline: none;
  box-shadow: var(--ip-shadow-focus);
}
/* Alias BS5: .btn-secondary e' il sostituto di .btn-default.
   Bordo un gradino più marcato di --ip-border: su fondo card (bianco su bianco)
   il bordo dei campi risulterebbe troppo debole per leggere come pulsante. */
.btn-secondary {
  background: var(--ip-bg-card);
  border-color: var(--ip-border-strong);
  color: var(--ip-text);
  box-shadow: var(--ip-shadow-sm);
}
.btn-secondary:hover {
  background: var(--ip-hover);
  border-color: var(--ip-border-strong-hover);
  color: var(--ip-text);
}
.btn-secondary:active,
.btn-secondary:focus-visible {
  background: var(--ip-hover);
  border-color: var(--ip-border-strong-hover);
  color: var(--ip-text);
}
/* Bootstrap imposta hover/active/focus/disabled di ogni variante come proprie
   CSS custom properties (--bs-btn-hover-bg, --bs-btn-active-bg, ecc.),
   inizializzate al blu/verde/rosso di default del framework. Sovrascrivere
   solo background/border-color (stato di riposo + :hover) lascia intatto
   --bs-btn-active-bg: al mousedown (:active) il bottone lampeggiava del
   colore Bootstrap originale prima di tornare al nostro colore al rilascio —
   capitava su ogni .btn-primary/.btn-success/.btn-danger dell'app (es. il
   pulsante "Login"), non un caso isolato. Qui si sovrascrivono le variabili
   stesse, così tutti gli stati derivano dai nostri token. */
.btn-primary {
  --bs-btn-color: var(--ip-on-primary);
  --bs-btn-bg: var(--ip-primary);
  --bs-btn-border-color: var(--ip-primary);
  --bs-btn-hover-color: var(--ip-on-primary);
  --bs-btn-hover-bg: var(--ip-primary-hover);
  --bs-btn-hover-border-color: var(--ip-primary-hover);
  --bs-btn-active-color: var(--ip-on-primary);
  --bs-btn-active-bg: var(--ip-primary-dark);
  --bs-btn-active-border-color: var(--ip-primary-dark);
  --bs-btn-active-shadow: none;
  --bs-btn-focus-shadow-rgb: 24, 24, 27;
  --bs-btn-disabled-bg: var(--ip-primary);
  --bs-btn-disabled-border-color: var(--ip-primary);
  background: var(--ip-primary);
  border-color: var(--ip-primary);
}
.btn-primary:hover {
  background: var(--ip-primary-hover);
  border-color: var(--ip-primary-hover);
}
.btn-success {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--ip-success-solid);
  --bs-btn-border-color: var(--ip-success-solid);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--ip-success-hover);
  --bs-btn-hover-border-color: var(--ip-success-hover);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--ip-success-hover);
  --bs-btn-active-border-color: var(--ip-success-hover);
  --bs-btn-active-shadow: none;
  --bs-btn-focus-shadow-rgb: 70, 136, 71;
  --bs-btn-disabled-bg: var(--ip-success-solid);
  --bs-btn-disabled-border-color: var(--ip-success-solid);
  background: var(--ip-success-solid); border-color: var(--ip-success-solid);
}
.btn-success:hover { background: var(--ip-success-hover); border-color: var(--ip-success-hover); }
.btn-danger {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--ip-danger);
  --bs-btn-border-color: var(--ip-danger);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: var(--ip-danger-hover);
  --bs-btn-hover-border-color: var(--ip-danger-hover);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: var(--ip-danger-hover);
  --bs-btn-active-border-color: var(--ip-danger-hover);
  --bs-btn-active-shadow: none;
  --bs-btn-focus-shadow-rgb: 185, 74, 72;
  --bs-btn-disabled-bg: var(--ip-danger);
  --bs-btn-disabled-border-color: var(--ip-danger);
  background: var(--ip-danger); border-color: var(--ip-danger);
}
.btn-danger:hover { background: var(--ip-danger-hover); border-color: var(--ip-danger-hover); }
/* btn-info e btn-warning ricondotti ai token del tema: prima usavano i ciano
   e giallo puri di Bootstrap (#0dcaf0, #ffc107) che stonavano con la palette. */
.btn-info {
  --bs-btn-color: #fff;
  --bs-btn-bg: var(--ip-info-solid);
  --bs-btn-border-color: var(--ip-info-solid);
  --bs-btn-hover-color: #fff;
  --bs-btn-hover-bg: color-mix(in srgb, var(--ip-info-solid) 88%, black);
  --bs-btn-hover-border-color: color-mix(in srgb, var(--ip-info-solid) 88%, black);
  --bs-btn-active-color: #fff;
  --bs-btn-active-bg: color-mix(in srgb, var(--ip-info-solid) 80%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ip-info-solid) 80%, black);
  --bs-btn-active-shadow: none;
  --bs-btn-focus-shadow-rgb: 49, 112, 143;
  --bs-btn-disabled-bg: var(--ip-info-solid);
  --bs-btn-disabled-border-color: var(--ip-info-solid);
  background: var(--ip-info-solid); border-color: var(--ip-info-solid);
}
.btn-info:hover {
  background: color-mix(in srgb, var(--ip-info-solid) 88%, black);
  border-color: color-mix(in srgb, var(--ip-info-solid) 88%, black);
}
.btn-warning {
  --bs-btn-color: var(--ip-on-bright);
  --bs-btn-bg: var(--ip-warning);
  --bs-btn-border-color: var(--ip-warning);
  --bs-btn-hover-color: var(--ip-on-bright);
  --bs-btn-hover-bg: var(--ip-warning);
  --bs-btn-hover-border-color: var(--ip-warning);
  --bs-btn-active-color: var(--ip-on-bright);
  --bs-btn-active-bg: color-mix(in srgb, var(--ip-warning) 85%, black);
  --bs-btn-active-border-color: color-mix(in srgb, var(--ip-warning) 85%, black);
  --bs-btn-active-shadow: none;
  --bs-btn-focus-shadow-rgb: 248, 148, 6;
  --bs-btn-disabled-bg: var(--ip-warning);
  --bs-btn-disabled-border-color: var(--ip-warning);
  background: var(--ip-warning); border-color: var(--ip-warning);
}
.btn-warning:hover { background: var(--ip-warning); border-color: var(--ip-warning); }
.btn-sm, .btn-group-sm > .btn { font-size: var(--ip-text-sm); padding: 5px 10px; min-height: var(--ip-control-h-sm); }
/* Il <button> centra da sé il contenuto quando min-height lo allunga a 40px;
   un <a> no: resta un blocco e il testo parte dal padding superiore. Nella
   stessa barra convivono le due forme ("Filtra" è un <button>, "Crea
   preventivo" un <a>) e nella misura i due riquadri risultavano alti entrambi
   40px (68→108) ma il centro dell'etichetta stava a 87,75px nel button e
   82,75px nell'anchor: stesso pulsante, testo 5px più in alto. Qui l'anchor
   diventa inline-flex e centra. Il gap sostituisce lo spazio fra icona e
   testo, che in un contenitore flex fra due elementi non viene reso. */
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}
/* Icone dentro bottoni: leggero margine e dimensione coerente */
.btn .fa.fa-margin { margin-right: 4px; }
/* pulsante delle dropdown "Opzioni": tono più sobrio */
.dropdown-toggle.btn-default .fa,
.dropdown-toggle.btn-secondary .fa { font-size: var(--ip-text-base); }
/* -----------------------------------------------------------------------------
   7. Form — base condivisa .form-control / select native / input data
    --------------------------------------------------------------------------
    Tutti i campi (text, select, datepicker) condividono bordo, raggio,
    font, hover e focus-ring. I dettagli specifici di Select2 (popup, risultati,
    Il select con ricerca (IPSelect) è in 12a; il calendario (IPDatepicker) in 12b.
    -------------------------------------------------------------------------- */

/* Base condivisa: .form-control, <select class="form-control">, <select class="form-select">
   condividono bordo, raggio, altezza, font, colore e transizione. Lo styling di
   Select2 (bordo/hover/focus/altezza/popup) vive per intero in sezione 12a più
   sotto, per evitare due blocchi che si sovrascrivono a vicenda.

   Altezza, raggio e corpo sono dichiarati QUI una volta sola. Prima le tre
   misure arrivavano da contesti diversi: un campo dentro .ip-doc-field era
   36px/8px/13px (regole scritte per la pagina export, ma non limitate al suo
   contenitore), uno nudo fuori da lì 35px/4px/14px, uno compatto 30px/4px/12px.
   Tre campi dello stesso tipo, tre aspetti: il raggio 8px per giunta senza
   token e in disaccordo con i 4px dell'appendice dello stesso input-group. */
.form-control,
select.form-control,
select.form-select {
  border-color: var(--ip-border-field);
  border-radius: var(--ip-radius);
  box-shadow: none;
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition), background-color var(--ip-transition);
  height: var(--ip-control-h);
  /* Padding orizzontale e basta: con `height` fissa il centraggio verticale lo
     fa la riga, e padding sopra/sotto spostava il testo di un pixel rispetto
     all'altezza dichiarata. */
  padding: 0 var(--ip-space-6);
  font-size: var(--ip-text-base);
  color: var(--ip-text);
  background-color: var(--ip-bg-card);
}
/* Testo lungo e selezioni multiple non hanno un'altezza sola: lì si parte dal
   minimo di un controllo e crescono col contenuto. (Il textarea dentro
   .ip-doc-field veniva schiacciato a 36px dalla regola dell'export: due righe
   di note diventavano una sola.) */
textarea.form-control {
  height: auto;
  min-height: calc(var(--ip-control-h) + var(--ip-space-6));
  padding: var(--ip-space-3) var(--ip-space-6);
  line-height: 1.45;
}
select.form-control[multiple],
select.form-control[size] { height: auto; }
/* Hover: leggero scenic hint (non solo 'rimane identico') su tutti i campi */
.form-control:hover,
select.form-control:hover,
select.form-select:hover {
  border-color: var(--ip-border-hover);
}
/* Focus coerente a tutta l'app: bordo primary + anello focus */
.form-control:focus,
select.form-control:focus,
select.form-select:focus {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
  outline: none;
}
/* Select native <select class="form-control"> (markup legacy BS3) —
   niente freccia di sistema, padding destro per hover coerente con .form-select BS5. */
select.form-control {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .75rem center;
  background-size: 12px 12px;
  padding-right: 2.25rem;
}
/* Select nativa con .input-sm mantiene proporzioni */
select.form-control.input-sm,
select.input-sm.form-control {
  background-position: right .5rem center;
  padding-right: 1.75rem;
}
/* Anche gli input compatti (righe articolo, barre filtro): .input-sm è una
   classe BS3 morta in BS5 e i campi tornavano all'altezza piena. Stessa forma
   del campo normale, un gradino più piccola: altezza e corpo dalla scala, non
   un'eccezione a sé. */
.form-control.input-sm,
.form-control-sm {
  height: var(--ip-control-h-sm);
  padding: 0 var(--ip-space-5);
  font-size: var(--ip-text-sm);
  border-radius: var(--ip-radius);
}
/* Select multipla nativa: niente freccia, padding-right normale */
select.form-control[multiple],
select.form-control[size] { background-image: none; padding-right: .75rem; }
/* BS5 .form-select: sostituiamo il chevron nativo con uno in palette IP */
select.form-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2364748b'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
}
/* Stato validazione BS5: valori coerenti coi token IP */
.form-control.is-invalid,
select.form-control.is-invalid,
select.form-select.is-invalid { border-color: var(--ip-danger); }
.form-control.is-invalid:focus,
select.form-control.is-invalid:focus,
select.form-select.is-invalid:focus {
  border-color: var(--ip-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ip-danger) 22%, transparent);
}
.form-control.is-valid,
select.form-control.is-valid,
select.form-select.is-valid { border-color: var(--ip-success); }
.form-control.is-valid:focus,
select.form-control.is-valid:focus,
select.form-select.is-valid:focus {
  border-color: var(--ip-success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ip-success) 22%, transparent);
}
/* Placeholder grigio coerente con BS5 */
.form-control::placeholder { color: var(--ip-text-muted); opacity: 1; }
/* Disabled / readonly: abbassano il contrasto invece di cambiare tono */
.form-control:disabled,
.form-control[readonly],
select.form-control:disabled,
select.form-select:disabled {
  background-color: var(--ip-bg);
  color: var(--ip-text-muted);
  cursor: not-allowed;
}
/* input-group di tipo data (icona calendario affiancata al piè):
   l'appendice .input-group-text eredita bordo/raggio coerenti. */
.input-group-text {
  background-color: var(--ip-bg);
  border-color: var(--ip-border);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  /* Lo stesso raggio del campo che affianca: prima l'appendice era a 4px e il
     campo a 8px, e i due angoli sinistri del gruppo non combaciavano. */
  border-radius: var(--ip-radius);
}
.input-group > .form-control,
.input-group > .form-select,
/* Anche un bottone in coda (filtri "Filtra fatture", genera chiave cron):
   il gruppo è un controllo solo, e con il raggio dei bottoni (4px) da una
   parte e quello del campo (6px) dall'altra gli angoli esterni non
   combaciavano. */
.input-group > .btn { border-radius: var(--ip-radius); }
/* Il gruppo e' un controllo solo: gli angoli interni vanno dritti, altrimenti
   campo e appendice sembrano due pillole accostate con una tacca in mezzo.
   Prima il raggio pieno era imposto a tutti e quattro gli angoli. */
.input-group > :not(:last-child):not(.dropdown-menu):not(.form-floating),
.input-group > .form-floating:not(:last-child) > .form-control {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.invalid-tooltip) {
  margin-left: -1px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.input-group .input-group-text .fa { font-size: var(--ip-text-base); }
.input-group:focus-within .input-group-text {
  border-color: var(--ip-primary);
  color: var(--ip-primary);
  transition: color var(--ip-transition), border-color var(--ip-transition);
}
/* Qui stava il fallback che dava stile alle label nude dentro .form-group,
   .mb-3, .control-label e .form-label — le quattro grafie con cui l'app
   scriveva un campo prima di unificarle su .ip-doc-field + .ip-doc-label.
   Le prime tre non esistono più in nessuna view; l'ultima, .mb-3 > label,
   restava agganciata solo alle checkbox .form-check rimaste dentro un .mb-3
   di spaziatura, a cui imponeva un 13px in grassetto che nessun'altra
   checkbox dell'app ha — l'esatto contrario dell'uniformita' che la regola
   voleva garantire. Non va sostituita: le label di campo hanno .ip-doc-label
   e le checkbox seguono il pattern Bootstrap.

   Nota di specificita', il motivo per cui questa regola andava tolta e non
   solo svuotata: .mb-3 > label vale 0-1-1 e batteva .form-badge (0-1-0), la
   variante compatta voluta per login e form brevi, che quindi non si vedeva
   mai dove serviva. */

/* .form-badge: etichetta compatta dei form compatti (login, serie di
   numerazione, conti, categorie…). Prima esisteva solo dentro
   .ip-auth-form-box: la stessa classe fuori dal login rendeva a 14px. */
.form-badge {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  color: var(--ip-text);
  margin-bottom: 6px;
}
/* Messaggio di validazione sotto un campo. Prima questo blocco era agganciato
   a .text-danger, che in Bootstrap e' una utility di colore: ogni elemento
   colorato di rosso nell'app ereditava display:block + 12px + margin-top.
   Effetti collaterali reali, non teorici: i <td> "importo negativo" di
   fatture/finance uscivano dalla riga (display:block su una cella), la voce
   "Logout" del menu utente rendeva a 12px contro i 14px delle altre, e nelle
   tabelle della dashboard la data scaduta spingeva a capo l'icona allungando
   la riga. Le ~50 view che mostrano un errore di validazione usano ora questa
   classe; .text-danger torna a essere solo un colore (BS5 la definisce con
   !important, quindi la tinta era comunque gia' la sua).
   NB: il colore qui vince perché .ip-field-error non e' una utility BS. */
.ip-field-error {
  display: block;
  font-size: var(--ip-text-sm);
  margin-top: 4px;
  color: var(--ip-danger);
}
/* -----------------------------------------------------------------------------
   Checkbox e radio — un controllo solo per tutta l'app
   -----------------------------------------------------------------------------
   Prima ne convivevano tre: il quadrato di Bootstrap sulle .form-check-input,
   il controllo di sistema tinto con accent-color sulle checkbox nude delle
   tabelle, e il misto delle due dentro le righe ripetibili. Cambiavano
   dimensione, raggio, colore della spunta e allineamento rispetto alla label,
   quindi la stessa azione ("seleziona") aveva tre aspetti a seconda della
   pagina.

   Qui il controllo e' disegnato una volta sola: appearance:none azzera il
   rendering nativo e la spunta e' un SVG inline (background-image), l'unico
   modo affidabile di decorare un <input>, che essendo elemento sostituito non
   ha pseudo-elementi. */
/* Anche le due varianti con attributo: Bootstrap scrive
   .form-check-input[type="checkbox"] (0-2-0), che batte input[type="checkbox"]
   (0-1-1) e teneva il raggio a 0.25em = 3.5px sui quadrati da 16px. */
.form-check-input,
.form-check-input[type="checkbox"],
.form-check-input[type="radio"],
input[type="checkbox"],
input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--ip-border-field);
  border-radius: 4px;
  background-color: var(--ip-bg-card);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  cursor: pointer;
  vertical-align: -3px;
  transition: background-color var(--ip-transition),
              border-color var(--ip-transition),
              box-shadow var(--ip-transition);
  print-color-adjust: exact;
}
input[type="radio"],
.form-check-input[type="radio"] { border-radius: 50%; }

.form-check-input:hover:not(:disabled),
input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) { border-color: var(--ip-border-strong-hover); }

.form-check-input:checked,
input[type="checkbox"]:checked,
input[type="radio"]:checked {
  background-color: var(--ip-primary);
  border-color: var(--ip-primary);
}
.form-check-input:checked[type="checkbox"],
input[type="checkbox"]:checked {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2.2' d='m4 8.2 2.6 2.6L12 5.4'/%3e%3c/svg%3e");
}
.form-check-input:checked[type="radio"],
input[type="radio"]:checked {
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3ccircle cx='8' cy='8' r='3.2' fill='%23fff'/%3e%3c/svg%3e");
}
/* Spunta parziale delle liste con "seleziona tutto": senza, una selezione a
   meta' si legge come nessuna selezione.
   L'attributo nel selettore non e' decorativo: Bootstrap scrive
   .form-check-input[type="checkbox"]:indeterminate, e senza lo stesso peso
   la casella restava blu di sistema. */
.form-check-input[type="checkbox"]:indeterminate,
input[type="checkbox"]:indeterminate {
  background-color: var(--ip-primary);
  border-color: var(--ip-primary);
  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-width='2.2' d='M4.5 8h7'/%3e%3c/svg%3e");
}
.form-check-input:focus-visible,
input[type="checkbox"]:focus-visible,
input[type="radio"]:focus-visible {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
  outline: none;
}
.form-check-input:disabled,
input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  background-color: var(--ip-bg);
  border-color: var(--ip-border);
  cursor: not-allowed;
  opacity: .65;
}

/* La riga "controllo + etichetta" e' una sola: flex con gap, invece del
   padding-left compensato da un margine negativo (il float di Bootstrap), che
   nelle celle strette mandava l'etichetta sotto il quadrato. */
.form-check {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  min-height: 0;
  margin: 0;
  padding-left: 0;
}
.form-check + .form-check { margin-top: 8px; }
/* Aiuto sotto una checkbox: .form-check e' una riga flex, quindi il paragrafo
   di spiegazione ci finiva dentro come terzo elemento, di fianco all'etichetta
   invece che sotto — con l'etichetta schiacciata su due righe e il testo lungo
   che le rubava meta' riga. Va a capo e si allinea all'etichetta (16px della
   casella + 8px di gap). */
.form-check { flex-wrap: wrap; }
.form-check > .ip-field-help,
.form-check > .ip-number-help {
  flex: 0 0 100%;
  margin-left: 24px;
}
.form-check.form-check-inline {
  display: inline-flex;
  margin-right: 1rem;
}
.form-check .form-check-input { float: none; margin: 0; }
.form-check-label {
  margin: 0;
  font-size: var(--ip-text-base);
  line-height: 1.4;
  color: var(--ip-text);
  cursor: pointer;
}
.form-check-input:disabled ~ .form-check-label {
  color: var(--ip-text-muted);
  cursor: not-allowed;
}
/* -----------------------------------------------------------------------------
   8. Alert — border-left accent per scansione veloce del tipo
   -------------------------------------------------------------------------- */

.alert {
  border-radius: var(--ip-radius);
  padding: 12px 16px 12px 14px;
  border: 1px solid transparent;
  border-left-width: 3px;
  font-size: var(--ip-text-md);
}
/* Il filo a sinistra usa il colore di stato pieno, il testo la sua variante
   -text: l'ambra #F89406 sul fondo crema dell'avviso faceva 2,1:1 e gli
   avvisi di Utile e imposte, RiBa e modifica fattura erano i testi meno
   leggibili dell'app proprio dove dicono qualcosa di importante. */
.alert-success { background: var(--ip-success-bg); border-left-color: var(--ip-success); color: var(--ip-success-text); }
.alert-info    { background: var(--ip-info-bg);    border-left-color: var(--ip-info);    color: var(--ip-info-text); }
.alert-warning { background: var(--ip-warning-bg); border-left-color: var(--ip-warning); color: var(--ip-warning-text); }
.alert-danger  { background: var(--ip-danger-bg);  border-left-color: var(--ip-danger);  color: var(--ip-danger-text); }
/* Entrata dolce degli alert (toast di conferma/errore compresi): il messaggio
   non "appare" bruscamente. La guardia prefers-reduced-motion la neutralizza. */
@keyframes ip-alert-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.alert {
  animation: ip-alert-in 180ms ease both;
}
/* -----------------------------------------------------------------------------
   9. Empty states — il momento in cui il vuoto diventa invito
   -------------------------------------------------------------------------- */

/* Quando una tabella è vuota: niente riga piattissima. Spazio + tono + invito. */
.table > tbody > tr.empty-row > td {
  text-align: center;
  padding: 48px 16px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-md);
}
.table > tbody > tr.empty-row > td .empty-icon {
  display: block;
  font-size: 30px;
  margin-bottom: 10px;
}
/* L'opacità e il colore dell'icona sono definiti una sola volta in sezione 5
   (tr.empty-row .empty-icon: primary, 0.5): qui restano solo forma e spazio.
   Anche il CTA è unico (sezione 5, chip con bordo): il vecchio blocco qui
   sotto lo ridefiniva come link semplice con margini diversi. */
/* Empty states: stili qualsiasi td[colspan] usato come stato vuoto, in modo che
   anche le view senza classi esplicite abbiano un invito leggibile e coerente.
   Le view esistenti usano variante: td text-muted (senza class), td text-muted
   text-center, o semplicemente td con messaggio. Copriamo tutti i casi. */
.table > tbody > tr > td[colspan]:only-child {
  text-align: center;
  padding: 40px 16px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-md);
}
/* L'icona non viene più iniettata automaticamente per ogni td[colspan] vuoto
   (iniettava "utenti" anche su fatture/pagamenti/attività vuote). Le view che
   vogliono un'icona contestuale usano .empty-row con markup esplicito. */

/* -----------------------------------------------------------------------------
   10. Topbar e sidebar — la navbar scura legacy e l'icon-rail sono stati
   sostituiti dalla topbar chiara + sidebar a gruppi (markup in
   layout/base.php, stili più sotto in questo file). Il blocco CSS legacy
   della navbar scura è stato rimosso: qualunque residuo .navbar nelle view
   ricade sui default Bootstrap. L'avatar utente .ip-nav-avatar resta usato
   nella topbar (ristilizzato qui sotto).
   -------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   11. Pagine auth — il login è stato ridisegnato come pannello doppio
   (brand + modulo), sezione più sotto. Il CSS legacy #login è stato
   rimosso: il markup nuovo usa .ip-auth-shell / .ip-auth-card.
   -------------------------------------------------------------------------- */

/* -----------------------------------------------------------------------------
   12. Utilità — spaziature e micro-dettato
   -------------------------------------------------------------------------- */

/* Margin standard per form importi (evita .amount inline */
.input-group .form-control.amount { text-align: right; }
/* Spaziatura coerente fra pannelli in una sezione multi-panel */
#content > .row + .row,
#content .panel + .form-actions,
#content .card + .form-actions { margin-top: 4px; }
/* Azione rapida dashboard: distanziamento coerente dei bottoni in card */
.panel .panel-body .btn-default.btn-sm,
.panel .panel-body .btn-secondary.btn-sm,
.card .card-body .btn-default.btn-sm,
.card .card-body .btn-secondary.btn-sm { margin: 2px 4px 2px 0; }
/* Options dropdown globale: stile ghost coerente con il pulsante "Altro"
   dell'headerbar, e larghezza minima per il menu. */
.options.btn-group > .btn-secondary.dropdown-toggle {
  background: transparent;
  border: 1px solid var(--ip-border);
  color: var(--ip-text-muted);
  box-shadow: none;
  transition: background-color var(--ip-transition), border-color var(--ip-transition), color var(--ip-transition);
}
.options.btn-group > .btn-secondary.dropdown-toggle:hover,
.options.btn-group > .btn-secondary.dropdown-toggle:focus-visible {
  background: var(--ip-hover);
  border-color: var(--ip-border-strong);
  color: var(--ip-text);
}
.options.btn-group > .btn-secondary.dropdown-toggle:focus-visible {
  box-shadow: var(--ip-shadow-focus);
}
.options.btn-group .dropdown-menu { min-width: 180px; }
.options.btn-group .dropdown-menu > li > a > i.fa.fa-margin { width: 16px; text-align: center; }
.options.btn-group .dropdown-menu > li > form { margin: 0; }
/* Rosso solo dove l'azione distrugge qualcosa. Il rosso era sul selettore di
   base perché lì .dropdown-button era sempre e solo "Elimina"; ora che il menu
   contiene anche copia e nota di credito, tingerle tutte di rosso le faceva
   sembrare tutte pericolose. La conferma è il segnale che distingue le due
   famiglie: le azioni distruttive la chiedono, le altre no. */
.dropdown-menu .dropdown-button[data-bs-toggle="confirm"] { color: var(--ip-danger); }
.dropdown-menu .dropdown-button[data-bs-toggle="confirm"]:hover { background: var(--ip-danger-bg); }
/* Tabella dentro #content.table-content: .table-responsive senza bordo extra
   (padding gestito da #content.table-content qui sopra, sezione 2). */
.table-content .table-responsive { border: 0; border-radius: 0; }
/* Lo shim per le linguette legacy .nav-tabs e' stato rimosso: le uniche due
   schermate a schede (impostazioni e scheda cliente) usano ora il componente
   .ip-tabs della sezione 37, emesso da layout/tabs.php, e nessuna view emette
   più quel markup. */
/* Voci dei menu a tendina. Nell'app convivono tre forme di markup: l'<a> nudo
   dentro <li> (forma legacy CI3, usata da tutti i menu "Opzioni" delle tabelle),
   .dropdown-item e .dropdown-button (quest'ultimo è un <button> dentro un
   <form> per le azioni POST, quindi non è figlio diretto del <li>). Sono lo
   stesso componente visivo, quindi condividono un'unica regola: prima la forma
   legacy restava con padding e colori di default Bootstrap mentre il menu
   utente in navbar era gia' ridisegnato, e i due menu non si somigliavano; e i
   .dropdown-button (dietro <form>) non venivano presi dal selettore > li > e
   restavano con il vecchio padding 3px 20px, disallineati dalle voci vicine. */
.dropdown-menu > li > a:not(.dropdown-button),
.dropdown-menu > li > a.dropdown-item,
.dropdown-menu .dropdown-button,
.dropdown-menu > .dropdown-item {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* I <button> sono form control: si dimensionano sul contenuto anche da
     display:flex, mentre gli <a> riempiono il <li>. Senza questo, la riga
     "Elimina" era larga la meta' del menu e l'evidenziazione al passaggio del
     mouse copriva solo il testo — con l'effetto che voci lunghe sembravano
     piene e voci corte no. */
  width: 100%;
  gap: var(--ip-space-4);
  padding: 7px 10px;
  clear: both;
  border-radius: var(--ip-radius-sm);
  font-weight: 400;
  color: var(--ip-text);
  text-align: inherit;
  text-decoration: none;
  white-space: nowrap;
  background: none;
  border: 0;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.dropdown-menu > li > a:not(.dropdown-button):hover,
.dropdown-menu > li > a:not(.dropdown-button):focus-visible,
.dropdown-menu .dropdown-button:hover,
.dropdown-menu > .dropdown-item:hover {
  color: var(--ip-text);
  background-color: var(--ip-hover);
}
/* Voci distruttive (Elimina/Logout) */
.dropdown-menu > li > a.text-danger:hover,
.dropdown-menu .dropdown-button.text-danger:hover,
.dropdown-menu > .dropdown-item.text-danger:hover {
  background-color: var(--ip-danger-bg);
  color: var(--ip-danger-text);
}
/* Icone incolonnate: fa-user e fa-power-off hanno larghezze diverse, senza
   larghezza fissa le etichette non partivano allineate. */
.dropdown-menu > li > a .fa,
.dropdown-menu .dropdown-button .fa,
.dropdown-menu > .dropdown-item .fa {
  flex: 0 0 auto;
  width: 15px;
  margin-right: 0;
  text-align: center;
  opacity: .75;
}
/* Shim: .dropdown-divider legacy <li class="dropdown-divider"> deve essere <hr> */
.dropdown-menu > li.dropdown-divider {
  height: 0;
  margin: 4px 0;
  overflow: hidden;
  border-top: 1px solid var(--ip-border-soft);
  list-style: none;
}
/* -----------------------------------------------------------------------------
   12a. IPSelect — select con ricerca (componente proprio, vedi ip-components.js)
   --------------------------------------------------------------------------
   Sostituisce Select2. Il <select> nativo resta nel DOM (invio form + handler
   'change' esistenti) ma è visivamente nascosto; il controllo visibile è un
   <button> che replica esattamente .form-control, così un select e un input di
   testo affiancati sono indistinguibili per altezza, bordo, hover e focus.
   -------------------------------------------------------------------------- */

.ip-select {
  position: relative;
  display: block;
  width: 100%;
}
/* Il select nativo resta accessibile ai form ma fuori dal flusso visivo.
   display:none lo escluderebbe dalla validazione HTML5 (:invalid, required). */
.ip-select__native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  opacity: 0;
  pointer-events: none;
}
.ip-select__control {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  width: 100%;
  height: calc(1.5em + .75rem + 2px);
  padding: .375rem .75rem;
  font-family: inherit;
  font-size: var(--ip-text-md);
  line-height: 1.5;
  color: var(--ip-text);
  text-align: left;
  background-color: var(--ip-bg-card);
  /* Stesso bordo degli input nativi: e' un controllo di modulo, non un
     separatore decorativo (WCAG 1.4.11). */
  border: 1px solid var(--ip-border-field);
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition), background-color var(--ip-transition);
}
.ip-select__control:hover { border-color: var(--ip-border-hover); }
.ip-select__control:focus-visible,
.ip-select.is-open .ip-select__control {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
  outline: none;
}
.ip-select__control:disabled {
  background-color: var(--ip-bg);
  color: var(--ip-text-muted);
  cursor: not-allowed;
}
.ip-select--sm .ip-select__control {
  height: calc(1.5em + .5rem + 2px);
  padding: .25rem .5rem;
  font-size: var(--ip-text-base);
}
.ip-select__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ip-select__value.is-placeholder { color: var(--ip-text-muted); }
.ip-select__arrow {
  flex: 0 0 auto;
  display: inline-flex;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
  transition: transform var(--ip-transition);
}
.ip-select.is-open .ip-select__arrow { transform: rotate(180deg); }
.ip-select__clear {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-2xs);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-select__clear:hover { background: var(--ip-hover); color: var(--ip-danger); }
/* Pannello: in position fixed e agganciato al body (o alla modale), così non
   viene tagliato da contenitori con overflow. */
.ip-select-panel {
  position: fixed;
  z-index: var(--ip-z-toast);
  display: none;
  max-width: calc(100vw - 16px);
  padding: 4px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
}
.ip-select-panel.is-open { display: block; }
.ip-select-panel__search {
  padding: 4px 4px 6px;
  border-bottom: 1px solid var(--ip-border-soft);
  margin-bottom: 4px;
}
.ip-select-panel__input {
  width: 100%;
  padding: .375rem .625rem;
  font-family: inherit;
  font-size: var(--ip-text-base);
  color: var(--ip-text);
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition);
}
.ip-select-panel__input:focus {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
  outline: none;
}
.ip-select-panel__list {
  max-height: 260px;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.ip-select-option {
  padding: 7px 10px;
  font-size: var(--ip-text-base);
  color: var(--ip-text);
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* .is-active = riga sotto cursore/tastiera, .is-selected = valore corrente. */
.ip-select-option.is-active { background: var(--ip-hover); }
.ip-select-option.is-selected {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  font-weight: 500;
}
.ip-select-option.is-selected.is-active { background: var(--ip-primary-hover); }
.ip-select-option.is-disabled {
  color: var(--ip-text-muted);
  cursor: not-allowed;
  opacity: .6;
}
.ip-select-group {
  padding: 8px 10px 4px;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ip-text-muted);
}
.ip-select-panel__empty {
  padding: 12px 10px;
  font-size: var(--ip-text-base);
  color: var(--ip-text-muted);
  text-align: center;
}
/* -----------------------------------------------------------------------------
   12b. IPDatepicker — calendario (componente proprio, vedi ip-components.js)
   --------------------------------------------------------------------------
   Sostituisce bootstrap-datepicker. L'input resta una normale .form-control
   (si può ancora digitare la data); il pannello è nostro e usa i token.
   -------------------------------------------------------------------------- */

.ip-dp {
  position: fixed;
  z-index: var(--ip-z-toast);
  display: none;
  width: 268px;
  padding: 10px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
}
.ip-dp.is-open { display: block; }
.ip-dp__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-2);
  margin-bottom: 8px;
}
.ip-dp__nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  padding: 0;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  background: transparent;
  border: 0;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-dp__nav:hover { background: var(--ip-hover); color: var(--ip-text); }
.ip-dp__title {
  flex: 1 1 auto;
  padding: 4px 8px;
  font-family: inherit;
  font-size: var(--ip-text-base);
  font-weight: 600;
  color: var(--ip-text);
  background: transparent;
  border: 0;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  text-transform: capitalize;
  transition: background-color var(--ip-transition);
}
.ip-dp__title:hover { background: var(--ip-hover); }
.ip-dp__dow,
.ip-dp__grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: var(--ip-space-1);
}
.ip-dp__dow {
  margin-bottom: 4px;
}
.ip-dp__dow span {
  padding: 4px 0;
  font-size: var(--ip-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ip-text-muted);
  text-align: center;
}
.ip-dp__day {
  height: 32px;
  padding: 0;
  font-family: inherit;
  font-size: var(--ip-text-base);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
  background: transparent;
  border: 0;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-dp__day:hover { background: var(--ip-hover); }
.ip-dp__day.is-outside { color: var(--ip-text-muted); opacity: .5; }
.ip-dp__day.is-today {
  font-weight: 700;
  box-shadow: inset 0 0 0 1px var(--ip-border);
}
.ip-dp__day.is-selected {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  font-weight: 600;
  box-shadow: none;
  opacity: 1;
}
.ip-dp__day.is-selected:hover { background: var(--ip-primary-hover); }
.ip-dp__months {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--ip-space-2);
}
.ip-dp__month {
  padding: 10px 0;
  font-family: inherit;
  font-size: var(--ip-text-base);
  color: var(--ip-text);
  background: transparent;
  border: 0;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  text-transform: capitalize;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-dp__month:hover { background: var(--ip-hover); }
.ip-dp__month.is-selected { background: var(--ip-primary); color: var(--ip-on-primary); font-weight: 600; }
.ip-dp__foot {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--ip-border-soft);
  text-align: center;
}
.ip-dp__today {
  padding: 5px 12px;
  font-family: inherit;
  font-size: var(--ip-text-sm);
  font-weight: 500;
  color: var(--ip-text-muted);
  background: transparent;
  border: 0;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-dp__today:hover { background: var(--ip-hover); color: var(--ip-text); }
/* -----------------------------------------------------------------------------
   12c. IPFile — input file personalizzato (sostituisce il nativo del browser)
   --------------------------------------------------------------------------
   L'input file nativo viene nascosto visivamente ma resta nel DOM per
   l'invio del form. Il controllo visibile è un pulsante + area trascinamento
   che mostra il nome del file selezionato e permette la rimozione. */
.ip-file {
  position: relative;
  display: block;
  width: 100%;
}
.ip-file__native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
  opacity: 0;
  pointer-events: none;
}
.ip-file__control {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  width: 100%;
  min-height: calc(1.5em + .75rem + 2px);
  padding: .375rem .75rem;
  font-family: inherit;
  font-size: var(--ip-text-md);
  line-height: 1.5;
  color: var(--ip-text);
  text-align: left;
  background-color: var(--ip-bg-card);
  border: 1px solid var(--ip-border-field);
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition), background-color var(--ip-transition);
}
.ip-file__control:hover { border-color: var(--ip-border-hover); }
.ip-file__control:focus-visible,
.ip-file.is-focused .ip-file__control {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
  outline: none;
}
.ip-file__control:disabled {
  background-color: var(--ip-bg);
  color: var(--ip-text-muted);
  cursor: not-allowed;
}
.ip-file__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ip-text-muted);
}
.ip-file__name.has-file { color: var(--ip-text); }
.ip-file__btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ip-space-2);
  padding: .25rem .5rem;
  font-size: var(--ip-text-base);
  font-weight: 500;
  color: var(--ip-primary);
  background: var(--ip-primary-soft);
  border: 1px solid transparent;
  border-radius: var(--ip-radius-sm);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-file__btn:hover { background: var(--ip-primary); color: var(--ip-on-primary); }
.ip-file__btn:disabled { opacity: .5; cursor: not-allowed; }
.ip-file__clear {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  cursor: pointer;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-file__clear:hover { background: var(--ip-hover); color: var(--ip-danger); }
.ip-file__clear:disabled { opacity: .5; cursor: not-allowed; }
.ip-file__help {
  display: block;
  margin-top: 4px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}

/* -----------------------------------------------------------------------------
   14. Submenu mobile — wrapper collassabile per filtri di stato su mobile
   -------------------------------------------------------------------------- */

#submenu {
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border);
}
/* Pannello chiuso = contenuto nascosto: senza questa regola restava il solo
   bordo inferiore, una riga orfana appiccicata sotto l'headerbar. Sulle liste
   con la barra filtri (fatture, spese, preventivi) il pannello è però aperto
   anche senza .show — lo forza il CSS da 992px in su — quindi lì vale solo
   su telefono. */
#submenu:has(> .collapse:not(.show)):not(.ip-filter-panel) { border-bottom: 0; }
@media (max-width: 991.98px) {
  #submenu.ip-filter-panel:has(> .collapse:not(.show)) { border-bottom: 0; }
}
#submenu > .collapse {
  /* Bootstrap .collapse usa height:0; lasciamo padding solo quando espanso */
  padding: 0;
}
#submenu .submenu-row {
  padding: 10px 16px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-3);
}
#submenu .submenu-row .btn-group.index-options {
  flex: 1 1 auto;
}
.submenu-toggle .fa { margin-right: 4px; }
/* Su lg+ il pannello filtri (fatture, spese, preventivi) non ha un pulsante
   che lo apra o lo chiuda — il toggle è d-lg-none — quindi va tenuto forzato
   aperto: l'headerbar non ne ha più una copia e chiuderlo lascerebbe la lista
   senza comandi di filtro. */
@media (min-width: 992px) {
  #submenu > .collapse { display: none !important; }
  #submenu.ip-filter-panel > .collapse {
    display: block !important;
    height: auto !important;
    visibility: visible !important;
  }
}
/* -----------------------------------------------------------------------------
   15. Empty-state condiviso — per report non ancora eseguiti e liste vuote
   -------------------------------------------------------------------------- */

.empty-state .empty-body {
  text-align: center;
  padding: 56px 16px 48px;
  color: var(--ip-text-muted);
}
/* #content.table-content ha padding verticale 0: senza margine, la card
   vuota aderirebbe all'headerbar. Stesso distacco delle card-lista. */
.empty-state.card { margin: 18px 0; }
.empty-state .empty-state-icon {
  display: block;
  font-size: 30px;
  margin-bottom: 12px;
}
/* L'opacità e il colore dell'icona sono definiti in sezione 5 (0.4, primary). */
.empty-state .empty-state-text {
  font-size: var(--ip-text-md);
  margin: 0 auto;
  max-width: 60ch;
}
/* Il pulsante d'azione sotto il testo: senza distacco toccava l'ultima riga
   (Importa dati, prima importazione). */
.empty-state .empty-state-text + .btn { margin-top: 16px; }
/* -----------------------------------------------------------------------------
   16. Confirm modal — icona + corpo coerenti
   -------------------------------------------------------------------------- */
.confirm-modal-icon { font-size: 1.4rem; }
.confirm-modal-icon-danger { color: var(--ip-danger); }
#ip-confirm-modal .modal-dialog { max-width: 460px; }
#ip-confirm-modal .modal-body { padding: 1.25rem 1.5rem; }
#ip-confirm-modal .modal-footer { padding: 0.75rem 1.5rem; }
/* Colore dell'icona guidato dallo stato: neutro di default, rosso solo
   quando il delegate aggiunge .confirm-modal-icon-danger (azione distruttiva).
   Prima c'era un override globale che rendeva l'icona sempre rossa anche per
   conferme non-danger (es. " Genera PDF?"). */

/* -----------------------------------------------------------------------------
   17. Link — niente underline globale (Bootstrap 5 imposta a{text-decoration:
   underline} di default). Per un'app di fatturazione l'underline su ogni link
   risulta rumoroso: lo rimuoviamo e manteniamo il colore primary come
   affordance, con hover che scurisce verso primary-hover. Gli stati che
   vogliono dare affordance esplicita (btn-link-secondary:hover) ridichiano
   l'underline localmente. Il CTA dello stato vuoto è un chip con bordo (sezione
   5): l'hover cambia il fondo, non aggiunge sottolineatura.
   -------------------------------------------------------------------------- */
a,
a:hover,
a:focus {
  text-decoration: none;
}
/* -----------------------------------------------------------------------------
   18. Tabelle dashboard — padding coeso, footer "Vedi tutti" leggibile,
   supporto .danger su <tr> (legacy BS3), e angoli arrotondati delle card
   rispettati dal contenuto tabellare.
   --------------------------------------------------------------------------
   Le card del dashboard racchiudono .table-responsive senza .card-body: la
   tabella aderisce al .card-header. Con .card senza overflow:hidden il
   background delle righe striped "scavalca" i bordi arrotondati inferiori.
   Le card del dashboard non ospitano dropdown che debordano, quindi possiamo
   applicare overflow:hidden in sicurezza.
   -------------------------------------------------------------------------- */
.dashboard-content .card {
  overflow: hidden;
}
/* Header + celle: padding verticale più compatto per stare dentro la card
   senza gonfiarla. Orizzontale ridotto a 12px. */
.dashboard-content .table > thead > tr > th {
  /* 14px come la testata della finestra (.ip-window-header): il primo
     testo di colonna cade sotto il titolo della finestra. */
  padding: 8px 14px;
}
.dashboard-content .table > tbody > tr > td {
  padding: 8px 14px;
}
/* Footer "Vedi tutti": riga finale dei listing recenti (quotes/invoices).
   Separatore leggero (border-top gia' presente da .table td), testo muted,
   hover accent primary. L'icona arrow-right segue il colore del link. */
.dashboard-content .table > tbody > tr:last-child > td.text-end {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  background: var(--ip-bg);
}
.dashboard-content .table > tbody > tr:last-child > td.text-end a,
.dashboard-content .table > tbody > tr:last-child > td.text-end a .fa {
  color: var(--ip-primary);
  font-weight: 600;
}
.dashboard-content .table > tbody > tr:last-child > td.text-end a:hover,
.dashboard-content .table > tbody > tr:last-child > td.text-end a:focus {
  color: var(--ip-primary-hover);
}
.dashboard-content .table > tbody > tr:last-child > td.text-end .fa-arrow-right {
  margin-left: 4px;
  transition: transform var(--ip-transition);
}
.dashboard-content .table > tbody > tr:last-child > td.text-end a:hover .fa-arrow-right {
  transform: translateX(2px);
}
/* Righe overview (quote/invoice status totals): stile "elenco importi",
   senza zebratura per un look più pulito nelle card di riepilogo. */
.dashboard-content .table > tbody > tr:hover > * {
  background: var(--ip-primary-soft);
}
/* Legacy: <tr class="danger"> (BS3) sulla riga "Fatture scadute" dell'Invoice
   Overview. BS5 non ha questa classe via riga => senza regola la riga
   restava identica alle altre, priva del segnale visivo di pericolo. */
.dashboard-content .table > tbody > tr.danger > td {
  background: var(--ip-danger-bg);
  color: var(--ip-danger-text);
}
/* Utility di colore di Bootstrap sui token dell'app. .text-danger & co.
   restavano i colori del framework (#dc3545, #198754…), con un !important
   che vinceva anche sulle regole più specifiche qui sotto: nella tabella
   scadute della bacheca il rosso era quello di Bootstrap, a 4,4:1 sul fondo
   della riga. Stessa forza (!important) per poterle davvero sostituire. */
.text-danger  { color: var(--ip-danger-text) !important; }
.text-warning { color: var(--ip-warning-text) !important; }
.text-success { color: var(--ip-success-text) !important; }
.text-info    { color: var(--ip-info-text) !important; }
.dashboard-content .table > tbody > tr.danger > td a,
.dashboard-content .table > tbody > tr.danger > td .text-danger {
  color: var(--ip-danger-text);
  font-weight: 600;
}
/* Hover su riga danger: resta danger-soft (il default indigo sarebbe fuori
   tono su una riga di allarme). */
.dashboard-content .table-hover > tbody > tr.danger:hover > td {
  background: var(--ip-danger-soft);
}
.dashboard-content .table-hover > tbody > tr.danger:hover > td a {
  color: var(--ip-danger-hover);
}
/* -----------------------------------------------------------------------------
   19. Dashboard — card KPI, header con icona, quick actions a tile, chip
   cliente nelle tabelle "recenti".
   -----------------------------------------------------------------------------
   Obiettivo: dare alle card una gerarchia visiva più immediata (icona =
   categoria a colpo d'occhio) senza toccare il resto del tema — tutto scoped
   a .card-header-icon / .quick-action-* / .ip-avatar-sm per non avere effetti
   collaterali fuori dal dashboard.
   -------------------------------------------------------------------------- */

/* Le .kpi-card* che stavano qui sono state rimosse: la dashboard e' passata
   alle finestre .ip-window (sezione 22b) e nessuna view o script emetteva piu'
   quel markup. L'equivalente odierno di .kpi-card--alert e' .ip-window--alert.
   Restano invece .card-header-icon e .ip-avatar-sm, ancora in uso. */

/* Icona nell'header di sezione: badge colorato compatto — dà identità visiva
   immediata a ogni pannello (Preventivi, Fatture, Attività...) senza
   appesantire l'header.
   Dimensione unica (30px) condivisa con .ip-table-card-title e
   .ip-report-card__icon: la "pillola di identità" è lo stesso linguaggio
   visivo su ogni tipo di card dell'app, non un formato a caso per header. */
.card-header-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--ip-radius-sm);
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-base);
  margin-right: 6px;
  vertical-align: middle;
}
/* Chip cliente nelle tabelle "recenti" (Preventivi/Fatture/Progetti):
   iniziale in cerchio + nome, più scansionabile del solo testo su liste
   con molte righe — stesso linguaggio visivo di .ip-nav-avatar in navbar. */
.ip-avatar-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ip-primary-soft);
  background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.05));
  color: var(--ip-primary);
  font-size: var(--ip-text-2xs);
  font-weight: 700;
  line-height: 1;
  margin-right: 7px;
  vertical-align: middle;
  text-transform: uppercase;
}
/* -----------------------------------------------------------------------------
   19b. Dashboard — card breakdown + tabelle recenti (ottimizzazione card):
   - header con icona + titolo + selettore periodo inline / link "view_all"
   - riga di breakdown mostra badge conteggio + importo (più scansionabile
     del solo numero)
   - riga fattura/preventivo evidenziata se scaduto
   - a mobile le tabelle "recenti" collassano in card-row tramite data-label
   - subtext KPI separa periodo e count per gerarchia visiva
   -------------------------------------------------------------------------- */

/* Subtext KPI: periodo + count affiancati con separatore intelligente.
   count in badge soft per dare peso visivo alla quantità. */
.ip-kpi-period {
  display: inline-block;
  margin-right: 6px;
  /* Cifre e periodi allineati in colonna: stesso trattamento gia' riservato
     agli importi, così periodi come "2024" o "Q1" non ballano tra le card. */
  font-variant-numeric: tabular-nums;
}
.ip-kpi-count {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  /* "1 Fattura" andava a capo dentro il pill, spezzando la pastiglia in due
     righe quando la colonna KPI si stringe. */
  white-space: nowrap;
}
/* Make KPI card value a clickable link: mantiene le dimensioni e il peso
   di .card-value ma diventa interattivo. */
.ip-window-body .card-value {
  text-decoration: none;
  cursor: pointer;
}
.ip-window-body .card-value:hover {
  color: var(--ip-primary-hover);
  text-decoration: underline;
}
/* Header delle card breakdown: icona + titolo a sinistra, selettore periodo
   o link "view_all" a destra. flex-wrap per non rompere su mobile medio. */
.ip-breakdown-header {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  flex-wrap: wrap;
}
.ip-breakdown-title {
  font-weight: 600;
  margin-right: auto;
}
.ip-view-all-link {
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
}
.ip-view-all-link:hover {
  text-decoration: underline;
  color: var(--ip-primary-hover);
}
/* Selettore periodo inline: rimosso con i selettori della Panoramica —
   il periodo ora è l'anno del filtro globale in topbar. */
/* Breakdown table: riga "stato" con badge conteggio + importo.
   Le righe vuote (num_total = 0) non sono filtrate in view: restano
   visibili, ridotte a grigetto per non pesare sugli stati attivi. */
.ip-overview-table .ip-amount-cell {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
  justify-content: flex-end;
  width: 100%;
}
.ip-overview-table .ip-count-badge {
  font-size: var(--ip-text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
  background: var(--ip-bg, #f6f6f7);
  border: 1px solid var(--ip-border-soft);
  padding: 2px 7px;
}
/* Righe di stato con conteggio 0: tenute visibili (l'utente vuole vedere
   tutti gli stati) ma spente per non pesare sulla gerarchia visiva degli
   stati attivi. Il link resta cliccabile. A spegnerle è il colore muted e il
   peso normale, non più un'opacità sopra il muted: le due cose insieme
   portavano il nome dello stato a 2,2:1 (2,9:1 in tema scuro), cioè un link
   che si fatica a leggere. Il muted da solo resta sopra 4,5:1 e lo stacco
   dalle righe attive, che sono in grassetto e nel colore pieno, si vede lo
   stesso. */
.ip-overview-table tbody tr.ip-status-empty > td {
  color: var(--ip-text-muted);
}
.ip-overview-table tbody tr.ip-status-empty > td a,
.ip-overview-table tbody tr.ip-status-empty .ip-amount-value {
  color: var(--ip-text-muted);
  font-weight: 400;
}
.ip-overview-table tbody tr.ip-status-empty:hover > td a { color: var(--ip-text); }
/* Anagrafiche: la stessa riga spenta, che finora era definita solo dentro
   .ip-overview-table (le tabelle "recenti" della dashboard) e quindi sui
   fornitori non si vedeva: la classe c'era, il colore no. Vale per ogni lista
   dentro una ip-table-card, clienti e fornitori compresi. */
.ip-table-card tbody tr.ip-status-empty > td {
  color: var(--ip-text-muted);
}
.ip-table-card tbody tr.ip-status-empty > td a {
  color: var(--ip-text-muted);
  font-weight: 400;
}
.ip-table-card tbody tr.ip-status-empty:hover > td a { color: var(--ip-text); }
/* Righe "recenti" scadute: leggero sfondo rosso + accento sulla data.
   Definito qui sotto forma di utility opaca (la classe è aggiunta dal view). */
.ip-recent-table tbody tr.ip-row-overdue > td,
.ip-recent-table tbody tr.ip-row-expired > td {
  background-color: var(--ip-danger-bg);
}
.ip-recent-table tbody tr.ip-row-overdue:hover > td,
.ip-recent-table tbody tr.ip-row-expired:hover > td {
  background-color: color-mix(in srgb, var(--ip-danger-bg) 70%, var(--ip-bg-card));
}
/* Stessa evidenza nelle tabelle della scheda cliente (tab Fatture): le
   fatture da sollecitare si vedono a colpo d'occhio anche nella lista
   del singolo cliente. */
.ip-table-card tbody tr.ip-row-overdue > td {
  background-color: var(--ip-danger-bg);
}
.ip-table-card tbody tr.ip-row-overdue:hover > td {
  background-color: color-mix(in srgb, var(--ip-danger-bg) 70%, var(--ip-bg-card));
}
/* Righe "recenti" cliccabili per intero (dashboard: fatture e preventivi).
   Il link sul numero documento resta quello reale; queste regole servono solo
   a dichiarare che l'intera riga e' un bersaglio, altrimenti il click che
   funziona non si vede. L'hover di sfondo lo da' gia' .table-hover. */
/* Cliente su una riga sola nelle tabelle "recenti" della bacheca: la
   finestra è a mezza larghezza e la colonna resta sotto i 130 px, dove i
   nomi andavano a capo su tre o quattro righe. Il max-width: 0 sulla cella è
   il modo per far rispettare l'ellissi dentro una tabella a layout
   automatico; la larghezza vera la decide la tabella. Nome intero nel title. */
.ip-recent-table td:has(> .ip-client-chip__name) {
  max-width: 0;
  width: 40%;
  white-space: nowrap;
}
.ip-client-chip__name {
  display: inline-block;
  max-width: calc(100% - 28px);
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: middle;
}
.ip-recent-table tbody tr.ip-row-link {
  cursor: pointer;
}
.ip-recent-table tbody tr.ip-row-link:hover .ip-num {
  text-decoration: underline;
}
/* Focus da tastiera sul link interno: la riga si illumina come al passaggio
   del mouse, così chi naviga con Tab vede quale riga sta per aprire. */
.ip-recent-table tbody tr.ip-row-link:focus-within > td {
  background: var(--ip-primary-soft);
}
.ip-recent-table .ip-row-meta {
  display: inline-block;
  margin-left: 4px;
  font-size: var(--ip-text-xs);
  white-space: nowrap;
}
/* Segnale "scaduta" nella colonna scadenza: icona inline, non badge pieno.
   Il badge occupava una riga propria quando la colonna si stringeva, e la
   riga scaduta finiva più alta delle altre in tabella. */
/* La data scaduta e l'icona di segnalazione restano sulla stessa riga. */
.ip-due-cell { white-space: nowrap; }
.ip-overdue-flag {
  margin-left: 5px;
  font-size: var(--ip-text-xs);
  color: var(--ip-danger-text);
  vertical-align: baseline;
}
/* Mobile: le tabelle "recenti" collassano in card-row senza scroll
   orizzontale. Funziona via [data-label] emesso dal view: ogni td
   diventa una riga con la label a sinistra (data-label) e il valore
   a destra. thead nascosto perché ridondante. */
@media (max-width: 575.98px) {
  .ip-recent-table thead {
    display: none;
  }
  .ip-recent-table,
  .ip-recent-table tbody,
  .ip-recent-table tr,
  .ip-recent-table td {
    display: block;
    width: 100%;
  }
  .ip-recent-table tbody tr {
    border: 1px solid var(--ip-border-soft);
    border-radius: var(--ip-radius-sm);
    margin-bottom: 10px;
    padding: 6px 0;
    background: var(--ip-bg-card);
  }
  .ip-recent-table tbody tr:last-child {
    margin-bottom: 0;
  }
  .ip-recent-table tbody td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--ip-space-6);
    padding: 6px 12px;
    border-bottom: 1px dashed var(--ip-border-soft);
    text-align: right;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .ip-recent-table tbody td:last-child {
    border-bottom: none;
  }
  /* Etichetta campo via ::before, dal data-label: riconosce la riga
     senza duplicare traduzioni. */
  .ip-recent-table tbody td::before {
    content: attr(data-label);
    font-weight: 600;
    font-size: var(--ip-text-sm);
    color: var(--ip-text-muted);
    text-align: left;
    white-space: nowrap;
    flex: 0 0 auto;
  }
  /* La colonna amount deve rimanere allineata a destra; data-label a sx. */
  .ip-recent-table tbody td.amount,
  .ip-recent-table tbody td[class*="amount"] {
    justify-content: space-between;
  }
  /* Metadati inline (riga scadenza quote) vanno a capo su cella verticale:
     lo span meta diventa blocco sotto la data. */
  .ip-recent-table .ip-row-meta {
    display: block;
    margin-left: 0;
    margin-top: 2px;
    text-align: left;
  }
  /* Finestre KPI scendono a 1 da molto stretto: più leggibili su telefono
     piccolo (sovrascrive le 2 colonne del blocco precedente). */
  .ip-dash-windows {
    grid-template-columns: minmax(0, 1fr);
  }
}
.ip-chart-legend {
  display: inline-flex;
  gap: var(--ip-space-7);
  margin-left: auto;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  align-items: center;
}
.ip-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  white-space: nowrap;
}
.ip-chart-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  display: inline-block;
  background: var(--ip-primary);
}
.ip-chart-legend-dot--invoiced { background: var(--ip-info); }
.ip-chart-legend-dot--paid     { background: var(--ip-success); }
.ip-chart-body {
  padding: 12px 16px 14px;
}
/* Riepilogo totale periodo (sopra il grafico): mostra subito i totali
   6-mesi senza costringere l'utente a scorrere le barre col mouse. */
.ip-chart-summary {
  display: flex;
  gap: var(--ip-space-8);
  flex-wrap: wrap;
  margin-bottom: 10px;
  padding: 0 4px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-chart-summary-item {
  display: inline-flex;
  align-items: baseline;
  gap: var(--ip-space-3);
}
.ip-chart-summary-value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-strong);
}
.ip-chart-summary-item--invoiced .ip-chart-summary-value { color: var(--ip-info-text); }
.ip-chart-summary-item--paid     .ip-chart-summary-value { color: var(--ip-success-text); }
/* ---------- SVG del grafico a barre -------------------------------------- */
/* Il viewBox e' 600x190 e l'aspect di default e' "meet": con height fissa a
   200px il disegno veniva scalato sulla larghezza (~183px) e centrato, con una
   striscia morta sopra e sotto le barre. Legando l'altezza all'aspect del
   viewBox il grafico riempie esattamente il suo box a ogni larghezza. */
svg.ip-chart {
  width: 100%;
  height: auto;
  aspect-ratio: 600 / 190;
  display: block;
  /* Senza questo cap, su monitor larghi dove #main-content non ha un
     max-width, le due colonne di .ip-window-split (ricavi | costi) possono
     superare abbondantemente i 600px del viewBox: barre ed etichette
     scalano insieme (l'aspect-ratio le lega) e il grafico appare
     sproporzionato rispetto al resto della UI. 620px tiene il disegno alla
     sua scala di lettura naturale (poco sopra il viewBox 1:1) anche quando
     la colonna che lo ospita e' più larga; lo spazio in eccesso lo assorbe
     il margin auto qui sotto, restando centrato nella card invece di
     restare ancorato a sinistra. */
  max-width: 620px;
  margin-left: auto;
  margin-right: auto;
}
/* ---------- Donut composizione (scheda cliente) --------------------------- */
/* SVG inline: tre archi su circonferenza unica (stroke-dasharray), nessuna
   libreria esterna, coerente col resto dei grafici dell'app. Donut e legenda
   viaggiano affiancati a OGNI larghezza: la card convive con il grafico a
   barre accanto e, impilata in colonna, lasciava meta' card vuota sotto il
   cerchio; su schermi stretti e' il wrap a riportare la legenda sotto.
   Anello ispessito (+2px rispetto al valore precedente) per dare piu'
   presenza alla sua card dedicata. */
.ip-donut-wrap {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--ip-space-8);
  padding: 10px 6px;
  width: 100%;
}
svg.ip-donut {
  width: 100%;
  max-width: 180px;
  min-width: 148px;
  height: auto;
  display: block;
  flex: 1 1 160px;
  filter: drop-shadow(0 1px 6px rgba(0,0,0,.08));
}
html[data-theme="dark"] svg.ip-donut { filter: drop-shadow(0 1px 8px rgba(0,0,0,.30)); }
.ip-donut-total {
  fill: var(--ip-text-strong);
  font-size: var(--ip-text-4xl);
  font-weight: 700;
  font-family: var(--ip-display, Fraunces, serif);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.04em;
  line-height: 1;
}
.ip-donut-sub {
  fill: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
  font-weight: 500;
  font-family: var(--ip-font, Figtree, sans-serif);
  text-transform: none;
  letter-spacing: 0;
  opacity: .92;
}
/* La legenda occupa lo spazio rimasto accanto al cerchio: importi allineati
   a destra in colonna (tabular-nums) e quota percentuale di ogni fetta, che
   sull'arco si stima ma non si legge. */
.ip-donut-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1 1 180px;
  max-width: 300px;
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-donut-legend li {
  display: flex;
  align-items: baseline;
  gap: var(--ip-space-4);
  padding: 7px 8px;
  border-radius: var(--ip-radius-sm, 6px);
  transition: background .15s ease;
}
.ip-donut-legend li:hover { background: var(--ip-hover, #f4f4f5); }
html[data-theme="dark"] .ip-donut-legend li:hover { background: rgba(255,255,255,.06); }
.ip-donut-legend-label { flex: 1 1 auto; }
.ip-donut-legend b {
  color: var(--ip-text-strong);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  font-size: var(--ip-text-sm);
}
.ip-donut-share {
  flex: 0 0 auto;
  min-width: 3.8ch;
  text-align: right;
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--ip-hover, #f4f4f5);
}
html[data-theme="dark"] .ip-donut-share { background: rgba(255,255,255,.08); }
.ip-donut-dot {
  width: 11px;
  height: 11px;
  border-radius: 3px;
  display: inline-block;
  flex: 0 0 auto;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.08);
}
html[data-theme="dark"] .ip-donut-dot { box-shadow: inset 0 0 0 1px rgba(255,255,255,.12); }
.ip-donut-dot--paid     { background: var(--ip-success); }
.ip-donut-dot--open     { background: var(--ip-warning); }
.ip-donut-dot--overdue  { background: var(--ip-danger); }
/* L'arco sotto il puntatore si ispessisce (+4 su 24) e le altre fette si
   abbassano: su tre colori simili per luminosita' (e per chi non distingue
   i colori) il contrasto acceso/spento dice quale fetta si sta leggendo meglio
   della posizione. Solo gli archi hanno stroke-dasharray, il cerchio di fondo
   no: così il selettore li distingue senza classi extra. La riga di legenda
   corrispondente si accende in coppia via :has() — zero JS. */
svg.ip-donut circle[stroke-dasharray] { transition: stroke-width .15s ease, filter .15s ease, opacity .15s ease; }
svg.ip-donut circle[stroke-dasharray]:hover { stroke-width: 28; filter: brightness(1.06); }
svg.ip-donut:hover circle[stroke-dasharray]:not(:hover) { opacity: .35; }
.ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(2):hover) .ip-donut-legend li:nth-child(1),
.ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(3):hover) .ip-donut-legend li:nth-child(2),
.ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(4):hover) .ip-donut-legend li:nth-child(3) { background: var(--ip-hover, #f4f4f5); }
html[data-theme="dark"] .ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(2):hover) .ip-donut-legend li:nth-child(1),
html[data-theme="dark"] .ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(3):hover) .ip-donut-legend li:nth-child(2),
html[data-theme="dark"] .ip-donut-wrap:has(svg.ip-donut circle[stroke-dasharray]:nth-child(4):hover) .ip-donut-legend li:nth-child(3) { background: rgba(255,255,255,.06); }
/* Entrata discreta: le coppie traccia/incassato "crescono" dal basso mese
   per mese (sfasamento via --i inline) e il donut sfuma, per dare vita al
   riepilogo senza distrarre. Tutto neutralizzato se l'utente chiede
   reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
  .ip-chart-bar {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    animation: ipBarGrow .5s cubic-bezier(.22, .61, .36, 1) both;
  }
  svg.ip-donut { animation: ipDonutIn .5s ease-out both; }
}
@keyframes ipBarGrow { from { transform: scaleY(0); } }
@keyframes ipDonutIn { from { opacity: 0; transform: scale(.94); } }
.ip-chart-grid {
  stroke: var(--ip-border);
  stroke-width: 1;
  stroke-dasharray: 2 3;
  opacity: 0.7;
}
/* Etichette della scala dei valori (asse y) — 12px/600 leggibili anche
   sul fondo scuro: in dark il muted (#94a3b8 su #1c2128) scende sotto 4.5:1. */
.ip-chart-axis {
  fill: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  font-family: var(--ip-mono, monospace);
  font-variant-numeric: tabular-nums;
  user-select: none;
}
html[data-theme="dark"] .ip-chart-axis { fill: #cbd5e1; }
.ip-chart-baseline {
  stroke: var(--ip-border-strong);
  stroke-width: 1.5;
}
.ip-chart-bar {
  /* colore di default (sovrascritto dalle varianti sotto) */
  transition: opacity .15s ease;
}
.ip-chart-bar:hover {
  opacity: 0.78;
}
/* "Barra di incasso" (scheda cliente): la traccia e' il fatturato (tinta
   chiara dello stesso blu acciaio), il riempimento verde e' l'incassato.
   Il gap fra i due e' la quota ancora da riscuotere, leggibile a colpo
   d'occhio senza tooltip. La traccia resta sotto ma al 22%: sotto quella
   soglia il "non ancora incassato" spariva sul fondo della card. */
.ip-chart-bar--track { fill: var(--ip-info); fill-opacity: 0.22; }
.ip-chart-bar--paid  { fill: var(--ip-success); }
/* Coppia blu acciaio + verde: i colori della coppia fatturato/incassato,
   meno duri del nero pieno su fondo chiaro. La variante --invoiced serve
   ai grafici a barre affiancate (dashboard) dove fatturato e incassato
   non si sovrappongono. */
.ip-chart-bar--invoiced { fill: var(--ip-info); }
/* Il pallino legenda mostra la stessa tinta della traccia: se i due
   livelli di opacita' divergono, la legenda sembra descrivere un altro
   colore rispetto alle barre. */
.ip-chart-legend-dot--track { background: var(--ip-info); opacity: 0.45; }
.ip-chart-label {
  fill: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-family: var(--ip-font);
  font-weight: 600;
  user-select: none;
}
html[data-theme="dark"] .ip-chart-label { fill: #e2e8f0; }
.ip-chart-month:hover .ip-chart-label { fill: var(--ip-text-strong); }
html[data-theme="dark"] .ip-chart-month:hover .ip-chart-label { fill: #ffffff; }
/* Colonna "viva": al passaggio la banda di sfondo si tinge e il mese
   sotto le barre si scurisce, così occhio collega subito barre e mese
   senza aspettare i tooltip nativi. La banda sta sotto le barre (prima
   nel gruppo), che conservano i propri <title>. */
.ip-chart-col {
  fill: transparent;
  transition: fill .15s ease;
}
.ip-chart-month:hover .ip-chart-col { fill: var(--ip-hover, #f4f4f5); }
/* Stato vuoto: quando non ci sono serie o sono tutte zero, il grafico
   viene nascosto e mostrato questo placeholder. */
.ip-chart-empty {
  padding: 36px 16px;
  text-align: center;
  color: var(--ip-text-muted);
}
.ip-chart-empty i {
  font-size: var(--ip-text-4xl);
  opacity: 0.6;
  display: block;
  margin-bottom: 8px;
}
/* ---------- Sparkline (mini bar chart dentro le KPI card) ----------------- */
svg.ip-spark {
  width: 100%;
  height: 28px;
  display: block;
  margin-top: 4px;
}
/* ---------- Trend badge --------------------------------------------------- */
.ip-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-2);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  padding: 2px 6px;
  border-radius: var(--ip-radius-sm, 4px);
  white-space: nowrap;
}
.ip-trend--up    { color: var(--ip-success-text, var(--ip-success)); background: var(--ip-success-bg); }
.ip-trend--down  { color: var(--ip-danger-text, var(--ip-danger));  background: var(--ip-danger-bg); }
.ip-trend--muted { color: var(--ip-text-muted); background: var(--ip-hover, #f4f4f5); }
@media (max-width: 575.98px) {
  .ip-chart-legend {
    margin-left: 0;
    flex-basis: 100%;
  }
}
/* -----------------------------------------------------------------------------
   20. Pagine "lista tabella" (invoices/quotes/clients index...) — card dock.
   La tabella aderisce al .card-header (no .card-body). Qui la tabella è
   lunga e l'header ospita titolo + conteggio + ricerca live. La colonna
   Opzioni ospita dropdown BS che devono poter debordare, quindi niente
   overflow:hidden (a differenza delle card dashboard #19).
   -------------------------------------------------------------------------- */

/* Superficie: border + shadow-sm coerenti con .panel/.modern-card per dare
   elevazione alla lista-tabella, altrimenti la card appariva piatta. */
.ip-table-card {
  overflow: visible;
  border-radius: var(--ip-radius);
  /* #content.table-content ha padding verticale 0: la card aderisce al
     bordo inferiore dell'headerbar. Aggiungiamo margine alto per
     staccarla dall'headerbar/filtri, sullo stesso gradino delle altre card
     (16px, --ip-space-7). */
  margin: var(--ip-space-7) 0;
  /* Ereditiamo il border/shadow di .card (BS5) ma lo rinforziamo per
     matchesre il .panel legacy. */
  border: 1px solid var(--ip-border);
  background: var(--ip-bg-card);
  box-shadow: var(--ip-shadow-sm);
}
/* Header card-lista: scheda con bg neutro + border-bottom leggero.
   Altezza comoda per ospitare titolo + ricerca live senza affollamento.
   Padding allineato a .card > .card-header (14px 18px) per coerenza. */
.ip-table-card .card-header {
  padding: var(--ip-card-head-y) var(--ip-card-pad-x);
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border-soft);
}
/* Layout header: titolo a sx, ricerca live a dx; mobile stack verticale. */
.ip-table-card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-6);
  flex-wrap: wrap;
}
/* Titolo: icona in pill soft + label + badge conteggio.
   Allinea il linguaggio visivo delle card lista a quello del dashboard. */
.ip-table-card-title {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  color: var(--ip-text);
  min-width: 0;   /* permette ellipsis su label lunga quando search occupa spazio */
}
.ip-table-card-title .card-header-icon {
  width: 30px;
  height: 30px;
  font-size: var(--ip-text-base);
  margin-right: 0;
}
.ip-table-card-title .ip-table-card-label {
  font-size: var(--ip-text-md);
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
}
/* Badge conteggio righe: pill con primary-soft + primary text, più
   scansionabile del text-bg-secondary grigio di Bootstrap.
   Eredita lo stile di .ip-kpi-count del dashboard (#19b) per coerenza. */
.ip-table-card-title .badge.text-bg-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 2px 8px;
  font-size: var(--ip-text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  /* !important: la utility BS5 .text-bg-secondary usa !important su fondo e
     colore; senza, il pill primary-soft perdeva su entrambi (testo bianco). */
  color: var(--ip-primary) !important;
  background: var(--ip-primary-soft) !important;
  border-radius: 999px;
}
/* Ricerca live dentro il card-header: niente margin-bottom, flex la
   dimensiona secondo spazio disponibile (max 320px su desktop). */
.ip-table-card-search.ip-live-search {
  margin-bottom: 0;
  max-width: 320px;
  flex: 1 1 240px;
  min-width: 0;
}
/* Input di ricerca: stessa altezza di ogni altro controllo
   (--ip-control-h). Era 34px, 2px meno dei campi della stessa pagina, e
   nella testata della card stonava accanto a titolo e icona. */
.ip-table-card-search.ip-live-search input.form-control {
  height: var(--ip-control-h);
  font-size: var(--ip-text-base);
  border-color: var(--ip-border);
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition);
}
.ip-table-card-search.ip-live-search input.form-control:focus {
  border-color: var(--ip-primary);
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
}
@media (max-width: 575.98px) {
  /* Header stack verticale su mobile: titolo riga, ricerca a tutta larghezza. */
  .ip-table-card-header {
    flex-direction: column;
    align-items: stretch;
  }
  /* flex: 1 1 240px vale sull'asse principale: con l'header in colonna quel
     240px diventa un'altezza minima, e la ricerca apriva un vuoto di due dita
     fra il titolo della card e la tabella (su ogni elenco dell'app). Sull'asse
     verticale la casella deve stare alta quanto il suo contenuto. */
  .ip-table-card-search.ip-live-search {
    max-width: 100%;
    flex: 0 0 auto;
  }
}
/* Tabella dock: bordi puliti e — soprattutto — overflow: visible per non
   tagliare i dropdown della colonna Opzioni. .table-responsive di BS5 ha
   overflow-x:auto che, per la spec CSS, rende overflow-y:auto → il menu
   "Opzioni" che deborda verticalmente restava nascosto sotto la card.
   Lasciamo overflow: visible su tutti i breakpoint (anche mobile): se una
   tabella davvero non sta in viewport orizzontale, attivare il pattern
   collapse opzionale (.ip-table-card-collapse) o nascondere colonne
   secondarie con utility d-md-none. NON riattiviamo overflow-x:auto su
   mobile perché si ripresenta il bug dropdown-hidden. */
.ip-table-card .table-responsive {
  border: 0;
  border-radius: 0;
  overflow: visible;
}
/* Anche la card stessa deve restare visibile e #filter_results, che nei
   moduli invoices/clients/quotes/payments fa da wrapper AJAX: NON deve
   clippare i dropdown che debordano dalla singola riga. Forziamo
   overflow: visible a tutti gli ancestor della tabella dentro la card dock. */
.ip-table-card > #filter_results,
.ip-table-card > #clients-table-wrap,
.ip-table-card > #quotes-table-wrap,
.ip-table-card > #payments-table-wrap {
  overflow: visible;
}
/* Z-index del dropdown menu: di default BS è 1000, ma l'headerbar sticky
   è a 1020 — su liste lunghe un menu che si apre su una riga in alto
   finiva sotto l'headerbar durante lo scroll. 1035 sta sopra headerbar
   (1020) e sidebar (1025) e sotto i pannelli flottanti (1080) e i toast
   (1085); la topbar (1030) non viene mai raggiunta perché i menu si
   aprono sotto il loro trigger. Dentro una modale il menu vive nello
   stacking context della modale (1055) e l'elevazione qui non interessa. */
.dropdown-menu {
  z-index: var(--ip-z-popover);
  /* Elevazione di livello medio: i menu a tendina si staccano dalla superficie
     con la stessa ombra dei pannelli fluttuanti, invece del default Bootstrap
     piatto. Coerente con --ip-shadow-md. */
  box-shadow: var(--ip-shadow-md);
}
/* Modali: ombra di livello superiore (overlay) invece del default Bootstrap,
   allineata alla stessa scala di elevazione dei menu e dei pannelli. Raggio
   morbido (--ip-radius-lg) per staccare dal raggio dei campi. */
.modal-content {
  box-shadow: var(--ip-shadow-lg);
  border-radius: var(--ip-radius-lg);
}
/* Tooltip e popover: niente nero di default Bootstrap. Usano la superficie
   card con bordo hairline e il testo muto, coerenti con il resto del tema. */
.tooltip-inner {
  background: var(--ip-bg-card);
  color: var(--ip-text);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  box-shadow: var(--ip-shadow-sm);
  padding: 5px 9px;
}
/* Freccia del tooltip: tinta come il bordo così non spunta nera su sfondo. */
.tooltip.show { opacity: 1; }
.tooltip .tooltip-arrow::before { border-top-color: var(--ip-border) !important; }
.bs-tooltip-auto[data-popper-placement^="top"] .tooltip-arrow::before,
.bs-tooltip-top .tooltip-arrow::before { border-top-color: var(--ip-border) !important; }
.bs-tooltip-auto[data-popper-placement^="bottom"] .tooltip-arrow::before,
.bs-tooltip-bottom .tooltip-arrow::before { border-bottom-color: var(--ip-border) !important; }
.bs-tooltip-auto[data-popper-placement^="left"] .tooltip-arrow::before,
.bs-tooltip-left .tooltip-arrow::before { border-left-color: var(--ip-border) !important; }
.bs-tooltip-auto[data-popper-placement^="right"] .tooltip-arrow::before,
.bs-tooltip-right .tooltip-arrow::before { border-right-color: var(--ip-border) !important; }
.popover {
  background: var(--ip-bg-card);
  color: var(--ip-text);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-lg);
  box-shadow: var(--ip-shadow-md);
}
.popover .popover-header {
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border-soft);
  color: var(--ip-text);
  font-weight: 600;
}
.popover .popover-body { color: var(--ip-text-muted); }
.ip-table-card .table > thead > tr > th {
  /* bg neutro soft per distinguere l'header di colonna dalle righe striped
     e dare "tier" visivo dentro la card (bg-card attorno). */
  background: var(--ip-bg, #f6f6f7);
  box-shadow: inset 0 -1px 0 var(--ip-border);
  /* Sticky: l'header di colonna resta visibile durante lo scroll nelle liste
     lunghe. top calcolato sotto navbar + headerbar misurate via JS. */
  position: sticky;
  top: calc(var(--ip-navbar-height) + var(--ip-headerbar-height));
  z-index: var(--ip-z-raised);
}
/* Padding celle: compatto dentro la card, senza gonfiare (9/12, il ritmo che
   tiene in piedi le liste con più colonne). Solo le due colonne di bordo
   prendono il rientro della card, così il primo testo di colonna cade sotto
   il titolo della testata e l'ultimo chiude sulla stessa verticale del pager:
   a 12px su tutti i lati il contenuto stava 6px fuori asse, ma portare tutte
   le celle a 18 avrebbe rubato ~80px di larghezza alle liste più fitte. */
.ip-table-card .table > thead > tr > th,
.ip-table-card .table > tbody > tr > td {
  padding: 9px 12px;
}
/* Le intestazioni di colonna sono etichette di una o due parole: spezzate
   su due righe ("Data di / scadenza") alzavano l'intera testata per
   guadagnare pochi pixel a una colonna. Il layout automatico della tabella
   restringe le celle di dati, che possono andare a capo, e non le etichette. */
.ip-table-card .table > thead > tr > th,
.ip-recent-table > thead > tr > th {
  white-space: nowrap;
}
.ip-table-card .table > thead > tr > :first-child,
.ip-table-card .table > tbody > tr > :first-child,
.ip-table-card .table > tfoot > tr > :first-child {
  padding-left: var(--ip-card-pad-x);
}
.ip-table-card .table > thead > tr > :last-child,
.ip-table-card .table > tbody > tr > :last-child,
.ip-table-card .table > tfoot > tr > :last-child {
  padding-right: var(--ip-card-pad-x);
}
/* Tabelle messe dentro un .card-body (schede riepilogo cliente): il corpo ha
   già il rientro della card, quindi le celle non ne aggiungono un secondo.
   Prima il testo stava a 18+12=30px dal bordo, il titolo a 18. */
.card-body > .table > thead > tr > th,
.card-body > .table > tbody > tr > th,
.card-body > .table > tbody > tr > td,
.card-body > .table-responsive > .table > thead > tr > th,
.card-body > .table-responsive > .table > tbody > tr > th,
.card-body > .table-responsive > .table > tbody > tr > td {
  padding-left: 0;
  padding-right: 0;
}
/* Hover righe: accento più morbido del default (primary-soft già usato
   da .table-hover); qui lo rafforziamo leggermente per dare "tap target"
   più evidente nelle liste con righe fitte. */
.ip-table-card .table-hover > tbody > tr:hover > * {
  background: var(--ip-primary-soft);
}
/* Empty-row dentro la card: padding più ampio per dare respiro e far
   percepire lo spazio come "invito", non come "errore". */
.ip-table-card .table > tbody > tr.empty-row > td,
.ip-table-card .table > tbody > tr > td[colspan]:only-child {
  padding: 48px var(--ip-card-pad-x);
}
/* -----------------------------------------------------------------------
   Sotto i 1200px: tabelle dentro la card, che scorrono da sole
   -----------------------------------------------------------------------
   Sopra i 1200px la colonna contenuti è abbastanza larga per le liste più
   fitte (fatture, costi): le colonne sono state strette perché ci stiano.
   Sotto, la stessa lista non entra (a 992 px la colonna è 756 px, la tabella
   ne chiede 835) e con overflow: visible allargava la pagina intera.
   Qui si riattiva lo scorrimento interno della card. L'ancoraggio verticale
   dell'intestazione si azzera perché dentro un contenitore che scorre il
   browser la spingerebbe sotto di sé. Le tabelle con .ip-table-card-collapse
   restano fuori sotto i 576px: lì diventano schede (regola più specifica,
   più sotto nel file). */
@media (max-width: 1199.98px) {
  .ip-table-card .table-responsive {
    overflow-x: auto;
  }
  .ip-table-card .table-responsive > .table > thead > tr > th {
    top: auto;
  }
}

/* -----------------------------------------------------------------------
   Responsive <576: lista-tabella collassa in card-row verticali
   -----------------------------------------------------------------------
   Pattern mobile coerente col dashboard (.ip-recent-table): thead nascosto
   e ogni <tr> diventa una mini-card con data-label::before. Richiede che
   la vista emetta data-label sulle <td> — dove NON presente, la tabella
   resta orizzontale-scroll (fallback sicuro via .table-responsive).
   Disabilitato di default: si attiva aggiungendo la classe
   .ip-table-card-collapse alla <table> o alla card. */
@media (max-width: 575.98px) {
  .ip-table-card .table-responsive:has(table.ip-table-card-collapse) {
    overflow: visible;
  }
  .ip-table-card table.ip-table-card-collapse thead { display: none; }
  .ip-table-card table.ip-table-card-collapse,
  .ip-table-card table.ip-table-card-collapse tbody,
  .ip-table-card table.ip-table-card-collapse tr,
  .ip-table-card table.ip-table-card-collapse td {
    display: block;
    width: 100%;
  }
  .ip-table-card table.ip-table-card-collapse tbody tr {
    border: 1px solid var(--ip-border-soft);
    border-radius: var(--ip-radius-sm);
    margin-bottom: 10px;
    padding: 4px 0;
    background: var(--ip-bg-card);
  }
  .ip-table-card table.ip-table-card-collapse tbody td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--ip-space-6);
    padding: 8px 12px;
    border-bottom: 1px dashed var(--ip-border-soft);
    text-align: right;
    /* Un valore lungo (email, indirizzo) non deve allargare la card oltre il
       viewport: va a capo sotto l'etichetta e, se serve, si spezza. */
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .ip-table-card table.ip-table-card-collapse tbody td:last-child {
    border-bottom: none;
  }
  /* Celle vuote (es. la colonna posizione nella riga Totale dei report):
     in modalita' card diventerebbero una riga alta 33px con solo il bordo
     tratteggiato, perché non hanno ne' etichetta ne' valore da mostrare. */
  .ip-table-card table.ip-table-card-collapse tbody td:empty {
    display: none;
  }
  /* La riga "nessun documento" non è una riga di dati: le regole card qui
     sopra la spezzavano in tre elementi flex distribuiti ai due bordi (icona
     a sinistra, messaggio al centro, pulsante a destra). Torna un blocco
     unico e centrato, con la chiamata all'azione sotto il messaggio. */
  .ip-table-card table.ip-table-card-collapse tbody tr.empty-row > td {
    display: block;
    text-align: center;
    padding: 40px 16px;
  }
  .ip-table-card table.ip-table-card-collapse tbody td::before {
    content: attr(data-label);
    font-weight: 600;
    font-size: var(--ip-text-sm);
    color: var(--ip-text-muted);
    text-align: left;
    white-space: nowrap;
    flex: 0 0 auto;
  }
  .ip-table-card table.ip-table-card-collapse tbody td.amount,
  .ip-table-card table.ip-table-card-collapse tbody td[class*="amount"] {
    justify-content: space-between;
  }
  /* Colonne secondarie nascoste in modalità card mobile per ridurre l'altezza
     della riga: preferito, creata, opzioni. */
  .ip-table-card table.ip-table-card-collapse tbody td.ip-hide-on-mobile {
    display: none;
  }

  /* ---- Scheda compatta delle liste documenti ----------------------------
     Il collasso qui sopra impila fedelmente una riga per colonna: corretto
     come regola generale, ma su fatture e preventivi dava sei righe con
     etichetta per ogni documento, circa 250px a testa — una fattura per
     schermata, e "Stato"/"Cliente" ripetuti venti volte pesavano quanto i
     dati. Dove la vista dichiara il ruolo delle celle (data-cell) la riga
     diventa invece una scheda a due colonne:

         numero fattura            importo
         cliente                   stato
         scade il  ...             saldo ...

     Il ruolo sta nel markup (data-cell) e non nella posizione (nth-child)
     perché fatture e preventivi hanno ordini di colonna diversi, e una
     colonna aggiunta in mezzo avrebbe spostato silenziosamente il layout. */
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 2px 12px;
    padding: 10px 12px;
  }
  /* :not(.ip-hide-on-mobile) — senza, questa regola (più specifica e più in
     basso) rimetteva in pagina proprio le colonne che il blocco qui sopra
     nasconde: stella, data di creazione e menu opzioni. */
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) td:not(.ip-hide-on-mobile) {
    display: block;
    /* Bootstrap dipinge ogni cella con --bs-table-bg (il canvas) e le separa
       con un bordo superiore: sulla riga, che in modalità scheda ha il fondo
       della card, ogni cella disegnava un rettangolo più scuro con una riga
       sopra, e la scheda si leggeva come una griglia di caselle invece che
       come un documento solo. */
    background: transparent;
    border-top: 0;
    width: auto;
    padding: 0;
    border-bottom: 0;
    text-align: left;
  }
  /* Le etichette sopravvivono solo dove il valore da solo sarebbe ambiguo
     (una data, un secondo importo): lì restano in linea e minuscole. */
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) td::before {
    content: none;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="number"] {
    grid-column: 1;
    grid-row: 1;
    font-weight: 600;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="amount"] {
    grid-column: 2;
    grid-row: 1;
    text-align: right;
    font-weight: 600;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="client"] {
    grid-column: 1;
    grid-row: 2;
    color: var(--ip-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="status"] {
    grid-column: 2;
    grid-row: 2;
    text-align: right;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="due"],
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="balance"] {
    font-size: var(--ip-text-sm);
    color: var(--ip-text-muted);
    padding-top: 4px;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="due"] {
    grid-column: 1;
    grid-row: 3;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="balance"] {
    grid-column: 2;
    grid-row: 3;
    text-align: right;
  }
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) td[data-cell="due"]::before,
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) td[data-cell="balance"]::before {
    content: attr(data-label) " ";
    display: inline;
    font-weight: 400;
    font-size: var(--ip-text-sm);
    white-space: normal;
  }
  /* Cella che contiene solo il trattino di "dato assente" (le viste delle
     anagrafiche e il partial della ricerca lo emettono dentro .ip-cell-empty):
     in modalità card sarebbe una riga intera — o due, con l'etichetta — per
     dire che non c'è niente. Nella tabella larga il trattino resta e tiene
     la colonna; qui l'assenza si legge da sola.
     Sta in fondo al blocco e con la stessa specificità della regola che
     rimette in pagina le celle della scheda a due colonne
     ("tbody tr:has(td[data-cell]) td:not(.ip-hide-on-mobile)"): più in alto,
     o più leggera, perdeva e le celle vuote tornavano visibili. */
  .ip-table-card table.ip-table-card-collapse tbody tr:has(td[data-cell]) td:has(> .ip-cell-empty:only-child) {
    display: none;
  }
}
/* ---- ex saas.css ---- */
/* =============================================================================
   saas.css — "Casa verde postale"
   -----------------------------------------------------------------------------
   L'identità visiva dell'app: la fattura come modello. Carta calda, inchiostro
   verde-nero, verde postale per il denaro, vermiglio per il timbro (attenzione),
   cifre da registro contabile (Fraunces + tabular), stato = timbro sulla carta.
   Questo file è l'ultimo layer: definisce il guscio (topbar + sidebar a gruppi,
   off-canvas su mobile) e le firme — timbri di stato, cifre Fraunces, login a
   due pannelli. Tutti i token sono in custom.css (sezione 1).
   ========================================================================== */

/* -----------------------------------------------------------------------------
   1. Guscio — topbar
   -------------------------------------------------------------------------- */

.ip-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* min-height: l'altezza reale la misura il JS (--ip-navbar-height) perché
     font Figtree o un wrap su viewport stretti possono allungare la barra;
     una height fissa di 56px avrebbe tagliato il contenuto in quei casi. */
  min-height: 56px;
  z-index: var(--ip-z-dropdown);
  background: var(--ip-navbar-bg);
  /* La chiusura e' a due livelli: un filo pieno larghezza-intera (il token
     --ip-navbar-border, gia' definito per light/dark ma prima inutilizzato)
     che garantisce sempre uno stacco netto dal contenuto, anche su viewport
     larghi dove la ricerca globale e il menu utente occupano tutta la barra.
     Sopra, la "scia" del marchio (::after) resta l'accento cromatico: i tre
     blu dell'aeroplanino di carta partono dal logo e sfumano verso destra.
     Prima la scia si dissolveva nel nulla al 58% della barra e il filo pieno
     non c'era: sui monitor larghi la meta' destra della topbar restava senza
     nessun bordo, col solo box-shadow a reggere la separazione. */
  border-bottom: 1px solid var(--ip-navbar-border);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
  display: flex;
  align-items: center;
}
/* Scia del brand: gli stessi esadecimali del logo (#1E73B8/#2181CE/#429AE1),
   forti sotto il marchio e sfumati verso destra fino a fondersi col filo
   neutro sottostante (non più fino al trasparente: così la transizione
   dal blu al bordo grigio e' continua, invece di lasciare un vuoto a meta'
   barra). ::after (non un secondo bordo) perché un gradiente non si puo'
   dipingere su un border-bottom. La mask verticale la fa "evaporare" verso
   il basso, così resta una scia morbida sopra al filo netto, non un
   duplicato rigido. */
.ip-topbar::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: linear-gradient(90deg,
    #429AE1 0%,
    #2181CE 18%,
    var(--ip-navbar-border) 70%);
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0) 100%);
          mask-image: linear-gradient(to bottom, #000 0%, rgba(0, 0, 0, 0) 100%);
  pointer-events: none;
  z-index: 1;
}
/* Barra scura + sidebar chiara: e' l'impaginazione dell'InvoicePlane originale
   (navbar-inverse #333c4a sopra, rail chiaro a sinistra). Dentro la barra la
   cromia si inverte, quindi marchio, link e avatar vanno ridichiarati. */
.ip-topbar .ip-brand-name { color: var(--ip-navbar-text); }
.ip-topbar .ip-brand:hover .ip-brand-name,
.ip-topbar .ip-brand:focus .ip-brand-name { color: #fff; }
/* Il logo originale (aeroplano di carta in tre blu: #1E73B8/#2181CE/#429AE1)
   è già leggibile sul navy della topbar così com'è: nessun override di
   colore qui, a differenza della vecchia icona neutra pensata per adattarsi
   allo sfondo. */
.ip-topbar .ip-topbar-link { color: var(--ip-navbar-text); }
.ip-topbar .ip-topbar-link:hover {
  background: var(--ip-navbar-hover);
  color: #fff;
}
.ip-topbar .ip-topbar-link .ip-nav-avatar {
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
}
.ip-topbar .ip-topbar-link:hover .ip-nav-avatar {
  background: rgba(255, 255, 255, 0.26);
  color: #fff;
}
.ip-topbar .ip-sidebar-toggle { color: var(--ip-navbar-text); }
.ip-topbar .ip-sidebar-toggle:hover {
  background: var(--ip-navbar-hover);
  color: #fff;
}
.ip-topbar .ip-sidebar-toggle:focus-visible,
.ip-topbar .ip-topbar-link:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.75);
  outline-offset: 1px;
}
/* La dropdown utente resta su fondo chiaro: e' un pannello, non la barra. */
.ip-topbar .dropdown-menu { color: var(--ip-text); }
/* Selettore tema chiaro/scuro. Nelle pagine area interna vive nella dropdown
   utente (voce .dropdown-item); nel portale guest, che non ha menu avatar,
   resta un pulsante quadrato nella barra. In entrambi i casi lo scambio
   luna/sole avviene via CSS su html[data-theme], così lo stato non dipende
   dal JS. */
.ip-theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
  border: 0;
  cursor: pointer;
}
/* Scoped a > diretto: questa e' la variante "pulsante quadrato" usata solo
   nella topbar guest (figlio diretto di .ip-topbar-right). La stessa classe
   .ip-theme-toggle, riusata come voce del dropdown avatar (base.php), NON e'
   figlia diretta di .ip-topbar-right (sta dentro <li><ul class="dropdown-menu">)
   e non deve ereditare larghezza/altezza fisse ne' i colori pensati per la
   barra scura: prima il selettore generico ".ip-topbar .ip-theme-toggle" la
   intercettava comunque (discendente, non diretto), schiacciando la voce di
   menu in un quadrato 38x38 con l'etichetta "Tema scuro/chiaro" tagliata fuori
   e ne sporcava l'hover con i colori della navbar invece di quelli del
   pannello dropdown (sezione 23, .dropdown-menu > li > .dropdown-item). */
.ip-topbar-right > .ip-theme-toggle {
  justify-content: center;
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  border-radius: var(--ip-radius);
  background: transparent;
  color: var(--ip-navbar-text);
  font-size: var(--ip-text-lg);
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-topbar-right > .ip-theme-toggle:hover { background: var(--ip-navbar-hover); color: #fff; }
.ip-topbar-right > .ip-theme-toggle:focus-visible {
  outline: 2px solid rgba(255, 255, 255, 0.75);
  outline-offset: 1px;
}
.ip-theme-toggle .fa-sun { display: none; }
html[data-theme="dark"] .ip-theme-toggle .fa-sun { display: inline-block; }
html[data-theme="dark"] .ip-theme-toggle .fa-moon { display: none; }
/* Etichetta dinamica: in tema chiaro propone "Tema scuro", in tema scuro
   "Tema chiaro" — l'azione, non lo stato. */
.ip-theme-toggle__label[data-action="light"] { display: none; }
html[data-theme="dark"] .ip-theme-toggle__label[data-action="light"] { display: inline-block; }
html[data-theme="dark"] .ip-theme-toggle__label[data-action="dark"] { display: none; }
/* Variante login (layout/auth.php): pillola in alto a destra, sopra il
   pannello del modulo. Non e' figlia di .ip-topbar-right, quindi non eredita
   il quadrato 38x38 ne' i colori pensati per la navbar scura. Con l'etichetta
   accanto all'icona: il login e' la pagina che l'utente vede una volta sola,
   e una lunetta sola in un angolo non si capisce. */
.ip-auth-theme-toggle {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 3;
  padding: 7px 13px;
  border-radius: 999px;
  border: 1px solid var(--ip-border);
  background: var(--ip-bg-card);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  transition: color var(--ip-transition), border-color var(--ip-transition);
}
.ip-auth-theme-toggle:hover {
  color: var(--ip-text);
  border-color: var(--ip-border-strong);
}
.ip-auth-theme-toggle:focus-visible {
  outline: 2px solid var(--ip-primary);
  outline-offset: 2px;
}
/* Sotto i 480px il pannello del modulo occupa tutto: l'etichetta ruberebbe
   spazio alla card, resta la sola icona (il nome vive in aria-label). */
@media (max-width: 480px) {
  .ip-auth-theme-toggle {
    top: 10px;
    right: 10px;
    padding: 7px 9px;
  }
  .ip-auth-theme-toggle .ip-theme-toggle__label,
  html[data-theme="dark"] .ip-auth-theme-toggle .ip-theme-toggle__label[data-action="light"] {
    display: none;
  }
}
.ip-topbar-inner {
  display: flex;
  align-items: center;
  /* flex-start + right in margine automatico: con space-between il campo di
     ricerca galleggiava dove capitava (lo spazio libero veniva spartito tra
     gli interstizi). Ora l'unico spazio flessibile è quello davanti ai
     comandi di destra: il campo resta ancorato al bordo della colonna
     contenuti, da qualsiasi parte stia il resto. */
  justify-content: flex-start;
  gap: var(--ip-space-7);
  width: 100%;
  padding: 0 16px;
}
/* Fra 576 e 991px la sidebar è off-canvas e il bordo sinistro della barra è
   il bordo del contenuto: allineiamo il padding a quello di #content (20px).
   Su desktop la sidebar fa da bordo, sotto 576px entrambi usano 16px. */
@media (min-width: 576px) and (max-width: 991.98px) {
  .ip-topbar-inner { padding: 0 20px; }
}
.ip-topbar-left { display: flex; align-items: center; gap: var(--ip-space-5); min-width: 0; }
/* Filtro anno e menu utente sono due comandi distinti: appaiati a 2px
   sembravano un unico gruppo di controlli. margin-left:auto assorbe tutto lo
   spazio libero: è questo che tiene i comandi incollati al bordo destro,
   indipendentemente da quanto è largo il campo di ricerca. */
.ip-topbar-right { display: flex; align-items: center; gap: var(--ip-space-6); margin-left: auto; position: relative; }
/* Toggle hamburger: visibile solo su mobile (<lg), quando la sidebar è
   off-canvas. Su desktop la sidebar è sempre aperta. */
.ip-sidebar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-lg);
  cursor: pointer;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-sidebar-toggle:hover { background: var(--ip-hover); color: var(--ip-text); }
.ip-sidebar-toggle:focus-visible { outline: 2px solid var(--ip-primary); outline-offset: 1px; }
@media (min-width: 992px) { .ip-sidebar-toggle { display: none; } }
/* Marchio: una piastrella blu con le righe di una fattura, l'ultima delle
   quali finisce in un "+" — la voce in più, cioè la parte "plus" del nome —
   con il nome accanto in Sora. I tracciati stanno in layout/brand_mark.php.
   Sostituisce l'aeroplanino di InvoicePlane, che era il loro logo (nome e
   logo sono coperti dal loro avviso sul marchio). */
/* Sora è servito dal sito e non da Google Fonts come gli altri tre: serve a
   una sola parola, e in locale non aggiunge un'origine alla
   Content-Security-Policy né una richiesta a terzi. È un @font-face, non un
   @import: a differenza di quello può stare in qualunque punto del foglio.
   File variabile (pesi 400-800), sottoinsieme latino, licenza OFL. */
@font-face {
  font-family: "Sora";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/sora-latin.woff2?v=f544d77b") format("woff2");
}
.ip-brand {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  text-decoration: none;
  min-width: 0;
}
/* Niente chip dietro il segno: serviva ai segni precedenti (un tracciato
   sottile, poi un foglio chiaro) per staccarsi dal fondo, ma questo è già
   una piastrella piena e un riquadro dentro un riquadro lo rimpiccioliva
   soltanto. Lo spazio del chip va al segno: 32px + 2px di margine interno
   danno lo stesso ingombro di prima (36px), quindi la barra non si sposta.
   box-sizing: content-box perché il padding non schiacci il tracciato SVG. */
.ip-brand-mark {
  width: 32px;
  height: 32px;
  flex: 0 0 auto;
  display: block;
  padding: 2px;
  box-sizing: content-box;
  transition: filter var(--ip-transition);
}
/* Al passaggio il segno si schiarisce appena: è il riscontro che prima dava
   il chip, senza ritinteggiare i colori del marchio. */
.ip-brand:hover .ip-brand-mark,
.ip-brand:focus .ip-brand-mark {
  filter: brightness(1.12);
}
/* Colori del marchio (sorgenti: layout/brand_mark.php, favicon.svg,
   logo.svg in public/assets/core/img). Sono esadecimali fissi, non token del
   tema: un marchio non si ritinteggia cambiando tema, e il segno deve restare
   uguale nella barra scura, nella colonna chiara, nella favicon e nel logo
   del README. Le righe sfumano dall'alto in basso per far cadere l'occhio
   sul "+", che è l'unico elemento in azzurro. */
.ip-mark-tile  { fill: #1E73B8; }
.ip-mark-line  { fill: #fff; }
.ip-mark-line--2 { opacity: .8; }
.ip-mark-line--3 { opacity: .6; }
.ip-mark-plus  { fill: #9FD3FA; }
.ip-brand-name {
  font-family: var(--ip-brand-font);
  font-size: var(--ip-text-xl);
  font-weight: 700;
  /* Sora è largo: stretto di un filo sta nella colonna della sidebar come
     ci stava il serif, e le lettere fanno parola invece di restare staccate. */
  letter-spacing: -0.03em;
  color: var(--ip-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--ip-transition);
}
.ip-brand:hover .ip-brand-name,
.ip-brand:focus .ip-brand-name { color: var(--ip-primary); }
/* Blocco nome+sottotitolo: impila le due righe sotto l'aeroplanino.
   min-width:0 serve a far funzionare l'ellipsis sul nome quando la
   navbar è stretta. */
.ip-brand-text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-width: 0;
  line-height: 1.15;
}
.ip-brand-subtitle {
  font-family: var(--ip-font);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ip-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Sotto i 576px il nome non ha spazio: l'ellissi lo riduceva a "Inv..." e
   sembrava un errore. Resta il marchio; il link conserva l'aria-label. */
@media (max-width: 575.98px) {
  .ip-topbar .ip-brand-text { display: none; }
}
.ip-topbar .ip-brand-subtitle { color: var(--ip-navbar-text); opacity: 0.7; }
.ip-auth-brand-mark .ip-brand-subtitle { color: rgba(255, 255, 255, 0.72); }
.ip-topbar-link {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
  height: 38px;
  padding: 0 10px;
  border-radius: var(--ip-radius);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-topbar-link:hover { background: var(--ip-hover); color: var(--ip-text); }
.ip-topbar-link .ip-nav-avatar { background: var(--ip-primary-soft); color: var(--ip-primary); }
.ip-topbar-link:hover .ip-nav-avatar { background: var(--ip-primary); color: var(--ip-on-primary); }
.ip-topbar-user { font-weight: 500; }
/* Navigazione del portale guest (a fianco del brand) */
.ip-guest-nav {
  display: flex;
  align-items: center;
  gap: var(--ip-space-1);
  margin-left: 8px;
}
.ip-guest-link {
  padding: 6px 10px;
  border-radius: var(--ip-radius);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  text-decoration: none;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-guest-link:hover { background: var(--ip-hover); color: var(--ip-text); }
.ip-guest-link.active { background: var(--ip-primary-soft); color: var(--ip-primary); font-weight: 600; }
@media (max-width: 575.98px) {
  .ip-guest-nav { display: none; }
}
/* -----------------------------------------------------------------------------
   2. Guscio — sidebar a gruppi (desktop fissa, mobile off-canvas)
   -------------------------------------------------------------------------- */

.ip-sidebar {
  position: fixed;
  top: var(--ip-navbar-height, 56px);
  bottom: 0;
  left: 0;
  width: var(--ip-sidebar-width, 236px);
  z-index: 1025;
  background: var(--ip-bg-card);
  border-right: 1px solid var(--ip-border);
  display: flex;
  flex-direction: column;
}
.ip-sidebar-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 12px 18px;
  /* Colonna: con le sezioni ora richiudibili il menu può essere molto corto e
     la firma finiva a mezz'aria sotto l'ultima voce. Il margine automatico sul
     piede la tiene incollata in fondo, e quando il menu è lungo torna a
     scorrere dopo l'ultima voce come prima. */
  display: flex;
  flex-direction: column;
  /* Apertura/chiusura delle sezioni: la posizione della freccia premuta la
     tiene lo script in layout/base.php. Con l'ancoraggio automatico del
     browser acceso le due correzioni si sommavano e il menu saltava. */
  overflow-anchor: none;
}
.ip-sidebar-scroll > .ip-sidebar-foot { margin-top: auto; }
/* Etichetta di gruppo: maiuscola spaziata, come le voci di un indice. */
.ip-nav-eyebrow {
  margin: 20px 10px 6px;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ip-text-muted);
}
.ip-nav-eyebrow:first-child { margin-top: 4px; }
.ip-nav { list-style: none; margin: 0 0 4px; padding: 0; }
.ip-nav-item { margin-bottom: 2px; }
.ip-nav-link {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  padding: 8px 10px;
  border-radius: var(--ip-radius);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  text-decoration: none;
  position: relative;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-nav-link i {
  width: 18px;
  text-align: center;
  font-size: var(--ip-text-md);
  flex: 0 0 auto;
}
.ip-nav-link span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ip-nav-link:hover,
.ip-nav-link:focus { background: var(--ip-hover); color: var(--ip-text); }
/* Sezione che contiene la pagina corrente: la voce si scurisce e basta.
   Il link e' figlio di .ip-nav-row, non della voce: finche' il selettore usava
   il combinatore figlio diretto (`.ip-nav-item.active > .ip-nav-link`, com'era
   prima che la riga venisse introdotta nella sezione 33) nessuna voce di primo
   livello si e' mai accesa — solo le sotto-voci, che hanno una regola propria. */
.ip-nav-item.active > .ip-nav-row .ip-nav-link {
  color: var(--ip-text);
  font-weight: 600;
}
/* La sezione aperta tiene un accenno di barra a sinistra, più lieve di quella
   della pagina corrente (.is-current): la colonna segnala così sia "dove sei"
   sia "che sezione hai aperto" senza due pennellate piene in competizione. */
.ip-nav-item.active > .ip-nav-row .ip-nav-link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 10px;
  bottom: 10px;
  width: 2px;
  border-radius: 3px;
  background: var(--ip-border-strong);
}
/* Pagina corrente: tinta + barra inchiostro a sinistra. Sta qui e non sulla
   sezione aperta perché sono due cose diverse — stando su "Crea fattura" la
   voce "Fatture" e' la sezione in cui ti trovi, ma la pagina e' la sotto-voce,
   e due evidenziazioni piene in colonna si contendevano il "sei qui".
   Stessa specificita' della regola sopra (0,4,0) e più in basso nel file:
   quando una voce e' insieme aperta e corrente, vince questa. */
.ip-nav-item > .ip-nav-row .ip-nav-link.is-current {
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-weight: 600;
}
.ip-nav-item > .ip-nav-row .ip-nav-link.is-current::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  bottom: 9px;
  width: 3px;
  border-radius: 3px;
  background: var(--ip-primary);
}
/* Sotto-voci: azioni rapide della sezione, un gradino più in basso. */
.ip-nav-sub { list-style: none; margin: 2px 0 6px; padding: 0 0 0 38px; }
.ip-nav-sub-link {
  display: block;
  padding: 5px 8px;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  text-decoration: none;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-nav-sub-link:hover { background: var(--ip-primary-soft); color: var(--ip-primary); }
/* Sotto-voce corrente: prima solo la sezione si accendeva, quindi stando su
   "Crea fattura" o "Conti di saldo" non si vedeva su quale delle sottovoci
   si fosse. */
.ip-nav-sub-link.active {
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-weight: 600;
}
/* Piede sidebar: la firma dell'app, due pieghe + nome. */
.ip-sidebar-foot {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  margin: 18px 10px 0;
  padding-top: 14px;
  border-top: 1px solid var(--ip-border-soft);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 500;
}
.ip-sidebar-foot em { font-style: normal; color: var(--ip-text-muted); }
/* Il piè di pagina tiene il margine largo di prima (5px): lì il segno è un
   richiamo piccolo e deve restare allineato al testo che lo segue. */
.ip-brand-mark--foot { width: 15px; height: 15px; padding: 5px; opacity: 0.75; }
/* Backdrop per l'off-canvas mobile */
.ip-sidebar-backdrop {
  position: fixed;
  inset: var(--ip-navbar-height, 56px) 0 0 0;
  z-index: 1024;
  background: rgba(24, 24, 27, 0.38);
}
@media (min-width: 992px) { .ip-sidebar-backdrop { display: none !important; } }
/* Area contenuto: lascia il posto alla sidebar su desktop. Il selettore
   fratello (#ip-sidebar ~) vale solo dove la sidebar esiste davvero nel
   DOM — la pagina guest resta a tutta larghezza. */
#ip-sidebar ~ #main-area { margin-left: var(--ip-sidebar-width, 236px); }
/* Off-canvas: sotto lg la sidebar entra da sinistra con il backdrop */
@media (max-width: 991.98px) {
  #ip-sidebar ~ #main-area { margin-left: 0; }
  .ip-sidebar {
    transform: translateX(-105%);
    transition: transform 0.22s ease;
    box-shadow: none;
  }
  body.ip-sidebar-open .ip-sidebar {
    transform: translateX(0);
    box-shadow: var(--ip-shadow-lg);
  }
}
/* -----------------------------------------------------------------------------
   3. Timbri di stato — i badge di stato sono timbri sulla carta
   -----------------------------------------------------------------------------
   Contorno + testo nel colore di stato, maiuscole spaziate, come un timbro
   apposto sulla fattura. I conteggi (.text-bg-secondary/.text-bg-light) e i
   bottoni restano pieni. Sull'hover di riga il timbro si "imprime" (si riempie).
   -------------------------------------------------------------------------- */

/* I .text-bg-* di Bootstrap usano !important: i timbri devono forzare
   background/colore per vincere sul riempimento della utility. */
/* Forma "timbro": bordo e testo nel colore di stato, fondo trasparente.
   Misure (corpo, peso, spaziatura, padding, raggio) dalla base .badge: qui
   resta solo ciò che distingue il timbro dagli altri badge. Prima questa
   regola riportava le proprie misure e il raggio a 3px, che conviveva con i
   4px di .badge.ip-status sullo stesso tipo di etichetta. */
.badge.text-bg-primary,
.badge.text-bg-success,
.badge.text-bg-danger,
.badge.text-bg-warning,
.badge.text-bg-info {
  background: transparent !important;
  border: 1px solid;
  text-transform: uppercase;
  transition: background-color var(--ip-transition), color var(--ip-transition), border-color var(--ip-transition);
}
/* Bordo e testo dai token *-text, non da rgba() con gli RGB del tema chiaro:
   il bordo è la stessa tinta al 55%, così segue il tema invece di restare
   grigio-verde su un fondo scuro. */
.badge.text-bg-success { color: var(--ip-success-text) !important; border-color: color-mix(in srgb, var(--ip-success-text) 55%, transparent); }
.badge.text-bg-danger  { color: var(--ip-danger-text)  !important; border-color: color-mix(in srgb, var(--ip-danger-text) 55%, transparent); }
.badge.text-bg-warning { color: var(--ip-warning-text) !important; border-color: color-mix(in srgb, var(--ip-warning-text) 75%, transparent); }
.badge.text-bg-primary { color: var(--ip-primary) !important; border-color: color-mix(in srgb, var(--ip-primary) 45%, transparent); }
.badge.text-bg-info    { color: var(--ip-info-text)    !important; border-color: color-mix(in srgb, var(--ip-info-text) 55%, transparent); }
/* Sulle righe delle tabelle il timbro si imprime all'hover.
   Il fondo usa le versioni piene dei token (*-solid dove la tinta nascosta è
   troppo chiara) e il testo il token "on-*" abbinato: con #fff su --ip-* il
   timbro perdeva leggibilità in tema scuro — "Pagato" a 2.94:1, "Inviato"
   addirittura a 1.23:1, perché lì --ip-primary è chiaro. Ora: 5.18/6.21
   (success), 5.08/5.44 (danger), 7.79/8.01 (warning, testo scuro sull'ambra),
   17.72/14.77 (primary), 5.46/6.22 (info) — sempre sopra 4.5. */
.table-hover tbody tr:hover .badge.text-bg-success,
.table-hover tbody tr:hover .badge.text-bg-danger,
.table-hover tbody tr:hover .badge.text-bg-primary,
.table-hover tbody tr:hover .badge.text-bg-info {
  color: var(--ip-on-primary) !important;
}
.table-hover tbody tr:hover .badge.text-bg-warning {
  color: var(--ip-on-bright) !important;
}
.table-hover tbody tr:hover .badge.text-bg-success { background: var(--ip-success-solid) !important; border-color: var(--ip-success-solid); }
.table-hover tbody tr:hover .badge.text-bg-danger  { background: var(--ip-danger)  !important; border-color: var(--ip-danger); }
.table-hover tbody tr:hover .badge.text-bg-warning { background: var(--ip-warning) !important; border-color: var(--ip-warning); }
.table-hover tbody tr:hover .badge.text-bg-primary { background: var(--ip-primary) !important; border-color: var(--ip-primary); }
.table-hover tbody tr:hover .badge.text-bg-info    { background: var(--ip-info-solid) !important; border-color: var(--ip-info-solid); }
/* Il badge neutro (conteggi) resta un chip pieno, ma più calmo. Il !important
   serve per battere la utility BS5 .text-bg-secondary (che lo usa).
   Il testo passa da #fff a --ip-on-neutral: in tema scuro --ip-neutral-solid è
   chiaro e il bianco dava 2.60:1. */
.badge.text-bg-secondary {
  background: var(--ip-neutral-solid) !important;
  color: var(--ip-on-neutral) !important;
}
/* 'canceled' nell'originale e' #333333, distinto dal grigio di 'draft' (#999):
   con entrambi su text-bg-secondary i due stati erano indistinguibili.
   Qui il bianco regge in entrambi i temi (12.63 in chiaro, 8.78 in scuro). */
.badge.text-bg-dark { background: var(--ip-canceled) !important; color: #fff !important; }

/* -----------------------------------------------------------------------------
   Timbri di stato: .ip-status
   --------------------------------------------------------------------------
   Le regole qui sopra rendono "timbro" solo i badge colorati, perché i
   .text-bg-secondary/.text-bg-dark servono anche ai conteggi, che devono
   restare chip pieni. Risultato: nella stessa colonna "Bozza" e "Cancellato"
   erano chip pieni in minuscolo mentre INVIATO/PAGATO/VISTO erano timbri
   maiuscoli. .ip-status marca esplicitamente i badge di stato, così tutti gli
   stati hanno la stessa forma e cambia solo il colore. */
/* Marchia i badge di stato, così anche "Bozza" (text-bg-secondary) e
   "Annullata" (text-bg-dark) sono timbri e non chip pieni. Misure e raggio
   vengono dalla base .badge: qui solo la forma (bordo visibile) e le tinte.
   Niente padding proprio: 0.3em/0.7em davano un timbro più alto di 0,4px
   rispetto agli altri, abbastanza da non allinearsi in colonna. */
.badge.ip-status {
  border: 1px solid;
  text-transform: uppercase;
  background: transparent;
}
/* Tinte dei timbri: i token --ip-*-bg (la tinta chiara del tema), non un
   color-mix() al 8% del testo di stato. Il mix del colore scuro sul canvas
   sembrava la stessa cosa, ma abbassava il contrasto proprio dove serve: il
   timbro "Pagata" stava a 4.0:1 ("Scaduta" 4.44, "In attesa" 4.19, "Inviata"
   4.15) contro i 4.8 dei timbri senza tinta, perché il fondo tinto si avvicina
   al colore del testo. Con i --ip-*-bg, che sono già i fondi su cui i testi di
   stato sono verificati, si torna sopra 4.5 in entrambi i temi. */
.badge.ip-status.text-bg-secondary {
  color: var(--ip-neutral-solid) !important;
  background: var(--ip-bg) !important;
  border-color: color-mix(in srgb, var(--ip-neutral-solid) 42%, transparent);
}
.badge.ip-status.text-bg-dark {
  color: var(--ip-canceled) !important;
  background: color-mix(in srgb, var(--ip-canceled) 8%, transparent) !important;
  border-color: color-mix(in srgb, var(--ip-canceled) 42%, transparent);
}
/* In scuro --ip-canceled (#4a4a52) come testo darebbe 1.8:1: "Annullata"
   userebbe il grigio dei testi secondari, che è la stessa famiglia di tono. */
html[data-theme="dark"] .badge.ip-status.text-bg-dark {
  color: var(--ip-text-muted) !important;
  background: color-mix(in srgb, var(--ip-text-muted) 10%, transparent) !important;
  border-color: color-mix(in srgb, var(--ip-text-muted) 42%, transparent);
}
.badge.ip-status.text-bg-success { background: var(--ip-success-bg) !important; }
.badge.ip-status.text-bg-danger  { background: var(--ip-danger-bg) !important; }
.badge.ip-status.text-bg-warning { background: var(--ip-warning-bg) !important; }
.badge.ip-status.text-bg-info    { background: var(--ip-info-bg) !important; }
.badge.ip-status.text-bg-primary { background: var(--ip-primary-soft) !important; }
.table-hover tbody tr:hover .badge.ip-status.text-bg-secondary {
  background: var(--ip-neutral-solid) !important;
  border-color: var(--ip-neutral-solid);
  color: var(--ip-on-neutral) !important;
}
.table-hover tbody tr:hover .badge.ip-status.text-bg-dark {
  background: var(--ip-canceled) !important;
  border-color: var(--ip-canceled);
  color: #fff !important;
}
/* Numeri documento (fatture/preventivi): monospace da registro contabile. */
.ip-num {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Link che contiene .ip-num: padding verticale per bersaglio click >= 24px
   (criterio WCAG 2.5.8). display:inline-block rende il padding cliccabile. */
a > .ip-num,
a > span.ip-num {
  display: inline-block;
  padding: 4px 0;
}
/* -----------------------------------------------------------------------------
   5. Empty state — il vuoto come invito, con la piega dell'aeroplanino
   -------------------------------------------------------------------------- */

.empty-state .empty-state-icon { opacity: 0.4; color: var(--ip-primary); }
tr.empty-row > td {
  text-align: center;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-md);
}
tr.empty-row .empty-icon { color: var(--ip-primary); opacity: 0.5; margin-right: 6px; }
/* La chiamata all'azione sta sotto il messaggio, non accanto: dentro la frase
   il pulsante sembrava parte del testo ("Nessuna fattura presente. + Nuova
   fattura") e su telefono finiva a capo a metà. Da blocco, con larghezza
   propria e margini automatici, occupa una riga sua ed è centrato come
   l'icona e il messaggio sopra. */
tr.empty-row .empty-cta {
  display: flex;
  width: fit-content;
  align-items: center;
  gap: var(--ip-space-2);
  margin: 14px auto 0;
  padding: 5px 12px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg-card);
  color: var(--ip-primary);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background var(--ip-transition), color var(--ip-transition);
}
tr.empty-row .empty-cta:hover { background: var(--ip-primary-soft); }
/* Più azioni possibili (spese: nuova spesa + importa XML): stanno sulla stessa
   riga sotto il messaggio, non una per riga. Il contenitore si centra e i
   pulsanti perdono il margine verticale che spetta al primo. */
tr.empty-row .empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--ip-space-4);
  margin-top: 14px;
}
tr.empty-row .empty-actions .empty-cta { margin: 0; }
/* Tabella vuota: senza righe dati la intestazione è rumore — cosa dovrebbe
   ordinare o leggere l'utente su una colonna che non esiste? L'intestazione
   scompare quando il tbody contiene SOLO la riga di stato vuoto, sia nella
   forma standard (tr.empty-row) sia in quella legacy (unica riga con td
   colspan), e ricompare non appena c'è almeno una riga vera. :has() è
   supportato da tutti i browser moderni (l'app usa già color-mix).

   :only-child non è un dettaglio: senza, bastava che una vista includesse
   empty_row() senza il controllo "lista vuota" perché l'intestazione
   sparisse da una tabella piena — è esattamente quello che è successo alle
   liste clienti e fornitori, dove la riga di stato vuoto conviveva con le
   righe vere e nascondeva le etichette delle colonne. */
.table:has(> tbody > tr.empty-row:only-child) > thead,
.table:has(> tbody > tr:only-child > td[colspan]) > thead {
  display: none;
}
/* -----------------------------------------------------------------------------
   6. Accessibilità — focus visibile e riduzione del movimento
   -------------------------------------------------------------------------- */

/* :not(...) esclude gli elementi che hanno già un proprio border-radius
   (input, select, bottoni, i nostri componenti custom): il border-radius:2px
   qui sotto sovrascriveva il loro raggio appena andavano a fuoco, facendo
   "scattare" gli angoli (bordo squadrato mentre lo stato :focus è attivo).
   Anche i due input dei componenti JS (ricerca globale, filtro IPSelect)
   sono esclusi: hanno già il loro anello di focus dedicato. */
:focus-visible:not(.btn):not(.form-control):not(select):not(.ip-select__control):not(.ip-datepicker-input):not(.ip-global-search__input):not(.ip-select-panel__input) {
  outline: 2px solid var(--ip-primary);
  outline-offset: 1px;
  border-radius: 2px;
}
.btn:focus-visible,
.form-control:focus-visible,
select.form-control:focus-visible,
select.form-select:focus-visible {
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .ip-sidebar { transition: none !important; }
}
/* -----------------------------------------------------------------------------
   7. Login a due pannelli — brand a sinistra, modulo a destra
   -------------------------------------------------------------------------- */

.auth-page {
  background: var(--ip-bg);
}
.ip-auth-shell {
  display: flex;
  min-height: 100vh;
  /* Ancora per il selettore tema, che galleggia in alto a destra. */
  position: relative;
}
.ip-auth-brand {
  display: none;
  flex: 1 1 46%;
  /* Stesso tono della barra superiore (--ip-navbar-bg): l'app usa un solo
     scuro, non due, così login e area interna si riconoscono come lo stesso
     prodotto. */
  background:
    radial-gradient(120% 90% at 85% -10%, rgba(255, 255, 255, 0.07) 0%, transparent 55%),
    radial-gradient(120% 100% at -10% 110%, rgba(0, 0, 0, 0.18) 0%, transparent 60%),
    var(--ip-navbar-bg);
  color: #f4f4f5;
  padding: 56px 7%;
  flex-direction: column;
  justify-content: space-between;
}
@media (min-width: 992px) {
  .ip-auth-brand { display: flex; }
}
.ip-auth-brand-mark {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  color: #fff;
}
/* Sfondo scuro: fold/body neutri quasi-neri diventano invisibili, vanno
   ribaltati su toni chiari. L'accento rosso resta invariato (contrasta bene
   su entrambi gli sfondi). */
/* Stesso discorso della topbar: il logo a colori fissi si legge bene anche
   sul pannello scuro del login, niente override verso il bianco. */
.ip-auth-brand-mark .ip-brand-mark { width: 36px; height: 36px; }
.ip-auth-brand-mark .ip-brand-name { color: #fff; }
.ip-auth-brand-copy { max-width: 460px; }
.ip-auth-brand-copy h1 {
  font-family: var(--ip-display);
  font-weight: 600;
  font-size: clamp(2rem, 3.2vw, 2.75rem);
  line-height: 1.12;
  letter-spacing: -0.015em;
  color: #fff;
  margin: 0 0 18px;
  /* Bilanciato: a 460px il titolo andava a capo lasciando "ordine." da solo
     sulla seconda riga, una parola orfana proprio nella frase più grande. */
  text-wrap: balance;
}
.ip-auth-brand-copy p {
  color: rgba(244, 244, 245, 0.72);
  font-size: var(--ip-text-lg);
  line-height: 1.6;
  margin: 0 0 28px;
  max-width: 380px;
}
.ip-auth-points { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--ip-space-6); }
.ip-auth-points li {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  font-size: var(--ip-text-base);
  color: rgba(244, 244, 245, 0.88);
}
.ip-auth-points i {
  width: 28px;
  height: 28px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  font-size: var(--ip-text-sm);
  font-weight: 600;
}
.ip-auth-brand-foot {
  font-size: var(--ip-text-sm);
  color: rgba(244, 244, 245, 0.45);
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
}
.ip-auth-form {
  flex: 1 1 54%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
}
.ip-auth-card {
  width: 100%;
  max-width: 400px;
}
/* Logo caricato dall'utente: quando esiste anche la variante per tema scuro
   (Impostazioni > Generali) la pagina rende entrambe le immagini e sceglie qui
   quale mostrare — il tema vive su html[data-theme] ed è deciso dal browser,
   quindi il server non può fare la scelta al render (vedi layout/auth.php). */
.login-logo--light { display: block; }
.login-logo--dark { display: none; }
html[data-theme="dark"] .login-logo--light { display: none; }
html[data-theme="dark"] .login-logo--dark { display: block; }
.ip-auth-form-box {
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
  padding: 26px 26px 22px;
}
/* Pulsante "Accedi": stesso blu del brand (var(--ip-primary), non un rosso
   fisso scollegato dal tema), a piena larghezza. Niente lift né scia lucida
   al passaggio del mouse: nessun altro bottone dell'app si solleva, e
   un'ombra ferma sul tema scuro (dove --ip-primary si ribalta in
   quasi-bianco, vedi regola html[data-theme="dark"] .btn-primary sopra)
   restava comunque scura — qui basta lo scambio colore già previsto da
   Bootstrap sul :hover.

   Altezza: prima il padding 14px sommato al corpo 16px portava il bottone a
   54px, contro i 38px del "Accedi con passkey" che gli sta 6px sotto e i 36px
   dei campi sopra. In una card da 400px due pulsanti a tutta larghezza di
   altezze diverse sembravano appartenere a due pagine; il padding orizzontale
   resta più generoso (14px) per non stringere il testo, quello verticale scende
   a 6px, la stessa misura del pulsante passkey: stessa altezza, stesso giro. */
.ip-auth-form-box .btn-primary {
  width: 100%;
  margin-bottom: 14px;
  padding: 6px 14px;
  font-size: var(--ip-text-lg);
  font-weight: 600;
  letter-spacing: 0.025em;
  border-radius: var(--ip-radius-sm);
}
.ip-auth-form-box .btn-primary:focus-visible {
  outline: none;
  box-shadow: var(--ip-shadow-focus);
}
/* Separatore "oppure" fra form e login passkey: riga su entrambi i lati,
   come nelle card di autenticazione moderne, senza introdurre markup extra. */
.auth-separator {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  color: var(--ip-text-muted);
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  margin: 4px 0 14px;
}
.auth-separator::before,
.auth-separator::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--ip-border);
}
.ip-auth-form-box .btn-link-secondary {
  display: block;
  text-align: center;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  text-decoration: none;
  font-weight: 500;
}
.ip-auth-form-box .btn-link-secondary:hover { color: var(--ip-primary); }
/* Piede della card di accesso (torna al login, password dimenticata). */
.ip-auth-foot { margin-top: 18px; }
/* -----------------------------------------------------------------------------
   7bis. Accesso e secondo fattore — il codice come cifra stampata
   ----------------------------------------------------------------------------
   Il prodotto parla di cifre (importi, partite IVA, scadenze) con IBM Plex
   Mono; anche il codice TOTP è una cifra dell'utente, quindi si stampa con
   la stessa faccia, in sei caselle da modulo, invece del campo generico.
   Il login con passkey è il secondo momento caratteristico: il pulsante
   porta il marchio a tocchi concentrici disegnato con gli stessi tratti
   geometrici del brand mark, e mentre si aspetta il prompt del dispositivo
   pulsa solo il marchio — il resto della pagina resta fermo. */
.ip-passkey-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ip-space-5);
  width: 100%;
}
.ip-passkey-mark {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
/* Il marchio a tocchi concentrici del pulsante passkey: usa currentColor, così
   eredita il colore del testo del bottone (bianco su .btn-primary, scuro su un
   secondario neutro) anziché --ip.primary, che coincideva con lo sfondo del
   pulsante primario e rendeva l'icona invisibile. */
.ip-passkey-btn .ip-passkey-mark { color: currentColor; }
.ip-passkey-btn.is-waiting .ip-passkey-mark {
  animation: ip-passkey-pulse 1.1s ease-in-out infinite;
}
.ip-passkey-btn[aria-busy="true"] { pointer-events: none; }
@keyframes ip-passkey-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}
@media (prefers-reduced-motion: reduce) {
  .ip-passkey-btn.is-waiting .ip-passkey-mark { animation: none; }
}
/* Riga di stato sotto i pulsanti passkey: mono, corta, precisa. In errore
   diventa danger — niente apologhi, solo cosa è andato storto. */
.ip-webauthn-status {
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
  margin: 10px 0 0;
  min-height: 17px;
}
.ip-webauthn-status.is-error { color: var(--ip-danger); }
/* Caselle del codice: sei celle da modulo stampato. Ogni cella è un input
   vero (digitabile, navigabile, accessibile); la coordinazione fra celle
   (avanzamento, backspace, incolla) sta in session_mfa.php.
   flex: 1 1 0 + max-width: su schermi stretti le celle si comprimono
   (6 x 44px + 5 gap = 304px sforano una card da 320px: sulle vecchie
   caselle l'ultima cella usciva dal bordo). Sulla card da 400px (348px di
   contenuto) le sei celle al loro massimo occupano solo 304px: senza
   justify-content restavano appiccicate al bordo sinistro con 44px di
   vuoto a destra, come un modulo compilato a metà — centrate, il gruppo
   si legge come un unico blocco nel mezzo della card. */
.ip-code-slots {
  display: flex;
  justify-content: center;
  gap: var(--ip-space-4);
  margin: 8px 0 24px;
}
.ip-code-slot {
  flex: 1 1 0;
  max-width: 44px;
  height: 52px;
  padding: 0;
  text-align: center;
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-3xl);
  line-height: 1;
  color: var(--ip-text-strong);
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border-strong);
  border-radius: var(--ip-radius-sm);
  caret-color: var(--ip-primary);
  transition: border-color var(--ip-transition),
              box-shadow var(--ip-transition),
              background-color var(--ip-transition);
}
/* Niente anello (--ip-shadow-focus) qui: su una casella così piccola
   (44×52px, radius 4px) il bordo di 1px più l'anello di 3px a bordo netto
   si leggevano come due contorni concentrici invece che come un alone
   morbido. Il focus resta comunque chiaro con bordo scuro + sfondo tinto. */
.ip-code-slot:focus {
  outline: none;
  border-color: var(--ip-primary);
  background: var(--ip-primary-soft);
}
.ip-code-slots.is-invalid .ip-code-slot {
  border-color: var(--ip-danger);
  background: var(--ip-danger-bg);
}
/* -----------------------------------------------------------------------------
   7ter. Pagine di accesso — l'ingresso del registro
   ----------------------------------------------------------------------------
   Ogni passo del login porta un'intestazione propria dentro la card (titolo in
   Fraunces + sottotitolo, tutte le pagine con la stessa forma: niente icone
   che distinguono un passo dall'altro, la coerenza fa da guida), le righe di
   scelta del metodo sono voci di un indice, i campi sono righi del libro
   mastro (bordo solo sotto, il focus è la penna che sottolinea) e i richiami
   importanti (tentativi rimasti, uso limitato dei codici) sono righe laterali
   colorate, non box urlanti. */
.ip-auth-head {
  margin-bottom: 26px;
  text-align: center;
}
.ip-auth-head h2 {
  font-family: var(--ip-display);
  font-weight: 600;
  font-size: 1.5rem;
  letter-spacing: -0.015em;
  margin: 0 0 6px;
}
.ip-auth-head p {
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  line-height: 1.55;
  margin: 0;
}
/* Righe di scelta del metodo (selezione MFA): una voce di indice, con tile
   icona, titolo + descrizione e chevron. La riga passkey è la voce primaria
   (bordo e tinta primari), le altre restano neutre. */
.ip-mfa-method-btn {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  width: 100%;
  padding: 14px 16px;
  margin-bottom: 10px;
  text-align: left;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  color: var(--ip-text);
  transition: border-color var(--ip-transition),
              background-color var(--ip-transition);
}
.ip-mfa-method-btn:hover {
  border-color: var(--ip-border-strong-hover);
  background: var(--ip-hover);
}
.ip-mfa-method-btn:focus-visible {
  outline: none;
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus);
}
.ip-mfa-method-btn__icon {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg);
  color: var(--ip-text-muted);
}
.ip-mfa-method-btn__icon svg { width: 20px; height: 20px; }
.ip-mfa-method-btn__text { flex: 1 1 auto; min-width: 0; }
.ip-mfa-method-btn__title {
  font-weight: 500;
  font-size: var(--ip-text-md);
  margin-bottom: 2px;
}
.ip-mfa-method-btn__desc {
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  line-height: 1.4;
}
.ip-mfa-method-btn__chevron {
  flex: 0 0 auto;
  color: var(--ip-text-muted);
}
.ip-mfa-method-btn__chevron svg { width: 18px; height: 18px; }
.ip-mfa-method-btn__title,
.ip-mfa-method-btn__desc { display: block; }
/* Voce consigliata: in tema scuro --ip-primary e' quasi bianco, e un bordo
   pieno di quel colore gridava piu' del titolo. Basta un bordo appena piu'
   marcato degli altri e l'icona tinta: si distingue senza abbagliare. */
.ip-mfa-method-btn--primary {
  border-color: color-mix(in srgb, var(--ip-primary) 45%, var(--ip-border));
}
/* --ip-primary-soft e' trasparente: sopra --ip-bg-card spariva e l'icona
   della passkey sembrava senza fondo rispetto alle altre. Tinta opaca
   mescolata al fondo card, cosi' resta visibile in entrambi i temi. */
.ip-mfa-method-btn--primary .ip-mfa-method-btn__icon {
  background: color-mix(in srgb, var(--ip-primary) 14%, var(--ip-bg-card));
  color: var(--ip-primary);
}
.ip-mfa-method-btn--primary:hover {
  border-color: color-mix(in srgb, var(--ip-primary) 70%, var(--ip-border));
  background: var(--ip-hover);
}
/* Richiamo a riga laterale (tentativi rimasti, codici di recupero): bordo
   sinistro spesso + fondo tenue del colore di stato, testo del colore pieno.
   Nessun box: è una nota a margine del registro. */
.ip-auth-callout {
  padding: 10px 12px;
  margin-bottom: 18px;
  border-left: 3px solid var(--ip-border-strong);
  border-radius: 0 var(--ip-radius-sm) var(--ip-radius-sm) 0;
  background: var(--ip-bg);
  font-size: var(--ip-text-base);
  line-height: 1.5;
}
.ip-auth-callout strong { font-weight: 600; }
.ip-auth-callout--warning {
  border-left-color: var(--ip-warning-text);
  background: var(--ip-warning-bg);
  /* --ip-warning-text e non --ip-warning: l'arancio pieno su questa tinta sta
     a 2.1:1, e qui è testo corrente che spiega cosa fare con il secondo
     fattore. Il gradino "-text" è quello verificato sui fondi di stato. */
  color: var(--ip-warning-text);
}
.ip-auth-callout--danger {
  border-left-color: var(--ip-danger-text);
  background: var(--ip-danger-bg);
  color: var(--ip-danger);
}
.ip-auth-callout .fa { margin-right: 6px; }
/* Pannello informativo della pagina passkey (come funziona, per chi non le
   ha mai usate): fondo canvas, testo piccolo e grigio — una nota, non una
   lezione. */
.ip-auth-info {
  padding: 14px 16px;
  margin-bottom: 18px;
  background: var(--ip-bg);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  font-size: var(--ip-text-sm);
  line-height: 1.7;
  color: var(--ip-text-muted);
}
.ip-auth-info__title {
  font-weight: 600;
  color: var(--ip-text);
  margin-bottom: 6px;
}
.ip-auth-info strong { color: var(--ip-text); }
/* Conto alla rovescia del codice TOTP: una cifra, in mono come le altre. */
.ip-auth-timer {
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  margin: 0 0 18px;
  text-align: center;
}
/* I campi dei moduli di accesso seguono ora la stessa casella con bordo
   pieno usata nel resto dell'app (sezione 7 sopra, .form-control base):
   prima qui c'era una variante "a riga" (solo sottolineatura, fondo
   trasparente) che rendeva il login visivamente incoerente rispetto a
   tutti gli altri form. Lo stato invalid resta locale perché il login non
   passa da .ip-doc-field (che gestisce .is-invalid altrove). */
.ip-auth-form-box .form-control.is-invalid,
.ip-auth-form-box .form-control.is-invalid:focus {
  border-color: var(--ip-danger);
  box-shadow: var(--ip-shadow-focus-danger));
}
/* Campo codice di recupero: la cifra stampata in mono, come gli importi. */
.ip-auth-form-box .form-control.is-mono {
  font-family: var(--ip-mono, monospace);
  letter-spacing: 0.1em;
}
/* -----------------------------------------------------------------------------
   7ter. Pagina Sicurezza account — i fattori come carte affiancate
   ----------------------------------------------------------------------------
   MFA e passkey sono due fattori dello stesso rango: su desktop stanno
   affiancati (una griglia di due colonne), e ogni card porta lo stato
   nell'header, non nascosto nel corpo. Il setup TOTP usa un "ticket" a bordo
   tratteggiato — un QR da timbrare sull'app — e i codici di recupero sono
   stampati in mono come una fila di talloncini. */
.ip-factors {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ip-space-7);
}
@media (min-width: 992px) {
  .ip-factors { grid-template-columns: 1fr 1fr; }
}
/* Un solo ritmo verticale per tutta la pagina: il margin-bottom che il
   tema da' a ogni .card, sommato al gap della griglia, rendeva le due
   card di altezza diversa (stretch sul box esterno, non sul bordo) e la
   distanza verso i codici di recupero diversa da quella tra le colonne.
   Le card nella griglia azzerano il margine; lo spazio lo decide il gap. */
.ip-factors { margin-bottom: var(--ip-space-7); }
.ip-factors > .card { margin-bottom: 0; height: 100%; }
.ip-backup-card { margin-bottom: var(--ip-space-7); }
.ip-factor-card .card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-4);
}
.ip-factor-card .card-header .fa {
  margin-right: 6px;
}
.ip-totp-ticket {
  display: inline-block;
  padding: 12px;
  border: 1px dashed var(--ip-border-strong);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card);
}
.ip-totp-ticket img {
  display: block;
  width: 168px;
  height: 168px;
}
.ip-secret-line {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-sm);
  padding: 8px 10px;
  background: var(--ip-primary-soft);
  border-radius: var(--ip-radius-sm);
  word-break: break-all;
}
.ip-secret-line button {
  flex: 0 0 auto;
  border: 0;
  background: none;
  color: var(--ip-text-muted);
  padding: 2px 6px;
  border-radius: var(--ip-radius-sm);
}
.ip-secret-line button:hover { color: var(--ip-text); background: var(--ip-hover); }
.ip-secret-line button:focus-visible {
  outline: none;
  box-shadow: var(--ip-shadow-focus);
}
.ip-backup-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: var(--ip-space-3, 10px);
  margin: 0 0 var(--ip-space-4, 16px);
  padding: 0;
  list-style: none;
  counter-reset: ip-backup;
}
/* Indice in grigio a sinistra: serve a dire "mi manca il 7" senza
   rileggere tutto, e a spuntare i codici usati sul foglio stampato. */
.ip-backup-stub::before {
  content: counter(ip-backup, decimal-leading-zero);
  font-weight: 400;
  font-size: 0.85em;
  color: var(--ip-text-muted, #888);
  letter-spacing: 0;
}
.ip-backup-sep { color: var(--ip-text-muted, #888); margin: 0 1px; }
.ip-backup-card .card-header { display: flex; justify-content: space-between; align-items: center; }
.ip-backup-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px;
  padding-top: var(--ip-space-3, 12px);
  border-top: 1px solid var(--ip-border);
}
.ip-backup-actions__save { display: flex; flex-wrap: wrap; gap: 8px; }
/* Stampa: solo i codici, niente sidebar ne' pulsanti. */
@media print {
  body * { visibility: hidden; }
  .ip-backup-card, .ip-backup-card * { visibility: visible; }
  .ip-backup-card { position: absolute; inset: 0 auto auto 0; width: 100%; border: 0; }
  .ip-backup-actions { display: none; }
}
.ip-backup-stub {
  counter-increment: ip-backup;
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  user-select: all;
  padding: 8px 12px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg-card);
}
.ip-empty-passkeys {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ip-space-5);
  text-align: center;
  padding: 24px 12px;
  border: 1px dashed var(--ip-border-strong);
  border-radius: var(--ip-radius);
  color: var(--ip-text-muted);
  margin-bottom: 16px;
}
.ip-empty-passkeys .ip-passkey-mark {
  width: 28px;
  height: 28px;
  opacity: 0.55;
}
/* Elenco passkey: una riga per chiave, nome sopra e date in una frase
   sotto, cestino allineato a destra. Sostituisce la tabella a quattro
   colonne, che con una o due chiavi era quasi tutta spazio vuoto. */
.ip-passkey-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
}
.ip-passkey-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
}
.ip-passkey-item + .ip-passkey-item { border-top: 1px solid var(--ip-border-soft); }
.ip-passkey-item__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: var(--ip-radius);
  background: var(--ip-bg);
  color: var(--ip-text-muted);
}
.ip-passkey-item__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ip-passkey-item__name {
  font-weight: 600;
  color: var(--ip-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ip-passkey-item__meta {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-passkey-item__action { flex: none; margin: 0; }
/* Disattivazione MFA: <details> nativo, trigger silenzioso in danger. */
.ip-factor-danger summary {
  cursor: pointer;
  list-style: none;
  color: var(--ip-danger-text);
  font-size: var(--ip-text-base);
}
.ip-factor-danger summary::-webkit-details-marker { display: none; }
.ip-factor-danger summary:hover { text-decoration: underline; }
.ip-factor-danger summary:focus-visible {
  outline: none;
  box-shadow: var(--ip-shadow-focus);
  border-radius: var(--ip-radius-sm);
}
/* Riepilogo stato in cima alla pagina: uno scudo tinta il livello e tre chip
   elencano i fattori (password sempre attiva, MFA, passkey). Il colore dello
   scudo è l'unico segnale "a semaforo" — il resto resta testo leggibile. */
.ip-security-summary {
  display: flex;
  gap: var(--ip-space-7);
  align-items: flex-start;
  margin-bottom: 16px;
  padding: 18px 20px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
}
.ip-security-summary__shield {
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ip-radius);
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-3xl);
}
.ip-security-summary.is-strong .ip-security-summary__shield { background: var(--ip-success-bg); color: var(--ip-success); }
.ip-security-summary.is-good   .ip-security-summary__shield { background: var(--ip-warning-bg); color: var(--ip-warning); }
.ip-security-summary.is-weak   .ip-security-summary__shield { background: var(--ip-danger-bg);  color: var(--ip-danger); }
.ip-security-summary__body { flex: 1 1 auto; min-width: 0; }
.ip-security-summary__title {
  font-family: var(--ip-display);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
  margin: 0 0 4px;
}
.ip-security-summary__text {
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  line-height: 1.55;
  margin: 0 0 12px;
}
.ip-security-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.ip-security-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 6px 11px;
  border: 1px solid var(--ip-border);
  border-radius: 999px;
  background: var(--ip-bg);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-security-chip .fa { font-size: var(--ip-text-base); }
.ip-security-chip.is-on {
  color: var(--ip-text);
  border-color: var(--ip-border-strong);
  background: var(--ip-bg-card);
}
.ip-security-chip.is-on .fa { color: var(--ip-success); }
.ip-security-chip__meta {
  font-family: var(--ip-mono, monospace);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-security-chip.is-on .ip-security-chip__meta { color: var(--ip-success-text); }
/* Pill di stato negli header delle card: tinte il tema, non Bootstrap. */
/* Pillola di stato degli header di card (pagina Sicurezza): resta una pillola,
   forma diversa dai timbri perché non è lo stato di un documento ma una
   condizione dell'account. Le misure però ora sono coerenti fra le tre
   varianti: il bordo trasparente serve a "is-muted" per non essere 2px più
   alta delle altre due. */
.ip-status-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: var(--ip-text-sm);
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
}
.ip-status-pill.is-success { background: var(--ip-success-bg); color: var(--ip-success-text); }
.ip-status-pill.is-warning { background: var(--ip-warning-bg); color: var(--ip-warning-text); }
.ip-status-pill.is-muted {
  background: var(--ip-bg);
  color: var(--ip-text-muted);
  border-color: var(--ip-border);
}
/* Su schermi stretti lo scudo passa sopra il testo e le chip vanno a capo. */
@media (max-width: 575px) {
  .ip-security-summary { flex-direction: column; align-items: center; text-align: center; }
  .ip-security-chips { justify-content: center; }
}
/* -----------------------------------------------------------------------------
    8. Stampa — il guscio sparisce, resta la carta
    -------------------------------------------------------------------------- */

@media print {
  .ip-topbar, .ip-sidebar, .ip-sidebar-backdrop, #headerbar, #modal-placeholder { display: none !important; }
  #main-area { margin-left: 0 !important; }
  .card { box-shadow: none !important; }
}
/* -----------------------------------------------------------------------------
   19. Scheda cliente — intestazione dentro card
   -------------------------------------------------------------------------- */

.ip-client-title {
  margin: 0 0 6px;
  font-family: var(--ip-display);
  font-size: var(--ip-text-2xl);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ip-text);
}
.ip-client-address {
  margin: 0;
  font-size: var(--ip-text-base);
  line-height: 1.7;
  color: var(--ip-text-muted);
}
/* Righe vuote dell'indirizzo (campi non compilati) non devono lasciare buchi. */
.ip-client-address br + br { display: none; }
/* Data di iscrizione sotto l'indirizzo: filo sottile prima della riga,
   separa l'identità del cliente dal suo storico nell'app. */
.ip-client-since {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--ip-border-soft);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
/* Riepilogo economico del cliente: griglia 2x2 di cifre con etichetta
   maiuscoletta muta, stesso linguaggio delle KPI del dashboard. */
.ip-client-stats {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 24px;
}
.ip-client-stat-label {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-client-stat-value {
  margin-top: 3px;
    font-weight: 700;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}
.ip-client-stat-value--success { color: var(--ip-success-text); }
.ip-client-stat-value--warning { color: var(--ip-warning-text); }
.ip-client-stat-value--danger  { color: var(--ip-danger-text); }
/* Hint sotto le statistiche: quanti documenti sono scaduti (solo se > 0). */
.ip-client-stat-hint {
  margin: 12px 0 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-danger-text);
}
/* Il conteggio scadute e' anche un link diretto alla tab Fatture: stesso
   colore del testo circostante, sottolineato solo al passaggio/focus per
   non sembrare un'etichetta qualunque quando la pagina e' ferma. */
.ip-client-stat-hint-link {
  color: inherit;
  text-decoration: none;
  font-weight: 600;
}
.ip-client-stat-hint-link:hover,
.ip-client-stat-hint-link:focus-visible {
  text-decoration: underline;
}
.ip-client-stat-meta {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--ip-border-soft);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
/* Riga segnaposto dentro una card-tabella senza dati.
   Niente bordi: una riga unica in una tabella altrimenti vuota disegnava due
   filetti orizzontali che sembravano un separatore rimasto li' per errore. */
.ip-card-empty {
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-style: italic;
  text-align: center;
  padding: 18px var(--ip-card-pad-x) !important;
  border: 0 !important;
  box-shadow: none !important;
}

/* -----------------------------------------------------------------------------
   19b. Scheda cliente — masthead anagrafico e riepilogo rinnovati
   -------------------------------------------------------------------------- */

/* Tessera anagrafica: il biglietto da visita del cliente. Avatar monogramma,
   identità testuale e — a destra — la piastra dei codici fiscali. */
.ip-client-masthead__body {
  display: flex;
  gap: var(--ip-space-9);
  align-items: flex-start;
  padding: 22px 24px;
}
.ip-client-mono {
  flex: 0 0 auto;
  width: 62px;
  height: 62px;
  border-radius: 16px;
  display: grid;
  place-items: center;
  font-family: var(--ip-display);
  font-weight: 600;
  font-size: 26px;
  letter-spacing: -0.02em;
  color: var(--ip-on-primary);
  background: linear-gradient(140deg, var(--ip-primary), var(--ip-primary-dark));
  box-shadow: var(--ip-shadow-sm);
}
/* Tre iniziali ("ICS Duca d'Aosta" → IDA) nella stessa piastrella: a 26px la
   terza lettera uscirebbe dal quadrato, quindi il carattere scende e il
   tracking si stringe finché le tre lettere tornano centrate. */
.ip-client-mono--3 {
  font-size: 19px;
  letter-spacing: -0.01em;
}
.ip-client-id {
  flex: 1 1 auto;
  min-width: 0;
}
.ip-client-name {
  margin: 0;
  font-family: var(--ip-display);
  font-size: 25px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ip-text);
  overflow-wrap: anywhere;
}
.ip-client-id__meta {
  margin-top: 7px;
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  flex-wrap: wrap;
  font-size: var(--ip-text-sm);
}
/* Il tipo cliente diventa un chip tondo all'interno della tessera. */
.ip-client-masthead .ip-client-type {
  background: var(--ip-primary-soft);
  color: var(--ip-text);
  padding: 3px 10px;
  border-radius: 999px;
  font-weight: 600;
}
.ip-client-id__loc {
  color: var(--ip-text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-2);
}
.ip-client-id__loc i { font-size: var(--ip-text-xs); }
.ip-client-since {
  margin: 10px 0 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-2);
}
.ip-client-since i { font-size: var(--ip-text-xs); }

/* Piastra codici fiscali: i veri identificativi di un cliente italiano. Mono
   perché i codici sono stringhe, non testo da leggere. */
.ip-client-codes {
  flex: 0 0 auto;
  display: grid;
  gap: var(--ip-space-5);
  min-width: 240px;
}
/* Da tre codici in su (P.IVA, C.F., destinatario SdI, codice cliente) la
   colonna singola farebbe crescere la tessera fino a sembrare un modulo. Due
   colonne tengono la piastra bassa e fanno leggere i codici a coppie, che è
   anche il modo in cui si confrontano a colpo d'occhio. Sotto i due codici
   resta una colonna sola, come prima. */
.ip-client-codes:has(.ip-fisc-code:nth-child(3)) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  min-width: 360px;
}
.ip-fisc-code {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-rows: auto auto;
  align-items: center;
  column-gap: var(--ip-space-5);
  padding: 9px 12px;
  background: var(--ip-bg);
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius);
}
.ip-fisc-code__label {
  grid-column: 1;
  grid-row: 1;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-fisc-code__value {
  grid-column: 1;
  grid-row: 2;
  font-family: var(--ip-mono);
  font-size: var(--ip-text-md);
  font-weight: 500;
  letter-spacing: .02em;
  color: var(--ip-text-strong);
  overflow-wrap: anywhere;
}
.ip-fisc-code__copy {
  grid-column: 2;
  grid-row: 1 / 3;
  border: 0;
  background: transparent;
  color: var(--ip-text-muted);
  cursor: pointer;
  padding: 4px 6px;
  border-radius: var(--ip-radius-sm);
  transition: var(--ip-transition);
}
.ip-fisc-code__copy:hover,
.ip-fisc-code__copy:focus-visible { color: var(--ip-primary); background: var(--ip-primary-soft); }
.ip-fisc-code__copy.is-copied { color: var(--ip-success); }
.ip-fisc-code__copy:focus-visible { outline: none; box-shadow: var(--ip-shadow-focus); }

/* Riepilogo economico: da griglia di sole cifre a piastrelle con icona. */
.ip-client-stats {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}
.ip-client-stat {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-2);
  padding: 12px 14px;
  background: var(--ip-bg);
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius);
}
.ip-client-stat-label { display: inline-flex; align-items: center; gap: var(--ip-space-3); }
.ip-client-stat-label i { font-size: var(--ip-text-xs); opacity: .7; }
.ip-client-stat-value { font-size: var(--ip-text-xl); }

/* Contatti: lista con icona anziché tabella chiave/valore. */
.ip-contact-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}
.ip-contact-list li {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  padding: 9px 4px;
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-contact-list li:last-child { border-bottom: 0; }
.ip-contact-list__ic {
  flex: 0 0 30px;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 8px;
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-base);
}
.ip-contact-list a { color: var(--ip-text); text-decoration: none; }
.ip-contact-list a:hover { color: var(--ip-primary); text-decoration: underline; }

/* Note del cliente: registro, non card annidate. Ogni nota era un .card.small
   con bordo e piè di card grigio per la sola data: tre note facevano tre
   scatole dentro la scatola e la data occupava una riga intera. Qui la voce
   è testo + data separati da un filetto, come le righe di un registro. Il
   tetto di altezza evita che dieci note allunghino la coppia di card e
   lascino la rubrica contatti accanto mezza vuota. */
.ip-note-list {
  display: grid;
  max-height: 240px;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin-bottom: var(--ip-space-6);
  padding-right: var(--ip-space-2);
}
.ip-note {
  padding: 10px 0;
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-note:first-child { padding-top: 0; }
.ip-note:last-child { padding-bottom: 0; border-bottom: 0; }
.ip-note__text {
  margin: 0;
  font-size: var(--ip-text-md);
  color: var(--ip-text);
  overflow-wrap: anywhere;
}
.ip-note__date {
  margin: var(--ip-space-2) 0 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
}
/* Compositore: campo a tutta larghezza e azione sotto, allineata a destra.
   L'input-group teneva il pulsante dentro il bordo del campo e toglieva al
   testo 131px: su telefono restavano poche decine di pixel utili per riga. */
.ip-note-form .form-control {
  min-height: 56px;
  resize: vertical;
}
.ip-note-form__actions {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--ip-space-4);
}

/* La spunta "cliente attivo" vive nella testata della prima card del form:
   resetta il margin-bottom di .form-badge per allinearsi alla riga icona+label. */
.ip-table-card-header > .form-badge.ip-card-header-badge { margin: 0; }

/* In colonna stretta le piastrelle tornano a 2, la tessera si impila. */
@media (max-width: 767.98px) {
  .ip-client-masthead__body { flex-direction: column; }
  .ip-client-codes { width: 100%; min-width: 0; }
  .ip-client-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* -----------------------------------------------------------------------------
   Aree tattili su dispositivi touch (WCAG 2.5.8 chiede 24px, qui si punta ai
   44px delle linee guida per il dito).
   --------------------------------------------------------------------------
   Condizione sul puntatore e non sulla larghezza: un tablet largo 1024px usa
   comunque le dita, un desktop con finestra stretta no. Con il mouse i 30px
   di .btn-sm restano, perche' le barre dei filtri e le righe delle tabelle
   sono dimensionate su quella misura. Cresce solo l'altezza dei pulsanti
   compatti; per i controlli di sola icona anche la larghezza, altrimenti
   restano un rettangolo alto e stretto. */
@media (pointer: coarse) {
  .btn-sm,
  .btn-group-sm > .btn {
    min-height: 44px;
  }
  .ip-theme-toggle,
  .ip-import-chosen__clear {
    min-width: 44px;
    min-height: 44px;
  }
}

/* -----------------------------------------------------------------------------
   20. Aree tattili — sidebar off-canvas su mobile
   -------------------------------------------------------------------------- */

@media (max-width: 991.98px) {
  .ip-nav-link {
    min-height: 44px;
    padding-top: 10px;
    padding-bottom: 10px;
  }
  .ip-nav-sub-link {
    /* Target tattile pieno (44px, WCAG 2.5.5) come le voci di primo livello. */
    min-height: 44px;
    display: flex;
    align-items: center;
  }
  .ip-sidebar-toggle {
    width: 44px;
    height: 44px;
  }
  #submenu .nav-link {
    min-height: 44px;
    display: flex;
    align-items: center;
  }
}
/* -----------------------------------------------------------------------------
   21. Shim classi Bootstrap 3 rimaste nel markup
   --------------------------------------------------------------------------
   Queste classi non esistono ne' in Bootstrap 5 ne' altrove nel foglio: erano
   quindi inerti, e il markup che le usa rendeva con lo stile di default
   (testo di aiuto come paragrafo normale, bottoni "xs" a grandezza piena,
   tabelle "condensed" a densita' normale). Le rimappiamo sull'equivalente BS5
   invece di riscrivere le view, così vale anche per il markup legacy residuo.
   -------------------------------------------------------------------------- */

/* .help-block → .form-text */
.help-block {
  margin-top: .25rem;
  margin-bottom: 0;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
}
/* .btn-xs → più compatto di .btn-sm (usato nelle righe fitte delle tabelle) */
.btn-xs {
  padding: .15rem .4rem;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  border-radius: var(--ip-radius-sm);
}
/* .table-condensed → .table-sm */
.table-condensed > :not(caption) > * > * {
  padding: .3rem .5rem;
}
/* .clearfix → utilita' BS5 */
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}
/* .no-margin → margini azzerati (BS3; usato ancora in molte viste per
   stringere righe KPI, schede e intestazioni) */
.no-margin {
  margin: 0 !important;
}
/* .form-inline → riga orizzontale (in BS5 non esiste più) */
.form-inline {
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  gap: var(--ip-space-5);
}
.form-inline .form-group,
.form-inline .mb-3 { margin-bottom: 0; }
.form-inline select.form-control,
.form-inline .form-control { width: auto; }
.form-inline > .float-end { margin-left: auto; }
/* .btn-block → full-width */
.btn-block {
  display: block;
  width: 100%;
}
/* Meter forza password (users/form_change_password): in BS5 le classi
   progress-bar-danger/warning/success non esistono e le tre barre rendevano
   tutte blu di default — il livello di forza non era più leggibile. */
.progress-bar-danger  { background-color: var(--ip-danger); }
.progress-bar-warning { background-color: var(--ip-warning); }
.progress-bar-success { background-color: var(--ip-success); }
/* Checkbox nelle celle: il controllo e' quello unico definito nella sezione
   dei form, qui si allinea solo al testo della riga. */
.ip-items-table td input[type="checkbox"],
.ip-items-table th input[type="checkbox"],
.table td input[type="checkbox"],
.table th input[type="checkbox"] { vertical-align: middle; }
/* -----------------------------------------------------------------------------
   22b. Dashboard a finestre (stile Fattura24)
   --------------------------------------------------------------------------
   La dashboard e' un report di sintesi a "finestre": card indipendenti con
   header (icona + titolo + azioni espandi/nascondi) e corpo. L'utente puo'
   espandere una finestra a tutta larghezza, nasconderla e ripristinarla dal
   pannello "Personalizza finestre" (persistenza in localStorage). Il
   periodo di riferimento e' l'anno del filtro globale in topbar; il pulsante
   "Personalizza finestre" vive nell'headerbar della pagina (slot azioni).
   -------------------------------------------------------------------------- */

.ip-dash-personalize {
  position: relative;
}
.ip-windows-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  border: 1px solid var(--ip-border);
  background: var(--ip-bg-card);
  color: var(--ip-text);
  font-weight: 500;
}
.ip-windows-toggle:hover {
  border-color: var(--ip-primary);
  color: var(--ip-primary);
  background: var(--ip-primary-soft);
}
.ip-windows-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: var(--ip-z-modal);
  min-width: 240px;
  padding: 10px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
}
.ip-windows-panel[hidden] { display: none; }
.ip-windows-panel-title {
  margin: 0 6px 8px;
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-windows-opt {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  padding: 4px 6px;
  margin: 0;
  border-radius: var(--ip-radius-sm);
  font-size: var(--ip-text-base);
  color: var(--ip-text);
  cursor: pointer;
}
.ip-windows-opt:hover { background: var(--ip-hover); }
.ip-windows-opt input[type="checkbox"] { margin: 0; }

/* Griglia finestre: 3 colonne desktop, 2 tablet, 1 mobile. */
.ip-dash-windows {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--ip-space-7);
  /* stretch (non start): le finestre della stessa riga chiudono alla stessa
     altezza. Con align-items:start bastava un sottotitolo in più (IVA) o in
     meno (Saldo) perché la riga di KPI risultasse a gradini. */
  align-items: stretch;
  margin-bottom: var(--ip-space-7);
}
@media (max-width: 1099.98px) {
  .ip-dash-windows { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767.98px) {
  .ip-dash-windows { grid-template-columns: minmax(0, 1fr); }
}

/* Finestra: stessa base delle card moderne, ma strutturata in colonna
   header + corpo. Le tabelle dentro le finestre flush restano a bordo. */
.ip-window {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  /* Ombra di sistema (stessa di .card/.ip-table-card) invece del rgba
     cablato: così tutte le superfici condividono lo stesso rigo d'ombra. */
  box-shadow: var(--ip-shadow-sm);
  overflow: hidden;
}
.ip-window--wide,
.ip-window--expanded { grid-column: 1 / -1 !important; }
/* La finestra "Indice" è già a tutta larghezza (--wide): il pulsante espandi
   togglerebbe una classe senza effetto visibile, sembrando rotto. Via. */
.ip-window--wide .ip-window-expand { display: none; }
.ip-window.is-hidden { display: none !important; }
.ip-window--alert { border-left: 3px solid var(--ip-danger); }

.ip-window-header {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  padding: 10px 14px;
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-window-title {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  text-decoration: none;
  /* Titolo <h2>: il margine inferiore di Bootstrap (8px) lo spostava in su di
     4px dentro la testata flex, rispetto a icona, conteggio e azioni. */
  margin: 0;
}
.ip-window-title:hover { color: var(--ip-primary); }
/* Il titolo della finestra è anche il link alla schermata di dettaglio: da
   solo è alto 17px, sotto il minimo di 24px per un bersaglio (WCAG 2.5.8).
   Il padding cresce l'area senza muovere il testo. */
.ip-window-title a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: inherit;
  text-decoration: none;
}
.ip-window-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-sm);
  font-weight: 700;
}
.ip-window-more {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  text-decoration: none;
  white-space: nowrap;
}
.ip-window-more:hover { color: var(--ip-primary); }
/* Footer della finestra: ospita il link "Mostra tutto", spostato dalla
   testata per non affollare la riga dei comandi. Barra leggera che chiude la
   card e riprende l'allineamento della testata. */
.ip-window-footer {
  padding: 8px 14px;
  border-top: 1px solid var(--ip-border-soft);
  text-align: right;
}
.ip-window-footer .ip-window-more { font-weight: 600; }
/* Le azioni chiudono la riga a destra (margin-left:auto) e restano discrete
   finche' il puntatore non entra nella finestra. Con 15 finestre a schermo,
   30 icone sempre accese (fra cui altrettante "x") coprivano di rumore i
   titoli. */
.ip-window-actions {
  display: flex;
  align-items: center;
  gap: var(--ip-space-1);
  margin-left: auto;
  /* Controlli di layout della finestra, non dati: restano discreti finche'
     il puntatore non entra nella finestra. Con 15 finestre a schermo, 30
     icone sempre accese (fra cui altrettante "x") coprivano di rumore i
     titoli. */
  opacity: 0;
  transition: opacity var(--ip-transition);
}
.ip-window:hover .ip-window-actions,
.ip-window:focus-within .ip-window-actions { opacity: 1; }
/* Touch e utenti che riducono le animazioni: niente hover da cui dipendere,
   i controlli restano sempre visibili. */
@media (hover: none) {
  .ip-window-actions { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .ip-window-actions { transition: none; }
}
.ip-window-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: var(--ip-radius-sm);
  background: transparent;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  cursor: pointer;
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-window-btn:hover {
  background: var(--ip-hover);
  color: var(--ip-text);
}
/* Focus da tastiera: il bottone deve essere visibile anche prima che il
   :focus-within accenda il gruppo, e avere un anello proprio. */
.ip-window-btn:focus-visible {
  outline: 0;
  background: var(--ip-hover);
  color: var(--ip-text);
  box-shadow: var(--ip-shadow-focus);
}

/* flex: le finestre in griglia (.ip-dash-windows / .ip-window-split) sono
   grid item con align-items: stretch (default di Grid): quando una finestra
   e' più corta della vicina sulla stessa riga, viene allungata all'altezza
   della più alta. .ip-window e' flex column ma prima nessun figlio aveva
   flex-grow: l'altezza in più restava come spazio vuoto dopo l'ultimo
   figlio (il footer "Vedi tutto"), che quindi sembrava "crescere" — in
   realta' era il vuoto fra fine tabella e link a farsi enorme. flex:1 fa
   assorbire lo spazio avanzato al corpo (tabella/contenuto), così il
   footer resta ancorato subito sotto, alla sua altezza naturale, invece di
   galleggiare in fondo a una card più alta. Stesso fix gia' applicato a
   .ip-window-body--kpi qui sotto per lo stesso problema sulle KPI. */
.ip-window-body { padding: 14px; flex: 1 1 auto; }
.ip-window-body--flush { padding: 0; }

/* Corpo delle finestre KPI: riga [contenuto | trend], come la colonna
   trend delle vecchie card KPI (badge in alto a destra). */
.ip-window-body--kpi {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: var(--ip-space-7);
  padding: 16px 14px;
  /* Occupa l'altezza avanzata dalla finestra: con le righe a stretch il corpo
     si distende e il sottotitolo resta ancorato in basso invece di lasciare
     un vuoto sotto la card più corta. */
  flex: 1 1 auto;
}
.ip-window-body-kpi-main {
  min-width: 0;
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}
/* La cifra (e il dettaglio che la segue) si appoggiano al fondo della
   card: così i numeri grossi di una riga di KPI condividono la stessa
   linea di base, indipendentemente dal numero di righe di nota (IVA ne ha
   tre, "Saldo da incassare" nessuna). L'etichetta resta in alto, il vuoto
   si apre tra etichetta e cifra invece che sotto la nota. */
.ip-window-body-kpi-main .card-value { margin-top: auto; }
.ip-window-body-kpi-main .card-subtext { padding-top: 2px; }
.ip-window-body--kpi .ip-window-trend {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--ip-space-3);
}
/* Telefono: le finestre KPI vanno in colonna singola, quindi le sei card sono
   sei schermate di scorrimento prima dei grafici. La cifra resta la voce
   dominante della card ma non deve valere 2rem su 375px di larghezza, e la
   sparkline (informazione accessoria) si ritira.
   La soglia era 1200px, ereditata dalle vecchie .kpi-card a tre colonne: ma
   sotto 1100px la griglia passa a due colonne e le finestre diventano piu'
   larghe, non più strette — la sparkline spariva proprio dove c'era piu'
   spazio, lasciando mezza card vuota su tablet. */
@media (max-width: 575.98px) {
  .ip-window-header { padding: 8px 12px; }
  .ip-window-body--kpi { padding: 12px; }
  .ip-window-body--kpi .card-value {
    font-size: 1.625rem;
    margin: 2px 0 4px;
  }
  .ip-window-body--kpi .ip-window-trend .ip-spark { display: none; }
}

/* Barra impilata "composizione stati" dentro la finestra flush: il suo
   padding non viene ereditato dalla tabella. */
.ip-status-bar-wrap {
  padding: 14px 14px 0;
}
.ip-status-bar-wrap .ip-status-bar { border-radius: var(--ip-radius-sm); }

/* Riga "IVA" della dashboard: la cifra e' la "da pagare" (esclusa la
   scissione dei pagamenti), e il dettaglio si legge come un mini mastro —
   vendite e acquisti in annotazione, l'IVA versata dagli enti in split
   payment isolata in blu sotto una riga di chiusura. */
.ip-iva-detail {
  padding-top: 8px;
  border-top: 1px dashed var(--ip-border);
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-1);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  line-height: 1.35;
}
.ip-iva-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--ip-space-5);
}
.ip-iva-row dt {
  color: var(--ip-text-muted);
  letter-spacing: .02em;
}
.ip-iva-row dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-strong);
}
.ip-iva-row--split {
  margin-top: 3px;
  padding-top: 4px;
  border-top: 1px solid var(--ip-border);
}
.ip-iva-row--split dt,
.ip-iva-row--split dd {
  color: var(--ip-info-text);
}
.ip-iva-row--split dd {
  font-weight: 600;
}

/* -----------------------------------------------------------------------------
   22c. Dashboard come registro di cassa — tipografia e blocco di chiusura:
   - le cifre delle finestre KPI e dell'Indice passano a Fraunces (il serif di
     display del sistema, finora riservato ai titoli di pagina): la pagina dei
     soldi si legge come un registro contabile stampato.
   - etichette e sottotitoli in IBM Plex Mono: annotazioni di mastro, stesso
     linguaggio degli assi dei grafici e dei numeri documento (.ip-num).
   - il sommario dell'Indice diventa il blocco "chiusura del periodo": fondo
     carta verde pallido + doppia riga sotto l'utile (il tratto che il
     ragioniere traccia sotto il totale quando chiude una pagina del mastro).
   Scoped alle finestre: .card-label / .card-value / .card-subtext sono usati
   anche dalla scheda cliente, che non deve cambiare voce.
   -------------------------------------------------------------------------- */

/* Etichette KPI e Indice: annotazione di mastro (mono, maiuscole). */
.ip-window-body--kpi .card-label,
.ip-indice .card-label {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .08em;
}
/* Sottotitoli KPI e Indice: stessa voce di annotazione, cifre tabulari. */
.ip-window-body--kpi .card-subtext,
.ip-indice .card-subtext {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
}
/* Cifre in Fraunces. Il peso scende a 600: il serif a 700 (peso del titolo
   di pagina) sarebbe più pesante del necessario per un importo. */
.ip-window-body--kpi .card-value,
.ip-indice .card-value {
  font-family: var(--ip-display);
  font-weight: 600;
  letter-spacing: -0.01em;
}
/* Cifra dell'Indice: è la voce dominante della pagina (totale di chiusura). */
.ip-indice .card-value { font-size: 2.5rem; }
@media (max-width: 575.98px) {
  .ip-indice .card-value { font-size: 1.875rem; }
}

/* Doppia riga di chiusura: due tratti (1px + 2px) sotto il totale, come la
   chiusura del mastro. Firma della finestra Indice: non ripetuta altrove. */
.ip-ledger-rule {
  display: block;
  width: 64px;
  height: 8px;
  margin: 2px 0 8px;
  border-top: 1px solid var(--kpi, var(--ip-primary));
  border-bottom: 2px solid var(--kpi, var(--ip-primary));
}

/* Blocco "chiusura del periodo": fondo soft della metrica con bordo calibrato. */
.ip-indice-summary {
  background: var(--kpi-bg, var(--ip-success-bg));
  border: 1px solid color-mix(in srgb, var(--kpi, var(--ip-success)) 25%, var(--ip-border));
  border-radius: var(--ip-radius);
  padding: 14px 16px;
}

/* Finestre espanse: i grafici SVG scalavano col viewBox (testo ~1.9x più
   grande). Il cap riporta il grafico a una larghezza di lettura (~820px) e
   lo centra nella finestra; le KPI cappano la sparkline. Il testo resta
   leggibile e la finestra piena non è più un'etichetta ingrandita. */
.ip-window--expanded svg.ip-chart {
  max-width: 820px;
  margin-left: auto;
  margin-right: auto;
}
.ip-window--expanded .ip-window-body--kpi .ip-window-trend .ip-spark {
  width: 280px;
  max-width: 280px;
}

/* Finestra "Indice di redditività": sommario a sinistra, grafico ad area
   a destra; a mobile si impila. */
.ip-indice {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--ip-space-9);
  flex-wrap: wrap;
}
.ip-indice-summary { min-width: 200px; }
.ip-indice-trend { margin-top: 8px; }
.ip-indice-chart {
  flex: 1 1 320px;
  min-width: 0;
  /* Su schermi molto larghi (21:9) la finestra --wide si allunga e il grafico
     ad area, con aspect-ratio fisso, scalava di conseguenza: etichette degli
     assi e mesi ingranditi fino a ~3x. Stesso cap di lettura delle finestre
     espanse: il testo resta leggibile e il contenuto resta proporzionato. */
  max-width: 820px;
}
/* Il cap da 820px valeva per le finestre espanse, non per questo grafico: la
   gabbia SVG è larga 480 unità, quindi a 820px il testo (asse e mesi) veniva
   scalato a ~1.7x e superava in corpo le etichette del sommario accanto.
   A 560px la scala resta ~1.15, allineata ai grafici a barre. */
.ip-dash-windows .ip-indice-chart { max-width: 560px; }
/* A tutta larghezza (la finestra e' --wide) l'aspect 600x170 produceva oltre
    220px di altezza per una serie di 6 punti: più spazio di entrambi i grafici
    a barre messi insieme, per meno informazione. Il cap tiene la finestra
    proporzionata al resto della griglia. */
.ip-area {
  width: 100%;
  height: auto;
  aspect-ratio: 480 / 150;
  display: block;
}
/* -----------------------------------------------------------------------------
   22d. KPI — inchiostro per metrica + pulizia colonna trend:
   - ogni finestra KPI porta il proprio "inchiostro" (colore di stato coerente
     col resto dell'app): ricavi=info, incassato=success, saldo=warning,
     scadute=danger, costi=violet, iva=neutro. Il filetto superiore da 3px e
     l'icona d'intestazione tinteggiati dello stesso inchiostro fanno scansionare
     la parete di KPI a colto d'occhio, come le colonne colorate di un mastro.
   - la colonna trend ha larghezza fissa: le 6 sparkline condividono così la
     stessa misura e la stessa baseline, invece di ridimensionarsi sul badge.
   - badge + sparkline impilati dall'alto (non space-between): restano affiancati
     alla cifra invece di scivolare in fondo alla card.
   - micro-interazione hover (sollevamento + ombra) per far "vivere" la parete,
     neutralizzata sotto prefers-reduced-motion.
   -------------------------------------------------------------------------- */

/* Inchiostro per finestra: ereditato da header e icona via custom property.
   Le sei KPI restano ancorate al loro colore di metrica; le altre finestre
   (breakdown, grafici, tabelle "recenti", indice) ricevono lo stesso
   trattamento così l'intera parete si legge con la medesima "inchiostrazione". */
.ip-window[data-window="ricavi"]          { --kpi: var(--ip-info);    --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="incassato"]       { --kpi: var(--ip-success); --kpi-bg: var(--ip-success-bg); }
.ip-window[data-window="saldo"]           { --kpi: var(--ip-warning); --kpi-bg: var(--ip-warning-bg); }
.ip-window[data-window="scadute"]         { --kpi: var(--ip-danger);  --kpi-bg: var(--ip-danger-bg); }
.ip-window[data-window="costi"]           { --kpi: var(--ip-violet);  --kpi-bg: var(--ip-violet-bg); }
.ip-window[data-window="iva"]             { --kpi: var(--ip-primary); --kpi-bg: var(--ip-primary-soft); }
.ip-window[data-window="indice"]          { --kpi: var(--ip-success); --kpi-bg: var(--ip-success-bg); }
.ip-window[data-window="indice"].ip-window--alert { --kpi: var(--ip-danger); --kpi-bg: var(--ip-danger-bg); }
.ip-window[data-window="graf_ricavi"]     { --kpi: var(--ip-info);    --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="graf_costi"]      { --kpi: var(--ip-violet);  --kpi-bg: var(--ip-violet-bg); }
.ip-window[data-window="stato_fatture"]   { --kpi: var(--ip-info);    --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="stato_preventivi"]{ --kpi: var(--ip-info);    --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="ultime_fatture"]  { --kpi: var(--ip-info);    --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="ultimi_preventivi"]{ --kpi: var(--ip-info);   --kpi-bg: var(--ip-info-bg); }
.ip-window[data-window="ultimi_ddt"]      { --kpi: var(--ip-warning); --kpi-bg: var(--ip-warning-bg); }
.ip-window[data-window="progetti"]        { --kpi: var(--ip-success); --kpi-bg: var(--ip-success-bg); }
.ip-window[data-window="attivita"]        { --kpi: var(--ip-warning); --kpi-bg: var(--ip-warning-bg); }

/* Filetto superiore: l'unica variazione cromatica per card. Tutto il resto
   della finestra resta disciplinato (valore Fraunces, etichette mono, testo
   neutro), così l'inchiostro segnala la metrica senza rumore. La finestra ha
   overflow:hidden, quindi il filetto non deborda sugli angoli arrotondati.
   Esteso a TUTTE le finestre della dashboard (non più solo le sei KPI): ogni
   card porta il proprio --kpi, così la parete si inchiostra in modo uniforme. */
.ip-dash-windows .ip-window .ip-window-header {
  box-shadow: inset 0 3px 0 0 var(--kpi, var(--ip-primary));
}
/* Icona d'intestazione tinteggiata: lo stesso segnale dell'inchiostro, ma in
   superficie soft, così l'header non "grida" colore pieno. */
.ip-dash-windows .ip-window .card-header-icon {
  background: var(--kpi-bg, var(--ip-primary-soft));
  color: var(--kpi, var(--ip-primary));
}
/* Scadute e' una finestra --alert (bordo rosso a sinistra di default): lo
   togliamo per non doppiare il segnale, che ora e' portato dal filetto superiore
   rosso (inchiostro della metrica). Ripristiniamo il bordo neutro da 1px come
   le altre KPI, così la card resta allineata per misura. */
.ip-window[data-window="scadute"].ip-window--alert {
  border-left: 1px solid var(--ip-border-soft);
}

/* Colonna trend a larghezza fissa: tutte e sei le KPI la condividono, quindi
   le sparkline sono allineate per misura e per baseline lungo la riga. */
.ip-window-body--kpi .ip-window-trend {
  flex: 0 0 92px;
  justify-content: flex-start;
  gap: var(--ip-space-4);
}
/* La sparkline riempie la colonna fissa: niente più larghezze a scalino sul
   badge. Margine azzerato (prima 4px di troppo spingevano la barra sul bordo). */
.ip-window-body--kpi .ip-window-trend .ip-spark {
  width: 100%;
  height: 30px;
  margin-top: 0;
}
/* Cifra KPI: dimensione calibrata sulla griglia a 3 colonne, in Fraunces
   (ereditata da 22c). Limitata ai viewport >= 576px: sotto i 575px vale gia'
   la regola mobile (1.625rem) definita più sopra, che altrimenti perderebbe
   per ordine di sorgente. */
@media (min-width: 576px) {
  .ip-window-body--kpi .card-value {
    font-size: 1.875rem;
    line-height: 1.05;
    margin: 4px 0 6px;
  }
}
/* Micro-interazione: la finestra si solleva e acquista ombra all'hover,
   dando alla parete una sensazione "viva" senza alterare la gerarchia.
   Esteso a tutte le finestre della dashboard. */
.ip-dash-windows .ip-window {
  transition: box-shadow var(--ip-transition), transform var(--ip-transition);
}
.ip-dash-windows .ip-window:hover {
  transform: translateY(-2px);
  box-shadow: var(--ip-shadow-sm);
}
@media (prefers-reduced-motion: reduce) {
  .ip-dash-windows .ip-window { transition: none; }
  .ip-dash-windows .ip-window:hover { transform: none; }
}

/* Velo d'inchiostro: la parete di finestre si legge come "colonne colorate di
   un mastro" (come già anticipa il filetto superiore della finestra). Un lavaggio
   dell'inchiostro della metrica, appena percettibile e solo in testa alla card,
   tinge il corpo senza "gridare" colore: il resto resta disciplinato (numeri in
   Fraunces, etichette in mono) e la cifra resta la voce dominante. Il velo sale
   dal bordo superiore e sfuma prima del sottotitolo, così la metrica si
   riconosce a colpo d'occhio anche quando le finestre sono accostate.
   Esteso a tutte le finestre della dashboard. */
.ip-dash-windows .ip-window {
  position: relative;
}
.ip-dash-windows .ip-window::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 92px;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--kpi, var(--ip-primary)) 12%, transparent),
    transparent);
  pointer-events: none;
  z-index: 0;
}
/* Header e corpo restano sopra il velo: la cifra non ci passa sotto. */
.ip-dash-windows .ip-window > .ip-window-header,
.ip-dash-windows .ip-window > .ip-window-body {
  position: relative;
  z-index: 1;
}

/* -----------------------------------------------------------------------------
   22e. Dashboard — animazioni di caricamento (registro che si compone):
   - le finestre salgono e sfumano a scaletta (--i assegnato via JS), come la
     pagina di mastro che si riempie quando la si apre.
   - le barre dei grafici e delle sparkline "crescono" dalla baseline (scaleY).
   - le cifre Fraunces si "stendono" dall'inchiostro (opacity + blur leggero),
     lo stesso gesto della penna che scrive l'importo sul registro.
   Tutto e' guidato da classi CSS: se il JS non assegnasse --i, le finestre
   appaiono comunque (ritardo 0). Neutralizzato sotto prefers-reduced-motion.
   -------------------------------------------------------------------------- */
@keyframes ip-window-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes ip-bar-grow {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}
@keyframes ip-ink-settle {
  from { opacity: 0; filter: blur(3px); }
  to   { opacity: 1; filter: blur(0); }
}

/* Finestre: ingresso a scaletta. La chronologia e' data da --i (ordine DOM
   delle .ip-window) impostato dallo script della dashboard. */
.ip-dash-windows .ip-window {
  animation: ip-window-rise 520ms cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: calc(var(--i, 0) * 55ms);
}
/* Barre dei grafici e delle sparkline: crescono dalla baseline. transform-box
   fill-box ancora l'origine alla singola barra, non al viewBox. */
.ip-chart-bar,
.ip-spark rect {
  transform-box: fill-box;
  transform-origin: bottom;
  animation: ip-bar-grow 620ms cubic-bezier(.22, .61, .36, 1) both;
  animation-delay: calc(220ms + var(--i, 0) * 55ms);
}
/* Grafico ad area (Indice): sfuma e sale insieme alla sua finestra. */
.ip-area {
  animation: ip-window-rise 600ms ease both;
  animation-delay: calc(220ms + var(--i, 0) * 55ms);
}
/* Cifre KPI: si stendono dall'inchiostro, sfasate con la card. */
.ip-window-body--kpi .card-value,
.ip-indice .card-value {
  animation: ip-ink-settle 600ms ease both;
  animation-delay: calc(140ms + var(--i, 0) * 55ms);
}
@media (prefers-reduced-motion: reduce) {
  .ip-dash-windows .ip-window,
  .ip-chart-bar,
  .ip-spark rect,
  .ip-area,
  .ip-window-body--kpi .card-value,
  .ip-indice .card-value {
    animation: none !important;
  }
}
/* Punti della serie: rendono leggibili i 6 valori mensili e danno un bersaglio
   al <title> nativo. */
.ip-area-dot {
  stroke: var(--ip-bg-card);
  stroke-width: 2;
}

/* Meta dei grafici: periodo a sinistra, legenda a destra (prima stavano
   nell'header della card, ora l'header è della finestra). */
.ip-chart-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-5);
  flex-wrap: wrap;
  margin-bottom: 10px;
}

/* Spese (grafico mensile): rosso costo, in opposizione ai verdi incassati.
   Stesso ruolo del --ip-danger delle KPI scaduto: segnala l'uscita. */
.ip-chart-legend-dot--expenses { background: var(--ip-danger); }
.ip-chart-bar--expenses        { fill: var(--ip-danger); }
.ip-chart-summary-item--expenses .ip-chart-summary-value { color: var(--ip-danger-text); }
.ip-chart-summary-item--profit    .ip-chart-summary-value { color: var(--ip-success-text); }

/* Riga a tutta larghezza dentro la griglia finestre: grafici affiancati
   e dettaglio su due colonne, come le finestre "grafico" di Fattura24.
   Le sezioni del dettaglio sono figli diretti (niente più wrapper a
   colonna): la griglia a due colonne li accoppia per posizione DOM —
   l'ordine nel view è interleaved (stato fatture, stato preventivi,
   ultime fatture, ultimi preventivi, progetti, attività) così le coppie
   stanno sulla stessa riga e i due gruppi restano affiancati. L'ordine
   DOM conta: con 3+3 sezioni impilate l'auto-placement spaccava i
   preventivi (Progetti finiva in mezzo). Con progetti disattivi restano
   4 sezioni, comunque accoppiate. */
.ip-window-split {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ip-space-7);
}
/* Sotto i 1200px le due colonne non bastano più alle tabelle "recenti":
   a 1100px la colonna scende sotto i 500px e le cinque colonne di
   fatture/preventivi sforavano di 20-50px, con la finestra che scorreva di
   lato senza che lo si vedesse (niente barra, niente indizio). Stessa soglia
   delle liste .ip-table-card, che sotto 1200 riattivano lo scorrimento
   interno: qui invece si passa a una colonna sola, così la tabella resta
   intera — togliere una colonna o lasciarla scorrere dentro una card a metà
   pagina costava più di quanto la coppia affiancata rendesse. */
@media (max-width: 1199.98px) {
  .ip-window-split { grid-template-columns: minmax(0, 1fr); }
}

/* Gerarchia a fasce: hero (indice) -> striscia KPI -> grafici -> tabelle.
   Prima tutte le finestre stavano in un'unica griglia con lo stesso gap
   uniforme (15px ovunque): hero, KPI, grafici e tabelle si leggevano come
   una massa unica invece di quattro livelli. Un margine crescente prima di
   ogni riga ".ip-window-split" (che nel DOM segna sempre l'inizio di un
   nuovo livello: prima i due grafici affiancati, poi il dettaglio/tabelle)
   dà lo scarto d'aria che nel pattern "KPI in alto, grafico principale,
   tabella sotto" separa i tre blocchi senza toccare il markup PHP. */
.ip-window-split {
  margin-top: 10px;
}
.ip-window-split + .ip-window-split {
  margin-top: 4px;
}

/* Le due finestre-grafico sono il "main chart" della pagina (il centro del
   pattern KPI -> grafico -> tabella): un'ombra e un bordo leggermente più
   marcati delle KPI/tabelle danno loro il peso visivo di blocco primario,
   non solo un'altra card della griglia. */
.ip-window-split:first-of-type .ip-window {
  box-shadow: var(--ip-shadow-md, 0 4px 14px rgba(24, 24, 27, .1));
  border-color: var(--ip-border-strong, var(--ip-border));
}

/* Periodo di riferimento nell'header del grafico dashboard */
.ip-chart-period {
  font-size: var(--ip-text-sm);
  font-weight: 500;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
/* -----------------------------------------------------------------------------
   23. Navbar — avatar utente e dropdown
   --------------------------------------------------------------------------
   .ip-nav-avatar non aveva alcuna regola di base: esistevano solo gli override
   di colore, quindi lo span rendeva come testo inline (9x20px, spigoli vivi)
   invece del cerchio con l'iniziale. Qui la forma, coerente con .ip-avatar-sm
   usato nelle tabelle.
   -------------------------------------------------------------------------- */

.ip-nav-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0;
  /* Leggera sheen diagonale: da' profondita' all'avatar senza introdurre
     colore (resta sul neutro) e funziona identica in chiaro e scuro. */
  background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.06), rgba(0, 0, 0, 0.05));
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
/* Freccia del dropdown: il caret di Bootstrap è più marcato del necessario
   accanto all'avatar; qui resta ma smorzato e allineato. */
.ip-topbar .dropdown-toggle::after {
  margin-left: 2px;
  opacity: .6;
  border-top-color: currentColor;
}
/* ---------- Pannello dropdown ---------------------------------------------
   Un menu flottante senza ombra si legge come attaccato alla pagina: la
   navbar è scura e il pannello bianco aveva box-shadow:none. Stessa
   elevazione dei pannelli IPSelect, per un linguaggio unico. */
.dropdown-menu {
  border-color: var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
  padding: 4px;
  min-width: 200px;
  font-size: var(--ip-text-base);
  color: var(--ip-text);
}
.ip-topbar .dropdown-menu { margin-top: 6px; }
/* Intestazione col nome utente: fa da titolo del menu, non da voce cliccabile */
.dropdown-menu .dropdown-header {
  padding: 7px 10px 6px;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ip-text-muted);
}
/* Nota specificita': esiste più sopra uno shim
   `.dropdown-menu > li > a.dropdown-item` (0,2,2) che vince su un semplice
   `.dropdown-menu .dropdown-item` (0,2,0). Queste regole ne ricalcano la forma,
   altrimenti display/padding restavano quelli dello shim. */
.dropdown-menu > li > .dropdown-item,
.dropdown-menu > li > a.dropdown-item,
.dropdown-menu > .dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  padding: 7px 10px;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text);
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.dropdown-menu > li > .dropdown-item:hover,
.dropdown-menu > li > a.dropdown-item:hover,
.dropdown-menu > li > .dropdown-item:focus-visible,
.dropdown-menu > .dropdown-item:hover {
  background: var(--ip-hover);
  color: var(--ip-text);
}
.dropdown-menu > li > .dropdown-item.text-danger:hover,
.dropdown-menu > li > a.dropdown-item.text-danger:hover,
.dropdown-menu > li > .dropdown-item.text-danger:focus-visible {
  background: var(--ip-danger-bg);
  color: var(--ip-danger-text);
}
/* Icone su una colonna di larghezza fissa: senza, le etichette partivano da x
   diverse a seconda della glifo (fa-user più stretta di fa-power-off). */
.dropdown-menu > li > .dropdown-item .fa,
.dropdown-menu > .dropdown-item .fa {
  width: 15px;
  margin-right: 0;
  text-align: center;
  opacity: .75;
  flex: 0 0 auto;
}
/* Il separatore usava var(--bs-dropdown-divider-bg, #dee2e6): ennesimo grigio
   di Bootstrap fuori palette. */
.dropdown-menu .dropdown-divider,
.dropdown-menu li.dropdown-divider {
  border-top-color: var(--ip-border-soft);
  margin: 4px 0;
}
/* -----------------------------------------------------------------------------
   24. Composizione stati — barra impilata
   --------------------------------------------------------------------------
   Sostituisce la card col donut: la torta duplicava gli importi della tabella
   "Fatture per stato" e aggiungeva solo la quota percentuale. Qui la quota
   resta, su una barra impilata (lunghezza, più leggibile dell'angolo) alta
   6px sotto l'header della stessa card.
   -------------------------------------------------------------------------- */

.ip-status-bar {
  display: flex;
  height: 6px;
  overflow: hidden;
  background: var(--ip-border-soft);
}
.ip-status-bar-seg {
  height: 100%;
  min-width: 2px;
  transition: opacity var(--ip-transition);
  /* Colore di riserva: uno stato nuovo, o non mappato qui sotto, resta
     visibile invece di produrre un segmento trasparente. */
  background: var(--ip-neutral-solid);
}
/* Tinta del segmento per stato. Sono riempimenti grafici: chiedono 3:1 verso
   la card (1.4.11), ma le tinte piene "identitarie" non li reggono in chiaro
   (ambra 2.28, grigio 2.85). Le varianti *-text sono il gradino verificato su
   entrambi i temi; "annullata" usa --ip-text perché --ip-canceled è quasi
   nero in chiaro e quasi fondo-card in scuro, e sparirebbe in uno dei due.
   Un tempo questi colori arrivavano inline dal PHP (Dashboard::DONUT_COLORS),
   quindi non potevano ribaltarsi col tema. */
.ip-status-bar-seg--draft    { background: var(--ip-neutral-solid); }
.ip-status-bar-seg--sent     { background: var(--ip-info-text); }
.ip-status-bar-seg--viewed   { background: var(--ip-warning-text); }
.ip-status-bar-seg--paid,
.ip-status-bar-seg--approved { background: var(--ip-success-text); }
.ip-status-bar-seg--rejected,
.ip-status-bar-seg--overdue  { background: var(--ip-danger-text); }
.ip-status-bar-seg--canceled { background: var(--ip-text); }
.ip-status-bar:hover .ip-status-bar-seg { opacity: .55; }
.ip-status-bar .ip-status-bar-seg:hover { opacity: 1; }

/* -----------------------------------------------------------------------------
   25. Navigazione temporale della lista documenti (anno + mesi)
   --------------------------------------------------------------------------
   Impostazione presa da Fatture in Cloud: prima l'anno, poi il mese, con i
   totali del periodo selezionato accanto. Vive fra headerbar e tabella, quindi
   riprende il padding orizzontale di #content (20px) per allinearsi.
   -------------------------------------------------------------------------- */

.ip-period-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px 20px;
  padding: 14px 20px 12px;
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border);
  /* La barra mesi si adatta alla larghezza del contenuto, non del viewport:
     con la sidebar aperta (236px) lo spazio utile cambia molto tra 1024 e
     1440, e le media query sul viewport sbaglierebbero. Le regole @container
     più sotto scelgono griglia o riga fissa in base a questo. */
  container-type: inline-size;
}
@media (max-width: 575.98px) {
  .ip-period-nav { padding: 12px 16px 10px; }
}

.ip-period-nav-months {
  /* Larghezza fissa per ogni voce: gli importi hanno cifre diverse e senza
     un minimo comune i pulsanti cambiavano larghezza da un mese all'altro.
     Con gli importi arrotondati (senza centesimi) 74px basta e avanza per
     "47.601 €", e tiene le tredici voci in una riga su un contenuto da
     ~1000px in su. Sotto quella soglia la striscia va a capo, centrata. */
  --ip-period-month-w: 74px;
  display: flex;
  flex-wrap: wrap;
  /* stretch, non center: la voce "Tutti i mesi" ha una riga sola, i mesi due
     (nome + importo). Con center restava più bassa e sembrava un pulsante
     rotto accanto agli altri; con stretch tutte le voci della stessa riga
     prendono l'altezza della più alta. */
  align-items: stretch;
  justify-content: center;
  gap: var(--ip-space-1);
  padding: 3px;
  border-radius: var(--ip-radius);
  background: var(--ip-primary-soft);
}

.ip-period-month {
  position: relative;
  display: inline-flex;
  flex: 0 0 var(--ip-period-month-w);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: var(--ip-period-month-w);
  padding: 5px 5px;
  border-radius: var(--ip-radius-sm);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  line-height: 1.15;
  color: var(--ip-text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--ip-transition),
              background-color var(--ip-transition),
              box-shadow var(--ip-transition),
              transform var(--ip-transition);
}
/* L'etichetta "Tutti i mesi" è più lunga di un nome di mese: un punto più
   piccola sta nella stessa larghezza fissa senza allargare la voce. */
.ip-period-month--all .ip-period-month-name {
  font-size: var(--ip-text-xs);
}
/* Il contorno di focus del browser è nero e spesso: qui usa il colore
   d'accento, con lo stesso offset su tutte le voci della barra. */
.ip-period-month:focus-visible {
  outline: 2px solid var(--ip-primary);
  outline-offset: 2px;
}
.ip-period-month-name {
  display: block;
}
/* Importo del mese sotto il nome: è un dato di supporto, non un secondo
   titolo, quindi resta piccolo e tabulare. La cifra è il totale lordo del
   mese con lo stesso perimetro della lista (stato, preferiti, pagamento). */
.ip-period-month-amount {
  display: block;
  font-size: var(--ip-text-xs);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  opacity: .8;
}
.ip-period-month.is-active .ip-period-month-amount {
  opacity: .95;
}
.ip-period-month--all {
  justify-content: center;
}
.ip-period-month:hover {
  color: var(--ip-text);
  background: var(--ip-hover);
}
.ip-period-month.is-active {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  font-weight: 600;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .12), 0 1px 2px rgba(0, 0, 0, .08);
}

/* Istruzioni della selezione multipla (Shift/clic e Ctrl/clic). Senza una
   riga di testo la funzione restava invisibile: chi voleva due mesi ne
   apriva uno per volta. Compare solo con JavaScript attivo, perché lo
   script del partial toglie `hidden`; senza script i mesi sono link singoli
   e l'istruzione sarebbe una promessa falsa. */
.ip-period-hint {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 2px 8px;
  /* Il contenitore distanzia le righe di 12px. La nota appartiene ai mesi,
     non ai totali che la seguono: il margine negativo la avvicina alla
     barra (6px) e lascia intero lo stacco verso la striscia di riepilogo. */
  margin: -6px 0 0;
  /* Un gradino sopra la prima versione (11px grigio): a quella misura la
     nota c'era ma non si vedeva, ed era proprio il difetto da correggere.
     Corpo 13px e colore del testo pieno la rendono leggibile a colpo
     d'occhio; resta senza fondo né bordo per non competere con i mesi. */
  font-size: var(--ip-text-base);
  line-height: 1.45;
  color: var(--ip-text);
  text-align: center;
}
.ip-period-hint[hidden] { display: none; }
/* L'icona nel colore primario fa da richiamo: è lei a segnalare che la riga
   è un'istruzione e non un altro dato del periodo. */
.ip-period-hint i { color: var(--ip-primary); }
/* Shift, Ctrl e ⌘ in forma di tasto: sono la parte della frase che l'occhio
   deve trovare. Bootstrap disegna <kbd> come blocco scuro a testo bianco,
   troppo pesante dentro una nota; qui è un tasto chiaro col bordo, lo stesso
   disegno della scorciatoia nella ricerca globale. Il bordo inferiore più
   spesso dà il rilievo del tasto senza ricorrere a un'ombra. */
.ip-period-hint kbd {
  display: inline-block;
  min-width: 18px;
  padding: 0 5px;
  border: 1px solid var(--ip-border);
  border-bottom-width: 2px;
  border-radius: 4px;
  background: var(--ip-bg, #f6f6f7);
  color: var(--ip-text-strong);
  font-family: inherit;
  font-size: var(--ip-text-sm);
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  white-space: nowrap;
}

/* Adattamento alla larghezza reale del contenuto (non del viewport).
   Sotto i 1000px le tredici voci fisse non stanno su una riga: la striscia
   diventa una griglia, "Tutti i mesi" occupa la riga intera come interruttore
   generale e i dodici mesi si dispongono in celle uguali. */
@container (max-width: 999px) {
  .ip-period-nav-months {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    width: 100%;
    gap: var(--ip-space-2);
  }
  .ip-period-month {
    flex: initial;
    width: auto;
  }
  .ip-period-month--all {
    grid-column: 1 / -1;
  }
  /* Nella griglia la voce è larga quanto la cella: l'etichetta può tornare
     alla dimensione dei nomi di mese. */
  .ip-period-month--all .ip-period-month-name {
    font-size: var(--ip-text-sm);
  }
}
/* Su telefono stretto sei colonne diventano illeggibili: quattro per riga,
   tre righe esatte per i dodici mesi. */
@container (max-width: 575px) {
  .ip-period-nav-months {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* Contatore nei pulsanti filtro delle liste (es. "In corso 12"):
   cifra piccola e tabulare, non deve competere con l'etichetta. */
.ip-btn-count {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-variant-numeric: tabular-nums;
  opacity: .75;
  margin-left: 2px;
}

/* Riepilogo in testa a una schermata: conteggi e totali del perimetro
   mostrato sotto. Unico componente per liste documenti, scadenzario,
   progetti e attività — il markup sta in layout/summary_strip.php.
   Prima erano quattro copie quasi uguali (.ip-period-total*, .ip-schedule-sum*,
   .ip-summary-* e le card della bacheca): la stessa riga di numeri cambiava
   faccia da una schermata all'altra. Le card KPI restano alla bacheca, dove
   i numeri sono il contenuto della pagina e non l'intestazione di un elenco. */
.ip-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-9);
  padding: 12px 0 16px;
}
.ip-summary-item {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 14px;
  border-radius: var(--ip-radius);
  background: var(--ip-primary-soft);
  min-width: 0;
}
.ip-summary-item a { color: inherit; text-decoration: none; }
.ip-summary-item a:hover { color: var(--ip-primary); }
/* Etichette, non badge: 11px (--ip-text-xs) e non i 10px che la scala
   riserva a badge e timbri. A 10px in maiuscolo spaziato erano il testo più
   piccolo dell'app proprio dove si legge cosa misura la cifra sotto; la
   stessa correzione vale per le etichette KPI della bacheca, per "Esporta"
   e per la legenda del calendario.
   Il colore è il muted scurito di un quarto verso il testo: il muted pieno,
   sul fondo tinto della piastrella (--ip-primary-soft), si fermava a 4,3:1,
   sotto la soglia di 4,5:1 per il testo piccolo. */
.ip-summary-label {
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: color-mix(in srgb, var(--ip-text-muted) 75%, var(--ip-text));
  line-height: 1;
}
.ip-summary-value {
  font-family: var(--ip-display);
  font-size: var(--ip-text-3xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
  line-height: 1.2;
  letter-spacing: -.01em;
}
.ip-summary-value.is-danger,
.ip-summary-value.is-overdue { color: var(--ip-danger-text); }
.ip-summary-value.is-warning { color: var(--ip-warning-text); }
.ip-summary-value.is-success { color: var(--ip-success-text); }

.ip-period-nav .ip-summary {
  width: 100%;
  padding: 6px 0 2px;
  gap: var(--ip-space-6);
  justify-content: center;
}
.ip-period-nav .ip-summary-item {
  padding: 6px 12px;
  background: transparent;
  border-left: 2px solid var(--ip-border);
  border-radius: 0;
}
.ip-period-nav .ip-summary-item:first-child {
  border-left: none;
  padding-left: 0;
}

/* -----------------------------------------------------------------------------
   Piede totale delle liste (fatture, spese): imponibile, IVA, totale lordo
   --------------------------------------------------------------------------
   Il footer resta dentro <tfoot>, sotto le colonne che riassume: l'importo
   sotto "Importo", il saldo sotto "Saldo". Fondo soft e bordo superiore
   separano i totali dalle righe senza aggiungere un secondo componente.
   -------------------------------------------------------------------------- */

.ip-list-totals th,
.ip-list-totals td {
  background: var(--ip-primary-soft);
  border-top: 1px solid var(--ip-border-soft);
  padding: 6px var(--ip-space-6);
  font-size: var(--ip-text-sm);
  vertical-align: middle;
}
.ip-list-totals__label {
  text-align: right;
  font-weight: 600;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-list-totals__value {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}
.ip-list-totals__row--grand th,
.ip-list-totals__row--grand td {
  border-top: 1px solid var(--ip-border);
  font-size: var(--ip-text-md);
  color: var(--ip-text);
}
/* Su telefono le colonne secondarie spariscono: la riga dei totali non deve
   restare con un'etichetta larga quanto mezza tabella. La tabella collassa in
   card (thead nascosto, righe come blocchi), quindi anche il piede va letto
   come un blocco: una riga per totale, etichetta a sinistra e cifra a destra.
   Senza queste regole i <td> diventavano blocchi mentre i <th> restavano
   celle di tabella, e il footer si sfaldava. */
@media (max-width: 575.98px) {
  .ip-table-card table.ip-table-card-collapse tfoot,
  .ip-table-card table.ip-table-card-collapse tfoot tr,
  .ip-table-card table.ip-table-card-collapse tfoot th,
  .ip-table-card table.ip-table-card-collapse tfoot td {
    display: block;
    width: 100%;
  }
  .ip-table-card table.ip-table-card-collapse tfoot tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 4px 12px;
    padding: 8px 12px;
    border-top: 1px solid var(--ip-border-soft);
  }
  .ip-table-card table.ip-table-card-collapse tfoot th,
  .ip-table-card table.ip-table-card-collapse tfoot td {
    width: auto;
    padding: 0;
    border: 0;
    background: transparent;
  }
  .ip-table-card table.ip-table-card-collapse tfoot td:empty {
    display: none;
  }
  .ip-table-card table.ip-table-card-collapse tfoot td[data-label]::before {
    content: attr(data-label) " ";
    font-weight: 600;
    color: var(--ip-text-muted);
  }
  .ip-list-totals__label {
    white-space: normal;
    text-align: left;
  }
}

/* -----------------------------------------------------------------------------
   Barra di esportazione in coda a un elenco
   --------------------------------------------------------------------------
   Stesso componente su fatture e spese: etichetta discreta a sinistra e
   pulsanti a destra, sul bordo inferiore della card.
   -------------------------------------------------------------------------- */

.ip-list-export {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ip-space-5);
  padding: 12px var(--ip-card-pad-x);
  border-top: 1px solid var(--ip-border-soft);
  background: var(--ip-bg-card);
}
.ip-list-export__label {
  margin-right: auto;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--ip-text-muted);
}
.ip-list-export__label .fa {
  margin-right: 4px;
}
@media (max-width: 575.98px) {
  .ip-list-export {
    justify-content: stretch;
  }
  .ip-list-export__label {
    flex: 1 0 100%;
    margin-right: 0;
  }
}

/* Striscia di composizione nella lista progetti — la firma del registro:
   ogni progetto mostra le sue attività come segmenti sottili nelle tinte
   di stato (grigio = non iniziata, blu = in corso, verde = completata,
   verde scuro = fatturata, il lavoro chiuso nel registro). Stesso
   linguaggio della barra di stato della bacheca, a scala di riga. */
.ip-strip-col { min-width: 170px; }
.ip-task-strip-wrap {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  min-width: 0;
}
.ip-task-strip {
  display: flex;
  gap: var(--ip-space-1);
  width: 96px;
  height: 4px;
  flex: 0 0 auto;
  border-radius: var(--ip-radius-sm);
  /* Il token --ip-bg-muted non esiste mai esistito: il fondo della striscia
     ricadeva sullo sfondo della card (trasparente) e la riga di 4px spariva
     quando la lista aveva una sola attivita'. --ip-border-soft e' definito in
     entrambi i temi e da' il fondo tenue da "registro" voluto dal commento
     di sezione. */
  background: var(--ip-border-soft);
  overflow: hidden;
}
.ip-task-strip-seg {
  display: block;
  height: 100%;
  min-width: 2px;
  border-radius: 1px;
}
/* Stesse tinte dei timbri di stato e della barra di composizione nella
   scheda progetto: le due viste raccontano lo stesso lavoro e devono usare
   lo stesso alfabeto. */
.ip-task-strip-seg.is-not-started { background: var(--ip-neutral); }
.ip-task-strip-seg.is-in-progress { background: var(--ip-warning-text); }
.ip-task-strip-seg.is-complete    { background: var(--ip-primary); }
.ip-task-strip-seg.is-invoiced    { background: var(--ip-success); }
.ip-task-strip-meta {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-task-strip-meta .ip-overdue-flag {
  color: var(--ip-danger-text);
  margin-left: 2px;
}

/* Nome progetto: voce del registro, in Fraunces come le cifre della
   cartoleria; il cliente è l'annotazione a margine, più quieta. */
.ip-project-name {
  font-family: var(--ip-display);
  font-weight: 600;
  font-size: var(--ip-text-lg);
  letter-spacing: -0.01em;
  color: var(--ip-text);
  text-decoration: none;
}
.ip-project-name:hover { color: var(--ip-primary-hover); }
.ip-client-link { color: var(--ip-text-muted); text-decoration: none; }
.ip-client-link:hover { color: var(--ip-primary); }
.ip-date-cell {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Le opzioni si rivelano all'hover della riga (solo desktop): il registro
   resta leggibile, il menu non compete con la striscia. Su touch restano
   sempre visibili. */
@media (min-width: 992px) {
  .ip-table-card tbody tr .ip-row-options {
    opacity: 0;
    transition: opacity .15s ease;
  }
  .ip-table-card tbody tr:hover .ip-row-options,
  .ip-table-card tbody tr:focus-within .ip-row-options {
    opacity: 1;
  }
}

/* Barra avanzamento in testa al dettaglio progetto: più larga della
   versione in tabella, con la percentuale a fianco. */

/* Contatti del cliente nella scheda progetto: prima il corpo della card
   ripeteva lo stesso nome gia' visibile nell'intestazione. Riga per riga,
   stessa forma icona+testo delle voci dei menu a tendina (icona 15px, testo
   accanto), così non introduce un terzo linguaggio visivo. */
.ip-project-client-contacts {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-4);
  font-size: var(--ip-text-base);
}
.ip-project-client-contacts a {
  display: flex;
  align-items: center;
  color: var(--ip-text);
  text-decoration: none;
}
.ip-project-client-contacts a:hover { color: var(--ip-primary); }
.ip-project-client-contacts .fa {
  width: 15px;
  /* .fa-margin da solo non basta: da' margine solo dentro .btn, e qui
     l'icona sta in un <a> semplice. */
  margin-right: 6px;
  text-align: center;
  opacity: .75;
}

/* Tabelle dettaglio (es. attività del progetto): collasso mobile
   con [data-label], come le tabelle della bacheca. */
@media (max-width: 767.98px) {
  .ip-project-tasks thead { display: none; }
  .ip-project-tasks,
  .ip-project-tasks tbody,
  .ip-project-tasks tr,
  .ip-project-tasks td { display: block; width: 100%; }
  .ip-project-tasks tbody tr {
    padding: 10px 0;
    border-bottom: 1px solid var(--ip-border);
  }
  .ip-project-tasks tbody tr:last-child { border-bottom: 0; }
  .ip-project-tasks tbody td {
    padding: 3px 12px;
    border: 0;
    text-align: right;
  }
  .ip-project-tasks tbody td::before {
    content: attr(data-label);
    float: left;
    font-size: var(--ip-text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--ip-text-muted);
    padding-top: 5px;
  }
  .ip-project-tasks tbody td.amount::before,
  .ip-project-tasks tbody td[class*="amount"]::before { text-align: right; }
  .ip-project-tasks .options { display: inline-flex; }
}

/* -----------------------------------------------------------------------------
   26. Scadenzario
   -------------------------------------------------------------------------- */

/* Totale della fascia, allineato a destra nell'header della card */
.ip-schedule-bucket-total {
  margin-left: auto;
  font-family: var(--ip-display);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}

/* Filetto colorato a sinistra: identifica la fascia a colpo d'occhio, con le
   stesse tinte di stato usate nel resto dell'app. */
.ip-schedule-bucket { border-left: 3px solid var(--ip-border); }
.ip-schedule-bucket--danger  { border-left-color: var(--ip-danger); }
.ip-schedule-bucket--warning { border-left-color: var(--ip-warning); }
.ip-schedule-bucket--info    { border-left-color: var(--ip-info); }
.ip-schedule-bucket--neutral { border-left-color: var(--ip-neutral); }

/* Distanza dalla scadenza, come nota accanto alla data */
.ip-schedule-days {
  display: block;
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
}

/* -----------------------------------------------------------------------------
   27. Toast di notifica (flashdata: success/info/error)
   --------------------------------------------------------------------------
   Prima erano banner fissi in cima a #main-content: spingevano giù tutta la
   pagina (il layout "saltava" al caricamento) e restavano finché non si
   ricaricava la pagina. Ora sono toast Bootstrap 5 flottanti in alto a
   destra, con auto-dismiss (tranne gli errori, che l'utente chiude a mano).
   -------------------------------------------------------------------------- */

.ip-toast-container {
  position: fixed;
  top: calc(var(--ip-navbar-height, 56px) + 12px);
  right: 16px;
  z-index: var(--ip-z-sheet);
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-4);
  width: min(360px, calc(100vw - 32px));
}

.ip-toast {
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-left: 3px solid var(--ip-neutral);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
}
.ip-toast--success { border-left-color: var(--ip-success); }
.ip-toast--info     { border-left-color: var(--ip-info); }
.ip-toast--warning  { border-left-color: var(--ip-warning); }
.ip-toast--danger   { border-left-color: var(--ip-danger); }

.ip-toast .toast-body {
  display: flex;
  align-items: flex-start;
  gap: var(--ip-space-5);
  padding: 12px 14px;
  font-size: var(--ip-text-base);
  color: var(--ip-text);
}

.ip-toast-icon {
  flex: 0 0 auto;
  margin-top: 1px;
  font-size: var(--ip-text-lg);
}
.ip-toast--success .ip-toast-icon { color: var(--ip-success-text); }
.ip-toast--info     .ip-toast-icon { color: var(--ip-info-text); }
.ip-toast--warning  .ip-toast-icon { color: var(--ip-warning-text); }
.ip-toast--danger   .ip-toast-icon { color: var(--ip-danger-text); }

.ip-toast-message {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.5;
}

.ip-toast .btn-close {
  flex: 0 0 auto;
  font-size: var(--ip-text-xs);
  opacity: .55;
}
.ip-toast .btn-close:hover { opacity: .85; }

/* La classe standard Bootstrap .fade gestisce già opacity + display in modo
   corretto (serve per non avere uno scatto secco fra "assente" e "visibile");
   qui si aggiunge solo lo slide orizzontale, sincronizzato sulla stessa
   transizione. */
.ip-toast.fade {
  transition: transform .2s ease, opacity .15s linear;
  transform: translateX(12px);
}
.ip-toast.fade.show {
  transform: translateX(0);
}

/* -----------------------------------------------------------------------------
   28. Filtro anno globale (topbar)
   --------------------------------------------------------------------------
   Componente dropdown custom (IPSelect, .simple-select), lo stesso usato nel
   resto dell'app — non un <select> nativo che apre il menu di sistema.
   Il pannello si apre comunque su sfondo chiaro standard (monta in <body>,
   fuori dalla topbar): qui si sovrascrive solo il controllo chiuso, per
   intonarlo alla barra scura. L'altezza massima del pannello (con scroll)
   è già gestita da .ip-select-panel__list nella sezione 12a.
   -------------------------------------------------------------------------- */

.ip-year-filter,
.ip-company-switch { display: flex; align-items: center; }

/* Contesto (azienda + anno) in testa alla sidebar, vedi layout/base.php:
   sono i due filtri che riscrivono ogni cifra della pagina e stanno sotto
   un'unica etichetta, con lo stesso trattamento visivo delle voci di menu.
   Su mobile la sidebar è off-canvas, quindi restano entrambi raggiungibili —
   il filtro anno in topbar spariva del tutto sotto i 576px. */
/* Azienda e anno restano in cima alla colonna mentre il menu scorre: sono i
   due comandi che riscrivono ogni cifra della pagina, e prima sparivano
   appena si scorreva fino in fondo alle sezioni. `top` negativo compensa il
   padding del contenitore che scorre, così la piastra si appoggia al bordo
   invece di lasciare una striscia da cui si vedono passare le voci. */
.ip-sidebar-context {
  position: sticky;
  top: -14px;
  z-index: 3;
  margin: -14px -12px 4px;
  padding: 14px 12px 10px;
  background: var(--ip-bg-card);
  box-shadow: 0 1px 0 var(--ip-border-soft);
}
.ip-sidebar-context form + form { margin-top: 6px; }
/* Codice destinatario dell'azienda: riga sola, il codice in mono come i
   codici fiscali della scheda cliente. */
.ip-sidebar-sdi {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 8px 2px 0;
  color: var(--ip-text-muted);
}
.ip-sidebar-sdi__label {
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .06em;
}
.ip-sidebar-sdi__value {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  letter-spacing: .02em;
  color: var(--ip-text-strong);
}
/* In visione off-canvas il contesto non deve incollarsi: la colonna non
   scorre sotto di sé, e sticky lascerebbe uno stacco dal bordo. */
@media (max-width: 991.98px) {
  .ip-sidebar-context { position: static; margin: 0 0 4px; padding: 0; box-shadow: none; }
}
.ip-sidebar .ip-company-switch,
.ip-sidebar .ip-year-filter { display: block; }
.ip-sidebar .ip-company-switch .ip-select,
.ip-sidebar .ip-year-filter .ip-select { width: 100%; }
.ip-sidebar .ip-company-switch .ip-select__control,
.ip-sidebar .ip-year-filter .ip-select__control {
  height: 36px;
  padding: 0 10px;
  border-color: var(--ip-border);
  background-color: var(--ip-hover);
  color: var(--ip-text);
  font-size: var(--ip-text-base);
  font-weight: 500;
}
.ip-sidebar .ip-company-switch .ip-select__control:hover,
.ip-sidebar .ip-year-filter .ip-select__control:hover {
  background-color: var(--ip-hover);
  border-color: var(--ip-border-hover);
}
.ip-sidebar .ip-company-switch .ip-select.is-open .ip-select__control,
.ip-sidebar .ip-company-switch .ip-select__control:focus-visible,
.ip-sidebar .ip-year-filter .ip-select.is-open .ip-select__control,
.ip-sidebar .ip-year-filter .ip-select__control:focus-visible {
  background-color: var(--ip-hover);
  border-color: var(--ip-primary);
  box-shadow: none;
}
.ip-sidebar .ip-company-switch .ip-select__arrow,
.ip-sidebar .ip-year-filter .ip-select__arrow { color: currentColor; opacity: .8; }
/* L'anno è una cifra: incolonnata come tutte le altre dell'app. */
.ip-sidebar .ip-year-filter .ip-select__value { font-variant-numeric: tabular-nums; }

/* -----------------------------------------------------------------------------
   29. Creazione documento (nuova fattura / nuovo preventivo)
   --------------------------------------------------------------------------
   Schermata unica a sezioni numerate con tabella righe e totali live, sullo
   stile dei gestionali di fatturazione (Fatture in Cloud): il documento si
   compila dall'alto verso il basso — cliente, dati, righe. Totale e
   salvataggio vivono nell'headerbar (vedi .headerbar-total, sezione 2bis) e
   non in una barra fissa in fondo, quindi qui non serve spazio riservato. */

/* ---- sezioni ---- */

.ip-doc-section {
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  /* Stessa superficie delle .ip-table-card / .card: ombra leggera di
     sistema invece di card "piatte" che spiccano fra le altre. */
  box-shadow: var(--ip-shadow-sm);
  margin-bottom: 16px;
  /* L'intestazione ha un fondo proprio: senza clip squadrarebbe gli angoli
     alti della sezione. I menu di IPSelect/IPDatepicker sono position:fixed,
     quindi non vengono ritagliati (altrimenti qui sparirebbero). */
  overflow: hidden;
}
/* Testata e corpo usano la stessa geometria delle card (.card > .card-header,
   .card-body): una sezione di documento e una card di form sono lo stesso
   oggetto per chi compila, e prima la sezione rispondeva a 13/16 con titolo
   13px/650 mentre la card a 14/18 con titolo 14px/600. Le due famiglie di
   form (cliente e preventivo da una parte, fattura e impostazioni
dall'altra) avevano quindi intestazioni di altezza diversa. */
.ip-doc-section__head {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  padding: var(--ip-card-head-y) var(--ip-card-pad-x);
  border-bottom: 1px solid var(--ip-border-soft);
  background: var(--ip-bg);
}
.ip-doc-section__title {
  font-size: var(--ip-text-md);
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.01em;
}
.ip-doc-section__hint {
  margin-left: auto;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  font-weight: 500;
}
@media (max-width: 580px) { .ip-doc-section__hint { display: none; } }
.ip-doc-section__body { padding: var(--ip-card-body-y) var(--ip-card-pad-x); }
.ip-doc-section.is-invalid {
  border-color: var(--ip-danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ip-danger) 14%, transparent);
}

/* Icona delle intestazioni di sezione (schermate impostazioni): cala dal
   peso del titolo e si tintinge di neutro, così guida l'occhio senza
   competere con il testo. */
.ip-doc-section__title > i {
  margin-right: 9px;
  font-size: var(--ip-text-md);
  color: var(--ip-text-muted);
  vertical-align: -1px;
}

/* Pallino numerato: dà l'ordine di compilazione a colpo d'occhio. */
.ip-doc-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--ip-text);
  color: var(--ip-bg-card);
  font-size: var(--ip-text-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

/* ---- campi ----
   Componente campo canonico dell'app. Un campo e' sempre:

     <div class="ip-doc-field">
       <label class="ip-doc-label" for="id">Etichetta</label>
       <input id="id" class="form-control">
       <p class="ip-field-help">aiuto</p>        (facoltativo)
       <span class="ip-field-error">errore</span> (facoltativo)
     </div>

   Lo emette layout/field.php: usare il partial, non riscrivere il markup.

   .ip-doc-field nasceva senza regole proprie: era solo un'ancora per la
   griglia, e la spaziatura arrivava tutta dal gap di .ip-doc-grid. Fuori da
   una griglia (invoices/view_sumex.php, expenses/import.php) i campi si
   toccavano, perché il wrapper non portava alcun margine con se'. Un
   componente che funziona solo dentro un contenitore preciso e' un errore
   che si ripete a ogni riuso: qui il margine sta sul campo, e la griglia lo
   azzera perché li' a distanziare ci pensa gia' il gap. */

/* Il campo è una colonna: etichetta, controllo, aiuto, errore. Il gap
   (6px) è l'unica distanza fra questi elementi — prima c'era anche un
   margin-bottom:5px sull'etichetta, quindi etichetta e controllo stavano a
   11px dentro .ip-doc-field e a 6px dove c'era .form-badge: 5px di differenza
   fra due campi uno sopra l'altro nella stessa scheda. */
.ip-doc-field {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-3);
  margin-bottom: 16px;
}
/* Dentro la griglia il gap fa gia' il lavoro: sommare il margine
   raddoppierebbe lo spazio fra le righe di campi. Il reset resta limitato
   ai figli diretti della griglia: un reset globale sull'ultimo figlio
   azzererebbe anche il margine dei campi appaiati in .row > .col (ogni
   campo e' l'unico figlio della sua colonna), schiacciando le righe fra
   loro. */
.ip-doc-grid > .ip-doc-field {
  margin-bottom: 0;
}

/* Coppia di campi sulla stessa riga (es. Nome + Cognome per persona fisica).
   Flex: se uno dei due è nascosto (data-client-type-only), l'altro si espande
   a tutta larghezza; su schermi stretti i campi si impilano. */
.ip-doc-duo {
  display: flex;
  gap: var(--ip-space-7);
  margin-bottom: 16px;
}
.ip-doc-duo > .ip-doc-field {
  flex: 1 1 0;
  min-width: 0;
  margin-bottom: 0;
}
@media (max-width: 575.98px) {
  .ip-doc-duo { flex-direction: column; }
}

/* Un pulsante dentro un campo non deve allargarsi come il controllo: .ip-doc-field
   è una colonna flex, quindi per default i figli si stendono a tutta larghezza.
   "Aggiungi metodo" diventava una fascia trasparente larga quanto la scheda con
   la scritta centrata, e sembrava una riga di testo orfana in mezzo al form.
   Le azioni di un campo stanno a sinistra e larghe quanto il loro testo. */
.ip-doc-field > .btn,
.ip-doc-field > .btn-group { align-self: flex-start; }

/* Ripartizione di un pagamento su più metodi: una riga per metodo. L'importo
   sta stretto perché sono cifre, il metodo si allarga fino a una misura
   leggibile (non oltre: in una sezione a tutta pagina un select elastico
   diventerebbe lungo un metro e mezzo per scegliere fra sei voci), e il
   pulsante di rimozione resta attaccato alla riga. */
.payment-split-rows {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-4);
}
/* Senza righe il contenitore non deve lasciare il proprio posto nel gap del
   campo: il pulsante salirebbe di 6px rispetto all'aiuto sopra. */
.payment-split-rows:empty { display: none; }
.payment-split-row {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  /* Su schermo stretto importo, metodo e cestino non ci stanno su una riga:
     invece di comprimere il select fino a nascondere il nome del metodo,
     la riga va a capo. */
  flex-wrap: wrap;
}
.payment-split-row .payment-split-amount { flex: 0 0 9rem; }
/* La misura va sul contenitore di IPSelect, non sul <select>: IPSelect avvolge
   il controllo in .ip-select, che porta width:100% e nel flex contava come
   base il 100% della riga — la select occupava tutto lo spazio e sospingeva
   il cestino fuori dalla card (visibile solo clippato dall'overflow:hidden).
   Il <select> nativo resta nell'elenco per il caso in cui IPSelect non sia
   ancora passato a inizializzare la riga appena clonata. */
.payment-split-row .ip-select,
.payment-split-row select.form-control {
  flex: 0 1 22rem;
  width: auto;
  min-width: 0;
}
.payment-split-row .payment-split-remove { flex: 0 0 auto; }
/* Sotto i 576px la riga si organizza su due righe invece di comprimersi:
   l'importo prende la prima, perché è il dato che si legge e si controlla, e
   metodo + cestino stanno sulla seconda. Con le misure da schermo largo il
   cestino finiva da solo su una terza riga, staccato dal metodo che cancella. */
@media (max-width: 575.98px) {
  .payment-split-row .payment-split-amount { flex: 1 1 100%; }
  .payment-split-row .ip-select,
  .payment-split-row select.form-control { flex: 1 1 8rem; }
}
/* L'aiuto della ripartizione spiega un caso raro in una frase lunga: a tutta
   sezione supererebbe i 160 caratteri per riga e si rileggerebbe con fatica. */
.payment-split-help { max-width: 78ch; }

/* Etichetta di campo: una sola definizione (prima la tipografia stava in una
   regola duplicata 4000 righe più avanti, insieme al resto del blocco export).
   Niente margine: la distanza dal controllo la dà il gap di .ip-doc-field, così
   etichetta e campo non possono più allontanarsi o avvicinarsi a seconda di
   dove sono. */
.ip-doc-label {
  display: block;
  margin-bottom: 0;
  font-size: var(--ip-text-sm);
  font-weight: 600;
  color: var(--ip-text);
  letter-spacing: .01em;
}

/* Campo obbligatorio: il segno lo mette il CSS leggendo l'attributo required
   del controllo, non una classe da ricordare sull'etichetta. Prima l'unico
   modo di sapere che un campo era obbligatorio era provare a salvare — la
   scheda cliente ha ~200 controlli e nessuno di essi lo dichiarava a schermo.
   Il lettore di schermo annuncia già "obbligatorio" dall'attributo, quindi
   qui serve solo il segnale visivo. */
.ip-doc-field:has(> input[required], > select[required], > textarea[required]) > .ip-doc-label::after,
.ip-doc-field:has(> .ip-select input[required], > .ip-file input[required]) > .ip-doc-label::after,
.form-group:has(> input[required], > select[required], > textarea[required]) > label::after {
  content: " *";
  color: var(--ip-danger-text);
  font-weight: 700;
}

/* Voce di un campo personalizzato MULTIPLE-CHOICE: e' una lista di checkbox
   (vedi CLAUDE.md — IPSelect e' a scelta singola), quindi ogni opzione ha la
   sua label. Era uno style inline ripetuto in clients/form.php e
   payments/form.php: stesso aspetto scritto sei volte, non modificabile dal
   tema. Il peso normale la distingue dal titolo del gruppo (.ip-doc-label). */
.ip-choice-option {
  display: block;
  font-weight: 400;
  color: var(--ip-text);
}
.ip-choice-option input {
  margin-right: 6px;
}

/* Due colonne, e una sola sotto i 768px. Le tre colonne dichiarate qui non si
   vedevano da nessuna parte: una regola della pagina export (più avanti nel
   foglio, stessa specificità) le sostituiva con due su tutta l'app, portandosi
   dietro anche il proprio max-width e sovrascrivendo la media query qui sotto,
   che quindi non collassava mai a una colonna. Il valore che si vedeva era due:
   ora è quello dichiarato, con il collasso che funziona davvero. */
.ip-doc-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ip-space-7);
}
@media (max-width: 767.98px) {
  .ip-doc-grid { grid-template-columns: minmax(0, 1fr); }
}

.ip-doc-hint-link {
  display: inline-block;
  margin-top: 8px;
  font-size: var(--ip-text-sm);
}

/* ---- report: intervalli rapidi + azioni filtro ----
   I filtri dei report (storico pagamenti, vendite per cliente/data) chiedono
   quasi sempre lo stesso periodo (mese corrente, anno corrente...): niente di
   nuovo da digitare ogni volta, solo un tasto. I chip scrivono nell'input del
   datepicker e sparano un evento 'input' nativo, così il datepicker
   ricalcola la sua vista come se l'utente avesse digitato la data a mano. */
.ip-quick-range {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-3);
  margin: 2px 0 16px;
}
.ip-quick-range__label {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  margin-right: 2px;
}
.ip-quick-range__btn {
  border: 1px solid var(--ip-border);
  background: transparent;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  line-height: 1;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background var(--ip-transition), color var(--ip-transition), border-color var(--ip-transition);
}
.ip-quick-range__btn:hover {
  background: var(--ip-hover);
  color: var(--ip-text);
}
/* Focus visibile gia' coperto dalla regola globale :focus-visible (non e' un
   .btn/.form-control, quindi la eredita senza bisogno di ripeterla qui). */

/* Colonna dei pulsanti azione (Esegui/Esporta) accanto ai campi data: senza
   un'etichetta "fantasma" della stessa altezza di .ip-doc-label i pulsanti
   restano più in alto degli input, disallineati sulla riga. */
.ip-doc-field__ghost-label {
  visibility: hidden;
}

/* ---- cliente + anteprima ---- */

/* Colonna singola nei collassi responsive: sempre minmax(0, 1fr) e mai 1fr.
   Il minimo di una traccia 1fr è il contenuto (min-content), quindi una riga
   che non va a capo teneva la colonna più larga del contenitore: in modifica
   fattura, su 375 px, la tabella "Imposte fattura" sporgeva di 15 px e veniva
   tagliata proprio sugli importi. Stesso difetto già corretto su .ip-preview;
   ora vale per tutti i tredici collassi del foglio. */
.ip-doc-client {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--ip-space-8);
  align-items: start;
}
@media (max-width: 767.98px) {
  .ip-doc-client { grid-template-columns: minmax(0, 1fr); }
}

.ip-doc-client__preview {
  padding: 12px 14px;
  border: 1px dashed var(--ip-border);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg);
}
.ip-doc-client__preview p { margin: 0; }
.ip-doc-client__name {
  font-weight: 600;
  margin-bottom: 3px;
}
.ip-doc-client__address,
.ip-doc-client__meta {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  line-height: 1.5;
}

/* ---- tabella righe ---- */

.ip-doc-items {
  /* La tabella può eccedere in larghezza su schermi stretti: scorre da sola
     invece di allargare la pagina. */
  overflow-x: auto;
  /* Contenitore di posizionamento per i discendenti assoluti. Le intestazioni
     "Ordine"/"Opzioni" sono span.visually-hidden (position: absolute) dentro
     le celle: senza un antenato posizionato il loro static position cade sul
     bordo destro della tabella larga, che così sfugge allo scroll del
     contenitore e allunga l'intera pagina (visto su modifica fattura e
     preventivo). */
  position: relative;
}

.ip-items-table {
  width: 100%;
    border-collapse: collapse;
}

.ip-items-table thead th {
  padding: 10px 12px;
  border-bottom: 1px solid var(--ip-border);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ip-text-muted);
  text-align: left;
  white-space: nowrap;
}

.ip-items-table tbody td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--ip-border-soft);
  vertical-align: top;
}
/* Rientro del blocco righe. Intestazione della sezione, totali e "Aggiungi
   riga" stanno a 18px; la tabella partiva a filo card (0) e le celle a 12,
   quindi il primo campo e i comandi finali non cadevano sulla verticale di
   titolo e Totale. Il rientro lo dà il contenitore quando il blocco è figlio
   diretto della sezione; quando la tabella vive dentro un __body lo dà già il
   corpo. Le celle di bordo non aggiungono padding proprio (piuttosto che
   sommare 18 a una colonna larga 44 come la maniglia e le azioni, che si
   schiaccerebbero), così il contenuto parte esattamente dal bordo del blocco.
   Totali e piè del blocco non rientrano una seconda volta: lo fa già il
   contenitore. */
.ip-doc-section > .ip-doc-items {
  padding-left: var(--ip-card-pad-x);
  padding-right: var(--ip-card-pad-x);
}
.ip-doc-items > .ip-doc-totals,
.ip-doc-items > .ip-doc-items__footer {
  padding-left: 0;
  padding-right: 0;
}
.ip-doc-section .ip-items-table > thead > tr > :first-child,
.ip-doc-section .ip-items-table > tbody > tr > :first-child,
.ip-doc-section .ip-items-table > tfoot > tr > :first-child {
  padding-left: 0;
}
.ip-doc-section .ip-items-table > thead > tr > :last-child,
.ip-doc-section .ip-items-table > tbody > tr > :last-child,
.ip-doc-section .ip-items-table > tfoot > tr > :last-child {
  padding-right: 0;
}

.ip-items-table__num,
.ip-items-table__total { text-align: right; }
.ip-items-table__num   { width: 110px; }
/* 130px bastava quando l'opzione era solo "22%": da quando l'etichetta
   include anche nome aliquota e, se presente, il codice Natura IVA
   (es. "0% — Reverse charge edile (inversione contabile N6.3)"), il testo
   nel controllo chiuso veniva tagliato senza ellissi (i <select> nativi non
   la applicano da soli). 210px riduce il taglio per le aliquote più comuni;
   il testo completo resta comunque leggibile aprendo il menu (le opzioni del
   popup nativo non sono vincolate alla larghezza del controllo chiuso). */
 .ip-items-table__tax   { width: 210px; }
/* Unita' di misura: select più stretto delle colonne importo, ma sufficiente
   a contenere "pz / pezzi" senza che il menu custom tronchi l'etichetta. */
 .ip-items-table__unit { width: 130px; }
 /* Codice riga (MEPA o articolo concordato): stretto come l'unità di misura,
    il codice è un'etichetta corta e la descrizione ha bisogno della larghezza. */
 .ip-items-table__code { width: 120px; }
 .ip-items-table__code .form-control { font-family: var(--ip-mono); font-size: var(--ip-text-sm); }

/* Righe cliccabili delle tabelle elenco (vedi ip-table-rows.js): il cursore
   segnala che la riga intera porta al record, non solo i singoli link. */
.ip-clickable { cursor: pointer; }
.ip-items-table__total { width: 130px; white-space: nowrap; }
.ip-items-table__actions { width: 44px; text-align: center; }

.ip-items-table__num .form-control { text-align: right; }

.ip-item-row__desc {
  margin-top: 6px;
  resize: vertical;
  min-height: 34px;
}

.ip-item-row__total {
  display: inline-block;
  padding-top: 7px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.ip-item-row__remove {
  margin-top: 4px;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--ip-radius-sm);
  background: transparent;
  color: var(--ip-text-muted);
  line-height: 1;
  transition: color var(--ip-transition), background-color var(--ip-transition);
}
.ip-item-row__remove:hover {
  background: var(--ip-danger-bg);
  color: var(--ip-danger);
}

.ip-doc-items__footer {
  padding: 12px 18px;
}

/* ---- totali ---- */

.ip-doc-totals {
  display: flex;
  justify-content: flex-end;
  padding: 0 18px 18px;
}

.ip-doc-totals__list {
  display: grid;
  grid-template-columns: auto minmax(120px, auto);
  gap: 6px 32px;
  margin: 0;
  min-width: 260px;
}
.ip-doc-totals__list dt {
  font-weight: 400;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
}
.ip-doc-totals__list dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ip-doc-totals__list dt.is-total,
.ip-doc-totals__list dd.is-total {
  padding-top: 8px;
  border-top: 1px solid var(--ip-border);
  font-size: var(--ip-text-lg);
  font-weight: 700;
  color: var(--ip-text);
}

/* -----------------------------------------------------------------------------
   30. Serie di numerazione (gruppi fatture)
   --------------------------------------------------------------------------
   Il formato identificatore è un mini-linguaggio ({{{id}}}, {{{year}}}, testo
   libero): senza anteprima si capisce che numero produce solo dopo aver
   salvato e creato un documento. */

/* .ip-field-help e' il nome canonico: il testo di aiuto sotto un campo non
   riguarda solo i formati numerici, ma nasce li' (l'anteprima del numero
   documento) e il nome vecchio e' rimasto in uso in molte view. Stessa
   regola per entrambi finche' .ip-number-help non sparisce del tutto. */
.ip-field-help,
.ip-number-help {
  margin: 6px 0 0;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
}
/* Dentro un campo canonico la distanza la dà il gap del contenitore: i margini
   propri di aiuto ed errore si sommerebbero (6+6 e 4+6), e la stessa scheda
   avrebbe tre distanze diverse fra etichetta, controllo e messaggi. Fuori da
   .ip-doc-field i margini restano, perché lì non c'è nessun gap. */
.ip-doc-field > .ip-field-help,
.ip-doc-field > .ip-number-help,
.ip-doc-field > .ip-field-error { margin-top: 0; }

/* Etichetta "obsoleto" accanto al nome di un campo mantenuto per sola
   compatibilita' (es. l'IBAN nel profilo utente, superato dai Conti di saldo):
   dice che il campo non e' il posto giusto per inserire quel dato, senza
   nasconderlo a chi ce l'ha ancora valorizzato. */
.ip-legacy-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--ip-warning-bg);
  color: var(--ip-warning-text);
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  vertical-align: middle;
}

/* Riga "aggiungi cliente assegnato": select + bottone allineati sulla stessa
   base. Prima il select stava in un .mb-3 dentro un .form-inline, quindi il
   bottone finiva sotto, disallineato di 16px. */
.ip-user-client-add {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  flex-wrap: wrap;
  margin-top: 12px;
}
.ip-user-client-add .simple-select,
.ip-user-client-add .ip-select {
  flex: 1 1 240px;
  min-width: 0;
  max-width: 360px;
}

.ip-number-preview {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  margin-bottom: 16px;
  padding: 12px 14px;
  border: 1px dashed var(--ip-border);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg);
}
.ip-number-preview__label {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--ip-text-muted);
}
.ip-number-preview__value {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-lg);
  font-weight: 600;
  color: var(--ip-text);
  word-break: break-all;
}

.ip-tag-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-4);
  margin-top: 10px;
}

.ip-tag-button {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
              line-height: 1.4;
  }

.ip-tag-button code {
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--ip-primary-soft);
  color: var(--ip-text);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
}

/* Il formato grezzo con i segnaposto serve solo per anno/mese/giorno: sta
   chiuso per non mettere un mini-linguaggio davanti a chi vuole solo la
   sigla dopo il numero. */
.ip-disclosure {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
}
.ip-disclosure i {
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
}

/* I tre campi semplici non sanno rappresentare un formato con anno/mese: in
   quel caso restano visibili ma inerti, così è chiaro che comanda il campo
   avanzato invece di mostrare valori che non corrispondono al vero. */
#simple_builder.is-disabled {
  opacity: 0.55;
}

/* -----------------------------------------------------------------------------
   31. Anteprima documento (vista fattura / preventivo)
   --------------------------------------------------------------------------
   Il foglio è servito in un iframe che carica lo stesso template del PDF:
   l'anteprima non può divergere da ciò che si scarica o si invia. Intorno,
   solo il riepilogo e le azioni. */

.ip-preview {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--ip-space-7);
  align-items: start;
}

/* Anteprima XML: gerarchia più pulita, meno chrome e maggiore spazio al documento. */
.ip-preview .ip-preview__sheet {
  padding: 12px;
  border-radius: var(--ip-radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .08);
}
.ip-preview .ip-preview__side {
  position: sticky;
  top: 16px;
}
/* La colonna laterale dell'anteprima (300px) aveva una geometria propria:
   testata 11/14 con titolo 12px/650, corpo 14/16 e un'ombra cablata. Erano
   gli stessi .card del resto dell'app rimpiccioliti: ora usano i token
   (--ip-card-*), quindi un pannello laterale e uno centrale si leggono come
   lo stesso oggetto. */
/* Fornitore accanto al numero nel titolo dell'anteprima spesa. */
.ip-preview-supplier {
  font-size: var(--ip-text-base);
  font-weight: 500;
  color: var(--ip-text-muted);
}

@media (max-width: 991.98px) {
  .ip-preview .ip-preview__side { position: static; }
}
@media (max-width: 991.98px) {
  /* minmax(0, 1fr) e non 1fr: con 1fr il minimo della traccia è il contenuto
     (min-content), quindi le tabelle interne larghe (prospetto di Utile e
     imposte) tenevano la colonna a 754 px su uno schermo da 390 e la pagina
     scorreva in orizzontale. */
  .ip-preview { grid-template-columns: minmax(0, 1fr); }
}

/* Sfondo scuro attorno al foglio: lo stacca dalla pagina e lo fa leggere
   come un documento, non come un'altra sezione dell'interfaccia. */
.ip-preview__sheet {
  padding: 20px;
  border-radius: var(--ip-radius);
  background: var(--ip-preview-bg);
}

/* Il riquadro non è più una scatola bianca alta 800 px: dentro c'è la pagina
   A4 vera (210 mm, scalata da layout/preview_fit) e l'altezza la misura lo
   script sul contenuto. Un min-height alto lasciava sotto i documenti brevi
   una pagina di bianco che non esiste nel PDF. Lo sfondo resta trasparente
   perché il foglio (bianco, con la sua ombra) si stacchi dal fondo
   "scrivania" del riquadro, come la carta sul tavolo. */
.ip-preview__frame {
  display: block;
  width: 100%;
  min-height: 320px;
  border: 0;
  background: transparent;
}

.ip-preview__side .card:last-child { margin-bottom: 0; }

.ip-preview__totals {
  display: grid;
  /* Prima l'etichetta, poi il valore. Con due colonne auto la griglia
     divideva lo spazio in proporzione al contenuto: un valore lungo (la
     ragione sociale, la forma giuridica) allargava la colonna di destra e
     schiacciava le etichette, che andavano a capo anche se corte
     ("Aliquota contributi" per 2 px). fit-content dà all'etichetta la sua
     larghezza intera fino al 60%; il valore prende il resto e va a capo lui. */
  grid-template-columns: fit-content(60%) minmax(90px, 1fr);
  gap: 6px 16px;
  margin: 0;
}
.ip-preview__totals dt {
  font-weight: 400;
  font-size: var(--ip-text-base);
  color: var(--ip-text-muted);
}
.ip-preview__totals dd {
  margin: 0;
  text-align: right;
  font-size: var(--ip-text-base);
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}
.ip-preview__totals dt.is-total,
.ip-preview__totals dd.is-total {
  padding-top: 6px;
  border-top: 1px solid var(--ip-border-soft);
  font-weight: 700;
  color: var(--ip-text);
}
/* Soggetti (emittente/destinatario): nomi lunghi, su riga intera e a
   sinistra, altrimenti l'allineamento a destra li spezzava. Il filetto in
   cima separa i gruppi (documento / importi / soggetti) senza etichette. */
.ip-preview__totals dt.is-wide,
.ip-preview__totals dd.is-wide {
  grid-column: 1 / -1;
  text-align: left;
}
.ip-preview__totals dt.is-wide {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--ip-border-soft);
}
.ip-preview__totals dd.is-wide {
  margin-top: -2px;
  font-weight: 500;
}
.ip-preview__empty {
  margin: 0;
  font-size: var(--ip-text-base);
  color: var(--ip-text-muted);
}

.ip-preview__payments {
  margin: 0;
  padding: 0;
  list-style: none;
}
.ip-preview__payments li {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--ip-space-4);
  padding: 6px 0;
  border-bottom: 1px solid var(--ip-border-soft);
  font-size: var(--ip-text-base);
}
.ip-preview__payments li:last-child { border-bottom: 0; }
.ip-preview__payment-date   { color: var(--ip-text-muted); font-variant-numeric: tabular-nums; }
.ip-preview__payment-method { color: var(--ip-text-muted); }
.ip-preview__payment-amount { font-weight: 600; font-variant-numeric: tabular-nums; }

/* Invii email: il destinatario è un indirizzo lungo e senza spazi, e nella
   colonna stretta dell'anteprima allargherebbe la card invece di andare a capo. */
.ip-preview__payments--tracks li { grid-template-columns: auto minmax(0, 1fr) auto; }
.ip-preview__track-recipient { overflow-wrap: anywhere; }

/* -----------------------------------------------------------------------------
   32. Preferiti e riferimenti incrociati nelle liste
   -------------------------------------------------------------------------- */

.ip-fav-col {
  width: 36px;
  text-align: center;
  padding-right: 0 !important;
}
.ip-fav-col form { display: inline; }

.ip-fav-toggle {
  /* 24x24: bersaglio minimo di WCAG 2.5.8 (era 23x18). L'aspetto non cambia,
     cresce l'area cliccabile attorno all'icona. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  padding: 2px 4px;
  border: 0;
  background: transparent;
  line-height: 1;
  /* Stella spenta appena accennata: la colonna non deve competere con i dati
     della riga finché non è accesa. Il token del bordo campi (3:1) invece di
     --ip-border (1,3:1): è un comando, non una linea di separazione, e a
     1,3:1 non si vedeva che c'era. */
  color: var(--ip-border-field);
  transition: color var(--ip-transition), transform var(--ip-transition);
}
.ip-fav-toggle:hover {
  color: var(--ip-warning);
  transform: scale(1.15);
}
.ip-fav-toggle.is-on { color: var(--ip-warning); }

/* Numero documento nelle liste: è il link che porta al record ed era alto
   16px. inline-block + padding verticale lo portano a 24px senza allargare
   la riga (il padding rientra nell'altezza già occupata dalla cella). */
.ip-table-card tbody td > a,
.ip-table-card tbody td > form > a,
.ip-window td > a,
.ip-window-footer > .ip-window-more {
  display: inline-block;
  min-width: 24px;
  padding: 4px 0;
}

/* -----------------------------------------------------------------------------
   Azioni di gruppo nelle liste
   --------------------------------------------------------------------------
   La barra compare solo con almeno una riga selezionata (attributo hidden
   gestito da ip-bulk-actions.js): a riposo non c'è, e non offre comandi
   inutilizzabili. Flottante in basso: resta sempre raggiungibile senza
   scrollare. Mentre è visibile il fondo pagina si allunga della sua altezza
   (body.ip-bulk-active, altezza misurata dallo script perché i bottoni
   vanno a capo): pager e ultima riga non finiscono coperti. */
.ip-bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 14px;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: var(--ip-z-overlay);
  padding: 12px 20px;
  background: var(--ip-bg-card);
  border-top: 1px solid var(--ip-border);
  box-shadow: 0 -4px 12px rgba(0,0,0,.08);
}
/* Desktop con sidebar: la barra flotta sull'area contenuto, non a tutta
   viewport — altrimenti la sua fascia (z-index 1050) copre la parte bassa
   della sidebar fissa (z-index 1025). Il selettore fratello (#ip-sidebar ~),
   stesso criterio di #main-area, esclude le pagine senza guscio (guest). */
@media (min-width: 992px) {
  #ip-sidebar ~ #main-area .ip-bulk-bar { left: var(--ip-sidebar-width, 236px); }
}
body.ip-bulk-active #main-area {
  padding-bottom: calc(var(--ip-bulk-bar-h, 0px) + 12px);
}
.ip-bulk-bar[hidden] { display: none; }
.ip-bulk-bar__count {
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
}
.ip-bulk-bar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-3);
  margin-left: auto;
}
/* L'eliminazione resta un pulsante neutro finché non ci si passa sopra: in
   una barra di comandi ravvicinati un fondo rosso fisso attira il click
   proprio sull'azione che non si annulla. */
.ip-bulk-bar__danger:hover,
.ip-bulk-bar__danger:focus-visible {
  background: var(--ip-danger);
  border-color: var(--ip-danger);
  color: #fff;
}
html[data-theme="dark"] .ip-bulk-bar__danger:hover,
html[data-theme="dark"] .ip-bulk-bar__danger:focus-visible { color: var(--ip-bg); }

/* Colonna delle caselle: stretta quanto basta, allineata al centro come la
   colonna dei preferiti che le sta accanto. */
.ip-bulk-col {
  width: 34px;
  text-align: center;
  vertical-align: middle;
}
.ip-bulk-col .form-check-input { margin: 0; }
/* Area cliccabile della casella: 24x24 (WCAG 2.5.8) attorno a un quadratino
   che resta di 16px. La <label> avvolge l'input, quindi il click su tutta
   l'area lo commuta senza JavaScript. */
.ip-bulk-hit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
  margin: 0;
  cursor: pointer;
}

@media (max-width: 575.98px) {
  /* Nella scheda mobile la selezione multipla non ha senso: la barra
     resterebbe sopra una lista di schede senza caselle da spuntare. */
  .ip-bulk-bar { display: none; }
}

/* Gruppo di filtri: etichetta in maiuscoletto accanto ai pulsanti. Prima
   ogni gruppo portava un filetto verticale a sinistra; dentro .ip-filter-bar
   il filetto è stato tolto (vedi più sotto), qui restano solo spaziatura e
   tipografia dell'etichetta. */
.ip-filter-group {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
}
.ip-filter-group__label {
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ip-text-muted);
  white-space: nowrap;
}

/* -----------------------------------------------------------------------------
   Barra filtri delle liste (fatture, spese, preventivi)
   --------------------------------------------------------------------------
   Un solo componente per ogni larghezza. Su desktop i gruppi stanno in fila e
   li distingue lo spazio più largo fra un gruppo e l'altro (i pulsanti dentro
   un gruppo sono attaccati, fra gruppi c'è il doppio del respiro) più
   l'etichetta in maiuscoletto. Niente filetti verticali: quando i gruppi vanno
   a capo il filetto del primo della riga nuova resta orfano, appeso al nulla.
   Su telefono il pannello è chiuso dietro il pulsante "Filtra" e ogni gruppo
   occupa la riga intera, così le etichette restano leggibili invece di
   comprimersi.
   -------------------------------------------------------------------------- */
.ip-filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-6) var(--ip-space-7);
  padding: 10px 20px;
}
/* Da 992px il pannello è sempre aperto e la barra deve stare sullo stesso asse
   della striscia dei mesi che le sta sotto (centrata, ~990px): prima partiva
   da sinistra mentre mesi e riepilogo erano centrati, e le tre righe
   sembravano tre blocchi diversi. Il blocco si centra, le voci restano
   allineate a sinistra al suo interno: centrare anche ogni singola riga
   mandava a galleggiare da sola la riga che andava a capo (es. "Preferite"). */
@media (min-width: 992px) {
  .ip-filter-bar {
    width: fit-content;
    max-width: 100%;
    margin-inline: auto;
  }
}
/* Il primo gruppo non ha nulla a sinistra da cui separarsi. */
.ip-filter-bar .ip-filter-group {
  margin-left: 0;
  padding-left: 0;
  border-left: 0;
}
/* Sotto i 992px (dove il pulsante "Filtra" esiste) i gruppi vanno in colonna:
   in riga la somma delle voci supererebbe la larghezza del pannello. Qui il
   filetto torna utile, perché separa righe intere e non può restare orfano. */
@media (max-width: 991.98px) {
  .ip-filter-bar {
    flex-direction: column;
    align-items: stretch;
    gap: var(--ip-space-6);
    padding: 12px 16px 14px;
  }
  .ip-filter-bar .ip-filter-group + .ip-filter-group {
    padding-top: var(--ip-space-6);
    border-top: 1px solid var(--ip-border);
  }
}
/* Telefono: l'etichetta sopra e i pulsanti sotto, in una griglia di celle
   uguali. Come segmento unico gli stessi sei stati diventavano larghi 50px
   l'uno e il testo andava a capo dentro il pulsante. */
@media (max-width: 575.98px) {
  .ip-filter-bar .ip-filter-group {
    flex-direction: column;
    align-items: stretch;
    gap: var(--ip-space-2);
  }
  .ip-filter-bar .btn-group {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 4px;
  }
  /* Fuori dal segmento i raggi dei bordi li decide la cella, non la
     posizione nel gruppo. */
  .ip-filter-bar .btn-group > .btn {
    border-radius: var(--ip-radius-sm);
  }
}

.ip-quote-invoice-ref {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  white-space: nowrap;
}
.ip-quote-invoice-ref i {
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
}

/* -----------------------------------------------------------------------------
   33. Sidebar espandibile e menu utente
   --------------------------------------------------------------------------
   Le voci principali restano link alla loro pagina; la freccia accanto apre e
   chiude le sotto-voci. Prima le sotto-voci erano sempre tutte aperte e la
   colonna diventava un muro di link. */

.ip-nav-row {
  display: flex;
  align-items: center;
  gap: var(--ip-space-1);
}
.ip-nav-row .ip-nav-link {
  flex: 1 1 auto;
  min-width: 0;
}

.ip-nav-toggle {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--ip-radius-sm);
  background: transparent;
  color: var(--ip-text-muted);
  transition: background var(--ip-transition), color var(--ip-transition);
}
.ip-nav-toggle i {
  font-size: var(--ip-text-2xs);
  transition: transform var(--ip-transition);
}
.ip-nav-toggle:hover {
  background: var(--ip-hover);
  color: var(--ip-text);
}
/* Freccia in giù = sezione aperta; ruotata a destra = chiusa. */
.ip-nav-toggle[aria-expanded="false"] i { transform: rotate(-90deg); }

.ip-nav-item.active > .ip-nav-row .ip-nav-toggle { color: var(--ip-primary); }

/* ---- menu utente in topbar ---- */

/* Stesso trattamento del filtro anno: sono i due comandi della barra, e
   prima uno sembrava un bottone e l'altro un link. */
.ip-user-button {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-4);
  height: 32px;
  padding: 0 10px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  /* Stesso raggio del controllo IPSelect del filtro anno (--ip-radius-sm):
     due bottoni affiancati con angoli diversi si notano. */
  border-radius: var(--ip-radius-sm);
  background-color: rgba(255, 255, 255, 0.08);
  color: var(--ip-navbar-text);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  line-height: 1;
  transition: background-color var(--ip-transition), border-color var(--ip-transition), color var(--ip-transition);
}
.ip-user-button:hover,
.ip-user-button[aria-expanded="true"] {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.4);
  color: #fff;
}
.ip-user-button .ip-nav-avatar {
  width: 22px;
  height: 22px;
  font-size: var(--ip-text-xs);
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}
.ip-user-button:hover .ip-nav-avatar,
.ip-user-button[aria-expanded="true"] .ip-nav-avatar {
  background: rgba(255, 255, 255, 0.3);
}
.ip-user-button__arrow {
  display: inline-flex;
  font-size: var(--ip-text-2xs);
  opacity: .8;
}
.ip-user-button[aria-expanded="true"] .ip-user-button__arrow i { transform: rotate(180deg); }
.ip-user-button__arrow i { transition: transform var(--ip-transition); }

/* -----------------------------------------------------------------------------
   34. Modifica documento — stesso impianto della creazione
   --------------------------------------------------------------------------
   Le schermate di modifica riusano .ip-doc-section / .ip-items-table del form
   di creazione; qui restano solo gli elementi che la creazione non ha: il
   salvataggio per riga, la riga di aggiunta rapida e i totali di dettaglio. */

/* Riga di aggiunta dentro la tabella righe (tfoot): stessi campi e stesse
   colonne delle righe salvate, così compilare una riga nuova e correggerne
   una esistente si fanno con lo stesso gesto e nello stesso posto. Il fondo
   appena più chiaro e il bordo pieno la distinguono da una riga vera senza
   toglierla dalla griglia. */
.ip-items-table tfoot td {
  padding: 10px 12px;
  vertical-align: top;
  background: var(--ip-hover);
  border-top: 1px solid var(--ip-border);
}
.ip-item-row--new__mark {
  display: inline-block;
  padding-top: 9px;
  color: var(--ip-text-muted);
}
.ip-item-row__total--empty {
  color: var(--ip-text-muted);
  font-weight: 400;
}
/* Il contatore caratteri della descrizione e' un blocco: nella cella deve
   restare sotto il campo, non affiancarlo. */
.ip-items-table__desc .ip-desc-wrap { display: block; }

/* Riga di aggiunta rapida sotto una tabella (nuova riga, nuova imposta). */
.ip-doc-newrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-4);
  margin-top: 10px;
}
.ip-doc-newrow .form-control { width: auto; flex: 1 1 140px; min-width: 0; }
/* IPSelect sostituisce il <select> con uno span a larghezza piena: senza
   questo la riga di aggiunta andava a capo con l'aliquota da sola. */
.ip-doc-newrow .ip-select { flex: 0 0 120px; width: 120px; }
.ip-doc-newrow .amount { flex: 0 0 90px; text-align: right; }
.ip-doc-newrow .btn { flex: 0 0 auto; }
.ip-doc-newrow__check {
  flex: 0 0 auto;
  margin: 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}

/* In modifica ogni riga si salva da sé: il segno di spunta sta accanto al
   cestino, con lo stesso peso visivo. */
.ip-item-row__save {
  margin-top: 4px;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--ip-radius-sm);
  background: transparent;
  color: var(--ip-text-muted);
  line-height: 1;
  transition: color var(--ip-transition), background-color var(--ip-transition);
}
.ip-item-row__save:hover {
  background: var(--ip-success-bg);
  color: var(--ip-success-text);
}

/* Imponibile + IVA sotto il totale di riga: in modifica servono i dettagli,
   ma senza rubare due colonne alla tabella. */
.ip-item-row__breakdown {
  display: block;
  margin-top: 2px;
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
}

.ip-items-table__empty {
  padding: 16px 12px !important;
  color: var(--ip-text-muted);
  text-align: center;
}

/* Il riquadro cliente in modifica è informativo, non un campo. */
.ip-doc-client__field .ip-doc-client__name a { text-decoration: none; }

/* Barra azioni del documento.
   Era un .btn-group, ma metà dei comandi sono <form> (nota di credito, copia,
   elimina): Bootstrap salda gli angoli con :first-child/:last-child, che così
   cadevano sul form invece che sul bottone, e il gruppo si vedeva sbeccato.
   Qui è una barra flex e i form spariscono dal flusso con display: contents,
   così i bottoni restano figli diretti e allineati. */
.ip-doc-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-3);
}
/* Solo i form figli diretti: quelli dentro il menu a tendina sono voci di
   menu, e con display:contents perdevano il proprio riquadro collassando. */
.ip-doc-actions > form { display: contents; }
.ip-doc-actions .dropdown { display: inline-flex; }
/* Le voci che sono form POST (CSRF) dentro il menu: niente margine che
   spaccherebbe la riga, la voce (il button) gestisce larghezza e hover. */
.ip-doc-actions .dropdown-menu form { margin: 0; }
/* Pulsante "Altro": stile ghost per creare la gerarchia visiva
   Salva (pieno verde) > Visualizza (pieno primario) > Altro (trasparente).
   Il bottone secondario pieno competeva con l'azione primaria; il ghost
   lo arretra senza nasconderlo, e l'icona ellipsis resta il segnale
   che indica "qui c'è di più". */
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle {
  background: transparent;
  border: 1px solid var(--ip-border-strong);
  color: var(--ip-text-muted);
  box-shadow: none;
  transition: background-color var(--ip-transition), border-color var(--ip-transition), color var(--ip-transition);
}
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle:hover,
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle:focus-visible {
  background: var(--ip-hover);
  border-color: var(--ip-border-strong-hover);
  color: var(--ip-text);
}
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle:focus-visible {
  box-shadow: var(--ip-shadow-focus);
}
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle .fa {
  opacity: 0.7;
  transition: opacity var(--ip-transition);
}
.ip-doc-actions .dropdown > .btn-secondary.dropdown-toggle:hover .fa {
  opacity: 1;
}

.ip-email-config-form {
  margin-bottom: 20px;
}
.ip-email-config-form .ip-doc-actions {
  margin-top: 12px;
}

/* -----------------------------------------------------------------------------
   35. Esportazioni
   --------------------------------------------------------------------------
   Le scelte sono poche ma con conseguenze diverse (un CSV non è un archivio
   fiscale): schede con descrizione invece di radio in fila, così si sceglie
   leggendo cosa si ottiene. */

.ip-export-types {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--ip-space-5);
}

/* Il resto dell'aspetto delle schede (bordo, selezione, titolo, nota) sta
   nel blocco "card tipo" più sotto. Qui c'era un primo strato che dava al
   corpo interno padding, raggio e fondo propri sulla scheda selezionata: con
   la card che a sua volta ha bordo e fondo, la selezione si vedeva come un
   riquadro dentro il riquadro. */

/* Formato non disponibile per il tipo scelto: resta visibile ma spento, così
   si capisce che esiste e perché ora non si può usare. */

.ip-export-shortcuts .ip-doc-label { margin: 0 4px 0 0; }

/* -----------------------------------------------------------------------------
   36. Utile e imposte
   -------------------------------------------------------------------------- */

/* Prospetto scalare — la forma del conto economico che questa pagina riassume:
   ricavi in alto, le sottrazioni in discesa, il netto in fondo. Prima erano
   quattro riquadri affiancati, che davano lo stesso peso agli addendi (ricavi,
   costi) e ai risultati (utile, imposte) e non mostravano il legame fra le
   cifre — che e' esattamente cio' che la pagina deve dire. Gli operatori
   stanno in colonna propria: codificano l'aritmetica, non decorano. */
.ip-prospetto {
  width: 100%;
  border-collapse: collapse;
}
.ip-prospetto th,
.ip-prospetto td {
  padding: 9px 0;
  vertical-align: baseline;
  border: 0;
}
.ip-prospetto__op {
  width: 26px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-lg);
  text-align: center;
  user-select: none;
}
.ip-prospetto__label {
  text-align: left;
  font-size: var(--ip-text-md);
  font-weight: 500;
  color: var(--ip-text);
}
/* Il dato di secondo piano (incassato, deducibile) resta agganciato alla sua
   riga invece di occupare un riquadro proprio. */
.ip-prospetto__note {
  display: block;
  margin-top: 2px;
  font-size: var(--ip-text-sm);
  font-weight: 400;
  color: var(--ip-text-muted);
}
.ip-prospetto__amount {
  width: 190px;
  text-align: right;
  font-size: var(--ip-text-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--ip-text);
}
.ip-prospetto__amount.is-expense { color: var(--ip-danger-text); }
.ip-prospetto__amount.is-tax     { color: var(--ip-warning-text); }
.ip-prospetto__amount.is-net     { color: var(--ip-success-text); }

/* Composizione di una riga: rientra, si alleggerisce e stringe il passo, cosi'
   si legge come dettaglio del totale sopra e non come un altro addendo del
   prospetto. La freccia nella colonna operatori dice la stessa cosa in un
   segno. Il secondo livello (le due addizionali dentro il loro totale) rientra
   ancora, senza freccia: la gerarchia la porta gia' il margine. */
.ip-prospetto tr.is-detail > * {
  padding-top: 3px;
  padding-bottom: 3px;
}
.ip-prospetto tr.is-detail .ip-prospetto__op {
  /* Il segno deve reggere il suo compito — dire che la riga appartiene al
     totale sopra — quindi resta quieto ma leggibile: sul bordo forte era
     sotto 2:1 su bianco e spariva. */
  color: var(--ip-neutral);
  font-size: var(--ip-text-sm);
}
.ip-prospetto tr.is-detail .ip-prospetto__label {
  padding-left: 10px;
  font-size: var(--ip-text-sm);
  font-weight: 400;
  color: var(--ip-text-muted);
}
.ip-prospetto tr.is-detail .ip-prospetto__amount {
  font-size: var(--ip-text-sm);
  font-weight: 500;
  color: var(--ip-text-muted);
}
.ip-prospetto tr.is-detail.is-nested .ip-prospetto__label { padding-left: 26px; }
/* Fiato prima e dopo il blocco di dettaglio, per staccarlo dagli operandi. */
.ip-prospetto tr.is-detail:first-of-type > * { padding-top: 6px; }
.ip-prospetto tr.is-detail + tr:not(.is-detail) > * { padding-top: 12px; }

/* Filo singolo sopra il subtotale, filo doppio sopra il risultato: e' la
   notazione dei prospetti contabili, e dice quali righe sono operandi e quali
   conclusioni senza bisogno di ripeterlo a parole. */
.ip-prospetto tr.is-subtotal > * { border-top: 1px solid var(--ip-border); }
.ip-prospetto tr.is-result > *   { border-top: 3px double var(--ip-border-strong); }
.ip-prospetto tr.is-result .ip-prospetto__label { font-weight: 600; }
/* Fraunces per la cifra risolutiva: e' la convenzione gia' in uso per le cifre
   KPI e per l'Indice, il serif del registro contabile. */
.ip-prospetto tr.is-result .ip-prospetto__amount {
  font-family: var(--ip-display);
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* Barra di ripartizione: dove finiscono i ricavi dell'anno. Le tre quote
   sommano per costruzione (costi + imposte + netto = ricavi), quindi la barra
   e' una divisione onesta e non un'approssimazione. E' l'unico elemento
   colorato in grande della pagina: risponde a colpo d'occhio alla domanda che
   la pagina riceve davvero, "quanto di quello che ho fatturato e' mio". */
.ip-split { margin-top: 22px; }
.ip-split .ip-doc-label { margin-bottom: 8px; }
.ip-split__bar {
  display: flex;
  height: 10px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--ip-border-soft);
}
.ip-split__seg { display: block; height: 100%; }
/* Filo di carta fra i segmenti: due tinte contigue altrimenti si toccano e la
   divisione si perde. Inset, per non alterare le larghezze in percentuale. */
.ip-split__seg + .ip-split__seg { box-shadow: inset 1px 0 0 var(--ip-bg-card); }
/* Vermiglio = uscito, ambra = accantonato, verde postale = tuo. Stessa
   semantica dei colori di stato dell'app; l'ambra e' la versione scurita
   (--ip-warning-text), perché l'arancione pieno accanto al vermiglio e al
   verde spenti griderebbe. */
.ip-split__seg.is-costi    { background: var(--ip-danger); }
.ip-split__seg.is-imposte  { background: var(--ip-warning-text); }
.ip-split__seg.is-netto    { background: var(--ip-success); }

.ip-split__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-split__legend li { display: flex; align-items: center; gap: var(--ip-space-3); }
.ip-split__legend b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}
.ip-split__key {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.ip-split__key.is-costi   { background: var(--ip-danger); }
.ip-split__key.is-imposte { background: var(--ip-warning-text); }
.ip-split__key.is-netto   { background: var(--ip-success); }

/* Le stesse quattro tinte dei timbri di stato delle attivita', perché la
   barra sta sopra una tabella che quei timbri li mostra: se il segmento
   "in corso" fosse blu e il badge ambra, la pagina si contraddirebbe da sola.
   Grigio = fermo, ambra = in movimento, inchiostro = concluso, verde = incassato
   (il verde postale della cartoleria, che nell'app vuol dire sempre denaro). */
.ip-split__seg.is-not-started, .ip-split__key.is-not-started { background: var(--ip-neutral); }
.ip-split__seg.is-in-progress, .ip-split__key.is-in-progress { background: var(--ip-warning-text); }
.ip-split__seg.is-complete,    .ip-split__key.is-complete    { background: var(--ip-primary); }
.ip-split__seg.is-invoiced,    .ip-split__key.is-invoiced    { background: var(--ip-success); }

/* Intestazione della barra: etichetta a sinistra, avanzamento a destra. */
.ip-split__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ip-space-6);
  margin-bottom: 8px;
}
.ip-split__head .ip-doc-label { margin: 0; }
.ip-split__progress {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
/* Nel riepilogo progetto la legenda porta conteggi, non percentuali: "3
   attivita' in corso" e' l'informazione che si cerca, non "il 37,5%". */
.ip-project-split { margin: 4px 0 20px; }

.ip-split__loss {
  display: flex;
  align-items: flex-start;
  gap: var(--ip-space-4);
  margin: 20px 0 0;
  padding: 10px 12px;
  border-radius: var(--ip-radius);
  background: var(--ip-danger-bg);
  color: var(--ip-danger-text);
  font-size: var(--ip-text-base);
}

@media (max-width: 640px) {
  .ip-prospetto__amount { width: 130px; font-size: var(--ip-text-md); }
  .ip-prospetto tr.is-result .ip-prospetto__amount { font-size: var(--ip-text-3xl); }
}

/* Andamento mensile come registro a barre: ricavi in verde postale (il colore
   del denaro), costi in vermiglio (il colore del timbro), importi in inchiostro
   sopra la barra. La scala e' unica per l'anno (il massimo del prospetto):
   due importi uguali hanno la stessa lunghezza, e il confronto fra le colonne
   e' diretto. Il mese resta un dato di riga: la posizione delle righe codifica
   la sequenza, non c'e' bisogno di numerare nulla. */
/* .ip-items-table (da cui eredita) usa border-collapse: collapse per il
   look "righe cucite" dei documenti — ma con collapse i browser ignorano il
   border-radius sulle celle, quindi l'arrotondamento dell'ultima riga (vedi
   sezione tabelle più sopra: ".ip-doc-section ... tfoot > tr:last-child")
   non veniva disegnato e la riga Totale sforava l'angolo della card in
   hover. Qui basta separare le celle senza spaziatura visibile: stesso
   effetto del collapse, ma il border-radius torna a funzionare. */
.ip-finance-month { border-collapse: separate; border-spacing: 0; }
/* Niente min-width: la tabella vive nella colonna principale di .ip-preview,
   che accanto al pannello da 300px scende a ~450px sui laptop. Con 640px di
   minimo Spese e Saldo finivano dietro lo scroll orizzontale; quattro colonne
   di importi ci stanno, purché il padding laterale resti contenuto. Su
   schermi più stretti .table-responsive fa comunque da rete. */
.ip-finance-month td { padding: 9px 12px; vertical-align: middle; }
.ip-finance-month__name {
  white-space: nowrap;
  font-weight: 500;
}
.ip-finance-month th.ip-finance-month__col,
.ip-finance-month th.ip-items-table__total { text-align: right; }
.ip-finance-month__cell {
  display: grid;
  gap: var(--ip-space-2);
  /* 100 e non 120: il binario della barra resta leggibile e le due colonne
     di importi rientrano nella colonna di .ip-preview anche sui laptop. */
  min-width: 100px;
  position: relative;
}
/* Separatore visivo tra le colonne Ricavi e Costi per evitare che le barre
   sembrino attraversare il confine (WCAG 1.4.11 - confini chiari). */
.ip-finance-month__col.is-income + .ip-finance-month__col.is-expense .ip-finance-month__cell::before {
  content: "";
  position: absolute;
  left: -6px;
  top: 50%;
  transform: translateY(-50%);
  width: 1px;
  height: 80%;
  background: var(--ip-border);
}
.ip-finance-month__amount {
  display: block;
  text-align: right;
  font-size: var(--ip-text-base);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text);
}
/* La barra cresce da destra, ancorata allo stesso bordo dell'importo che
   descrive: crescendo da sinistra finiva sotto la colonna precedente e a
   colpo d'occhio non si capiva a quale delle due cifre appartenesse. */
.ip-finance-month__bar {
  display: flex;
  justify-content: flex-end;
  height: 7px;
  border-radius: 999px;
  background: var(--ip-border-soft);
  overflow: hidden;
}
.ip-finance-month__bar > span {
  display: block;
  height: 100%;
  border-radius: inherit;
}
/* Il fondo del binario si vede solo dove c'è una barra: senza valore la
   cella non ne stampa nessuno. */
.ip-finance-month__bar { background: var(--ip-border-soft); }
.ip-finance-month__bar.is-income > span { background: var(--ip-success); }
.ip-finance-month__bar.is-expense > span { background: var(--ip-danger); }
.ip-finance-month__saldo { white-space: nowrap; }

/* Mese senza movimenti: resta in elenco, perché i vuoti fanno parte della
   forma dell'anno, ma arretra. Prima ogni mese fermo stampava due binari grigi
   vuoti e i mesi con lavoro finivano in mezzo al rumore. */
.ip-finance-month tr.is-quiet .ip-finance-month__amount,
.ip-finance-month tr.is-quiet .ip-finance-month__name,
.ip-finance-month tr.is-quiet .ip-finance-month__saldo {
  color: var(--ip-text-muted);
  font-weight: 500;
}

/* Totale d'anno: lega le dodici righe alle cifre del prospetto, che prima
   andavano sommate a mente per verificare che parlassero dello stesso anno.
   Gli importi stanno sulla cella e non dentro .ip-finance-month__amount: quella
   classe porta display:block, che su un <td> lo toglie dal layout tabellare e
   sfascia la riga. */
.ip-finance-month__foot > * {
  padding: 11px 12px;
  border-top: 2px solid var(--ip-border-strong);
  font-weight: 600;
}
.ip-finance-month__foot th { text-align: left; }
.ip-finance-month__foot td {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Quota per categoria: solo la cifra. La barra qui non si guadagnava il posto —
   le categorie sono poche e gia' ordinate per importo, quindi le percentuali si
   confrontano leggendole; nei dodici mesi invece la barra serve, perché li' si
   scorre. In più la colonna con la barra sfondava la tabella. */
/* .ip-items-table impone min-width:760px, soglia giusta per la tabella delle
   righe di un documento (descrizione, quantita', prezzo, IVA, totale, azioni)
   ma non per quattro colonne corte: ereditandola, questa tabella sfondava la
   card appena la colonna centrale scendeva sotto i 760px. */
.ip-finance-cat { min-width: 0; }
.ip-finance-cat__share {
  width: 110px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
}
.ip-finance-cat__hint { margin-top: 12px; }

/* Campo numerico con unità di misura appiccicata: l'unita' e' parte del dato
   (un'aliquota e' 24,48 *per cento*, un minimale e' 18.955 *euro*), e tenerla
   nell'etichetta costringeva a rileggerla per capire cosa si sta digitando. */
/* -----------------------------------------------------------------------------
   Campo con segno: unita' (%, €) o icona dentro il campo
   -----------------------------------------------------------------------------
   Regola unica per i campi "con appendice", che l'app scriveva in due modi:
   .input-group di Bootstrap (riquadro attaccato fuori dal campo) e questo
   segno interno. Due aspetti per la stessa cosa — l'aliquota della scheda
   azienda e quella delle imposte si compilavano allo stesso modo e si
   vedevano diverse.

   Il criterio ora e': segno che si legge (unita', icona) sta dentro il campo,
   perché fa parte del valore; comando che si preme (pulsante Genera, Copia,
   Filtra) resta un .input-group attaccato, perché e' un elemento a se'. */
.ip-input-suffix { position: relative; }
.ip-input-suffix .form-control { padding-right: 30px; }
/* Gli importi restano allineati a destra come dentro .input-group: il
   padding lascia il posto al segno, quindi le cifre non ci finiscono sotto. */
.ip-input-suffix .form-control.amount { text-align: right; }
.ip-input-suffix__mark {
  position: absolute;
  top: 50%;
  right: 10px;
  transform: translateY(-50%);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  line-height: 1;
  pointer-events: none;
}
.ip-input-suffix__mark .fa { font-size: var(--ip-text-base); }
/* Il campo attivo porta con se' il proprio segno. */
.ip-input-suffix:focus-within .ip-input-suffix__mark { color: var(--ip-text); }
/* I numeri degli scaglioni si confrontano in colonna: niente min-width da
   tabella-documento, che qui lascerebbe due campi persi in mezzo al vuoto. */
/* === Parametri fiscali: tabella — impronta aliquote e anno selezionato ===
   Le colonne di soli numeri rendevano difficile cogliere le differenze tra
   anni; la barra a segmenti sovrapposti e' la "firma" di ogni anno (la
   proporzione tra IRES, IRAP, addizionali e forfettario si vede prima di
   leggere le cifre). La riga dell'anno attivo nel filtro globale e' marcata
   perché la tabella alimenta la stima di quell'anno. */
.ip-fyear-profile-col { width: 200px; }

/* IRPEF: mini-scala a gradini per riga (stessa "firma" del form), con
   l'aliquota marginale massima come numero di riferimento. */
.ip-fyear-irpef-col { width: 140px; }
.ip-fyear-irpef { display: flex; align-items: flex-end; gap: 9px; }
.ip-fyear-irpef__ladder {
  display: flex;
  align-items: flex-end;
  gap: var(--ip-space-1);
  height: 32px;
}
.ip-fyear-irpef__bar {
  width: 7px;
  min-width: 7px;
  border-radius: 2px 2px 0 0;
  background: var(--ip-primary);
  transition: height var(--ip-transition), opacity var(--ip-transition);
}
.ip-fyear-irpef__bar:hover { filter: brightness(1.1); }
.ip-fyear-irpef__meta { display: flex; flex-direction: column; line-height: 1.15; }
.ip-fyear-irpef__top {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-base);
  font-weight: 600;
  color: var(--ip-text-strong);
}
.ip-fyear-irpef__count { font-size: var(--ip-text-xs); color: var(--ip-text-muted); }

.ip-fyear-bar {
  display: flex;
  align-items: center;
  gap: var(--ip-space-1);
  height: 14px;
  min-width: 120px;
}
.ip-fyear-seg {
  height: 100%;
  border-radius: 2px;
  min-width: 2px;
  transition: width var(--ip-transition);
}
.ip-fyear-seg:hover { filter: brightness(1.08); }

/* Sotto i 576px la tabella collassa in card (.ip-table-card-collapse):
   ogni <td> normale diventa una riga "etichetta a sinistra / valore a
   destra". La scala IRPEF e la barra profilo pero' hanno bisogno della
   larghezza intera per restare leggibili — affiancate all'etichetta
   finirebbero schiacciate in meta' card. Qui le impiliamo (etichetta
   sopra, "firma" visiva sotto) invece di lasciarle nel pattern generico. */
@media (max-width: 575.98px) {
  .ip-table-card table.ip-table-card-collapse tbody td.ip-fyear-irpef-col,
  .ip-table-card table.ip-table-card-collapse tbody td.ip-fyear-profile-col {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ip-space-3);
  }
  .ip-fyear-profile-col .ip-fyear-bar { width: 100%; }
}

.ip-row-current { background: var(--ip-primary-soft); }
.ip-row-current:hover { background: var(--ip-primary-soft); }
/* Misure dall'ossatura comune dei badge (in fondo al foglio): qui restano solo
   colore e posizione. Il raggio era 999px, quindi "selezionato" era una pillola
   accanto a timbri ad angoli 4px. */
.ip-row-current-badge {
  margin-left: 6px;
  color: var(--ip-on-primary);
  background: var(--ip-primary);
}

.ip-brackets { min-width: 0; }
.ip-brackets td { padding: 6px 12px; vertical-align: middle; }
.ip-brackets .form-control { text-align: right; }

/* Cifre tassonomiche: allineamento tabellare su limiti e aliquote, così la
   colonna dei numeri resta leggibile mentre si digita. */
.ip-brackets input,
.ip-input-suffix .form-control {
  font-variant-numeric: tabular-nums;
}

/* Scala progressiva IRPEF — la firma della schermata. Ogni scaglione e' una
   barra; l'altezza e' proporzionale all'aliquota, l'opacita' pure, così la
   natura "a gradini" dell'imposta si vede prima di leggere i numeri. Ridisegnata
   live da ip-fiscal-form.js mentre si compila (fallback: tabella comunque presente). */
.ip-brackets-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: var(--ip-space-9);
  align-items: start;
}
.ip-ladder {
  position: sticky;
  top: calc(var(--ip-navbar-height) + 64px);
  display: flex;
  align-items: flex-end;
  gap: var(--ip-space-3);
  height: 180px;
  padding: 12px 10px 0;
  background: var(--ip-bg);
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius);
}
.ip-ladder__col {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  min-width: 0;
}
.ip-ladder__bar {
  width: 100%;
  max-width: 30px;
  background: var(--ip-violet);
  border-radius: 3px 3px 0 0;
  transition: height var(--ip-transition), opacity var(--ip-transition);
}
.ip-ladder__rate {
  margin-top: 5px;
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 500;
  color: var(--ip-text);
  white-space: nowrap;
}
.ip-ladder__limit {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-2xs);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
@media (max-width: 767.98px) {
  .ip-brackets-layout { grid-template-columns: minmax(0, 1fr); }
  .ip-ladder { position: static; height: 140px; }
}

/* Didascalie riservate agli screen reader (la tabella del prospetto). */
.ip-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Verifica tracciato XML: gli errori dello schema sono tecnici ma vanno letti
   uno per uno, quindi elenco spaziato e non paragrafo unico. */
.ip-xml-errors {
  margin: 0;
  padding-left: 18px;
  font-size: var(--ip-text-base);
  line-height: 1.6;
}
.ip-xml-errors li { margin-bottom: 6px; }

.ip-xml-preview {
  max-height: 420px;
  margin: 0;
  padding: 14px;
  overflow: auto;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  white-space: pre;
}

/* Blocco XML: una barra sola con i comandi che servono su un tracciato lungo
   (copia tutto, vai a capo). Bordo e angoli stanno sul blocco, così barra e
   codice si leggono come un oggetto solo invece di due rettangoli accostati. */
.ip-xml-block {
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius-sm);
  overflow: hidden;
}
.ip-xml-block__tools {
  display: flex;
  justify-content: flex-end;
  gap: var(--ip-space-3);
  padding: 6px 8px;
  border-bottom: 1px solid var(--ip-border-soft);
  background: var(--ip-bg-card);
}
.ip-xml-block .ip-xml-preview,
.ip-xml-block .ip-xml-code {
  max-height: 480px;
  border: 0;
  border-radius: 0;
}
/* Su un tracciato la riga del namespace è più larga dello schermo: senza
   questo si scorre in orizzontale per leggerne un pezzo alla volta. */
.ip-xml-block.is-wrapped .ip-xml-preview,
.ip-xml-block.is-wrapped .ip-xml-code {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.ip-xml-tool-btn {
  padding: 3px 9px;
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius-sm);
  background: var(--ip-bg-card);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: var(--ip-transition);
}
.ip-xml-tool-btn:hover { color: var(--ip-text); border-color: var(--ip-border); }
.ip-xml-tool-btn.is-done { color: var(--ip-success); border-color: var(--ip-success); }
.ip-xml-tool-btn[aria-pressed="true"] { color: var(--ip-primary); border-color: var(--ip-primary); }

/* -----------------------------------------------------------------------------
   37. Tab (.ip-tabs) — un solo componente per tutte le schermate a schede
   --------------------------------------------------------------------------
   Nell'app convivevano due grafiche per la stessa funzione: la barra segmentata
   delle impostazioni e le linguette Bootstrap (.nav-tabs) della scheda cliente.
   Vince la barra segmentata, coerente con i filtri delle liste; il markup lo
   emette layout/tabs.php, quindi le due schermate non possono più divergere.
   Niente .nav-tabs sulla lista: lo shim legacy (sezione 12) avrebbe specificità
   maggiore e sovrascriverebbe bordo/raggio/padding/active della barra. */

/* La barra sta fuori da #content (fra headerbar e pannelli), quindi non ne
   eredita il padding: con margine orizzontale 0 correva da bordo a bordo
   mentre titolo e card sotto partivano da 20px. Il margine ripete l'inset di
   #headerbar / #content, compreso il valore ridotto sotto i 576px. Il margine
   superiore tiene la barra staccata dall'headerbar sticky: senza, la toccava
   con il bordo inferiore. */
.ip-tabs {
  /* Sticky sotto la headerbar: nelle schermate a schede lunghe (impostazioni,
     scheda cliente) senza questo, appena si scorreva sotto il primo pannello
     la barra spariva e cambiare scheda voleva dire risalire in cima.
     top:var(--ip-navbar-height) come la headerbar: quando entrambe sono
     agganciate, la barra (z-index 1015) scivola SOTTO la headerbar (1020) e
     riappare esattamente sotto il suo bordo inferiore, a qualunque altezza
     sia arrivata la headerbar (wrap su viewport strette inclusi). Il margine
     superiore è lo stacco a riposo: appena la barra si aggancia finisce
     dietro la headerbar, quindi niente contenuto che sbuca dalla fessura. */
  position: sticky;
  top: var(--ip-navbar-height);
  z-index: 1015;
  gap: var(--ip-space-1);
  padding: 4px;
  margin: 10px 20px 18px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card);
  flex-wrap: wrap;
}
.ip-tabs .nav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 7px 14px;
  border: 0;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-tabs .nav-link:hover {
  background: var(--ip-hover);
  color: var(--ip-text);
}
.ip-tabs .nav-link.active {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
}
/* Focus da tastiera: l'anello ci sarebbe gia' (Bootstrap lo mette su
   .nav-link:focus-visible) ma e' il blu di default del framework, l'unico
   punto dell'app in cui comparirebbe. Qui usa il token dell'app. */
.ip-tabs .nav-link:focus-visible {
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
  color: var(--ip-text);
}
.ip-tabs .nav-link.active:focus-visible { color: var(--ip-on-primary); }

/* Conteggio dentro la scheda (preventivi/fatture/pagamenti del cliente).
   Prima era un .badge.text-bg-secondary: sulla pastiglia attiva, scura, il
   badge grigio con testo bianco spariva. Qui il contatore prende il colore
   dallo stato della scheda che lo ospita. */
.ip-tab-badge {
  justify-content: center;
  min-width: 20px;
  /* Fondo --ip-bg e non --ip-primary-soft: il testo muted su quella tinta
     dava 4.27:1, sotto la soglia, per un conteggio che si legge di sfuggita. */
  background: var(--ip-bg);
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
}
.ip-tabs .nav-link.active .ip-tab-badge {
  /* Sul fondo --ip-primary il testo passa al token "on-primary" invece del
     bianco fisso: in tema scuro il primary è chiaro e il bianco spariva. */
  background: color-mix(in srgb, var(--ip-on-primary) 22%, transparent);
  color: var(--ip-on-primary);
}

/* Il pannello attivo riceve il focus quando si arriva con la tastiera: senza
   outline proprio, perché il contorno lo da' gia' la scheda selezionata. */
.tab-content > .tab-pane:focus { outline: 0; }

@media (max-width: 575.98px) {
  .ip-tabs { padding: 3px; margin: 14px 16px 14px; }
  .ip-tabs .nav-link { padding: 6px 10px; font-size: var(--ip-text-sm); }
}

/* Variante inline della barra schede: usata nell'anteprima della fattura
   elettronica, dove le "schede" sono i fogli di stile e stanno su una riga
   insieme all'etichetta e ai bottoni. */
.ip-tabs--inline {
  display: inline-flex;
  /* Dentro .ip-xml-preview-bar (flex) la barra e' un item: senza questi due
     vincoli si allargava a tutta la riga e le due pastiglie si stiravano a
     meta' schermo ciascuna. */
  flex: 0 0 auto;
  width: auto;
  margin: 0;
  vertical-align: middle;
}
.ip-tabs--inline .nav-link { flex: 0 0 auto; white-space: nowrap; }
.ip-tabs .nav-link.is-missing {
  color: var(--ip-warning-text);
}
.ip-tabs .nav-link.is-missing .fa { font-size: var(--ip-text-xs); }

/* -----------------------------------------------------------------------------
   37b. Impostazioni di sistema
   --------------------------------------------------------------------------
   Le sezioni erano incolonnate su 8/12 con offset: su monitor larghi metà
   schermo restava vuota mentre i campi si stringevano. Qui il pannello usa la
   larghezza disponibile. */

/* Il pannello si estende su tutta la larghezza del contenuto, come le altre
   pagine "card dock". Le card qui sono .ip-table-card: il margine del dock
   (18px 0) raddoppierebbe i vuoti, quindi lo riportiamo a quello del pannello. */
/* .ip-form-pane e' lo stesso guscio fuori dalle impostazioni. Le schermate di
   modifica (serie di numerazione, conti, aliquote, categorie, campi
   personalizzati…) incolonnavano la card in "col-lg-8 offset-lg-2" o
   "col-md-6 offset-md-3": su un monitor largo restavano centinaia di pixel
   vuoti ai due lati mentre i campi dentro si stringevano. E' lo stesso
   difetto gia' corretto qui sopra per le impostazioni, rimasto pero' in
   diciannove altre view perché il rimedio era stato scritto a mano su una
   sola schermata invece che dato un nome.

   Il guscio da solo non basta: un form di due campi steso su tutta la
   larghezza produce righe di input lunghissime, peggio del riquadro
   centrato. Chi lo usa mette i campi in .ip-doc-grid, che li tiene a un
   terzo di riga. */
.ip-form-pane,
.ip-settings-pane {
  width: 100%;
}
.ip-form-pane .card,
.ip-settings-pane .card,
.ip-settings-pane > .ip-doc-section { margin: 0 0 16px; }
.ip-form-pane .card:last-child,
.ip-settings-pane .card:last-child,
.ip-settings-pane > .ip-doc-section:last-child { margin-bottom: 0; }

/* Pannello Sicurezza sotto il form del profilo utente (users/form): sta
   nello stesso #content del form, quindi ne eredita i margini; qui solo lo
   stacco dall'ultima scheda, uguale a quello fra le schede del form. */
.ip-user-security { margin-top: 16px; }
.ip-user-security__body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.ip-user-security__status { display: flex; flex-wrap: wrap; gap: 8px; }
.ip-user-security__status .ip-status-pill { gap: 6px; }

/* I pannelli impostazioni impaginano i campi sulla griglia Bootstrap
   (.row > .col), non su .ip-doc-grid: il campo porta quindi con se' il suo
   margine, che la griglia canonica azzera — e l'ultima riga di ogni card
   finiva con il doppio spazio sotto (16 di margine + 18 di padding del
   body), con le card "pesanti" in fondo rispetto alla testa. Il reset
   replica quello di .ip-doc-grid limitato all'ultimo campo di ogni colonna:
   le righe della stessa colonna restano distanziate perche' a farlo e' il
   campo precedente, non l'ultimo. */
.ip-settings-pane .ip-doc-section__body .row > [class*="col-"] > .ip-doc-field:last-child {
  margin-bottom: 0;
}

/* Separatore fra invio e credenziali SMTP: l'<hr> di Bootstrap porta 1rem
   su entrambi i lati che, sommato ai margini dei campi adiacenti, faceva
   buchi irregolari sopra e sotto la riga. Stesso respiro del resto della
   pagina: 16 sopra, 16 sotto, bordo morbido di sistema. */
.ip-settings-pane .ip-doc-section__body hr {
  margin: 16px 0;
  border-color: var(--ip-border-soft);
  opacity: 1;
}

/* Campo di ricerca delle impostazioni (slot filters della headerbar): icona
   incastonata e larghezza contenuta, così non ruba spazio al pulsante Salva
   e resta leggibile anche affiancato alle azioni. */
.ip-settings-search { position: relative; }
.ip-settings-search__icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  pointer-events: none;
}
.ip-settings-search .form-control {
  width: 230px;
  max-width: 46vw;
  padding-left: 28px;
}

/* Nessuna corrispondenza dal filtro delle impostazioni: senza un messaggio la
   pagina sembra vuota e non si capisce se il filtro sta lavorando o se i campi
   sono spariti. */
.ip-doc-empty {
  margin: 0 0 16px;
  padding: 24px;
  border: 1px dashed var(--ip-border-strong);
  border-radius: var(--ip-radius);
  color: var(--ip-text-muted);
  text-align: center;
  font-size: var(--ip-text-base);
}
.ip-doc-empty[hidden] { display: none; }

/* Riga introduttiva delle sezioni impostazioni: dice in una riga cosa si
   configura nella sezione. Sta fra la barra schede e le card, con lo stesso
   inset orizzontale delle card. */
.ip-settings-intro {
  margin: 0 0 16px;
  font-size: var(--ip-text-base);
  line-height: 1.5;
  color: var(--ip-text-muted);
}

/* Sottotitolo dei gruppi di campi condizionali (es. canali SdI). */
.ip-settings-subtitle {
  margin: 20px 0 12px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--ip-border, #e3e6ea);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-settings-subtitle:first-child { margin-top: 0; }

/* ==========================================================================
   Ricerca globale (topbar)
   Il blocco brand a sinistra occupa esattamente la colonna della sidebar
   (236px), e il gap del contenitore sale a 20px su desktop: lo stesso
   padding di #content. Risultato: il campo di ricerca comincia esattamente
   dove cominciano le card (bordo destro della sidebar + padding del
   contenuto), allineato con la colonna che riempie. Sotto lg la sidebar e'
   off-canvas: il vincolo cade e la ricerca si prende lo spazio che resta.
   ========================================================================== */
@media (min-width: 992px) {
  .ip-topbar-inner { padding-left: 0; gap: var(--ip-space-8); }
  .ip-topbar-left {
    flex: 0 0 var(--ip-sidebar-width, 236px);
    width: var(--ip-sidebar-width, 236px);
    /* Il padding di 16px che prima era del contenitore ora sta qui: il
       blocco occupa 0..236 (il bordo della sidebar), con il brand interno
       che comincia a 16px come il menu a fianco. */
    padding-left: 16px;
  }
}

.ip-global-search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  max-width: 520px;
  min-width: 0;
}

.ip-global-search__icon {
  position: absolute;
  left: 12px;
  font-size: var(--ip-text-base);
  color: var(--ip-text-muted);
  pointer-events: none;
}

.ip-global-search__input {
  width: 100%;
  height: 36px;
  padding: 0 38px 0 34px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card, #fff);
  color: var(--ip-text);
  font-size: var(--ip-text-base);
  /* Ombra di riposo leggera: la pill bianca "galleggia" sulla topbar scura e
     si legge subito come il comando primario di navigazione. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
  transition: border-color var(--ip-transition), box-shadow var(--ip-transition);
}
.ip-global-search__input::placeholder { color: var(--ip-text-muted); }
.ip-global-search__input:focus {
  outline: none;
  /* Focus coerente col resto dell'app: bordo primary + anello, non il
     vermiglio di default di un token --ip-accent mai definito. */
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus), 0 3px 10px rgba(0, 0, 0, 0.2);
}
/* Focus catturato sul wrapper (click sull'icona/risultati): solleva la pill
   anche quando il fuoco finisce sull'input figlio, non solo sull'input stesso. */
.ip-global-search:focus-within .ip-global-search__input {
  border-color: var(--ip-primary);
  box-shadow: var(--ip-shadow-focus), 0 3px 10px rgba(0, 0, 0, 0.2);
}

/* Scorciatoia (Ctrl K, che su Mac diventa ⌘ K): indicata sul campo perché
   altrimenti resterebbe una funzione invisibile. Una sola forma, non due:
   con "Ctrl + K / Cmd + K" l'etichetta era larga 103px, più della metà del
   campo su telefono, e correva sotto il segnaposto. Sparisce quando il campo
   e' in uso. */
.ip-global-search__kbd {
  position: absolute;
  right: 10px;
  min-width: 18px;
  padding: 1px 5px;
  border: 1px solid var(--ip-border);
  border-radius: 4px;
  background: var(--ip-bg, #f6f6f7);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
  line-height: 16px;
  text-align: center;
  pointer-events: none;
}
.ip-global-search__input:focus ~ .ip-global-search__kbd,
.ip-global-search.is-filled .ip-global-search__kbd { display: none; }

.ip-global-search__panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: var(--ip-z-toast);
  max-height: min(70vh, 520px);
  overflow-y: auto;
  padding: 6px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card, #fff);
  box-shadow: var(--ip-shadow-lg);
}
.ip-global-search__panel[hidden] { display: none; }

.ip-global-search__group-label {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 8px 10px 4px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.ip-global-search__item {
  display: flex;
  align-items: baseline;
  gap: var(--ip-space-5);
  padding: 7px 10px;
  border-radius: var(--ip-radius);
  color: var(--ip-text);
  text-decoration: none;
}
.ip-global-search__item:hover,
.ip-global-search__item.is-active {
  background: var(--ip-hover, #f4f4f5);
  color: var(--ip-text);
  text-decoration: none;
}
.ip-global-search__item-main { min-width: 0; flex: 1 1 auto; }
.ip-global-search__item-title {
  display: block;
  font-size: var(--ip-text-base);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ip-global-search__item-sub {
  display: block;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ip-global-search__item-meta {
  flex: 0 0 auto;
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
}

.ip-global-search__empty {
  padding: 20px 12px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  text-align: center;
}

/* Su schermi stretti il campo diventa il solo elemento espandibile: brand e
   comandi di destra restano, l'etichetta del brand no. */
@media (max-width: 767.98px) {
  .ip-global-search { max-width: none; }
  .ip-global-search__kbd { display: none; }
}

/* Sottogruppo dentro .ip-doc-grid: occupa l'intera riga della griglia e separa
   blocchi di campi che si compilano insieme. Serve nei form lunghi (dati
   documento, fattura elettronica) dove un elenco piatto di quindici campi non
   lascia capire quali si riferiscono a cosa. */
.ip-doc-subgroup {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: var(--ip-space-4);
  margin: 4px 0 -4px;
  padding-top: 12px;
  border-top: 1px solid var(--ip-border);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}
/* Il primo sottogruppo apre la sezione: la riga sopra sarebbe doppia
   rispetto al bordo dell'intestazione. */
.ip-doc-grid > .ip-doc-subgroup:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.ip-doc-subgroup__hint {
  font-size: var(--ip-text-sm);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

/* Sezione "Fattura elettronica": logica stile FattureinCloud. I campi
   essenziali (tipo documento, esigibilita' IVA) restano in vista; tutto il
   resto vive in gruppi collassabili "opzionali" che si aprono da soli quando
   contengono gia' dei valori (così in modifica non si nasconde cio' che e'
   stato compilato) e mostrano un contatore dei campi valorizzati. Ogni gruppo
   e' una card annidata dentro la card della sezione. */
.ip-einv-group {
  grid-column: 1 / -1;
  border: 1px solid var(--ip-border-soft, var(--ip-border));
  border-radius: var(--ip-radius, 10px);
  margin: 12px 0 2px;
  overflow: hidden;
  background: var(--ip-bg-card, #fff);
}
.ip-einv-group:first-of-type {
  margin-top: 4px;
}
.ip-einv-group__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  padding: 12px 14px;
  background: var(--ip-bg-card, #fff);
  border: 0;
  color: var(--ip-text);
  font-size: var(--ip-text-base);
  font-weight: 600;
  letter-spacing: -0.01em;
  cursor: pointer;
  text-align: left;
  transition: background .15s ease;
}
.ip-einv-group__toggle:hover {
  background: rgba(128, 128, 128, .08);
}
.ip-einv-group__toggle:focus-visible {
  outline: 2px solid var(--ip-primary, #0d6efd);
  outline-offset: -2px;
}
.ip-einv-group__title {
  flex: 1 1 auto;
}
.ip-einv-group__tag {
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  background: var(--ip-bg, #f6f6f7);
  border: 1px solid var(--ip-border-soft, var(--ip-border));
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}
.ip-einv-group__tag:not(:empty) {
  color: var(--ip-primary, #0d6efd);
  border-color: color-mix(in srgb, var(--ip-primary, #0d6efd) 28%, transparent);
  background: color-mix(in srgb, var(--ip-primary, #0d6efd) 9%, transparent);
}
.ip-einv-group__chevron {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
  border-radius: 50%;
  background: var(--ip-bg, #f6f6f7);
  transition: transform .2s ease, background .15s ease;
}
.ip-einv-group__toggle[aria-expanded="true"] .ip-einv-group__chevron {
  transform: rotate(180deg);
  background: color-mix(in srgb, var(--ip-primary, #0d6efd) 14%, transparent);
  color: var(--ip-primary, #0d6efd);
}
.ip-einv-group__body {
  padding: 4px 14px 14px;
  border-top: 1px solid var(--ip-border-soft, var(--ip-border));
}
/* Dentro il corpo collassato la griglia dei campi respira un po' meno. */
.ip-einv-group__body .ip-doc-grid {
  gap: var(--ip-space-7);
}
.ip-einv-group__body .ip-doc-subgroup:first-child {
  margin-top: 6px;
}

/* Scelta fra poche alternative che cambiano il resto del form (tipologia
   cliente). Una select nasconderebbe le opzioni dietro un clic proprio dove
   serve capire subito le differenze, e ognuna porta una riga di spiegazione. */
.ip-type-choice {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--ip-space-4);
}

.ip-type-choice__option {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "icon label" "icon hint";
  align-items: center;
  gap: 2px 10px;
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card, #fff);
  cursor: pointer;
  transition: border-color var(--ip-transition), background var(--ip-transition);
}
.ip-type-choice__option:hover { border-color: var(--ip-text-muted); }
.ip-type-choice__option.is-active {
  /* Come per la ricerca globale: niente token --ip-accent (mai definito,
     con fallback vermiglio fuori palette): il selezionato usa il primary. */
  border-color: var(--ip-primary);
  background: var(--ip-primary-soft);
}

/* Il radio resta nel DOM per il POST e per la navigazione da tastiera, ma la
   selezione la comunica il riquadro. */
.ip-type-choice__option input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.ip-type-choice__option i {
  grid-area: icon;
  font-size: var(--ip-text-xl);
  color: var(--ip-text-muted);
}
.ip-type-choice__option.is-active i { color: var(--ip-primary); }
.ip-type-choice__label { grid-area: label; font-size: var(--ip-text-base); font-weight: 600; }
.ip-type-choice__hint  { grid-area: hint;  font-size: var(--ip-text-sm); color: var(--ip-text-muted); }

/* Focus da tastiera: senza questo il riquadro selezionato con le frecce non
   darebbe alcun segnale, essendo il radio invisibile. */
.ip-type-choice__option:focus-within {
  outline: 2px solid var(--ip-primary);
  outline-offset: 2px;
}

/* Quando la card "Informazioni personali" (genere, data di nascita) si
   nasconde perché il cliente non è una persona fisica (JS in clients/form.php),
   la card fiscale accanto occupa tutta la riga invece di restare a metà con
   uno spazio vuoto a destra. */
.row:has(> .ip-client-personal-col[hidden]) > .col-12.col-sm-6:not([hidden]) {
  flex: 0 0 100%;
  max-width: 100%;
}

[data-client-type-only] { display: none; }
[data-client-type-only].is-visible { display: block; }
/* Il campo canonico è flex e la sua regola sta più in basso nel foglio:
   a parità di specificità vinceva lei, quindi Cognome (solo persona fisica)
   e Partita IVA (solo azienda/PA) restavano sempre visibili. Qui la
   specificità del componente, con il display corretto. */
.ip-doc-field[data-client-type-only] { display: none; }
.ip-doc-field[data-client-type-only].is-visible { display: flex; }

/* Tipologia cliente in elenco e in scheda: si legge come contesto del nome,
   non come stato, quindi niente pillola colorata. */
.ip-client-type {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-2);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  white-space: nowrap;
}
.ip-client-type i { font-size: var(--ip-text-xs); }

/* Riga secondaria del nome negli elenchi di anagrafiche (tipo cliente,
   partita IVA). Rientrata della larghezza dell'avatar (22px + margine) perché
   parta allineata al testo del nome.
   Colore e corpo stanno qui, non nei figli: clienti e fornitori mettono nella
   stessa riga due cose diverse — il timbro del tipo e la P. IVA — e le due
   liste le mostravano in modo diverso, il tipo grigio a 11px e la P. IVA nera
   a 12px in semibold. Nella stessa posizione lo stesso ruolo: testo
   secondario, più piccolo e spento del nome che sta sopra. Così la riga
   scala uguale in entrambe le tabelle e il nome resta l'unico elemento
   marcato della cella. */
.ip-anagrafica-meta {
  margin-top: 2px;
  padding-left: 30px;
  line-height: 1.2;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
}
.ip-anagrafica-meta .ip-client-type,
.ip-anagrafica-meta .ip-num {
  font-size: inherit;
  font-weight: 400;
  color: inherit;
}
/* Conteggio in una colonna numerica (es. "Spese" nei fornitori): è un numero
   da leggere, non una cifra da confrontare. Resta tabulare e allineato a
   destra come gli importi (porta .amount accanto), ma si spegne: due numeri
   affiancati dello stesso nero — un conteggio e un totale in euro — si
   leggono come se fossero la stessa grandezza. */
/* Il selettore non è "solo" .ip-col-count: Bootstrap dipinge il colore del
   testo direttamente sulla cella con .table > :not(caption) > * > *, che ha
   specificità (0,1,2) e batte una classe singola. Con .table davanti la
   regola vince, e resta confinata alle tabelle. */
.table .ip-col-count {
  color: var(--ip-text-muted);
  font-weight: 400;
}

/* La tipologia accanto al nome nel titolo di scheda: dimensione e peso del
   testo secondario, non dell'intestazione che accompagna. */
.ip-client-type-title { margin-left: 10px; font-weight: 400; vertical-align: middle; }

/* =============================================================================
   Skeleton di caricamento (UI)
   -----------------------------------------------------------------------------
   Placeholder shimmer usati in tre punti:
   1. #ip-page-skeleton — overlay sul contenuto mentre il browser carica la
      pagina successiva (navigazione da link/form, vedi js/ip-skeleton.js).
   2. .ip-skeleton-table — righe placeholder nelle tabelle durante la ricerca
      live (filtri AJAX dei moduli elenco).
   3. Pannello della ricerca globale durante la chiamata.
   I blocchi usano i token --ip-skeleton-* così seguono tema e riduzione
   del movimento dell'utente.
   ========================================================================== */

/* Blocco base: ogni barra è uno span .ip-skeleton con altezza di riga testo.
   La larghezza si varia con la custom property --w (default 100%). */
.ip-skeleton {
  display: block;
  width: var(--w, 100%);
  height: 14px;
  border-radius: var(--ip-radius-sm);
  background: linear-gradient(90deg,
    var(--ip-skeleton-bg) 25%,
    var(--ip-skeleton-shine) 37%,
    var(--ip-skeleton-bg) 63%);
  background-size: 400% 100%;
  animation: ip-skeleton-shimmer 1.4s ease infinite;
}
.ip-skeleton--circle { border-radius: 50%; }
.ip-skeleton--sm { height: 10px; }
.ip-skeleton--lg { height: 20px; }

@keyframes ip-skeleton-shimmer {
  0%   { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* Card skeleton: contorno uguale alle .ip-table-card/.modern-card della app. */
.ip-skeleton-card {
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  overflow: hidden;
}
.ip-skeleton-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-7);
  /* Stessa testata della card vera: se cambia il rientro, al momento in cui
     il contenuto arriva i titoli di pagina si spostano di lato. */
  padding: var(--ip-card-head-y) var(--ip-card-pad-x);
  border-bottom: 1px solid var(--ip-border);
}
.ip-skeleton-card__body { padding: 4px 0; }

/* Tabella skeleton: intestazione più righe, stessa griglia di una tabella. */
.ip-skeleton-table__head,
.ip-skeleton-table__row {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--ip-space-6);
  align-items: center;
  padding: 13px 16px;
  border-bottom: 1px solid var(--ip-border);
}
.ip-skeleton-table__head { padding: 14px 16px; }
.ip-skeleton-table__head .ip-skeleton { height: 12px; }
.ip-skeleton-table__row:last-child { border-bottom: 0; }

/* Overlay pagina: copre il contenuto (headerbar compresa) e finge la pagina
   in caricamento. Opaco: nasconde la pagina vecchia mentre arriva la nuova. */
#ip-page-skeleton {
  position: absolute;
  inset: 0;
  z-index: var(--ip-z-sticky);
  padding: 0 20px 20px;
  background: var(--ip-bg);
  overflow: hidden;
}
#ip-page-skeleton[hidden] { display: none; }
/* #main-content diventa un contenitore posizionato per ancorare l'overlay;
   nessun effetto sul layout (è già flex item). Le regole ip-auth-shell per
   la card centrata sono state rimosse: il skeleton non è più incluso nella
   pagina di login (vedi layout/auth.php). */
#main-content { position: relative; }

/* Barra in stile headerbar, full-bleed come quella vera (padding ai lati). */
.ip-page-skeleton__bar {
  height: 56px;
  margin: 0 -20px 12px;
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  border-bottom: 1px solid var(--ip-border);
}
.ip-page-skeleton__bar .ip-skeleton { flex: 0 1 auto; }
.ip-page-skeleton__bar .ip-skeleton:first-child { flex: 0 1 30%; }
.ip-page-skeleton__bar .ip-skeleton:last-child { flex: 0 1 12%; margin-left: auto; }

/* Skip link: fuori schermo finché non riceve il focus, poi visibile sopra
   la topbar. Primo elemento tabulabile della pagina (base e guest). */
.ip-skip-link {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: var(--ip-z-top);
  padding: 8px 14px;
  background: var(--ip-bg-card);
  color: var(--ip-primary);
  /* Bordo in inchiostro pieno (17.7:1): il link deve staccarsi subito
     anche da chi lo vede per la prima volta a meta' schermata. */
  border: 1px solid var(--ip-primary);
  border-radius: var(--ip-radius-sm);
  font-weight: 600;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
  transform: translateY(-250%);
  transition: transform 150ms ease;
}
.ip-skip-link:focus,
.ip-skip-link:focus-visible {
  transform: none;
  outline: 2px solid var(--ip-primary);
  outline-offset: 2px;
}

/* <main> riceve il focus programmatico dopo la navigazione: non è un
   elemento interattivo, quindi niente outline di default. */
#main-content:focus { outline: none; }

/* Ricerca live: la tabella skeleton entra nel target (.table-responsive)
   al posto della tabella vera mentre il partial AJAX è in volo. */
.ip-skeleton-table--standalone { padding: 12px 0; background: var(--ip-bg-card); }

/* Pannello ricerca globale: righe placeholder durante la chiamata. */
.ip-skeleton--panel {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-5);
  padding: 12px 14px;
}

/* Riduzione del movimento: shimmer statico, solo il colore di base. */
@media (prefers-reduced-motion: reduce) {
  .ip-skeleton,
  .ip-skeleton-table,
  #ip-page-skeleton .ip-skeleton {
    animation: none;
  }
}

/* Contenuto contrastato: i placeholder diventano blocchi pieni e scuri
   (3.21:1 sulla card) e lo shimmer si ferma: leggibili come elementi anche
   con bassa acuita', senza il disturbo del movimento. */
@media (prefers-contrast: more) {
  :root {
    --ip-skeleton-bg: #8f8f96;
    --ip-skeleton-shine: #8f8f96;
  }
  .ip-skeleton,
  .ip-skeleton-table,
  #ip-page-skeleton .ip-skeleton {
    animation: none;
  }
}

/* Contrasto ridotto: placeholder ancora più tenui, per chi li trova
   invadenti. */
@media (prefers-contrast: less) {
  :root {
    --ip-skeleton-bg: #e8e8ec;
    --ip-skeleton-shine: #f2f2f5;
  }
}

/* Windows High Contrast (forced-colors): i gradient di background vengono
   rimossi e lo skeleton sparirebbe. Blocchi bordati con i colori di sistema,
   così i placeholder restano visibili come forme. */
@media (forced-colors: active) {
  .ip-skeleton {
    background: Canvas;
    border: 1px solid CanvasText;
  }
}

/* -----------------------------------------------------------------------------
   Calendario scadenze
   -----------------------------------------------------------------------------
   Griglia mensile: le stesse tinte di stato usate ovunque nell'app (fatture,
   attivita', prospetto) applicate ai tre tipi di scadenza — così "rosso" vuol
   dire la stessa cosa qui che nella lista fatture. */

.ip-calendar-nav .btn { display: inline-flex; align-items: center; }

.ip-calendar-feed-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-6);
  padding: 10px 16px;
  margin-bottom: 16px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  font-size: var(--ip-text-base);
}
.ip-calendar-feed-bar__main {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  min-width: 0;
  flex-wrap: wrap;
}
.ip-calendar-feed-bar__icon {
  color: var(--ip-primary);
  font-size: var(--ip-text-md);
  flex-shrink: 0;
}
.ip-calendar-feed-bar__label {
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-calendar-feed-bar__url {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  background: var(--ip-bg);
  border: 1px solid var(--ip-border);
  padding: 2px 8px;
  border-radius: var(--ip-radius-sm);
  word-break: break-all;
  color: var(--ip-text);
}
.ip-calendar-feed-bar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-3);
}
.ip-calendar-feed-bar__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-2);
  white-space: nowrap;
  font-size: var(--ip-text-sm);
  padding: 3px 10px;
}
.ip-calendar-feed-bar__link {
  white-space: nowrap;
  font-weight: 600;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  flex-shrink: 0;
  transition: color 0.15s;
}
.ip-calendar-feed-bar__link:hover {
  color: var(--ip-primary);
}
.ip-calendar-feed-bar--missing {
  border-color: var(--ip-warning-border);
  background: var(--ip-warning-bg, #fcf8e3);
}
.ip-calendar-feed-bar--missing .ip-calendar-feed-bar__icon {
  color: var(--ip-warning-text, #8a6d3b);
}
@media (max-width: 768px) {
  .ip-calendar-feed-bar {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--ip-space-4);
  }
  .ip-calendar-feed-bar__label {
    white-space: normal;
  }
}

.ip-calendar-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--ip-space-7);
  align-items: start;
}
/* La soglia conta anche la sidebar (~250px): la griglia vuole 640px, il
   pannello scadenze 300 più il gap. A 900px il pannello restava affiancato
   fino a schermi da laptop, dove la colonna della griglia scendeva a ~480px e
   sabato e domenica finivano dietro uno scroll orizzontale quasi invisibile. */
@media (max-width: 1280px) {
  .ip-calendar-layout { grid-template-columns: minmax(0, 1fr); }
}

.ip-calendar-card { overflow: hidden; }

.ip-calendar-grid {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  table-layout: fixed;
}
.ip-calendar-grid thead th {
  padding: 8px 6px;
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ip-text-muted);
  text-align: left;
  border-bottom: 1px solid var(--ip-border);
}
.ip-calendar-cell {
  height: 96px;
  padding: 6px;
  vertical-align: top;
  border: 1px solid var(--ip-border-soft);
}
.ip-calendar-cell.is-empty { background: var(--ip-bg); }
.ip-calendar-cell.is-today { background: var(--ip-primary-soft); }
.ip-calendar-daynum {
  display: block;
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  color: var(--ip-text-muted);
  margin-bottom: 4px;
}
.ip-calendar-cell.is-today .ip-calendar-daynum { color: var(--ip-primary); }
/* Quando il giorno ha scadenze il numero diventa un bottone (apre il popover
   con tutte le scadenze): reset dei default da bottone, stessa resa del
   vecchio span. */
button.ip-calendar-daynum {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  border-radius: var(--ip-radius-sm);
}
button.ip-calendar-daynum:hover,
button.ip-calendar-daynum:focus-visible {
  color: var(--ip-primary);
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
}
/* Il pallino sotto il numero indica "oggi" senza dover decodificare il fondo
   tenue della cella: accento pieno, lo stesso tratto del numero. */
.ip-calendar-cell.is-today .ip-calendar-daynum::after {
  content: '';
  display: inline-block;
  width: 4px;
  height: 4px;
  margin-left: 5px;
  vertical-align: 1px;
  border-radius: 999px;
  background: var(--ip-primary);
}

.ip-calendar-event {
  display: block;
  font-size: var(--ip-text-xs);
  line-height: 1.4;
  padding: 1px 5px;
  margin-bottom: 2px;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border-left: 3px solid var(--ip-neutral);
  background: var(--ip-hover);
}
.ip-calendar-event:hover { background: var(--ip-primary-soft); }
/* Stesse tinte della cartoleria: verde postale per il denaro (fatture),
   ambra per l'intermedio (F24), inchiostro per il lavoro (attivita'). */
.ip-calendar-event--invoice { border-left-color: var(--ip-success); }
.ip-calendar-event--f24     { border-left-color: var(--ip-warning-text); }
.ip-calendar-event--task    { border-left-color: var(--ip-primary); }
.ip-calendar-event.is-overdue {
  background: var(--ip-danger-bg);
  border-left-color: var(--ip-danger);
  color: var(--ip-danger-text);
  font-weight: 600;
}
.ip-calendar-event-more {
  display: block;
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
}
/* "+N altre" ora è un bottone che apre il popover del giorno: reset e un
   accento al hover per dire che lì c'è altro da vedere. */
button.ip-calendar-event-more {
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  font-family: inherit;
  text-align: left;
}
button.ip-calendar-event-more:hover,
button.ip-calendar-event-more:focus-visible {
  color: var(--ip-primary);
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
}

.ip-calendar-list { list-style: none; margin: 0; padding: 0; }
.ip-calendar-list li + li { border-top: 1px solid var(--ip-border-soft); }
.ip-calendar-list a {
  display: grid;
  grid-template-columns: 40px 1fr auto;
  gap: var(--ip-space-4);
  align-items: center;
  padding: 10px 14px;
  text-decoration: none;
  color: var(--ip-text);
}
.ip-calendar-list a:hover { background: var(--ip-hover); }
.ip-calendar-list__date {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-calendar-list__title {
  font-size: var(--ip-text-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ip-calendar-list__amount {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-calendar-list li.is-overdue .ip-calendar-list__date,
.ip-calendar-list li.is-overdue .ip-calendar-list__title {
  color: var(--ip-danger-text);
  font-weight: 600;
}

/* Weekend: il fondo leggermente diverso delle colonne Sab/Dom scandisce la
   settimana senza obbligare a leggere le intestazioni. */
.ip-calendar-grid thead th.is-weekend { color: var(--ip-text-muted); }
.ip-calendar-cell.is-weekend { background: var(--ip-bg); }
.ip-calendar-cell.is-weekend .ip-calendar-daynum { color: var(--ip-text-muted); }

/* Legenda sotto la griglia: le tinte dei tre tipi e del "scaduto", che la
   griglia usa per i bordi degli eventi. Le stesse della cartoleria. */
.ip-calendar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  align-items: center;
  padding: 10px 14px;
  border-top: 1px solid var(--ip-border-soft);
  background: var(--ip-bg);
  font-size: var(--ip-text-xs);
}
.ip-calendar-legend__label {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-calendar-legend__item {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  color: var(--ip-text-muted);
}
.ip-calendar-legend__item::before {
  content: '';
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--ip-hover);
}
.ip-calendar-legend__item--invoice::before { background: var(--ip-success); }
.ip-calendar-legend__item--f24::before { background: var(--ip-warning-text); }
.ip-calendar-legend__item--task::before { background: var(--ip-primary); }
.ip-calendar-legend__item.is-overdue::before { background: var(--ip-danger); }

/* Sezioni del riepilogo laterale: "Scadute / Oggi / In programma" come le
   eyebrow delle altre pagine (mono maiuscole), con il contatore accanto. */
.ip-calendar-group {
  display: flex;
  align-items: center;
  gap: var(--ip-space-4);
  margin: 0;
  padding: 10px 14px 2px;
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-calendar-group__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--ip-hover);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-2xs);
  font-weight: 700;
  letter-spacing: 0;
}
.ip-calendar-group.is-overdue-group .ip-calendar-group__count {
  background: var(--ip-danger-bg);
  color: var(--ip-danger-text);
}

/* Popover del giorno: pannello fisso sopra la cella con tutte le scadenze.
   Stessa base delle finestre (card) e delle liste dei pannelli IPSelect. */
.ip-calendar-popover {
  position: fixed;
  z-index: var(--ip-z-top);
  width: 320px;
  max-width: calc(100vw - 24px);
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-lg);
  overflow: hidden;
}
.ip-calendar-popover__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--ip-space-5);
  padding: 10px 14px;
  border-bottom: 1px solid var(--ip-border-soft);
  background: var(--ip-bg);
}
.ip-calendar-popover__day {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  color: var(--ip-text);
}
.ip-calendar-popover__count {
  font-size: var(--ip-text-2xs);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-calendar-popover__list {
  list-style: none;
  margin: 0;
  padding: 6px;
  max-height: 320px;
  overflow: auto;
}
.ip-calendar-popover__item + .ip-calendar-popover__item { border-top: 1px solid var(--ip-border-soft); }
.ip-calendar-popover__item a {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--ip-space-4);
  align-items: baseline;
  padding: 8px;
  border-radius: var(--ip-radius-sm);
  text-decoration: none;
  color: var(--ip-text);
}
.ip-calendar-popover__item a:hover { background: var(--ip-hover); }
.ip-calendar-popover__badge {
  font-size: var(--ip-text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--ip-hover);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-calendar-popover__badge--invoice { background: var(--ip-success-bg); color: var(--ip-success-text); }
.ip-calendar-popover__badge--f24 { background: var(--ip-warning-bg); color: var(--ip-warning-text); }
.ip-calendar-popover__badge--task { background: var(--ip-primary-soft); color: var(--ip-primary); }
.ip-calendar-popover__title {
  font-size: var(--ip-text-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ip-calendar-popover__amount {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-calendar-popover__item.is-overdue .ip-calendar-popover__title {
  color: var(--ip-danger-text);
  font-weight: 600;
}
.ip-calendar-popover__item.is-overdue .ip-calendar-popover__badge {
  background: var(--ip-danger-bg);
  color: var(--ip-danger-text);
}

@media (max-width: 480px) {
  .ip-calendar-cell { height: 76px; }
  .ip-calendar-event { font-size: var(--ip-text-2xs); }
}

/* -----------------------------------------------------------------------------
   Voci contributive aggiuntive (scheda azienda)
   -----------------------------------------------------------------------------
   Righe ripetibili: nome, aliquota, base di calcolo, deducibilità. Su viewport
   strette le righe si impilano invece di schiacciare i campi.

   La colonna nome ha un max esplicito (non 1fr libero): su schermi larghi un
   1fr senza limite si mangia tutto lo spazio disponibile e spinge aliquota/
   base/deducibile in un blocco compresso lontano dal campo nome, con un vuoto
   enorme in mezzo. La colonna finale 1fr assorbe lo spazio residuo alla fine
   della riga invece che al centro. */
.ip-tax-extra-row {
  display: grid;
  grid-template-columns: minmax(200px, 320px) 130px 190px 150px 34px 1fr;
  gap: var(--ip-space-6);
  align-items: center;
  margin-bottom: 8px;
}
.ip-tax-extra-row .form-control { height: 36px; }
.ip-tax-extra-row .ip-input-suffix__mark { min-width: 20px; }
/* Le frecce native di type=number si sovrappongono al simbolo "%": in una
   colonna stretta il risultato è un ammasso illeggibile di icone. */
.ip-tax-extra-row .ip-input-suffix input[type="number"] {
  -moz-appearance: textfield;
}
.ip-tax-extra-row .ip-input-suffix input[type="number"]::-webkit-outer-spin-button,
.ip-tax-extra-row .ip-input-suffix input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.ip-tax-extra-check {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  margin: 0;
  white-space: nowrap;
}
.ip-tax-extra-check .form-check-input { margin: 0; }

@media (max-width: 900px) {
  .ip-tax-extra-row {
    grid-template-columns: 1fr 1fr;
  }
  .ip-tax-extra-check { align-self: center; }
  /* Impilate, il comando "rimuovi" finiva da solo sotto la colonna del nome,
     lontano dalla riga che elimina: va all'estremita' destra, dove sta anche
     in orizzontale. */
  .ip-tax-extra-row .tax-extra-remove {
    grid-column: 2;
    justify-self: end;
  }
}

/* Riquadro che raccoglie le righe: prima le righe stavano a giro libero nella
   sezione, senza un bordo che le distinguesse dagli altri campi del regime
   sopra. Un contenitore proprio rende chiaro dove inizia e finisce l'elenco. */
.ip-tax-extras-help { margin-top: 2px; }
.ip-tax-extras-card {
  margin-top: 8px;
  padding: 12px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg);
}
.ip-tax-extras-card .ip-tax-extra-row:last-child { margin-bottom: 0; }

/* Intestazione di colonna: senza, "Base" e "Deducibile" si capiscono solo
   guardando dentro ogni riga. Nascosta sotto i 900px insieme allo stack a due
   colonne delle righe, dove non corrisponderebbe più ai campi sottostanti. */
.ip-tax-extra-row--head {
  padding-bottom: 4px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--ip-border);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
}
@media (max-width: 900px) {
  .ip-tax-extra-row--head { display: none; }
}

.ip-tax-extra-add { margin-top: 8px; }

/* -----------------------------------------------------------------------------
   Regime e imposte (scheda azienda)
   -----------------------------------------------------------------------------
   Campo che occupa tutta la riga della griglia. Prima stava come stile inline
   sulle singole .ip-doc-field: la stessa dichiarazione ripetuta a mano in ogni
   view che ne aveva bisogno. */
.ip-doc-field--full { grid-column: 1 / -1; }

/* Campo largo due colonne: per i valori che una colonna non regge senza andare
   a capo (l'IBAN, 27 caratteri monospazio) ma che a tutta riga lascerebbero un
   input lungo il triplo del contenuto. */
.ip-doc-field--wide { grid-column: span 2; }
/* Sotto i 768px la griglia e' a una colonna: span 2 vi creerebbe una seconda
   colonna implicita, e il campo uscirebbe dal riquadro. */
@media (max-width: 767.98px) {
  .ip-doc-field--wide { grid-column: auto; }
}

/* Input di pochi caratteri (codice SIA, sigle): la casella si ferma alla
   misura del dato anche quando il campo occupa più colonne perché il testo
   di aiuto ne ha bisogno. Senza, un codice di cinque caratteri finiva in una
   casella larga come quella dell'IBAN. */
.ip-input-short { max-width: 160px; }

/* Numero che puo' andare a capo: l'IBAN a gruppi di quattro, in una scheda di
   telefono, non ci sta su una riga sola, e .ip-num (nowrap) faceva debordare
   la cella fuori dalla scheda. Va a capo fra un gruppo e l'altro, mai dentro. */
.ip-num--wrap { white-space: normal; }

/* Campi e intestazioni di sottogruppo non pertinenti al regime scelto.
   display:none li toglie dal flusso della griglia senza lasciare celle vuote,
   a differenza di visibility:hidden. */
.ip-regime-field[hidden],
.ip-doc-subgroup[hidden] { display: none; }

/* Le tre forme aziendali stanno su una riga sola finche' c'e' spazio: sono
   alternative da confrontare, non un elenco da scorrere. */
#ip-company-form { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }

/* -----------------------------------------------------------------------------
   Conti di saldo e metodi di pagamento
   -----------------------------------------------------------------------------
   Le due anagrafiche decidono cosa viene stampato nel riquadro pagamento del
   documento e cosa finisce nel blocco DatiPagamento dell'XML. L'elenco e il
   form lo dicono con gli stessi elementi: una seconda riga di dettaglio nelle
   celle, e un foglio d'anteprima che mostra il blocco come sara' stampato. */

/* Seconda riga di una cella: la qualifica del dato principale (banca e
   intestatario sotto il nome del conto, descrizione sotto il codice MP). Il
   dato secondario così non ruba una colonna alla tabella. */
.ip-cell-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--ip-text-sm);
  line-height: 1.4;
  font-style: normal;
  color: var(--ip-text-muted);
}

/* Voce di menu spenta: l'azione esiste ma ora non e' disponibile (il record e'
   collegato a documenti). Resta visibile — nasconderla farebbe cercare un
   comando che c'e' sempre stato — ma non e' cliccabile, e porta con se' il
   motivo. Forma e spaziatura arrivano dalla regola comune delle voci di menu:
   qui cambia solo il colore e il cursore. */
.dropdown-menu .dropdown-button.is-disabled {
  color: var(--ip-text-muted);
  cursor: not-allowed;
}
.dropdown-menu .dropdown-button.is-disabled:hover { background: none; }

/* Colonna form + anteprima. L'anteprima resta a fianco finche' lo schermo lo
   consente e sotto quando non ci sta: e' un riscontro di cio' che si sta
   scrivendo, quindi va guardata mentre si compila, non dopo. */
.ip-account-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--ip-space-7);
  align-items: start;
}
@media (max-width: 1199.98px) {
  .ip-account-layout { grid-template-columns: minmax(0, 1fr); }
}
.ip-account-layout__side { position: sticky; top: 16px; }
@media (max-width: 1199.98px) {
  .ip-account-layout__side { position: static; }
}
/* Variante a colonna singola: toglie la colonna laterale da 320px (sempre
   vuota quando la pagina non ha un pannello affiancato) così la card si
   prende tutta la larghezza disponibile. */
.ip-account-layout--wide { grid-template-columns: minmax(0, 1fr); }

/* Il foglio: carta bianca su un fondo scuro, la stessa messa in scena
   dell'anteprima PDF (.ip-preview__sheet), in piccolo. Il fondo scuro serve a
   dire "questa e' la stampa, non un altro riquadro del form". */
.ip-paysheet {
  padding: 14px;
  border-radius: var(--ip-radius);
  background: var(--ip-preview-bg);
}
.ip-paysheet__paper {
  padding: 16px 18px;
  border-radius: 2px;
  background: #fff;
  color: #18181b;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
}
.ip-paysheet__label {
  display: block;
  margin-bottom: 8px;
  color: rgba(255, 255, 255, 0.82);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.ip-paysheet__title {
  margin: 0 0 8px;
  font-size: var(--ip-text-base);
  font-weight: 600;
  letter-spacing: -0.01em;
}
.ip-paysheet__line {
  margin: 0 0 4px;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: #3f3f46;
}
/* L'IBAN e' la ragione per cui il conto esiste: monospazio a gruppi di
   quattro, come sull'estratto conto, così si ricontrolla cifra per cifra. */
.ip-paysheet__iban {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  font-weight: 500;
  word-break: break-all;
  color: #18181b;
}
.ip-paysheet__note {
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid #e4e4e7;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: #52525b;
  white-space: pre-line;
}
.ip-paysheet__empty {
  margin: 0;
  font-size: var(--ip-text-sm);
  font-style: italic;
  color: #71717a;
}

/* Presentazione RiBa: le fatture emesse con metodo RiBa stanno in cima e sono
   gia' spuntate. La riga separatrice dice dove finisce quel gruppo e comincia
   il resto delle fatture aperte, che restano presentabili. */
.ip-riba-divider td {
  padding: 10px 12px;
  border-top: 1px solid var(--ip-border);
  background: var(--ip-bg);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 500;
}
.ip-item-row.is-riba .badge { margin-left: 6px; }

/* ---- Integrazione MCP (app/Views/mcp/settings.php) ----
   Snippet di configurazione client: blocco monospace in sola lettura, con
   sfondo leggermente diverso dal card per staccarsi dal resto del form. */
.ip-mcp-snippet {
  margin: 8px 0 0;
  padding: 12px 14px;
  background: var(--ip-bg);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  font-family: var(--bs-font-monospace, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: var(--ip-text-sm);
  line-height: 1.55;
  color: var(--ip-text);
  overflow-x: auto;
  white-space: pre;
}
.ip-mcp-snippet code {
  font-family: inherit;
  color: inherit;
  background: none;
  padding: 0;
}
/* Badge di stato: allineato verticalmente al testo che lo accompagna. */
.ip-mcp-status .badge {
  font-weight: 600;
}
.ip-mcp-status__text {
  font-size: var(--ip-text-base);
}

/* Pager condiviso in fondo alle tabelle elenco. Il footer del card
   allinea a destra nav + conteggio; su schermi stretti va a capo. */
.ip-table-card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-6);
  flex-wrap: wrap;
}
.ip-pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ip-space-6);
  flex-wrap: wrap;
}
.ip-pager-info {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-pager .pagination {
  margin: 0;
  /* Bootstrap definisce l'attivo con --bs-pagination-active-bg hardcoded
     su #0d6efd (il blu di default), non legato a --bs-primary/--ip-primary
     come il resto dei componenti: qui lo riallineiamo al colore del tema. */
  --bs-pagination-active-bg: var(--ip-primary);
  --bs-pagination-active-border-color: var(--ip-primary);
}
/* In tema scuro --ip-primary si ribalta in quasi-bianco (#e7e7ea, pensato
   come accento/testo su sfondo scuro, non come fondo pieno): il testo
   dell'attivo restava #fff fisso di Bootstrap, quindi bianco su quasi-bianco,
   contrasto illeggibile. Stesso ribaltamento gia' applicato a .btn-primary,
   .ip-select-option.is-selected, .ip-tabs .nav-link.active. */
html[data-theme="dark"] .ip-pager .pagination {
  --bs-pagination-active-color: var(--ip-bg);
}

/* Campo descrizione riga fattura più ampio: la colonna guadagna una
   larghezza minima comoda e la textarea cresce in altezza col contenuto
   (serve per le diciture di legge dell'inversione contabile, es.
   "Operazione in regime di inversione contabile ai sensi dell'art. 17
   comma 6 DPR 633/72", che non starebbero mai su una riga sola). */
.ip-items-table__desc {
  width: 40%;
  /* 230 e non 320: il contenitore della tabella porta ora il rientro
     laterale (--ip-card-pad-x per lato, così le celle di bordo non si
     schiacciano), quindi la tabella dispone di 36px in meno. Con la
     minima a 320 su un portatile da 1280 la riga andava in scorrimento
     orizzontale di ~70px e la colonna Opzioni restava fuori schermo.
     Sopra i ~1350px la colonna si allarga comunque da sola (width: 40%). */
  min-width: 230px;
}
/* Colonna Imposta: la tendina mostra la descrizione della natura IVA
   ("0,00% — Non soggetta – forfettario (N2.2)"), che è nowrap e quindi
   detta la larghezza minima della cella. Con un valore lungo la tabella
   righe sforava il contenitore anche a 1280 (colonna Opzioni e Totale da
   raggiungere con lo scorrimento). Sotto i 1400 il controllo viene
   limitato e il testo si chiude con i puntini: l'etichetta intera resta
   nella tendina aperta e nel select nativo, che è quello che viene
   inviato. Sopra, lo spazio c'è e resta leggibile per esteso. */
@media (max-width: 1399.98px) {
  .ip-items-table__tax .ip-select {
    max-width: 140px;
  }
}
.ip-items-table__tax .ip-select__value {
  overflow: hidden;
  text-overflow: ellipsis;
}
.ip-items-table__desc textarea.ip-item-row__desc {
  min-height: 60px;
  resize: vertical;
  overflow: hidden;
  /* i browser moderni adattano l'altezza al testo; dove non supportato
     resta la min-height + resize manuale. */
  field-sizing: content;
}
/* Nella riga "aggiungi riga" la descrizione è più larga dell'articolo.
   Lo flex va sul wrapper (creato da ip-desc-counter.js) così il contatore
   resta sotto il campo e non ruba spazio nella riga flessibile. */
.ip-doc-newrow .ip-desc-wrap {
  flex: 3 1 320px;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.ip-doc-newrow .ip-desc-wrap .form-control {
  width: 100%;
}

/* Contatore caratteri della descrizione riga (vedi ip-desc-counter.js). Il
   cap è voluto: la Descrizione della FatturaPA è un String1000Type, quindi
   oltre 1000 caratteri l'XML verrebbe rifiutato dallo SdI — meglio segnalarlo
   subito, mentre si compila, invece di scoprirlo al tentativo di invio. */
.ip-desc-wrap {
  display: block;
}
.ip-desc-counter {
  display: block;
  margin-top: 2px;
  font-size: 0.72rem;
  line-height: 1.2;
  text-align: right;
  color: var(--ip-text-muted, #6c757d);
  font-variant-numeric: tabular-nums;
}
.ip-desc-counter--full {
  color: var(--bs-danger, #dc3545);
  font-weight: 600;
}

/* Tabella righe: scorrevole in orizzontale sugli schermi stretti, così le
   colonne a larghezza minima non sfondano il layout della pagina. */
.ip-doc-items {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
/* La minima di 720px serve alla griglia righe (nove colonne, con la
   descrizione larga). Applicata a ogni .ip-items-table faceva scorrere anche
   le tabelle di riepilogo a tre colonne, che nella modifica fattura stanno
   nella colonna destra larga ~470px (Imposte, pagamenti): 247px di
   scorrimento orizzontale per tre colonne che ci stavano senza problemi.
   Il discriminante è la colonna descrizione, che solo la griglia righe ha. */
.ip-items-table:has(.ip-items-table__desc) {
  min-width: 720px;
}
/* Larghezze della griglia righe. Le misure generiche di .ip-items-table
   (Codice 120, tre colonne numeriche da 110, Unità 130, Imposta 210, Totale
   130, più il 40% della descrizione) sommano ben oltre i ~970px che la
   scheda offre su un portatile da 1280: il browser le riduceva tutte in
   proporzione e i campi uscivano più corti del loro contenuto — Codice a
   48px (il segnaposto "Cod." tagliato), Prezzo a 49px, cioè meno di
   "1.234,56". Qui ogni colonna chiede solo ciò che serve al suo valore tipico
   e le spaziature orizzontali tra celle si dimezzano (da 12 a 6px per lato:
   nove colonne di campi non hanno bisogno di 24px di aria tra l'una e
   l'altra). Il totale delle colonne fisse scende a ~740px, quindi a 1280 la
   descrizione tiene la sua minima e i campi hanno la lunghezza giusta; sugli
   schermi più larghi lo spazio in più va tutto alla descrizione, che è
   l'unica colonna senza larghezza dichiarata. Vale solo per la griglia righe
   (riconosciuta dalla colonna descrizione), non per le tabelle di riepilogo
   che riusano la classe. */
.ip-items-table:has(.ip-items-table__desc) > thead > tr > th,
.ip-items-table:has(.ip-items-table__desc) > tbody > tr > td {
  padding-left: 6px;
  padding-right: 6px;
  /* Le misure qui sotto sono l'ingombro intero della colonna, spaziatura
     compresa: così la somma si legge a colpo d'occhio (724px di colonne
     fisse + 242 di descrizione minima = i 966px disponibili a 1280). */
  box-sizing: border-box;
}
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__desc { width: auto; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__handle { width: 56px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__code { width: 70px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__num { width: 96px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__num--qty { width: 70px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__num--price { width: 96px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__num--discount { width: 70px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__unit { width: 112px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__tax { width: 128px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__total { width: 92px; }
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__actions { width: 30px; }
/* Dentro la griglia i campi numerici e il codice hanno 6px di margine
   interno invece di 10: con 10 per lato un Prezzo da 70px lasciava 50px alle
   cifre, e "1.234,00" usciva tagliato a sinistra; lo stesso per "1,00" nella
   Quantità. Le larghezze sopra sono calcolate su questo margine: Prezzo
   contiene "12.345,67", Quantità e Sconto "100,00". */
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__num .form-control,
.ip-items-table:has(.ip-items-table__desc) .ip-items-table__code .form-control {
  padding-left: 6px;
  padding-right: 6px;
}
/* Sotto i 1400px le due tendine cedono qualche pixel ai campi importo:
   l'Unità resta leggibile ("Nessuno", "pz") e l'Imposta mostra comunque la
   percentuale, che è la parte che si controlla a colpo d'occhio; l'etichetta
   intera è nel menu aperto. */
@media (max-width: 1399.98px) {
  .ip-items-table:has(.ip-items-table__desc) .ip-items-table__unit .ip-select { max-width: 100px; }
  .ip-items-table:has(.ip-items-table__desc) .ip-items-table__tax .ip-select { max-width: 116px; }
  .ip-items-table:has(.ip-items-table__desc) .ip-items-table__unit .ip-select__value {
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* La "x" per svuotare l'Unità qui toglierebbe 20px al testo ("Nes…"): per
     tornare a "Nessuno" basta sceglierlo dal menu, dov'è la prima voce. */
  .ip-items-table:has(.ip-items-table__desc) .ip-items-table__unit .ip-select__clear { display: none; }
}
/* "Q.tà" è un'abbreviazione d'intestazione, non un termine da spiegare: via
   la sottolineatura puntinata e il cursore d'aiuto che il browser dà a
   <abbr title>. Il nome per esteso resta nel title. */
.ip-items-table th abbr[title] { text-decoration: none; cursor: inherit; }
/* Sopra i 1400px l'Imposta mostra l'etichetta per esteso (vedi sopra): lì la
   colonna torna larga, e lo spazio c'è. */
@media (min-width: 1400px) {
  .ip-items-table:has(.ip-items-table__desc) .ip-items-table__tax { width: 222px; }
}

/* Maniglia e frecce su/giù per riordinare le righe (vedi ip-items-reorder.js).
   La maniglia serve al drag da mouse, le frecce a touch e tastiera (il drag
   nativo HTML5 non parte su dispositivi senza mouse). La colonna è stretta e
   gli elementi non hanno sfondo a riposo, così non appesantiscono la riga. */
.ip-items-table__handle {
  width: 44px;
  text-align: center;
  vertical-align: middle;
  white-space: nowrap;
}
.ip-item-row__grip {
  display: block;
  line-height: 1;
}
.ip-item-row__handle {
  border: 0;
  background: transparent;
  color: var(--ip-text-muted, #6c757d);
  cursor: grab;
  padding: 2px 6px;
  border-radius: 4px;
  line-height: 1;
  touch-action: none;
}
.ip-item-row__handle:hover,
.ip-item-row__handle:focus-visible {
  background: rgba(0, 0, 0, 0.06);
  color: var(--ip-text, #212529);
  outline: none;
}
.ip-item-row__handle:active {
  cursor: grabbing;
}
.ip-item-row__reorder {
  display: inline-flex;
  gap: var(--ip-space-1);
  margin-top: 4px;
}
.ip-item-row__move {
  border: 0;
  background: transparent;
  color: var(--ip-text-muted, #6c757d);
  cursor: pointer;
  padding: 1px 5px;
  border-radius: 4px;
  line-height: 1;
  font-size: var(--ip-text-2xs);
}
.ip-item-row__move:hover:not(:disabled),
.ip-item-row__move:focus-visible:not(:disabled) {
  background: rgba(0, 0, 0, 0.06);
  color: var(--ip-text, #212529);
  outline: none;
}
.ip-item-row__move:disabled {
  opacity: 0.3;
  cursor: default;
}
.ip-item-row.ip-dragging {
  opacity: 0.55;
  background: var(--ip-bg-subtle);
}
/* Indica che la riga è trascinabile mentre si impugna la maniglia. */
.ip-item-row[draggable="true"] {
  cursor: grabbing;
}

/* -----------------------------------------------------------------------------
   Calendario — vista a elenco
   ----------------------------------------------------------------------------- */
/* La griglia risponde a "dove cade una scadenza", l'elenco a "cosa scade e in
   che ordine". L'elenco sta su una colonna sola: il pannello laterale
   ripeterebbe le stesse righe accanto a se stesso. */
.ip-calendar-layout--single { grid-template-columns: minmax(0, 1fr); }
.ip-calendar-view { margin-right: 8px; }
.ip-calendar-agenda .ip-calendar-totals { border-bottom: 1px solid var(--ip-border); }

.ip-agenda-day + .ip-agenda-day { border-top: 1px solid var(--ip-border); }
.ip-agenda-day__head {
  display: flex;
  align-items: baseline;
  gap: var(--ip-space-4);
  margin: 0;
  padding: 12px 14px 4px;
  font-size: var(--ip-text-sm);
  font-weight: 600;
}
/* La data in cifre è quello che si cerca scorrendo: monospaziata e in primo
   piano, il giorno della settimana resta un contorno. */
.ip-agenda-day__weekday { color: var(--ip-text-muted); font-weight: 500; }
.ip-agenda-day__date { font-family: var(--ip-mono); font-size: var(--ip-text-base); letter-spacing: .01em; }
.ip-agenda-day__count {
  margin-left: auto;
  color: var(--ip-text-muted);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
}
.ip-agenda-day.is-today .ip-agenda-day__weekday,
.ip-agenda-day.is-today .ip-agenda-day__date { color: var(--ip-primary); }
.ip-agenda-today {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
}
/* Qui la data sta nell'intestazione del giorno, quindi al posto della colonna
   da 40px c'è la targhetta del tipo di scadenza. */
.ip-agenda-day__list a { grid-template-columns: auto 1fr auto; }
.ip-agenda-event__badge {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--ip-hover);
  color: var(--ip-text-muted);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
}
.ip-agenda-event__badge--invoice { background: var(--ip-primary-soft); color: var(--ip-primary); }
.ip-agenda-event__badge--f24 { background: var(--ip-warning-bg); color: var(--ip-warning-text); }
.ip-agenda-event__badge--task { background: var(--ip-success-bg); color: var(--ip-success-text); }
/* -----------------------------------------------------------------------------
   39. Viewer XML — Anteprima fattura elettronica / spesa
   --------------------------------------------------------------------------
   Layout a due colonne: documento a sinistra, "cifre da registro" e scelta
   del foglio di stile a destra — stesso principio di .ip-preview (sezione
   31), qui con più contesto in barra (identità documento, stato, azioni).
   Tutto il colore passa dai token --ip-*: niente tinte fisse, così il
   viewer segue chiaro/scuro da solo senza bisogno di override per tema. */
.ip-xml-viewer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(270px, 21vw, 320px);
  gap: 18px;
  align-items: start;
  min-width: 0;
}
.ip-xml-viewer,
.ip-xml-viewer * { box-sizing: border-box; }
.ip-xml-viewer__main {
  min-width: 0;
  overflow: hidden;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
}
.ip-xml-toolbar {
  display: grid;
  grid-template-columns: minmax(200px, 1fr) auto minmax(200px, 1fr);
  align-items: center;
  min-height: 60px;
  gap: var(--ip-space-7);
  padding: 10px 18px;
  background: var(--ip-bg-card);
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-xml-document {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  min-width: 0;
}
.ip-xml-document__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: var(--ip-radius);
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
  font-size: var(--ip-text-lg);
}
.ip-xml-document__copy {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-1);
  overflow: hidden;
}
.ip-xml-document__copy strong {
  font-size: var(--ip-text-md);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ip-xml-document__copy > span {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-xml-num { font-weight: 600; color: var(--ip-text); }
.ip-xml-dot { color: var(--ip-text-muted); }
.ip-xml-date { color: var(--ip-text-muted); }
.ip-xml-toolbar__center {
  display: flex;
  justify-content: center;
  min-width: 0;
}
.ip-xml-switch {
  display: inline-flex;
  padding: 3px;
  gap: var(--ip-space-1);
  background: var(--ip-bg);
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius);
}
.ip-xml-switch__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 6px 12px;
  border: 0;
  border-radius: calc(var(--ip-radius) - 2px);
  background: transparent;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  cursor: pointer;
  transition: var(--ip-transition);
}
.ip-xml-switch__btn:hover { color: var(--ip-text); }
.ip-xml-switch__btn.is-active {
  background: var(--ip-bg-card);
  color: var(--ip-text);
  box-shadow: var(--ip-shadow-sm);
}
.ip-xml-toolbar__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ip-space-4);
}
/* Lo stato "non pronta" non ha più una pillola dedicata in toolbar (era
   ridondante con l'anteprima stessa): un puntino sul tab "Resa" basta, con
   il motivo nel title del bottone. */
.ip-xml-warn-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--ip-warning);
}
.ip-xml-switch__btn { position: relative; }
.ip-xml-switch__btn.has-warning { color: var(--ip-warning); }
.ip-xml-panels {
  position: relative;
}
.ip-xml-panel {
  min-height: 400px;
}
.ip-xml-frame {
  display: block;
  width: 100%;
  min-height: 800px;
  border: 0;
  background: #fff;
}
.ip-xml-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--ip-space-4);
  min-height: 400px;
  padding: 40px 20px;
  color: var(--ip-text-muted);
}
.ip-xml-empty__icon {
  font-size: 34px;
  color: var(--ip-text-muted);
  opacity: 0.6;
}
.ip-xml-empty h2 {
  font-size: var(--ip-text-lg);
  color: var(--ip-text);
  margin: 0;
}
.ip-xml-empty p {
  font-size: var(--ip-text-base);
  max-width: 420px;
  margin: 0;
}
.ip-xml-rawbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 18px;
  border-bottom: 1px solid var(--ip-border-soft);
  font-size: var(--ip-text-sm);
}
.ip-xml-rawbar strong { margin-right: 8px; color: var(--ip-text); }
.ip-xml-rawbar span { color: var(--ip-text-muted); }
.ip-xml-code {
  margin: 0;
  padding: 16px 18px;
  max-height: 800px;
  overflow: auto;
  background: var(--ip-bg);
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  line-height: 1.6;
  white-space: pre;
}
.ip-xml-code code { font-family: inherit; }
.xml-comment { color: var(--ip-text-muted); font-style: italic; }
.xml-prolog { color: var(--ip-text-muted); }
.xml-tag { color: var(--ip-primary); font-weight: 600; }
.xml-attr { color: var(--ip-warning); }
.xml-value { color: var(--ip-success); }
.ip-xml-sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-7);
  min-width: 0;
  position: sticky;
  top: 16px;
}
.ip-xml-card {
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
  overflow: hidden;
}
.ip-xml-card > header {
  padding: 10px 14px;
  font-size: var(--ip-text-sm);
  font-weight: 650;
  color: var(--ip-text-muted);
  text-transform: uppercase;
  letter-spacing: .02em;
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-xml-card__body {
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-4);
}
.ip-xml-field {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ip-space-5);
  font-size: var(--ip-text-sm);
}
.ip-xml-field span { color: var(--ip-text-muted); }
.ip-xml-field strong { color: var(--ip-text); font-weight: 600; text-align: right; }
.ip-xml-field--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--ip-space-1);
}
.ip-xml-field--stacked strong { text-align: left; }
.ip-xml-mono { font-family: var(--ip-mono); font-size: var(--ip-text-sm); }

/* Subtotali (imponibile/IVA): due righe compatte, chiaramente secondarie
   rispetto al totale che segue. */
.ip-xml-subtotals {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-2);
  padding: 8px 0;
  border-top: 1px solid var(--ip-border-soft);
}
.ip-xml-subtotals .ip-xml-field { font-size: var(--ip-text-sm); }
.ip-xml-subtotals .ip-xml-field strong { font-weight: 500; color: var(--ip-text-muted); }

/* Il totale è l'unico numero che deve saltare all'occhio in questa card:
   corpo grande, colore primario, riga propria — tutto il resto intorno
   resta piccolo e muto apposta. */
.ip-xml-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ip-space-5);
  padding-top: 4px;
}
.ip-xml-total span {
  font-size: var(--ip-text-sm);
  font-weight: 600;
  color: var(--ip-text-muted);
  text-transform: uppercase;
  letter-spacing: .02em;
}
.ip-xml-total strong {
  font-size: var(--ip-text-3xl);
  font-weight: 700;
  color: var(--ip-primary);
  letter-spacing: -0.01em;
}
.ip-xml-card__meta {
  padding: 8px 14px;
  border-top: 1px solid var(--ip-border-soft);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
}
.ip-xml-styles {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-3);
}
/* Riga singola per foglio di stile: nome + stato, senza sottotitolo fisso
   (la descrizione resta nel title, a comparsa, perché è un'impostazione
   che si tocca raramente e non merita spazio verticale permanente). */
.ip-xml-style {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-5);
  padding: 7px 10px;
  border: 1px solid var(--ip-border-soft);
  border-radius: var(--ip-radius);
  text-decoration: none;
  color: inherit;
  transition: var(--ip-transition);
}
.ip-xml-style:hover { border-color: var(--ip-border); background: var(--ip-bg); }
.ip-xml-style.is-active {
  border-color: var(--ip-primary);
  background: var(--ip-primary-soft);
}
.ip-xml-style.is-missing { opacity: 0.6; }
.ip-xml-style__name {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  min-width: 0;
  font-size: var(--ip-text-sm);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ip-xml-badge {
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
}
.ip-xml-style__state {
  flex: 0 0 auto;
  color: var(--ip-text-muted);
}
.ip-xml-style.is-active .ip-xml-style__state { color: var(--ip-primary); }
.ip-xml-help {
  display: flex;
  align-items: flex-start;
  gap: var(--ip-space-3);
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--ip-border-soft);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
}
.ip-xml-help code {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-xs);
  background: var(--ip-bg);
  padding: 1px 4px;
  border-radius: 3px;
}
/* Scarica è l'unica azione rimasta in toolbar (la copia è stata tolta,
   ridondante con il download): un po' più di respiro tra icona e testo
   così il pulsante non sembra un'icona ristretta rimasta sola. */
.ip-xml-download {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 6px 14px;
}
@media (max-width: 991.98px) {
  .ip-xml-viewer { grid-template-columns: minmax(0, 1fr); }
  .ip-xml-sidebar { position: static; }
  .ip-xml-toolbar { grid-template-columns: minmax(0, 1fr); min-height: auto; }
  .ip-xml-toolbar__center { order: 3; }
  .ip-xml-toolbar__actions { order: 2; justify-content: flex-start; }
}

/* =============================================================================
   Scheda cliente — refresh UI 2026
   -----------------------------------------------------------------------------
   Obiettivo dallo screenshot: tre card affastellate, bordi slavati sul fondo
   scuro, label "muted" illeggibili, campi schiacciati, sezione commerciale
   senza gerarchia. Il blocco nasceva però come una seconda scala di misura
   per la stessa pagina (card 12/18 e 18/20/14, campi 40/8, etichette
   maiuscole) e in tema scuro riduceva il bordo dei campi sotto il 3:1. Le
   misure ora sono quelle di sistema: qui resta solo la presentazione del
   contenuto — misura di riga del testo di aiuto, rientro della riga SDI,
   spunta sulle scelte di tipologia e il riquadro delle note commerciali.
   ========================================================================== */

/* La griglia usa il gutter standard delle .row e le card il corpo dei token:
   questa pagina non ridimensiona più né le colonne né i pannelli. */
#content form[action*="clients/form"] .ip-doc-field:last-child { margin-bottom: 4px; }

/* Etichette: canonico .ip-doc-label (12px/600, inchiostro, margine 0 perché
   la distanza la dà il gap del campo). Qui erano maiuscole, muted, con 6px
   di margine che si sommavano al gap: etichetta e controllo stavano a 12px
   invece che a 6. */

/* Campi: nessuna misura né colore locale. Erano 40px/8px qui contro i 36/6
   canonici (--ip-control-h), e in tema scuro fondo #23232b con bordo
   #3a3a46 — su quel fondo il bordo sta a 1.4:1, sotto il 3:1 richiesto per
   il contorno di un controllo, mentre --ip-border-field lo tiene sopra.
   Anche il focus era viola solo su questa pagina: ora è neutro come nel
   resto dell'app. Stesso discorso per il placeholder, che qui scendeva a
   opacità .7 e quindi sotto la soglia di lettura. */

/* Testo di aiuto: l'unica scelta locale è la misura di riga (42ch). Tipo,
   colore e distanza sono canonici: il margin-top di 6px che stava qui si
   sommava al gap del campo e l'aiuto risultava il doppio più staccato che
   altrove. */
#content form[action*="clients/form"] .ip-field-help { max-width: 42ch; }
#content form[action*="clients/form"] #ip-sdi-help:not(:empty) {
  padding-left: 16px;
  position: relative;
}
#content form[action*="clients/form"] #ip-sdi-help:not(:empty)::before {
  content: "↳";
  position: absolute; left: 0; top: 0;
  color: var(--ip-text-muted);
}

/* Tipologia cliente: misure canoniche di .ip-type-choice (padding 10/12,
   bordo 1px, raggio 6, gap 8). Le scelte locali erano 12/14, 1.5px, raggio
   10 e gap 10, con un inset shadow che sul bordo attivo sommava un secondo
   contorno. Resta solo la spunta a destra, che il componente non ha. */
#content form[action*="clients/form"] .ip-type-choice__option.is-active::after {
  content: "✓";
  grid-area: label;
  justify-self: end;
  width: 20px; height: 20px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--ip-primary);
  color: var(--ip-on-primary);
  font-size: var(--ip-text-xs); font-weight: 700;
}

/* Rapporti commerciali: card con header staccato, griglia più ariosa */
#content form[action*="clients/form"] .card:has(.ip-field-help.mb-3) .row { --bs-gutter-x: 16px; --bs-gutter-y: 6px; }
#content form[action*="clients/form"] .card:has(.ip-field-help.mb-3) .ip-field-help.mb-3 {
  font-size: var(--ip-text-sm);
  max-width: none;
  padding: 10px 12px;
  margin-bottom: 16px !important;
  background: var(--ip-bg);
  border: 1px dashed var(--ip-border);
  border-radius: 8px;
}
html[data-theme="dark"] #content form[action*="clients/form"] .card:has(.ip-field-help.mb-3) .ip-field-help.mb-3 {
  background: rgba(255,255,255,.03);
  border-color: var(--ip-border);
}

/* Sticky save bar su mobile: i bottoni non scompaiono sotto la piega */
@media (max-width: 767.98px) {
  #content form[action*="clients/form"] .row > [class*="col-"] { margin-bottom: 0; }
}

/* =============================================================================
   IMPORTAZIONI ED ESPORTAZIONI
   -----------------------------------------------------------------------------
   Hub, form di import e pagina di export condividono lo stesso registro
   (carta + inchiostro, ingresso verde / uscita inchiostro) e vivevano in tre
   blocchi <style> dentro le rispettive view: circa 700 righe di CSS servite a
   ogni caricamento, non minificate e non messe in cache dal browser, con le
   stesse regole (card, drop area, pill di periodo) riscritte tre volte.
   Qui stanno in un posto solo, come tutto il resto del foglio di stile.
   ============================================================================= */

/* --- Hub Importa / Esporta (imports_exports/index) --- */
/* Hub Importa / Esporta — carta + inchiostro, due direzioni */
.ip-ie-intro{
  /* Stesse misure di .ip-import-intro e .ip-export-intro: hub e sottopagine
     usano lo stesso schema (occhiello, titolo, descrizione) e prima ognuna
     aveva titolo e distacco propri, 23 / 21,6 / 20 px, che passando da una
     pagina all'altra facevano "saltare" l'intestazione. */
  max-width: 720px;
  margin: 2px 0 18px;
}
.ip-ie-intro__eyebrow{
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  margin: 0 0 6px;
}
.ip-ie-intro__title{
  font-family: var(--ip-display);
  font-size: 1.35rem;
  font-weight: 650;
  letter-spacing: -.02em;
  margin: 0;
  line-height: 1.2;
}
.ip-ie-intro__desc{
  margin: 8px 0 0;
  font-size: var(--ip-text-base);
  line-height: 1.55;
  color: var(--ip-text-muted);
  max-width: 62ch;
}
.ip-ie-grid{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
  align-items: stretch;
}
@media (max-width: 860px){
  .ip-ie-grid{ grid-template-columns: minmax(0, 1fr); }
}
.ip-ie-card{
  position: relative;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
  padding: 20px 20px 18px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: transform var(--ip-transition), box-shadow var(--ip-transition), border-color var(--ip-transition);
}
/* filo superiore: verde-ingresso vs inchiostro-uscita — sottile, non decorativo */
/* Le due vie si distinguono per semantica (ingresso = verde di stato, uscita =
   accento neutro), non per due tinte cablate con un override per il tema:
   --ip-success e --ip-primary si invertono già da sole. */
.ip-ie-card--import{ border-top: 3px solid var(--ip-success); }
.ip-ie-card--export{ border-top: 3px solid var(--ip-primary); }

/* Niente sollevamento al passaggio: la card non è un link (si entra dai
   pulsanti in fondo), e un hover da elemento cliccabile su un blocco che non
   risponde al clic è una promessa mancata. Stesso motivo per .ip-import-card. */
.ip-ie-card__head{
  display:flex;
  align-items:flex-start;
  gap: var(--ip-space-7);
  margin-bottom: 12px;
}
.ip-ie-card__icon{
  flex: 0 0 42px;
  width:42px; height:42px;
  border-radius: 10px;
  display:grid; place-items:center;
  font-size: var(--ip-text-lg);
  border: 1px solid var(--ip-border);
  background: var(--ip-bg);
}
.ip-ie-card--import .ip-ie-card__icon{ color: var(--ip-success-text); background: var(--ip-success-bg); border-color: color-mix(in srgb, var(--ip-success) 30%, transparent); }
.ip-ie-card--export .ip-ie-card__icon{ color: var(--ip-text); background: var(--ip-hover); }

.ip-ie-card__eyebrow{
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  margin: 1px 0 3px;
}
.ip-ie-card__title{
  font-family: var(--ip-display);
  font-size: 1.15rem;
  font-weight: 650;
  letter-spacing: -.015em;
  margin: 0;
  line-height: 1.25;
}
.ip-ie-card__desc{
  font-size: var(--ip-text-base);
  line-height: 1.55;
  color: var(--ip-text-muted);
  margin: 6px 0 0;
}
.ip-ie-card__meta{
  margin: 14px 0 0;
  /* Senza rientro laterale: "File attesi" / "Formati" partivano 12px più a
     destra del resto della card, un allineamento in più senza motivo. */
  padding: 12px 0 0;
  border-top: 1px dashed var(--ip-border-soft);
}
.ip-ie-card__meta-label{
  font-size: var(--ip-text-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
  margin: 0 0 8px;
}
.ip-ie-files{
  display:flex;
  flex-wrap:wrap;
  gap: var(--ip-space-3);
}
.ip-ie-file{
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  padding: 4px 8px;
  border-radius: 999px;
  border: 1px solid var(--ip-border);
  background: var(--ip-bg);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-ie-list{
  list-style:none;
  padding:0; margin:10px 0 0;
  display:grid; gap: var(--ip-space-3);
}
.ip-ie-list li{
  display:flex;
  gap: var(--ip-space-4);
  font-size: var(--ip-text-base);
  line-height: 1.45;
  color: var(--ip-text);
}
.ip-ie-list li i{
  margin-top: 2px;
  font-size: var(--ip-text-xs);
  /* La spunta dice "incluso": è informazione, quindi il gradino di testo
     (sopra 4.5:1 in entrambi i temi) e non il verde pieno, che in chiaro si
     ferma a 4.3:1. Con il token "-text" non serve più la regola per lo scuro. */
  color: var(--ip-success-text);
}

.ip-ie-card__actions{
  margin-top: auto;
  padding-top: 16px;
  display:flex;
  flex-wrap:wrap;
  gap: var(--ip-space-4);
  align-items:center;
}

/* Scorciatoie — secondarie, non in competizione con le due vie */
.ip-ie-shortcuts{
  margin-top: 18px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
  overflow:hidden;
}
.ip-ie-shortcuts__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: var(--ip-space-6);
  padding: 14px 18px;
  border-bottom: 1px solid var(--ip-border-soft);
  background: linear-gradient(to bottom, var(--ip-bg-card), var(--ip-bg));
}
.ip-ie-shortcuts__title{
  font-size: var(--ip-text-base);
  font-weight: 650;
  letter-spacing: -.01em;
  margin:0;
  display:flex; align-items:center; gap: var(--ip-space-4);
}
.ip-ie-shortcuts__title i{ color: var(--ip-text-muted); }
.ip-ie-shortcuts__hint{
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  margin:0;
  /* 64ch e non 36: la frase sta su una riga accanto al titolo quando lo
     spazio c'è; a 36 andava a capo a metà con mezza scheda vuota a fianco. */
  max-width: 64ch;
  text-align:right;
}
@media (max-width: 640px){
  .ip-ie-shortcuts__head{ flex-direction:column; align-items:flex-start; }
  .ip-ie-shortcuts__hint{ text-align:left; max-width:none; }
}
.ip-ie-shortcuts__body{
  display:grid;
  grid-template-columns: 1fr 1fr;
  gap:0;
}
@media (max-width: 640px){
  .ip-ie-shortcuts__body{ grid-template-columns: minmax(0, 1fr); }
}
.ip-ie-shortcut{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: var(--ip-space-6);
  padding: 14px 18px;
  text-decoration:none;
  color: inherit;
  border-right: 1px solid var(--ip-border-soft);
  transition: background var(--ip-transition);
}
.ip-ie-shortcut:last-child{ border-right: 0; }
@media (max-width: 640px){
  .ip-ie-shortcut{ border-right:0; border-bottom:1px solid var(--ip-border-soft); }
  .ip-ie-shortcut:last-child{ border-bottom:0; }
}
.ip-ie-shortcut:hover{ background: var(--ip-hover); text-decoration:none; }
.ip-ie-shortcut__left{
  display:flex; gap: var(--ip-space-6); align-items:center; min-width:0;
}
.ip-ie-shortcut__icon{
  flex:0 0 36px; width:36px; height:36px;
  border-radius: 8px;
  display:grid; place-items:center;
  background: var(--ip-bg);
  border:1px solid var(--ip-border);
  font-size: var(--ip-text-base);
  color: var(--ip-text-muted);
}
.ip-ie-shortcut__label{ font-size: var(--ip-text-base); font-weight:600; margin:0; line-height:1.2; }
.ip-ie-shortcut__sub{ font-size: var(--ip-text-sm); color:var(--ip-text-muted); margin:2px 0 0; line-height:1.3; }
.ip-ie-shortcut__arrow{ color: var(--ip-text-muted); font-size: var(--ip-text-sm); }
.ip-ie-foot{
  margin-top: 14px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  display:flex; gap: var(--ip-space-4); align-items:flex-start;
  line-height:1.5;
}
.ip-ie-foot i{ margin-top:2px; }

/* --- Form di importazione CSV (import/import_index) --- */
/* Import form — banchina di carico */
.ip-import-intro{
  margin: 2px 0 18px;
}
.ip-import-intro__eyebrow{
  font-size: var(--ip-text-xs); font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color: var(--ip-text-muted); margin:0 0 6px;
}
.ip-import-intro__title{
  font-family: var(--ip-display); font-size:1.35rem; font-weight:650; letter-spacing:-.015em; margin:0; line-height:1.25;
}
.ip-import-intro__desc{ font-size: var(--ip-text-base); line-height:1.55; color:var(--ip-text-muted); margin:8px 0 0; max-width:80ch; }
.ip-import-steps{
  display:flex; gap: var(--ip-space-6); margin-top:12px; flex-wrap:wrap;
}
/* I passi sono una sequenza da leggere, non comandi: in pillola con bordo e
   fondo card sembravano tre pulsanti accanto ai "Scarica il modello" veri. */
.ip-import-step{
  display:flex; gap: var(--ip-space-4); align-items:center; font-size: var(--ip-text-sm); color:var(--ip-text-muted);
}
.ip-import-step__n{
  flex:0 0 20px; width:20px; height:20px; border-radius:50%; display:grid; place-items:center;
  background: var(--ip-text); color: var(--ip-bg-card); font-size: var(--ip-text-xs); font-weight:700;
}
html[data-theme="dark"] .ip-import-step__n{ background: var(--ip-text); color: var(--ip-bg); }

/* Flusso a passi: una colonna sola, numerata nell'ordine di elaborazione.
   La griglia 2x2 si leggeva a righe (clienti, fatture / righe, pagamenti) e
   nascondeva la dipendenza fra i file; il binario a sinistra la rende visibile. */
.ip-import-flow{
  list-style:none; margin:0; padding:0 0 0 44px; position:relative;
  display:flex; flex-direction:column; gap: var(--ip-space-7);
}
.ip-import-flow::before{
  content:""; position:absolute; left:15px; top:18px; bottom:-26px;
  border-left:2px solid var(--ip-border);
}
.ip-import-card__n{
  position:absolute; left:-44px; top:14px; width:32px; height:32px; border-radius:50%;
  display:grid; place-items:center; font-size: var(--ip-text-sm); font-weight:700;
  background:var(--ip-bg-card); color:var(--ip-text-muted); border:2px solid var(--ip-border);
  transition: background var(--ip-transition), color var(--ip-transition), border-color var(--ip-transition);
}
.ip-import-card.is-active .ip-import-card__n,
.ip-import-card__n--final{ background:var(--ip-text); color:var(--ip-bg-card); border-color:var(--ip-text); }
.ip-import-card.is-active{ border-color:var(--ip-border-strong); }
.ip-import-card__titles{ min-width:0; }
.ip-import-card__title{
  font-size: var(--ip-text-md); font-weight:650; margin:0; line-height:1.25;
  display:flex; align-items:baseline; gap: var(--ip-space-4); flex-wrap:wrap;
}
.ip-import-card__needs{
  margin:0; display:flex; gap: var(--ip-space-3); align-items:flex-start;
  font-size: var(--ip-text-sm); line-height:1.45; color:var(--ip-text-muted);
}
.ip-import-card__needs i{ margin-top:3px; }
@media (max-width: 640px){
  .ip-import-flow{ padding-left:0; }
  .ip-import-flow::before{ display:none; }
  .ip-import-card__n{ position:static; width:26px; height:26px; flex:0 0 26px; margin:12px 0 -4px 14px; }
  .ip-import-card__head{ flex-wrap:wrap; }
}

.ip-import-card{
  position:relative; overflow:visible !important;
  background: var(--ip-bg-card); border:1px solid var(--ip-border); border-radius:var(--ip-radius);
  box-shadow: var(--ip-shadow-sm); overflow:hidden; display:flex; flex-direction:column;
  transition: box-shadow var(--ip-transition), border-color var(--ip-transition), transform var(--ip-transition);
}
.ip-import-card__head{
  display:flex; align-items:center; justify-content:space-between; gap: var(--ip-space-5);
  padding:12px 14px; border-bottom:1px solid var(--ip-border-soft);
  background: linear-gradient(to bottom, var(--ip-bg-card), var(--ip-bg));
  border-radius: var(--ip-radius) var(--ip-radius) 0 0;
}
.ip-import-card__left{ display:flex; align-items:center; gap: var(--ip-space-5); min-width:0; }
.ip-import-card__icon{
  flex:0 0 36px; width:36px; height:36px; border-radius:8px; display:grid; place-items:center;
  background:var(--ip-bg); border:1px solid var(--ip-border); font-size: var(--ip-text-md); color:var(--ip-text-muted);
}
.ip-import-card--clients .ip-import-card__icon{ color:#3d7a3e; background:#eef5ee; border-color:#d6e8d6; }
.ip-import-card--invoices .ip-import-card__icon{ color:#31708f; background:#edf3f7; border-color:#d4e4ed; }
.ip-import-card--items .ip-import-card__icon{ color:#5b4bc2; background:#f1eefe; border-color:#ddd6f5; }
.ip-import-card--payments .ip-import-card__icon{ color:#b65a1f; background:#fdf4e6; border-color:#f5dec2; }
html[data-theme="dark"] .ip-import-card--clients .ip-import-card__icon{ color:#7ec780; background:#1c271d; border-color:#2a3a2b; }
html[data-theme="dark"] .ip-import-card--invoices .ip-import-card__icon{ color:#7ab4d4; background:#192430; border-color:#233548; }
html[data-theme="dark"] .ip-import-card--items .ip-import-card__icon{ color:#b3a6ef; background:#211d33; border-color:#302a4a; }
html[data-theme="dark"] .ip-import-card--payments .ip-import-card__icon{ color:#e8a13d; background:#2e2414; border-color:#4a3518; }

.ip-import-card__file{
  font-family:var(--ip-mono); font-size: var(--ip-text-sm); font-weight:500; letter-spacing:-.01em; line-height:1.2;
  color:var(--ip-text-muted);
}
.ip-import-card__hint{ font-size: var(--ip-text-sm); color:var(--ip-text-muted); margin:2px 0 0; line-height:1.3;
}
.ip-import-badge{
  flex:0 0 auto;
  border:1px solid var(--ip-border);
}
.ip-import-badge.is-available{ background:#eef5ee; color:#3d7a3e; border-color:#cfe6cf; }
.ip-import-badge.is-missing{ background:var(--ip-bg); color:var(--ip-text-muted); }
html[data-theme="dark"] .ip-import-badge.is-available{ background:#1c271d; color:#7ec780; border-color:#2a3a2b; }

.ip-import-card__body{ padding:14px; display:flex; flex-direction:column; gap: var(--ip-space-5); flex:1; }
.ip-import-drop{
  position:relative; border:1.5px dashed var(--ip-border-strong); border-radius:8px; background:var(--ip-bg);
  padding:16px 14px; display:flex; align-items:center; gap: var(--ip-space-6); cursor:pointer;
  transition: border-color var(--ip-transition), background var(--ip-transition), box-shadow var(--ip-transition);
}
.ip-import-drop:hover{ border-color:var(--ip-border-hover); background:var(--ip-hover); }
.ip-import-drop.is-dragover{ border-color:var(--ip-text); background:var(--ip-primary-soft); border-style:solid; box-shadow:var(--ip-shadow-sm); }
.ip-import-drop__icon{
  flex:0 0 38px; width:38px; height:38px; border-radius:8px; display:grid; place-items:center;
  background:var(--ip-bg-card); border:1px solid var(--ip-border); font-size: var(--ip-text-md); color:var(--ip-text-muted);
}
.ip-import-drop__text{ min-width:0; flex:1; }
.ip-import-drop__title{ font-size: var(--ip-text-base); font-weight:600; margin:0; line-height:1.25; }
.ip-import-drop__title span{ font-family:var(--ip-mono); font-size: var(--ip-text-sm); font-weight:600; }
.ip-import-drop__sub{ font-size: var(--ip-text-sm); color:var(--ip-text-muted); margin:3px 0 0; line-height:1.35; }
.ip-import-drop input[type="file"]{
  position:absolute; inset:0; opacity:0; cursor:pointer; width:100%; height:100%;
}
.ip-import-chosen{
  display:none; align-items:center; gap: var(--ip-space-4); font-size: var(--ip-text-sm); line-height:1.3; padding:8px 10px;
  background:var(--ip-bg-card); border:1px solid var(--ip-border); border-radius:8px;
}
.ip-import-chosen.is-visible{ display:flex; }
.ip-import-chosen__name{ font-family:var(--ip-mono); font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ip-import-chosen__meta{ color:var(--ip-text-muted); white-space:nowrap; }
.ip-import-chosen__clear{
  margin-left:auto; flex:0 0 auto; border:0; background:transparent; color:var(--ip-text-muted);
  width:28px; height:28px; border-radius:50%; display:grid; place-items:center; cursor:pointer;
}
.ip-import-chosen__clear:hover{ background:var(--ip-hover); color:var(--ip-text); }

.ip-import-card__foot{
  display:flex; align-items:center; gap: var(--ip-space-4); padding:10px 14px; background:var(--ip-bg);
  border-top:1px dashed var(--ip-border-soft); border-radius:0 0 var(--ip-radius) var(--ip-radius);
  min-height:44px;
}
.ip-import-card__foot.is-empty{ justify-content:center; color:var(--ip-text-muted); font-size: var(--ip-text-sm); }
.ip-import-check{ display:flex; align-items:center; gap: var(--ip-space-4); font-size: var(--ip-text-base); margin:0; cursor:pointer; user-select:none; }
.ip-import-check input{ accent-color: var(--ip-text); }

.ip-import-actions{
  position:relative; margin:var(--ip-space-7) 0 0 44px; display:flex; flex-wrap:wrap; gap: var(--ip-space-5); align-items:center;
  background:var(--ip-bg-card); border:1px solid var(--ip-border); border-radius:var(--ip-radius);
  padding:14px 16px; box-shadow:var(--ip-shadow-sm);
}
.ip-import-actions .ip-import-card__n{ top:50%; margin-top:-16px; }
.ip-import-actions__summary{ flex:1 1 260px; min-width:0; }
.ip-import-actions__title{ margin:0; font-weight:650; font-size: var(--ip-text-md); }
.ip-import-actions__hint{ font-size: var(--ip-text-sm); color:var(--ip-text-muted); margin:2px 0 0; line-height:1.45; }
.ip-import-actions__buttons{ display:flex; gap: var(--ip-space-4); margin-left:auto; }
.ip-import-chosen__icon{ color: var(--ip-text-muted); }
@media (max-width:640px){
  .ip-import-actions{ margin-left:0; }
  .ip-import-actions .ip-import-card__n{ display:none; }
  .ip-import-actions__buttons{ width:100%; }
  .ip-import-actions__buttons .btn{ flex:1; }
}

/* --- Esportazione in tre passi (exports/index) --- */
/* Esportazione — tre passi, aria intorno alle scelte (polish) */
.ip-export-intro{
  max-width: 720px; margin: 2px 0 18px;
}
.ip-export-intro__eyebrow{
  font-size: var(--ip-text-xs); font-weight:700; letter-spacing:.09em; text-transform:uppercase;
  color:var(--ip-text-muted); margin:0 0 6px;
}
.ip-export-intro__title{
  font-family:var(--ip-display); font-size:1.35rem; font-weight:650; letter-spacing:-.02em; margin:0; line-height:1.2;
}
.ip-export-intro__desc{ font-size: var(--ip-text-base); line-height:1.6; color:var(--ip-text-muted); margin:8px 0 0; max-width:58ch; }

/* — gruppi — sottrazione: niente cornici annidate, solo titolo + griglia ariosa */
.ip-export-groups{ display:grid; gap: var(--ip-space-9); }
.ip-export-group__title{
  font-size: var(--ip-text-xs); font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color:var(--ip-text-muted); margin:0 0 10px; display:flex; align-items:center; gap: var(--ip-space-3);
}
.ip-export-group__title i{ font-size: var(--ip-text-xs); opacity:.9; }
/* Colonne = numero di card nel gruppo (fino a 4): una riga da 2 card non deve
   lasciare meta' griglia vuota come faceva con 4 colonne fisse per tutti i
   gruppi. --cols-N e' scelto in PHP da count($g['types']), non per gruppo. */
.ip-export-group__grid{
  display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--ip-space-5);
}
.ip-export-group__grid--cols-1{ grid-template-columns: minmax(0,1fr); }
.ip-export-group__grid--cols-2{ grid-template-columns: repeat(2, minmax(0,1fr)); }
.ip-export-group__grid--cols-3{ grid-template-columns: repeat(3, minmax(0,1fr)); }
@media (max-width: 1100px){
  .ip-export-group__grid, .ip-export-group__grid--cols-3{ grid-template-columns: repeat(2, minmax(0,1fr)); }
  .ip-export-group__grid--cols-1{ grid-template-columns: minmax(0,1fr); }
}
@media (max-width: 580px){
  .ip-export-group__grid, .ip-export-group__grid--cols-1, .ip-export-group__grid--cols-2, .ip-export-group__grid--cols-3{ grid-template-columns: minmax(0, 1fr); }
}

/* card tipo — singolo bordo, mai doppio */
.ip-export-group, .ip-export-group__grid{ border:0; background:transparent; }
.ip-export-type{
  position:relative; display:flex; cursor:pointer; margin:0;
  border:1px solid var(--ip-border); border-radius:8px; background:var(--ip-bg-card);
  padding:13px 13px 12px; min-height:110px; outline:none; box-shadow:none;
  transition: border-color var(--ip-transition), background var(--ip-transition);
}
.ip-export-type:hover{ border-color:var(--ip-border-strong); background:var(--ip-hover); }
/* Selezionata: bordo nell'accento dell'app e fondo hover, come la voce attiva
   della subnav. Prima erano grigi cablati per tema, e in scuro un blu-ardesia
   (#2a2e3a) che non compariva in nessun'altra parte dell'interfaccia. */
.ip-export-type:has(input:checked){
  border-color: var(--ip-primary); background: var(--ip-hover); color: var(--ip-text);
  box-shadow:none; outline:none;
}
/* La radio vera è nascosta: il focus da tastiera va mostrato sulla card.
   Prima :focus-within azzerava ogni anello e con Tab non si vedeva dove si
   era. */
.ip-export-type:has(input:focus-visible){ box-shadow: var(--ip-shadow-focus); }
.ip-export-type:has(input:checked) .ip-export-type__title{ color: var(--ip-text); }
.ip-export-type:has(input:checked) .ip-export-type__title i{ opacity:.85; }
.ip-export-type:has(input:checked) .ip-export-type__hint{ color: var(--ip-text-muted); opacity:1; }
html[data-theme="dark"] .ip-export-type:has(input:checked) .ip-export-type__hint{ color: #a8adb8; }
.ip-export-type:has(input:checked) .ip-export-badge{ background: var(--ip-bg-card); color: var(--ip-text-muted); border-color: var(--ip-border); }
html[data-theme="dark"] .ip-export-type:has(input:checked) .ip-export-badge{ background: #1e1e23; color: #9b9ba5; border-color: #33333b; }
.ip-export-type:has(input:checked) .ip-export-badge--annual{ background:#fdf4e6; color:#8a5a00; border-color:#f5dec2; }
html[data-theme="dark"] .ip-export-type:has(input:checked) .ip-export-badge--annual{ background:#2e2414; color:#e8a13d; border-color:#4a3518; }
.ip-export-type:has(input:checked)::before, .ip-export-type:has(input:checked)::after{
  box-shadow:none; outline:none;
}
.ip-export-type:has(input:checked)::after{
  content:"\f00c"; font-family: FontAwesome; position:absolute; top:10px; right:10px;
  width:18px; height:18px; border-radius:50%; display:grid; place-items:center;
  background:var(--ip-text); color:var(--ip-bg-card); font-size: var(--ip-text-2xs); line-height:1; border:0; box-shadow: 0 1px 3px rgba(0,0,0,.18);
}
html[data-theme="dark"] .ip-export-type:has(input:checked)::after{ background:var(--ip-text); color:var(--ip-bg); }
.ip-export-type input{ position:absolute; opacity:0; pointer-events:none; }
.ip-export-type__body{ display:flex; flex-direction:column; gap: var(--ip-space-3); width:100%; padding-right:16px; border:0; outline:0; box-shadow:none; background:transparent; }
.ip-export-type__title{ font-size: var(--ip-text-base); font-weight:650; line-height:1.25; display:flex; align-items:center; gap: var(--ip-space-3); }
.ip-export-type__title i{ font-size: var(--ip-text-base); color:var(--ip-text-muted); flex:0 0 auto; }
.ip-export-type:has(input:checked) .ip-export-type__title i{ color:var(--ip-text); }
.ip-export-type__hint{ font-size: var(--ip-text-sm); line-height:1.5; color:var(--ip-text-muted); }
.ip-export-type.is-disabled{ opacity:.48; cursor:not-allowed; }
.ip-export-type.is-disabled:hover{ border-color:var(--ip-border); background:var(--ip-bg-card); }
/* Ossatura (corpo, peso, spaziatura, padding, raggio) dalla regola condivisa
   dei badge in fondo al foglio: qui solo colore e bordo, altrimenti questa
   regola ridichiarava misure proprie — raggio 999px compreso — e il badge
   della stessa famiglia diventava una pillola dentro una card ad angoli 8px. */
.ip-export-badge{
  margin-top:8px;
  border:1px solid var(--ip-border); background:var(--ip-bg); color:var(--ip-text-muted);
  width:fit-content;
}
.ip-export-badge--annual{ background:#fdf4e6; color:#8a5a00; border-color:#f5dec2; }
html[data-theme="dark"] .ip-export-badge--annual{ background:#2e2414; color:#e8a13d; border-color:#4a3518; }
/* Controlli del flusso: un'icona + due etichette in una riga, il timbro dello
   stato come elemento flessibile che descrive l'azione. */
.ip-flow-control { display: flex; align-items: center; gap: var(--ip-space-6); }

/* Periodo.
   Qui c'erano le regole scritte per questa pagina che però non erano limitate
   al suo contenitore: `.ip-doc-grid` con 2 colonne, gap 14px e max-width 720px,
   più .ip-doc-field/.ip-doc-label/.form-control ridisegnati. Vincevano su tutto
   il resto del foglio (stessa specificità, più avanti nel file), quindi ogni
   griglia di campi dell'app era larga al massimo 720px e non collassava a una
   colonna sotto i 580px: la media query della regola generale stava 4000 righe
   prima e veniva sovrascritta. Le misure ora stanno una volta sola nella
   sezione dei campi; qui resta solo la larghezza di lettura di questa pagina. */
.ip-doc-grid--narrow{ max-width:720px; }
.ip-doc-field.is-disabled{ opacity:.5; }
.ip-doc-field.is-disabled .form-control{ background:var(--ip-bg); cursor:not-allowed; }
.ip-export-shortcuts{
  margin-top:16px; display:flex; flex-wrap:wrap; gap: var(--ip-space-4); align-items:center;
  padding:11px 12px; background:var(--ip-bg); border:1px dashed var(--ip-border); border-radius:8px;
}
.ip-export-shortcuts .ip-doc-label{ margin-right:2px; white-space:nowrap; font-size: var(--ip-text-xs); letter-spacing:.06em; text-transform:uppercase; color:var(--ip-text-muted); }
.ip-tag-button{
  font-size: var(--ip-text-sm); font-weight:600; padding:6px 11px; border-radius:999px;
  border:1px solid var(--ip-border); background:var(--ip-bg-card); color:var(--ip-text);
  transition: background var(--ip-transition), border-color var(--ip-transition), color var(--ip-transition);
}
.ip-tag-button:hover{ background:var(--ip-hover); border-color:var(--ip-border-strong); }
.ip-tag-button.is-active{ background:var(--ip-text); color:var(--ip-bg-card); border-color:var(--ip-text); }
html[data-theme="dark"] .ip-tag-button.is-active{ background:var(--ip-text); color:var(--ip-bg); }
#export_annual_note{
  border-radius:8px; border:1px solid #cfe6cf; background:#eef5ee; color:#2f5e30;
  padding:10px 12px; font-size: var(--ip-text-sm); line-height:1.45; margin-bottom:12px;
}
html[data-theme="dark"] #export_annual_note{ background:#1c271d; border-color:#2a3a2b; color:#7ec780; }
#export_annual_note[hidden]{ display:none !important; }

/* Formato — stessa card, meno alta */
.ip-format-grid{ display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--ip-space-5); }
@media (max-width:860px){ .ip-format-grid{ grid-template-columns: minmax(0, 1fr); } }
.ip-format-grid .ip-export-type{ min-height:104px; }

/* --- Import: modello CSV, tracciato atteso e storico --- */
.ip-mono-inline{ font-family: var(--ip-mono); font-size: .92em; }

.ip-import-intro__order{
  display:flex; gap: var(--ip-space-4); align-items:flex-start;
  margin:12px 0 0; padding:9px 11px; max-width:72ch;
  font-size: var(--ip-text-sm); line-height:1.5; color:var(--ip-text-muted);
  background:var(--ip-bg); border:1px dashed var(--ip-border); border-radius:8px;
}
.ip-import-intro__order i{ margin-top:2px; opacity:.8; }

/* Riga "tracciato": il modello da scaricare e le intestazioni attese, cioe' le
   due informazioni che prima esistevano solo nel codice sorgente. */
.ip-import-card__tracciato{
  display:flex; align-items:center; gap: var(--ip-space-5); flex-wrap:wrap;
}
.ip-import-template{
  display:inline-flex; align-items:center; gap: var(--ip-space-3);
  font-size: var(--ip-text-sm); font-weight:600; text-decoration:none;
  padding:4px 9px; border-radius:999px;
  color:var(--ip-text); background:var(--ip-bg-card);
  border:1px solid var(--ip-border);
  transition: border-color var(--ip-transition), background var(--ip-transition);
}
.ip-import-template:hover{ border-color:var(--ip-border-strong); background:var(--ip-hover); color:var(--ip-text); }
.ip-import-headers{ font-size: var(--ip-text-sm); color:var(--ip-text-muted); min-width:0; }
.ip-import-headers summary{ cursor:pointer; user-select:none; }
.ip-import-headers summary::marker{ color:var(--ip-text-muted); }
.ip-import-headers p{
  margin:6px 0 0; font-family:var(--ip-mono); font-size: var(--ip-text-sm); line-height:1.6;
  word-break:break-word; color:var(--ip-text-muted);
}
.ip-import-card__filemeta{
  margin-left: var(--ip-space-3); font-size: var(--ip-text-sm); color:var(--ip-text-muted); font-family:var(--ip-mono);
}

.ip-import-filetag{
  display:inline-block; margin:0 4px 3px 0; padding:2px 7px;
  font-family:var(--ip-mono); font-size: var(--ip-text-sm); line-height:1.5;
  background:var(--ip-bg); border:1px solid var(--ip-border); border-radius:5px;
  color:var(--ip-text-muted);
}
.ip-import-outcome{
  display:inline-flex; align-items:center; gap: var(--ip-space-2);
  font-size: var(--ip-text-sm); font-weight:600; padding:3px 8px; border-radius:999px;
  border:1px solid var(--ip-border);
}
.ip-import-outcome.is-ok{ background:#eef5ee; color:#3d7a3e; border-color:#cfe6cf; }
.ip-import-outcome.is-error{ background:#fdf0ee; color:#a8442c; border-color:#f2d5cd; }
html[data-theme="dark"] .ip-import-outcome.is-ok{ background:#1c271d; color:#7ec780; border-color:#2a3a2b; }
html[data-theme="dark"] .ip-import-outcome.is-error{ background:#2c1c19; color:#e08a6d; border-color:#452822; }

/* Avviso dentro una griglia di campi (es. CIG senza numero d'ordine in
   invoices/partial_einvoice_fields.php): occupa tutta la riga invece di
   stringersi in una colonna fra due campi. */
.ip-doc-grid > .ip-doc-alert {
  grid-column: 1 / -1;
  margin: 0;
}

/* -----------------------------------------------------------------------
   Liste documenti: tabelle che stanno nel contenitore
   -----------------------------------------------------------------------
   .ip-table-card .table-responsive ha overflow: visible di proposito (i menu
   Opzioni non devono essere tagliati e l'intestazione è sticky rispetto alla
   pagina), quindi una tabella più larga del contenitore faceva scorrere in
   orizzontale l'intera pagina: a 1200 px le liste fatture e costi erano
   larghe 1.030–1.050 px in 929 px utili. Invece di riattivare lo scroll
   interno si toglie la larghezza superflua. */

/* Pulsante Opzioni: solo icona. La parola resta nel pulsante (font-size 0)
   e quindi nel nome accessibile; la colonna passa da ~117 a ~56 px. */
.ip-table-card .options.btn-group > .dropdown-toggle {
  font-size: 0;
  min-width: 40px;
  min-height: 32px;
  padding: 6px 10px;
}
.ip-table-card .options.btn-group > .dropdown-toggle > .fa {
  font-size: var(--ip-text-md);
}
.ip-table-card .options.btn-group > .dropdown-toggle::after {
  margin-left: 6px;
  border-top-width: 5px;
  border-right-width: 4px;
  border-left-width: 4px;
  vertical-align: 2px;
}
/* Intestazione della colonna Opzioni: visivamente nascosta ma letta dagli
   screen reader, così non è lei a dettare la larghezza della colonna. */
.ip-table-card table:has(td .options.btn-group) > thead > tr > th:last-child {
  width: 1%;
  font-size: 0;
}

/* Esito SdI nella lista: il badge può andare a capo invece di allargare la
   colonna, e lo stato "importata" è una nota discreta (vedi invoices/index). */
.ip-sdi-badge {
  white-space: normal;
  max-width: 14em;
  text-align: left;
}
.ip-sdi-note {
  display: block;
  margin-top: 2px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-sdi-note .fa {
  font-size: var(--ip-text-xs);
  margin-right: 2px;
}

/* Tracciamento email nella lista: sotto il numero, come la nota SdI, così
   l'icona non allarga la colonna. Il verde segnala l'apertura, che è
   l'informazione cercata; "non aperta" resta discreto perché può voler dire
   solo che il programma di posta ha bloccato le immagini. */
.ip-email-note {
  display: block;
  margin-top: 2px;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
  white-space: nowrap;
}
.ip-email-note .fa {
  font-size: var(--ip-text-xs);
  margin-right: 2px;
}
.ip-email-note--opened {
  color: var(--ip-success-text);
}

/* Badge accanto a un numero documento (XML nella lista costi): sotto il
   numero, non di fianco — i numeri dei fornitori sono lunghi
   (2026F001340825) e il badge in linea allargava la colonna. */
.ip-num-tag {
  display: table;
  margin-top: 3px;
}

/* KPI positivo (IVA a credito). */
.card-value.card-value-success {
  color: var(--ip-success, #2e7d32);
}

/* Scheda compatta su telefono: cella azioni (lista costi, che non ha una
   colonna Stato) al posto dello stato, accanto al fornitore; la partita IVA
   del fornitore resta nella vista tabellare. */
@media (max-width: 575.98px) {
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="actions"] {
    grid-column: 2;
    grid-row: 2;
    text-align: right;
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="client"] small,
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="client"] br {
    display: none;
  }
  /* Hover di .table-hover: Bootstrap lo disegna cella per cella con
     un'ombra interna, che nella scheda diventava una serie di rettangoli
     grigi a macchie. In modalità scheda lo stato vale per la riga intera. */
  .ip-table-card table.ip-table-card-collapse.table-hover > tbody > tr > * {
    box-shadow: none;
  }
  .ip-table-card table.ip-table-card-collapse.table-hover > tbody > tr:hover {
    background: var(--ip-hover);
  }
  .ip-table-card table.ip-table-card-collapse tbody td[data-cell="number"] .ip-num-tag {
    display: inline-block;
    margin: 0 0 0 6px;
    vertical-align: 1px;
  }
}

/* Pagine di import XML (fatture emesse e ricevute, quietanze F24): il campo
   file è la zona su cui si trascina, quindi alto e tratteggiato, e i testi
   stanno a misura di lettura.

   Il tratteggio sta sul controllo e non su un contenitore attorno: prima un
   .ip-import-drop avvolgeva etichetta, controllo e aiuto e disegnava un
   secondo riquadro tratteggiato attorno a quello vero. Sembrava una zona di
   rilascio e non lo era — il drop lo ascolta .ip-file__control — e in più
   l'etichetta del campo finiva al centro del riquadro come un titolo. */
.ip-file-drop {
  /* Larghezza piena del corpo, non 640px: la zona tratteggiata è il bersaglio
     del trascinamento, quindi più è grande più è facile centrarla, e a 640px
     lasciava vuota la metà destra del corpo mentre l'aiuto andava a capo
     cinque volte su una frase sola. */
  max-width: none;
}
/* ...ma non nelle card dell'import CSV, dove la zona deve riempire la card:
   questa regola, pensata per le pagine XML, la lasciava a metà larghezza. */
.ip-import-card .ip-import-drop { max-width: none; }
.ip-file-drop .ip-file__control {
  min-height: 72px;
  border-style: dashed;
  /* Nome e pulsante restano un gruppo solo, centrato nella zona: a tutta
     larghezza il nome stava a sinistra e "Scegli file" a un metro di
     distanza, e il campo leggeva come una barra vuota. */
  justify-content: center;
}
.ip-file-drop .ip-file__name {
  /* flex-grow 0: altrimenti il nome si prende tutto lo spazio libero e
     risospinge il pulsante a destra, annullando la centratura. */
  flex: 0 1 auto;
}
.ip-file.is-dragover .ip-file__control {
  border-color: var(--ip-primary);
  background: var(--ip-primary-soft);
}
/* Ora che l'aiuto sta su una riga piena serve una misura di lettura: oltre
   80ch una frase da 200 caratteri diventa una riga che attraversa tutto lo
   schermo e si rilegge con fatica. */
.ip-file-drop .ip-field-help { max-width: 80ch; }

/* Due card affiancate: a sinistra il gesto (il campo, la categoria), a destra
   cosa succede al caricamento. Su una riga sola le istruzioni finivano sotto
   il campo e la card del gesto si allungava di quattro righe mentre metà
   pagina restava vuota; di fianco leggono come il riquadro d'aiuto che sono.
   La colonna di destra è stretta e fissa (340px): è testo da consultare, non
   deve competere con il campo. Sotto i 992px le card tornano una sotto
   l'altra, il gesto prima e le istruzioni dopo. */
.ip-import-layout form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ip-space-7);
  align-items: start;
  /* Oltre i 1600px il campo diventerebbe una scatola vuota lunga un metro e
     mezzo: la coppia di card si ferma lì e lo spazio che resta è margine,
     non una zona di rilascio più larga di quanto serva. */
  max-width: 1600px;
}
/* Il distacco fra le due card lo dal gap della griglia; il margine che
   .ip-doc-section porta con sé serve quando le card sono impilate, dove
   separa il gesto dalle istruzioni invece di sommare spazio al gap. */
.ip-import-layout .ip-doc-section { margin-bottom: 0; }
@media (min-width: 992px) {
  .ip-import-layout form { grid-template-columns: minmax(0, 1fr) 340px; }
}

.ip-import-notes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* 12px fra una nota e l'altra, contro i 6,5px che separano due righe della
     stessa nota: a 8px i due ritmi si assomigliavano e le quattro voci
     leggevano come un paragrafo unico spezzato a caso. */
  gap: var(--ip-space-6);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  line-height: 1.5;
}
/* Spunta appesa al margine sinistro e testo rientrato di 24px: nella card da
   340px ogni nota va a capo, e con l'icona in linea col testo la seconda riga
   riparte da sotto la spunta invece che dal testo. Il rientro vale per tutto
   il blocco, quindi anche un <strong> o un <code> dentro la nota resta dentro
   la stessa colonna di testo; con una griglia, invece, testo ed elemento
   diventerebbero due celle affiancate e la riga si spezzerebbe. */
.ip-import-notes li {
  position: relative;
  padding-left: 24px;
  /* A capo più equilibrati dove il browser lo supporta: senza, l'ultima riga
     resta di una o due parole e l'elenco sembra una scaletta. */
  text-wrap: pretty;
}
.ip-import-notes .fa {
  position: absolute;
  left: 0;
  top: 2px;
  width: 16px;
  /* Centrata nella colonna dell'icona, così le spunte formano una fila
     verticale pulita invece di dipendere dalla larghezza del glifo. */
  text-align: center;
  color: var(--ip-success, #468847);
}
.ip-import-category {
  max-width: 320px;
  margin-top: 16px;
}

/* -----------------------------------------------------------------------------
   37. Controllo di gestione (Bilancino)
   -------------------------------------------------------------------------- */

/* Navigazione fra le cinque schermate del modulo. Sono link a pagine, non
   linguette: il piano dei conti e il budget si modificano, il report si legge.
   La grafica però è quella della barra schede (.ip-tabs, sezione 37): prima
   qui c'erano voci piatte con una sottolineatura da 2px sull'attiva e un
   bordo solo sotto, così "Controllo di gestione" e "Impostazioni" sembravano
   due componenti diversi pur facendo la stessa cosa. Ora il contenitore è la
   stessa pastiglia bordata e la voce attiva è piena, quindi si vede a colpo
   d'occhio anche dove sta il modulo. */
.ip-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-1);
  /* Nessun margine orizzontale: la barra sta dentro #content, che ha già il
     suo inset. Serve solo lo stacco dai numeri sotto. */
  margin: 0 0 18px;
  padding: 4px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card);
}
.ip-subnav__item {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  padding: 7px 14px;
  border-radius: var(--ip-radius-sm);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-base);
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--ip-transition), color var(--ip-transition);
}
.ip-subnav__item .fa { font-size: var(--ip-text-sm); opacity: .8; }
.ip-subnav__item:hover,
.ip-subnav__item:focus-visible {
  background: var(--ip-hover);
  color: var(--ip-text);
}
/* Voce attiva piena, come la scheda selezionata. Il testo usa il token
   "on-primary" e non il bianco fisso: in tema scuro --ip-primary si ribalta
   in chiaro e il bianco sarebbe bianco su bianco. */
.ip-subnav__item.is-active {
  background: var(--ip-primary);
  color: var(--ip-on-primary);
}
.ip-subnav__item.is-active .fa { opacity: 1; }
/* Focus da tastiera: un reset globale toglie l'outline e qui non c'era altro,
   quindi scorrendo con Tab le cinque voci non mostravano dove si era. Stesso
   anello di .ip-tabs. */
.ip-subnav__item:focus-visible {
  outline: 0;
  box-shadow: var(--ip-shadow-focus);
  color: var(--ip-text);
}
.ip-subnav__item.is-active:focus-visible { color: var(--ip-on-primary); }
/* Schermo stretto: una riga sola che scorre, non tre righe, con le stesse
   misure ridotte di .ip-tabs. La barra di scorrimento è nascosta: a dire
   che ce n'è ancora sono il taglio dell'ultima voce e l'ombra sul bordo, e
   lo script in _tabs centra l'attiva. */
@media (max-width: 767.98px) {
  .ip-subnav {
    flex-wrap: nowrap;
    padding: 3px;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* Il taglio dell'ultima voce non basta sempre: a 375px la terza voce del
       Controllo di gestione finisce esattamente sul bordo e le due dopo non
       si intuiscono. Un'ombra sul lato da cui c'è ancora contenuto lo dice
       senza script: le due ombre stanno ferme sui bordi (scroll), le due
       coperture nel colore della barra scorrono col contenuto (local) e le
       nascondono quando si è arrivati in fondo a quel lato. */
    background:
      linear-gradient(to right, var(--ip-bg-card) 40%, transparent) left center / 28px 100% no-repeat local,
      linear-gradient(to left, var(--ip-bg-card) 40%, transparent) right center / 28px 100% no-repeat local,
      linear-gradient(to right, color-mix(in srgb, var(--ip-text) 22%, transparent), transparent) left center / 14px 100% no-repeat scroll,
      linear-gradient(to left, color-mix(in srgb, var(--ip-text) 22%, transparent), transparent) right center / 14px 100% no-repeat scroll,
      var(--ip-bg-card);
  }
  .ip-subnav::-webkit-scrollbar { display: none; }
  .ip-subnav__item { flex: 0 0 auto; white-space: nowrap; padding: 6px 10px; font-size: var(--ip-text-sm); }
  .ip-subnav::-webkit-scrollbar-track { background: transparent; }
  .ip-subnav::-webkit-scrollbar-thumb { background: var(--ip-border-soft); border-radius: 999px; }
}

/* Barra degli strumenti sopra una tabella (importa piano, avvisi, filtri
   locali alla schermata): sta su una riga sola e non si confonde con la
   testata della card. */
.ip-management-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ip-space-5);
  margin-bottom: 14px;
}
.ip-bilancino-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-5);
}

/* Quattro numeri in testa al report. Riquadri distinti e non una striscia
   unica: margine ed EBITDA sono risultati, ricavi e cassa sono grandezze di
   partenza, e il bordo li separa senza bisogno di dirlo a parole. */
.ip-bilancino-kpi {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--ip-space-6);
  margin-bottom: 18px;
}
.ip-bilancino-kpi__card {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-2);
  padding: 14px 16px;
  border: 1px solid var(--ip-border);
  border-left: 3px solid var(--ip-border-strong);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card);
  box-shadow: var(--ip-shadow-sm);
}
.ip-bilancino-kpi__card.is-revenue { border-left-color: var(--ip-success); }
.ip-bilancino-kpi__card.is-margin  { border-left-color: var(--ip-primary); }
.ip-bilancino-kpi__card.is-ebitda  { border-left-color: var(--ip-warning); }
.ip-bilancino-kpi__card.is-cash    { border-left-color: var(--ip-info, #3A87AD); }
/* La perdita è l'informazione più importante della pagina: il rosso va sul
   bordo e sulla cifra, non su tutta la card, che diventerebbe illeggibile. */
.ip-bilancino-kpi__card.is-negative { border-left-color: var(--ip-danger); }
.ip-bilancino-kpi__card.is-negative .ip-bilancino-kpi__value { color: var(--ip-danger-text); }
.ip-bilancino-kpi__label {
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ip-text-muted);
  line-height: 1.3;
}
.ip-bilancino-kpi__value {
  font-family: var(--ip-display);
  font-size: var(--ip-text-3xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
  line-height: 1.15;
  color: var(--ip-text);
}
.ip-bilancino-kpi__note {
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}

/* Tabella del conto economico: dodici colonne mensili più nome e totale. Il
   contenitore .table-responsive la fa scorrere in orizzontale, ma la colonna
   della voce resta agganciata a sinistra: senza, scorrendo verso dicembre non
   si sa più quale riga si sta leggendo. */
/* Il suggerimento serve finché la tabella non entra nella colonna; sopra i
   1640px di viewport (≈1364 di contenuto) ci sta tutta. */
@media (min-width: 1640px) {
  .ip-bilancino-scroll-hint { display: none; }
}
.ip-bilancino {
  width: 100%;
  min-width: 1080px;
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--ip-text-sm);
}
.ip-bilancino th,
.ip-bilancino td {
  padding: 6px 8px;
  vertical-align: baseline;
  border: 0;
}
.ip-bilancino thead th {
  padding-bottom: 8px;
  border-bottom: 2px solid var(--ip-border-strong);
  color: var(--ip-text-muted);
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  white-space: nowrap;
}
.ip-bilancino__name {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 230px;
  text-align: left;
  font-weight: 400;
  background: var(--ip-bg-card);
}
.ip-bilancino thead .ip-bilancino__name { z-index: 3; }
.ip-bilancino__code {
  width: 58px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ip-bilancino__month,
.ip-bilancino__total {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ip-bilancino__total {
  width: 110px;
  font-weight: 600;
}
/* Lo zero resta indietro, ma deve leggersi: --ip-border-strong sul fondo
   scuro stava a 1,9:1 e la cella sembrava vuota. Una tinta di --ip-text-muted
   tiene il passo arretrato in entrambi i temi senza scendere sotto il 3:1. */
.ip-bilancino__month.is-zero { color: color-mix(in srgb, var(--ip-text-muted) 75%, transparent); }
.ip-bilancino__month.is-negative,
.ip-bilancino__total.is-negative { color: var(--ip-danger-text); }
.ip-bilancino__month.is-good,
.ip-bilancino__total.is-good { color: var(--ip-success-text); }
.ip-bilancino__month.is-bad,
.ip-bilancino__total.is-bad { color: var(--ip-danger-text); }

/* Intestazione di sezione (RICAVI, COSTI VARIABILI...) in banda scura: è la
   cesura che rende leggibile un conto economico scalare, e senza di essa le
   venti righe sembrano un elenco unico. */
.ip-bilancino__section > th {
  padding: 7px 8px;
  background: var(--ip-primary);
  color: var(--ip-bg-card);
  font-size: var(--ip-text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .08em;
  text-align: left;
}
/* La banda di sezione occupa tutte le colonne: da sola non può restare
   agganciata (è più larga del contenitore, quindi sticky non ha spazio per
   spostarla) e scorrendo verso dicembre resterebbe una striscia vuota senza
   dire più in che sezione ci si trova. L'etichetta invece è un inline-block
   stretto e si ferma al bordo sinistro del contenitore. */
.ip-bilancino__band {
  display: inline-block;
  position: sticky;
  left: 8px;
}
/* In tema scuro --ip-primary è quasi bianco: la banda "scura" diventava una
   striscia grigio chiaro, la riga più luminosa della pagina, che strappava
   l'occhio dai numeri. Qui la cesura la fa un fondo appena più chiaro della
   card con il testo pieno: stessa funzione, senza abbagliare. */
html[data-theme="dark"] .ip-bilancino__section > th {
  background: var(--ip-hover);
  color: var(--ip-text);
}

/* Assegna voci: voce, opzione e pulsante in una barra sopra l'elenco. Non è
   sticky di proposito: agganciata sotto l'headerbar veniva spostata verso il
   basso già a pagina ferma e copriva l'intestazione della tabella. Stare in
   cima basta a risolvere il problema vero (il pulsante era sotto tutte le
   righe), e il conteggio accanto conferma cosa si sta per assegnare. */
.ip-assign-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--ip-space-3) var(--ip-space-5);
  margin: 0 0 var(--ip-space-4);
  padding: 0 0 12px;
  border-bottom: 1px solid var(--ip-border);
}
.ip-assign-bar__field { flex: 1 1 240px; max-width: 340px; }
.ip-assign-bar__field .ip-doc-label { margin-bottom: 4px; }
.ip-assign-bar__check { margin: 0 0 8px; }
.ip-assign-bar__count {
  margin: 0 0 8px auto;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  font-variant-numeric: tabular-nums;
}
.ip-assign-bar .btn { margin-bottom: 2px; }
/* Le tabelle di assegnazione riusano la tipografia del Bilancino ma hanno
   cinque colonne, non quindici: il min-width da 1080px le allargava oltre la
   colonna e spingeva righe e imponibile fuori vista, dietro lo scroll. */
.ip-bilancino--assign { min-width: 0; }
/* Tutta la riga seleziona la fattura: il cursore lo dice. */
tr.is-selectable { cursor: pointer; }
tr.is-selectable:has(input:checked) > td { background: var(--ip-primary-soft); }

/* Completezza della classificazione, sopra i KPI: dice subito se margine ed
   EBITDA si possono leggere o sono ancora parziali. */
.ip-bilancino-coverage {
  display: flex;
  align-items: center;
  gap: var(--ip-space-5);
  margin-bottom: var(--ip-space-5);
  padding: 12px 16px;
  border: 1px solid var(--ip-border);
  border-left: 3px solid var(--ip-warning);
  border-radius: var(--ip-radius);
  background: var(--ip-bg-card);
}
.ip-bilancino-coverage__text {
  display: grid;
  gap: 4px;
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-bilancino-coverage__text strong {
  color: var(--ip-text);
  font-size: var(--ip-text-base);
}
.ip-bilancino-coverage__bar {
  display: block;
  height: 6px;
  margin-top: 4px;
  max-width: 420px;
  border-radius: 999px;
  background: var(--ip-border-soft);
  overflow: hidden;
}
.ip-bilancino-coverage__bar > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--ip-warning);
}
.ip-bilancino-coverage .btn { flex: 0 0 auto; }
@media (max-width: 575.98px) {
  .ip-bilancino-coverage { flex-direction: column; align-items: stretch; }
}

/* Rientro dei sottoconti: la profondità arriva da --ip-level impostato dalla
   view, così aggiungere un livello non richiede una classe nuova. */
.ip-bilancino__indent {
  display: inline-block;
  width: calc(var(--ip-level, 0) * 16px);
}
.ip-bilancino__caret {
  margin-right: 4px;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-2xs);
}
.ip-bilancino tr.is-parent > .ip-bilancino__name { font-weight: 600; }
.ip-bilancino tr.is-parent td { font-weight: 500; }

.ip-bilancino tr.is-subtotal > * {
  border-top: 1px solid var(--ip-border);
  font-weight: 600;
  background: var(--ip-primary-soft);
}
.ip-bilancino tr.is-subtotal > .ip-bilancino__name { font-weight: 700; }

/* Margine ed EBITDA: doppio filo e cifra in serif, come il netto del
   prospetto di "Utile e imposte". Sono le conclusioni del conto, non un'altra
   voce dell'elenco. */
.ip-bilancino tr.is-result > * {
  border-top: 3px double var(--ip-border-strong);
  font-weight: 700;
  background: var(--ip-primary-soft);
}
.ip-bilancino tr.is-result .ip-bilancino__total b {
  font-family: var(--ip-display);
  font-size: var(--ip-text-lg);
  letter-spacing: -.01em;
}

/* Non assegnati: giallo d'avviso, non rosso. Non sono un errore, sono un
   lavoro da finire, e il colore deve dire questo. */
.ip-bilancino tr.is-unassigned > * {
  background: var(--ip-warning-bg);
  color: var(--ip-warning-text);
  font-style: italic;
}
.ip-bilancino tr.is-unassigned .ip-bilancino__total b { font-style: normal; }

.ip-bilancino__empty {
  padding: 14px 8px;
  color: var(--ip-text-muted);
  font-style: italic;
}

/* Righe senza movimenti (mese a zero) restano leggibili ma arretrano: il
   trattino sullo zero è già nella formattazione, il grigio chiaro aggiunge
   che quel mese non ha nulla da dire. */
.ip-bilancino__hint,
.ip-bilancino__break-even {
  display: flex;
  align-items: flex-start;
  gap: var(--ip-space-3);
  margin: 14px 0 0;
  color: var(--ip-text-muted);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
}
.ip-bilancino__hint .fa,
.ip-bilancino__break-even .fa { margin-top: 3px; }
.ip-bilancino__break-even {
  padding: 10px 14px;
  border-radius: var(--ip-radius);
  background: var(--ip-success-bg);
  color: var(--ip-success-text);
  font-weight: 500;
}

/* Piano dei conti: codice in evidenza monospaziata e riga spenta per le voci
   disattivate, che restano in elenco perché servono a capire i totali storici. */
.ip-plan-code {
  display: inline-block;
  min-width: 34px;
  margin-right: 4px;
  color: var(--ip-text-muted);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.ip-bilancino--plan tr.is-inactive > * { opacity: .6; }
.ip-bilancino--plan tr.is-inactive .ip-bilancino__name { text-decoration: line-through; }
/* Il piano dei conti ha quattro colonne: la min-width da dodici mesi del
   conto economico non serve e, dentro la card, allargava la pagina (1080 px
   su 898 disponibili). Qui la tabella sta nel contenitore. */
/* Non serve altro: la colonna Opzioni contiene il menu `.options.btn-group`
   delle altre liste, quindi eredita la regola che la stringe sul pulsante
   (width: 1%, etichetta nascosta agli occhi e letta dagli screen reader).

   Le due strade provate prima e scartate: lo scorrimento interno alla card
   rendeva `.table-responsive` un contenitore di scorrimento anche per
   `position: sticky`, e l'intestazione fissata a --ip-navbar-height compariva
   dentro le righe; il `table-layout: fixed` invece sovrascriveva quel 1% con
   una misura fissa e lasciava comunque il pulsante fuori dalla cella. */

/* Griglia budget: dodici caselle strette per riga. Il campo è più basso del
   form-control standard, altrimenti quattro righe di intensità diverse
   diventano un muro di riquadri e la lettura per colonna si perde. */
/* Griglia budget: quattordici colonne (voce + dodici mesi + totale) non
   stanno nei ~898 px della card. Il .table-responsive dentro .ip-table-card
   ha overflow: visible per non tagliare i menu Opzioni delle liste; qui non
   ci sono menu, quindi lo scroll interno si può riattivare senza effetti
   collaterali. La colonna Voce resta agganciata a sinistra. */
.ip-table-card .table-responsive:has(> .ip-bilancino--budget-grid) {
  overflow-x: auto;
}

/* Dentro un contenitore che scorre in orizzontale l'ancoraggio verticale
   (top: navbar + headerbar) non ha più un riferimento utile: il browser
   spingerebbe l'intestazione 112 px più in basso, sopra le prime righe. Si
   azzera solo il top: la colonna Voce conserva il suo left:0 e resta ferma
   mentre si scorrono i mesi. */
.ip-table-card .table-responsive:has(> .ip-bilancino--budget-grid) > .table > thead > tr > th {
  top: auto;
}
.ip-bilancino--budget-grid .ip-bilancino__month { padding: 3px 4px; }
.ip-budget-input {
  width: 88px;
  min-width: 70px;
  padding: 4px 6px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.ip-bilancino--budget-grid tfoot > tr > * {
  border-top: 2px solid var(--ip-border-strong);
  font-weight: 700;
  background: var(--ip-primary-soft);
}

/* ==========================================================================
   Badge — token condivisi
   --------------------------------------------------------------------------
   Qui viveva il cosiddetto "badge system unificato" a classi .ip-badge*: nessuna view
   però le ha mai usate (0 occorrenze in app/ e in public/assets/core/js), mentre
   la sua regola `.badge, .ip-badge` continuava a decidere l'ossatura dei badge
   veri — due sistemi per un componente solo, e quello morto vinceva sulle
   misure. Le classi sono state tolte; restano i token, che è l'unica parte
   davvero letta dal resto del foglio (base .badge, timbri, badge di tabella).
   ========================================================================== */

:root {
  /* Il corpo è 10px e non 11: la scala destina --ip-text-2xs ai badge, e un
     corpo solo evita che due timbri affiancati abbiano altezze diverse. */
  --ip-badge-font-size:      var(--ip-text-2xs);
  --ip-badge-font-weight:    700;
  --ip-badge-letter-spacing: 0.07em;
  --ip-badge-line-height:    1.25;
  /* Padding in px, non in em: in em dipende dal corpo e produceva i 3,08px che
     disallineavano le etichette di un pixel. */
  --ip-badge-padding-y:      3px;
  --ip-badge-padding-x:      7px;
  --ip-badge-radius:         var(--ip-radius-sm);
  --ip-badge-transition:     var(--ip-transition);
}

/* ------------------------------------------------------------------ forme assorbite */
/* Etichette compatte che nel markup non hanno la classe .badge: da qui prendono
   la stessa ossatura dei timbri (corpo 10px, peso 700, maiuscoletto spaziato,
   padding 3px/7px, raggio 4px, riga 1.25, inline-flex centrato), così un badge
   XML, un conteggio di tab e uno stato hanno la stessa forma.
   Prima ognuna si ridichiarava tutte le misure con valori diversi — tre raggi
   (3px, 4px, 999px), due corpi (10 e 11px), due pesi e quattro padding: era
   questo che faceva sembrare i badge "sbagliati" a seconda della pagina.
   Colore e bordo restano dove vivono i componenti. */
.ip-row-current-badge,
.ip-tab-badge,
.ip-aging-range-badge,
.ip-calendar-popover__badge,
.ip-import-badge,
.ip-export-badge,
.ip-xml-badge,
.ip-count-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  padding: var(--ip-badge-padding-y) var(--ip-badge-padding-x);
  font-size: var(--ip-badge-font-size);
  font-weight: var(--ip-badge-font-weight);
  line-height: var(--ip-badge-line-height);
  letter-spacing: var(--ip-badge-letter-spacing);
  text-transform: uppercase;
  border-radius: var(--ip-badge-radius);
  white-space: nowrap;
  vertical-align: middle;
}
/* Le classi che non hanno un bordo proprio ne portano uno trasparente: così
   anche i chip senza contorno hanno i 20,5px dei timbri con bordo, e una riga
   che mescola i due ("selezionato" accanto a uno stato) resta allineata. */
.ip-row-current-badge,
.ip-tab-badge,
.ip-aging-range-badge,
.ip-calendar-popover__badge,
.ip-xml-badge,
.ip-count-badge {
  border: 1px solid transparent;
}

/* Il badge della nota di consegna vive dentro il PDF (pdf_styles.php ha la sua
   copia): qui la stessa forma, perché anche il documento deve avere lo stesso
   timbro della schermata da cui è stato generato. */
.doc-badge {
  display: inline-block;
  padding: var(--ip-badge-padding-y) var(--ip-badge-padding-x);
  font-size: var(--ip-badge-font-size);
  font-weight: var(--ip-badge-font-weight);
  line-height: var(--ip-badge-line-height);
  letter-spacing: var(--ip-badge-letter-spacing);
  text-transform: uppercase;
  border: 1px solid;
  border-radius: var(--ip-badge-radius);
}

/* Select della voce di ricavo dentro la riga di fattura: sta sotto
   descrizione, a larghezza piena della cella, perché il nome della voce è
   lungo ("100 · Vendite di prodotti") e un campo stretto lo troncherebbe
   proprio nella parte che distingue le voci. */
.ip-item-row__account {
  margin-top: 6px;
}

@media (max-width: 767.98px) {
  /* Sotto i 768px la colonna agganciata occuperebbe metà schermo: si lascia
     scorrere tutta la tabella, che è il comportamento normale di
     .table-responsive. */
  .ip-bilancino__name { position: static; }
  .ip-bilancino-kpi { grid-template-columns: 1fr 1fr; }
  .ip-bilancino-kpi__value { font-size: var(--ip-text-2xl); }
}

/* -----------------------------------------------------------------------------
   Navigazione tra pagine senza "ripartire da zero"
   --------------------------------------------------------------------------
   Ogni pagina e' un caricamento completo, e a ogni click topbar e sidebar
   sparivano e ricomparivano. Con le view transition tra documenti (Chrome 126+,
   Safari 18.2+; Firefox per ora ignora la regola e naviga come prima) le due
   barre restano ferme e si sfuma solo il contenuto. Il nome dato a topbar e
   sidebar le isola dalla transizione della pagina. Non basta pero' spegnere
   l'animazione del gruppo: il cross-fade di default vive sulle pseudo-immagini
   old/new, e finche' restano accese la sidebar sbiadisce e ricompare — cioe'
   sembra ricaricarsi. Si scarta lo snapshot vecchio e si mostra subito quello
   nuovo, identico tranne per la voce attiva. */
@view-transition { navigation: auto; }
.ip-topbar  { view-transition-name: ip-topbar; }
.ip-sidebar { view-transition-name: ip-sidebar; }
::view-transition-group(ip-topbar),
::view-transition-group(ip-sidebar) { animation: none; }
::view-transition-old(ip-topbar),
::view-transition-old(ip-sidebar) { display: none; }
::view-transition-new(ip-topbar),
::view-transition-new(ip-sidebar) { animation: none; }
::view-transition-old(root),
::view-transition-new(root) { animation-duration: 0.14s; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* -----------------------------------------------------------------------------
   Movimento: il tocco e la scelta si sentono
   --------------------------------------------------------------------------
   Il foglio animava gia' l'ingresso delle pagine, i grafici della bacheca e i
   toast: quello che mancava era la risposta a un gesto. Qui stanno solo
   micro-movimenti da 140-300ms, tutti su transform/opacity (nessun reflow),
   che dicono "ho ricevuto il clic" senza spostare niente di permanente. La
   guardia prefers-reduced-motion in testa al foglio li azzera.
   -------------------------------------------------------------------------- */

/* Barra dei mesi: il pulsante si alza di un pixel sotto il mouse e si
   comprime quando lo si preme — la stessa pressione dei .btn, che da soli
   non coprono questi link. */
.ip-period-month:hover:not(.is-active) { transform: translateY(-1px); }
.ip-period-month:active { transform: scale(.97); }
@keyframes ip-chip-pick {
  from { opacity: .4; transform: scale(.94); }
  to   { opacity: 1;  transform: none; }
}
/* Il mese appena scelto si "accende": cresce dal 94% invece di comparire
   gia' pieno. Solo quando l'arco intero e' spento: nello stato d'apertura i
   dodici mesi sono accesi insieme e l'animazione sarebbe un'onda di rumore. */
.ip-period-month--all:not(.is-active) ~ .ip-period-month.is-active {
  /* Senza `both`: un'animazione conclusa con fill "forwards" continua a
     vincere sul transform dichiarato, e il chip acceso non risponderebbe piu'
     alla pressione (:active). Lo stato finale coincide con quello naturale,
     quindi non c'e' niente da trattenere.

     Le curve "a scatto" sono state misurate campionando il transform
     fotogramma per fotogramma: con cubic-bezier(.22, .61, .36, 1) a 240ms il
     59% della scala era gia' fatto nei primi 60ms e il 98% entro 180ms — si
     vedeva un lampo, non un gesto. Con la curva standard
     cubic-bezier(.4, 0, .2, 1) a 300ms il 22% si compie nel primo quarto e il
     78% a meta': il mese scelto
     si apre davanti agli occhi per tutta la durata, restando sotto la soglia
     in cui un clic comincia a sembrare lento. */
  animation: ip-chip-pick 300ms cubic-bezier(.4, 0, .2, 1);
}

/* Pannelli che compaiono (select con ricerca, datepicker): prima il passaggio
   era display:none -> block, cioe' un salto secco. Il pannello e' position:
   fixed e il JS gli assegna left/top, quindi il transform non sposta il
   calcolo della posizione, si limita alla comparsa. */
@keyframes ip-pop-in {
  from { opacity: 0; transform: translateY(-2px) scale(.985); }
  to   { opacity: 1; transform: none; }
}
.ip-select-panel.is-open,
.ip-dp.is-open {
  animation: ip-pop-in 140ms ease-out;
}

/* Barra delle azioni di gruppo: entra dal basso quando si seleziona la prima
   casella (il JS alterna [hidden]) e non piu' come un blocco gia' fermo. */
@keyframes ip-bulk-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
#ip-bulk-bar:not([hidden]) {
  animation: ip-bulk-rise 180ms ease-out;
}

/* Righe delle liste. Quando il contenuto arriva da una navigazione parziale
   (o dal primo caricamento) le righe entrano a scaletta invece di comparire
   gia' ferme: e' la tabella che si riempie, non una pagina che si accende.
   Nessuna classe dal JS: ip-pjax sostituisce #main-content, quindi le righe
   sono nodi nuovi e l'animazione riparte da sola — anche cambiando pagina dal
   pager, che e' un link GET come gli altri (layout/pager.php). La ricerca
   istantanea dentro la lista non ne e' toccata: nasconde le righe con
   display, non le ricrea.
   Fill `backwards` e non `both`: durante l'attesa la riga e' gia' nella forma
   iniziale (invisibile), a fine corsa non resta pero' nessun transform
   appeso, che renderebbe la riga containing block dei discendenti fixed.
   Solo le prime otto righe hanno uno scarto progressivo: su una lista da
   cinquanta, una scaletta completa sarebbe un'attesa di oltre un secondo per
   righe che nessuno sta guardando. */
@keyframes ip-row-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
/* Le liste normali stanno in una .ip-table-card; quelle fuori (le opzioni di
   un campo personalizzato, le tabelle dentro una card semplice) seguono la
   stessa scaletta. Restano fuori le .ip-items-table — righe di documento e
   prospetti: li' le righe si aggiungono mentre si compila, e una comparsa a
   scaletta a ogni "Aggiungi riga" rallenterebbe il lavoro invece di
   accompagnarlo. Restano fuori anche le tabelle della bacheca, che vivono in
   .ip-window (non in .card) e hanno gia' la scaletta delle finestre. */
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:not(.empty-row) {
  animation: ip-row-in 220ms ease-out backwards;
  animation-delay: calc(var(--row-i, 8) * 22ms);
}
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(1) { --row-i: 0; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(2) { --row-i: 1; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(3) { --row-i: 2; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(4) { --row-i: 3; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(5) { --row-i: 4; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(6) { --row-i: 5; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(7) { --row-i: 6; }
:is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr:nth-child(8) { --row-i: 7; }

/* Stampa: su carta le righe devono esserci tutte e subito, non entrare mentre
   il foglio esce dalla stampante. */
@media print {
  :is(.ip-table-card table, .card table.table:not(.ip-items-table)) > tbody > tr {
    animation: none !important;
  }
}

/* -----------------------------------------------------------------------------
   Allineamenti dell'ultimo giro
   --------------------------------------------------------------------------
   Tre punti dove la stessa cosa era disegnata in due modi: il caricamento
   dell'estratto conto, la colonna destra di "Utile e imposte" e gli importi
   negativi delle tabelle dei ricavi. Le regole stanno in fondo perché
   correggono blocchi definiti sopra; riscriverle in loco avrebbe lasciato due
   definizioni della stessa misura in un foglio che ne ha già troppe.
   -------------------------------------------------------------------------- */

/* Riconciliazione: il file è l'azione, il conto è un parametro. */
.ip-statement-upload {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 260px;
  gap: var(--ip-space-6);
  align-items: start;
}
.ip-statement-upload .ip-import-drop { padding: 16px; max-width: none; min-height: 100%; }
/* Il max-width:640px delle pagine di import XML qui lasciava un buco fra la
   zona di trascinamento e la colonna del conto: la zona riempie la sua
   colonna e si allinea in altezza al blocco conto + pulsante. */
.ip-statement-upload { align-items: stretch; }
.ip-statement-upload__side {
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-3);
}
/* IPSelect sostituisce il <select> con uno span: senza larghezza piena il
   campo resta lungo quanto il testo e sembra disattivato. */
.ip-statement-upload__side .ip-select { width: 100%; }
.ip-statement-file {
  display: flex;
  align-items: center;
  gap: var(--ip-space-3);
  margin: 0;
  min-width: 0;
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-statement-file span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ip-statement-note { margin-top: var(--ip-space-5); }
@media (max-width: 767.98px) {
  .ip-statement-upload { grid-template-columns: minmax(0, 1fr); }
  .ip-statement-upload__side .btn { width: 100%; }
}

/* La colonna destra di "Utile e imposte" accompagna un prospetto lungo e deve
   fermarsi sotto la topbar fissa: il top:16 della regola generale non conosce
   l'altezza della barra, misurata a runtime, e la card le scorreva dietro.
   Va sommata anche la headerbar, sticky anche lei: contando solo la topbar
   la testata "Stima imposte" restava nascosta sotto il titolo della pagina. */
.ip-preview__side.ip-finance-side {
  top: calc(var(--ip-navbar-height) + var(--ip-headerbar-height) + var(--ip-space-7));
}

/* Importi negativi nelle tabelle di ricavi e cassa: --ip-danger-text è la
   tinta prevista nei due temi, text-danger è il rosso di Bootstrap che sul
   fondo scuro resta sotto il contrasto minimo. */
.ip-items-table__total.is-negative,
.ip-finance-month__saldo.is-negative { color: var(--ip-danger-text); }

/* Scomposizione netto/lordo dentro una KPI della dashboard: riusa il mini
   mastro già scritto per la finestra IVA (righe dt/dd in monospaziato, filo
   tratteggiato in testa), quindi le due finestre si leggono allo stesso modo.
   Solo i margini sono da card: dentro un dl di Bootstrap resterebbe lo spazio
   del paragrafo e la cifra grande si staccherebbe dalle sue voci. */
.ip-kpi-ledger {
  margin: var(--ip-space-4) 0 var(--ip-space-3);
}

/* --------------------------------------------------------------------------
   Riconciliazione: elenco movimenti a righe-card. Il verso del denaro è la
   prima informazione che serve per abbinare (incasso → fattura, uscita →
   spesa), quindi ha un'icona e un colore propri; le righe già abbinate si
   attenuano per lasciare in primo piano il lavoro che resta.
   -------------------------------------------------------------------------- */
.ip-recon-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ip-space-4);
}
.ip-recon-row {
  display: grid;
  grid-template-columns: 52px 32px minmax(0, 1fr) auto minmax(220px, 34%);
  align-items: center;
  gap: var(--ip-space-6);
  padding: var(--ip-space-6) var(--ip-space-7);
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: 10px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.ip-recon-row:hover { border-color: var(--ip-border-strong); box-shadow: 0 1px 3px rgba(0,0,0,.05); }
.ip-recon-row.is-done { background: var(--ip-bg); }
.ip-recon-row.is-done .ip-recon-desc__text,
.ip-recon-row.is-done .ip-recon-amount { color: var(--ip-text-muted); }

.ip-recon-date {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.1;
  padding: var(--ip-space-3) 0;
  border-right: 1px solid var(--ip-border-soft);
}
.ip-recon-date__day { font-size: var(--ip-text-xl); font-weight: 600; font-family: var(--ip-mono); color: var(--ip-text); }
.ip-recon-date__month { font-size: var(--ip-text-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--ip-text-muted); }

.ip-recon-dir {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  font-size: var(--ip-text-sm);
}
.ip-recon-dir.is-in  { background: var(--ip-success-bg); color: var(--ip-success-text); }
.ip-recon-dir.is-out { background: var(--ip-violet-bg);  color: var(--ip-violet); }

.ip-recon-desc { display: flex; flex-direction: column; gap: var(--ip-space-2); min-width: 0; }
.ip-recon-desc__text {
  font-size: var(--ip-text-md);
  color: var(--ip-text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.ip-recon-desc__meta { font-size: var(--ip-text-sm); color: var(--ip-text-muted); }

.ip-recon-amount {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-lg);
  font-weight: 600;
  white-space: nowrap;
  text-align: right;
}
.ip-recon-amount.is-in  { color: var(--ip-success-text); }
.ip-recon-amount.is-out { color: var(--ip-text); }

.ip-recon-action {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--ip-space-4);
  min-width: 0;
  padding-left: var(--ip-space-6);
  border-left: 1px solid var(--ip-border-soft);
}
.ip-recon-match { display: flex; align-items: center; gap: var(--ip-space-4); width: 100%; min-width: 0; }
.ip-recon-match .ip-select,
.ip-recon-match select { flex: 1 1 auto; min-width: 0; }
.ip-recon-match .btn { flex: 0 0 auto; }
.ip-recon-suggest {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  padding: var(--ip-space-3) var(--ip-space-5);
  border: 1px dashed var(--ip-border-strong);
  border-radius: 8px;
  background: var(--ip-primary-soft);
}
.ip-recon-suggest__label { font-size: var(--ip-text-base); font-weight: 600; color: var(--ip-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ip-recon-suggest__why { font-size: var(--ip-text-xs); color: var(--ip-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ip-recon-matched { display: inline-flex; align-items: center; gap: var(--ip-space-3); color: var(--ip-success-text); font-weight: 600; font-size: var(--ip-text-base); margin-right: auto; }
.ip-recon-matched a { color: inherit; }
.ip-recon-undo { color: var(--ip-text-muted); font-size: var(--ip-text-sm); text-decoration: none; padding: 0 var(--ip-space-3); }
.ip-recon-undo:hover { color: var(--ip-danger-text); }
.ip-recon-none { color: var(--ip-text-muted); font-size: var(--ip-text-sm); margin-right: auto; }
.ip-recon-empty {
  padding: var(--ip-space-9);
  text-align: center;
  color: var(--ip-text-muted);
  border: 1px dashed var(--ip-border);
  border-radius: 10px;
}
.ip-recon-empty .fa { display: block; font-size: 22px; margin-bottom: var(--ip-space-4); }

@media (max-width: 991.98px) {
  .ip-recon-row { grid-template-columns: 52px 32px minmax(0, 1fr) auto; }
  .ip-recon-action { grid-column: 1 / -1; border-left: 0; padding-left: 0; border-top: 1px solid var(--ip-border-soft); padding-top: var(--ip-space-5); }
}
@media (max-width: 575.98px) {
  .ip-recon-row { grid-template-columns: 44px minmax(0, 1fr) auto; gap: var(--ip-space-5); padding: var(--ip-space-5); }
  .ip-recon-dir { display: none; }
  .ip-recon-amount { font-size: var(--ip-text-md); }
}

/* -----------------------------------------------------------------------------
   Revisione design (novembre 2026)
   --------------------------------------------------------------------------
   Correzioni nate guardando Bacheca, elenco e scheda fattura a 375px, a
   desktop e in tema scuro. Stanno insieme, in coda, perché toccano regole
   sparse nel foglio e ognuna è un'eccezione motivata alla scala: tenerle
   vicine fa vedere cosa è stato deciso e perché.
   -------------------------------------------------------------------------- */

/* Campi su telefono. Sotto i 16px iOS ingrandisce la pagina a ogni tocco su
   un campo, e la pagina resta ingrandita finché non la si rimpicciolisce a
   mano; 36px di altezza sono sotto i 44 che un dito centra senza mirare.
   Si alzano i token delle altezze, così ogni controllo che li usa segue da
   solo, e il testo dei soli campi (non dell'interfaccia) sale a 16px. */
@media (max-width: 767.98px) {
  :root {
    --ip-control-h: 44px;
    --ip-control-h-sm: 40px;
  }
  input.form-control,
  select.form-control,
  textarea.form-control,
  .form-select,
  .ip-select__control,
  .ip-select__search input {
    /* !important voluto: le varianti compatte (righe della fattura, campi
       input-sm) fissano 12px con selettori più specifici, e basta un solo
       campo sotto i 16px perché iOS ingrandisca la pagina. */
    font-size: 16px !important;
  }
  input.form-control,
  select.form-control,
  .form-select,
  .ip-select__control {
    min-height: var(--ip-control-h);
  }
  input.form-control.input-sm,
  input.form-control.form-control-sm,
  .ip-select--sm .ip-select__control {
    min-height: var(--ip-control-h-sm);
  }
  .btn { min-height: var(--ip-control-h-sm); }
}

/* Cifre da leggere, non etichette. --ip-text-xs (11px) va bene per una
   didascalia in maiuscolo, ma qui sotto ci sono importi: imponibile e IVA
   delle card della Bacheca, il totale di ogni mese, i metadati delle righe.
   Un gradino in più (12px) senza cambiare il peso visivo del blocco. I timbri
   di stato passano da 10 a 11px: in maiuscolo con il bordo restano compatti. */
.ip-iva-detail,
.ip-period-month-amount,
.ip-recent-table .ip-row-meta {
  font-size: var(--ip-text-sm);
}
.badge.ip-status {
  font-size: var(--ip-text-xs);
}

/* Bersagli di almeno 24x24 (WCAG 2.5.8). La "x" che svuota una select era
   18px, le frecce che spostano una riga 20x12, la maniglia 21x18: tre
   controlli che si usano con precisione proprio mentre si compila. L'icona
   resta della sua misura, cresce l'area che risponde al clic. */
.ip-select__clear {
  width: 24px;
  height: 24px;
}
.ip-item-row__handle,
.ip-item-row__move {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  min-height: 24px;
}
/* Il focus di questi due era il solo fondo al 6%: da tastiera non si vedeva
   dove si era. Stesso contorno d'accento del resto dell'interfaccia. */
.ip-item-row__handle:focus-visible,
.ip-item-row__move:focus-visible:not(:disabled) {
  outline: 2px solid var(--ip-primary);
  outline-offset: 1px;
}

/* Barra dei mesi con l'arco intero selezionato. Tredici blocchi pieni erano
   l'elemento più scuro della pagina e pesavano più di "Nuova fattura", pur
   dicendo solo "nessun filtro". La logica non cambia (i dodici mesi restano
   selezionati, vedi period_nav.php): quando è acceso l'interruttore generale
   i mesi usano la tinta leggera, e il pieno resta a chi dice qualcosa — il
   singolo mese o l'intervallo scelto. */
.ip-period-month--all.is-active ~ .ip-period-month.is-active {
  background: var(--ip-primary-soft);
  color: var(--ip-text);
  font-weight: 500;
  box-shadow: none;
}
.ip-period-month--all.is-active ~ .ip-period-month.is-active:hover {
  background: var(--ip-hover);
}

/* Riepilogo sotto la barra dei mesi, su telefono. Cinque voci in flex
   centrato andavano a capo dove capitava: "IVA" restava da sola in mezzo alla
   riga. Due colonne allineate a sinistra, cifre un gradino sotto: stanno in
   tre righe ordinate e la lista sale di un centinaio di pixel. */
@media (max-width: 575.98px) {
  .ip-period-nav .ip-summary {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--ip-space-4) var(--ip-space-6);
    justify-content: stretch;
  }
  .ip-period-nav .ip-summary-item,
  .ip-period-nav .ip-summary-item:first-child {
    padding: 2px 0 2px 10px;
    border-left: 2px solid var(--ip-border);
  }
  .ip-period-nav .ip-summary-value {
    font-size: var(--ip-text-xl);
  }
}

/* Select del cliente nella scheda documento. Ereditava i 120px pensati per
   l'aliquota nella riga di aggiunta (.ip-doc-newrow .ip-select) e troncava il
   nome a "Istituto Mario Ros…" con mezza card libera accanto. */
.ip-doc-client .ip-doc-newrow .ip-select {
  flex: 1 1 240px;
  width: auto;
  max-width: 360px;
}

/* Titolo di una card come intestazione vera (<h2>): chi usa uno screen
   reader salta fra le sezioni della scheda. L'aspetto resta quello della
   testata della card. */
.ip-card-heading {
  display: inline;
  margin: 0;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
}

/* Colonne secondarie delle liste a larghezze intermedie.
   Fra 768 e 1280px la tabella fatture era più larga del suo contenitore
   (956px di minimo contro 715-960 disponibili, perché la barra laterale ne
   toglie 236): Saldo e Opzioni finivano fuori vista dietro uno scorrimento
   orizzontale che nessuno cerca, cioè sparivano proprio le azioni. I
   breakpoint di Bootstrap (d-xxl-table-cell sull'IVA) guardano il viewport,
   non lo spazio rimasto alla tabella.
   .ip-col-secondary marca le colonne che si possono ricavare dalle altre o
   dalla scheda (data di creazione, imponibile): in quella fascia escono, e
   le celle stringono il margine. Sotto i 576px la tabella diventa a schede e
   decide .ip-hide-on-mobile, come prima.
   La fascia partiva da 768px e lasciava scoperto il tratto 576-767: lì la
   tabella non era ancora a schede ma nessuna colonna usciva, quindi le liste
   documento (10 colonne, 832px di minimo) scorrevano dentro la card di 130px
   e le ultime due colonne — totale e Opzioni — finivano fuori dalla card,
   invisibili. È la larghezza dell'iPad in verticale (744), dove le azioni
   sparivano. Con la fascia estesa la tabella scende a 662px e ci sta (702
   disponibili a 744). */
@media (min-width: 576px) and (max-width: 1279.98px) {
  .ip-table-card .ip-col-secondary {
    display: none;
  }
  .ip-table-card table.ip-table-card-collapse > thead > tr > th,
  .ip-table-card table.ip-table-card-collapse > tbody > tr > td,
  .ip-table-card table.ip-table-card-collapse > tfoot > tr > * {
    padding-left: 8px;
    padding-right: 8px;
  }
  .ip-table-card table.ip-table-card-collapse > * > tr > :first-child { padding-left: 18px; }
  .ip-table-card table.ip-table-card-collapse > * > tr > :last-child  { padding-right: 18px; }
}
/* Fra 1280 e 1399px (un portatile da 13") le colonne secondarie tornavano
   tutte, e la lista fatture occupava l'intera larghezza con colonne che non
   vanno a capo: l'unica che può stringersi è il cliente, che restava a 116px
   e spezzava su due righe anche "Istituto Mario Rossi", con l'aria fra le altre
   colonne a far sembrare l'a capo immotivato. In questa fascia esce solo la
   colonna secondaria non numerica (data di creazione, categoria della
   spesa): i suoi ~95px vanno al cliente. L'imponibile resta, perché è una
   cifra che si confronta; da 1400px, dove arriva anche l'IVA, torna tutto. */
@media (min-width: 1280px) and (max-width: 1399.98px) {
  .ip-table-card .ip-col-secondary:not(.amount) {
    display: none;
  }
}

/* Piede dei totali: una riga senza la sua cifra non dice niente.
   La cella dell'IVA segue la colonna IVA (visibile solo da 1400px), quindi
   sotto quella larghezza restava l'etichetta "IVA" con accanto il vuoto; lo
   stesso succede alla riga dell'imponibile quando la sua colonna è secondaria.
   Le due cifre restano nel riepilogo in testa alla lista. Su telefono (sotto
   576px) il piede è a blocchi e mostra tutto, quindi lì non si tocca. */
@media (min-width: 576px) and (max-width: 1399.98px) {
  .ip-list-totals__row--tax { display: none; }
}
/* Stessa fascia della regola qui sopra: da quando le colonne secondarie
   escono anche fra 576 e 767px, il piede non deve mostrarne la cifra. */
@media (min-width: 576px) and (max-width: 1279.98px) {
  .ip-list-totals__row--secondary { display: none; }
}

/* Piede dei totali su telefono: la cella dell'IVA portava d-none (la colonna
   IVA esiste solo da 1400px) e nel piede a blocchi restava l'etichetta "IVA"
   senza cifra. Qui non c'è una colonna da rispettare: la cifra si mostra. */
@media (max-width: 575.98px) {
  .ip-table-card table.ip-table-card-collapse tfoot .ip-list-totals__row--tax td.ip-list-totals__value {
    display: block !important;
  }
}

/* Liste "recenti" della Bacheca nelle finestre a mezza larghezza. Sotto i
   1440px la tabella dei preventivi era più larga della finestra (data e
   "· scade il…" sulla stessa riga, 150px di colonna) e scorreva di lato per
   una ventina di pixel, tagliando il saldo. La scadenza scende sotto la data,
   come già succede su telefono: la colonna torna stretta e nulla si perde. */
@media (min-width: 768px) and (max-width: 1439.98px) {
  .ip-recent-table .ip-row-meta {
    display: block;
    margin-left: 0;
    margin-top: 2px;
    /* Può andare a capo: "· Scadenza 17/11/2026" su una riga sola è da solo
       più largo della data, e la colonna non si stringeva. */
    white-space: normal;
  }
  .ip-recent-table > thead > tr > th,
  .ip-recent-table > tbody > tr > td {
    padding-left: 8px;
    padding-right: 8px;
  }
  .ip-recent-table > * > tr > :first-child { padding-left: 14px; }
  .ip-recent-table > * > tr > :last-child  { padding-right: 14px; }
}

/* =============================================================================
   Stato del sistema — riepilogo in testa e controlli in due gruppi
   -----------------------------------------------------------------------------
   La pagina nasceva come una tabella a tre colonne (controllo, stato,
   dettaglio). Funzionava, ma per rispondere alla domanda con cui la si apre —
   "devo preoccuparmi?" — bisognava leggere dieci badge uno per uno, e i
   suggerimenti lunghi finivano in una colonna stretta.

   Il riepilogo dà l'esito in una riga (stato peggiore, una tacca per
   controllo, conteggi). Sotto, i controlli sono divisi in "Da sistemare" e
   "Regolari": in un elenco unico un guasto e otto righe verdi avevano lo
   stesso peso, e l'occhio doveva scartare il verde per trovare il resto. Le
   righe da sistemare sono ariose e portano il "cosa fare" in evidenza; quelle
   regolari sono compatte, una riga ciascuna su schermo largo.

   Il colore non è l'unico segnale: icona, parola ("regolare", "da
   controllare", "guasto") e numero dei conteggi restano leggibili anche senza
   distinguere le tinte.
   -------------------------------------------------------------------------- */

/* Un solo insieme di token per stato, usato da riepilogo, tacche e righe: la
   tinta piena per barra, tacca e pallino; la variante -text per il testo
   (l'ambra piena su fondo chiaro sta intorno a 2:1). */
.ip-status-hero--ok,
.ip-status-row--ok,
.ip-status-count--ok,
.ip-status-strip__cell--ok {
  --ip-stato:      var(--ip-success);
  --ip-stato-bg:   var(--ip-success-bg);
  --ip-stato-text: var(--ip-success-text);
}
.ip-status-hero--warning,
.ip-status-row--warning,
.ip-status-count--warning,
.ip-status-strip__cell--warning {
  --ip-stato:      var(--ip-warning);
  --ip-stato-bg:   var(--ip-warning-bg);
  --ip-stato-text: var(--ip-warning-text);
}
.ip-status-hero--error,
.ip-status-row--error,
.ip-status-count--error,
.ip-status-strip__cell--error {
  --ip-stato:      var(--ip-danger);
  --ip-stato-bg:   var(--ip-danger-bg);
  --ip-stato-text: var(--ip-danger-text);
}

/* Riepilogo: fondo tinto dallo stato peggiore, barra piena a sinistra (lo
   stesso segno di .ip-window--alert, così "c'è un problema" si vede dal
   bordo della pagina senza leggere). Griglia e non flex: icona, testo e
   conteggi hanno tre larghezze di natura diversa, e con il wrap l'icona
   restava sola sulla prima riga appena il testo andava a capo. */
.ip-status-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(220px, 280px);
  align-items: center;
  gap: var(--ip-space-6) var(--ip-space-7);
  margin: var(--ip-space-7) 0 0;
  padding: var(--ip-space-8) var(--ip-space-9);
  background: var(--ip-stato-bg);
  border: 1px solid color-mix(in srgb, var(--ip-stato) 30%, var(--ip-border));
  border-left: 4px solid var(--ip-stato);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-sm);
}
/* L'icona sta su fondo card, non sulla tinta: il glifo in --ip-stato-text
   resta sopra 4,5:1 in entrambi i temi, mentre il pieno ambra con il bianco
   scendeva a 2,28:1. */
.ip-status-hero__icon {
  align-self: start;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--ip-bg-card);
  border: 1px solid color-mix(in srgb, var(--ip-stato) 35%, transparent);
  color: var(--ip-stato-text);
  font-size: var(--ip-text-xl);
}
.ip-status-hero__body {
  min-width: 0;
}
.ip-status-hero__eyebrow {
  margin: 0 0 2px;
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ip-stato-text);
}
.ip-status-hero__title {
  margin: 0;
  font-size: var(--ip-text-3xl);
  line-height: 1.2;
  text-wrap: balance;
}
.ip-status-hero__lead {
  margin: var(--ip-space-2) 0 0;
  /* Il testo sta sulla tinta di stato, non sulla card: --ip-text-muted è
     tarato sul canvas e qui scenderebbe sotto 4,5:1. Resta in inchiostro. */
  color: var(--ip-text);
  font-size: var(--ip-text-base);
  line-height: 1.5;
  max-width: 62ch;
}

/* Tacche: una per controllo, nell'ordine della lista (guasti, avvisi,
   regolari). Dicono la proporzione prima dei numeri — "uno su dieci" si vede,
   non si conta. Le regolari sono attenuate: sono lo sfondo su cui devono
   staccare le altre, e a tinta piena dieci tacche verdi gridavano più del
   titolo. Il binario sotto è il fondo card, così le tacche non poggiano
   sulla tinta di stato dello stesso colore. */
.ip-status-strip {
  display: flex;
  gap: 3px;
  padding: 3px;
  background: var(--ip-bg-card);
  border: 1px solid color-mix(in srgb, var(--ip-stato) 25%, transparent);
  border-radius: var(--ip-radius-sm);
}
.ip-status-strip__cell {
  flex: 1 1 0;
  height: 10px;
  border-radius: 2px;
  background: var(--ip-stato);
}
.ip-status-strip__cell--ok {
  background: color-mix(in srgb, var(--ip-stato) 38%, var(--ip-bg-card));
}

/* Conteggi: legenda delle tacche, non tre KPI. Il pallino porta il colore,
   la cifra resta in inchiostro — uno zero rosso sembrerebbe un guasto anche
   quando non c'è; per lo stesso motivo le voci a zero si attenuano. Nel
   sorgente l'etichetta (dt) precede la cifra (dd), come vuole una <dl>; a
   schermo si legge "1 guasto", quindi l'ordine visivo è invertito. */
.ip-status-hero__counts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--ip-space-2) var(--ip-space-7);
  margin: var(--ip-space-4) 0 0;
  padding: 0;
}
.ip-status-count {
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  gap: var(--ip-space-2);
  font-size: var(--ip-text-sm);
  line-height: 1.4;
  white-space: nowrap;
}
.ip-status-count dt {
  font-weight: 500;
  color: var(--ip-text);
}
.ip-status-count dd {
  display: inline-flex;
  align-items: center;
  gap: var(--ip-space-3);
  margin: 0;
  font-family: var(--ip-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-strong);
}
.ip-status-count.is-zero dt,
.ip-status-count.is-zero dd {
  font-weight: 400;
}
.ip-status-count.is-zero .ip-status-dot {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--ip-stato);
}
.ip-status-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ip-stato);
}

/* Le due card stanno vicine: sono lo stesso elenco diviso in due, non due
   argomenti. Il titolo è un <h2> per la struttura del documento ma veste
   come le altre etichette di card (non in Fraunces a 22px). */
.ip-status-card {
  margin-bottom: 0;
}
.ip-status-card .ip-table-card-label {
  font-family: var(--ip-font);
  line-height: 1.35;
}

/* Lista dei controlli: righe separate da un filo, senza hover — non sono
   cliccabili, e un fondo che si accende al passaggio del mouse prometterebbe
   un'azione che non c'è. */
.ip-status-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Riga a griglia: icona, nome del controllo, contenuto, timbro. Il nome ha
   una colonna a larghezza fissa così i dettagli partono tutti dallo stesso
   margine e la colonna si scorre in verticale come in una tabella, senza
   esserlo (i suggerimenti lunghi in una cella stretta erano il difetto
   della versione a tabella). */
.ip-status-row {
  display: grid;
  grid-template-columns: 30px minmax(150px, 210px) minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--ip-space-2) var(--ip-space-6);
  padding: var(--ip-card-head-y) var(--ip-card-pad-x);
  border-top: 1px solid var(--ip-border-soft);
}
.ip-status-row:first-child {
  border-top: 0;
}
.ip-status-row__icon {
  grid-column: 1;
  grid-row: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: var(--ip-radius-sm);
  background: var(--ip-stato-bg);
  color: var(--ip-stato-text);
  font-size: var(--ip-text-base);
}
.ip-status-row__label {
  grid-column: 2;
  grid-row: 1;
  margin: 0;
  /* Centra otticamente il nome sull'icona da 30px. */
  padding-top: 5px;
  font-family: var(--ip-font);
  font-size: var(--ip-text-md);
  font-weight: 600;
  line-height: 1.4;
}
.ip-status-row__body {
  grid-column: 3;
  grid-row: 1;
  min-width: 0;
  padding-top: 5px;
}
.ip-status-row__state {
  grid-column: 4;
  grid-row: 1;
  margin-top: 5px;
}
.ip-status-row__detail {
  margin: 0;
  font-size: var(--ip-text-base);
  line-height: 1.5;
  color: var(--ip-text);
  /* Un dettaglio con un percorso o un messaggio di errore lungo non deve
     allargare la riga oltre la lettura comoda. */
  max-width: 78ch;
  overflow-wrap: anywhere;
}

/* Righe regolari: una riga sola, più bassa. L'icona perde la mattonella —
   otto quadrati verdi in colonna erano una striscia di colore più forte del
   contenuto — e resta il solo segno di spunta. */
.ip-status-row--ok {
  padding-top: var(--ip-space-5);
  padding-bottom: var(--ip-space-5);
}
.ip-status-row--ok .ip-status-row__icon {
  background: transparent;
  font-size: var(--ip-text-md);
}
.ip-status-row--ok .ip-status-row__label {
  font-weight: 500;
}

/* "Cosa fare": è il motivo per cui la riga esiste, quindi non è più una nota
   grigia sotto il dettaglio ma un riquadro tinto dallo stato, con il testo in
   inchiostro. L'etichetta lo separa dal dettaglio, che dice cosa è stato
   rilevato; questo dice come se ne esce. */
.ip-status-row__hint {
  margin: var(--ip-space-4) 0 0;
  padding: var(--ip-space-4) var(--ip-space-6);
  max-width: 78ch;
  font-size: var(--ip-text-base);
  line-height: 1.55;
  color: var(--ip-text);
  background: var(--ip-stato-bg);
  border-left: 3px solid var(--ip-stato);
  border-radius: 0 var(--ip-radius-sm) var(--ip-radius-sm) 0;
}
.ip-status-row__hint-label {
  display: block;
  margin-bottom: 1px;
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ip-stato-text);
}
/* Comandi e nomi di file citati nei testi: in mono, su un fondo appena
   segnato, e selezionabili con un clic solo — sono lì per essere copiati in
   un terminale. Il fondo è un velo dell'inchiostro e non un grigio fisso,
   così regge sia sulla card sia sul riquadro tinto, in chiaro e in scuro. */
.ip-status-row__detail code,
.ip-status-row__hint code {
  padding: 1px 5px;
  font-family: var(--ip-mono);
  font-size: 0.92em;
  color: var(--ip-text-strong);
  background: color-mix(in srgb, var(--ip-text) 8%, transparent);
  border-radius: 3px;
  overflow-wrap: anywhere;
  -webkit-user-select: all;
  user-select: all;
}

/* Azioni della riga (per ora solo il download del backup). Stanno sotto il
   dettaglio e non accanto al timbro: quello è lo stato, e un pulsante lì
   accanto lo farebbe sembrare parte del giudizio. La nota spiega perché
   scaricare non è un doppione del backup automatico. */
.ip-status-row__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ip-space-4) var(--ip-space-6);
  margin-top: var(--ip-space-4);
  max-width: 78ch;
}
.ip-status-row__actions-note {
  flex: 1 1 260px;
  min-width: 0;
  font-size: var(--ip-text-sm);
  line-height: 1.45;
  color: var(--ip-text-muted);
}

.ip-status-footnote {
  margin: var(--ip-space-7) 0 var(--ip-space-9);
  max-width: 78ch;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
}

/* Sotto i 1200px (con la barra laterale aperta il contenuto è già stretto)
   tacche e conteggi passano sotto il testo: accanto rubavano la larghezza al
   titolo, che andava a capo a metà frase. */
@media (max-width: 1199.98px) {
  .ip-status-hero {
    grid-template-columns: auto minmax(0, 1fr);
  }
  .ip-status-hero__tally {
    grid-column: 1 / -1;
  }
}
/* Sotto i 992px il contenuto non è più una colonna a fianco del nome: va
   sotto, su tutta la larghezza utile, e il timbro resta in alto a destra. */
@media (max-width: 991.98px) {
  .ip-status-row {
    grid-template-columns: 30px minmax(0, 1fr) auto;
  }
  .ip-status-row__state {
    grid-column: 3;
  }
  .ip-status-row__body {
    grid-column: 2 / -1;
    grid-row: 2;
    padding-top: 0;
  }
}
@media (max-width: 575.98px) {
  .ip-status-hero {
    padding: var(--ip-space-7);
    gap: var(--ip-space-5) var(--ip-space-6);
  }
  .ip-status-hero__icon {
    width: 38px;
    height: 38px;
    font-size: var(--ip-text-lg);
  }
  .ip-status-hero__title {
    font-size: var(--ip-text-2xl);
  }
  /* Su telefono 30px di rientro a sinistra sono un decimo dello schermo:
     dettaglio, "cosa fare" e pulsante usano tutta la larghezza della riga. */
  .ip-status-row {
    padding-left: var(--ip-space-7);
    padding-right: var(--ip-space-7);
    column-gap: var(--ip-space-5);
  }
  .ip-status-row__body {
    grid-column: 1 / -1;
  }
  .ip-status-row__actions .btn {
    width: 100%;
    justify-content: center;
  }
}

/* Scheda azienda: anteprima del logo già caricato, sopra il campo per
   sostituirlo. Fondo a scacchi tenui perché il "solo segno" è spesso bianco o
   trasparente e su una card bianca non si vedrebbe che c'è. */
.ip-company-logo {
  display: flex;
  align-items: center;
  gap: var(--ip-space-6);
  margin-bottom: var(--ip-space-4);
}
.ip-company-logo__img {
  max-height: 56px;
  max-width: 220px;
  padding: 6px;
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  background: repeating-conic-gradient(#f4f4f5 0 25%, #e4e4e7 0 50%) 0 0 / 12px 12px;
}

/* -----------------------------------------------------------------------------
   Movimento: marchio, accesso, menu e sottomenu
   --------------------------------------------------------------------------
   Il foglio animava gia' ingresso pagina, righe, grafici e pannelli di
   selezione; restavano senza movimento il marchio, la pagina di accesso (il
   primo schermo che si vede) e due aperture secche: il menu utente e i
   sottomenu della sidebar, che passavano da hidden a visibili di colpo.
   Come per il resto: solo transform e opacita', da 140 a 420ms, niente che
   sposti il layout. La guardia prefers-reduced-motion in testa al foglio li
   azzera tutti (animation-duration e transition-duration a ~0).
   -------------------------------------------------------------------------- */

/* Il "+" del marchio: al passaggio sul brand ruota di un quarto di giro e
   cresce appena. E' la parte "plus" del nome, quella che dice "aggiungi": si
   muove lei, il resto del segno sta fermo. transform-box: fill-box fa ruotare
   attorno al centro del "+" e non dell'angolo dell'SVG. Rotazione di 90 gradi
   e non 180: il "+" ruotato di un quarto e' identico a se' stesso, quindi a
   fine corsa non c'e' uno stato "storto" da raddrizzare. */
.ip-mark-plus {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 320ms cubic-bezier(.34, 1.4, .64, 1);
}
.ip-brand:hover .ip-mark-plus,
.ip-brand:focus-visible .ip-mark-plus {
  transform: rotate(90deg) scale(1.12);
}

/* Accesso: il pannello del brand e la scheda del modulo entrano a scaletta
   dal basso (12px, 420ms) invece di comparire gia' fermi. Fill backwards, non
   both: a fine corsa non resta alcun transform appeso (un antenato con
   transform diventerebbe containing block dei discendenti position:fixed). */
@keyframes ip-auth-rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.ip-auth-brand-mark,
.ip-auth-brand-copy h1,
.ip-auth-brand-copy p,
.ip-auth-points li,
.ip-auth-card {
  animation: ip-auth-rise 420ms cubic-bezier(.22, .61, .36, 1) backwards;
}
.ip-auth-brand-mark     { animation-delay: 40ms; }
.ip-auth-brand-copy h1  { animation-delay: 120ms; }
.ip-auth-brand-copy p   { animation-delay: 180ms; }
.ip-auth-points li:nth-child(1) { animation-delay: 240ms; }
.ip-auth-points li:nth-child(2) { animation-delay: 290ms; }
.ip-auth-points li:nth-child(3) { animation-delay: 340ms; }
.ip-auth-card           { animation-delay: 160ms; }

/* Menu a tendina (utente, azioni di riga): solo dissolvenza. Popper posiziona
   il menu con un `transform` inline: un keyframe che tocca `transform` lo
   sovrascrive per la durata dell'animazione e il menu fa un saltino verso
   l'angolo e poi torna al suo posto. */
@keyframes ip-menu-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.dropdown-menu.show {
  animation: ip-menu-fade 120ms ease-out;
}

/* Sottomenu della sidebar: quando un gruppo si apre (il JS toglie hidden) le
   voci scendono di 4px e compaiono, invece di spuntare di colpo e spingere
   in giu' tutto il resto. */
@keyframes ip-sub-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.ip-nav-sub:not([hidden]) {
  animation: ip-sub-in 180ms ease-out backwards;
}

/* Voci della sidebar: l'icona scivola di 2px verso il testo al passaggio, un
   cenno di direzione che non muove il testo ne' la riga. */
.ip-nav-link i:first-child {
  transition: transform 160ms ease;
}
.ip-nav-link:hover i:first-child {
  transform: translateX(2px);
}

@media print {
  .ip-mark-plus,
  .ip-auth-card { animation: none !important; transition: none !important; }
}


/* Istruzioni dei mesi dietro una "i" (vedi layout/period_nav.php): prima una
   riga di testo fissa sotto la barra, che si leggeva una volta e poi restava
   a occupare spazio. Ora un pulsante da 28px; il testo compare in un pannello
   al passaggio, al focus da tastiera o al clic, e da chiuso resta leggibile
   dai lettori di schermo (opacity/visibility, non display). */
.ip-period-hint {
  /* Subito a destra di «Dic», sulla stessa riga dei mesi e centrata in
     altezza: sta attaccata a ciò che spiega. Sotto la striscia restava una
     «i» isolata in mezzo alla pagina. Il margine negativo recupera parte
     dello stacco di 20px tra le voci della barra. */
  position: relative;
  flex: none;
  margin: 0 0 0 -12px;
}
/* Mesi in griglia (contenuto stretto): la griglia cede alla «i» lo spazio
   che le serve invece di prendersi la riga intera e mandarla a capo. */
@container (max-width: 999px) {
  .ip-period-nav-months { flex: 1 1 0; width: auto; min-width: 0; }
  .ip-period-hint { align-self: flex-start; }
}
.ip-period-hint__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ip-text-muted);
  font-size: 15px;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.ip-period-hint__btn:hover,
.ip-period-hint__btn:focus-visible,
.ip-period-hint.is-open .ip-period-hint__btn { background: var(--ip-primary-soft); color: var(--ip-primary); }
.ip-period-hint__text {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: var(--ip-z-popover);
  width: max-content;
  max-width: min(520px, calc(100vw - 32px));
  padding: 8px 12px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
  font-size: var(--ip-text-sm);
  line-height: 1.6;
  color: var(--ip-text);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .14s ease, visibility 0s linear .14s;
}
.ip-period-hint:hover .ip-period-hint__text,
.ip-period-hint:focus-within .ip-period-hint__text,
.ip-period-hint.is-open .ip-period-hint__text {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}


/* -----------------------------------------------------------------------------
   Calendario — revisione
   ----------------------------------------------------------------------------- */
/* Oggi: il numero in un disco pieno. Il fondo tenue della cella e il pallino
   accanto al numero c'erano, ma nel tema scuro il fondo quasi spariva e il
   pallino da 4px non si trovava a colpo d'occhio: la cella di oggi è la prima
   cosa che si cerca in un calendario. */
.ip-calendar-cell.is-today { box-shadow: inset 0 2px 0 var(--ip-primary); }
.ip-calendar-cell.is-today .ip-calendar-daynum,
.ip-calendar-cell.is-today button.ip-calendar-daynum {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-width: 24px;
  height: 24px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--ip-primary);
  color: var(--ip-on-primary);
}
.ip-calendar-cell.is-today .ip-calendar-daynum::after { content: none; }
/* Giorni passati senza scadenze aperte: il numero si attenua, così l'occhio
   va da oggi in avanti. */
.ip-calendar-cell.is-past:not(.has-events) .ip-calendar-daynum { opacity: .55; }
/* Un giorno con scadenze reagisce al passaggio: è cliccabile per intero dal
   numero, e la cella lo anticipa. */
.ip-calendar-cell.has-events { transition: background-color .15s ease; }
.ip-calendar-cell.has-events:hover { background: var(--ip-hover); }

/* Barra di sottoscrizione aperta dal pulsante in testata. */
.ip-calendar-feed-toggle { margin-right: 8px; }
.ip-calendar-feed-toggle[aria-expanded="true"] {
  background: var(--ip-primary-soft);
  color: var(--ip-primary);
}

/* Totali del mese in testa al riepilogo laterale. */
.ip-calendar-totals {
  display: flex;
  gap: 16px;
  margin: 0;
  padding: 12px 14px;
  border-bottom: 1px solid var(--ip-border-soft);
}
.ip-calendar-totals > div { flex: 1 1 0; min-width: 0; }
.ip-calendar-totals dt {
  font-size: var(--ip-text-xs);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ip-text-muted);
}
.ip-calendar-totals dd {
  margin: 2px 0 0;
  font-family: var(--ip-mono);
  font-size: var(--ip-text-lg, 16px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ip-text-strong);
}
.ip-calendar-totals .is-overdue dd { color: var(--ip-danger); }

/* Lista laterale: il titolo va a capo invece di essere troncato ("Fattura 90
   — I.P…" non diceva di chi fosse la fattura); data e importo restano in
   alto, allineati alla prima riga. */
.ip-calendar-list a { align-items: start; }
.ip-calendar-list__title {
  white-space: normal;
  overflow: visible;
  overflow-wrap: anywhere;
  line-height: 1.35;
}


/* -----------------------------------------------------------------------------
   Importa / Esporta — pagina di smistamento ristrutturata
   ----------------------------------------------------------------------------- */
/* Nota di formato dentro la card a cui si riferisce (prima una riga unica in
   fondo alla pagina, larga quanto lo schermo: oltre 200 caratteri per riga).
   Misura da testo di lettura e un filetto a sinistra che la stacca dai punti
   elenco senza farne un avviso. */
.ip-ie-card__note {
  max-width: 62ch;
  margin: 14px 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--ip-border);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
}
/* Senza occhiello il titolo dell'introduzione parte dal bordo. */
.ip-ie-intro { margin-top: 0; }
/* Scorciatoie: titolo e spiegazione su due righe, non ai due capi di una riga
   larga (la spiegazione finiva a destra, lontana da ciò che spiegava). */
.ip-ie-shortcuts__head {
  display: block;
}
.ip-ie-shortcuts__hint {
  max-width: 70ch;
  margin: 2px 0 0;
  text-align: left;
}


/* -----------------------------------------------------------------------------
   Campi "segreto" delle Impostazioni (chiave cron, URL del feed)
   ----------------------------------------------------------------------------- */
/* I due pulsanti accanto al campo erano a misura piena di Bootstrap (16px) e
   «Genera» era un blocco nero: pesavano più del campo e più di «Salva», che è
   l'azione vera della pagina. Ora hanno il corpo del campo e lo stile
   secondario; il valore è in monospazio perché è una stringa da copiare, non
   da leggere. */
.ip-secret-group > .form-control {
  font-family: var(--ip-mono);
  font-size: var(--ip-text-sm);
  text-overflow: ellipsis;
}
.ip-secret-group > .form-control::placeholder {
  font-family: var(--ip-font, inherit);
  font-size: var(--ip-text-base);
}
.ip-secret-group > .btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  font-size: var(--ip-text-base);
  font-weight: 500;
  white-space: nowrap;
}
.ip-secret-group > .btn .fa { font-size: var(--ip-text-sm); }
.ip-secret-copy { min-width: 38px; justify-content: center; }
.ip-secret-copy.is-copied { color: var(--ip-success-text); }
.ip-secret-copy.is-copied .fa::before { content: "\f00c"; }


/* Pulsante «Installa app» nella barra (PWA): compare solo se il browser offre
   l'installazione (vedi layout/pwa_head.php). Contorno chiaro sulla barra
   scura, così non compete con le azioni di pagina; su schermo stretto resta
   la sola icona. */
.ip-install-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: var(--ip-radius);
  background: transparent;
  color: var(--ip-navbar-text);
  font-size: var(--ip-text-sm);
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--ip-transition), border-color var(--ip-transition), color var(--ip-transition);
}
.ip-install-btn[hidden] { display: none; }
.ip-install-btn:hover { background: var(--ip-navbar-hover); border-color: rgba(255, 255, 255, .6); color: #fff; }
.ip-install-btn:focus-visible { outline: 2px solid rgba(255, 255, 255, .75); outline-offset: 1px; }
@media (max-width: 768px) { .ip-install-btn span { display: none; } .ip-install-btn { padding: 0 10px; } }

/* Spiegazione dell'installazione quando il browser non offre il prompt
   (incognito, Safari, Firefox): un fumetto sotto il pulsante. */
.ip-install-help {
  position: fixed;
  z-index: var(--ip-z-popover);
  width: min(340px, calc(100vw - 16px));
  padding: 10px 12px;
  background: var(--ip-bg-card);
  border: 1px solid var(--ip-border);
  border-radius: var(--ip-radius);
  box-shadow: var(--ip-shadow-md);
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text);
}
.ip-install-help[hidden] { display: none; }


/* -----------------------------------------------------------------------------
   Primi passi (pagina, promemoria in Bacheca) e verifica del primo avvio
   ----------------------------------------------------------------------------- */
.ip-first-steps__hero {
  display: flex; flex-wrap: wrap; gap: 16px 32px; align-items: center; justify-content: space-between;
  padding: 18px 20px; margin-bottom: 22px;
  background: var(--ip-bg-card); border: 1px solid var(--ip-border); border-radius: var(--ip-radius);
}
.ip-first-steps__title { font-family: var(--ip-display); font-size: 1.25rem; margin: 0; }
.ip-first-steps__lead { margin: 6px 0 0; max-width: 56ch; color: var(--ip-text-muted); line-height: 1.5; }
.ip-first-steps__meter { min-width: 180px; flex: 0 1 220px; }
.ip-first-steps__count { margin: 0 0 6px; font-family: var(--ip-mono); font-size: var(--ip-text-lg); text-align: right; }
.ip-first-steps__bar { height: 8px; border-radius: 999px; background: var(--ip-border-soft); overflow: hidden; }
.ip-first-steps__bar span { display: block; height: 100%; background: var(--ip-success, #3d7a3e); border-radius: inherit;
  transition: width .5s ease; }
.ip-first-steps__group { margin-bottom: 22px; }
.ip-first-steps__group-title {
  margin: 0 0 8px; font-size: var(--ip-text-xs); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ip-text-muted);
}
.ip-first-steps__list {
  list-style: none; margin: 0; padding: 0;
  background: var(--ip-bg-card); border: 1px solid var(--ip-border); border-radius: var(--ip-radius);
}
.ip-first-steps__item {
  display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; gap: 14px; align-items: center;
  padding: 14px 16px;
}
.ip-first-steps__item + .ip-first-steps__item { border-top: 1px solid var(--ip-border-soft); }
.ip-first-steps__mark {
  display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
  border: 2px solid var(--ip-border-strong); color: transparent; font-size: 11px;
}
.ip-first-steps__item.is-done .ip-first-steps__mark {
  background: var(--ip-success-text, #3d7a3e); border-color: var(--ip-success-text, #3d7a3e); color: #fff;
}
.ip-first-steps__item:not(.is-done) .ip-first-steps__mark i { display: none; }
.ip-first-steps__label { margin: 0; font-weight: 650; }
.ip-first-steps__item.is-done .ip-first-steps__label { color: var(--ip-text-muted); }
.ip-first-steps__why { margin: 2px 0 0; font-size: var(--ip-text-sm); line-height: 1.5; color: var(--ip-text-muted); }
.ip-first-steps__foot { margin-top: 8px; }
@media (max-width: 640px) {
  .ip-first-steps__item { grid-template-columns: 28px minmax(0, 1fr); }
  .ip-first-steps__item .btn { grid-column: 2; justify-self: start; }
}

.ip-first-steps-banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px;
  margin: 0 0 16px; padding: 10px 14px;
  background: var(--ip-primary-soft); border: 1px solid var(--ip-border); border-radius: var(--ip-radius);
  font-size: var(--ip-text-base);
}
.ip-first-steps-banner > i { color: var(--ip-primary); }
.ip-first-steps-banner > span { flex: 1 1 260px; }

/* Esito dei controlli del primo avvio: era il triangolo nativo del browser
   in grassetto, l'unico elemento della card con quell'aspetto. Ora è una
   riga muta con chevron, separata dal form da un filetto, come i piedi delle
   altre card di accesso. */
.ip-setup-checks {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--ip-border);
  font-size: var(--ip-text-sm);
  color: var(--ip-text-muted);
}
.ip-setup-checks summary {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-weight: 500;
  list-style: none;
}
.ip-setup-checks summary::-webkit-details-marker { display: none; }
.ip-setup-checks summary::before {
  content: '';
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--ip-transition);
}
.ip-setup-checks[open] summary::before { transform: rotate(45deg); }
.ip-setup-checks summary:hover { color: var(--ip-text); }
.ip-setup-checks li { color: var(--ip-text); }
.ip-setup-checks li .fa-circle-check { color: var(--ip-success); }
.ip-setup-checks li .fa-triangle-exclamation { color: var(--ip-warning); }
/* Nota di piede del primo avvio e suggerimento sotto la password: testo
   corrente muto. La nota usava .ip-webauthn-status, la riga di stato mono
   dei pulsanti passkey, e così sembrava un messaggio di sistema. */
.ip-auth-note {
  margin: 14px 0 0;
  font-size: var(--ip-text-sm);
  line-height: 1.5;
  color: var(--ip-text-muted);
  text-align: center;
}
.ip-field-hint {
  display: block;
  margin-top: 4px;
  font-size: var(--ip-text-xs);
  color: var(--ip-text-muted);
}
.ip-setup-checks ul { margin: 8px 0 0; padding: 0; list-style: none; display: grid; gap: 6px; }
.ip-setup-checks li i { margin-right: 6px; }

/* Pulsanti delle modali di conferma alla misura dei pulsanti dell'app (.btn-sm:
   13px). Nel partial erano .btn pieni (16px), e il codice che li ricolora per
   «elimina» / «esci senza salvare» riscrive className da zero, quindi un
   btn-sm nel markup non sopravviveva: la misura sta qui, sul contenitore. */
#ip-confirm-modal .modal-footer .btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--ip-text-sm);
  padding: 5px 12px;
  min-height: var(--ip-control-h-sm);
}
#ip-confirm-modal .modal-footer .btn .fa { font-size: var(--ip-text-sm); }
