/* Deux familles auto-hébergées (public/polices/, licence OFL, sous-ensemble
   latin, ~140 ko) : la CSP n'autorise aucun hôte externe, et ce ne sont que
   des fichiers servis comme les icônes — pas une dépendance au sens du §2.
   Barlow Condensed pour tout ce qui étiquette (marque, cercle, jour, badges,
   titres de boîte), Atkinson Hyperlegible pour le contenu et les contrôles. */
@font-face {
    font-family: "Barlow Condensed";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/polices/barlow-condensed-500.woff2") format("woff2");
}
@font-face {
    font-family: "Barlow Condensed";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/polices/barlow-condensed-600.woff2") format("woff2");
}
@font-face {
    font-family: "Barlow Condensed";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/polices/barlow-condensed-700.woff2") format("woff2");
}
@font-face {
    font-family: "Atkinson Hyperlegible";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/polices/atkinson-hyperlegible-400.woff2") format("woff2");
}
@font-face {
    font-family: "Atkinson Hyperlegible";
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url("/polices/atkinson-hyperlegible-400-italic.woff2") format("woff2");
}
@font-face {
    font-family: "Atkinson Hyperlegible";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("/polices/atkinson-hyperlegible-700.woff2") format("woff2");
}

/* Direction « le casier » : graphite bleuté plutôt que noir, cuivre plutôt
   que néon — le laiton des tubes pneumatiques, pas l'écran d'un terminal. */
:root {
    color-scheme: dark;
    --fond: #1c2027;
    --fond-carte: #262b34;
    --fond-champ: #2f3540;
    --bordure: #3c434f;
    --texte: #e9e6dd;
    --texte-discret: #9aa0a8;
    --accent: #d18b4c;
    /* Une couleur par type d'item (§11, §17 phase 16) : un lien, une note et
       un fichier se distinguent au premier coup d'œil dans un flux dense,
       sans dépendre du seul texte. Le texte brut, le plus fréquent, reste
       neutre — la couleur signale l'exception, pas la norme. Les quatre sont
       personnalisables par appareil (menu ⋮, §17 phase 20) : JS pose alors la
       valeur choisie directement sur ces propriétés, ici ou en thème clair.
       --accent-sombre ne se règle jamais lui-même, il se déduit toujours de
       --accent ci-dessous, y compris pour une couleur personnalisée. */
    --accent-lien: #6aa3d8;
    --accent-note: #a98be0;
    --accent-fichier: #c9a86a;
    --accent-sombre: color-mix(in srgb, var(--accent) 78%, black);
    --erreur: #e0645a;
    --ombre: 0 10px 30px rgb(0 0 0 / 45%);
    --rayon: 6px;
    --etiquette: "Barlow Condensed", "Arial Narrow", "Roboto Condensed", sans-serif;
    --corps: "Atkinson Hyperlegible", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --mono: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
}

/* Thème clair (§11) : mêmes variables, valeurs inversées. L'accent et
   l'erreur sont assombris par rapport au thème sombre pour rester lisibles
   en texte sur fond blanc (le vert et le rouge d'origine visent un fond
   sombre et perdent trop de contraste une fois inversés) — même règle pour
   les couleurs de type ci-dessous. */
:root[data-theme="clair"] {
    color-scheme: light;
    --fond: #f2efe8;
    --fond-carte: #fbfaf7;
    --fond-champ: #e9e5dc;
    --bordure: #d3cdc0;
    --texte: #1f2329;
    --texte-discret: #5f6670;
    --accent: #a8622a;
    --accent-lien: #245f96;
    --accent-note: #6b3fb2;
    --accent-fichier: #7d5a17;
    --erreur: #b3261e;
    --ombre: 0 10px 30px rgb(40 30 10 / 15%);
}

* {
    box-sizing: border-box;
}

/* Sans cette règle, le `display` posé par une classe l'emporte sur l'attribut
   hidden, et un élément masqué en HTML reste affiché. */
[hidden] {
    display: none !important;
}

body {
    margin: 0;
    background: var(--fond);
    color: var(--texte);
    font-family: var(--corps);
    font-size: 16px;
    line-height: 1.5;
    /* Un jeton, une URL ou un nom de fichier sans espace ne doit jamais pousser
       la page hors de l'écran : sur un téléphone, on ne défile pas de côté. */
    overflow-wrap: anywhere;
}

main {
    max-width: 720px;
    margin: 0 auto;
    padding: 0 1rem 3rem;
}

.entete {
    position: sticky;
    top: 0;
    /* Au-dessus de .barre-composeur (4) et .toast/.barre-selection (5) :
       sinon le menu ⋮, qui déborde sous l'en-tête à l'ouverture, se
       retrouvait recouvert par ces éléments sticky/fixed plus bas dans la
       page malgré son propre z-index de 3 — confiné au contexte
       d'empilement de l'en-tête, il ne comptait jamais face à eux. */
    z-index: 6;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding: 0.75rem 1rem;
    background: var(--fond);
    border-bottom: 1px solid var(--bordure);
}

.titre {
    margin: 0;
    font-family: var(--etiquette);
    font-weight: 700;
    font-size: 1.55rem;
    line-height: 1;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
}

/* Vocabulaire commun à tout ce qui « étiquette » : cercle, jour, badges,
   titres de boîte, bouton principal. */
.etiquette-typo {
    font-family: var(--etiquette);
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
}

.identite {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 0;
}

.selecteur-cercle {
    position: relative;
    min-width: 0;
}

/* Rappelle en permanence dans quel cercle on écrit : deux comptes ouverts sur
   la même machine ne doivent pas pouvoir être confondus. C'est aussi le
   bouton qui ouvre la liste des cercles rejoints par cet appareil (§17 ter). */
.cercle {
    overflow: hidden;
    max-width: 12rem;
    padding: 0.15rem 0.7rem;
    color: var(--texte);
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: 999px;
    font: inherit;
    font-family: var(--etiquette);
    font-weight: 600;
    font-size: 0.95rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
    text-overflow: ellipsis;
    cursor: pointer;
}

.cercle:hover {
    border-color: var(--accent);
}

.menu-cercles {
    position: absolute;
    top: calc(100% + 0.4rem);
    left: 0;
    z-index: 3;
    min-width: 12rem;
    margin: 0;
    padding: 0.35rem;
    list-style: none;
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: 0 4px 16px rgb(0 0 0 / 40%);
}

.item-cercle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    padding: 0.45rem 0.6rem;
    color: var(--texte);
    background: none;
    border: none;
    border-radius: 6px;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
}

.item-cercle:hover {
    background: var(--fond-champ);
}

.item-cercle[disabled] {
    cursor: default;
    opacity: 0.85;
}

.separateur-menu {
    height: 1px;
    margin: 0.35rem 0.2rem;
    background: var(--bordure);
}

.actions-entete {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.5rem 0.75rem;
    min-width: 0;
}

/* Regroupe préférences d'appareil (thème, notifications) et actions de
   cercle (appairage, appareils, comptes) sous un seul bouton : affichées à
   plat, elles éclipsaient le flux lui-même sur un écran étroit. */
.menu-reglages {
    position: relative;
}

.bouton-menu {
    position: relative;
    display: flex;
    align-items: center;
    padding: 0.4rem 0.55rem;
}

/* Même dropdown que le sélecteur de cercle (§11), ouvert à droite plutôt qu'à
   gauche pour rester dans l'écran depuis un bouton collé au bord. */
.menu-actions {
    left: auto;
    right: 0;
    min-width: 13rem;
    max-height: min(28rem, calc(100vh - 5rem));
    padding: 0.35rem;
    overflow-y: auto;
}

.menu-actions .bouton {
    display: flex;
    width: 100%;
    justify-content: flex-start;
    padding: 0.5rem 0.6rem;
    background: none;
    border: none;
    border-radius: 6px;
    text-align: left;
    text-decoration: none;
}

.menu-actions .bouton:hover {
    border-color: transparent;
    background: var(--fond-champ);
}

/* Couleurs personnalisées par appareil (§11, §17 phase 20), dans le même
   menu que le thème clair/sombre dont elles reprennent les codes visuels. */
.menu-couleurs {
    padding: 0.5rem 0.6rem 0.6rem;
}

.menu-couleurs-titre {
    display: block;
    margin-bottom: 0.5rem;
    color: var(--texte-discret);
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.grille-couleurs {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.5rem 0.75rem;
    margin-bottom: 0.6rem;
}

.couleur-role {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    font-size: 0.8rem;
    color: var(--texte);
    cursor: pointer;
}

/* Le rendu natif d'un <input type="color"> varie trop d'un navigateur à
   l'autre pour rester discret sans ces overrides ; le padding à zéro évite
   une bordure interne blanche sur Chromium. */
.couleur-role input[type="color"] {
    width: 1.7rem;
    height: 1.7rem;
    padding: 0;
    background: none;
    border: 1px solid var(--bordure);
    border-radius: 6px;
    cursor: pointer;
}

#bouton-couleurs-defaut {
    width: 100%;
}

.etat {
    font-size: 0.8rem;
    color: var(--texte-discret);
}

.vue {
    padding-top: 1rem;
}

.centree {
    text-align: center;
}

.aide {
    color: var(--texte-discret);
    font-size: 0.85rem;
}

.erreur {
    color: var(--erreur);
    font-size: 0.9rem;
}

.carte {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: 320px;
    margin: 1.5rem auto;
    padding: 1.25rem;
    text-align: left;
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

label {
    font-size: 0.8rem;
    color: var(--texte-discret);
}

input,
textarea {
    width: 100%;
    padding: 0.6rem 0.7rem;
    color: var(--texte);
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    font: inherit;
    resize: vertical;
}

input:focus,
textarea:focus {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.champ-code {
    font-family: var(--mono);
    font-size: 1.6rem;
    letter-spacing: 0.4em;
    text-align: center;
}

.bouton {
    padding: 0.45rem 0.9rem;
    color: var(--texte);
    background: transparent;
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    font: inherit;
    line-height: 1.3;
    cursor: pointer;
}

.bouton:hover {
    border-color: var(--texte-discret);
}

.bouton.principal {
    padding: 0.45rem 1.2rem;
    color: #1c2027;
    background: var(--accent);
    border-color: var(--accent);
    font-family: var(--etiquette);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
}

.bouton.principal:hover {
    background: var(--accent-sombre);
    border-color: var(--accent-sombre);
}

.bouton.discret {
    font-size: 0.85rem;
    color: var(--texte-discret);
}

/* Un abonnement actif se signale par la couleur, sans occuper plus de place. */
.bouton.discret.actif {
    color: var(--accent);
    border-color: var(--accent);
}

.bouton[disabled] {
    opacity: 0.5;
    cursor: progress;
}

/* Un bouton désactivé faute d'autorisation n'attend rien : le curseur ne doit
   pas suggérer qu'un traitement est en cours. */
#bouton-notifications[disabled] {
    cursor: not-allowed;
}

.composeur {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 0.5rem 0.7rem 0.6rem;
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    transition: border-color 0.35s ease;
}

/* La fente — signature de l'interface : une ligne cuivre en haut du
   composeur, qui s'ouvre sur toute la largeur et luit au focus ou quand un
   fichier survole la page. Seul endroit animé de la page. */
.composeur::before {
    content: '';
    position: absolute;
    top: -1px;
    right: 12%;
    left: 12%;
    height: 3px;
    background: var(--accent);
    border-radius: 0 0 3px 3px;
    box-shadow: 0 0 0 0 transparent;
    transition:
        left 0.45s cubic-bezier(0.2, 0.8, 0.2, 1),
        right 0.45s cubic-bezier(0.2, 0.8, 0.2, 1),
        height 0.3s ease,
        box-shadow 0.45s ease;
}

.composeur:focus-within::before,
.composeur.ouverte::before {
    right: 0;
    left: 0;
    height: 4px;
    box-shadow: 0 0 22px 2px color-mix(in srgb, var(--accent) 45%, transparent);
}

.composeur:focus-within,
.composeur.ouverte {
    border-color: color-mix(in srgb, var(--accent) 55%, var(--bordure));
}

.composeur textarea {
    min-height: 4.2rem;
    padding: 0.7rem 0.4rem 0.3rem;
    background: transparent;
    border: 0;
    font-size: 1.05rem;
    line-height: 1.45;
}

.composeur textarea:focus {
    outline: none;
}

@media (prefers-reduced-motion: reduce) {
    .composeur,
    .composeur::before {
        transition: none;
    }
}

/* Collée au bas de l'écran visible tant que le composeur n'a pas
   entièrement défilé hors champ : sur mobile, un clavier virtuel ouvert
   réduit la hauteur visible sous la zone de saisie (3 lignes + réglages) au
   point de pousser le bouton Envoyer hors champ sans ce filet. */
.barre-composeur {
    position: sticky;
    bottom: 0;
    z-index: 4;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    padding-top: 0.5rem;
    background: var(--fond-carte);
    border-top: 1px dashed var(--bordure);
}

.actions-composeur {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}

.actions-composeur select,
.recherche select {
    max-width: 100%;
    padding: 0.35rem 0.5rem;
    color: var(--texte-discret);
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    font: inherit;
    font-size: 0.85rem;
}

.actions-composeur .bouton {
    white-space: nowrap;
}

.zone-progression {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.zone-progression .progression {
    flex: 1;
}

/* Barre plus épaisse qu'à l'origine (4px), avec un pourcentage à côté : sur
   un gros fichier, une barre trop discrète passait pour une page figée. */
.progression {
    width: 100%;
    height: 8px;
    overflow: hidden;
    border-radius: 999px;
    accent-color: var(--accent);
}

.progression::-webkit-progress-bar {
    background: var(--fond-champ);
    border-radius: 999px;
}

.progression::-webkit-progress-value {
    background: linear-gradient(90deg, var(--accent), var(--accent-lien));
    border-radius: 999px;
}

.progression::-moz-progress-bar {
    background: linear-gradient(90deg, var(--accent), var(--accent-lien));
    border-radius: 999px;
}

/* Entre la fin de l'upload réseau et la réponse serveur (miniature, écriture
   disque), la barre n'a plus de valeur à montrer : l'état indéterminé natif
   du navigateur (pas d'attribut `value`) évite qu'elle reste figée à 100 %,
   ce qui passait pour un blocage. */
.progression:not([value]) {
    accent-color: var(--texte-discret);
}

.progression:not([value])::-webkit-progress-value,
.progression:not([value])::-moz-progress-bar {
    background: var(--texte-discret);
}

.progression-texte {
    min-width: 3.2rem;
    color: var(--texte-discret);
    font-size: 0.75rem;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Le voile couvre toute la page : on doit pouvoir lâcher un fichier n'importe où. */
.voile-depot {
    position: fixed;
    inset: 0;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--fond) 82%, transparent);
    border: 3px dashed var(--accent);
    color: var(--accent);
    font-size: 1.3rem;
    pointer-events: none;
}

/* Le délai de grâce avant suppression réelle se signale ici, pas par une
   boîte de dialogue bloquante : on doit pouvoir continuer à utiliser le flux
   pendant qu'il court. */
.toast {
    position: fixed;
    left: 50%;
    bottom: 1.25rem;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    max-width: calc(100vw - 2rem);
    padding: 0.6rem 0.75rem 0.6rem 1rem;
    transform: translateX(-50%);
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: var(--ombre);
    font-size: 0.9rem;
}

.recherche {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 1.25rem 0 0;
}

.recherche input {
    flex: 1;
    min-width: 12rem;
}

.recherche select {
    color: var(--texte);
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    padding: 0 0.5rem;
    font: inherit;
}

.flux {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    margin: 0.6rem 0 0;
    padding: 0;
    list-style: none;
}

/* Historique au-delà de la première page (§17, phase 18) : centré, sous le
   flux, dans le même style discret que les autres actions secondaires. */
.bouton-historique {
    display: block;
    margin: 0.6rem auto 0;
}

.masque-recherche,
.masque-galerie,
.masque-epingles,
.masque-etiquette,
.masque-bloc {
    display: none;
}

/* Grille construite à partir des aperçus déjà chargés (§17, phase 15) — un
   item sans miniature (fichier non image, ou aperçu non généré) en est
   absent via `masque-galerie`, jamais affiché à vide dans une case. */
.flux.vue-galerie {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 0.5rem;
}

.flux.vue-galerie .item {
    padding: 0;
    overflow: hidden;
}

.flux.vue-galerie .item-fichier {
    gap: 0;
    margin: 0;
}

.flux.vue-galerie .item-apercu {
    width: 100%;
    max-height: none;
    aspect-ratio: 1;
    margin: 0;
    border-radius: 0;
}

.flux.vue-galerie .item-pied,
.flux.vue-galerie .item-fichier .aide,
.flux.vue-galerie .item-fichier .item-contenu,
.flux.vue-galerie .fichier-nom,
.flux.vue-galerie .separateur-jour,
.flux.vue-galerie .item-etiquettes {
    display: none;
}

.item {
    position: relative;
    padding: 0.7rem 0.9rem 0.6rem 1.1rem;
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    /* Liseré gauche dans la couleur du type (ci-dessous) : la seule couleur
       sur la carte, gris pour un texte brut — l'exception se signale, pas la
       norme. */
    border-left: 4px solid var(--bordure);
    border-radius: var(--rayon);
}

/* Sélection multiple (§17, phase 20) : la case reste dans le DOM en
   permanence, seule sa visibilité bascule — plus simple que d'ajouter ou
   retirer un élément par item à chaque bascule de mode. */
.case-selection {
    position: absolute;
    top: 0.75rem;
    left: 0.7rem;
    z-index: 1;
    display: none;
    /* La règle générique `input` (largeur 100 %, remplissage, bordure) est
       pensée pour les champs texte : sur une case à cocher elle produisait
       un pavé qui recouvrait tout l'item. On la neutralise explicitement. */
    width: 1.15rem;
    height: 1.15rem;
    margin: 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

.flux.mode-selection .case-selection {
    display: block;
}

.flux.mode-selection .item {
    padding-left: 2.4rem;
    cursor: pointer;
}

/* Le délai de grâce avant suppression réelle (§17, phase 12) réutilise déjà
   ce composant pour le nettoyage ; la sélection groupée s'y ajoute avec les
   mêmes codes visuels. */
.barre-selection {
    position: fixed;
    left: 50%;
    bottom: 1.25rem;
    z-index: 5;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    max-width: calc(100vw - 2rem);
    padding: 0.6rem 0.75rem;
    transform: translateX(-50%);
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: 0 4px 16px rgb(0 0 0 / 40%);
    font-size: 0.85rem;
}

.item.nouveau {
    animation: apparition 0.4s ease-out;
}

/* Repère de jour (§17, phase 18) : un ring suivi sur plusieurs semaines a
   besoin d'un point de repère absolu, pas seulement d'un horodatage relatif
   par item. N'apparaît que sur le premier item d'un jour donné — bascule
   par appliquerReperesFlux(), jamais recréé. */
.separateur-jour {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin: -0.7rem -0.9rem 0.7rem -1.1rem;
    padding: 0.3rem 0.9rem;
    color: var(--texte-discret);
    background: var(--fond);
    border-bottom: 1px solid var(--bordure);
    font-family: var(--etiquette);
    font-weight: 600;
    font-size: 0.9rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

/* Une perforation, pas un filet : le trait pointillé dit « nouveau lot ». */
.separateur-jour::before,
.separateur-jour::after {
    content: '';
    flex: 1;
    border-top: 2px dotted var(--bordure);
}

/* Envois consécutifs d'un même appareil, rapprochés dans le temps (§17,
   phase 18) : l'en-tête répété (nom d'appareil, heure) n'apporte rien à un
   ring très actif, seuls les boutons d'action restent utiles item par item. */
.item.suite {
    margin-top: -0.35rem;
}

.item.suite .item-origine {
    display: none;
}

@keyframes apparition {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: none; }
}

.item-contenu {
    margin: 0 0 0.5rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-family: inherit;
    font-size: 0.95rem;
}

.item-lien {
    color: var(--accent-lien);
}

/* Une couleur par type, au premier coup d'œil dans un flux dense — le texte
   brut, le plus fréquent, reste neutre : la couleur signale l'exception,
   pas la norme. Placées avant les états ci-dessous pour qu'épinglé/éphémère
   l'emportent toujours sur la couleur de type quand les deux s'appliquent. */
.item[data-type="note"] {
    border-left-color: var(--accent-note);
}

.item[data-type="url"] {
    border-left-color: var(--accent-lien);
}

.item[data-type="fichier"] {
    border-left-color: var(--accent-fichier);
}

.item.epingle {
    border-left-color: var(--accent);
    background: linear-gradient(
        135deg,
        color-mix(in srgb, var(--accent) 8%, var(--fond-carte)),
        var(--fond-carte) 60%
    );
}

/* Un flou purement visuel — la protection est contre un regard indiscret,
   pas contre un accès à la donnée, déjà chez le client. */
.contenu-floute {
    filter: blur(6px);
    cursor: pointer;
    user-select: none;
}

.item.revele .contenu-floute {
    filter: none;
    cursor: auto;
    user-select: text;
}

.indice-sensible {
    margin: 0 0 0.5rem;
    font-style: italic;
}

.item.revele .indice-sensible,
.item.lu-ephemere .indice-sensible {
    display: none;
}

/* Avertit avant coup : contrairement à « sensible », la lecture d'un item
   éphémère est irréversible pour le reste du cercle. */
.item.ephemere {
    border-left: 3px solid var(--erreur);
}

.item.lu-ephemere {
    opacity: 0.6;
}

/* Message ciblé (§17, phase 24) : visible seulement de l'émetteur et du
   destinataire, jamais du reste du cercle — un pointillé le distingue des
   autres accents de bordure ci-dessus, tous pleins. */
.item.cible {
    border-left: 3px dashed var(--accent-lien);
}

/* Marqueur lu/non lu : un pense-bête purement local à l'appareil (comme le
   thème, jamais envoyé au serveur ni au reste du cercle) — le texte barré
   reprend le même geste visuel que le contenu sensible révélé. */
.item.marque-lu .item-contenu,
.item.marque-lu .fichier-nom {
    color: var(--texte-discret);
    text-decoration: line-through;
}

.item-fichier {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
    margin-bottom: 0.5rem;
}

.fichier-lien {
    color: var(--accent-fichier);
    font-weight: 700;
    text-decoration: none;
    overflow-wrap: anywhere;
}

.fichier-lien:hover .fichier-nom {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* Les actions restent groupées sur une ligne, poussées à droite. */
.item-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    margin-left: auto;
    white-space: nowrap;
}

.item-fichier .item-contenu {
    margin: 0.35rem 0 0;
}

.item-apercu {
    display: block;
    max-width: 100%;
    max-height: 12rem;
    margin-bottom: 0.35rem;
    border-radius: 6px;
    object-fit: cover;
}

/* Lecture inline audio/vidéo (§17, phase 19) : au-dessus du lien de
   téléchargement, qui reste en repli (partage, appareil sans lecteur natif). */
.item-media {
    display: block;
    width: 100%;
    max-height: 16rem;
    margin-bottom: 0.35rem;
    border-radius: 6px;
}

/* Aperçu PDF inline (§17, phase 28), chargé au clic — voir le commentaire de
   elementApercuPdf() dans app.js sur l'absence d'équivalent à preload="none"
   pour un <iframe>. */
.item-pdf {
    margin-bottom: 0.35rem;
}

.item-pdf-cadre {
    display: block;
    width: 100%;
    height: 24rem;
    border: 1px solid var(--bordure);
    border-radius: 6px;
}

.item-pied {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem 0.5rem;
    font-size: 0.78rem;
    color: var(--texte-discret);
}

.marqueur-lu {
    padding: 0;
    color: var(--accent);
    background: none;
    border: none;
    font-size: 0.8rem;
    line-height: 1;
    cursor: pointer;
}

.item.marque-lu .marqueur-lu {
    color: var(--texte-discret);
}

.marqueur-lu:hover {
    color: var(--texte);
}

/* Repousse les actions à droite sur un écran large, et disparaît dès que la
   ligne se replie : sur un téléphone, un séparateur élastique ne ferait que
   pousser les boutons hors de l'écran. */
.item-pied .separateur {
    flex: 1 1 0;
    min-width: 0;
}

.item-action {
    padding: 0.15rem 0.5rem;
    color: var(--texte-discret);
    background: none;
    border: 0;
    border-radius: 4px;
    font: inherit;
    font-size: 0.78rem;
    cursor: pointer;
}

.item-action:hover,
.item-action.ouvert {
    color: var(--texte);
    background: var(--fond-champ);
}

.item-action.plus {
    font-weight: 700;
    letter-spacing: 0.1em;
}

/* Menu ⋯ d'un item : un seul élément partagé, repositionné sous le bouton
   qui l'ouvre — deux actions visibles par item, le reste ici. */
.menu-item {
    position: fixed;
    z-index: 7;
    min-width: 12rem;
    margin: 0;
    padding: 0.35rem;
    list-style: none;
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: var(--ombre);
}

.menu-item li button {
    width: 100%;
    padding: 0.4rem 0.6rem;
    color: var(--texte);
    background: transparent;
    border: 0;
    border-radius: 4px;
    font: inherit;
    font-size: 0.88rem;
    text-align: left;
    cursor: pointer;
}

.menu-item li button:hover {
    background: var(--fond-champ);
}

.menu-item li.separateur-menu {
    height: 1px;
    margin: 0.3rem 0.4rem;
    background: var(--bordure);
}

.menu-item li button.danger {
    color: var(--erreur);
}

.dialogue {
    color: var(--texte);
    background: var(--fond-carte);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: var(--ombre);
    text-align: center;
}

.dialogue h2 {
    font-family: var(--etiquette);
    font-weight: 700;
    font-size: 1.35rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
}

.dialogue::backdrop {
    background: rgb(0 0 0 / 60%);
}

/* Le QR est rendu en canvas, toujours sur fond blanc : un lecteur attend du
   sombre sur du clair, l'inverse n'est pas garanti d'être lu. */
.qr {
    display: flex;
    justify-content: center;
    margin: 0.75rem 0;
}

.qr canvas {
    max-width: 100%;
    height: auto;
    padding: 0.25rem;
    background: #fff;
    border-radius: 6px;
}

.etapes {
    margin: 1rem 0;
    padding-left: 1.2rem;
    font-size: 0.95rem;
}

.etapes li {
    margin-bottom: 0.4rem;
}

.etapes code,
.valeur {
    padding: 0.05rem 0.3rem;
    background: var(--fond-champ);
    border-radius: 4px;
    font-size: 0.85em;
}

.code-affiche {
    margin: 0.5rem 0;
    font-size: 2.6rem;
    letter-spacing: 0.3em;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
}

.dialogue.large {
    width: min(46rem, 92vw);
    text-align: left;
}

.creation-compte {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 1rem 0;
}

.creation-compte input[type="text"] {
    flex: 1 1 12rem;
    margin: 0;
}

.case {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.85rem;
    color: var(--texte-discret);
    white-space: nowrap;
}

.zone-code {
    margin: 1rem 0;
    padding: 0.75rem;
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    text-align: center;
}

.liste-comptes {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin: 1rem 0;
    padding: 0;
    list-style: none;
}

.compte {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.6rem 0.75rem;
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.compte.inactif .compte-nom {
    color: var(--texte-discret);
    text-decoration: line-through;
}

.compte-identite {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    min-width: 0;
}

.compte-nom {
    font-weight: 600;
}

.etiquette {
    padding: 0.05rem 0.4rem;
    border: 1px solid var(--bordure);
    border-radius: 999px;
    font-size: 0.7rem;
    color: var(--texte-discret);
}

.etiquette.accent {
    color: var(--accent);
    border-color: var(--accent-sombre);
}

.compte-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* Le bouton reste visible mais inerte sur son propre compte : l'interdiction
   se comprend mieux qu'une action qui disparaît. */
.item-action[disabled] {
    opacity: 0.4;
    cursor: not-allowed;
}

.item-action.danger {
    color: var(--erreur);
    border-color: var(--erreur);
}

.bouton.danger {
    color: var(--erreur);
    border-color: var(--erreur);
}

.lien-partage {
    margin: 0.5rem 0;
    overflow-wrap: anywhere;
}

.lien-partage a {
    color: var(--accent);
}

/* Sous-liste des appareils d'un compte, dépliée dans le panneau Comptes. */
.liste-appareils-compte {
    margin: 0.5rem 0 0.25rem 1.25rem;
}

/* Graphique d'activité d'un compte, déplié dans le panneau Comptes (§17, phase 17). */
.zone-activite {
    width: 100%;
    margin: 0.5rem 0 0.25rem 1.25rem;
}

.graphique-svg {
    display: block;
    width: 100%;
    height: 90px;
}

.graphique-aire {
    fill: rgb(131 183 53 / 18%);
    stroke: none;
}

.graphique-ligne {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
}

.graphique-legende {
    display: flex;
    justify-content: space-between;
    margin-top: 0.15rem;
}

/* Édition inline d'un item (§17, phase 20) : remplace le paragraphe de
   contenu, sans dialogue bloquante. */
.edition-contenu {
    margin-bottom: 0.4rem;
}

.edition-actions {
    display: flex;
    gap: 0.5rem;
    margin: 0 0 0.6rem;
}

/* Étiquettes affichées sur un item, et panneau de sélection ouvert par le
   bouton « Étiquettes » du pied (§17, phase 20). Non exclusives : un item
   peut en porter plusieurs à la fois, contrairement au bloc. */
.item-etiquettes {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin: 0 0 0.5rem;
}

.chip-etiquette {
    padding: 0.05rem 0.45rem;
    border-radius: 999px;
    color: #14210a;
    font-size: 0.7rem;
    font-weight: 600;
}

.panneau-etiquettes {
    margin-top: 0.5rem;
    padding: 0.5rem 0.6rem;
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.case-etiquette {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0;
    color: var(--texte);
    font-size: 0.82rem;
}

.case-etiquette input {
    width: auto;
}

.pastille-couleur {
    display: inline-block;
    width: 0.7rem;
    height: 0.7rem;
    border-radius: 50%;
}

.suppr-etiquette {
    margin-left: auto;
    padding: 0 0.3rem;
    color: var(--texte-discret);
    background: none;
    border: none;
    font-size: 0.95rem;
    cursor: pointer;
}

.suppr-etiquette:hover {
    color: var(--erreur);
}

.creation-etiquette-inline {
    display: flex;
    gap: 0.4rem;
    margin-top: 0.4rem;
}

.creation-etiquette-inline input {
    flex: 1;
    min-width: 0;
    padding: 0.35rem 0.5rem;
    font-size: 0.82rem;
}

/* Sans ça, le bouton se partage l'espace avec le champ à parts égales et son
   libellé finit par passer à la ligne dès que le panneau se resserre — le
   champ doit être le seul à céder de la place. */
.creation-etiquette-inline button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Chip du bloc d'un item, et poignée de glisser-déposer vers la rangée de
   blocs au-dessus du flux (§17, phase 20) — un handle dédié, plutôt que
   l'item entier, pour ne pas gêner la sélection de texte à la souris. */
.chip-bloc-item,
.badge-epingle,
.badge-rappel {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.05rem 0.5rem;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-family: var(--etiquette);
    font-weight: 600;
    font-size: 0.78rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
}

.chip-bloc-item {
    color: var(--texte-discret);
}

.badge-epingle {
    color: var(--accent);
}

.poignee-glisser {
    color: var(--texte-discret);
    cursor: grab;
}

.poignee-glisser:active {
    cursor: grabbing;
}

.rangee-blocs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin: 0.75rem 0 0;
}

.chip-bloc {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.7rem;
    color: var(--texte);
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-radius: 999px;
    font: inherit;
    font-size: 0.78rem;
    cursor: pointer;
}

.chip-bloc.actif {
    border-color: var(--accent);
    color: var(--accent);
}

/* Retour visuel pendant qu'un item glissé survole un bloc cible. */
.chip-bloc.survole-depot {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent);
}

.chip-bloc-suppr {
    color: inherit;
    opacity: 0.55;
    font-size: 0.9rem;
    line-height: 1;
}

.chip-bloc-suppr:hover {
    opacity: 1;
    color: var(--erreur);
}

.chip-bloc-nouveau-forme {
    display: flex;
}

.chip-bloc-champ {
    width: 9rem;
    padding: 0.3rem 0.7rem;
    background: var(--fond-champ);
    border: 1px dashed var(--bordure);
    border-radius: 999px;
    font-size: 0.78rem;
}

@media (prefers-reduced-motion: reduce) {
    .item.nouveau { animation: none; }
}

/* Téléphones. Tout ce qui est en ligne se replie, et les éléments à taille fixe
   — code d'appairage, dialogues — passent en unités relatives à l'écran. */
@media (max-width: 30rem) {
    main {
        padding: 0 0.75rem 3rem;
    }

    .entete {
        padding: 0.6rem 0.75rem;
    }

    .actions-entete {
        flex: 1 1 100%;
        justify-content: space-between;
    }

    .cercle {
        max-width: 8rem;
    }

    .bouton {
        padding: 0.4rem 0.6rem;
        font-size: 0.85rem;
    }

    .carte {
        max-width: none;
        padding: 1rem;
    }

    .champ-code {
        font-size: 1.3rem;
        letter-spacing: 0.25em;
    }

    .dialogue {
        width: calc(100vw - 1.5rem);
        margin: auto;
        padding: 1rem;
    }

    .dialogue.large {
        width: calc(100vw - 1.5rem);
    }

    .code-affiche {
        font-size: 2rem;
        letter-spacing: 0.15em;
    }

    .compte {
        flex-direction: column;
        align-items: flex-start;
    }

    /* La case Sensible/Éphémère et le délai d'expiration forment un groupe
       d'options ; Joindre/Envoyer sont l'action principale. Séparer les deux
       en lignes distinctes évite l'écrasement du sélecteur observé sur
       téléphone quand tout tient sur une seule ligne non pliable. */
    #champ-expiration,
    #champ-destinataire,
    #champ-rappel {
        flex: 1 1 100%;
    }

    .actions-composeur .bouton {
        flex: 1;
    }

    .actions-composeur .bouton.principal {
        flex: 2;
    }

    /* L'état de connexion n'a pas la place à côté du titre et du cercle. */
    .etat {
        display: none;
    }

    .actions-entete {
        flex: 0 0 auto;
    }

    .titre {
        font-size: 1.3rem;
    }

    /* Même repli que la barre du composeur : sur un écran étroit, le champ
       de recherche prend toute la largeur et le reste passe en dessous
       plutôt que de s'écraser sur une seule ligne. */
    .recherche input {
        flex: 1 1 100%;
    }
}

/* Rappels (§17, phase 33) --------------------------------------------- */

.champ-rappel,
#champ-rappel-dialogue {
    width: auto;
    padding: 0.35rem 0.5rem;
    font-size: 0.85rem;
}

#champ-rappel-dialogue {
    width: 100%;
    margin: 0.75rem 0 0.5rem;
    font-size: 1rem;
}

.raccourcis-rappel {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

.actions-dialogue {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 0.75rem;
}

.badge-rappel {
    color: var(--accent-note);
}

.badge-rappel .ico {
    flex: none;
}

.badge-rappel.passe {
    color: var(--texte-discret);
    border-style: dashed;
}

/* Un rappel qui vient de sonner se signale dans le flux quelques secondes,
   puis redevient un item comme les autres — le badge « passé » suffit ensuite. */
.item.rappel-sonne {
    animation: rappel-pulsation 1.2s ease-out 3;
}

@keyframes rappel-pulsation {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-note) 70%, transparent); }
    100% { box-shadow: 0 0 0 0.6rem transparent; }
}

/* Bloc d'amorçage d'un assistant IA (§17, phase 34) : il est fait pour être
   copié en entier, pas relu ligne à ligne — il défile plutôt que de se
   réenrouler, un retour à la ligne au mauvais endroit couperait une commande
   en deux dans le presse-papiers de la personne qui la relit. */
.bloc-ia {
    margin: 0.5rem 0 0.9rem;
    padding: 0.9rem 1rem;
    overflow-x: auto;
    background: var(--fond-champ);
    border: 1px solid var(--bordure);
    border-left: 3px solid var(--accent);
    border-radius: 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.82rem;
    line-height: 1.55;
    white-space: pre;
    tab-size: 4;
}

.liste-commandes {
    list-style: none;
    padding-left: 0;
}

.liste-commandes li {
    margin-bottom: 0.5rem;
}

/* Réglages du relais SMTP (§17, phase 36) : un formulaire dense, où chaque
   champ a besoin de son libellé au-dessus — la mise en ligne du composeur ne
   conviendrait pas ici, on remplit ça une fois puis on n'y revient plus. */
.formulaire-reglages {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.formulaire-reglages label {
    margin-top: 0.5rem;
    font-family: var(--etiquette);
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--texte-discret);
}

.formulaire-reglages label.case {
    margin-top: 0.9rem;
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.9rem;
    color: inherit;
}

.formulaire-reglages .aide {
    margin: 0.15rem 0 0;
}

.ligne-test-email {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

.ligne-test-email input {
    flex: 1 1 14rem;
}

/* Invitations en attente (§17, phase 38) : une liste courte, sous le champ
   d'envoi — elles vivent quinze minutes, il n'y en a jamais beaucoup. */
.liste-invitations {
    margin: 0.6rem 0 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
}

.liste-invitations li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    font-size: 0.85rem;
    color: var(--texte-discret);
}

/* Pastille de demandes d'adhésion (§17, phase 39).

   Deux formes pour un même compteur : un point sur l'engrenage, seul endroit
   visible quand le menu est replié, et le nombre sur l'entrée « Rings » une
   fois ce menu ouvert. Sans le point, le signal n'existerait que pour qui
   ouvre déjà le menu — c'est-à-dire pour personne. */
.pastille {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.15rem;
    height: 1.15rem;
    margin-left: 0.4rem;
    padding: 0 0.3rem;
    border-radius: 999px;
    background: var(--accent);
    color: var(--fond);
    font-family: var(--etiquette);
    font-size: 0.7rem;
    font-weight: 600;
    line-height: 1;
}

.bouton-menu .pastille {
    position: absolute;
    top: 0.1rem;
    right: 0.1rem;
    min-width: 0.5rem;
    width: 0.5rem;
    height: 0.5rem;
    margin: 0;
    padding: 0;
}

/* Motif d'une demande : du texte d'un tiers, cantonné à sa propre ligne pour
   qu'on voie tout de suite où il commence et où il s'arrête. */
.demande-motif {
    flex-basis: 100%;
    margin: 0.2rem 0 0;
    padding-left: 0.6rem;
    border-left: 2px solid var(--bordure);
    color: var(--texte-discret);
    font-size: 0.85rem;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* Item coffre (§17, phase 41) : rien n'est affiché tant qu'on n'a pas
   déverrouillé — pas même une enveloppe tronquée, qui donnerait l'illusion
   d'un contenu à moitié lisible. */
.contenu-coffre {
    display: flex;
}

.bouton-coffre {
    border-style: dashed;
    font-family: var(--etiquette);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}
