Files
gestion_projets/css/style.css
Bertrand Benjamin 6b15972e3b Rendre la barre cumulative identique, pliée ou dépliée
Elle s'épaississait en mode plié, au motif qu'elle y était seule sur sa
ligne. Mais plier un couloir ne change rien à ce que cette barre
représente : la voir changer d'aspect au pli laissait croire à deux
objets différents.

Elle garde donc partout la hauteur de sa version dépliée, plus mince
qu'une barre de phase — puisqu'elle voisine avec elles dès que le projet
est déplié, et qu'elle ne se glisse pas.

`--hauteur-cumulative` remonte dans `:root` : la barre et le fantôme qui
la double sont frères et non imbriqués, la variable doit donc vivre
au-dessus des deux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:03:18 +02:00

1071 lines
24 KiB
CSS

/* Frise multi-projets — feuille de style unique.
*
* Deux principes structurent ce fichier :
*
* 1. Les barres, jalons et cellules d'en-tête sont positionnés en absolu :
* timeline.js leur calcule un `left` et une `width` en pixels depuis
* l'échelle temporelle. Les couloirs, eux, restent en flux normal.
*
* 2. La frise entière est une grille dans un unique conteneur défilant, dont
* l'en-tête et la colonne des libellés sont figés par `position: sticky`.
* Le défilement des deux axes est donc entièrement natif.
*/
:root {
--fond: #f8fafc;
--surface: #ffffff;
--bordure: #e2e8f0;
--bordure-forte: #cbd5e1;
--texte: #0f172a;
--texte-doux: #64748b;
--accent: #3b82f6;
--danger: #dc2626;
--aujourdhui: #ef4444;
--hauteur-barre: 26px;
/* Barre cumulative d'un projet, et fantôme qui la double. Ils sont frères,
pas imbriqués : la variable doit donc vivre au-dessus des deux. */
--hauteur-cumulative: 9px;
--hauteur-ligne: 38px;
--hauteur-entete: 66px; /* trois bandes de 22 px : année, mois, semaine */
/* Doit rester en accord avec `LARGEUR_LIBELLES.defaut` (app.js), sur lequel
retombe le double-clic de la poignée. */
--largeur-libelles: 300px;
--rayon: 6px;
--ombre: 0 1px 2px rgb(15 23 42 / 0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--fond: #0f172a;
--surface: #1e293b;
--bordure: #334155;
--bordure-forte: #475569;
--texte: #f1f5f9;
--texte-doux: #94a3b8;
--ombre: 0 1px 2px rgb(0 0 0 / 0.3);
}
}
* {
box-sizing: border-box;
}
/* Indispensable : plus bas, `.panneau` et `.alerte` déclarent `display: flex`,
ce qui écrase le `display: none` que le navigateur associe à l'attribut
`hidden`. Sans cette règle, ces éléments restent des calques `position: fixed`
étendus sur toute la page, invisibles mais interceptant tous les clics — la
frise devient inerte à la souris. */
[hidden] {
display: none !important;
}
body {
margin: 0;
font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
color: var(--texte);
background: var(--fond);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* --- barre d'outils (préfixe `outils` : `barre` est réservé aux barres de phase) ------------------------------------------------------ */
.outils {
display: flex;
align-items: center;
gap: 16px;
padding: 10px 16px;
background: var(--surface);
border-bottom: 1px solid var(--bordure);
flex-shrink: 0;
}
.outils h1 {
font-size: 15px;
font-weight: 600;
margin: 0;
white-space: nowrap;
}
.outils__groupe {
display: flex;
gap: 4px;
}
.outils__etat {
margin: 0 0 0 auto;
font-size: 12px;
color: var(--texte-doux);
min-width: 130px;
text-align: right;
}
.outils__etat[data-etat="erreur"] {
color: var(--danger);
font-weight: 500;
}
button {
font: inherit;
padding: 5px 11px;
border: 1px solid var(--bordure-forte);
border-radius: var(--rayon);
background: var(--surface);
color: var(--texte);
cursor: pointer;
}
button:hover {
border-color: var(--accent);
}
button[aria-pressed="true"] {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.bouton--principal {
background: var(--accent);
border-color: var(--accent);
color: #fff;
}
.bouton--danger {
color: var(--danger);
border-color: var(--danger);
}
.bouton--danger:hover {
background: var(--danger);
color: #fff;
}
/* --- barre de filtres ----------------------------------------------------- */
.filtres {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 16px;
background: var(--surface);
border-bottom: 1px solid var(--bordure);
flex-shrink: 0;
}
.filtres__titre {
font-size: 12px;
font-weight: 500;
color: var(--texte-doux);
white-space: nowrap;
}
.filtres button:not(.tag) {
font-size: 12px;
padding: 3px 9px;
margin-left: auto;
white-space: nowrap;
}
/* --- tags ----------------------------------------------------------------- */
/* Chaque pastille porte sa teinte dans `--teinte-tag`, calculée par hachage du
nom du tag (model.js). La saturation et la clarté restent fixes, elles : ce
sont elles qui garantissent le contraste, quelle que soit la teinte tirée. */
.tags {
display: flex;
gap: 4px;
min-width: 0;
overflow: hidden;
}
.tags--filtres,
.tags--suggestions {
flex-wrap: wrap;
overflow: visible;
}
.tag {
font: inherit;
font-size: 10px;
font-weight: 500;
line-height: 1.6;
padding: 0 6px;
max-width: 84px;
border: 1px solid transparent;
border-radius: 9px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
background: hsl(var(--teinte-tag) 62% 92%);
color: hsl(var(--teinte-tag) 55% 27%);
}
.tags--filtres .tag,
.tags--suggestions .tag {
font-size: 11px;
padding: 1px 9px;
max-width: none;
border-radius: 11px;
}
.tag:hover {
border-color: hsl(var(--teinte-tag) 45% 55%);
}
/* Un tag actif s'inverse : le fond prend la teinte pleine. Il faut donc
neutraliser la règle générale `button[aria-pressed="true"]`, qui repeindrait
toutes les pastilles de la même couleur d'accent. */
.tag[aria-pressed="true"] {
background: hsl(var(--teinte-tag) 52% 38%);
border-color: hsl(var(--teinte-tag) 52% 38%);
color: #fff;
}
@media (prefers-color-scheme: dark) {
.tag {
background: hsl(var(--teinte-tag) 34% 24%);
color: hsl(var(--teinte-tag) 62% 76%);
}
.tag[aria-pressed="true"] {
background: hsl(var(--teinte-tag) 58% 62%);
border-color: hsl(var(--teinte-tag) 58% 62%);
color: #0f172a;
}
}
/* --- structure de la frise ------------------------------------------------ */
/* Un seul conteneur défilant, sur les deux axes. L'en-tête et la colonne des
libellés restent en place grâce à `position: sticky`, ce qui évite toute
synchronisation de défilement en JavaScript. */
.frise {
flex: 1;
min-height: 0;
overflow: auto;
background: var(--fond);
}
.grille {
display: grid;
grid-template-columns: var(--largeur-libelles) max-content;
grid-template-rows: var(--hauteur-entete) max-content;
width: max-content;
/* Sans cela, la dernière ligne colle au bas de la fenêtre et la barre de
défilement horizontale recouvre le dernier couloir. */
padding-bottom: 24px;
}
/* Le coin cumule les deux ancrages, et doit donc passer au-dessus des deux. */
.grille__coin {
position: sticky;
top: 0;
left: 0;
z-index: 6;
background: var(--surface);
border-right: 1px solid var(--bordure);
border-bottom: 1px solid var(--bordure);
}
/* Zone de saisie large de 9 px pour un trait qui n'en fait qu'un : viser une
bordure d'un pixel à la souris est un exercice de patience. */
.poignee-colonne {
position: absolute;
top: 0;
bottom: 0;
right: -4px;
width: 9px;
cursor: col-resize;
z-index: 7;
}
.poignee-colonne::after {
content: "";
position: absolute;
inset: 0 4px;
background: var(--accent);
opacity: 0;
transition: opacity 0.12s;
}
.poignee-colonne:hover::after,
.poignee-colonne:focus-visible::after,
.poignee-colonne[data-glisse="true"]::after {
opacity: 1;
}
.poignee-colonne:focus-visible {
outline: none;
}
.grille__entete {
position: sticky;
top: 0;
z-index: 5;
background: var(--surface);
border-bottom: 1px solid var(--bordure);
}
.grille__libelles {
position: sticky;
left: 0;
z-index: 4;
background: var(--surface);
border-right: 1px solid var(--bordure);
}
.grille__couloirs {
position: relative;
z-index: 1;
}
/* --- en-tête de dates : trois bandes empilées ----------------------------- */
.bande {
position: relative;
height: calc(var(--hauteur-entete) / 3);
border-bottom: 1px solid var(--bordure);
}
.bande--semaine {
border-bottom: 0;
}
.bande__cellule {
position: absolute;
top: 0;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-left: 1px solid var(--bordure);
font-size: 11px;
color: var(--texte-doux);
white-space: nowrap;
overflow: hidden;
pointer-events: none;
}
.bande--annee .bande__cellule {
font-size: 12px;
font-weight: 600;
color: var(--texte);
border-left-color: var(--bordure-forte);
background: color-mix(in srgb, var(--texte) 4%, transparent);
}
.bande--mois .bande__cellule {
font-weight: 500;
color: var(--texte);
border-left-color: var(--bordure-forte);
}
/* Les numéros de semaine sont le grain le plus fin : discrets par défaut,
ils ne doivent pas concurrencer le mois et l'année. */
.bande--semaine .bande__cellule {
font-size: 10px;
letter-spacing: -0.2px;
}
/* --- traits verticaux ----------------------------------------------------- */
.trait {
position: absolute;
top: 0;
width: 1px;
background: var(--bordure);
pointer-events: none;
}
.trait--majeur {
background: var(--bordure-forte);
}
.trait--aujourdhui {
background: var(--aujourdhui);
width: 2px;
z-index: 4;
}
.trait--aujourdhui::after {
content: "";
position: absolute;
top: 0;
left: -3px;
border: 4px solid transparent;
border-top-color: var(--aujourdhui);
}
/* --- libellés ------------------------------------------------------------ */
.libelle-projet {
--fond-libelle: color-mix(in srgb, var(--couleur-projet) 8%, var(--surface));
position: relative;
display: flex;
align-items: center;
gap: 6px;
height: var(--hauteur-ligne);
padding: 0 8px 0 6px;
font-weight: 600;
border-bottom: 1px solid var(--bordure);
background: var(--fond-libelle);
}
/* Commandes secondaires, en surimpression sur le bord droit de la ligne. Le
dégradé leur donne un fond opaque sans trancher : le nom qu'elles recouvrent
s'estompe au lieu d'être coupé net. */
.libelle-projet__commandes {
position: absolute;
top: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
gap: 2px;
padding: 0 6px 0 18px;
opacity: 0;
background: linear-gradient(to right, transparent, var(--fond-libelle) 18px);
transition: opacity 0.12s;
}
.libelle-projet:hover .libelle-projet__commandes,
.libelle-projet:focus-within .libelle-projet__commandes {
opacity: 1;
}
/* Un projet masqué garde ses commandes visibles : le menu est le seul chemin
pour le rétablir, il ne doit pas falloir le deviner sous le curseur. */
.libelle-projet--masque .libelle-projet__commandes {
opacity: 1;
}
/* Le nom est ce qui identifie le projet : il cède en dernier, et jamais
au-delà d'une largeur où il reste reconnaissable. Sous l'ancienne règle
(`min-width: 4em`), les tags et l'étiquette de dérive le réduisaient à
« Refon… » dans une colonne pourtant deux fois trop large pour lui. */
.libelle-projet__nom {
flex: 1 1 auto;
min-width: 7em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.libelle-projet .tags {
flex: 0 1 auto;
max-width: 40%;
}
/* Une pastille écrasée à « c. » occupe la place d'un tag sans en donner
l'information. Elle garde donc une largeur minimale et cède en bloc :
`pastillesTags` n'en affiche qu'un nombre fixe, suivies d'un compteur. */
.libelle-projet .tag {
flex: 0 0 auto;
min-width: 3.4em;
text-align: center;
}
.tag--reste {
flex: 0 0 auto;
align-self: center;
font-size: 10px;
font-weight: 500;
color: var(--texte-doux);
white-space: nowrap;
cursor: default;
}
.libelle-projet__derive {
font-size: 11px;
font-weight: 500;
padding: 1px 5px;
border-radius: 10px;
white-space: nowrap;
}
.libelle-projet__derive[data-sens="retard"] {
background: color-mix(in srgb, var(--danger) 15%, transparent);
color: var(--danger);
}
.libelle-projet__derive[data-sens="avance"] {
background: color-mix(in srgb, #16a34a 15%, transparent);
color: #16a34a;
}
.plier {
border: 0;
background: none;
padding: 2px 4px;
font-size: 10px;
color: var(--texte-doux);
cursor: pointer;
transition: transform 0.15s;
}
.plier[aria-expanded="true"] {
transform: rotate(90deg);
}
/* `display: block` et non `flex` : `text-overflow: ellipsis` ne s'applique pas
au texte d'un conteneur flex, et le nom serait coupé net. Le centrage
vertical passe donc par `line-height`. */
.libelle-phase {
display: block;
line-height: var(--hauteur-ligne);
height: var(--hauteur-ligne);
padding: 0 8px 0 26px;
color: var(--texte-doux);
border-bottom: 1px solid var(--bordure);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
}
.libelle-phase:hover {
color: var(--texte);
}
.libelle-projet--masque {
opacity: 0.45;
}
/* --- couloirs et barres -------------------------------------------------- */
.couloir {
position: relative;
height: var(--hauteur-ligne);
border-bottom: 1px solid var(--bordure);
}
.couloir--projet {
background: color-mix(in srgb, var(--couleur-projet) 5%, transparent);
}
.barre {
position: absolute;
top: calc((var(--hauteur-ligne) - var(--hauteur-barre)) / 2);
height: var(--hauteur-barre);
border-radius: 4px;
background: var(--couleur-projet);
color: #fff;
font-size: 12px;
line-height: var(--hauteur-barre);
cursor: grab;
box-shadow: var(--ombre);
user-select: none;
z-index: 2;
}
/* Le nom se décale pour rester au bord de la vue quand la barre s'enfonce sous
la colonne des libellés — le cas d'une phase longue dont on ne voit que la
fin. Trois conditions le rendent possible, et aucune n'est décorative :
- `sticky` s'ancre au conteneur défilant le plus proche. Un `overflow:
hidden` sur `.barre` en ferait ce conteneur, et le décalage n'aurait plus
lieu : le rognage du texte trop long est donc porté ici, pas sur la barre.
- le décalage est plafonné par la boîte du parent, donc le nom ne sort jamais
de sa barre — d'où `inline-block`, qui laisse au texte la marge de manœuvre
qu'un bloc pleine largeur n'aurait pas.
- `left` vaut la largeur de la colonne des libellés : c'est là que commence
la portion réellement visible de la frise. */
.barre__nom {
position: sticky;
left: var(--largeur-libelles);
display: inline-block;
max-width: 100%;
padding: 0 7px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: top;
pointer-events: none;
}
.barre:hover {
filter: brightness(1.08);
}
.barre--glissee {
cursor: grabbing;
z-index: 5;
filter: brightness(1.12);
box-shadow: 0 4px 12px rgb(15 23 42 / 0.25);
}
.barre--selectionnee {
outline: 2px solid var(--texte);
outline-offset: 1px;
}
/* Le statut se lit à la texture de la barre, pas à sa couleur : la couleur
appartient au projet, elle sert à distinguer les couloirs entre eux. */
.barre--todo {
background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface));
color: var(--texte);
border: 1px solid var(--couleur-projet);
}
.barre--doing {
background: var(--couleur-projet);
}
.barre--done {
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
}
.barre--blocked {
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 6px,
color-mix(in srgb, var(--danger) 75%, var(--couleur-projet)) 6px,
color-mix(in srgb, var(--danger) 75%, var(--couleur-projet)) 12px
);
}
/* Poignées de redimensionnement, révélées au survol de la barre. */
.poignee {
position: absolute;
top: 0;
width: 7px;
height: 100%;
cursor: ew-resize;
opacity: 0;
background: rgb(255 255 255 / 0.5);
}
.barre:hover .poignee {
opacity: 1;
}
.poignee--debut {
left: 0;
border-radius: 4px 0 0 4px;
}
.poignee--fin {
right: 0;
border-radius: 0 4px 4px 0;
}
/* --- jalons -------------------------------------------------------------- */
.jalon {
position: absolute;
top: calc((var(--hauteur-ligne) - 14px) / 2);
width: 14px;
height: 14px;
margin-left: -7px;
background: var(--couleur-projet);
transform: rotate(45deg);
cursor: grab;
z-index: 3;
box-shadow: var(--ombre);
}
.jalon--done {
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
}
.jalon--blocked {
background: var(--danger);
}
.jalon--selectionne {
outline: 2px solid var(--texte);
outline-offset: 1px;
}
/* Le nom du jalon vit hors du losange, pour ne pas subir la rotation. */
.jalon-nom {
position: absolute;
top: calc((var(--hauteur-ligne) - 16px) / 2);
margin-left: 12px;
font-size: 11px;
color: var(--texte-doux);
white-space: nowrap;
pointer-events: none;
z-index: 3;
}
/* --- barre cumulative d'un projet ---------------------------------------- */
/* Barre unique segmentée par phase, affichée que le projet soit plié ou
déplié, et rendue à l'identique dans les deux cas — c'est le même objet, il
n'a pas à changer d'aspect quand on ouvre le couloir.
Volontairement plus mince qu'une barre de phase : elle voisine avec elles
dès que le projet est déplié, et ne se glisse pas.
Lecture seule : voir docs/decisions.md, section 8. */
.barre-cumulative {
position: absolute;
top: calc((var(--hauteur-ligne) - var(--hauteur-cumulative)) / 2);
height: var(--hauteur-cumulative);
border-radius: 3px;
overflow: hidden;
display: flex;
background: color-mix(in srgb, var(--couleur-projet) 15%, var(--surface));
cursor: default;
z-index: 2;
}
.segment {
position: absolute;
top: 0;
height: 100%;
background: var(--couleur-projet);
}
.segment--todo {
opacity: 0.3;
}
.segment--doing {
opacity: 1;
}
.segment--done {
opacity: 0.6;
}
.segment--blocked {
background: var(--danger);
opacity: 0.85;
}
/* --- barre fantôme de référence ------------------------------------------ */
.fantome {
position: absolute;
top: calc(
(var(--hauteur-ligne) - var(--hauteur-barre)) / 2 + var(--hauteur-barre) - 3px
);
height: 5px;
border-radius: 3px;
background: repeating-linear-gradient(
90deg,
var(--bordure-forte),
var(--bordure-forte) 4px,
transparent 4px,
transparent 7px
);
pointer-events: none;
z-index: 1;
}
/* Le fantôme suit la barre qu'il double. Sous la barre cumulative, plus mince
qu'une barre de phase, il flotterait sinon dans le vide. Déclaré après
`.fantome`, dont il partage la spécificité — l'ordre est ce qui le fait
gagner. */
.fantome--cumulatif {
top: calc((var(--hauteur-ligne) + var(--hauteur-cumulative)) / 2);
height: 4px;
}
/* --- panneau de détail --------------------------------------------------- */
/* Non modal : aucun voile derrière lui, la frise reste manipulable pendant
l'édition et cliquer une autre barre y bascule la sélection. */
.panneau {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 340px;
max-width: 90vw;
background: var(--surface);
border-left: 1px solid var(--bordure);
box-shadow: -4px 0 16px rgb(15 23 42 / 0.12);
z-index: 20;
display: flex;
flex-direction: column;
}
/* Le panneau reçoit le focus à l'ouverture pour que le clavier le suive, mais
il n'est pas un contrôle : un anneau de focus autour de tout le panneau
n'apprendrait rien et attirerait l'œil loin du champ à remplir. */
.panneau:focus {
outline: none;
}
.panneau__entete {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--bordure);
}
.panneau__entete h2 {
margin: 0;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.panneau__entete button {
border: 0;
font-size: 20px;
line-height: 1;
padding: 0 6px;
background: none;
}
.panneau__corps {
padding: 16px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 14px;
}
.panneau__corps label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 12px;
font-weight: 500;
color: var(--texte-doux);
}
.panneau__corps input[type="text"],
.panneau__corps input[type="date"],
.panneau__corps select,
.panneau__corps textarea {
font: inherit;
padding: 6px 8px;
border: 1px solid var(--bordure-forte);
border-radius: var(--rayon);
background: var(--fond);
color: var(--texte);
width: 100%;
}
.panneau__corps textarea {
resize: vertical;
}
.champ-double {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.case {
flex-direction: row !important;
align-items: center;
gap: 8px !important;
font-weight: 400 !important;
color: var(--texte) !important;
}
.case input {
width: auto;
}
.panneau__erreur {
margin: 0;
padding: 8px 10px;
border-radius: var(--rayon);
background: color-mix(in srgb, var(--danger) 12%, transparent);
color: var(--danger);
font-size: 12px;
}
.panneau__actions {
margin-top: 4px;
}
/* --- menu contextuel ------------------------------------------------------ */
.menu {
position: fixed;
z-index: 30;
min-width: 210px;
padding: 4px;
background: var(--surface);
border: 1px solid var(--bordure-forte);
border-radius: var(--rayon);
box-shadow: 0 8px 24px rgb(15 23 42 / 0.18);
}
.menu__entree {
display: block;
width: 100%;
text-align: left;
padding: 6px 10px;
border: 0;
border-radius: 4px;
background: none;
white-space: nowrap;
}
.menu__entree:hover:not(:disabled),
.menu__entree:focus-visible {
background: color-mix(in srgb, var(--accent) 14%, transparent);
border-color: transparent;
outline: none;
}
.menu__entree:disabled {
color: var(--texte-doux);
opacity: 0.5;
cursor: default;
}
.menu__entree--danger {
color: var(--danger);
}
.menu__entree--danger:hover:not(:disabled),
.menu__entree--danger:focus-visible {
background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.menu__separateur {
margin: 4px 6px;
border: 0;
border-top: 1px solid var(--bordure);
}
/* Commandes de la ligne d'un projet : ajouter une phase, ouvrir le menu. */
.commande-projet {
border: 0;
background: none;
padding: 2px 5px;
cursor: pointer;
color: var(--texte-doux);
font-size: 14px;
line-height: 1;
border-radius: 4px;
}
.commande-projet:hover {
background: color-mix(in srgb, var(--accent) 16%, transparent);
color: var(--texte);
}
/* --- dialogue de confirmation --------------------------------------------- */
/* Seul `<dialog>` restant : les paramètres d'un projet ont rejoint un panneau
(voir `.panneau`). Modal ici à dessein — interrompre est le but quand une
action est irréversible. */
.dialogue {
padding: 0;
border: 1px solid var(--bordure-forte);
border-radius: 10px;
background: var(--surface);
color: var(--texte);
max-width: 400px;
box-shadow: 0 12px 40px rgb(15 23 42 / 0.28);
}
.dialogue::backdrop {
background: rgb(15 23 42 / 0.45);
}
.dialogue form {
display: flex;
flex-direction: column;
gap: 14px;
padding: 18px 20px 16px;
}
.dialogue h2 {
margin: 0;
font-size: 15px;
}
.dialogue p {
margin: 0;
color: var(--texte-doux);
line-height: 1.5;
}
.couleurs {
margin: 0;
padding: 0;
border: 0;
}
.couleurs legend {
padding: 0 0 6px;
font-size: 12px;
font-weight: 500;
color: var(--texte-doux);
}
.couleurs div {
display: flex;
flex-wrap: wrap;
gap: 7px;
}
.pastille {
width: 24px;
height: 24px;
padding: 0;
border: 2px solid transparent;
border-radius: 50%;
cursor: pointer;
}
.pastille[aria-pressed="true"] {
border-color: var(--texte);
box-shadow: inset 0 0 0 2px var(--surface);
}
.dialogue__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin: 0;
padding: 0;
}
/* --- états divers -------------------------------------------------------- */
.vide {
margin: auto;
text-align: center;
color: var(--texte-doux);
line-height: 2.4;
}
.alerte {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--fond);
z-index: 50;
padding: 24px;
}
.alerte p {
max-width: 560px;
padding: 16px 20px;
border-radius: var(--rayon);
border: 1px solid var(--danger);
background: color-mix(in srgb, var(--danger) 8%, var(--surface));
color: var(--texte);
white-space: pre-wrap;
}
/* Pendant un glisser, on neutralise la sélection de texte sur toute la page :
sinon le navigateur surligne les libellés au passage du curseur. */
body.glisse-en-cours {
user-select: none;
cursor: grabbing;
}