Rendre lisibles le statut et la date imposée
All checks were successful
Build / build (push) Successful in 1m2s

Statut : quatre boutons peints de leur texture, si bien que le panneau
sert enfin de légende. « En cours » se raye et « terminé » redevient vif
— la trame les distingue, la teinte n'a plus à le faire.

Date imposée : un cadre tireté cerne la forme entière. Les taquets
tombaient sous les poignées de redimensionnement et disparaissaient sur
une barre courte. Son libellé passe dans le modèle, ce qui la fait
apparaître dans la vue par mois, qui la taisait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 19:17:15 +02:00
parent 4a513f9a59
commit 815e144d9a
10 changed files with 574 additions and 98 deletions

View File

@@ -804,19 +804,52 @@ button[aria-pressed="true"] {
}
/* Le statut se lit à la texture de la barre, pas à sa couleur : la couleur
appartient au projet, elle sert à distinguer les couloirs entre eux. */
appartient au projet, elle sert à distinguer les couloirs entre eux.
Une seule grammaire pour les quatre, et elle tient en deux traits :
- **plein** pour un état stable, **rayé** pour ce qui bouge ou coince ;
- la couleur du projet **pleine** dès qu'une phase a commencé, **diluée** tant
qu'elle n'a pas commencé.
D'où : à venir dilué et bordé, en cours plein et rayé, terminé plein et nu,
bloqué rayé de rouge — seule teinte étrangère, parce que c'est la seule chose
qui ne parle pas d'avancement.
« Terminé » a longtemps été assourdi, d'abord d'un gris fixe puis d'une
dilution. Les deux disaient « c'est derrière nous » en donnant une frise
morte, où la majorité des barres d'un projet avancé sont éteintes. C'est la
rayure d'« en cours » qui a permis de le rendre vif : la trame distingue
désormais ces deux-là, la teinte n'a plus à s'en charger. */
.barre--todo {
background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface));
color: var(--texte);
border: 1px solid var(--couleur-projet);
}
/* Rayée, et non pleine : posée à côté d'une barre terminée — un aplat de la même
teinte —, une couleur pleine ne se distinguait pas assez pour qu'on lise
l'avancement d'un couloir en diagonale. La rayure lui donne ce que les trois
autres textures ont déjà : une trame, et pas seulement une nuance. Elle mêle
la couleur du projet à elle-même diluée, là où celle d'une phase bloquée y
mêle du rouge : le motif est le même, la teinte dit lequel des deux on
regarde. */
.barre--doing {
background: var(--couleur-projet);
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 6px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 6px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 12px
);
}
/* La couleur du projet, pleine et nue — c'est la seule des quatre à n'avoir
aucune marque, et c'est bien ce qu'on veut dire : il n'y a plus rien à en
suivre. Déclarée quand même, et pas laissée au fond de `.barre` : ce qui vaut
pour un statut doit se lire à l'endroit où on lit les statuts. */
.barre--done {
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
background: var(--couleur-projet);
}
.barre--blocked {
@@ -832,19 +865,37 @@ button[aria-pressed="true"] {
/* Une phase dont la date ne nous appartient pas — déléguée, contractuelle,
réglementaire (docs/decisions.md, section 28).
Deux **taquets** verticaux à ses extrémités, comme les butées d'une pièce
qu'on ne peut plus faire coulisser. La marque se cumule avec les quatre
textures de statut au lieu de leur disputer la place : c'est bien une phase à
venir, en cours ou bloquée, qui se trouve en plus tenue par le dehors.
La forme est **cerclée**, tout entière : c'est tenu de partout, et le trait
suit le contour quel qu'il soit — une barre, un losange. Deux taquets aux
extrémités disaient la même chose et le disaient mal : sur une barre de deux
jours ils l'avalaient, sur une barre à venir ils se lisaient comme une bordure
épaissie, et ils occupaient la place où le survol révèle les poignées de
redimensionnement — le signe « cette date ne vous appartient pas » tombait
exactement là où l'outil dit « tirez ici pour la changer ».
En `inset`, donc sans toucher à la géométrie : la barre garde exactement la
largeur de ses dates, et l'ombre portée reste déclarée à côté — un
`box-shadow` ne se cumule pas d'une règle à l'autre, il se remplace. */
.barre--imposee {
box-shadow:
var(--ombre),
inset 3px 0 0 var(--texte),
inset -3px 0 0 var(--texte);
La marque se cumule avec les quatre textures de statut au lieu de leur
disputer la place : c'est bien une phase à venir, en cours ou bloquée, qui se
trouve en plus tenue par le dehors.
Le trait est **tireté**, et c'est ce qui le sépare du contour de la sélection,
plein. Un cerclage plein avait été essayé d'abord : à la taille réelle des
barres, on ne le distinguait pas d'une phase simplement sélectionnée. Le tiret
dit d'ailleurs ce qu'il faut — ce n'est pas nous qui traçons cette ligne.
Il vit sur un `::after` plutôt que sur la barre : un élément n'a qu'un
`outline`, et c'est la sélection qui le prend. Les deux marques se superposent
donc sans se gêner sur une phase imposée qu'on vient de sélectionner — un
cadre tireté dedans, un contour plein dehors. */
.barre--imposee::after,
.jalon--imposee::after {
content: '';
position: absolute;
inset: 0;
border: 2px dashed var(--texte);
border-radius: inherit;
/* Le losange tourne, son cadre avec lui : le pseudo-élément hérite de la
rotation du jalon, et n'a donc rien à en savoir. */
pointer-events: none;
}
/* Poignées de redimensionnement, révélées au survol de la barre. */
@@ -894,23 +945,35 @@ button[aria-pressed="true"] {
box-shadow: var(--ombre);
}
/* Mêmes textures que les barres, hachures resserrées : le losange fait 14px de
côté, une rayure de 6px n'y tiendrait pas deux fois. */
.jalon--doing {
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 6px
);
}
.jalon--done {
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
background: var(--couleur-projet);
}
.jalon--blocked {
background: var(--danger);
}
/* Un losange n'a pas d'extrémités où poser les taquets d'une barre imposée : il
se cercle. Déclarée après `.jalon`, dont elle partage la spécificité — c'est
l'ordre qui la fait gagner, un `box-shadow` se remplaçant en bloc. */
.jalon--impose {
box-shadow:
var(--ombre),
inset 0 0 0 2px var(--texte);
}
/* Rien ici pour `.jalon--imposee` : son cadre tireté est déclaré avec celui des
barres, plus haut, à la même épaisseur. C'est la même marque, et un losange
n'est qu'une autre forme à cerner — son `::after` s'appuie sur le
`position: absolute` que `.jalon` porte déjà, et tourne avec lui.
Le trait avait été affiné à 1px, au motif que la longueur des tirets suit
l'épaisseur et qu'un losange de 14px n'en porte que quelques-uns. Mais c'est
la lisibilité qui tranche : à 1px sur une forme de cette taille, la marque
s'efface. Mieux vaut peu de tirets qu'on voit que beaucoup qu'on devine. */
.jalon--selectionne {
outline: 2px solid var(--texte);
outline-offset: 1px;
@@ -1167,7 +1230,14 @@ button[aria-pressed="true"] {
et le second garde le droit de rétrécir sous la taille de son contenu — sans
lui, un flex item refuse de descendre sous sa hauteur intrinsèque et c'est la
page entière qui déborderait au lieu du corps qui défile. */
/* `--couleur-projet` se déclare ici, sur l'élément même que detail.js peint en
style inline à l'ouverture : le style inline l'emporte sur cette règle, et
l'accent ne sert que de repli tant qu'aucune phase n'est ouverte — sans quoi
les `color-mix` des textures de statut se peindraient dans le vide. La
déclarer plus bas, sur le groupe de boutons, ferait l'inverse : une valeur
posée sur un descendant gagne toujours contre celle dont il hérite. */
.panneau__corps {
--couleur-projet: var(--accent);
flex: 1;
min-height: 0;
padding: 16px;
@@ -1188,7 +1258,6 @@ button[aria-pressed="true"] {
.panneau__corps input[type="text"],
.panneau__corps input[type="date"],
.panneau__corps select,
.panneau__corps textarea {
font: inherit;
padding: 6px 8px;
@@ -1209,6 +1278,122 @@ button[aria-pressed="true"] {
gap: 10px;
}
/* Le cerclage de la barre imposée, reporté sur les deux champs que la case
qualifie : cocher « date imposée » n'avait jusque-là aucun effet visible dans
le panneau, alors que la conséquence porte précisément sur ces dates-là.
Le padding est repris par une marge négative de même valeur : les champs ne
bougent pas d'un pixel quand la case change d'état, seul le contour apparaît.
Il déborde de 8px dans les 16px de padding du corps, donc sans jamais sortir
du panneau. */
.champ-double--imposee {
padding: 6px 8px;
margin: -6px -8px;
border-radius: var(--rayon);
outline: 2px dashed var(--texte);
outline-offset: -2px;
}
/* --- statut, en quatre boutons ------------------------------------------- */
/* Chacun peint de la texture qu'il donne à la barre sur la frise : voir
index.html pour le pourquoi. La couleur, elle, vient du formulaire — cf. le
repli déclaré sur `.panneau__corps`. */
.statuts {
margin: 0;
padding: 0;
border: 0;
}
.statuts legend {
padding: 0 0 4px;
font-size: 12px;
font-weight: 500;
color: var(--texte-doux);
}
.statuts__choix {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
/* Le label *est* le bouton. Le radio natif s'efface sans quitter le DOM : c'est
lui qui porte la valeur, réunit le groupe et répond aux flèches. */
.panneau__corps .statut {
flex-direction: row;
align-items: center;
gap: 8px;
padding: 6px 8px;
border: 1px solid var(--bordure-forte);
border-radius: var(--rayon);
font-weight: 400;
color: var(--texte);
cursor: pointer;
}
.statut input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.statut:hover {
background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.statut:has(input:checked) {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
font-weight: 600;
}
/* Le focus se pose sur un radio devenu invisible : c'est au bouton de le
montrer, sans quoi la navigation aux flèches se ferait à l'aveugle. */
.statut:has(input:focus-visible) {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
/* Les quatre textures, reprises telles quelles des barres — hachures resserrées
à l'échelle de la marque, comme celles de la vue par mois. */
.statut__marque {
flex: none;
width: 18px;
height: 9px;
border-radius: 2px;
background: var(--couleur-projet);
}
.statut--todo .statut__marque {
background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface));
border: 1px solid var(--couleur-projet);
}
.statut--doing .statut__marque {
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 6px
);
}
.statut--done .statut__marque {
background: var(--couleur-projet);
}
.statut--blocked .statut__marque {
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 3px,
color-mix(in srgb, var(--danger) 75%, var(--couleur-projet)) 3px,
color-mix(in srgb, var(--danger) 75%, var(--couleur-projet)) 6px
);
}
.case {
flex-direction: row !important;
align-items: center;
@@ -1964,8 +2149,18 @@ kbd {
border: 1px solid var(--couleur-projet);
}
.mois-entree--doing .mois-entree__marque {
background: repeating-linear-gradient(
45deg,
var(--couleur-projet),
var(--couleur-projet) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 3px,
color-mix(in srgb, var(--couleur-projet) 50%, var(--surface)) 6px
);
}
.mois-entree--done .mois-entree__marque {
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
background: var(--couleur-projet);
}
.mois-entree--blocked .mois-entree__marque {
@@ -1990,6 +2185,16 @@ kbd {
background: var(--danger);
}
/* Le même cadre tireté que sur la frise, à l'échelle de la marque : 1px sur huit
de haut plutôt que 2 sur vingt-six. En `border` et non sur un `::after` — la
marque n'a pas de sélection à qui laisser la place, et `border-box` lui garde
sa taille. Déclaré après les quatre textures de statut, dont il ne prend pas
la place : c'est une phase à venir, en cours ou bloquée qui se trouve en plus
tenue par le dehors. */
.mois-entree--imposee .mois-entree__marque {
border: 1px dashed var(--texte);
}
/* Chiffres tabulaires : les quantièmes s'alignent d'une ligne à l'autre, ce qui
fait de la colonne une échelle lisible plutôt qu'une pile de textes. */
.mois-entree__dates {