/* 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;
  /* 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) */
  --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 "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 */
  --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é) et les
   couleurs fonctionnelles (or, rouge, vert) sont RELEVÉS pour rester
   distinguables, chacun mesuré à ≥7:1 (seuil AAA) contre le noir pur —
   preuve : scripts/test-contraste-haute-visibilite.js. Aucune mise en
   page ne change ici : ce bloc ne touche QUE des couleurs. */
: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 */
  --gold-dim: rgba(201, 169, 110, .90); /* 7,64:1 — même teinte --gold, alpha relevé */
  --red: #f37272;                       /* 7,46:1 — même teinte, relevé (était 5,58:1 sur noir pur) */
}
/* 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 */
.app-header{display:flex;align-items:baseline;gap:26px;flex-shrink:0;
  padding:calc(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 */
.page{display:flex;flex-direction:column;
  padding:0 64px calc(56px + 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}

/* 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) {
  .app-header{
    padding-left:max(20px, env(safe-area-inset-left, 0px));
    padding-right:max(20px, env(safe-area-inset-right, 0px));
  }
  .tabs{
    gap:22px;
    padding-left:max(20px, env(safe-area-inset-left, 0px));
    padding-right:max(20px, env(safe-area-inset-right, 0px));
  }
  .tabs-rule{
    margin-left:max(20px, env(safe-area-inset-left, 0px));
    margin-right:max(20px, env(safe-area-inset-right, 0px));
  }
  .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;
    padding:28px max(20px, env(safe-area-inset-right,0px))
      calc(24px + 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}
}
