Glisser la barre résumé décale toutes les phases du même nombre de jours, écarts conservés : un projet engagé glisse souvent, et le faire phase par phase les déformait. Restent en place ce qui est terminé — on ne réécrit pas le passé — et les phases à date imposée, nouveau booléen `fixed` pour ce qui est délégué ou tenu du dehors. D'où le format en version 6 : sans l'incrément, un binaire antérieur effacerait ces dates imposées à la première sauvegarde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2202 lines
57 KiB
CSS
2202 lines
57 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;
|
|
/* 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,
|
|
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;
|
|
/* Même règle, pour `LARGEUR_PANNEAU.defaut`. Les deux panneaux la partagent :
|
|
ce qu'on élargit est le volet, pas l'un ou l'autre de ses contenus. */
|
|
--largeur-panneau: 380px;
|
|
|
|
--rayon: 6px;
|
|
--ombre: 0 1px 2px rgb(15 23 42 / 0.08);
|
|
|
|
/* Seule taille de la vue par mois à descendre sous celle du corps (14 px).
|
|
Elle est réservée à ce qui commente la liste — compte d'un mois, repère
|
|
« ce mois-ci », pastilles de tags — et jamais à ce qui la compose. */
|
|
--taille-annotation: 12px;
|
|
}
|
|
|
|
@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);
|
|
--attention: #fbbf24;
|
|
}
|
|
}
|
|
|
|
* {
|
|
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;
|
|
}
|
|
|
|
/* --- onglets de vue ------------------------------------------------------- */
|
|
|
|
/* Deux boutons accolés en un seul bloc, pour qu'ils se lisent comme un choix
|
|
entre deux états et non comme deux commandes indépendantes. */
|
|
.onglets {
|
|
display: flex;
|
|
}
|
|
|
|
.onglets button {
|
|
border-radius: 0;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.onglets button:first-child {
|
|
border-radius: var(--rayon) 0 0 var(--rayon);
|
|
}
|
|
|
|
.onglets button:last-child {
|
|
border-radius: 0 var(--rayon) var(--rayon) 0;
|
|
margin-left: -1px;
|
|
}
|
|
|
|
/* La couleur de l'état pressé vient de la règle générale `button[aria-pressed]`.
|
|
Ne reste ici que l'empilement : sans lui, la bordure du voisin passerait
|
|
par-dessus la sienne à la jointure. */
|
|
.onglets button[aria-pressed="true"] {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* --- 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;
|
|
}
|
|
|
|
/* --- 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
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
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;
|
|
}
|
|
|
|
/* --- curseur clavier ------------------------------------------------------ */
|
|
|
|
/* Le curseur des raccourcis vim se marque ici, et pas seulement sur la barre :
|
|
il se pose aussi sur des lignes qui n'ont aucune barre en face — celle d'un
|
|
projet, un projet plié, un projet masqué. Sans cette marque, `j` et `k`
|
|
déplaceraient quelque chose d'invisible.
|
|
|
|
Un trait sur le bord gauche, posé en `box-shadow` : une vraie bordure
|
|
pousserait le contenu de trois pixels à chaque déplacement du curseur.
|
|
|
|
Les deux sortes de ligne prennent aussi une teinte d'accent, mais pas sur le
|
|
même fond : celui d'un projet est déjà coloré à ses couleurs, et le mélange
|
|
part donc de là. Le trait seul ne suffisait pas sur cette ligne-là, où son
|
|
fond propre et le triangle de pli le noyaient. */
|
|
.libelle-projet--curseur,
|
|
.libelle-phase--curseur {
|
|
color: var(--texte);
|
|
box-shadow: inset 3px 0 0 var(--accent);
|
|
}
|
|
|
|
.libelle-phase--curseur {
|
|
background: color-mix(in srgb, var(--accent) 12%, var(--surface));
|
|
}
|
|
|
|
/* On redéfinit `--fond-libelle` au lieu de peindre le fond : le dégradé qui
|
|
donne un fond aux commandes s'appuie sur cette même variable, et un fond posé
|
|
en direct laisserait une couture visible sous elles. La valeur reprend donc
|
|
celle de `.libelle-projet`, teintée d'accent — on ne peut pas se citer
|
|
soi-même dans une variable. */
|
|
.libelle-projet--curseur {
|
|
--fond-libelle: color-mix(
|
|
in srgb,
|
|
var(--accent) 16%,
|
|
color-mix(in srgb, var(--couleur-projet) 8%, var(--surface))
|
|
);
|
|
}
|
|
|
|
/* Le curseur désigne une ligne de projet : ses commandes doivent se voir, sans
|
|
quoi rien ne dirait ce que cette ligne sait faire. */
|
|
.libelle-projet--curseur .libelle-projet__commandes {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* --- 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);
|
|
}
|
|
|
|
/* Curseur clavier posé sur la ligne d'un projet. Elle n'a que sa barre
|
|
cumulative, trop fine pour porter un contour : c'est le couloir entier qui
|
|
prend la teinte. Après `.couloir--projet`, dont elle remplace le fond. */
|
|
.couloir--curseur {
|
|
background: color-mix(in srgb, var(--accent) 10%, 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
|
|
);
|
|
}
|
|
|
|
/* 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.
|
|
|
|
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);
|
|
}
|
|
|
|
/* 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,
|
|
.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;
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
.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é.
|
|
Elle se glisse pour repousser le projet : 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;
|
|
}
|
|
|
|
/* La prise ne s'annonce que là où elle mène quelque part : sur un projet dont
|
|
tout est terminé, la barre n'a pas de rôle et garde le curseur par défaut —
|
|
rien ne bougerait. */
|
|
.barre-cumulative[data-role='cumul'] {
|
|
cursor: grab;
|
|
}
|
|
|
|
.barre-cumulative[data-role='cumul']:hover {
|
|
background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface));
|
|
}
|
|
|
|
/* Mince comme elle est, une barre cumulative saisie se repère mal. L'ombre la
|
|
décolle du couloir le temps du geste, sans changer sa géométrie — ce qui
|
|
décalerait ce qu'on est en train de viser. */
|
|
.barre-cumulative.barre--glissee {
|
|
cursor: grabbing;
|
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--couleur-projet) 60%, transparent);
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* --- 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
|
|
l'édition et cliquer une autre barre y bascule la sélection. */
|
|
.panneau {
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
width: var(--largeur-panneau);
|
|
max-width: 100vw;
|
|
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;
|
|
}
|
|
|
|
/* Poignée de largeur, sur le bord gauche. Jumelle de `.poignee-colonne` : même
|
|
épaisseur, même trait d'accent au survol, même curseur. Ce qui change est le
|
|
bord auquel elle s'accroche, puisque le panneau est ancré à droite. */
|
|
.poignee-panneau {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
left: -4px;
|
|
width: 9px;
|
|
cursor: col-resize;
|
|
z-index: 1;
|
|
}
|
|
|
|
.poignee-panneau::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0 4px;
|
|
background: var(--accent);
|
|
opacity: 0;
|
|
transition: opacity 0.12s;
|
|
}
|
|
|
|
.poignee-panneau:hover::after,
|
|
.poignee-panneau:focus-visible::after,
|
|
.poignee-panneau[data-glisse="true"]::after {
|
|
opacity: 1;
|
|
}
|
|
|
|
.poignee-panneau:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* `flex: 1` et `min-height: 0` : le corps prend toute la hauteur sous l'en-tête,
|
|
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. */
|
|
.panneau__corps {
|
|
flex: 1;
|
|
min-height: 0;
|
|
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;
|
|
}
|
|
|
|
/* 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
|
|
ce qui distingue une note d'un champ : les autres ont une taille dictée par
|
|
ce qu'ils contiennent — une date, un statut —, une note n'en a aucune, et
|
|
quatre lignes fixes étaient un plafond arbitraire qui obligeait à écrire dans
|
|
une meurtrière. Le plancher, lui, reste : sur une fenêtre basse, le corps du
|
|
panneau défile plutôt que d'écraser la note à deux lignes. */
|
|
.notes {
|
|
flex: 1 1 auto;
|
|
min-height: 190px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.notes__entete {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-height: 24px;
|
|
}
|
|
|
|
.notes__intitule {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
/* La barre d'outils n'apparaît qu'en saisie : en aperçu elle ne commanderait
|
|
rien, et sa place revient au texte. */
|
|
.notes__outils {
|
|
display: flex;
|
|
gap: 2px;
|
|
margin-left: auto;
|
|
}
|
|
|
|
.notes__outils button {
|
|
width: 24px;
|
|
height: 24px;
|
|
padding: 0;
|
|
border: 1px solid transparent;
|
|
border-radius: var(--rayon);
|
|
background: none;
|
|
color: var(--texte-doux);
|
|
font-size: 13px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.notes__outils button:hover {
|
|
border-color: var(--bordure-forte);
|
|
color: var(--texte);
|
|
}
|
|
|
|
.notes__outils [data-outil="gras"] {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.notes__outils [data-outil="italique"] {
|
|
font-style: italic;
|
|
font-family: Georgia, serif;
|
|
}
|
|
|
|
/* Le crayon reste à l'extrémité droite quelle que soit la présence de la barre
|
|
d'outils : c'est le seul bouton toujours là, il lui faut une place fixe. */
|
|
.notes__bascule {
|
|
margin-left: auto;
|
|
width: 26px;
|
|
height: 24px;
|
|
padding: 0;
|
|
border: 1px solid var(--bordure-forte);
|
|
border-radius: var(--rayon);
|
|
background: var(--surface);
|
|
color: var(--texte-doux);
|
|
font-size: 13px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.notes__outils:not([hidden]) + .notes__bascule {
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.notes__bascule:hover {
|
|
color: var(--texte);
|
|
border-color: var(--texte-doux);
|
|
}
|
|
|
|
.notes__apercu {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--bordure);
|
|
border-radius: var(--rayon);
|
|
background: var(--fond);
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
cursor: text;
|
|
}
|
|
|
|
.notes__invite {
|
|
margin: 0;
|
|
color: var(--texte-doux);
|
|
font-style: italic;
|
|
}
|
|
|
|
/* `resize: none` contre le `resize: vertical` des autres textarea du panneau :
|
|
celui-ci occupe la place disponible, une poignée de redimensionnement dans
|
|
son coin ne ferait que se battre avec la poignée du volet. */
|
|
.notes__saisie {
|
|
flex: 1;
|
|
min-height: 0;
|
|
resize: none;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
tab-size: 2;
|
|
}
|
|
|
|
/* --- rendu markdown ------------------------------------------------------- */
|
|
|
|
/* Ces règles servent au panneau comme à la vue par mois : une note doit se lire
|
|
pareil des deux côtés, sans quoi on douterait d'avoir la même sous les yeux.
|
|
Les marges sont serrées — c'est un encart dans un volet, pas un article. */
|
|
/* Le style suit `data-niveau` — le nombre de dièses écrits — et non la balise :
|
|
celle-ci est décalée de deux rangs pour ne pas casser la hiérarchie du
|
|
document, et son plafond à `h6` confond les rangs 4 à 6. Les niveaux se
|
|
séparent surtout par le blanc au-dessus : dans un encart de treize pixels,
|
|
deux corps de titre distants d'un pixel ne se distinguent pas, alors qu'un
|
|
interligne plus large se voit tout de suite. */
|
|
.md-titre {
|
|
margin: 12px 0 4px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.md-titre[data-niveau="1"] {
|
|
font-size: 16px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.md-titre[data-niveau="2"] {
|
|
font-size: 14px;
|
|
margin-top: 14px;
|
|
}
|
|
|
|
/* Au-delà du troisième rang, le titre ne grossit plus : il s'efface. C'est un
|
|
intertitre dans un encart, pas une section — et une note qui descend à cinq
|
|
niveaux a de toute façon perdu la partie. */
|
|
.md-titre[data-niveau="4"],
|
|
.md-titre[data-niveau="5"],
|
|
.md-titre[data-niveau="6"] {
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
.md-titre:first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.md-paragraphe {
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.md-paragraphe:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md-liste {
|
|
margin: 0 0 8px;
|
|
padding-left: 20px;
|
|
}
|
|
|
|
.md-liste:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md-liste li {
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
/* La case *est* la puce : les deux ensemble décaleraient le texte sans rien
|
|
ajouter. */
|
|
.md-liste--taches {
|
|
list-style: none;
|
|
padding-left: 2px;
|
|
}
|
|
|
|
/* Une sous-liste de tâches perdrait sinon toute indentation, son retrait ne
|
|
venant que de la puce qu'on vient de lui retirer. Or l'imbrication est
|
|
justement ce qu'on est venu lire : « variante sombre » dépend de « relancer
|
|
sur les maquettes », et rien d'autre que le décalage ne le dit. */
|
|
.md-liste .md-liste--taches {
|
|
padding-left: 20px;
|
|
}
|
|
|
|
/* `.panneau__corps label` empile ses enfants en colonne et les grise — c'est ce
|
|
qu'il faut d'un champ de formulaire, dont l'intitulé surmonte la saisie. Une
|
|
case à cocher dans une note n'est pas cela : la case précède son texte sur la
|
|
même ligne, et ce texte est du contenu, pas une étiquette. D'où la
|
|
spécificité rehaussée, seule façon de reprendre la main sur une règle qui
|
|
vise tous les labels du panneau. */
|
|
.notes__apercu .md-tache,
|
|
.mois-entree__notes .md-tache {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
gap: 6px;
|
|
font-size: inherit;
|
|
font-weight: 400;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* `width: auto` contre la règle qui étire à 100 % les champs du panneau : une
|
|
case à cocher garde sa taille native. */
|
|
.md-tache input {
|
|
margin: 3px 0 0;
|
|
width: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Une tâche faite s'efface sans disparaître : elle compte encore dans ce qui a
|
|
été décidé, elle ne compte plus dans ce qui reste. */
|
|
.md-tache--faite {
|
|
color: var(--texte-doux);
|
|
text-decoration: line-through;
|
|
}
|
|
|
|
/* Les cases de la vue par mois sont désactivées (lecture seule, décision 24) :
|
|
sans cela, le curseur y promettrait un clic sans effet. */
|
|
.md-tache input:disabled {
|
|
cursor: default;
|
|
}
|
|
|
|
.md-tache:has(input:disabled) {
|
|
cursor: default;
|
|
}
|
|
|
|
.md-citation {
|
|
margin: 0 0 8px;
|
|
padding-left: 10px;
|
|
border-left: 3px solid var(--bordure-forte);
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
.md-citation > :last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.md-code {
|
|
margin: 0 0 8px;
|
|
padding: 6px 8px;
|
|
overflow-x: auto;
|
|
border-radius: var(--rayon);
|
|
background: color-mix(in srgb, var(--texte) 7%, transparent);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.md-code-ligne {
|
|
padding: 1px 4px;
|
|
border-radius: 3px;
|
|
background: color-mix(in srgb, var(--texte) 7%, transparent);
|
|
font-size: 0.92em;
|
|
}
|
|
|
|
.md-titre + .md-liste,
|
|
.md-titre + .md-paragraphe {
|
|
margin-top: 0;
|
|
}
|
|
|
|
.notes__apercu hr,
|
|
.mois-entree__notes hr {
|
|
margin: 10px 0;
|
|
border: 0;
|
|
border-top: 1px solid var(--bordure-forte);
|
|
}
|
|
|
|
.notes__apercu a,
|
|
.mois-entree__notes a {
|
|
color: var(--accent);
|
|
}
|
|
|
|
/* --- 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;
|
|
}
|
|
|
|
/* --- aide des raccourcis -------------------------------------------------- */
|
|
|
|
/* Le dialogue de confirmation tient en 400 px parce qu'il pose une question ;
|
|
celui-ci est une table de référence qu'on parcourt du regard. Il prend donc
|
|
la largeur qu'il faut, plafonnée par la fenêtre, et ses sections se rangent
|
|
en colonnes tant qu'elles y tiennent. */
|
|
.dialogue--aide {
|
|
max-width: min(920px, calc(100vw - 32px));
|
|
max-height: calc(100vh - 32px);
|
|
}
|
|
|
|
.aide__colonnes {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 4px 28px;
|
|
}
|
|
|
|
.aide__colonnes h3 {
|
|
margin: 10px 0 6px;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
/* Deux colonnes dans un `<dl>` : la colonne des touches se cale sur la plus
|
|
large de la section — fixée en `em`, elle creusait un couloir vide devant les
|
|
raccourcis d'une seule lettre ; laissée libre, elle mettrait les libellés en
|
|
escalier d'une ligne à l'autre. */
|
|
dl.aide {
|
|
display: grid;
|
|
grid-template-columns: max-content 1fr;
|
|
gap: 3px 16px;
|
|
margin: 0;
|
|
font-size: 13px;
|
|
}
|
|
|
|
dl.aide dt {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 3px;
|
|
align-items: baseline;
|
|
}
|
|
|
|
dl.aide dd {
|
|
margin: 0;
|
|
color: var(--texte-doux);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
kbd {
|
|
padding: 1px 5px;
|
|
border: 1px solid var(--bordure-forte);
|
|
border-bottom-width: 2px;
|
|
border-radius: 4px;
|
|
background: var(--fond);
|
|
color: var(--texte);
|
|
font-family: inherit;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.aide__note {
|
|
padding-top: 4px;
|
|
border-top: 1px solid var(--bordure);
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Le point d'interrogation de la barre d'outils. Rond et discret : c'est un
|
|
recours, pas une commande qu'on utilise en travaillant. */
|
|
.outils__aide {
|
|
width: 28px;
|
|
padding: 0;
|
|
border-radius: 50%;
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* --- vue par mois -------------------------------------------------------- */
|
|
|
|
/* Rien n'est positionné en absolu ici, contrairement à la frise : la liste
|
|
n'a pas d'échelle temporelle, elle empile des lignes dans l'ordre du
|
|
calendrier. Un seul axe de défilement, le vertical.
|
|
`position: relative` fait du conteneur l'`offsetParent` de ses groupes, dont
|
|
`revelerMoisCourant` (mois.js) lit l'`offsetTop`. */
|
|
.mois {
|
|
flex: 1;
|
|
min-height: 0;
|
|
position: relative;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 0 16px 40px;
|
|
}
|
|
|
|
/* Une largeur de lecture plutôt que toute la page : sur un écran large, une
|
|
ligne étirée sur 2000 px sépare la date de son statut par un désert. */
|
|
.mois-groupe {
|
|
max-width: 1080px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* Le titre reste visible tant qu'on parcourt son mois : sans lui à l'écran,
|
|
une ligne isolée ne dit plus de quel mois elle relève, puisque les dates y
|
|
sont abrégées au quantième. */
|
|
.mois-groupe__titre {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1;
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 10px;
|
|
margin: 0;
|
|
padding: 18px 2px 7px;
|
|
/* Même taille que les lignes qu'il coiffe : c'est la graisse et le filet qui
|
|
le détachent, pas un corps plus gros. Déclaré tout de même, sinon la taille
|
|
par défaut des `h2` s'appliquerait. */
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
background: var(--fond);
|
|
border-bottom: 1px solid var(--bordure-forte);
|
|
}
|
|
|
|
.mois-groupe__nom {
|
|
text-transform: capitalize;
|
|
}
|
|
|
|
/* Les annotations du titre — le repère du mois courant et le compte — partagent
|
|
la taille des pastilles de tags : ce sont les seuls éléments de la vue à
|
|
descendre sous la taille du corps, parce qu'ils commentent la liste au lieu
|
|
d'en faire partie. */
|
|
.mois-groupe__courant {
|
|
font-size: var(--taille-annotation);
|
|
font-weight: 500;
|
|
padding: 1px 8px;
|
|
border-radius: 9px;
|
|
background: color-mix(in srgb, var(--aujourdhui) 16%, var(--surface));
|
|
color: var(--aujourdhui);
|
|
}
|
|
|
|
.mois-groupe__compte {
|
|
margin-left: auto;
|
|
font-size: var(--taille-annotation);
|
|
font-weight: 400;
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
.mois-entrees {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Colonnes fixes d'une ligne à l'autre : c'est l'alignement vertical des dates,
|
|
des noms et des statuts qui rend la liste parcourable en diagonale. Les
|
|
colonnes de texte sont en `minmax(0, …)` pour que le rognage se fasse à
|
|
l'intérieur, sans jamais pousser le statut hors de l'écran.
|
|
L'ordre suit celui de la lecture : quand, chez qui, puis quoi. Le ressort
|
|
(`1fr`) est sur l'intitulé de la phase, dernière colonne de texte, pour que
|
|
l'espace disponible se déverse à sa droite plutôt qu'au milieu de la ligne. */
|
|
.mois-entree {
|
|
display: grid;
|
|
grid-template-columns: 14px 16px 124px minmax(0, 200px) 172px minmax(0, 1fr) 86px;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 6px 2px;
|
|
border-bottom: 1px solid var(--bordure);
|
|
}
|
|
|
|
.mois-entree:hover {
|
|
background: var(--surface);
|
|
}
|
|
|
|
/* Toute la ligne déplie ses notes, ce qui doit se voir avant le clic : le
|
|
curseur et le fond qui s'appuie au survol le disent, le triangle de la
|
|
gouttière dit ce qui va se passer. Les lignes sans notes ne réagissent pas —
|
|
promettre un geste qui ne fait rien est pire que ne rien promettre. */
|
|
.mois-entree--commentee {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.mois-entree--commentee:hover {
|
|
background: color-mix(in srgb, var(--accent) 6%, var(--surface));
|
|
}
|
|
|
|
/* Reprise des formes de la frise : une barre pour une tâche, un losange pour un
|
|
jalon, et les mêmes textures de statut — c'est le même objet vu autrement. */
|
|
.mois-entree__marque {
|
|
width: 14px;
|
|
height: 8px;
|
|
border-radius: 2px;
|
|
background: var(--couleur-projet);
|
|
}
|
|
|
|
.mois-entree--todo .mois-entree__marque {
|
|
background: color-mix(in srgb, var(--couleur-projet) 28%, var(--surface));
|
|
border: 1px solid var(--couleur-projet);
|
|
}
|
|
|
|
.mois-entree--done .mois-entree__marque {
|
|
background: color-mix(in srgb, var(--couleur-projet) 55%, #64748b);
|
|
}
|
|
|
|
.mois-entree--blocked .mois-entree__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
|
|
);
|
|
}
|
|
|
|
.mois-entree--jalon .mois-entree__marque {
|
|
width: 10px;
|
|
height: 10px;
|
|
margin-left: 2px;
|
|
border-radius: 0;
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.mois-entree--jalon.mois-entree--blocked .mois-entree__marque {
|
|
background: var(--danger);
|
|
}
|
|
|
|
/* 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 {
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--texte-doux);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.mois-entree__projet,
|
|
.mois-entree__nom {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Un jalon se distingue à la lecture, pas seulement à sa marque : sa date est
|
|
un rendez-vous, pas une durée. */
|
|
.mois-entree--jalon .mois-entree__nom {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Triangle de dépli des notes, en gouttière. C'est le même bouton que celui qui
|
|
plie un projet sur la frise (`.plier`), au même endroit et avec le même
|
|
geste ; il en reprend donc l'aspect, en un peu plus lisible — ici il est seul
|
|
dans sa colonne, là-bas il jouxte le nom du projet.
|
|
Les lignes sans notes gardent la case, vide, pour que les colonnes suivantes
|
|
ne se décalent pas d'une ligne à l'autre. */
|
|
.mois-entree__plier {
|
|
justify-self: center;
|
|
padding: 0;
|
|
border: 0;
|
|
background: none;
|
|
font-size: 12px;
|
|
line-height: 1;
|
|
color: var(--texte-doux);
|
|
transition: transform 0.15s;
|
|
}
|
|
|
|
/* La case des lignes sans notes est un `span` vide : elle réserve la colonne,
|
|
elle ne se clique pas. */
|
|
button.mois-entree__plier {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.mois-entree__plier[aria-expanded="true"] {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.mois-entree__plier:hover {
|
|
color: var(--texte);
|
|
}
|
|
|
|
/* Les notes prennent une seconde rangée, en retrait de la gouttière et de la
|
|
marque — les colonnes sont nommées par leur rang, ce qui laisse la grille
|
|
faire le calcul, là où un retrait en pixels aurait recopié des largeurs et se
|
|
serait désaccordé à la première retouche. */
|
|
/* Le `pre-wrap` d'autrefois n'a plus lieu d'être : les retours à la ligne sont
|
|
désormais portés par le rendu markdown, qui les traduit en `<br>` et en
|
|
éléments de liste. */
|
|
.mois-entree__notes {
|
|
grid-column: 3 / -1;
|
|
margin: 2px 0 6px;
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
/* Le projet passe devant la phase, mais reste en retrait : il situe, il n'est
|
|
pas ce qu'on cherche. Le contraste sépare les deux colonnes de texte mieux
|
|
qu'un filet ne le ferait. */
|
|
.mois-entree__projet {
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
.mois-entree__projet::before {
|
|
content: "";
|
|
display: inline-block;
|
|
width: 7px;
|
|
height: 7px;
|
|
margin-right: 6px;
|
|
border-radius: 50%;
|
|
background: var(--couleur-projet);
|
|
vertical-align: baseline;
|
|
}
|
|
|
|
/* Les tags gardent leur colonne même quand le projet n'en porte aucun : c'est
|
|
une colonne de grille, de largeur fixe, pas un élément qui flotterait à la
|
|
suite du projet. Sans cette fixité, l'intitulé des phases démarrerait à une
|
|
abscisse différente selon le nombre de tags de leur projet. */
|
|
.mois-entree .tags {
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Les pastilles de la colonne des libellés sont taillées pour une colonne
|
|
étroite (10 px) ; ici la place ne manque pas, et une ligne où seuls les tags
|
|
seraient minuscules se lirait mal. Elles rejoignent donc la taille des
|
|
annotations, comme dans la barre de filtres. */
|
|
.mois-entree .tag {
|
|
font-size: var(--taille-annotation);
|
|
padding: 1px 8px;
|
|
border-radius: 10px;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.mois-entree__statut {
|
|
text-align: right;
|
|
color: var(--texte-doux);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.mois-entree__statut[data-statut="doing"] {
|
|
color: var(--accent);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.mois-entree__statut[data-statut="blocked"] {
|
|
color: var(--danger);
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Une phase terminée reste consultable mais recule d'un plan : ce qui compte
|
|
dans une liste tournée vers ce qui arrive, c'est ce qui n'est pas fait. */
|
|
.mois-entree--done .mois-entree__nom {
|
|
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;
|
|
text-align: center;
|
|
color: var(--texte-doux);
|
|
}
|
|
|
|
/* --- é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;
|
|
}
|