/*
 * Kandihouse — design system web : "Cotonou".
 *
 * Dérivé du vrai design system de voyage.benin.bj (bundle CSS analysé :
 * variables de couleur, police, rayons, ombres, transitions), désaturé et
 * réagencé pour un outil de gestion utilisé toute la journée — le vert
 * reste l'action, l'or et le rouge du drapeau passent en accents ponctuels
 * plutôt qu'en aplats. Composante sombre "Cotonou Nuit" pensée à part (pas
 * une inversion mécanique). Le thème choisi par l'utilisateur (ou le
 * système, par défaut) est appliqué via [data-theme] sur <html>, positionné
 * AVANT ce fichier par le script anti-flash inline dans partials/header.php.
 *
 * Typo : une seule famille — Sora — au lieu d'un duo empattée/sans-serif,
 * hiérarchie portée par la graisse (comme le site source avec Aeonik).
 * Self-hébergée (backend/assets/fonts/), licence SIL OFL.
 *
 * Emprunté tel quel au site source : échelle de rayons 8/12/20px, ombres
 * noires très douces à 3 paliers, transition unique en
 * cubic-bezier(.165,.84,.44,1), survol de bouton (assombri + soulèvement +
 * ombre teintée — reproduit depuis leur .btn-primary:hover réel).
 */

@font-face {
	font-family: 'Sora';
	src: url('../fonts/SoraVariable.woff2') format('woff2-variations'), url('../fonts/SoraVariable.woff2') format('woff2');
	font-weight: 300 800;
	font-style: normal;
	font-display: swap;
}

:root {
	/* Neutres (clair — "Cotonou") */
	--bg: #FAFAF5;
	--bg-sunken: #F1F0E6;
	--surface: #FFFFFF;
	--border: #E6E2D3;
	--text: #16261D;
	--text-muted: #64705F;

	/* Accent */
	--accent: #077C4C;
	--accent-hover: #056139;
	--accent-contrast: #FFFFFF;
	--accent-2: #D9A62E; /* or institutionnel — accent ponctuel, jamais l'action principale */
	--accent-3: #C63C33; /* rouge institutionnel — encore plus rare (urgence/rappel) */

	/* Sémantique */
	--success: #1F9D6C;
	--success-bg: #E3F6EC;
	--warning: #C97A1E;
	--warning-bg: #FBF0DD;
	--danger: #C63C33;
	--danger-bg: #FBE9E7;
	--danger-contrast: #FFFFFF;
	--success-fg: #177450;
	--warning-fg: #935916;
	--danger-fg: #B6372F;
	--neutral-fg: #566052;

	/* Échelle — reprise du site source */
	--radius-sm: 8px;
	--radius: 12px;
	--radius-lg: 20px;
	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .05);
	--shadow-md: 0 10px 30px rgba(0, 0, 0, .08);
	--sidebar-width: 256px;

	/* Mouvement — transition unique, reprise du site source */
	--ease: cubic-bezier(.165, .84, .44, 1);
}

@media (prefers-color-scheme: dark) {
	:root:not([data-theme="light"]) {
		/* "Cotonou Nuit" — pas une inversion mécanique : même vert, éclairci
		   pour porter sur un fond profond ; or/rouge du drapeau en braise. */
		--bg: #0A1F17;
		--bg-sunken: #0D2B1F;
		--surface: #123A29;
		--border: #1E4A36;
		--text: #EDF5EE;
		--text-muted: #8FB09D;

		--accent: #2FCB86;
		--accent-hover: #58DA9E;
		--accent-contrast: #06231A;
		--accent-2: #F0C75A;
		--accent-3: #E8776C;

		--success: #3ED28E;
		--success-bg: #103524;
		--warning: #F5AB55;
		--warning-bg: #3A2712;
		--danger: #E8776C;
		--danger-bg: #3A1516;
		--danger-contrast: #3A1516;
		--success-fg: #3ED28E;
		--warning-fg: #F5AB55;
		--danger-fg: #E8776C;
		--neutral-fg: #A3BEAF;

		--shadow-sm: 0 2px 10px rgba(0, 0, 0, .35);
		--shadow-md: 0 14px 34px rgba(0, 0, 0, .5);
	}
}

:root[data-theme="dark"] {
	--bg: #0A1F17;
	--bg-sunken: #0D2B1F;
	--surface: #123A29;
	--border: #1E4A36;
	--text: #EDF5EE;
	--text-muted: #8FB09D;

	--accent: #2FCB86;
	--accent-hover: #58DA9E;
	--accent-contrast: #06231A;
	--accent-2: #F0C75A;
	--accent-3: #E8776C;

	--success: #3ED28E;
	--success-bg: #103524;
	--warning: #F5AB55;
	--warning-bg: #3A2712;
	--danger: #E8776C;
	--danger-bg: #3A1516;
	--danger-contrast: #3A1516;
	--success-fg: #3ED28E;
	--warning-fg: #F5AB55;
	--danger-fg: #E8776C;
	--neutral-fg: #A3BEAF;

	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .35);
	--shadow-md: 0 14px 34px rgba(0, 0, 0, .5);
}

:root[data-theme="light"] {
	--bg: #FAFAF5;
	--bg-sunken: #F1F0E6;
	--surface: #FFFFFF;
	--border: #E6E2D3;
	--text: #16261D;
	--text-muted: #64705F;

	--accent: #077C4C;
	--accent-hover: #056139;
	--accent-contrast: #FFFFFF;
	--accent-2: #D9A62E;
	--accent-3: #C63C33;

	--success: #1F9D6C;
	--success-bg: #E3F6EC;
	--warning: #C97A1E;
	--warning-bg: #FBF0DD;
	--danger: #C63C33;
	--danger-bg: #FBE9E7;
	--danger-contrast: #FFFFFF;
	--success-fg: #177450;
	--warning-fg: #935916;
	--danger-fg: #B6372F;
	--neutral-fg: #566052;

	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .05);
	--shadow-md: 0 10px 30px rgba(0, 0, 0, .08);
}

/*
 * "Hostex" — 2ᵉ identité de marque sélectionnable par l'owner (Compte >
 * Apparence, cf. Account::theme_save()), en plus de "Cotonou" (2026-08-27,
 * demande user, accent fourni #3155FF). Même structure de tokens que
 * Cotonou ci-dessus (neutres/accent/sémantique/ombres, clair + sombre
 * pensés ensemble) — jamais une simple substitution d'accent : neutres
 * froids choisis pour ne pas jurer avec le bleu (au lieu des neutres
 * chauds crème de Cotonou). Portée par [data-brand="hostex"] sur <html>
 * (Auth_Controller::render() calcule $ui_theme depuis `settings`,
 * header.php le pose au rendu serveur — aucun flash possible, contrairement
 * au clair/sombre qui dépend de localStorage). Spécificité toujours
 * supérieure aux règles Cotonou (attribut en plus) : recouvrement propre,
 * AUCUNE des règles Cotonou ci-dessus n'est modifiée — zéro régression pour
 * les comptes qui ne changent rien. Le reste du CSS (dégradé hero
 * .auth-shell, boutons, hachures...) référence déjà var(--accent) etc. et
 * se réadapte automatiquement, sans code supplémentaire.
 */
:root[data-brand="hostex"] {
	--bg: #F5F7FF;
	--bg-sunken: #EAEDFB;
	--surface: #FFFFFF;
	--border: #DDE3F5;
	--text: #10142B;
	--text-muted: #5B6482;

	--accent: #3155FF;
	--accent-hover: #2444E0;
	--accent-contrast: #FFFFFF;
	--accent-2: #7C5CFC;
	--accent-3: #E5484D;

	--success: #16A34A;
	--success-bg: #E7F8EC;
	--warning: #D97706;
	--warning-bg: #FCF1DC;
	--danger: #E5484D;
	--danger-bg: #FCE8E8;
	--danger-contrast: #FFFFFF;
	--success-fg: #107736;
	--warning-fg: #9C5604;
	--danger-fg: #B3383C;
	--neutral-fg: #555D79;

	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .05);
	--shadow-md: 0 10px 30px rgba(0, 0, 0, .08);
}

@media (prefers-color-scheme: dark) {
	:root[data-brand="hostex"]:not([data-theme="light"]) {
		/* "Hostex Nuit" — même logique que Cotonou Nuit : accent éclairci
		   pour porter sur fond profond, accent-contrast repasse sombre car
		   ce bleu plus clair ne supporte plus un texte blanc. */
		--bg: #0B0E1F;
		--bg-sunken: #10142E;
		--surface: #171B38;
		--border: #272C52;
		--text: #EDEFFC;
		--text-muted: #9AA0C4;

		--accent: #5B7CFF;
		--accent-hover: #7C97FF;
		--accent-contrast: #0B0E1F;
		--accent-2: #A78BFA;
		--accent-3: #FF6B70;

		--success: #4ADE80;
		--success-bg: #113322;
		--warning: #FBBF24;
		--warning-bg: #3A2A10;
		--danger: #FF6B70;
		--danger-bg: #3A1518;
		--danger-contrast: #3A1518;
		--success-fg: #4ADE80;
		--warning-fg: #FBBF24;
		--danger-fg: #FF6B70;
		--neutral-fg: #9AA0C4;

		--shadow-sm: 0 2px 10px rgba(0, 0, 0, .35);
		--shadow-md: 0 14px 34px rgba(0, 0, 0, .5);
	}
}

:root[data-brand="hostex"][data-theme="dark"] {
	--bg: #0B0E1F;
	--bg-sunken: #10142E;
	--surface: #171B38;
	--border: #272C52;
	--text: #EDEFFC;
	--text-muted: #9AA0C4;

	--accent: #5B7CFF;
	--accent-hover: #7C97FF;
	--accent-contrast: #0B0E1F;
	--accent-2: #A78BFA;
	--accent-3: #FF6B70;

	--success: #4ADE80;
	--success-bg: #113322;
	--warning: #FBBF24;
	--warning-bg: #3A2A10;
	--danger: #FF6B70;
	--danger-bg: #3A1518;
	--danger-contrast: #3A1518;
	--success-fg: #4ADE80;
	--warning-fg: #FBBF24;
	--danger-fg: #FF6B70;
	--neutral-fg: #9AA0C4;

	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .35);
	--shadow-md: 0 14px 34px rgba(0, 0, 0, .5);
}

:root[data-brand="hostex"][data-theme="light"] {
	--bg: #F5F7FF;
	--bg-sunken: #EAEDFB;
	--surface: #FFFFFF;
	--border: #DDE3F5;
	--text: #10142B;
	--text-muted: #5B6482;

	--accent: #3155FF;
	--accent-hover: #2444E0;
	--accent-contrast: #FFFFFF;
	--accent-2: #7C5CFC;
	--accent-3: #E5484D;

	--success: #16A34A;
	--success-bg: #E7F8EC;
	--warning: #D97706;
	--warning-bg: #FCF1DC;
	--danger: #E5484D;
	--danger-bg: #FCE8E8;
	--danger-contrast: #FFFFFF;
	--success-fg: #107736;
	--warning-fg: #9C5604;
	--danger-fg: #B3383C;
	--neutral-fg: #555D79;

	--shadow-sm: 0 2px 10px rgba(0, 0, 0, .05);
	--shadow-md: 0 10px 30px rgba(0, 0, 0, .08);
}

/* -------------------------------------------------------------------- */
/* Reset + base                                                          */
/* -------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }

body {
	margin: 0;
	font-family: 'Sora', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
	font-size: 14px;
	line-height: 1.5;
	background: var(--bg);
	color: var(--text);
	-webkit-font-smoothing: antialiased;
	transition: background .3s var(--ease), color .3s var(--ease);
}

/* Une seule famille (Sora) : la hiérarchie vient de la graisse, pas d'un
   changement de police — même logique que le site source avec Aeonik. */
h1, h2, h3 { margin: 0; font-weight: 800; letter-spacing: -0.01em; }
h1 { font-size: 23px; }
h2 { font-size: 17px; }
p { margin: 0; color: var(--text-muted); }

a { color: var(--accent); text-decoration: none; transition: color .15s var(--ease); }
a:hover { color: var(--accent-hover); }

table { font-variant-numeric: tabular-nums; }

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

/* -------------------------------------------------------------------- */
/* Layout : sidebar + contenu                                            */
/* -------------------------------------------------------------------- */

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

.sidebar {
	width: var(--sidebar-width);
	flex-shrink: 0;
	background: var(--bg-sunken);
	border-right: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	padding: 20px 12px;
}

.sidebar-brand {
	font-size: 17px;
	font-weight: 800;
	letter-spacing: -0.01em;
	padding: 0 8px 20px;
	color: var(--text);
	display: flex;
	align-items: center;
	justify-content: space-between;
}

/* Cloche de notifications In-App (2026-08-28, capture SuperHote
   "Notifications" — colonnes Push/In-App). */
.notif-bell-wrap { position: relative; }
.notif-bell {
	position: relative;
	background: none;
	border: none;
	cursor: pointer;
	font-size: 16px;
	line-height: 1;
	padding: 4px;
	border-radius: var(--radius-sm);
}
.notif-bell:hover { background: var(--border); }
.notif-bell-badge {
	position: absolute;
	top: -2px;
	right: -4px;
	background: var(--danger);
	color: var(--danger-contrast);
	font-size: 9.5px;
	font-weight: 700;
	line-height: 1;
	padding: 2px 4px;
	border-radius: 999px;
	min-width: 14px;
	text-align: center;
}
.notif-bell-dropdown {
	display: none;
	position: absolute;
	top: calc(100% + 8px);
	left: 0;
	width: 300px;
	max-height: 380px;
	overflow-y: auto;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	box-shadow: var(--shadow-md);
	z-index: 50;
}
.notif-bell-dropdown.is-open { display: block; }
.notif-bell-dropdown-header {
	display: flex; align-items: center; justify-content: space-between;
	padding: 10px 12px; border-bottom: 1px solid var(--border);
	font-size: 12.5px; font-weight: 700; color: var(--text);
}
.notif-bell-empty { padding: 20px 12px; text-align: center; font-size: 12.5px; color: var(--text-muted); }
.notif-bell-item-form { display: block; }
.notif-bell-item {
	display: block; width: 100%; padding: 9px 12px; border-bottom: 1px solid var(--border);
	text-decoration: none; color: var(--text); font-size: 12.5px;
	background: none; border-left: none; border-right: none; border-top: none;
	font-family: inherit; text-align: left; cursor: pointer;
}
.notif-bell-item:last-of-type { border-bottom: none; }
.notif-bell-item:hover { background: var(--bg-sunken); }
.notif-bell-item.is-unread { background: color-mix(in srgb, var(--accent) 6%, transparent); font-weight: 600; }
.notif-bell-item-title { display: block; }
.notif-bell-item-time { display: block; margin-top: 2px; font-size: 11px; font-weight: 400; color: var(--text-muted); }
.notif-bell-viewall {
	display: block; text-align: center; padding: 9px 12px; font-size: 12px;
	color: var(--accent); text-decoration: none; font-weight: 600;
}
.notif-bell-viewall:hover { background: var(--bg-sunken); }

.sidebar-search { padding: 0 8px 14px; }
.sidebar-search input { width: 100%; font-size: 13px; padding: 7px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--bg-sunken); color: var(--text); }

.sidebar-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }

/* 2026-08-19 (retour user, réorganisation du menu en sections repliables —
   "ca ne deroule pas", corrige un 1er essai à sections statiques). Intitulé
   de section = <button>, pas juste un texte : cliquable pour replier/
   déplier son groupe (sidebar_nav dans footer.php).
   2026-08-19 (même jour, retour user — "utilise un design différenciant") :
   la version texte-nu (muted, sans fond) se confondait avec une simple
   étiquette non interactive, d'où le "ca ne deroule pas" du 1er retour —
   fond en pilule + chevron plus marqué pour signaler clairement "ceci se
   clique", visuellement distinct à la fois des items de nav (pilule active
   seulement, jamais par défaut) et du texte plat de "Au quotidien"
   (aucun fond, aucune section pour ce groupe). Replié par défaut désormais
   (cf. header.php/footer.php) — l'affordance doit donc être sans ambiguïté
   même à froid. */
.sidebar-nav-heading {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 6px;
	width: 100%;
	font-family: inherit;
	font-size: 10.5px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: .06em;
	color: var(--text-muted);
	background: var(--bg-sunken);
	border: 1px solid transparent;
	border-radius: var(--radius-sm);
	cursor: pointer;
	margin: 10px 0 4px;
	padding: 7px 10px;
	transition: background .15s var(--ease), color .15s var(--ease), border-color .15s var(--ease);
}
.sidebar-nav-heading:hover { background: var(--border); color: var(--text); }
.sidebar-nav-heading:first-child { margin-top: 0; }
.sidebar-nav-heading-chevron {
	font-size: 11px;
	color: var(--accent);
	transition: transform .15s var(--ease);
}
.sidebar-nav-heading.is-collapsed .sidebar-nav-heading-chevron { transform: rotate(-90deg); }

.sidebar-nav-group { display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
.sidebar-nav-group.is-collapsed { display: none; }

.sidebar-nav-item {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 8px 10px;
	border-radius: var(--radius-sm);
	color: var(--text-muted);
	font-size: 13px;
	font-weight: 600;
	transition: background .15s var(--ease), color .15s var(--ease);
}

.sidebar-nav-item:hover { background: var(--border); color: var(--text); }

.sidebar-nav-item.active {
	background: var(--surface);
	color: var(--accent);
	box-shadow: var(--shadow-sm);
	border: 1px solid var(--border);
}

/* Badge "Messages non lus" (2026-09-06, retour user) — même style que
   .notif-bell-badge (pilule rouge), mais en flux (margin-left:auto pousse
   à droite de la ligne) plutôt qu'en position absolute : .sidebar-nav-item
   est déjà display:flex. */
.sidebar-nav-badge {
	margin-left: auto;
	background: var(--danger);
	color: var(--danger-contrast);
	font-size: 10.5px;
	font-weight: 700;
	line-height: 1;
	padding: 2px 6px;
	border-radius: 999px;
	min-width: 16px;
	text-align: center;
}

.sidebar-footer {
	border-top: 1px solid var(--border);
	padding-top: 12px;
	margin-top: 12px;
	display: flex;
	flex-direction: column;
	gap: 4px;
}

/* .sidebar-footer a.sidebar-user : spécificité plus élevée que la règle
   générique .sidebar-footer a juste en dessous — nécessaire depuis que
   "Mon compte" a rendu ce nom cliquable (<a> au lieu d'un <div>), sinon
   la règle générique écraserait sa taille/son padding d'origine. */
.sidebar-footer a.sidebar-user, .sidebar-user { padding: 0 8px; font-size: 12px; color: var(--text-muted); }
.sidebar-footer a, .sidebar-footer button { font-size: 13px; padding: 6px 8px; }

.theme-toggle {
	display: flex;
	align-items: center;
	gap: 8px;
	background: none;
	border: none;
	color: var(--text-muted);
	font-family: inherit;
	font-size: 13px;
	cursor: pointer;
	border-radius: var(--radius-sm);
	text-align: left;
	width: 100%;
	transition: background .15s var(--ease), color .15s var(--ease);
}
.theme-toggle:hover { background: var(--border); color: var(--text); }

.main { flex: 1; min-width: 0; padding: 32px 40px; }
.main-header { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; }
.main-header h1 { flex: 1; }

/* -------------------------------------------------------------------- */
/* Boutons                                                                */
/* -------------------------------------------------------------------- */

/* 2026-11-06 (revue visuelle ecran par ecran) — `.btn-primary`,
   `.btn-outline`, `.btn-danger` et `.btn-secondary` n'etaient que des
   MODIFICATEURS : seules les couleurs. Sans `.btn` a cote, l'element
   n'heritait ni du padding, ni du rayon, ni de la largeur de bordure, et
   le navigateur le rendait en bouton natif (bordure `outset` grise,
   coins carres). 88 elements dans 45 vues etaient dans ce cas — dont les
   boutons « Filtrer » de Logements et de Taches, les « Masquer » de
   Suggestions IA et le selecteur Agenda/Liste/Kanban, ou `.active`
   peignait un aplat vert sans aucun padding, comme une selection de
   texte. Corriger les 88 appels laissait le piege en place pour la
   prochaine vue ; la base porte donc desormais les modificateurs, dans
   le meme esprit que la note du 2026-08-28 sur `.btn-secondary`.
   `.btn-ghost` reste volontairement en dehors : il est autonome et
   n'a ni bordure ni padding par definition. */
.btn,
.btn-primary,
.btn-outline,
.btn-danger,
.btn-secondary {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	font-family: 'Sora', inherit;
	font-size: 13px;
	font-weight: 700;
	padding: 8px 14px;
	border-radius: var(--radius-sm);
	border: 1px solid transparent;
	cursor: pointer;
	text-decoration: none;
	line-height: 1.2;
	transition: background .15s var(--ease), border-color .15s var(--ease), color .15s var(--ease), transform .15s var(--ease), box-shadow .15s var(--ease);
}
.btn:active, .btn-primary:active, .btn-outline:active, .btn-danger:active, .btn-secondary:active { transform: translateY(1px); }

/* Survol repris tel quel du .btn-primary:hover réel de voyage.benin.bj :
   assombrissement + soulèvement + ombre teintée dans la couleur d'accent. */
.btn-primary { background: var(--accent); color: var(--accent-contrast); }
.btn-primary:hover { background: var(--accent-hover); color: var(--accent-contrast); transform: translateY(-2px); box-shadow: 0 4px 14px -2px color-mix(in srgb, var(--accent) 45%, transparent); }

/* 2026-08-27 : contour coloré accent (au lieu de --border quasi invisible sur
   fond clair) pour que les boutons secondaires se remarquent immédiatement. */
.btn-outline { background: var(--surface); color: var(--accent); border-color: var(--accent); font-weight: 700; }
.btn-outline:hover { background: var(--accent); color: var(--accent-contrast); }
/* Toggle Agenda/Liste/Kanban (tasks_agenda.php/tasks.php) — préexistait sans effet visuel sur Liste/Kanban avant ce lot, ajouté en même temps (2026-08-18). */
.btn-outline.active { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }

/* 🐛 2026-11-10 (balayage UI) — `.btn-ghost` est le style de TOUTES les
   actions de ligne du produit (Modifier, Dupliquer, Supprimer, Retry,
   Exporter…). Il lui manquait `cursor: pointer` : un <button> garde le
   curseur flèche par défaut, contrairement à un lien. Gris, sans soulignement
   et sans main au survol, ces actions se lisaient comme du texte désactivé.
   Le soulignement au survol lève le dernier doute sans alourdir les tableaux
   au repos. */
.btn-ghost {
	background: none;
	border: none;
	color: var(--text-muted);
	padding: 4px 0;
	font-size: 12px;
	margin-right: 14px;
	cursor: pointer;
}
.btn-ghost:hover { color: var(--accent); text-decoration: underline; }
.btn-ghost.danger:hover { color: var(--danger); }

/* 2026-11-06 (audit de contraste calcule depuis le CSS) — ces règles
   posaient `color: #fff` sur un fond `var(--accent)` ou `var(--danger)`.
   C'était juste en thème clair (vert/rouge foncés), mais en thème sombre
   ces deux tokens deviennent CLAIRS — du blanc sur #2FCB86 tombait à
   2,10:1 et sur #E8776C à 2,88:1, sous le seuil de 3,0. Le badge de
   messages non lus et mes propres bulles de conversation étaient les plus
   touchés. Le texte suit désormais le token de contraste, qui bascule avec
   le thème (7,92:1 et 5,61:1 en sombre, 5,13:1 et plus en clair). */
.btn-danger { background: var(--danger); color: var(--danger-contrast); }
.btn-danger:hover { background: var(--danger); opacity: .9; }

/* 2026-08-28 (audit UI/UX) : "btn-secondary" utilisée dans 8 vues
   (actions.php, property_rules.php, booking_detail.php, account.php,
   task_detail.php, property_room_types.php, account_integrations.php,
   rate_panel.php) sans AUCUNE règle CSS — boutons rendus sans style,
   quasi invisibles. Repris du même traitement que .btn-outline
   (contour accent, cf. note 2026-08-27 ci-dessus) plutôt que d'introduire
   une 3e convention visuelle. */
.btn-secondary { background: var(--surface); color: var(--accent); border-color: var(--accent); font-weight: 700; }
.btn-secondary:hover { background: var(--accent); color: var(--accent-contrast); }

/* -------------------------------------------------------------------- */
/* Cartes, badges, bannières                                             */
/* -------------------------------------------------------------------- */

/* 🐛 2026-11-10 (capture user, Équipe > Équipes : le menu déroulant
   « Ajouter un membre » passe SOUS la carte de l'équipe suivante) — la cause
   n'est PAS le z-index du menu (30, bien au-dessus de tout ce qui l'entoure),
   c'est cette animation.

   `both` contient `forwards` : les 0,4 s passées, la carte CONSERVE les
   valeurs de la dernière image-clé, dont `transform: translateY(0)`. Un
   `transform` autre que `none` crée un CONTEXTE D'EMPILEMENT — définitif ici,
   puisque la valeur ne redescend jamais. Le z-index du menu ne vaut plus que
   DANS sa propre carte, et les cartes, elles, se peignent dans l'ordre du
   DOM : toute carte qui SUIT recouvre le menu ouvert de la précédente.
   Partout dans le produit, pas seulement sur cet écran.

   `backwards` laisse l'animation d'entrée rigoureusement identique — l'état
   final est déjà l'état normal (opacité 1, aucune translation) — et rend la
   carte à son empilement normal dès la fin.

   Réparé par la même occasion : une déclaration d'animation l'emporte sur une
   déclaration normale, donc le `transform: translateY(0)` retenu écrasait
   aussi `.kpi-card:hover` et `.besoin-card:hover` { transform:
   translateY(-2px) }. Le léger soulèvement au survol de ces cartes n'avait
   jamais eu lieu depuis qu'il a été écrit. */
.card {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	padding: 24px;
	box-shadow: var(--shadow-sm);
	transition: box-shadow .2s var(--ease), border-color .2s var(--ease);
	animation: card-rise .4s var(--ease) backwards;
}

/* 2026-09-17 (retour user « les statuts sont quasi invisibles », d'abord
   constaté sur mobile) : le texte reprenait la couleur VIVE du statut sur
   son propre fond pâle — 2,9:1 à 4,4:1 en thème clair, sous le seuil de
   4,5:1. Les variables --*-fg sont la même teinte assombrie juste ce qu'il
   faut (≥ 5:1), et un contour donne sa forme à la pastille, comme
   KhStatusPill côté mobile. La couleur vive (--success, --warning…) reste
   inchangée partout ailleurs (icônes, bordures, graphiques). */
/* 🐛 2026-11-10 (balayage UI) — `white-space: nowrap` manquait : dans une
   colonne étroite, « Séjour en cours » ou « Payé partiellement » passait à la
   ligne À L'INTÉRIEUR de la pastille, qui devenait un pavé arrondi de trois
   lignes au lieu d'une étiquette. Un statut se lit d'un coup d'œil ou ne sert
   à rien. Les tableaux concernés vivent tous dans `.table-wrap`, qui défile
   déjà horizontalement : rien ne déborde de la page. */
.badge {
	display: inline-block;
	padding: 2px 8px;
	border-radius: 999px;
	font-size: 11px;
	font-weight: 700;
	border: 1px solid transparent;
	white-space: nowrap;
}
/* 🐛 2026-11-11 (balayage UI) — exception au `nowrap` ci-dessus, et c'est une
   RÉGRESSION de la règle d'hier : son commentaire se justifiait par « les
   tableaux concernés vivent dans .table-wrap, qui défile ». Une carte KPI, elle,
   ne défile pas — et sa pastille porte une PHRASE (« nouveau (pas de données
   N-1) »), pas un statut. À 900 px la parenthèse fermante était coupée et le
   texte mordait sur la carte voisine. Une phrase a le droit de passer à la
   ligne ; un statut, non. */
.kpi-label .badge { white-space: normal; }
/* 🐛 2026-11-12 (capture de l'hôte) — TROISIÈME occurrence du même piège, et
   la leçon est maintenant claire : `white-space: nowrap` sur une pastille
   suppose que son conteneur est large ou qu'il défile. Le panneau
   « Réservation » du fil de messages ne fait que 260 px ET pose DEUX colonnes
   de ~104 px côte à côte : « Gérée par Airbnb » débordait, et
   `overflow-y: auto` du panneau la rognait net — l'hôte lisait
   « Gérée par Air… ». Laisser le texte passer à la ligne ne casse aucune mise
   en page ; le rognage, lui, supprime de l'information. */
.messages-panel-col .badge { white-space: normal; }

.badge-success { background: var(--success-bg); color: var(--success-fg); border-color: color-mix(in srgb, var(--success) 40%, transparent); }
.badge-warning { background: var(--warning-bg); color: var(--warning-fg); border-color: color-mix(in srgb, var(--warning) 40%, transparent); }
.badge-danger  { background: var(--danger-bg); color: var(--danger-fg); border-color: color-mix(in srgb, var(--danger) 40%, transparent); }
.badge-neutral { background: var(--border); color: var(--neutral-fg); border-color: color-mix(in srgb, var(--text-muted) 30%, transparent); }

.banner {
	padding: 10px 14px;
	border-radius: var(--radius-sm);
	font-size: 13px;
	margin-bottom: 16px;
}
/* 2026-09-17 : mêmes couleurs de texte lisibles que .badge-* (le texte
   vif sur son propre fond pâle tombait à 3:1). */
.banner-success { background: var(--success-bg); color: var(--success-fg); }
.banner-error { background: var(--danger-bg); color: var(--danger-fg); }
.banner-warning { background: var(--warning-bg); color: var(--warning-fg); }

.empty-state {
	color: var(--text-muted);
	font-size: 14px;
	padding: 32px;
	text-align: center;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
}

/* -------------------------------------------------------------------- */
/* Formulaires                                                           */
/* -------------------------------------------------------------------- */

/* 2026-11-06 (audit UI web) — deux motifs de mise en page étaient recopiés
   à la main dans tout le produit : « display:flex; gap:12px; flex-wrap:wrap »
   39 fois dans 10 vues, et « flex:1; min-width:Npx » 163 fois avec 8 valeurs
   de N différentes. Recopiés, ils dérivent : une rangée corrigée ici ne
   corrige rien ailleurs, exactement ce qui était arrivé à table.table.
   `.form-row` remplace la rangée à l'identique ; `.form-col` prend 160px de
   largeur minimale par défaut (la valeur la plus fréquente, 27 occurrences)
   et accepte `--col-min` quand il en faut une autre — une seule valeur à
   écrire au lieu de trois déclarations. */
.form-row { display: flex; gap: 12px; flex-wrap: wrap; }
.form-col { flex: 1; min-width: var(--col-min, 160px); }

/* Grille de champs d'un formulaire long — 2026-11-11 (balayage UI). Les
   rangées de champs (`display:flex` à 2 `.field`) s'empilaient verticalement :
   sur la réservation manuelle, une vingtaine de champs faisaient une colonne
   de plus de 1 500 px pendant que les deux tiers de la page restaient vides.
   Chaque rangée devient une cellule de grille, donc plusieurs rangées tiennent
   côte à côte quand la largeur le permet, et une seule par ligne quand elle ne
   le permet pas. Le flux reste en LIGNES : l'ordre de lecture et l'ordre de
   tabulation ne changent pas. */
.kh-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 0 20px; align-items: start; }

/* Champ posé EN LIGNE dans une cellule de tableau, avec sa micro-étiquette.
   🐛 2026-11-11 (balayage UI) — dans la table des extras d'un logement, cinq
   contrôles tenaient dans une seule cellule sans aucune étiquette : le
   `placeholder` est la seule chose qui nommait le champ, et il disparaît dès
   que le champ est rempli. Sur une offre existante, l'hôte voyait donc
   « 12,5 » et « 0 » côte à côte, sans rien pour dire lequel est le prix et
   lequel est le coût de revient. Un `.field` complet (14 px de marge, label
   de 13 px) aurait triplé la hauteur de chaque ligne ; cette variante garde
   l'étiquette et la hauteur. */
.inline-field { display: flex; flex-direction: column; gap: 2px; }
.inline-field > span { font-size: 11px; line-height: 1.2; color: var(--text-muted); }

.field { margin-top: 14px; }
.field:first-child { margin-top: 0; }

/* Rangée de champs côte à côte (ex. style="display:flex; gap:12px" avec 2+ .field
   enfants, comme Adultes/Enfants/Bébés/Animaux) — seul le 1er champ de la rangée
   est :first-child de SON parent (la rangée), donc les suivants gardaient leur
   margin-top:14px individuel et semblaient décalés vers le bas par rapport au
   1er champ de la même ligne (repéré par le user au pixel près, 2026-08-15). Fix :
   dans une telle rangée, plus aucun .field ne porte son propre espacement — la
   rangée entière (le conteneur flex) le porte à sa place, exactement comme un
   .field autonome le ferait. */
/* 2026-11-06 — ces trois règles s'accrochaient au CONTENU de l'attribut
   `style`. C'est fragile par nature : en remplaçant `style="display:flex;
   gap:12px; flex-wrap:wrap;"` par la classe `.form-row`, les champs sont
   silencieusement sortis de leur portée et ont repris 14 px de marge
   chacun — un décalage mesuré sur 2 écrans, invisible à la relecture du
   diff. `.form-row` est désormais nommée explicitement ; le sélecteur
   d'attribut reste pour les rangées encore écrites en inline. */
/* 2026-11-07 — `.kh-filtres` ajoutée à la liste pour la même raison que
   `.form-row` au-dessus : en remplaçant le `style="display:flex; …"` d'une
   barre de filtres par une classe, la rangée serait sortie en silence de la
   portée du sélecteur d'attribut et aurait perdu ses 14 px de marge haute. */
[style*="display:flex"]:has(> .field),
.form-row:has(> .field),
.kh-filtres:has(> .field) { margin-top: 14px; }

/* 🐛 2026-11-11 (balayage UI, rendu réel) — dans une rangée, deux étiquettes
   de longueurs différentes ne font pas le même nombre de lignes : « Frais
   animaux » tient sur une, « Frais voyageurs supplémentaires » sur deux, et
   les deux champs de saisie se retrouvaient décalés de 18 px. Les champs sont
   alignés par le BAS, ce qui aligne les saisies quelle que soit l'étiquette.
   Les rangées qui contiennent un bloc d'aide en sont exclues : là, le bas d'un
   champ n'est plus sa saisie mais sa phrase d'explication, et l'alignement
   produirait le décalage inverse. 16 rangées sur 99 sont dans ce cas ; elles
   gardent exactement le comportement qu'elles avaient. */
[style*="display:flex"]:has(> .field):not(:has(.hint)),
.form-row:has(> .field):not(:has(.hint)),
.kh-filtres:has(> .field):not(:has(.hint)) { align-items: flex-end; }
[style*="display:flex"]:has(> .field):first-child,
.form-row:has(> .field):first-child,
.kh-filtres:has(> .field):first-child { margin-top: 0; }
[style*="display:flex"]:has(> .field) > .field,
.form-row:has(> .field) > .field,
.kh-filtres:has(> .field) > .field { margin-top: 0; }

label, .label {
	display: block;
	font-size: 12px;
	font-weight: 700;
	color: var(--text-muted);
	margin-bottom: 5px;
}

/* 2026-08-15 (retour user, capture "Détails réservation" — Adultes/Enfants
   se voyaient, Bébés/Animaux non) : bug réel trouvé — cette règle partagée
   oubliait `type="number"/"date"/"time"/"datetime-local"/"tel"/"url"`
   depuis le début. Ces champs (84 number/42 date/9 time/4 datetime-local/1
   tel/1 url dans 34 vues, confirmé par recherche globale) retombaient donc
   sur le style natif du navigateur (taille/police/bordure incohérentes),
   jamais celui de Kandihouse — chaque input `flex:1` de son `.field`
   parent restait à sa largeur native minuscule au lieu de remplir sa
   colonne, d'où l'effet "pas dans le bloc" en ligne (Adultes/Enfants
   avaient une valeur tapée qui les rendait moins visibles à l'œil, mais le
   même bug les touchait déjà). Correctif unique, partagé par toute l'app. */
/* 🐛 2026-11-11 (balayage UI) — `input[type="search"]` manquait des DEUX
   listes ci-dessous : un champ de recherche sans classe propre restait rendu
   par le navigateur (fond blanc, coins carrés, ~20 px de haut) au milieu du
   thème. Vu sur « Réponses rapides » ; 10 champs du produit sont dans ce cas.
   Ceux qui ont déjà leur habillage (recherche de la barre latérale,
   messagerie, filtre du guide) ne bougent pas : leur règle est plus
   spécifique. */
.input, input[type="text"], input[type="search"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="time"], input[type="datetime-local"],
input[type="tel"], input[type="url"], select, textarea {
	width: 100%;
	padding: 9px 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface);
	color: var(--text);
	font-family: inherit;
	font-size: 14px;
	box-sizing: border-box;
	transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}

/* 2026-08-15 (retour user, "évite les décalages de taille entre les
   champs") : le padding seul ne suffit pas — date/time/number gardent leur
   widget natif (icône calendrier/horloge, flèches de spinner) qui pousse
   leur hauteur RENDUE au-delà de celle d'un champ texte à padding
   identique (confirmé visuellement : "Heure d'arrivée" visiblement plus
   haut qu'"Arrivée" sur la même ligne). `height` fixe (pas juste padding)
   sur les champs À UNE LIGNE uniquement — jamais `textarea`, qui doit
   rester libre de grandir (règle dédiée juste après, volontairement
   exclue de cette liste). box-sizing:border-box (ci-dessus) fait que cette
   hauteur inclut déjà la bordure et le padding, donc aucun conflit entre
   les deux règles. */
.input, input[type="text"], input[type="search"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="time"], input[type="datetime-local"],
input[type="tel"], input[type="url"], select {
	/* 44px, pas 40px : le widget natif de time/date (icône horloge/calendrier,
	   shadow-DOM interne) a une hauteur minimale incompressible que Chrome
	   ignore si `height` est plus petit qu'elle — mesuré précisément
	   (~43-44px réels sur input[type=time] même avec height:40px forcé,
	   confirmé par capture d'écran zoomée avant ce correctif). 44px est le
	   plus petit palier qui absorbe ce minimum sur tous les types de champs
	   à la fois, texte compris. */
	height: 44px;
}

.input:focus, input:focus, select:focus, textarea:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

textarea { min-height: 120px; resize: vertical; font-family: inherit; }

/* 🐛 2026-11-10 (balayage UI) — `input[type=file]` n'avait AUCUN style : son
   bouton restait celui du système (fond blanc, texte noir, coins carrés),
   posé tel quel sur le thème sombre du produit. Visible partout où l'on
   téléverse : photos du guide, vidéos de checklist, logo du site, signature,
   pièces d'identité. `::file-selector-button` est la seule poignée que le
   navigateur expose sur ce widget — le reste de son interne est hors de
   portée du CSS, exactement comme le popup d'un <select> natif (cf.
   kh_select.js). On habille donc le bouton, et le texte « Aucun fichier
   choisi » reste rendu par le navigateur. */
input[type="file"] {
	font-size: 13px; color: var(--text-muted); max-width: 100%;
	/* 🐛 2026-11-11 (balayage UI) — sans `width`, le champ gardait sa largeur
	   INTRINSÈQUE (~240 px chez Chrome), calculée pour le bouton natif. Le
	   bouton habillé juste en dessous étant plus large (padding 8/14 +
	   marge 10), il ne restait plus assez de place au texte : « Aucun fichier
	   choisi » se rendait « Aucun f…er choisi », tronqué en plein mot. Vu sur
	   l'onglet Ressources d'un logement. `width:100%` aligne en outre ces
	   31 champs sur tous les autres champs du produit, qui le sont déjà ;
	   `max-width:100%` ci-dessus continue de les borner dans un conteneur
	   étroit. */
	width: 100%;
}
input[type="file"]::file-selector-button {
	font: inherit; font-weight: 600; font-size: 13px; cursor: pointer;
	margin-right: 10px; padding: 8px 14px;
	background: var(--surface); color: var(--accent);
	border: 1px solid var(--accent); border-radius: var(--radius-sm);
	transition: background .15s var(--ease), color .15s var(--ease);
}
input[type="file"]::file-selector-button:hover {
	background: var(--accent); color: var(--accent-contrast);
}

/* 🐛 2026-11-10 (balayage UI) — cases à cocher et boutons radio restaient
   peints par le système : bleu macOS vif sur un produit vert, au milieu de
   champs qui, eux, suivent le thème. `accent-color` est la poignée standard
   pour ces deux widgets natifs ; elle vaut mieux qu'une case redessinée à la
   main, qui perdrait l'accessibilité clavier et l'état indéterminé. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }

.checkbox-row { display: flex; align-items: center; gap: 8px; margin-top: 14px; }
.checkbox-row label { margin: 0; }

/* Interrupteur "Active/Inactive" (Notifications, prompt jumelage x Modèles)
   — un <button> stylé, pas un <input type="checkbox"> caché : pas de risque
   du bug "double cercle" rencontré sur les radios de Messages (aucun
   contrôle natif superposé ici), et ça reste un vrai <form>/submit, donc
   fonctionne sans JS. */
.switch-toggle {
	position: relative;
	width: 36px;
	height: 20px;
	border-radius: 999px;
	border: none;
	background: var(--border);
	cursor: pointer;
	padding: 0;
	flex-shrink: 0;
	transition: background .15s var(--ease);
}
.switch-toggle::after {
	content: '';
	position: absolute;
	top: 2px;
	left: 2px;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	background: #fff;
	box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
	transition: transform .15s var(--ease);
}
.switch-toggle.is-on { background: var(--accent); }
.switch-toggle.is-on::after { transform: translateX(16px); }

/* Onglets de la fiche réservation (parité SuperHote, "Informations/
   Messages/Paiements/...") — panneaux déjà tous rendus côté serveur en une
   seule requête (aucun contenu n'est chargé en AJAX), booking_tabs.js se
   contente de basculer leur visibilité + le hash d'URL, donc chaque onglet
   reste partageable/rafraîchissable (#messages, #paiements...). */
.tab-bar {
	display: flex;
	gap: 4px;
	flex-wrap: wrap;
	border-bottom: 1px solid var(--border);
	margin-bottom: 20px;
}
.tab-btn {
	background: none;
	border: none;
	border-bottom: 2px solid transparent;
	padding: 10px 14px;
	/* 2026-11-11 (demande user, « unifie les deux barres d'onglets ») — le
	   produit affichait deux barres soulignées de styles différents : petites
	   MAJUSCULES espacées ici, casse normale sur la fiche logement, où
	   `.tab-bar--property` avait dû les désactiver pour faire tenir 13
	   onglets. Les deux conventions se côtoyaient à un clic d'écart. C'est le
	   style « logement » qui l'emporte : il se lit mieux, il est plus compact
	   à nombre d'onglets égal, et c'est déjà la casse de `.page-tab`, l'autre
	   composant d'onglets du produit. */
	font-size: 13.5px;
	font-weight: 700;
	letter-spacing: 0;
	text-transform: none;
	color: var(--text-muted);
	cursor: pointer;
	display: flex;
	align-items: center;
	gap: 6px;
	transition: color .15s var(--ease), border-color .15s var(--ease);
}
.tab-btn:hover { color: var(--text); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab-btn .badge { font-size: 10px; padding: 1px 6px; }
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* 🐛 2026-11-10 (balayage UI, « j'en vois partout ») — ce bloc d'aide portait
   EXACTEMENT l'habillage d'un champ de saisie : `var(--bg-sunken)`, bordure
   1 px `var(--border)`, `var(--radius-sm)`. Les mêmes trois propriétés que
   `.field input`. Résultat, dans tout le produit, une phrase d'explication se
   lisait comme un champ vide à remplir — « Voici ce qui demande votre
   attention » sur le tableau de bord, « Cotonou · #4 » sur chaque carte
   logement, les en-têtes AUJOURD'HUI/DEMAIN des tâches…

   La preuve que la gêne était connue : 20 vues neutralisaient déjà le cadre
   à la main (`style="padding:0; background:none; border:none"`).

   Remplacé par un filet vertical : un bloc d'aide reste signalé comme tel,
   sans jamais pouvoir être pris pour un champ. La distinction documentée
   plus bas tient toujours — `.hint` = bloc d'aide, `.text-soft` = texte
   discret en ligne. */
.hint {
	background: none;
	border: none;
	border-left: 2px solid var(--border);
	border-radius: 0;
	padding: 2px 0 2px 10px;
	font-size: 12px;
	color: var(--text-muted);
	margin-top: 14px;
}
.hint code {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 4px;
	padding: 1px 5px;
	margin-right: 4px;
	font-size: 11px;
}

/* `.hint` EN LIGNE — 2026-10-21 (retour user : « arrête de m'afficher les
   infos comme ça, il y en a plein comme ça dans le web », capture à l'appui :
   dans la liste des extras d'un logement, la description recouvrait son
   propre libellé).

   `.hint` est pensée pour un CALLOUT de bloc : fond, bordure, 10px de padding
   et 14px de marge haute. Posée sur un élément en ligne — un <span> glissé
   sous un libellé — ce padding déborde de la boîte de ligne, et la pastille
   mord sur le texte du dessus. Le symptôme était connu et avait été traité
   13 fois à la main (`style="padding:0; background:none; border:none;"`),
   mais 43 <span class="hint"> restaient nus dans 28 vues : une rustine par
   endroit ne tient pas, et le prochain <span class="hint"> écrit serait
   reparti cassé.

   La règle porte donc sur la BALISE, pas sur l'usage : un <span>/<label> est
   en ligne par nature, il n'a jamais à porter un encadré. Les <p>/<div>
   class="hint", eux, gardent exactement leur callout — c'est leur raison
   d'être, et y toucher casserait toutes les aides en bloc du produit.
   Cf. [[feedback_hint_class_inline_overlap]]. */
span.hint,
label.hint {
	background: none;
	border: none;
	padding: 0;
	margin: 0;
}

/* Même famille, cas de la cellule de tableau : `.hint` y peignait un fond et
   trois bordures (la 4e étant reprise par `table.table td`), ce qui donnait
   des cellules encadrées au milieu d'un tableau qui a déjà ses propres
   séparateurs. On ne retire QUE la décoration : le padding et le
   `border-bottom` du tableau, plus spécifiques, continuent de s'appliquer,
   donc l'alignement des colonnes ne bouge pas. */
td.hint {
	background: none;
	border-top: none;
	border-left: none;
	border-right: none;
	margin: 0;
}

/* Variable {{CODE}} cliquable (Notifications/Modèles, prompt "shortcodes")
   — même look que .hint code mais un vrai <button>, pas juste un hint
   visuel : clique = insertion dans le textarea du message, cf. le JS de
   notification_form.php/template_form.php. */
.shortcode-pill {
	display: inline-block;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 4px;
	padding: 2px 6px;
	margin: 0 4px 4px 0;
	font-size: 11px;
	font-family: monospace;
	color: var(--text);
	cursor: pointer;
	transition: border-color .15s var(--ease), color .15s var(--ease);
}
.shortcode-pill:hover { border-color: var(--accent); color: var(--accent); }

/* Grille de permissions (formulaire équipe) : 1 ligne par ressource, 3 niveaux en radio. */
.permission-grid {
	display: grid;
	grid-template-columns: 1fr auto;
	row-gap: 10px;
	align-items: center;
	margin-top: 8px;
}
.permission-grid .resource-label { font-size: 13px; color: var(--text); }
.permission-grid .levels { display: flex; gap: 14px; }
.permission-grid .levels label {
	display: flex; align-items: center; gap: 5px;
	font-size: 12px; font-weight: 400; color: var(--text-muted);
	margin: 0; text-transform: none;
}
.permission-grid .levels input { margin: 0; }

.checkbox-list {
	display: flex;
	flex-wrap: wrap;
	gap: 8px 18px;
	margin-top: 8px;
}
.checkbox-list label {
	display: flex; align-items: center; gap: 6px;
	font-size: 13px; font-weight: 400; color: var(--text);
	margin: 0; text-transform: none;
}
.checkbox-list input { margin: 0; }

/* -------------------------------------------------------------------- */
/* Tableaux                                                              */
/* -------------------------------------------------------------------- */

/* 2026-11-05 (audit UI web, mesuré dans Chrome) — ce wrapper était en
   `overflow: hidden` : un tableau plus large que son conteneur était
   COUPÉ, sans barre de défilement, le contenu devenant inatteignable.
   Mesuré à 1024 px : 14 tableaux sur 7 écrans, jusqu'à 330 px perdus
   (Comptabilité), les 8 tableaux de Notifications. Invisible à 1440 px,
   d'où un défaut qui a survécu à toutes les relectures. `overflow-x:
   auto` règle la classe entière du problème, y compris les tableaux à
   venir ; `overflow-y: hidden` conserve le clipping des coins arrondis.
   `.table-wrap--scroll` devient redondant et reste sans effet de bord. */
/* 🐛 2026-11-10 (balayage UI) — ces tableaux défilent horizontalement depuis
   toujours, mais RIEN ne le disait : macOS masque les barres de défilement
   au repos, donc « Statut paiement », « Montant » ou la colonne d'actions
   d'un tableau large (Réservations, Paiements, Relevé propriétaire) étaient
   simplement invisibles et inatteignables pour qui ne pense pas à faire
   glisser deux doigts. Une colonne qu'on ne sait pas pouvoir atteindre
   n'existe pas.

   Ombres de défilement, en CSS seul (aucun script, aucun écouteur) : deux
   dégradés OPAQUES couleur du fond, collés au contenu (`local`), masquent
   deux ombres fixées au cadre (`scroll`). Tant qu'il reste à défiler d'un
   côté, le masque de ce côté a glissé et l'ombre apparaît ; une fois au
   bout, le masque la recouvre à nouveau. Les `<td>` n'ont pas de fond, les
   ombres se voient donc à travers ; les `<th>`, si, et c'est voulu —
   l'en-tête reste franc. */
.table-wrap {
	background-color: var(--surface);
	background-image:
		linear-gradient(to right, var(--surface), rgba(0, 0, 0, 0)),
		linear-gradient(to left, var(--surface), rgba(0, 0, 0, 0)),
		/* 🐛 2026-11-11 (balayage UI) — ces deux calques étaient en
		   `rgba(0, 0, 0, .22)` : du noir sur `--bg: #0A1F17`, donc rien de
		   visible en thème sombre. Le repère de défilement existait dans la
		   feuille et ne disait rien à l'écran — la matrice d'occupation
		   d'Analytics se coupait en plein mois sans la moindre indication.
		   Même correction que pour `.tab-bar--property`. */
		linear-gradient(to right, color-mix(in srgb, var(--text) 24%, transparent), rgba(0, 0, 0, 0)),
		linear-gradient(to left, color-mix(in srgb, var(--text) 24%, transparent), rgba(0, 0, 0, 0));
	background-position: left center, right center, left center, right center;
	background-repeat: no-repeat;
	background-size: 30px 100%, 30px 100%, 10px 100%, 10px 100%;
	background-attachment: local, local, scroll, scroll;
	border: 1px solid var(--border); border-radius: var(--radius);
	overflow-x: auto; overflow-y: hidden; box-shadow: var(--shadow-sm);
}
/* 🐛 2026-11-11 (balayage UI) — Planning, vue semaine. Les 7 jours étaient
   posés en flex avec `min-width:220px` : 7×220 + 6×12 de gouttière = 1612 px,
   bien au-delà de la zone de contenu. Samedi et dimanche sortaient donc de
   l'écran, et la barre de défilement de macOS étant masquée au repos, RIEN
   n'indiquait qu'il manquait deux jours — on croyait la semaine finie le
   vendredi. Grille de 7 colonnes égales qui tiennent toujours ; le repli en
   défilement horizontal n'est gardé que sous 1000 px, là où 7 colonnes
   seraient de toute façon illisibles. `minmax(0, 1fr)` et non `1fr` : sans
   lui, une carte au contenu long refuse de rétrécir et repousse les
   suivantes hors cadre — le défaut qu'on corrige ici. */
.planning-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 12px; align-items: start; }
/* 🐛 2026-11-11 (balayage UI, rendu réel) — les 7 colonnes tiennent depuis le
   10/11, mais chacune est une `.card` et `.card` porte 24 px de marge
   intérieure. Sur un écran de 1280, une colonne fait 129 px : 48 px partaient
   en marge, 16 de plus dans la vignette de tâche, et il restait 65 px de
   texte. « Check-out » s'y coupait en « Check- / out », et « Appartement
   Centre-Ville » tenait sur trois lignes. La marge d'une carte de tableau de
   bord n'a pas de raison de s'appliquer à une colonne de jour. */
.planning-week > .card { padding: 14px; }
@media (max-width: 1000px) {
	.planning-week { display: flex; overflow-x: auto; padding-bottom: 8px; }
	.planning-week > .card { min-width: 200px; flex: none; }
}

table.data, table.table { width: 100%; border-collapse: collapse; }
table.data th, table.table th, table.data td, table.table td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid var(--border); }
table.data th, table.table th { color: var(--text-muted); font-weight: 700; background: var(--bg-sunken); font-size: 11.5px; text-transform: uppercase; letter-spacing: .05em; }
table.data tr:last-child td, table.table tr:last-child td { border-bottom: none; }
table.data tbody tr, table.table tbody tr { transition: background .12s var(--ease); }
table.data tbody tr:hover, table.table tbody tr:hover { background: var(--bg-sunken); }
/* 2026-08-19 (retour user) : surbrillance du mois en cours dans les
   tableaux mensuels (Analytics) — même classe/teinte que .is-today du
   calendrier (calendar.php), jamais une 2e convention. */
table.data tbody tr.is-today, table.table tbody tr.is-today,
table.data tbody tr.is-today:hover, table.table tbody tr.is-today:hover { background: color-mix(in srgb, var(--accent) 14%, var(--bg-sunken)); }
/* 2026-09-08 (retour user, capture SuperHote) : même surbrillance mais au
   niveau CELLULE — nécessaire quand les mois sont des COLONNES (tableau de
   trésorerie mensuel, logements en lignes) plutôt que des lignes. */
table.data td.is-today, table.table td.is-today, table.data th.is-today, table.table th.is-today { background: color-mix(in srgb, var(--accent) 14%, var(--bg-sunken)); }
table.data th.is-today, table.table th.is-today { color: var(--accent); font-weight: 700; }
/* Lignes Recettes/Dépenses repliables (chevron ▼/▶) — chaque ligne de
   détail par logement porte data-cashflow-group="recettes|depenses",
   masquée par cashflow_table.js au clic sur le chevron, jamais en CSS pur
   (l'état ouvert/fermé doit persister au tri/filtre fait par le même
   script). */
.cashflow-toggle { background: none; border: none; cursor: pointer; padding: 0 4px 0 0; font-size: 10px; color: inherit; vertical-align: middle; }
.cashflow-toggle[aria-expanded="false"] { transform: rotate(-90deg); display: inline-block; }
/* 2026-11-07 (revue visuelle : « Statut » rendait « Annu⌄ » sur Tâches) —
   `.cashflow-filter-bar` faisait DEJA exactement ce qu'il fallait, pour un
   seul bloc : donner une largeur plancher aux champs d'une barre de filtres.
   Les vingt autres barres étaient écrites en `style="display:flex; gap:8px;
   flex-wrap:wrap; align-items:flex-end;"` sans ce plancher. Sans lui, un
   `.field` est un enfant flex rétrécissable : avec huit filtres sur la même
   ligne, chacun descend sous la largeur de son contenu et `text-overflow:
   ellipsis` fait le reste — « Annulée » devient « Annu… », « Assignée à »
   n'affiche plus le nom choisi. Le point important : la ligne NE DÉBORDE
   PAS et ne passe pas à la ligne non plus, donc aucune mesure de
   débordement ne voit ce défaut. Il ne se voit qu'à l'écran.
   `.kh-filtres` est la forme nommée et partagée ; `.cashflow-filter-bar` ne
   garde que son interligne propre à lui. */
.kh-filtres, .cashflow-filter-bar { display: flex; align-items: flex-end; flex-wrap: wrap; }
.kh-filtres { gap: 8px; margin-bottom: 16px; }
.cashflow-filter-bar { gap: 20px; margin-bottom: 14px; }
.kh-filtres > .field, .cashflow-filter-bar .field { margin: 0; min-width: 180px; }
.cashflow-filter-bar .field { min-width: 200px; }
/* Les champs de date sont bornés par leur widget natif, jamais par leur
   libellé : leur imposer 180 px creuse un trou dans la rangée. */
.kh-filtres > .field:has(> input[type="date"]),
.kh-filtres > .field:has(> input[type="number"]) { min-width: 140px; }
.cashflow-sort { display: flex; align-items: center; gap: 6px; }
.cashflow-sort-btn {
	background: none; border: 1px solid var(--border); border-radius: 6px; padding: 5px 10px; font-size: 12px;
	color: var(--text-muted); cursor: pointer; display: flex; align-items: center; gap: 4px;
}
.cashflow-sort-btn.is-active { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.payment-link-row { cursor: pointer; }
.clickable-row { cursor: pointer; }
/* .num sur <th> était sans effet (seul td.num était ciblé) : l'en-tête restait
   aligné à gauche alors que les valeurs en dessous étaient à droite. */
table.data th.num, table.table th.num, table.data td.num, table.table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* 2026-08-28 puis 2026-11-05 — "table.table" était un ALIAS dupliqué de
   table.data, créé pour corriger 13 vues sans les toucher. Elles sont 31
   aujourd'hui (52 occurrences), et les deux familles avaient DIVERGÉ :
   la colonne collante (.acct-sticky) et la mise en évidence du jour
   (.is-today) n'existaient que du côté table.data. Un composant dupliqué
   ne reste jamais synchronisé. Les deux noms partagent désormais les
   MÊMES sélecteurs ci-dessus — plus de bloc séparé à maintenir deux fois.
   La migration du markup vers .data reste souhaitable, mais cosmétique. */

/* Légende inline compacte sous un nom dans une cellule de tableau (ex.
   "Dernier départ"/"Prochaine arrivée" du Tableau de bord, cf. home_helper.php)
   — jamais .hint ici : son padding/background/border pensés pour un bloc
   callout autonome déborde visuellement sur la ligne au-dessus quand il est
   collé en <span> juste après un <br>. */
.turnover-meta { display: inline-block; color: var(--text-muted); font-size: 12px; margin-top: 2px; }

/* -------------------------------------------------------------------- */
/* Grille calendrier (structure fixe ; grid-column/row calculés en PHP)   */
/* -------------------------------------------------------------------- */

.cal-toolbar { display: flex; align-items: center; gap: 16px; margin-bottom: 16px; flex-wrap: wrap; }
.cal-toolbar h1 { flex: 1; min-width: 160px; font-size: 18px; }
.cal-toolbar > a:first-child, .cal-toolbar > a:nth-child(3) { font-size: 18px; color: var(--text-muted); padding: 0 4px; }
.cal-toolbar > a:first-child:hover, .cal-toolbar > a:nth-child(3):hover { color: var(--accent); }

/* Onglets de période (Mensuel/Trimestre/Semestre) — même esprit que
   .sidebar-nav-item.active, un groupe de liens dont un seul est actif. */
/* .page-tabs/.page-tab : version générique des mêmes règles, réutilisée
   par finance_payments.php/finance_payment_schedules.php (tabs Paiements /
   Planifier les paiements) — même composant visuel que cal-period-tabs,
   juste un nom qui ne présuppose pas un contexte calendrier. */
.cal-period-tabs, .page-tabs { display: flex; gap: 2px; background: var(--bg-sunken); border-radius: var(--radius-sm); padding: 3px; }
.cal-period-tab, .page-tab { padding: 5px 12px; border-radius: 6px; font-size: 12.5px; font-weight: 600; color: var(--text-muted); }
.cal-period-tab:hover, .page-tab:hover { color: var(--text); }
.cal-period-tab.is-active, .page-tab.is-active { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }

/* L'ancien menu global "Gestion des prix" (.cal-rate-toggle) a été retiré
   le 2026-08-15, remplacé par un chevron par logement — cf. .cal-pg-toggle
   plus bas et calendar_property_toggle.js. */

/* Menu "Logements" (CAL-034, audit calendrier 2026-08-10, "Filtrer par
   propriété") — bouton+dropdown (calendar_property_filter.js), contenu
   différent d'un simple show/hide : un vrai formulaire GET avec cases à
   cocher (state dans l'URL, pas de localStorage). */
.cal-property-filter { position: relative; }
.cal-property-filter-menu {
	display: none; position: absolute; top: calc(100% + 6px); right: 0; z-index: 10;
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
	box-shadow: var(--shadow-md); padding: 10px 14px; min-width: 220px; max-height: 320px; overflow-y: auto;
}
.cal-property-filter-menu.open { display: block; }
.cal-property-filter-menu label { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); padding: 5px 0; white-space: nowrap; }
.cal-property-filter-actions { display: flex; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
/* Raccourcis "par groupe"/"par bâtiment" (CAL-035/036) en haut du menu
   "Logements" — boutons compacts, pas des cases à cocher (ce ne sont pas
   des valeurs soumises au serveur, juste des présélections client-side). */
.cal-property-filter-quick { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
.cal-property-filter-quick .btn-ghost { font-size: 11px; padding: 3px 8px; border: 1px solid var(--border); border-radius: 999px; }

/* max-height + overflow:auto (pas juste overflow-x) : rend .cal-wrap
   scrollable dans les 2 sens quand il y a beaucoup de logements/jours,
   condition nécessaire pour que l'en-tête sticky ci-dessous s'accroche
   correctement au bon conteneur plutôt qu'à la page entière. */
.cal-wrap { overflow: auto; max-height: 72vh; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 8px; box-shadow: var(--shadow-sm); }

/* -------------------------------------------------------------------- */
/* Grille "par logement" (.cal-pg-*) — un mini-grille CSS indépendante   */
/* par ligne (barres / nuits min / règles / disponibilité), plutôt qu'une */
/* seule grille géante partagée : masquer une ligne (menu ci-dessus)     */
/* devient un simple display:none qui collapse proprement, sans recalcul */
/* de grid-template-rows pour tout le reste de la page.                  */
/* -------------------------------------------------------------------- */

/* `width: max-content` (pas juste min-width) — 2026-08-30, retour user
   ("pourquoi les tailles de colonnes ne sont pas les mêmes entre deux mois") :
   avec seulement min-width, .cal-pg s'étirait quand même à 100% de .cal-wrap
   sur une fenêtre large (comportement par défaut d'une box bloc en width:auto),
   ce qui étirait ensuite chaque colonne `1fr` en dessous — largeur de colonne
   dépendante de la largeur de fenêtre. width:max-content fige .cal-pg à sa
   largeur de contenu réelle quelle que soit .cal-wrap, .cal-wrap (overflow:auto)
   prend le relais avec un scroll horizontal si besoin. */
.cal-pg { width: max-content; }
.cal-cell { display: flex; align-items: center; justify-content: center; font-size: 11px; color: var(--text-muted); text-align: center; }
.cal-day-header { flex-direction: column; line-height: 1.3; }

/* Colonne "Logement" collante horizontalement (audit calendrier 2026-08-10,
   CAL-008 — jusqu'ici seul l'en-tête de dates était collant verticalement ;
   le nom du logement disparaissait dès qu'on scrollait horizontalement sur
   une longue période). `left: 0` sur le conteneur scrollable `.cal-wrap`
   (`overflow: auto`, le seul ancêtre scrollant) — même fond opaque que la
   règle existante, sinon les barres de résa défileraient visiblement
   derrière le nom du logement. */
.cal-pg-corner, .cal-pg-corner-spacer, .cal-pg-label {
	width: 180px; flex-shrink: 0; font-size: 13px; font-weight: 700; color: var(--text);
	background: var(--surface);
	position: sticky; left: 0; z-index: 2;
	/* 🐛 2026-11-11 (balayage UI, rendu réel du calendrier) — `.cal-wrap` porte
	   8 px de marge intérieure, et `left: 0` colle au bord de cette marge, pas
	   au bord de la carte. Il restait donc une bande de 8 px À GAUCHE de la
	   colonne « Logement » où les barres de réservation continuaient de
	   défiler : un éclat orange de la résa de Maison du Lac apparaissait
	   contre la bordure de la carte, détaché de tout. L'ombre portée prolonge
	   le fond de la colonne sur ces 8 px — même couleur que la carte, donc
	   invisible, mais opaque. Pas de décalage de mise en page, contrairement à
	   un `left: -8px`. */
	box-shadow: -8px 0 0 var(--surface);
}
.cal-pg-corner, .cal-pg-corner-spacer { display: flex; align-items: center; padding-left: 8px; }
.cal-pg-corner { font-size: 12px; z-index: 4; /* coin haut-gauche : au-dessus de .cal-pg-header (z-index:3) qui le contient déjà, pour rester collant dans LES DEUX sens à la fois. */ }

/* Colonne "Logement" : photo miniature + nom (même hauteur que .cal-pg-row-main,
   48px) puis un libellé compact par ligne secondaire (Nuits min./Règles/Dispo.,
   même hauteur que .cal-rate-value, 26px) — empilés en colonne pour rester
   alignés verticalement avec leur ligne de données SANS toucher à la grille
   des jours elle-même (aucun risque de désalignement des colonnes de dates).
   Inspiré du calendrier Hostex/SuperHote (photo + libellés de ligne visibles
   sans avoir à ouvrir un menu). */
.cal-pg-label { display: flex; flex-direction: column; }
.cal-pg-label-main { min-height: 48px; display: flex; align-items: center; gap: 8px; padding: 0 6px 0 8px; overflow: hidden; }
.cal-pg-label-photo, .cal-pg-label-photo-placeholder {
	width: 28px; height: 28px; border-radius: var(--radius-sm); flex-shrink: 0; object-fit: cover; background: var(--bg-sunken);
}
.cal-pg-label-photo-placeholder { display: flex; align-items: center; justify-content: center; font-size: 14px; }
.cal-pg-label-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.cal-pg-label-sub {
	min-height: 26px; display: flex; align-items: center; padding-left: 8px;
	font-size: 10px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: .02em;
}

/* Titre de bloc-mois (2026-08-15, "pourquoi tu allonges les lignes...") —
   Trimestre/Semestre empilent désormais un .cal-wrap COMPLET par mois
   (grille à largeur normale, jamais 90/180 colonnes) plutôt que d'élargir
   une seule grille géante — ce titre remplace l'ancien bandeau de labels
   .cal-pg-months-row qui surplombait cette grille unique. Masqué par
   calendar.php elle-même si la période tient sur un seul mois (Mensuel). */
.cal-pg-month-title { font-size: 15px; font-weight: 700; color: var(--text); margin: 20px 0 8px; }
.cal-pg-month-title:first-of-type { margin-top: 0; }

/* En-tête collant (numéros de jour + coin "Logement") : reste visible en
   scrollant verticalement une longue liste de logements. Fond opaque
   obligatoire pour masquer les lignes qui défilent en dessous. */
.cal-pg-header { display: flex; position: sticky; top: 0; z-index: 3; background: var(--surface); }
/* Largeur de colonne FIXE (40px, plus jamais `1fr`) — même raison que
   `.cal-pg` ci-dessus : une colonne devait rester la même largeur en Août et
   en Septembre, indépendamment de la fenêtre du navigateur. */
.cal-pg-days-header { flex: 1; display: grid; grid-template-columns: repeat(var(--cal-days), 40px); }

.cal-pg-body { display: flex; flex-direction: column; }
.cal-pg-property { display: flex; border-top: 1px solid var(--border); }
.cal-pg-property:first-child { border-top: none; }
.cal-pg-rows { flex: 1; display: flex; flex-direction: column; }

/* En-têtes de groupes de logements (CAL-029/037/038/039, audit calendrier
   2026-08-10) — bande pleine largeur au-dessus de chaque cluster de
   logements, pas alignée sur la grille de dates (un simple séparateur de
   section, pas une ligne de données). calendar_group_toggle.js pilote le
   repli/dépli (localStorage, clé par groupe — nombre de groupes inconnu à
   l'avance, contrairement à calendar_property_toggle.js qui a une clé
   unique listant les logements dépliés). */
.cal-group-header {
	display: flex; align-items: center; gap: 8px; padding: 6px 8px; margin-top: 4px;
	background: var(--border); border-radius: var(--radius-sm);
	font-size: 12px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .03em;
}
.cal-pg-property:first-child + .cal-group-header,
.cal-group-header:first-child { margin-top: 0; }
.cal-group-toggle {
	background: none; border: none; cursor: pointer; color: inherit; font-size: 11px; padding: 0 2px;
	transition: transform .12s var(--ease);
}
.cal-group-header.collapsed .cal-group-toggle { transform: rotate(-90deg); }
.cal-group-body { display: flex; flex-direction: column; }
.cal-group-body.collapsed { display: none; }
.cal-pg-row { display: grid; grid-template-columns: repeat(var(--cal-days), 40px); position: relative; }
.cal-pg-row-main { min-height: 48px; }

/* Repères visuels pour rattacher une barre/valeur à sa colonne de jour
   sans avoir à compter : trait vertical entre chaque jour, fond teinté sur
   les week-ends, liseré d'accent sur la colonne du jour actuel. */
.cal-day-header, .cal-day-cell, .cal-rate-value { border-right: 1px solid var(--border); background: var(--surface); }
.cal-day-header.is-weekend, .cal-day-cell.is-weekend, .cal-rate-value.is-weekend { background: var(--bg-sunken); }
.cal-day-header.is-today, .cal-day-cell.is-today, .cal-rate-value.is-today { background: color-mix(in srgb, var(--accent) 14%, var(--bg-sunken)); }

.cal-day-price { font-size: 10px; color: var(--text-muted); font-variant-numeric: tabular-nums; }

/* Lignes Nuits minimum / Règles / Disponibilité — valeur compacte, sous le
   seuil de lisibilité d'un montant, jamais de barre ici. */
.cal-rate-value { min-height: 26px; font-size: 11px; font-variant-numeric: tabular-nums; color: var(--text-muted); }
.cal-rate-empty { color: var(--border); }
.cal-rate-cell { cursor: pointer; transition: background .12s var(--ease); }
.cal-rate-cell:hover { background: var(--bg-sunken); color: var(--text); }
/* 2026-08-22 (retour user, "sélectionner plusieurs jours par glisser-déposer") — surbrillance pendant la sélection, cf. calendar_rate_panel.js. */
.cal-rate-cell.cal-rate-dragging { background: var(--accent); color: var(--accent-contrast); }
/* Parité SuperHote ("Créer une réservation manuellement", cliquer-glisser). */
.cal-slot.cal-slot-dragging { background: var(--accent); }
.cal-avail-cell.is-unavailable { color: var(--danger-fg); font-weight: 700; }
.cal-avail-cell.is-available { color: var(--success-fg); }

/* Repli/dépli PAR LOGEMENT des lignes secondaires (2026-08-15, remplace le
   menu global "Gestion des prix") — display:none sur un bloc entier,
   même logique que l'ancien mécanisme, juste scopé à .cal-pg-property
   plutôt qu'à #cal-wrap tout entier. Repli par défaut (classe posée
   côté serveur dans calendar.php), calendar_property_toggle.js retire la
   classe pour les logements explicitement dépliés par le user. */
.cal-pg-property.collapsed .cal-pg-label-sub,
.cal-pg-property.collapsed .cal-pg-row-minnights,
.cal-pg-property.collapsed .cal-pg-row-maxnights,
.cal-pg-property.collapsed .cal-pg-row-rules,
.cal-pg-property.collapsed .cal-pg-row-availability { display: none; }

.cal-pg-toggle {
	background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 11px;
	padding: 0; margin-left: auto; flex-shrink: 0; line-height: 1;
	transition: transform .12s var(--ease), color .12s var(--ease);
}
.cal-pg-toggle:hover { color: var(--text); }
.cal-pg-property.collapsed .cal-pg-toggle { transform: rotate(-90deg); }

/* 2026-11-12 (retour user, capture : « on dirait que les barres n'ont pas la
   même taille ») — HAUTEUR FIXE. La barre se dimensionnait sur son contenu
   (`align-self: center`, aucune hauteur) : une barre portant un ⚠ ou un nom
   en idéogrammes (学林 鲁) ouvre une ligne plus haute qu'un nom latin seul, et
   deux réservations voisines n'avaient donc pas la même épaisseur. Le défaut
   est invisible sur un jeu de test anglo-latin sans indicateur — il apparaît
   dès que les vraies données arrivent.
   22px = 14 de ligne + 2×4 de padding, `box-sizing` compris : le contenu qui
   déborde est déjà coupé par `overflow: hidden` juste en dessous. */
.cal-bar { border-radius: var(--radius-sm); padding: 4px 8px; height: 22px; line-height: 14px; box-sizing: border-box; font-size: 11px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: #fff; align-self: center; display: block; text-decoration: none; transition: filter .12s var(--ease), transform .12s var(--ease); }
.cal-bar.status-confirmed { background: var(--success); }
.cal-bar.status-pending { background: var(--warning); }
.cal-bar.status-cancelled { background: var(--text-muted); text-decoration: line-through; opacity: .7; }
.cal-bar.status-completed { background: var(--text-muted); }
/* 2026-11-06 (audit UI web) — sans `flex-wrap`, cette légende tenait sur
   une seule ligne quoi qu'il arrive : son dernier élément (« Dépendance »)
   sortait du cadre et élargissait LA PAGE ENTIÈRE à 1094 px sur une
   fenêtre de 1024, donnant une barre de défilement horizontale au
   document. Le calendrier lui-même n'y était pour rien : il défile déjà
   proprement dans `.cal-wrap`. `row-gap` plus serré que `column-gap`
   pour que le passage à la ligne reste compact. */
.cal-legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-top: 12px; font-size: 12px; color: var(--text-muted); }
.cal-legend span { display: inline-flex; align-items: center; gap: 6px; }
.cal-legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.cal-legend i.status-confirmed { background: var(--success); }
.cal-legend i.status-pending { background: var(--warning); }
.cal-legend i.status-cancelled { background: var(--text-muted); }
/* 2026-11-13 — la légende dit désormais « Terminée » (les annulées ne sont
   plus tracées, cf. Booking_model::get_for_calendar()). Sans cette règle, la
   pastille restait transparente : un libellé sans couleur en face. */
.cal-legend i.status-completed { background: var(--text-muted); }
.cal-legend i.status-blocked {
	background: repeating-linear-gradient(45deg, var(--danger), var(--danger) 3px, var(--danger-bg) 3px, var(--danger-bg) 6px);
}
/* Pastilles de légende par type de blocage (AVL-006→013) — mêmes couleurs
   que .cal-bar.cal-block-type-* ci-dessous, dupliquées ici comme
   .status-blocked l'est déjà entre .cal-bar et .cal-legend i (2 contextes
   distincts, même convention déjà en place dans ce fichier). */
.cal-legend i.cal-block-type-maintenance {
	background: repeating-linear-gradient(45deg, #2A8C82, #2A8C82 3px, #E1F2F0 3px, #E1F2F0 6px);
}
.cal-legend i.cal-block-type-renovation {
	background: repeating-linear-gradient(45deg, #A8611F, #A8611F 3px, #F3E6D9 3px, #F3E6D9 6px);
}
.cal-legend i.cal-block-type-personal {
	background: repeating-linear-gradient(45deg, #8B5FA3, #8B5FA3 3px, #EFE6F5 3px, #EFE6F5 6px);
}
.cal-legend i.cal-block-type-admin {
	background: repeating-linear-gradient(45deg, #5C6F8A, #5C6F8A 3px, #E7ECF0 3px, #E7ECF0 6px);
}
.cal-legend i.cal-block-type-dependency {
	background: repeating-linear-gradient(45deg, #8B8B7A, #8B8B7A 3px, #EFEFE9 3px, #EFEFE9 6px);
}

/* Modificateur de `.cal-day-cell` ajouté seulement en écriture (curseur +
   survol) : la cellule sous les barres, une par jour × logement, capte le
   clic sur un jour disponible (grille mois) — désormais toujours présente
   visuellement (gridlines/week-end/aujourd'hui), seule l'interactivité
   reste conditionnelle. Une barre "annulée" laisse le clic passer au
   travers (pointer-events:none) — une résa annulée libère à nouveau les
   dates, le jour doit rester cliquable pour un blocage manuel (même
   logique que la vue jour, cf. calendar_day.php). Les barres
   confirmée/en attente/terminée sont cliquables (lien vers booking/view) ;
   "bloqué" reste cliquable pour le déblocage, comme avant. */
.cal-slot { cursor: pointer; transition: background .12s var(--ease); }
.cal-slot:hover { background: var(--bg-sunken); }
.cal-bar.status-cancelled { pointer-events: none; }
.cal-bar.status-confirmed:hover,
.cal-bar.status-pending:hover,
.cal-bar.status-completed:hover { filter: brightness(1.1); cursor: pointer; transform: translateY(-1px); }
.cal-bar.status-blocked {
	pointer-events: auto;
	cursor: pointer;
	background: repeating-linear-gradient(45deg, var(--danger), var(--danger) 4px, var(--danger-bg) 4px, var(--danger-bg) 8px);
}
/* Couleur par type de blocage (AVL-006→013, révisé 2026-08-10 — priorité
   donnée au comportement Hostex réel plutôt qu'à la lecture littérale du
   texte d'aide SuperHote qui ne mentionnait qu'un "label"). Fixes dans les
   2 thèmes (comme kh_channel_badge() pour les canaux, channel_helper.php)
   — ce sont des couleurs d'IDENTITÉ de type, pas des couleurs de contraste
   texte/fond à adapter. owner/other gardent la hachure rouge d'origine
   (--danger), déjà établie comme le "blocage générique". */
.cal-bar.cal-block-type-maintenance {
	background: repeating-linear-gradient(45deg, #2A8C82, #2A8C82 4px, #E1F2F0 4px, #E1F2F0 8px);
}
.cal-bar.cal-block-type-renovation {
	background: repeating-linear-gradient(45deg, #A8611F, #A8611F 4px, #F3E6D9 4px, #F3E6D9 8px);
}
.cal-bar.cal-block-type-personal {
	background: repeating-linear-gradient(45deg, #8B5FA3, #8B5FA3 4px, #EFE6F5 4px, #EFE6F5 8px);
}
.cal-bar.cal-block-type-admin {
	background: repeating-linear-gradient(45deg, #5C6F8A, #5C6F8A 4px, #E7ECF0 4px, #E7ECF0 8px);
}
.cal-bar.cal-block-type-dependency {
	background: repeating-linear-gradient(45deg, #8B8B7A, #8B8B7A 4px, #EFEFE9 4px, #EFEFE9 8px);
}

/* Indicateurs sur les barres (RES-016/017/019/022/023, audit calendrier
   2026-08-10) — cluster de petits badges après le nom du voyageur ; se
   font simplement couper par overflow:hidden sur une barre 1-2 jours trop
   étroite (même limite qu'un vrai SuperHote/Hostex sur une barre courte,
   pas une régression). Flèches (arrivée/départ) = caractères, le reste =
   pastilles colorées façon kh_channel_badge() (cohérence visuelle, pas de
   glyphe/emoji ajouté au projet). */
.cal-bar-indicators { display: inline-flex; align-items: center; gap: 3px; margin-left: 4px; vertical-align: middle; }
.cal-bar-dot { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.cal-bar-dot-arrival, .cal-bar-dot-departure { font-size: 10px; font-weight: 700; line-height: 1; color: #fff; }
.cal-bar-dot-message, .cal-bar-dot-cleaning, .cal-bar-dot-payment, .cal-bar-dot-deposit, .cal-bar-dot-recent { width: 6px; height: 6px; border-radius: 50%; background: #fff; }
.cal-bar-dot-message { background: var(--accent); border: 1px solid #fff; }
.cal-bar-dot-cleaning { background: var(--warning); border: 1px solid #fff; }
.cal-bar-dot-payment { background: var(--danger); border: 1px solid #fff; }
/* RES-018/020/021/024 (audit calendrier 2026-08-11) : mêmes conventions
   (pastille = statut binaire, glyphe = plus riche/actionnable). */
.cal-bar-dot-deposit { background: var(--accent-2, #D9A62E); border: 1px solid #fff; }
.cal-bar-dot-recent { background: #fff; border: 1px solid var(--text-muted); }
.cal-bar-dot-problem { font-size: 10px; line-height: 1; color: #fff; }
.cal-bar-dot-vip { font-size: 10px; line-height: 1; color: #FFD700; }

/* 2026-08-19 (retour user, capture calendrier SuperHote réelle) : carte au
   survol des barres de réservation (calendar_hover_tooltip.js) — remplace
   l'ancien title natif. Fond sombre fixe volontairement (pas de token
   clair/sombre) : même esprit que la capture de référence, et lisible sur
   n'importe quel fond de barre coloré en dessous, thème clair ou sombre. */
.cal-hover-tooltip {
	position: fixed;
	z-index: 60;
	background: #1E2A22;
	color: #fff;
	border-radius: var(--radius-sm);
	padding: 10px 12px;
	font-size: 12.5px;
	line-height: 1.5;
	max-width: 280px;
	box-shadow: var(--shadow-md);
	pointer-events: none;
}
.cal-hover-tooltip-title { display: flex; align-items: center; gap: 6px; font-weight: 700; margin-bottom: 4px; }
.cal-hover-tooltip-range { color: #fff; }
.cal-hover-tooltip-muted { color: rgba(255, 255, 255, .68); font-style: italic; }
.cal-hover-tooltip-money { font-weight: 700; margin-top: 4px; }

/* Vue jour : les mêmes éléments .cal-slot / .cal-bar.status-blocked sont
   réutilisés hors grille (tableau), en boutons visibles plutôt qu'en
   cellules transparentes. */
.cal-slot-button {
	display: inline-flex;
	align-items: center;
	padding: 4px 10px;
	border-radius: var(--radius-sm);
	font-size: 12px;
}
.cal-slot.cal-slot-button { border: 1px solid var(--border); background: var(--surface); color: var(--text); }
.cal-slot.cal-slot-button:hover { border-color: var(--accent); color: var(--accent); }

/* Panneau latéral bloquer/débloquer (partials/block_panel.php) — premier
   composant off-canvas du projet, tout le reste étant du rendu plein-page.
   Règles partagées avec rate-panel (gestion des prix) et payment-panel
   (détail d'un lien de paiement, Finance::payments()) — même composant
   visuel réutilisé 3x, jamais dupliqué. NB : rate-panel a existé un temps
   sans ces règles (bug trouvé et corrigé le 2026-08-07, jamais vérifié
   visuellement avant ce jour — le panneau s'affichait donc en plein flux
   de page au lieu de coulisser en off-canvas). */
#block-panel-backdrop, #rate-panel-backdrop, #payment-panel-backdrop, #booking-panel-backdrop {
	position: fixed; inset: 0; background: rgba(10, 31, 23, .4);
	opacity: 0; pointer-events: none; transition: opacity .2s ease; z-index: 40;
}
#block-panel-backdrop.open, #rate-panel-backdrop.open, #payment-panel-backdrop.open, #booking-panel-backdrop.open { opacity: 1; pointer-events: auto; }

#block-panel, #rate-panel, #payment-panel, #booking-panel {
	position: fixed; top: 0; right: 0; bottom: 0; width: 360px; max-width: 90vw;
	background: var(--surface); border-left: 1px solid var(--border);
	box-shadow: -8px 0 24px rgba(10, 31, 23, .18);
	transform: translateX(100%); transition: transform .25s var(--ease);
	z-index: 41; overflow-y: auto;
}
#block-panel.open, #rate-panel.open, #payment-panel.open, #booking-panel.open { transform: translateX(0); }

.block-panel-header {
	display: flex; align-items: center; justify-content: space-between;
	padding: 16px 20px; border-bottom: 1px solid var(--border);
}
.block-panel-header h2 { font-size: 15px; margin: 0; }
.block-panel-body { padding: 20px; }
.block-panel-property { color: var(--text-muted); font-size: 13px; margin-bottom: 12px; }
#block-panel-delete .label { font-size: 12px; color: var(--text-muted); }
#block-panel-delete p { margin-bottom: 14px; }
/* AVL-003/017/018 (audit calendrier 2026-08-10, "bulk multi-logements") */
.cal-bulk-property-list {
	max-height: 160px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px;
}
.cal-bulk-property-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); padding: 4px 0; }
/* RES-030 (audit calendrier 2026-08-10, drawer réservation) */
.booking-panel-section-title {
	font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
	color: var(--text-muted); margin: 18px 0 8px; padding-top: 10px; border-top: 1px solid var(--border);
}
.booking-panel-section-title:first-of-type { margin-top: 0; padding-top: 0; border-top: none; }

/* -------------------------------------------------------------------- */
/* Auth (login) — pas de sidebar                                         */
/* -------------------------------------------------------------------- */

.auth-shell {
	min-height: 100vh; display: flex; align-items: center; justify-content: center;
	background:
		radial-gradient(60% 55% at 12% 6%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 65%),
		radial-gradient(45% 40% at 94% 94%, color-mix(in srgb, var(--accent-2) 14%, transparent), transparent 60%),
		var(--bg-sunken);
}
.auth-card {
	width: 100%; max-width: 360px; animation: card-rise .5s var(--ease) backwards;
	border-radius: var(--radius-lg);
}
.auth-card .sidebar-brand { padding: 0 0 20px; text-align: center; font-size: 22px; font-weight: 800; }

/* -------------------------------------------------------------------- */
/* Dashboard analytics (prompt 30)                                       */
/* -------------------------------------------------------------------- */

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin-bottom: 20px; }
.kpi-card {
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
	padding: 16px; box-shadow: var(--shadow-sm); transition: transform .15s var(--ease), box-shadow .15s var(--ease);
	animation: card-rise .4s var(--ease) backwards;
}
.kpi-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.kpi-value { font-family: 'Sora', sans-serif; font-size: 24px; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; }
.kpi-label { font-size: 12px; color: var(--text-muted); margin-top: 4px; font-weight: 600; }

/* ====================================================================== */
/* « Mes tâches » du prestataire (my_tasks.php) — 2026-11-07.              */
/*                                                                        */
/* Une carte par tâche, pensée pour quelqu'un qui est debout, souvent sur  */
/* un téléphone, et qui a trois questions : QUOI, OÙ, QUAND. D'où la photo */
/* du logement en tête de carte (on reconnaît un lieu avant d'en lire le   */
/* nom), l'adresse cliquable vers une carte, et l'heure quand elle existe. */
/*                                                                        */
/* La carte ENTIÈRE est cliquable par un lien étiré : un <form> ne peut    */
/* pas vivre dans un <a> (contenu interactif imbriqué, invalide en HTML),  */
/* le lien est donc un FRÈRE en position absolue, sous les boutons dans    */
/* l'ordre d'empilement.                                                   */
.tache-groupe { margin-bottom: 26px; }
.tache-groupe-titre {
	display: flex; align-items: center; gap: 8px;
	font-size: 12px; text-transform: uppercase; letter-spacing: .05em;
	color: var(--text-muted); margin: 0 0 10px;
}
.tache-pastille { width: 8px; height: 8px; border-radius: 50%; background: var(--border); flex-shrink: 0; }
.tache-pastille--danger { background: var(--danger); }
.tache-pastille--accent { background: var(--accent); }
.tache-compte {
	background: var(--bg-sunken); border: 1px solid var(--border); border-radius: 999px;
	padding: 1px 8px; font-size: 11px; letter-spacing: 0; color: var(--text-muted);
}

.tache-carte {
	position: relative;
	display: flex; align-items: flex-start; gap: 14px;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); padding: 14px;
	margin-bottom: 10px;
	transition: border-color .15s var(--ease), box-shadow .15s var(--ease);
}
.tache-carte:hover { border-color: var(--accent); box-shadow: var(--shadow-sm); }
.tache-lien { position: absolute; inset: 0; z-index: 1; border-radius: var(--radius); }

.tache-photo {
	width: 64px; height: 64px; border-radius: var(--radius-sm);
	object-fit: cover; flex-shrink: 0; background: var(--bg-sunken);
}
.tache-photo--vide {
	display: flex; align-items: center; justify-content: center;
	font-size: 26px; border: 1px solid var(--border);
}

.tache-corps { flex: 1; min-width: 0; }
.tache-logement { font-weight: 700; font-size: 14.5px; margin: 0 0 2px; color: var(--text); }
.tache-titre { margin: 0 0 6px; font-size: 13px; color: var(--text); }
/* Métadonnées en TEXTE NU, jamais dans un `.hint` : `.hint` est un callout
   encadré, et une ligne de métadonnée posée dedans se lit comme un champ de
   formulaire désactivé — exactement ce que le user a signalé en capture. */
.tache-meta {
	margin: 0; font-size: 12.5px; color: var(--text-muted);
	display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
}
.tache-heure { font-weight: 700; color: var(--text); }
.tache-adresse {
	position: relative; z-index: 2;
	display: inline-block; margin-top: 6px;
	font-size: 12.5px; color: var(--accent); text-decoration: none;
}
.tache-adresse:hover { text-decoration: underline; }
.tache-badges { margin: 8px 0 0; }

.tache-actions {
	position: relative; z-index: 2;
	display: flex; flex-direction: column; gap: 8px; flex-shrink: 0;
}
.tache-actions form { margin: 0; }
.tache-actions .btn, .tache-actions .btn-outline { white-space: nowrap; }
/* Un <a> stylé en bouton n'hérite pas de la hauteur de ligne d'un <button>. */
.tache-bouton-lien { display: inline-flex; align-items: center; text-decoration: none; }

/* Téléphone : la carte passe en colonne, l'action prend toute la largeur —
   une cible large est plus sûre qu'un bouton de 90 px pour un pouce. */
@media (max-width: 560px) {
	.tache-carte { flex-wrap: wrap; }
	.tache-photo { width: 48px; height: 48px; }
	.tache-actions { flex-direction: row; width: 100%; }
	.tache-actions form, .tache-actions .btn, .tache-actions .btn-outline { flex: 1; }
	.tache-actions .btn, .tache-actions .btn-outline { width: 100%; justify-content: center; }
}

/* Infobulle explicative « ⓘ » — 2026-11-07 (demande user, captures SuperHote
   à l'appui). Le déclencheur vit dans le flux, à côté du libellé ; la bulle
   est un élément UNIQUE posé sur <body> par kh_tip.js, en `position: fixed`,
   parce qu'elle doit déborder de sa carte tout en restant dans l'écran
   (cf. le docblock du script).

   Fond sombre FIXE, pas un token clair/sombre — exactement la décision déjà
   prise pour `.cal-hover-tooltip` : la bulle flotte au-dessus d'un contenu
   quelconque et doit rester lisible dans les deux thèmes. */
.kh-tip {
	display: inline-block; margin-left: 3px; cursor: help;
	color: var(--text-muted); font-size: 11px; line-height: 1;
	vertical-align: baseline; opacity: .7; transition: opacity .15s var(--ease);
}
.kh-tip:hover, .kh-tip:focus-visible { opacity: 1; color: var(--accent); }
.kh-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
.kh-tip-bubble {
	position: fixed; z-index: 1100;
	background: #1E2A22; color: #fff;
	border-radius: var(--radius-sm);
	padding: 10px 14px;
	font-size: 12.5px; line-height: 1.45; font-weight: 400; text-align: center;
	width: max-content; max-width: min(300px, calc(100vw - 16px));
	box-shadow: var(--shadow-md);
	/* 2026-11-07 — MESURÉ : en thème sombre, le fond de la bulle (#1E2A22)
	   n'est qu'à 1,18:1 de celui d'une carte KPI (rgb(18,58,41)). Le texte y
	   est parfaitement lisible (14,9:1), mais la bulle ne se lit pas comme
	   une SURFACE distincte : son bord disparaît, et l'ombre portée ne
	   compense pas sur un fond déjà sombre. Une bordure blanche très diluée
	   dessine l'arête sans toucher à l'aplat — invisible en thème clair, où
	   la bulle tranche déjà sur une carte blanche. */
	border: 1px solid rgba(255, 255, 255, .18);
	pointer-events: none;
}

/* Masqué à l'œil, lu par les lecteurs d'écran. Sert à kh_tip_sr() : dans une
   carte qui EST un lien, le ⓘ est décoratif (un descendant focusable d'un <a>
   est du contenu interactif imbriqué, invalide), donc la définition doit
   entrer dans le nom accessible du lien par ce biais. */
.kh-sr-only {
	position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
	overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Stagger discret : jusqu'à 8 cartes visibles sans configuration, au-delà
   toutes partagent le dernier délai (pas d'attente qui s'allonge sans fin). */
.kpi-grid .kpi-card:nth-child(1) { animation-delay: .02s; }
.kpi-grid .kpi-card:nth-child(2) { animation-delay: .06s; }
.kpi-grid .kpi-card:nth-child(3) { animation-delay: .10s; }
.kpi-grid .kpi-card:nth-child(4) { animation-delay: .14s; }
.kpi-grid .kpi-card:nth-child(5) { animation-delay: .18s; }
.kpi-grid .kpi-card:nth-child(6) { animation-delay: .22s; }
.kpi-grid .kpi-card:nth-child(7) { animation-delay: .26s; }
.kpi-grid .kpi-card:nth-child(n+8) { animation-delay: .30s; }

/* Variante "icône" du kpi-card (Gestion trésorerie, 2026-09-08 — retour
   user, capture SuperHote à l'appui) : pastille carrée teintée + libellé en
   petites majuscules au-dessus de la valeur, sous-titre en dessous. Même
   famille visuelle que .kpi-card (fond/bordure/ombre/hover-lift hérités),
   jamais de hex en dur — --kpi-icon-color fixé inline par le PHP via
   var(--success)/var(--danger)/var(--accent) existants. */
.kpi-card--icon { --kpi-icon-color: var(--accent); }
.kpi-icon-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.kpi-icon-box {
	width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center;
	font-size: 15px; flex-shrink: 0;
	background: color-mix(in srgb, var(--kpi-icon-color) 16%, transparent);
}
.kpi-card--icon .kpi-label { margin-top: 0; letter-spacing: .04em; text-transform: uppercase; font-size: 11px; }
.kpi-card--icon .kpi-value { font-size: 26px; }
.kpi-sub { font-size: 12px; color: var(--text-muted); margin-top: 6px; }

/* "Ce qui a besoin de vous" (home.php, 2026-09-07 — retour user "revois le
   design du bloc") — remplace 2 <a class="card"> ad hoc qui réutilisaient
   `.hint` (pensée pour un callout encadré, cf. feedback_hint_class_inline_
   overlap) pour le libellé ET le lien d'action : les 2 se rendaient en
   pastilles bordées disjointes, jamais l'intention. Même famille visuelle
   que .kpi-card (fond/bordure/ombre/hover-lift), mais élargie pour porter
   un vrai bouton d'action à droite plutôt qu'un simple libellé. */
.besoin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; margin-bottom: 20px; }
.besoin-card {
	display: flex; align-items: center; justify-content: space-between; gap: 16px;
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
	padding: 18px 20px; box-shadow: var(--shadow-sm); text-decoration: none; color: inherit;
	transition: transform .15s var(--ease), box-shadow .15s var(--ease);
	animation: card-rise .4s var(--ease) backwards;
}
.besoin-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.besoin-card.is-alert.warning { border-left: 3px solid var(--warning); }
.besoin-card.is-alert.danger { border-left: 3px solid var(--danger); }
.besoin-label { font-size: 13px; font-weight: 600; color: var(--text-muted); margin-bottom: 4px; }
.besoin-value { font-family: 'Sora', sans-serif; font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--text); }
.besoin-value.warning { color: var(--warning); }
.besoin-value.danger { color: var(--danger); }
.besoin-cta {
	flex-shrink: 0; display: inline-flex; align-items: center; gap: 4px;
	padding: 7px 14px; border-radius: 999px; background: var(--bg-sunken); color: var(--accent);
	font-size: 12.5px; font-weight: 700; transition: background .15s var(--ease), color .15s var(--ease);
}
.besoin-card:hover .besoin-cta { background: var(--accent); color: var(--accent-contrast); }

/* Donut CSS pur (conic-gradient) — répartition du CA par canal, zéro JS/SVG. */
.donut-chart { width: 140px; height: 140px; border-radius: 50%; flex-shrink: 0; box-shadow: var(--shadow-sm); }
.donut-legend { list-style: none; margin: 0; padding: 0; }
.donut-legend li { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-size: 13px; }
.donut-legend .swatch { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }

.sparkline-cell { line-height: 0; }

/* 2026-08-19 (retour user, "l'affichage ne fonctionne pas sur le web") :
   tooltip survol/clic des graphiques Analytics (analytics_charts.js) — même
   parité que le tooltip mobile livré le même jour. */
.kh-chart svg circle[data-idx] { cursor: pointer; transition: r .1s var(--ease); }
.kh-chart-tooltip {
	margin-top: 8px;
	padding: 8px 12px;
	background: var(--bg-sunken);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	font-size: 12px;
	max-width: max-content;
}
.kh-chart-tooltip-title { font-weight: 700; margin-bottom: 4px; }
.kh-chart-tooltip-row { display: flex; align-items: center; gap: 6px; padding: 1px 0; }
.kh-chart-tooltip-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }

/* Badge de canal (Airbnb/Booking/Vrbo/Direct...) — cf. channel_helper.php. Le style inline porte couleur/taille (par appel), cette classe ne fait qu'écarter le texte qui suit. */
.channel-badge { margin-right: 5px; }

/* -------------------------------------------------------------------- */
/* Messages — layout 3 colonnes (liste/fil/panneau résa), mirroir de la  */
/* vraie page SuperHote (capture partagée par le user, /messages/{id}). */
/* -------------------------------------------------------------------- */
.messages-layout {
	display: flex; height: calc(100vh - 170px); min-height: 480px;
	border: 1px solid var(--border); border-radius: var(--radius-md);
	overflow: hidden; background: var(--surface); box-shadow: var(--shadow-sm);
}
.messages-list-col { width: 320px; flex-shrink: 0; border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; }
.messages-list-header { padding: 14px 16px 10px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
.messages-list-header-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.messages-list-header-top select { font-size: 12px; border: 1px solid var(--border); border-radius: 6px; padding: 3px 6px; background: var(--surface); color: var(--text); }
.messages-search-input { width: 100%; padding: 7px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-sunken); font-size: 13px; }
.messages-list-tabs { display: flex; gap: 6px; margin-top: 10px; }
.messages-list-items { flex: 1; overflow-y: auto; }
.messages-list-item { display: flex; gap: 10px; padding: 12px 16px; border-bottom: 1px solid var(--border); text-decoration: none; color: inherit; position: relative; }
.messages-list-item:hover { background: var(--bg-sunken); }
.messages-list-item.is-active { background: var(--bg-sunken); box-shadow: inset 3px 0 0 var(--accent); }
.messages-list-avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--bg-sunken); display: flex; align-items: center; justify-content: center; color: var(--text-muted); flex-shrink: 0; }
.messages-list-main { flex: 1; min-width: 0; }
.messages-list-top-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.messages-list-name { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messages-list-item.is-unread .messages-list-name { font-weight: 700; }
.messages-list-time { font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
.messages-list-dates { font-size: 12px; color: var(--text-muted); }
.messages-list-bottom-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 2px; }
.messages-list-property { font-size: 12px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messages-count-pill { font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
/* Dans le flux, devant le nom (cf. note dans messages_index.php) : en absolu
   sur le coin de la ligne, cette pastille recouvrait l'avatar et le nom. */
.messages-unread-pill { flex: none; align-self: center; background: var(--text); color: var(--surface); font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; padding: 2px 6px; border-radius: 999px; }
.messages-list-top-row .messages-list-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.messages-thread-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.messages-thread-header { padding: 12px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.messages-thread-body { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 4px; }
.messages-bubble-row { display: flex; flex-direction: column; margin-bottom: 10px; }
.messages-bubble-row.is-host { align-items: flex-end; }
.messages-bubble-row.is-guest { align-items: flex-start; }
.messages-bubble { max-width: 65%; padding: 10px 14px; border-radius: 14px; white-space: pre-wrap; word-break: break-word; }
.messages-bubble-row.is-guest .messages-bubble { background: var(--bg-sunken); }
.messages-bubble-row.is-host .messages-bubble { background: var(--accent); color: var(--accent-contrast); }
.messages-bubble-meta { font-size: 11px; color: var(--text-muted); margin-top: 3px; padding: 0 4px; }
.messages-compose { border-top: 1px solid var(--border); padding: 12px 20px; flex-shrink: 0; }
.messages-channel-row { display: flex; align-items: center; gap: 16px; margin-bottom: 10px; flex-wrap: wrap; }
.messages-channel-option { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; cursor: pointer; color: var(--text-muted); }
/* Le rond natif du <input type="radio"> est caché (bug trouvé par le user :
   2 ronds affichés côte à côte, le natif ET .messages-channel-dot) — le
   <label> qui l'entoure continue de router le clic vers l'input même
   invisible (comportement standard du DOM, pas de JS nécessaire). Le halo
   sur :checked remplace l'indicateur natif perdu. */
.messages-channel-option input[type="radio"] { position: absolute; opacity: 0; width: 1px; height: 1px; margin: -1px; }
.messages-channel-dot { width: 12px; height: 12px; border-radius: 50%; display: inline-block; }
.messages-channel-option input[type="radio"]:checked + .messages-channel-dot { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--text); }
.messages-followup { margin-left: auto; }
.messages-followup button { border: none; background: none; cursor: pointer; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .02em; color: var(--text-muted); display: flex; align-items: center; gap: 5px; }
.messages-followup button.is-active { color: var(--danger); }
.messages-compose textarea { width: 100%; resize: vertical; min-height: 44px; border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; font: inherit; background: var(--surface); color: var(--text); }
.messages-options-select { display: none; margin-bottom: 8px; }
.messages-options-select.open { display: block; }

/* Panneau "Modèles" (notifications + réponses rapides, parité SuperHote,
   capture réelle du panneau "Modèles de Message") — remplace l'ancien
   <select> plat par un vrai panneau à onglets avec Insérer/Modifier/
   Supprimer par réponse rapide. */
.messages-templates-panel { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); padding: 12px; max-height: 320px; overflow-y: auto; }
.messages-templates-tabs { display: flex; gap: 16px; margin-bottom: 10px; }
.messages-templates-tabs label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; cursor: pointer; color: var(--text); }
.messages-template-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--border); font-size: 13px; }
.messages-template-row:last-of-type { border-bottom: none; }
.messages-template-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.messages-template-actions .btn-ghost { margin-right: 0; padding: 2px 6px; }
.messages-template-edit { padding: 8px 0; border-bottom: 1px solid var(--border); }
.messages-template-edit .field { margin-top: 8px; }
.messages-template-edit textarea { min-height: 60px; }
.messages-panel-col { width: 260px; flex-shrink: 0; border-left: 1px solid var(--border); padding: 20px; overflow-y: auto; }
.messages-panel-col h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--text-muted); margin-bottom: 16px; }
.messages-panel-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 10px; margin-bottom: 16px; }
.messages-panel-label { font-size: 10.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--text-muted); margin-bottom: 2px; }
.messages-panel-value { font-size: 13px; font-weight: 600; }
.messages-empty-center { flex: 1; display: flex; align-items: center; justify-content: center; color: var(--text-muted); font-size: 14px; text-align: center; padding: 20px; }

/* "Historique des envois" + Renvoyer (demande user, "parti ou non") —
   <details> natif plutôt qu'un JS de toggle (pas de dépendance en plus). */
.messages-log { border-top: 1px solid var(--border); flex-shrink: 0; max-height: 180px; overflow-y: auto; }
.messages-log summary { padding: 8px 20px; font-size: 12.5px; font-weight: 600; color: var(--text-muted); cursor: pointer; user-select: none; }
.messages-log-list { padding: 0 20px 10px; }
.messages-log-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 12.5px; }
.messages-log-row:last-child { border-bottom: none; }

/* -------------------------------------------------------------------- */
/* Mouvement                                                             */
/* -------------------------------------------------------------------- */

@keyframes card-rise {
	from { opacity: 0; transform: translateY(6px); }
	to { opacity: 1; transform: translateY(0); }
}

/* -------------------------------------------------------------------- */
/* Logements — liste en grille (Property::list_page(), parité SuperHote) */
/* -------------------------------------------------------------------- */

.property-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
.property-card { display: block; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); overflow: hidden; text-decoration: none; color: var(--text); transition: box-shadow .15s ease, transform .15s ease; }
.property-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.10); transform: translateY(-2px); }
.property-card-photo { position: relative; aspect-ratio: 4 / 3; background: var(--border); }
.property-card-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.property-card-photo-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 34px; color: var(--text-muted); }
.property-card-badge { position: absolute; top: 10px; left: 10px; }
.property-card-edit { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.45); color: #fff; font-weight: 600; font-size: 13px; letter-spacing: .02em; text-transform: uppercase; opacity: 0; transition: opacity .15s ease; }
.property-card:hover .property-card-edit { opacity: 1; }
.property-card-body { padding: 12px 14px; }
.property-card-name { font-weight: 600; font-size: 14px; margin-bottom: 2px; }

/* -------------------------------------------------------------------- */
/* Website — page publique de réservation directe (Website.php)         */
/* -------------------------------------------------------------------- */

.public-shell { min-height: 100vh; background: var(--bg-sunken); padding: 32px 16px 64px; }
.public-page { width: 100%; max-width: 720px; margin: 0 auto; }
.public-brand { text-align: center; font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 20px; }

/* En-tête logo (personnalisation visuelle, 2026-08-28) — remplace .public-brand quand le site a un logo réglé, même bloc central sur les 5 pages publiques. */
.public-header { text-align: center; margin-bottom: 20px; }

/* Sélecteur de langue (multilingue FR/EN, 2026-08-28) — position fixe (coin
   supérieur droit) pour rester cohérent quel que soit le layout de la page
   (hero plein écran, 2 colonnes, page étroite...) sans dépendre d'un
   conteneur parent positionné différemment selon la vue. */
.public-lang-switcher { position: fixed; top: 14px; right: 14px; z-index: 20; display: flex; gap: 6px; font-size: 12px; font-weight: 700; background: var(--surface); padding: 4px 6px; border-radius: 999px; box-shadow: var(--shadow-sm); }
.public-lang-switcher a { color: var(--text-muted); text-decoration: none; padding: 2px 6px; border-radius: 999px; }
.public-lang-switcher a.is-active { color: var(--accent-contrast); background: var(--accent); }
.public-lang-switcher a:hover:not(.is-active) { color: var(--accent); }
.public-header-logo { max-width: 100%; object-fit: contain; }
.public-header-logo.logo-small { height: 28px; }
.public-header-logo.logo-medium { height: 40px; }
.public-header-logo.logo-large { height: 56px; }

/* Pied de page (contact/réseaux sociaux, cahier §6.3) — ne s'affiche que si le site a renseigné au moins un champ (cf. website/_footer.php). */
.public-footer { margin-top: 48px; padding: 24px 16px; border-top: 1px solid var(--border); }
.public-footer-inner { max-width: 1040px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 10px; text-align: center; }
.public-footer-logo { max-height: 32px; object-fit: contain; }
.public-footer-contact { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; font-size: 13.5px; color: var(--text-muted); }
.public-footer-contact a { color: inherit; }
.public-footer-socials { display: flex; gap: 12px; }
.public-footer-socials a { color: var(--text-muted); display: flex; transition: color .15s; }
.public-footer-socials a:hover { color: var(--accent); }
.public-footer-credit { font-size: 12px; color: var(--text-muted); margin: 0; }
.public-hero-photo { width: 100%; aspect-ratio: 16/9; object-fit: cover; border-radius: var(--radius-lg); margin-bottom: 20px; display: block; }
.public-title { font-size: 22px; font-weight: 800; margin-bottom: 4px; }
.public-meta { color: var(--text-muted); font-size: 13.5px; margin-bottom: 16px; }
.public-description { white-space: pre-line; margin-bottom: 20px; line-height: 1.6; }
.availability-month { margin-bottom: 16px; }
.availability-month-label { font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; color: var(--text-muted); margin-bottom: 8px; }
.availability-days { display: flex; flex-wrap: wrap; gap: 4px; }
.availability-day { width: 30px; height: 30px; border-radius: 7px; display: flex; align-items: center; justify-content: center; font-size: 11.5px; font-variant-numeric: tabular-nums; }
.availability-day.is-available { background: var(--success-bg); color: var(--success-fg); }
.availability-day.is-unavailable { background: var(--border); color: var(--text-muted); text-decoration: line-through; }
/* 🐛 2026-11-12 (balayage UI, fiche logement publique) — les pastilles de la
   LÉGENDE portaient leur taille en style inline et héritaient du fond
   thématique. Sur le site public, dont le fond est sombre, `--success-bg` se
   confond avec celui de la carte : le carré « Disponible » était tout
   simplement invisible, et le visiteur lisait une légende à une seule entrée.
   Une bordure les rend visibles quel que soit le thème, sans toucher aux
   cases du calendrier lui-même. */
.availability-legend-swatch {
	display: inline-flex; width: 16px; height: 16px; font-size: 0;
	vertical-align: middle; box-sizing: border-box;
	border: 1px solid var(--border);
}
.quote-breakdown { width: 100%; border-collapse: collapse; margin-bottom: 16px; }
.quote-breakdown td { padding: 6px 0; font-size: 14px; }
.quote-breakdown td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.quote-breakdown tr.quote-total td { font-weight: 700; font-size: 16px; border-top: 1px solid var(--border); padding-top: 10px; }
.quote-breakdown tr.quote-discount td { color: var(--success-fg); }

/* Codes promo (cahier §18, "J'ai un code promo") — 2026-08-28. */
.promo-code-details { margin-top: 16px; }
.promo-code-details summary { cursor: pointer; font-size: 13.5px; font-weight: 600; color: var(--accent); list-style: none; }
.promo-code-details summary::-webkit-details-marker { display: none; }
.promo-code-details summary::before { content: "▸ "; }
.promo-code-details[open] summary::before { content: "▾ "; }
.promo-code-details input[type="text"] { padding: 8px 12px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--surface); color: var(--text); }

/* DIRECT-XXX (galerie/équipements/sidebar, 2026-08-14) : mise en page à 2
   colonnes façon Hostex/SuperHote bookingsite, SCOPÉE à website/view.php
   (nouvelles classes, jamais de modification de .public-page — reste
   inchangée pour quote/confirmation/legal, toujours 1 colonne étroite). */
.property-layout { width: 100%; max-width: 1040px; margin: 0 auto; display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap; }
.property-main { flex: 1 1 520px; min-width: 0; }
.property-sidebar { flex: 0 0 320px; position: sticky; top: 24px; }
@media (max-width: 760px) { .property-sidebar { position: static; flex-basis: 100%; } }
/* 2026-11-12 — « Découvrir nos autres logements » est désormais un troisième
   enfant de `.property-layout`, après la barre latérale (cf. website/view.php).
   Base 100 % : il passe donc à la ligne sous les deux colonnes sur ordinateur,
   et arrive APRÈS le formulaire de réservation sur téléphone. */
.property-siblings { flex: 1 1 100%; min-width: 0; }
.amenity-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px 16px; margin: 4px 0 20px; }
.amenity-item { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.amenity-item::before { content: "✓"; color: var(--success-fg); font-weight: 700; }
.amenity-list.is-truncated .amenity-item:nth-child(n+9) { display: none; }

/* Slider/carousel (fiche logement 2026-08-14, "n'oublie pas les sliders")
   — vanilla JS, aucune dépendance externe (cohérent avec le reste du
   projet, jamais de librairie front ajoutée pour un besoin ponctuel). */
.slider { position: relative; border-radius: var(--radius-lg); overflow: hidden; background: var(--surface-2); margin-bottom: 20px; }
.slider-track { display: flex; aspect-ratio: 16/10; transition: transform .35s ease; }
.slider-slide { flex: 0 0 100%; width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; display: block; }
.slider-btn { position: absolute; top: 50%; transform: translateY(-50%); width: 36px; height: 36px; border-radius: 50%; border: none; background: rgba(0,0,0,.45); color: #fff; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background .15s; }
.slider-btn:hover { background: rgba(0,0,0,.7); }
.slider-btn.prev { left: 12px; }
.slider-btn.next { right: 12px; }
.slider-dots { position: absolute; bottom: 12px; left: 0; right: 0; display: flex; justify-content: center; gap: 6px; }
.slider-dot { width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.55); border: none; padding: 0; cursor: pointer; }
.slider-dot.active { background: #fff; width: 18px; border-radius: 4px; transition: width .2s; }
.slider-counter { position: absolute; top: 12px; right: 12px; background: rgba(0,0,0,.5); color: #fff; font-size: 12px; padding: 3px 9px; border-radius: 999px; font-variant-numeric: tabular-nums; }

/* Lightbox plein écran (clic sur une photo de la galerie). */
.lightbox { position: fixed; inset: 0; background: rgba(0,0,0,.92); z-index: 999; display: none; align-items: center; justify-content: center; padding: 24px; }
.lightbox.is-open { display: flex; }
.lightbox .slider { max-width: 1100px; width: 100%; margin-bottom: 0; background: transparent; }
.lightbox .slider-track { aspect-ratio: auto; height: 80vh; }
.lightbox .slider-slide { object-fit: contain; cursor: default; }
.lightbox-close { position: absolute; top: 18px; right: 22px; background: none; border: none; color: #fff; font-size: 28px; line-height: 1; cursor: pointer; z-index: 1000; }

/* Emplacement (carte statique OpenStreetMap, aucune clé API — img-src https: déjà autorisé par le CSP par défaut). */
.property-map-img { width: 100%; border-radius: var(--radius); display: block; }

/* Règles de la maison (dérivées des champs déjà existants — checkin/checkout/min nights — aucune nouvelle donnée). */
.house-rules-list { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.house-rules-item { display: flex; align-items: flex-start; gap: 10px; font-size: 14px; }
.house-rules-icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }

/* Description tronquée + "Afficher plus" (fiche logement). */
.public-description.is-truncated { max-height: 4.8em; overflow: hidden; position: relative; }
.public-description.is-truncated::after { content: ""; position: absolute; bottom: 0; left: 0; right: 0; height: 2.4em; background: linear-gradient(to bottom, transparent, var(--surface)); }
.text-toggle-btn { background: none; border: none; color: var(--accent); font-weight: 600; font-size: 13.5px; cursor: pointer; padding: 6px 0; }

/* Page d'accueil multi-propriétés (2026-08-14) — hero slider + recherche + cartes enrichies. */
.home-hero { position: relative; width: 100%; height: 340px; overflow: hidden; }
.home-hero .slider, .home-hero .slider-track { height: 100%; margin-bottom: 0; border-radius: 0; }
/* 2026-11-08 (recette) — le hero ne couvrait pas toute la largeur : 1123 px
   mesures sur une fenetre de 1280. `.slider-track` herite `aspect-ratio:16/10`
   du slider de galerie ; avec `height:100%` et une largeur `auto`, le
   navigateur deduit la LARGEUR DE LA HAUTEUR (702 x 1.6 = 1123) au lieu de
   remplir la bande. Visible des qu'une banniere est posee, sur les 6 gabarits.
   Le ratio reste utile a la galerie d'un logement, d'ou l'annulation ciblee
   ici seulement : la banniere est une bande pleine largeur, c'est
   `object-fit:cover` sur l'image qui gere le cadrage. */
.home-hero .slider-track { width: 100%; aspect-ratio: auto; }
.home-hero .slider-slide { height: 100%; }
.home-hero-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Masque du hero (cahier §6.5) — .mask-gradient-black reprend le comportement HISTORIQUE (défaut), les autres combinaisons sont nouvelles. */
.home-hero-overlay { position: absolute; inset: 0; display: flex; align-items: flex-end; pointer-events: none; }
.home-hero-overlay.mask-gradient-black { background: linear-gradient(to top, rgba(0,0,0,.6), rgba(0,0,0,.15) 55%); }
.home-hero-overlay.mask-gradient-gray { background: linear-gradient(to top, rgba(107,114,128,.7), rgba(107,114,128,.2) 55%); }
.home-hero-overlay.mask-solid-black { background: rgba(0,0,0,.45); }
.home-hero-overlay.mask-solid-gray { background: rgba(107,114,128,.45); }
.home-hero-overlay.mask-none { background: none; }
.home-hero-content { max-width: 1040px; margin: 0 auto; padding: 28px 16px; width: 100%; color: #fff; }
.home-hero-content h1 { color: #fff; margin-bottom: 6px; font-size: 30px; text-wrap: balance; }
.home-hero-content p { color: #f0f0f0; font-size: 16px; }
.home-hero-content.text-black, .home-hero-content.text-black h1 { color: #14181f; }
.home-hero-content.text-black p { color: #3a3f4a; }
.home-search { max-width: 1040px; margin: -28px auto 32px; padding: 0 16px; position: relative; z-index: 5; }
.home-search-bar { background: var(--surface); border-radius: var(--radius-lg); box-shadow: 0 10px 30px rgba(0,0,0,.18); padding: 14px 18px; display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; }
.home-search-field { flex: 1; min-width: 140px; }
.home-search-hint { margin: 10px 0 0; font-size: 12.5px; line-height: 1.4; color: var(--text-muted); }
.home-search-field label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: .03em; color: var(--text-muted); margin-bottom: 3px; }
.home-search-field select, .home-search-field input { border: none; background: transparent; font-size: 14px; width: 100%; padding: 4px 0; color: var(--text); }
.home-search-field select:focus, .home-search-field input:focus { outline: none; }
.home-city-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 20px; }
.home-city-tab { border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 999px; padding: 7px 16px; font-size: 13.5px; cursor: pointer; transition: background .15s, color .15s, border-color .15s; }
.home-city-tab.active, .home-city-tab:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
/* 2026-09-18 (portage des gabarits en PHP) — la liste et les cartes de la
   page d'accueil publique étaient stylées en attributs `style=""` dans
   home.php : une feuille de gabarit (site_templates.css) ne pouvait alors
   RIEN redéfinir, un style en ligne l'emportant sur toute règle de fichier.
   Valeurs reprises à l'identique ici : le site classique (aucun gabarit
   choisi) rend exactement comme avant. Seule exception assumée, le carré de
   secours sans photo utilisait `var(--surface-2)`, une variable qui n'existe
   nulle part — donc un fond transparent ; il prend le fond creusé du thème. */
.home-property-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 20px; margin-bottom: 40px; }
.home-property-card { transition: transform .18s ease, box-shadow .18s ease; text-decoration: none; color: inherit; display: flex; flex-direction: column; overflow: hidden; padding: 0; }
.home-property-card:hover { transform: translateY(-4px); box-shadow: 0 14px 28px rgba(0,0,0,.12); }
.home-property-media { position: relative; }
.home-property-photo { width: 100%; height: 170px; object-fit: cover; display: block; }
.home-property-photo.is-empty { background: var(--bg-sunken); display: flex; align-items: center; justify-content: center; font-size: 32px; }
.home-property-body { padding: 14px; }
.home-property-name { display: block; margin-bottom: 4px; }
.home-property-badge { display: none; } /* visible seulement sur les gabarits qui en ont un (villa-04) */
.home-property-cta { display: none; } /* idem — la carte entière est déjà cliquable */
.home-property-price { font-weight: 700; margin-top: 6px; }
.home-section-header { text-align: center; margin-bottom: 20px; }
.home-section-header h2 { font-size: 22px; text-wrap: balance; }
.home-section-overline { display: none; } /* idem : sur-titre propre aux gabarits éditoriaux */
.home-about { max-width: 1040px; margin: 40px auto; padding: 0 16px; }
.home-about-grid { display: grid; grid-template-columns: 1.1fr .9fr; gap: 28px; align-items: center; }
@media (max-width: 720px) { .home-about-grid { grid-template-columns: 1fr; } }
.home-about-photos { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.home-about-photos img { width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: var(--radius); }

/* -------------------------------------------------------------------- */
/* Checklists (onglet CHECKLISTS de la fiche logement, parité SuperHote)  */
/* -------------------------------------------------------------------- */

.checklist-card { position: relative; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); padding: 14px; }
.checklist-card-link { display: block; text-decoration: none; color: var(--text); padding-right: 20px; }
.checklist-card-icon { font-size: 22px; margin-bottom: 8px; }
.checklist-card-name { font-weight: 600; font-size: 13.5px; margin-bottom: 2px; }
.checklist-card-menu { position: absolute; top: 10px; right: 10px; }
.checklist-card-menu summary { list-style: none; cursor: pointer; color: var(--text-muted); font-weight: 700; padding: 2px 6px; border-radius: 4px; }
.checklist-card-menu summary::-webkit-details-marker { display: none; }
.checklist-card-menu summary:hover { background: var(--bg-sunken); }
.checklist-card-menu[open] summary { background: var(--bg-sunken); }
.checklist-card-menu-panel { position: absolute; top: 24px; right: 0; z-index: 5; width: 220px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: 0 8px 24px rgba(0,0,0,.14); padding: 10px; }
.checklist-card-new { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border-style: dashed; cursor: pointer; }
.checklist-card-new summary { list-style: none; display: flex; flex-direction: column; align-items: center; }
.checklist-card-new summary::-webkit-details-marker { display: none; }
.checklist-card-new[open] { align-items: stretch; text-align: left; cursor: default; }

/* -------------------------------------------------------------------- */
/* "Ménages et tâches" — agenda (tasks_agenda.php, parité SuperHote,      */
/* capture d'écran réelle 2026-08-18, vue par défaut de tasks/index).     */
/* -------------------------------------------------------------------- */

/* Chips de comptage cliquables (Ménages/Checkins/Checkouts/Incidents/
   Autres/Non assignées) — même famille visuelle que .cal-property-filter-quick
   (pilule 999px) mais avec un compteur intégré et un état actif marqué. */
.agenda-bucket-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.agenda-bucket-chip {
	display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px;
	border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
	color: var(--text); font-size: 12.5px; font-weight: 600; text-decoration: none;
	transition: border-color .15s, background .15s, color .15s;
}
.agenda-bucket-chip:hover { border-color: var(--accent); color: var(--accent); }
.agenda-bucket-chip.is-active { background: var(--accent); border-color: var(--accent); color: var(--accent-contrast); }
.agenda-bucket-chip .count { font-variant-numeric: tabular-nums; opacity: .75; }
.agenda-bucket-chip.is-active .count { opacity: 1; }
/* "Non assignées" reste teinté danger même hors sélection — signal d'alerte volontaire (parité capture SuperHote, chip rouge dès le repos). */
.agenda-bucket-chip.bucket-unassigned:not(.is-active) { border-color: var(--danger); color: var(--danger); }
.agenda-bucket-chip.bucket-unassigned.is-active { background: var(--danger); border-color: var(--danger); color: var(--danger-contrast); }

/* Plage de dates glissante + navigation (from/span, cf. Tasks::_index_agenda()). */
.agenda-range-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.agenda-range-bar .hint { font-weight: 600; color: var(--text); }

/* Chips de raccourci vers les jours qui ont des tâches (ancres #day-YYYY-MM-DD). */
.agenda-day-jumps { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 20px; }
.agenda-day-jump {
	display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; font-size: 11.5px;
	border: 1px solid var(--border); border-radius: 999px; background: var(--bg-sunken); color: var(--text-muted);
	text-decoration: none; font-weight: 600;
}
.agenda-day-jump:hover { border-color: var(--accent); color: var(--accent); }
.agenda-day-jump .count { background: var(--surface); border-radius: 999px; padding: 0 6px; font-variant-numeric: tabular-nums; }

/* Une section = un jour ayant au moins une tâche (les jours vides ne sont jamais affichés, cf. Tasks::_index_agenda()). */
.agenda-day-section { margin-bottom: 26px; scroll-margin-top: 16px; }
.agenda-day-title { font-size: 15px; font-weight: 700; margin-bottom: 2px; }
.agenda-day-summary { font-size: 12.5px; color: var(--text-muted); margin-bottom: 10px; }

/* 2026-08-27 : chaque jour a sa propre <table> (cf. agenda_task_table.php),
   donc en table-layout auto (par défaut) chaque tableau calcule ses largeurs
   de colonnes indépendamment selon son propre contenu — les colonnes
   décalent d'un jour à l'autre. Largeurs fixes en % pour que toutes les
   tables de la page s'alignent verticalement. */
.agenda-table { table-layout: fixed; }
/* 🐛 2026-11-07 (revue visuelle) — « Statut » était à 10 %, soit 94 px à
   1280 px de fenêtre et moins encore en dessous. La pastille d'état y
   demande 88 px pour écrire « Annulée », plus ~28 px de marges de cellule :
   elle affichait « Annu⌄ ». Un pourcentage ne peut rien garantir — il suit
   la fenêtre, et c'est précisément ce qu'on ne veut pas d'une colonne dont
   le contenu a une largeur minimale connue. Largeur en PIXELS pour celle-ci
   (en `table-layout: fixed`, px et % cohabitent : les px sont servis
   d'abord, les % se partagent le reste), les autres recalées pour sommer
   à 86 %. Les trois points gagnés viennent de « Description », qui tronque
   déjà son texte à 60 caractères côté PHP. */
.agenda-table th:nth-child(1), .agenda-table td:nth-child(1) { width: 16%; }
.agenda-table th:nth-child(2), .agenda-table td:nth-child(2) { width: 17%; }
.agenda-table th:nth-child(3), .agenda-table td:nth-child(3) { width: 16%; }
.agenda-table th:nth-child(4), .agenda-table td:nth-child(4) { width: 7%; }
.agenda-table th:nth-child(5), .agenda-table td:nth-child(5) { width: 13%; }
.agenda-table th:nth-child(6), .agenda-table td:nth-child(6) { width: 17%; }
.agenda-table th:nth-child(7), .agenda-table td:nth-child(7) { width: 130px; }
.agenda-table td { vertical-align: middle; }
.agenda-property-cell { display: flex; align-items: center; gap: 8px; }
.agenda-property-photo { width: 34px; height: 34px; border-radius: var(--radius-sm); object-fit: cover; flex-shrink: 0; background: var(--bg-sunken); }
.agenda-property-photo-placeholder { width: 34px; height: 34px; border-radius: var(--radius-sm); background: var(--bg-sunken); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.agenda-task-name { display: flex; align-items: center; gap: 6px; font-weight: 600; }
.agenda-task-name .icon { font-size: 14px; }

/* Avatar rond (initiales, couleur déterministe par nom — cf. Tasks::_avatar_color() côté vue). */
.agenda-avatar { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; font-size: 10px; font-weight: 700; color: #fff; flex-shrink: 0; }

/* Selects "pilule" pour l'assignation/le statut modifiables en ligne — se
   soumettent seuls au onchange (cf. tasks_agenda.php), stylés pour ne pas
   ressembler à un <select> natif (apparence supprimée). */
/* 🐛 2026-11-07 (revue visuelle, colonne « Statut » de Tâches) — ce
   composant est un <select> NATIF (appearance supprimée), donc il héritait
   de la règle de base `select { width: 100%; }` et remplissait sa cellule.
   Dans un tableau à huit colonnes à 1280 px, la colonne « Statut » reçoit
   ~60 px : la pastille affichait « Annu⌄ » au lieu d'« Annulée ». Le mot
   n'était pas coupé par un `text-overflow` mais par le rendu natif du
   select, donc sans même l'ellipse qui aurait signalé la troncature.
   Une pastille se dimensionne sur son contenu, par définition : `width:
   auto` rend au <select> sa largeur intrinsèque (la plus longue option) et
   la colonne du tableau s'ajuste. `max-width: 100%` garde la borne quand la
   liste des membres contient un nom très long ; le tableau vit dans
   `.table-wrap`, qui défile, donc la page ne déborde pas. */
.pill-select {
	appearance: none; -webkit-appearance: none; border-radius: 999px; border: 1px solid var(--border);
	padding: 4px 24px 4px 10px; font-size: 12px; font-weight: 600; background-color: var(--border); color: var(--text-muted);
	cursor: pointer; font-family: inherit; width: auto; max-width: 100%;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%2364705F' stroke-width='1.5' fill='none' fill-rule='evenodd'/%3E%3C/svg%3E");
	background-repeat: no-repeat; background-position: right 8px center;
}
/* Mêmes couleurs de texte lisibles que .badge-* ci-dessus (2026-09-17). */
.pill-select.status-todo { background-color: var(--border); color: var(--neutral-fg); }
.pill-select.status-in_progress { background-color: var(--warning-bg); color: var(--warning-fg); }
.pill-select.status-done { background-color: var(--success-bg); color: var(--success-fg); }
.pill-select.status-cancelled { background-color: var(--border); color: var(--neutral-fg); }
.pill-select.assignee-set { background-color: var(--bg-sunken); color: var(--text); }
.pill-select.assignee-unassigned { background-color: var(--danger-bg); color: var(--danger-fg); }
.checklist-card-new[open] summary { flex-direction: row; gap: 6px; }

/* 2026-08-28 (retour user, capture SuperHote "Catégories de revenus/
   dépenses" à l'appui) : pilule catégorie + petite croix de suppression,
   cf. finance_categories.php. Composant générique (jamais utilisé
   ailleurs pour l'instant, mais pas spécifique à Finance dans son nom). */
.chip-list { display: flex; flex-wrap: wrap; gap: 10px; }
.chip {
	display: inline-flex; align-items: center; gap: 6px; background: var(--bg-sunken);
	border-radius: 999px; padding: 8px 10px 8px 16px; font-size: 13px; font-weight: 600; color: var(--text);
}
.chip-delete-form { display: inline-flex; }
.chip-delete {
	background: none; border: none; cursor: pointer; color: var(--text-muted); font-size: 16px;
	line-height: 1; padding: 2px 4px; border-radius: 50%; font-family: inherit;
}
.chip-delete:hover { color: var(--danger); background: var(--danger-bg); }
.chip-add-toggle {
	background: none; border: none; cursor: pointer; color: var(--accent); font-size: 13px; font-weight: 700;
	padding: 12px 0 0; font-family: inherit;
}
.chip-add-toggle:hover { text-decoration: underline; }

/* Parité SuperHote ("Créer une réservation manuellement") — dropdown
   d'autocomplétion voyageur (manual_booking_form.php). */
.autocomplete-results {
	position: absolute; top: 100%; left: 0; right: 0; z-index: 20;
	background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
	margin-top: 2px; max-height: 220px; overflow-y: auto; list-style: none; padding: 4px 0;
	box-shadow: 0 4px 12px rgba(0,0,0,.12);
}
.autocomplete-results li { padding: 8px 12px; cursor: pointer; font-size: 13px; }
.autocomplete-results li:hover { background: var(--bg-sunken); }
.autocomplete-results li .hint { display: block; font-size: 11px; }

/* kh_select.js (2026-08-29, retour user "tu n'as pas résolu le problème des
   select partout") — menu déroulant JS qui remplace visuellement un
   <select> natif (popup natif = jamais stylable en CSS, cf. docblock JS).
   Même famille visuelle que .autocomplete-results ci-dessus (coins
   arrondis + ombre + marge), jamais bord-à-bord comme un select natif. */
/* 🐛 2026-10-13 (retour user, capture à l'appui) — `inline-block` sans borne
   se dimensionne sur le TEXTE DE L'OPTION SÉLECTIONNÉE, jamais sur son
   conteneur. Mesuré sur « Canal par plateforme » : la ligne faisait 279 px,
   l'habillage 353 px — il débordait de 74 px et recouvrait la colonne
   voisine, donnant trois sélecteurs superposés sur la ligne Airbnb. La ligne
   « Direct » semblait saine par pur hasard : « Email » est court.
   `max-width` borne sans rien figer — un sélecteur qui tenait déjà garde sa
   largeur naturelle. Posé ici, donc pour les 152 emplois du composant.

   `min-width: 0` est la pièce qui manque à `max-width` : dans un conteneur
   flex, un élément refuse par défaut de rétrécir sous la taille de son
   contenu, et `max-width: 100%` se calcule sur le conteneur ENTIER sans
   déduire le libellé placé à sa gauche. Avec les deux, l'habillage occupe la
   place réellement disponible et tronque au-delà. */
.kh-select-wrap { position: relative; display: inline-block; max-width: 100%; min-width: 0; }
/* 2026-11-06 (audit UI web) — `!important` sur les dimensions, et c'est
   nécessaire : plusieurs vues écrivent `style="width:auto;"` EN LIGNE sur
   leur <select>, ce qui l'emportait sur ce `width: 1px`. Le select « masqué »
   gardait alors sa largeur réelle (162 px mesurés sur le tableau de bord) et,
   positionné en absolu près du bord droit, il élargissait la PAGE ENTIÈRE —
   un débordement horizontal visible jusqu'en 1280 px. Un utilitaire dont le
   seul rôle est de ne plus occuper de place doit gagner contre l'inline. */
.kh-select-native-hidden {
	position: absolute; opacity: 0; pointer-events: none;
	width: 1px !important; height: 1px !important;
	min-width: 0 !important; max-width: 1px !important;
	left: 0; top: 0;
}
/* 🐛 2026-11-07 (revue visuelle : « libellés désalignés ») — le déclencheur
   mesurait 28 px de haut en police 12 px, quand TOUT champ natif du produit
   mesure 44 px en police 14 px (règle `height: 44px` ci-dessus, posée le
   2026-08-15 précisément « pour éviter les décalages de taille entre les
   champs »). Ce composant, ajouté après, ne l'avait jamais reprise.

   Ce que ça donnait : sur une rangée `align-items: flex-end` qui mélange un
   sélecteur enrichi et un `input[type=date]` ou `[type=number]` — Relevé
   propriétaire (Propriétaire/Mois/Année/Affichage), Sites de réservation,
   Tâches (Statut/Assignée à/Du/Au) — les contrôles s'alignaient bien par le
   BAS, mais leurs étiquettes, posées au-dessus, se retrouvaient à 16 px
   d'écart vertical. Rien ne débordait, aucun contraste n'était en cause :
   seul le fait de regarder l'écran le montre.

   Et aussi deux polices de formulaire côte à côte, 12 px contre 14 px, dans
   la même rangée.

   `min-width` reste volontairement à 90 px : plusieurs grilles déclarent
   `--col-min: 90px/100px/110px` (cf. `.form-col`), et `min-width` l'emporte
   sur le `max-width: 100%` juste en dessous — l'élargir ferait déborder ces
   colonnes. La largeur utile se règle sur la rangée (`.kh-filtres`), pas sur
   le composant. */
.kh-select-trigger {
	font: inherit; font-size: 14px; text-align: left; cursor: pointer;
	background: var(--surface); color: var(--text); border: 1px solid var(--border);
	border-radius: var(--radius-sm); padding: 9px 32px 9px 12px; min-width: 90px;
	height: 44px; line-height: 24px; box-sizing: border-box;
	position: relative; white-space: nowrap;
	/* Borné puis tronqué proprement : sans ça, `nowrap` fait grandir le bouton
	   indéfiniment et c'est LUI qui pousse l'habillage hors du conteneur.
	   L'option entière reste lisible dans le menu déroulant. */
	max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.kh-select-trigger::after {
	content: ''; position: absolute; right: 13px; top: 50%; width: 6px; height: 6px;
	border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
	transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.kh-select-trigger:hover { border-color: var(--text-muted); }
.kh-select-trigger.is-open { border-color: var(--accent); }
.kh-select-trigger:disabled { opacity: .55; cursor: not-allowed; }
.kh-select-menu {
	position: absolute; top: calc(100% + 4px); left: 0; z-index: 30; min-width: 100%;
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
	box-shadow: var(--shadow-md); padding: 4px; max-height: 260px; overflow-y: auto;
}
.kh-select-option {
	/* 2026-11-07 — 14 px, comme le déclencheur et comme les <option> natives :
	   le menu rendait 13 px, donc le libellé rapetissait d'un pixel au moment
	   même où on le choisissait. */
	padding: 8px 10px; font-size: 14px; border-radius: 6px; cursor: pointer; white-space: nowrap;
}
/* 2026-09-08 (retour user, capture SuperHote) : pastille de couleur devant
   le libellé d'une <option data-color="#hex"> — même pastille que les
   lignes de tableau/légendes de donut (kh_dimension_color()/kh_channel_
   color()), pour qu'un filtre "Logement"/"Plateforme" reste visuellement
   raccordé aux graphes qu'il filtre. */
.kh-select-swatch { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 7px; vertical-align: middle; flex-shrink: 0; }
.kh-select-option:hover { background: var(--bg-sunken); }
.kh-select-option.is-selected { color: var(--accent); font-weight: 600; }
/* 2026-08-30 (booking_detail.php, statut legacy "selected disabled") —
   affiché mais jamais re-sélectionnable, jamais confondu avec une option
   normale au survol. */
.kh-select-option.is-disabled { color: var(--text-muted); cursor: not-allowed; }
.kh-select-option.is-disabled:hover { background: none; }
/* 2026-08-30 (property_rules.php, select combiné équipe/membre à 2 <optgroup>)
   — en-tête de groupe non cliquable, jamais confondu avec une option. */
.kh-select-group-label {
	padding: 6px 10px 2px; font-size: 11px; font-weight: 600; text-transform: uppercase;
	letter-spacing: .02em; color: var(--text-muted);
}

/* kh_multiselect.js — "Logements" (et tout autre `.checkbox-list` qui peut
   grossir : rôles, canaux...) : jamais une liste à plat, un déclencheur
   compact + panneau repliable avec recherche, même famille visuelle que
   .kh-select-* ci-dessus. */
.kh-multiselect-wrap { position: relative; display: inline-block; }
/* 🐛 2026-10-13 — `display` déclaré ici ÉCRASE le `[hidden] { display:none }`
   du navigateur : poser l'attribut `hidden` sur ce composant ne le masquait
   pas. Mesuré sur « Tous les logements » — `hidden` était bien vrai, et le
   menu restait affiché. Tout composant qui déclare son `display` doit rendre
   `hidden` à nouveau effectif, sinon l'attribut ment silencieusement. */
.kh-multiselect-wrap[hidden] { display: none; }
.kh-select-wrap[hidden] { display: none; }
/* 2026-11-07 — même correction que `.kh-select-trigger` : ce déclencheur
   vivait lui aussi à 28 px / 12 px alors que le champ natif du produit fait
   44 px / 14 px. Sur la barre de filtres d'Analytics, « Logements » (un
   multiselect) côtoie « Année », « Mois », « Groupe de logements » et
   « Propriétaire » (des kh-select) : corriger un seul des deux composants
   aurait déplacé le désalignement au lieu de le supprimer. Les deux
   déclencheurs partagent désormais exactement la même boîte. */
.kh-multiselect-trigger {
	font: inherit; font-size: 14px; text-align: left; cursor: pointer;
	background: var(--surface); color: var(--text); border: 1px solid var(--border);
	border-radius: var(--radius-sm); padding: 9px 32px 9px 12px; min-width: 200px; max-width: 320px;
	height: 44px; line-height: 24px; box-sizing: border-box;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap; position: relative;
}
.kh-multiselect-trigger::after {
	content: ''; position: absolute; right: 13px; top: 50%; width: 6px; height: 6px;
	border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
	transform: translateY(-70%) rotate(45deg); pointer-events: none;
}
.kh-multiselect-trigger:hover { border-color: var(--text-muted); }
.kh-multiselect-trigger.is-open { border-color: var(--accent); }
.kh-multiselect-trigger:disabled { opacity: .55; cursor: not-allowed; }
/* position:fixed + top/left posés en JS (getBoundingClientRect du
   déclencheur) — jamais absolute/wrap : cf. docblock kh_multiselect.js
   ("panneau en portail", contexte d'empilement de .card). */
.kh-multiselect-panel {
	position: fixed; z-index: 1000; width: 280px; max-width: calc(100vw - 24px);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
	box-shadow: var(--shadow-md); padding: 8px;
}
.kh-multiselect-controls { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.kh-multiselect-search {
	flex: 1; font: inherit; font-size: 12.5px; padding: 6px 9px; border: 1px solid var(--border);
	border-radius: 6px; background: var(--bg-sunken); color: var(--text);
}
.kh-multiselect-action {
	font-size: 11.5px; font-weight: 700; color: var(--accent); cursor: pointer;
	background: none; border: none; padding: 0; white-space: nowrap;
}
.kh-multiselect-action:hover { text-decoration: underline; }
.kh-multiselect-empty { font-size: 12px; color: var(--text-muted); padding: 8px 4px; display: none; }
/* Le `.checkbox-list` d'origine passe d'étiquettes en ligne (flex-wrap) à
   une vraie liste verticale scrollable une fois dans le panneau — jamais un
   changement visuel en dehors de ce contexte (règle imbriquée, pas globale). */
.kh-multiselect-panel .checkbox-list {
	flex-direction: column; flex-wrap: nowrap; gap: 1px; max-height: 240px; overflow-y: auto; margin-top: 0;
}
.kh-multiselect-panel .checkbox-list label { padding: 6px 6px; border-radius: 6px; }
.kh-multiselect-panel .checkbox-list label:hover { background: var(--bg-sunken); }

/* "Notifications plannings" (notification_planning.php, 2026-09-07) —
   options "Quand ?"/"Comment ?" en cartes cochables (checkbox réelle
   cachée visuellement, jamais retirée du flux — un lecteur d'écran/la
   soumission du formulaire restent inchangés) plutôt qu'un simple
   <input type="checkbox"> nu, pour la même lisibilité que la capture
   SuperHote de référence. */
.option-card {
	position: relative; display: block; cursor: pointer; border: 1px solid var(--border);
	border-radius: var(--radius); padding: 16px; background: var(--surface);
	transition: border-color .15s var(--ease), background .15s var(--ease);
}
.option-card input[type="checkbox"], .option-card input[type="radio"] {
	position: absolute; opacity: 0; pointer-events: none;
}
.option-card .option-card-check {
	position: absolute; top: 14px; right: 14px; width: 20px; height: 20px; border-radius: 999px;
	border: 1.5px solid var(--border); background: var(--surface);
}
.option-card input:checked ~ .option-card-check {
	background: var(--accent); border-color: var(--accent);
}
.option-card input:checked ~ .option-card-check::after {
	content: ''; position: absolute; left: 6px; top: 2px; width: 5px; height: 9px;
	border: solid var(--accent-contrast); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.option-card:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
.option-card-title { font-weight: 700; font-size: 14px; margin-bottom: 3px; padding-right: 28px; }
.option-card-desc { font-size: 12.5px; color: var(--text-muted); }
.option-card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }

/* Modale légère (<dialog> natif) — 1ʳᵉ utilisation dans ce projet, cf.
   "Aperçu du planning" (notification_planning.php). Même famille visuelle
   que .card (fond/bordure/ombre/radius), pas un nouveau système. */
dialog.kh-dialog {
	border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-md);
	padding: 0; width: min(560px, calc(100vw - 32px)); max-height: min(720px, calc(100vh - 64px));
	background: var(--surface); color: var(--text);
}
dialog.kh-dialog::backdrop { background: rgba(0, 0, 0, .45); }
.kh-dialog-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.kh-dialog-header h2 { font-size: 16px; margin: 0; }
.kh-dialog-body { padding: 20px; overflow-y: auto; }
.kh-dialog-close { background: none; border: none; font-size: 20px; line-height: 1; color: var(--text-muted); cursor: pointer; }
.kh-dialog-close:hover { color: var(--text); }

/* --- Légende de la heatmap Analytics (2026-09-08, parité SuperHote) -------
   Les couleurs des pastilles ne sont PAS définies ici : elles sont peintes
   en inline par kh_heatmap_bg() (analytics_helper.php), la même fonction que
   les cellules du tableau — une pastille ne peut donc pas se désynchroniser
   de ce qu'elle est censée légender. */
.heatmap-legend {
	display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
	gap: 8px; margin-top: 8px;
}
.heatmap-legend-scale { display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-muted); }
.heatmap-swatch {
	display: inline-block; width: 18px; height: 12px; border-radius: 3px;
	border: 1px solid var(--border);
}
.heatmap-legend-note { font-size: 11px; color: var(--text-muted); }
/* Ligne de pied "Total" du tableau : séparée du corps, jamais colorée
   (c'est un agrégat de portefeuille, pas une valeur à comparer). */
.data tfoot td { font-weight: 600; border-top: 2px solid var(--border); background: var(--bg-sunken); }

/* --- Vue jour : mouvements du jour (2026-09-08, parité SuperHote) ----------
   Bandeau de 7 jours + filtres + sections Départs/Arrivées/Tâches. */
.day-strip { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 8px; margin-bottom: 16px; }
.day-strip-card {
	display: block; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px;
	background: var(--surface); text-decoration: none; color: inherit; transition: border-color .15s, background .15s;
}
.day-strip-card:hover { border-color: var(--accent); }
.day-strip-card.is-current { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.day-strip-card.is-current .day-strip-date { color: var(--accent); }
.day-strip-date { font-weight: 700; font-size: 13px; margin-bottom: 4px; }
.day-strip-line { font-size: 12px; color: var(--text-muted); }

.day-filters { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-bottom: 12px; }
.day-filters input[type="search"] { min-width: 240px; }

.day-segments { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.day-segment {
	display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px;
	border: 1px solid var(--border); text-decoration: none; color: inherit; font-size: 13px; background: var(--surface);
}
.day-segment:hover { border-color: var(--accent); }
.day-segment.is-active { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: var(--accent); font-weight: 600; }
.day-segment-count { color: var(--text-muted); font-size: 12px; }
.day-segment--toggle { margin-left: auto; }

.day-section-head {
	display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
	font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
	padding: 8px 12px; border-radius: 8px 8px 0 0; background: var(--bg-sunken); margin-top: 8px;
}
.day-section-head--out { background: color-mix(in srgb, var(--danger) 8%, transparent); }
.day-section-head--in { background: color-mix(in srgb, var(--success) 10%, transparent); }

.day-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex: none; }
.day-state { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Texte secondaire EN LIGNE, générique (ex-`.day-muted`, généralisée le
   2026-09-09). Volontairement PAS `.hint` : cette classe porte un
   `margin-top: 14px` que la neutralisation inline habituelle
   (`padding:0;background:none;border:none`) ne retire pas — d'où un décalage
   vertical qui faisait littéralement chevaucher deux libellés voisins dans la
   colonne Ménage de la vue jour. `.hint` est un bloc d'aide, pas un style de
   texte discret : pour du texte discret EN LIGNE, c'est cette classe. */
.text-soft { color: var(--text-muted); font-size: 12px; }
/* Montant négatif dans un tableau de comptes (Comptabilité, mois par mois).
   `.besoin-value.danger` existait déjà mais porte la mise en forme d'une
   carte : ici il ne faut QUE la couleur, sur une cellule de tableau. */
.amount-negative { color: var(--danger); }
.amount-positive { color: var(--success); }

/* ---- Comptabilité (2026-10-24) -------------------------------------------
   Séparateur de section entre deux groupes de cartes : la page Comptabilité
   se lit d'abord (synthèse, mois, résultat) puis se règle (saisie). */
.section-title {
	font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
	color: var(--text-muted); margin: 28px 0 12px; padding-top: 18px;
	border-top: 1px solid var(--border);
}

/* Barres mensuelles de cash-flow. Douze colonnes autour d'une ligne de zéro :
   le SIGNE se lit à la position, la couleur ne fait que confirmer (la paire
   succès/danger ne sépare qu'à ΔE 7,8 en deutéranopie, elle ne peut pas
   porter l'information seule). Extrémités arrondies côté donnée seulement,
   4 px, ancrées à la ligne de base ; 4 px d'écart entre colonnes. */
.acct-bars { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 4px; margin: 4px 0 14px; }
.acct-col { display: flex; flex-direction: column; border-radius: 6px; padding: 2px 0; }
.acct-col.is-today { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.acct-half { height: 52px; display: flex; justify-content: center; }
.acct-half--pos { align-items: flex-end; }
.acct-half--neg { align-items: flex-start; }
.acct-fill {
	width: 100%; max-width: 26px; background: var(--success);
	border-radius: 4px 4px 0 0; transition: box-shadow .12s var(--ease);
}
.acct-fill--neg { background: var(--danger); border-radius: 0 0 4px 4px; }
.acct-col:hover .acct-fill { box-shadow: 0 0 0 2px var(--surface), 0 0 0 3px currentColor; }
.acct-base { height: 1px; background: var(--border); }
.acct-month { font-size: 10.5px; color: var(--text-muted); text-align: center; margin-top: 5px; }
.acct-tag {
	font-size: 10.5px; font-variant-numeric: tabular-nums; text-align: center;
	color: var(--text-muted); min-height: 14px; white-space: nowrap;
}

/* Tableau mensuel : 14 colonnes. La colonne de libellés reste visible quand
   on fait défiler horizontalement — sans elle, un nombre au milieu du
   tableau ne dit plus de quelle ligne il vient. */
table.data th.acct-sticky, table.table th.acct-sticky, table.data td.acct-sticky, table.table td.acct-sticky {
	position: sticky; left: 0; z-index: 1; background: var(--surface);
	/* Filet à droite de la colonne collée : sans lui, rien ne dit que le
	   tableau continue derrière, ni où il reprend quand on le fait défiler. */
	box-shadow: 1px 0 0 var(--border);
}
table.data thead th.acct-sticky, table.table thead th.acct-sticky { background: var(--bg-sunken); z-index: 2; }
table.data tbody tr:hover td.acct-sticky, table.table tbody tr:hover td.acct-sticky { background: var(--bg-sunken); }

/* `.table-wrap` masque le débordement : parfait pour arrondir les coins d'un
   tableau normal, mortel pour un tableau à 14 colonnes, dont les nombres se
   cassaient alors en deux lignes (« 1 » / « 839,74 »). Ici il défile, et la
   colonne de libellés reste collée à gauche. */
.table-wrap--scroll { overflow-x: auto; }
/* 2026-11-06 (audit UI web) — 53 tableaux de données vivaient directement
   dans une `.card`, sans conteneur : plus larges que la carte, ils étaient
   coupés et leur dernière colonne devenait inatteignable (Campagnes perdait
   121 px à 1024). `.table-wrap` aurait réglé le défilement mais aurait aussi
   posé une bordure, un fond et une ombre À L'INTÉRIEUR d'une carte qui en a
   déjà — un cadre dans un cadre, sur 53 écrans. D'où ce conteneur sans
   habillage : il ne fait qu'une chose, rendre le contenu atteignable.

   🐛 2026-11-11 (balayage UI) — « atteignable » ne suffisait pas : sur ces 53
   tableaux, RIEN ne disait qu'il restait des colonnes à droite, et la barre
   de défilement de macOS est masquée au repos. L'ombre ci-dessous n'est pas
   un habillage — ni bordure, ni fond, ni ombre portée, donc toujours pas de
   cadre dans un cadre : les deux calques `local` suivent le contenu et
   masquent le repère dès qu'on atteint le bord, il n'apparaît donc que s'il
   reste vraiment quelque chose à voir.

   Le calque de masque est peint en `var(--surface)` : il ne se fond que sur
   le fond d'une carte. Posé sur `--bg` (#0A1F17 contre #123A29), il
   laisserait une bande claire de 28 px à chaque bord — un défaut à la place
   d'un repère. D'où le sélecteur descendant : les `.table-scroll` qui vivent
   ailleurs gardent exactement le comportement qu'ils avaient. */
.table-scroll { overflow-x: auto; }
.card .table-scroll {
	background:
		linear-gradient(to right, var(--surface) 40%, transparent) left center / 28px 100% no-repeat local,
		linear-gradient(to left,  var(--surface) 40%, transparent) right center / 28px 100% no-repeat local,
		radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) left center / 12px 100% no-repeat scroll,
		radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) right center / 12px 100% no-repeat scroll;
}

/* 2026-11-06 (audit UI web) — « largeur de cette page », écrite 33 fois en
   style inline dans property_rules.php (`max-width:1080px` sur chaque
   carte). Un nombre magique recopié 33 fois, c'est 33 endroits à corriger
   le jour où la largeur change, et la certitude qu'il en restera un. La
   classe d'écran posée sur <main> permet de le dire UNE fois. */
.main--property-rules .card { max-width: 1080px; }
.table-dense th, .table-dense td { padding: 8px 10px; font-size: 12.5px; white-space: nowrap; }
/* Un montant ne se coupe jamais en deux lignes (« 1 » / « 185,00 ») : à
   appliquer sur les tableaux larges, là où le navigateur est tenté. */
.table-nums th.num, .table-nums td.num { white-space: nowrap; }

/* Comparaison longue / courte durée : trois nombres côte à côte, le verdict
   au bout. Mis en évidence parce que c'est la conclusion de la page, pas une
   ligne de tableau de plus. */
.acct-compare {
	display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
	background: var(--bg-sunken); border: 1px solid var(--border);
	border-radius: var(--radius); padding: 14px 18px; margin-top: 14px;
}
.acct-compare-label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); font-weight: 700; }
.acct-compare-value { font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; margin-top: 2px; }
.acct-compare-sep { color: var(--text-muted); font-size: 12px; }

/* Les hypothèses du scénario, réglées à côté du résultat qu'elles changent. */
.acct-hyp { display: flex; align-items: flex-end; gap: 14px; flex-wrap: wrap; margin-top: 16px; }

/* ---- Guide d'utilisation (2026-11-05) ------------------------------------
   Deux colonnes : un sommaire collant à gauche, le guide à droite. Sous
   1024 px le sommaire repasse au-dessus du contenu — un sommaire collant
   sur un téléphone mangerait la moitié de l'écran. */
.help-layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 24px; align-items: start; }
.help-toc-inner {
	position: sticky; top: 16px; background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); padding: 16px; box-shadow: var(--shadow-sm);
}
.help-toc-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--text-muted); margin-bottom: 10px; }
.help-search { width: 100%; margin-bottom: 10px; }
.help-toc-link {
	display: block; padding: 7px 10px; border-radius: 8px; font-size: 13px;
	color: var(--text); text-decoration: none; border-left: 2px solid transparent;
}
.help-toc-link:hover { background: var(--bg-sunken); }
.help-toc-link.is-active { background: var(--bg-sunken); border-left-color: var(--accent); color: var(--accent); font-weight: 700; }
.help-toc-aide { font-size: 12px; color: var(--text-muted); margin: 12px 0 0; }

.help-chapter { margin-bottom: 20px; scroll-margin-top: 16px; }
.help-chapter-title { font-size: 20px; font-weight: 800; margin: 0 0 4px; letter-spacing: -.2px; }
.help-chapter-resume { color: var(--text-muted); margin: 0 0 14px; }
.help-text { margin: 0 0 10px; line-height: 1.6; }

/* Les étapes sont NUMÉROTÉES par le compteur CSS : l'ordre est
   l'information principale d'un guide, il ne doit pas dépendre de ce que
   l'auteur a tapé à la main. */
.help-steps { list-style: none; counter-reset: etape; padding: 0; margin: 0; }
.help-step { counter-increment: etape; position: relative; padding: 0 0 20px 44px; }
.help-step::before {
	content: counter(etape); position: absolute; left: 0; top: 0;
	width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
	background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent);
	font-weight: 800; font-size: 13px;
}
.help-step:not(:last-child)::after {
	content: ''; position: absolute; left: 13px; top: 32px; bottom: 6px;
	width: 2px; background: var(--border);
}
.help-step-title { font-size: 15px; font-weight: 700; margin: 4px 0 6px; }
/* Sous-section d'un chapitre : trois procédures de connexion distinctes ne
   doivent pas se lire comme une seule séquence de onze étapes. */
.help-section {
	font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
	color: var(--accent); margin: 18px 0 10px; padding-top: 14px; border-top: 1px dashed var(--border);
}
.help-steps + .help-section { margin-top: 4px; }
.help-list { margin: 0 0 10px; padding-left: 18px; line-height: 1.7; }

.help-callout {
	display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px;
	border-radius: var(--radius); margin: 0 0 12px; font-size: 13px; line-height: 1.55;
}
.help-callout-icon {
	flex: 0 0 20px; width: 20px; height: 20px; border-radius: 50%;
	display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 12px;
	background: var(--surface);
}
.help-callout--info { background: var(--bg-sunken); border: 1px solid var(--border); }
.help-callout--info .help-callout-icon { color: var(--accent); }
.help-callout--attention { background: var(--warning-bg); color: var(--warning-fg); border: 1px solid color-mix(in srgb, var(--warning) 40%, transparent); }
.help-callout--attention .help-callout-icon { color: var(--warning-fg); }

.help-figure { margin: 0 0 12px; }
.help-figure img {
	width: 100%; height: auto; display: block; border: 1px solid var(--border);
	border-radius: var(--radius); box-shadow: var(--shadow-sm);
}
.help-cta { display: inline-block; }
.help-next { display: inline-block; margin-top: 4px; font-size: 13px; font-weight: 600; text-decoration: none; }
.help-empty { color: var(--text-muted); }

@media (max-width: 1024px) {
	.help-layout { grid-template-columns: minmax(0, 1fr); }
	.help-toc-inner { position: static; }
}
.table-dense th.acct-sticky, .table-dense td.acct-sticky { white-space: normal; min-width: 150px; }
.day-state-label { color: var(--text-muted); font-size: 12px; }
.day-time {
	display: inline-block; padding: 3px 8px; border-radius: 6px;
	border: 1px solid var(--border); font-variant-numeric: tabular-nums; font-size: 13px;
}

.day-move { display: flex; align-items: center; gap: 10px; }
.day-move-title { font-weight: 600; display: flex; align-items: center; gap: 6px; }
.day-thumb { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; flex: none; }
.day-thumb--empty { background: var(--bg-sunken); border: 1px solid var(--border); }

@media (max-width: 900px) {
	.day-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.day-segment--toggle { margin-left: 0; }
}

/* --- Vue liste : bascule de vues + entêtes de journée (2026-09-08) ---------
   Le sélecteur vit dans un partial partagé par les 3 vues du calendrier. */
.view-switcher {
	display: inline-flex; gap: 4px; padding: 4px; margin-bottom: 16px;
	border: 1px solid var(--border); border-radius: 10px; background: var(--bg-sunken);
}
.view-switcher-item {
	padding: 6px 14px; border-radius: 7px; text-decoration: none; color: inherit; font-size: 13px;
}
.view-switcher-item:hover { background: var(--surface); }
.view-switcher-item.is-active { background: var(--surface); font-weight: 600; box-shadow: 0 1px 2px rgba(0,0,0,.06); }

.list-day-summary {
	display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
	cursor: pointer; font-size: 14px;
}

/* --- Fiche logement : en-tête héro, sections de formulaire, barre d'action
   collante (2026-09-09, « fais-moi un beau design »). Tout est PRÉFIXÉ
   `.property-*` / porté par un modificateur : `.tab-bar` et `.card` sont
   partagés par une dizaine d'écrans, les restyler globalement pour cette page
   casserait ailleurs (piège déjà rencontré sur ce projet). ------------------ */
.property-hero {
	display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
	padding: 18px 20px; margin-bottom: 16px;
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.property-hero-photo {
	width: 84px; height: 84px; border-radius: 12px; object-fit: cover; flex: none;
	background: var(--bg-sunken); border: 1px solid var(--border);
}
.property-hero-body { flex: 1 1 260px; min-width: 0; }
.property-hero-title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin: 0 0 2px; }
.property-hero-title h1 { margin: 0; font-size: 22px; line-height: 1.2; }
.property-hero-alias { font-size: 13px; color: var(--text-muted); }
.property-hero-meta { font-size: 13px; color: var(--text-muted); margin-bottom: 8px; }
.property-hero-badges { display: flex; gap: 6px; flex-wrap: wrap; }
.property-hero-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* Barre d'onglets de CETTE page : 13 entrées débordaient sur 2 rangées en
   petites majuscules. Défilement horizontal + repères plus lisibles, sans
   toucher au `.tab-bar` partagé. */
.tab-bar--property {
	overflow-x: auto; flex-wrap: nowrap; scrollbar-width: thin;
	gap: 2px; padding-bottom: 2px;

	/* 2026-11-06 (audit UI web) — 13 onglets sur une seule rangée défilante :
	   le contenu RESTE atteignable (overflow-x: auto), mais rien ne disait
	   qu'il y en avait d'autres. À 1440 px le dernier onglet était coupé en
	   plein mot (« Journ… ») et « Journal » ne se découvrait qu'au hasard
	   d'un geste de défilement. Ombre de défilement en CSS pur : les deux
	   calques `local` suivent le contenu et viennent MASQUER l'ombre quand
	   on atteint le bord, donc le repère n'apparaît que s'il reste vraiment
	   quelque chose à voir — jamais de fausse indication.

	   🐛 2026-11-11 (balayage UI, rendu réel des onglets) — l'ombre était
	   écrite en `rgba(0, 0, 0, .14)`, pensée pour le thème CLAIR et jamais
	   vérifiée sur l'autre : sur fond sombre (`--bg: #0A1F17`), du noir à
	   14 % sur du presque-noir ne se voit pas. Le repère existait dans la
	   feuille de style et ne disait rien à l'écran — « Journ… » restait
	   coupé sans la moindre indication, exactement le défaut que ce bloc
	   était censé corriger. `color-mix` sur `--text` (déjà la convention
	   ici, 21 autres emplois) inverse la teinte avec le thème : sombre sur
	   clair, clair sur sombre. */
	background:
		linear-gradient(to right, var(--bg) 40%, transparent) left center / 28px 100% no-repeat local,
		linear-gradient(to left,  var(--bg) 40%, transparent) right center / 28px 100% no-repeat local,
		radial-gradient(farthest-side at 0 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) left center / 14px 100% no-repeat scroll,
		radial-gradient(farthest-side at 100% 50%, color-mix(in srgb, var(--text) 22%, transparent), transparent) right center / 14px 100% no-repeat scroll;
}
.tab-bar--property .tab-btn {
	/* 2026-11-11 — casse, taille et interlettrage sont remontés dans `.tab-btn`
	   pour tout le produit ; il ne reste ici que ce qui est propre à une barre
	   qui défile. */
	white-space: nowrap;
	/* 2026-11-11 (balayage UI) — 14 px de marge interne de chaque côté, sur
	   13 onglets, faisaient déborder la barre d'une vingtaine de pixels à
	   1440 px : « Journal » se coupait pour presque rien. À 10 px tout tient
	   sur la largeur de bureau courante, sans rien faire défiler. Le
	   défilement et son ombre restent en place pour les écrans plus étroits
	   et pour un logement qui afficherait plus d'onglets. */
	padding-left: 10px; padding-right: 10px;
}

/* Sous-section de formulaire : titre + description, séparée par un filet.
   Remplace les <h2> nus empilés dans une carte de 400 lignes. */
.form-section { padding-top: 18px; margin-top: 18px; border-top: 1px solid var(--border); }
.form-section:first-of-type { padding-top: 0; margin-top: 0; border-top: 0; }
.form-section-title { font-size: 14px; font-weight: 700; margin: 0 0 2px; }
.form-section-desc { font-size: 12.5px; color: var(--text-muted); margin: 0 0 12px; }

/* Barre d'enregistrement collante : sur un formulaire de plusieurs écrans de
   haut, le bouton ne doit pas être à chercher tout en bas. */
.sticky-actions {
	position: sticky; bottom: 0; z-index: 5;
	display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
	/* -24px = le padding exact de `.card` : la barre affleure ses bords au
	   lieu de flotter à l'intérieur. */
	margin: 20px -24px -24px; padding: 14px 24px;
	background: var(--surface); border-top: 1px solid var(--border);
	border-radius: 0 0 var(--radius) var(--radius);
}

/* Encart d'aide compact : les 3 gros blocs gris empilés en tête d'onglet
   écrasaient le formulaire. */
.callout {
	display: flex; gap: 10px; align-items: flex-start;
	padding: 10px 12px; margin-bottom: 12px;
	border: 1px solid var(--border); border-left: 3px solid var(--accent);
	border-radius: 8px; background: var(--bg-sunken);
	font-size: 12.5px; color: var(--text-muted);
}
.callout--warning { border-left-color: var(--warning); }
.callout-icon { flex: none; font-size: 14px; line-height: 1.4; }
.callout strong { color: var(--text); }
/* Titre de carte de la fiche logement : 3 marges différentes coexistaient
   (4/8/12px) selon l'âge du code. Une seule règle, appliquée partout. */
.card-title { font-size: 14px; margin: 0 0 10px; }
/* Recherche dans le menu "Logements" du calendrier (2026-09-09). */
.cal-property-search { width: 100%; margin-bottom: 8px; }
/* Coque de consultation de la fiche logement (2026-09-10) : un <fieldset>
   n'est là que pour son `disabled` natif, jamais pour son habillage. */
.ro-shell { border: 0; padding: 0; margin: 0; min-width: 0; }

/* Sections repliables d'un formulaire long — 2026-10-13 (retour user : « on
   s'y perd complet »). Le formulaire de règle empilait une quinzaine de blocs
   en une seule colonne : rien ne distinguait l'essentiel (quoi, quand, quel
   message) de l'optionnel (conditions, plateformes), et il fallait faire
   défiler tout pour trouver un champ.

   <details>/<summary> natif plutôt qu'un repli en JS : accessible au clavier
   et à l'ouverture d'un Ctrl+F sans rien écrire, et surtout le contenu REPLIÉ
   RESTE DANS LE DOM — donc toujours soumis. Un repli JS qui retirerait les
   champs perdrait silencieusement des réglages à l'enregistrement. */
.kh-section { border: 1px solid var(--border); border-radius: var(--radius-sm); margin: 12px 0; background: var(--surface); }
.kh-section > summary {
	cursor: pointer; padding: 12px 14px; font-size: 13px; font-weight: 600;
	display: flex; align-items: center; gap: 10px; list-style: none;
	border-radius: var(--radius-sm);
}
.kh-section > summary::-webkit-details-marker { display: none; }
.kh-section > summary::before {
	content: ''; width: 6px; height: 6px; flex-shrink: 0;
	border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted);
	transform: rotate(-45deg); transition: transform .15s ease;
}
.kh-section[open] > summary::before { transform: rotate(45deg); }
.kh-section > summary:hover { background: var(--surface-alt, rgba(0, 0, 0, .025)); }
/* Le résumé d'une section repliée : il doit suffire à savoir si on a besoin
   de l'ouvrir, sinon replier ne fait que cacher. */
.kh-section > summary .kh-section-resume {
	margin-left: auto; font-weight: 400; font-size: 12px; color: var(--text-muted);
	text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
	max-width: 55%; min-width: 0;
}
.kh-section[open] > summary .kh-section-resume { opacity: .5; }
.kh-section-corps { padding: 0 14px 14px; }

/* Légende « * Champ obligatoire » — 2026-10-22.
   Posée par kh_legende_obligatoire.js en tête des formulaires qui portent au
   moins un libellé étoilé. Discrète par construction : elle explique un
   marqueur, elle n'est pas un message. */
/* 🐛 2026-11-07 (revue visuelle, « Sites de réservation ») — le script
   l'insère avec `form.insertBefore(legende, form.firstChild)`, ce qui
   suppose un formulaire en bloc. Sur un formulaire `display: flex`, la
   légende devient un ÉLÉMENT FLEX : elle se range en première colonne,
   à gauche du premier champ, et se lit comme le libellé d'un champ
   invisible. Un seul formulaire du produit est dans ce cas aujourd'hui
   (« Créer un site »), mais la règle est générale et le prochain
   formulaire en ligne retomberait dedans. `flex-basis: 100%` la remet sur
   sa propre ligne ; `order: -1` garantit qu'elle y reste la première même
   si un jour le script l'insérait ailleurs. Inoffensif hors flex. */
.kh-legende-obligatoire {
	font-size: 12px;
	color: var(--text-muted);
	margin: 0 0 12px;
	flex-basis: 100%;
	order: -1;
}

/* ====================================================================== */
/* Responsive — coquille mobile (2026-11-11, retour user : « l'affichage   */
/* web sur un téléphone est horrible. Pas du tout responsive »).           */
/*                                                                        */
/* Constat : la feuille comptait bien six points de rupture, mais AUCUN ne */
/* touchait la coquille. `.app-shell` restait `display:flex` et `.sidebar` */
/* gardait `width: var(--sidebar-width)` (256 px) à toutes les tailles :   */
/* sur un écran de 390 px le menu prenait les deux tiers, le contenu       */
/* tombait à un mot par ligne, et ce qui dépassait rendait la page         */
/* scrollable latéralement — les trois captures du 11/11 montrent les deux */
/* symptômes.                                                             */
/*                                                                        */
/* Choix : tiroir hors-champ plutôt que menu replié en icônes. Le menu a   */
/* des sections repliables et des libellés longs ; réduit à 56 px il       */
/* deviendrait une rangée de pictogrammes muets. Le tiroir garde le menu   */
/* intact et rend TOUTE la largeur au contenu.                            */
/*                                                                        */
/* Au-dessus de 860 px, pas une seule déclaration ne s'applique : la barre */
/* du haut est masquée et rien d'autre n'est redéfini hors media query.    */
/* ====================================================================== */

.kh-topbar { display: none; }

@media (max-width: 860px) {
	.app-shell { display: block; }

	.kh-topbar {
		display: flex; align-items: center; gap: 10px;
		position: sticky; top: 0; z-index: 40;
		padding: 9px 14px;
		background: var(--bg-sunken);
		border-bottom: 1px solid var(--border);
	}
	.kh-topbar-brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 15px; }
	.kh-nav-toggle {
		display: inline-flex; align-items: center; justify-content: center;
		width: 38px; height: 38px; flex: none;
		border: 1px solid var(--border); border-radius: 9px;
		background: var(--surface); color: var(--text);
		font-size: 16px; line-height: 1; cursor: pointer;
	}
	.kh-nav-toggle:hover { background: var(--border); }

	.sidebar {
		position: fixed; top: 0; left: 0; bottom: 0; z-index: 60;
		width: min(86vw, 300px);
		transform: translateX(-100%);
		transition: transform .22s var(--ease);
		overflow-y: auto;
		border-right: 1px solid var(--border);
	}
	.sidebar.is-open { transform: none; box-shadow: 0 18px 44px rgba(0, 0, 0, .3); }

	.kh-nav-backdrop {
		position: fixed; inset: 0; z-index: 50;
		background: rgba(0, 0, 0, .45);
		opacity: 0; transition: opacity .22s var(--ease);
	}
	.kh-nav-backdrop.is-open { opacity: 1; }
	body.kh-nav-open { overflow: hidden; }

	.main { padding: 16px 16px 40px; }
	.main-header { flex-wrap: wrap; gap: 10px; }
	.main-header h1 { flex: 1 1 100%; font-size: 21px; line-height: 1.25; }

	/* Rien ne doit dépasser la largeur de l'écran : c'est ce débordement qui
	   rendait la page scrollable latéralement et coupait les cartes en deux
	   (captures 2 et 3 du 11/11). On traite les CAUSES plutôt que de poser un
	   `overflow-x: hidden` sur <body>, qui casserait les barres collantes. */
	.main > * { max-width: 100%; }
	.card, .banner, .callout { max-width: 100%; }

	/* Un tableau large reste lisible en défilant DANS sa carte, jamais en
	   poussant la page entière. `display: block` rend la table elle-même
	   scrollable ; ses lignes continuent de se disposer en tableau. */
	table.data, table.table { display: block; width: 100%; overflow-x: auto; }

	/* Un filtre ne descend plus sous la largeur de son libellé : à 390 px,
	   deux filtres côte à côte font deux colonnes de 70 px. */
	.kh-filtres > .field, .cashflow-filter-bar .field { min-width: 0; flex: 1 1 100%; }

	/* Les grilles à colonnes FIXES (les `auto-fill/minmax` se replient déjà
	   toutes seules une fois la largeur rendue au contenu). */
	.messages-panel-grid, .home-about-grid, .home-about-photos, .help-layout { grid-template-columns: minmax(0, 1fr); }
	.day-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.acct-bars { grid-template-columns: repeat(6, minmax(0, 1fr)); }

	/* Le calendrier mensuel ne se replie pas — il se parcourt. */
	.cal-pg-scroll, .table-wrap, .table-scroll { overflow-x: auto; }

	/* 2026-11-11, seconde passe — constats faits SUR le téléphone de l'hôte
	   (Xiaomi, 393 px de large) une fois la coquille déployée. */

	/* La colonne « Logement » du calendrier gardait ses 180 px, soit la moitié
	   des 358 px utiles, pour n'afficher QUE 4 jours — et les noms étaient
	   tronqués malgré tout (« La Venise Secrè… »). À 112 px ils ne le sont pas
	   davantage, et la grille gagne 4 jours de visibilité. Les trois sélecteurs
	   restent ensemble : la colonne, son en-tête et son cache doivent avoir la
	   MÊME largeur, sinon la grille se désaligne. */
	.cal-pg-corner, .cal-pg-corner-spacer, .cal-pg-label { width: 112px; }
	.cal-pg-label-main { gap: 6px; padding: 0 4px 0 6px; }
	/* La vignette est retirée, pas rétrécie : vérifié sur l'appareil, les six
	   logements affichent la MÊME icône générique de maison (aucun n'a de photo
	   renseignée), donc elle ne distingue rien et prenait 38 px des 112 — le nom
	   tombait à « La Veni… ». Sans elle il reste ~96 px, assez pour trancher
	   entre « Le Kaki… » et « Le charme… ». Un logement AVEC photo la perd aussi
	   sur téléphone : le nom prime quand la place manque. */
	.cal-pg-label-photo, .cal-pg-label-photo-placeholder { display: none; }
	.cal-pg-label-sub { padding-left: 6px; }

	/* Barre d'actions du calendrier : cinq boutons à la taille du bureau
	   occupaient trois rangées et repoussaient la grille hors de l'écran — il
	   fallait faire défiler pour voir son planning. */
	.cal-toolbar { gap: 8px; }
	.cal-toolbar .btn, .cal-toolbar .btn-outline { font-size: 12.5px; padding: 7px 11px; }
	.cal-toolbar h1 { flex: 1 1 100%; min-width: 0; }

	/* Première colonne des tableaux : « Pause Urbaine – Studio au cœur de
	   Montargis » s'étalait sur CINQ lignes et gonflait toute la rangée, parce
	   que la table essayait de faire tenir quatre colonnes dans 390 px.
	   On lui donne une largeur PLANCHER : le nom retombe sur deux ou trois
	   lignes et les colonnes suivantes restent visibles.
	   🐛 Première tentative, vérifiée sur l'appareil puis abandonnée :
	   `white-space: nowrap`. Les rangées redevenaient compactes, mais la
	   colonne prenait alors TOUT l'écran — on ne voyait plus le type de la
	   tâche, et deux lignes d'affilée n'affichaient plus que le même nom de
	   logement, sans rien pour les distinguer. Compacter ne vaut rien si la
	   colonne qui PORTE l'information disparaît. */
	table.data td:first-child, table.table td:first-child { min-width: 170px; }

	/* 🐛 2026-11-12 (capture de l'hôte) — la messagerie est une boîte à TROIS
	   colonnes : liste 320 px + fil + panneau réservation 260 px, les deux
	   latérales en `flex-shrink: 0`. Sur 390 px, le total dépasse largement le
	   conteneur, qui est en `overflow: hidden` : le panneau de droite se
	   superposait à la liste, les deux illisibles. Elle n'était pas couverte
	   par la règle générale, qui ne vise que les `style="display:flex"` en
	   ligne — ici c'est une CLASSE.
	   On empile, et on rend la hauteur libre : `height: calc(100vh - 170px)`
	   sur une colonne de trois blocs écraserait chacun à un tiers d'écran. */
	.messages-layout { flex-direction: column; height: auto; min-height: 0; overflow: visible; }
	.messages-list-col {
		width: 100%; border-right: 0; border-bottom: 1px solid var(--border);
		max-height: 58vh;
	}
	.messages-thread-col { min-height: 55vh; }
	.messages-panel-col {
		width: 100%; border-left: 0; border-top: 1px solid var(--border);
		max-height: none;
	}
}

@media (max-width: 700px) {
	/* Ce code écrit ses rangées de champs en `style="display:flex; gap:12px;"`
	   dans la vue, pas en classe — plus de 200 occurrences. Sans retour à la
	   ligne, « Ville » et « Pays » deviennent deux colonnes de 70 px. Même
	   convention que la règle `[style*="display:flex"]:has(> .field)` déjà
	   présente plus haut : on vise l'attribut, puisque c'est ainsi que le
	   produit est écrit. */
	.main [style*="display:flex"] { flex-wrap: wrap; }

	/* 🐛 2026-11-11, trouvé par l'hôte sur son iPhone dans l'heure suivant la
	   mise en ligne — RÉGRESSION de la règle ci-dessus. Elle ne doit viser que
	   les rangées. Sur un conteneur en COLONNE, `flex-wrap: wrap` fait repartir
	   le contenu en une SECONDE COLONNE à côté de la première dès qu'il dépasse
	   la hauteur : sur la page Logements, les actions « Exporter / Dupliquer /
	   Supprimer » basculaient ainsi par-dessus le nom du logement et la ville,
	   le tout rogné par l'`overflow: hidden` de la carte — trois lignes
	   superposées, illisibles. 32 blocs dans 18 vues étaient dans ce cas.
	   Les deux orthographes sont nécessaires : un sélecteur d'attribut compare
	   le texte brut, et le code emploie les deux. */
	.main [style*="flex-direction:column"],
	.main [style*="flex-direction: column"] { flex-wrap: nowrap; }

	/* Et là où l'enroulement casserait un alignement voulu (icône + texte,
	   pastille + libellé) : ces blocs portent une classe, on les exempte. */
	.main .kh-status-pill[style*="display:flex"],
	.main .badge[style*="display:flex"],
	.main .kh-topbar-brand { flex-wrap: nowrap; }
}

/* ------------------------------------------------------------------
   Bas de la fiche réservation — 2026-11-13 (retour user : « trop touffu,
   organise mieux pour une meilleure visibilité »).

   Avant : NEUF cartes autonomes dans un même `flex-wrap`, chacune avec son
   titre, son paragraphe d'explication et son bouton. Hauteurs inégales, donc
   une mosaïque en escalier ; aucune hiérarchie entre « prolonger un séjour »
   et « supprimer définitivement la réservation » ; et les actions
   destructrices, en `.btn-ghost`, se lisaient comme du texte inerte au bas
   d'un pavé de texte.

   Après : deux groupes nommés et repliables — ce qu'on MODIFIE, ce qui
   ARRÊTE — en grille régulière, et des boutons destructeurs qui ressemblent
   à des boutons. Tout est porté par `.kh-actions`, jamais par `.card` ou
   `.btn-ghost` globaux : aucun autre écran n'est touché
   (cf. régressions de markup partagé déjà vues).
   ------------------------------------------------------------------ */
.kh-actions { padding: 0; margin-bottom: 12px; }
.kh-actions > summary {
	cursor: pointer;
	padding: 16px 20px;
	font-size: 14px;
	font-weight: 600;
	display: flex;
	align-items: center;
	gap: 10px;
	list-style: none;
}
.kh-actions > summary::-webkit-details-marker { display: none; }
.kh-actions > summary::before { content: '▸'; color: var(--text-muted); font-size: 12px; }
.kh-actions[open] > summary::before { content: '▾'; }
.kh-actions[open] > summary { border-bottom: 1px solid var(--border); }
.kh-actions > summary .hint { font-weight: 400; }
.kh-actions-grid {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
	gap: 24px;
	padding: 20px;
	align-items: start;
}
.kh-action { border-left: 2px solid var(--border); padding-left: 14px; }
.kh-action h3 { font-size: 13px; margin: 0 0 6px; }
.kh-action .hint { margin-bottom: 12px; display: block; }
.kh-actions-danger .kh-action { border-left-color: color-mix(in srgb, var(--danger) 45%, transparent); }
/* Un bouton irréversible ne doit pas avoir l'air d'un lien de tableau. */
.kh-actions-danger .btn-ghost.danger {
	display: inline-block;
	border: 1px solid color-mix(in srgb, var(--danger) 55%, transparent);
	border-radius: var(--radius-sm);
	color: var(--danger-fg);
	padding: 8px 14px;
	font-size: 13px;
	margin-right: 8px;
}
.kh-actions-danger .btn-ghost.danger:hover {
	background: var(--danger);
	color: var(--danger-contrast);
	text-decoration: none;
}
