/* ==========================================================================
   trouvailles — liens + vidéos
   Structure héritée de TubeNotes, palette bleu / violet / magenta pastel.
   Sommaire : 1 variables · 2 base · 3 aquarelle · 4 topbar · 5 onglets
   6 recherche · 7 filtres · 8 grille & cartes · 9 vidéos & notes
   10 formulaires (feuilles) · 11 connexion · 12 bannière d'installation
   13 bouton flottant · 14 divers
   ========================================================================== */

/* ---------- 1. Variables ---------- */
:root {
  --paper: #faf8fc;
  --card: #ffffff;
  --ink: #2e2a3d;
  --muted: #6b6478;
  /* --faint ne sert plus qu'aux icônes, aux croix et aux bordures : le
     seuil qui s'y applique est 3:1, pas 4,5. #a59cb3 valait 2,62 et
     échouait même à celui-là. #8d84a0 monte à 3,53 sur blanc, 3,35 sur le
     fond de page, et reste assez clair pour ne pas rejoindre --muted :
     la hiérarchie ink / muted / faint tient toujours à 13,86 / 5,65 / 3,53. */
  --faint: #8d84a0;
  --line: #e8e2f2;
  --line-soft: #f2eef9;

  --blue: #5588d6;      --blue-soft: #e2ebfb;
  --violet: #8862c9;    --violet-soft: #ebe2fa;
  --magenta: #cc4f96;   --magenta-soft: #fae0ef;
  /* Le magenta vif ne passe pas AA en texte : 4,10 sur blanc, il faut 4,5.
     Or c'est la couleur des messages d'ERREUR — « Envoi impossible »,
     « Mot de passe incorrect ». Le texte qu'on doit pouvoir lire est le seul
     qu'on n'arrivait pas à lire.

     Même parade que --orange-text et --ocre-text, créés hier pour ce motif
     exact : la teinte vive garde son rôle de signal (pastilles, bordures,
     où le seuil est 3), une variante foncée porte les mots. */
  --magenta-text: #c2437f;
  --pink: #c15c84;      --pink-soft: #fbe5ed;
  --vert: #3f9d8f;      --vert-soft: #ddf1ed;

  --note-bg: #f4effc;
  --note-edge: #c9b3ea;

  --grad: linear-gradient(135deg, var(--blue), var(--violet) 50%, var(--magenta));
  --radius: 12px;
  --shadow: 0 1px 2px rgba(46,42,61,0.05), 0 2px 8px rgba(46,42,61,0.06);
  --shadow-hover: 0 2px 4px rgba(46,42,61,0.07), 0 6px 18px rgba(46,42,61,0.09);
}

/* ---------- 2. Base ---------- */
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; }
body {
  background: var(--paper); color: var(--ink);
  font-family: 'Karla', sans-serif; font-size: 16px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3 { font-family: 'Bricolage Grotesque', sans-serif; }
a { color: inherit; }
button, input, select, textarea { font-family: inherit; }
::selection { background: var(--violet-soft); }

.app { position: relative; z-index: 1; min-height: 100vh; padding: 14px clamp(12px, 3vw, 40px) 120px; max-width: 1500px; margin: 0 auto; }
.loading { display: grid; place-items: center; min-height: 70vh; color: var(--muted); }

/* ---------- 3. Voile aquarelle ---------- */
.watercolor { position: absolute; top: 0; left: 0; right: 0; height: 360px; overflow: hidden; z-index: 0; pointer-events: none; }
.watercolor span { position: absolute; border-radius: 50%; filter: blur(70px); opacity: 0.35; }
.wc1 { width: 300px; height: 300px; background: var(--blue); top: -80px; left: -70px; }
.wc2 { width: 260px; height: 260px; background: var(--magenta); top: -60px; right: -50px; }
.wc3 { width: 230px; height: 230px; background: var(--violet); top: 80px; left: 40%; }

/* ---------- 4. Barre du haut ---------- */
.topbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.brand-mark { width: 40px; height: 40px; border-radius: 11px; flex-shrink: 0; }
.brand h1 { font-size: 23px; font-weight: 800; margin: 0; letter-spacing: -0.4px; }
.brand .count {
  font-size: 12px; font-weight: 700; color: var(--violet);
  background: var(--violet-soft); border-radius: 999px;
  padding: 3px 9px; line-height: 1.3; vertical-align: 2px;
}
.topbar-right { display: flex; align-items: center; gap: 9px; }

/* Engrenage des espaces, dans l'en-tête, À TOUTES LES LARGEURS.

   Il remplace la pastille « Gérer » qui vivait dans la barre des espaces.
   Deux raisons. La première est ergonomique : cette barre sert à CHANGER
   d'espace, l'engrenage sert à les CONFIGURER — deux intentions qui n'ont
   pas à partager une ligne. La seconde est arithmétique : la pastille
   mangeait la largeur, et au troisième espace les suivants se retrouvaient
   coupés.

   Partout, et non sur téléphone seulement : une commande qui change de
   place selon la largeur de la fenêtre se perd dès qu'on redimensionne. */
.topbar-gerer {
  display: flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: 0 0 auto;
  border: 1.5px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--muted); cursor: pointer;
  transition: color .15s, border-color .15s;
}
.topbar-gerer:hover { color: var(--violet); border-color: var(--violet); }
.sync-wrap { display: flex; align-items: center; gap: 6px; border: none; background: none; cursor: pointer; padding: 4px; }
.sync-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--faint); }
.sync-dot.synced { background: var(--violet); }
.sync-dot.syncing { background: var(--blue); animation: pulse 1s infinite ease-in-out; }
.sync-dot.error { background: var(--magenta); }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.35} }
.sync-txt { font-size: 13px; color: var(--muted); font-weight: 600; }
@media (max-width: 430px) { .sync-txt { display: none; } }
.avatar {
  width: 38px; height: 38px; border-radius: 50%; background: var(--grad); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: 'Bricolage Grotesque', sans-serif; font-weight: 700; font-size: 15px;
  border: none; cursor: pointer; flex-shrink: 0;
}

/* ---------- 5. (les onglets de type sont descendus dans la barre du bas : voir plus bas) ---------- */

/* ---------- 6. Recherche ---------- */
/* Aucun fond, jamais : ni au repos, ni collée en haut. Seul le champ arrondi
   porte son fond blanc. Le champ étant opaque et pleine largeur, le contenu
   qui défile derrière ne transparaît que sur quelques pixels de marge. */
.searchbar { position: sticky; top: 0; z-index: 20; background: transparent; padding: 6px 0 10px; }
.searchbar .inner { position: relative; }
.search-input {
  width: 100%; padding: 13px 44px 13px 44px; font: inherit; font-size: 16px;
  color: var(--ink); background: var(--card);
  border: 1.5px solid var(--line); border-radius: 999px; outline: none;
  box-shadow: var(--shadow); transition: border-color 0.15s;
}
.search-input:focus { border-color: var(--violet); }
.search-input::placeholder { color: var(--faint); }
.search-icon { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
.clear-btn {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px; border-radius: 50%; border: none;
  background: var(--line-soft); color: var(--muted); cursor: pointer; font-size: 14px;
}

/* ---------- 7. Filtres ---------- */
.filters { margin-bottom: 16px; }
.chip-row { display: flex; gap: 8px; align-items: center; flex-wrap: nowrap; overflow-x: auto; padding-bottom: 4px; -webkit-overflow-scrolling: touch; }

/* La rangée des espaces défile seule sur toute la largeur : « Gérer » est
   parti dans l'en-tête. Le fondu à droite dit que la liste continue —
   quand elle ne déborde pas, il n'a rien à estomper et ne se voit pas. */
/* Le conteneur ne sert plus qu'a porter la marge basse et le fondu :
   « Gerer » qu'il epinglait est parti dans l'en-tete. */
.espaces-bar { min-width: 0; margin-bottom: 10px; }
.espaces-bar > .chip-row {
  -webkit-mask-image: linear-gradient(90deg, #000 88%, transparent);
  mask-image: linear-gradient(90deg, #000 88%, transparent);
}
.chip-badge {
  margin-left: 7px; font-size: 9.5px; font-weight: 800; letter-spacing: 0.07em;
  text-transform: uppercase; opacity: 0.62; vertical-align: 1px;
}
.chip-row::-webkit-scrollbar { display: none; }
.chip {
  font: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
  padding: 8px 14px; min-height: 38px; border-radius: 999px;
  border: 1.5px solid var(--line); background: var(--card); color: var(--muted);
  white-space: nowrap; transition: all 0.15s; flex-shrink: 0;
}
.chip:hover { color: var(--ink); }
.chip.active { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.chip.c-bleu { background: var(--blue-soft); border-color: transparent; color: var(--blue); }
.chip.c-bleu.active { background: var(--blue); color: #fff; }
.chip.c-violet { background: var(--violet-soft); border-color: transparent; color: var(--violet); }
.chip.c-violet.active { background: var(--violet); color: #fff; }
.chip.c-vert { background: var(--vert-soft); border-color: transparent; color: var(--vert); }
.chip.c-vert.active { background: var(--vert); color: #fff; }
.chip.c-magenta { background: var(--magenta-soft); border-color: transparent; color: var(--magenta); }
.chip.c-magenta.active { background: var(--magenta); color: #fff; }

/* ---------- 8. Grille & cartes ---------- */
.grid { display: grid; grid-template-columns: 1fr; gap: 15px; }
@media (min-width: 641px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .grid { grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); } }

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; display: flex; flex-direction: column;
  box-shadow: var(--shadow); transition: box-shadow 0.15s; position: relative;
}
.card:hover { box-shadow: var(--shadow-hover); }
.card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; z-index: 2; }
.card.s-bleu::before { background: var(--blue); }
.card.s-violet::before { background: var(--violet); }
.card.s-vert::before { background: var(--vert); }
.card.s-magenta::before { background: var(--magenta); }
.card.archived { opacity: 0.55; }

.card-body { padding: 13px 14px 14px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.card-head { display: flex; align-items: flex-start; width: 100%; }
.card-head .menu-wrap { margin-left: auto; flex-shrink: 0; }
.card-title { font-family: 'Bricolage Grotesque', sans-serif; font-size: 17px; font-weight: 700; margin: 0; line-height: 1.3; min-width: 0; }
.card-title a { text-decoration: none; overflow-wrap: anywhere; }
.card-title a:hover { color: var(--violet); }
.card-domain { font-size: 13px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.card-domain img { width: 16px; height: 16px; border-radius: 4px; }
.card-desc { margin: 0; font-size: 14.5px; color: var(--muted); overflow-wrap: anywhere; }
.card-desc.i { font-style: italic; }

/* « seul » : ce texte n'a pas de titre au-dessus de lui, il EST la carte.

   La description est en --muted, gris. C'est juste quand elle accompagne un
   titre : le titre ancre, elle explique. Mais sur une idée sans titre, elle
   est le seul texte — et une carte entièrement grise a l'air éteinte à côté
   d'une carte de lien dont le titre est noir.

   C'est ce que le titre automatique allait chercher, en recopiant le début
   du texte en gras au-dessus du texte. On fonce celui qui existe plutôt que
   d'en fabriquer un second : même ancrage, 53 pixels de carte en moins. */
.card-desc.seul {
  font-size: 15.5px;
  color: var(--ink);
  font-weight: 500;
  line-height: 1.45;
}
.card-action { font-size: 13.5px; background: var(--line-soft); border-radius: 8px; padding: 8px 10px; }
.card-action b { display: block; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px; }

/* Le repère « Où j'en suis ». Compact et aligné à gauche : c'est un mot,
   pas un paragraphe — S3E7, Page 142. Un bloc pleine largeur comme
   .card-action lui donnerait l'importance d'une description.
   La flèche dit « reprendre ici » sans qu'on ait à écrire un libellé :
   « Où j'en suis : S3E7 » ferait trois fois la longueur de l'information. */
.card-repere {
  align-self: flex-start;
  display: inline-flex; align-items: baseline; gap: 6px;
  max-width: 100%;
  font-size: 13.5px; font-weight: 500; color: var(--ink);
  background: var(--line-soft); border-radius: 8px; padding: 5px 10px;
  overflow-wrap: anywhere;
}
.card-repere-fleche { color: var(--faint); font-weight: 400; }
.clamp3 {
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  overflow: hidden; cursor: pointer;
}
.card-action .clamp3 { cursor: inherit; }
.card-action:has(.clamp3) { cursor: pointer; }
.clamp3.short { cursor: default; }
.detail-body { font-size: 15px; line-height: 1.6; color: var(--ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.detail-section { margin-bottom: 16px; }
.detail-section b { display: block; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px; font-weight: 700; }
.detail-link { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--muted); margin-bottom: 14px; }
.detail-link img { width: 16px; height: 16px; border-radius: 4px; }

/* Invite « Compléter » — même habillage que la ligne d'ajout de note des
   vidéos (.note-add) : un champ, puis un « ＋ ». Le geste se lit donc de
   la même façon d'une carte à l'autre, même si l'un écrit sur place et
   l'autre ouvre le formulaire.

   Elle ne s'affiche que s'il reste de la place, et sa hauteur est
   PLAFONNÉE : la version précédente occupait tout le vide, donc sa
   taille était inversement proportionnelle à ce que la fiche contient —
   la plus pauvre criait le plus fort. Le reste du vide redevient blanc.

   Les cartes vidéo n'en ont pas : elles portent déjà leur propre champ
   de note horodatée au même endroit, et deux invitations superposées
   dans le même espace se concurrencent. */
.card-fill {
  flex: 1 1 0; min-height: 0; max-height: 42px; overflow: hidden;
  margin: -2px 0 0; padding: 0; width: 100%;
  border: none; background: none; cursor: pointer;
  display: flex; align-items: stretch; gap: 7px;
  font: inherit; text-align: left;
}
/* Les deux morceaux restent invisibles tant qu'il n'y a pas la place :
   la transparence évite tout calcul de hauteur, donc toute oscillation. */
.card-fill-champ, .card-fill-plus { opacity: 0; transition: opacity .15s; }
.card-fill.rempli .card-fill-champ, .card-fill.rempli .card-fill-plus { opacity: 1; }

.card-fill-champ {
  flex: 1; min-width: 0; display: flex; align-items: center;
  border: 1.5px dashed var(--line); border-radius: 9px; padding: 0 11px;
  background: var(--card); color: var(--muted); font-size: 14px;
}
.card-fill-plus {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  min-width: 42px; border-radius: 9px;
  background: var(--violet-soft); color: var(--violet);
  font-weight: 700; font-size: 16px;
}
.card-fill:hover .card-fill-champ { border-color: var(--violet); color: var(--ink); }

/* Sous 641 px la grille passe à UNE colonne : chaque carte prend sa
   hauteur naturelle, il n'y a jamais de vide, donc jamais d'invite. */
@media (max-width: 640px) { .card-fill { display: none; } }

.card-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: auto; padding-top: 4px; }
.card-tags { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.tag { font-size: 12.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }
.tag.t-blue { background: var(--blue-soft); color: var(--blue); }
.tag.t-violet { background: var(--violet-soft); color: var(--violet); }
.tag.t-magenta { background: var(--magenta-soft); color: var(--magenta); }
.tag.t-pink { background: var(--pink-soft); color: var(--pink); }
.tag.date { background: transparent; padding: 0; color: var(--muted); font-weight: 500; }

.status-pill {
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  padding: 5px 12px; min-height: 30px; border-radius: 999px; border: none; color: #fff; flex-shrink: 0;
}
.status-pill.p-bleu { background: var(--blue); }
.status-pill.p-violet { background: var(--violet); }
.status-pill.p-vert { background: var(--vert); }
.status-pill.p-magenta { background: var(--magenta); }

.iconbtn {
  width: 34px; height: 34px; border-radius: 9px; border: none; background: transparent;
  color: var(--faint); display: flex; align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0;
}
.iconbtn:hover, .iconbtn:active { background: var(--line-soft); color: var(--ink); }

.menu-wrap { position: relative; }
.menu {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 12px; box-shadow: var(--shadow-hover); overflow: hidden; z-index: 60; min-width: 190px;
}
/* Voile transparent : un clic à côté referme le menu. */
.menu-voile { position: fixed; inset: 0; z-index: 59; }
.menu button {
  display: flex; align-items: center; gap: 9px; width: 100%; border: none; background: none;
  padding: 11px 13px; font-size: 14.5px; color: var(--ink); cursor: pointer; text-align: left;
}
.menu button:hover { background: var(--line-soft); }
.menu button.danger { color: var(--magenta); }

.empty { text-align: center; padding: 60px 20px; color: var(--muted); border: 1.5px dashed var(--line); border-radius: 16px; grid-column: 1 / -1; }

/* ---------- 9. Vidéos & notes ---------- */
.thumb-wrap { position: relative; aspect-ratio: 16/9; background: var(--line-soft); cursor: pointer; }
.thumb-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb-wrap iframe { width: 100%; height: 100%; border: 0; display: block; }
.thumb-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.thumb-play span {
  width: 52px; height: 52px; border-radius: 50%; background: rgba(46,42,61,0.72);
  display: flex; align-items: center; justify-content: center; color: #fff;
  transition: transform 0.15s, background 0.15s;
}
.thumb-wrap:hover .thumb-play span { transform: scale(1.08); background: var(--magenta); }

.notes { display: flex; flex-direction: column; gap: 8px; }
.note {
  background: var(--note-bg); border-left: 4px solid var(--note-edge);
  border-radius: 6px; padding: 9px 11px 5px; font-size: 14.5px; line-height: 1.55;
}
.note-text { margin: 0 0 2px; white-space: pre-wrap; overflow-wrap: anywhere; }
.note .ts {
  color: var(--violet); font-weight: 700; text-decoration: none;
  background: var(--violet-soft); padding: 1px 7px; border-radius: 6px;
}
.note .ts:hover { background: var(--violet); color: #fff; }
.note-tools { display: flex; justify-content: flex-end; gap: 2px; }
.note-tools button { border: none; background: none; color: var(--muted); cursor: pointer; padding: 3px 6px; font-size: 13px; border-radius: 6px; }
.note-tools button:hover { background: var(--card); color: var(--ink); }
.note-edit { display: flex; gap: 6px; }
.note-edit input { flex: 1; border: 1.5px solid var(--note-edge); border-radius: 8px; padding: 7px 10px; font-size: 14px; outline: none; }

.note-add { display: flex; gap: 7px; }
.note-add input {
  flex: 1; min-width: 0; border: 1.5px solid var(--line); border-radius: 9px; padding: 9px 11px;
  font-size: 14.5px; outline: none; background: var(--card);
}
.note-add input:focus { border-color: var(--violet); }
.note-add button {
  border: none; border-radius: 9px; padding: 0 14px; min-width: 42px; background: var(--violet-soft);
  color: var(--violet); font-weight: 700; cursor: pointer; font-size: 16px;
}
.more-notes { border: 1.5px solid var(--line); background: var(--card); color: var(--muted); border-radius: 9px; padding: 8px; font-size: 13.5px; font-weight: 700; cursor: pointer; }

/* ---------- 10. Formulaires (feuilles) ---------- */
.overlay {
  position: fixed; inset: 0; background: rgba(46,42,61,0.4); z-index: 40;
  display: flex; align-items: flex-end; justify-content: center;
}
.sheet {
  background: var(--paper); width: 100%; max-height: 92vh; overflow-y: auto;
  border-radius: 20px 20px 0 0; padding: 10px 18px calc(22px + env(safe-area-inset-bottom));
  animation: up .22s ease;
}
.sheet-handle { width: 38px; height: 4px; border-radius: 3px; background: var(--line); margin: 4px auto 14px; }
@keyframes up { from { transform: translateY(100%); } to { transform: translateY(0); } }
@media (min-width: 680px) {
  .overlay { align-items: center; }
  .sheet { max-width: 540px; border-radius: 18px; padding: 22px 24px; max-height: 86vh; }
  .sheet-handle { display: none; }
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.sheet-head h2 { font-size: 19px; font-weight: 700; margin: 0; }

.field { margin-bottom: 13px; }
.field label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; font-weight: 700; }
.field label .hint { font-weight: 500; text-transform: none; }
.field input[type=text], .field input[type=url], .field input[type=email], .field input[type=password], .field textarea {
  width: 100%; background: var(--card); border: 1.5px solid var(--line); border-radius: 11px;
  color: var(--ink); font-size: 16px; padding: 11px 13px; outline: none;
}
.field input:focus, .field textarea:focus { border-color: var(--violet); }
.field textarea { resize: vertical; min-height: 52px; }
.form-err { color: var(--magenta-text); font-size: 13.5px; margin: 4px 0 0; }
.form-preview { width: 100%; max-width: 260px; border-radius: 10px; display: block; }

.state-choice { display: flex; gap: 8px; }
.state-choice button {
  flex: 1; border: 1.5px solid var(--line); border-radius: 12px; padding: 11px 6px;
  background: var(--card); color: var(--muted); font-size: 13.5px; font-weight: 700; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 5px; min-height: 44px;
}
.state-choice button.sel-bleu { border-color: var(--blue); background: var(--blue-soft); color: var(--blue); }
.state-choice button.sel-violet { border-color: var(--violet); background: var(--violet-soft); color: var(--violet); }
.state-choice button.sel-vert { border-color: var(--vert); background: var(--vert-soft); color: var(--vert); }
.state-choice button.sel-magenta { border-color: var(--magenta); background: var(--magenta-soft); color: var(--magenta); }

.disclosure {
  display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 14px; font-weight: 700;
  padding: 10px 2px; cursor: pointer; border-top: 1px solid var(--line-soft); margin-top: 6px;
}

.sheet-actions { display: flex; gap: 10px; margin-top: 16px; }
.btn { flex: 1; border-radius: 12px; padding: 13px; font-size: 15px; font-weight: 700; cursor: pointer; border: none; min-height: 48px; }
.btn-secondary { background: var(--card); border: 1.5px solid var(--line); color: var(--muted); }
.btn-primary { background: var(--grad); color: #fff; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-text { background: none; border: none; color: var(--muted); font-size: 14px; font-weight: 700; cursor: pointer; padding: 10px; }
.btn-text:hover { color: var(--ink); }

.espace-item { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--line-soft); }
.espace-nom { flex: 1; min-width: 0; display: flex; align-items: center; gap: 7px; font-weight: 700; font-size: 14.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.espace-code {
  display: flex; align-items: center; gap: 5px; border: 1.5px dashed var(--line);
  background: var(--card); color: var(--violet); font-weight: 700; font-size: 13px;
  letter-spacing: 0.08em; border-radius: 8px; padding: 6px 10px; cursor: pointer; flex-shrink: 0;
}
.espace-code:hover { border-color: var(--violet); }

.espace-membres-jauge {
  display: flex; align-items: center; gap: 4px; flex: 0 0 auto;
  background: var(--violet-soft); color: var(--violet); border: none;
  border-radius: 999px; padding: 4px 10px; font-size: 12px; font-weight: 700; cursor: pointer;
}
.espace-membres-jauge:hover { filter: brightness(0.96); }
.espace-membres {
  flex-basis: 100%; margin-top: 4px; padding: 8px 0 10px 22px;
}
.espace-membres-titre {
  margin: 0 0 8px; font-size: 12px; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; color: var(--faint);
}
.espace-membre {
  display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 13.5px;
}
.espace-membre-mail {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink);
}
.espace-membre-vous, .espace-membre-role {
  flex: 0 0 auto; font-size: 11.5px; font-weight: 700; border-radius: 999px; padding: 2px 8px;
}
.espace-membre-vous { background: var(--violet-soft); color: var(--violet); }
.espace-membre-role { background: var(--line-soft); color: var(--muted); }
.espace-form { display: flex; gap: 8px; margin-top: 10px; }
.espace-form input {
  flex: 1; min-width: 0; background: var(--card); border: 1.5px solid var(--line); border-radius: 10px;
  color: var(--ink); font-size: 14.5px; padding: 10px 12px; outline: none;
}
.espace-form input:focus { border-color: var(--violet); }
.espace-form .btn { flex: 0 0 auto; min-height: 42px; padding: 10px 16px; font-size: 14px; }

.saisie-champ {
  width: 100%; background: var(--paper); border: 1.5px solid var(--line); border-radius: 11px;
  color: var(--ink); font: inherit; font-size: 16px; padding: 12px 13px; outline: none;
  box-sizing: border-box; margin-bottom: 12px;
}
.saisie-champ:focus { border-color: var(--violet); }

.admin-users { display: flex; flex-direction: column; gap: 7px; margin-bottom: 6px; }
.admin-user-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 10px;
  padding: 9px 12px;
}
.admin-user-info { display: flex; align-items: center; gap: 8px; min-width: 0; }
.admin-user-email { font-size: 14.5px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.admin-badge {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--violet); background: var(--violet-soft); border-radius: 999px; padding: 2px 8px; flex-shrink: 0;
}
.iconbtn.danger { color: var(--magenta); }

.reglage-bloc { border: 1.5px solid var(--line); border-radius: 12px; padding: 12px 13px; margin-bottom: 12px; background: var(--paper); }
.reglage-bloc > label { display: block; font-size: 13px; color: var(--muted); font-weight: 700; }
.admin-conso {
  font-size: 11.5px; font-weight: 700; color: var(--vert); background: var(--vert-soft);
  border-radius: 999px; padding: 2px 8px; flex-shrink: 0; white-space: nowrap;
}
.jauge { margin-top: 4px; }
.jauge-barre { height: 10px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.jauge-remplie { height: 100%; border-radius: 999px; transition: width .3s ease; }
.jauge-txt { font-size: 13.5px; color: var(--muted); margin-top: 8px; }
.jauge-txt b { color: var(--ink); }

/* L'alerte de quota : elle prévient AVANT le mur, et elle a une porte.

   Fond magenta pâle, pas rouge : ce n'est pas une erreur, c'est un
   avertissement. Rien n'est cassé, il reste de la place — mais il faut
   agir avant qu'il n'y en ait plus. Le bouton n'est pas un lien de plus
   à recopier : il ouvre un courriel déjà écrit. */
/* La tuile d'alerte de l'admin : même magenta que l'alerte de
   l'utilisateur, parce que c'est la même information vue de l'autre côté.
   Elle ne s'affiche que s'il y a quelqu'un — une tuile à zéro en
   permanence, on cesse de la voir. */
/* Le « / 500 » du cadran : présent mais discret. C'est le chiffre de
   gauche qui compte ; celui-ci ne fait que lui donner son échelle. */
.stat__sur {
  font-size: 0.55em;
  font-weight: 700;
  color: var(--faint);
  letter-spacing: 0;
}

.stat.stat--alerte {
  background: var(--magenta-soft);
  border-color: var(--magenta);
}
.stat.stat--alerte .stat__value { color: var(--magenta-text); }

.quota-alerte {
  margin-top: 12px;
  background: var(--magenta-soft);
  border: 1px solid var(--magenta);
  border-radius: 12px;
  padding: 12px 14px;
}
.quota-alerte-txt {
  margin: 0 0 10px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--ink);
}
.quota-alerte-txt b { color: var(--magenta-text); }
.quota-alerte-btn {
  width: 100%;
  border: none;
  border-radius: 10px;
  padding: 9px 14px;
  background: var(--magenta-text);
  color: #fff;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}
.quota-alerte-btn:active { transform: scale(0.98); }
.admin-stockage {
  font-size: 11.5px; font-weight: 700; color: var(--blue); background: var(--blue-soft);
  border-radius: 999px; padding: 2px 8px; flex-shrink: 0; white-space: nowrap;
}

.admin-neuf {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--blue); background: var(--blue-soft); border-radius: 999px; padding: 2px 8px; flex-shrink: 0;
}
.admin-attente {
  font-size: 11px; font-weight: 700; color: var(--magenta); background: var(--magenta-soft);
  border-radius: 999px; padding: 2px 8px; flex-shrink: 0; white-space: nowrap;
}

.tag-row { display: flex; align-items: stretch; gap: 6px; }
.tag-row .tag-pick {
  flex: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 11px;
  padding: 11px 13px; font: inherit; font-size: 14.5px; cursor: pointer; min-height: 44px;
}
.tag-row .tag-pick:hover { border-color: var(--violet); }

/* Le même bouton, mais seul dans sa liste — la feuille « Taguer N
   trouvailles » n'a pas de bouton de fusion à côté, donc pas de .tag-row
   pour l'habiller. Sans cette règle il serait nu. */
.tag-liste > .tag-pick {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 11px;
  padding: 11px 13px; font: inherit; font-size: 14.5px; cursor: pointer; min-height: 44px;
}
.tag-liste > .tag-pick:hover { border-color: var(--violet); }

/* Le choix d'un statut : pastille puis libellé, alignés à gauche. Le motif
   des tags (nom à gauche, compteur à droite) ne convient pas — il n'y a
   rien à compter, et space-between enverrait le libellé au bout de la ligne,
   loin de sa couleur. */
.statut-pick {
  width: 100%; display: flex; align-items: center; gap: 11px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 11px;
  padding: 11px 13px; font: inherit; font-size: 14.5px; color: var(--ink);
  text-align: left; cursor: pointer; min-height: 44px;
}
.statut-pick:hover { border-color: var(--violet); }

.statut-point {
  flex: 0 0 auto; width: 12px; height: 12px; border-radius: 50%;
}
.statut-point.c-gris    { background: var(--gris); }
.statut-point.c-bleu    { background: var(--blue); }
.statut-point.c-violet  { background: var(--violet); }
.statut-point.c-magenta { background: var(--magenta); }
.statut-point.c-vert    { background: var(--vert); }
.statut-point.c-rouge   { background: var(--rouge); }
.statut-point.c-orange  { background: var(--orange); }
.statut-point.c-ocre    { background: var(--ocre); }
.statut-point.c-pink    { background: var(--pink); }
.tag-row .tag-merge {
  flex: 0 0 auto; width: 44px; display: flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 11px;
  color: var(--muted); cursor: pointer;
}
.tag-row .tag-merge:hover { border-color: var(--violet); color: var(--violet); }

.vues-bar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; align-items: center; }
.vue-chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 999px;
  padding: 7px 8px 7px 14px; font-size: 14px; font-weight: 700; color: var(--muted);
  user-select: none; -webkit-user-select: none; transition: all 0.15s;
}
.vue-chip:hover { border-color: var(--violet); }
.vue-chip.active { background: var(--violet-soft); border-color: var(--violet); color: var(--violet); }
.vue-chip .vue-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; font-size: 12px; color: var(--faint);
  background: var(--line-soft);
}
.vue-chip .vue-x:hover { background: var(--magenta-soft); color: var(--magenta); }
.vue-save {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  background: none; border: 1.5px dashed var(--line); border-radius: 999px;
  padding: 7px 14px; font: inherit; font-size: 13.5px; font-weight: 700; color: var(--muted);
}
.vue-save:hover { border-color: var(--violet); color: var(--violet); }

.taginput { position: relative; }
.taginput input {
  width: 100%; background: var(--paper); border: 1.5px solid var(--line); border-radius: 11px;
  color: var(--ink); font-size: 16px; padding: 12px 13px; outline: none; box-sizing: border-box;
}
.taginput input:focus { border-color: var(--violet); }
.taginput-box {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 40;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 12px;
  box-shadow: var(--shadow-hover); overflow: hidden; max-height: 38vh; overflow-y: auto;
}
.taginput-item {
  display: flex; align-items: center; width: 100%; border: none; background: none;
  cursor: pointer; text-align: left; padding: 10px 13px; font: inherit; font-size: 14.5px; min-height: 42px;
}
.taginput-item:hover, .taginput-item.on { background: var(--violet-soft); }
.taginput-item + .taginput-item { border-top: 1px solid var(--line-soft); }

/* Tuiles en grille plutot qu'en liste : a quatre entrees, une colonne
   unique obligeait a descendre pour voir la derniere, alors que le choix
   se fait d'un coup d'oeil. « auto-fit » avec un minimum de 170 px donne
   deux colonnes des qu'il y a la place et retombe seul a une colonne sur
   les feuilles etroites — pas de point de rupture a maintenir.

   La tuile passe en colonne : l'emoji au-dessus du texte, comme la
   maquette. En rangee, deux colonnes de 150 px auraient serre les
   sous-titres a deux mots par ligne. */
.choix-type { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 10px; }
.choix-type button {
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
  width: 100%; cursor: pointer;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 14px;
  padding: 16px 18px; font: inherit; text-align: left; transition: all 0.15s;
}
.choix-type button:hover { border-color: var(--violet); background: var(--violet-soft); }
.choix-emoji { font-size: 26px; line-height: 1; flex-shrink: 0; }
.choix-txt { display: flex; flex-direction: column; gap: 2px; }
.choix-txt b { font-size: 16px; color: var(--ink); }
.choix-txt small { font-size: 13px; color: var(--muted); }
.idee-emoji { font-size: 15px; line-height: 1; }
.idee-src { color: var(--violet); text-decoration: none; }
.idee-src:hover { text-decoration: underline; }

/* État « traduit » visible sans ouvrir le menu. */
.badge-traduit {
  font-size: 11px; font-weight: 700; color: var(--vert); background: var(--vert-soft);
  border-radius: 999px; padding: 1px 7px; margin-left: 6px; white-space: nowrap;
}
/* Dans le menu : la coche dit l'état, le sous-titre dit l'action. */
.menu button.menu-traduit { color: var(--vert); align-items: flex-start; }
.menu .menu-2l { display: flex; flex-direction: column; gap: 1px; line-height: 1.25; }
.menu .menu-2l b { font-weight: 700; }
.menu .menu-2l small { font-size: 11.5px; color: var(--muted); font-weight: 500; }

.inspi-photo {
  width: 100%; border-radius: 12px; margin: 10px 0 4px; display: block;
  border: 1.5px solid var(--line); object-fit: cover; max-height: 260px;
}
.inspi-apercu {
  width: 100%; border-radius: 12px; display: block; border: 1.5px solid var(--line);
  object-fit: cover; max-height: 240px;
}
/* Le verrou : ce qu'on voit à la place d'un bouton qu'on n'a pas le droit
   d'utiliser. Il dit pourquoi, et où aller — un mur sans porte n'est pas
   une offre, c'est une impasse. */
.photo-verrou {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
  background: var(--violet-soft);
  border: 1px solid var(--line);
  border-radius: 12px; padding: 12px 14px;
}
.photo-verrou-txt { font-size: 13.5px; color: var(--ink); }
.photo-verrou-lien {
  border: none; background: none; cursor: pointer;
  font-family: inherit; font-size: 13.5px; font-weight: 700;
  color: var(--violet); padding: 2px 0;
  text-decoration: underline; text-underline-offset: 2px;
}
.photo-verrou-lien:active { opacity: 0.6; }

.astuce-collage { font-size: 12.5px; color: var(--muted); text-align: center; margin: 8px 0 0; }
.astuce-collage kbd {
  font: inherit; font-size: 11.5px; font-weight: 700; background: var(--line-soft);
  border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; color: var(--muted);
}

.inspi-vide {
  width: 100%; min-height: 90px; border-radius: 12px; margin: 10px 0 4px;
  border: 1.5px dashed var(--line); display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 13.5px; background: var(--paper);
}
.cache-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cache-poids { font-size: 14px; color: var(--muted); }
.cache-row .btn { flex: 0 0 auto; min-height: 40px; padding: 9px 16px; font-size: 14px; }

/* ---------- Profil : sections repliables ---------- */
.sect { border: 1.5px solid var(--line); border-radius: 14px; background: var(--card); margin-bottom: 8px; overflow: hidden; }
.sect-ouverte { border-color: var(--violet); }
.sect-tete {
  display: flex; align-items: center; gap: 12px; width: 100%; cursor: pointer;
  border: none; background: none; font: inherit; text-align: left; padding: 14px 15px; min-height: 56px;
}
.sect-tete:hover { background: var(--paper); }
.sect-icone { font-size: 19px; line-height: 1; flex-shrink: 0; }
.sect-txt { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.sect-txt b { font-size: 15px; color: var(--ink); }
.sect-txt small { font-size: 12.5px; color: var(--muted); }
.sect-chevron { color: var(--faint); flex-shrink: 0; }
.sect-corps { padding: 2px 15px 15px; border-top: 1px solid var(--line-soft); }
.deconnexion { width: 100%; margin-top: 14px; }

.vide-recherche { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.vide-recherche p { margin: 0; }
.vide-sous { font-size: 13.5px; color: var(--muted); margin-top: 6px !important; }
.vide-liens { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 4px; }
.vide-liens .chip { cursor: pointer; }

.scope-row {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 8px 4px 0; font-size: 13px; color: var(--muted); font-weight: 600;
}
.scope-chip {
  font: inherit; font-size: 13px; font-weight: 700; cursor: pointer;
  padding: 5px 12px; border-radius: 999px; border: 1.5px solid var(--line);
  background: var(--card); color: var(--muted); transition: all 0.15s;
}
.scope-chip.on { background: var(--violet-soft); border-color: var(--violet); color: var(--violet); }

.enrich-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  background: var(--violet-soft); border: 1.5px solid var(--violet);
  border-radius: 12px; padding: 10px 13px; margin-bottom: 14px;
  font-size: 14px; color: var(--ink);
}
.enrich-bar b { color: var(--violet); }

/* Bannière de mise à jour : visible sans être bloquante. Le contenu reste
   accessible, l'utilisateur choisit son moment. */
.maj-bar {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
  background: var(--violet-soft); border: 1.5px solid var(--violet);
  border-radius: 12px; padding: 11px 13px; font-size: 14px; color: var(--ink);
}
.maj-bar span { flex: 1; min-width: 0; }
.maj-bar b { color: var(--violet); }
.maj-btn {
  flex: 0 0 auto; border: none; border-radius: 999px; padding: 8px 15px;
  background: var(--grad); color: #fff; font: inherit; font-size: 13.5px; font-weight: 700; cursor: pointer;
}
.maj-close {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; border: none; background: none;
  color: var(--faint); cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center;
}
.maj-close:hover { background: rgba(255,255,255,0.6); color: var(--violet); }

.brand-clic { cursor: pointer; border-radius: 12px; padding: 2px 6px; margin-left: -6px; transition: background .15s; }
.brand-clic:hover { background: rgba(255,255,255,0.5); }
.brand-clic:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.enrich-bar .enrich-close {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  border: none; background: none; color: var(--faint); cursor: pointer;
  font-size: 14px; line-height: 1; display: flex; align-items: center; justify-content: center;
}
.enrich-bar .enrich-close:hover { background: var(--violet-soft); color: var(--violet); }
.enrich-bar button {
  border: none; border-radius: 9px; padding: 8px 15px; background: var(--violet);
  color: #fff; font-weight: 700; font-size: 13.5px; cursor: pointer; flex-shrink: 0;
}

.confirm-overlay {
  position: fixed; inset: 0; z-index: 80; background: rgba(46,42,61,0.45);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.confirm-box {
  background: var(--violet); border-radius: 16px; padding: 20px;
  max-width: 340px; width: 100%; box-shadow: 0 14px 40px rgba(46,42,61,0.3);
  animation: bannerUp .2s ease;
}
/* pre-wrap : les dialogues qui listent des changements passent à la ligne.
   Sans lui, les \n s'écrasent en espaces et les puces se collent en pavé.
   Sans effet sur les autres dialogues : ils n'ont pas de retour à la ligne. */
.confirm-box p { margin: 0 0 16px; font-size: 15.5px; font-weight: 600; line-height: 1.5; color: var(--paper); white-space: pre-wrap; }
.btn-danger { background: var(--magenta); color: #fff; }

.labels-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.labels-grid > div { display: flex; flex-direction: column; gap: 7px; }
.labels-titre { font-size: 12px; color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.labels-grid input {
  width: 100%; background: var(--card); border: 1.5px solid var(--line); border-radius: 9px;
  color: var(--ink); font-size: 14px; padding: 9px 11px; outline: none;
}
.labels-grid input:focus { border-color: var(--violet); }

.suggest-box {
  position: absolute; top: calc(100% + 6px); left: 10px; right: 10px; z-index: 30;
  background: var(--card); border: 1.5px solid var(--line); border-radius: 14px;
  box-shadow: var(--shadow-hover); overflow: hidden; max-height: 46vh; overflow-y: auto;
}
.suggest-item {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; border: none; background: none; cursor: pointer; text-align: left;
  padding: 11px 14px; font: inherit; font-size: 14.5px; min-height: 44px;
}
.suggest-item:hover { background: var(--line-soft); }
.suggest-item + .suggest-item { border-top: 1px solid var(--line-soft); }
.suggest-tag { color: var(--violet); font-weight: 700; }
.suggest-n {
  font-size: 12.5px; font-weight: 700; color: var(--muted);
  background: var(--line-soft); border-radius: 999px; padding: 2px 9px; flex-shrink: 0;
}
.suggest-titre { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.tag-liste { max-height: 52vh; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; }
.tag-liste button {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1.5px solid var(--line); background: var(--card); border-radius: 11px;
  padding: 11px 13px; font: inherit; font-size: 14.5px; cursor: pointer; min-height: 44px;
}
.tag-liste button:hover { border-color: var(--violet); }
.tag-liste button.tri-actif { border-color: var(--violet); background: var(--violet-soft); color: var(--violet); font-weight: 700; }
.chip-tri { display: flex; align-items: center; }

.btn-lien {
  background: none; border: none; padding: 0; font: inherit; font-size: inherit;
  color: var(--violet); font-weight: 700; text-decoration: underline; cursor: pointer;
}

.profile-card { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.profile-avatar {
  width: 46px; height: 46px; border-radius: 50%; flex-shrink: 0; background: var(--grad); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: 'Bricolage Grotesque', sans-serif; font-weight: 700; font-size: 18px;
}
.profile-name { font-weight: 700; font-size: 15px; overflow-wrap: anywhere; }
.profile-sub { font-size: 13px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.profile-actions { display: flex; flex-direction: column; gap: 9px; }
.profile-actions .btn { width: 100%; display: flex; align-items: center; justify-content: center; gap: 8px; }
.sync-note { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin: 0 0 14px; }

/* ---------- 11. Connexion ---------- */
.login-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: #faf8fc; color: #2e2a3d;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  overflow: hidden;
}
.login-overlay::before, .login-overlay::after {
  content: ''; position: absolute; border-radius: 50%; filter: blur(80px); opacity: 0.4; pointer-events: none;
}
.login-overlay::before { width: 380px; height: 380px; background: #5588d6; top: -120px; left: -100px; }
.login-overlay::after { width: 360px; height: 360px; background: #cc4f96; bottom: -120px; right: -100px; }
.login-glow {
  position: absolute; width: 320px; height: 320px; border-radius: 50%;
  background: #8862c9; filter: blur(90px); opacity: 0.28; pointer-events: none;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
}
.login-card {
  position: relative; z-index: 1; width: 100%; max-width: 380px;
  background: #ffffff; border: 1.5px solid #e8e2f2; border-radius: 22px;
  padding: 38px 34px 34px; box-shadow: 0 12px 40px rgba(46, 42, 61, 0.12);
  overflow: hidden;
}
.login-card::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px;
  background: linear-gradient(135deg, #5588d6, #8862c9 50%, #cc4f96);
}
.login-mark { width: 64px; height: 64px; border-radius: 17px; margin: 0 auto 16px; display: block; box-shadow: 0 6px 18px rgba(140, 90, 180, 0.28); }
.login-card h1 { font-size: 27px; font-weight: 800; text-align: center; margin: 0 0 4px; color: #2e2a3d; }
.login-sub { text-align: center; color: var(--muted); font-size: 14px; margin: 0 0 26px; }
.login-field { margin-bottom: 13px; }
.login-field label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 6px; font-weight: 700; }
.login-field input {
  width: 100%; background: #faf8fc; border: 1.5px solid #e8e2f2; border-radius: 11px;
  color: #2e2a3d; font-size: 16px; padding: 12px 13px; outline: none; box-sizing: border-box;
  transition: border-color 0.15s, background 0.15s;
}
.login-field input:focus { border-color: #8862c9; background: #ffffff; }

/* ── Champ de mot de passe avec affichage ──────────────────────────
   L'œil se pose PAR-DESSUS le champ, d'où le conteneur en position
   relative. Le « transform » de centrage est sur le bouton lui-même et
   non sur l'enveloppe : un transform posé sur un ancêtre redéfinit le
   repère de tout descendant en position fixe, ce qui avait coûté cher
   le 13 août avec le menu de statut. Ici rien de flottant n'y vit,
   mais la règle se tient partout ou nulle part.

   La réserve à droite (44px) empêche le texte saisi de passer sous
   l'icône : un mot de passe long viendrait sinon s'y cacher au moment
   précis où on cherche à le relire.

   Couleur : --muted et non --faint. L'œil est une commande, il doit se
   voir ; --faint est réservé aux bordures et aux traits décoratifs. */
.mdp-wrap { position: relative; }

.mdp-wrap input { padding-right: 44px; }

.mdp-oeil {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  background: none; border: none; border-radius: 9px;
  color: var(--muted); cursor: pointer;
  transition: color 0.15s, background 0.15s;
}

.mdp-oeil:hover { color: var(--ink); background: rgba(136, 98, 201, 0.09); }

/* Le contour de focus reste visible : c'est le seul repère de la
   personne qui navigue au clavier. */
.mdp-oeil:focus-visible { outline: 2px solid #8862c9; outline-offset: 1px; color: var(--ink); }

/* Chrome et Edge ajoutent leur propre œil sur les champs de mot de
   passe. Sans ça, deux icônes se chevaucheraient. */
.mdp-wrap input::-ms-reveal,
.mdp-wrap input::-ms-clear { display: none; }

/* ── Navigation entre vidéos du lecteur ────────────────────────────
   Flèches, compteur « 3 / 238 » et enchaînement automatique. La barre
   se replie sur téléphone : trois éléments plus un libellé ne tiennent
   pas sur une ligne étroite, et rien ne doit sortir de l'écran. */
/* La note qui vient d'être ajoutée. Le tri chronologique la place à
   son instant, parfois loin de là où on regardait : un bref halo dit
   où elle est allée. Il s'efface tout seul — un marqueur permanent
   deviendrait du bruit dès la note suivante. */
.note-arrivee {
  animation: note-arrivee 1.6s ease-out;
}

@keyframes note-arrivee {
  0%   { background: #ede4ff; box-shadow: 0 0 0 2px #8862c9 inset; }
  70%  { background: #f6f1ff; box-shadow: 0 0 0 2px rgba(136, 98, 201, 0.35) inset; }
  100% { background: transparent; box-shadow: 0 0 0 2px transparent inset; }
}

/* Sans animation : le halo reste, en plus discret, puis disparaît avec
   la classe. On ne prive pas du repère ceux qui coupent les
   animations — c'est justement eux qui en ont le plus besoin. */
@media (prefers-reduced-motion: reduce) {
  .note-arrivee { animation: none; background: #f6f1ff; }
}

.lecteur-nav {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  /* Au-dessus de la vidéo : la marge passe donc en dessous. Changer de
     vidéo et lancer l'enchaînement sont des gestes qu'on veut sous la
     main avant de regarder, pas à chercher après. */
  margin-bottom: 10px;
}

.lecteur-nav-fleche {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  background: #faf8fc; border: 1.5px solid var(--line); border-radius: 11px;
  color: var(--ink); cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.lecteur-nav-fleche:hover:not(:disabled) { background: #fff; border-color: #8862c9; color: #8862c9; }

/* Éteinte, pas invisible : voir qu'on est au bout de la liste est une
   information. Le curseur par défaut évite de promettre un clic. */
.lecteur-nav-fleche:disabled { opacity: 0.4; cursor: default; }

.lecteur-nav-fleche:focus-visible { outline: 2px solid #8862c9; outline-offset: 1px; }

/* Chiffres tabulaires : sans ça, passer de « 9 / 238 » à « 10 / 238 »
   décale toute la barre, et les flèches sautent sous le doigt. */
.lecteur-nav-compte {
  font-size: 14px; font-weight: 700; color: var(--muted);
  min-width: 64px; text-align: center;
  font-variant-numeric: tabular-nums;
}

.lecteur-nav-auto {
  display: flex; align-items: center; gap: 7px;
  margin-left: auto; padding: 9px 13px;
  background: #faf8fc; border: 1.5px solid var(--line); border-radius: 11px;
  color: var(--muted); font-size: 13px; font-weight: 700; cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.lecteur-nav-auto:hover { border-color: #8862c9; }

/* Actif : le violet plein, comme les autres états retenus de l'app. Le
   témoin ne porte pas l'information à lui seul — le libellé reste lu
   par les lecteurs d'écran via aria-pressed. */
.lecteur-nav-auto.actif { background: #f3edff; border-color: #8862c9; color: #6a3fb5; }

.lecteur-nav-auto:focus-visible { outline: 2px solid #8862c9; outline-offset: 1px; }

.lecteur-nav-temoin {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--faint); flex: 0 0 auto;
  transition: background 0.15s;
}

.lecteur-nav-auto.actif .lecteur-nav-temoin { background: #8862c9; }

@media (max-width: 480px) {
  /* Le bouton passe à la ligne et s'étale : au pouce, une cible pleine
     largeur vaut mieux qu'un bouton serré dans un coin. */
  .lecteur-nav-auto { margin-left: 0; width: 100%; justify-content: center; }
}
.login-error { color: var(--magenta-text); font-size: 13.5px; margin: 0 0 12px; }
.login-submit {
  width: 100%; border: none; border-radius: 12px; padding: 14px; font-size: 16px; font-weight: 700;
  background: linear-gradient(135deg, #5588d6, #8862c9 50%, #cc4f96); color: #fff; cursor: pointer; margin-top: 6px;
  box-shadow: 0 6px 18px rgba(140, 90, 180, 0.3);
}
.login-submit:disabled { opacity: 0.6; }

/* Champ piège : hors écran, invisible, non focalisable. Un humain ne le voit
   jamais ; un robot qui remplit tous les champs se trahit. */
.piege { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.turnstile { display: flex; justify-content: center; margin: 12px 0 4px; min-height: 0; }
.login-bascule { text-align: center; font-size: 14px; color: var(--muted); margin: 16px 0 0; }
.login-ok { text-align: center; }
.login-ok p { font-size: 14.5px; color: var(--ink); line-height: 1.55; margin: 0 0 12px; }

/* ---------- 12. Bannière d'installation (bas d'écran) ---------- */
.install-banner {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(14px + env(safe-area-inset-bottom));
  width: calc(100% - 24px); max-width: 560px; z-index: 60;
  background: var(--card); border: 1.5px solid var(--line);
  border-radius: 16px; padding: 14px 15px;
  box-shadow: 0 10px 32px rgba(46,42,61,0.22);
  font-size: 14.5px; line-height: 1.55;
  animation: bannerUp .3s ease;
}
@keyframes bannerUp { from { transform: translate(-50%, 30px); opacity: 0; } to { transform: translate(-50%, 0); opacity: 1; } }
.install-banner .ib-head { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; padding-right: 30px; }
.install-banner .ib-icon {
  width: 38px; height: 38px; border-radius: 10px; background: var(--grad);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: #fff;
}
.install-banner .ib-title { font-weight: 700; font-family: 'Bricolage Grotesque', sans-serif; font-size: 15.5px; }
.install-banner .ib-close {
  position: absolute; top: 8px; right: 8px; width: 34px; height: 34px;
  border: none; background: transparent; color: var(--faint); cursor: pointer;
  border-radius: 8px; font-size: 16px;
}
.install-banner .ib-close:hover { background: var(--line-soft); color: var(--ink); }
.install-banner .ib-steps { margin: 4px 0 0; padding-left: 0; list-style: none; color: var(--muted); }
.install-banner .ib-steps li { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
.install-banner .ib-key {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 26px; border-radius: 7px; background: var(--blue-soft);
  color: var(--blue); font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.install-banner .ib-install-btn {
  margin-top: 10px; width: 100%; border: none; border-radius: 10px; padding: 12px 16px;
  background: var(--grad); color: #fff; font-weight: 700; font-size: 15px; cursor: pointer;
}

/* ---------- 13. Bouton flottant ---------- */
.fab {
  position: fixed; right: 18px; bottom: calc(20px + env(safe-area-inset-bottom)); z-index: 30;
  border: none; border-radius: 999px; padding: 15px 22px;
  background: var(--grad); color: #fff; cursor: pointer;
  font-size: 15.5px; font-weight: 700; font-family: inherit;
  display: flex; align-items: center; gap: 8px;
  box-shadow: 0 8px 22px rgba(140, 90, 180, 0.4);
}
.fab:active { transform: scale(0.96); }
.fab { transition: transform .22s ease, opacity .22s ease; }
/* Le bouton s'efface quand on descend, revient dès qu'on remonte : il ne
   masque plus le contenu qu'on est en train de lire. */
.fab.fab-cache { transform: translateY(120%); opacity: 0; pointer-events: none; }

@media (max-width: 640px) {
  /* Sur petit écran, le libellé est retiré : un rond compact chevauche
     beaucoup moins les cartes. */
  .fab { padding: 0; width: 56px; height: 56px; justify-content: center; border-radius: 50%; }
  .fab .fab-label { display: none; }
}

/* ---------- 14. Divers ---------- */
/* Notifications : contraste fort, texte plus grand et durée d'affichage
   prolongée. Les teintes restent celles de l'application. */
.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(96px + env(safe-area-inset-bottom)); z-index: 90;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  width: max-content; min-width: min(360px, calc(100vw - 32px));
  max-width: min(560px, calc(100vw - 32px));
  background: linear-gradient(135deg, var(--ink), #4a3f63);
  color: #fff; border: 2px solid var(--violet); border-radius: 16px;
  padding: 14px 20px; font-size: 16px; font-weight: 700; line-height: 1.35;
  box-shadow: 0 16px 42px rgba(46,42,61,.34), 0 0 0 4px rgba(136,98,201,.12);
  animation: toastEntree .28s cubic-bezier(.2,.8,.2,1);
  text-align: left;
}
.toast span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.toast svg {
  width: 24px; height: 24px; flex-shrink: 0;
  border-radius: 50%; padding: 4px; color: #fff;
  background: rgba(255,255,255,.16);
}
.toast-succes {
  background: linear-gradient(135deg, var(--ink), #276f65);
  border-color: var(--vert);
  box-shadow: 0 16px 42px rgba(46,42,61,.34), 0 0 0 4px rgba(63,157,143,.16);
}
.toast-succes svg { color: #fff; background: var(--vert); }
.toast-neutre {
  background: linear-gradient(135deg, var(--ink), #514071);
  border-color: var(--violet);
}
.toast-erreur {
  background: linear-gradient(135deg, var(--ink), #79365d);
  border-color: var(--magenta);
}
@keyframes toastEntree {
  from { transform: translate(-50%, 18px) scale(.97); opacity: 0; }
  to   { transform: translate(-50%, 0) scale(1); opacity: 1; }
}
@media (max-width: 640px) {
  .toast {
    bottom: calc(82px + env(safe-area-inset-bottom));
    min-width: 0; width: calc(100vw - 24px); max-width: calc(100vw - 24px);
    border-radius: 14px; padding: 13px 16px; font-size: 15.5px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* ---- 21 · offres (plans) et exceptions, dans l'administration ---------- */
.plans-grille { display: grid; grid-template-columns: 1fr 88px 88px 88px; gap: 6px 8px; align-items: center; margin-top: 9px; }

/* Sur téléphone, les trois colonnes de 88 px ne rentrent pas.

   88 px est une largeur FIXE : la piste ne rétrécit pas quand la place
   manque. Il fallait 358 px au tableau, la feuille en offrait 306 — les
   52 px de trop ne disparaissaient pas, ils sortaient par la droite,
   emportant la colonne « Traductions/jour » hors de l'écran et
   déclenchant un défilement horizontal sur toute la page.

   En dessous de 680 px, la feuille occupe toute la largeur : les trois
   colonnes passent en fractions, elles se partagent ce qui reste après
   le nom de l'offre au lieu de réclamer une taille que personne ne peut
   leur donner. Au-dessus, la feuille est large et les 88 px tiennent :
   la règle d'origine reste intacte. */
@media (max-width: 679px) {
  .plans-grille {
    grid-template-columns: auto repeat(3, minmax(0, 1fr));
    gap: 6px;
  }

  /* « Traductions/jour » est un seul mot pour le moteur de rendu : sans
     point de coupe autorisé, il déborderait de sa cellule au lieu de
     passer à la ligne. */
  .plans-entete {
    font-size: 10px;
    line-height: 1.25;
    overflow-wrap: anywhere;
  }

  .plans-champ {
    padding: 8px 5px;
    font-size: 14px;
  }
}
.plans-entete { font-size: 11px; color: var(--muted); font-weight: 700; text-align: center; letter-spacing: .02em; }
.plans-nom { font-size: 14px; color: var(--ink); font-weight: 600; }
.plans-champ, .except-grille input {
  width: 100%; border: 1.5px solid var(--line); border-radius: 9px; padding: 8px 9px;
  font: inherit; font-size: 15px; color: var(--ink); background: var(--card);
  box-sizing: border-box; outline: none; text-align: center;
}
.plans-champ:focus, .except-grille input:focus { border-color: var(--violet); }

.admin-user-row { flex-direction: column; align-items: stretch; }
.admin-user-tete { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-width: 0; }
.admin-user-tete .admin-user-info { flex-wrap: wrap; }

.admin-plans { display: flex; gap: 6px; margin-top: 10px; }
.plan-chip {
  flex: 1; border: 1.5px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: 999px; padding: 6px 4px; font: inherit; font-size: 12.5px; font-weight: 700;
  cursor: pointer; transition: background .15s, border-color .15s, color .15s;
}
.plan-chip.on { border-color: var(--violet); background: var(--violet-soft); color: var(--violet); }
.plan-chip:disabled { opacity: .5; cursor: default; }

.admin-conso-ligne { display: flex; align-items: center; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.admin-except-btn {
  margin-left: auto; border: 1.5px solid var(--line); background: var(--card); color: var(--muted);
  border-radius: 999px; padding: 3px 11px; font: inherit; font-size: 11.5px; font-weight: 700; cursor: pointer;
}
.admin-except-btn.on { border-color: var(--magenta); color: var(--magenta); background: var(--magenta-soft); }
.admin-exception {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--magenta); background: var(--magenta-soft); border-radius: 999px; padding: 2px 8px; flex-shrink: 0;
}
.admin-exception-bloc { margin-top: 11px; padding-top: 11px; border-top: 1.5px dashed var(--line); }
.except-grille { display: grid; grid-template-columns: 1fr 116px; gap: 8px; align-items: center; }
.except-grille label { font-size: 13.5px; color: var(--muted); font-weight: 600; }
.admin-exception-bloc .sheet-actions { margin-top: 11px; }

/* ---- 22 · écusson d'offre, dans la barre du haut et le profil -------- */
/* ---- l'écusson d'offre ------------------------------------------------
   Une silhouette et son étoile. Contour seul dans la barre du haut, où il
   doit pouvoir être ignoré ; pastille pleine dans le profil, où c'est
   l'information qu'on est venu chercher. */
.plan-ecusson {
  --teinte: var(--faint);
  display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0;
  border: 1.5px solid transparent; border-radius: 999px;
  padding: 4px 12px 4px 9px; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 700; letter-spacing: .015em;
  line-height: 1; white-space: nowrap;
  background: var(--teinte); color: #fff;
  transition: transform .12s ease, box-shadow .15s ease;
}
.plan-ecusson.p-payant    { --teinte: var(--violet); }
.plan-ecusson.p-fondateur { --teinte: #c8901a; background: linear-gradient(135deg, #e0a92c, #c07d10); }
.profile-sub .plan-ecusson { cursor: default; margin-left: 2px; padding: 5px 13px 5px 10px; }
.profile-sub .plan-ecusson.p-gratuit { background: var(--line-soft); color: var(--muted); }

/* Barre du haut : contour seul, couleur portée par le texte et l'étoile. */
.topbar-right .plan-ecusson {
  background: transparent; border-color: var(--teinte); color: var(--teinte);
  padding: 0 13px 0 4px; height: 40px; font-size: 11.5px; gap: 8px;
}

/* L'avatar et sa marque. --halo est la couleur de la pastille qui porte
   les étoiles : celle du fond de l'écusson, pas un blanc arbitraire. */
.av-plan { --halo: #fff; position: relative; display: inline-flex; flex-shrink: 0; }
.av-disc {
  border-radius: 50%; background: var(--grad); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-family: 'Bricolage Grotesque', sans-serif; font-weight: 700; line-height: 1;
}
.av-lettre { transform: translate(-2px, -2px); display: block; }
.av-lettre-centree { transform: none; }
.av-marque {
  position: absolute; right: -3px; bottom: -3px; pointer-events: none;
  filter: drop-shadow(0 1px 1.5px rgba(40, 20, 70, .18));
}
.topbar-right .plan-ecusson.p-gratuit { --teinte: var(--faint); border-color: var(--line); }
.topbar-right .plan-ecusson:hover { transform: translateY(-1px); }
.topbar-right .plan-ecusson.p-gratuit:hover { border-color: var(--faint); }
/* L'avatar est descendu dans la barre du bas : celui du haut fait doublon.

   On le retire là où la barre est fixée en bas, sous le pouce — c'est-à-dire
   sous 720px, le même seuil qu'elle. Deux portes vers la même pièce, côte à
   côte, ne servent personne ; et sur ta capture, l'écusson passait à la ligne
   sous le titre et volait une rangée entière à l'écran qui en a le moins.

   Au-dessus de 720px il reste : la barre y flotte en pastille, sans place
   pour le mot « Fondateur ». L'écusson nommé garde donc sa place là où il y
   a la place de le nommer. La pastille de synchronisation reste partout —
   elle informe, elle n'emmène nulle part. */
@media (max-width: 719px) {
  .topbar-right .plan-ecusson,
  .topbar-right .avatar { display: none; }
}
@media (max-width: 380px) {
  .topbar-right .plan-ecusson .plan-nom { display: none; }
  .topbar-right .plan-ecusson { width: 42px; padding: 0; justify-content: center; gap: 0; }
}
/* ---- 23 · pastille de compte sur les chips de vue --------------------- */
.vue-n {
  font-size: 11px; font-weight: 700; color: var(--muted);
  background: var(--line-soft); border-radius: 999px; padding: 1px 7px;
  margin-left: 6px; flex-shrink: 0;
}
.vue-chip.active .vue-n { background: rgba(255,255,255,.28); color: inherit; }
.vue-n.vide { color: var(--magenta); background: var(--magenta-soft); }
/* ---- 24 · sélection multiple ------------------------------------------ */
.sel-wrap { position: relative; cursor: pointer; }
.sel-wrap > article { transition: box-shadow .12s, opacity .12s; }
.sel-wrap:not(.sel-on) > article { opacity: .82; }
.sel-wrap.sel-on > article { box-shadow: 0 0 0 2.5px var(--violet); }
.sel-coche {
  position: absolute; top: 10px; right: 10px; z-index: 5;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; font-weight: 800; pointer-events: none;
  background: #fff; color: var(--faint);
  box-shadow: 0 1px 5px rgba(40,20,70,.22); border: 1.5px solid var(--line);
}
.sel-wrap.sel-on .sel-coche { background: var(--violet); color: #fff; border-color: var(--violet); }

.sel-bar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  z-index: 60; display: flex; align-items: center; gap: 9px;
  background: var(--ink); border: 1.5px solid var(--line); border-radius: 999px;
  padding: 9px 12px 9px 16px; box-shadow: 0 8px 28px rgba(40,20,70,.18);
  /* width: max-content — la ligne qui manquait.
     Un élément « fixed » avec left:50% et aucune largeur ne dispose, pour se
     dimensionner, que de l'espace allant de 50% à 100% : la MOITIÉ de l'écran.
     Le translateX(-50%) le recentre à l'œil, mais ne lui rend pas la place.
     La barre se croyait donc à l'étroit dans 215px sur un écran de 430, et
     s'empilait sur six rangées. Avec max-content elle réclame sa largeur
     naturelle, que max-width ramène ensuite à la taille de l'écran — et
     flex-wrap ne replie que ce qui dépasse vraiment.
     Mesuré à 320px : 369px de haut avant, 168px après. */
  width: max-content;
  max-width: calc(100vw - 24px); flex-wrap: wrap; justify-content: center;
}
.sel-bar b { font-size: 14px; white-space: nowrap; color: var(--paper) }
.sel-bar .btn { padding: 8px 14px; font-size: 13.5px; }

/* ---- 25 · détails des liens non enrichis ------------------------------ */
.enrich-bar .enrich-message {
  flex: 1 1 360px; min-width: 0; display: flex; align-items: center;
  justify-content: space-between; gap: 14px; padding: 2px 4px;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink);
  font: inherit; font-size: 14px; font-weight: 500; text-align: left; cursor: pointer;
}
.enrich-bar .enrich-message:hover,
.enrich-bar .enrich-message:focus-visible {
  background: rgba(255,255,255,.48); outline: none;
}
.enrich-voir {
  flex-shrink: 0; color: var(--violet); font-weight: 800; white-space: nowrap;
}
.enrich-fail-list {
  display: flex; flex-direction: column; gap: 9px; max-height: 52vh;
  overflow-y: auto; padding: 2px;
}
.enrich-fail-item {
  display: flex; flex-direction: column; gap: 10px; padding: 12px 13px;
  border: 1.5px solid var(--line); border-radius: 12px;
  background: var(--card);
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.enrich-fail-lien {
  display: grid; grid-template-columns: minmax(0,1fr) minmax(150px,1.2fr) 24px;
  align-items: center; gap: 12px; color: var(--ink); text-decoration: none;
}
.enrich-fail-valider { align-self: flex-end; padding: 7px 13px; font-size: 13.5px; min-height: 0; }
.enrich-fail-item:hover {
  border-color: var(--violet); box-shadow: var(--shadow-hover); transform: translateY(-1px);
}
.enrich-fail-main { display: flex; flex-direction: column; min-width: 0; gap: 2px; }
.enrich-fail-main b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.enrich-fail-main small { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.enrich-fail-reason { font-size: 13px; line-height: 1.35; color: var(--muted); }
.enrich-fail-open { color: var(--violet); font-size: 20px; font-weight: 800; text-align: center; }
@media (max-width: 680px) {
  .enrich-bar { flex-wrap: wrap; }
  .enrich-bar .enrich-message { flex-basis: 100%; align-items: flex-start; }
  .enrich-fail-lien { grid-template-columns: minmax(0,1fr) 24px; }
  .enrich-fail-reason { grid-column: 1 / -1; grid-row: 2; }
  .enrich-fail-open { grid-column: 2; grid-row: 1; }
}


/* ── Filtres unifiés : pill active « libellé × » et menu Statut ─────────────
   La barre n'affiche plus la rangée de statuts : un bouton « Statut ▾ » ouvre
   un menu, et le filtre choisi revient dans la barre sous forme de pill.
   Le libellé rouvre le menu, le ✕ retire le filtre. Mêmes couleurs que les
   anciennes chips : le repère visuel des statuts est conservé. */
.chip-actif { display: inline-flex; align-items: stretch; padding: 0; }
.chip-actif .chip-lbl, .chip-actif .chip-x {
  background: none; border: none; color: inherit; font: inherit;
  font-weight: 700; cursor: pointer; display: flex; align-items: center;
}
.chip-actif .chip-lbl { padding: 0 4px 0 14px; }
.chip-actif .chip-x { padding: 0 12px 0 7px; opacity: .75; font-size: 13px; }
.chip-actif .chip-x:hover { opacity: 1; }

.statut-liste button { position: relative; }
.statut-lbl { display: flex; align-items: center; gap: 9px; min-width: 0; }
.statut-droite { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.statut-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.dot-todo { background: var(--blue); }
.dot-done { background: var(--violet); }
.dot-exploit { background: var(--vert); }
.dot-keep { background: var(--magenta); }
/* Archives : un état à part, séparé du cycle de vie par un filet */
.statut-liste .statut-archives { margin-top: 12px; }
.statut-liste .statut-archives::before {
  content: ""; position: absolute; top: -7px; left: 6px; right: 6px;
  height: 1px; background: var(--line);
}

/* Intitulé du groupe de tags hors contexte dans « Filtrer par tag ».
   Un seul espace de nommage : ces tags restent sélectionnables ici, l'état
   vide de l'onglet guidera ensuite vers là où ils vivent. */
.tags-ailleurs {
  margin: 10px 2px 2px; font-size: 12.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}


/* ---------- V2 — Texte d'accueil et barre de navigation basse ---------- */


/* La barre du bas EST le filtre par type : chaque appui change vraiment le
   contenu affiché. Elle remplace la rangée d'onglets du haut, dont seuls
   trois tenaient sur un écran de téléphone — les Idées et les Inspirations
   étaient cachées derrière un défilement horizontal. */
.tabbar-v2 {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  height: var(--tb-haut);
  padding-bottom: var(--tb-bas);
  background: rgba(255,255,255,0.93);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border-top: 1px solid var(--line);
  display: flex;
}
.tabbar-v2 .tb-item {
  flex: 1 1 0; min-width: 0; padding: 0 2px;
  border: none; background: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-family: inherit;
  /* --faint (#a59cb3) donnait un contraste de 2,62 sur le fond blanc de la
     barre. Le seuil WCAG AA pour du texte de cette taille est 4,5 : on en
     était à moins de la moitié. --muted (#6b6478) monte à 5,65.

     Ce n'était pas un choix, c'était un oubli — les libellés font 10 à
     11,5 px, la taille où le contraste compte le plus, et c'est justement
     là qu'on avait pris la couleur la plus pâle de la palette. */
  color: var(--muted);
  transition: color 0.15s ease;
}
.tabbar-v2 .tb-item.active { color: var(--violet); }
.tabbar-v2 .tb-item:active { transform: scale(0.93); }

/* Le Profil n'est pas un filtre.

   Les quatre premiers boutons disent « montre-moi ça », celui-ci dit
   « emmène-moi ailleurs ». Un filet le sépare : sans lui, l'œil lirait cinq
   onglets de même nature et s'attendrait à ce que le cinquième filtre
   quelque chose.

   Il ne prend pas une part entière : un avatar se lit à sa forme, pas à sa
   largeur, et les quatre filtres méritent la place. */
.tabbar-v2 .tb-profil {
  /* Une part entière, comme les quatre autres.

     Avec « flex: 0 0 auto » il n'occupait que 60px pendant que les onglets
     s'étalaient à 83px chacun : quatre boutons largement espacés, puis un
     cinquième serré contre le bord droit. Le déséquilibre se voyait.

     Je pensais qu'un avatar se lit à sa forme et non à sa largeur, et que
     les filtres méritaient la place. C'est vrai de l'avatar, faux de la
     rangée : cinq parts égales se lisent comme une rangée, quatre parts
     plus un reste se lisent comme un accident. Le filet suffit à dire que
     le cinquième n'est pas de la même nature. */
  flex: 1 1 0;
  position: relative;
}
.tabbar-v2 .tb-profil::before {
  content: ''; position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 1px; height: 26px; background: var(--line);
}
/* L'avatar garde ses couleurs quel que soit l'état du bouton. */
.tabbar-v2 .tb-profil .tb-ico { color: var(--violet); }

/* Sans plan, pas d'écusson : une lettre suffit. */
.tb-avatar-lettre {
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--violet); color: #fff;
  font-size: 12px; font-weight: 700; line-height: 24px;
  text-align: center; display: block;
}

.tabbar-v2 .tb-ico { position: relative; display: flex; }
/* Compteur posé sur l'icône, discret. Absent quand il n'y a rien. */
/* Le compteur était accroché à « left: 100% » — le bord DROIT de l'icône —
   puis ramené de 8 px : il chevauchait l'icône au lieu de la surmonter, et
   son contour de 1 px ajoutait un troisième trait dans un espace qui en
   comptait déjà deux. Ancré au centre de l'icône et décalé vers la droite,
   il se pose dessus sans mordre dedans ; le contour disparaît, la teinte
   suffit à le détacher. */
.tabbar-v2 .tb-count {
  position: absolute; top: -6px; left: calc(50% + 5px);
  background: var(--violet-soft); color: var(--violet);
  font-size: 9px; font-weight: 700; line-height: 1;
  padding: 2px 4px; border-radius: 999px;
  min-width: 14px; text-align: center;
  border: 0;
  /* Un badge qui mord sur l'icône a besoin d'être détaché d'elle, sinon
     les deux dessins se mélangent. Le contour gris d'avant ajoutait un
     trait de plus ; un liseré de la couleur de la barre creuse au lieu
     de dessiner — il sépare sans rien ajouter à lire. */
  box-shadow: 0 0 0 2px rgba(255,255,255,.96);
}
.tabbar-v2 .tb-item.active .tb-count {
  background: var(--violet); color: #fff; border-color: var(--violet);
}
.tabbar-v2 .tb-lab {
  font-size: 10.5px; font-weight: 700; letter-spacing: -0.015em;
  max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Dégager le contenu, le bouton flottant et la bannière au-dessus de la barre.
   Ces règles viennent AVANT la media query : sinon elles l'écraseraient. */
.app { padding-bottom: calc(150px + env(safe-area-inset-bottom)); }
.fab { bottom: calc(84px + env(safe-area-inset-bottom)); }
.install-banner { bottom: calc(88px + env(safe-area-inset-bottom)); z-index: 65; }

/* ══════════════════════════════════════════════════════════════════
   BARRE DES TYPES — version haute, ordinateur
   ══════════════════════════════════════════════════════════════════
   Cachée par défaut : sur téléphone c'est la barre du bas qui sert,
   et le pouce y arrive. Elle n'apparaît qu'au-dessus de 720 px — le
   même seuil que l'avatar de l'en-tête, ce qui garantit qu'un accès
   au profil existe toujours, d'un côté ou de l'autre. */
.types-haut { display: none; }

@media (min-width: 720px) {
  .types-haut {
    display: flex; gap: 4px; margin-bottom: 14px;
    padding: 5px; border-radius: 999px;
    /* Un violet tres dilue plutot qu'une couleur fixe : le fond de la
       page est un degrade, une teinte opaque y ferait une tache. */
    background: rgba(136, 98, 201, 0.08);
  }
  .th-item {
    flex: 1; display: flex; align-items: center; justify-content: center; gap: 8px;
    padding: 9px 14px; min-height: 42px; border: none; background: none;
    border-radius: 999px; cursor: pointer;
    font: inherit; font-size: 14px; font-weight: 600; color: var(--muted);
    transition: background .15s, color .15s;
  }
  .th-item:hover { background: rgba(255, 255, 255, 0.5); color: var(--ink); }
  .th-item.active {
    background: var(--card); color: var(--violet); font-weight: 700;
    box-shadow: 0 1px 3px rgba(46, 42, 61, 0.10);
  }
  .th-count {
    flex: 0 0 auto; min-width: 20px; padding: 1px 6px; border-radius: 999px;
    background: var(--line-soft); color: var(--muted);
    font-size: 11.5px; font-weight: 700; line-height: 1.5;
  }
  .th-item.active .th-count { background: var(--violet-soft); color: var(--violet); }

  /* Une seule barre a la fois : celle du bas disparait, et le rembourrage
     qu'elle reservait avec elle. */
  .tabbar-v2 { display: none; }
  .app { padding-bottom: 40px; }
}

/* Au-dessus de 720px la barre du bas n'existe plus : elle est remplacée
   par .types-haut. Les règles qui en faisaient une pastille flottante ont
   donc disparu, et le bouton flottant redescend — il remontait de 98px
   pour la laisser passer, il n'a plus rien à enjamber. */
@media (min-width: 720px) {
  .fab { bottom: calc(22px + env(safe-area-inset-bottom)); }
  .install-banner { bottom: calc(22px + env(safe-area-inset-bottom)); }
}
}

@media (prefers-reduced-motion: reduce) {
  .tabbar-v2 .tb-item { transition: none; }
}


/* Le bandeau rouge du nombre de comptes, sur le raccourci. */
.admin-lien-pastille {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--magenta);
  color: #fff;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1;
  margin-left: 7px;
  flex-shrink: 0;
}

/* ═══════════════════════════════════════════════════════════════════
   PLANS & TARIFS — les trois tuiles
   ═══════════════════════════════════════════════════════════════════
   Trois colonnes tiennent sur un écran large, pas sur un téléphone :
   à 390 px, chaque tuile ferait 118 px et « environ 500 photos »
   partirait sur quatre lignes. Donc une colonne d'abord, trois à
   partir de 560 px. La grille reprend la maquette, le seuil est en
   plus — mesuré, pas deviné. */
.plan-mini-grille {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}

@media (min-width: 560px) {
  .plan-mini-grille {
    grid-template-columns: repeat(3, 1fr);
  }
}

.plan-mini-tuile {
  border: 1.5px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  padding: 11px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

/* L'offre en cours se signale par un liseré, pas par un fond : un
   fond plein sur trois tuiles côte à côte écrase les deux autres. */
.plan-mini-tuile.on {
  border-color: var(--violet);
  box-shadow: 0 0 0 1.5px var(--violet);
}

.plan-mini-tuile b {
  font-size: 14px;
  color: var(--ink);
}

.plan-mini-prix {
  font-size: 15px;
  font-weight: 700;
  color: var(--violet);
  line-height: 1.2;
}

/* Le « quand » : la mécanique du prix — par mois, une fois, pour
   toujours. Il colle au prix, d'où le line-height serré. */
.plan-mini-quand {
  font-size: 11.5px;
  color: var(--muted);
  line-height: 1.3;
  margin-top: -1px;
}

/* Le « quoi » : ce que l'offre donne. Séparé du « quand » par une
   respiration — ce sont deux questions différentes. */
.plan-mini-photos {
  font-size: 12px;
  color: var(--ink);
  line-height: 1.4;
  margin-top: 5px;
}

.plan-mini-vous {
  align-self: flex-start;
  margin-top: 2px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: #fff;
  background: var(--violet);
  border-radius: 999px;
  padding: 2px 7px;
}

.plan-carte-limite {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--violet);
  background: var(--violet-soft);
  border-radius: 999px;
  padding: 5px 10px;
}

/* ═══════════════════════════════════════════════════════════════════
   ADMINISTRATION
   ═══════════════════════════════════════════════════════════════════
   Écrit à la main, dans les jetons de l'app. La version précédente
   recopiait la maquette à la machine : elle a aplati les @media —
   « 4 colonnes » et « 2 colonnes » se sont retrouvées côte à côte au
   même niveau, la dernière gagnait — et laissé passer .stat, d'où le
   rectangle rose sans forme. Ici, rien n'est recopié. */

/* ── La fenêtre ─────────────────────────────────────────────────────
   Les autres feuilles font 540 px : elles portent un formulaire, une
   colonne. Celle-ci porte un tableau de SIX colonnes. À 540 px, la
   moitié — Stockage, Quota, Actions — sortait du cadre, atteignable
   seulement par une barre horizontale que personne ne voit. */
@media (min-width: 680px) {
  .sheet-admin {
    max-width: min(1140px, 94vw);
  }
}

.admin-popup-content {
  display: flex;
  flex-direction: column;
}

.admin-intro {
  margin: 0 0 14px;
}

/* ── Les quatre chiffres ────────────────────────────────────────────
   Deux de front sur un téléphone, quatre sur un écran. Le @media est
   ici, à sa place, et une seule fois. */
.admin-stat-grid,
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}

@media (min-width: 680px) {
  .admin-stat-grid,
  .stat-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.stat {
  background: var(--card);
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 14px;
  min-width: 0;
}

.stat__value {
  font-family: "Bricolage Grotesque", system-ui, sans-serif;
  font-size: 26px;
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -.02em;
  color: var(--ink);
}

.stat__label {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 4px;
}

/* La seule tuile qui appelle un geste. Le chiffre en magenta le dit
   déjà : la teinte reste discrète, elle accompagne, elle ne crie pas. */
.stat--new {
  border-color: #f0cfe2;
  background: var(--magenta-soft);
}

.stat--new .stat__value {
  color: var(--magenta);
}

/* ── Le tableau ─────────────────────────────────────────────────────
   Pas de min-width sur grand écran : les colonnes se répartissent.
   En dessous de 680 px un tableau de six colonnes ne rentre nulle
   part — là, et là seulement, on assume le défilement horizontal. */
.admin-table-wrap,
.table-wrap {
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  overflow-x: auto;
  margin-bottom: 10px;
}

.admin-table,
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13.5px;
}

@media (max-width: 679px) {
  .admin-table {
    min-width: 640px;
  }
}

.admin-table th {
  text-align: left;
  padding: 10px 13px;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  background: var(--line-soft);
  border-bottom: 1.5px solid var(--line);
  white-space: nowrap;
}

.admin-table td {
  padding: 11px 13px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
  color: var(--ink);
}

.admin-table tbody tr:last-child > td {
  border-bottom: none;
}

.admin-table tbody tr:hover > td {
  background: #fdfbff;
}

/* L'email est la seule colonne élastique : c'est elle qui doit céder,
   pas les chiffres. Les autres se calent sur leur contenu. */
.admin-email-cell {
  width: 100%;
  min-width: 190px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  word-break: break-word;
}

.admin-table td:not(.admin-email-cell) {
  white-space: nowrap;
}

.admin-plan-select {
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--muted);
  background: var(--card);
  border: 1.5px solid var(--line);
  border-radius: 999px;
  padding: 5px 26px 5px 11px;
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b6478' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 12px;
  transition: border-color .15s, background-color .15s;
}

.admin-plan-select:hover {
  border-color: var(--faint);
}

.admin-plan-select:focus {
  outline: none;
  border-color: var(--violet);
  box-shadow: 0 0 0 3px var(--violet-soft);
}

/* Chaque offre porte sa couleur : on lit la colonne d'un coup d'œil,
   sans lire les mots. */
.admin-plan-select.p-payant {
  color: var(--violet);
  border-color: #d6c4f0;
  background-color: var(--violet-soft);
}

.admin-plan-select.p-fondateur {
  color: #9a6a12;
  border-color: #ecd08a;
  background-color: #fdf3dc;
}

.admin-quota-cell {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  font-weight: 700;
}

.admin-quota-cell small {
  font-size: 11px;
  font-weight: 400;
  color: var(--muted);
  margin-top: 2px;
}

.admin-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
}

.iconbtn.danger {
  color: var(--magenta);
}

.iconbtn.danger:hover {
  background: var(--magenta-soft);
}

/* ── Le panneau qui se déplie sous une ligne ────────────────────────
   Exception ou mot de passe. Il vit DANS le tableau, donc dans une
   cellule qui traverse toutes les colonnes : sans largeur bornée, il
   étirait la table et son bouton partait hors du cadre. */
.admin-exception-row > td {
  padding: 0;
  background: var(--note-bg);
  border-bottom: 1px solid var(--line-soft);
}

.admin-exception-panel {
  padding: 14px;
  max-width: 560px;
}

.admin-exception-panel .sync-note {
  margin: 0 0 10px;
}

.admin-exception-panel .sheet-actions {
  margin-top: 12px;
}

.admin-table-note {
  margin: 0 0 16px;
}

.admin-plafonds,
.admin-create-block {
  margin-bottom: 16px;
}

/* ── Statut par défaut ───────────────────────────────────────────────
   Un point à cocher devant chaque libellé. Un rond, pas une étoile :
   l'étoile dit déjà « Fondateur » ailleurs dans l'app, et un rond dit
   « un seul parmi plusieurs » sans qu'on ait à l'apprendre. */
.label-ligne {
  display: flex;
  align-items: center;
  gap: 9px;
}

.label-ligne + .label-ligne {
  margin-top: 7px;
}

.label-ligne input {
  flex: 1;
  min-width: 0;
}

.label-defaut {
  flex-shrink: 0;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--card);
  cursor: pointer;
  padding: 0;
  position: relative;
  transition: border-color .15s, background .15s;
}

.label-defaut:hover {
  border-color: var(--faint);
}

.label-defaut.on {
  border-color: var(--violet);
}

/* Le point : peint en ::after, pas en fond — le liseré reste visible,
   et c'est lui qui porte la couleur de la marque. */
.label-defaut.on::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--violet);
}

.label-defaut:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--violet-soft);
}

/* La légende du rond. Elle montre le contrôle dont elle parle, à trois
   centimètres de lui : un rond nu n'explique rien, un rond légendé se
   comprend sans qu'on ait à chercher la phrase qui va avec. Elle vit
   séparée de la note sur le renommage — deux idées, deux phrases. */
.defaut-legende {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 10px;
  padding: 8px 10px;
  background: var(--violet-soft);
  border-radius: 9px;
}

.defaut-legende .label-defaut {
  pointer-events: none;
  cursor: default;
}


/* ═══════════════════════════════════════════════════════════════════
   STATUTS — les couleurs et le réglage
   ═══════════════════════════════════════════════════════════════════
   Les classes portaient des noms de statuts (c-todo, c-done…) qui
   étaient en réalité des noms de couleurs déguisés. Maintenant que
   l'utilisateur choisit, elles portent leur vrai nom. Il manquait le
   gris — le fond, celui du statut par défaut, discret parce qu'il est
   sur presque toutes les cartes — et le rouge, le seul qui crie. */
:root {
  --gris: #7d7490;
  --gris-soft: #eeebf3;
  --rouge: #b3242c;
  --rouge-soft: #fbe3e4;
  /* Orange brûlé et ocre : les deux dernières places du nuancier.
     Le rose partait — il se confondait avec le magenta à un mètre
     de l'écran, et deux couleurs qu'on doit comparer côte à côte
     pour les distinguer ne distinguent rien.

     Les teintes vives ne passent PAS le contraste AA sur leur
     propre fond pastel (l'orange y tombe à 3,5). D'où --x-text :
     même teinte, assombrie juste ce qu'il faut pour franchir 4,5.
     C'est elle qui porte le libellé ; --x reste la pastille. */
  --orange: #c0642a;
  --orange-soft: #f9e8dc;
  --orange-text: #a35322;
  --ocre: #8f7117;
  --ocre-soft: #f4edd8;
  --ocre-text: #846815;
}

.chip.c-gris   { background: var(--gris-soft);   border-color: transparent; color: var(--gris); }
.chip.c-gris.active   { background: var(--gris);   color: #fff; }
.chip.c-rouge  { background: var(--rouge-soft);  border-color: transparent; color: var(--rouge); }
.chip.c-rouge.active  { background: var(--rouge);  color: #fff; }
.chip.c-pink   { background: var(--pink-soft);   border-color: transparent; color: var(--pink); }
.chip.c-pink.active   { background: var(--pink);   color: #fff; }
.chip.c-orange { background: var(--orange-soft); border-color: transparent; color: var(--orange-text); }
.chip.c-orange.active { background: var(--orange); color: #fff; }
.chip.c-ocre   { background: var(--ocre-soft);   border-color: transparent; color: var(--ocre-text); }
.chip.c-ocre.active   { background: var(--ocre);   color: #fff; }

.card.s-gris::before  { background: var(--gris); }
.card.s-rouge::before { background: var(--rouge); }
.card.s-pink::before  { background: var(--pink); }
.card.s-orange::before { background: var(--orange); }
.card.s-ocre::before   { background: var(--ocre); }

.status-pill.p-gris  { background: var(--gris); }
.status-pill.p-rouge { background: var(--rouge); }
.status-pill.p-pink  { background: var(--pink); }
.status-pill.p-orange { background: var(--orange); }
.status-pill.p-ocre   { background: var(--ocre); }

.state-choice button.sel-gris  { border-color: var(--gris);  background: var(--gris-soft);  color: var(--gris); }
.state-choice button.sel-rouge { border-color: var(--rouge); background: var(--rouge-soft); color: var(--rouge); }
.state-choice button.sel-pink  { border-color: var(--pink);  background: var(--pink-soft);  color: var(--pink); }
.state-choice button.sel-orange { border-color: var(--orange); background: var(--orange-soft); color: var(--orange-text); }
.state-choice button.sel-ocre   { border-color: var(--ocre);   background: var(--ocre-soft);   color: var(--ocre-text); }

/* ── Le réglage : une ligne par statut ──────────────────────────────
   Une seule liste maintenant, plus deux colonnes : elle prend toute
   la largeur, chaque ligne porte ses quatre commandes. */
.statuts-liste {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.statut-ligne {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
}

/* Rangé : on l'estompe sans le cacher. Il doit rester lisible — c'est
   par lui qu'on le remet en service. */
.statut-ligne.off {
  opacity: .5;
}

.statut-nom {
  flex: 1;
  min-width: 110px;
}

.label-defaut:disabled {
  opacity: .4;
  cursor: not-allowed;
}

/* ── Les sept pastilles ─────────────────────────────────────────── */
.statut-couleurs {
  display: flex;
  gap: 4px;
  flex-shrink: 0;
}

.pastille {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  transition: transform .12s, box-shadow .12s;
}

.pastille.c-gris    { background: var(--gris); }
.pastille.c-bleu    { background: var(--blue); }
.pastille.c-violet  { background: var(--violet); }
.pastille.c-magenta { background: var(--magenta); }
.pastille.c-pink    { background: var(--pink); }
.pastille.c-orange  { background: var(--orange); }
.pastille.c-ocre    { background: var(--ocre); }
.pastille.c-vert    { background: var(--vert); }
.pastille.c-rouge   { background: var(--rouge); }

.pastille:hover:not(:disabled) { transform: scale(1.15); }

/* La couleur en cours : un anneau blanc puis le liseré d'encre. Le
   contraste tient sur les sept, du gris pâle au rouge profond. */
.pastille.on {
  box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--ink);
}

/* Prise par un autre statut. Elle n'est plus interdite : elle
   ÉCHANGE. À huit statuts pour huit couleurs, le nuancier n'offrait
   plus aucune alternative — toutes grises sauf la sienne. Interdire
   revient à demander de désactiver un statut, changer, réactiver,
   pour un simple échange bleu/violet. Un clic suffit maintenant :
   les deux statuts permutent, l'unicité tient toujours.

   Estompée, pas barrée : elle reste choisissable, on doit juste
   savoir qu'elle appartient déjà à quelqu'un. */
.pastille.pris {
  opacity: .3;
}

.pastille.pris:hover {
  opacity: .65;
}

/* ── Le pied de liste : repli des rangés + bouton d'ajout ──────────
   Les créneaux libres ne s'affichent pas en lignes vides. Un statut
   qu'on n'a pas créé n'existe pas ; une ligne fantôme demande à être
   remplie et fait passer le réglage pour un formulaire. */
/* La corbeille. Discrète au repos — elle ne vit que sur les lignes
   rangées, et une ligne rangée est déjà en retrait. Elle ne rougit
   qu'au survol : une corbeille rouge en permanence, dans une liste
   de huit, c'est huit invitations à se tromper. */
.statut-sup {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 8px;
  color: var(--muted);
  cursor: pointer;
  opacity: .55;
  transition: opacity .12s ease, color .12s ease, background .12s ease;
}

.statut-sup:hover {
  opacity: 1;
  color: var(--rouge);
  background: var(--rouge-soft);
}

.statut-sup:focus-visible {
  opacity: 1;
  outline: 2px solid var(--rouge);
  outline-offset: 1px;
}

.statuts-pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--line-soft);
}

.statuts-ranges-titre {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 0;
  padding: 4px 0;
  font: inherit;
  font-size: .82rem;
  color: var(--muted);
  cursor: pointer;
}

.statuts-ranges-titre:hover { color: var(--ink); }

.statuts-ranges-titre .chevron {
  display: inline-block;
  transition: transform .15s ease;
}

details[open] > .statuts-ranges-titre .chevron {
  transform: rotate(90deg);
}

.statuts-ranges > summary { list-style: none; }
.statuts-ranges > summary::-webkit-details-marker { display: none; }

.statuts-ranges-nb {
  background: var(--line-soft);
  border-radius: 9px;
  padding: 1px 7px;
  font-size: .72rem;
}

.statut-ajout {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 6px 11px;
  font: inherit;
  font-size: .82rem;
  color: var(--ink);
  cursor: pointer;
}

.statut-ajout:hover:not(:disabled) {
  border-color: var(--violet);
  color: var(--violet);
}

/* Grisé, jamais retiré. Un bouton qui disparaît à huit statuts
   laisse croire à une panne ; grisé, il dit qu'on a atteint la
   limite — et son title dit laquelle. */
.statut-ajout:disabled {
  opacity: .4;
  cursor: not-allowed;
}

.statut-compteur {
  font-size: .72rem;
  color: var(--muted);
  margin-left: 2px;
}

/* ── L'interrupteur actif / rangé ───────────────────────────────── */
.statut-bascule {
  flex-shrink: 0;
  width: 38px;
  height: 22px;
  border-radius: 999px;
  border: none;
  background: var(--line);
  padding: 0;
  cursor: pointer;
  position: relative;
  transition: background .18s;
}

.statut-bascule > span {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--card);
  box-shadow: 0 1px 3px rgba(46, 42, 61, .25);
  transition: transform .18s;
}

.statut-bascule.on {
  background: var(--violet);
}

.statut-bascule.on > span {
  transform: translateX(16px);
}

.statut-bascule:disabled {
  opacity: .4;
  cursor: not-allowed;
}

/* ========================================================================== 
   REFONTE NATIVE — bibliothèque personnelle
   Cette section remplace la présentation « grille de site » par une couche
   d'application : surfaces regroupées, navigation plus compacte et cartes
   différenciées selon la nature réelle du contenu.
   ========================================================================== */

:root {
  --native-bg: #f1edf6;
  --native-surface: rgba(255,255,255,.88);
  --native-ink: #272334;
  --native-muted: #716a7d;
  --native-line: #e7e1ee;
  --native-shadow: 0 12px 30px rgba(55,44,78,.08);
  --native-shadow-hover: 0 17px 42px rgba(55,44,78,.13);
  --native-radius-xl: 25px;
  --native-radius-lg: 20px;
  --native-radius-md: 14px;
}

body {
  background: var(--native-bg);
}

/* LE DECOR AQUARELLE, SANS FILTRE — mesure du 13 aout.

   Ce decor etait fait de trois cercles pleins adoucis par
   « filter: blur(92px) ». Un filtre force WebKit a composer l'element
   dans un calque separe, et un flou de 92 px etend ce calque bien
   au-dela du cercle. L'inspecteur a chiffre le resultat : 7,78 Mo pour
   le seul decor, et surtout la promotion en cascade de « .app », haut de
   14 350 px, au motif — mot pour mot — que « l'element peut se
   superposer a un autre element de composition ». Soit 45,9 Mo de
   calques ecran ferme, 76,2 Mo popup ouvert. Fermer une feuille obligeait
   alors l'iPhone a defaire puis reassembler tout cela : 2,5 secondes de
   gel, mesurees a la composition et nulle part ailleurs (14 589 ms
   cumules en composition, contre 139 ms pour tout le JavaScript).

   Les memes taches sont maintenant peintes en degrades radiaux dans le
   fond du conteneur. Un degre se PEINT — il n'a pas de calque, pas de
   memoire graphique, pas de cout de recomposition. Le rendu est le meme.

   Ce qui a ete essaye et ecarte, par mesure et non par intuition :
     · repasser en « position: absolute »  → toujours lent
     · revenir au flou de 70 px du 31 juillet → toujours lent
   Le flou lui-meme etait en cause, pas son intensite ni sa position.
   Le 31 juillet y survivait faute de voisins : ni chrome, ni tabbar-v2,
   ni chrome::before n'etaient encore composes a cote. */
.watercolor {
  position: fixed;
  height: 440px;

  /* 0,26 et non 0,10 : un degrade s'eteint vers ses bords la ou un cercle
     flou reste plein en son centre. A opacite egale, il paraitrait pale. */
  opacity: .26;

  background:
    radial-gradient(circle 250px at 80px 70px,               var(--blue),    transparent),
    radial-gradient(circle 230px at calc(100% - 80px) 70px,  var(--magenta), transparent),
    radial-gradient(circle 215px at calc(40% + 115px) 195px, var(--violet),  transparent);
  background-repeat: no-repeat;
}

/* Les trois <span> restent rendus par React — les masquer coute moins
   qu'une modification du composant, et garde la porte ouverte si le
   decor devait un jour redevenir un empilement d'elements. */
.watercolor span { display: none; }

.app.app-native {
  max-width: 1480px;
  padding: 12px clamp(12px, 3vw, 38px) 150px;
}

/* ── En-tête d'application ─────────────────────────────────────────────── */
.app-native .topbar {
  position: sticky;
  top: 10px;
  z-index: 35;
  display: flex;
  flex-wrap: nowrap;
  min-height: 68px;
  margin: 0 0 14px;
  padding: 10px 12px;
  border: 1px solid rgba(255,255,255,.92);
  border-radius: 22px;
  background: rgba(255,255,255,.84);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  box-shadow: 0 8px 28px rgba(55,44,78,.075);
}

/* La marque : une image, puis un bloc de texte de deux lignes.

   L'ancienne version était une grille de trois colonnes où le slogan,
   posé en ::after, s'étalait de la colonne 2 à la colonne 4. Comme il est
   plus large que le titre, c'est LUI qui dictait la largeur de la colonne
   du compteur : la pastille « 248 » s'étirait sur toute la place restante.
   Et le logo, cible d'une classe que le composant ne posait pas, tombait
   en placement automatique dans la première cellule, calé en haut au lieu
   d'être centré sur les deux lignes.

   Deux boîtes flex imbriquées disent la même chose sans effet de bord :
   le logo se centre sur l'ensemble, le compteur reste collé au titre. */
.app-native .brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.app-native .brand .brand-mark {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 14px;
  object-fit: contain;
  display: block;
  box-shadow: 0 6px 18px rgba(111,79,188,.22);
}

.app-native .brand-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.app-native .brand-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.app-native .brand h1 {
  margin: 0;
  font-size: 18px;
  letter-spacing: -.025em;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-native .brand .count {
  flex: 0 0 auto;
  align-self: center;
  margin: 0;
  padding: 2px 8px;
  background: #f0ebf8;
  color: var(--violet);
  vertical-align: baseline;
}

.app-native .brand-tagline {
  margin: 0;
  color: var(--native-muted);
  font-size: 12px;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.app-native .topbar-right {
  margin-left: auto;
  gap: 8px;
}

.app-native .topbar-gerer,
.app-native .sync-wrap,
.app-native .avatar,
.app-native .plan-ecusson {
  min-height: 40px;
  border-radius: 13px;
}

.app-native .topbar-gerer {
  width: 40px;
  height: 40px;
  border: 1px solid var(--native-line);
}

.app-native .sync-wrap {
  padding: 0 10px;
  border: 1px solid var(--native-line);
  background: #fff;
}

.app-native .topbar-right .plan-ecusson {
  padding: 4px 9px 4px 5px;
  border-color: var(--native-line);
  background: #fff;
}

/* Espaces : une rangée de contexte, pas une seconde navigation principale. */
.app-native .espaces-bar {
  margin: 0 0 10px;
  padding: 7px 8px;
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 17px;
  background: rgba(255,255,255,.62);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
}

.app-native .espaces-bar .chip-row {
  padding: 0;
}

.app-native .espaces-bar .chip {
  min-height: 34px;
  padding: 7px 12px;
  border: 0;
  border-radius: 11px;
  background: transparent;
}

.app-native .espaces-bar .chip.active {
  background: #2a2537;
  color: #fff;
  box-shadow: 0 8px 18px rgba(39,35,52,.18);
}

.app-native .espaces-bar .chip.active .chip-badge {
  color: rgba(255,255,255,.72);
}

.app-native .espaces-bar .chip:hover:not(.active) {
  background: rgba(255,255,255,.68);
  color: var(--native-ink);
}

/* Navigation de bureau : plus proche d'une barre d'onglets native. */
@media (min-width: 720px) {
  .app-native .types-haut {
    max-width: 760px;
    margin: 0 auto 18px;
    padding: 5px;
    gap: 4px;
    border: 1px solid rgba(255,255,255,.88);
    border-radius: 17px;
    background: rgba(235,231,242,.76);
    -webkit-backdrop-filter: blur(12px);
    backdrop-filter: blur(12px);
    box-shadow: 0 6px 20px rgba(55,44,78,.045);
  }

  .app-native .th-item {
    min-height: 42px;
    padding: 9px 14px;
    border-radius: 12px;
  }

  .app-native .th-item.active {
    color: var(--native-ink);
    box-shadow: 0 4px 12px rgba(55,44,78,.09);
  }
}

/* ── Titre de la bibliothèque ──────────────────────────────────────────── */
.library-heading {
  display: grid;
  grid-template-columns: minmax(0,1fr) auto;
  align-items: end;
  gap: 22px;
  margin: 4px 2px 18px;
}

.library-heading-copy {
  min-width: 0;
}

.library-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--violet);
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: .02em;
}

.library-eyebrow::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--violet);
}

.library-heading h2 {
  margin: 7px 0 6px;
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: clamp(26px, 3.2vw, 39px);
  line-height: 1.05;
  letter-spacing: -.042em;
}

.library-heading p {
  max-width: 730px;
  margin: 0;
  color: var(--native-muted);
  font-size: 14.5px;
  line-height: 1.45;
}

.library-count {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 15px;
  border: 1px solid var(--native-line);
  border-radius: 17px;
  background: #fff;
  box-shadow: var(--native-shadow);
}

.library-count strong {
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 24px;
  line-height: 1;
}

.library-count span {
  color: var(--native-muted);
  font-size: 12px;
  font-weight: 700;
  line-height: 1.15;
}

/* ── Recherche et filtres ──────────────────────────────────────────────── */
.app-native .searchbar {
  top: 90px;
  z-index: 28;
  margin: 0 0 8px;
  padding: 10px;
  border: 1px solid rgba(255,255,255,.92);
  border-radius: 20px;
  background: rgba(255,255,255,.82);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  box-shadow: 0 9px 28px rgba(55,44,78,.06);
}

.app-native .search-input {
  min-height: 48px;
  padding-top: 12px;
  padding-bottom: 12px;
  border: 1px solid var(--native-line);
  border-radius: 14px;
  box-shadow: none;
}

.app-native .filters {
  margin-bottom: 16px;
}

.app-native .filters .chip-row {
  gap: 7px;
  padding: 2px 1px 4px;
}

.app-native .filters .chip,
.app-native .vues-bar .vue-chip {
  min-height: 38px;
  border-radius: 12px;
  border-color: var(--native-line);
  box-shadow: 0 2px 7px rgba(55,44,78,.025);
}

/* ── Grille native ─────────────────────────────────────────────────────── */
.app-native .grid {
  grid-template-columns: repeat(1, minmax(0,1fr));
  gap: 17px;
  align-items: start;
}

@media (min-width: 700px) {
  .app-native .grid {
    grid-template-columns: repeat(2, minmax(0,1fr));
  }
}

@media (min-width: 1180px) {
  .app-native .grid {
    grid-template-columns: repeat(3, minmax(0,1fr));
  }
}

.app-native .native-card {
  position: relative;
  overflow: hidden;
  min-width: 0;
  border: 1px solid var(--native-line);
  border-radius: var(--native-radius-xl);
  background: #fff;
  box-shadow: 0 9px 30px rgba(55,44,78,.07);
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
}

.app-native .native-card::before {
  display: none;
}

.app-native .native-card:hover {
  transform: translateY(-2px);
  border-color: #ddd4e8;
  box-shadow: var(--native-shadow-hover);
}

.app-native .native-card.archived {
  opacity: .62;
}

/* Visuels communs */
.native-link-visual,
.native-idea-visual,
.native-video-visual {
  position: relative;
  overflow: hidden;
}

.native-media-badge {
  position: absolute;
  z-index: 4;
  top: 13px;
  left: 13px;
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 6px 10px;
  border-radius: 10px;
  background: rgba(255,255,255,.9);
  color: var(--native-ink);
  -webkit-backdrop-filter: blur(9px);
  backdrop-filter: blur(9px);
  box-shadow: 0 4px 14px rgba(33,26,43,.08);
  font-size: 11.5px;
  font-weight: 800;
}

/* Lien : on ne fabrique pas une fausse capture de page. Le domaine devient
   l'identité visuelle de la fiche, avec son vrai favicon quand il existe. */
.native-link-visual {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 158px;
  padding: 48px 22px 24px;
  background:
    radial-gradient(circle at 82% 18%, rgba(204,79,150,.16), transparent 30%),
    radial-gradient(circle at 14% 88%, rgba(85,136,214,.2), transparent 34%),
    linear-gradient(145deg, #e9eef9, #eee8f8 56%, #f7eaf2);
}

.native-link-visual::before,
.native-link-visual::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: rgba(255,255,255,.32);
}

.native-link-visual::before {
  width: 130px;
  height: 130px;
  right: -50px;
  top: -62px;
}

.native-link-visual::after {
  width: 80px;
  height: 80px;
  left: -30px;
  bottom: -38px;
}

.native-link-emblem {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 66px;
  height: 66px;
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 20px;
  background: rgba(255,255,255,.76);
  box-shadow: 0 10px 24px rgba(55,44,78,.12);
  color: var(--violet);
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 25px;
  font-weight: 800;
}

.native-link-emblem img {
  position: absolute;
  inset: 15px;
  width: 36px;
  height: 36px;
  border-radius: 9px;
  object-fit: contain;
  background: #fff;
}

.native-link-domain {
  position: relative;
  z-index: 1;
  max-width: 90%;
  margin-top: 12px;
  overflow: hidden;
  color: #554d63;
  font-size: 12.5px;
  font-weight: 800;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.native-quick-open {
  position: absolute;
  z-index: 5;
  top: 13px;
  right: 13px;
  display: grid;
  place-items: center;
  width: 39px;
  height: 39px;
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 13px;
  background: rgba(255,255,255,.91);
  color: var(--native-ink);
  text-decoration: none;
  box-shadow: 0 7px 18px rgba(33,26,43,.13);
  transition: transform .15s ease, background .15s ease;
}

.native-quick-open:hover {
  transform: translateY(-1px);
  background: #fff;
}

/* Idées : le texte devient un vrai objet visuel ; une image conserve sa
   place dominante sans être enfermée entre le titre et les métadonnées. */
.native-idea-card {
  background: linear-gradient(145deg,#fff 0%,#fcf9ff 62%,#f2ecfb 100%);
}

.native-idea-visual {
  min-height: 205px;
  margin: 14px 14px 0;
  border-radius: 19px;
}

.native-idea-visual.has-text {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 18px;
  padding: 18px 18px 20px;
  background:
    radial-gradient(circle at 88% 7%, rgba(255,255,255,.2), transparent 30%),
    radial-gradient(circle at 5% 95%, rgba(255,255,255,.16), transparent 34%),
    linear-gradient(145deg,#8160c5,#9a66c1 58%,#cb5f92);
  color: #fff;
  cursor: default;
}

.native-idea-visual.has-text::before,
.native-idea-visual.has-text::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: rgba(255,255,255,.13);
}

.native-idea-visual.has-text::before {
  width: 150px;
  height: 150px;
  right: -68px;
  top: -76px;
}

.native-idea-visual.has-text::after {
  width: 84px;
  height: 84px;
  left: -28px;
  bottom: -38px;
}

.native-idea-mini {
  position: relative;
  z-index: 1;
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  opacity: .82;
}

.native-idea-quote {
  position: relative;
  z-index: 1;
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: clamp(19px, 2.1vw, 23px);
  font-weight: 700;
  line-height: 1.27;
  letter-spacing: -.03em;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
}

.native-idea-visual.has-image {
  min-height: 205px;
  background: var(--line-soft);
}

.native-idea-image,
.native-idea-visual .inspi-vide.native-idea-image {
  width: 100%;
  height: 100%;
  min-height: 205px;
  max-height: 260px;
  margin: 0;
  border: 0;
  border-radius: 19px;
  object-fit: cover;
}

.native-idea-visual.has-image::after {
  content: "";
  position: absolute;
  z-index: 2;
  inset: auto 0 0;
  height: 48%;
  background: linear-gradient(transparent, rgba(28,23,39,.42));
  pointer-events: none;
}

/* Vidéos */
.native-video-visual {
  aspect-ratio: 16 / 8.8;
  background: #211d2a;
}

.native-video-visual img,
.native-video-visual iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.app-native .native-video-visual .thumb-play span {
  width: 50px;
  height: 50px;
  background: rgba(39,35,52,.76);
  box-shadow: 0 8px 20px rgba(20,17,27,.2);
}

/* Corps et hiérarchie */
.app-native .native-card-body {
  gap: 0;
  padding: 15px 16px 14px;
}

.native-card-kicker {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  margin-bottom: 9px;
  color: var(--native-muted);
  font-size: 12px;
}

.native-source-icon {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: 23px;
  height: 23px;
  overflow: hidden;
  border-radius: 7px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 12px;
  font-weight: 900;
}

.native-source-icon img {
  width: 15px;
  height: 15px;
  object-fit: contain;
}

.native-source-icon.native-youtube {
  background: #fbe5e6;
  color: #b72d38;
}

.native-source-label {
  min-width: 0;
  overflow: hidden;
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.native-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-height: 28px;
  margin-left: auto;
  padding: 5px 9px;
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 10.8px;
  font-weight: 800;
  cursor: pointer;
}

.native-status > span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.status-gris { background: #eeedf1; color: #625d69; }
.status-bleu { background: var(--blue-soft); color: #416fae; }
.status-violet { background: var(--violet-soft); color: #6e4ead; }
.status-magenta { background: var(--magenta-soft); color: var(--magenta-text); }
.status-vert { background: var(--vert-soft); color: #2f7e72; }
.status-rouge { background: var(--rouge-soft); color: var(--rouge); }
.status-orange { background: var(--orange-soft); color: var(--orange-text); }
.status-ocre { background: var(--ocre-soft); color: var(--ocre-text); }
.status-pink { background: var(--pink-soft); color: var(--pink); }

.app-native .native-card-title {
  margin: 0 0 8px;
  font-size: 18px;
  line-height: 1.22;
  letter-spacing: -.026em;
}

.app-native .native-card-desc {
  margin: 0 0 11px;
  color: var(--native-muted);
  font-size: 14px;
  line-height: 1.46;
}

.app-native .native-card-desc.seul {
  color: var(--native-ink);
  font-size: 15px;
}

.app-native .native-card-action {
  margin: 0 0 9px;
  padding: 9px 10px;
  border: 1px solid #ece6f3;
  border-radius: 11px;
  background: #f7f4fa;
  font-size: 13.5px;
}

.app-native .native-card-action b {
  color: #766d80;
  font-size: 10px;
}

.app-native .native-repere {
  margin: 0 0 10px;
  padding: 6px 9px;
  border: 1px solid #e7e1ef;
  border-radius: 9px;
  background: #f7f4fa;
  color: #4b4356;
  font-size: 12.8px;
}

.native-complete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 38px;
  margin: 0 0 11px;
  padding: 0 8px 0 11px;
  border: 1px dashed #ded6e8;
  border-radius: 10px;
  background: transparent;
  color: #82788c;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}

.native-complete b {
  display: grid;
  place-items: center;
  width: 27px;
  height: 27px;
  border-radius: 8px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 16px;
}

.native-complete:hover {
  border-color: #bdaee0;
  color: var(--native-ink);
}

.app-native .native-card-foot {
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 4px;
}

.app-native .native-card-foot .card-tags {
  min-width: 0;
  gap: 5px;
}

.app-native .native-card-foot .tag {
  padding: 5px 8px;
  border-radius: 9px;
  font-size: 10.8px;
}

.app-native .native-date {
  margin-left: auto;
}

.native-menu-wrap {
  flex: 0 0 auto;
  margin-left: auto;
}

.app-native .native-more {
  width: 34px;
  height: 34px;
  border: 1px solid var(--native-line);
  border-radius: 11px;
  background: #fff;
  color: var(--native-muted);
}

.app-native .native-more:hover {
  background: #f5f2f8;
}

/* Notes vidéo : outil compact, mais pleinement éditable. */
.native-empty-note {
  margin-bottom: 10px !important;
}

.app-native .native-notes {
  gap: 6px;
  margin-bottom: 8px;
}

.app-native .native-note {
  padding: 8px 9px 4px;
  border: 1px solid #e4dbf2;
  border-left: 3px solid var(--note-edge);
  border-radius: 10px;
  background: #f7f3fc;
  font-size: 13.5px;
}

.app-native .native-note .note-text {
  margin-bottom: 0;
}

.app-native .native-note-add {
  margin: 0 0 9px;
}

.app-native .native-note-add input {
  min-height: 40px;
  padding: 9px 10px;
  border-radius: 10px;
  font-size: 13.5px;
}

.app-native .native-note-add button {
  min-width: 40px;
  border-radius: 10px;
}

/* ── Bouton d'ajout et barre mobile ───────────────────────────────────── */
.app-native .fab {
  right: clamp(16px,3vw,38px);
  border-radius: 17px;
  background: var(--native-ink);
  box-shadow: 0 15px 32px rgba(39,35,52,.27);
}

/* Géométrie de la barre du bas, écrite une fois et lue partout : la barre
   elle-même, le bouton +, le rembourrage bas du contenu et la bannière
   d'installation lisent tous ces trois valeurs. Aucun nombre magique
   recopié d'un endroit à l'autre — c'est ce qui avait laissé le bouton +
   à 89 px d'une barre qui n'en faisait plus que 56.

   62 px de zone utile, plus la barre de geste quand l'appareil en a une.
   Le fallback 0px n'est pas décoratif : sans lui, un navigateur qui ne
   connaît pas env() invalide toute la déclaration au lieu de la réduire
   à zéro, et la barre perd sa hauteur. */
:root {
  --tb-bas: env(safe-area-inset-bottom, 0px);
  --tb-utile: 62px;
  --tb-haut: calc(var(--tb-utile) + var(--tb-bas));
}

/* La barre est ancrée au bord, pleine largeur, décrite une seule fois
   dans .tabbar-v2 plus haut. La surcouche « .app-native » qui vivait ici
   redisait la même chose en moins bien : elle rajoutait une ombre
   montante, un filet plus pâle, un flou saturé, et surtout 4 px de
   rembourrage haut sur chaque bouton qui décentraient la rangée vers le
   bas. Une seule règle, un seul jeu de valeurs : c'est la version qui
   tient à l'usage.

   Ne restent ici que les ajustements propres à l'habillage natif, ceux
   qui n'ont pas d'équivalent dans la règle de base. */
.app-native .tabbar-v2 .tb-item.active {
  color: var(--violet);
}

/* Un rail d'icônes de hauteur fixe : les cinq libellés partagent enfin la
   même ligne de base. Sans lui, l'avatar du profil, plus haut que les
   icônes au trait, décalait le cinquième libellé vers le bas. */
.app-native .tabbar-v2 .tb-ico {
  height: 24px;
  align-items: center;
  justify-content: center;
}

.app-native .tabbar-v2 .tb-lab {
  font-size: 10.5px;
}

/* ── Mobile ───────────────────────────────────────────────────────────── */
@media (max-width: 719px) {
  .app.app-native {
    /* barre + 12 + 54 (bouton +) + 12 : la dernière fiche s'arrête
       au-dessus du bouton flottant au lieu de finir dessous. */
    padding: 9px 10px calc(var(--tb-haut) + 78px);
  }

  .app-native .install-banner {
    bottom: calc(var(--tb-haut) + 8px);
  }

  .app-native .topbar {
    top: 7px;
    min-height: 58px;
    margin-bottom: 11px;
    padding: 8px 9px;
    border-radius: 18px;
  }

  .app-native .brand {
    gap: 9px;
  }

  .app-native .brand .brand-mark {
    width: 38px;
    height: 38px;
    border-radius: 12px;
  }

  .app-native .brand h1 {
    font-size: 16.5px;
  }

  /* Sur téléphone, la barre doit rester basse : le slogan sort, le titre
     et le compteur suffisent à identifier l'application. */
  .app-native .brand-tagline {
    display: none;
  }

  .app-native .brand .count {
    font-size: 10px;
    padding: 2px 6px;
  }

  .app-native .topbar-gerer,
  .app-native .sync-wrap,
  .app-native .avatar,
  .app-native .plan-ecusson {
    min-height: 36px;
  }

  .app-native .topbar-gerer {
    width: 36px;
    height: 36px;
  }

  .app-native .plan-nom,
  .app-native .sync-txt {
    display: none;
  }

  .app-native .sync-wrap {
    width: 36px;
    justify-content: center;
    padding: 0;
  }

  .app-native .library-heading {
    grid-template-columns: minmax(0,1fr) auto;
    gap: 10px;
    margin: 7px 2px 13px;
  }

  .library-heading h2 {
    margin-top: 5px;
    font-size: 25px;
  }

  .library-heading p {
    display: none;
  }

  .library-count {
    flex-direction: column;
    gap: 2px;
    min-width: 62px;
    padding: 10px 11px;
    text-align: center;
  }

  .library-count strong {
    font-size: 21px;
  }

  .library-count span {
    max-width: 64px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .app-native .searchbar {
    top: 73px;
    margin-bottom: 8px;
    padding: 7px;
    border-radius: 17px;
  }

  .app-native .search-input {
    min-height: 44px;
    border-radius: 12px;
  }

  .app-native .grid {
    gap: 13px;
  }

  .app-native .native-card {
    border-radius: 21px;
  }

  /* Les liens deviennent compacts, comme une liste de bibliothèque.
     Les vidéos restent verticales : leurs notes sont des outils, pas une
     simple métadonnée à écraser dans une colonne étroite. */
  .app-native .native-link-card {
    display: grid;
    grid-template-columns: 112px minmax(0,1fr);
  }

  .app-native .native-link-card .native-link-visual {
    min-height: 100%;
    padding: 46px 10px 18px;
  }

  .app-native .native-link-card .native-media-badge {
    left: 9px;
    top: 9px;
    min-height: 26px;
    padding: 5px 7px;
    font-size: 9.8px;
  }

  .app-native .native-link-card .native-quick-open {
    top: auto;
    right: 9px;
    bottom: 9px;
    width: 34px;
    height: 34px;
  }

  .app-native .native-link-card .native-link-emblem {
    width: 52px;
    height: 52px;
    border-radius: 16px;
  }

  .app-native .native-link-card .native-link-emblem img {
    inset: 12px;
    width: 28px;
    height: 28px;
  }

  .app-native .native-link-card .native-link-domain {
    display: none;
  }

  .app-native .native-card-body {
    padding: 13px 13px 12px;
  }

  .app-native .native-card-title {
    font-size: 16.5px;
  }

  .app-native .native-card-desc {
    font-size: 13.5px;
    -webkit-line-clamp: 2;
  }

  .app-native .native-card-kicker {
    margin-bottom: 7px;
  }

  .native-status {
    max-width: 105px;
    padding: 5px 7px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .native-idea-visual {
    min-height: 184px;
    margin: 11px 11px 0;
    border-radius: 17px;
  }

  .native-idea-image,
  .native-idea-visual .inspi-vide.native-idea-image {
    min-height: 184px;
    border-radius: 17px;
  }

  .native-idea-quote {
    font-size: 19px;
  }

  .native-video-visual {
    aspect-ratio: 16 / 9;
  }

  .app-native .native-card-foot .tag:nth-of-type(n+4) {
    display: none;
  }

  .native-complete {
    min-height: 36px;
    font-size: 12px;
  }

  .app-native .fab {
    right: 17px;
    /* La barre, plus 12 de respiration. L'ancienne valeur, 89, était
       calculée pour la barre flottante : 64 de haut, 10 de marge basse,
       15 d'écart. La barre ayant changé de nature, la valeur suivait. */
    bottom: calc(var(--tb-haut) + 12px);
    width: 54px;
    height: 54px;
    padding: 0;
    border-radius: 17px;
  }
}

@media (min-width: 720px) {
  .app-native .fab {
    bottom: calc(24px + env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  .app-native .native-card,
  .native-quick-open {
    transition: none;
  }
}

/* ========================================================================== 
   25 · AFFICHAGE GRILLE / LISTE

   Le bouton ne retrie rien : il ne touche ni aux données, ni aux filtres,
   ni à l'ordre calculé par l'application. En grille, le DOM se lit toujours
   de gauche à droite puis ligne suivante. En liste, le même ordre devient
   simplement vertical. La préférence est mémorisée sur l'appareil.
   ========================================================================== */

.view-switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  gap: 2px;
  /* La marge automatique passe sur « Sélectionner », qui est désormais le
     premier des deux outils rangés à droite. La garder ici aussi
     répartirait l'espace ENTRE eux et les séparerait. */
  margin-left: 8px;
  padding: 3px;
  border: 1px solid var(--native-line);
  border-radius: 12px;
  background: #eeebf3;
  box-shadow: 0 2px 8px rgba(55,44,78,.04);
}

.view-switch-btn {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--native-muted);
  cursor: pointer;
  transition: color .15s ease, background .15s ease, box-shadow .15s ease;
}

.view-switch-btn:hover {
  color: var(--native-ink);
}

.view-switch-btn.active {
  background: #fff;
  color: var(--native-ink);
  box-shadow: 0 3px 9px rgba(55,44,78,.11);
}

.view-switch-btn:focus-visible {
  outline: 3px solid rgba(136,98,201,.28);
  outline-offset: 2px;
}

/* Une seule colonne, donc aucun vide provoqué par la hauteur de la carte
   voisine. L'ordre rendu par React est strictement conservé. */
.app-native .grid.affichage-liste {
  grid-template-columns: minmax(0,1fr) !important;
  gap: 14px;
}

@media (min-width: 700px) {
  .app-native .grid.affichage-liste .native-card {
    display: grid;
    grid-template-columns: clamp(210px, 23vw, 286px) minmax(0,1fr);
    grid-template-rows: auto;
    align-items: stretch;
    min-height: 184px;
    border-radius: 22px;
  }

  .app-native .grid.affichage-liste .native-card > .native-link-visual,
  .app-native .grid.affichage-liste .native-card > .native-idea-visual,
  .app-native .grid.affichage-liste .native-card > .native-video-visual {
    grid-column: 1;
    grid-row: 1;
    align-self: stretch;
    width: 100%;
    min-height: 184px;
    height: 100%;
    margin: 0;
    border-radius: 0;
  }

  .app-native .grid.affichage-liste .native-card > .native-card-body {
    grid-column: 2;
    grid-row: 1;
    min-width: 0;
    min-height: 184px;
    padding: 16px 18px 14px;
  }

  .app-native .grid.affichage-liste .native-link-visual {
    padding: 50px 20px 24px;
  }

  .app-native .grid.affichage-liste .native-idea-visual.has-image,
  .app-native .grid.affichage-liste .native-idea-image,
  .app-native .grid.affichage-liste .native-idea-visual .inspi-vide.native-idea-image {
    max-height: none;
    border-radius: 0;
  }

  .app-native .grid.affichage-liste .native-video-visual {
    aspect-ratio: auto;
  }

  .app-native .grid.affichage-liste .native-video-visual img,
  .app-native .grid.affichage-liste .native-video-visual iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .app-native .grid.affichage-liste .native-card-title {
    font-size: 19px;
  }

  .app-native .grid.affichage-liste .native-card-desc {
    max-width: 920px;
    -webkit-line-clamp: 3;
  }

  .app-native .grid.affichage-liste .native-card-action {
    max-width: 920px;
  }

  .app-native .grid.affichage-liste .native-card-foot {
    padding-top: 8px;
  }
}

/* Sur téléphone, grille et liste seraient toutes deux réduites à une seule
   colonne. Montrer un interrupteur qui ne change presque rien serait une
   fausse commande : il apparaît dès que deux colonnes deviennent possibles. */
@media (max-width: 699px) {
  .view-switch {
    display: none;
  }

  .app-native .grid.affichage-liste {
    gap: 11px;
  }

  .app-native .grid.affichage-liste .native-card {
    border-radius: 18px;
  }
}


/* ========================================================================== 
   26 · APERÇU COMPACT DANS « TOUT » + UNE NOTE VIDÉO VISIBLE

   « Tout » sert à balayer rapidement la collection. Les rubriques Liens,
   Vidéos et Idées conservent leurs cartes détaillées. La classe est posée
   par React uniquement quand l’onglet courant est « Tout » : aucun contenu
   n’est supprimé, le clic ouvre toujours la fiche complète.
   ========================================================================== */

.app-native .native-card-compact .native-card-title {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.app-native .native-card-compact .native-card-desc,
.app-native .native-card-compact .native-card-desc.seul {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.app-native .native-card-compact .native-action-text {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.app-native .native-card-compact .native-idea-quote {
  -webkit-line-clamp: 3;
}

.app-native .native-card-compact .native-card-foot .card-tags {
  flex-wrap: nowrap;
  overflow: hidden;
}

.app-native .native-card-compact .native-card-foot .tag {
  flex: 0 0 auto;
}

.app-native .native-tags-more {
  background: #eee9f5;
  color: #665c73;
  font-weight: 800;
}

.app-native .native-more-notes {
  margin: -1px 0 8px;
  padding: 6px 9px;
  border-color: #e5deee;
  background: #faf8fc;
  font-size: 12px;
  text-align: left;
}

.app-native .native-card-compact .native-note-add input {
  min-height: 36px;
  padding-top: 7px;
  padding-bottom: 7px;
}

.app-native .native-card-compact .native-note-add button {
  min-width: 36px;
}

/* En grille multi-colonnes, les trois familles de média reçoivent la même
   hauteur dans l’onglet Tout. L’affichage Liste garde sa propre géométrie. */
@media (min-width: 700px) {
  .app-native .grid.affichage-grille .native-card-compact > .native-link-visual,
  .app-native .grid.affichage-grille .native-card-compact > .native-video-visual {
    height: 184px;
    min-height: 184px;
    aspect-ratio: auto;
  }

  .app-native .grid.affichage-grille .native-card-compact > .native-idea-visual {
    height: 184px;
    min-height: 184px;
  }

  .app-native .grid.affichage-grille .native-card-compact .native-idea-image,
  .app-native .grid.affichage-grille .native-card-compact .native-idea-visual .inspi-vide.native-idea-image {
    min-height: 184px;
    max-height: 184px;
  }
}


/* ========================================================================== 
   27 · LIENS — CARTE DOCUMENT COMPACTE

   Une vidéo possède une miniature utile ; une idée peut posséder une image
   ou devenir elle-même un objet visuel. Un lien, lui, n'a généralement qu'un
   favicon. L'agrandir dans un grand panneau décoratif créait du volume sans
   information et rendait trente-cinq fiches presque identiques.

   Le lien est donc traité comme un document enregistré : identité de la
   source en bandeau court, titre immédiatement lisible, aperçu limité et
   métadonnées contenues. L'ordre de la grille et toutes les actions restent
   inchangés.
   ========================================================================== */

@media (min-width: 720px) {
  .app-native .native-link-card > .native-link-visual {
    flex-direction: row;
    justify-content: flex-start;
    gap: 13px;
    min-height: 82px;
    height: 82px;
    padding: 14px 58px 14px 16px;
    border-bottom: 1px solid #eee8f4;
    background:
      radial-gradient(circle at 92% 12%, rgba(204,79,150,.10), transparent 34%),
      linear-gradient(135deg, #f0f4fc 0%, #f5f0fa 58%, #fbf1f7 100%);
  }

  .app-native .native-link-card > .native-link-visual::before {
    width: 88px;
    height: 88px;
    right: -40px;
    top: -48px;
    opacity: .62;
  }

  .app-native .native-link-card > .native-link-visual::after {
    display: none;
  }

  .app-native .native-link-card .native-link-emblem {
    width: 48px;
    height: 48px;
    border-radius: 15px;
    box-shadow: 0 7px 18px rgba(55,44,78,.10);
  }

  .app-native .native-link-card .native-link-emblem img {
    inset: 11px;
    width: 26px;
    height: 26px;
    border-radius: 7px;
  }

  .app-native .native-link-card .native-link-domain {
    max-width: calc(100% - 65px);
    margin: 0;
    color: #4f475c;
    font-size: 13.5px;
    line-height: 1.25;
  }

  .app-native .native-link-card .native-quick-open {
    top: 50%;
    right: 13px;
    width: 36px;
    height: 36px;
    border-radius: 11px;
    transform: translateY(-50%);
    box-shadow: 0 5px 14px rgba(33,26,43,.10);
  }

  .app-native .native-link-card .native-quick-open:hover {
    transform: translateY(calc(-50% - 1px));
  }

  /* Dans Liens, le contexte suffit. Dans Tout, la petite étiquette reste
     utile pour distinguer le type au premier regard. */
  .app-native .native-link-card:not(.native-card-compact) .native-media-badge {
    display: none;
  }

  .app-native .native-link-card.native-card-compact .native-media-badge {
    position: static;
    flex: 0 0 auto;
    min-height: 25px;
    padding: 4px 7px;
    border: 1px solid rgba(255,255,255,.88);
    border-radius: 8px;
    box-shadow: none;
    font-size: 10px;
  }

  /* La source est déjà portée par le bandeau ; la répéter dans le corps
     ajoutait une ligne sans information nouvelle. Le statut reste visible. */
  .app-native .native-link-card .native-card-kicker > .native-source-icon,
  .app-native .native-link-card .native-card-kicker > .native-source-label {
    display: none;
  }

  .app-native .native-link-card .native-card-kicker {
    min-height: 28px;
    margin-bottom: 5px;
  }

  .app-native .native-link-card .native-card-body {
    padding: 13px 15px 13px;
  }

  .app-native .native-link-card .native-card-title {
    display: -webkit-box;
    margin-bottom: 7px;
    overflow: hidden;
    font-size: 17.5px;
    line-height: 1.24;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-card-desc,
  .app-native .native-link-card .native-card-desc.seul {
    display: -webkit-box;
    margin-bottom: 9px;
    overflow: hidden;
    font-size: 13.5px;
    line-height: 1.42;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-card-action {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin-bottom: 8px;
    padding: 7px 9px;
    border-radius: 9px;
  }

  .app-native .native-link-card .native-card-action b {
    flex: 0 0 auto;
    margin: 1px 0 0;
    white-space: nowrap;
  }

  .app-native .native-link-card .native-action-text {
    display: -webkit-box;
    min-width: 0;
    overflow: hidden;
    line-height: 1.35;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-repere {
    max-width: 100%;
    margin-bottom: 8px;
  }

  .app-native .native-link-card .native-card-foot .card-tags {
    flex-wrap: nowrap;
    overflow: hidden;
  }

  .app-native .native-link-card .native-card-foot .tag {
    flex: 0 0 auto;
  }
}

/* L'affichage Liste conserve une vraie zone d'identité à gauche : le nouveau
   bandeau court de la grille ne doit pas devenir une bande horizontale perdue
   dans une colonne large. */
@media (min-width: 700px) {
  .app-native .grid.affichage-liste .native-link-card > .native-link-visual {
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 10px;
    height: 100%;
    min-height: 184px;
    padding: 32px 20px 22px;
    border-bottom: 0;
    border-right: 1px solid #eee8f4;
  }

  .app-native .grid.affichage-liste .native-link-card .native-link-domain {
    max-width: 90%;
    text-align: center;
  }

  .app-native .grid.affichage-liste .native-link-card .native-quick-open {
    top: 13px;
    right: 13px;
    transform: none;
  }

  .app-native .grid.affichage-liste .native-link-card .native-quick-open:hover {
    transform: translateY(-1px);
  }
}

/* Sur téléphone, la carte latérale existante est déjà la bonne réponse. La
   source reste affichée dans le corps puisque le domaine du rail est masqué. */
@media (max-width: 719px) {
  .app-native .native-link-card .native-card-title {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-card-desc,
  .app-native .native-link-card .native-card-desc.seul {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-action-text {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .app-native .native-link-card .native-card-foot .card-tags {
    flex-wrap: nowrap;
    overflow: hidden;
  }
}

/* ==========================================================================
   LOT 1 — STABILISATION DES INTERACTIONS (04-08-2026)
   ========================================================================== */

/* La carte ne doit jamais devenir le bloc conteneur d'un menu fixed et ne
   doit plus rogner ses popovers. Le clipping reste limité aux médias. */
.app-native .native-card {
  overflow: visible;
  transition: box-shadow .16s ease, border-color .16s ease;
}
.app-native .native-card:hover {
  transform: none;
}
.native-link-visual,
.native-video-visual {
  border-radius: calc(var(--native-radius-xl) - 1px) calc(var(--native-radius-xl) - 1px) 0 0;
}

/* Menus rendus par portail dans document.body. */
.menu-voile {
  z-index: 299;
}
.menu {
  z-index: 300;
  min-width: 224px;
  max-width: min(300px, calc(100vw - 16px));
  max-height: calc(100vh - 16px);
  padding: 4px;
  overflow-x: hidden;
  overflow-y: auto;
  border-radius: 12px;
  box-shadow: 0 16px 48px rgba(39,35,52,.18);
  overscroll-behavior: contain;
}
.menu button {
  min-height: 44px;
  padding: 0 13px;
  border-radius: 8px;
}
.menu button:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: -2px;
  background: var(--line-soft);
}
.menu button:disabled {
  opacity: .45;
  cursor: not-allowed;
}
.menu button.danger {
  color: var(--rouge, #b3242c);
}
.menu button.danger:hover,
.menu button.danger:focus-visible {
  background: var(--rouge-soft, #fbe3e4);
}

/* Changement direct du statut : plus de cycle à l'aveugle. */
.native-status-wrap {
  position: relative;
  display: flex;
  margin-left: auto;
  flex: 0 0 auto;
}
.native-status {
  position: relative;
  min-height: 32px;
  margin-left: 0;
  padding: 5px 9px;
  font-size: 12px;
  font-weight: 700;
}
.native-status::after {
  content: "";
  position: absolute;
  inset: -6px;
}
.native-status svg {
  flex: 0 0 auto;
  opacity: .68;
}
.statut-menu {
  min-width: 220px;
}
.statut-menu-item {
  justify-content: flex-start;
}
.statut-menu-item > span:nth-child(2) {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.statut-menu-item.actif {
  background: var(--violet-soft);
  color: var(--ink);
}
.statut-menu-dot {
  width: 9px;
  height: 9px;
  flex: 0 0 auto;
  border-radius: 999px;
}
.statut-menu-dot.status-gris { background: var(--gris); }
.statut-menu-dot.status-bleu { background: var(--blue); }
.statut-menu-dot.status-violet { background: var(--violet); }
.statut-menu-dot.status-magenta { background: var(--magenta); }
.statut-menu-dot.status-vert { background: var(--vert); }
.statut-menu-dot.status-rouge { background: var(--rouge); }
.statut-menu-dot.status-orange { background: var(--orange); }
.statut-menu-dot.status-ocre { background: var(--ocre); }
.statut-menu-dot.status-pink { background: var(--pink); }

/* Cibles tactiles sûres sans agrandir visuellement tous les pictogrammes. */
.app-native .native-more {
  position: relative;
  width: 40px;
  height: 40px;
  color: var(--muted);
}
.app-native .native-more::after {
  content: "";
  position: absolute;
  inset: -2px;
}
.app-native .native-more:hover,
.app-native .native-more:focus-visible {
  color: var(--ink);
  background: var(--line-soft);
}

/* Tous les boutons désactivés disent visuellement la même chose. */
.btn:disabled,
button:disabled {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
}
.sel-bar .btn-danger {
  border: 1px solid rgba(255,255,255,.18);
  background: transparent;
  color: #ffd7dc;
}
.sel-bar .btn-danger:not(:disabled):hover,
.sel-bar .btn-danger:not(:disabled):focus-visible {
  background: var(--rouge, #b3242c);
  color: #fff;
}

/* En sélection, la coche occupe l'emplacement du badge de type au lieu de
   recouvrir le statut. */
.sel-coche {
  top: 8px;
  right: auto;
  left: 8px;
  width: 32px;
  height: 32px;
  font-size: 16px;
}
.sel-wrap .native-media-badge {
  visibility: hidden;
}

/* Une création de statut n'est jamais enregistrée sans nom. */
.statut-nouveau {
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--line-soft);
}
.statut-nouveau > label {
  display: block;
  margin-bottom: 7px;
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}
.statut-nouveau-ligne {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.statut-nouveau-ligne input {
  flex: 1 1 180px;
  min-width: 0;
  min-height: 44px;
  padding: 9px 11px;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  background: var(--card);
  color: var(--ink);
  font: inherit;
  outline: none;
}
.statut-nouveau-ligne input:focus {
  border-color: var(--violet);
  box-shadow: 0 0 0 3px var(--violet-soft);
}
.statut-nouveau-ligne .btn {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 9px 14px;
}

/* Les feuilles existantes deviennent de vraies zones modales au clavier. */
.overlay,
.confirm-overlay {
  overscroll-behavior: contain;
}
.sheet[role="dialog"],
.confirm-box[role="dialog"] {
  outline: none;
}
.sheet {
  overscroll-behavior: contain;
}
.sheet:focus-visible,
.confirm-box:focus-visible {
  box-shadow: 0 0 0 3px var(--violet-soft), var(--shadow-hover);
}

@media (max-width: 640px) {
  .native-status {
    max-width: 124px;
    font-size: 12px;
  }
  .statut-nouveau-ligne .btn {
    flex: 1 1 auto;
  }
}


/* ── Ajustements validés — espaces sombres + icône de lien centrée ─────── */
@media (min-width: 720px) {
  .app-native .native-link-card > .native-link-visual {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 132px;
    height: 132px;
    padding: 38px 18px 22px;
    background:
      radial-gradient(circle at 92% 12%, rgba(204,79,150,.10), transparent 34%),
      radial-gradient(circle at 12% 88%, rgba(85,136,214,.14), transparent 28%),
      linear-gradient(135deg, #eef3fc 0%, #f3eef9 58%, #faeef6 100%);
  }

  .app-native .native-link-card .native-link-emblem {
    width: 58px;
    height: 58px;
    border-radius: 18px;
    box-shadow: 0 8px 20px rgba(55,44,78,.11);
  }

  .app-native .native-link-card .native-link-emblem img {
    inset: 14px;
    width: 30px;
    height: 30px;
    border-radius: 8px;
  }

  .app-native .native-link-card .native-link-domain {
    max-width: 88%;
    margin: 0;
    text-align: center;
    color: #5c556a;
    font-size: 12.5px;
    line-height: 1.25;
  }

  .app-native .native-link-card .native-quick-open {
    top: 13px;
    right: 13px;
    transform: none;
  }

  .app-native .native-link-card .native-quick-open:hover {
    transform: translateY(-1px);
  }
}


/* Réorganisation du profil : groupes plus lisibles et badge plan visible. */
.profile-sub .plan-ecusson { background: linear-gradient(135deg, #e0a92c, #c07d10); color: #fff; border-color: transparent; }
.profile-sub .plan-ecusson.p-payant { background: var(--violet); }
.profile-sub .plan-ecusson.p-gratuit { background: var(--line-soft); color: var(--muted); border-color: transparent; }
.profile-groups { display: grid; gap: 14px; margin-top: 8px; }
.profile-group,
.profile-footer-actions { background: var(--card); border: 1.5px solid var(--line); border-radius: 16px; overflow: hidden; }
.profile-group-title { margin: 0; padding: 12px 16px 8px; font-size: 11px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); }
.profile-footer-actions .profile-group-title { padding: 4px 6px 6px; }
.profile-group .sect { margin: 0; border: 0; border-top: 1px solid var(--line-soft); border-radius: 0; box-shadow: none; }
.profile-group .sect:first-of-type { border-top: 0; }
.profile-group .sect-tete { padding: 14px 16px; }
.profile-group .sect-tete:hover { background: var(--paper); }
.profile-group .sect-corps { padding: 4px 16px 16px; border-top: 1px solid var(--line-soft); }
.profile-footer-actions { display: grid; gap: 8px; padding: 10px; }
.profile-footer-actions .btn { width: 100%; justify-content: flex-start; }
.profile-footer-actions .btn.deconnexion { justify-content: center; }
.app-native .profile-groups { gap: 12px; }
.app-native .profile-group,
.app-native .profile-footer-actions { border-radius: 18px; background: rgba(255,255,255,.92); }
.app-native .profile-group-title { color: var(--native-faint, var(--faint)); padding: 13px 16px 8px; }
.app-native .profile-group .sect-tete { padding: 15px 16px; }

/* ---- Ajustements de finition : marque + panneau profil ----------------- */
/* Les anciens rattrapages de la grille (align-self, margin-top de compensation,
   slogan en ::after) sont retirés : la marque est décrite une seule fois,
   plus haut, en flex. Empiler des correctifs sur un modèle qui ne tenait pas
   revenait à caler un meuble bancal avec des allumettes. */

.app-native .profile-card {
  align-items: flex-start;
}

.app-native .profile-card > div:last-child {
  min-width: 0;
  flex: 1 1 auto;
}

.app-native .profile-name {
  font-size: 15.5px;
  line-height: 1.18;
}

.app-native .profile-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 5px;
}

.app-native .profile-sub .plan-ecusson {
  min-height: 30px;
  padding: 6px 12px 6px 10px;
  border-color: transparent;
  color: #fff;
  background: var(--teinte);
  box-shadow: 0 8px 18px rgba(66, 50, 104, .14);
}

.app-native .profile-sub .plan-ecusson.p-fondateur {
  background: linear-gradient(135deg, #e0a92c, #c07d10);
}

.app-native .profile-sub .plan-ecusson.p-gratuit {
  background: var(--line-soft);
  color: var(--muted);
  box-shadow: none;
}

.app-native .profile-sub .plan-ecusson .plan-nom {
  display: inline;
}

.app-native .profile-sub .plan-ecusson svg,
.app-native .profile-sub .plan-ecusson .svg-wrap {
  display: block;
  flex: 0 0 auto;
}

.app-native .profile-groups {
  gap: 12px;
}

.app-native .profile-group-title {
  padding: 13px 16px 8px;
  color: #8f87a1;
  letter-spacing: .14em;
}

.app-native .profile-group,
.app-native .profile-footer-actions {
  background: rgba(255,255,255,.94);
  border-radius: 18px;
}

@media (max-width: 719px) {
  .app-native .profile-sub {
    gap: 6px;
  }

  .app-native .profile-sub .plan-ecusson {
    padding: 5px 10px 5px 9px;
  }
}


/* ==========================================================================
   28 · HABILLAGE UNIQUE — marque, espaces, navigation, recherche, filtres
   ==========================================================================
   Le haut de page était fait de trois îlots translucides autonomes, collés
   à trois hauteurs différentes (barre à top:7, recherche à top:73), séparés
   par du vide. Le contenu ne passait pas À TRAVERS ces îlots — il passait
   ENTRE eux, et réapparaissait dans leurs gouttières. C'est ce qui donnait
   les fragments de phrases au ras du bord.

   Ici, une seule surface, portée par un conteneur pleine largeur. Même
   transparence, même flou, mêmes couleurs : simplement plus aucune fente.
   Le voile déborde de 22 px sous la dernière rangée et s'y efface en
   dégradé — le contenu se dissout au lieu d'être tranché par une arête.
   ========================================================================== */

/* La page occupe toute la largeur ; ce sont les CONTENUS qui sont bornés et
   centrés, chacun de leur côté. Sinon la barre collante redevient un
   rectangle posé au milieu de l'écran, avec du vide sur les côtés. */
.app.app-native {
  max-width: none;
  padding: 0 0 150px;
}

.app-native .grid {
  max-width: 1480px;
  margin-left: auto;
  margin-right: auto;
  padding-left: clamp(10px, 3vw, 38px);
  padding-right: clamp(10px, 3vw, 38px);
}

.app-native .chrome {
  position: sticky;
  top: 0;
  z-index: 40;
}

/* L'ancrage du défilement, coupé.

   Quand l'habillage se déplie, il reprend 62 px de hauteur réelle et tout
   ce qui est dessous redescend. Le navigateur croit bien faire : il corrige
   scrollTop pour que le contenu lu reste immobile. Résultat, remonter la
   page faisait osciller la position autour du seuil de repli, et l'en-tête
   clignotait. Ici, le seul élément qui change de taille au-dessus de la
   zone lue est l'habillage lui-même — il n'y a donc rien à ancrer. */
html, body { overflow-anchor: none; }

.app-native .chrome-inner {
  max-width: 1480px;
  margin: 0 auto;
  padding: 10px clamp(10px, 3vw, 38px) 0;
  transition: padding .2s ease;
}

/* Le verre. Le dégradé n'est pas linéaire : une estompe linéaire de 30 px
   laissait une ligne de titre s'installer À MOITIÉ dedans, à demi lisible,
   donc lue comme un défaut plutôt que comme un effet. Les paliers gardent
   la teinte pleine jusqu'aux 12 derniers pixels puis lâchent vite — le
   texte traverse la zone trouble au lieu d'y séjourner.

   Le flou est à 26 px et non 20 : ce qu'on retire en couvrant (teinte à
   76 %), il faut le reprendre en brouillant. Une teinte à 76 % ne laisse
   pas repasser le texte, mais elle laisse repasser les APLATS de couleur
   des cartes, qui suffisent à salir la barre. */
.app-native .chrome::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 0; right: 0; top: 0; bottom: -22px;
  background: linear-gradient(
    to bottom,
    rgba(252,250,254,.76) 0%,
    rgba(252,250,254,.76) calc(100% - 12px),
    rgba(252,250,254,.52) calc(100% - 6px),
    rgba(252,250,254,0) 100%);
  -webkit-backdrop-filter: saturate(175%) blur(26px);
  backdrop-filter: saturate(175%) blur(26px);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 12px), rgba(0,0,0,.55) calc(100% - 6px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 12px), rgba(0,0,0,.55) calc(100% - 6px), transparent 100%);
  opacity: 0;
  transition: opacity .18s ease;
}

/* Au repos, rien ne passe derrière : le voile ne sert à rien et le fond
   aquarelle reste net. Il apparaît au premier pixel de défilement. */
body.colle .app-native .chrome::before { opacity: 1; }

/* ── Les rangées perdent leur habillage propre ─────────────────────────── */
.app-native .topbar {
  position: static;
  top: auto;
  height: 52px;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
  transition: height .2s ease;
}

.app-native .espaces-bar {
  margin: 8px 0 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* Le plafond ne sert qu'à animer le repli au défilement : il doit donc
     être largement supérieur à la hauteur réelle de la bande. À 44 px il
     coupait le bloc de contexte dès qu'il passait à la ligne sur
     téléphone — la bande était rognée et le contexte se superposait au
     bandeau suivant. */
  max-height: 240px;
  opacity: 1;
  transition: max-height .25s ease, opacity .15s ease, margin-top .2s ease;
}

.app-native .searchbar {
  position: static;
  top: auto;
  margin: 9px 0 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  box-shadow: none;
  transition: margin-top .2s ease;
}

.app-native .vues-bar { margin: 8px 0 0; }
.app-native .maj-bar,
.app-native .enrich-bar { margin: 9px 0 0; }

.app-native .filters {
  margin: 0;
  padding: 9px 0 10px;
  transition: padding .2s ease;
}

.app-native .brand-tagline {
  max-height: 16px;
  transition: max-height .2s ease, opacity .15s ease;
}

/* Le nom de l'espace courant, qui prend le relais quand la rangée se
   replie. Perdre le contexte en cours de lecture serait pire que garder
   la rangée. */
.app-native .espace-actif {
  display: none;
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: 999px;
  background: #2a2537;
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .02em;
  line-height: 1.35;
  max-width: 120px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── État réduit ──────────────────────────────────────────────────────────
   Au défilement, l'habillage abandonne ce qui relève de l'identité — le
   slogan, la hauteur de la marque, la rangée des espaces — et garde ce qui
   relève de l'outil : chercher et filtrer. */
body.defile .app-native .chrome-inner { padding-top: 7px; }
body.defile .app-native .topbar { height: 42px; }
body.defile .app-native .brand .brand-mark { width: 32px; height: 32px; border-radius: 10px; }
body.defile .app-native .brand-tagline { max-height: 0; opacity: 0; }
body.defile .app-native .espaces-bar { max-height: 0; opacity: 0; margin-top: 0; overflow: hidden; }
body.defile .app-native .espace-actif { display: inline-block; }
body.defile .app-native .searchbar { margin-top: 7px; }
body.defile .app-native .search-input { min-height: 44px; padding-top: 10px; padding-bottom: 10px; }
body.defile .app-native .filters { padding: 7px 0 8px; }

@media (max-width: 719px) {
  .app.app-native { padding: 0 0 calc(var(--tb-haut) + 78px); }
  .app-native .topbar { height: 48px; }
  body.defile .app-native .topbar { height: 42px; }
  /* Réduit, le compteur total cède sa place au nom de l'espace : il est
     déjà porté par la barre du bas, et deux chiffres pour la même chose
     dans un même écran ne servent qu'à tronquer le titre. */
  body.defile .app-native .brand .count { display: none; }
}

@media (min-width: 720px) {
  .app-native .types-haut { margin: 9px auto 0; }
  body.defile .app-native .types-haut { margin-top: 6px; }
}

/* ── Grand écran : une seule ligne d'en-tête ───────────────────────────────
   Marque à gauche, navigation au centre, commandes à droite — dont l'accès
   au profil, qui reste dans .topbar-right. Trois rangées deviennent une :
   c'est le seul vrai levier sur la SURFACE de la barre, et la surface est
   ce qui la fait paraître lourde, pas son opacité.

   « display: contents » sur l'en-tête fait de ses deux enfants des cases de
   la grille, sans toucher au HTML partagé avec le téléphone. */
@media (min-width: 1100px) {
  .app-native .chrome-inner {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 22px;
  }
  .app-native .chrome-inner > * { grid-column: 1 / -1; }
  .app-native .topbar { display: contents; }
  .app-native .brand { grid-column: 1; grid-row: 1; }
  .app-native .topbar-right { grid-column: 3; grid-row: 1; margin-left: 0; }
  .app-native .types-haut {
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    width: 100%;
    max-width: 620px;
    justify-self: center;
  }
  body.defile .app-native .types-haut { margin: 0; }
  body.defile .app-native .th-item { min-height: 36px; padding: 6px 12px; }
}

/* La pastille de synchronisation change de texte — « Synchronisé »,
   « Synchronisation... », « Hors ligne » — donc de largeur, et tout ce qui
   la suit se décalait à chaque changement d'état. Largeur plancher fixe. */
@media (min-width: 720px) {
  .app-native .sync-wrap { min-width: 142px; justify-content: center; }
  /* Sauf quand il se tait : la largeur plancher servait à empêcher les
     boutons voisins de danser au changement de texte. Sans texte, il n'y a
     plus rien à stabiliser — et 142 px de vide à côté d'un point de 8 px se
     lisent comme un bouton cassé. Il reprend la taille de ses voisins,
     l'avatar et l'icône de groupe. */
  .app-native .sync-wrap.sync-muet {
    min-width: 40px;
    width: 40px;
    padding: 0;
  }
}


/* ==========================================================================
   29 · VUE LISTE — coins arrondis et hauteurs de ligne
   ==========================================================================
   Trois défauts corrigés ensemble, parce qu'ils ont la même racine : la
   colonne de gauche était traitée comme une image posée dans un cadre, au
   lieu d'être traitée comme le flanc de la carte.
   ========================================================================== */

@media (min-width: 700px) {

  /* 1 · Les coins. La carte est arrondie à 22 px, son visuel était à angle
     droit : l'angle vif dépassait visuellement du galet et donnait ce coin
     carré à gauche. Le visuel reprend l'arrondi de la carte, moins le
     pixel de bordure — sinon un liseré clair apparaît dans la courbe. */
  .app-native .grid.affichage-liste .native-card > .native-link-visual,
  .app-native .grid.affichage-liste .native-card > .native-idea-visual,
  .app-native .grid.affichage-liste .native-card > .native-video-visual {
    border-radius: 21px 0 0 21px;
    overflow: hidden;
  }

  .app-native .grid.affichage-liste .native-idea-visual.has-image,
  .app-native .grid.affichage-liste .native-idea-image,
  .app-native .grid.affichage-liste .native-idea-visual .inspi-vide.native-idea-image {
    border-radius: 21px 0 0 21px;
  }

  /* 2 · La hauteur des lignes.

     L'image d'une idée gardait « min-height: 205px » et sa proportion
     d'origine : une photo en portrait de 286 px de large réclamait près de
     380 px de haut, et c'est ELLE qui dictait la hauteur de la ligne. Le
     texte à droite, lui, n'en remplissait que 150 — d'où les 200 px de
     blanc entre la note et la date. Ce n'était pas un choix de confort,
     c'était l'image qui commandait.

     La parade est celle déjà employée pour les vignettes vidéo : l'image
     sort du flux. Posée en absolu dans son flanc, elle ne pèse plus rien
     dans le calcul de hauteur, et la ligne se recale sur son contenu. Le
     recadrage reste identique — « cover » ne perd rien de plus qu'avant. */
  .app-native .grid.affichage-liste .native-card > .native-idea-visual {
    position: relative;
    min-height: 0;
  }

  .app-native .grid.affichage-liste .native-idea-image,
  .app-native .grid.affichage-liste .native-idea-visual .inspi-vide.native-idea-image {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    min-height: 0;
    max-height: none;
    margin: 0;
    object-fit: cover;
  }

  /* Une idée en TEXTE peut, elle aussi, faire déborder la ligne : la
     citation était coupée à cinq lignes, ce qui autorise près de 160 px
     rien que pour elle. Quatre suffisent à donner le sens, et la fiche
     complète reste à un clic. */
  .app-native .grid.affichage-liste .native-idea-quote {
    -webkit-line-clamp: 4;
    font-size: 19px;
  }

  /* 3 · L'homogénéité. Le plancher commun reste 184 px ; ce qui change,
     c'est que plus rien ne le dépasse sans raison venant du TEXTE. */
  .app-native .grid.affichage-liste .native-card > .native-idea-visual,
  .app-native .grid.affichage-liste .native-card > .native-link-visual,
  .app-native .grid.affichage-liste .native-card > .native-video-visual {
    min-height: 184px;
  }
}

/* Sur téléphone, une carte VIDÉO reste empilée : son visuel est en haut,
   il garde donc ses deux coins hauts. */
@media (max-width: 699px) {
  .app-native .grid.affichage-liste .native-card > .native-video-visual {
    border-radius: 17px 17px 0 0;
  }
}

/* Une carte de LIEN, elle, est en deux colonnes sur téléphone — dans les
   deux modes d'affichage. Son visuel est donc un FLANC, pas un bandeau.

   Il portait pourtant deux coins arrondis en haut : celui de gauche épouse
   bien la carte, mais celui de DROITE creuse une encoche en plein milieu,
   là où le flanc rencontre le texte. C'est l'entaille visible sur la
   capture. L'arrondi passe donc à gauche, des deux côtés, et la jonction
   avec le corps redevient une ligne droite.

   Les coins de gauche ne peuvent pas être supprimés : la carte laisse
   déborder son contenu (overflow visible, nécessaire aux menus), donc un
   angle droit dépasserait de la courbe du galet. */
@media (max-width: 719px) {
  .app-native .native-link-card > .native-link-visual,
  .app-native .grid.affichage-liste .native-link-card > .native-link-visual {
    border-radius: 20px 0 0 20px;
  }
}


/* ==========================================================================
   30 · LE LECTEUR — la promesse rendue visible
   ==========================================================================
   Une vidéo de 285 px sans commande ne fait rien voir et n'appelle rien.
   Ici : une scène large, un bouton qui capture l'instant, des pastilles qui
   y ramènent. Le reste de la feuille est celui de l'application.
   ========================================================================== */

@media (min-width: 680px) {
  /* 540 px est la largeur d'un formulaire, pas d'un lecteur. On monte à
     1000, borné à 94 % de la fenêtre pour ne jamais toucher les bords. */
  .sheet-lecteur { max-width: min(1000px, 94vw); }
}

.lecteur { display: flex; flex-direction: column; gap: 12px; }

/* La scène garde le 16/9 quoi qu'il arrive : l'API YouTube remplace le div
   par une iframe qu'elle dimensionne elle-même, et sans cadre imposé elle
   se rabattrait sur 150 px de haut. */
.lecteur-scene {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 16px;
  background: #17141f;
  box-shadow: 0 14px 34px rgba(30,24,44,.22);
}
.lecteur-scene iframe,
.lecteur-scene > div {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.lecteur-barre { display: flex; align-items: center; gap: 10px; }

/* Le bouton persistant. Il porte le temps courant, donc son libellé change
   deux fois par seconde : largeur plancher et chiffres tabulaires, sinon il
   respire et fait bouger tout ce qui l'entoure. */
.lecteur-noter {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 168px;
  min-height: 44px;
  padding: 11px 18px;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.lecteur-noter:disabled { opacity: .5; cursor: progress; }

.lecteur-champ { margin: 0; }
.lecteur-champ input { min-height: 46px; font-size: 15.5px; }

.lecteur-notes { gap: 8px; }
.lecteur-vide { margin: 2px 0 0; }

/* La pastille de temps est devenue un BOUTON quand elle commande le
   lecteur : sans ces trois lignes, elle hériterait du gris et de la bordure
   des boutons de formulaire au lieu de garder l'aspect qu'elle avait en
   lien. Le geste change, pas l'apparence. */
button.ts {
  border: 0;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

@media (max-width: 679px) {
  .lecteur-scene { border-radius: 13px; }
  .lecteur-noter { min-width: 0; flex: 1 1 auto; padding: 11px 14px; }
}


/* ==========================================================================
   30 · UN SEUL JEU D'ICÔNES
   ==========================================================================
   Le panneau Profil et la feuille « Nouvelle trouvaille » portaient des
   émojis — 🏷️ 📦 🌍 💾 🔑 ✉️ 📱, 💡 🔗 🎬. Trois défauts, tous invisibles
   un par un et voyants ensemble : le dessin CHANGE selon l'appareil (le
   paquet d'un iPhone n'est pas celui d'un Android), la couleur ne suit
   jamais celle de l'interface, et le niveau de détail jure avec un trait
   de 2 px. C'est le genre de détail qui fait dire « bricolé » sans qu'on
   sache dire pourquoi.

   Ils sont remplacés par le jeu maison, complété de six dessins tracés sur
   la même grille de 24. Reste ici à leur donner leur place et leur teinte.
   ========================================================================== */

/* Les icônes de section : la taille vient du composant, la couleur de la
   feuille. --muted et non --ink : elles accompagnent le libellé, elles ne
   le concurrencent pas. */
.sect-icone {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  color: var(--muted);
}

.sect-ouverte > .sect-tete .sect-icone { color: var(--violet); }

/* Les tuiles de choix : l'émoji tenait tout seul, une icône au trait a
   besoin d'un support. Une pastille douce, celle des sources de cartes —
   la même forme revient, donc rien de nouveau à apprendre. */
.choix-emoji {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 0;
}

.choix-type button:hover .choix-emoji {
  background: #fff;
}

/* Un lien peut porter l'habit d'un bouton : « Ouvrir le lien » DOIT être
   une ancre — clic du milieu, ouvrir dans un onglet, copier l'adresse.
   Un bouton en JavaScript perdrait ces trois gestes. */
a.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  text-decoration: none;
}


/* ── La barre des types : plus de liseré blanc, un fond un cran plus dense
   ──────────────────────────────────────────────────────────────────────
   Le contour clair servait à détacher la pastille d'un fond lui aussi
   clair. Avec une teinte plus dense, le contraste fait ce travail tout
   seul — et un trait blanc sur du blanc cassé ne dessine rien, il ternit.
   L'onglet actif, lui, reste blanc pur : c'est l'écart entre les deux qui
   se creuse, donc la lecture qui gagne. */
.app-native .types-haut {
  border: 0;
  background: rgba(219,212,234,.86);
}


/* ==========================================================================
   31 · ESPACES PARTAGÉS — contexte, jeton, teinte
   ==========================================================================
   Le bleu de cette section ne désigne pas un LIEU, il désigne un ÉTAT :
   « ici, d'autres vous voient ». Perso est sombre, c'est-à-dire sans
   couleur — donc sans regard. C'est un code binaire, apprenable en une
   session, et c'est pour ça qu'il ne faut PAS donner une couleur propre à
   chaque espace : le bleu cesserait de vouloir dire quelque chose, et à six
   espaces on taperait dans l'orange, le vert et le rouge, c'est-à-dire dans
   la sémantique des statuts.

   L'identité d'un espace est portée ailleurs : par son jeton, une initiale
   dans un disque, sur une palette fermée définie côté JavaScript.
   ========================================================================== */

:root {
  --espace: #4a7fc9;
  --espace-soft: #e4edfb;
}

/* La page change de température. Aucun objet de plus à interpréter : la
   vision périphérique suffit à savoir où l'on est. */
body.dans-espace { background: #edf1f8; }
body.dans-espace .app-native .chrome::before {
  background: linear-gradient(
    to bottom,
    rgba(249,251,254,.76) 0%,
    rgba(249,251,254,.76) calc(100% - 12px),
    rgba(249,251,254,.52) calc(100% - 6px),
    rgba(249,251,254,0) 100%);
}
/* Dans un espace partage, deux des trois taches changent de teinte.
   La regle visait les <span> ; elle repeint maintenant le fond entier,
   puisque les taches y vivent desormais. */
body.dans-espace .watercolor {
  background:
    radial-gradient(circle 250px at 80px 70px,               var(--blue),   transparent),
    radial-gradient(circle 230px at calc(100% - 80px) 70px,  var(--espace), transparent),
    radial-gradient(circle 215px at calc(40% + 115px) 195px, var(--blue),   transparent);
  background-repeat: no-repeat;
}

/* ── La bande : pastilles à gauche, contexte ancré à droite ─────────────
   RÈGLE DE DÉGRADATION, écrite ici et pas improvisée : les pastilles
   occupent la place restante et DÉFILENT, le bloc de contexte ne bouge
   jamais. C'est ce qui tient à six espaces ou avec un nom long. */
.app-native .espaces-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.app-native .espaces-bar > .chip-row { flex: 1 1 0; min-width: 0; }

/* La barre des espaces porte déjà une règle « .chip.active { background:
   #2a2537 } » plus spécifique : sans reprendre le même niveau de
   sélecteur, la pastille d'un espace resterait sombre et le code
   privé/partagé ne se verrait pas. */
.app-native .espaces-bar .chip-perso.active { background: #2a2537; color: #fff; }
.app-native .espaces-bar .chip-espace.active {
  background: var(--espace);
  color: #fff;
  box-shadow: 0 5px 14px rgba(74,127,201,.24);
}
.app-native .espaces-bar .chip-espace.active .chip-badge { color: rgba(255,255,255,.72); }

/* Le jeton : initiale dans un disque, couleur tirée de la palette fermée. */
.jeton-espace {
  display: inline-flex; align-items: center; justify-content: center;
  width: 19px; height: 19px; margin-right: 7px;
  border-radius: 50%; color: #fff;
  font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 10px; font-weight: 800; line-height: 1;
  vertical-align: -4px; flex: 0 0 auto;
}
.chip-espace.active .jeton-espace { box-shadow: 0 0 0 1.5px rgba(255,255,255,.6); }

.contexte-espace {
  display: flex; align-items: center; gap: 10px;
  flex: 0 0 auto; min-width: 0;
  padding: 6px 10px 6px 8px;
  border-radius: 12px;
  background: rgba(228,237,251,.7);
  box-shadow: inset 3px 0 0 var(--espace);
}

.pile-membres { display: flex; align-items: center; cursor: help; }
.pile-membres > span {
  width: 26px; height: 26px; margin-left: -8px;
  border-radius: 50%; display: grid; place-items: center;
  color: #fff; font-family: 'Bricolage Grotesque', sans-serif;
  font-size: 11px; font-weight: 700;
  box-shadow: 0 0 0 2px #fbfcff;
}
.pile-membres > span:first-child { margin-left: 0; }

/* CE QUI RESTE EN PERMANENCE : le nombre de membres, rien de plus.
   « tout ce que vous déposez ici leur est visible » s'apprend UNE fois ;
   au bout d'une semaine c'est invisible, mais ça aura coûté sa place tous
   les jours. L'explication vit au survol de la pile et dans l'état vide. */
.contexte-txt { font-size: 12.5px; color: var(--ink); white-space: nowrap; }
.contexte-txt b { font-weight: 800; }

.btn-inviter {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  min-height: 34px; padding: 7px 13px;
  border: 0; border-radius: 10px; cursor: pointer;
  background: var(--espace); color: #fff;
  font: inherit; font-size: 13px; font-weight: 700;
}
.btn-inviter:hover { filter: brightness(1.06); }

/* Palier intermédiaire : sous 1000 px, le contexte se réduit aux avatars
   et au bouton ; le nombre de membres se lit au survol. */
@media (max-width: 999px) {
  .contexte-txt { display: none; }
}
@media (max-width: 719px) {
  .app-native .espaces-bar { gap: 9px; }
  .contexte-espace { flex: 1 1 100%; }
  .pile-membres > span { width: 24px; height: 24px; font-size: 10px; }
  .contexte-txt { display: inline; }
  .btn-inviter { margin-left: auto; }
}

/* ── Le bouton de dépôt porte l'espace ─────────────────────────────────── */
body.dans-espace .app-native .fab { background: var(--espace); box-shadow: 0 15px 32px rgba(74,127,201,.34); }
.fab-ou { font-weight: 600; opacity: .8; }
@media (max-width: 640px) { .fab-ou { display: none; } }

/* ── L'attribution, dans l'en-tête de carte ────────────────────────────── */
.carte-auteur {
  display: inline-flex; align-items: center; gap: 6px; flex: 0 0 auto;
  color: var(--espace); font-size: 11.5px; font-weight: 700;
}
.carte-auteur > span {
  width: 18px; height: 18px; border-radius: 50%;
  display: grid; place-items: center; color: #fff;
  font-family: 'Bricolage Grotesque', sans-serif; font-size: 9px; font-weight: 800;
}

/* ── Le modèle de visibilité, enseigné une fois ────────────────────────── */
.avis-espace {
  display: flex; align-items: center; gap: 10px;
  margin: 9px 0 0; padding: 8px 12px;
  border-radius: 12px; background: var(--espace-soft); color: var(--ink);
  font-size: 12.5px; line-height: 1.35;
}
.avis-espace svg { flex: 0 0 auto; color: var(--espace); }
.avis-espace span { flex: 1 1 auto; min-width: 0; }
.avis-espace button {
  flex: 0 0 auto; width: 26px; height: 26px;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--espace); cursor: pointer; font-size: 13px;
}
.avis-espace button:hover { background: rgba(255,255,255,.6); }

/* ── L'état vide d'un espace ───────────────────────────────────────────── */
/* J'avais masqué la recherche, les filtres et les onglets dans un espace
   vide, au motif qu'une commande incapable de produire un résultat fait
   croire que le contenu existe et qu'on l'a mal demandé.

   Décision de Sabrina : ils restent, dans TOUS les espaces. Le raisonnement
   qui l'emporte est celui de la constance — une barre d'outils qui
   apparaît et disparaît selon le contenu se réapprend à chaque fois, et
   l'écart entre un espace vide et un espace plein est déjà dit par le bloc
   guidé qui occupe la grille. */

/* L'ancien état vide — « Aucune trouvaille pour l'instant. » — n'a plus
   lieu d'être quand le bloc guidé occupe la place : deux messages vides
   l'un sous l'autre disent deux fois rien. */
body.espace-vide .app-native .grid > .empty { display: none; }

.vide-espace {
  grid-column: 1 / -1;
  text-align: center;
  padding: 42px 24px 38px;
  border: 1.5px dashed var(--espace-soft);
  border-radius: 22px;
  background: rgba(255,255,255,.6);
}
.vide-espace-rond {
  width: 62px; height: 62px; margin: 0 auto 16px;
  border-radius: 20px; display: grid; place-items: center; color: #fff;
  background: linear-gradient(135deg, var(--espace), #7ba7e0);
  box-shadow: 0 12px 26px rgba(74,127,201,.28);
}
.vide-espace h2 { margin: 0 0 8px; font-size: 21px; letter-spacing: -.03em; }
.vide-espace p {
  max-width: 470px; margin: 0 auto 20px;
  color: var(--native-muted); font-size: 14.5px; line-height: 1.5;
}
.vide-espace-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
/* Deux boutons côte à côte partagent la même hauteur ET la même ligne de
   texte : l'un porte une bordure de 1,5 px, l'autre non — sans correction
   ils se décalent d'un pixel et demi. */
.vide-espace-actions .btn {
  flex: 0 1 auto; min-height: 48px; padding: 0 22px;
  display: inline-flex; align-items: center; justify-content: center;
}
.btn-inviter-grand { background: var(--espace); color: #fff; border: 0; }
.btn-inviter-grand:hover { filter: brightness(1.06); }


/* ==========================================================================
   32 · RACCOURCIS DE RÉCUPÉRATION
   ==========================================================================
   Le tag affiché sur une carte était le chemin le plus court vers une
   recherche — et le seul qui n'était pas branché. Trois cents étiquettes
   dessinées, positionnées au bon endroit, et inertes.
   ========================================================================== */

/* Un tag cliquable doit se voir cliquable AVANT le clic, sinon personne ne
   l'essaie. Le curseur et le survol suffisent : lui donner une bordure ou
   une ombre en ferait un bouton parmi des dizaines d'autres sur la carte. */
.tag-clic {
  border: 0;
  cursor: pointer;
  font: inherit;
  font-size: 10.8px;
  font-weight: 600;
  transition: filter .12s ease, transform .12s ease;
}
.tag-clic:hover { filter: brightness(.94); }
.tag-clic:active { transform: scale(.96); }
.tag-clic:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 1px;
}

/* La cible tactile : le dessin reste petit, la zone d'appui ne l'est pas.
   Un pseudo-élément déborde de 5 px tout autour sans déplacer un pixel de
   mise en page — on passe d'environ 20 px de haut à 30. */
.tag-clic { position: relative; }
.tag-clic::after {
  content: "";
  position: absolute;
  inset: -5px -3px;
}

/* Le compteur d'onglet à zéro : présent, mais en retrait. Il répond à la
   question « zéro ou pas calculé ? » sans attirer l'œil sur un vide. */
.th-count.compte-zero,
.tb-count.compte-zero {
  background: transparent;
  color: var(--faint);
  box-shadow: none;
}
.tb-item.active .tb-count.compte-zero { background: transparent; color: var(--faint); }

/* « Tout effacer » : une commande de retrait, donc discrète — un contour,
   pas un aplat. Elle ne doit pas peser plus lourd que les filtres qu'elle
   annule. */
.chip.chip-effacer {
  border-style: dashed;
  border-color: var(--line);
  color: var(--muted);
  font-weight: 700;
}
.chip.chip-effacer:hover {
  border-color: var(--magenta);
  color: var(--magenta-text);
}


/* ==========================================================================
   33 · ANNULER, ET LE MENU DE STATUT
   ========================================================================== */

/* Le bouton « Annuler » du message. Il doit se voir sans voler la vedette au
   texte : c'est une porte de sortie, pas une invitation. */
.toast-action {
  flex: 0 0 auto;
  margin-left: 2px;
  padding: 7px 13px;
  border: 1px solid rgba(255,255,255,.32);
  border-radius: 999px;
  background: rgba(255,255,255,.1);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
}
.toast-action:hover { background: rgba(255,255,255,.2); }
.toast-action:active { transform: scale(.97); }

/* ── La pastille de statut ouvre un menu ────────────────────────────────
   Le cycle était aveugle : rien à l'écran ne disait quelle valeur venait
   après, il fallait cliquer trois fois pour atteindre la quatrième, et il
   n'était pas atteignable au clavier. Le menu rend les valeurs directement
   accessibles et nomme la seconde intention — filtrer — au lieu de la
   laisser deviner. */
.native-status svg { margin-left: 1px; opacity: .55; }

.statut-menu-liste { display: grid; }

.statut-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 42px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: 14.5px;
  text-align: left;
  cursor: pointer;
}
.statut-menu-item:hover { background: var(--line-soft); }
.statut-menu-item.actif { background: var(--violet-soft); font-weight: 700; }
.statut-menu-item > span:nth-child(2) { flex: 1; min-width: 0; }
.statut-menu-item svg { color: var(--violet); }

.statut-menu-dot {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: currentColor;
}

/* La ligne de filtrage, séparée par un filet : deux intentions dans un
   même menu doivent être visiblement deux. */
.statut-menu-filtrer {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: 42px;
  margin-top: 4px;
  padding: 0 12px;
  border: 0;
  border-top: 1px solid var(--line);
  border-radius: 0 0 8px 8px;
  background: none;
  color: var(--violet);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.statut-menu-filtrer:hover { background: var(--violet-soft); }
.statut-menu-n {
  margin-left: auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 12px;
  font-weight: 800;
}

/* Le badge d'un lien devient l'action d'ouverture : il prend la place de la
   flèche, en haut à droite, et se comporte comme un lien. */
/* L'étiquette d'ouverture est ancrée EN HAUT À DROITE, en toutes
   circonstances — et les deux règles ci-dessous expliquent pourquoi il faut
   le dire aussi fort.

   1. Sur l'onglet Tout, les cartes sont « compactes » : une ancienne règle
      repasse le badge en « position: static », ce qui en fait un élément de
      la colonne centrée. Il se retrouvait donc au MILIEU du visuel, au-
      dessus du logo, au lieu du coin. C'est ce que montrait la capture.

   2. Sur l'onglet Liens, une autre règle le MASQUE — elle datait du temps
      où le domaine écrit juste en dessous rendait l'étiquette redondante.
      Depuis qu'elle porte l'ouverture du lien, la masquer supprime
      l'action : la carte n'a plus aucun moyen d'ouvrir le site depuis son
      visuel, puisque la flèche a été retirée.

   Ces deux règles restent valables pour l'étiquette d'une IDÉE, qui n'est
   qu'un libellé. Elles ne le sont plus pour celle d'un lien. */
.native-media-badge.badge-ouvrir,
.app-native .native-link-card .native-media-badge.badge-ouvrir,
.app-native .native-link-card.native-card-compact .native-media-badge.badge-ouvrir,
.app-native .native-link-card:not(.native-card-compact) .native-media-badge.badge-ouvrir {
  display: inline-flex;
  position: absolute;
  z-index: 5;
  top: 13px;
  left: auto;
  right: 13px;
  min-height: 30px;
  padding: 6px 10px;
  border: 0;
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(33,26,43,.08);
  font-size: 11.5px;
  text-decoration: none;
  transition: transform .15s ease, background .15s ease;
}
.native-media-badge.badge-ouvrir:hover {
  transform: translateY(-1px);
  background: #fff;
}
@media (max-width: 719px) {
  .app-native .native-link-card .native-media-badge.badge-ouvrir { left: auto; right: 9px; }
}


/* ── La source d'un lien devient un filtre ──────────────────────────────
   Le domaine sous le logo nommait la provenance sans rien permettre. Il
   devient « tout ce qui vient d'ici ». Comme pour les tags, il doit se
   voir cliquable AVANT le clic — mais discrètement : c'est une étiquette
   d'abord, un bouton ensuite. */
.native-link-domain.domaine-clic {
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 800;
  color: #5c556a;
  cursor: pointer;
  padding: 3px 8px;
  border-radius: 8px;
  transition: background .12s ease, color .12s ease;
}
.native-link-domain.domaine-clic:hover {
  background: rgba(255,255,255,.7);
  color: var(--violet);
}
.native-link-domain.domaine-clic:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 1px;
}


/* ── Le titre d'un lien se voit comme un lien ───────────────────────────
   Depuis que l'étiquette « ↗ Ouvrir » est retirée, le titre porte SEUL
   l'ouverture du site. Or il était déclaré sans soulignement, et sa
   couleur ne changeait qu'au survol — c'est-à-dire jamais au doigt.
   Personne ne pouvait deviner qu'il menait quelque part.

   Un soulignement discret et permanent, décalé sous la ligne de base pour
   ne pas couper les jambages : le raccourci cesse d'être un secret sans
   alourdir la carte. */
/* AU REPOS, LE TITRE RESTE UN TITRE.

   Le soulignement permanent posait deux problèmes réels, pas de goût :
   c'était le seul trait rectiligne d'une carte entièrement faite de formes
   arrondies — et deux traits sur les titres à deux lignes — et surtout un
   titre souligné cesse de se lire comme un titre, il se lit comme de
   l'hypertexte dans un paragraphe.

   La marque se pose donc à côté du mot. Au survol et au focus, le
   soulignement revient : le repos est propre, le geste est confirmé. */
.card-title a.titre-lien {
  text-decoration: none;
  transition: color .15s ease;
}
.card-title a.titre-lien:hover {
  color: var(--violet);
  text-decoration: underline;
  text-decoration-color: rgba(136, 98, 201, .65);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

/* La flèche : une MARQUE, pas un verbe. C'est la convention qu'on croise
   partout depuis Wikipédia — « ça sort de la page » — et c'est ce qui la
   distingue de la flèche seule dans une pastille, que j'avais déconseillée :
   là, elle devait porter à elle seule le sens d'une commande. */
.titre-fleche {
  color: rgba(136, 98, 201, .6);
  font-size: .72em;
  font-weight: 700;
  white-space: nowrap;
}
.card-title a.titre-lien:hover .titre-fleche { color: var(--violet); }

.card-title a.titre-lien:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 2px;
  border-radius: 4px;
}


/* ── Les nouveautés d'un espace partagé ─────────────────────────────────
   La puce et le point reposent sur la MÊME donnée : la date de dernière
   visite. Ils vont donc ensemble — un marqueur sans raccourci laisse
   chercher à la main, un raccourci sans marqueur ne dit pas quoi lire. */
.chip.chip-neuf {
  border-color: var(--magenta-soft);
  color: var(--magenta-text);
  font-weight: 700;
}
.chip.chip-neuf .chip-n {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  margin-left: 6px;
  border-radius: 999px;
  background: var(--magenta);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
}
.chip.chip-neuf.active {
  border-color: var(--magenta);
  background: var(--magenta-soft);
}

/* Le point « ajoutée depuis votre dernière visite ».
   Un point, pas un bandeau : il doit se voir en balayant la grille et ne
   rien coûter à la lecture de la carte. Le liseré blanc le détache du
   visuel quel qu'en soit le fond. */
.native-card { position: relative; }
.point-neuf {
  position: absolute;
  z-index: 6;
  top: 11px;
  left: 11px;
  width: 11px;
  height: 11px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--magenta);
  box-shadow: 0 0 0 3px rgba(255,255,255,.92);
  cursor: help;
}


/* ── Une seule icône par type, partout ──────────────────────────────────
   L'emplacement portait tantôt une image (le favicon), tantôt un caractère
   typographique, tantôt une lettre. Il porte maintenant une icône du jeu
   maison, comme les onglets — donc une forme vectorielle qui prend la
   couleur qu'on lui donne, à la même graisse que le reste. */
.native-source-icon.icone-type {
  display: grid;
  place-items: center;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 0;
}
.native-source-icon.icone-type svg { display: block; }

/* Une carte d'idée n'a pas de source : sa ligne d'en-tête n'a donc que son
   icône. Sans cette règle, le libellé vide laisserait une gouttière. */
.native-source-label:empty { display: none; }

/* La puce de tri s'allume comme les autres quand elle agit. Elle affichait
   déjà QUEL tri, jamais qu'il n'était plus celui par défaut. */
.chip.chip-tri.active {
  border-color: var(--violet);
  background: var(--violet-soft);
  color: var(--violet);
}


/* ── L'avertissement de doublon ─────────────────────────────────────────
   Un AVIS, pas une erreur : ocre et non rouge. La personne est la seule à
   savoir si le doublon est volontaire — on garde parfois la même page chez
   soi et dans un espace partagé, ou une version longue et une courte.
   Interdire coûterait plus cher que le doublon. */
.form-doublon {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 8px 0 0;
  padding: 9px 11px;
  border-radius: 10px;
  background: var(--orange-soft);
  color: var(--orange-text);
  font-size: 13px;
  line-height: 1.4;
}
.form-doublon svg { flex: 0 0 auto; margin-top: 1px; }
.form-doublon b { font-weight: 800; }


/* ── « Sélectionner » rejoint les outils ────────────────────────────────
   Ce n'est pas un filtre mais un mode d'édition. Il quitte la rangée des
   filtres pour se ranger à droite, contre le sélecteur d'affichage, avec
   un filet qui marque la frontière entre « ce que je vois » et « ce que je
   fais ». */
.chip-row > .chip.chip-mode {
  margin-left: auto;
  flex: 0 0 auto;
}
@media (min-width: 700px) {
  .chip-row > .chip.chip-mode {
    margin-left: auto;
    box-shadow: -13px 0 0 -12px var(--line);
  }
}


/* ── Le bouton « Garder » sur une carte d'espace partagé ────────────────
   Discret mais visible : c'est une action fréquente, pas une commande
   principale. Il ne concurrence pas la pastille de statut. */
.btn-garder-carte {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: 0 0 auto;
  min-height: 28px;
  padding: 4px 10px;
  border: 1px solid var(--espace-soft);
  border-radius: 999px;
  background: #fff;
  color: var(--espace);
  cursor: pointer;
  font: inherit;
  font-size: 11.5px;
  font-weight: 700;
  transition: background .12s ease, border-color .12s ease;
}
.btn-garder-carte:hover {
  background: var(--espace-soft);
  border-color: var(--espace);
}


/* ── Le passage trouvé ──────────────────────────────────────────────────
   Une carte de résultat doit montrer POURQUOI elle est là. Un fond jaune
   pâle, la couleur d'encre inchangée : on repère le mot sans que le reste
   devienne illisible. Pas de gras — il déformerait la ligne et ferait
   sauter le texte au moindre caractère tapé. */
mark.surlignage {
  background: #fff3bf;
  color: inherit;
  border-radius: 3px;
  padding: 0 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.native-idea-visual mark.surlignage,
.native-idea-quote mark.surlignage {
  background: rgba(255,255,255,.34);
  color: #fff;
}


/* ── Retirer un repère depuis la carte ──────────────────────────────────
   Il fallait ouvrir « Modifier », vider le champ et enregistrer : trois
   gestes et une feuille pleine, pour effacer un mot. La croix suit le
   modèle des autres puces retirables, et le message « Annuler » couvre la
   fausse manœuvre. */
.card-repere .repere-x {
  margin-left: auto;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--faint);
  font: inherit;
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.card-repere .repere-x:hover { background: rgba(0,0,0,.06); color: var(--magenta-text); }
.card-repere .repere-x:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }


/* ── Les outils, à côté de la piste qui défile ──────────────────────────
   La rangée des filtres est en deux parties : les puces défilent dans leur
   propre piste, les outils sont ancrés à droite. Ils ne se superposent
   jamais, puisqu'ils ne partagent plus le même espace. */
.app-native .filters {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.app-native .filters > .chip-row {
  flex: 1 1 auto;
  min-width: 0;
}
.filters-outils {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
}
/* Sous 700 px le sélecteur d'affichage est masqué : il ne reste que
   « Sélectionner », qui n'a plus besoin d'un filet de séparation. */
@media (min-width: 700px) {
  .filters-outils { padding-left: 10px; border-left: 1px solid var(--line); }
}


/* ── LA BARRE DE SÉLECTION, REPENSÉE POUR LE TÉLÉPHONE ──────────────────
   Elle était un galet arrondi qui réclamait sa largeur naturelle puis se
   repliait quand l'écran ne suivait pas. Sur un iPhone, six commandes
   donnaient deux rangées inégales chevauchant la barre de navigation :
   « Partager » deux fois plus large que « Supprimer », et la croix pendue
   au bout.

   Deux décisions. D'abord la barre du bas DISPARAÎT pendant la sélection :
   on n'y navigue pas, et sa place revient aux actions — au passage, un
   pouce visant « Supprimer » ne peut plus toucher « Profil ». Ensuite les
   boutons prennent des colonnes ÉGALES, deux par rangée : une barre
   d'actions se lit comme un tableau, pas comme une phrase. */
@media (max-width: 719px) {
  body.sel-actif .tabbar-v2 { display: none; }
  body.sel-actif .fab { display: none; }

  .app-native .sel-bar {
    left: 8px;
    right: 8px;
    bottom: calc(8px + env(safe-area-inset-bottom, 0px));
    width: auto;
    max-width: none;
    transform: none;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    padding: 10px;
    border-radius: 18px;
  }
  /* Première rangée : l'état à gauche, la sortie à droite. */
  .app-native .sel-bar > b { flex: 1 1 auto; order: 1; }
  .app-native .sel-bar > .iconbtn { flex: 0 0 auto; order: 2; }
  /* Les actions ensuite, deux par rangée, toutes de la même largeur. */
  .app-native .sel-bar > .btn {
    order: 3;
    flex: 1 1 calc(50% - 4px);
    min-width: 0;
    justify-content: center;
    padding: 11px 8px;
    font-size: 14px;
  }
}


/* ── Le filtre par statut, dans le menu ancré ───────────────────────────
   Trois colonnes régulières : la pastille de couleur, le nom, le compte,
   aligné à droite en chiffres de même largeur pour qu'on puisse comparer
   les nombres d'un coup d'œil.

   La ligne active prend la teinte de SON statut et non un violet
   générique : le menu doit montrer de quelle couleur on filtre, comme la
   puce qui restera dans la barre après le choix. */
.statut-menu-dot.statut-menu-point.c-vide {
  background: transparent;
  border: 1.5px solid var(--faint);
}
.statut-menu-nom { flex: 1 1 auto; text-align: left; }
.statut-menu-n {
  flex: 0 0 auto;
  min-width: 22px;
  text-align: right;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.statut-menu-item.actif.s-gris    { background: rgba(125,116,144,.14); }
.statut-menu-item.actif.s-bleu    { background: var(--blue-soft); }
.statut-menu-item.actif.s-violet  { background: var(--violet-soft); }
.statut-menu-item.actif.s-magenta { background: var(--magenta-soft); }
.statut-menu-item.actif.s-vert    { background: var(--vert-soft); }
.statut-menu-item.actif.s-rouge   { background: var(--rouge-soft); }
.statut-menu-item.actif.s-orange  { background: var(--orange-soft); }
.statut-menu-item.actif.s-ocre    { background: var(--ocre-soft); }
.statut-menu-item.actif .statut-menu-n { color: inherit; opacity: .75; }


/* ── Une offre annoncée, pas éteinte ────────────────────────────────────
   Grisée, elle se lirait comme une panne — et surtout le prix du
   Fondateur perdrait son point de comparaison : c'est à côté de « 59 €
   l'année » que « 69 € pour toujours » veut dire quelque chose. La tuile
   garde donc ses couleurs ; seul un badge dit qu'elle n'est pas encore
   ouverte. */
.plan-mini-tuile.a-venir { position: relative; }
.plan-badge-venir {
  display: inline-block;
  align-self: flex-start;
  margin: 2px 0 4px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--violet-soft);
  color: var(--violet);
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
}


/* ── Les demandes d'offre, dans l'administration ────────────────────────
   Une demande n'arrive nulle part ailleurs : « Prévenez-moi » n'envoie
   aucun courriel, et le message pré-rempli du Fondateur peut ne jamais
   partir. C'est ici, et seulement ici, qu'on les voit. */
.admin-interets { margin: 14px 0 18px; }
.admin-interets h4 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 8px;
  font-size: 14.5px;
}
.admin-interets-n {
  min-width: 20px;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--violet);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
}
.admin-interets-liste { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.admin-interets-liste li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fff;
  font-size: 13px;
}
.admin-interets-liste b { font-size: 13px; }
.admin-interets-offre { color: var(--violet); font-weight: 700; }
.admin-interets-date { margin-left: auto; color: var(--muted); font-size: 12px; }


/* ── LA TUILE D'OFFRE EST ELLE-MÊME LA COMMANDE ─────────────────────────
   Un lien de trois mots au fond d'un bloc de deux cents pixels de haut
   est une cible minuscule dans une grande zone morte : on lit l'offre, on
   veut la prendre, et il faut viser. La tuile entière réagit donc — au
   survol, au doigt et au clavier.

   Le fond reste TRÈS clair au survol : le prix est écrit en violet, et un
   violet sur un violet soutenu deviendrait illisible. On change la
   bordure et l'élévation, qui ne coûtent aucun contraste. */
.plan-mini-tuile.cliquable {
  cursor: pointer;
  transition: border-color .14s ease, box-shadow .14s ease,
              background .14s ease, transform .1s ease;
}
.plan-mini-tuile.cliquable:hover,
.plan-mini-tuile.cliquable:focus-visible {
  border-color: var(--violet);
  background: #fbf9ff;
  box-shadow: 0 6px 18px rgba(136, 98, 201, .16);
  outline: none;
}
/* L'appui : la tuile s'enfonce légèrement. Sur mobile, c'est le seul
   retour possible — il n'y a pas de survol. */
.plan-mini-tuile.cliquable:active {
  transform: scale(.985);
  box-shadow: 0 2px 8px rgba(136, 98, 201, .18);
}
.plan-mini-tuile.cliquable:focus-visible {
  box-shadow: 0 0 0 3px var(--violet-soft), 0 6px 18px rgba(136, 98, 201, .16);
}

/* La ligne du bas nomme ce qui va se passer. Ce n'est plus un bouton —
   elle se souligne au survol de la TUILE, pour que le lien de cause à
   effet soit visible. */
.plan-mini-action {
  margin-top: 10px;
  color: var(--violet);
  font-size: 13.5px;
  font-weight: 700;
}
.plan-mini-tuile.cliquable:hover .plan-mini-action,
.plan-mini-tuile.cliquable:focus-visible .plan-mini-action {
  text-decoration: underline;
  text-underline-offset: 3px;
}
/* Une offre pas encore ouverte s'annonce en gris : le violet est la
   couleur de ce qu'on peut prendre tout de suite. */
.plan-mini-tuile.a-venir .plan-mini-action { color: var(--muted); }


/* La portée du « à vie ». Petit, mais lisible — 12 px et --muted passent
   le seuil AA. Une mention qu'on ne peut pas lire ne protège personne. */
.plan-mini-avie {
  margin-top: 7px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}


/* Une carte qui s'ouvre le dit — discrètement. Le curseur suffit à la
   souris ; au doigt, c'est le fait que ça réponde qui l'apprend. On évite
   l'ombre au survol : sur une grille de deux cents cartes, chaque
   déplacement de souris déclencherait une animation. */
.native-card.carte-ouvrable { cursor: pointer; }
.native-card.carte-ouvrable a,
.native-card.carte-ouvrable button,
.native-card.carte-ouvrable input,
.native-card.carte-ouvrable textarea,
.native-card.carte-ouvrable .tag { cursor: revert; }

/* ==========================================================================
   40 · GRILLE À HAUTEUR FIXE — 290 px
   ==========================================================================

   Ajouté le 11 août 2026. Bloc ADDITIF : il ne modifie aucune règle
   existante, il les surcharge par l'ordre. Le retirer restaure exactement
   l'état d'avant.

   POURQUOI. Les tuiles n'avaient pas la même hauteur, et le coupable
   n'était pas la longueur des textes — titres et descriptions étaient
   déjà coupés à deux lignes. C'étaient des BLOCS ENTIERS qui
   apparaissaient et disparaissaient : description, « À en faire »,
   repère, « Compléter cette trouvaille », tags. Quatre blocs optionnels,
   seize combinaisons de hauteur.

   LE CALCUL, et non un chiffre choisi au hasard :
     vignette                                        140
     titre     2 lignes de 16 px x 1,24 + marge       45
     créneau   2 lignes de 13,5 px x 1,42 + marge     43
     pied      le bouton ⋮ fait 40 px, pas 26         42
     marges du corps                                  24
                                                     ---
                                                     294, porté à 300

   LE PIED FAIT 42 px ET NON 28. Le bouton « ⋮ » mesure 40 x 40 : c'est
   la règle des cibles tactiles sûres, déjà dans la feuille. La première
   version budgétait 26 px, le bouton dépassait de 14 et se retrouvait
   rogné par le bas de la carte. Le pied se dimensionne sur SON contenu
   réel, pas sur celui des tags.

   CE QUI DISPARAÎT DE LA LIGNE D'EN-TÊTE. Le type et la source y étaient
   répétés : le domaine est déjà écrit EN GRAND dans la vignette d'un
   lien, et l'icône du type est déjà portée par l'onglet. La ligne coûtait
   26 px pour redire deux choses visibles.
   Ce qui reste change de place, jamais de sens :
     - statut  : à cheval sur la jonction vignette / contenu
     - auteur  : en haut à GAUCHE de la vignette (espaces partagés)
     - Garder  : en haut à DROITE, séparément
   Jamais les deux du même côté : ils formeraient un pavé qui heurterait
   les titres incrustés dans les miniatures.

   LE CRÉNEAU PARTAGÉ. Une seule zone de deux lignes accueille, dans cet
   ordre de priorité : la description, sinon « À en faire », sinon le
   repère. La hauteur ne bouge pas selon ce qui l'occupe.

   CE QUI RESTE À FAIRE EN JAVASCRIPT (second temps) : la rangée de
   signaux dans le pied avec les compteurs (◎ ↳ ⏱ 2), et le surlignage
   du mot cherché dans « À en faire », qui en est privé aujourd'hui.
   ========================================================================== */

.app-native .grid.affichage-grille{
  align-items: stretch;
}

/* ── La carte : hauteur unique, quel que soit le type et le contenu ────── */
.app-native .grid.affichage-grille .native-card{
  /* La hauteur de la vignette est déclarée UNE FOIS ici. Tout ce qui se
     pose à sa jonction — statut, tags, badge « traduit » — s'y réfère.
     Première version : chacun portait « top: 120px » en dur, et la seule
     règle adaptée au téléphone était celle du statut. Sur une colonne, la
     vignette passe en 16/9 et devient plus haute : les tags restaient à
     120 px, c'est-à-dire au milieu de l'image. */
  --vignette-h: 140px;
  /* OÙ SE POSE LE CENTRE du statut et des tags, par rapport au bas de la
     vignette. Deux points, tous les deux appris à l'usage :

     · UNE SEULE DÉCLARATION pour les deux. Ils portaient chacun leur
       valeur en dur, à deux endroits du fichier : rien n'empêchait qu'un
       réglage n'en corrige qu'un, et ils se seraient désalignés sans que
       personne le voie.

     · LE CENTRE, ET NON LE BORD DU HAUT. Alignés par le haut, ils
       paraissaient décalés : le statut mesure 30 px et les tags 28, donc
       leurs milieux ne tombaient pas à la même hauteur. Les centrer
       supprime l'écart quelles que soient leurs tailles — un statut au
       nom plus long, une police plus grande, et ils resteront alignés.

     La valeur dit de combien le centre REMONTE au-dessus du bas de la
     vignette. Mesuré : à 12 px, le centre se pose 5 px plus haut qu'avec
     l'ancien calage par le bord. */
  --jonction: 12px;
  height: 300px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ── 1 · La vignette : 140 px pour les trois types ─────────────────────── */
.app-native .grid.affichage-grille .native-card > .native-link-visual,
.app-native .grid.affichage-grille .native-card > .native-video-visual,
.app-native .grid.affichage-grille .native-card-compact > .native-link-visual,
.app-native .grid.affichage-grille .native-card-compact > .native-video-visual{
  flex: 0 0 140px;
  height: 140px;
  min-height: 140px;
  max-height: 140px;
  aspect-ratio: auto;
}

/* L'IDÉE PERD SON ENCART EN RETRAIT.

   La vignette d'une idée était posée à 11 px des bords, avec ses propres
   arrondis. Conséquence invisible mais gênante : sa jonction avec le
   contenu ne tombait pas à la même hauteur que celle d'un lien ou d'une
   vidéo. La pastille de statut, les tags et le badge « traduit » — tous
   calés sur cette jonction — se retrouvaient décalés sur les seules
   cartes « idée ».

   Le retrait disparaît. On sait qu'il s'agit d'une idée : c'est écrit
   « NOTE PERSONNELLE » dessus. Les trois types se règlent désormais sur
   la même géométrie, ce qui est la condition d'une grille régulière. */
.app-native .grid.affichage-grille .native-card > .native-idea-visual,
.app-native .grid.affichage-grille .native-card-compact > .native-idea-visual{
  flex: 0 0 140px;
  height: 140px;
  min-height: 140px;
  max-height: 140px;
  margin: 0;
  border-radius: calc(var(--native-radius-xl) - 1px) calc(var(--native-radius-xl) - 1px) 0 0;
}
.app-native .grid.affichage-grille .native-idea-image,
.app-native .grid.affichage-grille .native-idea-visual .inspi-vide.native-idea-image{
  border-radius: calc(var(--native-radius-xl) - 1px) calc(var(--native-radius-xl) - 1px) 0 0;
}
.app-native .grid.affichage-grille .native-idea-visual .inspi-vide.native-idea-image,
.app-native .grid.affichage-grille .native-idea-image{
  min-height: 0;
  height: 100%;
  max-height: 140px;
}
/* À 140 px, quatre lignes de citation ne tiennent plus. */
.app-native .grid.affichage-grille .native-idea-quote{
  -webkit-line-clamp: 3;
  font-size: 15px;
}
.app-native .grid.affichage-grille .native-link-visual{
  padding: 26px 18px 18px;
}

/* ── 2 · L'en-tête : vidé de ce qui était déjà lisible ailleurs ────────── */
.app-native .grid.affichage-grille .native-card-kicker{
  min-height: 0;
  margin: 0;
}
.app-native .grid.affichage-grille .native-card-kicker > .native-source-icon,
.app-native .grid.affichage-grille .native-card-kicker > .native-source-label{
  display: none;
}

/* Le statut, a cheval sur la jonction. Il ne coute plus aucune hauteur.

   LE TRANSFORM NE PORTE PLUS SUR LE CONTENEUR (.native-status-wrap),
   IL PORTE SUR LE BOUTON (.native-status) SEUL. Meme rendu visuel,
   mecanique differente — et c'est ce qui compte ici.

   Le conteneur .native-status-wrap est aussi le parent du menu de
   statut (MenuCarte), qui est en position:fixed positionne par du
   JavaScript en coordonnees ECRAN (getBoundingClientRect). Regle CSS
   peu connue : des qu'un ANCETRE porte un transform, il devient le
   repere de tout descendant position:fixed, a la place de l'ecran
   entier. Avec le transform sur le conteneur, le menu ET son voile de
   fermeture se retrouvaient repositionnes par rapport a un espace de
   quelques pixels au lieu du viewport — le voile ne couvrait plus le
   bon endroit, et le clic sur une option du menu traversait jusqu'a
   la carte en dessous, qui l'interpretait comme un clic d'ouverture de
   fiche. Change le statut d'une trouvaille (13-14 aout) : la fiche se
   figeait, se videait, puis un second clic ouvrait la fiche complete
   au lieu d'appliquer le statut choisi. Reproduit et mesure avant
   correction — un menu positionne par getBoundingClientRect() se
   retrouvait a une position doublee par rapport au bouton reel des
   qu'un ancetre portait un transform.

   Le bouton, lui, n'est parent de rien : lui donner le transform
   produit le meme centrage visuel sans jamais devenir le repere de
   quoi que ce soit. */
.app-native .grid.affichage-grille .native-card .native-status-wrap{
  position: absolute;
  z-index: 4;
  top: calc(var(--vignette-h) - var(--jonction));
  right: 13px;
  margin: 0;
}
.app-native .grid.affichage-grille .native-card .native-status{
  min-height: 26px;
  border: 1px solid #fff;
  box-shadow: 0 2px 9px rgba(55,44,78,.16);
  transform: translateY(-50%);
}

/* L'auteur : en haut à GAUCHE de la vignette, sur une plaque claire.
   Jamais de texte posé à même l'image : les miniatures YouTube portent
   souvent leur propre titre incrusté. */
.app-native .grid.affichage-grille .native-card .carte-auteur{
  position: absolute;
  z-index: 4;
  top: 11px;
  left: 11px;
  max-width: 46%;
  padding: 3px 9px 3px 3px;
  border: 1px solid rgba(255,255,255,.9);
  border-radius: 999px;
  background: rgba(255,255,255,.9);
  box-shadow: 0 2px 9px rgba(55,44,78,.16);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* « Garder » : en haut à DROITE, séparément. */
.app-native .grid.affichage-grille .native-card .btn-garder-carte{
  position: absolute;
  z-index: 5;
  top: 11px;
  right: 11px;
  border: 1px solid rgba(255,255,255,.9);
  background: rgba(255,255,255,.92);
  box-shadow: 0 2px 9px rgba(55,44,78,.16);
}

/* ── 3 · Le corps ──────────────────────────────────────────────────────── */
.app-native .grid.affichage-grille .native-card-body{
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 19px 14px 10px;
}

/* Le titre occupe deux lignes, présentes ou non. */
.app-native .grid.affichage-grille .native-card-title{
  flex: 0 0 auto;
  height: calc(16px * 1.24 * 2);
  margin: 0 0 5px;
  font-size: 16px;
  line-height: 1.24;
  -webkit-line-clamp: 2;
}

/* ── 4 · Le créneau partagé : description, sinon action, sinon repère ──── */
.app-native .grid.affichage-grille .native-card-desc,
.app-native .grid.affichage-grille .native-card-desc.seul,
.app-native .grid.affichage-grille .native-card-action,
.app-native .grid.affichage-grille .native-repere{
  flex: 0 0 auto;
  height: calc(13.5px * 1.42 * 2);
  margin: 0 0 5px;
  font-size: 13.5px;
  line-height: 1.42;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* LA FILE DE PRIORITÉ, un seul occupant à la fois :
     note horodatée  >  description  >  à en faire  >  repère

   ÉCRITE AVEC LE COMBINATEUR DE FRÈRE, ET NON AVEC :has().

   La première version demandait « ce corps de carte contient-il une
   note ? » — six fois, sur chacune des 249 cartes. Safari maintient pour
   chaque :has() un ensemble d'invalidation, et le réévalue à la moindre
   modification du DOM. Ouvrir un menu ajoute des nœuds : la feuille de
   style repassait sur tout. Quatre secondes pour afficher un popup.

   Le DOM range ces blocs dans l'ordre exact de la priorité — description,
   puis « à en faire », puis repère ; note puis repère sur une vidéo. Un
   simple « ~ » suffit donc : ce qui suit un bloc déjà présent se tait.
   Coût nul, résultat identique. */
.app-native .grid.affichage-grille .native-card-desc ~ .native-card-action,
.app-native .grid.affichage-grille .native-notes ~ .native-card-action,
.app-native .grid.affichage-grille .native-card-desc ~ .native-repere,
.app-native .grid.affichage-grille .native-notes ~ .native-repere,
.app-native .grid.affichage-grille .native-card-action ~ .native-repere{
  display: none;
}

/* « À en faire » PREND LA FORME DU REPÈRE, et non celle d'un paragraphe.

   Première version : la cible était posée devant le texte, sans cadre.
   Elle se lisait comme une puce de liste, pas comme un pictogramme —
   surtout devant une note en markdown qui commence par « ## ».

   Le repère, lui, fonctionne : un encart clair, la flèche en tête, la
   valeur ensuite. Même traitement ici. Les deux champs deviennent deux
   objets de même nature, distingués par leur seul glyphe. */
.app-native .grid.affichage-grille .native-card-action{
  display: flex;
  align-items: flex-start;
  gap: 7px;
  padding: 4px 9px;
  border: 1px solid #e7e1ef;
  border-radius: 9px;
  background: #f7f4fa;
  color: #4b4356;
  font-size: 12.8px;
  line-height: 1.35;
}
.app-native .grid.affichage-grille .native-card-action > b{
  display: none;
}
.app-native .grid.affichage-grille .native-card-action::before{
  content: "\2691\FE0E";
  flex: 0 0 auto;
  margin-top: 1px;
  color: var(--faint);
  font-size: 13px;
  font-weight: 400;
}
.app-native .grid.affichage-grille .native-action-text{
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  min-width: 0;
  line-height: 1.35;
}

/* Le repère, quand il occupe le créneau, reste dans son encart : c'est
   une valeur brute (S3E7, page 142), elle a besoin d'un cadre pour ne
   pas se lire comme une phrase. */
.app-native .grid.affichage-grille .native-repere{
  display: flex;
  align-items: center;
  gap: 7px;
  height: calc(13.5px * 1.42 * 2);
  padding: 4px 9px;
  font-size: 12.8px;
  line-height: 1.35;
}

/* ── Les notes des vidéos ──────────────────────────────────────────────
   La liste des notes et le champ « Ajouter une note » n'étaient pas
   neutralisés : ils débordaient de la carte et se faisaient rogner.

   Le CHAMP de saisie quitte la grille. Une zone de texte sur une tuile
   qu'on parcourt du regard n'est pas une information, c'est une action —
   et elle coûtait 45 px. Elle reste entière dans la fiche.

   La LISTE, elle, occupe le créneau partagé, en tête de la file : sur une
   vidéo, une note horodatée est écrite par vous, la description est celle
   de YouTube. Une seule note tient, celle qui compte — pendant une
   recherche, c'est déjà celle qui correspond, remontée en tête. */
.app-native .grid.affichage-grille .native-note-add,
.app-native .grid.affichage-grille .native-more-notes{
  display: none;
}
.app-native .grid.affichage-grille .native-notes{
  flex: 0 0 auto;
  height: calc(13.5px * 1.42 * 2);
  margin: 0 0 5px;
  overflow: hidden;
}
.app-native .grid.affichage-grille .native-notes > .native-note:nth-of-type(n+2){
  display: none;
}
.app-native .grid.affichage-grille .native-note{
  padding: 5px 8px;
}
.app-native .grid.affichage-grille .native-note .note-text{
  -webkit-line-clamp: 1;
  margin: 0;
}
.app-native .grid.affichage-grille .native-note .note-tools{
  display: none;
}

/* ── Le badge « traduit » ──────────────────────────────────────────────
   Il vivait dans la ligne d'en-tête. Celle-ci ne fait plus de hauteur :
   le badge se posait donc sur le titre. Il rejoint la vignette, en bas à
   GAUCHE — face au statut, qui occupe la droite. */
.app-native .grid.affichage-grille .native-card .badge-traduit{
  position: absolute;
  z-index: 4;
  top: 120px;
  left: 13px;
  margin: 0;
  border: 1px solid #fff;
  box-shadow: 0 2px 9px rgba(55,44,78,.16);
}

/* ── L'idée : la citation tenait sur quatre ou cinq lignes ─────────────
   Hors de l'onglet « Tout », la citation n'était pas ramenée à trois
   lignes : la quatrième se faisait couper en deux par le bas de la
   vignette réduite à 140 px. */
.app-native .grid.affichage-grille .native-idea-quote{
  -webkit-line-clamp: 3;
  font-size: 15.5px;
  line-height: 1.28;
}
.app-native .grid.affichage-grille .native-idea-visual.has-text{
  padding: 14px 15px;
  gap: 8px;
}

/* « Compléter cette trouvaille » quitte la grille : c'est une invitation,
   pas une information, et elle coûtait 40 px sur une carte contrainte.
   Elle reste entière dans la fiche et en affichage Liste. */
.app-native .grid.affichage-grille .native-complete{
  display: none;
}

/* ── 5 · Le pied, toujours collé en bas ────────────────────────────────── */
.app-native .grid.affichage-grille .native-card-foot{
  margin-top: auto;
  flex: 0 0 auto;
  min-height: 40px;
  padding-top: 2px;
}


/* ── 6 · LA RANGÉE DE SIGNAUX ──────────────────────────────────────────
   Posée par le JavaScript en tête du pied. Elle dit ce que le créneau ne
   peut plus montrer : le repère et sa valeur, l'existence d'une action,
   et surtout LE NOMBRE de moments clés — une seule note tient dans le
   créneau, rien ne disait qu'il y en avait trois.

   Elle n'existe qu'en grille. En affichage Liste, les blocs sont entiers
   et la répéter serait un doublon. */
.app-native .native-signaux{
  display: none;
}
.app-native .grid.affichage-grille .native-signaux{
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  min-width: 0;
  margin-left: 2px;
}
.app-native .grid.affichage-grille .sig{
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-width: 0;
  color: var(--faint);
  font-size: 11.5px;
  font-weight: 600;
}
.app-native .grid.affichage-grille .sig-g{
  font-size: 13px;
  line-height: 1;
}
.app-native .grid.affichage-grille .sig-action .sig-v{
  max-width: none;
  font-weight: 600;
}
.app-native .grid.affichage-grille .sig-v{
  max-width: 72px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.app-native .grid.affichage-grille .sig-notes{
  cursor: pointer;
}
.app-native .grid.affichage-grille .sig-notes:hover{
  color: var(--violet);
}
.app-native .grid.affichage-grille .sig-plus{
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--native-line);
  border-radius: 8px;
  background: #fff;
  color: var(--native-muted);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.app-native .grid.affichage-grille .sig-plus:hover{
  border-color: #d9cfe9;
  background: #f7f4fa;
  color: var(--violet);
}

/* Pas deux fois la même chose. Quand un bloc OCCUPE le créneau, son
   signal se tait : il ne resterait qu'un glyphe redondant à côté du
   texte qu'il annonce.
   Le compteur de notes fait exception — il porte un nombre que le
   créneau, limité à une note, ne peut pas donner.

   ÉCRIT À L'ENVERS POUR SE PASSER DE :has(). Plutôt que de demander
   « ce corps contient-il une description ? » sur 249 cartes, on cache le
   signal par défaut et on le RÉVÈLE quand un bloc le précède — donc
   quand il occupe le créneau à sa place. Même résultat, sans ensemble
   d'invalidation à maintenir. */
.app-native .grid.affichage-grille .sig-action,
.app-native .grid.affichage-grille .sig-repere{
  display: none;
}
.app-native .grid.affichage-grille .native-card-desc ~ .native-card-foot .sig-action,
.app-native .grid.affichage-grille .native-notes ~ .native-card-foot .sig-action,
.app-native .grid.affichage-grille .native-card-desc ~ .native-card-foot .sig-repere,
.app-native .grid.affichage-grille .native-notes ~ .native-card-foot .sig-repere,
.app-native .grid.affichage-grille .native-card-action ~ .native-card-foot .sig-repere{
  display: inline-flex;
}

/* Les tags cèdent la place aux signaux : un tag de plus se remplace par
   « +2 », un repère perdu ne se remplace par rien. */
.app-native .grid.affichage-grille .native-card-foot > .card-tags{
  min-width: 0;
  flex: 0 1 auto;
  overflow: hidden;
}


/* ── 7 · LES TAGS PASSENT SUR LA VIGNETTE ──────────────────────────────
   Deux raisons, dont une qui était un vrai défaut.

   LE DÉFAUT. « .card-tags » a « flex-wrap: wrap » par défaut. Le passage
   à « nowrap » n'existait que pour .native-card-compact et pour
   .native-link-card — donc PAS pour les cartes vidéo. Leurs tags
   passaient à la ligne et sortaient de la carte par le bas.

   LA RAISON DE FOND. Le pied portait quatre choses : les signaux, les
   tags, la date et le menu. C'est une de trop pour une ligne de 26 px.
   Les tags remontent sur la vignette, à gauche, face au statut. Le pied
   garde ce qui relève de l'objet lui-même : ses signaux, sa date, son
   menu.

   La date était une SŒUR des tags dans le même conteneur ; le
   JavaScript l'en a sortie, sinon elle serait partie sur l'image avec
   eux. */
.app-native .grid.affichage-grille .native-card-foot > .card-tags{
  position: absolute;
  z-index: 4;
  top: calc(var(--vignette-h) - var(--jonction));
  transform: translateY(-50%);
  left: 13px;
  max-width: calc(100% - 116px);   /* la pastille de statut occupe la droite */
  flex-wrap: nowrap;
  overflow: hidden;
  gap: 5px;
  margin: 0;
}

/* Sur une miniature sombre ou chargée, un tag posé à même l'image devient
   illisible. Même plaque que pour l'auteur et « Garder ». */
.app-native .grid.affichage-grille .native-card-foot > .card-tags > .tag{
  border: 1px solid rgba(255,255,255,.85);
  background: rgba(255,255,255,.92);
  box-shadow: 0 2px 9px rgba(55,44,78,.16);
  white-space: nowrap;
}

/* DEUX TAGS LISIBLES VALENT MIEUX QUE SIX ILLISIBLES.

   Première tentative : les tags rétrécissaient pour tenir dans la place
   disponible. Sur une fiche à six tags, ça donnait « # … » « # a… » —
   on ne savait même plus de quel tag il s'agissait. Un tag qu'on ne peut
   pas lire ne vaut pas mieux qu'un tag absent, et il prend la place.

   Le plafond est donc posé dans le JavaScript, à DEUX, et le compte exact
   part dans le « +4 ». Le CSS ne fait plus que garantir la lisibilité :
   les tags ne rétrécissent pas, et un nom très long s'arrête en « … » à
   la moitié de la largeur — « # prompt-engi… » reste identifiable, ce que
   « # … » n'était pas. */
.app-native .grid.affichage-grille .native-card-foot > .card-tags > .tag:not(.native-tags-more){
  flex: 0 0 auto;
  /* EN PIXELS, ET NON EN POURCENTAGE. Le conteneur des tags est en
     position absolue : sa largeur s'ajuste à son contenu. Un « max-width:
     50% » sur les enfants se calcule alors sur une largeur qui dépend
     d'eux — le navigateur tranche comme il peut, et « # doc » se
     retrouvait coupé en « # … » pendant que « # prompt-engineer… »
     s'affichait presque en entier.
     96 px : « # prompt-engi… » reste identifiable, « # doc » tient en
     entier, et deux tags plus le badge entrent dans la place. */
  max-width: 96px;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Le « +4 » ne rétrécit ni ne se coupe jamais : c'est le seul élément qui
   dise qu'il en reste. Le rogner ferait croire à une fiche à deux tags. */
.app-native .grid.affichage-grille .native-card-foot > .card-tags > .native-tags-more{
  flex: 0 0 auto;
  order: 9;
  max-width: none;
}

/* La date reprend sa place au bout du pied, poussée à droite. */
.app-native .grid.affichage-grille .native-card-foot > .native-date{
  margin-left: auto;
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--faint);
  font-size: 11px;
  white-space: nowrap;
}

/* Les signaux occupent le pied à gauche, sans se serrer. */
.app-native .grid.affichage-grille .native-signaux{
  margin-left: 0;
}

/* Le badge « traduit » libère la jonction, désormais prise par les tags :
   il remonte en haut à gauche de la vignette. */
.app-native .grid.affichage-grille .native-card .badge-traduit{
  top: 11px;
  left: 11px;
}


/* ── 8 · LE MENU DES TAGS MASQUÉS ──────────────────────────────────────
   Deux tags tiennent sur la vignette. Le « +4 » comptait les autres sans
   qu'on puisse les atteindre : il disait qu'il en restait, mais pas
   lesquels, et cliquer dessus ne faisait rien.

   Il déroule maintenant la liste. Le menu s'ouvre VERS LE BAS, sur le
   contenu et non sur la miniature : au-dessus, il aurait recouvert
   l'image, et sur les cartes du haut de la grille il serait sorti par le
   haut de la page. */
/* LA CARTE CESSE DE ROGNER PENDANT QUE LE MENU EST OUVERT.

   C'était le défaut : la carte porte « overflow: hidden » — nécessaire à
   la hauteur fixe et aux angles arrondis de la vignette. Le menu s'ouvre
   vers le bas et dépasse le bas de la carte dès quatre entrées : il
   s'ouvrait bel et bien, mais se faisait couper. Cliquer semblait ne
   rien faire.

   Tant qu'un menu est ouvert, la carte ne rogne plus et passe au-dessus
   de ses voisines. Elle retrouve son rognage à la fermeture. */
.app-native .grid.affichage-grille .native-card.menu-tags-ouvert{
  overflow: visible;
  z-index: 30;
}

/* ET LE CONTENEUR DES TAGS AUSSI.

   Premier correctif incomplet : j'avais levé le rognage de la CARTE en
   oubliant celui de « .card-tags », qui porte lui aussi « overflow:
   hidden » — c'est ce qui empêche les tags de déborder sur la vignette.
   Or le menu est son enfant, et ce conteneur fait 24 px de haut. Le menu
   était donc coupé par son propre parent, bien avant d'atteindre le bord
   de la carte.

   Les deux rognages devaient tomber ensemble. */
.app-native .grid.affichage-grille .native-card-foot > .card-tags.menu-tags-ouvert{
  overflow: visible;
}
.app-native .tags-masques{
  overflow: visible;
}

/* Sans « position: relative » ici, le menu se positionnerait par rapport
   à la CARTE : « top: 100% » l'enverrait sous son bord inférieur. Cette
   règle ne doit donc dépendre d'aucun mode d'affichage. */
.app-native .tags-masques{
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
}
.app-native .tags-masques-btn{
  cursor: pointer;
  border: 1px solid rgba(255,255,255,.85);
}
.app-native .tags-masques-btn:hover,
.app-native .tags-masques-btn.est-ouvert{
  background: var(--violet-soft);
  color: #5b3f92;
}

.app-native .tags-masques-menu{
  position: absolute;
  z-index: 40;
  top: calc(100% + 7px);
  left: 0;
  min-width: 168px;
  max-width: 232px;
  max-height: 196px;
  padding: 6px;
  overflow-y: auto;
  border: 1px solid var(--native-line);
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 16px 38px rgba(55,44,78,.19);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.app-native .tags-masques-item{
  display: block;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--native-ink);
  font-family: inherit;
  font-size: 12.6px;
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.app-native .tags-masques-item:hover,
.app-native .tags-masques-item:focus-visible{
  background: var(--line-soft);
  outline: none;
}

/* En affichage Liste, le badge reste dans le pied : le menu s'y ouvre de
   la même façon, rien à reprendre. */
.app-native .grid.affichage-liste .tags-masques{
  position: relative;
}

/* ── Une colonne : la vignette redevient proportionnelle ───────────────── */
@media (max-width: 679px){
  .app-native .grid.affichage-grille .native-card{
    height: auto;
    min-height: 300px;
  }
  /* LA CARTE « IDÉE » FIGURE ICI, ELLE AUSSI.

     Elle n'y était pas : ses trois voisines repassaient en 16/9 sur une
     colonne, elle restait bloquée à 140 px. Or « --vignette-h » passe à
     56,25 vw juste en dessous — environ 221 px sur un téléphone — et les
     tags comme le statut se posent à « --vignette-h moins 20 px ». Sur
     un lien ou une vidéo, c'est la jonction avec l'image ; sur une idée,
     c'était 61 px PLUS BAS que son visuel, donc en plein milieu du
     texte : le tag chevauchait la description, illisible, sur iPhone
     comme sur Android.

     Le commentaire juste en dessous disait déjà « au lieu d'une règle
     par élément, dont une avait été oubliée ». C'était encore vrai. */
  .app-native .grid.affichage-grille .native-card > .native-link-visual,
  .app-native .grid.affichage-grille .native-card > .native-video-visual,
  .app-native .grid.affichage-grille .native-card > .native-idea-visual,
  .app-native .grid.affichage-grille .native-card-compact > .native-link-visual,
  .app-native .grid.affichage-grille .native-card-compact > .native-video-visual,
  .app-native .grid.affichage-grille .native-card-compact > .native-idea-visual{
    flex: 0 0 auto;
    height: auto;
    min-height: 0;
    max-height: none;
    aspect-ratio: 16 / 9;
  }
  /* L'image d'une idée remplit son cadre, qui n'est plus plafonné à 140 px. */
  .app-native .grid.affichage-grille .native-idea-visual .inspi-vide.native-idea-image,
  .app-native .grid.affichage-grille .native-idea-image{
    max-height: none;
  }
  /* En une colonne, la vignette reprend les proportions 16/9. On met la
     variable à jour, et tout ce qui s'y accroche suit — au lieu d'une
     règle par élément, dont une avait été oubliée. */
  .app-native .grid.affichage-grille .native-card{
    --vignette-h: 56.25vw;
  }
}

/* ==========================================================================
   50 · FORMULAIRES SUR TÉLÉPHONE — la barre d'action ne défile plus
   ==========================================================================

   Ajouté le 12 août 2026. Bloc ADDITIF : le retirer restaure l'état d'avant.

   LE DÉFAUT. Sur téléphone, « Enregistrer » était en fin de flux dans une
   feuille qui défile. Il fallait traverser sept champs à l'aveugle pour
   l'atteindre, sans même savoir qu'il existait. Mesuré sur un écran de
   390 x 750 avec le clavier ouvert : la barre se trouvait 250 px SOUS le
   bord visible, quelle que soit la position de défilement.

   DEUX CHOSES À CORRIGER, ET LA PREMIÈRE NE SUFFIT PAS.

   1. La barre devient collante. Elle reste au bas de la feuille pendant
      que le corps défile derrière elle.

   2. Le voile réserve la place du clavier. C'est le point décisif, et
      celui qu'on rate : sur iPhone, la fenêtre ne rétrécit PAS quand le
      clavier s'ouvre. La feuille est ancrée en bas ; lui donner une
      hauteur maximale la raccourcit par le haut, son bord inférieur
      reste sous le clavier — et la barre collante avec lui.
      C'est donc au VOILE de se réserver la place. Les enfants alignés en
      bas se posent alors au-dessus du clavier.

   La variable --clavier est alimentée par l'API VisualViewport, dans le
   script en fin de document. Sans JavaScript, elle vaut 0 et tout se
   comporte comme avant.
   ========================================================================== */

.app-native .overlay{
  /* LE VOILE SE CALE SUR CE QUI SE VOIT.

     Il était en « inset: 0 », donc à la taille de la fenêtre. Sur iPhone,
     clavier ouvert, la fenêtre fait toujours 695 px alors que 260 px
     seulement sont visibles, décalés de 435 px vers le bas : le voile
     débordait par le haut, et la feuille ancrée en bas avec lui.

     « top » et « height » suivent maintenant la zone réellement visible,
     mesurée par VisualViewport. « max-height: 100% » sur la feuille
     redevient alors une vraie limite. Sans JavaScript, ou ailleurs que
     sur iPhone, les valeurs de repli rendent le comportement d'avant. */
  /* RETIRE LE 13 AOUT, APRES MESURE SUR APPAREIL.

     Caler le voile sur la zone visible supprimait bien le debordement
     (669 px de feuille pour 260 px visibles), mais creait pire : la barre
     d'action, collante en bas, remontait sous l'en-tete et recouvrait le
     champ de saisie. Les trois formulaires n'affichaient plus que leur
     titre, leur etiquette et la barre — le champ avait disparu.

     Contraindre la hauteur ne suffit pas : il faudrait aussi garantir une
     place minimale au contenu entre l'en-tete et la barre collante. Tant
     que ce n'est pas mesure, on revient au comportement connu. */
  /* LA FEUILLE TIENT DANS CE QUI SE VOIT — réglage validé en maquette
     autonome le 13 août, après trois tentatives ratées faute de mesure.

     Trois choses ensemble, et c'est leur conjonction qui compte :

     1. « top » et « height » suivent la zone visible. Sans cela, le voile
        garde la taille de la fenêtre et la feuille déborde par le haut.

     2. La feuille devient une colonne : en-tête figé, formulaire
        défilant, barre d'action au bord. L'en-tête ne défile plus, donc
        on sait toujours quel formulaire on remplit.

     3. Le formulaire garde une hauteur minimale. C'est ce qui manquait à
        ma première tentative : la feuille était bien contrainte, mais la
        barre collante remontait sous le titre et recouvrait le champ de
        saisie — les trois formulaires n'affichaient plus que leur
        étiquette. Contraindre sans garantir de place au contenu ne
        corrige rien, ça déplace le défaut. */
  top: var(--vv-haut, 0px);
  height: var(--vv-hauteur, 100%);
  bottom: auto;

  padding-top: 8px;                     /* on voit toujours l'app derrière */
  padding-bottom: 0;
}
.app-native .overlay > .sheet{
  --pad-x: 18px;
  max-height: 100%;
}

/* La feuille en colonne. Ciblée sur celles qui portent un formulaire :
   les menus et les listes gardent leur défilement d'ensemble. */
.app-native .overlay > .sheet:has(> .form){
  display: flex;
  flex-direction: column;
  overflow: hidden;          /* c'est le formulaire qui défile, pas elle */
  padding-bottom: 0;
}
.app-native .overlay > .sheet:has(> .form) > .sheet-handle{ display: none; }
.app-native .overlay > .sheet:has(> .form) > .sheet-head{
  flex: 0 0 auto;
  margin-bottom: 10px;
}
/* « min-height: 0 » est indispensable : sans lui, un enfant flex refuse de
   devenir plus petit que son contenu, et la zone défilante ne défile pas.
   La barre d'action mesure 75 px à elle seule : à 96 px de réserve, un
   écran très serré — clavier haut, barre de suggestions — laissait encore
   le champ recouvert de 9 px. Mesuré à 120 px : plus aucun recouvrement
   jusqu'à 200 px de hauteur visible. */
.app-native .overlay > .sheet:has(> .form) > .form{
  /* « 0 1 auto » et non « 1 1 auto » : le formulaire prend la hauteur de
     SON CONTENU, sans s'étirer. À « 1 1 auto », il occupait toute la
     place disponible même quand il n'avait rien à y mettre : la barre
     d'action restait accrochée à la fin du contenu et laissait sous elle
     une bande blanche — visible en ouvrant un formulaire depuis un menu,
     où la feuille dispose de plus de place que le formulaire n'en
     demande. Il se réduit et défile quand le contenu déborde ; il reste
     court quand il est court, et la feuille se cale au bas de l'écran. */
  flex: 0 1 auto;
  min-height: 120px;
  overflow-y: auto;
  overscroll-behavior: contain;
  margin: 0 calc(-1 * var(--pad-x, 18px));
  padding: 0 var(--pad-x, 18px);
}
@media (min-width: 680px){
  .app-native .overlay > .sheet{ --pad-x: 24px; }
}

/* Le rembourrage du bas passe à la barre elle-même : sans quoi la feuille
   garderait 22 px de vide sous une barre déjà collée au bord. */
.app-native .overlay > .sheet:has(.sheet-actions:last-child){
  padding-bottom: 0;
}

.app-native .overlay > .sheet .sheet-actions:last-child{
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin: 10px calc(-1 * var(--pad-x, 18px)) 0;
  /* Resserré après mesure : la barre pesait 75 px sur les 138 que laisse
     un écran très serré, et mordait le champ de 9 px. À 9 px de
     rembourrage, elle tombe à 66 px et le champ redevient entier. La
     zone tactile des boutons ne bouge pas : ce sont eux qui portent
     leur hauteur, pas la barre. */
  padding: 9px var(--pad-x, 18px) calc(9px + env(safe-area-inset-bottom));
  background: var(--paper);
  border-top: 1px solid var(--line);
}

/* ── Une zone de texte d'une ligne, qui grandit à la saisie ──────────────
   Le repère était un <input>. Un input fait défiler son contenu
   HORIZONTALEMENT : une fois « S3E7 · épisode 4 · à revoir avant lundi »
   saisi, on n'en voit plus que la fin. Sur téléphone, impossible de se
   relire — et un repère qu'on ne peut pas relire ne sert à rien.

   Une zone de texte affiche tout. Elle démarre sur UNE ligne pour ne pas
   allonger le formulaire, et pousse à mesure qu'on écrit. Au-delà de six
   lignes elle défile, pour ne jamais chasser la barre d'action. */
.field textarea.auto-h{
  min-height: 0;
  height: auto;
  max-height: calc(1.42em * 6 + 24px);
  line-height: 1.42;
  overflow-y: auto;
  resize: none;
}

/* ==========================================================================
   51 · BARRE DU BAS — l'onglet actif se voit enfin
   ==========================================================================

   L'état actif ne changeait que la TEINTE du trait et du libellé, de gris
   à violet. Sur une icône de 24 px et un texte de 10,5 px, un écart de
   couleur seul se perd dès qu'il y a du soleil sur l'écran, et n'existe
   pas du tout pour qui distingue mal ces deux teintes. Un état ne devrait
   jamais reposer sur la seule couleur.

   La pastille reprend le langage de la barre du haut sur ordinateur : le
   même signe pour la même idée, à deux endroits de l'application.

   ELLE EST UN CALQUE, PAS UN FOND. Un fond avec du rembourrage
   élargirait la boîte de l'icône — or le compteur est ancré à
   « left: 100% », son bord droit. Il partirait vers l'extérieur et le
   réglage patiemment obtenu serait à refaire. Un pseudo-élément posé en
   absolu ne change aucune dimension : les icônes et le compteur ne
   bougent pas d'un pixel.
   ========================================================================== */

.app-native .tabbar-v2 .tb-item.active .tb-ico{
  /* Enferme le calque dans l'icône. Sans cette ligne, un z-index négatif
     descend SOUS le fond de la barre : la pastille existe mais devient
     invisible. */
  isolation: isolate;
}
.app-native .tabbar-v2 .tb-item.active .tb-ico::after{
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 52px;
  height: 32px;
  border-radius: 999px;
  background: var(--violet-soft);
  box-shadow: 0 1px 3px rgba(88,63,146,.16);
}
.app-native .tabbar-v2 .tb-item.active .tb-lab{
  font-weight: 800;
}


/* ==========================================================================
   60 · LENTEUR SUR iOS — les couches de flou
   ==========================================================================

   SYMPTÔME. Sur iPhone, deux secondes entre l'appui sur un bouton et
   l'action. Sur Android, réponse immédiate. Un écart aussi net entre les
   deux plateformes désigne une propriété que Safari traite mal.

   LE COUPABLE PROBABLE. « backdrop-filter » — le flou qui laisse deviner
   ce qui passe derrière. Chaque élément qui en porte un devient une
   couche de composition à part, dont le fond doit être recalculé à
   chaque repeinte. Chrome le fait sur le processeur graphique ; Safari
   sur iOS retombe souvent sur le processeur principal.

   Et « .native-media-badge » en porte un. C'est l'étiquette de type,
   posée sur CHAQUE carte. Avec 249 trouvailles montées d'un coup — la
   liste n'est pas encore virtualisée — cela fait jusqu'à deux cent
   cinquante couches floutées à recalculer au moindre changement d'état.
   Un appui déclenche un rendu React, le rendu déclenche une repeinte, et
   la repeinte réveille les deux cent cinquante.

   CE QU'ON RETIRE, ET CE QU'ON GARDE. Le flou des cartes disparaît : le
   fond de l'étiquette est déjà opaque à 90 %, on ne voyait quasiment
   rien à travers. Le flou des deux barres fixes reste — elles sont deux,
   pas deux cent cinquante, et c'est là que l'effet se voit vraiment.

   SI CELA NE SUFFIT PAS, dans l'ordre : retirer aussi le flou de
   « .chrome::before » (26 px de rayon plus une saturation, sur toute la
   largeur), puis virtualiser la liste — le vrai remède, déjà inscrit
   dans la feuille de route.
   ========================================================================== */

.app-native .native-media-badge{
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* Sans le flou, le fond doit porter seul : on le rend franchement
     opaque plutôt que de laisser une transparence qui salirait le texte
     sur une miniature claire. */
  background: rgba(255,255,255,.96);
}

/* content-visibility a été essayé ici puis retiré : il approche l'effet
   d'une virtualisation pour une ligne de CSS, mais il introduit une
   seconde variable dans une livraison qui sert d'abord à VÉRIFIER une
   hypothèse. Si le flou est bien la cause, on le saura sans ambiguïté.
   Sinon, ce sera le prochain essai, suivi de la vraie virtualisation. */

/* ══════════════════════════════════════════════════════════════════════
   PASTILLES DE COULEUR — écran Nouveautés / À venir du Profil.

   Reprend les trois teintes déjà déclarées dans la palette (--vert,
   --ocre, --violet) plutôt que d'en inventer une nouvelle : c'est ce qui
   garde l'écran cohérent avec le reste de l'application. Le texte de
   chaque pastille reste la légende — pas de simple pastille de couleur
   seule, qu'il faudrait déchiffrer via un lexique séparé. */
.pastille-rubrique,
.pastille-etat {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
}
.pastille-rubrique::before,
.pastille-etat::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: 0 0 auto;
}

.pastille-rubrique.r-nouveau  { color: var(--violet); background: var(--violet-soft); }
.pastille-rubrique.r-ameliore { color: var(--vert);   background: var(--vert-soft);   }
.pastille-rubrique.r-corrige  { color: var(--ocre-text); background: var(--ocre-soft); }

.pastille-etat.e-preparation { color: var(--ocre-text); background: var(--ocre-soft); }
.pastille-etat.e-etude       { color: var(--vert);      background: var(--vert-soft); }
.pastille-etat.e-retenue     { color: var(--violet);    background: var(--violet-soft); }
