/* notask — Material Design 3, thème sombre.
   Palette dérivée du violet de référence MD3 ; niveaux de surface obtenus
   par superposition tonale, comme le prescrit la spécification. */

@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');

/* Mode niveaux de gris — voir le toggle dans app.js (#btn-mode-gris,
   classe html.mode-gris). PAS un simple `filter` sur <html> : un filter
   CSS s'applique à TOUT le sous-arbre d'un élément et aucun filter posé
   sur un descendant ne peut l'annuler (limitation CSS, pas un bug d'ici) —
   impossible dans ce modèle de garder un enfant en couleur si un ancêtre
   est grisé. Le gris est donc posé BRANCHE PAR BRANCHE, en excluant à
   chaque niveau l'ancêtre direct de ce qui doit rester en couleur (logo/
   titre, icônes du menu, icône de chaque notask) plutôt que sur <html>
   entier. Étendre cette liste à un nouvel élément à garder en couleur
   veut dire remonter sa chaîne de parents et exclure chacun d'eux ici,
   pas juste ajouter une règle sur l'élément lui-même. */

/* En-tête : logo (cuillères) et titre "notask" restent en couleur ; le
   reste (pseudo, bouton bascule, Options, Outils) grisé directement. */
html.mode-gris header.topbar > *:not(.logo):not(.brand):not(#btn-mode-gris) { filter: grayscale(1); }

/* Menu latéral : icônes SVG des entrées (Notasks, Prévues, Favoris,
   Archives, Corbeille) restent en couleur ; leur texte, les libellés et
   séparateurs sont grisés. .drawer et .drawer-notes-group eux-mêmes ne
   reçoivent JAMAIS le filter (ce sont des ancêtres des icônes protégées),
   seuls leurs autres enfants directs le reçoivent. */
html.mode-gris .drawer > *:not(.drawer-notes-group) { filter: grayscale(1); }
html.mode-gris .drawer-item > *:not(svg) { filter: grayscale(1); }

/* Reste de la coquille (fond du menu mobile) : grisé en bloc. La colonne
   d'échéances (.agenda-col) et la vue "notasks prévues" (#view-tasks) sont
   traitées séparément pour préserver les icônes colorées. .shell et main
   ne reçoivent pas le filter (ancêtres protégés). */
html.mode-gris .shell > *:not(aside.drawer):not(main):not(.agenda-col) { filter: grayscale(1); }
html.mode-gris main > *:not(#view-notes):not(#view-tasks) { filter: grayscale(1); }
html.mode-gris #view-notes > *:not(.notes-grid) { filter: grayscale(1); }
html.mode-gris .notes-grid > *:not(.note) { filter: grayscale(1); }

/* Carte de notask : seule l'icône (.note-icon) reste en couleur, ainsi que
   le contenu textuel (.body — texte coloré, liens jaunes, etc.) ; le titre,
   les pièces jointes, la palette et les actions passent en gris. */
html.mode-gris .note > *:not(.note-title-row):not(.body) { filter: grayscale(1); }
html.mode-gris .note-title-row > *:not(.note-icon) { filter: grayscale(1); }
/* Fond coloré de la carte (posé par .c-XX directement sur .note) : copié
   sur ::before (background: inherit) plutôt que grisé sur .note lui-même
   — .note reste un ancêtre protégé de .note-icon. ::before, lui, n'a
   aucun descendant : le griser ne touche à rien d'autre. Voir la règle
   .note::before plus bas (hors bloc mode-gris) pour la mise en place. */
/* Un simple grayscale() donnait des gris quasi identiques pour toutes
   les catégories : la palette est volontairement construite avec des
   luminances proches entre couleurs (voir le commentaire au-dessus de
   .c-default plus bas), pour un contraste de texte homogène — un
   filtre fondé sur la luminance ne peut donc pas les distinguer.
   Remplacé par un fond gris EXPLICITE par catégorie, réparti en 6
   paliers du plus clair au plus foncé, 4 catégories par palier. */
html.mode-gris .note.c-white::before,
html.mode-gris .note.c-default::before,
html.mode-gris .note.c-red::before,
html.mode-gris .note.c-coral::before {
  background: #707070;
}
html.mode-gris .note.c-orange::before,
html.mode-gris .note.c-amber::before,
html.mode-gris .note.c-yellow::before,
html.mode-gris .note.c-lime::before {
  background: #5c5c5c;
}
html.mode-gris .note.c-green::before,
html.mode-gris .note.c-emerald::before,
html.mode-gris .note.c-teal::before,
html.mode-gris .note.c-cyan::before {
  background: #484848;
}
html.mode-gris .note.c-blue::before,
html.mode-gris .note.c-indigo::before,
html.mode-gris .note.c-violet::before,
html.mode-gris .note.c-purple::before {
  background: #363636;
}
html.mode-gris .note.c-magenta::before,
html.mode-gris .note.c-pink::before,
html.mode-gris .note.c-rose::before,
html.mode-gris .note.c-brown::before {
  background: #262626;
}
html.mode-gris .note.c-slate::before,
html.mode-gris .note.c-grey::before,
html.mode-gris .note.c-navy::before,
html.mode-gris .note.c-olive::before {
  background: #161616;
}

/* Colonne d'échéances (accueil, .agenda-col) et vue "notasks prévues"
   (#view-tasks) : les .agenda-item-icon (pastilles colorées) restent en
   couleur — même principe que .note-icon pour les cartes. Texte, dates et
   cases sont grisés en ciblant les enfants directs de .agenda-item sauf
   l'icône. Le fond coloré de la ligne (.agenda-item background inline) reste
   visible, comme le fond de la carte note. */
html.mode-gris .agenda-col .agenda-empty,
html.mode-gris #view-tasks .empty { filter: grayscale(1); }
html.mode-gris .agenda-col .agenda-group > h2,
html.mode-gris #view-tasks .agenda-group > h2 { filter: grayscale(1); }
html.mode-gris .agenda-col .agenda-item,
html.mode-gris #view-tasks .agenda-item { background: transparent !important; }
html.mode-gris .agenda-col .agenda-item::before,
html.mode-gris #view-tasks .agenda-item::before { background: #3d3d3d; }
html.mode-gris .agenda-col .agenda-item > *:not(.agenda-item-icon),
html.mode-gris #view-tasks .agenda-item > *:not(.agenda-item-icon) { filter: grayscale(1); }

/* Boîtes de dialogue (édition, profil, outils, historique…) : hors de la
   chaîne ci-dessus (siblings de #screen-app), grisées en bloc. */
html.mode-gris body > dialog { filter: grayscale(1); }

:root {
  /* Couleurs principales */
  --md-primary: #d0bcff;
  --md-on-primary: #381e72;
  --md-primary-container: #4f378b;
  --md-on-primary-container: #eaddff;

  --md-secondary: #ccc2dc;
  --md-secondary-container: #4a4458;
  --md-on-secondary-container: #e8def8;

  --md-error: #f2b8b5;
  --md-on-error: #601410;
  --md-error-container: #8c1d18;

  /* Surfaces : du plus sombre au plus clair (élévation croissante) */
  --md-surface: #141218;
  --md-surface-1: #1d1b20;
  --md-surface-2: #211f26;
  --md-surface-3: #2b2930;
  --md-surface-4: #322f37;
  --md-surface-5: #36343b;

  --md-on-surface: #e6e0e9;
  --md-on-surface-variant: #cac4d0;
  --md-outline: #938f99;
  --md-outline-variant: #49454f;

  --md-success: #b5ccba;

  /* Couleurs de marque — les deux cuillères du logo. Déclarées ici et non
     réécrites en dur dans les règles : elles servaient à 44 endroits, ce qui
     rendait tout changement de teinte incomplet par construction (un oubli
     sur une règle passe inaperçu jusqu'à ce qu'on tombe dessus à l'écran).
     Les variantes `-rgb` portent les composantes séparées, seule forme
     utilisable dans un rgba() dont on veut faire varier l'alpha :
     `rgba(var(--spoon-jaune-rgb), .18)`. Garder les deux en phase. */
  --spoon-jaune: #ffd54f;
  --spoon-jaune-rgb: 255, 213, 79;
  --spoon-bleu: #42a5f5;
  --spoon-bleu-rgb: 66, 165, 245;

  /* Formes — MD3 privilégie des rayons généreux */
  --shape-xs: 8px;
  --shape-sm: 12px;
  --shape-md: 16px;
  --shape-lg: 28px;
  --shape-full: 999px;

  /* Élévations */
  --elev-1: 0 1px 3px 1px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.30);
  --elev-2: 0 2px 6px 2px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.30);
  --elev-3: 0 4px 8px 3px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.30);

  /* Hauteur du clavier virtuel mobile, tenue à jour par l'API
     VisualViewport (voir app.js) — 0px par défaut (desktop, ou avant le
     premier calcul JS). Utilisée par .dns-bottom-bar pour rester au-dessus
     du clavier plutôt que de se faire recouvrir. */
  --kb-inset: 0px;
}

* { box-sizing: border-box; }

/* Police du code, hébergée par l'app elle-même (app/static/fonts/) plutôt
   que servie par un CDN externe : aucune requête vers un tiers, donc rien
   qui puisse être bloqué côté réseau ou navigateur.
   Px437 IBM VGA 8x16 reproduit le texte VGA d'un vrai terminal. Issue d'un
   bitmap 8x16 : nette aux multiples de 16px, plus floue entre deux — d'où
   les 16px posés sur .note-code-inline/.note-code-block. */
@font-face {
  font-family: "Px437 IBM VGA";
  src: url("/static/fonts/Px437_IBM_VGA_8x16.ttf") format("truetype");
  font-weight: normal;
  font-style: normal;
  font-display: swap;
}

/* Un sélecteur de classe (.gate, .tabs…) l'emporte sur le display:none que le
   navigateur applique à [hidden]. Sans cette règle, les écrans masqués restent
   visibles et se superposent. */
[hidden] { display: none !important; }

/* Réserve en permanence la place de la barre de défilement verticale.
   Sans ça, toute vue qui passe de "assez courte" à "trop longue" (filtrer
   par libellé, chercher, cocher une tâche…) fait apparaître/disparaître la
   barre, ce qui décale d'un coup toute la mise en page horizontalement —
   une des "sautes visuelles" signalées. */
html { scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: Verdana, Geneva, sans-serif;
  background: var(--md-surface);
  color: var(--md-on-surface);
  line-height: 1.5;
  letter-spacing: .01em;
}

/* Barres de défilement : discrètes et TRANSLUCIDES, jamais le gris sur
   noir du navigateur — posées sur une carte colorée elles doivent en
   reprendre la teinte. Un blanc semi-transparent y parvient quelle que
   soit la couleur dessous, sans avoir à décliner les 24 teintes.
   Les deux syntaxes sont nécessaires : `scrollbar-*` pour Firefox,
   `::-webkit-scrollbar` pour Chrome/Edge/Safari. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,.32) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: rgba(255,255,255,.28);
  border-radius: var(--shape-full);
  /* Bordure transparente + background-clip : la poignée paraît plus fine
     que la gouttière et ne colle pas aux bords. */
  border: 2px solid transparent;
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,.48);
  background-clip: padding-box;
}
*::-webkit-scrollbar-corner { background: transparent; }

button { font: inherit; cursor: pointer; }
input, textarea, select { font: inherit; }

/* ============================ Composants ============================ */

/* Boutons — pilule pleine, conforme MD3 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  min-height: 40px;
  padding: 0 1.5rem;
  /* Blanc plutôt que le mauve clair (--md-primary) par défaut de MD3. */
  background: #fff;
  color: #1b1b1f;
  border: none;
  border-radius: var(--shape-full);
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: .1px;
  transition: box-shadow .15s, filter .15s, background .15s;
}
.btn:hover { box-shadow: var(--elev-1); filter: brightness(1.06); }
.btn:active { filter: brightness(.96); }
.btn.wide { width: 100%; }

/* Bouton "outlined" */
.btn.ghost {
  background: transparent;
  /* Blanc plutôt que le mauve clair (--md-primary), comme pour .btn plein. */
  color: #fff;
  border: 1px solid var(--md-outline);
}
.btn.ghost:hover { background: rgba(208,188,255,.08); box-shadow: none; }

.btn.danger { background: var(--md-error); color: var(--md-on-error); }

.btn.sm { min-height: 32px; padding: 0 1rem; font-size: .8125rem; }

/* Champs texte — style "filled" MD3 */
.field { margin-bottom: 1.25rem; }
.field label {
  display: block;
  margin-bottom: .375rem;
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: .4px;
  color: var(--md-on-surface-variant);
}
.field input, .field textarea, .field select {
  width: 100%;
  padding: .875rem 1rem;
  background: var(--md-surface-3);
  border: 1px solid transparent;
  border-bottom: 1px solid var(--md-outline);
  border-radius: var(--shape-xs) var(--shape-xs) 0 0;
  color: var(--md-on-surface);
  transition: border-color .15s, background .15s;
}
.field input:focus, .field textarea:focus, .field select:focus {
  outline: none;
  border-bottom: 2px solid var(--md-primary);
  background: var(--md-surface-4);
}

.hint { font-size: .75rem; color: var(--md-on-surface-variant); margin: .375rem 0 0 1rem; }

/* Le champ description doit rester transparent/italique même à l'intérieur
   d'un .field (dialogue d'édition complet), qui impose sinon un fond plein
   à tous ses <input> — cette règle, plus spécifique, l'emporte. */
.field input.description-in {
  background: transparent;
  border: none;
  padding: .3rem 0;
  margin: 0 0 .75rem;
}
.field input.description-in:focus { background: transparent; border-bottom: none; }

/* Bandeaux de message */
.msg {
  margin-top: 1rem;
  padding: .75rem 1rem;
  border-radius: var(--shape-xs);
  font-size: .875rem;
}
.msg.error { background: var(--md-error-container); color: #f9dedc; }
.msg.ok { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

/* ====================== Écrans de garde ====================== */

.gate { min-height: 100vh; display: grid; place-items: center; padding: 1.5rem; }

.card {
  width: 100%;
  max-width: 420px;
  background: var(--md-surface-2);
  border-radius: var(--shape-lg);
  padding: 2rem;
  box-shadow: var(--elev-2);
}
.card h1 { margin: 0 0 .5rem; font-size: 1.5rem; font-weight: 400; }
.card .sub {
  margin: 0 0 2rem;
  color: var(--md-on-surface-variant);
  font-size: .875rem;
}

/* ============================ Ossature ============================ */

header.topbar {
  display: flex;
  align-items: center;
  gap: .75rem;
  height: 64px;
  padding: 0 1rem;
  background: var(--md-surface-2);
  position: sticky;
  top: 0;
  z-index: 10;
}
header.topbar .brand { font-size: 1.375rem; font-weight: 400; margin-right: .5rem; }
/* Deux cuillères, jaune puis bleue, à pleine hauteur du logo, sans se toucher. */
header.topbar .logo {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 28px;
}
header.topbar .logo svg { height: 100%; width: auto; display: block; flex: none; }

header.topbar .spacer { flex: 1; }

/* Pseudo : mêmes taille et graisse que le titre "notask" à gauche
   (header.topbar .brand ci-dessus), lettres alternées jaune/bleu cuillère
   — construites une par une en JS, voir renderWho(). */
header.topbar .who {
  font-size: 1.375rem;
  font-weight: 400;
  letter-spacing: .01em;
  margin-right: .25rem;
}
.who-jaune { color: var(--spoon-jaune); }
.who-bleu  { color: var(--spoon-bleu); }

/* Entrées Profil / Outils : pastilles Material, sans cadre — texte BLANC
   comme partout dans l'app, sur un fond teinté qui reprend les deux
   couleurs du logo (jaune pour la première, bleue pour la seconde). La
   couleur est portée par la pastille, pas par le texte : c'est ce qui
   permet de garder la lisibilité du blanc tout en gardant le repère. */
.top-entry {
  border: none;
  color: var(--md-on-surface);
  font-size: 1rem;
  font-weight: 500;
  padding: .4rem .9rem;
  border-radius: var(--shape-full);
  cursor: pointer;
  transition: background .15s, box-shadow .15s;
}
.top-entry-yellow { background: rgba(var(--spoon-jaune-rgb), .28); }
.top-entry-yellow:hover { background: rgba(var(--spoon-jaune-rgb), .45); }
.top-entry-blue { background: rgba(var(--spoon-bleu-rgb), .30); }
.top-entry-blue:hover { background: rgba(var(--spoon-bleu-rgb), .48); }

/* Bascule niveaux de gris — rond à 4 quadrants : gauche = couleurs du logo
   (bleu/jaune), droite = deux gris. Le côté qui représente l'état ACTIF
   reste net (opacité pleine), l'autre s'estompe — voir renderModeGris()
   dans app.js. Toute l'app (y compris ce bouton) passe en niveaux de gris
   via `filter` sur <html> (voir plus bas, html.mode-gris) : le bouton lui-
   même est donc concerné en mode gris, seule l'opacité de ses moitiés
   distingue encore l'état une fois le filtre appliqué. */
.mode-gris-btn {
  flex: none;
  width: 1.35rem;
  height: 1.35rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  display: flex;
  overflow: hidden;
  cursor: pointer;
  background: none;
}
.mode-gris-btn .mgb-half {
  width: 50%;
  height: 100%;
  display: flex;
  flex-direction: column;
  transition: opacity .18s;
}
.mode-gris-btn .mgb-quart { flex: 1 1 50%; }
.mgb-bleu  { background: var(--spoon-bleu); }
.mgb-jaune { background: var(--spoon-jaune); }
.mgb-gris1 { background: #eeeeee; }
.mgb-gris2 { background: #424242; }
/* Repos : mode couleur actif, donc côté couleur net, côté gris estompé. */
.mode-gris-btn .mgb-gris { opacity: .35; }
/* Mode gris actif (html.mode-gris, classe posée aussi sur le bouton) :
   inversion — côté gris net, côté couleur estompé. */
.mode-gris-btn.actif .mgb-couleur { opacity: .35; }
.mode-gris-btn.actif .mgb-gris { opacity: 1; }

/* Bouton hamburger : n'existe visuellement que sous 860px (voir le second
   bloc @media plus bas) — sur desktop le menu reste affiché en permanence
   dans la grille, ce bouton n'aurait rien à faire. */
.mobile-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--md-on-surface);
  cursor: pointer;
}
.mobile-menu-btn:hover { background: rgba(230,224,233,.10); }
.mobile-menu-btn svg { width: 22px; height: 22px; display: block; }

/* ---- Boîtes Profil et Outils ---- */

/* Carte de thème dans le Profil : fond un cran plus sombre que la boîte
   (surface-1 sous la surface-3 du dialogue), aucune bordure, coins arrondis.
   Discrète — c'est le fond seul qui délimite le groupe, pas un trait. */
.profil-carte {
  background: var(--md-surface-1);
  border-radius: var(--shape-md, 12px);
  padding: 1rem 1.125rem;
  margin-bottom: .75rem;
}
.profil-carte > h3 {
  margin: 0 0 .75rem;
  font-size: 1rem;
  font-weight: 600;
}
.profil-carte h4 {
  margin: 0 0 .5rem;
  font-size: .9375rem;
  font-weight: 600;
  color: var(--md-on-surface);
}
/* Dernier enfant d'une carte : pas de marge basse résiduelle qui
   déséquilibrerait le rembourrage de la carte. */
.profil-carte > :last-child { margin-bottom: 0; }

.profil-infos {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .4rem 1rem;
  margin: 0;
  font-size: .875rem;
}
.profil-infos dt { color: var(--md-on-surface-variant); }
.profil-infos dd { margin: 0; color: var(--md-on-surface); }

/* Repère visuel connecté/déconnecté demandé pour Google Calendar : pastille
   grise (non connecté), verte (connecté), orange (jeton révoqué/expiré côté
   Google — reconnexion nécessaire, voir GoogleAccount.needs_reauth). */
/* Bloc Google/Rappels : les traits de séparation d'origine (border-top/bottom)
   ne sont plus nécessaires — c'est la carte .profil-carte qui délimite
   désormais chaque thème. Reste un simple espacement au-dessus quand ce bloc
   suit d'autres éléments dans la même carte (les identifiants admin). */
.profil-google {
  margin: 0;
}
.profil-carte .admin-google + .dialog-actions + .profil-google,
.profil-carte .admin-google + .profil-google {
  margin-top: 1rem;
}
.profil-google-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  font-size: .875rem;
  margin-bottom: .75rem;
}
.profil-google-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  background: var(--md-on-surface-variant);
}
.profil-google-dot.connected { background: #4caf50; }
.profil-google-dot.needs-reauth { background: #ff9800; }
.profil-google .dialog-actions { margin: 0; }

/* Choix de l'agenda de destination (liste déroulante, ou saisie manuelle de
   l'identifiant quand Google refuse de lister les agendas). Champ et bouton
   sur une même ligne : évite une rangée d'actions supplémentaire, qui
   ajoutait de la hauteur et désalignait les boutons entre eux. */
.profil-google-cal { margin-top: .75rem; }
.profil-google-cal-label {
  display: block;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
  margin-bottom: .3rem;
}
.profil-google-cal-row {
  display: flex;
  align-items: center;
  gap: .5rem;
}
.profil-google-cal-row select,
.profil-google-cal-row input {
  flex: 1;
  min-width: 0; /* sans ça, un long identifiant d'agenda pousse le bouton hors du cadre */
  padding: .5rem .65rem;
  border-radius: 8px;
  border: 1px solid var(--md-outline-variant);
  background: var(--md-surface);
  color: var(--md-on-surface);
  font: inherit;
  font-size: .875rem;
  height: 38px;
}
.profil-google-cal-row .btn {
  flex: none;
  height: 38px; /* même hauteur que le champ, pour un alignement net */
}
.profil-google-cal-hint {
  margin: .4rem 0 0;
  font-size: .75rem;
  line-height: 1.35;
  color: var(--md-on-surface-variant);
}
/* Rangée unique Effacer / Déconnecter / Enregistrer. Sélecteur
   volontairement à deux classes : `.dialog-actions` (plus bas dans le
   fichier) impose margin-top: 1.5rem et l'emporterait, à spécificité égale,
   sur une classe seule. */
.dialog-actions.profil-google-actions {
  margin-top: .75rem;
  flex-wrap: wrap; /* en étroit, les boutons passent à la ligne au lieu de déborder */
}

.outil-bloc {
  padding: 1rem 0;
  border-top: 1px solid var(--md-outline-variant);
}
.outil-bloc:first-of-type { border-top: none; padding-top: 0; }
.outil-bloc h3 { margin: 0 0 .5rem; font-size: 1rem; font-weight: 500; }
/* Les deux boutons côte à côte sous le champ : c'est le bouton choisi qui
   détermine le sens de l'opération, d'où leur poids visuel identique. */
.outil-actions { display: flex; gap: .5rem; margin-top: .25rem; }
#dlg-outils[open] { width: min(552px, 94vw); } /* +20%, même bump que la base dialog */

/* Note descriptive sous un titre de réglage : volontairement discrète et
   sur une seule ligne. Elle situe l'option, elle ne la documente pas — un
   paragraphe ici serait lu par personne et alourdirait chaque écran. */
.aide {
  margin: -.25rem 0 .6rem;
  font-size: .75rem;
  line-height: 1.35;
  color: var(--md-on-surface-variant);
  opacity: .85;
}

/* Ligne d'accès au journal dans Outils : texte à gauche, bouton à droite. */
.outil-ligne {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.outil-ligne-texte {
  flex: 1;
  min-width: 12rem;
  margin: 0;
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--md-on-surface-variant);
}
.outil-ligne-compte {
  display: block;
  margin-top: .15rem;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
}
/* Un décompte d'erreurs doit se distinguer du simple nombre d'entrées :
   c'est la seule information de cet écran qui appelle une action. */
.outil-ligne-compte.a-des-erreurs { color: #ff8a80; font-weight: 500; }

/* ------------------------------ Journal ------------------------------ */
#dlg-logs[open] { width: min(920px, 94vw); }
.logs-barre {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  margin-bottom: .6rem;
}
.logs-filtres { display: flex; gap: .35rem; flex-wrap: wrap; }
.logs-chip {
  padding: .3rem .65rem;
  font-size: .75rem;
  border: 1px solid var(--md-outline-variant);
  border-radius: 999px;
  background: transparent;
  color: var(--md-on-surface-variant);
  cursor: pointer;
}
.logs-chip.is-on {
  background: rgba(255,255,255,.14);
  color: var(--md-on-surface);
  border-color: transparent;
}
#logs-recherche {
  flex: 1;
  min-width: 9rem;
  padding: .4rem .65rem;
  font: inherit;
  font-size: .8125rem;
  color: var(--md-on-surface);
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
}

.logs-liste {
  /* Hauteur bornée + défilement : le journal peut compter des centaines de
     lignes, la fenêtre ne doit pas s'étirer hors de l'écran. */
  max-height: min(60vh, 520px);
  overflow-y: auto;
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-xs);
  background: rgba(0,0,0,.22);
}
.logs-vide {
  margin: 0;
  padding: 1.25rem;
  text-align: center;
  font-size: .8125rem;
  color: var(--md-on-surface-variant);
}
.logs-ligne {
  display: grid;
  /* Colonnes fixes pour heure/niveau/source : alignées d'une ligne à
     l'autre, l'oeil descend en colonne au lieu de chercher où commence
     chaque champ. Le message prend le reste. */
  grid-template-columns: auto auto auto 1fr;
  align-items: center;
  gap: .1rem .6rem;
  padding: .35rem .6rem;
  border-left: 3px solid transparent;
  border-bottom: 1px solid rgba(255,255,255,.06);
  font-size: .78125rem;
}
.logs-ligne:last-child { border-bottom: none; }
.logs-heure {
  font-family: "Px437 IBM VGA", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .72rem;
  color: var(--md-on-surface-variant);
}
.logs-niveau {
  font-family: "Px437 IBM VGA", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .68rem;
  letter-spacing: .04em;
}
.logs-source {
  font-size: .7rem;
  padding: .05rem .35rem;
  border-radius: 4px;
  background: rgba(255,255,255,.09);
  color: var(--md-on-surface-variant);
}
.logs-message {
  color: var(--md-on-surface);
  overflow-wrap: anywhere; /* une URL longue ne doit pas élargir la fenêtre */
}
.logs-ligne.est-error { border-left-color: #ff5252; background: rgba(255,82,82,.07); }
.logs-ligne.est-error .logs-niveau { color: #ff8a80; }
.logs-ligne.est-warn { border-left-color: #ffb74d; }
.logs-ligne.est-warn .logs-niveau { color: #ffcc80; }
.logs-ligne.est-info { border-left-color: #64b5f6; }
.logs-ligne.est-info .logs-niveau { color: #90caf9; }
.logs-ligne.est-debug { border-left-color: rgba(255,255,255,.2); }
.logs-ligne.est-debug .logs-niveau,
.logs-ligne.est-debug .logs-message { color: var(--md-on-surface-variant); }

.logs-detail { grid-column: 1 / -1; margin-top: .2rem; }
.logs-detail summary {
  cursor: pointer;
  font-size: .72rem;
  color: var(--md-on-surface-variant);
}
.logs-detail pre {
  margin: .3rem 0 0;
  padding: .5rem .6rem;
  max-height: 220px;
  overflow: auto;
  background: rgba(0,0,0,.35);
  border-radius: var(--shape-xs);
  font-family: "Px437 IBM VGA", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .72rem;
  line-height: 1.4;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--md-on-surface-variant);
}

/* ===================== Menu latéral (navigation drawer) ===================== */

.shell {
  display: grid;
  /* 288px plutôt que les 260px d'origine : "Toutes les notasks" (le plus
     long intitulé du menu) était tronqué avec des points de suspension à
     260px. */
  grid-template-columns: 278px minmax(0, 1fr);
  align-items: start;
}
/* Colonne d'échéances (voir #agenda-col) : classe posée par JS (switchView)
   plutôt qu'un simple attribut hidden sur la colonne elle-même — on veut
   une 3e piste de grille qui n'existe carrément pas quand elle est absente,
   pas une piste minmax(0, …) dont le calcul de taille avec un contenu
   display:none serait plus difficile à garantir stable. */
/* 330px et non 280 : la ligne de date d'une échéance récurrente débordait
   sur deux lignes. Les 50px de plus sont pris INTÉGRALEMENT sur les marges
   (voir juste en dessous) — demande explicite : ne pas rogner la zone
   d'affichage des notasks. C'est à peu près le maximum tenable : sur un
   écran de 1280px il ne reste alors qu'une trentaine de pixels entre la
   mosaïque et cette colonne. */
.shell.has-agenda { grid-template-columns: 278px minmax(0, 1fr) 330px; }
/* Les marges de la mosaïque se resserrent d'autant pour financer la
   colonne, plutôt que de rétrécir la grille de notes elle-même (demande
   explicite : gratter dans les marges, pas dans la zone centrale).
   Le « + 44px » compense EXACTEMENT les 50px repris par la colonne
   ci-dessus : la piste centrale ayant perdu 50px, 88 % de celle-ci en perd
   44 — les rajouter rend à la mosaïque la largeur au pixel près qu'elle
   avait avant l'élargissement, quelle que soit la taille de l'écran. Une
   simple hausse du pourcentage n'aurait pas cette propriété : elle ne
   tomberait juste qu'à une seule largeur de fenêtre. */
.shell.has-agenda main { width: calc(88% + 44px); }

.drawer {
  padding: .75rem .75rem 2rem 0;
  position: sticky;
  top: 64px;
  /* Pas de scroll interne : le menu prend la hauteur de son contenu. Un
     max-height en vh/dvh + overflow-y:auto produisait, sous `zoom`, un
     débordement sous-pixel — barre parasite puis scroll molette alors que
     le menu tient largement. S'il devenait plus haut que l'écran, c'est la
     page qui défile (le menu reste sticky). */
  max-height: none;
  overflow-y: visible;
}

/* Comme les autres titres de section : casse respectée, pas de majuscules
   forcées (voir .agenda-group h2). */
.drawer-section {
  margin: .75rem 0 .25rem;
  padding-left: 1.5rem;
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: .01em;
  color: var(--md-on-surface-variant);
}

.drawer-sep {
  border: none;
  border-top: 1px solid var(--md-outline-variant);
  margin: .75rem 0 .25rem 1rem;
}

/* Espace entre la barre de séparation et le premier libellé — sans toucher
   à .drawer-sep, partagée avec d'autres séparateurs du menu qui n'ont pas
   besoin de cet écart. */
#labels-list { margin-top: 1rem; }

/* Élément de navigation MD3 : pilule pleine à droite */
.drawer-item {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  width: 100%;
  min-height: 48px;
  padding: 0 1.5rem 0 1.5rem;
  background: transparent;
  border: none;
  border-radius: 0 var(--shape-full) var(--shape-full) 0;
  color: var(--md-on-surface-variant);
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: .1px;
  text-align: left;
  transition: background .15s, color .15s;
}
.drawer-item:hover { color: var(--md-on-surface); }
/* :not([style]) : un libellé avec une couleur propre (posée en style inline,
   voir app.js) garde cette couleur en toutes circonstances — le survol ne
   doit remplacer le fond gris que pour les entrées sans couleur propre. */
.drawer-item:not([style]):hover { background: rgba(230,224,233,.08); }
.drawer-item.active {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
}
/* Un libellé coloré + actif (filtre en cours) : l'outline reste visible même
   si le style inline l'emporte sur le fond de .active. */
/* Bleu (couleur de la cuillère bleue), pas le mauve par défaut. */
.drawer-item.label-item[style].active { outline: 2px solid var(--spoon-bleu); outline-offset: -2px; }
.drawer-item.label-item[style] { color: var(--md-on-surface); }
.drawer-item svg { width: 22px; height: 22px; flex: none; }
.drawer-item .label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.drawer-item.add { color: var(--md-on-surface-variant); }

/* Rond de comptage (nav-tasks) : poussé à droite par
   margin-left:auto (indépendant du gap régulier entre icône/texte), avec
   une marge propre avant le bord — le bord droit de la pilule est arrondi
   (voir .drawer-item plus haut), le rond ne doit pas le toucher ni se faire
   couper par lui. */
.nav-count {
  flex: none;
  margin-left: auto;
  margin-right: .125rem;
  min-width: 20px;
  height: 20px;
  padding: 0 .375rem;
  border-radius: var(--shape-full);
  background: rgba(255,255,255,.14);
  color: inherit;
  font-size: .6875rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Repère de navigation : la section Notes en jaune, la section Tâches en
   bleu — les mêmes teintes que les deux cuillères du logo (ICONS.spoon /
   ICONS.spoonBlue). Uniquement décoratif ici, sans rapport avec la couleur
   propre de chaque note (celle-là reste réglable parmi les 21 teintes, voir
   .c-* plus bas) : la nuance se limite au menu de gauche, pour s'y repérer
   d'un coup d'œil entre les deux familles de vues. */
.drawer-notes-group .drawer-section { color: var(--spoon-jaune); opacity: .85; }
.drawer-notes-group .drawer-item svg { color: var(--spoon-jaune); }
.drawer-notes-group .drawer-item.active { background: rgba(var(--spoon-jaune-rgb), .16); color: var(--spoon-jaune); }

/* "Notasks Prévues" vit désormais dans le groupe jaune (la section Tâches
   séparée a été supprimée), mais garde le bleu de la seconde cuillère :
   ces règles doivent donc l'emporter sur celles ci-dessus — d'où le
   sélecteur par id, plus spécifique, et le placement juste après. */
#nav-tasks svg { color: var(--spoon-bleu); }
#nav-tasks.active { background: rgba(var(--spoon-bleu-rgb), .16); color: var(--spoon-bleu); }

/* Sous 860 px, le menu n'est plus affiché en permanence dans la grille :
   il devient le MÊME menu que sur desktop (mêmes sections, mêmes
   libellés, même style — rien n'est redéfini pour .drawer-section/
   .drawer-sep/.drawer-item ci-dessous, ils gardent leur apparence
   "desktop" telle quelle), replié par défaut, ouvert en panneau superposé
   par le bouton hamburger (voir #mobile-menu-btn dans index.html et
   ouvrirMenuMobile()/fermerMenuMobile() dans app.js).

   La colonne d'échéances (agenda-col) n'a plus la place d'être une 3e
   piste de grille à côté de la mosaïque — sur demande explicite, elle
   devient sa propre PAGE verticale, à côté de l'accueil (mosaïque de
   notasks) plutôt qu'au-dessus ou en dessous : les deux se glissent
   horizontalement l'une vers l'autre au doigt, comme un carrousel à deux
   volets. Fait en scroll-snap natif du navigateur (overflow-x + scroll-
   snap-type) plutôt qu'en gérant le geste à la main en JS : plus fiable
   (défilement/inertie natifs, pas de conflit avec le défilement vertical
   de chaque page), et beaucoup moins de code qu'un glisser reconstruit
   pointeur par pointeur. Uniquement quand .shell.has-agenda est posée
   (voir switchView() dans app.js) : sur les autres vues (tâches, corbeille,
   comptes), il n'y a qu'une seule page, pas de carrousel à faire. */
@media (max-width: 860px) {
  .shell, .shell.has-agenda { grid-template-columns: 1fr; }
  .mobile-menu-btn { display: flex; }

  .shell.has-agenda {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
  }
  .shell.has-agenda > main,
  .shell.has-agenda > .agenda-col {
    flex: 0 0 100%;
    width: 100%;
    max-width: 100%;
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }
  /* La redéfinition complète de .agenda-col (position, hauteur, padding)
     vit plus bas dans ce fichier, groupée avec les autres ajustements
     "main { width: 100% }" — pas ici : la déclaration DESKTOP de
     .agenda-col (position:sticky…) est elle-même plus bas que ce bloc-ci,
     et à spécificité égale (même sélecteur .agenda-col), c'est l'ORDRE
     D'APPARITION dans le fichier qui tranche, indépendamment de la media
     query. La poser ici aurait été silencieusement écrasée par la règle
     desktop, plus bas — piège bête, croisé et évité avant de le commettre. */

  .drawer {
    /* position:fixed retire complètement .drawer du flux de la grille
       (comme position:absolute) — main récupère alors toute la largeur
       disponible, sans laisser de piste vide derrière un menu devenu
       flottant. */
    position: fixed;
    top: 0;
    left: 0;
    height: 100dvh;
    width: min(300px, 84vw);
    max-height: none;
    z-index: 2000;
    background: var(--md-surface-3);
    box-shadow: var(--elev-3);
    padding: max(.75rem, env(safe-area-inset-top)) .75rem 2rem max(0px, env(safe-area-inset-left));
    /* Replié par défaut : retiré du rendu (display:none), pas seulement
       masqué visuellement — un panneau invisible mais toujours cliquable
       resterait un piège à interaction juste sous le contenu. */
    display: none;
  }
  /* Classe posée par ouvrirMenuMobile() : transform-origin déjà réglé en
     JS (même mécanisme que animerOuvertureDialogue() pour les notes) au
     moment où elle est ajoutée, juste avant la classe d'animation
     dlg-open-anim. */
  .drawer.mobile-open { display: block; }
}

/* Fond assombri derrière le menu mobile déplié (voir #drawer-backdrop dans
   index.html) — même teinte que le fond des <dialog> (dialog::backdrop),
   pour rester cohérent visuellement. Toujours dans le DOM mais laissé
   `hidden` par défaut : jamais affiché sur desktop, où le menu ne se
   replie pas (voir ouvrirMenuMobile()/fermerMenuMobile() dans app.js). */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1999;
  background: rgba(0,0,0,.5);
}

/* La zone centrale (hors menu latéral) n'occupe que 3/4 de l'espace qui lui
   est laissé par le menu, et reste centrée dans cet espace. */
main { padding: 1.5rem 1.5rem 3rem; width: 75%; margin: 0 auto; }

/* Colonne d'échéances — voir #agenda-col dans index.html, alimentée par
   loadAgenda() dans app.js. Couleur de la note en fond (.c-*), une seule
   ligne par échéance. */
.agenda-col {
  position: sticky;
  top: 64px;
  max-height: calc(100dvh - 66px);
  overflow-y: auto;
  /* Pas de padding à droite : la colonne doit toucher le bord de l'écran
     (voir le commentaire sur .agenda-item juste en dessous — coin arrondi
     à gauche seulement, "collée au bord droit"), un padding-right cassait
     ça en laissant un liseré vide entre les cartes et le bord. */
  padding: 1.5rem 0 3rem;
}
/* Vue "notasks prévues" (desktop) : trois colonnes côte à côte, une par
   regroupement. minmax(0, 1fr) et non 1fr : un titre long dans une ligne
   pousserait sinon sa colonne au-delà de sa part, les trois colonnes
   perdraient leur largeur égale. Les lignes elles-mêmes réutilisent
   .agenda-item (voir creerLigneAgenda dans app.js). */
.tasks-columns {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)); /* upcoming/imminent/today/late/done */
  gap: 1.25rem;
  align-items: start;
}
/* Séparateurs entre colonnes : une bordure gauche posée sur chaque colonne
   sauf la première, plutôt que des éléments dédiés dans le DOM. Le padding
   compense la bordure pour que le trait tombe au milieu de l'écart et non
   collé au contenu. `align-items: start` laisse chaque colonne à sa hauteur
   propre, donc les traits s'arrêtent naturellement au bas de leur colonne. */
.tasks-column + .tasks-column {
  border-left: 1px solid var(--md-outline-variant);
  padding-left: 1.25rem;
}
/* Dans cette vue les lignes ne sont plus collées au bord droit de l'écran
   (contrairement à la colonne d'échéances) : coins arrondis des deux côtés.
   position:relative pour ancrer les boutons d'action au survol. */
.tasks-column .agenda-item { border-radius: var(--shape-sm); position: relative; }
.tasks-column h2 { margin-top: 0; }
.agenda-group.done h2 { color: var(--md-on-surface-variant); }

/* Tâche terminée : barrée et estompée, dans la colonne "terminées". */
.agenda-item.done .agenda-item-text { text-decoration: line-through; }
.agenda-item.done { opacity: .65; }

/* Archiver / corbeille, révélés au survol de la ligne — uniquement dans la
   vue "notasks prévues" (voir avecActions dans creerLigneAgenda), jamais
   dans la colonne d'échéances de l'accueil.
   Superposés (position:absolute) et non insérés dans le flux : ajouter deux
   boutons à l'apparition décalerait sinon le texte de la ligne au passage
   de la souris. Dégradé vers la gauche pour que le texte ne se lise pas au
   travers des icônes sur une ligne longue. */
.agenda-item-actions {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  gap: .125rem;
  padding: 0 .375rem 0 1.5rem;
  opacity: 0;
  transition: opacity .15s ease;
  background: linear-gradient(to right, transparent, rgba(0,0,0,.45) 40%);
  border-radius: 0 var(--shape-sm) var(--shape-sm) 0;
}
.tasks-column .agenda-item:hover .agenda-item-actions,
.tasks-column .agenda-item:focus-within .agenda-item-actions { opacity: 1; }
/* Pas de survol sur écran tactile : les actions y restent visibles. */
@media (hover: none) {
  .tasks-column .agenda-item-actions { opacity: 1; }
}
/* Noyau commun des boutons-icône ronds et NUS : fond transparent, pas de
   cadre, icône en gris secondaire. Ces six déclarations étaient recopiées
   à l'identique sur quatre règles, si bien qu'une retouche du gabarit n'en
   atteignait qu'une seule. Ce qui reste propre à chaque bouton — taille,
   positionnement, opacité au repos, transition — est déclaré dans sa règle,
   plus bas et donc prioritaire à spécificité égale.
   Sélecteur groupé plutôt qu'une classe commune : ces boutons sont produits
   à plusieurs endroits du JS, poser une classe partout aurait obligé à
   toucher au balisage sans rien y gagner. Les états (:hover, .active,
   .pinned) portent tous une spécificité supérieure et gagnent donc quoi
   qu'il arrive, indépendamment de leur position dans le fichier. */
.agenda-item-actions button,
.note .pin-btn,
.label-edit-btn,
.icon-opt {
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface-variant);
}

.agenda-item-actions button {
  width: 28px;
  height: 28px;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.agenda-item-actions button:hover { background: rgba(230,224,233,.16); color: var(--md-on-surface); }
.agenda-item-actions svg { width: 16px; height: 16px; display: block; }

/* Lignes à cocher archivées seules, sous la mosaïque des Archives. Mêmes
   lignes que "notasks prévues" (voir creerLigneAgenda), donc mêmes réglages
   de coins arrondis et d'actions au survol que .tasks-column. */
#archived-items { margin-top: 2rem; }
.archived-items-title {
  font-size: .9375rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--md-on-surface-variant);
  margin: 0 0 .75rem;
}
#archived-items-list {
  position: relative;
  --mosaic-min-col: 240px;
  --mosaic-gap: 16px;
}

/* Un groupe = les lignes archivées (ou mises seules à la corbeille) d'une
   même notask, réunies sous son icône et son titre. La carte elle-même
   (fond, coins, ombre au survol) vient de .note, la VRAIE classe des
   cartes de l'accueil (voir renderNotes) — .archived-group n'ajoute que ce
   qui n'existe pas dans une carte normale : l'en-tête cliquable et
   l'espacement entre les lignes imbriquées. */
.archived-group {
  padding-bottom: .625rem;
  /* Posée par le masonry commun (voir MOSAIQUES dans app.js), comme les
     cartes de l'accueil : chaque groupe se cale sous le plus bas de sa
     colonne au lieu de s'empiler sur toute la largeur. */
  position: absolute;
  top: 0;
  left: 0;
  transition: left .26s cubic-bezier(.34, 1.4, .5, 1), top .26s cubic-bezier(.34, 1.4, .5, 1);
}
.archived-group-title {
  cursor: pointer;
  border-radius: var(--shape-xs, 6px);
}
.archived-group-title:hover, .archived-group-title:focus-visible {
  outline: none;
  color: var(--md-primary, inherit);
}
.archived-group-items { display: grid; gap: .375rem; }
#archived-items-list .agenda-item,
#trashed-items-list .agenda-item { border-radius: var(--shape-sm); position: relative; }
#archived-items-list .agenda-item:hover .agenda-item-actions,
#archived-items-list .agenda-item:focus-within .agenda-item-actions,
#trashed-items-list .agenda-item:hover .agenda-item-actions,
#trashed-items-list .agenda-item:focus-within .agenda-item-actions { opacity: 1; }
@media (hover: none) {
  #archived-items-list .agenda-item-actions,
  #trashed-items-list .agenda-item-actions { opacity: 1; }
}

/* Lignes en corbeille (voir #trashed-items dans index.html) : même
   disposition que les lignes archivées ci-dessus, sous la grille des
   notasks supprimées plutôt que sous la mosaïque. */
#trashed-items { margin-top: 2rem; }
#trashed-items-list {
  position: relative;
  --mosaic-min-col: 240px;
  --mosaic-gap: 16px;
}

.agenda-group + .agenda-group { margin-top: 1.25rem; }
/* …sauf en colonnes : chacune démarre en haut de sa propre colonne, la
   marge d'empilement vertical n'a plus lieu d'être. */
.tasks-column + .tasks-column { margin-top: 0; }
/* Titres de section : pas de majuscules forcées, un peu plus gros à la
   place. On garde partout la casse telle qu'elle est écrite — comme le
   titre "notask" de l'en-tête et les entrées du menu de gauche. */
.agenda-group h2 {
  font-size: .9375rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--md-on-surface-variant);
  margin: 0 0 .5rem;
}
.agenda-group.late h2 { color: var(--md-error); }
.agenda-group.today h2 { color: var(--md-success); }
.agenda-group.imminent h2 { color: #f0a03c; } /* ambre, entre "today" (vert) et "upcoming" (bleu) */
.agenda-group.upcoming h2 { color: var(--spoon-bleu); } /* bleu de la cuillère du logo */
/* Même esprit que la pilule active du menu de gauche (.drawer-item : coin
   arrondi seulement du côté qui fait face au contenu, flush contre le bord
   de l'écran de l'autre côté) mais en miroir — le menu est collé au bord
   gauche (arrondi à droite), cette colonne est collée au bord droit (donc
   arrondi à gauche). Fond = la couleur propre de la note d'origine (classe
   .c-*, la même que sur sa carte), pas un jaune générique : on veut
   reconnaître la note, pas juste "une tâche". */
.agenda-item {
  display: flex;
  align-items: center;
  gap: .625rem;
  width: 100%;
  min-height: 44px;
  text-align: left;
  border: none;
  border-radius: var(--shape-full) 0 0 var(--shape-full);
  /* Retrait gauche : 6px à l'origine, puis 11px, puis 19px — case à cocher,
     icône et texte s'écartent d'autant du bord gauche de la pilule. */
  padding: .375rem .875rem .375rem 19px;
  margin-bottom: 4px;
  color: var(--md-on-surface);
  font-size: .8125rem;
  /* Plus un <button> (voir renderAgenda() dans app.js — un <input> ne peut
     pas vivre dans un <button>), donc le curseur et le focus visible ne
     sont plus donnés gratuitement par le navigateur. */
  cursor: pointer;
  /* Stacking context propre + ancre pour ::before (fond coloré sur calque
     séparé, même principe que .note). */
  position: relative;
  z-index: 0;
}
.agenda-item:hover { filter: brightness(1.12); }
.agenda-item:focus-visible { outline: 2px solid var(--md-on-surface); outline-offset: -2px; }
/* Fond coloré de la ligne d'échéance (posé en style inline par JS) dupliqué
   sur ::before — même principe que .note::before : en mode gris seul ce
   calque sera filtré, l'élément lui-même restant un ancêtre protégé de
   .agenda-item-icon. */
.agenda-item::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: inherit;
  pointer-events: none;
}
/* Case à cocher "Terminer" directement sur la ligne d'échéance — reprend
   la tâche que faisait auparavant .task-card dans la vue "Notasks Prévues",
   retirée : plus de vue en double, une seule case suffit désormais. */
/* Pas de bloc de dimensions ici : le reset `input[type=checkbox]` plus bas
   pèse 0-1-1 (type + attribut) contre 0-1-0 pour une classe seule, donc il
   l'emporte quel que soit l'ordre. Un bloc `.agenda-item-check` qui fixait
   width/height à 18px vivait ici sans aucun effet (la case fait 16.5px, et
   flex/margin/cursor y étaient déjà identiques au reset). Toute règle propre
   à cette case doit être écrite `input[type=checkbox].agenda-item-check`,
   avec les autres, près du reset. */
/* Icône de la notask, toujours affichée dans un rond — celle choisie sur
   la carte (ICON_CHOICES), ou la cuillère bleue par défaut si la notask
   n'en a pas (voir renderAgenda() dans app.js). Le texte est ainsi
   toujours décalé de la même largeur, icône présente ou non. */
.agenda-item-icon {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(0,0,0,.22);
  display: grid;
  place-items: center;
}
.agenda-item-icon svg { width: 17px; height: 17px; display: block; }
.agenda-item-body { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.agenda-item .agenda-item-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}
.agenda-item .agenda-item-due {
  display: block;
  font-size: .6875rem;
  color: var(--md-on-surface-variant);
  margin-top: .125rem;
  /* JAMAIS deux lignes, quelle que soit la date — exigence explicite. Aucune
     largeur de colonne ne peut le garantir : une plage sur deux jours avec
     récurrence ("12 déc. 19:00 → 14 déc. 21:00 · ↻ an") dépasse les 400px de
     texte, largeur qu'on ne peut pas offrir sans manger la mosaïque. On
     interdit donc le retour à la ligne et on coupe aux points de suspension,
     comme le titre juste au-dessus. Le texte entier (avec la récurrence en
     toutes lettres) reste lisible au survol — voir le `title` posé par
     creerLigneAgenda() dans app.js. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.agenda-empty { padding: 0 0 0 .125rem; text-align: left; font-size: .8125rem; }

/* Le composeur et la recherche vivent DANS la mosaïque (voir .notes-grid),
   larges de deux cartes, centrés, avec des notes des deux côtés. Position
   et largeur (left/width/top) sont calculées et posées en style inline par
   app.js (voir layoutMosaic()) — la mosaïque est un vrai masonry en
   positionnement absolu, pas une grille CSS, donc rien ici ne fixe plus
   l'emplacement lui-même. */
.notes-grid .composer-stack {
  position: absolute;
  margin: 0;
  /* Composeur puis recherche, empilés et solidaires : la recherche suit
     donc automatiquement la hauteur du composeur quand il se déplie. */
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.notes-grid .composer-stack > .note-composer { margin-bottom: 0; }
.notes-grid .composer-stack > .search-toolbar { margin: 0; }

@media (max-width: 860px) {
  main { width: 100%; }
  /* Cartes et composeur au plus près du bord de l'écran sur mobile — 8px
     de chaque côté au lieu des 1.5rem (24px) hérités du desktop, sur
     demande explicite : la mosaïque (.notes-grid, positionnée à l'intérieur
     de main sans marge propre à elle) suit directement ce padding, donc
     rien à changer côté JS (layoutMosaic() lit déjà la largeur réelle du
     conteneur). Le padding du haut et du bas n'est pas concerné, seuls les
     côtés gauche/droit sont resserrés. */
  main { padding-left: 8px; padding-right: 8px; }

  /* Pseudo retiré sur mobile — gagne de la place dans une barre déjà
     dense (hamburger + logo + Profil/Outils) et évite un décalage du
     reste de la barre à chaque connexion/déconnexion, quand sa largeur
     (variable selon la longueur du nom) apparaît/disparaît. */
  header.topbar .who { display: none; }

  /* Vraie redéfinition mobile de .agenda-col (voir le commentaire dans le
     premier bloc @media plus haut, qui explique pourquoi elle est ICI et
     pas là-bas) : sa propre page verticale pleine largeur plutôt qu'une
     barre latérale étroite à défilement borné. Les groupes existants
     restent dans le même ordre (en retard, puis aujourd'hui, puis à
     venir — voir renderAgenda() dans app.js) : en retard tout en haut,
     les autres à la suite en dessous, tel que demandé. */
  .agenda-col {
    position: static;
    max-height: none;
    overflow-y: visible;
    padding: 1.5rem 8px 3rem;
  }
}

.toolbar { display: flex; gap: .75rem; align-items: center; margin-bottom: 1.25rem; flex-wrap: wrap; }
.toolbar input[type=search], .toolbar input[type=text], .toolbar input[type=date] {
  padding: .75rem 1rem;
  background: var(--md-surface-3);
  border: none;
  border-radius: var(--shape-full);
  color: var(--md-on-surface);
}
.toolbar input[type=search] { flex: 1; min-width: 200px; }
.toolbar input:focus { outline: 2px solid var(--md-primary); outline-offset: -2px; }

/* Écho de la cuillère bleue du logo sur la barre de tri, atténué. Alpha
   aligné sur celui du composeur (.90) : ces barres doivent se détacher
   nettement des cartes autour, sans devenir tout à fait opaques. */
.search-toolbar input[type=search] { background: rgba(29, 84, 143, .90); }
/* Seconde barre — recherche en profondeur — en jaune cuillère, pour la
   distinguer d'un coup d'œil de la barre de tri juste au-dessus. */
.deep-search-toolbar input[type=search] { background: rgba(133, 102, 20, .90); }

/* Croix d'effacement : DANS la barre, calée contre son bord droit, et non
   posée à côté comme un bouton séparé. D'où le positionnement absolu par
   rapport à .search-toolbar, et le retrait à droite réservé sur le champ
   pour que le texte saisi ne passe jamais dessous.
   Masquée tant que le champ est vide (attribut hidden posé par
   brancherEffacementRecherche()). */
.search-toolbar { position: relative; }
.search-toolbar input[type=search] { padding-right: 2.75rem; }
/* Contour blanc pur à la sélection, au lieu du violet de --md-primary
   (règle générique .toolbar input:focus) : sur les fonds bleu et jaune des
   deux barres, le violet passait mal. Sélecteur plus spécifique et placé
   après pour l'emporter. */
.search-toolbar input[type=search]:focus { outline: 2px solid #fff; outline-offset: -2px; }
.search-clear {
  position: absolute;
  right: .5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgba(0,0,0,.28);
  color: var(--md-on-surface);
  cursor: pointer;
  transition: background .15s;
}
.search-clear:hover { background: rgba(0,0,0,.5); }
.search-clear svg { width: 15px; height: 15px; display: block; }

/* ---- Résultats de la recherche en profondeur (voir renderSearchHits) ----
   Cartes deux fois plus larges que dans la mosaïque : il ne s'agit plus
   d'un aperçu mais d'un extrait à lire, avec son contexte. */
.note.search-hit {
  /* Largeur 2 colonnes posée par layoutMosaic() (voir app.js) — plus une
     grille CSS, la largeur ne peut plus se déclarer ici. */
  cursor: pointer;
}
.hit-nav {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: .25rem 0 .5rem;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
}
.hit-nav button {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgba(0,0,0,.22);
  color: var(--md-on-surface);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}
.hit-nav button:hover { background: rgba(0,0,0,.38); }
/* La notask entière est rendue ici, mais la hauteur visible est bornée à
   2×4+1 = 9 lignes : on voit par défaut l'occurrence avec 4 lignes de
   contexte de chaque côté (cadrage fait en JS, voir cadrerExtrait()).
   overflow:auto en permanence plutôt qu'au survol seulement : basculer
   hidden->auto ferait apparaître/disparaître la barre de défilement et
   donc bouger le texte sous la souris. La hauteur, elle, ne change
   jamais — la carte garde exactement la même taille.
   --hit-lines est écrasée par JS depuis HIT_CONTEXT_LINES (voir
   cadrerExtrait) ; la valeur ci-dessous n'est qu'un repli. */
.hit-extract {
  --hit-lines: 9;
  font-size: .875rem;
  line-height: 1.5;
  max-height: calc(var(--hit-lines) * 1.5 * .875rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  white-space: pre-wrap;
  word-break: break-word;
  background: rgba(0,0,0,.16);
  border-radius: var(--shape-xs);
  padding: .5rem .625rem;
}
.hit-line { min-height: 1.4em; }
/* La ligne qui porte l'occurrence sélectionnée est légèrement détachée du
   contexte, pour qu'on la retrouve sans la chercher. */
.hit-line-current { background: rgba(255,255,255,.06); border-radius: 3px; }

/* Occurrence sélectionnée en bleu cuillère, les autres en jaune cuillère —
   mêmes teintes que les deux cuillères du logo. */
.hit-extract mark.hit {
  background: var(--spoon-jaune);
  color: #1a1a1a;
  border-radius: 3px;
  padding: 0 2px;
}
.hit-extract mark.hit.current {
  background: var(--spoon-bleu);
  color: #0d1b2a;
}

.empty { color: var(--md-on-surface-variant); text-align: center; padding: 4rem 1rem; font-size: .875rem; }

/* ============================== Notes ============================== */

/* Composeur en deux blocs distincts (voir index.html) plutôt qu'une seule
   carte : même esprit que .dns-card/.dns-bottom-bar en édition rapide,
   où la barre d'outils est visuellement à part, sous la note, séparée
   par un espace — pas fondue dans la même surface. .note-composer devient
   une simple coquille transparente qui empile les deux blocs. */
.note-composer {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  margin-bottom: 1.5rem;
}
.nc-card, .nc-toolbar-block {
  /* Écho de la cuillère jaune du logo, atténué (alpha) pour rester discret —
     les deux blocs partagent la même teinte, comme .dns-card/.dns-bottom-bar
     partagent --note-color en édition rapide.
     Alpha remonté de .55 à .90 : composeur et recherche doivent se détacher
     nettement des cartes qui les entourent, tout en laissant transparaître
     un peu du fond. */
  background: rgba(133, 102, 20, .90);
  border-radius: var(--shape-md);
  box-shadow: var(--elev-1);
}
.nc-card { padding: 1rem 1.25rem; }
.nc-toolbar-block { padding: .625rem 1rem; }
.composer-head { display: flex; align-items: center; gap: .625rem; }
.composer-head .title-in { flex: 1; }

.note-composer input, .note-composer textarea {
  width: 100%;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  resize: none;
  padding: .25rem 0;
}

/* Poignée de redimensionnement maison : le pseudo-élément ::-webkit-resizer
   (essayé précédemment) n'est pas fiable d'un navigateur/version à l'autre —
   certains ignorent silencieusement background-image dessus. On désactive
   donc le redimensionnement natif (resize: none ci-dessus) et on dessine
   nous-mêmes une poignée en flèche haut/bas (une pointe de chaque côté),
   avec le geste de glisser géré en JS (voir app.js, initTextareaResize). */
.ta-wrap { position: relative; }
.ta-resize-handle {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 14px;
  height: 14px;
  cursor: ns-resize;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14'><path d='M7 0L10.5 4.5H8V9.5H10.5L7 14L3.5 9.5H6V4.5H3.5Z' fill='%23aaa'/></svg>");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 10px 10px;
}
.note-composer input::placeholder, .note-composer textarea::placeholder { color: var(--md-on-surface-variant); }
.note-composer input:focus, .note-composer textarea:focus { outline: none; }
/* Titre nettement plus grand que la description et le contenu, pour que
   les trois niveaux se distinguent d'un coup d'œil. */
.title-in { font-size: 1.25rem; font-weight: 600; }

/* Description : en italique, plus discrète, entre le titre et le contenu —
   masquée entièrement sur la carte si vide (voir app.js). */
.description-in {
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  color: var(--md-on-surface-variant);
  font-style: italic;
  font-size: .8125rem;
  padding: .3rem 0;
  margin: .25rem 0 .5rem;
}
.description-in::placeholder { color: var(--md-on-surface-variant); opacity: .7; }
.description-in:focus { outline: none; }

/* Espace un peu plus marqué entre titre, description et contenu dans le
   composeur (les trois se suivaient trop serrés). */
.composer-head { margin-bottom: .2rem; }
#nc-content { margin-top: .375rem; }

/* Contenu du composeur : contenteditable (pas <textarea>), même raison et
   même mécanique que .dns-content-editable en édition rapide — voir le
   commentaire là-bas. Classe séparée plutôt que réutiliser
   .dns-content-editable telle quelle : hauteur mini bien plus petite ici
   (le composeur démarre replié sur 2 lignes, pas 120px). Un contenteditable
   ne supporte pas l'attribut placeholder : imité via :empty + attribut
   data-placeholder. */
.nc-content-editable {
  width: 100%;
  min-height: 44px;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  padding: .25rem 0;
  outline: none;
  white-space: pre-wrap;
  word-break: break-word;
}
.nc-content-editable:empty::before {
  content: attr(data-placeholder);
  color: var(--md-on-surface-variant);
}
.nc-content-editable .note-code-block { white-space: pre-wrap; }

/* Barre d'outils secondaire du composeur : masquée tant qu'aucun champ n'a
   été touché (voir composerExpand() dans app.js). Agencement calqué sur la
   barre basse de l'édition rapide (.dns-bottom-bar) — mise en forme +
   bascule case à cocher sur une ligne, actions (pièce jointe, couleur,
   libellés, échéance) sur la suivante. */
.nc-toolbar-block { display: flex; flex-direction: column; gap: .25rem; }
.nc-toolbar-block .fmt-toolbar { margin: 0; padding: 0; }
/* Annuler/Ajouter, dernière ligne du bloc d'outils. */
.nc-submit-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: .35rem;
}
/* flex-start et non le space-between hérité de .field-actions-row : ici les
   actions se suivent groupées à gauche, il n'y a pas de "+ Élément" à
   pousser à l'opposé comme dans les boîtes de dialogue. */
.composer-tools-row { margin: 0; justify-content: flex-start; }
.note-composer .palette {
  display: flex;
  gap: .375rem;
  flex-wrap: wrap;
  padding: .25rem 0 .5rem;
}
.note-composer .label-chips { padding-bottom: .25rem; }

/* Lignes du composeur en mode liste : identiques aux cases des notes,
   mais éditables en direct — Entrée sur la dernière ligne en ouvre une autre. */
#nc-items { margin-top: .25rem; }
.composer-item-row {
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .25rem 0;
}
/* Le champ d'une ligne à cocher est le même dans le composeur et dans la
   boîte d'édition : un simple trait sous le texte, qui s'allume au focus.
   Les deux règles étaient identiques mot pour mot — groupées ici pour qu'un
   changement de gabarit ne puisse plus n'en atteindre qu'une. */
.composer-item-row input[type=text],
.dn-item-row input[type=text] {
  flex: 1;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--md-outline-variant);
  color: var(--md-on-surface);
  padding: .3rem 0;
}
.composer-item-row input[type=text]:focus,
.dn-item-row input[type=text]:focus { outline: none; border-bottom-color: var(--md-primary); }
.composer-item-row button { flex: none; opacity: .6; }
.composer-item-row button:hover { opacity: 1; }
/* Le bouton d'échéance échappe à l'atténuation ci-dessus. Elle vise la croix
   de suppression, discrète tant qu'on ne la cherche pas ; le calendrier, lui,
   passe en jaune dès qu'une date est posée (voir .cal-btn.has-due) — à 60 %
   d'opacité ce signal serait délavé et illisible. L'édition rapide
   (.dn-item-row) n'atténue rien, les deux barres se ressemblent ainsi. */
.composer-item-row .cal-btn { opacity: 1; }

/* Vrai masonry en positionnement absolu (voir layoutMosaic() dans app.js) :
   plus une grille CSS. Un CSS Grid classique impose à chaque RANGÉE la
   hauteur de son occupant le plus haut — une notask courte à côté d'une
   très longue laissait un grand vide en dessous d'elle avant la rangée
   suivante. Ici chaque carte est calée directement sous la carte la plus
   basse de sa colonne, sans dépendre de ses voisines.
   --mosaic-min-col/--mosaic-gap : SEULE source de vérité pour ces deux
   valeurs, lues par app.js (getComputedStyle) plutôt que dupliquées en dur
   dans le JS — les garder en px (pas rem) pour que la lecture JS n'ait pas
   à résoudre l'unité. */
.notes-grid {
  position: relative;
  --mosaic-min-col: 240px;
  --mosaic-gap: 16px;
}
.notes-grid .note, .notes-grid .composer-stack { position: absolute; top: 0; left: 0; }
/* Repositionnement animé : au resize, à la fin d'un glisser-déposer, ou
   quand une image chargée en différé change la hauteur d'une carte. Coupé
   pendant qu'une carte est activement tenue (voir .dragging plus bas) —
   sinon elle traînerait derrière le pointeur au lieu de le suivre. */
.notes-grid .note, .notes-grid .composer-stack {
  /* Courbe à léger dépassement plutôt qu'un simple ease : les cartes
     voisines s'écartent en cédant le passage, avec un petit rebond en fin
     de course — l'effet "on se pousse pour laisser passer" recherché
     pendant un glisser. */
  transition: left .26s cubic-bezier(.34, 1.4, .5, 1), top .26s cubic-bezier(.34, 1.4, .5, 1),
              transform .12s ease-out, border-radius .12s ease-out;
}
.notes-grid .note.dragging { transition: none; }
/* Coupe la transition pendant un rendu COMPLET de la grille (voir
   layoutMosaic(true) dans app.js) : sans ça, tout le tableau semblait
   "sauter" depuis le coin en haut à gauche à chaque fermeture de notask,
   quand la grille se reconstruit entièrement à neuf. */
.notes-grid.mosaic-instant .note, .notes-grid.mosaic-instant .composer-stack,
.trash-grid.mosaic-instant .trash-card,
#archived-items-list.mosaic-instant .archived-group,
#trashed-items-list.mosaic-instant .archived-group { transition: none; }

/* Carte Material : aucune bordure, c'est le ton de la surface qui la détache
   du fond. L'élévation ne change qu'au survol.
   `z-index: 0` : fait de .note sa PROPRE zone d'empilement, pour que le
   ::before ci-dessous (z-index: -1, voir plus bas) reste cantonné DERRIÈRE
   le contenu de CETTE carte — sans ça, un z-index négatif remonterait à
   l'ancêtre d'empilement suivant et la carte disparaîtrait sous ses
   voisines dans la mosaïque. */
.note {
  border: none;
  border-radius: var(--shape-sm);
  padding: .875rem 1rem .5rem;
  background: var(--md-surface-2);
  position: relative;
  z-index: 0;
  transition: box-shadow .18s ease;
  /* Supprime le menu contextuel natif iOS ("Copier"/partage…) qu'un appui
     tactile maintenu déclenche par défaut sur n'importe quel élément —
     il apparaîtrait pile pendant la fenêtre d'appui long du réordonnancement
     tactile (voir enablePointerReorder() dans app.js). N'affecte PAS la
     sélection de texte elle-même (user-select reste actif) : recopier le
     texte d'une carte doit continuer à fonctionner normalement. */
  -webkit-touch-callout: none;
}
.note:hover, .note:focus-within { box-shadow: var(--elev-2); }
/* Duplique le fond de .note (couleur posée par .c-XX, voir plus haut) sur
   un calque séparé, DERRIÈRE tout le contenu réel de la carte (dont
   .note-icon) : c'est ce calque, et lui seul, qui est grisé en mode gris
   (html.mode-gris .note::before, voir plus haut) — jamais .note lui-même,
   qui reste un ancêtre protégé de .note-icon (un filter sur .note grisait
   l'icône aussi, voir le commentaire au-dessus des règles html.mode-gris).
   `background: inherit` : reprend simplement le fond déjà posé par .c-XX
   sur .note, sans dupliquer les 24 couleurs de la palette ici. */
.note::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: inherit;
  pointer-events: none;
}
/* Aucune animation de "décrochage" ici : elle jouait sur `transform`, or
   c'est exactement la propriété que le JS pilote pour faire suivre la carte
   au pointeur (voir enablePointerReorder). Une animation CSS l'emporte sur
   un style inline pendant toute sa durée — la carte restait donc figée sur
   place au lieu de suivre le curseur pendant les premières fractions de
   seconde du geste. Le repère visuel de la prise en main passe donc par
   l'opacité et l'ombre, qui n'entrent en conflit avec rien. */
.note.dragging {
  opacity: .55;
  box-shadow: var(--elev-3);
  cursor: grabbing;
}
/* Pendant un glisser : plus aucune sélection de texte possible, où que ce
   soit. Sans ça, le trajet parcouru avant le déclenchement du geste amorce
   une sélection native qui s'étend ensuite à toutes les cartes survolées.
   Posée sur <body> par enablePointerReorder() (voir startDrag), retirée à
   la fin du geste. */
body.is-dragging,
body.is-dragging * {
  user-select: none !important;
  -webkit-user-select: none !important;
  cursor: grabbing !important;
}

/* Icône et titre sur la même ligne, icône à gauche. */
.note-title-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0 1.75rem .375rem 0;   /* place réservée à l'épingle */
}
.note h3 {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 600;
}
/* Description : en italique, discrète — entièrement absente du DOM si vide
   (voir app.js), donc pas de règle "masquer si vide" nécessaire ici. */
.note .description {
  font-style: italic;
  font-size: .8125rem;
  color: var(--md-on-surface-variant);
  margin: 0 1.75rem .5rem 0;
  word-break: break-word;
}

.note .body {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: .875rem;
  line-height: 1.4;
  color: var(--md-on-surface-variant);
  min-height: 1.2em;
  /* La carte s'adapte au texte jusqu'à 10 lignes, puis tronque. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 10;
  line-clamp: 10;
  overflow: hidden;
  /* Même calage à mi-chemin que dans la boîte d'édition (voir
     #dns-content-field) : moitié du padding horizontal de .note (1rem), et
     le même padding+marge négative compensatoire pour que le débord de
     .note-ligne.bloc-actif reste À L'INTÉRIEUR de la boîte de padding de
     .body, jamais au-delà — sinon `overflow: hidden` ci-dessus (nécessaire
     au troncage à 10 lignes) le rognerait net, comme le faisait la
     coercition overflow-x/y dans la boîte d'édition. Le texte ne bouge pas :
     la marge négative annule exactement le padding ajouté. */
  --marge-bloc: .5rem;
  margin: 0 calc(var(--marge-bloc) * -1);
  padding: 0 var(--marge-bloc);
}

/* La première échéance dépasse de moitié au-dessus du premier bloc. Cette
   réserve la garde visible malgré le overflow:hidden utilisé pour limiter la
   carte à dix lignes. Elle s'applique à toute liste, datée ou non, afin que
   l'espacement ne change jamais quand une échéance est ajoutée ou retirée. */
.note .body.has-note-lines { padding-top: .88rem; }

/* Même plafond pour une liste à cocher : au-delà de 10 lignes, le reste
   est simplement masqué (la carte ne s'étire pas indéfiniment).
   Le padding (+ marge négative pour ne pas décaler la liste à l'écran)
   compense le rayon de l'anneau bleu d'une case cochée (outline 2px +
   décalage 1px = 3px) : sans lui, overflow:hidden le tronquait net sur
   les cases proches du bord — d'où l'effet de case "coupée". */
.note ul.check {
  max-height: 16em;
  overflow: hidden;
  padding: 3px;
  margin: .375rem -3px 0 -3px;
}

/* Épingle : visible seulement au survol, sauf si la note est épinglée */
.note .pin-btn {
  position: absolute;
  top: .5rem; right: .5rem;
  width: 32px; height: 32px;
  opacity: 0;
  transition: opacity .18s ease, background .15s;
}
.note .pin-btn:hover { background: rgba(230,224,233,.10); color: var(--md-on-surface); }
.note:hover .pin-btn, .note:focus-within .pin-btn { opacity: 1; }
.note.pinned .pin-btn { opacity: 1; color: var(--md-primary); }

/* Barre d'icônes : apparaît en fondu au survol, comme dans Keep.
   padding-top plutôt que margin-top : une marge appartient à la carte, pas
   à la barre elle-même — un clic dans cet espace passait donc à travers le
   garde-fou qui exclut .actions du clic "ouvrir la note" (constaté à la
   vérification : cliquer juste au-dessus des boutons ouvrait la note quand
   même). Avec du padding, cette zone fait partie de .actions et est donc
   bien exclue. Voir LOG.md, entrée notask du 2026-08-02 (Cowork). */
.note .actions {
  display: flex;
  gap: .125rem;
  padding-top: 1rem;
  opacity: 0;
  transition: opacity .18s ease;
}
.note:hover .actions, .note:focus-within .actions { opacity: 1; }

/* Sur écran tactile il n'y a pas de survol : on garde les icônes visibles */
@media (hover: none) {
  .note .actions, .note .pin-btn { opacity: 1; }
}

.note .actions button {
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  /* Rond visible en permanence (comme .fmt-toolbar button dans les boîtes
     d'édition), pas seulement au survol. Assombrissement (rgba noir) plutôt
     qu'un voile clair : un voile clair posé sur une carte saturée (ex. la
     carte verte, #2f7a3c) en décale visiblement la teinte perçue — un
     assombrissement reste dans la même famille de couleur quelle que soit
     la carte, jaune, bleue ou par défaut. */
  background: rgba(0,0,0,.16);
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface-variant);
  transition: background .15s, color .15s;
}
.note .actions button:hover { background: rgba(0,0,0,.28); color: var(--md-on-surface); }

/* Bascule ACTIVE (contenu masqué, lignes cochées masquées). Cette règle
   MANQUAIT : .active-toggle n'était stylée que dans .fmt-toolbar, si bien
   que le bouton œil avait exactement la même apparence actif ou non — on ne
   pouvait pas savoir, du coin de l'œil, si une notask était masquée.
   Un anneau interne clair + une icône en pleine opacité plutôt qu'un fond
   éclairci : pour la raison déjà donnée plus haut, un voile clair posé sur
   une carte saturée en décalerait la teinte perçue, alors qu'un liseré de
   1,5px n'en couvre presque rien. */
.note .actions button.active-toggle {
  background: rgba(0,0,0,.42);
  color: var(--md-on-surface);
  box-shadow: inset 0 0 0 1.5px rgba(255,255,255,.45);
}
.note .actions button.active-toggle:hover { background: rgba(0,0,0,.5); }
.note .actions .sep { flex: 1; }

/* Corbeille ARMÉE : premier clic passé, le second envoie la notask à la
   corbeille (voir data-act=delete dans renderNotes). Remplace la fenêtre de
   confirmation, qui bloquait tout l'écran pour une action pourtant
   réversible — la corbeille garde 30 jours.

   Rouge plein, pas une simple teinte de l'icône : ce bouton doit changer
   d'état de façon indiscutable, y compris du coin de l'œil. La pulsation
   signale qu'il est armé et qu'il ne le restera pas (désarmement au bout de
   3 s, ou dès que la souris quitte la carte). */
.note .actions button[data-act=delete].arme {
  background: var(--md-error-container);
  color: #f9dedc;
  animation: pouls-suppression 1.1s ease-in-out infinite;
}
.note .actions button[data-act=delete].arme:hover {
  background: var(--md-error-container);
  color: #fff;
}
@keyframes pouls-suppression {
  0%, 100% { box-shadow: 0 0 0 0 rgba(242,184,181,.55); }
  70%      { box-shadow: 0 0 0 6px rgba(242,184,181,0); }
}

/* Archiver s'arme en DEUX clics comme la corbeille (voir armerEnDeuxClics
   dans app.js), mais en ORANGE et non en rouge : le geste est le même, la
   portée ne l'est pas. Le rouge annonce une perte — l'archivage, lui, se
   défait d'un clic depuis les Archives. Garder la même couleur aurait mis
   les deux boutons sur le même plan d'alerte. */
.note .actions button[data-act=archive].arme {
  background: #7a4a12;
  color: #ffe0b2;
  animation: pouls-archivage 1.1s ease-in-out infinite;
}
.note .actions button[data-act=archive].arme:hover {
  background: #8a5514;
  color: #fff;
}
@keyframes pouls-archivage {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,183,77,.55); }
  70%      { box-shadow: 0 0 0 6px rgba(255,183,77,0); }
}

.note svg { width: 18px; height: 18px; display: block; }

/* Icône associée à la note, au-dessus du titre — 50% plus grande que la
   base (20px -> 30px, sur demande explicite) que les icônes fonctionnelles
   de la barre d'actions (.note svg, 18px), pour bien se distinguer d'un
   coup d'œil sur la carte. */
.note-icon { flex: none; display: block; color: var(--md-on-surface-variant); }
.note-icon svg { width: 30px; height: 30px; display: block; }
.note { cursor: pointer; }

/* Palette dépliable dans la carte */
.note .palette {
  display: flex;
  gap: .375rem;
  flex-wrap: wrap;
  padding: .5rem 0 .25rem;
}

.note ul.check { list-style: none; }
/* Padding minimal : une liste à cocher ne doit pas prendre plus de hauteur
   qu'une note en texte simple pour un nombre de lignes équivalent. */
.note ul.check li { display: flex; gap: .625rem; align-items: flex-start; font-size: .875rem; padding: 0; }
.note ul.check li.done span { text-decoration: line-through; opacity: .5; }

.swatches { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .5rem; }
.swatch {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 1px solid var(--md-outline-variant);
  cursor: pointer;
  padding: 0;
}
.swatch:hover { transform: scale(1.1); }
.swatch.active { outline: 2px solid var(--md-primary); outline-offset: 2px; }
.note .palette .swatch { width: 22px; height: 22px; }

/* Couleurs de note — saturées, sur deux rangées.
   Assez soutenues pour se distinguer nettement, assez sombres pour que le
   texte clair reste lisible.

   ÉCLAIRCIES en 0.9042 : les cartes paraissaient sombres, et la mesure le
   confirmait — sur un fond de page à 0,6 % de luminance, une carte marine
   n'avait qu'un contraste de 1,62 avec lui. Autant dire qu'elle ne se
   détachait pas.

   Chaque teinte a été remontée en LUMINOSITÉ HSL uniquement (teinte et
   saturation inchangées, donc la palette reste reconnaissable), jusqu'à la
   limite où le texte principal (--md-on-surface) descendrait sous le seuil
   AA de 4,5. C'est ce plafond qui fixe les valeurs, pas un choix à l'œil.

   Résultat : contraste avec le fond ramené entre 3,1 et 3,6 pour TOUTES les
   couleurs, au lieu de 1,6 à 3,6 — la palette est aussi plus homogène
   qu'avant. Six teintes (ambre, jaune, citron, vert, émeraude, olive) n'ont
   pas bougé : déjà à cette limite, elles étaient les seules réellement
   lumineuses. Leur contraste texte (3,9 à 4,2) est légèrement sous le seuil,
   c'était déjà le cas et rien ici ne l'a aggravé. */
/* --note-color dupliqué à côté de chaque background : permet à un enfant
   (voir .dns-card / .dns-bottom-bar) d'hériter la même teinte sans avoir à
   dupliquer les 21 couleurs ailleurs. */
.c-default { background: var(--md-surface-2); --note-color: var(--md-surface-2); }
.c-red     { background: #ab4047; --note-color: #ab4047; }
.c-coral   { background: #a84633; --note-color: #a84633; }
.c-orange  { background: #8f571d; --note-color: #8f571d; }
.c-amber   { background: #856614; --note-color: #856614; }
.c-yellow  { background: #7a6f12; --note-color: #7a6f12; }
.c-lime    { background: #55771c; --note-color: #55771c; }
.c-green   { background: #2f7a3c; --note-color: #2f7a3c; }
.c-emerald { background: #16785b; --note-color: #16785b; }
.c-teal    { background: #15706f; --note-color: #15706f; }
.c-cyan    { background: #146d8a; --note-color: #146d8a; }
.c-blue    { background: #2366ae; --note-color: #2366ae; }
.c-indigo  { background: #4e5bbf; --note-color: #4e5bbf; }
.c-violet  { background: #6a4fc3; --note-color: #6a4fc3; }
.c-purple  { background: #8a41be; --note-color: #8a41be; }
.c-magenta { background: #a038a0; --note-color: #a038a0; }
.c-pink    { background: #ac3779; --note-color: #ac3779; }
.c-rose    { background: #b13857; --note-color: #b13857; }
.c-brown   { background: #815d45; --note-color: #815d45; }
.c-slate   { background: #56667a; --note-color: #56667a; }
.c-grey    { background: #64646e; --note-color: #64646e; }
/* Trois teintes ajoutées pour compléter les deux rangées de 12 du
   sélecteur (21 -> 24). À répercuter dans COLORS/LABEL_COLOR_HEX d'app.js
   et dans COLORS de app/routers/notes.py. */
.c-navy    { background: #3465a5; --note-color: #3465a5; }
.c-olive   { background: #5d6b2f; --note-color: #5d6b2f; }
/* Blanc pur : à la place de plum (retiré), pour pouvoir remettre du texte
   en blanc — seule teinte claire du nuancier, le reste est volontairement
   sombre pour rester lisible sur le thème. Un léger liseré distingue la
   pastille du fond de la palette quand elle est blanche elle aussi. */
.c-white   { background: #ffffff; --note-color: #ffffff; box-shadow: inset 0 0 0 1px rgba(0,0,0,.25); }

/* Notasks blanches : texte NOIR.
   Le blanc est la seule teinte de la palette assez claire pour que le texte
   clair de l'application (--md-on-surface, quasi blanc) y devienne
   illisible. Plutôt que de repeindre un par un chaque élément d'une carte
   (titre, description, contenu, cases à cocher, étiquettes, échéance, icônes
   des boutons…), on REDÉFINIT LES VARIABLES de couleur sur l'élément blanc
   lui-même : tout ce qu'il contient et qui s'appuie sur var(--md-on-surface)
   ou var(--md-on-surface-variant) bascule alors en sombre par simple
   héritage, y compris les icônes SVG dessinées en currentColor.

   Le `color:` explicite est indispensable en plus des variables : une carte
   n'a pas de couleur de texte propre, elle hérite celle du <body>, déjà
   résolue en clair — redéfinir la variable seule ne la rattraperait pas.

   .composeur-clair est posée par appliquerCouleurComposeur() dans app.js :
   le composeur, lui, reçoit sa teinte en style inline et non par une classe
   .c-*, il ne serait donc pas atteint par le sélecteur ci-dessous. */
.note.c-white,
.trash-card.c-white,
.history-item.c-white,
.agenda-item.c-white,
dialog.c-white,
.nc-card.composeur-clair,
.nc-toolbar-block.composeur-clair {
  --md-on-surface: #16151a;
  --md-on-surface-variant: #46454d;
  --md-outline-variant: rgba(0,0,0,.16);
  color: var(--md-on-surface);
}
/* La .cal-popup est accrochée au dialog mais garde le thème sombre global —
   on réinitialise les variables que dialog.c-white aurait héritées, sinon
   le picker apparaît avec du texte foncé sur fond sombre. */
dialog.c-white .cal-popup {
  --md-on-surface: #e6e0e9;
  --md-on-surface-variant: #cac4d0;
  --md-outline-variant: #49454f;
  color: var(--md-on-surface);
}
/* Exception pour la colonne d'échéances : sa pastille "blanche" n'est PAS
   blanche mais grise, la teinte y étant atténuée sur le fond sombre de la
   page (voir ATTENUATION_PASTILLE dans app.js). Le gris secondaire ci-dessus
   n'y tenait qu'un contraste de 3,4:1 sur la ligne de date — sous le seuil
   lisible pour un texte de 11px. On le fonce donc, ce qui le remonte à
   5,2:1. Mesuré, pas estimé. */
.agenda-item.c-white { --md-on-surface-variant: #2b2a30; }

/* ---- Corbeille ---- : cartes en lecture seule, mêmes proportions que
   .notes-grid/.note mais sans interaction (pas de clic pour éditer, pas de
   glisser-déposer) — seules actions possibles : restaurer ou supprimer
   définitivement. Pas de `background` fixe ici, même piège que .agenda-item :
   la couleur vient de .c-*, posée après dans ce fichier. */
/* Même masonry que l'accueil (voir .notes-grid et layoutMosaic dans app.js),
   plus une grille CSS en rangées : une notask courte à côté d'une longue
   laissait sinon un grand vide sous elle jusqu'à la rangée suivante. */
.trash-grid {
  position: relative;
  --mosaic-min-col: 240px;
  --mosaic-gap: 16px;
}
.trash-grid .trash-card {
  position: absolute;
  top: 0;
  left: 0;
  transition: left .26s cubic-bezier(.34, 1.4, .5, 1), top .26s cubic-bezier(.34, 1.4, .5, 1);
  -webkit-touch-callout: none;   /* cf. .note : pas de menu iOS pendant l'appui long */
}
/* Pendant le glisser : transition coupée, sinon la carte tenue traînerait
   derrière le pointeur au lieu de le suivre (même raison que
   .notes-grid .note.dragging). Le repère visuel passe par l'opacité et
   l'ombre, jamais par `transform` — c'est la propriété que le JS pilote. */
.trash-grid .trash-card.dragging { transition: none; }
.trash-card.dragging {
  opacity: .55;
  box-shadow: var(--elev-3);
  cursor: grabbing;
}
.trash-card { cursor: pointer; }
.trash-card {
  border: none;
  border-radius: var(--shape-sm);
  padding: .875rem 1rem;
  opacity: .85;
}
.trash-card-title-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0 0 .375rem 0;
}
.trash-card-title-row h3 { margin: 0; font-size: 1.0625rem; font-weight: 600; }
.trash-card .description {
  font-style: italic;
  font-size: .8125rem;
  color: var(--md-on-surface-variant);
  margin: 0 0 .5rem 0;
  word-break: break-word;
}
.trash-card-snippet {
  font-size: .8125rem;
  line-height: 1.4;
  color: var(--md-on-surface-variant);
  white-space: pre-wrap;
  word-break: break-word;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
  /* Même raison qu'sur .note .body : .trash-card a 1rem de padding
     horizontal, la moitié sert de --marge-bloc pour que le fond de bloc
     puisse déborder sans jamais franchir la boîte de padding (que
     `overflow: hidden` ci-dessus rognerait sinon net). */
  --marge-bloc: .5rem;
  margin: 0 calc(var(--marge-bloc) * -1);
  padding: 0 var(--marge-bloc);
}
.trash-card-meta {
  margin-top: .625rem;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
}
.trash-card-actions {
  display: flex;
  gap: .5rem;
  margin-top: .75rem;
  flex-wrap: wrap;
}
.trash-card-actions button { display: inline-flex; align-items: center; gap: .35rem; }
.trash-card-actions button svg { width: 16px; height: 16px; }

/* ---- Masquage "terminal" du contenu d'une carte (accueil uniquement) ----
   Terminal qui tape du code et des journaux, dans l'esprit d'un écran à
   phosphore façon Fallout. Le texte réel n'est pas dans le DOM : le cacher
   en CSS le laisserait lisible dans l'inspecteur et sur bien des captures.
   Hauteur fixe : la carte garde une taille stable et ne trahit donc pas la
   longueur du contenu masqué. */
.term-mask {
  position: relative;
  height: 8.5em;
  margin-top: .5rem;
  overflow: hidden;
  border-radius: var(--shape-xs);
  /* Fond très sombre : c'est lui qui fait ressortir le phosphore, et il
     garantit le contraste même sur les cartes les plus claires. */
  background: rgba(0, 0, 0, .62);
  user-select: none;
}
.term-body {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;   /* la frappe se fait en bas, ça remonte */
  height: 100%;
  padding: .45rem .6rem;
  font-family: "Px437 IBM VGA", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .72rem;
  line-height: 1.35;
}
.term-line {
  white-space: pre;
  overflow: hidden;
  text-overflow: clip;
  /* Teinte de la carte (--note-color, posée par .c-* et héritée jusqu'ici)
     plutôt qu'un vert fixe. Ces teintes sont conçues pour servir de FOND,
     donc trop sombres telles quelles : le filtre les éclaircit et les
     sature pour en faire une couleur de trait lisible. */
  color: var(--note-color, #3ddc6b);
  filter: brightness(2.6) saturate(1.35);
  text-shadow: 0 0 5px currentColor;
}
/* Les anciennes lignes s'effacent progressivement vers le haut : on lit
   naturellement la frappe en cours plutôt que le bloc entier. */
.term-line:nth-last-child(2) { opacity: .85; }
.term-line:nth-last-child(3) { opacity: .68; }
.term-line:nth-last-child(4) { opacity: .52; }
.term-line:nth-last-child(5) { opacity: .38; }
.term-line:nth-last-child(6) { opacity: .26; }
.term-line:nth-last-child(n+7) { opacity: .16; }

/* Erreurs en rouge, indépendantes de la couleur de la carte : c'est
   justement leur intérêt de trancher. filter:none, sinon l'éclaircissement
   hérité les délave. */
.term-erreur {
  color: #ff5252 !important;
  filter: none !important;
  text-shadow: 0 0 6px rgba(255, 82, 82, .7);
}
/* Invites façon terminal Fallout : ambre, un cran plus clair. */
.term-fallout { filter: brightness(2.9) saturate(1.1); letter-spacing: .04em; }
.term-log { opacity: .95; }

/* Curseur plein qui clignote, comme sur un terminal à phosphore. */
.term-caret { animation: term-caret 1.05s steps(1) infinite; }
@keyframes term-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }

/* Lignes de balayage + léger vignetage : la signature de l'écran cathodique.
   pointer-events:none — ce voile ne doit jamais intercepter un clic sur la
   carte. */
.term-scan {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    repeating-linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0) 0px,
      rgba(0, 0, 0, 0) 2px,
      rgba(0, 0, 0, .28) 3px,
      rgba(0, 0, 0, .28) 4px
    ),
    radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 55%, rgba(0, 0, 0, .45) 100%);
  animation: term-scintille 3.6s ease-in-out infinite;
}
/* Scintillement très léger : perceptible sans être fatigant. */
@keyframes term-scintille {
  0%, 100% { opacity: 1; }
  47% { opacity: .93; }
  52% { opacity: 1; }
}

/* État enfoncé d'un bouton bascule de la barre de mise en forme (liste à
   cocher, masquage) — la règle existait déjà pour le seul bouton du
   composeur, généralisée ici aux deux barres. */
.fmt-toolbar button.active-toggle {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
}

/* Échéance affichée sur la carte de note */
.note-due {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  margin-top: .625rem;
  padding: .25rem .7rem .25rem .45rem;
  border-radius: var(--shape-full);
  /* Voile clair très léger, volontairement DISCRET : sur une carte colorée
     il ne dessine pas de pastille détachée, l'échéance se lit comme une
     mention posée sur la carte. C'est le parti pris retenu, et la pastille
     des lignes à cocher a été alignée dessus (voir .note-ligne-due). */
  background: rgba(230,224,233,.09);
  font-size: .75rem;
  /* Blanc franc et non le gris secondaire : sur un voile aussi transparent,
     c'est la couleur de la CARTE qu'on a derrière le texte. Mesuré sur les
     teintes réelles, le gris #cac4d0 n'y tenait que 2,7 à 3,0:1 — sous le
     seuil lisible — quand le blanc pur atteint 4,6 à 5,2:1. Seule façon de
     garder ce fond discret sans rendre la date pénible à lire. */
  color: #fff;
}
.note-due svg { width: 14px; height: 14px; }
.note-due input[type=checkbox] { width: 18px; height: 18px; }
/* En retard : le rouge passe du TEXTE au fond. Le rose pâle --md-error ne
   tenait que 2,9:1 sur une carte magenta — du rouge clair sur du magenta,
   deux teintes trop proches — alors qu'un voile rouge sous du texte blanc
   donne 6,3:1. L'alerte est donc portée par le fond, qui reste assez léger
   (mesuré à 1,07:1 sur magenta) pour ne pas redessiner la pastille franche
   dont on ne veut pas. */
.note-due.late { background: rgba(180, 35, 40, .32); color: #fff; }
.note-due.done { opacity: .6; }

/* Notasks BLANCHES : le texte redevient sombre (voir .c-white plus haut, qui
   redéfinit --md-on-surface). Sans cette règle, le blanc franc ci-dessus
   resterait du blanc sur blanc. Le voile passe en noir très léger pour que
   la mention se distingue quand même du fond de la carte. */
.c-white .note-due,
.c-white .note-ligne-due {
  background: rgba(0, 0, 0, .05);
  color: var(--md-on-surface);
}
.c-white .note-due.late { background: rgba(160, 32, 36, .10); color: #8c1d21; }
.note-due.done span { text-decoration: line-through; }

/* Échéance d'une ligne à cocher, à même la note */
.item-due-tag {
  margin-left: .4rem;
  padding: .05rem .4rem;
  border-radius: var(--shape-full);
  background: rgba(230,224,233,.10);
  font-size: .6875rem;
  font-style: normal;
  color: var(--md-on-surface-variant);
}

.dn-item-row { display: flex; gap: .4rem; align-items: center; margin-bottom: .35rem; }

/* Boutons-icônes des barres d'outils (trombone, historique, calendrier,
   couleur, libellés) : rond clair visible en permanence, pas seulement au
   survol — même traitement que .fmt-toolbar button et que les icônes de
   la carte (.note .actions button), pour que toutes les icônes de l'app
   se ressemblent. L'icône calendrier passe en jaune dès qu'une échéance
   est réglée. */
.cal-btn {
  flex: none;
  width: 32px;
  height: 32px;
  display: inline-grid;
  place-items: center;
  background: rgba(255,255,255,.14);
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface);
  cursor: pointer;
  transition: color .15s ease, background .15s ease;
}
.cal-btn svg { width: 19px; height: 19px; }
.cal-btn:hover { background: rgba(255,255,255,.24); color: var(--md-on-surface); }
.cal-btn.has-due { color: var(--spoon-jaune); background: rgba(var(--spoon-bleu-rgb), .36); }
.cal-btn.has-due:hover { background: rgba(var(--spoon-bleu-rgb), .56); }

/* Enregistrement (note vocale) ou dictée en cours. Rouge + pulsation : un
   micro ouvert doit se voir sans ambiguïté, y compris du coin de l'œil —
   c'est le seul état de l'appli qui capte quelque chose en continu. */
.cal-btn.is-recording {
  color: #ff5252;
  background: rgba(255,82,82,.16);
  animation: pouls-enregistrement 1.4s ease-in-out infinite;
}
.cal-btn.is-recording:hover { background: rgba(255,82,82,.28); }
@keyframes pouls-enregistrement {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,82,82,.5); }
  70%      { box-shadow: 0 0 0 7px rgba(255,82,82,0); }
}
/* Aucun repli sous prefers-reduced-motion, ici comme ailleurs dans l'app :
   les animations sont voulues dans tous les cas (choix explicite de
   l'utilisateur sur cette application personnelle). */

/* Apparition « gélatine » d'une nouvelle ligne à cocher : elle jaillit
   écrasée puis rebondit en s'étirant alternativement en hauteur et en
   largeur avant de se stabiliser. Une suite d'images clés et non une simple
   courbe : un cubic-bezier ne fait varier qu'une échelle uniforme et ne
   peut pas produire cette compensation entre les deux axes, qui est
   justement ce qui donne l'impression de matière molle.
   transform-origin à gauche : la ligne se déploie depuis sa case à cocher,
   pas depuis son milieu. */
@keyframes ligne-gelatine {
  0%   { transform: scale(.55, .1); opacity: 0; }
  30%  { transform: scale(1.04, 1.28); opacity: 1; }
  50%  { transform: scale(.99, .86); }
  68%  { transform: scale(1.01, 1.1); }
  84%  { transform: scale(.998, .96); }
  100% { transform: scale(1, 1); opacity: 1; }
}
.composer-item-row.ligne-apparait,
.dn-item-row.ligne-apparait {
  animation: ligne-gelatine .5s cubic-bezier(.3, .9, .4, 1);
  transform-origin: left center;
}

.due-row { display: flex; align-items: center; gap: .625rem; }

/* "+ Élément" et l'échéance sur la même ligne plutôt que l'un sous l'autre :
   le bouton d'ajout à gauche, l'échéance à droite. */
.field-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
}
.due-label { font-size: .875rem; color: var(--md-on-surface-variant); }
/* Rangée d'actions des barres d'outils (composeur et édition rapide) : les
   icônes se suivent groupées à gauche. Sélecteur à deux classes, et placé
   APRÈS .field-actions-row : à spécificité égale c'est l'ordre du fichier
   qui tranche, et .field-actions-row (déclaré ici) écrasait sinon le
   justify-content posé plus haut — d'où les grands écarts entre icônes. */
.field-actions-row.composer-tools-row {
  justify-content: flex-start;
  gap: .5rem;
  margin: 0;
}

/* Popup de sélection date + heure */
.cal-popup {
  position: absolute;
  z-index: 1000;
  /* 285px et non 240 : à 240, le champ date (flex:1, min-width:0) était
     comprimé sous la largeur du format jj/mm/aaaa et le navigateur tronquait
     l'année à l'affichage (« 03/08/20 »). La largeur doit tenir la date
     complète + heure + minutes sur une ligne. */
  width: 285px;
  padding: .75rem;
  background: var(--md-surface-4);
  border-radius: var(--shape-sm);
  box-shadow: var(--elev-3);
}
.cal-popup-input {
  width: 100%;
  padding: .5rem .625rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
}
/* Contour de sélection bleu (couleur de la cuillère bleue), pas le mauve
   par défaut du navigateur. */
.cal-popup-input:focus { outline: 2px solid var(--spoon-bleu); outline-offset: 1px; }
.cal-popup-actions {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  margin-top: .625rem;
}
/* Popover d'édition de libellé uniquement : Supprimer à gauche, Fermer/
   Enregistrer groupés à droite — l'espaceur absorbe la place restante
   plutôt que de compter sur justify-content:space-between (qui répartirait
   les 4 boutons à intervalles égaux, collant "Fermer" au milieu). */
.cal-popup-actions-spacer { flex: 1; }
/* Petit rond avec une croix plutôt qu'un troisième bouton texte "Supprimer"
   — à côté de "Fermer"/"Enregistrer" dans une largeur de popup aussi
   réduite, un bouton texte plein débordait du cadre. */
.label-delete-btn {
  flex: none;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgba(230,224,233,.08);
  color: var(--md-error);
  cursor: pointer;
}
.label-delete-btn svg { width: 15px; height: 15px; display: block; }
.label-delete-btn:hover { background: var(--md-error-container); }
/* Popup d'édition de libellé : un peu plus large que la base .cal-popup
   (240px, pensée pour le calendrier) pour laisser respirer les trois
   actions côte à côte. */
.label-edit-popup { width: 270px; }

/* Ligne date + heure + minutes (menus déroulants maison, minutes limitées à
   00/15/30/45 — voir openCalPopup dans app.js). */
.cal-popup-row { display: flex; align-items: center; gap: .375rem; }
.cal-popup-date {
  flex: 1;
  /* Pas de min-width:0 ici : c'est justement ce qui autorisait le champ à
     descendre sous la largeur du format jj/mm/aaaa et à tronquer l'année. */
  min-width: 118px;
  padding: .5rem .5rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
}
.cal-popup-hour, .cal-popup-min {
  padding: .5rem .375rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
}
.cal-popup-colon { color: var(--md-on-surface-variant); }
/* Regroupe heure+minutes dans un span (voir champsHeure() dans openCalPopup,
   app.js) — nécessaire pour pouvoir les masquer d'un bloc en mode « journée
   complète » sans toucher au champ date, qui reste toujours visible. Même
   espacement que .cal-popup-row pour ne rien changer visuellement. */
.cal-popup-heure { display: flex; align-items: center; gap: .375rem; }

/* Mode « période » : une seconde ligne date+heure pour la fin de plage.
   Même gabarit que la première ligne, d'où la réutilisation des classes
   .cal-popup-date / -hour / -min sur les champs de fin. */
.cal-popup-periode {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: .5rem 0 .35rem;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
  cursor: pointer;
}
.cal-popup-periode input { margin: 0; cursor: pointer; }
.cal-popup-date-end {
  flex: 1;
  min-width: 118px; /* cf. .cal-popup-date */
  padding: .5rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
}
.cal-popup-erreur {
  margin: .4rem 0 0;
  font-size: .7rem;
  line-height: 1.3;
  color: #ef9a9a;
}

/* Récurrence : « Répéter » + menu Jamais/Chaque semaine/Chaque année. Même
   gabarit que .cal-popup-periode juste au-dessus, en ligne plutôt qu'en case
   à cocher puisqu'il y a plus de deux états. Rappel côté logique (voir
   openCalPopup dans app.js) : ne reprogramme la tâche qu'au moment où elle
   est cochée terminée, jamais avant. */
.cal-popup-recur-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  margin: .6rem 0 .35rem;
  font-size: .75rem;
  color: var(--md-on-surface-variant);
}
.cal-popup-recur {
  padding: .4rem .5rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
  cursor: pointer;
}

/* Case à cocher Material : carré à coins doux, coche dessinée en CSS.
   Remplace le rendu par défaut du navigateur sur toutes les cases de
   l'app — même taille partout (édition comme affichage), pour ne laisser
   aucune ambiguïté entre ce qu'on édite et ce qu'on voit sur la carte. */
input[type=checkbox] {
  appearance: none;
  -webkit-appearance: none;
  width: 16.5px;
  height: 16.5px;
  flex: none;
  margin: 0;
  /* Sans ce reset, une case nichée dans un .field (ex. .dn-item-row en
     édition complète) hérite du padding de « .field input » (règle plus
     générique). Même quand width/height gagnent bien la cascade, Chromium
     ajoute ce padding par-dessus la taille déclarée pour ce type de champ
     (case à cocher) au lieu de le contenir dans la boîte — la case rendue
     grossit alors bien au-delà de 16.5px. Vérifié en isolant le cas dans un
     navigateur headless : suffit à lui seul à corriger la taille. */
  padding: 0;
  border: 2px solid var(--md-outline);
  border-radius: 4px;
  background: transparent;
  display: inline-grid;
  place-content: center;
  cursor: pointer;
  position: relative;
  transition: background .15s ease, border-color .15s ease, outline-color .15s ease;
}
/* Zone cliquable élargie, invisible, sans changer la taille affichée.
   À 16.5px de côté, un clic un peu à côté tombait sur le conteneur (ligne
   de liste, ligne d'échéance) et ouvrait la notask au lieu de cocher —
   d'où l'impression que la case "ne marche pas des fois". Le pseudo-
   élément appartient à la case : la cible de l'événement reste bien
   l'<input>, donc les gardes `closest('input')` des gestionnaires de clic
   continuent de fonctionner sans modification. */
input[type=checkbox]::before {
  content: '';
  position: absolute;
  inset: -7px;
  border-radius: 50%;
}
/* Coche en V, bords arrondis. */
input[type=checkbox]::after {
  content: '';
  width: 6px;
  height: 11px;
  margin-top: -1.5px;
  border: solid #1b1300;
  border-width: 0 3.5px 3.5px 0;
  border-radius: 0 3px 3px 3px;
  transform: rotate(45deg) scale(0);
  transition: transform .12s ease;
}
/* Coché : coeur jaune, liseret bleu à l'extérieur — écho des cuillères. */
input[type=checkbox]:checked {
  background: var(--spoon-jaune);
  border-color: var(--spoon-jaune);
  outline: 2px solid var(--spoon-bleu);
  outline-offset: 1px;
}
input[type=checkbox]:checked::after { transform: rotate(45deg) scale(1); }
input[type=checkbox]:hover { border-color: var(--md-on-surface); }
input[type=checkbox]:focus-visible { outline: 2px solid var(--md-primary); outline-offset: 2px; }
input[type=checkbox]:disabled { opacity: .4; cursor: not-allowed; }

/* Agenda : case à cocher — fond bleu notask + liseré jaune.
   Sélecteur `input[type=checkbox].agenda-item-check` et NON `.agenda-item-check`
   seul : le reset générique `input[type=checkbox]` plus haut pèse 0-1-1
   (type + attribut) contre 0-1-0 pour une simple classe — il gagnait donc la
   cascade quel que soit l'ordre, et son `background: transparent` écrasait ce
   fond. Ne pas retirer le `input[type=checkbox]` de tête. */
input[type=checkbox].agenda-item-check {
  border-color: var(--spoon-jaune);
  background: rgba(var(--spoon-bleu-rgb), .36);
}
input[type=checkbox].agenda-item-check:hover { border-color: var(--spoon-jaune); }
input[type=checkbox].agenda-item-check:checked {
  background: rgba(var(--spoon-bleu-rgb), .70);
  border-color: var(--spoon-jaune);
  outline: none;
}
input[type=checkbox].agenda-item-check:checked::after { border-color: var(--spoon-jaune); }

/* ============================= Libellés ============================= */

.drawer-item.label-item { padding-left: 2.5rem; padding-right: 2.25rem; }

/* Ligne de libellé : le crayon n'apparaît qu'au survol de la ligne, en
   overlay à droite — le bouton lui-même reste inchangé (pilule collée à
   gauche, arrondie à droite, couleur propre en fond permanent si réglée). */
.label-row { position: relative; margin-bottom: 4px; -webkit-touch-callout: none; }
.label-row.dragging { opacity: .4; }
.label-edit-btn {
  position: absolute;
  right: .5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  opacity: 0;
  transition: opacity .15s ease, background .15s ease, color .15s ease;
}
.label-edit-btn svg { width: 15px; height: 15px; }
.label-edit-btn:hover { background: rgba(230,224,233,.14); color: var(--md-on-surface); }
.label-row:hover .label-edit-btn { opacity: 1; }
@media (hover: none) { .label-edit-btn { opacity: 1; } }

.label-color-grid { display: flex; flex-wrap: wrap; gap: .4rem; margin: .625rem 0; }
.label-color-opt { width: 22px; height: 22px; }
.label-color-opt[data-color=""] { background: var(--md-surface-4); }

/* Même gabarit qu'un libellé (pilule .drawer-item.label-item : hauteur,
   arrondi, retrait à gauche) mais sans couleur de fond — transparent avec
   un simple contour de 1px, pour se lire comme un emplacement à remplir
   plutôt que comme un libellé existant. */
.label-new-row {
  display: flex;
  align-items: center;
  gap: .4rem;
  min-height: 48px;
  /* Aucune marge à gauche : la pilule part du bord de l'écran comme les
     libellés au-dessus. Le retrait du texte (2.5rem) reprend celui de
     .drawer-item.label-item, pour que les intitulés s'alignent.
     4px au-dessus : le même écart que celui qui sépare deux libellés
     entre eux (voir .label-row). */
  margin: 4px 0 0;
  padding: 0 .5rem 0 2.5rem;
  background: transparent;
  border: 1px solid var(--md-outline-variant);
  /* Bord gauche droit, bord droit arrondi : le menu vient s'appuyer sur le
     bord gauche de l'écran, comme les libellés au-dessus. */
  border-left: none;
  border-radius: 0 var(--shape-full) var(--shape-full) 0;
}
.label-new-row input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  font-size: .875rem;
  padding: 0;
}
/* Le contour de sélection est porté par la carte entière, pas par le champ :
   c'est la pilule qui fait office d'encadré (voir .label-new-row). */
.label-new-row input:focus { outline: none; }
.label-new-row:focus-within { border-color: var(--spoon-bleu); }
.label-new-row button { flex: none; opacity: .7; }
.label-new-row button:hover { opacity: 1; }

/* Chips de libellés dans le dialogue d'édition (complet ou simple) — liste
   complète, coche/décoche à cocher, voir renderLabelChipsInto(). */
.label-chips { display: flex; gap: .375rem; flex-wrap: wrap; margin-top: .5rem; }
.label-chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .2rem .7rem;
  border-radius: var(--shape-full);
  background: rgba(230,224,233,.09);
  color: var(--md-on-surface-variant);
  font-size: .75rem;
  cursor: pointer;
  border: none;
  transition: background .15s, color .15s;
}
.label-chip:hover { background: rgba(230,224,233,.16); }
/* Tag portant une couleur (fond posé en style inline par renderCardLabels /
   renderNoteLabelChips) : texte en blanc plein plutôt que le gris atténué
   ci-dessus, qui manquait de contraste depuis que ces tags portent la teinte
   PLEINE de la palette et non plus une version à 55 %. Sélecteur [style] :
   c'est la seule marque, en CSS, d'un tag effectivement coloré — même
   mécanique que .drawer-item.label-item[style] pour le menu de gauche. */
.label-chip[style] { color: var(--md-on-surface); }
/* Liseré de 1px sur un tag coloré : sans lui, un tag posé sur une notask de
   la MÊME couleur se fondait dedans et disparaissait — le fond du tag et
   celui de la carte étant alors identiques (à l'atténuation près, voir
   ATTENUATION_LIBELLE, qui ne creuse justement plus assez d'écart depuis
   qu'elle est à 75 %).

   Un blanc translucide, et non une couleur fixe : il éclaircit ce qu'il y a
   dessous quelle que soit la teinte de la carte, alors qu'un gris choisi une
   fois pour toutes se verrait trop sur les cartes sombres et pas assez sur
   les claires.

   Posé en box-shadow INSET plutôt qu'en `border` : une bordure s'ajoute aux
   dimensions de la pastille et décalerait toute la rangée de tags de 2px,
   là où l'ombre intérieure se dessine sans rien pousser. */
.label-chip[style] { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .30); }
.label-chip.active {
  background: var(--md-secondary-container);
  color: var(--md-on-secondary-container);
}

/* Rangée de libellés en bas d'une carte de note (mosaïque) — voir
   renderCardLabels() dans app.js. Toujours visible (contrairement à
   .actions, qui n'apparaît qu'au survol) : ce sont les libellés déjà
   posés sur la notask, pas un réglage secondaire. */
.note-labels {
  display: flex;
  align-items: center;
  gap: .375rem;
  flex-wrap: wrap;
  margin-top: .625rem;
}
/* Couleur posée en style inline par JS (voir renderCardLabels() et
   renderLabelChipsInto()), pas en classe .c-* : un style inline gagne
   toujours, même sur .label-chip:hover ci-dessus — même piège déjà
   rencontré et corrigé sur .drawer-item (voir renderLabelsDrawer()), qui
   utilise la même technique. Pas scopé à .note : réutilisé tel quel dans
   la boîte de dialogue (#dns-labels), pas seulement sur la carte. */
.label-chip-card {
  cursor: default;
  /* Retrait droit plus faible : c'est la croix de suppression qui occupe
     la place de ce côté (dans les boîtes d'édition uniquement). */
  padding: .2rem .3rem .2rem .7rem;
  color: var(--md-on-surface);
}
/* Sur la carte (mosaïque), les puces sont en lecture seule : pas de croix,
   donc rien pour combler le retrait droit réduit — le texte paraissait
   collé à droite et décalé. Retrait symétrique ici. */
.label-chip-card.is-readonly { padding: .2rem .7rem; }
/* Croix de retrait d'un libellé, dans les dialogues d'édition : masquée,
   révélée au survol de la puce (comportement demandé).
   Historique, pour ne pas refaire le tour : cette croix a été signalée
   plusieurs fois comme n'apparaissant jamais au survol, et elle a été
   passée en permanence visible faute de pouvoir reproduire. Le test
   navigateur a ensuite montré que le rendu des puces (couleurs, croix
   présentes dans le DOM, bouton +) était bien correct. Retour au survol
   à la demande. Si le problème réapparaît, le suspect n'est PLUS le
   rendu des puces : vérifier d'abord que la fenêtre est bien au premier
   plan (un onglet en arrière-plan ne recalcule pas :hover) puis la
   présence de la règle ci-dessous dans la feuille servie. */
.label-chip-x {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  opacity: 0;
  cursor: pointer;
  transition: opacity .12s ease, background .12s ease;
}
.label-chip-x svg { width: 12px; height: 12px; display: block; }
.label-chip-card:hover .label-chip-x,
.label-chip-card:focus-within .label-chip-x { opacity: .8; }
.label-chip-x:hover { opacity: 1; background: rgba(0,0,0,.25); }
/* Bouton "+" en fin de rangée, dans la boîte de dialogue (#dns-labels) —
   ouvre .label-add-picker, la liste des libellés pas encore posés. Plus
   utilisé sur la carte de la mosaïque (remplacé par le bouton "Libellés"
   de la barre d'options, voir renderCardLabels()). */
.label-add-btn {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: rgba(0,0,0,.16);
  color: var(--md-on-surface-variant);
  cursor: pointer;
}
.label-add-btn svg { width: 14px; height: 14px; display: block; }
.label-add-btn:hover { background: rgba(0,0,0,.28); color: var(--md-on-surface); }
.label-add-picker {
  display: flex;
  gap: .375rem;
  flex-wrap: wrap;
  padding: .5rem 0 0;
}
.label-add-picker .hint { font-size: .75rem; color: var(--md-on-surface-variant); }

/* ============================= Comptes ============================= */

table.users { width: 100%; border-collapse: collapse; }
table.users th, table.users td {
  text-align: left;
  padding: .875rem .75rem;
  border-bottom: 1px solid var(--md-outline-variant);
  font-size: .875rem;
}
table.users th { color: var(--md-on-surface-variant); font-weight: 500; font-size: .75rem; letter-spacing: .5px; }
.badge {
  font-size: .6875rem;
  font-weight: 500;
  padding: .2rem .625rem;
  border-radius: var(--shape-full);
  background: var(--md-surface-4);
  color: var(--md-on-surface-variant);
}
.badge.admin { background: var(--md-primary-container); color: var(--md-on-primary-container); }
.badge.off { background: var(--md-error-container); color: #f9dedc; }

/* Config Google Calendar (identifiants OAuth de l'installation) dans la
   boîte Profil — voir /api/google/admin-config. Masqué en entier pour un
   compte non admin (voir #btn-profil dans app.js). Empilé juste au-dessus
   de .profil-google (connexion personnelle) : même gabarit de bordures,
   pour que les deux sections s'enchaînent proprement. */
.admin-google {
  padding: 1rem 0;
  border-top: 1px solid var(--md-outline-variant);
}
.admin-google h3 { margin: 0 0 .25rem; font-size: .9375rem; font-weight: 500; }
.admin-google-hint {
  margin: 0 0 1rem;
  font-size: .8125rem;
  color: var(--md-on-surface-variant);
}
.admin-google .dialog-actions { margin: 0; }

/* ============================ Dialogues ============================ */

dialog {
  position: relative;
  background: var(--md-surface-3);
  color: var(--md-on-surface);
  border: none;
  border-radius: var(--shape-lg);
  padding: 1.5rem;
  /* 460px -> 552px (+20%), sur demande explicite — d'abord posé sur
     l'édition simple seule, puis étendu à toutes les boîtes qui partagent
     cette largeur de base (celles avec leur propre override, comme
     #dlg-outils, sont augmentées séparément juste en dessous). */
  width: min(552px, 92vw);
  box-shadow: var(--elev-3);
}
dialog::backdrop { background: rgba(0,0,0,.5); }

/* ---- Ouverture : la boîte "pousse" depuis le point cliqué ----
   Matière souple plutôt qu'un simple zoom : elle dépasse un peu sa taille
   finale, puis se stabilise en alternant étirement horizontal et vertical
   d'amplitude décroissante — l'écrasement/rebond qu'aurait un objet
   élastique. Les deux axes sont mis à l'échelle séparément (scale x, y),
   c'est ce décalage entre largeur et hauteur qui donne l'effet gélatine ;
   un scale uniforme ne ferait que grossir.
   L'origine (transform-origin) est posée en JS sur le point cliqué, voir
   animerOuvertureDialogue() dans app.js. */
/* Étapes calculées, pas posées à l'œil : montée rapide vers la taille
   finale (exponentielle) + oscillation amortie en antiphase sur les deux
   axes. La montée est volontairement plus rapide que l'amortissement, pour
   que le ballotement se voie AUTOUR de la taille finale (dépassement
   jusqu'à ~1,04) et non en dessous — sinon la boîte a l'air de monter
   mollement au lieu de rebondir.
   Un arrêt tous les 5% : assez dense pour que l'interpolation reste fidèle
   à 60 images/s sur 1 s, sans écrire 60 lignes illisibles. */
@keyframes dlg-gelatine {
  0% { transform: scale(0.2500, 0.2500); opacity: 0; }
  5% { transform: scale(0.6606, 0.4740); opacity: 0.33; }
  10% { transform: scale(0.8741, 0.6266); opacity: 0.67; }
  15% { transform: scale(0.9510, 0.7610); }
  20% { transform: scale(0.9512, 0.8826); }
  25% { transform: scale(0.9272, 0.9769); }
  30% { transform: scale(0.9140, 1.0307); }
  35% { transform: scale(0.9250, 1.0431); }
  40% { transform: scale(0.9557, 1.0258); }
  45% { transform: scale(0.9919, 0.9975); }
  50% { transform: scale(1.0192, 0.9746); }
  55% { transform: scale(1.0299, 0.9666); }
  60% { transform: scale(1.0244, 0.9736); }
  65% { transform: scale(1.0097, 0.9891); }
  70% { transform: scale(0.9945, 1.0048); }
  75% { transform: scale(0.9853, 1.0143); }
  80% { transform: scale(0.9845, 1.0153); }
  85% { transform: scale(0.9903, 1.0096); }
  90% { transform: scale(0.9985, 1.0014); }
  95% { transform: scale(1.0052, 0.9947); }
  100% { transform: scale(1.0000, 1.0000); }
}
@keyframes dlg-backdrop-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Fermeture : la boîte se rétracte vers le point d'où elle est sortie
   (transform-origin posée à l'ouverture, laissée telle quelle). Bien plus
   courte que l'ouverture — 0,26 s contre 1 s : on veut revenir à ses notes,
   pas regarder une sortie. Juste un léger gonflement au départ, comme un
   élastique qu'on relâche, puis rétraction franche. */
@keyframes dlg-gelatine-out {
  0%   { transform: scale(1, 1); opacity: 1; }
  25%  { transform: scale(1.04, 0.95); opacity: 1; }
  100% { transform: scale(0.3, 0.3); opacity: 0; }
}
@keyframes dlg-backdrop-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}
.dlg-close-anim { animation: dlg-gelatine-out .26s cubic-bezier(.5, -0.2, .8, .4) both; }
.dlg-close-anim::backdrop { animation: dlg-backdrop-out .26s ease both; }

/* Bulle "Copié" : même ressort gélatine que l'ouverture des fenêtres, en
   plus court, suivie d'un effacement en fondu. Deux animations enchaînées
   sur le même élément ; la seconde est en `forwards` et NON `both` — avec
   `both` son remplissage arrière s'appliquerait dès l'instant 0 et
   écraserait la transformation de la gélatine pendant tout le délai. */
.copy-bubble {
  position: fixed;
  z-index: 4000;
  transform: translate(-50%, -140%);
  transform-origin: 50% 100%;
  padding: .3rem .7rem;
  border-radius: var(--shape-full);
  background: var(--spoon-bleu);           /* bleu cuillère */
  color: #0d1b2a;
  font-size: .8125rem;
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;          /* ne doit jamais gêner le clic suivant */
  box-shadow: var(--elev-2);
  animation: copy-bubble-in .7s linear both, copy-bubble-out .3s ease 1s forwards;
}
/* Reprend les étapes de dlg-gelatine, en conservant le décalage
   translate(-50%, -140%) qui centre la bulle au-dessus du pointeur — une
   animation de `transform` remplace la propriété entière, le décalage doit
   donc être répété dans chaque étape. */
@keyframes copy-bubble-in {
  0%   { transform: translate(-50%, -140%) scale(0.25, 0.25); opacity: 0; }
  10%  { transform: translate(-50%, -140%) scale(0.87, 0.63); opacity: .7; }
  20%  { transform: translate(-50%, -140%) scale(0.95, 0.88); opacity: 1; }
  30%  { transform: translate(-50%, -140%) scale(0.91, 1.03); }
  40%  { transform: translate(-50%, -140%) scale(0.96, 1.03); }
  55%  { transform: translate(-50%, -140%) scale(1.03, 0.97); }
  70%  { transform: translate(-50%, -140%) scale(0.99, 1.00); }
  85%  { transform: translate(-50%, -140%) scale(0.99, 1.01); }
  100% { transform: translate(-50%, -140%) scale(1, 1); }
}
@keyframes copy-bubble-out {
  to { opacity: 0; transform: translate(-50%, -190%) scale(1, 1); }
}
.dlg-open-anim {
  /* 1 s, et timing LINÉAIRE : le mouvement est entièrement décrit par les
     étapes ci-dessus. Une courbe d'accélération viendrait s'ajouter au
     rebond déjà calculé et le déformerait. */
  animation: dlg-gelatine 1s linear both;
  /* Pas de will-change ici : laissé en permanence sur la boîte, il en fait
     un repère de positionnement pour ses descendants en `position: fixed`,
     dont les coordonnées écran cesseraient d'être fiables (bulle "Copié",
     popovers…). Le gain visuel ne valait pas cet effet de bord. */
}
.dlg-open-anim::backdrop { animation: dlg-backdrop-in .35s ease both; }

/* PAS de suppression sous prefers-reduced-motion, volontairement.
   Vérifié dans le navigateur de l'utilisateur : ce réglage est à "reduce"
   (Windows > Effets d'animation désactivés), si bien qu'une règle
   `animation: none` rendait l'effet strictement invisible — alors qu'il a
   été demandé explicitement. On garde donc l'animation dans tous les cas ;
   elle dure 1 s et ne concerne qu'une ouverture de fenêtre.
   Si un jour cette app sert à plusieurs personnes, c'est ici qu'il faudra
   rétablir un repli, en le rendant réglable plutôt qu'automatique. */

/* Les deux fenêtres de note (édition complète et édition simple) s'ouvrent
   toujours à la même taille, quel que soit le contenu (30% plus bas que la
   première version). Disposées en colonne flexible : la zone de contenu
   (texte ou liste) absorbe tout l'espace restant et défile elle-même si
   besoin, ce qui pousse naturellement échéance/couleur/libellés/boutons tout
   en bas de la fenêtre, sans jamais faire déborder la fenêtre elle-même.
   max-height protège les petits écrans. */
/* [open] est indispensable ici : un <dialog> fermé n'a pas cet attribut, et
   compte sur la feuille de style du navigateur (dialog:not([open]) {
   display:none }) pour rester invisible. Un sélecteur par ID a une
   spécificité plus forte que cette règle native — sans le qualifier avec
   [open], "display:flex" s'appliquait même fermé et la fenêtre restait
   visible en permanence derrière les autres. */
/* height:auto (pas une hauteur fixe) + overflow-y:auto : une notask avec
   pas mal de contenu (palette de 24 couleurs + plusieurs libellés, par
   exemple) dépassait la hauteur fixe de 584px, sans aucun moyen d'atteindre
   le reste — coupé net, sans défilement. 584px reste la taille MINIMALE
   (comportement inchangé pour une note courte), mais height:auto plutôt
   que fixe permet à la fenêtre de grandir avec le contenu jusqu'à
   max-height ; au-delà, c'est le défilement interne de .dns-card qui prend
   le relais (voir overflow-y:auto plus bas) plutôt que de couper le
   contenu ou dépasser l'écran. */
#dlg-note-simple[open] {
  /* min() : sur un écran court, un plancher fixe de 584px rendait la boîte
     PLUS HAUTE que la fenêtre. Une <dialog> modale étant centrée, le
     débordement se répartit en haut ET en bas — le haut de la notask
     passait donc hors de l'écran, sans aucun moyen d'y revenir (signalé en
     posant une échéance, qui rallonge la barre du bas d'une ligne). Le
     plancher ne peut désormais jamais dépasser la fenêtre.
     dvh plutôt que vh : sur mobile, vh ignore la barre d'adresse et vaut
     plus que la hauteur réellement visible. */
  min-height: min(584px, 88dvh);
  height: auto;
  max-height: 88dvh;
  /* Pas de largeur ici : la base `dialog` (552px, +20% demandé) suffit
     désormais, toutes les boîtes en ont bénéficié — cette règle avait
     d'abord posé le +20% ici seule, avant que la demande soit étendue. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

#dns-content-field, #dns-items-field {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
/* Poser --marge-bloc ICI (et non sur .dns-content-editable, son propre
   enfant) est indispensable : `overflow-y: auto` ci-dessus, combiné à un
   `overflow-x` resté à sa valeur par défaut `visible`, force le navigateur à
   calculer overflow-x comme `auto` lui aussi (règle CSS : impossible de
   n'avoir qu'un seul axe en 'visible' quand l'autre ne l'est pas). Le débord
   horizontal du fond de bloc (voir .note-ligne.bloc-actif plus bas), qui
   sort de la boîte de #dns-content par une marge négative, se faisait donc
   purement et simplement ROGNER à la bordure de #dns-content-field — le fond
   restait collé au texte, sans jamais atteindre le milieu de la marge de la
   carte. Un habillage padding+margin négatif compensatoire résout ça sans
   toucher à `overflow-y` : le débord de .note-ligne reste ainsi À
   L'INTÉRIEUR du padding fraîchement ouvert ici, jamais au-delà de la boîte
   de bordure — zone que le rognage laisse toujours visible, quel que soit
   l'axe. Le texte normal, lui, ne bouge pas d'un pixel : la marge négative
   de #dns-content-field annule exactement son propre padding. */
#dns-content-field {
  --marge-bloc: .75rem;
  margin: 0 calc(var(--marge-bloc) * -1);
  padding: 0 var(--marge-bloc);
}
/* Réserve la place pour la moitié supérieure des bulles d'échéance
   de la première .note-ligne, comme has-note-lines le fait sur la carte. */
#dns-content-field:has(.note-ligne) { padding-top: .88rem; }
/* flex: 1 (= flex: 1 1 0%) laissait le navigateur COMPRESSER la boîte réelle
   de la zone éditable en dessous de la taille de son propre contenu — une
   notask longue rendait alors une grande partie de son texte HORS de la
   boîte de #dns-content (overflow visible, mais la boîte elle-même restait
   petite). Le clic y était hasardeux (parfois capté ailleurs, curseur
   souris au lieu du curseur texte), aussi bien au milieu qu'en bas de la
   notask — trouvé en mesurant la boîte réelle (~368px) contre son contenu
   (~1200px) sur une longue notask.
   flex-shrink: 0 + flex-basis: auto (au lieu de 0%) empêchent cette
   compression : la boîte fait maintenant toujours AU MOINS la taille de son
   contenu, quitte à dépasser le champ — c'est alors #dns-content-field
   (overflow-y: auto) qui absorbe le surplus par défilement, comme prévu.
   flex-grow: 1 reste : une notask courte remplit toujours tout l'espace
   disponible (nécessaire à activerClicDansLeVide/activerClicEntreChamps). */
#dns-content { flex: 1 0 auto; min-height: 100%; resize: none; }
#dns-items-field #dns-items { flex: 1; overflow-y: auto; }

/* Barre d'outils de mise en forme, sous le texte, uniquement en édition
   rapide. Pilule Material Design ; sélectionner du texte dans la zone puis
   appuyer sur un bouton l'entoure de la syntaxe correspondante (voir
   applyFormat() dans app.js), qui est ensuite rendue en gras/italique/
   souligné/code sur la carte (voir renderFormatted()). */
.fmt-toolbar {
  flex: none;
  display: flex;
  align-items: center;
  gap: .375rem;
  width: fit-content;
  margin: .625rem 0 0;
  padding: .375rem;
  /* Transparent : se fond dans la couleur de la fenêtre (celle de la note),
     plutôt qu'un gris qui tranche dessus. */
  background: transparent;
  border-radius: var(--shape-full);
}
.fmt-toolbar button {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  /* Rond clair visible en permanence sur chaque icône (pas seulement au
     survol), pour le même effet que sur les icônes de la carte. */
  background: rgba(255,255,255,.14);
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface);
  font-size: .875rem;
  cursor: pointer;
  transition: background .15s;
}
.fmt-toolbar button:hover { background: rgba(255,255,255,.24); }
/* Regroupe gras/italique/souligné/code : masqué en mode liste à cocher (ces
   mises en forme ne s'appliquent qu'au texte libre), contrairement au
   bouton de bascule note/liste juste après, toujours visible pour pouvoir
   revenir en arrière dans les deux sens. */
/* Les deux groupes de mise en forme (édition rapide et composeur) : sans
   display:flex, ce <span> laisse ses boutons — des éléments en display:grid
   de 32px — s'empiler verticalement les uns sous les autres. */
#dns-fmt-group, #nc-fmt-group { display: flex; align-items: center; gap: .375rem; }
/* [hidden] doit l'emporter sur le display:flex ci-dessus (masquage du
   groupe en mode liste à cocher, voir renderComposer()/renderDnsMode()). */
#dns-fmt-group[hidden], #nc-fmt-group[hidden] { display: none; }
.fmt-toolbar button[data-fmt="bold"] { font-weight: 700; }
.fmt-toolbar button[data-fmt="italic"] { font-style: italic; }
.fmt-toolbar button[data-fmt="underline"] { text-decoration: underline; }
.fmt-toolbar button[data-fmt="code"] svg,
.fmt-toolbar button[data-fmt="color"] svg,
.fmt-toolbar button[data-fmt="archive"] svg,
.fmt-toolbar button[data-fmt="clear"] svg { width: 18px; height: 18px; }

/* La couleur du texte n'utilise plus de popover flottant : c'est la même
   palette dépliante que la couleur de note (.palette / #dns-text-colors,
   construite par construirePalette()), ce qui règle d'un coup les
   problèmes de superposition avec une <dialog> modale et de placement
   sous la barre d'outils. Rien à styler ici : .palette et .swatch
   suffisent. */
.fmt-toolbar .fmt-sep { width: 1px; height: 20px; background: var(--md-outline); margin: 0 .125rem; }

/* Sous-menu du bouton « zone d'archive » (voir initSousMenuArchive dans
   app.js) : un second bouton surgit AU-DESSUS après un survol prolongé. */
.fmt-archive-wrap { position: relative; display: inline-flex; }
.fmt-archive-all {
  position: absolute;
  left: 50%;
  bottom: 100%;
  margin-bottom: .375rem;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  background: var(--md-surface-4);
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface-variant);
  box-shadow: var(--elev-2);
  cursor: pointer;
  /* visibility (et pas seulement opacity) : sans elle, le bouton resterait
     cliquable alors qu'il est invisible, juste au-dessus de la barre. */
  transform: translate(-50%, 8px) scale(.4);
  transform-origin: 50% 100%;
  opacity: 0;
  visibility: hidden;
  transition: transform .2s ease-in, opacity .16s ease, visibility .2s;
}
/* Apparition « gélatine » : le bouton jaillit de son ancrage en s'écrasant
   puis en rebondissant, l'étirement alternant entre les deux axes avant de
   se stabiliser. Une suite d'images clés plutôt qu'une simple courbe : un
   cubic-bezier, même élastique, ne fait varier qu'une échelle uniforme et
   ne peut pas produire cette compensation largeur/hauteur. */
@keyframes fmt-archive-gelatine {
  0%   { transform: translate(-50%, 10px) scale(.35, .55); opacity: 0; }
  35%  { transform: translate(-50%, -4px) scale(1.28, .78); opacity: 1; }
  55%  { transform: translate(-50%, 2px) scale(.82, 1.18); }
  72%  { transform: translate(-50%, -2px) scale(1.11, .92); }
  86%  { transform: translate(-50%, 1px) scale(.96, 1.05); }
  100% { transform: translate(-50%, 0) scale(1, 1); opacity: 1; }
}
.fmt-archive-wrap.sous-menu-ouvert .fmt-archive-all {
  transform: translate(-50%, 0) scale(1);
  opacity: 1;
  visibility: visible;
  animation: fmt-archive-gelatine .52s cubic-bezier(.3, .9, .4, 1);
}
.fmt-archive-all:hover { background: var(--md-surface-5, rgba(255,255,255,.16)); color: var(--md-on-surface); }
.fmt-archive-all svg { width: 17px; height: 17px; display: block; }
/* Sens inverse quand l'action proposée est de tout rouvrir. */
.fmt-archive-all.is-expand svg { transform: rotate(180deg); }

/* Lien (voir NOTE_URL_MARK / renderFormatted). Le jaune de la cuillère du
   logo suffit à le distinguer du texte courant — pas de soulignement (déjà
   pris par la mise en forme « souligné ») ni de gras (qui alourdissait la
   lecture d'un texte truffé de liens).
   inline-flex + gap : la pastille de copie se pose à la suite du libellé,
   dans le flux, comme sur le code en ligne. */
.note-url {
  color: var(--spoon-jaune);
  text-decoration: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: .25rem;
}
.note-url:hover { text-decoration: underline; }
.note-url .code-copy-btn {
  /* Annule le sticky/float du bouton des blocs de code : ici il suit
     simplement le texte. */
  position: static;
  float: none;
  margin-left: 0;
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--spoon-jaune);
}
.note-url .code-copy-btn svg { width: 11px; height: 11px; }

/* Rendu des marqueurs de mise en forme (voir renderFormatted()) sur la carte.
   Harmonisé sur demande explicite avec .note-code-block : code en ligne ET
   bloc rendent tous les deux en barre pleine largeur, plus de petit pill
   inline-flex. Le bouton de copie suit donc le même comportement (sticky +
   float droite) que celui des blocs — voir .code-copy-btn plus bas, qui
   s'applique maintenant sans override ici. */
/* Code en ligne et code en bloc : même police, même taille, même fond, même
   cadre. Harmonisé sur demande explicite, et donc déclaré en un seul endroit
   — les deux règles portaient la même liste, à un commentaire près, et rien
   ne garantissait qu'elles restent en phase. Ne reste propre à chacune que
   la façon dont le bloc occupe la place (voir plus bas). */
.note-code-inline,
.note-code-block {
  font-family: "Px437 IBM VGA", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* 16px : taille native de la grille 8x16, seule à rendre la police nette
     (voir @font-face en tête de fichier). */
  font-size: 1.125rem;
  line-height: 1.5;
  background: rgba(230,224,233,.10);
  padding: .5rem .625rem;
  border-radius: var(--shape-xs);
  margin: .375rem 0;
}

.note-code-inline {
  display: block;
  white-space: pre-wrap;
}
/* Zone d'archive : repère purement VISUEL pour mettre un passage de côté.
   Aucun rapport avec les notasks archivées (vue Archives) — c'est une mise
   en forme du texte, comme le gras ou le code.
   Miroir de la zone de code : même cadre propre (rectangle à coins
   arrondis, var(--shape-xs), comme .note-code-block) mais plus sombre au
   lieu de plus clair, et en écriture manuscrite au lieu de la chasse fixe.
   Bloc (note-archive-block) ou en ligne (note-archive-inline) selon que la
   sélection contenait un saut de ligne ou non — même distinction que le
   code (voir wrapSelectionRich() et note-code-block/note-code-inline).
   Kalam (Google Fonts, voir index.html) plutôt qu'une pile de polices
   manuscrites système : celles-ci ne sont garanties sur aucune machine et
   retombaient en pratique sur le `cursive` par défaut du navigateur. */
.note-archive-zone {
  font-family: "Kalam", cursive;
  /* Taille fixe (16px) plutôt que relative au texte environnant : Satisfy
     est une cursive à l'œil réduit, elle a besoin d'une taille propre. */
  font-size: 1rem;
  background: rgba(0, 0, 0, .34);
  border-radius: var(--shape-xs);
  /* Liseré discret : sur une carte déjà sombre, l'assombrissement seul
     ne suffit pas toujours à délimiter la zone. */
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08);
}
.note-archive-block {
  display: block;
  padding: .5rem .625rem;
  /* Place réservée à droite pour l'icône, ancrée en haut du bloc : sans ce
     retrait, une ligne de texte passerait dessous. */
  padding-right: 2.375rem;
  margin: .375rem 0;
  white-space: pre-wrap;
  position: relative;
}
.note-archive-inline {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .1rem .4rem;
  /* Même ancrage que la variante bloc : l'icône est sortie du flux et collée
     à droite, avec la place réservée par le padding. Dans le flux, elle
     suivait la fin du texte et sautait donc d'un coup à l'escamotage du
     contenu — le bouton se retrouvait ailleurs sous le curseur au moment
     même où l'on venait de cliquer dessus. */
  padding-right: 1.75rem;
  position: relative;
}
/* Archive repliée : ramenée à une seule ligne portant le mot « archive »,
   l'icône restant visible à droite pour pouvoir la rouvrir.
   font-size:0 sur le conteneur escamote les nœuds de texte (impossible à
   viser autrement en CSS : un sélecteur ne désigne que des éléments), la
   taille étant rétablie sur le libellé et l'icône. Le contenu reste dans le
   DOM, seulement masqué : déplier ne coûte qu'un changement de classe, sans
   rien reconstruire ni risquer de perdre du texte. */
.note-archive-zone.is-closed {
  font-size: 0;
  white-space: nowrap;
}
/* Le texte fond progressivement au lieu de disparaître d'un coup : la
   taille de police s'anime en même temps que la dimension. Piloté en JS
   (voir animerPliArchive), PAS déclaré ici : une transition permanente sur
   font-size fausserait les mesures faites au moment du basculement, la
   valeur relevée étant alors un état intermédiaire de l'animation. */
.note-archive-zone.is-closed > *:not(.archive-icon-mark) { display: none; }
.note-archive-zone.is-closed::before {
  content: "archive";
  font-family: "Kalam", cursive;
  font-size: 1rem;
  opacity: .75;
}
/* Repliée, la zone tient sur une ligne : l'icône reste en haut à droite,
   ce qui la place naturellement au bon endroit sur une seule ligne — pas
   de bascule vers un autre mode d'affichage, qui ferait sauter la mise en
   page en plein milieu de l'animation. */
.note-archive-block.is-closed { padding-right: 2.375rem; }

/* Icône archive : bouton de pli/dépli de SA zone, sans toucher aux autres
   de la même notask (voir basculerPliArchive dans app.js). Elle était
   auparavant purement décorative (pointer-events:none). */
.archive-icon-mark {
  opacity: .8;
  cursor: pointer;
  transition: opacity .15s ease, background .15s ease;
}
.archive-icon-mark:hover { opacity: 1; background: rgba(255,255,255,.14); }
/* Ancrée en haut à droite du bloc. Auparavant en sticky+float, elle suivait
   le défilement et se retrouvait au milieu du texte sur un bloc long. */
.note-archive-block .archive-icon-mark {
  position: absolute;
  top: .35rem;
  right: .35rem;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, .4);
  color: var(--md-on-surface);
}
.note-archive-block .archive-icon-mark svg { width: 14px; height: 14px; display: block; }
.note-archive-inline .archive-icon-mark {
  /* Sortie du flux et collée à droite, comme dans la variante bloc : dans
     le flux, elle suivait la fin du texte et sautait donc d'un coup à
     l'escamotage du contenu — le bouton se retrouvait ailleurs sous le
     curseur à l'instant même où l'on venait de cliquer dessus. */
  position: absolute;
  right: .3rem;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--md-on-surface);
}
.note-archive-inline .archive-icon-mark svg { width: 12px; height: 12px; display: block; }

.note-code-block {
  overflow-x: auto;
  white-space: pre-wrap;
  /* Le bouton de copie flotte à droite en tête du bloc : le texte
     s'écoule autour, pas besoin de lui réserver un retrait. */
}
/* Plafond de hauteur ~10 lignes, scrollable — sur la CARTE seulement (pas
   dans les éditeurs, où on veut voir tout ce qu'on tape). Une notask avec
   plusieurs blocs de code assez longs devenait anormalement haute,
   déséquilibrant toute la mosaïque à côté d'elle. */
.note .note-code-block {
  max-height: calc(10 * 1.5 * .8125rem + 1rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Pastille de copie d'un bloc de code, en haut à droite. Visible en
   permanence et non au survol : c'est un raccourci qu'on doit voir pour
   savoir qu'il existe. Injectée par ajouterBoutonsCopieCode() dans app.js,
   pas par renderFormatted(), pour ne jamais finir dans le texte enregistré. */
.code-copy-btn {
  /* sticky + float : le bouton reste en haut à droite de la PARTIE VISIBLE
     du bloc quand un code long défile avec la carte. En `absolute` il
     restait collé au sommet du bloc et sortait de l'écran dès qu'on
     descendait dans un code de plusieurs lignes. */
  position: sticky;
  top: .35rem;
  float: right;
  margin-left: .5rem;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgba(0,0,0,.35);
  color: var(--md-on-surface);
  cursor: pointer;
  transition: background .15s, color .15s;
}
.code-copy-btn:hover { background: var(--spoon-bleu); color: #0d1b2a; }
.code-copy-btn svg { width: 14px; height: 14px; display: block; }
dialog h2 { margin: 0 0 1.5rem; font-size: 1.5rem; font-weight: 400; }
.dialog-actions { display: flex; gap: .5rem; justify-content: flex-end; margin-top: 1.5rem; }

/* Ligne titre + zone icône — composeur (#nc-title) et édition simple
   (#dns-title, ajouté après coup pour permettre de changer l'icône d'une
   notask déjà existante, l'ancienne boîte "Modifier" complète qui portait
   ce réglage ayant été retirée). */
.title-row { display: flex; align-items: center; gap: .625rem; }
.title-row input { flex: 1; }

/* Bouton de zone icône : taille d'une cuillère du bandeau du haut (28px).
   Fond visible même sans icône choisie (plus facile à repérer qu'un simple
   pointillé transparent), plein une fois une icône choisie. */
.icon-pick-btn {
  flex: none;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  background: rgba(230,224,233,.08);
  border: 1px dashed var(--md-outline);
  border-radius: 50%;
  color: var(--md-on-surface-variant);
  transition: background .15s, border-color .15s, color .15s;
}
.icon-pick-btn svg { width: 17px; height: 17px; }
.icon-pick-btn:hover { background: rgba(230,224,233,.16); border-color: var(--md-primary); }
.icon-pick-btn.has-icon { border-style: solid; border-color: var(--md-outline); background: transparent; }

/* max-height + défilement : la liste est passée de 15 à 35+ icônes,
   4 colonnes pleines dépasseraient largement une carte ou un petit écran
   sans ce plafond (~5 rangées visibles, le reste au défilement). */
.icon-popup-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* Écart resserré (.375rem -> .125rem) : c'est lui qui finance
     l'agrandissement des icônes, la fenêtre gardant exactement la même
     largeur. */
  gap: .125rem;
}
/* Le défilement porte désormais sur l'ensemble (recherche exclue, elle
   reste fixe en tête) et non sur chaque grille : sinon chaque thème aurait
   sa propre barre de défilement. */
.icon-popup-scroll {
  max-height: 15rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.icon-popup-search {
  width: 100%;
  margin-bottom: .5rem;
  padding: .375rem .5rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-xs);
  color: var(--md-on-surface);
  font-size: .8125rem;
}
.icon-popup-search:focus { outline: 2px solid var(--spoon-bleu); outline-offset: 1px; }
/* Titres de thème : repères de défilement, pas des séparateurs appuyés —
   d'où les petites capitales grises et le simple filet au-dessus. */
.icon-theme + .icon-theme .icon-theme-titre { border-top: 1px solid var(--md-outline-variant); padding-top: .5rem; }
.icon-theme-titre {
  font-size: .625rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--md-on-surface-variant);
  opacity: .75;
  margin: .375rem 0 .25rem;
}
.icon-popup-vide {
  padding: .75rem .25rem;
  font-size: .8125rem;
  color: var(--md-on-surface-variant);
  text-align: center;
}
.icon-opt {
  width: 34px; height: 34px;
  /* Taille pour les icônes emoji (les SVG ont leur propre width/height via
     .icon-opt svg, donc font-size ne les affecte pas). */
  font-size: 1.25rem;
  line-height: 1;
}
/* +20 % (18px -> 21.6px), pris sur l'écart entre icônes ci-dessus. */
.icon-opt svg { width: 21.6px; height: 21.6px; }
.icon-opt:hover { background: rgba(230,224,233,.12); }
.icon-opt.active { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

/* Dialogue d'édition simple, façon Keep : le titre et le contenu se
   fondent directement dans le fond coloré de la note, sans encadré.

   Le <dialog> ouvert passe dans le "top layer" du navigateur avec son
   propre ::backdrop ; un élément vraiment placé en dehors de son arbre DOM
   serait rendu derrière ce backdrop (assombri, non cliquable). Pour obtenir
   visuellement une barre à part sous la note tout en gardant les boutons
   cliquables, le <dialog> devient une coquille transparente sans fond ni
   padding propres, et ce sont deux blocs internes qui portent chacun leur
   propre fond arrondi : .dns-card (titre/description/contenu) et
   .dns-bottom-bar (outils de mise en forme + échéance), séparés par un
   espace. Les deux partagent la couleur de la note via --note-color,
   défini sur chaque classe .c-* (voir applyDialogColor() dans app.js, qui
   pose la classe sur le <dialog> lui-même : --note-color est hérité par
   les enfants normalement, en CSS). */
#dlg-note-simple[open] {
  background: transparent;
  padding: 0;
  box-shadow: none;
  gap: .75rem;
}
.dns-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--note-color, var(--md-surface-3));
  border-radius: var(--shape-lg);
  padding: 1.5rem;
  box-shadow: var(--elev-3);
  /* overflow-y: auto plutôt que hidden — une image jointe pleine largeur
     peut être plus haute que l'espace laissé par le titre/description au
     sein de la hauteur fixe de la boîte : sans défilement, elle serait
     purement et simplement coupée sans qu'aucune barre ne le signale.
     overflow-x reste hidden pour ne pas percer les coins arrondis. */
  overflow-y: auto;
  overflow-x: hidden;
}
.dns-bottom-bar {
  flex: none;
  /* Colonne, exactement comme .nc-toolbar-block dans le composeur : la
     rangée de mise en forme au-dessus, la rangée d'actions en dessous.
     Avant, c'était une seule rangée en space-between qui repoussait les
     actions à l'opposé et laissait un grand trou au milieu. */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .25rem;
  background: var(--note-color, var(--md-surface-3));
  border-radius: var(--shape-lg);
  padding: .625rem 1rem;
  box-shadow: var(--elev-3);
  /* Reste au-dessus du clavier virtuel plutôt que de se faire recouvrir
     (--kb-inset tenu à jour par l'API VisualViewport, voir app.js) : la
     boîte étant en colonne flexible avec .dns-card en flex:1 juste
     au-dessus, ce padding supplémentaire pousse naturellement cette barre
     vers le haut, .dns-card cédant l'espace correspondant — pas besoin de
     sortir cette barre du flux ni de repositionner quoi que ce soit
     d'autre. transition pour suivre l'ouverture/fermeture du clavier en
     douceur plutôt qu'un saut sec. */
  padding-bottom: calc(.625rem + var(--kb-inset, 0px));
  transition: padding-bottom .15s ease;
}
/* padding:0 en plus de margin:0 : le padding de .fmt-toolbar décalait la
   première ligne vers la droite, si bien que le "B" ne tombait pas à la
   verticale du trombone de la ligne du dessous. Les deux rangées partent
   maintenant exactement du même bord. Idem dans le composeur. */
.dns-bottom-bar .fmt-toolbar { margin: 0; padding: 0; }
/* Palette de couleurs dépliable dans la barre basse : sur toute la largeur,
   sous les deux rangées d'outils (la barre est en flex-wrap, flex-basis
   100% force le retour à la ligne). Même comportement que #nc-colors dans
   le composeur. */
.dns-bottom-bar .palette {
  flex-basis: 100%;
  display: flex;
  gap: .375rem;
  flex-wrap: wrap;
  padding: .25rem 0 0;
}
#dlg-note-simple .dns-title-in {
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  font-size: 1.25rem;
  font-weight: 600;
  padding: 0 0 .5rem;
}
#dlg-note-simple .dns-title-in:focus { outline: none; }
#dlg-note-simple .description-in { margin: 0 0 .75rem; }
#dlg-note-simple .field { margin-bottom: 0; }
#dlg-note-simple textarea {
  width: 100%;
  min-height: 120px;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  resize: none;
  padding: 0;
}
#dlg-note-simple textarea:focus { outline: none; }

/* Zone de contenu de l'édition rapide : contenteditable plutôt que
   <textarea>, pour que le gras/italique/souligné/code de la barre d'outils
   s'affiche réellement mis en forme pendant l'édition (WYSIWYG), et pas
   seulement une fois la note enregistrée et réaffichée sur la carte.
   Converti en texte façon markdown à l'enregistrement par richToText() dans
   app.js — l'inverse de renderFormatted(), qui remplit cette zone à
   l'ouverture. */
.dns-content-editable {
  width: 100%;
  min-height: 120px;
  background: transparent;
  border: none;
  color: var(--md-on-surface);
  padding: 0;
  outline: none;
  white-space: pre-wrap;
  word-break: break-word;
}
.dns-content-editable .note-code-block { white-space: pre-wrap; }

/* Image insérée dans le corps d'une notask (dessin/tableau) — voir
   NOTE_IMG_MARK et hydrateInlineImages() dans app.js. Même rendu dans la
   carte, l'édition rapide et le composeur : pleine largeur disponible,
   jamais déformée. */
.note-inline-img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: var(--shape-xs);
  margin: .4rem 0;
}

/* Note vocale insérée dans le corps d'une notask : rectangle occupant toute
   la largeur, bouton lecture à gauche, forme d'onde au centre, durée à
   droite. Le bloc est contenteditable="false" côté HTML — il se sélectionne
   d'un bloc et ne peut pas être disloqué caractère par caractère. */
/* ============ Case à cocher posée dans le texte (notask mixte) ===========
   Voir NOTE_LINE_MARK dans app.js. Le bloc est display:flex, donc il apporte
   sa propre rupture de ligne dans un texte en white-space: pre-wrap — c'est
   pour ça que le rendu avale le saut de ligne qui précède le marqueur.

   L'apparence reprend celle de l'ancien éditeur de liste (.dn-item-row) :
   même trait sous le texte, même place pour la case. C'est volontaire — le
   geste a changé, pas la lecture.

   align-items: baseline, et non center : sur une ligne qui déborde sur deux
   ou trois lignes, `center` calait la case au MILIEU du paragraphe, loin de
   son texte. `baseline` la garde en face de la PREMIÈRE ligne, quelle que
   soit la longueur. Vérifié côte à côte dans un navigateur avant de choisir.

   user-select: none sur l'enveloppe seulement : une sélection à la souris
   qui traverse la case ou les boutons n'emporte pas leur « contenu ». Le
   texte redevient explicitement sélectionnable juste en dessous. */
.note-ligne {
  display: flex;
  align-items: center;
  gap: .4rem;
  user-select: none;
  /* Ancre .note-ligne-due-wrap, positionnée en absolu (voir plus bas) — sans
     ça elle se positionnerait par rapport à un ancêtre bien plus loin,
     potentiellement toute la zone de saisie. */
  position: relative;

  /* Chaque ligne réserve le même en-tête, qu'elle porte une échéance ou non.
     La date peut ainsi rester au même endroit en haut à droite sans jamais
     chevaucher le texte, et le rythme ne change plus entre les lignes. */
  margin: 0 calc(var(--marge-bloc, 0px) * -1);
  padding: .88rem var(--marge-bloc, 0px) .22rem;
}

/* Débord horizontal du fond du bloc : il commence à MI-DISTANCE entre le bord
   de la notask et la case à cocher, et s'arrête à la même distance à droite.
   D'où la moitié de la marge intérieure de chaque carte — 1.5rem pour la
   boîte d'édition, 1.25rem pour le composeur.

   Le débord est obtenu par marge négative compensée par un padding égal : la
   position du texte ne bouge pas d'un pixel, seul le fond s'étend. Et comme
   il vaut 0 par défaut, l'affichage sur la carte d'accueil n'est pas touché —
   là, une case doit rester alignée sur le reste du contenu. */
.nc-content-editable { --marge-bloc: .625rem; }

/* Fond des blocs de cases à cocher — PERMANENT, peu importe où se trouve le
   curseur ou si la notask est en cours de modification (voir
   peindreTousLesBlocs dans app.js, appelée à l'hydratation puis à chaque
   insertion/suppression de ligne, jamais au focus/blur). Un « bloc » est une
   suite de cases qui se suivent, ne serait-ce qu'une seule ; il n'a pas
   d'existence dans le DOM (les cases sont des frères à plat dans la zone de
   saisie), il est dessiné en peignant le fond de chaque case de la suite et
   en n'arrondissant que la première et la dernière. Pas d'enveloppe ajoutée :
   en zone contenteditable, déplacer des nœuds sous le curseur au moment où
   l'utilisateur écrit est le plus sûr moyen de lui faire perdre sa saisie.

   Aucun contour, juste un fond à peine plus sombre : il s'agit de délimiter,
   pas d'encadrer. Et rien de tout ça ne change la mise en page — seule la
   couleur de fond apparaît, donc le texte ne bouge jamais. */
.note-ligne.bloc-actif { background: rgba(0, 0, 0, .12); }
.note-ligne.bloc-debut {
  border-top-left-radius: var(--shape-xs);
  border-top-right-radius: var(--shape-xs);
}
.note-ligne.bloc-fin {
  border-bottom-left-radius: var(--shape-xs);
  border-bottom-right-radius: var(--shape-xs);
}
.note-ligne-case {
  flex: none;
  margin: 0;
  /* La ligne de base d'une case à cocher est son bord bas : ce demi-pixel la
     recale optiquement sur le texte. */
  transform: translateY(1px);
  accent-color: var(--spoon-jaune);
}
.note-ligne-txt {
  flex: 1 1 auto;
  min-width: 0;
  user-select: text;
  /* .12rem et non .3rem : ce padding s'AJOUTE à celui de .note-ligne, les
     deux se cumulaient et les lignes d'un bloc se retrouvaient très aérées.
     Pas ramené à 0 : il garde le trait de soulignement de la case en attente
     à distance du texte (voir .est-vide plus bas). */
  padding: .12rem 0;
  color: var(--md-on-surface);
  white-space: pre-wrap;
  word-break: break-word;
  outline: none;
  /* Trait réservé à la case en attente — voir juste en dessous. Une case déjà
     remplie est du contenu, pas un champ à remplir : la souligner alignait
     des traits sous toute la notask pour rien. */
  border-bottom: 1px solid transparent;
}
.note-ligne.est-vide .note-ligne-txt { border-bottom-color: var(--md-outline-variant); }
.note-ligne.est-vide .note-ligne-txt:focus { border-bottom-color: var(--md-primary); }
/* Invite de la case en attente, reprise de l'ancien `placeholder`. Posée sur
   une classe et non sur :empty : dans une zone contenteditable, effacer le
   dernier caractère laisse souvent un <br> derrière, et :empty ne s'applique
   alors plus. La classe est tenue par le JS (voir majCaseVide). */
.note-ligne.est-vide .note-ligne-txt::before {
  content: 'Ajouter une case à cocher';
  color: var(--md-on-surface-variant);
  opacity: .55;
  pointer-events: none;
}
.note-ligne.done .note-ligne-txt {
  text-decoration: line-through;
  color: var(--md-on-surface-variant);
}
/* Case mise de côté seule (NoteItem.archived) : gardée dans l'éditeur pour ne
   pas perdre sa place dans le texte, mais visiblement en retrait. */
.note-ligne.est-archivee { opacity: .45; }
/* Échéance (+ bouton calendrier) : toujours au même endroit dans l'en-tête
   réservé par TOUTES les lignes, indépendamment de leur position dans le
   bloc et de la présence effective d'une date. */
.note-ligne-due-wrap {
  position: absolute;
  /* Le centre des bulles reste sur le bord supérieur de chaque ligne :
     moitié dans la bulle de ligne, moitié à l'extérieur. */
  top: 0;
  right: .5rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  gap: .2rem;
  z-index: 1;
}
.note-ligne-due {
  flex: none;
  margin: 0;
  padding: .05rem .5rem;
  border-radius: var(--shape-full);
  /* Aligné sur la mention d'échéance de la notask entière (voir .note-due) :
     même voile clair très discret, ni fond franc ni ombre portée. Les deux
     disent la même chose, elles doivent se ressembler — décision explicite.

     Historique, parce que la valeur a fait l'aller-retour : elle était
     passée à .55 avec une ombre pour qu'une pastille posée à cheval sur le
     bord d'un bloc actif s'en détache nettement. Ce contraste marqué n'a pas
     été retenu ; en contrepartie, sur le fond plus sombre d'un bloc actif,
     ce voile-ci ne dessine presque plus de pastille — c'est assumé, le
     texte, lui, reste lisible grâce au blanc franc ci-dessous. */
  background: rgba(230,224,233,.09);
  /* +15 %, comme le bouton calendrier à côté : les deux forment un même
     groupe, les agrandir séparément les aurait désaccordés. */
  font-size: .79rem;
  font-style: normal;
  /* Cf. .note-due : le gris secondaire tombait sous le seuil lisible sur les
     teintes de carte saturées, avec un fond aussi transparent. */
  color: #fff;
}
/* Bouton calendrier : discret au repos, révélé au survol de la ligne — il ne
   doit pas encombrer la lecture d'une notask qui en compte dix.

   Plus petit que le .cal-btn générique (32px, dimensionné pour les barres
   d'outils) : posé sur une ligne de texte, il y paraissait énorme. Ne change
   RIEN à la hauteur des lignes — il est en position absolue (voir
   .note-ligne-due-wrap), donc hors du flux. */
.note-ligne-cal {
  flex: none;
  /* +15 % sur demande (22px → 25px) : lisible sur écran tactile, où le
     bouton n'a pas le survol pour se signaler. */
  width: 25px;
  height: 25px;
  opacity: 0;
  transition: opacity .15s ease;
  /* Décentrage constaté en PWA : la feuille de style par défaut d'Android
     pose un padding propre aux <button>, qui pousse l'icône hors du rond.
     Le `place-items: center` de .cal-btn centre dans la boîte de CONTENU,
     pas dans le rond dessiné par border-radius — un padding asymétrique
     décale donc visiblement l'un par rapport à l'autre. */
  padding: 0;
  line-height: 0;
}
/* display: block, sinon le SVG reste une boîte EN LIGNE : le navigateur lui
   réserve la place du jambage sous la ligne de base, et l'icône paraît
   remontée dans son rond. Invisible sur desktop où le rond est plus grand,
   net en PWA. */
.note-ligne-cal svg { width: 16px; height: 16px; display: block; }
/* Jaune cuillère dès qu'une date est posée. `.cal-btn.has-due` le fait déjà,
   mais avec un fond qui n'apparaît qu'au survol : ici le bouton EST le seul
   repère d'une ligne datée quand la pastille est masquée, il doit se voir au
   repos. */
.note-ligne-cal.has-due {
  color: var(--spoon-jaune);
  background: rgba(var(--spoon-bleu-rgb), .36);
}
.note-ligne:hover .note-ligne-cal,
.note-ligne-cal.has-due { opacity: 1; }
/* Écran tactile : pas de survol possible, le bouton reste visible. */
@media (hover: none) {
  .note-ligne-cal { opacity: 1; }
}

.note-audio {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  box-sizing: border-box;
  margin: .45rem 0;
  padding: .5rem .65rem;
  background: rgba(255,255,255,.07);
  border: 1px solid var(--md-outline-variant);
  border-radius: var(--shape-sm);
  user-select: none;
}
.note-audio-play {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(var(--spoon-jaune-rgb), .18);
  color: var(--spoon-jaune);
  cursor: pointer;
}
.note-audio-play:hover { background: rgba(var(--spoon-jaune-rgb), .3); }
.note-audio-play svg { width: 17px; height: 17px; }
.note-audio-wave {
  flex: 1;
  min-width: 0; /* sans ça le canvas impose sa largeur et déborde de la carte */
  height: 34px;
  cursor: pointer;
}
.note-audio-time {
  flex: none;
  font-size: .72rem;
  font-variant-numeric: tabular-nums; /* la durée ne doit pas sautiller en défilant */
  color: var(--md-on-surface-variant);
}

/* ============================ Pièces jointes ============================
   Deux contextes : sur la carte (aperçu seul, lecture) et dans l'édition
   simple (miniatures + suppression, voir renderAttachmentsSimple() dans
   app.js). Les images sont chargées et déchiffrées de façon paresseuse —
   la balise <img> existe avant que sa source ne soit connue, d'où le fond
   neutre le temps du décodage. */
.note-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
  margin-top: .625rem;
}
.note-attach-files {
  display: flex;
  align-items: center;
  gap: .3rem;
  padding: .3rem .6rem;
  border-radius: var(--shape-full);
  background: rgba(0,0,0,.18);
  color: var(--md-on-surface-variant);
  font-size: .75rem;
}
.note-attach-files svg { width: 15px; height: 15px; }

.dns-attachments {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1rem;
  padding-top: .875rem;
  border-top: 1px solid rgba(255,255,255,.08);
}
.dns-attach-chip {
  position: relative;
  display: flex;
  align-items: center;
  gap: .4rem;
  background: rgba(0,0,0,.18);
  border-radius: var(--shape-sm);
  padding: .4rem .5rem;
  max-width: 220px;
}
/* Une image collée/déposée occupe toute la largeur de la carte (comme un
   bloc), hauteur libre — pas de recadrage en carré comme un simple fichier :
   c'est du contenu de la note, pas une pièce jointe secondaire. flex-basis
   100% la force sur sa propre ligne dans le flex-wrap, les chips fichiers
   (non recadrées) continuent de se ranger normalement à côté les unes des
   autres. */
.dns-attach-chip.is-broken { opacity: .5; }
.dns-attach-icon { flex: none; display: flex; color: var(--md-on-surface-variant); }
.dns-attach-icon svg { width: 18px; height: 18px; }
.dns-attach-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .8125rem;
  color: var(--md-on-surface);
  cursor: pointer;
}
.dns-attach-name:hover { text-decoration: underline; }
.dns-attach-size {
  flex: none;
  font-size: .6875rem;
  color: var(--md-on-surface-variant);
}
/* Bouton de retrait d'une pièce jointe. Sur une chip fichier il suit le
   texte ; sur une image il flotte en surimpression, en haut à droite.
   Pastille nettement contrastée et bien détachée du bord : posé plus près
   du coin et translucide, il se lisait comme une tache grise à cheval sur
   l'image, sans qu'on devine sa fonction. */
.dns-attach-remove {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 50%;
  color: var(--md-on-surface-variant);
  background: transparent;
  transition: background .15s, color .15s;
}
.dns-attach-remove:hover { background: var(--md-error); color: var(--md-on-error); }
.dns-attach-remove svg { width: 14px; height: 14px; }

/* Glisser-déposer un fichier sur la carte en édition simple, ou sur le
   composeur (même retour visuel, voir ncComposerEl dans app.js). */
.dns-card.drag-over, .note-composer.drag-over {
  outline: 2px dashed var(--md-primary);
  outline-offset: -2px;
}
.dns-dropzone-hint {
  flex: none;
  text-align: center;
  padding: .5rem;
  margin-top: .5rem;
  border-radius: var(--shape-sm);
  border: 2px dashed var(--md-primary);
  color: var(--md-primary);
  font-size: .8125rem;
}

/* ========================= Éditeur d'image ========================= *
 * Visualiseuse/éditeur ouverte au clic sur une pièce jointe image (carte
 * ou édition simple) — voir openImageEditor() dans app.js. Grande boîte
 * de dialogue occupant l'essentiel de l'écran (pas un nouvel onglet),
 * dans le même langage visuel que le reste de l'app : .btn/.swatch/.c-*
 * réutilisés tels quels pour la palette de couleurs, cohérente avec les
 * couleurs de notes et de libellés déjà présentes ailleurs. */
/* [open] est indispensable ici, pas juste cosmétique : un <dialog> est
   display:none par défaut tant qu'il n'a pas cet attribut (posé par
   showModal()), mais seulement via la feuille de style du navigateur —
   un display:flex posé sans condition sur la classe l'aurait emporté sur
   cette règle native et affiché la boîte en permanence, à même la page
   (bug constaté : l'éditeur apparaissait sous la mosaïque de l'accueil
   sans qu'aucune image n'ait été cliquée). Même précaution déjà prise
   pour #dlg-note-simple[open] plus bas. */
.img-editor-dlg[open] {
  width: 94vw;
  max-width: 1150px;
  height: 90vh;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.img-editor-toolbar {
  display: flex;
  align-items: center;
  gap: .75rem;
  flex-wrap: wrap;
  padding: .625rem .875rem;
  background: var(--md-surface-2);
  border-bottom: 1px solid var(--md-outline-variant);
  flex: none;
}
.img-editor-tools { display: flex; gap: .125rem; }
.img-tool-btn {
  width: 36px;
  height: 36px;
  flex: none;
  display: grid;
  place-items: center;
  border: none;
  border-radius: var(--shape-full);
  background: transparent;
  color: var(--md-on-surface-variant);
  cursor: pointer;
}
.img-tool-btn svg { width: 19px; height: 19px; display: block; }
.img-tool-btn:hover { background: rgba(230,224,233,.10); color: var(--md-on-surface); }
.img-tool-btn.active { background: var(--md-secondary-container); color: var(--md-on-secondary-container); }

.img-editor-colors {
  display: flex;
  align-items: center;
  gap: .3rem;
  flex-wrap: wrap;
  padding-left: .5rem;
  border-left: 1px solid var(--md-outline-variant);
}
/* Vraies couleurs de dessin (voir IMG_EDITOR_COLORS dans app.js), pas les
   teintes assombries du thème. Contour un peu plus marqué que le .swatch
   standard : sur la barre d'outils sombre, le noir se confondrait avec le
   fond et le blanc n'aurait aucune limite visible. */
.img-editor-colors .swatch {
  width: 22px;
  height: 22px;
  border: 1px solid rgba(255,255,255,.35);
}

/* Épaisseur du trait libre. */
.img-editor-size {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding-left: .5rem;
  border-left: 1px solid var(--md-outline-variant);
  color: var(--md-on-surface-variant);
  font-size: .75rem;
}
.img-editor-size input[type=range] { width: 90px; accent-color: var(--md-primary); }
.img-editor-size span { min-width: 1.5em; text-align: right; }

/* Couleur de fond : la palette se déplie sous le bouton, en surcouche
   pour ne pas repousser le reste de la barre d'outils. */
.img-editor-bg { position: relative; display: flex; align-items: center; }
.img-editor-bg #img-editor-bg-colors {
  position: absolute;
  top: calc(100% + .35rem);
  left: 0;
  z-index: 5;
  width: 190px;
  padding: .5rem;
  background: var(--md-surface-4);
  border: none;
  border-radius: var(--shape-sm);
  box-shadow: var(--elev-3);
}

/* Plein écran : la boîte prend tout l'écran, il ne reste que la barre
   d'outils et l'image/le fond. Pas l'API Fullscreen du navigateur — un
   <dialog> modal est déjà au-dessus de tout, une classe suffit (voir
   imgEditorSetFullscreen() dans app.js). */
.img-editor-dlg[open].fullscreen {
  width: 100vw;
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  border-radius: 0;
  margin: 0;
  top: 0;
  left: 0;
}
.img-editor-dlg[open].fullscreen .img-editor-canvas-wrap { padding: .5rem; }

/* Tableau blanc : la surface dessinable occupe toute la zone disponible,
   sans marges autour (le canvas est dimensionné sur cette zone à
   l'ouverture, voir openWhiteboard()). Une photo, elle, garde ses marges
   et son propre rapport — on ne l'étire pas. */
.img-editor-dlg[open].board-mode .img-editor-canvas-wrap { padding: 0; }
.img-editor-dlg[open].board-mode #img-editor-canvas {
  width: 100%;
  height: 100%;
  box-shadow: none;
}

.img-editor-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-left: auto;
}
.img-editor-actions .sep { width: 1px; height: 24px; background: var(--md-outline-variant); }

.img-editor-canvas-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
  background: #000;
  padding: 1.25rem;
  position: relative;
}
#img-editor-canvas {
  max-width: 100%;
  max-height: 100%;
  display: block;
  box-shadow: var(--elev-2);
  /* Indispensable au dessin au doigt/stylet : sans ça le navigateur
     interprète le geste comme un défilement ou un zoom et interrompt le
     tracé au bout de quelques pixels (voir aussi le preventDefault dans
     les gestionnaires pointerdown/pointermove d'app.js). */
  touch-action: none;
  /* Évite la sélection bleue du canvas quand on trace vite. */
  user-select: none;
  -webkit-user-select: none;
}
#img-editor-canvas.tool-text { cursor: text; }
#img-editor-canvas:not(.tool-text) { cursor: crosshair; }

/* Saisie de texte flottante, positionnée en JS pile sur le point cliqué
   (voir l'outil "texte" dans openImageEditor()) — un <input> plutôt qu'un
   prompt() natif, pour rester dans le style de l'app et prévisualiser la
   couleur choisie pendant la frappe. */
.img-editor-text-input {
  position: absolute;
  z-index: 5;
  background: rgba(0,0,0,.55);
  border: 1px dashed #fff;
  color: #fff;
  padding: .15rem .4rem;
  font-size: 20px;
  min-width: 60px;
  border-radius: 4px;
}
.img-editor-text-input:focus { outline: none; }

/* ========================= Historique des versions ========================= */
/* [open] obligatoire ici — un display:flex posé sans condition afficherait
   la boîte en permanence, même fermée (piège déjà rencontré et corrigé sur
   .img-editor-dlg, voir son commentaire). */
.history-dlg[open] {
  width: 504px; /* +20% (420 -> 504), même bump que la base dialog */
  max-width: 90vw;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.history-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.25rem;
  border-bottom: 1px solid var(--md-outline-variant);
  flex: none;
}
.history-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: .75rem 1rem;
}
.history-footer {
  padding: .875rem 1.25rem;
  border-top: 1px solid var(--md-outline-variant);
  display: flex;
  justify-content: flex-end;
  flex: none;
}
/* Pas de background ici : posé par la classe .c-* ajoutée à côté en JS
   (renderHistoryList()), déjà définie plus haut dans ce fichier — même
   mécanisme que .agenda-item/.trash-card (voir leurs commentaires
   respectifs pour le piège de spécificité déjà rencontré à plusieurs
   reprises). */
.history-item {
  display: flex;
  flex-direction: column;
  width: 100%;
  text-align: left;
  border: none;
  border-radius: var(--shape-sm);
  padding: .625rem .875rem;
  margin-bottom: .5rem;
  color: var(--md-on-surface);
  cursor: pointer;
}
.history-item:hover { filter: brightness(1.12); }
.history-item-title {
  font-weight: 500;
  font-size: .875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.history-item-date {
  font-size: .75rem;
  color: var(--md-on-surface-variant);
  margin-top: .125rem;
}
#history-back { margin-bottom: .75rem; }
.history-detail-desc { font-style: italic; color: var(--md-on-surface-variant); margin: .25rem 0; }
.history-detail-body { white-space: pre-wrap; font-size: .9375rem; margin: .5rem 0; }
.history-detail-items { list-style: none; padding: 0; margin: .5rem 0; }
.history-detail-items li { padding: .25rem 0; font-size: .9375rem; }
.history-detail-items li.done { text-decoration: line-through; opacity: .6; }
.history-detail-due {
  display: inline-flex; align-items: center; gap: .375rem;
  font-size: .8125rem; color: var(--md-on-surface-variant); margin-top: .5rem;
}
.history-detail-due svg { width: 15px; height: 15px; }
.history-detail-attachments { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .75rem; }
.history-attach-thumb {
  width: 72px; height: 72px; object-fit: cover;
  border-radius: var(--shape-sm); display: block;
}
.history-attach-file {
  display: flex; align-items: center; gap: .375rem;
  padding: .375rem .625rem; border-radius: var(--shape-sm);
  background: rgba(230,224,233,.08); font-size: .8125rem;
}
.history-attach-file svg { width: 16px; height: 16px; flex: none; }

/* ============ Mobile (<860px) : dialogues plein écran, dvh, anti-zoom iOS ============
   Placé en toute fin de fichier, volontairement : chaque règle ci-dessous
   reprend EXACTEMENT le même sélecteur que sa règle de base plus haut
   (même spécificité), donc c'est l'ordre d'apparition dans le fichier qui
   tranche — pas de !important nécessaire pour la mise en page, seulement
   pour l'anti-zoom (voir plus bas, raison expliquée sur place).

   Une boîte de dialogue "de bureau" (552px, centrée, coins arrondis) est
   minuscule et mal cadrée sur un téléphone en portrait. Sous 860px, elles
   passent donc toutes en plein écran — convention native sur mobile (Keep,
   Gmail…), pas une fenêtre flottante.

   100dvh plutôt que 100vh : sur iOS Safari, `vh` est calculé barre
   d'adresse RÉTRACTÉE, donc un élément en `100vh` déborde sous la barre
   tant qu'elle est visible (juste après l'ouverture, avant tout défilement)
   — `dvh` (dynamic viewport height) s'ajuste en temps réel à la hauteur
   réellement visible. Conservé en confiance : `viewport-fit=cover` est
   déjà posé dans index.html (nécessaire pour que `env(safe-area-inset-*)`
   ci-dessous produise autre chose que 0). */
@media (max-width: 860px) {
  /* Base : couvre dlg-user / dlg-password / dlg-profil, qui n'ont pas de
     règle de largeur dédiée plus haut et héritent donc simplement de
     `dialog` (552px) — elles doivent passer en plein écran comme les
     autres. Les dialogues avec un ID/une classe dédiée ci-dessous héritent
     de cette base (position/inset/margin) puis ajustent ce qui leur est
     propre (padding, display interne…) séparément — je ne les répète pas
     ici pour ne pas les dupliquer. */
  dialog[open] {
    position: fixed;
    inset: 0;
    margin: 0;
    width: 100vw;
    height: 100dvh;
    max-width: 100vw;
    max-height: 100dvh;
    border-radius: 0;
    overflow-y: auto;
    /* Recouvre le padding fixe de la règle de base (1.5rem) : sur un
       écran avec encoche/barre de gestes, on garde au moins cet espace
       et on l'agrandit là où le système réclame de la marge en plus,
       plutôt que de la choisir à l'aveugle sans pouvoir vérifier sur un
       vrai appareil. */
    padding-top: max(1.5rem, env(safe-area-inset-top));
    padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
    padding-left: max(1.5rem, env(safe-area-inset-left));
    padding-right: max(1.5rem, env(safe-area-inset-right));
  }

  /* Édition simple : coquille transparente (voir son commentaire plus
     haut), le plein écran profite directement à .dns-card, qui grandit en
     flex:1 — la marge de respiration devient l'espace de sécurité du
     système plutôt qu'un chiffre choisi à vue. */
  #dlg-note-simple[open] {
    position: fixed;
    inset: 0;
    margin: 0;
    width: 100vw;
    height: 100dvh;
    min-height: 100dvh;
    max-width: 100vw;
    max-height: 100dvh;
    border-radius: 0;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
  }

  /* Boîte Outils (export/import/etc.) : même traitement plein écran que
     les autres boîtes de dialogue, avec défilement interne au cas où le
     contenu dépasse. */
  #dlg-outils[open] {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    margin: 0;
    position: fixed;
    inset: 0;
    overflow-y: auto;
    padding-top: max(1.5rem, env(safe-area-inset-top));
    padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
    padding-left: max(1.5rem, env(safe-area-inset-left));
    padding-right: max(1.5rem, env(safe-area-inset-right));
  }

  /* Historique des versions : header/body/footer ont chacun leur propre
     zone (voir plus haut) — je ne touche qu'à l'enveloppe extérieure, la
     structure interne à trois bandes n'a pas besoin de changer. */
  .history-dlg[open] {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    margin: 0;
    position: fixed;
    inset: 0;
  }
  .history-header { padding-top: max(1rem, env(safe-area-inset-top)); }
  .history-footer { padding-bottom: max(.875rem, env(safe-area-inset-bottom)); }

  /* Éditeur d'image / tableau blanc : déjà pensé pour un mode plein écran
     desktop optionnel (classe .fullscreen, bouton dédié) — sous 860px, ce
     plein écran devient le mode PAR DÉFAUT (mêmes valeurs), la surface de
     dessin doit profiter de tout l'écran sur un petit format, pas d'une
     boîte à 94vw avec des coins arrondis qui grignotent l'espace utile. */
  .img-editor-dlg[open] {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    margin: 0;
    position: fixed;
    inset: 0;
  }
  .img-editor-toolbar { padding-top: max(.625rem, env(safe-area-inset-top)); }
  .img-editor-actions { padding-bottom: env(safe-area-inset-bottom); }

  /* Anti-zoom iOS : Safari zoome automatiquement toute la page quand on
     touche un champ dont la taille de police CALCULÉE est < 16px. Or la
     plupart des champs de cette app sont à .75/.8125/.875rem (12-14px)
     sur desktop, choisis pour la densité visuelle — très bien sur un
     écran large, mais chaque frappe sur mobile déclencherait un zoom
     intempestif qu'il faudrait annuler à la main. `!important` assumé ici
     : les règles de base viennent de sélecteurs très divers (.field
     input, .toolbar input[type=search], .dn-item-row input[type=text]…),
     certaines avec une spécificité supérieure à un simple `input` — les
     reprendre une par une serait fragile (un seul oubli referait
     réapparaître le zoom). Un `!important` local et ciblé sur une seule
     propriété, uniquement sous 860px, est le choix le plus fiable. */
  input, textarea, select, [contenteditable="true"] {
    font-size: 16px !important;
  }
}

/* ============ Mobile (<860px) : tableau blanc / éditeur d'image, barre d'outils compacte ============
   La résolution du canvas s'adapte déjà à l'espace réellement disponible
   et à l'orientation réelle de l'écran (voir openWhiteboard() dans
   app.js — recalculé pour ne plus jamais déformer le rapport largeur/
   hauteur). Ici, on ne touche qu'à la barre d'outils : sur un téléphone
   en portrait, chaque pixel de hauteur pris par la barre est un pixel en
   moins pour dessiner — elle est donc resserrée pour laisser le plus de
   place possible au canevas lui-même, sans rien retirer (tous les outils
   restent, juste plus compacts). */
@media (max-width: 860px) {
  .img-editor-toolbar {
    gap: .5rem;
    padding: .5rem .625rem;
    padding-top: max(.5rem, env(safe-area-inset-top));
  }
  .img-tool-btn { width: 32px; height: 32px; }
  .img-tool-btn svg { width: 17px; height: 17px; }
  .img-editor-size, .img-editor-bg { padding-left: .375rem; }
  .img-editor-colors .swatch { width: 19px; height: 19px; }
  .img-editor-size input[type=range] { width: 64px; }

  /* Les 20 couleurs de la palette (voir IMG_EDITOR_COLORS dans app.js) ne
     tiennent nulle part sur une largeur de téléphone : en flex-wrap normal,
     ça se traduisait par une ligne avec seulement 1-2 pastilles visibles
     avant retour à la ligne, PUIS le bouton "couleur de fond" qui se
     retrouvait tout seul sur SA propre ligne juste après — trois lignes
     entières mangées pour un simple choix de couleur. flex-basis:100% force
     ce groupe sur sa propre ligne (même technique déjà utilisée pour
     .dns-bottom-bar .palette), mais SANS wrap à l'intérieur : les pastilles
     défilent horizontalement au doigt à la place. Une seule ligne, toutes
     les couleurs accessibles, et .img-editor-size/.img-editor-bg qui la
     suivent dans le DOM se retrouvent groupés ensemble juste en dessous au
     lieu d'être chacun isolé. */
  .img-editor-colors {
    flex-basis: 100%;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    padding-left: 0;
    padding-bottom: .25rem; /* respire un peu avant le bord du panneau */
    border-left: none;
  }
  .img-editor-colors .swatch { flex: none; }

  /* Le tableau blanc doit toucher les bords sur mobile comme sur desktop
     (déjà le cas via .board-mode, inchangé) ; pour une PHOTO en revanche,
     la marge desktop (1.25rem) grignote un espace précieux sur un petit
     écran sans bénéfice visuel équivalent — resserrée, pas supprimée : on
     veut encore voir que ce n'est pas collé au bord. */
  .img-editor-canvas-wrap { padding: .5rem; }
  .img-editor-dlg[open].fullscreen .img-editor-canvas-wrap,
  .img-editor-dlg[open].board-mode .img-editor-canvas-wrap { padding: 0; }
}

/* ========================= Page "fantôme" (/quick) =========================
   Même page que l'accueil (même app.js/style.css, aucune logique dupliquée :
   authentification, chiffrement, composeur, tout est déjà là) — seule la
   classe .quick-capture sur <body> (posée en dur dans quick.html, jamais par
   JS) change l'affichage : tout disparaît sauf le composeur, déjà déplié
   (voir NOTASK_QUICK_CAPTURE dans app.js) et mis en plein écran. Non reliée
   depuis le menu : sert uniquement de cible à une icône d'écran d'accueil
   dédiée (voir quick-manifest.json). !important nécessaire partout où
   layoutMosaic() pose une position en style inline (voir app.js) : un
   sélecteur de feuille de style ne bat une valeur inline que via
   !important. */
body.quick-capture .topbar,
body.quick-capture .drawer,
body.quick-capture .drawer-backdrop,
body.quick-capture .agenda-col,
body.quick-capture .search-toolbar,
body.quick-capture #notes-empty {
  display: none !important;
}
body.quick-capture main { padding: 0; width: 100%; }
/* Grille de la mosaïque : normalement position:relative avec une hauteur
   posée en JS (layoutMosaic()) pour porter les cartes en position absolue —
   ici elle ne doit plus contenir qu'un seul bloc, en flux normal. */
body.quick-capture .notes-grid {
  height: auto !important;
  padding: 0;
}
body.quick-capture .notes-grid .note { display: none !important; }
body.quick-capture .notes-grid .composer-stack {
  position: static !important;
  left: auto !important;
  top: auto !important;
  width: 100% !important;
  max-width: 640px;
  margin: 0 auto;
  padding: 1rem;
  /* 100dvh MOINS la hauteur du clavier virtuel (--kb-inset, tenu à jour par
     l'API VisualViewport — voir app.js) : sur iOS, le clavier recouvre le
     bas de l'écran sans redimensionner la mise en page, donc une colonne de
     100dvh pleine et entière laisse forcément sa dernière ligne — la barre
     d'outils — dessous. En retirant cette hauteur, le bas de la colonne
     tombe exactement sur le haut du clavier, et la barre remonte avec lui.
     Rien à changer quand le clavier est fermé : --kb-inset vaut alors 0. */
  min-height: calc(100dvh - var(--kb-inset, 0px));
  display: flex;
  flex-direction: column;
  transition: none !important;
}
/* La colonne se resserre : #nc-content ne peut plus réclamer 30vh, sinon il
   repousse la barre d'outils hors de l'espace restant, ce que ce calcul
   cherche précisément à éviter. Il garde flex:1, donc il occupe toujours
   tout ce qui reste — simplement plus de plancher. */
html.clavier-ouvert body.quick-capture #nc-content { min-height: 0; }
body.quick-capture .note-composer { display: flex; flex-direction: column; flex: 1; }
/* Le bloc d'outils est révélé par composerExpand() (appelé au chargement,
   voir app.js) — [hidden] gagnerait quand même si jamais cet appel
   échouait pour une raison quelconque (contenu pas encore prêt, par ex.) :
   double sécurité, un composeur replié serait inutilisable ici. */
body.quick-capture #nc-toolbar-block[hidden] { display: flex !important; }
/* Pour que la zone de texte (#nc-content) remplisse vraiment tout l'écran
   restant, `flex: 1` doit être posé à CHAQUE niveau de la chaîne
   d'ancêtres jusqu'à .note-composer — pas seulement sur #nc-content lui-
   même : #nc-content est en fait imbriqué dans .ta-wrap, lui-même dans
   .nc-card (voir index.html), et `flex: 1` sur un enfant n'a aucun effet
   tant que son PARENT DIRECT n'est pas display:flex. Première version
   posait `flex: none` sur .nc-card, ce qui coupait net toute la chaîne dès
   le premier niveau — la zone de texte restait alors bloquée à son
   min-height (30vh), le reste de l'écran en dessous restant vide. Titre,
   description, et le bloc d'outils (en bas) gardent leur taille naturelle
   (aucun flex-grow déclaré dessus) : seule .ta-wrap (donc #nc-content)
   grandit pour absorber tout l'espace restant. min-height: 0 nécessaire
   sur les conteneurs flex intermédiaires : sans ça, un enfant flex ne peut
   par défaut pas devenir plus PETIT que son propre contenu, ce qui aurait
   empêché #nc-content de se limiter à l'espace réellement disponible. */
body.quick-capture .nc-card {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
body.quick-capture .ta-wrap {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
/* Pas d'overflow-y:auto ici volontairement : une notask longue doit faire
   grandir toute la page (comme le composeur normal, auto-agrandissant),
   pas défiler dans une petite zone interne isolée — rien dans la chaîne
   .composer-stack/.note-composer/.nc-card/.ta-wrap ci-dessus ne pose de
   `height`/`max-height` (seulement des `min-height`), donc rien ne
   l'empêche de dépasser 100dvh si le texte grandit. */
body.quick-capture #nc-content { flex: 1; min-height: 30vh; }

@media (max-width: 860px) {
  body.quick-capture .notes-grid .composer-stack { padding: max(1rem, env(safe-area-inset-top)) 1rem max(1rem, env(safe-area-inset-bottom)); }
}


/* ---- Sauvegardes Google Drive (bloc Outils, voir #outil-drive) ---- */
.outil-drive {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--md-outline-variant);
}
.outil-drive h4 {
  margin: 0 0 .375rem;
  font-size: .9375rem;
  font-weight: 600;
}
.drive-liste { display: grid; gap: .375rem; margin-top: .75rem; }
.drive-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .125rem;
  width: 100%;
  padding: .5rem .625rem;
  background: var(--md-surface-2);
  border: none;
  border-radius: var(--shape-sm);
  color: var(--md-on-surface);
  text-align: left;
  cursor: pointer;
  transition: background .15s;
}
.drive-item:hover { background: rgba(230,224,233,.12); }
.drive-item-nom { font-size: .875rem; }
.drive-item-info { font-size: .75rem; color: var(--md-on-surface-variant); }

/* Ligne d'alerte « reconnecter Google » en tête des échéances en retard
   (voir renderAgenda). Pas de case à cocher : ce n'est pas une notask, on
   ne la termine pas, elle disparaît quand le compte est reconnecté. D'où
   le retrait de la place normalement réservée à la case. */
.agenda-item.agenda-alerte { padding-left: .625rem; }


/* Cases d'activation par fonction dans Profil (voir #profil-google-fonctions).
   Libellé cliquable en entier via <label> : une case à cocher seule fait une
   cible de 13px, pénible à viser au doigt. */
.profil-google-fonctions {
  display: grid;
  gap: .5rem;
  margin: .75rem 0 .25rem;
}
.profil-google-case {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  font-size: .875rem;
  cursor: pointer;
}
.profil-google-case input { margin-top: .15rem; flex: none; cursor: pointer; }

/* ---- Zoom d'affichage (Profil > Affichage) ---- */
.profil-zoom-row {
  display: flex;
  align-items: center;
  gap: .875rem;
}
.profil-zoom-row input[type="range"] {
  flex: 1;
  accent-color: var(--md-primary, #d0bcff);
  cursor: pointer;
}
.profil-zoom-val {
  font-size: .875rem;
  font-variant-numeric: tabular-nums;  /* la largeur ne saute pas de 70 à 100 */
  min-width: 3.5ch;
  text-align: right;
  color: var(--md-on-surface-variant);
}

/* ---- Purge (Outils > sauvegarde) ---- */
.outil-purge {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--md-outline-variant);
}
.outil-purge h4 { margin: 0 0 .375rem; font-size: .9375rem; font-weight: 600; }
/* Bouton ARMÉ : même code visuel que la corbeille d'une carte (voir
   .note .actions button[data-act=delete].arme) — rouge plein et pulsation,
   pour que l'état « un clic de plus et c'est parti » soit indiscutable. */
#purge-run.arme {
  background: var(--md-error-container);
  color: #fff;
  animation: pouls-suppression 1.1s ease-in-out infinite;
}
