Files
gestion_projets/css/style.css
Bertrand Benjamin eaca2f7813 Revoir l'ergonomie des panneaux et de la colonne des projets
Les panneaux d'édition étaient modaux et la colonne de gauche tronquait
tout ce qu'elle portait. Sept corrections, mesurées sur data/exemple.json.

Panneaux
- Le voile qui recouvrait la page disparaît : la frise reste manipulable
  pendant l'édition, et cliquer une autre barre y bascule la sélection.
  Il rendait aussi le raccourci flèches inatteignable, le focus partant
  dans le champ « Nom » sans qu'on puisse cliquer ailleurs pour en sortir.
- Un clic dans une zone inerte referme le panneau : ce que rendait le
  voile, sans le calque qui figeait la frise.
- Les paramètres d'un projet quittent le <dialog> modal pour le même
  panneau qu'une phase, avec la même édition au fil de la saisie.
  « Nouveau projet » crée donc le projet aussitôt, comme le fait déjà
  l'ajout d'une phase : plus aucune création ne passe par un formulaire.

Colonne des projets
- Le nom ne disposait que de 56 px sur 250, et « client » s'affichait
  « c. ». Les commandes passent en surimpression au survol, les tags
  cèdent en bloc derrière un compteur, et le nom cède en dernier.
- La colonne se redimensionne, largeur conservée dans localStorage.
- L'œil disparaît : il faisait doublon avec l'entrée « Masquer » du menu.
  Un « + » le remplace, l'ajout d'une phase étant l'action la plus
  fréquente, et le nom du projet devient cliquable — il portait déjà un
  curseur « main » sans aucune action derrière.
- Plus de commande « Ajouter un jalon » : un jalon est une phase dont on
  coche la case, pas un autre objet.

Frise
- Le nom d'une phase reste accroché au bord visible quand sa barre
  s'enfonce sous la colonne.
- Cliquer un nom de phase hors écran ramène la frise sur elle.
- La barre cumulative d'un projet s'affiche aussi quand il est déplié,
  amincie pour ne pas se faire passer pour une barre de phase.

Arbitrages consignés dans docs/decisions.md, sections 8 et 17 à 21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:58:47 +02:00

1075 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;
--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é. Lecture seule : voir docs/decisions.md, section 8. */
.barre-cumulative {
position: absolute;
top: calc((var(--hauteur-ligne) - var(--hauteur-barre)) / 2);
height: var(--hauteur-barre);
border-radius: 4px;
overflow: hidden;
display: flex;
box-shadow: var(--ombre);
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;
}
/* Dépliée, la barre cumulative coiffe les phases du projet : elle s'amincit
pour ne pas se faire passer pour l'une d'elles — elle ne se glisse pas. */
.barre-cumulative--fine {
--hauteur-cumulative: 9px;
top: calc((var(--hauteur-ligne) - var(--hauteur-cumulative)) / 2);
height: var(--hauteur-cumulative);
border-radius: 3px;
box-shadow: none;
}
/* --- 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 une barre cumulative amincie,
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--fin {
top: calc((var(--hauteur-ligne) + 9px) / 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;
}