/* ═══════════════════════════════════════════════════════════════════
   Calidae — Design system
   Tokens → primitives → layout → components → responsive
   Deux thèmes : clair (défaut) et sombre, via [data-theme] sur <html>
═══════════════════════════════════════════════════════════════════ */

/* ─────────────────────────────────────────────
   0. POLICES

   Servies depuis ce domaine, et non plus par Google Fonts : une feuille
   de style tierce fait transmettre l'adresse IP du visiteur à son
   fournisseur avant même qu'il ait cliqué quoi que ce soit, ce qu'aucune
   base légale ne couvre ici.

   Fraunces v38 et Inter v20, découpées en sous-ensembles exactement comme
   Google les sert — les plages ci-dessous sont les siennes, à la virgule.
   Ce sont des polices VARIABLES : leur axe `wght` court de 100 à 900, donc
   un seul fichier par famille et par sous-ensemble porte les graisses 400
   à 700 employées ici. Aucune graisse n'est synthétisée par le navigateur.

   Tous les alphabets sont présents, y compris cyrillique, grec et
   vietnamien : le nom d'un locataire s'écrit dans sa lettre. Cela ne
   coûte rien au chargement — `unicode-range` fait que le navigateur ne
   télécharge un fichier que si un caractère de sa plage figure sur la
   page. Une page en français n'en récupère que deux.
───────────────────────────────────────────── */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/fraunces-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/fraunces-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/fraunces-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-greek.woff2') format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-greek-ext.woff2') format('woff2');
  unicode-range: U+1F00-1FFF;
}

/* ─────────────────────────────────────────────
   1. TOKENS
───────────────────────────────────────────── */
:root {
  color-scheme: light;

  /* Surfaces — axe chaud unique (~35°), sable.
     Toute la gamme partage la même teinte : c'est ce qui évite le rendu
     « délavé » d'une palette dont les surfaces et les textes se contredisent. */
  --bg:            #FAF9F6;
  --surface:       #FFFFFF;
  --surface-2:     #F5F3EE;
  --surface-3:     #EFEDE6;
  --surface-inv:   #1A1815;
  --border:        #E4E1D8;
  --border-2:      #CFCBBF;

  /* Encre — papier et encre, rien d'autre */
  --ink:           #1A1815;
  --ink-2:         #55514A;
  --ink-3:         #6E6A62;
  --ink-inv:       #FAF9F6;

  /* Accent — bronze sourd, employé avec parcimonie.
     Les actions principales sont en encre pleine, pas en couleur :
     un bouton noir sur papier est plus tenu qu'un bouton coloré. */
  --accent:        #7A6A3C;
  --accent-hover:  #635634;
  --accent-ink:    #FFFFFF;
  --accent-soft:   #F2EEE1;
  --accent-soft-2: #E2DABF;
  --accent-fg:     #5E5230;
  --accent-ring:   rgba(122, 106, 60, .26);

  /* Bouton plein : encre sur papier, inversé en thème sombre */
  --solid-hover:   #35312A;

  /* Statuts — fond / texte.
     L'accent n'étant plus doré, « provisoire » retrouve un ambre naturel
     au lieu de l'orange forcé qui servait à l'en distinguer. */
  --ok-bg:    #E8EEE2;  --ok-fg:    #38602F;  --ok-mark:   #4E8442;
  --warn-bg:  #F6EEDC;  --warn-fg:  #7E5A12;  --warn-mark: #B08420;
  --info-bg:  #E7EDF3;  --info-fg:  #2D5477;  --info-mark: #4C7CA5;
  --live-bg:  #EDE9F0;  --live-fg:  #5B4275;  --live-mark: #7D639A;
  --bad-bg:   #F5E6E1;  --bad-fg:   #8F3A26;  --bad-mark:  #B55B41;

  /* Data-viz — série unique, dans le bronze de l'accent */
  --viz-bar:       #A08D53;
  --viz-bar-hi:    #7A6A3C;
  --viz-grid:      #EAE7DE;

  /* Rayons — presque nets. Les grands arrondis « app mobile » sont
     ce qui tirait l'ensemble vers le tableau de bord générique. */
  --r-xs: 3px; --r-sm: 4px; --r: 5px; --r-lg: 7px; --r-xl: 9px; --r-full: 999px;

  /* Ombres — quasi supprimées : la hiérarchie vient de la typo et des filets.
     Seule l'élévation réelle (modale, tiroir) en conserve une. */
  --sh-1: 0 1px 1px rgba(26,24,21,.05);
  --sh-2: 0 1px 2px rgba(26,24,21,.05);
  --sh-3: 0 16px 44px rgba(26,24,21,.15), 0 4px 12px rgba(26,24,21,.07);

  /* Typo — un serif de titrage porte le caractère, l'Inter le fonctionnel */
  --f-ui:      'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-display: 'Fraunces', Georgia, 'Times New Roman', serif;

  /* Gabarit */
  --rail: 250px;
  --topbar-h: 62px;
  --ease: cubic-bezier(.32, .72, 0, 1);
}

:root[data-theme="dark"] {
  color-scheme: dark;

  /* Le sombre garde le même axe chaud : un gris neutre paraîtrait bleuté
     par contraste avec l'accent doré. */
  --bg:            #17150F;
  --surface:       #1E1C15;
  --surface-2:     #24211A;
  --surface-3:     #2C2921;
  --surface-inv:   #FAF9F6;
  --border:        #302D24;
  --border-2:      #423E33;

  --ink:           #EDEAE1;
  --ink-2:         #A5A093;
  --ink-3:         #8B8779;
  --ink-inv:       #1A1815;

  /* Sur fond sombre le bronze devient laiton et gagne en présence */
  --accent:        #C2AC6A;
  --accent-hover:  #D2BE80;
  --accent-ink:    #1A1710;
  --accent-soft:   #2C2718;
  --accent-soft-2: #3E3722;
  --accent-fg:     #D6C48A;
  --accent-ring:   rgba(194, 172, 106, .28);

  --solid-hover:   #E5E1D6;

  --ok-bg:    #1E2A18;  --ok-fg:    #9DBE8C;  --ok-mark:   #6B9459;
  --warn-bg:  #2C2515;  --warn-fg:  #D6B478;  --warn-mark: #B08420;
  --info-bg:  #1B242E;  --info-fg:  #9BB6CE;  --info-mark: #4C7CA5;
  --live-bg:  #262032;  --live-fg:  #B7A3C9;  --live-mark: #7D639A;
  --bad-bg:   #2E1E19;  --bad-fg:   #D9A08C;  --bad-mark:  #B55B41;

  --viz-bar:       #8E7C46;
  --viz-bar-hi:    #C2AC6A;
  --viz-grid:      #2A2721;

  --sh-1: 0 1px 2px rgba(0,0,0,.44);
  --sh-2: 0 4px 14px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.32);
  --sh-3: 0 24px 60px rgba(0,0,0,.64), 0 6px 18px rgba(0,0,0,.42);
}

/* ─────────────────────────────────────────────
   2. RESET & BASE
───────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Le double-tape du navigateur agrandit la page sans moyen commode de revenir
   en arrière, et il se déclenche dès qu'on presse deux fois de suite au même
   endroit : les flèches de mois du calendrier, un onglet, une case à cocher.
   `manipulation` le retire en ne gardant que le défilement et le pincement —
   c'est-à-dire :

     manipulation  =  pan-x pan-y pinch-zoom

   Le pincement reste ouvert à dessein, contrairement à ../catan : cette
   interface est faite de texte et de formulaires, et il est le dernier recours
   pour grossir un tableau serré. Pour la même raison, pas de user-scalable=no
   sur le viewport, qui interdirait les deux.

   Sur le sélecteur universel, et non sur html ou body : WebKit n'honore pas
   touch-action posé là et la valeur ne redescend pas sur les descendants.

   Safari ignore parfois jusqu'à cette déclaration face à son propre « zoom
   intelligent » ; assets/app.js refuse en plus le second tape d'un double. */
* { touch-action: manipulation; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--f-ui);
  font-size: 14px;
  line-height: 1.5;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* dvh suit la barre d'URL mobile ; vh se résout contre le grand viewport
     (barre masquée) et laisserait une bande vide en bas sous viewport-fit=cover.
     La ligne vh reste en repli pour les navigateurs sans dvh. */
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
}

/* Un serif ne veut pas du crénage négatif qu'on applique aux grotesques */
h1, h2, h3, h4 { font-family: var(--f-display); font-weight: 600; letter-spacing: -.005em; line-height: 1.22; }

/* Libellés en petites capitales espacées — le détail le plus « imprimé »
   du système, appliqué partout où un champ ou une colonne est nommé. */
.label, .fieldset-title, .rail-group, .block-title, .kpi-lbl,
.recap-title, .agenda-date, thead th {
  text-transform: uppercase;
  letter-spacing: .09em;
  font-weight: 600;
}

button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
a { color: inherit; text-decoration: none; }
svg { display: block; }

/* Focus — visible partout, jamais sur clic souris */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

::selection { background: var(--accent-soft-2); color: var(--accent-fg); }

/* Barres de défilement discrètes */
* { scrollbar-width: thin; scrollbar-color: var(--border-2) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: var(--r-full); border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-track { background: transparent; }

/* Piles de texte (titre + sous-titre).
   Ces libellés sont des <span> — la plupart vivent dans un <button>, où l'on
   évite <div>. Sans display:block ils resteraient inline : les deux lignes se
   suivraient sur la même ligne et `text-overflow: ellipsis` serait sans effet. */
.brand-name, .brand-sub,
.user-name, .user-mail,
.cell-main, .cell-sub,
.tc-name, .tc-sub,
.agenda-name, .agenda-meta,
.todo-name, .todo-meta,
.pc-lbl, .pc-val,
.pi-title, .pi-sub,
.kpi-lbl, .kpi-val, .kpi-meta { display: block; }

/* Chiffres alignés là où ils s'empilent */
.tnum, td .num, th .num, .cal-dn, .chart-tick { font-variant-numeric: tabular-nums; }

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

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ─────────────────────────────────────────────
   3. PRIMITIVES
───────────────────────────────────────────── */

/* ── Boutons ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: 38px; padding: 0 15px;
  border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 600; letter-spacing: -.005em;
  white-space: nowrap; cursor: pointer;
  transition: background .16s var(--ease), border-color .16s, color .16s, transform .1s, box-shadow .16s;
}
.btn:active { transform: translateY(.5px); }
.btn:disabled { opacity: .5; pointer-events: none; }
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }

.btn-primary   { background: var(--surface-inv); color: var(--ink-inv); }
.btn-primary:hover { background: var(--solid-hover); }

.btn-ghost     { background: transparent; color: var(--ink-2); }
.btn-ghost:hover { background: var(--surface-3); color: var(--ink); }

.btn-outline   { background: var(--surface); color: var(--ink); border: 1px solid var(--border); }
.btn-outline:hover { background: var(--surface-2); border-color: var(--border-2); }

.btn-soft      { background: var(--surface-3); color: var(--ink); }
.btn-soft:hover { background: var(--border); }

.btn-danger    { background: var(--bad-bg); color: var(--bad-fg); }
.btn-danger:hover { background: var(--bad-bg); filter: brightness(.95); }
:root[data-theme="dark"] .btn-danger:hover { filter: brightness(1.35); }

.btn-solid-danger { background: #B8442B; color: #fff; }
.btn-solid-danger:hover { background: #9A361F; }

.btn-sm  { height: 32px; padding: 0 11px; font-size: 12.5px; border-radius: var(--r-xs); }
.btn-sm svg { width: 14px; height: 14px; }
.btn-icon { width: 38px; padding: 0; }
.btn-icon.btn-sm { width: 32px; }

/* ── Champs ── */
/* `hidden` doit vouloir dire caché, partout et sans exception.
   La règle du navigateur — `[hidden] { display: none }` — a la même
   spécificité qu'une classe : le moindre `display` d'auteur la bat, et
   l'élément reste affiché alors que le script le croit masqué. C'est un
   piège silencieux, on le referme une fois pour toutes. */
[hidden] { display: none !important; }

.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.field.full { grid-column: 1 / -1; }

.label {
  font-size: 10.5px; color: var(--ink-3);
  display: flex; align-items: center; gap: 5px;
}
.label .req { color: var(--bad-mark); }
.hint { font-size: 11.5px; color: var(--ink-3); line-height: 1.45; }

.input, .select, .textarea {
  width: 100%; min-width: 0;
  height: 40px; padding: 0 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: 14px; color: var(--ink);
  outline: none;
  transition: border-color .15s, box-shadow .15s, background .15s;
}

/* Les champs d'heure et de date embarquent un contrôle natif qui se dimensionne
   sur son contenu : sur mobile, il ignore la largeur demandée et déborde de la
   carte. Neutraliser son apparence le fait rentrer dans le rang, et l'alignement
   à gauche le raccorde aux autres champs — le natif centre son texte. */
input[type="time"], input[type="date"] {
  -webkit-appearance: none; appearance: none;
  min-width: 0; max-width: 100%;
}
input[type="time"]::-webkit-date-and-time-value,
input[type="date"]::-webkit-date-and-time-value { text-align: left; }
.textarea { height: auto; min-height: 84px; padding: 10px 12px; resize: vertical; line-height: 1.55; }
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--border-2); }
/* `background-color` et non le raccourci : `background` réinitialiserait aussi
   l'image, la répétition et la position — donc le chevron du `.select`, que le
   thème sombre redéclare avec une spécificité supérieure et qui se répéterait
   alors en mosaïque faute de `no-repeat`. */
.input:focus, .select:focus, .textarea:focus {
  background-color: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
.input[type="date"], .input[type="time"] { padding-right: 8px; }
:root[data-theme="dark"] .input[type="date"]::-webkit-calendar-picker-indicator,
:root[data-theme="dark"] .input[type="time"]::-webkit-calendar-picker-indicator { filter: invert(.75); }

.select {
  appearance: none; cursor: pointer; padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23858C8F' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
:root[data-theme="dark"] .select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7571' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
}
/* Seules les options d'invite portent `hidden` : tant que l'une d'elles est
   sélectionnée, le champ se lit comme un placeholder et non comme une valeur. */
.select:has(> option[hidden]:checked) { color: var(--ink-3); }

.input-group { position: relative; display: flex; align-items: center; }
.input-group .input { padding-left: 36px; }
.input-group > svg {
  position: absolute; left: 11px; width: 16px; height: 16px;
  color: var(--ink-3); pointer-events: none;
}
/* La réserve à droite tient le suffixe : élargie par `unit-lg` quand l'unité
   est un mot (« pers. ») plutôt qu'un symbole. */
.input-suffix { position: relative; --unit-room: 38px; }
.input-suffix.unit-lg { --unit-room: 56px; }
.input-suffix .input { padding-right: var(--unit-room); }
.input-suffix .unit {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font-size: 13px; color: var(--ink-3); pointer-events: none;
  white-space: nowrap;
}
/* Les flèches natives du champ nombre se dessinent au ras du bord droit, juste
   sous l'unité : on les retire, le suffixe occupe seul cette place. */
.input-suffix .input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.input-suffix .input[type="number"]::-webkit-outer-spin-button,
.input-suffix .input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Champ de mot de passe, avec son œil.
   Ces règles servaient d'abord à l'écran de connexion, qui les portait en
   propre ; elles vivent ici depuis que les modales du compte s'en servent
   aussi — login.php charge cette feuille et les retrouve donc à l'identique. */
.pw-wrap { position: relative; }
.pw-wrap .input { padding-right: 42px; }
.pw-toggle {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; border-radius: var(--r-xs);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-3); transition: background .15s, color .15s;
}
.pw-toggle:hover { background: var(--surface-3); color: var(--ink); }
.pw-toggle svg { width: 16px; height: 16px; }

/* Interrupteur */
.switch { display: inline-flex; align-items: center; gap: 10px; cursor: pointer; font-size: 13.5px; font-weight: 500; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
  width: 38px; height: 22px; border-radius: var(--r-full);
  background: var(--border-2); position: relative; flex-shrink: 0;
  transition: background .18s var(--ease);
}
.switch .track::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff; box-shadow: var(--sh-1);
  transition: transform .18s var(--ease);
}
.switch input:checked + .track { background: var(--accent); }
.switch input:checked + .track::after { transform: translateX(16px); }
.switch input:focus-visible + .track { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── Badges & pastilles ── */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  height: 21px; padding: 0 8px;
  border-radius: var(--r-xs);
  font-size: 11.5px; font-weight: 600; letter-spacing: -.005em;
  white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.badge svg { width: 12px; height: 12px; }
.b-ok   { background: var(--ok-bg);   color: var(--ok-fg); }
.b-warn { background: var(--warn-bg); color: var(--warn-fg); }
.b-info { background: var(--info-bg); color: var(--info-fg); }
.b-live { background: var(--live-bg); color: var(--live-fg); }
.b-bad  { background: var(--bad-bg);  color: var(--bad-fg); }
.b-mute { background: var(--surface-3); color: var(--ink-2); }

/* ── Avatar ── */
.avatar {
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; flex-shrink: 0;
  border-radius: var(--r-full);
  background: var(--accent-soft); color: var(--accent-fg);
  font-family: var(--f-display); font-size: 13px; font-weight: 700;
  letter-spacing: .01em; text-transform: uppercase;
}
.avatar-lg { width: 48px; height: 48px; font-size: 17px; }
.avatar-sm { width: 28px; height: 28px; font-size: 11px; }

/* ── Carte ── */
.card {
  /* Déclarée en variable parce qu'un second endroit doit la reprendre : voir
     `.card-flush > .card-head` juste dessous. Les deux valeurs écrites à la
     main avaient déjà divergé de 4px, ce qui ne se voyait pas tant qu'une
     carte à fleur et une carte ordinaire ne se faisaient pas face. */
  --card-pad: 26px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--card-pad);
}
.card-flush { padding: 0; overflow: hidden; }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
}
/* Un intitulé au-dessus d'un tableau à fleur de carte. La carte n'a plus de
   marge intérieure — c'est ce qui laisse le tableau toucher ses bords — donc
   l'en-tête reprend la sienne, à lui seul, et exactement la même : son titre
   doit tomber sur l'axe de celui d'une carte ordinaire posée à côté.
   En étroit, cette règle est annulée : la carte y redevient une simple pile de
   lignes et reprend un padding ordinaire, que celui-ci viendrait doubler. */
.card-flush > .card-head { padding: var(--card-pad) var(--card-pad) 0; }
.card-title { font-family: var(--f-display); font-size: 17.5px; font-weight: 600; letter-spacing: -.005em; }
.card-sub { font-size: 12.5px; color: var(--ink-3); font-weight: 400; margin-top: 2px; }

/* ── État vide ── */
.empty { text-align: center; padding: 52px 24px; }
.empty-icon {
  width: 52px; height: 52px; margin: 0 auto 14px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--r); background: var(--surface-3); color: var(--ink-3);
}
.empty-icon svg { width: 24px; height: 24px; }
.empty-title { font-family: var(--f-display); font-size: 17px; font-weight: 600; margin-bottom: 6px; }
.empty-desc { font-size: 13px; color: var(--ink-3); max-width: 320px; margin: 0 auto 18px; line-height: 1.55; }
.empty-desc:last-child { margin-bottom: 0; }

/* ── Squelettes de chargement ── */
.skel { background: var(--surface-3); border-radius: var(--r-xs); position: relative; overflow: hidden; }
.skel::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,.5), transparent);
  animation: shimmer 1.4s infinite;
}
:root[data-theme="dark"] .skel::after { background: linear-gradient(90deg, transparent, rgba(255,255,255,.05), transparent); }
@keyframes shimmer { 100% { transform: translateX(100%); } }

/* ─────────────────────────────────────────────
   4. GABARIT
───────────────────────────────────────────── */
.app { display: flex; min-height: 100vh; min-height: 100dvh; }

/* ── Rail latéral ── */
.rail {
  width: var(--rail); flex-shrink: 0;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex; flex-direction: column;
  position: fixed; inset: 0 auto 0 0; z-index: 60;
  transition: transform .3s var(--ease);
}
.rail-head { padding: 18px 16px 14px; }
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.brand-mark {
  width: 34px; height: 34px; flex-shrink: 0;
  border-radius: var(--r-sm);
  background: var(--surface-inv); color: var(--ink-inv);
  display: flex; align-items: center; justify-content: center;
}
.brand-mark svg { width: 18px; height: 18px; }
.brand-text { min-width: 0; }
.brand-name {
  font-family: var(--f-display); font-size: 16.5px; font-weight: 600;
  letter-spacing: -.005em; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand-sub {
  font-size: 10.5px; color: var(--ink-3); letter-spacing: .04em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}


.rail-search {
  margin: 4px 12px 10px;
  display: flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 10px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--ink-3); font-size: 13px; width: calc(100% - 24px);
  transition: border-color .15s, background .15s;
}
.rail-search:hover { border-color: var(--border-2); background: var(--surface); }
.rail-search svg { width: 15px; height: 15px; flex-shrink: 0; }
.rail-search span { flex: 1; text-align: left; }
.kbd {
  display: inline-flex; align-items: center; gap: 1px;
  height: 19px; padding: 0 5px;
  background: var(--surface-3); border: 1px solid var(--border);
  border-radius: 5px;
  font-family: var(--f-ui); font-size: 10.5px; font-weight: 600;
  color: var(--ink-3);
}

.rail-nav { flex: 1; padding: 4px 12px; display: flex; flex-direction: column; gap: 2px; overflow-y: auto; }
.rail-group {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); padding: 16px 10px 6px;
}
.rail-group:first-child { padding-top: 4px; }

.nav-link {
  display: flex; align-items: center; gap: 10px;
  height: 38px; padding: 0 10px;
  border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 500; color: var(--ink-2);
  text-align: left; width: 100%;
  transition: background .14s, color .14s;
  position: relative;
}
.nav-link svg { width: 17px; height: 17px; flex-shrink: 0; opacity: .85; }
.nav-link:hover { background: var(--surface-3); color: var(--ink); }
.nav-link[aria-current="page"] {
  background: var(--accent-soft); color: var(--accent-fg); font-weight: 600;
}
.nav-link[aria-current="page"] svg { opacity: 1; }
.nav-count {
  margin-left: auto; font-size: 11.5px; font-weight: 600;
  color: var(--ink-3); font-variant-numeric: tabular-nums;
}
.nav-link[aria-current="page"] .nav-count { color: var(--accent-fg); }

.rail-foot { padding: 12px; border-top: 1px solid var(--border); }

/* Résumé « en direct » */
.pulse { padding: 2px 2px 10px; margin-bottom: 8px; border-bottom: 1px solid var(--border); }
.pulse-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 3px 0; }
.pulse-row + .pulse-row { border-top: 1px solid var(--border); margin-top: 3px; padding-top: 6px; }
.pulse-lbl { font-size: 12px; color: var(--ink-3); }
.pulse-val { font-family: var(--f-display); font-size: 16px; font-weight: 600; letter-spacing: -.005em; }

.user-row { display: flex; align-items: center; gap: 9px; padding: 4px 2px; }
/* La déconnexion est passée du lien au formulaire — elle change l'état, elle
   doit être un POST. Le formulaire se fait transparent pour la mise en page :
   c'est le bouton qu'il enveloppe qui reste l'élément flexible du parent. */
.logout-form { display: flex; }
.user-info { min-width: 0; flex: 1; }
.user-name { font-size: 12.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-mail { font-size: 11px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── Zone principale ── */
.main { flex: 1; min-width: 0; margin-left: var(--rail); display: flex; flex-direction: column; }

.topbar {
  height: var(--topbar-h); flex-shrink: 0;
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 0 24px;
}
.topbar-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.topbar-title { font-family: var(--f-display); font-size: 21px; font-weight: 600; letter-spacing: -.005em; }
.topbar-sub { font-size: 12px; color: var(--ink-3); margin-top: -1px; }
.topbar-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.icon-btn {
  width: 36px; height: 36px; border-radius: var(--r-sm);
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-2); transition: background .15s, color .15s;
}
.icon-btn:hover { background: var(--surface-3); color: var(--ink); }
.icon-btn svg { width: 17px; height: 17px; }

.content { flex: 1; padding: 22px 24px 48px; max-width: 1560px; width: 100%; margin-inline: auto; }
.view { display: none; animation: viewIn .26s var(--ease); }
.view.active { display: block; }
@keyframes viewIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ─────────────────────────────────────────────
   5. TABLEAU DE BORD
───────────────────────────────────────────── */
/* Une seule bande divisée par des filets, plutôt que des boîtes flottantes.
   Le nombre de colonnes se déduit de la largeur : la bande a d'abord compté
   quatre compteurs, la location longue durée en a ajouté un cinquième, et
   l'écran d'administration n'en affiche que quatre. Un compte figé obligeait
   à le reprendre à chaque fois, et laissait un cinquième compteur seul sur
   une deuxième ligne.

   Les filets sont portés par le `gap` — un fond de la couleur du trait, que
   les tuiles recouvrent — et non plus par une bordure sur chaque tuile. Une
   bordure suppose de savoir qui commence une ligne : elle réapparaissait au
   mauvais endroit dès que la bande se repliait, et demandait un `nth-child`
   par palier. Là, chaque intervalle est tracé, horizontal ou vertical. */
.kpi-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px;
  margin-bottom: 22px;
  background: var(--border);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  overflow: hidden;
}
.kpi {
  padding: 20px 22px;
  display: flex; flex-direction: column; gap: 2px;
  position: relative;
  background: var(--surface);
}
/* Un nombre impair de compteurs laisserait la dernière tuile à moitié de
   ligne, le reste en fond de filet. Elle occupe donc ce qui reste — et ne
   s'étire pas quand la ligne est déjà pleine, son début restant automatique. */
.kpi:last-child { grid-column: auto / -1; }
.kpi-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.kpi-lbl { font-size: 12.5px; font-weight: 500; color: var(--ink-2); }
.kpi-ico {
  width: 30px; height: 30px; border-radius: var(--r-xs);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.kpi-ico svg { width: 15px; height: 15px; }
.kpi-ico.t-accent { background: var(--accent-soft); color: var(--accent-fg); }
.kpi-ico.t-info   { background: var(--info-bg);    color: var(--info-fg); }
.kpi-ico.t-ok     { background: var(--ok-bg);      color: var(--ok-fg); }
.kpi-ico.t-warn   { background: var(--warn-bg);    color: var(--warn-fg); }
.kpi-val {
  font-family: var(--f-display); font-size: 34px; font-weight: 400;
  letter-spacing: -.01em; line-height: 1.08;
}
.kpi-val.sm { font-size: 27px; }
.kpi-meta { font-size: 12px; color: var(--ink-3); margin-top: 3px; }

.dash-grid { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 16px; align-items: start; }
.dash-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

/* ── Calendrier ── */
.cal-bar-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.cal-nav { display: flex; align-items: center; gap: 4px; }
.cal-month {
  font-family: var(--f-display); font-size: 17.5px; font-weight: 600;
  letter-spacing: -.005em; text-transform: capitalize;
  min-width: 148px; text-align: center;
}
.cal-legend { display: flex; align-items: center; gap: 12px; font-size: 11.5px; color: var(--ink-3); flex-wrap: wrap; }
.cal-legend i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; margin-right: 5px; vertical-align: -1px; }
/* Séparateur : ce qui suit relève d'un autre axe que les statuts qui précèdent. */
.cal-legend-sep { color: var(--border-2); }
/* Les barres hachurent avec `currentColor` par-dessus la couleur du statut
   (.is-ext) ; la pastille n'a pas de statut à porter, elle prend donc un fond
   neutre. La période est resserrée à 4px : à 9px de côté, les 10px des barres
   ne laisseraient pas voir une seule rayure entière. */
.cal-legend i.is-hatched {
  background-color: var(--ink-3);
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 2px,
    color-mix(in srgb, var(--surface) 70%, transparent) 2px 4px
  );
}

/* ── Grille mensuelle : un seul logement ──
   Rien à comparer, donc autant donner sa place à chaque jour. */
.cal { border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
.cal-dow { display: grid; grid-template-columns: repeat(7, 1fr); background: var(--surface-2); border-bottom: 1px solid var(--border); }
.cal-dow span {
  padding: 7px 4px; text-align: center;
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3);
}
.cal-week { position: relative; }
.cal-week + .cal-week { border-top: 1px solid var(--border); }
.cal-cells { display: grid; grid-template-columns: repeat(7, 1fr); }
.cal-cell {
  min-height: calc(32px + var(--lanes, 1) * 24px + 6px);
  padding: 5px 6px; text-align: left;
  border-left: 1px solid var(--border);
  transition: background .13s;
  position: relative;
  /* Les navigateurs centrent verticalement le contenu d'un <button> : sans ce
     flex, le numéro du jour tomberait au milieu de la case, sous les barres. */
  display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start;
}
.cal-cell:first-child { border-left: none; }
.cal-cell:hover { background: var(--surface-2); }
.cal-cell.out { background: var(--surface-2); }
.cal-cell.out .cal-dn { color: var(--ink-3); opacity: .55; }
.cal-cell.out:hover { background: var(--surface-3); }
.cal-dn { font-size: 12px; font-weight: 600; color: var(--ink-2); line-height: 20px; display: inline-block; min-width: 20px; text-align: center; }
.cal-cell.today .cal-dn { background: var(--accent); color: var(--accent-ink); border-radius: var(--r-full); }
.cal-cell.past .cal-dn { color: var(--ink-3); }

.cal-bars { position: absolute; left: 0; right: 0; top: 30px; bottom: 4px; pointer-events: none; }
.cal-bar {
  position: absolute; height: 21px;
  display: flex; align-items: center; gap: 5px;
  padding: 0 8px;
  font-size: 11.5px; font-weight: 600; letter-spacing: -.005em;
  white-space: nowrap; overflow: hidden;
  pointer-events: auto; cursor: pointer;
  /* Arêtes franches par défaut : un segment sans cap est une barre coupée par
     la fin de semaine, elle doit se lire comme « ça continue à la ligne d'après ». */
  border-radius: 0;
  transition: filter .14s, transform .14s;
}
/* `.is-linked` est posé par le JS sur tous les segments d'un même séjour, celui
   qu'on survole compris : une réservation coupée par la fin de semaine doit
   s'allumer d'un bloc. `:hover` reste dans le sélecteur comme filet — sans JS,
   le segment sous le curseur réagit quand même. */
.cal-bar:hover, .cal-bar.is-linked { filter: brightness(.95); transform: translateY(-1px); }
:root[data-theme="dark"] .cal-bar:hover,
:root[data-theme="dark"] .cal-bar.is-linked { filter: brightness(1.2); }
.cal-bar.cap-s { border-top-left-radius: var(--r-full); border-bottom-left-radius: var(--r-full); padding-left: 10px; }
.cal-bar.cap-e { border-top-right-radius: var(--r-full); border-bottom-right-radius: var(--r-full); padding-right: 10px; }
.cal-bar-txt { overflow: hidden; text-overflow: ellipsis; }

/* Statut du séjour, hachures pour ce qui vient d'une plateforme : les deux
   calendriers parlent la même langue. */
.cal-bar.s-conf, .tl-bar.s-conf { background: var(--ok-bg);   color: var(--ok-fg); }
.cal-bar.s-prov, .tl-bar.s-prov { background: var(--warn-bg); color: var(--warn-fg); }
.cal-bar.s-curr, .tl-bar.s-curr { background: var(--live-bg); color: var(--live-fg); }
.cal-bar.s-done, .tl-bar.s-done { background: var(--info-bg); color: var(--info-fg); }
.cal-bar.is-ext, .tl-bar.is-ext {
  background-image: repeating-linear-gradient(
    135deg,
    transparent 0 5px,
    color-mix(in srgb, currentColor 22%, transparent) 5px 10px
  );
}

/* ── Frise du mois : une ligne par logement ──
   Le nom du bien reste collé à gauche pendant le défilement horizontal ;
   sans ça, on perd de vue à quelle ligne appartient la barre qu'on lit. */
.tl-scroll { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r); }
.tl { min-width: 720px; }

.tl-head { display: flex; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.tl-corner { background: var(--surface-2); }
.tl-days { display: flex; flex: 1; }
.tl-day {
  flex: 1; min-width: 0; padding: 5px 0 6px; text-align: center;
  border-left: 1px solid var(--border);
  display: flex; flex-direction: column; align-items: center; gap: 1px;
}
.tl-day.we { background: var(--surface-3); }
.tl-dow { font-size: 9px; font-weight: 700; letter-spacing: .04em; color: var(--ink-3); }
.tl-dn  { font-size: 11.5px; font-weight: 600; color: var(--ink-2); line-height: 17px; min-width: 17px; }
.tl-day.today .tl-dn { background: var(--accent); color: var(--accent-ink); border-radius: var(--r-full); }

.tl-rowlabel {
  position: sticky; left: 0; z-index: 2;
  width: 168px; flex-shrink: 0;
  display: flex; align-items: center; gap: 7px;
  padding: 0 10px; min-height: 40px;
  background: var(--surface); border-right: 1px solid var(--border);
  font-size: 12.5px;
}
.tl-head .tl-rowlabel { min-height: 0; }
.tl-rowname { flex: 1; min-width: 0; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-rowpct { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.tl-dot { width: 8px; height: 8px; border-radius: var(--r-full); flex-shrink: 0; background: var(--tone); }

.tl-row { display: flex; }
.tl-row + .tl-row { border-top: 1px solid var(--border); }
/* Teintes de logement : elles n'encodent que l'identité du bien — le statut du
   séjour reste porté par la couleur de la barre. */
.tl-row[data-tone="1"] { --tone: #7C6A46; }
.tl-row[data-tone="2"] { --tone: #4A6C7A; }
.tl-row[data-tone="3"] { --tone: #6E5A78; }
.tl-row[data-tone="4"] { --tone: #5B7355; }
.tl-row[data-tone="5"] { --tone: #8A5A4A; }
.tl-row[data-tone="6"] { --tone: #4F5F86; }

.tl-track { position: relative; flex: 1; min-width: 0; }
.tl-cells { display: flex; height: 100%; }
.tl-cell {
  flex: 1; min-width: 0;
  /* La ligne grandit avec ses couloirs : deux séjours qui se chevauchent
     s'empilent au lieu de se cacher l'un l'autre. Le 16px vaut 10 de gouttière
     en haut + 10 en bas − les 4px d'écart que le dernier couloir laisse sous
     lui (pas de 28 pour une barre de 24) ; à retoucher avec le `top` des
     barres, sinon la pile se décentre. */
  min-height: calc(16px + var(--lanes, 1) * 28px);
  border-left: 1px solid var(--border);
  transition: background .13s;
}
.tl-cell.we   { background: var(--surface-2); }
.tl-cell.past { opacity: .72; }
.tl-cell:hover { background: var(--surface-3); }
.tl-cell.today { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent); }

/* `top` est porté par chaque barre selon son couloir : pas de centrage
   automatique ici, sinon toutes se superposeraient à mi-hauteur. */
.tl-bars { position: absolute; inset: 0; pointer-events: none; }
.tl-bar {
  position: absolute; height: 24px;
  display: flex; align-items: center;
  padding: 0 8px;
  font-size: 11.5px; font-weight: 600; letter-spacing: -.005em;
  white-space: nowrap; overflow: hidden;
  pointer-events: auto; cursor: pointer;
  /* Arêtes franches par défaut : un segment sans cap est un séjour coupé par
     le bord du mois, il doit se lire comme « ça continue avant / après ». */
  border-radius: 0;
  transition: filter .14s, transform .14s;
}
.tl-bar:hover, .tl-bar.is-linked { filter: brightness(.95); transform: translateY(-1px); }
:root[data-theme="dark"] .tl-bar:hover,
:root[data-theme="dark"] .tl-bar.is-linked { filter: brightness(1.2); }
.tl-bar.cap-s { border-top-left-radius: var(--r-full); border-bottom-left-radius: var(--r-full); padding-left: 10px; }
.tl-bar.cap-e { border-top-right-radius: var(--r-full); border-bottom-right-radius: var(--r-full); padding-right: 10px; }
.tl-bar-txt { overflow: hidden; text-overflow: ellipsis; }

@media (max-width: 720px) {
  .tl-rowlabel { width: 116px; }
}

/* ── Repères de logement hors frise ── */
.prop-dot {
  width: 8px; height: 8px; border-radius: var(--r-full); flex-shrink: 0;
  background: var(--tone, var(--ink-3));
}
.prop-dot[data-tone="1"] { --tone: #7C6A46; }
.prop-dot[data-tone="2"] { --tone: #4A6C7A; }
.prop-dot[data-tone="3"] { --tone: #6E5A78; }
.prop-dot[data-tone="4"] { --tone: #5B7355; }
.prop-dot[data-tone="5"] { --tone: #8A5A4A; }
.prop-dot[data-tone="6"] { --tone: #4F5F86; }

/* Un seul bien : la colonne n'apprend rien, elle disparaît. */
#resTable.solo-prop .col-prop { display: none; }

/* ── Logements : liste et détail ── */
.prop-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.prop-card {
  display: flex; flex-direction: column; gap: 12px;
  padding: 16px; text-align: left;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.prop-card:hover {
  border-color: var(--border-2); transform: translateY(-1px);
  box-shadow: 0 6px 18px rgba(0,0,0,.06);
}
.prop-card-head { display: flex; align-items: flex-start; gap: 9px; }
.prop-card-name {
  font-family: var(--f-display); font-size: 15.5px; font-weight: 600;
  letter-spacing: -.01em; flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.prop-card-sub {
  font-size: 12px; color: var(--ink-3); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.prop-card-stats { display: flex; gap: 18px; flex-wrap: wrap; }
.prop-stat-v { font-size: 17px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.prop-stat-l { font-size: 10.5px; color: var(--ink-3); letter-spacing: .03em; text-transform: uppercase; }
.prop-card-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-top: 11px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--ink-3);
}

.prop-detail-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.prop-detail-name {
  font-family: var(--f-display); font-size: 18px; font-weight: 600; letter-spacing: -.02em;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── Calendriers importés ── */
/* Trois contrôles, mais deux groupes seulement : le bouton d'ajout est
   solidaire du champ qu'il valide. Laissés indépendants, ils se repliaient
   chacun de leur côté et le bouton se retrouvait seul sur une ligne. */
.input-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.input-row .select { flex: 0 1 145px; }
.input-row-grow {
  display: flex; gap: 8px; align-items: center;
  flex: 1 1 240px; min-width: 0;
}
.input-row-grow .input { flex: 1 1 auto; min-width: 0; }

/* Rangée de boutons qui se replie proprement au lieu de déborder. */
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; }


.feed-row {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 11px 0; border-bottom: 1px solid var(--border);
}
.feed-row:first-child { border-top: 1px solid var(--border); }
.feed-main { flex: 1 1 220px; min-width: 0; }
.feed-name { font-size: 13.5px; font-weight: 600; display: flex; align-items: center; gap: 7px; }
.feed-url {
  font-size: 11.5px; color: var(--ink-3); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.feed-state { font-size: 11.5px; margin-top: 3px; }
.feed-state.ok   { color: var(--ok-fg); }
.feed-state.err  { color: var(--bad-fg); }
.feed-state.none { color: var(--ink-3); }
.feed-actions { display: flex; gap: 4px; flex-shrink: 0; }

@media (max-width: 560px) {
  /* En étroit, les actions passent sous le flux et s'alignent à droite
     plutôt que de comprimer son adresse jusqu'à l'illisible. */
  .feed-main { flex: 1 1 100%; }
  .feed-actions { margin-left: auto; }
  /* Le sélecteur prend sa propre ligne ; le champ et son bouton gardent la leur. */
  .input-row .select { flex: 1 1 100%; }
}

/* Séjour importé sans locataire rattaché : le libellé de substitution ne doit
   pas se lire comme un nom. */
.cell-muted { color: var(--ink-3); font-style: italic; font-weight: 500; }
.avatar svg { width: 14px; height: 14px; }

/* ── Bandeau du logement réglé ── */
.prop-picker {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 12px 14px; margin-bottom: 14px;
}
.prop-picker-lbl {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-2); white-space: nowrap;
}
.prop-picker-lbl svg { width: 15px; height: 15px; color: var(--ink-3); }
.prop-picker .select { flex: 1; min-width: 170px; max-width: 340px; }


/* ── Graphique de revenus ── */
.chart { display: flex; flex-direction: column; }
.chart-plot { position: relative; height: 150px; display: flex; align-items: flex-end; gap: 2px; }
.chart-grid { position: absolute; inset: 0; pointer-events: none; }
.chart-grid i { position: absolute; left: 0; right: 0; height: 1px; background: var(--viz-grid); }
.chart-col {
  flex: 1; height: 100%; min-width: 0;
  display: flex; flex-direction: column; justify-content: flex-end; align-items: stretch;
  position: relative; padding: 0 1px;
  border-radius: var(--r-xs) var(--r-xs) 0 0;
  transition: background .14s;
}
.chart-col:hover { background: var(--surface-3); }
.chart-fill {
  background: var(--viz-bar);
  border-radius: 4px 4px 0 0;
  min-height: 2px;
  transition: background .14s;
}
.chart-col.is-max .chart-fill { background: var(--viz-bar-hi); }
.chart-val {
  position: absolute; left: 0; right: 0; text-align: center;
  font-size: 10.5px; font-weight: 700; color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.chart-axis { display: flex; gap: 2px; padding-top: 7px; }
.chart-tick {
  flex: 1; min-width: 0; text-align: center;
  font-size: 10.5px; color: var(--ink-3);
  white-space: nowrap; overflow: hidden;
}
.chart-tick.now { color: var(--ink); font-weight: 600; }
.chart-scale { display: flex; justify-content: space-between; font-size: 11px; color: var(--ink-3); margin-bottom: 6px; }

/* ── Agenda / à-venir ── */
.agenda { display: flex; flex-direction: column; }
.agenda-day { padding: 12px 0; border-top: 1px solid var(--border); }
.agenda-day:first-child { border-top: none; padding-top: 0; }
.agenda-date {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 8px;
}
.agenda-date.is-today { color: var(--accent-fg); }
.agenda-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; text-align: left;
  padding: 8px 9px; border-radius: var(--r-sm);
  transition: background .14s;
}
.agenda-item:hover { background: var(--surface-2); }
.agenda-item + .agenda-item { margin-top: 2px; }
.agenda-pin {
  width: 30px; height: 30px; border-radius: var(--r-xs); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.agenda-pin svg { width: 15px; height: 15px; }
.agenda-pin.in  { background: var(--info-bg); color: var(--info-fg); }
.agenda-pin.out { background: var(--bad-bg);  color: var(--bad-fg); }
.agenda-body { min-width: 0; flex: 1; }
.agenda-name { font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.agenda-meta { font-size: 11.5px; color: var(--ink-3); }

/* ── À traiter ── */
.todo-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  padding: 12px 2px;
  transition: opacity .14s;
}
.todo-item + .todo-item { border-top: 1px solid var(--border); }
.todo-item:hover { opacity: .62; }
.todo-body { flex: 1; min-width: 0; }
.todo-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.todo-meta { font-size: 11.5px; color: var(--ink-3); }
.todo-amount { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* ─────────────────────────────────────────────
   6. BARRE D'OUTILS & TABLEAU
───────────────────────────────────────────── */
.toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.toolbar .input-group { flex: 1 1 240px; min-width: 200px; }
.toolbar .select { width: auto; min-width: 130px; }
/* Enveloppe du filtre logement — elle n'existe que pour porter l'attribut
   `hidden` quand le compte n'a qu'un bien. Elle se comporte donc comme le
   select qu'elle contient, largeur comprise. */
.filter-slot { display: flex; min-width: 0; }
.filter-slot .select { flex: 1; min-width: 0; }

.segmented {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px; background: var(--surface-3); border-radius: var(--r-sm);
  overflow-x: auto; max-width: 100%;
}
.seg {
  height: 30px; padding: 0 11px; border-radius: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--ink-2);
  white-space: nowrap;
  transition: background .14s, color .14s;
}
.seg:hover { color: var(--ink); }
.seg[aria-pressed="true"] { background: var(--surface); color: var(--ink); box-shadow: var(--sh-1); }
.seg .n { font-size: 11px; color: var(--ink-3); margin-left: 4px; font-variant-numeric: tabular-nums; }

/* Champ dont l'unité se choisit. Il était auparavant dessiné pour ressembler
   aux `.unit` figés des champs voisins — même position, aucun fond, aucun
   chevron. Rien n'indiquait alors qu'on pouvait le toucher, et c'est le seul
   suffixe cliquable du formulaire : il doit se distinguer, pas se fondre. */
.unit-pick { --unit-room: 70px; }
.unit-pick .unit-select {
  position: absolute; top: 5px; right: 5px;
  width: auto; height: 30px;
  /* Réserve à droite pour le chevron, dessiné en fond par `.select`. */
  padding: 0 22px 0 10px;
  /* Propriétés détaillées et non le raccourci `background` : le chevron est une
     image de fond que le thème sombre redéclare, l'écraser la ferait disparaître
     d'un thème sur deux. */
  background-color: var(--surface-3);
  background-position: right 6px center;
  border: 1px solid var(--border); border-radius: 7px;
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  text-align: left; cursor: pointer;
  transition: background-color .14s, border-color .14s, color .14s;
}
.unit-pick .unit-select:hover {
  background-color: var(--surface); border-color: var(--border-2); color: var(--ink);
}

.result-line { font-size: 12.5px; color: var(--ink-3); margin-bottom: 12px; display: flex; gap: 14px; flex-wrap: wrap; }
.result-line b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-2);
  padding: 9px 14px; text-align: left;
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-3); white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
thead th.sortable { cursor: pointer; user-select: none; transition: color .14s; }
thead th.sortable:hover { color: var(--ink); }
thead th .sort-ind { display: inline-block; width: 10px; margin-left: 3px; opacity: .5; }
thead th[aria-sort] { color: var(--ink); }
thead th[aria-sort] .sort-ind { opacity: 1; }
tbody td { padding: 11px 14px; font-size: 13.5px; border-bottom: 1px solid var(--border); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background .12s; cursor: pointer; }
tbody tr:hover td { background: var(--surface-2); }
.cell-flex { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cell-main { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cell-sub { font-size: 11.5px; color: var(--ink-3); }
.row-actions { display: flex; gap: 3px; justify-content: flex-end; }
tbody tr .row-actions .icon-btn { opacity: .55; transition: opacity .14s, background .15s, color .15s; }
tbody tr:hover .row-actions .icon-btn { opacity: 1; }
.pay-chips { display: flex; gap: 5px; flex-wrap: wrap; }

/* ─────────────────────────────────────────────
   7. LOCATAIRES
───────────────────────────────────────────── */
.tenant-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(268px, 1fr)); gap: 14px; }
.tenant-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: 20px;
  display: flex; flex-direction: column; gap: 15px;
  text-align: left; width: 100%;
  transition: border-color .16s;
}
.tenant-card:hover { border-color: var(--ink-3); }
.tc-head { display: flex; align-items: center; gap: 11px; min-width: 0; }
.tc-name { font-family: var(--f-display); font-size: 16px; font-weight: 600; letter-spacing: -.005em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tc-sub { font-size: 11.5px; color: var(--ink-3); }
.tc-rows { display: flex; flex-direction: column; gap: 5px; }
.tc-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--ink-2); min-width: 0; }
.tc-row svg { width: 14px; height: 14px; color: var(--ink-3); flex-shrink: 0; }
.tc-row span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tc-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding-top: 12px; border-top: 1px solid var(--border);
  /* La grille étire les cartes à hauteur égale : sans ceci, le pied des
     cartes peu remplies flotte au milieu avec un vide en dessous. */
  margin-top: auto;
}
.tc-stat { font-size: 12px; color: var(--ink-3); }
.tc-stat b { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }

.account-card { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 20px; }
/* Base de 190px plutôt que `min-width: 0` : sans largeur plancher, l'identité
   se laissait écraser indéfiniment et le bouton ne passait jamais à la ligne
   malgré `flex-wrap`. Il descend maintenant dès que les deux ne tiennent plus
   côte à côte, au lieu de se faire chevaucher par l'adresse. */
.account-card .ac-body { flex: 1 1 190px; min-width: 0; }
.account-card .ac-name { display: block; font-family: var(--f-display); font-size: 16px; font-weight: 600; }
/* Une adresse e-mail n'offre aucun point de césure : sans troncature elle
   déborde de sa boîte au lieu de se replier. Même garde-fou que .user-mail. */
.account-card .ac-mail {
  display: block; font-size: 12.5px; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Les trois actions du compte tiennent ensemble à droite et se replient d'un
   bloc : sans cette enveloppe, `flex-wrap` les distribuait une par une et la
   déconnexion se retrouvait seule sur sa ligne, loin de ce qu'elle conclut. */
.account-card .ac-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ─────────────────────────────────────────────
   8. PARAMÈTRES
───────────────────────────────────────────── */
/* `overflow-x: auto` force l'axe vertical à passer de `visible` à `auto` : la
   barre se laissait donc glisser verticalement dès qu'un enfant dépassait d'un
   pixel. Le trait de séparation est peint en ombre interne plutôt qu'en
   `border-bottom` : il reste ainsi dans la boîte de padding, l'indicateur actif
   peut le recouvrir sans déborder (`bottom: 0`), et il ne reste plus rien à
   faire défiler sur l'axe vertical. */
.tabs {
  display: flex; gap: 2px;
  box-shadow: inset 0 -1px 0 var(--border);
  margin-bottom: 20px;
  overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
}
/* La barre de défilement est masquée : l'onglet actif souligné et les libellés
   coupés en bord de zone signalent déjà qu'il reste à faire défiler. */
.tabs::-webkit-scrollbar { display: none; }
.tab {
  height: 40px; padding: 0 14px;
  font-size: 13.5px; font-weight: 600; color: var(--ink-3);
  white-space: nowrap; position: relative;
  transition: color .14s;
}
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--accent-fg); }
.tab[aria-selected="true"]::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--accent); border-radius: 2px 2px 0 0;
}
.tabpanel { display: none; }
.tabpanel.active { display: block; animation: viewIn .2s var(--ease); }

.settings-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); gap: 16px; align-items: start; }
/* `min-width: auto` est la valeur par défaut d'un élément de grille : il refuse
   de descendre sous la largeur minimale de son contenu. Une longue adresse sans
   espace suffit alors à pousser la colonne au-delà du cadre — et comme le body
   masque le débordement horizontal, ce qui dépasse est découpé, pas défilable. */
.settings-cols > * { min-width: 0; }
/* Une carte placée hors de la grille — le retrait d'un logement, par exemple —
   n'hérite pas de son `gap` : elle viendrait se coller à la rangée précédente. */
.settings-cols + .card { margin-top: 16px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.form-grid.c1 { grid-template-columns: 1fr; }
.form-grid.c3 { grid-template-columns: repeat(3, 1fr); }

/* Chaque champ s'empilait pour son propre compte : une étiquette qui se replie
   sur deux lignes — « Dernier loyer du précédent locataire » — poussait sa
   saisie d'une ligne vers le bas, et la colonne d'à côté ne suivait pas. Les
   champs d'une même rangée partagent donc les trois rangées du parent
   (étiquette · saisie · explication), qui se dimensionnent sur le plus haut des
   trois. Hors `.form-grid`, `.field` garde son empilement en flex : `subgrid`
   n'a de sens que pour un enfant direct de grille.

   Le `row-gap` du parent s'applique aussi entre ces trois pistes. À 14px, la
   piste d'explication — vide pour la plupart des champs — se payait quand même
   son écart, et le formulaire s'allongeait d'une dizaine de pixels par rangée.
   On le met donc à zéro et chaque écart passe par une marge, qui ne coûte rien
   là où l'élément n'existe pas. La marge négative de la grille reprend les 14px
   du dernier rang, qui n'a personne à séparer.

   Trois rangées, pas plus : une poignée de champs empilent un quatrième élément
   (« Arrhes — règlement » : intitulé, mode, case « encaissées », date). Ceux-là
   débordaient de leurs pistes et leur dernier élément chevauchait le précédent
   de 20px. Ils sont donc écartés du traitement par `:not(:has(> :nth-child(4)))`
   et gardent l'empilement en flex, tout en occupant les mêmes trois rangées
   pour ne pas rompre le rythme de la grille. Rien ne les distingue à l'œil.

   Sous `@supports` parce que la reprise en cas d'absence serait pire que le mal :
   `grid-template-rows` serait seul écarté, `display: grid` et `grid-row: span 3`
   resteraient, et le champ prendrait trois rangées séparées par le `row-gap` du
   parent. `:has()` y figure aussi : sans lui, plus de mise à l'écart, donc le
   chevauchement. Faute de l'un ou l'autre, on garde l'empilement en flex. */
@supports (grid-template-rows: subgrid) and selector(:has(*)) {
  .form-grid { row-gap: 0; margin-bottom: -14px; }
  /* Le rang de trois vaut pour tous les champs, y compris ceux mis à l'écart
     ci-dessous : c'est lui qui garde les colonnes en phase. */
  .form-grid > .field { grid-row: span 3; margin-bottom: 14px; }

  .form-grid > .field:not(:has(> :nth-child(4))) {
    display: grid;
    grid-template-rows: subgrid;
    row-gap: 0;
    /* Sans ça, une rangée haute — un `.textarea` en vis-à-vis — étirerait la
       saisie d'en face à sa hauteur. Le `.input` a bien ses 40px en dur, mais
       `.input-suffix` n'en a aucune : il s'étirerait, et son unité (« € »),
       centrée sur lui, se retrouverait sous le champ. */
    align-items: start;
  }
  /* L'écart ne vise aucune classe en particulier : un champ qui intercale une
     case à cocher ou une alerte garde le sien sans qu'on ait à le prévoir. Une
     marge portée par l'élément ne coûte d'ailleurs rien là où il n'existe pas —
     c'est ce qui rend la piste d'explication vide gratuite. */
  .form-grid > .field:not(:has(> :nth-child(4))) > * + * { margin-top: 6px; }
  /* L'étiquette se cale sur le bas de sa piste, pas sur le haut : face à une
     étiquette repliée sur deux lignes, une étiquette courte flottait sinon tout
     en haut, à 20px de sa saisie quand sa voisine n'en était qu'à 6. Elle
     descend donc sur la ligne du bas, et les deux se lisent au même niveau.
     Réservé aux champs en grille : en flex, `align-self` agit sur l'axe
     transversal et pousserait l'étiquette à droite de sa colonne. */
  .form-grid > .field:not(:has(> :nth-child(4))) > .label { align-self: end; }
}

.savebar {
  position: fixed; left: 50%; bottom: var(--savebar-bottom, 22px); z-index: 70;
  display: flex; align-items: center; gap: 14px;
  /* `left: 50%` sans `right` réduit le bloc conteneur à la moitié de l'écran :
     en largeur auto, le libellé se repliait sur trois lignes dès que 50vw
     passait sous la largeur naturelle de la barre. */
  width: max-content; max-width: calc(100vw - 32px);
  padding: 10px 10px 10px 18px;
  background: var(--surface-inv); color: var(--ink-inv);
  /* Rayon sur l'échelle du reste de l'app : `--r-full` faisait un pill de 26px
     de rayon effectif (52px de haut), dont l'arc mordait sur les coins des
     boutons `--r-xs` qui n'ont que 10px de marge. */
  border-radius: var(--r-lg); box-shadow: var(--sh-3);
  font-size: 13.5px; font-weight: 500;
  /* Un simple décalage ne suffit pas à la masquer : la distance à parcourir
     dépend de `bottom`, qui change selon la taille d'écran. On s'appuie donc
     sur visibility/opacity, le transform ne servant qu'à l'animation. */
  transform: translate(-50%, 140%);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: transform .34s var(--ease), opacity .22s var(--ease), visibility 0s linear .34s;
}
.savebar.show {
  transform: translate(-50%, 0);
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: transform .34s var(--ease), opacity .22s var(--ease), visibility 0s;
}
.savebar .btn-primary { background: var(--accent); color: var(--accent-ink); }
/* Le libellé rétrécit avant les boutons, et se coupe plutôt que de se replier. */
.savebar-msg { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.savebar-short { display: none; }

/* ─────────────────────────────────────────────
   9. MODALES / TIROIR / PALETTE
───────────────────────────────────────────── */
.overlay {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(26, 22, 16, .45);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  opacity: 0; pointer-events: none;
  transition: opacity .22s var(--ease);
}
.overlay.open { opacity: 1; pointer-events: auto; }

.modal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-3);
  width: 640px; max-width: 100%; max-height: min(88vh, 900px);
  display: flex; flex-direction: column;
  transform: scale(.97) translateY(8px);
  transition: transform .22s var(--ease);
}
.overlay.open .modal { transform: none; }
.modal-lg { width: 820px; }
.modal-sm { width: 420px; }

.modal-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 20px 22px 14px; flex-shrink: 0;
}
.modal-title { font-family: var(--f-display); font-size: 22px; font-weight: 600; letter-spacing: -.005em; }
.modal-sub { font-size: 12.5px; color: var(--ink-3); margin-top: 2px; }
.modal-body { padding: 0 22px 20px; overflow-y: auto; flex: 1; }
.modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 9px;
  padding: 14px 22px; flex-shrink: 0;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  border-radius: 0 0 var(--r-xl) var(--r-xl);
}
.modal-foot .spacer { margin-right: auto; }
.foot-hint { display: flex; align-items: center; gap: 6px; }

/* Liste de choix d'une feuille — deux voies également légitimes, donc deux
   lignes de même poids : ni bouton principal, ni bouton secondaire. Le doigt
   vise une bande de 60 px de haut sur toute la largeur, pas un bouton. */
.choice-list { display: flex; flex-direction: column; gap: 8px; }
.choice {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 14px 15px; text-align: left;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-lg);
  transition: background .14s, border-color .14s;
}
.choice:hover, .choice:focus-visible { background: var(--surface-3); border-color: var(--accent); }
.choice-ico {
  width: 38px; height: 38px; border-radius: var(--r-sm); flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent-fg);
}
.choice-ico svg { width: 19px; height: 19px; }
.choice-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.choice-title { font-size: 14.5px; font-weight: 700; letter-spacing: -.01em; }
.choice-sub { font-size: 12.5px; color: var(--ink-3); }
/* Le raccourci ne sert qu'à qui a un clavier : il s'efface avec la souris. */
@media (max-width: 640px) { .choice-kbd { display: none; } }

.fieldset { border: none; }
.fieldset + .fieldset { margin-top: 22px; padding-top: 20px; border-top: 1px solid var(--border); }
.fieldset-title {
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 13px;
}

/* Formulaire de réservation : saisie à gauche, récapitulatif collant à droite */
.res-layout { display: grid; grid-template-columns: minmax(0, 1fr) 244px; gap: 24px; align-items: start; }
.res-main { min-width: 0; }
.res-side { position: sticky; top: 0; }
@media (max-width: 860px) {
  .res-layout { grid-template-columns: 1fr; gap: 18px; }
  .res-side { position: static; order: -1; }
}

/* Récapitulatif financier collant */
.recap {
  background: var(--accent-soft); border: 1px solid var(--accent-soft-2);
  border-radius: var(--r); padding: 14px 15px;
}
.recap-title {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-fg); margin-bottom: 10px;
}
.recap-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-size: 13px; color: var(--ink-2); padding: 3.5px 0;
}
.recap-row .v { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 500; }
.recap-row.total {
  margin-top: 7px; padding-top: 9px; border-top: 1px solid var(--accent-soft-2);
  font-size: 14px; font-weight: 700; color: var(--ink);
}
.recap-row.total .v { font-family: var(--f-display); font-size: 20px; font-weight: 600; letter-spacing: -.005em; }
.recap-row.muted { font-size: 12.5px; color: var(--ink-3); }

/* Confirmation */
/* ── Tiroir latéral ── */
.drawer-scrim {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(26, 22, 16, .42);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  opacity: 0; pointer-events: none; transition: opacity .26s var(--ease);
}
.drawer-scrim.open { opacity: 1; pointer-events: auto; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 91;
  width: 440px; max-width: 100%;
  background: var(--surface); border-left: 1px solid var(--border);
  box-shadow: var(--sh-3);
  display: flex; flex-direction: column;
  transform: translateX(102%);
  transition: transform .3s var(--ease);
}
.drawer.open { transform: none; }
.drawer-head {
  padding: 18px 20px 14px; flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  display: flex; align-items: flex-start; gap: 12px;
}
.drawer-body { flex: 1; overflow-y: auto; padding: 18px 20px; }
.drawer-foot {
  flex-shrink: 0; padding: 12px 20px; border-top: 1px solid var(--border);
  background: var(--surface-2);
  display: flex; gap: 8px; flex-wrap: wrap;
}

.dl { display: grid; grid-template-columns: auto 1fr; gap: 9px 16px; align-items: baseline; }
.dl dt { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; }
.dl dd { font-size: 13.5px; font-weight: 500; text-align: right; }
.block-title {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); margin: 20px 0 10px;
}
.block-title:first-child { margin-top: 0; }

.pay-card {
  display: flex; align-items: center; gap: 11px;
  padding: 13px 2px;
}
.pay-card + .pay-card { border-top: 1px solid var(--border); }
.pay-card .pc-body { flex: 1; min-width: 0; }
.pay-card .pc-lbl { font-size: 12.5px; color: var(--ink-3); }
.pay-card .pc-val { font-size: 14.5px; font-weight: 700; font-variant-numeric: tabular-nums; }

.note-box {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-sm); padding: 11px 12px;
  font-size: 13px; color: var(--ink-2); line-height: 1.6;
  white-space: pre-wrap; word-break: break-word;
}

/* État de signature. L'icône reste en haut de la première ligne : le texte
   court sur deux ou trois lignes, et un alignement centré la ferait flotter
   au milieu du paragraphe. */
.sig-state {
  display: flex; gap: 9px; align-items: flex-start;
  font-size: 13px; color: var(--ink-2); line-height: 1.55;
}
.sig-state svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 2px; color: var(--ink-3); }
.sig-state.ok svg   { color: var(--ok-fg); }
.sig-state.warn svg { color: var(--warn-fg); }

/* Même traitement à plat que .todo-item et .pay-card.
   Le rayon est retiré : combiné à une bordure sur un seul côté, il faisait
   courber la ligne de séparation à ses extrémités. */
.mini-stay {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left;
  padding: 11px 2px;
  transition: opacity .14s;
}
.mini-stay:hover { opacity: .62; }
.mini-stay + .mini-stay { border-top: 1px solid var(--border); }

/* ── Palette de commandes ── */
.palette-overlay {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(26, 22, 16, .44);
  backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 12vh 20px 20px;
  opacity: 0; pointer-events: none; transition: opacity .18s var(--ease);
}
.palette-overlay.open { opacity: 1; pointer-events: auto; }
.palette {
  width: 580px; max-width: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--sh-3);
  overflow: hidden; display: flex; flex-direction: column;
  max-height: 62vh;
  transform: scale(.98) translateY(-6px);
  transition: transform .18s var(--ease);
}
.palette-overlay.open .palette { transform: none; }
.palette-input-row {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; border-bottom: 1px solid var(--border);
}
.palette-input-row svg { width: 18px; height: 18px; color: var(--ink-3); flex-shrink: 0; }
.palette-input { flex: 1; border: none; background: none; outline: none; font-size: 15.5px; }
.palette-input::placeholder { color: var(--ink-3); }
.palette-list { overflow-y: auto; padding: 6px; }
.palette-group {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-3); padding: 10px 10px 5px;
}
.palette-item {
  display: flex; align-items: center; gap: 11px;
  width: 100%; text-align: left;
  padding: 9px 10px; border-radius: var(--r-sm);
  font-size: 13.5px;
}
.palette-item .pi-ico {
  width: 28px; height: 28px; border-radius: var(--r-xs); flex-shrink: 0;
  background: var(--surface-3); color: var(--ink-2);
  display: flex; align-items: center; justify-content: center;
}
.palette-item .pi-ico svg { width: 15px; height: 15px; }
.palette-item .pi-body { flex: 1; min-width: 0; }
.palette-item .pi-title { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-item .pi-sub { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.palette-item.sel { background: var(--accent-soft); }
.palette-item.sel .pi-ico { background: var(--accent-soft-2); color: var(--accent-fg); }
.palette-item.sel .pi-title { color: var(--accent-fg); font-weight: 600; }
.palette-foot {
  display: flex; align-items: center; gap: 14px;
  padding: 9px 16px; border-top: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 11.5px; color: var(--ink-3);
}
.palette-foot span { display: inline-flex; align-items: center; gap: 5px; }

/* ── Infobulle graphique ── */
.viz-tip {
  position: fixed; z-index: 130; pointer-events: none;
  background: var(--surface-inv); color: var(--ink-inv);
  border-radius: var(--r-xs); padding: 7px 10px;
  font-size: 12px; line-height: 1.45;
  box-shadow: var(--sh-2);
  opacity: 0; transform: translate(-50%, -100%) translateY(-6px);
  transition: opacity .12s;
  white-space: nowrap;
}
.viz-tip.show { opacity: 1; }
.viz-tip b { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── Notifications ── */
.toasts {
  position: fixed; z-index: 140; bottom: 20px; right: 20px;
  display: flex; flex-direction: column-reverse; gap: 9px;
  pointer-events: none; max-width: calc(100vw - 40px);
}
.toast {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 15px; border-radius: var(--r-sm);
  background: var(--surface-inv); color: var(--ink-inv);
  box-shadow: var(--sh-3);
  font-size: 13.5px; font-weight: 500;
  animation: toastIn .28s var(--ease);
  pointer-events: auto;
}
.toast.out { animation: toastOut .22s var(--ease) forwards; }

/* Les deux sont ancrées en bas, et la notification passe devant : elle
   recouvrait les boutons de la barre d'enregistrement, `pointer-events: auto`
   interceptant le clic par-dessus le marché. Elle s'élève donc au-dessus tant
   que la barre est affichée — 64px, soit sa hauteur et un intervalle.
   `~` et non `+` : les deux éléments sont frères, mais pas voisins. */
.savebar.show ~ .toasts { bottom: calc(var(--savebar-bottom, 22px) + 64px); }
.toast .t-ico {
  width: 20px; height: 20px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
}
.toast .t-ico svg { width: 12px; height: 12px; }
.toast.ok  .t-ico { background: var(--ok-mark);  color: #fff; }
.toast.err .t-ico { background: var(--bad-mark); color: #fff; }
.toast.info .t-ico { background: var(--info-mark); color: #fff; }
@keyframes toastIn  { from { opacity: 0; transform: translateY(14px) scale(.96); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateY(8px) scale(.97); } }

/* ── Bandeau d'erreur ── */
.alert {
  display: none; align-items: center; gap: 11px;
  padding: 12px 15px; margin-bottom: 16px;
  background: var(--bad-bg); color: var(--bad-fg);
  border: 1px solid color-mix(in srgb, var(--bad-fg) 22%, transparent);
  border-radius: var(--r); font-size: 13.5px; font-weight: 500;
}
.alert.show { display: flex; }
.alert svg { width: 18px; height: 18px; flex-shrink: 0; }
/* Un rappel n'est pas une panne : le rouge est réservé à ce qui est cassé. */
.alert-warn {
  background: var(--warn-bg); color: var(--warn-fg);
  border-color: color-mix(in srgb, var(--warn-fg) 22%, transparent);
}
.alert-warn strong { font-weight: 700; }
@media (max-width: 600px) {
  .alert-warn { flex-wrap: wrap; }
  .alert-warn .btn { margin-left: 0 !important; width: 100%; }
}

/* ── Écran de démarrage ── */
.boot {
  position: fixed; inset: 0; z-index: 200;
  background: var(--bg);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px;
  transition: opacity .35s var(--ease);
}
.boot.hide { opacity: 0; pointer-events: none; }
.boot-mark {
  width: 52px; height: 52px; border-radius: var(--r);
  background: var(--surface-inv); color: var(--ink-inv);
  display: flex; align-items: center; justify-content: center;
  animation: bootPulse 1.6s var(--ease) infinite;
}
.boot-mark svg { width: 26px; height: 26px; }
@keyframes bootPulse { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(.92); opacity: .78; } }
.boot-name { font-family: var(--f-display); font-size: 17px; font-weight: 700; letter-spacing: -.02em; }
.boot-track { width: 116px; height: 3px; border-radius: var(--r-full); background: var(--border); overflow: hidden; }
.boot-fill { height: 100%; width: 40%; border-radius: var(--r-full); background: var(--accent); animation: bootSlide 1.3s var(--ease) infinite; }
@keyframes bootSlide { 0% { transform: translateX(-110%); } 100% { transform: translateX(300%); } }

/* ── Barre d'onglets mobile ── */
.tabbar { display: none; }

/* L'icône « Paramètres » de la barre supérieure double l'entrée du rail :
   elle ne sert que lorsque celui-ci est masqué, soit sous 900 px. */
@media (min-width: 901px) { .topbar-nav { display: none; } }
.topbar-nav[aria-current="page"] { color: var(--accent-fg); background: var(--accent-soft); }

/* ─────────────────────────────────────────────
   10. RESPONSIVE
───────────────────────────────────────────── */
@media (max-width: 1200px) {
  .dash-grid { grid-template-columns: minmax(0, 1fr) 300px; }
}

@media (max-width: 1024px) {
  .dash-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 900px) {
  /* La barre du bas couvre les quatre destinations : le rail ferait doublon.
     display:none plutôt qu un décalage hors écran, sinon ses liens restent
     atteignables à la tabulation. */
  .rail { display: none; }
  .main { margin-left: 0; }
  /* « Créer » figure déjà dans la barre du bas */
  .topbar-actions .btn-primary { display: none; }
  /* Hauteur réelle de la tabbar (~60px) + un souffle, la safe-area étant déjà
     absorbée par le padding de la barre elle-même. Un 96px figé creusait un
     vide quand la barre d'URL est en bas et que l'inset retombe à 0. */
  .content { padding: 18px 16px calc(72px + env(safe-area-inset-bottom, 0px)); }
  .topbar { padding: 0 14px; }
  /* Deux colonnes imposées : à 190px de large minimum, `auto-fit` n'en tiendrait
     plus qu'une sous 420px et la bande deviendrait une colonne de cinq tuiles.
     Les filets suivent d'eux-mêmes, et la tuile impaire prend toute la largeur. */
  .kpi-row { grid-template-columns: repeat(2, 1fr); }
  .drawer { width: 100%; }

  /* Barre d'onglets bas d'écran */
  .tabbar {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 55;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter: blur(14px) saturate(1.4);
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    border-top: 1px solid var(--border);
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom, 0px));
  }
  .tabbar button {
    flex: 1; min-width: 0;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    padding: 6px 2px; border-radius: var(--r-sm);
    font-size: 10.5px; font-weight: 600; color: var(--ink-3);
    /* Un libellé qui déborde est coupé net plutôt que replié sur deux
       lignes, ce qui ferait grandir la barre sous le contenu. */
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    transition: color .14s, background .14s;
  }
  .tabbar button svg { width: 19px; height: 19px; }
  .tabbar button[aria-current="page"] { color: var(--accent-fg); background: var(--accent-soft); }
  .toasts { bottom: calc(74px + env(safe-area-inset-bottom, 0px)); left: 16px; right: 16px; align-items: stretch; }
  :root { --savebar-bottom: calc(78px + env(safe-area-inset-bottom, 0px)); }
}

@media (max-width: 720px) {
  /* La recherche prend toute une ligne ; les filtres se partagent la suivante ;
     le bouton s'installe à côté s'il reste la place, sinon il prend la sienne
     et l'occupe entièrement.
     Aucune règle conditionnelle n'est nécessaire : avec un seul logement le
     filtre logement est absent, l'année voisine alors avec le bouton ; avec
     plusieurs, les deux filtres remplissent la ligne et le bouton descend. */
  .toolbar .input-group { flex: 1 1 100%; min-width: 0; }
  /* `auto` plutôt qu'une base fixe : chaque filtre part de la largeur de son
     propre libellé, et le partage se fait au prorata au lieu d'être coupé en
     deux parts égales. « Toutes les années » cesse ainsi de payer pour le
     voisin plus court. */
  .toolbar > .select,
  .toolbar > .filter-slot { flex: 1 1 auto; min-width: 0; }
  /* Le chevron est dessiné en fond : 34px de réserve à droite valent sur un
     champ pleine largeur, pas sur un filtre qui se partage la ligne. */
  .toolbar .select { min-width: 0; padding-right: 28px; background-position: right 8px center; }
  /* Le bouton prend sa propre ligne, entière. Le laisser se glisser à côté
     des filtres suffisait à les rétrécir jusqu'à couper leurs libellés — et
     le cas se produisait justement quand il n'y avait qu'un seul filtre.
     Les trois listes ont ainsi la même silhouette : recherche, filtres, action. */
  .toolbar .btn-primary { flex: 1 1 100%; justify-content: center; }

  .kpi { padding: 15px 16px; }
  .kpi-val { font-size: 27px; }
  .kpi-val.sm { font-size: 21px; }
  .form-grid, .form-grid.c3 { grid-template-columns: 1fr; }
  .settings-cols { grid-template-columns: minmax(0, 1fr); }
  /* 26px de marge intérieure de chaque côté, c'est 52px pris sur 375 : les
     cartes respirent moins mais leur contenu reste lisible. */
  .card { padding: 18px 16px; }

  /* Modales en feuille glissante */
  .overlay { padding: 0; align-items: flex-end; }
  .modal, .modal-lg, .modal-sm {
    width: 100%; max-height: 94vh;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    transform: translateY(18px);
  }
  .modal-foot { border-radius: 0; padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)); }
  .palette-overlay { padding: 8vh 12px 12px; }

  /* Le tableau devient une liste de cartes. Chaque ligne portant désormais son
     propre fond et sa bordure, la carte qui enveloppe le tableau ferait un
     second cadre autour de la pile — on la neutralise (comme .tenant-grid,
     qui n'a jamais eu de conteneur).

     `padding: 0` fait partie de la neutralisation, au même titre que le fond et
     la bordure. Une marge intérieure n'a de sens qu'à l'intérieur d'un cadre ;
     sans cadre, elle n'entoure plus rien et ne fait que décaler. La laisser
     rentrait les lignes de seize pixels par rapport à la barre de recherche qui
     les surmonte — et par rapport aux grilles de logements et de locataires,
     qui n'ont jamais eu de conteneur et partent donc du bord. Trois listes,
     deux alignements. */
  .card-flush {
    background: none; border: none; border-radius: 0; overflow: visible;
    padding: 0;
  }
  /* L'en-tête reprend la marge de la carte quand elle en a une (voir la règle
     de base) ; ici elle n'en a plus, donc lui non plus — sinon son intitulé
     s'indenterait tout seul au-dessus des lignes qu'il annonce. */
  .card-flush > .card-head { padding: 0; }
  .table-wrap { overflow: visible; }
  table, thead, tbody, tr, td { display: block; }
  thead { display: none; }
  tbody tr {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r); padding: 13px 14px; margin-bottom: 10px;
  }
  tbody tr:last-child { margin-bottom: 0; }
  tbody tr:hover td { background: transparent; }
  tbody td { border-bottom: none; padding: 4px 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
  tbody td::before {
    content: attr(data-label);
    font-size: 11.5px; font-weight: 600; color: var(--ink-3); flex-shrink: 0;
  }
  tbody td[data-label="Locataire"] { padding-bottom: 11px; margin-bottom: 6px; border-bottom: 1px solid var(--border); }
  tbody td[data-label="Locataire"]::before,
  tbody td[data-label=""]::before { display: none; }
  tbody td[data-label=""] { padding-top: 10px; }
  .row-actions { width: 100%; }
  .row-actions .icon-btn { opacity: 1; background: var(--surface-3); }
  .cal-cell { padding: 4px 3px; }
  .cal-bar, .tl-bar { font-size: 10.5px; padding: 0 5px; }
  .cal-bar.cap-s, .tl-bar.cap-s { padding-left: 7px; }
  .cal-bar.cap-e, .tl-bar.cap-e { padding-right: 7px; }
  .cal-legend { display: none; }

  /* La barre reste une pastille ajustée à son contenu, comme en bureau : c'est
     le libellé qui raccourcit pour tenir sur une ligne, pas la barre qui
     s'étire d'un bord à l'autre. */
  .savebar { gap: 12px; padding: 10px 10px 10px 15px; max-width: calc(100vw - 24px); }
  .savebar-long { display: none; }
  .savebar-short { display: inline; }
}

@media (max-width: 480px) {
  /* Le bouton est passé sur sa propre ligne : étalé, il se lit comme une
     action de la carte plutôt que comme un reliquat collé à gauche. */
  /* Le bouton est enveloppé d'un <form> depuis qu'il poste au lieu de suivre
     un lien. Sans largeur sur l'enveloppe, « 100 % » ne vaut que la largeur du
     bouton lui-même : c'est le formulaire qui doit occuper la ligne. */
  .account-card .ac-actions { width: 100%; }
  .account-card .logout-form { width: 100%; }
  .account-card .btn { width: 100%; }
  .topbar-sub { display: none; }
  /* Les boutons des barres d'outils occupent une ligne entière à cette
     largeur : la place ne manque pas, et un « + » seul n'annonce pas ce qu'il
     crée. Seuls les boutons logés dans un champ gardent l'abréviation. */
  .btn-label { display: none; }
  .toolbar .btn-label { display: inline; }
  .topbar-actions .btn-primary { width: 36px; padding: 0; }
  .tenant-grid { grid-template-columns: 1fr; }
}

/* Safari iOS zoome automatiquement sur tout champ dont le texte fait moins
   de 16px. On monte donc à 16px sur les appareils tactiles uniquement : la
   densité du bureau est préservée, et le zoom manuel à deux doigts reste
   possible — contrairement à un maximum-scale=1 sur le viewport, qui
   supprimerait le zoom auto au prix de l'accessibilité.
   Ce bloc doit rester en fin de feuille : une media query n'ajoute pas de
   spécificité, il doit donc passer après .palette-input, définie plus haut. */
@media (pointer: coarse) {
  .input, .select, .textarea, .palette-input { font-size: 16px; }
}

/* Un indice clavier ne sert à rien sans clavier. Le critère est le pointeur,
   pas la largeur : une tablette en paysage dépasse le seuil du rail et n'a
   toujours pas de touche Ctrl, tandis qu'une fenêtre de bureau rétrécie en a
   une. `hover: none` écarte les portables tactiles, qui gardent le clavier. */
@media (hover: none) and (pointer: coarse) {
  .rail-search .kbd { display: none; }
  .foot-hint { display: none; }
  .palette-foot { display: none; }
}

@media print {
  .rail, .topbar, .tabbar, .toolbar, .row-actions, .toasts, .savebar { display: none !important; }
  .main { margin: 0; }
  .card { border: 1px solid #ddd; break-inside: avoid; }
}

/* ═══════════════════════════════════════════════════════════
   ABONNEMENT
   ═══════════════════════════════════════════════════════════ */

/* Bandeau d'alerte. Il ne s'affiche que lorsqu'une action est attendue :
   un bandeau permanent cesse d'être lu au bout de deux jours. */
.billing-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 14px; margin-bottom: 14px;
  border-radius: var(--r); border: 1px solid var(--border);
  font-size: 13.5px; line-height: 1.5;
}
.billing-banner[hidden] { display: none; }
.billing-banner svg { width: 16px; height: 16px; flex-shrink: 0; }
.billing-banner span { flex: 1; min-width: 180px; }
.billing-banner .btn { background: color-mix(in srgb, currentColor 13%, transparent); color: inherit; }
.billing-banner .btn:hover { background: color-mix(in srgb, currentColor 22%, transparent); }
.billing-banner.is-warn { background: var(--warn-bg); color: var(--warn-fg); border-color: color-mix(in srgb, var(--warn-fg) 22%, transparent); }
.billing-banner.is-bad  { background: var(--bad-bg);  color: var(--bad-fg);  border-color: color-mix(in srgb, var(--bad-fg) 22%, transparent); }

/* État courant */
.plan-state {
  display: flex; flex-direction: column; gap: 3px;
  padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
}
.plan-state-title { font-weight: 600; font-size: 14px; }
.plan-state-sub   { font-size: 12.5px; color: var(--ink-3); }
.plan-state.is-ok   { background: var(--ok-bg);   color: var(--ok-fg);   border-color: transparent; }
.plan-state.is-warn { background: var(--warn-bg); color: var(--warn-fg); border-color: transparent; }
.plan-state.is-bad  { background: var(--bad-bg);  color: var(--bad-fg);  border-color: transparent; }
.plan-state.is-ok .plan-state-sub,
.plan-state.is-warn .plan-state-sub,
.plan-state.is-bad .plan-state-sub { color: inherit; opacity: .82; }

/* Offres */
.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.plan-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 15px; border-radius: var(--r);
  border: 1px solid var(--border); background: var(--surface);
}
/* L'offre recommandée est celle qui correspond au nombre de biens du compte :
   c'est une aide à la lecture, pas une mise en avant commerciale. */
.plan-card.is-suggested { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.plan-card.is-current   { background: var(--surface-2); }
.plan-head  { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.plan-name  { font-family: var(--f-display); font-size: 15.5px; font-weight: 600; }
.plan-price { font-size: 23px; font-weight: 700; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.plan-price span { font-size: 12px; font-weight: 500; color: var(--ink-3); margin-left: 3px; }
/* `flex: 1` reprend le rôle qu'avait le pavé de présentation : c'est ce qui
   pousse le bouton en bas et aligne les boutons des trois cartes entre eux,
   quelle que soit la longueur de la ligne de capacité. */
.plan-cap { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; flex: 1; }
.plan-cta   { width: 100%; margin-top: 2px; }

/* Repère de bac à sable. Voyant à dessein : il doit être impossible de croire
   encaisser alors qu'on est en mode test. */
/* Les cartes n'ont pas de marge propre : leur espacement vient toujours du
   `gap` du parent. Sans cette règle, celles de l'abonnement se collent. */
#planBody { display: flex; flex-direction: column; gap: 16px; }

.plan-testflag {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 14px;
  border-radius: var(--r);
  background: var(--warn-bg); color: var(--warn-fg);
  border: 1px dashed color-mix(in srgb, var(--warn-fg) 40%, transparent);
  font-size: 13px; line-height: 1.55;
}
.plan-testflag svg { width: 16px; height: 16px; flex-shrink: 0; margin-top: 2px; }
.plan-testflag code {
  font-family: var(--f-mono, ui-monospace, monospace); font-size: .92em;
  background: color-mix(in srgb, currentColor 12%, transparent);
  padding: 1px 4px; border-radius: 4px;
}

/* ═══════════════════════════════════════════════════════════
   ADMINISTRATION
   ═══════════════════════════════════════════════════════════ */
/* L'écran tient en trois panneaux à onglets. Ceux qui empilent des cartes
   prennent le `gap` du conteneur, comme `#planBody` ; celui des comptes suit
   le flux ordinaire, ses éléments portant déjà chacun leur marge.

   `.kpi-row` garde ici la sienne : c'est elle qui espace les compteurs des
   réglages, et rien d'autre ne s'en charge. Une marge d'élément et un `gap`
   de conteneur ne doivent jamais se partager le même intervalle — c'est ce
   cumul qui donnait 38 px sous les compteurs contre 16 entre les cartes. */
#adminHealth, #adminLog, #adminAccounts { display: flex; flex-direction: column; gap: 16px; }
#adminSettings { margin-bottom: 16px; }

/* ── Pagination ──
   Commune au tableau des comptes et au journal. */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--ink-3);
}
.pager b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.pager-nav { display: flex; align-items: center; gap: 6px; }
.pager-pos { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Un bouton de bout de course reste en place, grisé, plutôt que de
   disparaître : sinon les deux autres se décalent d'un cran en arrivant sur la
   première ou la dernière page, et la flèche qu'on vise se dérobe. */
.pager-nav .icon-btn[disabled] { opacity: .35; pointer-events: none; }

/* ── Santé du service ──
   Une ligne par constat : pastille, ce qui est vérifié, ce que ça implique,
   et la valeur trouvée. Le ton de la pastille est ce qui se lit en premier —
   un relevé qui met tout au même niveau se parcourt une fois et jamais plus. */
.hz-row { display: flex; align-items: flex-start; gap: 12px; padding: 13px 0; }
.hz-row + .hz-row { border-top: 1px solid var(--border); }
/* Pas `:first-of-type` : l'en-tête de carte est un `div` lui aussi, et c'est
   lui que le sélecteur désignerait. C'est l'adjacence qui vise juste. */
.card-head + .hz-row { padding-top: 2px; }
.hz-row:last-child { padding-bottom: 2px; }

.hz-ico {
  width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; margin-top: 1px;
  display: flex; align-items: center; justify-content: center;
}
.hz-ico svg { width: 13px; height: 13px; }
.hz-ico.t-ok   { background: var(--ok-bg);    color: var(--ok-fg); }
.hz-ico.t-warn { background: var(--warn-bg);  color: var(--warn-fg); }
.hz-ico.t-bad  { background: var(--bad-bg);   color: var(--bad-fg); }
.hz-ico.t-mute { background: var(--surface-3); color: var(--ink-3); }

.hz-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.hz-label { font-size: 13.5px; font-weight: 600; }
.hz-body .hint { line-height: 1.55; }
.hz-body code {
  font-family: var(--f-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .92em; background: var(--surface-3); border-radius: 4px; padding: 1px 4px;
}
/* La valeur reste à droite, alignée d'une ligne à l'autre, et ne se laisse pas
   comprimer par une explication longue. */
.hz-value {
  font-size: 13px; font-weight: 600; color: var(--ink-2);
  white-space: nowrap; flex-shrink: 0; text-align: right; margin-top: 1px;
  font-variant-numeric: tabular-nums;
}
/* En étroit, la valeur passe sous l'explication plutôt que de la réduire à une
   colonne de deux mots. Le retrait la garde alignée sur le texte, pas sur la
   pastille. */
@media (max-width: 560px) {
  .hz-row { flex-wrap: wrap; }
  .hz-value { width: 100%; text-align: left; padding-left: 34px; }
}

.adm-set {
  display: flex; align-items: center; gap: 14px;
  /* Une hauteur plancher plutôt que le seul remplissage : elle donne le même
     pas d'une ligne à l'autre, que le contrôle fasse 22 px ou 40 px de haut. */
  padding: 12px 0; min-height: 64px;
}
.adm-set + .adm-set { border-top: 1px solid var(--border); }
.adm-set:first-child { padding-top: 4px; }
.adm-set:last-child  { padding-bottom: 4px; }

/* Colonne du contrôle, de largeur fixe : c'est elle qui aligne les intitulés
   entre eux, quelle que soit la nature du contrôle qu'elle contient. */
.adm-set-control { width: 132px; flex-shrink: 0; display: flex; align-items: center; }
.adm-set-control .input-suffix { width: 100%; }

.adm-set-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.adm-set-label { font-size: 13.5px; font-weight: 600; }

/* Adresse à recopier avant suppression d'un compte. Ni en capitales ni
   tronquée : elle doit se lire caractère par caractère, et une adresse longue
   se replie plutôt que de déborder de la modale. */
.adm-del-target {
  display: block;
  font-family: var(--f-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px; line-height: 1.5;
  color: var(--ink);
  background: var(--surface-3);
  border-radius: var(--r-xs);
  padding: 7px 10px;
  overflow-wrap: anywhere;
  user-select: all;
}

/* ── Changement d'offre ── */
.plan-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.chg-swap {
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: 14px; margin-bottom: 16px;
  background: var(--surface-2); border-radius: var(--r-sm);
  font-family: var(--f-display); font-size: 15px; font-weight: 600;
}
.chg-swap svg { width: 17px; height: 17px; color: var(--ink-3); }
.chg-from { color: var(--ink-3); }
.chg-to   { color: var(--ink); }

/* ── Factures ── */
.inv-list { display: flex; flex-direction: column; }
.inv-row {
  display: grid; align-items: center; gap: 10px;
  grid-template-columns: 92px 1fr auto auto 32px;
  padding: 10px 0; font-size: 13px;
}
.inv-row + .inv-row { border-top: 1px solid var(--border); }
.inv-date   { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.inv-num    { color: var(--ink-3); font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-amount { font-weight: 600; font-variant-numeric: tabular-nums; }
/* Sous 560 px la ligne à cinq colonnes déborde : le numéro, qui n'est utile
   qu'en cas de réclamation, cède la place au reste. */
@media (max-width: 560px) {
  .inv-row { grid-template-columns: 84px auto 1fr 32px; }
  .inv-num { display: none; }
}

/* Changement d'offre programmé — informatif, pas alarmant : rien n'est
   cassé, une décision attend simplement son échéance. */
.plan-pending {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 12px; padding: 11px 13px;
  background: var(--surface-2); border: 1px dashed var(--border-2);
  border-radius: var(--r-sm);
  font-size: 13px; line-height: 1.5;
}
.plan-pending svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--ink-3); }
.plan-pending span { flex: 1; min-width: 180px; }

/* Cases de consentement. Cible de clic généreuse et texte pleinement lisible :
   un consentement obtenu sur un texte illisible n'en est pas un. */
.consent {
  display: flex; align-items: flex-start; gap: 11px;
  padding: 12px 0; cursor: pointer;
  font-size: 13px; line-height: 1.6; color: var(--ink-2);
}
.consent + .consent { border-top: 1px solid var(--border); }
.consent input { margin-top: 2px; width: 17px; height: 17px; flex-shrink: 0; accent-color: var(--ink); }
.consent a { color: var(--accent-fg); text-decoration: underline; text-underline-offset: 2px; }
.consent strong { color: var(--ink); font-weight: 600; }

/* Boutons de téléchargement des exports : ils se replient plutôt que de
   déborder, les libellés étant longs. */
.dl-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* Retour d'un paiement. Le bandeau se distingue du reste : c'est la réponse
   à la question qu'on se pose en revenant de Stripe, « est-ce que ça a marché ». */
.pay-return {
  display: flex; align-items: center; gap: 11px;
  padding: 13px 15px; border-radius: var(--r);
  font-size: 13.5px; line-height: 1.55;
}
.pay-return span { flex: 1; min-width: 0; }
.pay-return strong { font-weight: 600; }
.pay-return > svg { width: 18px; height: 18px; flex-shrink: 0; }
.pay-return .icon-btn { flex-shrink: 0; color: inherit; opacity: .65; }
.pay-return .icon-btn:hover { opacity: 1; background: color-mix(in srgb, currentColor 12%, transparent); }
.pay-return.is-done { background: var(--ok-bg);   color: var(--ok-fg); }
.pay-return.is-wait { background: var(--warn-bg); color: var(--warn-fg); }
/* L'attente tourne : un rond figé se lit comme un écran bloqué. */
.pay-return.is-wait > svg { animation: pay-spin 1s linear infinite; }
@keyframes pay-spin { to { transform: rotate(360deg); } }
