/* Mise en page de ChallengeMe, sur les tokens de Fluent UI v5 (#157).

   Partage des rôles : les contrôles (boutons, champs, listes, bannières, badges, indicateurs de
   chargement) sont des composants Fluent et portent leur propre style ; cette feuille ne garde que
   ce que Fluent n'a pas — la structure de l'application (en-tête, barre du pouce qui devient barre
   latérale), les cartes, l'agenda, les listes de membres, les points d'arrêt.

   Feuille partagée par l'application mobile (MAUI BlazorWebView) et l'application web
   (Blazor Server). Elle est servie sous _content/ChallengeMe.Client/app.css dans les deux cas, et
   chargée APRÈS la feuille de Fluent : à spécificité égale, c'est elle qui gagne.

   Écriture mobile-first : tout ce qui suit vaut pour un téléphone, et la section « Grand écran »
   en fin de fichier réadapte la mise en page au-delà de 768 px. Un seul jeu de composants .razor
   pour les deux plateformes — la différence est ici, pas dans le balisage. */

/* ---- Tokens ----

   Une seule source de couleurs : celles de Fluent, que son JS pose sur la racine au démarrage à
   partir des attributs du <body> (thème sombre, couleur de marque #3b82f6). Les tokens ci-dessous
   n'en sont que des alias, nommés d'après leur rôle dans CETTE mise en page.

   ⚠️ Préfixe --cm- obligatoire. Fluent injecte sur :root une feuille « requise », non désactivable,
   qui définit entre autres --success, --warning, --error et --info. Feuille adoptée par le JS, donc
   placée après les nôtres dans la cascade : à spécificité égale elle gagne, et un --success défini
   ici prendrait silencieusement la valeur de Fluent.

   La valeur de repli de chaque var() n'est pas décorative : elle couvre la première peinture,
   avant que le JS de Fluent n'ait posé ses tokens. Sans elle, la page s'afficherait un instant sur
   fond blanc — et le mobile montrerait « Chargement… » en noir sur blanc. Ce sont les valeurs du
   thème sombre de Fluent, relevées dans son JS. */

:root {
    --cm-bg: var(--colorNeutralBackground2, #1f1f1f);
    --cm-surface: var(--colorNeutralBackground1, #292929);
    --cm-surface-raised: var(--colorNeutralBackground1Hover, #3d3d3d);
    --cm-line: var(--colorNeutralStroke2, #525252);

    --cm-text: var(--colorNeutralForeground1, #ffffff);
    --cm-text-muted: var(--colorNeutralForeground3, #adadad);

    --cm-accent: var(--colorBrandForeground1, #3a7eee);
    --cm-warning: var(--colorStatusWarningForeground1, #faa06b);
    --cm-danger: var(--colorStatusDangerForeground1, #dc626d);

    --cm-radius: var(--borderRadiusXLarge, 8px);
    --gap: 12px;

    /* Largeur d'une page chronologique : agenda et fil de notifications. Une seule valeur pour les
       deux, sinon deux pages qui se lisent de la même façon finiraient à deux largeurs. */
    --feed-max: 780px;

    --header-height: 56px;
    --nav-height: 64px;
}

* {
    box-sizing: border-box;
}

html, body {
    /* L'application est sombre (data-theme="dark") : les contrôles natifs aussi — pictogramme du
       sélecteur de date, barres de défilement. */
    color-scheme: dark;
    margin: 0;
    padding: 0;
    height: 100%;
    background: var(--cm-bg);
    color: var(--cm-text);
    font-family: var(--fontFamilyBase, "Segoe UI", system-ui, -apple-system, "Helvetica Neue", sans-serif);
    /* 16 px et non les 14 px du corps de texte Fluent (Base300) : le texte courant se lit à trente
       centimètres sur un téléphone. Base400 est le cran suivant de la même échelle. */
    font-size: var(--fontSizeBase400, 16px);
    -webkit-tap-highlight-color: transparent;
}

h1, h2, h3 {
    margin: 0;
    font-weight: 600;
    line-height: 1.25;
}

h1 { font-size: 1.35rem; }
h2 { font-size: 1.05rem; }

a {
    color: var(--cm-accent);
    text-decoration: none;
}

/* Zone sûre sous l'encoche / la barre d'état iOS et Android. */
.status-bar-safe-area {
    height: env(safe-area-inset-top);
    background: var(--cm-surface);
}

/* ---- Structure ---- */

.app-shell {
    display: flex;
    flex-direction: column;
    min-height: 100%;
    align-items: center;
}

.app-header {
    display: flex;
    align-items: center;
    gap: var(--gap);
    height: var(--header-height);
    padding: 0 16px;
    background: var(--cm-surface);
    border-bottom: 1px solid var(--cm-line);
    position: sticky;
    top: 0;
    z-index: 10;
}

.app-brand {
    font-weight: 700;
    letter-spacing: -0.01em;
}

.app-content {
    flex: 1;
    padding: 16px 16px calc(var(--nav-height) + 24px);
}

/* Même plan que l'en-tête : sans z-index, la barre se peint sous tout élément du contenu qui en
   porte un — `.card-actions` (z-index 1) faisait passer les boutons des cartes par-dessus elle au
   défilement, alors que le reste de la carte glissait dessous. */
.app-nav {
    position: fixed;
    z-index: 10;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--nav-height);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    background: var(--cm-surface);
    border-top: 1px solid var(--cm-line);
    padding-bottom: env(safe-area-inset-bottom);
}

.nav-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    color: var(--cm-text-muted);
    font-size: 0.7rem;
}

.nav-item.active {
    color: var(--cm-accent);
}

/* Deux pictogrammes Fluent par destination, trait au repos et plein quand elle est courante
   (NavMenu.razor) : la classe `active` posée par NavLink choisit lequel se voit. Les icônes Fluent
   v5 prennent `currentColor`, donc la couleur de l'item. */
.nav-icon {
    display: inline-flex;
    line-height: 0;
}

.nav-icon-active,
.nav-item.active .nav-icon-rest {
    display: none;
}

.nav-item.active .nav-icon-active {
    display: inline-block;
}

/* ---- Écrans d'authentification ---- */

.auth-shell {
    min-height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.auth-card {
    width: 100%;
    max-width: 420px;
}

.auth-logo {
    display: block;
    width: 80px;
    height: 80px;
    margin: 0 auto 16px;
}

.auth-brand {
    font-size: 1.75rem;
    text-align: center;
}

.auth-tagline {
    margin: 8px 0 28px;
    text-align: center;
    color: var(--cm-text-muted);
}

.auth-title {
    margin-bottom: 16px;
}

.auth-switch {
    margin-top: 20px;
    text-align: center;
    color: var(--cm-text-muted);
}

.auth-intro {
    margin: 0 0 18px;
    color: var(--cm-text-muted);
    font-size: 0.9rem;
    line-height: 1.5;
}

/* ---- En-têtes de page ---- */

.page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap);
    margin-bottom: 12px;
}

.page-head-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.page-intro {
    margin: 0 0 16px;
    color: var(--cm-text-muted);
    line-height: 1.5;
}

.page-back {
    display: inline-block;
    margin-bottom: 16px;
    color: var(--cm-text-muted);
}

.section {
    margin: 24px 0;
}

.section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--gap);
}

.section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}

.section-hint {
    margin: 0 0 10px;
    color: var(--cm-text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
}

/* Employé comme complément d'un titre (« Carnet 2 sur 12 »), l'indice devient un élément flex :
   sa marge basse le décalerait vers le haut par rapport au reste de la ligne. */
.section-title .section-hint {
    margin: 0;
}

/* ---- Contrôles Fluent : ce que la mise en page leur demande (#157) ----

   Le style des contrôles est celui de Fluent ; on ne fixe ici que leur place. */

/* Un champ occupe la largeur de son conteneur, comme ceux d'avant : c'est le conteneur (auth-card,
   page-narrow) qui borne la saisie, pas le champ. Sans cela, un champ Fluent prend sa largeur
   intrinsèque et les formulaires deviennent une colonne de boîtes de tailles diverses. */
fluent-field,
fluent-text-input,
fluent-textarea,
fluent-dropdown {
    width: 100%;
}

/* Un seul style de libellé au-dessus d'un champ : celui de la taille Large. FluentSelect ne
   transmet pas sa taille à son FluentField (5.0.0 : attribut `size` absent, relevé dans le
   navigateur) — son libellé sortait en 14 px maigre quand ceux des champs texte voisins sont en
   16 px semi-gras. Le libellé est dans le DOM léger : une règle de la page l'atteint et l'emporte
   sur le ::slotted de Fluent. Les libellés placés après (case, interrupteur) ne sont pas visés. */
fluent-field[label-position="above"] > label[slot="label"] {
    font-size: var(--fontSizeBase400, 16px);
    font-weight: var(--fontWeightSemibold, 600);
    line-height: var(--lineHeightBase400, 22px);
}

/* Indication en petit texte : description d'un réglage (matrice des notifications), ou précision
   sous un champ. */
.field-hint {
    display: block;
    margin: 2px 0 0;
    font-size: var(--fontSizeBase200, 12px);
    line-height: var(--lineHeightBase200, 16px);
    color: var(--cm-text-muted);
}

/* Sous un champ Fluent (politique de mot de passe, quorum) : collée au champ qu'elle décrit. Le
   FluentField pose une marge basse de 12 px, que cette marge négative reprend en partie. Limitée
   à ce voisinage exprès : appliquée à toute indication, elle faisait remonter la description d'un
   réglage sur son propre titre. */
fluent-field + .field-hint {
    margin: -8px 0 12px;
}

/* Contrôle natif placé dans un FluentField — date et heure d'une session (CreateEvent, D5) : ni
   FluentDatePicker ni fluent-text-input ne portent l'attribut `min`. Il reprend l'apparence d'un
   champ Fluent « outline » de taille Large, sur les mêmes tokens, pour ne pas se distinguer à
   l'œil de ses voisins. */
.cm-native-input {
    width: 100%;
    min-height: 40px;
    padding: 0 var(--spacingHorizontalM, 12px);
    background: var(--colorNeutralBackground1, #292929);
    border: 1px solid var(--colorNeutralStroke1, #666666);
    border-bottom-color: var(--colorNeutralStrokeAccessible, #adadad);
    border-radius: var(--borderRadiusMedium, 4px);
    color: var(--colorNeutralForeground1, #ffffff);
    font-family: inherit;
    font-size: var(--fontSizeBase400, 16px);
}

.cm-native-input:hover {
    border-color: var(--colorNeutralStroke1Hover, #757575);
    border-bottom-color: var(--colorNeutralStrokeAccessibleHover, #bdbdbd);
}

/* Le soulignement de marque de Fluent au focus, sans décaler le contenu (2 px d'ombre interne au
   lieu d'un second pixel de bordure). */
.cm-native-input:focus {
    outline: none;
    border-bottom-color: var(--colorCompoundBrandStroke, #3a7eee);
    box-shadow: inset 0 -1px 0 0 var(--colorCompoundBrandStroke, #3a7eee);
}

/* Bouton d'action principal d'un formulaire ou d'un écran : pleine largeur, comme avant. */
.cm-block {
    display: flex;
    width: 100%;
    margin-top: 8px;
}

/* Ce qui suit un bouton pleine largeur ne s'y colle pas : sans cet écart, la rangée de boutons de
   la proposition d'email touchait le paragraphe suivant du profil, et la fiche trouvée par
   « Chercher » (JoinGroup) touchait le bouton. */
.cm-block + :is(p, .section-hint, .card) {
    margin-top: 12px;
}

/* Action destructrice. Fluent n'a pas d'apparence « danger » : on reprend ses propres tokens sur
   l'élément, et le shadow DOM les hérite — la couleur du texte et du contour suit, états de survol
   compris, sans toucher à sa feuille. */
.cm-danger {
    --colorNeutralForeground1: var(--cm-danger);
    --colorNeutralForeground1Hover: var(--cm-danger);
    --colorNeutralForeground1Pressed: var(--cm-danger);
    --colorNeutralForeground2: var(--cm-danger);
    --colorNeutralForeground2Hover: var(--cm-danger);
    --colorNeutralForeground2Pressed: var(--cm-danger);
    --colorNeutralStroke1: var(--cm-danger);
    --colorNeutralStroke1Hover: var(--cm-danger);
    --colorNeutralStroke1Pressed: var(--cm-danger);
}

/* Barre de message dans le flux d'une page : même respiration que les bannières d'avant. */
.cm-banner {
    margin: 12px 0;
}

/* ---- Carte de permission de notifier (#160) ----

   Posée au-dessus de la barre de navigation, hors du flux : elle est montée par Routes.razor, hors
   des layouts, et doit se voir sur le premier écran quel qu'il soit. Mobile seulement — le site n'a
   pas de push et ne l'affiche jamais. */
.cm-push-prompt {
    position: fixed;
    left: 16px;
    right: 16px;
    bottom: calc(var(--nav-height) + env(safe-area-inset-bottom) + 12px);
    z-index: 20;
    max-width: 480px;
    margin: 0 auto;
    padding: 16px;
    background: var(--cm-surface-raised);
    border: 1px solid var(--cm-line);
    border-radius: var(--cm-radius);
    box-shadow: 0 8px 24px rgb(0 0 0 / 0.4);
}

.cm-push-prompt-title {
    margin: 0 0 8px;
    font-size: 1.05rem;
}

.cm-push-prompt-text {
    margin: 0 0 12px;
    color: var(--cm-text-muted);
    font-size: 0.9rem;
}

.cm-push-prompt-actions {
    display: flex;
    gap: var(--gap);
}

.cm-push-prompt-actions > * {
    flex: 1;
}

/* ---- Formulaires ----

   Les champs, leurs libellés et leurs messages de validation sont ceux de Fluent (FluentField) :
   il ne reste ici que leur disposition. */

/* align-items: flex-start et non l'étirement par défaut : un champ suivi d'une indication (le
   quorum) est plus haut que son voisin, et le voisin étiré — un fluent-field est une grille —
   répartissait la hauteur gagnée entre ses rangées : libellé et saisie décalés d'une colonne à
   l'autre (relevé dans le navigateur). */
.field-row {
    display: flex;
    align-items: flex-start;
    gap: var(--gap);
}

/* Deux champs côte à côte (date et heure, durée et quorum) : parts égales, et min-width: 0 pour
   qu'un contrôle à largeur intrinsèque (le sélecteur natif) ne pousse pas la ligne hors de l'écran. */
.field-row > * {
    flex: 1;
    min-width: 0;
}

/* Un champ et son bouton sur une ligne (inviter par code, renommer un contact, nom affiché) : le
   champ prend la place, le bouton garde la sienne. Champ et bouton ont la même hauteur (taille
   Large, voir AddChallengeMeClient), d'où l'alignement au centre. */
.inline-form {
    display: flex;
    align-items: center;
    gap: var(--gap);
}

.inline-form > :not(fluent-button) {
    flex: 1;
    min-width: 0;
}

.inline-form > fluent-button {
    flex-shrink: 0;
}

/* ---- Codes publics ---- */

/* Code public dans l'en-tête : un bouton Fluent (contour, forme ronde) dont seul le texte change —
   chasse fixe et lettres espacées, comme partout où un code s'affiche. */
.code-chip {
    margin-left: auto;
    font-family: var(--fontFamilyMonospace, ui-monospace, "Consolas", monospace);
    letter-spacing: 0.08em;
}

/* Code cité dans une phrase : une pastille, pas un contrôle. */
.code-chip-inline {
    padding: 2px 8px;
    background: var(--cm-surface-raised);
    border: 1px solid var(--cm-line);
    border-radius: var(--borderRadiusCircular, 999px);
    color: var(--cm-text);
    font-family: var(--fontFamilyMonospace, ui-monospace, "Consolas", monospace);
    font-size: 0.8rem;
    letter-spacing: 0.08em;
}

.code-block {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 16px;
    background: var(--cm-surface-raised);
    border: 1px dashed var(--cm-line);
    border-radius: var(--cm-radius);
    color: var(--cm-text);
    font-family: var(--fontFamilyMonospace, ui-monospace, "Consolas", monospace);
    font-size: 1.3rem;
    letter-spacing: 0.16em;
    cursor: pointer;
}

.code-action {
    font-family: var(--fontFamilyBase, "Segoe UI", system-ui, sans-serif);
    font-size: 0.8rem;
    letter-spacing: normal;
    color: var(--cm-accent);
}

/* ---- Cloche de notifications ---- */

/* Le badge (FluentBadge, rouge pour les non-lues, orange pour « à traiter ») se pose sur le coin
   de la cloche : c'est l'ancre qui lui sert de repère. */
.bell {
    position: relative;
    display: inline-flex;
    padding: 4px;
    color: var(--cm-text);
    line-height: 0;
}

.bell-badge {
    position: absolute;
    top: -2px;
    right: -6px;
}

/* ---- Filtres du feed ----

   Des onglets Fluent sans panneau (Notifications.razor) : le fil, en dessous, est la seule chose
   qu'ils filtrent. Sur un téléphone étroit, les trois onglets et leurs compteurs peuvent dépasser —
   ils défilent alors horizontalement plutôt que de passer à la ligne. */
.cm-tabs {
    margin-bottom: 12px;
    overflow-x: auto;
}

/* Pictogramme de type, aligné sur le titre de la carte : dans un fil hétérogène (invitations,
   sessions, réponses), il donne la nature de la ligne avant même de la lire. */
.notif-icon {
    margin-right: 6px;
    vertical-align: -4px;
    color: var(--cm-text-muted);
}

/* ---- Cartes ---- */

.card-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--gap);
}

.card {
    position: relative;
    padding: 14px;
    background: var(--cm-surface);
    border: 1px solid var(--cm-line);
    border-radius: var(--cm-radius);
    cursor: pointer;
}

/* ---- Carte cliquable ----

   Une carte entière n'est pas un élément interactif : un `<li>` porteur d'un `@onclick` répond à
   la souris et au doigt, mais n'entre dans aucun ordre de tabulation et n'est annoncé comme rien.
   La règle de focus visible plus bas visait `.card` depuis le début — elle ne pouvait tout
   simplement jamais s'appliquer.

   Le lien est donc porté par le titre, qui est une vraie ancre : atteignable au clavier, annoncée
   comme lien, ouvrable dans un nouvel onglet. Son `::after` s'étend sur toute la carte pour que
   la cible au doigt reste la carte entière — c'est le seul rôle de la position relative ci-dessus.

   Conséquence à connaître : tout élément interactif ajouté dans une carte doit passer au-dessus de
   ce calque, sans quoi le lien mange son clic. C'est ce que fait `.card-actions`. */

.card-link {
    color: inherit;
}

/* `inherit` hériterait de l'ancre, qui n'a pas de rayon : le calque serait carré et l'anneau de
   focus dessinerait un rectangle à angles vifs sur une carte arrondie. On reprend le token. */
.card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--cm-radius);
}

/* L'anneau de focus entoure la carte, pas les quelques mots du titre : c'est la carte que la
   touche Entrée ouvre. */
.card-link:focus-visible {
    outline: none;
}

.card-link:focus-visible::after {
    outline: 2px solid var(--cm-accent);
    outline-offset: 2px;
}

.card-static {
    cursor: default;
}

.card-unread {
    border-left: 3px solid var(--cm-accent);
}

.card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}

.card-title {
    font-weight: 600;
}

.card-date {
    flex-shrink: 0;
    font-size: 0.75rem;
    color: var(--cm-text-muted);
}

.card-body {
    margin: 8px 0 0;
    color: var(--cm-text-muted);
    font-size: 0.9rem;
    line-height: 1.45;
}

.card-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
    font-size: 0.82rem;
    color: var(--cm-text-muted);
}

.card-flag {
    margin-top: 10px;
    padding: 8px 10px;
    background: var(--cm-surface-raised);
    border-radius: 8px;
    font-size: 0.82rem;
    color: var(--cm-warning);
}

/* Au-dessus du calque de `.card-link`, sans quoi le lien de la carte intercepte le clic destiné
   au bouton — le symptôme est une action qui « ne marche pas » alors qu'elle navigue. */
.card-actions {
    position: relative;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 12px;
}

/* Une carte sans action n'occupe pas de place vide : le feed en contient beaucoup (sessions
   publiées, réponses reçues) et la rangée d'actions y serait un blanc de 40 px répété. */
.card-actions:empty {
    display: none;
}

/* ---- Agenda ---- */

/* En-tête de journée. C'est le seul élément qui distingue un agenda d'une liste de cartes : sans
   lui, « aujourd'hui » et « samedi prochain » se lisent au même niveau. */
.agenda-day {
    margin: 18px 0 8px;
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--cm-text-muted);
}

/* Le premier en-tête suit soit un titre de section, soit la ligne de synthèse de l'agenda : sa
   marge haute y creuserait un trou. */
.section > .agenda-day:first-of-type,
.section-hint + .agenda-day {
    margin-top: 8px;
}

/* L'heure est l'information qu'on vient chercher sur cette page : elle ne peut pas être un détail
   gris comme la date d'une notification. Chiffres tabulaires pour que les créneaux d'une même
   journée s'alignent en colonne. */
.agenda-time {
    flex-shrink: 0;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--cm-text);
    font-variant-numeric: tabular-nums;
}

/* ---- Listes de membres ---- */

.member-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.member {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 11px 0;
    border-bottom: 1px solid var(--cm-line);
}

.member:last-child {
    border-bottom: none;
}

.member-position {
    width: 24px;
    flex-shrink: 0;
    color: var(--cm-text-muted);
    font-size: 0.8rem;
    text-align: right;
}

.member-name {
    flex: 1;
}

/* Actions d'une ligne de membre. Groupées à droite, et repliées sous le nom quand la place
   manque : la confirmation de retrait ajoute un second bouton, que la mise en colonnes des
   membres (>= 1024 px) ne peut pas tenir sur la même ligne. */
.member-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-left: auto;
    flex-shrink: 0;
}

/* ---- Détails ---- */

.details {
    margin: 0;
    padding: 0;
}

.details > div {
    display: flex;
    justify-content: space-between;
    gap: var(--gap);
    padding: 11px 0;
    border-bottom: 1px solid var(--cm-line);
}

.details dt {
    color: var(--cm-text-muted);
    font-size: 0.85rem;
}

.details dd {
    margin: 0;
    text-align: right;
}

/* ---- États vides et chargement ---- */

.empty {
    padding: 40px 20px;
    text-align: center;
}

.empty-hint {
    color: var(--cm-text-muted);
    line-height: 1.5;
}

.empty-actions {
    display: flex;
    justify-content: center;
    gap: var(--gap);
    margin-top: 20px;
}

.empty-inline {
    color: var(--cm-text-muted);
    font-size: 0.88rem;
}

/* Le cercle est un FluentSpinner (Loading.razor) : on ne fait que l'empiler au-dessus du texte. */
.loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 40px 20px;
    text-align: center;
}

.loading-text {
    margin: 0;
    color: var(--cm-text-muted);
    font-size: 0.88rem;
}

/* ---- Bandeau d'erreur Blazor ---- */

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: 14px 20px;
    /* Ce bandeau vit hors de Blazor (il annonce justement sa panne) : il ne s'appuie que sur des
       tokens, avec leurs replis, pour rester lisible même si le JS de Fluent n'a pas tourné. */
    background: var(--colorStatusWarningBackground3, #f98845);
    color: var(--colorNeutralForegroundInverted, #242424);
    box-shadow: var(--shadow8, 0 -1px 3px rgba(0, 0, 0, 0.3));
}

#blazor-error-ui .dismiss {
    position: absolute;
    top: 8px;
    right: 12px;
    cursor: pointer;
}

/* ---- Accessibilité au clavier ----

   Le mobile n'a pas de clavier physique et ne posait donc pas la question ; sur le web, une
   navigation entièrement au clavier sans anneau de focus visible est inutilisable. `:focus-visible`
   plutôt que `:focus` : l'anneau n'apparaît pas au clic à la souris ou au doigt. */

/* `.card` a quitté cette liste : la carte n'est plus focusable elle-même, c'est son lien de titre
   qui l'est, et il pose son propre anneau autour de la carte (voir « Carte cliquable »).
   Les contrôles Fluent n'y figurent pas non plus : ils dessinent leur propre anneau de focus. */
:where(a, button, .code-block):focus-visible {
    outline: 2px solid var(--colorStrokeFocus2, var(--cm-accent));
    outline-offset: 2px;
}

/* ---- Conteneurs de mise en page ----

   Trois classes posées par les pages, sans effet sur mobile : ce sont des `div` ordinaires, donc
   des blocs, et les marges des sections qu'elles contiennent se comportent exactement comme si
   elles n'existaient pas. Elles ne prennent un sens qu'aux points d'arrêt de bureau.

   .page-narrow  — page de formulaire ou de réglages : borne la largeur de saisie.
   .page-feed    — page chronologique : reste sur une colonne, bornée à --feed-max.
   .page-grid    — page de consultation : passe en deux colonnes.
   .page-grid-main / .page-grid-side — les deux colonnes de .page-grid.

   .page-feed et .card-feed disent la même chose à deux niveaux : la page, pour que le titre et les
   en-têtes de journée s'alignent sur les cartes (agenda) ; la liste seule, quand l'en-tête de page
   garde la pleine largeur (notifications, et sa rangée d'onglets). Elles partagent --feed-max pour
   que ce ne soit pas deux largeurs. À fusionner le jour où les notifications passent au conteneur
   de page.

   Elles sont volontairement posées dans un ordre du DOM qui reproduit l'empilement mobile
   d'origine. C'est le placement explicite en grille, et non l'ordre du DOM, qui répartit les blocs
   en colonnes — le mobile n'a donc rien à réapprendre. */

/* ---- Parrainage : code QR et lien d'invitation ---- */

/* Fond clair imposé, à contre-courant du thème sombre. Ce n'est pas une inconséquence : un lecteur
   de QR attend du sombre sur du clair, et le fond blanc du SVG a besoin d'une bordure de la même
   teinte pour ne pas paraître flotter sur la page. La carte *est* la zone de silence visible. */
.qr-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 16px;
    background: #ffffff;
    border-radius: var(--cm-radius);
}

/* Le SVG porte width/height, mais max-width le laisse rétrécir sur un écran étroit — sans quoi un
   code de 240 px déborde d'une carte de 200 px sur les plus petits téléphones. */
.qr {
    display: block;
    width: 100%;
    max-width: 240px;
    height: auto;
}

/* Le code en clair sous l'image : le lien reste utilisable quand la caméra ne veut pas coopérer. */
.qr-caption {
    color: #0b1526;
    font-family: ui-monospace, "SFMono-Regular", "Consolas", monospace;
    font-size: 1rem;
    letter-spacing: 0.16em;
}

/* Variante de .code-block pour une URL complète : la graisse et l'interlettrage d'un code de huit
   caractères rendent une adresse illisible et la font déborder. */
/* Le lien sous le code QR : deux blocs distincts, pas un seul. */
.qr-card + .code-block {
    margin-top: 12px;
}

.code-block-url {
    font-size: 0.9rem;
    letter-spacing: normal;
    gap: 12px;
    text-align: left;
    word-break: break-all;
}

/* Le libellé « Copier » ne doit pas se faire écraser par une URL qui coupe en fin de ligne. */
.code-block-url .code-action {
    flex: 0 0 auto;
}

.muted {
    color: var(--cm-text-muted);
}

/* ---- Matrice des canaux de notification ----

   Quatre catégories × deux canaux. Pas d'en-tête de colonnes : chaque case porte son propre
   libellé, ce qui laisse la matrice se replier en pile sur un téléphone sans qu'une ligne de titres
   parte se retrouver hors écran, loin des cases qu'elle nommait. Reprend la géométrie de .member
   (séparateurs entre lignes, actions poussées à droite) : ce sont les mêmes gestes de lecture. */

/* Le conteneur porte le décalage et, surtout, borne le `:last-child` ci-dessous : sans lui, la
   dernière ligne garderait son séparateur puisque le bouton d'enregistrement vient après elle. */
.pref-matrix {
    margin-top: 4px;
}

.pref-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 11px 0;
    border-bottom: 1px solid var(--cm-line);
}

.pref-row:last-child {
    border-bottom: none;
}

/* La base flex de 220 px est ce qui décide du repli : au-delà les cases tiennent à droite du
   libellé, en dessous elles passent sous lui. Pas de point d'arrêt à écrire — c'est la largeur
   réelle du bloc qui tranche, et cette section vit aussi dans une colonne étroite. */
.pref-text {
    flex: 1 1 220px;
    min-width: 0;
}

.pref-name {
    display: block;
}

.pref-toggles {
    display: flex;
    gap: 18px;
    margin-left: auto;
    flex-shrink: 0;
}

/* Assistants IA connectés (#271) : même ligne que les préférences — libellé qui s'étire, bouton
   qui passe dessous quand la colonne est étroite. */
.assistant-list {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
}

.assistant-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 11px 0;
    border-bottom: 1px solid var(--cm-line);
}

.assistant-row:last-child {
    border-bottom: none;
}

.assistant-text {
    flex: 1 1 220px;
    min-width: 0;
}

.assistant-name {
    display: block;
    font-weight: 600;
}

/* ---- Grand écran (>= 768 px) ----

   Le mobile n'atteint jamais ce point d'arrêt ; ces règles ne concernent en pratique que
   l'application web et la fenêtre Windows de l'application MAUI.

   Une application de téléphone affichée en grand n'est pas une application de bureau : on ne se
   contente pas d'élargir la colonne. La barre du pouce devient une barre latérale, la typographie
   remonte d'un cran, les zones de saisie cessent de s'étirer, et les pages de consultation passent
   sur deux colonnes (voir 1024 px). */

@media (min-width: 768px) {
    :root {
        --nav-width: 240px;
        --content-max: 1180px;
        --header-height: 64px;
    }

    /* Un texte lu à un bras de distance sur un écran de 24 pouces n'a pas la même taille utile
       qu'à trente centimètres sur un téléphone. */
    html, body { font-size: 16px; }
    h1 { font-size: 1.75rem; }
    h2 { font-size: 1.15rem; }

    .app-brand { font-size: 1.05rem; }
    .app-header { padding: 0 28px; gap: 16px; }

    /* La barre latérale se place sous l'en-tête, qui reste pleine largeur : la marque et le code
       public restent au même endroit que sur mobile, un seul repère à apprendre. */
    .app-nav {
        top: var(--header-height);
        right: auto;
        bottom: 0;
        width: var(--nav-width);
        height: auto;
        grid-template-columns: 1fr;
        grid-auto-rows: min-content;
        gap: 4px;
        padding: 16px 12px;
        border-top: none;
        border-right: 1px solid var(--cm-line);
    }

    .nav-item {
        flex-direction: row;
        justify-content: flex-start;
        gap: 12px;
        padding: 11px 12px;
        border-radius: var(--cm-radius);
        font-size: 0.95rem;
    }

    .nav-item:hover {
        background: var(--cm-surface-raised);
        color: var(--cm-text);
    }

    /* Même état sélectionné qu'une navigation Fluent : fond neutre relevé, texte et icône pleins
       de la marque. */
    .nav-item.active {
        background: var(--colorNeutralBackground1Selected, var(--cm-surface-raised));
        color: var(--cm-accent);
    }

    .nav-icon { font-size: 1.1rem; }

    /* La barre latérale est en position fixe : c'est le conteneur qui lui réserve sa place, et le
       contenu se centre dans ce qui reste. Sans ce déport porté par le parent, `align-items: center`
       centrait le contenu sur la **fenêtre entière** tout en lui ajoutant une marge gauche de la
       largeur de la barre : sur un écran large, le résultat était un vide de plusieurs centaines de
       pixels entre la navigation et la page, et une colonne poussée hors du centre optique. */
    .app-shell {
        align-items: stretch;
        padding-left: var(--nav-width);
    }

    /* Plus de barre basse à dégager : la réserve de padding disparaît. */
    .app-content {
        width: 100%;
        max-width: var(--content-max);
        margin: 0 auto;
        padding: 28px 32px 56px;
    }

    .page-head { margin-bottom: 20px; }
    .page-intro { max-width: 68ch; }
    .section-hint { max-width: 68ch; }

    /* Formulaires et réglages. Un champ de saisie de 1 100 px de large est illisible et donne
       l'impression que la page est vide : on borne, et la colonne reste calée à gauche plutôt que
       centrée, pour rester alignée sur le titre de la page. */
    .page-narrow { max-width: 720px; }
    .page-feed { max-width: var(--feed-max); }

    .code-block { max-width: 460px; }

    /* Le survol n'existe pas au doigt : ces retours ne sont ajoutés que là où un pointeur existe.
       Ceux des contrôles Fluent sont dans leur propre feuille. */
    .card {
        transition: border-color 0.12s ease, background 0.12s ease;
    }

    .card:not(.card-static):hover {
        border-color: var(--cm-accent);
    }

    .code-block:hover {
        border-color: var(--cm-accent);
    }
}

/* ---- Bureau (>= 1024 px) ----

   Le point d'arrêt où une seule colonne cesse d'être défendable : deux tiers de l'écran restent
   vides et l'utilisateur fait défiler pour atteindre des blocs qui tiendraient côte à côte. */

@media (min-width: 1024px) {
    /* Les listes de cartes (groupes, sessions, demandes) passent en grille. `auto-fill` plutôt
       qu'un nombre de colonnes figé : la même règle donne deux colonnes dans la zone principale
       d'une page en deux colonnes, et trois en pleine largeur. */
    .card-list {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
        align-items: start;
    }

    /* Exception : un fil de notifications se lit de haut en bas, par ordre d'arrivée. En grille,
       l'œil perd la chronologie — on garde une colonne, simplement bornée. */
    .card-feed {
        grid-template-columns: 1fr;
        max-width: var(--feed-max);
    }

    /* Les listes de personnes tiennent en colonnes dès qu'il y a de la place. `column-width`
       plutôt qu'un nombre : une liste étroite (panneau de droite) reste sur une colonne, une liste
       large en prend deux ou trois, sans règle supplémentaire. */
    .member-list {
        column-width: 240px;
        column-gap: 28px;
    }

    .member { break-inside: avoid; }

    /* Les métadonnées d'une session se lisent mieux en deux colonnes qu'en liste d'une ligne
       chacune sur toute la largeur. */
    .details {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
        column-gap: 28px;
    }

    .empty { padding: 64px 24px; }
}

/* ---- Deux colonnes (>= 1180 px) ----

   Séparé du point d'arrêt de 1024 px, et volontairement : à 1024 px, une colonne principale et un
   panneau de 340 px se partagent 740 px utiles — le « principal » finit à 337 px, soit plus étroit
   que son propre panneau latéral. La hiérarchie s'inverse alors que c'est elle qui justifie les deux
   colonnes. On attend donc d'avoir la largeur de contenu cible (1180 px) pour scinder ; en dessous,
   les pages restent empilées, ce qui se lit très bien. */

@media (min-width: 1180px) {
    /* Deux colonnes : le contenu vivant à gauche, ce qui s'y rapporte à droite.
       Le placement est explicite (grid-column), donc indépendant de l'ordre du DOM — c'est ce qui
       permet de conserver l'empilement mobile sans le réécrire. */
    .page-grid {
        display: grid;
        grid-template-columns: minmax(0, 1fr) 340px;
        /* row-gap : quand la colonne de droite est coupée en deux blocs, ils occupent deux rangées
           distinctes et se toucheraient sans cet écart — les marges internes des panneaux sont
           remises à zéro sur le dernier de chaque bloc. */
        gap: 20px 28px;
        align-items: start;
    }

    .page-grid > * { min-width: 0; }

    .page-grid-main { grid-column: 1; grid-row: 1; }
    .page-grid-side { grid-column: 2; }

    /* Quand la colonne de droite est coupée en deux blocs (l'un avant la colonne principale dans
       le DOM, l'autre après), la colonne principale doit couvrir les deux rangées — sinon la
       hauteur de la première borne la liste et pousse le second bloc trop bas. */
    .page-grid-main-tall { grid-column: 1; grid-row: 1 / span 2; }

    /* La colonne de droite devient un panneau : sans ce fond, deux colonnes de texte nu se lisent
       comme deux pages juxtaposées, sans hiérarchie. */
    .page-grid-side .section {
        margin: 0 0 20px;
        padding: 18px;
        background: var(--cm-surface);
        border: 1px solid var(--cm-line);
        border-radius: var(--cm-radius);
    }

    .page-grid-side .section:last-child { margin-bottom: 0; }
    .page-grid-side .code-block { max-width: 100%; }

    /* Une carte dans un panneau de droite : même fond que le panneau, donc invisible. On la
       remonte d'un cran de gris — c'est ce qui permet à l'agenda de mettre une vraie liste dans sa
       colonne secondaire, là où GroupDetail n'y mettait que du texte et des formulaires. */
    .page-grid-side .card {
        background: var(--cm-surface-raised);
    }

    /* La colonne fait 340 px : la grille de cartes n'y tient jamais sur deux colonnes, et
       `minmax(300px, 1fr)` y provoquerait un débordement horizontal. */
    .page-grid-side .card-list {
        grid-template-columns: 1fr;
    }

    /* Le premier bloc de chaque colonne doit démarrer à la même hauteur : sans cela, la marge
       haute de la section décale la colonne principale sous le premier panneau. */
    .page-grid-main > :first-child,
    .page-grid-main-tall > :first-child { margin-top: 0; }
}

/* ---- Très grand écran (>= 1440 px) ----

   Un seul token bouge. Au-delà de 1180 px de contenu, la ligne de texte devient trop longue pour
   être lue confortablement — mais une page en deux colonnes, elle, gagne à respirer : c'est
   l'agenda et les fiches qui profitent de ces 140 px, pas les paragraphes, qui restent bornés à
   68ch par leurs propres règles. */

@media (min-width: 1440px) {
    :root {
        --content-max: 1320px;
    }
}

/* Version installée, en pied de l'écran profil (#107). Discrète par construction : elle ne se lit
   que quand on la cherche, et l'écran ne doit pas s'ouvrir dessus. */
.app-version {
    margin: 28px 0 0;
    text-align: center;
    font-size: 0.78rem;
    color: var(--cm-text-muted);
}
