/* Socle visuel Cockpit — source unique de la charte (§5.8) */

/* Chargement des polices — vendorisé en local, zéro dépendance CDN.
   Absent du contenu fourni par le brief S30 ; ajouté ici car un
   font-family ne peut être déclaré qu'ici (règle du lot) et le
   chargement doit bien vivre quelque part. Signalé au récap. */
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Light.woff2') format('woff2');
  font-weight: 300;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('../fonts/Inter-Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}
/* LOT "police et taille de la citation" (S99) — Great Vibes (cursive)
   retirée : brief explicite, "la citation actuelle est difficile à
   lire", jamais reconduite comme choix. Quatre candidates, une seule
   graisse chacune (Regular/400 — "jamais du gras", la citation se lit
   de près, pas un signal vu de loin), sous-ensemble latin étendu (même
   méthode que Inter, fonttools/pyftsubset local, jamais un CDN — même
   licence SIL OFL 1.1 vérifiée pour les quatre, textes complets dans
   web/assets/fonts/OFL-*.txt). Les quatre sont déclarées ici mais
   --font-quote (plus bas) n'en active qu'UNE seule à la fois — comme
   pour Inter-Light, une graisse déclarée mais jamais utilisée n'est
   jamais téléchargée par le navigateur. */
@font-face {
  font-family: 'Quote Literata';
  src: url('../fonts/quote/Literata-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Quote EB Garamond';
  src: url('../fonts/quote/EBGaramond-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Quote Fraunces';
  src: url('../fonts/quote/Fraunces-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

:root{
  --font-main:'Inter',-apple-system,sans-serif;
  /* REFONTE AGENDA MOBILE (08/10/2026) — police EDITORIALE : titre de
     page, nom du mois, numeros de date, titres d'evenement. Le rendu de
     reference demande Cormorant Garamond ; EB Garamond est sa cousine
     directe, DEJA embarquee ici (web/assets/fonts/quote/, sous-ensemble
     local, OFL verifiee) et deja declaree en @font-face plus haut. Pas
     un octet de plus, pas un CDN — la doctrine du produit est "jamais un
     CDN" (voir le commentaire des polices de citation) et un ecran
     ambiant ne doit pas dependre d'un tiers pour s'afficher.
     Une seule graisse (400) existe : aucune regle ne doit demander 600
     ici, le navigateur la synthetiserait. */
  --font-editorial:'Quote EB Garamond',Georgia,'Times New Roman',serif;
  /* Police de la citation (kiosk + prévisualisation Réglages) — pilotée
     par le réglage config.quote.font (LOT "police et taille de la
     citation", S99), appliquée via une classe posée sur <body>/le
     conteneur d'aperçu (web/index.html::applyConfig, web/agenda.html —
     jamais un nom de police littéral hors de CE fichier, même règle
     que --font-main). "Inter" (neutre, déjà chargée par ailleurs)
     reste le repli si la classe est absente — jamais un écran vide. */
  --font-quote:'Inter',-apple-system,sans-serif;
  /* LOT "la dernière violation" (A10, S110) — dernière police restée
     littérale. Nommée dans la même famille que --font-main/--font-quote
     ci-dessus (déjà établie) : un futur ajout se choisit en demandant
     "quel rôle de texte ?", jamais "quelle police est la plus proche ?". */
  --font-mono:ui-monospace,Consolas,monospace; /* texte technique à chasse fixe (adresse de connexion) */
  /* REFONTE AGENDA MOBILE (07/10/2026) — rôle NEUF : grand titre d'en-tête
     et en-têtes de jour de l'app téléphone (web/assets/css/agenda-mobile.css).
     Règle du socle : "un font-family ne peut être déclaré qu'ici" — jamais
     dans la feuille qui le consomme. Police SYSTÈME (Georgia), même choix
     que --font-quote (pas de @font-face : aucun fichier à charger). */
  --font-display:Georgia,'Times New Roman',serif;
  --bg:#060c18;
  --bg-lift:#0a1120;
  --bg-field:#0b1220;
  --text-primary:#f9fafb;
  --text-secondary:#94a3b8;
  /* RELEVÉ (S120 T3, seuil extérieur mesuré dans docs/diag-s119-
     comparaison-contraste.html, S119 T2 — proposé cette nuit-là, PAS
     appliqué, tranché aujourd'hui par Arnaud "mode haute visibilité").
     #7d8ba0 (5,41:1) → #95a0b1 : 7,10:1 mesuré contre le fond le plus
     défavorable (bg-field), la valeur EXACTE de la proposition
     extérieure — jamais au-delà, brief explicite ("PAS au-delà"). */
  --text-muted:#95a0b1;
  /* CORRECTIF (S119 T2, dette §C signalée S62) PUIS RELEVÉ (S120 T3,
     même seuil extérieur que ci-dessus) — mesuré : #334155 donnait
     1,81:1 à 1,89:1 selon le fond réel, très sous l'AA (texte
     réellement utilisé : .row-it-fresh, .agenda-pc-empty, .state-off,
     micro-libellés uppercase — pas seulement des états désactivés
     exemptés du SC 1.4.3). Relevé une première fois à #6680a9 (4,67:1,
     AA minimum, S119) puis à #8da1bf (7,10:1, seuil extérieur exact,
     S120) — même teinte (slate 216°) aux deux étapes, jamais un blanc. */
  --text-faint:#8da1bf;
  --gold:#c9a96e;
  /* CORRECTIF (S119 T2) PUIS RELEVÉ (S120 T3, même seuil extérieur) —
     mesuré : alpha .55 donnait 3,17:1 à 3,31:1, sous 4,5:1 pour son
     seul usage en TEXTE (.state-on, 10px — pas un "texte large" au
     sens WCAG). Alpha .70 (4,63:1, AA minimum, S119) puis .90 (6,95:1,
     seuil extérieur exact, S120) — même teinte --gold aux deux étapes.
     Effet secondaire bénin sur les deux usages en BORDURE
     (.member-pill.is-on, .field:focus) — un bord plus visible n'est
     jamais une régression. */
  --gold-dim:rgba(201,169,110,.90);
  --gold-faint:rgba(201,169,110,.16);
  --hair:rgba(255,255,255,.055);
  --green:#22c55e;
  /* RELEVÉ (S120 T3, seuil extérieur mesuré S119 T2) — #ef4444 (4,98:1,
     déjà conforme AA) → #f37070 : 6,54:1 mesuré contre bg-field, la
     valeur EXACTE de la proposition extérieure S119 — jamais au-delà.
     Même teinte (rouge), luminosité relevée seulement. */
  --red:#f37070;
  --red-dim:rgba(239,68,68,.4);
  --red-faint:rgba(239,68,68,.1);
  /* LOT "les couleurs restantes" (A10, S109) — huit tokens, chacun UNE
     valeur déjà en usage aujourd'hui, portée à un seul endroit. Zéro
     changement visuel voulu : ce ne sont pas huit arrondis pour la
     même notion (l'accumulation que l'échelle de rayons a réduite),
     ce sont des rôles distincts qui n'avaient simplement encore
     jamais de nom — deux surimpressions noires par exemple servent
     deux fins différentes (scrim de modale, ombre portée), jamais
     fusionnées pour autant. */
  --white:#fff;                      /* texte/icône plein blanc sur fond saturé (badge d'alerte, bouton de danger) */
  --black:#000;                      /* fond neutre plein noir avant peinture d'un contenu externe (aperçu d'iframe) */
  --text-stale:#eef1f8;              /* indicateur de fraîcheur discret et permanent (agenda mobile) */
  --scrim:rgba(0,0,0,.6);            /* assombrissement plein écran derrière une modale/feuille */
  --shadow:rgba(0,0,0,.4);           /* ombre portée d'un élément flottant (bouton vocal) */
  --dot-none:rgba(255,255,255,.4);   /* pastille de source/membre quand aucune couleur n'est assignée */
  /* LOT "membres du foyer et discrétion d'affichage" §1 — palette FIXE,
     jamais un sélecteur de couleur libre. Huit teintes, contraste WCAG
     vérifié ≥4.5:1 (AA) sur --bg (calcul réel, api/config-store.js::
     MEMBER_COLORS — source de vérité programmatique, ces variables sont
     la référence VISUELLE, gardées identiques aux deux endroits à la
     main). Hues espacées d'environ 35°, en évitant le voisinage de
     --gold/--red/--green : une couleur de membre ne doit jamais se lire
     comme un état du produit (danger, succès, foyer par défaut).
     Non vérifié sur un kiosque réel à trois mètres — aucun appareil
     physique disponible dans cet environnement, signalé comme limite
     (même statut que le diagnostic écran mural, S65). */
  --member-1:#c0d45e;
  --member-2:#7bd45e;
  --member-3:#5ed4c0;
  --member-4:#5ea3d4;
  --member-5:#8181da;
  --member-6:#a766d6;
  --member-7:#d666c4;
  --member-8:#d66695;
  /* LOT 24 BIS, défaut 2 — fond du badge de garde (web/agenda.html,
     .garde-badge), un par FOYER ("ici"/"ailleurs"), jamais par type
     d'événement : conforme à S99 ("les couleurs ne servent plus qu'aux
     agendas/sources"), un foyer étant une source au même titre. Ne
     remplace PAS .badge-icon (var(--hair), fond neutre) pour les autres
     badges (collecte, rappel) — seul .garde-badge en hérite, par
     sélecteur d'attribut dans mobile.css.
     Choisies dans un registre de luminosité distinct de --gold/--red/
     --green (états) ET de --member-* (plus clairs, réservés à l'identité
     d'une PERSONNE) : du bleu profond et du brun/ambre profond, assez
     sombres pour que le texte --white posé dessus atteigne l'AAA (7:1),
     pas seulement l'AA — mesure MANUELLE (formule WCAG, luminance
     relative), aucun appareil pour une mesure rendue disponible à
     l'écriture de ce lot :
       --garde-ici (#2f4e93) contre --white : L=0,0816 → 7,98:1
       --garde-ailleurs (#7a3d00) contre --white : L=0,0748 → 8,42:1
     Le même jeton sert en mode normal ET en mode haute visibilité
     (:root[data-contrast="haute-visibilite"] plus bas) : la marge
     au-dessus de 7:1 absorbe déjà l'exigence AAA de ce mode, aucune
     variante distincte n'est donc nécessaire ici (contrairement à
     --gold-dim/--red juste en dessous). */
  /* RÉVISÉ LE 09/10/2026 — « les couleurs des badges ne sont pas jolies,
     on fait simple en rose pour "M" et en bleu pour "P" » (Arnaud). Le
     brun ambre part, le bleu reste mais s'éclaircit. Chaque côté est
     maintenant un DÉGRADÉ (relief, mobile.css::.garde-badge), d'où deux
     jetons par côté au lieu d'un.
     MESURÉ (formule WCAG, luminance relative), texte --white sur le
     point le plus CLAIR du dégradé, qui est le cas défavorable :
       #2060b8 (bleu haut)  → 6,13:1     #123f7e (bleu bas)  → 10,30:1
       #c2185b (rose haut)  →  5,87:1    #8e1143 (rose bas)  →  9,09:1
     Tous au-dessus de l'AA renforcé (4,5:1), le bas dépasse l'AAA. On
     perd le 7:1 d'avant sur le haut du dégradé : c'est le prix de
     teintes franches, payé en connaissance de cause, et la lettre reste
     le porteur primaire du sens (doctrine S99) — pas la couleur.
     CONTRE LE FOND SOMBRE de la liste (#0b1b36), ces teintes ne font que
     2,9:1, sous les 3:1 d'un composant d'interface. C'est le LISERÉ
     CLAIR du relief qui porte le bord, mesuré lui aussi : 6,29:1 (rose)
     et 7,00:1 (bleu) — voir mobile.css::.garde-badge.
     LE GENRE N'EST PAS DEVINÉ : la couleur suit le CÔTÉ (ici/ailleurs),
     pas une personne. Chez un foyer où la mère utilise Cockpit, « ici »
     sera donc bleu. La convention rose/bleu n'est pas une règle du
     produit, et la lettre, elle, vient d'un libellé saisi. */
  --garde-ici:#2060b8;
  --garde-ici-bas:#123f7e;
  --garde-ailleurs:#c2185b;
  --garde-ailleurs-bas:#8e1143;
  /* LOT "police et taille de la citation" (S99) — trois crans NOMMÉS,
     jamais un nombre de pixels choisi à l'écran de réglage. Partent
     d'une valeur DISCRÈTE (celle déjà en place, clamp(13px,1.2vw,18px)
     — "rien ne change sans qu'Arnaud le décide" vaut aussi ici tant
     qu'aucun cran n'est choisi) et montent PEU (brief explicite :
     "pas un demi-point de moins" vaut dans les deux sens — un cran
     doit se voir, sans jamais devenir un gros texte pour une citation
     qui se lit de près). L'interlignage (1.6, généreux) et la graisse
     (400, jamais plus) sont FIXES, identiques aux trois crans — c'est
     l'espace autour du texte qui porte la lisibilité, brief explicite,
     pas la taille seule. */
  --quote-font-size: clamp(13px, 1.2vw, 18px);
  --quote-line-height: 1.6;
  /* LOT "taille de la grille de l'agenda" (S99) — PAR ÉCRAN, jamais
     global (contrairement à --quote-font-size ci-dessus) : posée par
     web/index.html::applyConfig depuis profiles.<slug>.agendaGridSize,
     jamais ici en dur. 1 = "normale", la taille déjà en place
     aujourd'hui (cockpit.css::.dnum/.evt-title/etc., inchangés tant
     qu'aucun cran n'est choisi). */
  --agenda-grid-scale: 1;
  /* LOT "échelle d'arrondis" (A10, S107 mesuré / S108 écrit) — cinq
     tokens, jamais huit valeurs. Nommés par le RÔLE de l'élément, pas
     par leur grandeur : un futur ajout se choisit en demandant "quel
     rôle ?", jamais "quelle valeur est la plus proche ?".
     Proposition mesurée et page de comparaison : S107 (scripts/
     diag-s107-comparaison-rayons.html, table de correspondance dans le
     récapitulatif). Validée par Arnaud sur pièces, TOUS les "après"
     sans exception, y compris les deux qui déplacent un rayon existant
     de plus de 2px (la feuille modale, la pastille de membre mobile). */
  --radius-none: 0;               /* carré assumé — jamais arrondi par erreur */
  --radius-sm: 8px;                /* grille d'agenda, badges, petits chips interactifs */
  --radius-md: 12px;               /* champs de saisie, boutons, choix, cartes/rangées, grands panneaux */
  /* REFONTE AGENDA MOBILE (08/10/2026) — troisieme cran, MESURE et
     ARBITRE, comme le demande la note ci-dessous : il vient du rendu de
     reference valide par Arnaud (maquette calibree 430x932), ou la carte
     d'evenement est nettement plus ronde qu'un champ de saisie. Ne pas
     l'etendre a d'autres composants sans le meme arbitrage. */

  /* =================================================================
     AGENDA MOBILE — LES SIX FAMILLES DE COULEUR DU RENDU DE REFERENCE
     =================================================================
     Recopiees VALEUR PAR VALEUR depuis le rendu valide par Arnaud
     (agenda_mockup_exact.html), jamais recalculees : toute tentative de
     les deriver par color-mix a donne des cartes delavees, parce que
     melanger au bleu nuit du socle eteint la teinte alors que ce jeu-ci
     l'assombrit.

     Un evenement porte une couleur de source arbitraire (un hex quelconque
     venu d'un calendrier ICS). agenda.html la rapproche de la famille la
     plus proche en teinte (agmFamilleCouleur) et pose la classe
     correspondante. Six familles, parce que six suffisent a couvrir le
     cercle chromatique sans qu'aucune ne se confonde avec sa voisine.
     ================================================================= */
  --evt-or-trait:    rgba(229,184,93,.65);
  --evt-or-a:        rgba(125,83,23,.62);
  --evt-or-b:        rgba(64,43,24,.44);
  --evt-or-icone:    #ffd982;

  --evt-violet-trait: rgba(173,114,255,.42);
  --evt-violet-a:     rgba(73,45,119,.55);
  --evt-violet-b:     rgba(28,35,55,.52);
  --evt-violet-icone: #c790ff;

  --evt-vert-trait:  rgba(69,229,173,.42);
  --evt-vert-a:      rgba(10,94,72,.52);
  --evt-vert-b:      rgba(19,51,52,.47);
  --evt-vert-icone:  #56efba;

  --evt-bleu-trait:  rgba(97,174,248,.43);
  --evt-bleu-a:      rgba(29,79,130,.54);
  --evt-bleu-b:      rgba(24,42,67,.49);
  --evt-bleu-icone:  #83c2ff;

  --evt-rose-trait:  rgba(239,99,159,.44);
  --evt-rose-a:      rgba(105,28,67,.55);
  --evt-rose-b:      rgba(53,25,45,.48);
  --evt-rose-icone:  #ff86ba;

  --evt-gris-trait:  rgba(183,194,207,.19);
  --evt-gris-a:      rgba(51,62,78,.58);
  --evt-gris-b:      rgba(24,33,47,.49);
  --evt-gris-icone:  #c5d0dc;

  --radius-lg: 17px;              /* carte d'evenement de l'agenda mobile, cellule du bandeau des jours */
  --radius-pill: 999px;            /* pastille NOMMÉE, pleinement ronde quelle que soit sa hauteur */
  --radius-full: 50%;              /* cercle parfait — points de couleur, curseurs, avatars */

  /* Espacement entre lignes d'une LISTE DE CARTES empilées (mrow-card
     et analogues) — DESSERRÉ, décision d'Arnaud sur pièces, S125 T5
     (page de comparaison scripts/diag-s125-comparaison-espacement.html
     : neuf lignes visibles deviennent huit sur activites.html, au prix
     accepté). Un seul rôle, une seule valeur — pas une échelle comme
     --radius-* tant qu'un second cran n'est mesuré et arbitré. */
  --list-row-spacing: 18px;

  /* Hauteur minimale d'une ligne balayable (S127 T1) — DE L'ESPACE,
     PAS un espacement : --list-row-spacing (ci-dessus) sépare deux
     lignes, celui-ci garantit que CHAQUE ligne, prise seule, est assez
     haute pour ne PAS TRANCHER le panneau qu'elle révèle.
     CAUSE RÉELLE, prouvée par sabotage (scripts/test-swipe-panel-
     height.js) — PAS "le bouton n'était pas assez grand" : le bouton
     du panneau est DÉJÀ forcé à 44px par la règle universelle
     `button, a, [role="button"]...` posée plus bas dans ce fichier
     (cible tactile, pointer:coarse) — TOUJOURS vraie, sabotage ou pas.
     Le défaut est que le CONTENEUR (.swipe-front, dont dépend la
     hauteur de .swipe-shell puis de .swipe-actions, S124 addendum)
     pouvait être plus BAS que ce bouton déjà correct (mesuré 22px sur
     un événement d'une ligne) : `.swipe-shell{overflow:hidden}`
     tranchait alors le bouton de 44px à la hauteur du conteneur — d'où
     le texte "Modifier"/"Supprimer" coupé à mi-hauteur constaté par
     Arnaud, jamais un bouton trop petit. Corrigé en portant le
     CONTENEUR à au moins 44px (+ une marge de confort), jamais en
     touchant le bouton, déjà juste. Deux boutons CÔTE À CÔTE : la
     contrainte porte sur la hauteur de ligne, jamais sur leur
     empilement. Posé sur .swipe-front (web/assets/css/mobile.css) —
     jamais un littéral par page, une ligne balayable future en hérite
     d'office. Origine : constat d'Arnaud sur iPhone réel, quatrième
     signalement, capture 2026-09-10 16h42. */
  --list-row-min-height: 48px;
}

/* ===== MODE HAUTE VISIBILITÉ (S120, décision d'Arnaud 2026-09-10) —
   fond noir, texte blanc, comme les applications métier d'EGDFS.
   Attribut sur la racine (<html data-contrast="haute-visibilite">),
   même patron qu'un thème : AUCUNE couleur littérale ailleurs que ce
   bloc (A10, "le mode se définit entièrement au socle") — mêmes noms
   de tokens que :root ci-dessus, jamais un second jeu de variables.
   Bascule un ATTRIBUT, jamais une classe posée par page (une seule
   source de vérité pour tout le produit, portée par mobile-nav.js).

   Noir pur / blanc pur : le maximum atteignable (21:1), et un OLED ne
   consomme rien sur du noir pur — brief explicite, pas un compromis.
   Les trois autres tiers de texte (secondaire/discret/estompé) sont
   RELEVÉS, chacun mesuré à ≥7:1 (seuil AAA) contre le noir pur — preuve :
   scripts/test-contraste-haute-visibilite.js.
   LOT 35 (décision d'Arnaud, option D) : le mode est en NOIR ET BLANC
   STRICT. Aucune couleur chromatique ne subsiste : toute couleur rendue a
   ses trois composantes RVB égales — garde-fou :
   scripts/test-haute-visibilite-noir-blanc.js. Les commandes se
   distinguent par la forme (cadre épais, graisse forte), jamais par la
   teinte. Les tailles du cran D (page de comparaison
   scripts/diag-lot34-comparaison-haute-visibilite.html, colonne D) ne
   s'appliquent qu'aux composants partagés listés plus bas ; le mode normal
   ne change jamais. */
:root[data-contrast="haute-visibilite"] {
  --bg: #000000;
  --bg-lift: #000000;
  --bg-field: #000000;
  --text-primary: #ffffff;              /* 21,00:1 */
  --text-secondary: #d0d0d0;            /* 13,62:1 */
  --text-muted: #ababab;                /* 9,14:1 */
  --text-faint: #999999;                /* 7,37:1 */
  /* LOT 35 — aucune teinte : les jetons fonctionnels deviennent des gris
     (ou du blanc) et la distinction passe par la forme. Ordre : d'abord les
     jetons, ensuite les règles ciblées plus bas, dans ce même bloc. */
  --text-stale: #ffffff;
  --gold: #ffffff;
  --gold-dim: #ffffff;
  --gold-faint: rgba(255, 255, 255, .16);
  --red: #ffffff;
  --red-dim: rgba(255, 255, 255, .40);
  --red-faint: rgba(255, 255, 255, .10);
  --green: #ffffff;
  /* Fond saturé (foyer de garde, bouton de danger, pastille d'alerte) : il
     devient BLANC, donc son texte --white devient NOIR. Aucun usage de
     --white comme fond n'existe (vérifié LOT 35). */
  --white: #000000;
  /* Cadre et filet : .055 (ancienne valeur) donnait 1,09:1 sur noir, donc
     invisible à toute épaisseur. .40 donne 3,66:1 sur noir, au-dessus du
     seuil 3:1 de WCAG 1.4.11 pour un contour de composant. */
  --hair: rgba(255, 255, 255, .40);
  /* Foyers de garde : "ici" blanc, "ailleurs" gris clair — l'initiale
     noire (.garde-badge, mobile.css) porte le sens. Gris clair : 8,03:1
     avec le texte noir (AAA). */
  --garde-ici: #ffffff;
  --garde-ici-bas: #ffffff;
  --garde-ailleurs: #a0a0a0;
  --garde-ailleurs-bas: #a0a0a0;
}

/* LOT 35 — pastilles de couleur d'événement ou de source : MASQUÉES, pas
   blanchies. Sans couleur, elles deviendraient des points identiques, c'est-
   à-dire du bruit qui n'informe plus. La pastille de garde, elle, reste : elle
   porte une initiale (S99). Classe .dot-src posée par agenda.html ; .swipe-
   info-dot posé par swipe-actions.js. */
:root[data-contrast="haute-visibilite"] .dot-src,
:root[data-contrast="haute-visibilite"] .swipe-info-dot { display: none; }

/* LOT 35 — couleurs de MEMBRE, posées EN LIGNE depuis la donnée
   (style="background:<hex>"). Une déclaration en ligne bat toute règle de
   feuille de style : seul !important la fait tomber. Exception admise et
   nécessaire ici, la spécificité seule ne suffit pas. */
:root[data-contrast="haute-visibilite"] .member-dot,
:root[data-contrast="haute-visibilite"] .member-row-dot,
:root[data-contrast="haute-visibilite"] .member-pill-dot,
:root[data-contrast="haute-visibilite"] .mmember-pill-dot,
:root[data-contrast="haute-visibilite"] .qam-member-dot,
:root[data-contrast="haute-visibilite"] .color-swatch,
:root[data-contrast="haute-visibilite"] .mcolor-swatch { background-color: var(--text-secondary) !important; }

/* LOT 35 — panneau de diagnostic (debug-net.js, ouvert à la demande) : fond
   et texte posés EN LIGNE (rgba bleuté, #e8ecf4). Même raison que plus haut :
   !important nécessaire contre une déclaration en ligne. */
:root[data-contrast="haute-visibilite"] #cockpitDebugPanel { background-color: var(--bg) !important; color: var(--text-primary) !important; }

/* LOT 35 — cran D de la page de comparaison (colonne D), appliqué aux
   composants PARTAGÉS seulement : une rangée de liste, un bouton, la pastille
   de garde, un événement d'agenda. Chaque autre écran garde sa taille. */
:root[data-contrast="haute-visibilite"] .mrows { gap: 20px; }
:root[data-contrast="haute-visibilite"] .mrow-card { padding: 22px; gap: 12px; border: 3px solid var(--hair); }
:root[data-contrast="haute-visibilite"] .mrow-card-title,
:root[data-contrast="haute-visibilite"] .agenda-evt-title,
:root[data-contrast="haute-visibilite"] .agenda-allday { font-size: 26px; font-weight: 700; line-height: 1.6; }
:root[data-contrast="haute-visibilite"] .mrow-card-sub,
:root[data-contrast="haute-visibilite"] .agenda-evt-time { font-size: 20px; font-weight: 700; line-height: 1.6; }
:root[data-contrast="haute-visibilite"] .mrow-card-sub { margin-top: 12px; }
:root[data-contrast="haute-visibilite"] .mbtn { min-height: 64px; font-size: 22px; font-weight: 700; border: 3px solid var(--text-primary); }
:root[data-contrast="haute-visibilite"] .garde-badge { width: 44px; height: 44px; font-size: 22px; font-weight: 700; line-height: 44px; }
:root[data-contrast="haute-visibilite"] .badge-icon svg,
:root[data-contrast="haute-visibilite"] .mrow-card-chevron { stroke-width: 3px; }

/* LOT 35 — actions de balayage (Modifier, Supprimer, Terminer) : leur fond
   plein (or, rouge, vert) devient blanc, donc leur texte, --text-primary
   (blanc), devient noir. Sans cette règle, texte blanc sur fond blanc. */
:root[data-contrast="haute-visibilite"] .swipe-action { color: var(--bg); }

/* LOT 35 — littéraux de socle.css qui ne passent pas par un jeton : le filet
   or du haut de page et le survol des liens (teinte dorée). */
:root[data-contrast="haute-visibilite"] .edge {
  background: linear-gradient(to right, transparent, rgba(255, 255, 255, .4) 22%, rgba(255, 255, 255, .4) 78%, transparent);
}
:root[data-contrast="haute-visibilite"] a:hover { color: var(--text-primary); }

/* LOT 35 — superposition du module de saisie rapide (quick-add-modal.js,
   feuille injectée) : fond bleuté littéral, non neutre. --scrim est déjà
   noir. */
:root[data-contrast="haute-visibilite"] .qam-overlay { background: var(--scrim); }
/* Quatre crans NOMMÉS, jamais un pourcentage tapé à la main dans
   l'écran de réglage — brief explicite : "les crans descendent
   réellement : le premier doit être nettement plus petit qu'aujourd'hui,
   pas un demi-point de moins." 0.75 (25% de moins) est net à l'œil sur
   une grille déjà en clamp() serré ; 1.15/1.3 montent sans jamais
   rivaliser avec le corps du texte de la citation ou casser la mise en
   page des cellules (mesuré visuellement avant embarquement). */
.agenda-grid-petite { --agenda-grid-scale: 0.75; }
.agenda-grid-normale { --agenda-grid-scale: 1; }
.agenda-grid-grande { --agenda-grid-scale: 1.15; }
.agenda-grid-tres-grande { --agenda-grid-scale: 1.3; }
/* LOT "taille des icônes, par écran" (S99) — cinquième réglage de la
   même fiche, PAR ÉCRAN comme --agenda-grid-scale ci-dessus, mais
   JAMAIS lié à lui : une valeur en PX fixe, pas un multiplicateur — le
   plancher de lisibilité d'une icône (18px, event-icons.js) est un
   absolu séparé du confort de lecture d'un texte, qui peut légitimement
   descendre plus bas. "normale" = 20px, la taille fixe déjà en place
   aujourd'hui (cockpit.css::.grid-day-badges .badge-icon avant ce lot)
   — défaut, aucun écran existant ne change au déploiement. */
.icon-size-petite { --icon-grid-size: 18px; }
.icon-size-normale { --icon-grid-size: 20px; }
.icon-size-grande { --icon-grid-size: 24px; }
.icon-size-tres-grande { --icon-grid-size: 28px; }
/* Une seule police active à la fois (classe posée sur <body>/le
   conteneur d'aperçu par JS, depuis config.quote.font) — jamais les
   quatre en même temps, jamais une déclaration en dur ailleurs
   (§5.8 : une seule source pour ce choix). */
.quote-font-inter { --font-quote: 'Inter', -apple-system, sans-serif; }
.quote-font-literata { --font-quote: 'Quote Literata', Georgia, serif; }
.quote-font-ebgaramond { --font-quote: 'Quote EB Garamond', Georgia, serif; }
.quote-font-fraunces { --font-quote: 'Quote Fraunces', Georgia, serif; }
.quote-size-discrete { --quote-font-size: clamp(13px, 1.2vw, 18px); }
.quote-size-moderee { --quote-font-size: clamp(14.5px, 1.3vw, 19px); }
.quote-size-confortable { --quote-font-size: clamp(16px, 1.4vw, 20px); }
*{box-sizing:border-box}
body{
  margin:0;background:var(--bg);color:var(--text-primary);
  font-family:var(--font-main);font-weight:300;letter-spacing:.01em;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--gold);text-decoration:none}
a:hover{color:#e0c48d}

/* Tranche or en haut de page — seule ornementation du socle */
.edge{height:1px;flex-shrink:0;
  background:linear-gradient(to right,transparent,rgba(201,169,110,.5) 22%,rgba(201,169,110,.5) 78%,transparent)}

/* En-tête commun à toute page non-kiosk */
/* LOT 14 — CORRECTIF (compensation d'encoche codée en dur) : ce padding
   ADDITIONNAIT 38px à env(safe-area-inset-top) — sur un iPhone à
   encoche/île dynamique (44 à 59px d'inset), le résultat allait jusqu'à
   97px de marge haute, là où la zone sûre seule suffisait déjà. 38px
   était la seule gouttière VOULUE avant l'ajout d'env() ; une fois les
   deux additionnés, l'encoche était compensée deux fois. max() consomme
   la zone sûre UNE fois : la gouttière de 38px reste pleine sur un écran
   sans encoche (env()=0), et cède la place à l'inset réel dès qu'il la
   dépasse — jamais l'inverse.
   COCKPIT:SAFE-AREA-GOUTTIERE raison="38px = gouttière VOULUE avant
   l'ajout d'env() (voir ci-dessus), jamais réduite à 12px par
   l'arbitrage du 28/09/2026 : ce dernier ne vaut que pour une valeur
   fixe ADDITIONNÉE à un inset pour dégager une barre système, pas pour
   un max() où la gouttière n'est de toute façon consommée qu'une fois
   (LOT 15 T1/T2)" */
.app-header{display:flex;align-items:baseline;gap:26px;flex-shrink:0;
  padding:max(38px, env(safe-area-inset-top, 0px)) 64px 0}
.app-back{display:flex;align-items:center;gap:10px;color:var(--text-muted);font-size:10px;
  letter-spacing:.22em;text-transform:uppercase;cursor:pointer;position:relative;top:-1px}
.app-back:hover{color:var(--gold)}
.app-title{font-size:11px;letter-spacing:.38em;text-transform:uppercase;color:var(--gold);
  font-weight:300;margin:0}
.app-spacer{flex-grow:1}
/* S41 lot 3 : un lien de retour explicite vers le kiosk, pas une icône
   d'engrenage ambiguë (« réglages » et non « voir le calendrier ») —
   même traitement visuel que .app-back, symétrique à droite. */
.app-gear{display:flex;align-items:center;gap:10px;color:var(--text-muted);font-size:10px;
  letter-spacing:.22em;text-transform:uppercase;cursor:pointer;position:relative;top:-1px}
.app-gear:hover{color:var(--gold)}

/* Onglets : un mot, un filet */
.tabs{display:flex;gap:46px;padding:34px 64px 0;flex-shrink:0}
.tab{font-size:12px;letter-spacing:.06em;color:var(--text-muted);padding-bottom:12px;
  border-bottom:1px solid transparent;cursor:pointer}
.tab:hover{color:var(--text-secondary)}
.tab.is-on{color:var(--text-primary);border-bottom-color:var(--gold)}
.tabs-rule{height:1px;background:var(--hair);margin:-1px 64px 0;flex-shrink:0}

/* Page : des sections, pas des boîtes */
/* LOT 14 — CORRECTIF, même famille que .app-header ci-dessus : 56px de
   gouttière basse ne dégage aucun élément réel (aucune barre fixe dans
   ce régime bureau/kiosk-adjacent), c'est une clôture décorative de fin
   de contenu — même pattern d'addition à la zone sûre, même correctif.
   LOT 15 T1 : reclassé MAX (confirmé, aucune barre système à dégager
   ici). LOT 15 T3 : espace RÉSERVÉ (padding), variante statique Android
   bord-à-bord (safe-area-max-inset-bottom), repli inset classique sur
   iOS.
   COCKPIT:SAFE-AREA-GOUTTIERE raison="56px = clôture décorative VOULUE
   de fin de contenu, jamais réduite à 12px : l'arbitrage du 28/09/2026
   ne vaut que pour une valeur ADDITIONNÉE à un inset pour dégager une
   barre système, pas pour ce max() (LOT 15 T1/T2)" */
.page{display:flex;flex-direction:column;
  padding:0 64px max(56px, env(safe-area-max-inset-bottom, env(safe-area-inset-bottom, 0px)))}
.sect{padding:44px 0;border-bottom:1px solid var(--hair)}
.sect:last-child{border-bottom:none}
.sect-head{display:flex;align-items:baseline;gap:22px;margin-bottom:26px}
.sect-title{font-size:10px;letter-spacing:.28em;text-transform:uppercase;color:var(--text-muted);margin:0}
.sect-note{font-size:12px;color:var(--text-muted);margin:0}
.hint{font-size:12px;color:var(--text-muted);margin:0;letter-spacing:.02em}

/* ===== Vocabulaire d'état — sain / dégradé / avertissement / jamais mesuré
   DÉPLACÉ ICI depuis admin.css le 13/10/2026, sans rien changer d'autre.
   Ces quatre classes étaient dans admin.css, c'est-à-dire chargées
   uniquement par les pages d'ORDINATEUR (menu, panel, superadmin,
   inspection, diag). La console du parc a deux volets et son volet
   téléphone charge mobile.css, pas admin.css : il écrivait « en service »
   et « arrêté » en gris, sans aucune distinction visible. Les recopier
   dans la feuille de la console en aurait fait une seconde définition de
   la même chose (§5.8) — elles descendent donc d'un étage, dans la
   feuille que TOUTES les interfaces chargent.
   Rien ne bouge pour le produit : les six pages qui chargent admin.css
   chargent socle.css d'abord, et les sélecteurs plus précis d'admin.css
   (.tbl .ev-ok, …) gardent la main là où ils existent. Placées juste
   après .hint, comme avant : un .hint.ev-ko reste rouge. */
.ev-ok{color:var(--green)}
.ev-ko{color:var(--red)}
.ev-neutre{color:var(--text-muted)}
/* AVERTISSEMENT, ni sain ni en échec : un tenant lent répond encore, un
   parc homogène dont une instance est muette n'est pas un parc cassé.
   Même teinte que le verrou du journal des connexions (.tbl .ev-lock,
   admin.css), qui dit déjà « attention » et non « erreur ». */
.ev-lock{color:var(--gold)}

/* Contrôles : rien ne crie */
.act{font-family:inherit;font-size:11px;font-weight:300;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gold);background:none;border:none;padding:0 0 5px;cursor:pointer;
  border-bottom:1px solid var(--gold-faint)}
.act:hover{border-bottom-color:var(--gold)}
.act-mute{color:var(--text-muted);border-bottom-color:var(--hair)}
.act-mute:hover{color:var(--text-secondary);border-bottom-color:rgba(255,255,255,.16)}

/* 16px minimum (S38 lot 2) : en dessous, Safari iOS zoome au focus et
   casse la mise en page — pas un choix esthétique, une contrainte iOS. */
.field{width:100%;font-family:inherit;font-size:16px;font-weight:300;color:var(--text-primary);
  background:transparent;border:none;border-bottom:1px solid var(--hair);border-radius:0;padding:9px 0}
.field::placeholder{color:var(--text-faint)}
.field:focus{outline:none;border-bottom-color:var(--gold-dim)}
/* PATCH "contrôles de formulaire hors charte" (S88), point 4 — un champ
   sur une ligne qui défile au clavier est acceptable ; un texte coupé
   SANS indication ne l'est pas (constaté : "Place de l'églis", lieu
   tronqué en aveugle une fois le champ quitté). Ellipse visible dès que
   le contenu dépasse — scope à `input.field` seulement : `.field` sert
   aussi à un `<textarea>` (localEventExdates, panel.html) qui doit
   garder son retour à la ligne, jamais cette règle. */
input.field{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* PATCH "contrôles de formulaire hors charte" (S88), point 1 — cases à
   cocher au bleu système du navigateur, hors charte (noir et or).
   Aucun composant de case à cocher n'existait déjà dans le produit
   (vérifié : aucune règle CSS `checkbox` nulle part avant ce patch) —
   celui-ci est le premier, posé ici pour que toute case du produit en
   profite d'un coup (mobile, bureau, modale partagée bureau/kiosque) :
   `accent-color` recolore la case nativement (coche + remplissage),
   sans construire un second composant qui divergerait au premier
   correctif — garde tout le comportement natif (clavier, lecteur
   d'écran, focus). */
input[type="checkbox"]{accent-color:var(--gold)}
.field:-webkit-autofill,
.field:-webkit-autofill:hover,
.field:-webkit-autofill:focus{
  -webkit-text-fill-color:var(--text-primary);
  -webkit-box-shadow:0 0 0 1000px var(--bg-field) inset;
  box-shadow:0 0 0 1000px var(--bg-field) inset;
  transition:background-color 9999s ease-in-out 0s;
}
.label{display:block;font-size:10px;letter-spacing:.24em;text-transform:uppercase;
  color:var(--text-muted);margin-bottom:4px}

.sel{display:flex;gap:34px;align-items:baseline}
.sel-it{font-size:13px;color:var(--text-muted);cursor:pointer;padding-bottom:6px;
  border-bottom:1px solid transparent}
.sel-it:hover{color:var(--text-secondary)}
.sel-it.is-on{color:var(--text-primary);border-bottom-color:var(--gold)}
.sel-add{font-size:13px;color:var(--text-faint);cursor:pointer}
.sel-add:hover{color:var(--text-muted)}

.state{font-size:10px;letter-spacing:.18em;text-transform:uppercase}
.state-on{color:var(--gold-dim)}
.state-off{color:var(--text-faint)}

/* ===== Mobile (S38 lot 2) =====
   64px de marge fixe ne laisse que 262px de contenu utile à 390px CSS
   (iPhone portrait) — resserré à 20px + zone sûre en dessous de 600px.
   Onglets : resserrés, jamais réduits/empilés/défilables (brief explicite). */
@media (max-width: 600px) {
  /* COCKPIT:SAFE-AREA-GOUTTIERE raison="20px = marge latérale resserrée
     VOULUE sous 600px (voir commentaire du bloc ci-dessus), pas une
     compensation d'encoche : marge LATÉRALE, max() est la seule formule
     correcte ici (WebKit), l'arbitrage 12px du 28/09/2026 ne s'applique
     qu'aux dégagements de barre système (LOT 15 T1/T2)" */
  .app-header{
    padding-left:max(20px, env(safe-area-inset-left, 0px));
    padding-right:max(20px, env(safe-area-inset-right, 0px));
  }
  /* COCKPIT:SAFE-AREA-GOUTTIERE raison="20px = même gouttière latérale
     que .app-header ci-dessus, même justification (LOT 15 T1/T2)" */
  .tabs{
    gap:22px;
    padding-left:max(20px, env(safe-area-inset-left, 0px));
    padding-right:max(20px, env(safe-area-inset-right, 0px));
  }
  /* COCKPIT:SAFE-AREA-GOUTTIERE raison="20px = même gouttière latérale
     que .app-header ci-dessus, même justification (LOT 15 T1/T2)" */
  .tabs-rule{
    margin-left:max(20px, env(safe-area-inset-left, 0px));
    margin-right:max(20px, env(safe-area-inset-right, 0px));
  }
  /* COCKPIT:SAFE-AREA-GOUTTIERE raison="20px = même gouttière latérale
     que .app-header ci-dessus, même justification (LOT 15 T1/T2)" */
  .page{
    padding-left:max(20px, env(safe-area-inset-left, 0px));
    padding-right:max(20px, env(safe-area-inset-right, 0px));
  }
}

/* Cibles tactiles 44px (S38 lot 2, comme lot 1) : sur pointeur grossier
   uniquement — la boîte cliquable grandit via padding/min-height, le
   survol souris (hors de ce bloc) reste exactement ce qu'il était.
   S50 — mesuré sur iPhone réel via /diag : hauteurs conformes (44 à
   48px, ce lot validé), mais .act/.tab/.sel-it restaient à 11-13px de
   police, illisibles au doigt malgré la cible tactile correcte. Le
   commentaire d'origine ("le texte garde sa taille visuelle") était une
   hypothèse jamais vérifiée sur appareil réel — corrigée ici, pas
   reconduite. */
@media (hover: none), (pointer: coarse) {
  .app-back{min-height:44px;padding:8px 0}
  .app-gear{min-height:44px;padding:8px 0}
  .tab{display:inline-flex;align-items:center;justify-content:center;min-height:44px;min-width:44px;padding-bottom:0;font-size:16px}
  .act,.act-mute{display:inline-flex;align-items:center;min-height:44px}
  .act{font-size:16px}
  .sel-it,.sel-add{display:inline-flex;align-items:center;justify-content:center;min-height:44px;min-width:44px;padding-bottom:0;font-size:16px}
  .field{min-height:44px;box-sizing:border-box}
  .mrow{min-height:44px}
  /* Le trait visuel du curseur reste fin (identité visuelle) — c'est la
     zone de saisie tactile autour qui grandit. */
  input[type="range"]{min-height:44px;padding:21px 0;box-sizing:border-box}

  /* LOT "cibles, une bonne fois, partout" (S99, décision Arnaud) —
     RÈGLE UNIQUE, jamais recopiée composant par composant : toute cible
     interactive (bouton, lien, contrôle personnalisé) fait au moins
     44×44px sur pointeur tactile/grossier. Volontairement générique
     (button/a/[role="button"]/[tabindex], PAS input/select — déjà
     couverts par .field ci-dessus avec leur propre traitement visuel,
     un champ de saisie et un bouton ne se dimensionnent pas pareil).
     min-height/min-width sont des NO-OP sur un élément resté
     display:inline (un lien de prose au fil du texte n'est donc jamais
     déformé) — ne prend effet que sur ce qui est déjà block/flex,
     exactement les boutons/icônes/chevrons/flèches visés. box-sizing
     garantit que le padding existant ne fait jamais dépasser la cible
     de son 44px minimal plutôt que de s'y additionner. */
  button, a, [role="button"], [tabindex]:not([tabindex="-1"]) {
    min-height: 44px;
    min-width: 44px;
    box-sizing: border-box;
  }
}

/* ===== Modal générique (S45 défaut 4) =====
   Coquille partagée par toute fenêtre modale du produit (point focal,
   ajout d'écran...) — jamais redéfinie localement : une modale corrigée
   ici l'est pour toutes. Desktop/souris : boîte centrée classique.
   Pointeur grossier (téléphone) : feuille pleine largeur ancrée en bas,
   marges de sécurité, jamais une boîte calibrée pour un écran de bureau
   rétrécie au milieu d'un écran de poche (mesuré illisible sur iPhone
   réel). Corps de texte 16px minimum partout dans .modal-sheet (aucun
   champ ni texte de modale en dessous — la règle iOS 16px qui empêche
   le zoom au focus, §S38 lot 2, s'applique ici aussi). */
.modal-overlay{
  position:fixed;inset:0;z-index:1000;
  background:color-mix(in srgb, var(--bg) 88%, transparent);
  display:flex;align-items:center;justify-content:center;padding:24px;
  box-sizing:border-box;
}
.modal-overlay.hidden{display:none}
.modal-sheet{
  background:var(--bg-lift);
  width:100%;max-width:480px;max-height:86vh;overflow-y:auto;
  border-radius:6px;padding:32px;box-sizing:border-box;
  display:flex;flex-direction:column;gap:20px;
  font-size:16px;line-height:1.6;color:var(--text-primary);
}
/* Étiquette d'en-tête : même langage graphique que .sect-title ailleurs
   dans le produit (10-11px, petites capitales) — jamais du corps de
   texte à lire, une convention visuelle déjà établie partout ailleurs.
   Tout le RESTE d'une modale (labels, champs, options, boutons) suit la
   règle 16px minimum ci-dessous : c'est ce que le brief S45 vise. */
.modal-title{font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--gold);margin:0}
.modal-steps{display:flex;flex-direction:column;gap:18px;margin:0;padding:0;list-style:none;counter-reset:modal-step}
.modal-steps li{display:flex;gap:16px;align-items:baseline}
.modal-steps li::before{
  counter-increment:modal-step;content:counter(modal-step);
  flex-shrink:0;width:26px;height:26px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-size:14px;color:var(--gold);border:1px solid var(--gold-dim);
  position:relative;top:-1px;
}
.modal-actions{display:flex;gap:20px;justify-content:flex-end;margin-top:auto}

/* S45 défaut 4, complété S46 — 16px minimum partout dans une modale :
   les composants partagés sont à dessein plus petits hors modale
   (densité de panel.html), mais illisibles au même corps sur un iPhone
   en main. Liste établie en énumérant CHAQUE classe réellement rendue
   dans les deux modales existantes (grep sur le HTML), pas devinée —
   .hint et .msg manquaient à l'appel S45 (mesuré en corrigeant ce lot :
   12px, sous le seuil). Spécificité délibérée (.modal-sheet en préfixe)
   : ne change rien en dehors d'une modale. */
.modal-sheet .label{font-size:16px;letter-spacing:.08em}
.modal-sheet .sel-it{font-size:16px}
.modal-sheet .act,.modal-sheet .act-mute{font-size:16px}
.modal-sheet .hint{font-size:16px}
.modal-sheet .msg{font-size:16px}

/* Variante large (S46) — le point focal a besoin de plus que 480px pour
   rester précis au clic ; tout le reste (texte, cibles, feuille pleine
   largeur sur pointeur grossier) suit .modal-sheet à l'identique. */
.modal-sheet.modal-sheet-wide{max-width:92vw}
@media (hover:none),(pointer:coarse){
  .modal-sheet.modal-sheet-wide{max-width:none}
}

@media (hover:none),(pointer:coarse){
  .modal-overlay{padding:0;align-items:flex-end}
  .modal-sheet{
    max-width:none;max-height:92vh;border-radius:14px 14px 0 0;
    /* LOT 14 — CORRECTIF bas : même famille que .app-header/.page
       ci-dessus (aucun élément réel à dégager sous cette feuille, seule
       une clôture décorative) — gauche/droite étaient déjà au bon
       patron (max()), seul le bas additionnait encore.
       LOT 15 T1 : reclassé MAX partout (confirmé — gauche/droite relèvent
       de la règle WebKit "marge latérale", le bas d'une clôture
       décorative sans barre à dégager, même conclusion que .page).
       LOT 15 T3 : seul le bas est concerné par l'inset dynamique Android
       (bord-à-bord) — gauche/droite restent sur l'inset classique.
       COCKPIT:SAFE-AREA-GOUTTIERE raison="20/24px = clôture décorative
       VOULUE de cette feuille (voir ci-dessus), jamais réduite à 12px :
       l'arbitrage du 28/09/2026 ne vaut que pour une valeur ADDITIONNÉE
       à un inset pour dégager une barre système, pas pour ces max()
       (LOT 15 T1/T2)" */
    padding:28px max(20px, env(safe-area-inset-right,0px))
      max(24px, env(safe-area-max-inset-bottom, env(safe-area-inset-bottom,0px))) max(20px, env(safe-area-inset-left,0px));
  }
  /* Boutons au pouce, en bas (brief explicite) — jamais en tête de
     modale sur pointeur grossier. */
  .modal-actions{flex-direction:column-reverse;gap:12px}
  .modal-actions .act,.modal-actions .act-mute{justify-content:center;min-height:48px}
}
