Frise multi-projets : outil de planification macro
Une frise chronologique unique où plusieurs projets s'empilent en couloirs pliables, pour voir d'un coup d'œil où en est chacun et comment ils se situent les uns par rapport aux autres. Ce n'est pas un outil de suivi quotidien : une phase se compte en semaines, et il n'y a ni sous-tâches, ni tickets, ni dépendances entre phases. - Manipulation directe des barres (glisser, redimensionner), accrochées au lundi - Couloirs pliables : plié, un projet devient une barre segmentée par phase - Planning de référence figeable, avec barre fantôme et calcul de dérive - En-tête à trois bandes : année, mois, numéro de semaine ISO 8601 - Frise qui s'élargit au défilement, pour planifier dans un futur encore vide - Micro-serveur Python (bibliothèque standard) exposant GET/PUT sur /api/data, avec sauvegarde horodatée avant chaque écriture - Zéro build : modules ES natifs, aucune dépendance à installer 50 tests unitaires sur la logique métier (node --test, sans dépendance). Vérifié dans Chromium et Firefox. Les arbitrages de conception et surtout leurs raisons sont consignés dans docs/decisions.md — notamment l'abandon des dépendances entre phases, de la File System Access API, du SVG et des niveaux de zoom. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
877
css/style.css
Normal file
877
css/style.css
Normal file
@@ -0,0 +1,877 @@
|
||||
/* 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 */
|
||||
--largeur-libelles: 250px;
|
||||
|
||||
--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;
|
||||
}
|
||||
|
||||
/* --- 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);
|
||||
}
|
||||
|
||||
.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 {
|
||||
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: color-mix(in srgb, var(--couleur-projet) 8%, var(--surface));
|
||||
}
|
||||
|
||||
.libelle-projet__nom {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.icone-oeil {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 2px 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0.45;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.icone-oeil:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* `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);
|
||||
padding: 0 7px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: grab;
|
||||
box-shadow: var(--ombre);
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* --- projet plié --------------------------------------------------------- */
|
||||
|
||||
/* Barre unique segmentée par phase. Lecture seule : voir docs/decisions.md,
|
||||
section 8. */
|
||||
.barre-pliee {
|
||||
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;
|
||||
}
|
||||
|
||||
/* --- 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;
|
||||
}
|
||||
|
||||
/* --- panneau de détail --------------------------------------------------- */
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.voile {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgb(15 23 42 / 0.15);
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
/* --- 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);
|
||||
}
|
||||
|
||||
.menu-projet {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 2px 5px;
|
||||
cursor: pointer;
|
||||
color: var(--texte-doux);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.menu-projet:hover {
|
||||
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
||||
color: var(--texte);
|
||||
}
|
||||
|
||||
/* --- dialogues ------------------------------------------------------------ */
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.dialogue label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--texte-doux);
|
||||
}
|
||||
|
||||
.dialogue input[type="text"] {
|
||||
font: inherit;
|
||||
padding: 7px 9px;
|
||||
border: 1px solid var(--bordure-forte);
|
||||
border-radius: var(--rayon);
|
||||
background: var(--fond);
|
||||
color: var(--texte);
|
||||
}
|
||||
|
||||
.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__erreur {
|
||||
color: var(--danger) !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
Reference in New Issue
Block a user