diff --git a/css/style.css b/css/style.css
index 1cd702d..fad8a57 100644
--- a/css/style.css
+++ b/css/style.css
@@ -21,6 +21,10 @@
--accent: #3b82f6;
--danger: #dc2626;
--aujourdhui: #ef4444;
+ /* Réservé à ce qui réclame un arbitrage sans être une erreur : un horizon
+ dépassé n'est pas une faute, c'est une décision à reprendre. D'où une teinte
+ distincte de `--danger`, qui signale ce qui ne va pas. */
+ --attention: #b45309;
--hauteur-barre: 26px;
/* Barre cumulative d'un projet, et fantôme qui la double. Ils sont frères,
@@ -53,6 +57,7 @@
--texte: #f1f5f9;
--texte-doux: #94a3b8;
--ombre: 0 1px 2px rgb(0 0 0 / 0.3);
+ --attention: #fbbf24;
}
}
@@ -209,6 +214,68 @@ button[aria-pressed="true"] {
white-space: nowrap;
}
+/* --- filtre par état ------------------------------------------------------ */
+
+/* Même bandeau que le filtre par tags, mais des boutons carrés et non des
+ pastilles rondes : les deux filtres jouent en sens contraire — cocher un tag
+ resserre, cocher un état élargit — et rien ne serait plus trompeur que deux
+ commandes identiques aux effets opposés. */
+.etats {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 7px 16px;
+ background: var(--surface);
+ border-bottom: 1px solid var(--bordure);
+ flex-shrink: 0;
+}
+
+.etats__liste {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+
+.etat-filtre {
+ font-size: 12px;
+ padding: 3px 10px;
+ border: 1px solid var(--bordure-forte);
+ border-radius: var(--rayon);
+ background: var(--surface);
+ color: var(--texte-doux);
+ cursor: pointer;
+ white-space: nowrap;
+}
+
+/* Un état affiché est plein, un état masqué est creux. La différence se lit au
+ remplissage et pas seulement à la teinte : la barre en porte quatre côte à
+ côte, et la couleur seule n'y suffirait pas. */
+.etat-filtre[aria-pressed="true"] {
+ background: color-mix(in srgb, var(--accent) 12%, var(--surface));
+ border-color: var(--accent);
+ color: var(--accent);
+ font-weight: 500;
+}
+
+.etat-filtre:hover:not(:disabled) {
+ border-color: var(--accent);
+}
+
+/* Le dernier état affiché ne se décoche pas : tout masquer reviendrait à vider
+ la frise sans qu'aucun geste ne l'ait demandé. */
+.etat-filtre:disabled {
+ cursor: default;
+ opacity: 0.75;
+}
+
+/* Les écartés se distinguent des trois états vivants, cochés ou non : c'est le
+ seul dont l'ouverture change la nature de ce qu'on regarde. */
+.etat-filtre[data-etat="discarded"][aria-pressed="true"] {
+ background: color-mix(in srgb, var(--attention) 12%, var(--surface));
+ border-color: var(--attention);
+ color: var(--attention);
+}
+
/* --- tags ----------------------------------------------------------------- */
/* Chaque pastille porte sa teinte dans `--teinte-tag`, calculée par hachage du
@@ -539,6 +606,48 @@ button[aria-pressed="true"] {
color: #16a34a;
}
+/* Un projet envisagé dont l'horizon est passé — ou qui n'en a jamais eu. Même
+ forme que la dérive, dont c'est le pendant : l'une dit qu'un projet engagé a
+ glissé, l'autre qu'un projet envisagé attend qu'on tranche.
+ Volontairement sobre : ce signal doit rester lisible sur une colonne qui en
+ porte plusieurs, sans devenir l'alerte qu'on apprend à ignorer. */
+.libelle-projet__adater {
+ flex: 0 0 auto;
+ font-size: 11px;
+ font-weight: 500;
+ padding: 1px 5px;
+ border-radius: 10px;
+ white-space: nowrap;
+ background: color-mix(in srgb, var(--attention) 15%, transparent);
+ color: var(--attention);
+ cursor: default;
+}
+
+/* --- états du cycle de vie ------------------------------------------------ */
+
+/* Un projet terminé **reste** sur la frise : elle est notre mémoire, et l'en
+ retirer perdrait le bilan (décision 27). Il s'éteint donc au lieu de
+ disparaître — assez pour que l'œil aille d'abord à ce qui est vivant, pas
+ assez pour qu'on ne puisse plus le lire.
+ L'écarté va plus loin : il n'est visible que si on l'a demandé au filtre, et
+ le gris dit qu'il ne se passera plus rien. */
+.libelle-projet[data-etat="completed"],
+.couloir--projet[data-etat="completed"] {
+ opacity: 0.65;
+}
+
+.libelle-projet[data-etat="discarded"],
+.couloir--projet[data-etat="discarded"] {
+ opacity: 0.5;
+ filter: grayscale(0.85);
+}
+
+/* Le nom d'un projet écarté se barre : l'opacité seule dirait « en retrait »,
+ là où il faut lire « on ne le fera pas ». */
+.libelle-projet[data-etat="discarded"] .libelle-projet__nom {
+ text-decoration: line-through;
+}
+
.plier {
border: 0;
background: none;
@@ -731,10 +840,17 @@ button[aria-pressed="true"] {
background: rgb(255 255 255 / 0.5);
}
-.barre:hover .poignee {
+.barre:hover .poignee,
+.barre-horizon:hover .poignee {
opacity: 1;
}
+/* Sur fond pâle et hachuré, un voile blanc à demi transparent ne se voit pas :
+ les poignées d'un horizon prennent la teinte de son projet. */
+.barre-horizon .poignee {
+ background: color-mix(in srgb, var(--couleur-projet) 55%, transparent);
+}
+
.poignee--debut {
left: 0;
border-radius: 4px 0 0 4px;
@@ -858,6 +974,67 @@ button[aria-pressed="true"] {
height: 4px;
}
+/* --- horizon d'un projet envisagé ---------------------------------------- */
+
+/* La barre d'un projet qui n'a pas encore de phase. Tout, ici, sert à dire que
+ les dates ne sont pas fermes (décision 27) :
+
+ - les **bords fondus** (`mask-image`), qui suppriment le trait net qu'on
+ pourrait prendre pour une date de début ou de fin ;
+ - les **hachures obliques**, qui la distinguent au premier coup d'œil d'une
+ barre de phase, y compris pour qui ne perçoit pas la nuance de teinte.
+
+ Elle se glisse et se redimensionne comme une barre de phase, mais accrochée au
+ **mois** plutôt qu'au lundi : le geste est possible, il ne permet simplement
+ pas de poser une date qu'on n'a pas.
+
+ Sa largeur porte à elle seule l'incertitude : un mois pour un projet mûr, une
+ large plage pour un projet vague. */
+.barre-horizon {
+ position: absolute;
+ top: calc((var(--hauteur-ligne) - var(--hauteur-barre)) / 2);
+ height: var(--hauteur-barre);
+ border-radius: 4px;
+ color: var(--texte-doux);
+ font-size: 12px;
+ font-style: italic;
+ line-height: var(--hauteur-barre);
+ cursor: grab;
+ user-select: none;
+ z-index: 2;
+ background:
+ repeating-linear-gradient(
+ -45deg,
+ color-mix(in srgb, var(--couleur-projet) 26%, transparent),
+ color-mix(in srgb, var(--couleur-projet) 26%, transparent) 5px,
+ transparent 5px,
+ transparent 10px
+ ),
+ color-mix(in srgb, var(--couleur-projet) 12%, var(--surface));
+ mask-image: linear-gradient(
+ 90deg,
+ transparent,
+ #000 14px,
+ #000 calc(100% - 14px),
+ transparent
+ );
+}
+
+/* Un horizon dépassé se teinte, en écho à la pastille « à dater » de la colonne.
+ La marque est portée deux fois — dans la colonne et sur la frise — parce que
+ les deux se lisent séparément : on parcourt l'une, on balaie l'autre. */
+.barre-horizon--depasse {
+ background:
+ repeating-linear-gradient(
+ -45deg,
+ color-mix(in srgb, var(--attention) 30%, transparent),
+ color-mix(in srgb, var(--attention) 30%, transparent) 5px,
+ transparent 5px,
+ transparent 10px
+ ),
+ color-mix(in srgb, var(--attention) 12%, var(--surface));
+}
+
/* --- panneau de détail --------------------------------------------------- */
/* Non modal : aucun voile derrière lui, la frise reste manipulable pendant
@@ -1011,6 +1188,106 @@ button[aria-pressed="true"] {
margin-top: 4px;
}
+/* Les deux actes du cycle de vie, côte à côte et à parts égales : aucun des deux
+ n'est plus courant que l'autre, et les mettre l'un sous l'autre suggérerait
+ une progression qui n'existe pas — on termine *ou* on écarte. */
+.panneau__actions--cycle {
+ display: flex;
+ gap: 8px;
+}
+
+.panneau__actions--cycle button {
+ flex: 1 1 0;
+ white-space: nowrap;
+}
+
+/* --- cycle de vie dans le panneau ---------------------------------------- */
+
+/* Où en est le projet. Une ligne de texte et non un sélecteur : l'état se
+ déduit des données (décision 27), il n'y a rien à y choisir. */
+.panneau__etat {
+ margin: 0;
+ font-size: var(--taille-annotation);
+ font-weight: 600;
+ letter-spacing: 0.02em;
+ color: var(--texte-doux);
+}
+
+.panneau__etat[data-etat="discarded"] {
+ color: var(--attention);
+}
+
+.horizon {
+ border: 1px solid var(--bordure);
+ border-radius: var(--rayon);
+ padding: 8px 10px 10px;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.horizon > legend {
+ font-size: var(--taille-annotation);
+ color: var(--texte-doux);
+ padding: 0 4px;
+}
+
+/* Les deux bornes côte à côte : c'est un intervalle, et le lire sur deux lignes
+ ferait perdre ce que sa largeur veut dire. */
+.horizon__intervalle {
+ display: flex;
+ gap: 8px;
+}
+
+.horizon__intervalle label {
+ flex: 1 1 0;
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ font-size: var(--taille-annotation);
+ color: var(--texte-doux);
+}
+
+.horizon__crans {
+ display: flex;
+ gap: 6px;
+}
+
+.horizon__crans button {
+ flex: 1 1 0;
+ padding: 4px 6px;
+ font-size: var(--taille-annotation);
+ border: 1px solid var(--bordure-forte);
+ border-radius: var(--rayon);
+ background: var(--surface);
+ color: var(--texte);
+ cursor: pointer;
+}
+
+.horizon__crans button:hover:not(:disabled) {
+ border-color: var(--accent);
+ color: var(--accent);
+}
+
+.horizon__crans button:disabled {
+ opacity: 0.45;
+ cursor: default;
+}
+
+.horizon__aide {
+ margin: 0;
+ font-size: var(--taille-annotation);
+ line-height: 1.35;
+ color: var(--texte-doux);
+}
+
+/* Un horizon passé le dit ici comme il le dit sur la frise et dans la colonne :
+ c'est le même signal, vu depuis les trois endroits où l'on travaille. */
+.horizon__aide[data-alerte="true"] {
+ color: var(--attention);
+}
+
/* --- bloc de notes -------------------------------------------------------- */
/* Le bloc prend toute la hauteur que les champs au-dessus lui laissent. C'est
@@ -1791,6 +2068,48 @@ button.mois-entree__plier {
color: var(--texte-doux);
}
+/* --- ligne d'un projet envisagé ------------------------------------------ */
+
+/* Mêmes colonnes qu'une ligne de phase — la liste reste une grille qu'on lit en
+ diagonale —, mais tout y dit que rien n'est arrêté : l'italique de l'intitulé
+ et les hachures de la marque, qui reprennent celles de la barre sur la frise.
+ Ce qu'on a appris à lire d'un côté vaut de l'autre. */
+.mois-entree--envisage .mois-entree__nom {
+ font-style: italic;
+ color: var(--texte-doux);
+}
+
+.mois-entree--envisage .mois-entree__marque {
+ background: repeating-linear-gradient(
+ -45deg,
+ color-mix(in srgb, var(--couleur-projet) 60%, transparent),
+ color-mix(in srgb, var(--couleur-projet) 60%, transparent) 3px,
+ transparent 3px,
+ transparent 6px
+ );
+ border: 1px solid color-mix(in srgb, var(--couleur-projet) 45%, transparent);
+}
+
+.mois-entree__statut[data-statut="considered"],
+.mois-entree__statut[data-statut="discarded"] {
+ font-style: italic;
+}
+
+/* Un projet écarté qui n'a jamais été engagé passe aussi par cette ligne — il
+ n'a que son horizon pour se situer. Il s'y lit comme sur la frise : éteint et
+ barré, pour qu'on ne le confonde pas avec ce qui va arriver. */
+.mois-entree--envisage[data-etat="discarded"] {
+ opacity: 0.55;
+}
+
+.mois-entree--envisage[data-etat="discarded"] .mois-entree__projet {
+ text-decoration: line-through;
+}
+
+.mois-entree--envisage[data-etat="discarded"] .mois-entree__statut {
+ color: var(--attention);
+}
+
.mois__vide {
max-width: 1080px;
margin: 48px auto;
diff --git a/index.html b/index.html
index 499a891..1dcdfa9 100644
--- a/index.html
+++ b/index.html
@@ -54,6 +54,22 @@
+
+
+ Afficher
+
+
+
- Aucun projet ne porte tous les tags sélectionnés.
+ Aucun projet ne passe les filtres en cours.