/* RSS93 — feuille de style unique, sans préprocesseur ni étape de compilation.
   La couleur d'emphase est le mauve de jv.montreuillois.com, pour que les sites
   de la maison se répondent.

   Pas de règle `style` en ligne dans le HTML : la politique de sécurité
   (style-src 'self') les interdit. Les valeurs variables passent donc par des
   éléments qui les portent nativement, ou par des classes. */

/* Deux thèmes, une seule liste de couleurs : light-dark() donne à chaque teinte
   sa valeur claire puis sa valeur sombre, et c'est `color-scheme` qui tranche.
   Sans réglage il vaut `light dark` : le système décide. Le bouton de la barre
   latérale pose data-theme sur <html> et fige alors le choix — le serveur le
   relit dans un témoin pour rendre la page dans le bon thème dès la première
   image, sans éclair de couleur.

   `color-scheme` sert aussi aux éléments natifs — barres de défilement, champ de
   recherche, menus déroulants — qui suivent le thème sans une ligne de plus. */
:root {
	color-scheme: light dark;

	--fond: light-dark(#fff, #0b0b0d);
	--laterale: light-dark(#f4f4f7, #000);
	--surface: light-dark(#f7f7fa, #17181c);
	--surface-haute: light-dark(#eeeef4, #212328);
	--bordure: light-dark(#e1e1e9, #2a2c33);
	--survol: light-dark(#eaeaf1, #131418);
	--texte: light-dark(#16171a, #f2f3f5);
	--texte-doux: light-dark(#3b3f47, #c8ccd3);
	--discret: light-dark(#62687a, #9096a0);

	/* Mauve repris de jv.montreuillois.com. Le thème sombre en prend la
	   déclinaison claire, qui réclame un texte sombre quand elle sert de fond —
	   d'où --accent-texte, et l'inverse en thème clair. */
	--accent: light-dark(#7b2cbf, #c589f2);
	--accent-texte: light-dark(#fff, #17171b);
	--accent-voile: light-dark(rgba(123, 44, 191, .10), rgba(197, 137, 242, .14));
	--accent-halo: light-dark(rgba(123, 44, 191, .35), rgba(197, 137, 242, .45));

	--erreur: light-dark(#c0272d, #ff6b6b);
	--succes: light-dark(#0f7a52, #5ddba0);
	--ombre: light-dark(rgba(0, 0, 0, .12), rgba(0, 0, 0, .40));

	/* Taille du texte dans les champs. Par une variable, parce qu'elle change au
	   doigt : sous iOS, un champ de moins de seize pixels fait zoomer la page dès
	   qu'on le touche, et la page ne redescend jamais. Régler « input » tout seul
	   ne suffisait pas — « .recherche input » l'emporte par spécificité, et la
	   règle passait inaperçue. */
	--taille-champ: .875rem;

	--rayon: 6px;
	--largeur-laterale: 244px;
	/* Largeur de la liste quand la lecture est ouverte, réglable à la seconde
	   poignée. La lecture prend tout le reste. */
	--largeur-liste: 36rem;
	/* Largeur maximale de la colonne de lecture. Le texte s'y tient ; ce qui
	   gagne à être vu grand — images, tableaux, code — déborde jusqu'à la
	   largeur du panneau. */
	--largeur-media: 58rem;
	/* La largeur de lecture : environ 70 caractères, la longueur de ligne au-delà
	   de laquelle l'œil peine à retrouver le début de la suivante. */
	--largeur-lecture: 42rem;
}

/* Le choix explicite, posé sur <html> par le serveur puis par le bouton. */
:root[data-theme="clair"] { color-scheme: light; }
:root[data-theme="sombre"] { color-scheme: dark; }

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
	margin: 0;
	background: var(--fond);
	color: var(--texte);
	font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
	-webkit-font-smoothing: antialiased;
}

a { color: inherit; }
h1, h2, h3 { font-weight: 700; line-height: 1.25; }

svg.icone {
	width: 1.15em; height: 1.15em; flex: none;
	fill: none; stroke: currentColor; stroke-width: 1.8;
	stroke-linecap: round; stroke-linejoin: round;
}

/* Contour de focus visible partout, et sur tout : c'est le seul repère de qui
   navigue au clavier. `:focus-visible` et non `:focus` — un clic à la souris ne
   doit pas laisser un halo derrière lui. */
:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
	border-radius: 3px;
}

/* Contenu réservé aux synthèses vocales : hors écran mais bien dans l'arbre
   d'accessibilité, contrairement à `display: none` qui l'en sortirait. */
.hors-ecran {
	position: absolute; width: 1px; height: 1px;
	margin: -1px; padding: 0; overflow: hidden;
	clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Lien d'évitement : invisible jusqu'à ce qu'il reçoive le focus. */
.saut {
	position: fixed; top: .5rem; left: .5rem; z-index: 100;
	padding: .6rem 1rem; border-radius: var(--rayon);
	background: var(--accent); color: var(--accent-texte);
	text-decoration: none; transform: translateY(-200%);
}
.saut:focus { transform: none; }

.discret { color: var(--discret); font-size: .8125rem; }
.alerte { color: var(--erreur); }
.succes { color: var(--succes); }
.vide { color: var(--discret); padding: 3rem 1rem; text-align: center; }
.aide { display: block; color: var(--discret); font-size: .8125rem; }

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

.application {
	display: grid;
	/* Barre latérale, poignée, liste, poignée, lecture. Les poignées occupent de
	   vraies colonnes plutôt que d'être posées en absolu : elles ne peuvent ainsi
	   jamais recouvrir un lien.
	   
	   La liste garde la même largeur qu'un article soit ouvert ou non : la voir
	   se rétrécir sous le doigt au moment où l'on clique déplace tout ce qu'on
	   était en train de lire. La colonne de lecture existe donc en permanence —
	   vide, elle invite simplement à choisir un article. */
	grid-template-columns:
		var(--largeur-laterale) 5px
		minmax(18rem, var(--largeur-liste)) 5px
		minmax(0, 1fr);
	min-height: 100vh;
}

/* Les pages de gestion — réglages, tableau des flux, article en pleine page —
   n'ont pas de colonne de lecture et réclament toute la largeur. */
body.pleine-largeur .application {
	grid-template-columns: var(--largeur-laterale) 5px minmax(0, 1fr);
}
body.pleine-largeur .poignee--liste,
body.pleine-largeur .panneau { display: none; }

/* En vue de liste, c'est la colonne qui borne : un second plafond sur la vue ne
   ferait que creuser un vide à sa droite. */
body.vue-liste .vue { max-width: none; }

/* Poignée de redimensionnement. Discrète au repos, elle se teinte au survol et
   prend l'accent quand elle a le focus — c'est le seul repère de qui la
   manœuvre au clavier. */
.poignee {
	grid-column: 2;
	cursor: col-resize;
	background: var(--bordure);
	opacity: .5;
	transition: opacity .12s, background .12s;
	touch-action: none;
}
.poignee:hover, body.redimensionne .poignee { opacity: 1; background: var(--accent); }
.poignee:focus-visible { opacity: 1; background: var(--accent); outline-offset: 0; }

/* Pendant le glissement, le curseur reste celui du redimensionnement partout, et
   plus rien ne se sélectionne : sans cela, on emporte le texte de la page avec
   la poignée. */
body.redimensionne { cursor: col-resize; user-select: none; }

/* Seconde poignée : le partage entre la liste et la lecture.
   
   Ce bloc doit rester APRÈS celui de .poignee : les deux sélecteurs ont la même
   spécificité, et l'ordre seul décide. Placé avant, il se faisait écraser sa
   colonne et la seconde poignée venait se superposer à la première. */
.poignee--liste { grid-column: 4; }

.laterale {
	position: fixed;
	inset: 0 auto 0 0;
	width: var(--largeur-laterale);
	display: flex;
	flex-direction: column;
	padding: .9rem .7rem;
	gap: .75rem;
	background: var(--laterale);
	z-index: 30;
}

/* Ce bloc ne défile pas : les quatre vues restent à portée, quel que soit le
   nombre de flux en dessous. C'est la raison d'être de la découpe. */
.laterale-fixe {
	flex: none;
	display: flex;
	flex-direction: column;
	gap: .75rem;
	padding-bottom: .6rem;
	border-bottom: 1px solid var(--bordure);
}

/* Le cadre défilant du milieu : les trois bandes — filtres, digests, flux — y
   défilent ensemble, entre une tête et un pied qui ne bougent pas.

   Chacune avait le sien : une hauteur maximale de 40 % pour les filtres, le
   reste de la place pour les flux, et rien du tout pour les digests, qui
   étaient donc incompressibles. Sur un écran bas, la somme des parties qui ne
   cèdent pas — le logo, les quatre vues, les digests, le lien de l'app, le
   compte — dépassait la fenêtre, et .laterale n'ayant aucun `overflow`, le bas
   de la colonne était purement coupé : ni le nom du compte, ni la bascule de
   thème, ni « Quitter » n'étaient atteignables.

   Un seul ascenseur, donc, et le partage de la place revient au lecteur : c'est
   le pliage des bandes qui décide, non plus un arbitrage écrit ici.

   Pas de `display: flex` sur ce cadre, malgré la tentation de retrouver le
   `gap` de .laterale : les pseudo-éléments ::before et ::after deviendraient
   des éléments flexibles, et ce sont eux qui portent les flèches de
   débordement — collantes, donc hors flot. L'écart entre bandes se fait à la
   marge. */
.laterale-milieu {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	scrollbar-width: thin;
}

/* Chaque bande garde son trait de séparation, sauf la dernière : le bord du
   cadre y suffit. */
.laterale-milieu > .menu {
	padding-bottom: .6rem;
	border-bottom: 1px solid var(--bordure);
}
.laterale-milieu > .menu:last-child { padding-bottom: 0; border-bottom: 0; }
.laterale-milieu > .menu + .menu { margin-top: .75rem; }

/* Trop bas pour le modèle « tête et pied fixes ». Sous cette hauteur, les parties
   qui ne cèdent pas — le logo, les quatre vues, le lien de l'application, le
   compte — occupent déjà tout : le cadre du milieu tombe à zéro et le pied
   repasse sous le bord. C'est le cas d'un téléphone tenu à l'horizontale, où le
   tiroir n'a que trois cents pixels de haut.
   
   On y renonce alors au pied fixe : toute la colonne défile d'un seul
   mouvement. Un ascenseur unique vaut mieux qu'un pied inatteignable, et mieux
   qu'un second ascenseur emboîté dans le premier — `overscroll-behavior:
   contain` empêcherait justement le geste de passer de l'un à l'autre.

   La borne est en rem et non en pixels : la tête grandit avec la taille de
   police du système, donc le seuil doit grandir avec elle. */
@media (max-height: 30rem) {
	.laterale { overflow-y: auto; overscroll-behavior: contain; }

	.laterale-milieu {
		flex: none;
		min-height: auto;
		overflow: visible;
	}

	/* Les flèches de débordement perdent leur zone défilante : leur chronologie
	   serait inactive et elles resteraient invisibles en toute circonstance.
	   Autant ne pas leur réserver de place. */
	.laterale-milieu::before, .laterale-milieu::after { content: none; }
}

/* L'en-tête « Digests » n'est pas un lien : il n'y a pas de page qui les
   rassemble, et il n'y a rien à y créer à la main. Le curseur et la couleur le
   disent, sinon on cliquerait dessus en vain. */
/* Une ligne de digest : le lien mange la place, le « … » se range au bout.
   Le menu n'apparaît qu'au survol ou au focus — trois points sur chaque ligne
   feraient une colonne de bruit dans une barre déjà chargée —, mais il reste
   atteignable au clavier, sans quoi la suppression serait à la souris seulement. */
.digest-ligne { display: flex; align-items: center; gap: .15rem; border-radius: var(--rayon); }
.digest-ligne:hover { background: var(--survol); }

.digest-lien {
	display: flex; align-items: center; gap: .55rem;
	flex: 1; min-width: 0;
	padding: .42rem .5rem;
	border-radius: var(--rayon);
	color: var(--texte-doux); font-size: .875rem; text-decoration: none;
}
.digest-lien span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.digest-ligne.actif .digest-lien { color: var(--texte); font-weight: 600; }

/* Une pastille sans nombre : un digest est lu ou ne l'est pas, il n'y a rien à
   compter. */
.pastille--point { width: .5rem; height: .5rem; padding: 0; min-width: 0; border-radius: 50%; }

.digest-menu { opacity: 0; flex: none; }
.digest-ligne:hover .digest-menu,
.digest-menu[open],
.digest-menu:focus-within { opacity: 1; }

.menu-entete-lien--inerte {
	display: flex; align-items: center; gap: .55rem;
	flex: 1; min-width: 0;
	padding: .42rem .5rem;
	color: var(--discret);
	font-size: .875rem;
	cursor: default;
}


/* --------------------------------------- Indicateurs de débordement

   Le cadre qui défile ne disait rien de ce qu'il cache : on croit la liste
   finie alors qu'il reste vingt flux plus bas. Une petite flèche à chaque bord, affichée seulement quand il y a
   effectivement quelque chose de ce côté-là.

   Tout se joue en CSS, par une animation dont le temps est le défilement
   lui-même (`animation-timeline: scroll()`). Pas d'écouteur de défilement,
   donc pas de calcul à chaque image ; pas de `ResizeObserver` non plus, alors
   que la barre latérale change de largeur à la poignée et que les dossiers se
   plient et se déplient sans arrêt — autant d'occasions de rater une mise à
   jour si on la calculait soi-même.

   Quand la zone ne déborde pas, la chronologie est inactive : l'animation ne
   s'applique pas et la flèche reste à son style de base, donc invisible. C'est
   exactement ce qu'on veut, sans une seule condition à écrire.

   Repli : un navigateur sans les chronologies de défilement n'affiche aucune
   flèche, et rien d'autre ne change. C'est un repère, pas une commande — rien
   ne devient inatteignable.

   Le triangle est tracé par des bordures sur une boîte de taille nulle, et la
   lueur de la couleur du fond le détache du titre qui passe dessous. Les
   pseudo-éléments sont collants (`sticky`) : ils tiennent le bord pendant que
   le contenu défile. Leur marge négative annule la place qu'ils prendraient
   dans le flot, sinon la liste se décalerait de cinq pixels. */
.laterale-milieu::before, .laterale-milieu::after {
	content: ''; display: block;
	position: sticky; z-index: 2;
	width: 0; height: 0;
	margin-inline: auto;
	border-inline: 5px solid transparent;
	opacity: 0;
	pointer-events: none;
	filter: drop-shadow(0 0 2px var(--laterale)) drop-shadow(0 0 3px var(--laterale));
}

/* `nearest` et non `self` : pour un pseudo-élément, `self` désigne le
   pseudo-élément — qui ne défile pas. La chronologie était alors inactive et
   la flèche restait invisible en toute circonstance. `nearest` remonte à la
   première zone défilante, c'est-à-dire l'élément qui le porte. */

.laterale-milieu::before {
	top: 0; margin-bottom: -5px;
	border-bottom: 5px solid var(--texte-doux);
}

.laterale-milieu::after {
	bottom: 0; margin-top: -5px;
	border-top: 5px solid var(--texte-doux);
}

@supports (animation-timeline: scroll()) {
	/* La flèche du haut paraît dès qu'on a quitté le sommet ; celle du bas
	   s'efface à l'approche du fond. Le premier centimètre de défilement suffit
	   à l'une comme à l'autre : au-delà, l'information est déjà passée. */
	.laterale-milieu::before {
		animation: fleche-paraitre linear both;
		animation-timeline: scroll(nearest block);
		animation-range: 0 1rem;
	}

	.laterale-milieu::after {
		animation: fleche-disparaitre linear both;
		animation-timeline: scroll(nearest block);
		animation-range: calc(100% - 1rem) 100%;
	}
}

@keyframes fleche-paraitre { from { opacity: 0; } to { opacity: .8; } }
@keyframes fleche-disparaitre { from { opacity: .8; } to { opacity: 0; } }

/* ------------------------------------------------- Dossiers repliables */

.dossier { margin-bottom: .1rem; }

.dossier summary {
	display: flex; align-items: center; gap: .4rem;
	padding: .35rem .5rem;
	border-radius: var(--rayon);
	cursor: pointer;
	/* Ni majuscules forcées ni lettres espacées : un dossier s'appelle comme on
	   l'a nommé. « RSS JV@montreuillois » et « Jeux-Vidéo » perdaient leur
	   casse propre, et une capitale imposée se lit moins vite qu'un bas de
	   casse. Le gras et la teinte suffisent à distinguer un dossier d'un flux. */
	/* Plus grand que les flux qu'il coiffe (.875rem) : un titre de dossier plus
	   petit que son contenu lisait à l'envers. */
	font-size: .9375rem; font-weight: 700;
	color: var(--texte-doux);
	list-style: none;
}
/* Le triangle natif de <summary> : on le retire pour poser le nôtre, qui suit
   le sens de rotation du chevron et se colore comme le reste. */
.dossier summary::-webkit-details-marker { display: none; }
.dossier summary:hover { background: var(--survol); color: var(--texte); }

/* La rotation ne vise QUE le chevron, jamais toutes les icônes du résumé :
   écrite en « .dossier summary .icone », elle emportait aussi l'icône de
   dossier, qui pivotait sur elle-même à chaque pliage. Amusant, non voulu. */
.dossier-chevron .icone {
	transform: rotate(-90deg);
	transition: transform .15s;
	width: 1em; height: 1em;
}
/* « > summary » et non la simple descendance : les dossiers vivent DANS une
   bande, et « .bande[open] .dossier-chevron » redressait aussi leurs chevrons à
   eux — tous les dossiers d'une bande ouverte paraissaient dépliés, y compris
   les fermés. Chaque règle ne doit viser que le chevron de son propre résumé. */
.dossier[open] > summary .dossier-chevron .icone,
.bande[open] > summary .dossier-chevron .icone { transform: none; }

/* ------------------------------------------------- Bandes repliables

   Les trois sections de la barre latérale — Filtres, Digests, Flux — se plient
   comme les dossiers qu'elles contiennent, et par le même moyen : un <details>
   dont le <summary> est la ligne d'en-tête. C'est ce qui permet de n'ouvrir que
   ce qu'on veut voir, plutôt que de laisser une hauteur maximale arbitrer entre
   des filtres et des flux qu'elle ne connaît pas.

   Le clic sur un lien de l'en-tête navigue sans plier : l'intercepteur de liens
   du script annule l'action par défaut du clic, et c'est elle qui aurait basculé
   le <details>. Sans script, le navigateur suit le lien et quitte la page — le
   pliage n'a plus d'objet. Le reste de la ligne bascule, comportement natif
   qu'on ne contrarie pas.

   La bande ne porte aucune marge propre : c'est le <nav> qui l'entoure qui tient
   le trait et l'écart, de sorte qu'une bande repliée laisse exactement la place
   de sa ligne d'en-tête. */
summary.menu-entete {
	cursor: pointer;
	list-style: none;
}
summary.menu-entete::-webkit-details-marker { display: none; }

/* Le chevron s'aligne sur ceux des dossiers, un cran en dessous : sans ce
   rembourrage il collait au bord du cadre, et les deux niveaux de pliage
   partaient de deux verticales différentes. */
summary.menu-entete .dossier-chevron {
	display: inline-flex;
	flex: none;
	padding-left: .5rem;
	color: var(--discret);
}
summary.menu-entete:hover .dossier-chevron { color: var(--texte); }

/* L'icône de dossier, elle, ne bouge pas — et se tient un peu plus grande que
   le chevron, qui n'est qu'une flèche. */
.dossier summary > .icone { width: 1.25em; height: 1.25em; flex: none; }

.dossier-nom {
	flex: 1; min-width: 0;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Le titre du dossier est un lien, mais il vit dans une ligne de menu : la règle
   générale « .menu a » en ferait une entrée à part entière, avec son cadre et
   ses marges.

   Le sélecteur est volontairement plus spécifique qu'elle — « .menu » en plus —
   plutôt que simplement placé après : ce bloc-ci est déclaré AVANT « .menu a »
   dans la feuille, et à spécificité égale c'est l'ordre qui tranche. S'en
   remettre à l'ordre, dans un fichier qu'on réorganise, c'est se préparer une
   panne silencieuse. */
.menu a.dossier-nom {
	display: inline;
	padding: 0;
	border-radius: 0;
	font-size: inherit;
	color: inherit;
	text-decoration: none;
}
/* Pas de soulignement : le titre est déjà en gras, et la teinte d'accent suffit
   à dire qu'il est cliquable. Un trait de plus alourdirait une ligne qui porte
   déjà un chevron, un nom, deux compteurs. */
a.dossier-nom:hover { background: none; color: var(--accent); }

/* La flèche de pliage : c'est elle, et le reste de la ligne, qui basculent. */
.dossier-chevron { display: inline-flex; }
/* Le compte d'un dossier — visible seulement quand il est REPLIÉ.

   C'était l'inverse, et pour une raison qui se tenait : deux nombres côte à côte
   sur une ligne fermée se lisaient tous les deux comme des non lus. Mais le
   remède coûtait plus cher que le mal — replié, un dossier ne disait plus rien
   du tout de ce qu'il contenait, alors que c'est précisément le moment où on ne
   peut pas le voir. Déplié, la liste dessous compte d'elle-même.

   L'ambiguïté d'origine reste à surveiller : si un dossier à jour affichant son
   seul total se remet à passer pour une pile de non lus, c'est ici qu'il faudra
   distinguer les deux autrement — par la forme, pas en cachant le nombre.

   « > summary » et non un descendant : le compte d'un dossier suit son propre
   pliage, pas celui de la bande qui le contient. Le sélecteur est déclaré APRÈS
   la règle de base et lui est plus spécifique : les deux précautions valent
   mieux qu'une dans ce fichier. */
.dossier-compte { font-weight: 400; text-transform: none; letter-spacing: 0; }
.dossier[open] > summary .dossier-compte,
.bande[open] > summary .dossier-compte { display: none; }

/* Les flux se rangent sous le nom de leur dossier, pas sous son chevron : le
   décalage aligne leur icône sur celle du dossier, un cran plus à droite. Sans
   quoi la liste paraissait décrochée du titre qui la coiffe.

   « .menu » en tête pour la même raison que ci-dessus : « .menu a » porte un
   raccourci `padding` qui écrasait ce `padding-left` à spécificité égale, la
   règle générale étant déclarée plus bas dans le fichier. Le décalage était
   simplement ignoré. */
.menu .dossier > a { padding-left: 2.5rem; }

/* Les digests se rangent de même sous le nom de leur programmation. Le décalage
   est posé sur le lien et non sur la ligne : la ligne porte le fond du survol et
   le « … » du bout, qui doivent rester alignés sur le bord comme ailleurs. Sans
   cette règle, les digests s'alignaient sur le chevron de leur dossier et la
   liste paraissait décrochée du titre qui la coiffe. */
.menu .dossier > .digest-ligne .digest-lien { padding-left: 2.5rem; }

/* ------------------------------------------------------ Ranger par glissement */

/* Le flux en cours de déplacement s'efface : on suit le curseur, pas la ligne
   d'origine. */
.flux-glisse { opacity: .4; }

/* Le titre d'un dossier se prend et se pose : le curseur le dit avant même
   qu'on essaie. `grab` seulement au survol du titre, pas de la ligne entière —
   c'est le titre qu'on saisit. */
.dossier summary[draggable="true"] { cursor: grab; }
.dossier summary[draggable="true"]:active { cursor: grabbing; }

/* Le dossier survolé s'annonce comme prêt à recevoir. Le liseré est posé en
   ombre intérieure plutôt qu'en bordure : une bordure décalerait le contenu de
   deux pixels au passage du curseur, et le menu tressauterait. */
.depot-vise {
	background: var(--accent-voile);
	border-radius: var(--rayon);
	box-shadow: inset 0 0 0 2px var(--accent);
}
.depot-vise > summary { background: none; }

.logo {
	display: flex; align-items: center; gap: .5rem;
	padding: .25rem .4rem;
	text-decoration: none; font-weight: 700; font-size: 1.05rem;
}
.logo svg { width: 28px; height: 28px; flex: none; }
.logo-fond { fill: var(--accent); stroke: none; }
.logo-point { fill: var(--accent-texte); stroke: none; }
.logo-trace { fill: none; stroke: var(--accent-texte); stroke-width: 2.1; stroke-linecap: round; }

.menu { display: flex; flex-direction: column; gap: 1px; }

.menu a {
	display: flex; align-items: center; gap: .55rem;
	padding: .42rem .5rem;
	border-radius: var(--rayon);
	text-decoration: none;
	color: var(--texte-doux);
	font-size: .875rem;
}
.menu a span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu a:hover { background: var(--survol); color: var(--texte); }
.menu a.actif { background: var(--accent-voile); color: var(--accent); font-weight: 600; }

.menu-titre {
	display: flex; align-items: center; gap: .4rem;
	margin: .5rem 0 .15rem; padding: 0 .5rem;
	font-size: .75rem; font-weight: 700;
	color: var(--texte-doux);
}
.menu-vide { padding: 0 .5rem; font-size: .8125rem; color: var(--discret); }

/* La pastille des non lus. `margin-left: auto` la pousse en bout de ligne sans
   position absolue, ce qui la laisse participer à la mise en page. */
.pastille {
	margin-left: auto;
	padding: .05rem .38rem;
	border-radius: 999px;
	background: var(--surface-haute);
	color: var(--texte-doux);
	font-size: .6875rem; font-weight: 700;
	font-variant-numeric: tabular-nums;
}
.menu a.actif .pastille { background: var(--accent); color: var(--accent-texte); }

/* Un flux écarté des non lus garde sa pastille, mais en sourdine : son compte
   est juste, il n'entre simplement dans aucun total. Le contour dit « compté à
   part » sans crier comme le fond plein.

   APRÈS .pastille et .menu a.actif .pastille, jamais avant : à spécificité égale
   c'est l'ordre qui tranche, et la règle de base reprendrait le dessus. Le piège
   s'est déjà refermé deux fois sur ce fichier. */
.pastille--sourdine,
.menu a.actif .pastille--sourdine {
	background: transparent;
	color: var(--discret);
	box-shadow: inset 0 0 0 1px currentColor;
}

.alerte-flux {
	margin-left: auto; color: var(--erreur); font-weight: 700;
}

.favicon {
	width: 1.15em; height: 1.15em; flex: none;
	border-radius: 2px; object-fit: contain;
}

/* Le lien vers l'application Android, juste au-dessus de la ligne du compte. Même
   filet et même retrait que celle-ci : ce sont les deux blocs de pied de la barre,
   ils doivent se lire comme une paire et non comme deux ajouts successifs. */
.application-lien {
	display: flex; align-items: center;
	/* Le même écart et le même retrait horizontal que `.menu a` : la colonne
	   d'icônes du menu se prolonge jusqu'ici au lieu de repartir huit pixels plus
	   à gauche, ce qui cassait le bord gauche de la barre sur ses derniers blocs. */
	gap: .55rem;
	padding: .5rem .5rem 0;
	border-top: 1px solid var(--bordure);
	color: var(--texte-doux);
	font-size: .8125rem; text-decoration: none;
}
.application-lien:hover { color: var(--texte); }
/* Un peu plus grande que la valeur commune : elle tient la hauteur de deux lignes,
   et à 1,15em elle flottait au milieu d'un bloc deux fois plus haut qu'elle. */
.application-lien .icone { width: 1.4em; height: 1.4em; }
.application-lien span { display: flex; flex-direction: column; min-width: 0; }
/* La version et le poids, sous le libellé : une information qu'on lit une fois, pas
   une étiquette qui doit peser autant que le lien. */
.application-lien small { font-size: .75rem; color: var(--discret); }

.compte {
	display: flex; align-items: center; gap: .35rem;
	padding-top: .5rem;
	border-top: 1px solid var(--bordure);
}
.compte-nom {
	flex: 1; min-width: 0;
	padding: .3rem .4rem; border-radius: var(--rayon);
	font-size: .8125rem; text-decoration: none; color: var(--texte-doux);
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.compte-nom:hover { background: var(--survol); }
.compte form { margin: 0; }

.colonne {
	grid-column: 3;
	display: flex; flex-direction: column;
	min-width: 0;
}

.haut {
	position: sticky; top: 0; z-index: 20;
	display: flex; align-items: center; gap: .6rem;
	padding: .5rem .9rem;
	background: color-mix(in srgb, var(--fond) 88%, transparent);
	backdrop-filter: blur(8px);
	border-bottom: 1px solid var(--bordure);
}

/* La loupe ne sert que sur téléphone : ailleurs le champ tient sans gêner. */
.loupe { display: none; }

.titre-haut {
	flex: 1; min-width: 0;
	margin: 0; font-size: .9375rem; font-weight: 600;
	overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.recherche { position: relative; display: flex; align-items: center; margin: 0; }
.recherche .icone { position: absolute; left: .55rem; color: var(--discret); pointer-events: none; }
.recherche input {
	width: 12rem; padding: .35rem .6rem .35rem 2rem;
	border: 1px solid var(--bordure); border-radius: 999px;
	background: var(--surface); color: inherit; font: inherit; font-size: var(--taille-champ);
}

.burger { display: none; }

.vue {
	flex: 1;
	/* La marge latérale passe par une variable : l'en-tête de vue la reprend en
	   négatif pour aller au bord, et les deux doivent valoir exactement la même
	   chose. Écrites séparément, elles ont fini par diverger sur téléphone — de
	   trois pixels, assez pour que la page glisse latéralement. */
	--marge-vue: .9rem;
	padding: 1rem var(--marge-vue) 4rem;
	max-width: 62rem; width: 100%;
}
/* Le conteneur de vue reçoit le focus après une navigation JavaScript, pour que
   le clavier reparte du nouveau contenu et non du haut de page. Il ne doit pas
   pour autant s'entourer d'un contour : ce n'est pas un élément interactif. */
.vue:focus { outline: none; }
/* Pendant un chargement partiel, le contenu s'estompe légèrement plutôt que de
   disparaître : la page ne saute pas, et l'attente se voit. */
.vue[aria-busy="true"] { opacity: .55; transition: opacity .12s; }

.entete-vue {
	display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
	margin-bottom: .75rem;
}

/* Le nom du flux ou du dossier reste sous les yeux pendant qu'on descend la
   liste : c'est lui qui dit où l'on est, et cinquante articles plus bas, on ne
   s'en souvient plus.
   
   `top` vaut la hauteur de la barre du haut, elle-même collante : le script la
   mesure et la pose dans --hauteur-haut, faute de pouvoir l'écrire en dur — elle
   dépend de la taille de police du système. La valeur par défaut correspond au
   cas courant, de sorte que sans JavaScript l'en-tête colle quand même, à un ou
   deux pixels près. */
body.vue-liste .entete-vue {
	position: sticky;
	top: var(--hauteur-haut, 3rem);
	z-index: 10;
	margin: 0 calc(-1 * var(--marge-vue)) .75rem;
	padding: .5rem var(--marge-vue);
	background: var(--fond);
	border-bottom: 1px solid var(--bordure);
}
.entete-vue h2 { margin: 0; font-size: 1.25rem; }

/* -------------------------------------------------------------- Progression */

/* « 34 / 120 » à côté du titre, et le filet du bas de l'en-tête qui se remplit
   d'accent à mesure qu'on avance. Le filet existe déjà — la bordure de l'en-tête
   collant — : la jauge s'y superpose au lieu d'ajouter une barre de plus. */
.entete-vue { position: relative; }
.progression-texte {
	font-size: .75rem; color: var(--discret);
	font-variant-numeric: tabular-nums; white-space: nowrap;
}
.progression-piste {
	position: absolute; left: 0; right: 0; bottom: -1px;
	height: 2px;
	pointer-events: none;
}
.progression-jauge {
	display: block; height: 100%;
	background: var(--accent);
	transform: scaleX(0); transform-origin: left;
	transition: transform .25s ease-out;
}

/* En lecture, la même jauge, au bord haut de l'écran. Téléphone seulement — sur
   grand écran, l'en-tête de vue reste visible à côté.

   Fixe et non collante : la lecture y est déjà fixe et plein écran, et une
   position collante dans un conteneur à marge intérieure s'est montrée
   capricieuse — mesurée, la jauge disparaissait au premier défilement. Le
   compteur prend un fond : il passe au-dessus du texte qui défile dessous. */
.panneau-progression { display: none; }

@media (max-width: 860px) {
	.panneau-progression:not([hidden]) {
		display: block;
		position: fixed; top: 0; left: 0; right: 0; z-index: 3;
		height: 0;
		pointer-events: none;
	}
	.panneau-progression .progression-piste { top: env(safe-area-inset-top, 0px); bottom: auto; height: 3px; }
	.panneau-progression .progression-texte {
		position: absolute; top: calc(.4rem + env(safe-area-inset-top, 0px)); left: 50%;
		transform: translateX(-50%);
		padding: .05rem .5rem;
		border-radius: 999px;
		background: var(--surface);
		font-size: .6875rem;
	}
}

@media (prefers-reduced-motion: reduce) {
	.progression-jauge { transition: none; }
}
.entete-actions { margin: 0; }
/* L'interrupteur pousse le reste à droite : il est le premier des deux, et sa
   place est contre le titre. */
.entete-filtre { margin: 0 0 0 auto; }

/* ------------------------------------------------------------ Interrupteur */

/* Un vrai bouton, dessiné en interrupteur. Le rôle « switch » et aria-checked
   portent l'état ; le CSS ne fait que le montrer, en le lisant sur l'attribut
   plutôt que sur une classe posée en double. */
.interrupteur {
	display: inline-flex; align-items: center; gap: .45rem;
	padding: .25rem .5rem .25rem .3rem;
	border: 0; border-radius: 999px;
	background: transparent; color: var(--discret);
	font: inherit; font-size: .8125rem; cursor: pointer;
}
.interrupteur:hover { background: var(--survol); color: var(--texte); }

.interrupteur-piste {
	width: 2.1rem; height: 1.15rem; flex: none;
	padding: 2px;
	border-radius: 999px;
	background: var(--surface-haute);
	transition: background .15s;
}

.interrupteur-pastille {
	display: block;
	width: calc(1.15rem - 4px); height: calc(1.15rem - 4px);
	border-radius: 50%;
	background: var(--discret);
	transition: transform .15s, background .15s;
}

.interrupteur[aria-checked="true"] { color: var(--accent); font-weight: 600; }
.interrupteur[aria-checked="true"] .interrupteur-piste { background: var(--accent); }
.interrupteur[aria-checked="true"] .interrupteur-pastille {
	background: var(--accent-texte);
	transform: translateX(.95rem);
}

/* Le libellé change de mot d'un état à l'autre — « Tous » puis « Non lus » — et
   la largeur avec lui : on la fige pour que le bouton ne saute pas sous le
   curseur au moment où l'on clique. */
.interrupteur-libelle { min-width: 3.6rem; text-align: left; }

/* ---------------------------------------------------------------- Liste */

.liste { display: flex; flex-direction: column; gap: .5rem; }

.carte {
	/* En colonne : la vignette et le texte forment la rangée du haut, les
	   actions occupent toute la largeur en dessous. Elles étaient auparavant
	   dans la colonne de texte, donc rétrécies et décalées dès qu'il y avait une
	   image — et la rangée passait à deux lignes sur un téléphone. */
	display: flex; flex-direction: column;
	padding: .7rem .8rem;
	background: var(--surface);
	border: 1px solid var(--bordure);
	border-radius: var(--rayon);
	/* La carte entière ouvre la lecture — sauf ses liens et ses boutons, qui
	   gardent leur curseur et leur rôle propres. */
	cursor: pointer;
}
.carte a, .carte button { cursor: pointer; }
.carte:focus { outline: none; }
.carte:hover { border-color: var(--accent-halo); }
/* Un article lu s'efface sans disparaître : le titre reste lisible, mais la
   carte cesse de réclamer l'attention. */
.carte--lue { background: transparent; }

/* Celui que les flèches désignent sans l'avoir ouvert. Plus discret que la carte
   ouverte : c'est un curseur de parcours, pas une sélection. */
.carte--visee { border-color: var(--accent-halo); background: var(--accent-voile); }

/* Celui qu'on lit dans la troisième colonne, repéré par un liseré d'accent :
   la liste continue de défiler, il faut pouvoir retrouver où l'on en est. */
.carte--ouverte {
	border-color: var(--accent);
	box-shadow: inset 3px 0 0 var(--accent);
}

/* La colonne de liste étant étroite et de largeur fixe, les cartes s'y adaptent
   une fois pour toutes — plus besoin de les recompacter à l'ouverture d'un
   article, ce qui faisait sauter toute la liste au moment du clic. */
body.vue-liste .carte-visuel { width: 84px; height: 64px; }
.carte--lue .carte-titre a { color: var(--discret); font-weight: 500; }

.carte-visuel {
	width: 104px; height: 78px; flex: none;
	border-radius: 4px; object-fit: cover;
	background: var(--surface-haute);
}

/* La rangée du haut : l'image à gauche, le texte à droite. */
.carte-haut { display: flex; gap: .75rem; min-width: 0; }

.carte-corps { flex: 1; min-width: 0; }

.carte-source {
	display: flex; align-items: center; gap: .35rem; flex-wrap: wrap;
	margin: 0 0 .15rem;
	font-size: .75rem; color: var(--discret);
}
.carte-source a { color: var(--discret); text-decoration: none; }
/* Le temps de lecture : en retrait de la ligne, qui l'est déjà. Il aide à
   choisir entre lire tout de suite et ranger pour plus tard, sans plus. */
.carte-duree { opacity: .8; white-space: nowrap; }
.carte-source a:hover { color: var(--accent); text-decoration: underline; }

.carte-titre { margin: 0 0 .2rem; font-size: .9375rem; }
.carte-titre a { text-decoration: none; }
.carte-titre a:hover { color: var(--accent); text-decoration: underline; }

.carte-extrait {
	margin: 0;
	color: var(--texte-doux); font-size: .8125rem;
	/* Deux lignes au plus : au-delà, c'est l'article qu'on lit, pas la liste. */
	display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
	overflow: hidden;
}

.carte-actions {
	display: flex; align-items: center; gap: .25rem; flex-wrap: wrap;
	margin: .45rem 0 0;
}

/* ------------------------------------------------------------ Boutons-outils */

.outil {
	display: inline-flex; align-items: center; gap: .3rem;
	padding: .28rem .45rem;
	border: 0; border-radius: var(--rayon);
	background: transparent; color: var(--discret);
	font: inherit; font-size: .75rem;
	text-decoration: none; cursor: pointer;
}
.outil:hover { background: var(--survol); color: var(--texte); }
.outil--texte { font-size: .8125rem; }

/* Le trait du haut appartient à .laterale-fixe, ceux d'en dessous aux bandes du
   cadre défilant : le menu lui-même n'en porte plus. En porter un de son côté
   aurait doublé la ligne du haut, les deux blocs étant voisins. */

/* Le cœur et le signet portent seuls leur sens — ils n'ont pas de libellé à
   côté d'eux, contrairement à « Marquer comme lu ». Un peu plus grands, ils se
   visent mieux au doigt comme à la souris, et pèsent dans la rangée à la mesure
   de ce qu'ils font. */
.action-favori .icone, .action-plus-tard .icone { width: 1.45em; height: 1.45em; }

/* Sauf en tête de la lecture, où il voisine avec deux autres icônes de la même
   taille : les grossir toutes reviendrait à n'en grossir aucune, et le signet
   seul plus gros que ses voisines se lisait comme un défaut d'alignement. */
.lecture-outils .action-plus-tard .icone { width: 1.15em; height: 1.15em; }

/* aria-pressed porte l'état : le CSS le lit plutôt qu'une classe posée en
   double, ce qui garantit que ce qu'on voit et ce qu'annonce la synthèse vocale
   ne peuvent pas diverger. */
.outil[aria-pressed="true"] { color: var(--accent); }
.action-favori[aria-pressed="true"] .icone { fill: currentColor; }
.action-plus-tard[aria-pressed="true"] .icone { fill: currentColor; }

.bouton {
	display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
	padding: .5rem 1rem;
	border: 0; border-radius: var(--rayon);
	background: var(--accent); color: var(--accent-texte);
	font: inherit; font-weight: 600; text-decoration: none; cursor: pointer;
}
.bouton:hover { filter: brightness(1.08); }
.bouton--nuage { background: var(--surface-haute); color: var(--texte); }

/* ------------------------------------------------------- Panneau de lecture */

/* ------------------------------------------- Le passage d'une fiche à l'autre */

/* Au balayage, la fiche qu'on quitte part du côté du doigt et celle qui arrive
   monte de derrière, du côté opposé. L'animation sert à dire ce qui vient de se
   passer — on a changé d'article, dans tel sens —, pas à faire joli : elle dure
   moins d'un quart de seconde en tout, et la sortie est plus brève que l'entrée
   pour que l'attente de la réponse se voie le moins possible.

   La sortie garde son état final (« forwards ») : la fiche reste effacée le
   temps que le serveur réponde, plutôt que de revenir en place pour repartir
   aussitôt. */
.fiche--sort-gauche { animation: fiche-sort-gauche .13s ease-in forwards; }
.fiche--sort-droite { animation: fiche-sort-droite .13s ease-in forwards; }
.fiche--entre-gauche { animation: fiche-entre-gauche .22s cubic-bezier(.22, .61, .36, 1); }
.fiche--entre-droite { animation: fiche-entre-droite .22s cubic-bezier(.22, .61, .36, 1); }

@keyframes fiche-sort-gauche {
	to { transform: translateX(-9%); opacity: 0; }
}

@keyframes fiche-sort-droite {
	to { transform: translateX(9%); opacity: 0; }
}

/* « De derrière » : la fiche commence un peu plus petite et transparente, comme
   une feuille sous la précédente, puis vient se poser. */
@keyframes fiche-entre-gauche {
	from { transform: translateX(-7%) scale(.97); opacity: 0; }
	to { transform: none; opacity: 1; }
}

@keyframes fiche-entre-droite {
	from { transform: translateX(7%) scale(.97); opacity: 0; }
	to { transform: none; opacity: 1; }
}

/* Qui a demandé moins d'animations n'en veut pas une de plus : le changement
   d'article reste instantané, et rien ne se perd — le sens était déjà dit par
   le geste lui-même. */
@media (prefers-reduced-motion: reduce) {
	.fiche--sort-gauche, .fiche--sort-droite,
	.fiche--entre-gauche, .fiche--entre-droite { animation: none; }
}

.panneau {
	grid-column: 5;
	position: sticky;
	/* Ni marge ni bordure arrondie : c'est une colonne, pas une carte. Elle en
	   portait par accident, en héritant de la règle des encadrés du profil. */
	margin: 0;
	top: 0;
	height: 100vh;
	overflow-y: auto;
	/* La fiche glisse latéralement le temps de l'animation : sans cela, elle
	   ouvrirait une barre de défilement horizontale pendant deux dixièmes de
	   seconde. */
	overflow-x: hidden;
	overscroll-behavior: contain;
	padding: 1rem 1.25rem 3rem;
	background: var(--surface);
	border-left: 1px solid var(--bordure);
}
.panneau:focus { outline: none; }

/* Le bouton de fermeture et l'invitation s'excluent : il n'y a rien à fermer
   tant que rien n'est ouvert, et rien à inviter une fois que ça l'est. */
.panneau-fermer {
	position: sticky; top: 0; float: right; z-index: 2;
	margin: -.25rem -.5rem 0 0;
	background: var(--surface);
	display: none;
}
body.panneau-ouvert .panneau-fermer { display: inline-flex; }

.panneau-vide {
	display: flex; align-items: center; justify-content: center;
	height: 60vh; padding: 2rem;
	color: var(--discret); font-size: .875rem; text-align: center;
}
body.panneau-ouvert .panneau-vide { display: none; }

/* Le panneau prend toute la largeur restante. Le texte, lui, garde la sienne :
   au-delà d'environ 70 caractères, l'œil peine à retrouver le début de la ligne
   suivante, et un écran large n'y change rien.
   
   Mais tout n'est pas du texte. Une photo de presse, un tableau, un bloc de code
   gagnent à être vus en grand — c'est même la seule chose qui justifie la place.
   La colonne de lecture est donc large, et ce sont les blocs de texte qui se
   resserrent à l'intérieur : l'image occupe la largeur sans qu'aucune marge
   négative ni aucun débordement n'entre en jeu. */
.panneau .lecture { max-width: min(var(--largeur-media), 100%); margin: 0 auto; }
.panneau .lecture-titre { font-size: 1.35rem; }

.panneau .lecture-entete,
.panneau .lecture-note,
.panneau .lecture-corps > p,
.panneau .lecture-corps > h1,
.panneau .lecture-corps > h2,
.panneau .lecture-corps > h3,
.panneau .lecture-corps > h4,
.panneau .lecture-corps > h5,
.panneau .lecture-corps > h6,
.panneau .lecture-corps > ul,
.panneau .lecture-corps > ol,
.panneau .lecture-corps > dl,
.panneau .lecture-corps > blockquote {
	max-width: var(--largeur-lecture);
	margin-inline: auto;
}

/* Ce qui profite de la largeur y reste, centré dans le panneau. */
.panneau .lecture-corps > figure,
.panneau .lecture-corps > img,
.panneau .lecture-corps > pre,
.panneau .lecture-corps > table,
.panneau .lecture-visuel {
	display: block;
	margin-inline: auto;
}

/* ---------------------------------------------------------------- Lecture */

.lecture { max-width: var(--largeur-lecture); margin: 0 auto; }
.lecture-entete { margin-bottom: 1.25rem; }
.lecture-titre { margin: .3rem 0 .6rem; font-size: 1.5rem; }
.lecture-titre a { text-decoration: none; }
.lecture-titre a:hover { color: var(--accent); text-decoration: underline; }
.lecture-actions { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; margin: 0; }

.lecture-corps { color: var(--texte-doux); font-size: 1rem; line-height: 1.7; }
.lecture-corps h1, .lecture-corps h2, .lecture-corps h3 { color: var(--texte); margin: 1.6rem 0 .5rem; }
.lecture-corps h1 { font-size: 1.3rem; }
.lecture-corps h2 { font-size: 1.15rem; }
.lecture-corps h3 { font-size: 1rem; }
.lecture-corps a { color: var(--accent); }
/* Une image de flux peut être annoncée à 2000 px de large : elle ne doit pas
   pousser la colonne de lecture. */
.lecture-corps img, .lecture-visuel { max-width: 100%; height: auto; border-radius: var(--rayon); }
.lecture-corps figure { margin: 1.2rem 0; }
.lecture-corps figcaption { font-size: .8125rem; color: var(--discret); }
.lecture-corps blockquote {
	margin: 1.2rem 0; padding: .2rem 0 .2rem 1rem;
	border-left: 3px solid var(--accent); color: var(--texte);
}
.lecture-corps pre {
	padding: .8rem; overflow-x: auto;
	background: var(--surface); border-radius: var(--rayon);
	font-size: .8125rem;
}
.lecture-corps code { font-size: .875em; }
/* Un tableau publié dans un flux déborde souvent : il défile dans sa boîte
   plutôt que d'élargir la page entière. */
.lecture-corps table { display: block; overflow-x: auto; max-width: 100%; border-collapse: collapse; }
.lecture-corps td, .lecture-corps th { padding: .35rem .6rem; border: 1px solid var(--bordure); }

.lecture-note {
	margin-top: 2rem; padding: .75rem 1rem;
	background: var(--accent-voile); border-radius: var(--rayon);
	font-size: .875rem;
}
.lecture-note a { color: var(--accent); }

/* ------------------------------------------------------- Formulaires, tables */

/* Encadré de réglages (page profil). Nommé « encadre » et non « panneau » :
   ce dernier désigne la colonne de lecture, et les deux règles se sont un temps
   marché dessus — le max-width de celle-ci empêchait celle-là de s'étirer dans
   sa colonne, sans que rien ne le laisse deviner. */
.encadre {
	max-width: var(--largeur-lecture);
	margin: 0 0 1.5rem; padding: 1rem;
	background: var(--surface); border: 1px solid var(--bordure); border-radius: var(--rayon);
}
.encadre h3 { margin: 0 0 .75rem; font-size: 1rem; }

.formulaire { max-width: 34rem; }
.formulaire--ligne { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; max-width: none; }
/* L'aide prend toute la largeur et passe donc sous la rangée : dans un champ,
   elle en aurait allongé un seul et aligné les autres en escalier. */
.formulaire--ligne .aide { flex-basis: 100%; margin: -.35rem 0 0; }
.formulaire--ligne .champ { flex: 1; min-width: 14rem; }

.champ { display: flex; flex-direction: column; gap: .25rem; margin: 0 0 .8rem; }
.champ label { font-size: .8125rem; font-weight: 600; }
.champ input, .champ select {
	padding: .45rem .6rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit;
}
.champ--bouton { justify-content: flex-end; }

/* Deux pages s'écartent du gabarit et prennent toute la largeur : le profil,
   dont les encadrés se rangent alors côte à côte, et la gestion des flux, dont
   le tableau a huit colonnes. Les autres pages en pleine largeur — l'article,
   les filtres — gardent la colonne de lecture confortable. */
body.page-profil .vue,
body.page-flux .vue { max-width: none; }

/* Les encadrés du profil se rangent en colonnes dès qu'il y a la place. Ils
   sont indépendants les uns des autres : rien n'oblige à les lire dans l'ordre,
   et les empiler sur un seul rang de 42 rem gâchait tout le reste de l'écran. */
body.page-profil .vue {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
	gap: 0 1.5rem;
	align-content: start;
}
body.page-profil .entete-vue,
body.page-profil .succes,
body.page-profil .alerte,
body.page-profil .jeton-emis { grid-column: 1 / -1; }
body.page-profil .encadre { max-width: none; }
/* Un encadré qui porte un tableau prend toute la largeur : le ranger dans une
   colonne de 24 rem reviendrait à comprimer ce qu'on vient d'aérer. */
body.page-profil .encadre--large { grid-column: 1 / -1; }

.tableau { width: 100%; border-collapse: collapse; font-size: .875rem; }
.tableau th { text-align: left; font-size: .75rem; color: var(--discret); font-weight: 600; }
.tableau th, .tableau td { padding: .45rem .5rem; border-bottom: 1px solid var(--bordure); vertical-align: top; }
.tableau tr.en-erreur { background: color-mix(in srgb, var(--erreur) 8%, transparent); }
/* Ce que le script prend en charge est masqué d'emblée, et rétabli par
   assets/css/sans-script.css quand il n'y a pas de script. L'inverse — masquer
   depuis le script — laissait l'élément visible jusqu'à l'exécution de app.js,
   qui est différé et ne tourne donc qu'après le premier rendu. */
.bouton-envoi { display: none; }

/* -------------------------------------------------- Dossiers, sur /flux */

/* Un bloc à part, entre l'ajout d'un flux et la liste : on vient ici pour
   ranger, et ranger commence par avoir où ranger. */
.dossiers { margin: 0 0 2rem; }
.dossiers h3 { margin: 0 0 .6rem; font-size: 1rem; }
.dossiers .aide { display: block; max-width: 44rem; }

.tableau--dossiers { max-width: 44rem; }
.tableau--dossiers td { vertical-align: middle; }
.tableau--dossiers tbody tr:hover { background: var(--survol); }

/* Les deux boutons d'ordre côte à côte : chacun est un formulaire, et deux
   formulaires s'empileraient l'un sous l'autre sans cette rangée. */
.rangee-outils { display: flex; align-items: center; gap: .1rem; }
.rangee-outils button[disabled] { opacity: .3; cursor: default; }

/* Le chevron sert deux fois, pointe en bas puis retournée : une icône de moins
   à charger, et les deux flèches sont forcément symétriques. */
.chevron-haut { display: inline-flex; transform: rotate(180deg); }

/* Le bloc des actions d'une règle. Le liseré d'accent le distingue des encadrés
   de critères : ceux du dessus décrivent ce qu'on désigne, celui-ci ce qu'on en
   fait — et c'est le seul qui écrive quelque part. */
.encadre--regle { box-shadow: inset 3px 0 0 var(--accent); }

/* --------------------------------------------------- Les programmations de digest */

/* L'encadré de ce qu'on est en train de modifier, et la programmation visée
   plus haut : le même liseré sur les deux, pour que l'œil fasse le lien entre
   le formulaire et la ligne qu'il va remplacer. */
.encadre--vise { box-shadow: inset 3px 0 0 var(--accent); border-color: var(--accent); }
.encadre h4 { margin: 0 0 .75rem; font-size: 1rem; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }

/* Le rythme coiffe ses programmations, comme dans la barre latérale : on y
   cherche « le quotidien », pas « la troisième ». */
.digests-rythme {
	max-width: var(--largeur-lecture);
	margin: 1.5rem 0 .6rem; font-size: .8125rem; font-weight: 600;
	text-transform: uppercase; letter-spacing: .04em; color: var(--discret);
}

/* Un état qui tient en un mot — « suspendue » — se lit mieux posé que noyé
   dans une phrase. */
.etiquette-etat {
	padding: .1rem .4rem; border-radius: var(--rayon);
	background: var(--surface-haute); color: var(--texte-doux);
	font-size: .75rem; font-weight: 400; text-transform: none; letter-spacing: 0;
}
.etiquette-etat--vise { background: var(--accent-voile); color: var(--accent); }

/* Le volet d'un fournisseur dans le profil : son résumé porte le nom, l'état et
   la clé en place, de quoi décider s'il faut l'ouvrir. */
.volet-fournisseur { margin: 0 0 1rem; }
.volet-fournisseur > summary {
	display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
	padding: .4rem 0; cursor: pointer; font-weight: 600;
}
.volet-fournisseur[open] > summary { margin-bottom: .6rem; }

/* Les faits d'une programmation : deux colonnes sur large, l'une sous l'autre
   sur un téléphone. Un <dl> et non un tableau — ce sont des paires, pas une
   grille, et chaque ligne doit pouvoir passer à la ligne sans casser l'autre. */
.faits { display: grid; grid-template-columns: 9rem 1fr; gap: .3rem .75rem; margin: 0 0 .9rem; }
.faits dt { color: var(--discret); font-size: .8125rem; }
.faits dd { margin: 0; }
.faits .aide { display: inline; }

@media (max-width: 34rem) {
	.faits { grid-template-columns: 1fr; gap: 0 0; }
	.faits dt { margin-top: .5rem; }
}

/* La rangée d'actions d'une programmation : « .actions-ligne » existe déjà
   plus bas, on ne fait qu'y pousser le geste irréversible à l'écart des deux
   autres. Elle doit être un <div> et non un <p> : un <p> ne peut pas contenir
   de <form>, le navigateur le referme avant, et les boutons se retrouvaient
   empilés hors de la rangée — c'était le défaut. */
.actions-ligne { align-items: center; margin: .2rem 0 0; }
/* Le geste irréversible se tient à l'écart des deux autres : c'est le
   formulaire qu'on pousse, et non son bouton, qui n'est pas enfant de la
   rangée. */
.actions-ligne .action-detruire { margin-left: auto; }

/* La rangée qui clôt un formulaire respire un peu plus que celle d'une carte. */
.actions-ligne--fin { margin-top: 1rem; }

/* Un groupe de champs : une question, ses réglages. Sans bordure — le trait
   d'un <fieldset> découperait le formulaire en autant de boîtes et ferait
   l'effet inverse de celui qu'on cherche. La légende suffit à séparer. */
.champ-groupe { margin: 0 0 1.2rem; padding: 0; border: 0; }
.champ-groupe > legend {
	padding: 0; margin-bottom: .5rem;
	font-size: .8125rem; font-weight: 600; text-transform: uppercase;
	letter-spacing: .04em; color: var(--discret);
}

/* Un bouton qui se lit comme un lien : pour le geste qu'on ne veut pas
   proposer aussi fort que les autres. Le <button> reste nécessaire — supprimer
   est un envoi, pas une adresse —, seuls ses atours s'effacent. */
.lien {
	padding: 0; border: 0; background: none; color: var(--texte-doux);
	font: inherit; text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.lien:hover { color: var(--texte); }
.lien--danger { color: var(--erreur); }

/* Un encadré qui se déplie : son titre est le résumé, et se tient comme un
   titre d'encadré. */
.encadre > summary.encadre-titre { cursor: pointer; font-weight: 600; font-size: 1rem; }
.encadre[open] > summary.encadre-titre { margin-bottom: .9rem; }
.regle-actions { margin: 0 0 .5rem; padding: 0 0 0 .9rem; border: 0; border-left: 1px solid var(--bordure); }
.regle-actions legend { padding: 0 0 .4rem; color: var(--discret); font-size: .8125rem; }
.regle-compteur { margin: .75rem 0 0; font-size: .875rem; }
.regle-compteur strong { font-variant-numeric: tabular-nums; }

/* La pastille d'une règle armée, dans la liste des filtres et la barre latérale :
   un filtre qui agit ne se distingue autrement en rien d'un filtre qui montre. */
.marque-regle {
	display: inline-flex; align-items: center;
	color: var(--accent);
}
.marque-regle .icone { width: .95em; height: .95em; }

/* Ce que la règle fait, sous le résumé des critères : une seconde ligne, plus
   discrète, parce qu'elle répond à une autre question — pas « ce qu'il retient »
   mais « ce qu'il en fait ». */
.filtre-agit { display: block; margin-top: .2rem; color: var(--accent); font-size: .8125rem; }

/* Import et export OPML : deux blocs côte à côte dès qu'il y a la place, l'un
   sous l'autre sinon. Ce sont deux gestes symétriques, ils se lisent mieux
   ensemble. */
.opml-blocs { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); }
.opml-blocs h3 { margin: 0 0 .5rem; font-size: 1rem; }
.opml-blocs .aide { max-width: 34rem; }

.opml-rapport { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--bordure); }
.opml-rapport h3 { margin: 0 0 .5rem; font-size: 1rem; }

.opml-lignes { margin: 0; padding: 0; list-style: none; max-height: 22rem; overflow-y: auto; }
.opml-ligne { display: flex; gap: .5rem; align-items: baseline; padding: .15rem 0; font-size: .875rem; }
.opml-titre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* La marque de gauche porte l'état, en colonne fixe pour que les titres
   s'alignent : sur deux cents lignes, c'est ce qui rend la liste lisible. */
.opml-marque { flex: none; width: 1ch; font-weight: 700; font-family: monospace; }
.opml-ligne--ajoute .opml-marque { color: var(--succes); }
.opml-ligne--echec .opml-marque { color: var(--erreur); }
.opml-ligne--deja .opml-marque,
.opml-ligne--hors-sujet .opml-marque { color: var(--discret); }

/* Le bouton d'ordre, dans l'en-tête de vue. La flèche se retourne pour dire le
   sens en cours, sans qu'il faille un second tracé. */
.entete-tri { display: flex; }
.tri-inverse { display: inline-flex; transform: scaleY(-1); }

/* ---------------------------------------------------- Tableau des flux */

/* Les réglages d'un flux, dépliés sous sa ligne et sur toute la largeur. Un
   fond distinct et un liseré à gauche : la ligne appartient au flux du dessus,
   ce n'est pas un flux de plus dans la liste. */
.ligne-reglages > td {
	padding: .9rem 1rem;
	background: var(--surface);
	box-shadow: inset 3px 0 0 var(--accent);
}

.reglages-flux fieldset { margin: 0; padding: 0; border: 0; }
.reglages-flux legend { padding: 0 0 .6rem; font-weight: 700; }
.reglages-flux .champ { margin: 0 0 .9rem; max-width: 46rem; }
.reglages-flux .aide { display: block; margin-top: .15rem; }

/* Case à cocher et libellé côte à côte, l'explication sous le libellé et alignée
   avec lui : elle appartient au réglage, pas à la colonne. */
.champ--case {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 0 .5rem;
	align-items: start;
}
.champ--case input { margin-top: .2rem; }
.champ--case label { font-weight: 600; cursor: pointer; }

/* L'explication est portée par le <label>, pour que cliquer dessus coche la case
   — mais elle ne doit pas en prendre la graisse : c'est le réglage qui se lit en
   gras, pas son commentaire. */
.champ--case label .aide { font-weight: 400; cursor: default; }

.resume-flux { margin: 0 0 1rem; color: var(--discret); font-size: .875rem; }
.resume-flux strong { color: var(--texte); font-variant-numeric: tabular-nums; }

.tableau--flux td { vertical-align: middle; }
/* Les dates relatives — « il y a 19 min » — tiennent sur une ligne ou la
   colonne double de hauteur pour rien. */
.tableau--flux time { white-space: nowrap; }
.tableau--flux th { white-space: nowrap; }
.tableau--flux tbody tr:hover { background: var(--survol); }
.tableau .nombre { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* La première colonne porte trois informations empilées : le nom, le domaine,
   et le motif de panne s'il y en a un. */
.cellule-flux { display: flex; flex-direction: column; gap: .1rem; min-width: 14rem; }

.flux-nom {
	display: flex; align-items: center; gap: .4rem;
	font-weight: 600; text-decoration: none;
}
.flux-nom:hover { color: var(--accent); }
.flux-domaine { text-decoration: none; }
.flux-domaine:hover { color: var(--accent); text-decoration: underline; }
.flux-erreur { font-size: .75rem; }

.action-supprimer:hover { color: var(--erreur); }

.actions-ligne { display: flex; gap: .4rem; flex-wrap: wrap; }
.actions-ligne form { display: flex; gap: .25rem; align-items: center; margin: 0; }
.ligne-ranger select {
	padding: .25rem .4rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit; font-size: var(--taille-champ);
	max-width: 10rem;
}

.actions-ligne input[type="text"] {
	width: 9rem; padding: .25rem .4rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit; font-size: var(--taille-champ);
}

/* ------------------------------------------- Flux proposés à l'ajout */

.propositions {
	margin: 0 0 1.5rem; padding: 1rem;
	background: var(--surface); border: 1px solid var(--bordure); border-radius: var(--rayon);
}
.propositions-titre { margin: 0 0 .75rem; font-size: .9375rem; }

/* En colonnes sur un écran large : quarante entrées à la file, c'est un mur. */
.candidats {
	list-style: none; margin: 0; padding: 0;
	display: grid; grid-template-columns: repeat(auto-fill, minmax(26rem, 1fr));
	gap: .35rem 1rem;
}

.candidat {
	display: flex; align-items: center; gap: .75rem;
	padding: .4rem .5rem;
	border: 1px solid transparent; border-radius: var(--rayon);
	min-width: 0;
}
.candidat:hover { background: var(--fond); border-color: var(--bordure); }

/* Le nom sur une ligne, l'adresse en dessous : côte à côte, un nom un peu long
   se coupait en deux et la colonne perdait sa lisibilité. */
.candidat-texte { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.candidat-nom { font-weight: 600; }
.candidat-nom, .candidat-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.candidat-ajout { margin: 0; flex: none; }

.bouton--discret {
	padding: .3rem .7rem;
	background: var(--surface-haute); color: var(--texte);
	font-size: .8125rem; font-weight: 600;
}
.bouton--discret:hover { background: var(--accent); color: var(--accent-texte); filter: none; }

/* Une proposition acceptée reste en place, marquée : on en ajoute souvent
   plusieurs à la suite, et voir disparaître la ligne qu'on vient de cliquer
   ferait perdre le fil. */
.candidat--ajoute { opacity: .55; }
.candidat--ajoute .candidat-nom::after {
	content: " ✓";
	color: var(--succes);
}

/* Le flux qui vient d'être ajouté, signalé dans la barre latérale le temps de
   le repérer. La teinte s'efface d'elle-même ; sous prefers-reduced-motion, la
   règle plus bas coupe l'animation et la couleur disparaît d'un coup. */
@keyframes signaler {
	from { background: var(--accent); color: var(--accent-texte); }
	to   { background: transparent; }
}
.flux-nouveau { animation: signaler 2.4s ease-out; }

.jeton-emis code {
	display: block; padding: .6rem .8rem; overflow-wrap: anywhere;
	background: var(--surface-haute); border-radius: var(--rayon); font-size: .8125rem;
}

/* La pagination sert aussi de repère au chargement au défilement : le script
   guette l'apparition du lien « suivant ». Quand il prend la main, il pose
   `chargement` le temps de l'aller-retour. */
.pagination { display: flex; align-items: center; gap: 1rem; justify-content: center; margin-top: 1.5rem; }
.pagination.chargement { opacity: .5; }
.pagination.chargement::after {
	content: "Chargement…";
	color: var(--discret); font-size: .8125rem;
}

/* ---------------------------------------------------------------- Connexion */

body.nu { display: grid; place-items: center; }
.vue--nue { padding: 1.5rem; width: 100%; max-width: 22rem; }

.connexion { text-align: center; }
.connexion-marque { display: flex; align-items: center; justify-content: center; gap: .5rem; margin: 0; font-size: 1.5rem; }
.connexion-logo { width: 34px; height: 34px; }
.connexion .secondaire { color: var(--discret); margin: .25rem 0 1.5rem; font-size: .875rem; }
.connexion form { display: flex; flex-direction: column; gap: .3rem; text-align: left; }
.connexion label { font-size: .8125rem; font-weight: 600; margin-top: .5rem; }
.connexion input {
	padding: .55rem .7rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--surface); color: inherit; font: inherit;
}
.connexion .bouton { margin-top: 1rem; }
.connexion .erreur {
	padding: .6rem .8rem; border-radius: var(--rayon);
	background: color-mix(in srgb, var(--erreur) 12%, transparent);
	color: var(--erreur); font-size: .875rem;
}

/* « ou » posé sur une ligne : deux traits de part et d'autre, dessinés par les
   pseudo-éléments plutôt que par des balises vides. */
.separateur-ou {
	display: flex; align-items: center; gap: .75rem;
	margin: 1.25rem 0; color: var(--discret); font-size: .8125rem;
}
.separateur-ou::before, .separateur-ou::after {
	content: ""; flex: 1; height: 1px; background: var(--bordure);
}

/* ------------------------------------------------------------ Bascule thème */

/* Le bouton porte les deux états ; on ne montre que celui qui correspond au
   thème *visé*. Sans choix explicite, c'est la préférence système qui décide,
   d'où les requêtes média — le serveur, lui, ne la connaît pas. */
.bascule-theme .theme-clair, .bascule-theme .theme-sombre { display: none; }
@media (prefers-color-scheme: dark) { .bascule-theme .theme-clair { display: inline-flex; } }
@media (prefers-color-scheme: light) { .bascule-theme .theme-sombre { display: inline-flex; } }
:root[data-theme="sombre"] .bascule-theme .theme-clair { display: inline-flex; }
:root[data-theme="sombre"] .bascule-theme .theme-sombre { display: none; }
:root[data-theme="clair"] .bascule-theme .theme-sombre { display: inline-flex; }
:root[data-theme="clair"] .bascule-theme .theme-clair { display: none; }

/* ------------------------------------------------------------------- Mobile */

.voile { display: none; }

/* ------------------------------------------------ Barre de navigation du bas

   Sur un écran de six pouces, le haut ne s'atteint pas d'une main : les quatre
   vues principales descendent à portée de pouce. Elle n'existe que sur
   téléphone — ailleurs, la barre latérale les porte déjà, et une seconde liste
   au même moment serait une redite.

   `padding-bottom` avec env() : sur un téléphone à barre d'accueil, les
   derniers pixels de l'écran ne sont pas cliquables. Sans cette réserve, le
   dernier rang de boutons se retrouve sous la barre du système. */
.barre-basse { display: none; }

@media (max-width: 860px) {
	.barre-basse {
		display: flex;
		position: fixed; inset: auto 0 0 0; z-index: 30;
		padding-bottom: env(safe-area-inset-bottom, 0px);
		background: color-mix(in srgb, var(--laterale) 94%, transparent);
		backdrop-filter: blur(8px);
		border-top: 1px solid var(--bordure);
	}

	.barre-basse a {
		flex: 1;
		display: flex; flex-direction: column; align-items: center; justify-content: center;
		gap: .1rem;
		/* 52 px de haut : au-dessus des 44 recommandés, parce qu'on vise ces
		   boutons-là en marchant. */
		min-height: 52px;
		padding: .35rem .2rem;
		position: relative;
		color: var(--discret); text-decoration: none;
		font-size: .6875rem;
	}

	/* Le tiroir ouvert la fait disparaître : il porte déjà ces quatre vues en
	   tête, et surtout elle masquait son bas — le nom du compte, la bascule de
	   thème et « Quitter » se retrouvaient dessous, hors d'atteinte. */
	body.menu-ouvert .barre-basse { display: none; }

	.barre-basse a.actif { color: var(--accent); }
	.barre-basse .icone { width: 22px; height: 22px; }
	.barre-basse-nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

	/* La pastille se pose sur l'icône, pas à côté : la colonne est étroite et le
	   libellé a besoin de toute sa largeur. */
	.pastille--basse {
		position: absolute; top: .15rem; left: 50%; margin-left: .3rem;
		background: var(--accent); color: var(--accent-texte);
		font-size: .625rem; line-height: 1.4;
	}

}

@media (max-width: 860px) {
	.application,
	body.pleine-largeur .application { grid-template-columns: 1fr; }
	.colonne { grid-column: 1; }

	/* La marge latérale se resserre sur un écran étroit, en un seul endroit :
	   l'en-tête de vue la reprend par la variable. */
	.vue { --marge-vue: .7rem; }

	/* L'encoche et la barre d'accueil : le viewport est en « viewport-fit=cover »,
	   la page s'étend donc sous elles. Sans ces réserves, la barre du haut passe
	   sous l'horloge et le bas de la lecture sous la barre du système. */
	.haut { padding-top: calc(.5rem + env(safe-area-inset-top, 0px)); }
	.laterale {
		padding-top: calc(.9rem + env(safe-area-inset-top, 0px));
		padding-bottom: calc(.9rem + env(safe-area-inset-bottom, 0px));
	}

	/* La loupe remplace le champ, qui se déplie par-dessus la barre entière. */
	.loupe { display: inline-flex; }

	.recherche { display: none; }

	body.recherche-ouverte .recherche {
		display: flex; flex: 1;
		align-items: center; gap: .4rem;
	}

	/* Barre repliée pendant la recherche : le champ prend toute la place, il n'y
	   a rien d'autre à regarder à ce moment-là. */
	body.recherche-ouverte .burger,
	body.recherche-ouverte .titre-haut,
	body.recherche-ouverte .loupe,
	body.recherche-ouverte .haut > .outil--texte { display: none; }

	body.recherche-ouverte .recherche input { width: 100%; flex: 1; }
	body.recherche-ouverte .ferme-recherche { display: inline-flex; flex: none; }

	/* Pas de troisième colonne sur un écran de téléphone : le panneau se pose
	   par-dessus, en plein écran, et seulement quand il a quelque chose à
	   montrer. Les poignées n'ont plus d'objet non plus. */
	.poignee, .poignee--liste { display: none; }
	.panneau { display: none; }
	body.panneau-ouvert .panneau { display: block; }

	.panneau {
		grid-column: 1;
		position: fixed; inset: 0; z-index: 40;
		height: 100%; width: 100%;
		border-left: 0;
		/* Au-dessus de la barre du bas (z-index 30) : la lecture prend tout
		   l'écran, elle ne partage pas la place. D'où la réserve pour l'encoche
		   et pour la barre d'accueil, que la barre du bas assurait sinon. */
		padding-top: calc(1rem + env(safe-area-inset-top, 0px));
		padding-bottom: calc(3rem + env(safe-area-inset-bottom, 0px));
	}

	/* La barre latérale devient un tiroir. `transform` et non `left` : la
	   première est animée par le compositeur, la seconde relance une mise en
	   page à chaque image. */
	.laterale {
		transform: translateX(-100%);
		transition: transform .18s ease-out;
		box-shadow: 0 0 24px var(--ombre);
		width: min(84vw, 300px);
	}
	body.menu-ouvert .laterale { transform: none; }

	/* `:not([hidden])` et non `display: block` tout court : le script cache le
	   voile par l'attribut `hidden`, et une règle d'auteur l'emporte sur
	   l'attribut. Écrit sans condition, le voile restait donc affiché en
	   permanence sur téléphone — un rideau à 45 % de noir par-dessus toute la
	   page, qui captait chaque toucher. On croyait l'application figée : les
	   cartes ne répondaient plus, et rien à l'écran ne disait pourquoi.

	   Sans JavaScript, le voile n'a pas lieu d'être et le gabarit le pose déjà
	   `hidden` : il ne s'affichera jamais. */
	.voile:not([hidden]) {
		display: block; position: fixed; inset: 0; z-index: 25;
		background: rgba(0, 0, 0, .45);
	}

	.burger {
		display: inline-flex; align-items: center;
		padding: .3rem; border: 0; border-radius: var(--rayon);
		background: transparent; color: inherit; cursor: pointer;
	}
	.burger svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }

	.recherche input { width: 7rem; }
	.carte-visuel { width: 72px; height: 60px; }

	/* La rangée d'actions d'une carte tient sur une ligne, ou elle n'est plus une
	   rangée : les libellés la faisaient déborder et le menu « … » se retrouvait
	   seul en dessous, à droite — un éparpillement qui se voyait plus que les
	   boutons eux-mêmes.
	   Rien n'est perdu à masquer le texte : l'état « lu » se voit à l'icône, qui
	   passe à la couleur d'accent, et à la carte qui se ternit. Le libellé reste
	   lu par la synthèse vocale, qui ne regarde pas la couleur. */
	.carte-actions .etiquette { display: none; }

	/* Quarante plutôt que quarante-quatre : six pictogrammes et leurs espaces
	   doivent tenir dans la largeur d'une carte sur un écran de 320 points. */
	.carte-actions .outil {
		min-width: 40px; min-height: 40px;
		justify-content: center;
		padding-inline: 0;
	}

	/* La réserve du bas laisse passer la barre de navigation, sinon la dernière
	   carte se lit à moitié. Elle est écrite ici et non plus haut : un raccourci
	   « padding » posé après une longhand l'écrase, et c'est exactement ce qui
	   s'était produit — cinq pixels de carte sous la barre. */
	.vue { padding: .8rem var(--marge-vue) calc(4.5rem + env(safe-area-inset-bottom, 0px)); }
}

/* Sans JavaScript, il n'y a rien à replier : le champ reste et la loupe s'en
   va. La feuille de <noscript> rétablit ce que le script prend en charge. */
.ferme-recherche { display: none; }

/* --------------------------------------------------- Au doigt plutôt qu'au curseur

   `pointer: coarse` vise le doigt, quelle que soit la taille de l'écran : une
   tablette de treize pouces se touche aussi. C'est le bon critère ici, alors
   qu'une largeur maximale ne dit rien de la façon dont on désigne.

   Deux réglages, et le premier n'a rien de cosmétique. */

@media (pointer: coarse) {
	/* 1. Seize pixels dans les champs, sinon Safari zoome.
	   iOS agrandit la page dès qu'on touche un champ dont le texte fait moins de
	   seize pixels — et il ne revient jamais en arrière. La page se retrouve
	   décalée, plus large que l'écran, et rien n'a l'air de fonctionner. C'est le
	   défaut qui rendait l'application inutilisable en portrait. */
	:root { --taille-champ: 16px; }

	/* 2. Des cibles qu'on peut viser.
	   Quarante-quatre points est la recommandation commune à Apple et à Google ;
	   les pictogrammes du site tombaient à 24. Le rembourrage grandit, pas
	   l'icône : c'est la zone sensible qui manquait, pas le dessin. */
	.outil,
	.burger,
	.loupe,
	.carte-menu > summary,
	.menu-entete-outil {
		min-width: 44px; min-height: 44px;
		display: inline-flex; align-items: center; justify-content: center;
	}

	/* La ligne d'en-tête d'une bande bascule le pliage au doigt. Celles qui
	   portent des outils atteignaient les 44 points par eux ; « Digests », qui
	   n'en a aucun, restait à 35 et se visait mal. On les met toutes à la même
	   hauteur plutôt que de laisser la présence d'un bouton en décider. */
	summary.menu-entete { min-height: 44px; }

	/* Un outil à libellé garde son rembourrage : la largeur minimale ne le
	   déforme pas, elle ne fait que le border. Ceux qui n'ont qu'un pictogramme —
	   le « + » de la barre du haut porte la même classe — y gagnent la leur. */
	.outil--texte { padding-inline: .5rem; }

	/* Les liens de la ligne de source d'une carte — nom du flux, domaine — sont
	   du texte de treize pixels : on ne peut pas les grandir sans déformer la
	   ligne. On agrandit donc la zone sensible sans toucher à la mise en page,
	   par un rembourrage que la marge négative annule. */
	.carte-source a, .carte-liens a {
		padding-block: .7rem;
		margin-block: -.7rem;
	}

	/* Les entrées de menu et les rangées d'action des cartes : même raison, une
	   hauteur qui se vise sans regarder. */
	.menu a:not(.dossier-nom), .barre-basse a, .dossier summary { min-height: 44px; }

	/* Le titre d'un dossier est à part. C'est un lien dans une ligne déjà haute
	   de quarante-quatre points : lui imposer la même hauteur posait son texte
	   tout en haut de sa boîte — la boîte était bien centrée, le texte non, et le
	   titre paraissait remonté de onze pixels par rapport à son chevron. Mesuré
	   sur un vrai téléphone, puis au banc.
	   Il garde donc sa hauteur naturelle et gagne sa zone sensible par un
	   rembourrage symétrique, que la marge négative annule — le texte reste
	   centré, la ligne ne bouge pas, et le découpage du texte trop long
	   fonctionne toujours, ce qu'un « display: flex » aurait cassé. */
	.menu a.dossier-nom { padding-block: .7rem; margin-block: -.7rem; }
	.carte-actions .outil { min-height: 40px; }
	.carte-menu-action { min-height: 44px; }
}

/* Réduire les animations quand le système le demande : c'est une préférence
   d'accessibilité, pas un goût — les mouvements déclenchent des malaises chez
   certaines personnes. */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: .01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: .01ms !important;
	}
}

/* Contrastes renforcés : on épaissit les bordures plutôt que de changer les
   couleurs, déjà conformes au niveau AA. */
@media (prefers-contrast: more) {
	.carte, .panneau, .champ input { border-width: 2px; }
	.carte-extrait, .lecture-corps { color: var(--texte); }
}

/* ================================================================= Filtres */

/* ------------------------------------------- La section dans la barre latérale */

/* La ligne d'en-tête porte trois choses : le lien vers la page, et les deux
   boutons de création. `.menu-entete` et non une entrée de menu ordinaire : une
   entrée de menu est un lien qui prend toute la ligne, et il fallait de la place
   à droite. */
.menu-entete {
	display: flex; align-items: center; gap: .15rem;
	border-radius: var(--rayon);
}

/* Le lien mange l'espace libre, les deux outils se rangent au bout. Le sélecteur
   passe par `.menu` — comme pour .dossier-nom plus haut — plutôt que de compter
   sur l'ordre des déclarations : à spécificité égale c'est l'ordre qui tranche,
   et un fichier qu'on réorganise perdrait la règle sans prévenir. */
.menu .menu-entete-lien { flex: 1; min-width: 0; }

.menu .menu-entete-outil {
	flex: none;
	padding: .3rem;
	color: var(--discret);
}
.menu .menu-entete-outil:hover { background: var(--survol); color: var(--accent); }
.menu-entete-outil .icone { width: 1.15em; height: 1.15em; }

/* Le champ « nouveau dossier », sous l'en-tête « Flux ». Masqué par l'attribut
   `hidden` que pose le serveur, ouvert par le script ; le `:not([hidden])` est
   indispensable, sans quoi le `display: flex` d'ici l'emporterait sur celui de
   l'attribut et le champ serait visible en permanence. */
.menu-nouveau:not([hidden]) {
	display: flex; align-items: center; gap: .25rem;
	margin: .15rem 0 .35rem;
}

.menu-nouveau input {
	flex: 1; min-width: 0;
	padding: .3rem .45rem;
	font-size: var(--taille-champ);
	color: var(--texte);
	background: var(--fond);
	border: 1px solid var(--bordure); border-radius: var(--rayon);
}

.menu-nouveau button { flex: none; padding: .3rem; color: var(--discret); }
.menu-nouveau button:hover { color: var(--accent); }

/* Un dossier de filtres a la même allure qu'un dossier de flux : ce sont deux
   listes rangeables de la même façon, dans la même barre. */
.dossier--filtres .menu-vide { padding: .2rem 0 .2rem 2.5rem; }

/* ---------------------------------------------------- La page des filtres */

/* Comme le profil et la gestion des flux : le formulaire tient en deux colonnes,
   les réglages d'un côté et l'aperçu de l'autre, ce qui demande toute la place.
   La liste, elle, porte un tableau de quatre colonnes. */
body.page-filtres .vue { max-width: none; }
body.page-filtres .encadre { max-width: none; }

.filtre-colonnes {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 1.5rem;
	align-items: start;
}

/* L'aperçu passe à côté dès qu'il y a la place, et reste visible pendant qu'on
   règle : c'est ce qui en fait un aperçu et non un rapport qu'on va lire une
   fois. En dessous de cette largeur il repasse dessous, où il gêne moins qu'une
   colonne de six centimètres. */
@media (min-width: 68rem) {
	.filtre-colonnes { grid-template-columns: minmax(0, 1fr) 22rem; }

	.filtre-essai {
		position: sticky; top: 1rem;
	}
}

.filtre-essai {
	padding: 1rem;
	background: var(--surface); border: 1px solid var(--bordure); border-radius: var(--rayon);
	margin-bottom: 1.5rem;
}
.filtre-essai h3 { margin: 0 0 .75rem; font-size: 1rem; }
.essai-compte { margin: 0 0 .75rem; }
.essai-compte strong { font-size: 1.4rem; font-variant-numeric: tabular-nums; }

.essai-liste { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: .6rem; }
.essai-liste li { display: flex; flex-direction: column; gap: .1rem; font-size: .875rem; }
.essai-liste a { color: inherit; text-decoration: none; font-weight: 600; }
.essai-liste a:hover { color: var(--accent); text-decoration: underline; }

/* ---------------------------------------------------------- Les sources */

/* Une liste de cinquante flux ne peut pas pousser le reste du formulaire hors de
   l'écran : elle défile chez elle. La hauteur est en `rem` et non en `vh` — sur
   un téléphone, une hauteur relative à la fenêtre laisse deux cases visibles. */
.sources-liste {
	max-height: 20rem; overflow-y: auto; overscroll-behavior: contain;
	padding: .4rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond);
	scrollbar-width: thin;
}

.sources-groupe { margin-bottom: .5rem; }
.sources-groupe:last-child { margin-bottom: 0; }

.sources-titre {
	margin: .3rem 0 .2rem; padding: 0 .3rem;
	font-size: .75rem; font-weight: 700; color: var(--discret);
}

.source {
	display: flex; align-items: center; gap: .45rem;
	padding: .22rem .3rem;
	border-radius: var(--rayon);
	font-size: .875rem;
	cursor: pointer;
}
.source:hover { background: var(--survol); }
.source span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.source input { flex: none; accent-color: var(--accent); }

/* Le dossier coiffe ses flux, qui se décalent d'un cran sous lui : la même
   indentation que dans la barre latérale, pour la même raison — sans elle, la
   liste paraît décrochée du titre qui la porte. */
.source--dossier { font-weight: 700; }
.source--dossier .icone { width: 1.1em; height: 1.1em; color: var(--discret); }
/* Seuls les groupes qui ont un dossier en tête décalent leurs flux : dans le
   fourre-tout « Hors dossier », il n'y a rien au-dessus dont se décaler. */
.sources-groupe:has(.source--dossier) .source:not(.source--dossier) { margin-left: 1.4rem; }

/* ---------------------------------------------------------- Les critères */

.combinaison {
	display: flex; gap: 1rem; flex-wrap: wrap;
	margin: 0 0 .8rem; padding: 0; border: 0;
	font-size: .875rem;
}
.combinaison label { display: flex; align-items: center; gap: .35rem; cursor: pointer; }
.combinaison input { accent-color: var(--accent); }

.criteres { display: flex; flex-direction: column; gap: .5rem; }

.critere {
	display: flex; align-items: center; gap: .4rem; flex-wrap: wrap;
}
.critere select, .critere input[type="text"] {
	padding: .4rem .5rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit; font-size: var(--taille-champ);
	min-width: 0;
}
/* La valeur mange la place restante ; les deux listes gardent la leur. Sans
   `min-width: 0` sur un enfant de flex, un `<select>` refuse de rétrécir sous la
   largeur de sa plus longue option et pousse la ligne hors du cadre. */
.critere-valeur { flex: 1 1 12rem; }
.critere-champ { flex: 0 1 14rem; }
.critere-operateur { flex: 0 1 12rem; }

/* Une expression régulière se lit caractère par caractère : la chasse fixe rend
   visible ce qui, en proportionnelle, se confond — l'espace de trop, le 1 et le
   l, deux parenthèses au lieu d'une. */
.critere-valeur--regex {
	font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
	font-size: .8125rem;
}

.critere-casse {
	display: flex; align-items: center; gap: .25rem;
	padding: .25rem .4rem;
	border-radius: var(--rayon);
	font-size: .8125rem; font-weight: 700; color: var(--discret);
	cursor: pointer;
}
.critere-casse:hover { background: var(--survol); color: var(--texte); }
.critere-casse input { accent-color: var(--accent); }
.critere-casse:has(input:checked) { color: var(--accent); }

.critere-retirer { flex: none; padding: .3rem; color: var(--discret); }
.critere-retirer:hover { color: var(--erreur); }

.critere-outils { margin: .8rem 0 0; }

/* L'aide-mémoire, replié par défaut : il rend service la première fois et gêne
   les suivantes. */
.aide-regex { margin-top: 1rem; font-size: .875rem; }
.aide-regex summary { cursor: pointer; color: var(--discret); }
.aide-regex summary:hover { color: var(--accent); }

.memo {
	display: grid; grid-template-columns: auto 1fr; gap: .3rem .8rem;
	margin: .8rem 0;
	font-size: .8125rem;
}
.memo dt { margin: 0; }
.memo dd { margin: 0; color: var(--discret); }
.memo code, .aide-regex code {
	padding: .05rem .3rem;
	border-radius: 3px;
	background: var(--surface-haute);
	font-family: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace;
	font-size: .95em;
}

/* ------------------------------------------------------------- Les dates */

.dates { display: flex; flex-direction: column; gap: .6rem; margin: 0 0 .8rem; padding: 0; border: 0; }

.date-choix {
	display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
	font-size: .875rem;
	cursor: pointer;
}
.date-choix input[type="radio"] { accent-color: var(--accent); }
.date-choix input[type="number"] { width: 5rem; }
.date-choix input:not([type="radio"]), .date-choix select {
	padding: .35rem .45rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit; font-size: var(--taille-champ);
}
/* Le mode qu'on n'a pas choisi s'estompe sans disparaître : la mise en page ne
   bouge pas, et on voit ce qu'on obtiendrait en changeant d'avis. */
.date-choix--eteint { color: var(--discret); }
.date-choix--eteint input:not([type="radio"]), .date-choix--eteint select { opacity: .55; }

/* ------------------------------------------------- Liste et actions */

.formulaire-actions {
	display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
	margin: 0 0 1.5rem;
}

.formulaire-ligne {
	display: flex; gap: .6rem; align-items: center; flex-wrap: wrap;
}
.formulaire-ligne input[type="text"] {
	flex: 1 1 16rem; min-width: 0;
	padding: .45rem .6rem;
	border: 1px solid var(--bordure); border-radius: var(--rayon);
	background: var(--fond); color: inherit; font: inherit;
}

.encadre-entete { display: flex; align-items: center; gap: .5rem; }
.encadre-entete h3 { flex: 1; min-width: 0; display: flex; align-items: center; gap: .4rem; }

/* Un tableau qui déborde défile chez lui plutôt que de faire défiler la page
   entière de travers. */
.tableau-defilant { overflow-x: auto; }

.tableau--filtres td { vertical-align: middle; }
.tableau--filtres tbody tr:hover { background: var(--survol); }
/* Deux colonnes de largeur fixe au bout : sans elles, le tableau répartit à
   parts égales et la colonne « Non lus » s'étale sur six centimètres pour deux
   chiffres, au détriment du résumé, qui est la seule à en avoir besoin. */
.tableau--filtres .nombre { width: 5rem; }
.tableau--filtres .colonne-actions { width: 7rem; }

.filtre-nom { font-weight: 600; text-decoration: none; white-space: nowrap; }
.filtre-nom:hover { color: var(--accent); }

/* Le résumé des conditions : la colonne qui doit céder quand la place manque,
   parce que c'est la seule dont on peut tronquer la fin sans perdre l'essentiel. */
.filtre-resume { color: var(--discret); font-size: .8125rem; min-width: 16rem; }

/* Pas de `display: flex` ici : la cellule cesserait d'être une cellule de
   tableau, et sa colonne se désaligne des autres lignes. Les enfants s'alignent
   en ligne, ce qui suffit à trois boutons. */
.colonne-actions { white-space: nowrap; }
.colonne-actions form { display: inline-block; vertical-align: middle; }
.colonne-actions .outil { padding: .35rem; vertical-align: middle; }
.colonne-actions .outil:hover { color: var(--accent); }

/* ------------------------------------------------- Journal des erreurs */

/* Replié par défaut, et discret jusque dans sa couleur : ce journal ne sert
   qu'à comprendre une panne. Le reste du temps, il n'a pas à peser sur la page. */
.journal { margin: 1.5rem 0; }
.journal summary {
	cursor: pointer;
	font-size: .875rem; font-weight: 600;
	color: var(--texte-doux);
}
.journal summary:hover { color: var(--accent); }
.journal .aide { margin: .6rem 0; }

.tableau--journal { font-size: .8125rem; }
.tableau--journal td { vertical-align: middle; }
.tableau--journal time { white-space: nowrap; }
/* Le motif est la seule colonne à pouvoir s'étirer, et la seule qu'on puisse
   tronquer sans perdre le sens des autres. */
.journal-motif { color: var(--discret); min-width: 18rem; }

/* ------------------------------------------------------ Recherche */

/* Ce que la saisie est devenue, entre le titre et les résultats. */
.recherche-analyse { margin: -.5rem 0 1rem; }

/* Le rappel de syntaxe, en bas de page et replié : il sert la première fois. */
.aide-recherche { margin: 2rem 0 1rem; font-size: .875rem; }
.aide-recherche summary { cursor: pointer; color: var(--discret); }
.aide-recherche summary:hover { color: var(--accent); }
.aide-recherche .memo dd { padding-bottom: .1rem; }

/* --------------------------------------------------- Menu d'une carte */

/* Poussé au bout de la rangée d'actions, donc dans le coin inférieur droit de
   la carte. `margin-left: auto` mange l'espace libre ; sur une colonne étroite où
   la rangée passe à la ligne, le menu reste le dernier élément et garde son coin. */
.carte-menu { position: relative; margin-left: auto; }

.carte-menu > summary {
	list-style: none;
	padding: .1rem .35rem;
	line-height: 1;
	letter-spacing: .05em;
}
.carte-menu > summary::-webkit-details-marker { display: none; }
.carte-menu[open] > summary { background: var(--survol); color: var(--texte); }

/* Le panneau s'ouvre vers le haut et vers la gauche : la carte est en bas de
   liste aussi souvent qu'en haut, et un menu qui déborde par le bas se retrouve
   hors de l'écran une fois sur deux. */
.carte-menu-liste {
	position: absolute; right: 0; bottom: 100%; z-index: 20;
	min-width: 15rem;
	margin-bottom: .25rem;
	padding: .25rem;
	background: var(--surface-haute);
	border: 1px solid var(--bordure);
	border-radius: var(--rayon);
	box-shadow: 0 6px 20px rgb(0 0 0 / .28);
}

.carte-menu-action {
	display: flex; align-items: center; gap: .5rem;
	width: 100%;
	padding: .45rem .55rem;
	border: 0; border-radius: var(--rayon);
	background: none; color: var(--texte-doux);
	font: inherit; font-size: .8125rem; text-align: left;
	/* Une entrée de menu, pas un lien dans une phrase : les autres entrées sont
	   des boutons, et un trait sous une seule d'entre elles la ferait passer pour
	   autre chose que ce qu'elle est. */
	text-decoration: none;
	cursor: pointer;
}
.carte-menu-action:hover { text-decoration: none; }
.carte-menu-action:hover { background: var(--survol); color: var(--texte); }
.carte-menu-action .icone { width: 1em; height: 1em; flex: none; }

/* La suppression porte la couleur d'erreur : c'est la seule entrée du menu qui
   détruise quelque chose, et un digest ne se régénère pas. Au survol, le fond
   la reprend — un rouge sur fond de survol clair passerait inaperçu. */
.carte-menu-action--danger { color: var(--erreur); }
.carte-menu-action--danger:hover { color: var(--erreur); background: color-mix(in srgb, var(--erreur) 12%, transparent); }

/* ------------------------------------------- Rangée d'outils de lecture */

/* En tête du panneau, au-dessus du nom du média : des actions qui portent sur
   l'article entier. Le filet du dessous les sépare de ce qui décrit l'article —
   ce sont deux natures d'information, et l'œil doit pouvoir les distinguer sans
   les lire. */
.lecture-outils {
	display: flex; align-items: center; gap: .3rem; flex-wrap: wrap;
	margin: 0 0 .8rem;
	padding-bottom: .6rem;
	border-bottom: 1px solid var(--bordure);
}

/* ------------------------------------------------- L'encadré du résumé */

/* Sous le titre, avant le texte de l'article : ce qu'on lit pour décider si l'on
   va lire le reste. Encadré et non fondu dans la page — un résumé n'est pas de
   l'article, c'est une machine qui en parle, et rien ne doit permettre de
   confondre les deux en survolant la page.

   Le liseré d'accent sur le bord gauche fait ce travail à lui seul : c'est la
   même marque que la citation, et elle dit « ceci vient d'ailleurs » sans qu'il
   faille lire l'intitulé. */
/* L'attente d'un résumé : trois lignes qui scintillent, à la place et à la
   forme du texte qui va venir. Un modèle met cinq à dix secondes à lire un
   article ; pendant ce temps, un encadré presque vide laisse croire qu'il ne
   s'est rien passé, et l'on reclique — sur un bouton qui facture. */
.resume-attente { display: grid; gap: .55rem; margin-top: .7rem; }

.resume-attente span {
	height: .7rem; border-radius: 999px;
	/* Le reflet qui passe est un dégradé quatre fois plus large que la ligne,
	   qu'on fait défiler : une seule propriété animée, et rien à redessiner. */
	/* La teinte du trait, et non celle de l'accent : ces lignes figurent du
	   texte à venir, pas une barre de progression. Essayé en accent, c'était
	   trois barres qui réclamaient l'attention dans un encadré qui l'a déjà. */
	background: linear-gradient(90deg, var(--bordure) 20%, var(--discret) 40%, var(--bordure) 60%);
	background-size: 400% 100%;
	animation: resume-scintille 1.5s ease-in-out infinite;
}

/* Des longueurs inégales, comme un paragraphe : trois lignes de même largeur
   ressemblent à un tableau, pas à du texte en train de s'écrire. */
.resume-attente span:nth-child(2) { width: 94%; }
.resume-attente span:nth-child(3) { width: 62%; }
.resume-attente span:nth-child(2) { animation-delay: .12s; }
.resume-attente span:nth-child(3) { animation-delay: .24s; }

@keyframes resume-scintille {
	from { background-position: 100% 0; }
	to { background-position: 0 0; }
}

/* Sans animation, les lignes restent : elles disent toujours où le résumé
   arrivera, et c'est l'essentiel de ce qu'elles font. */
@media (prefers-reduced-motion: reduce) {
	.resume-attente span { animation: none; background: var(--bordure); }
}

.resume {
	margin: 1rem 0 0;
	padding: .85rem 1rem;
	background: var(--accent-voile);
	border-left: 3px solid var(--accent);
	border-radius: 0 var(--rayon) var(--rayon) 0;
}

.resume-titre {
	margin: 0 0 .5rem;
	font-size: .8125rem; font-weight: 700;
	letter-spacing: .06em; text-transform: uppercase;
	color: var(--accent);
}

/* Un cran sous le corps de l'article : le résumé se lit vite, et il ne doit pas
   rivaliser de présence avec le texte qu'il annonce. */
.resume-corps { font-size: .9375rem; line-height: 1.55; }
.resume-corps > :first-child { margin-top: 0; }
.resume-corps > :last-child { margin-bottom: 0; }
.resume-corps ul { margin: .4rem 0; padding-left: 1.15rem; }
.resume-corps li { margin: .25rem 0; }
.resume-corps p { margin: .5rem 0; }

/* La légende et « Refaire » sur une même ligne, la seconde poussée à droite :
   qui a écrit le résumé se lit, le refaire se cherche. */
.resume-pied {
	display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
	margin: .7rem 0 0;
}

/* « Résumer » en tête de lecture et « Refaire » sous le résumé sont des boutons
   dans un <form> — c'est ce qui les fait marcher sans JavaScript. Le bloc du
   formulaire apportait son propre interligne, qui posait son icône 0,9 px plus
   haut que ses voisines de la même rangée : assez pour se voir, pas assez pour
   qu'on sache d'où ça vient. On ne veut du formulaire que son comportement,
   pas sa boîte. */
.lecture-outils form, .resume-pied form { display: flex; }
.resume-pied .aide { flex: 1 1 12rem; }
.resume-pied form { margin: 0; }

/* -------------------------------------------------------------- La bulle */

/* Confirmation brève d'un geste qui ne laisse aucune trace à l'écran. Au bas et
   au centre : c'est là qu'on la cherche, et elle ne recouvre ni la liste ni la
   colonne de lecture, dont le contenu utile est en haut.

   `visibility` en plus de `opacity` : sans elle, la bulle resterait cliquable et
   lisible par les synthèses vocales une fois effacée. Son délai de transition
   est mis à la fin de la disparition, sinon elle sauterait avant le fondu. */
.bulle {
	position: fixed; z-index: 60;
	left: 50%; bottom: 1.75rem;
	padding: .5rem .9rem;
	border-radius: 999px;
	background: var(--surface-haute);
	border: 1px solid var(--bordure);
	box-shadow: 0 6px 22px rgb(0 0 0 / .3);
	color: var(--texte);
	font-size: .8125rem; font-weight: 600; white-space: nowrap;
	/* Ne prend jamais le clic : elle passe devant le contenu, elle ne doit pas
	   l'intercepter au moment où elle disparaît. */
	pointer-events: none;
	opacity: 0; visibility: hidden;
	transform: translate(-50%, .5rem);
	transition: opacity .18s, transform .18s, visibility 0s .18s;
}

.bulle--visible {
	opacity: 1; visibility: visible;
	transform: translate(-50%, 0);
	transition-delay: 0s;
}

/* Qui a demandé moins de mouvement n'en reçoit pas : la bulle paraît et
   disparaît, sans glisser. */
@media (prefers-reduced-motion: reduce) {
	.bulle, .bulle--visible { transform: translate(-50%, 0); transition: opacity .01s, visibility 0s; }
}

/* ------------------------------------------------------------- Partage */

/* L'adresse partagée, telle qu'elle est arrivée : le titre s'il y en a un, et
   l'adresse dessous, coupée où il faut — une URL de réseau social ne connaît
   pas les espaces. */
.partage-adresse { display: grid; gap: .2rem; margin: 0 0 1rem; overflow-wrap: anywhere; }

/* ------------------------------------------------------------- Annuler */

/* À la place de la bulle, et de la même famille — mais celui-ci se touche. Au
   pouce sur un téléphone : au-dessus de la barre du bas, que la lecture plein
   écran recouvre, d'où les deux hauteurs. */
.annuler {
	position: fixed; z-index: 60;
	left: 50%; bottom: 1.75rem;
	display: flex; align-items: center; gap: .75rem;
	max-width: calc(100vw - 2rem);
	padding: .3rem .35rem .3rem 1rem;
	border-radius: 999px;
	background: var(--surface-haute);
	border: 1px solid var(--bordure);
	box-shadow: 0 6px 22px rgb(0 0 0 / .3);
	color: var(--texte);
	font-size: .8125rem; font-weight: 600;
	opacity: 0; visibility: hidden;
	transform: translate(-50%, .5rem);
	transition: opacity .15s, transform .15s, visibility 0s .15s;
}
.annuler--visible {
	opacity: 1; visibility: visible;
	transform: translate(-50%, 0);
	transition-delay: 0s;
}
.annuler-texte { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.annuler-bouton {
	flex: none;
	min-height: 2.25rem; padding: 0 .9rem;
	border: 0; border-radius: 999px;
	background: transparent; color: var(--accent);
	font: inherit; font-weight: 700; cursor: pointer;
}
.annuler-bouton:hover, .annuler-bouton:focus-visible { background: var(--accent-voile); }

@media (max-width: 860px) {
	.annuler { bottom: calc(52px + .75rem + env(safe-area-inset-bottom, 0px)); }
	body.panneau-ouvert .annuler,
	body.menu-ouvert .annuler { bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
	.annuler, .annuler--visible { transform: translate(-50%, 0); transition: opacity .01s, visibility 0s; }
}

/* Les cases du périmètre d'une programmation : en colonne, serrées, avec assez
   de place pour cliquer le libellé et pas seulement la case. */
.champ-case { display: flex; align-items: center; gap: .5rem; padding: .2rem 0; cursor: pointer; }
