Files
Planification/src/viewer.css
T
FroSteel 06c0195130 v2026.5.45 — Dock latéral drag&drop, fix verdicts ghost, multi-onglets EZV
Refonte de l'expérience drag&drop avec dock latéral à droite pour parquer
des interventions entre les jours. Fix critique du parser de fiche EV qui
marquait à tort des interventions terminées comme annulées (les dates
d'action sont détectées par scan regex au lieu d'indices fixes [8]/[9]).
Décorrélation des « Logs verbeux » et de la case « Garder les disparitions »
dans Paramètres → Diagnostics.

Issues résolues : #3 #4 #5 #6 #7 #8.

Closes #3 #4 #5 #6 #7 #8
2026-05-08 16:30:47 +02:00

6152 lines
183 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Planification — Extension navigateur EasyVista (Canton de Vaud / DGNSI)
*
* Copyright (c) 2026 Quentin Rouiller
* Licensed under the MIT License — see LICENSE file in the project root.
*
* @author Quentin Rouiller
* @repository https://gitea.netaplaid.ch/FroSteel/Planification
*/
/* ==========================================================================
Thème clair (défaut)
========================================================================== */
:root {
--bg: #f4f5f7;
--bg-elevated: #ffffff;
--bg-muted: #f0f1f3;
--bg-hover: #f7f8fa;
--border: #e2e4e8;
--border-strong: #cfd3da;
--text: #1a1f2b;
--text-muted: #2e3642; /* v2026.5.29 : +contraste (était #4a5260) */
--text-faint: #50596a; /* v2026.5.29 : +contraste (était #6c7583) */
--accent: #0f4f8b;
--accent-soft: #e1ecf7;
--danger: #b03030;
--danger-soft: #fbe6e6;
--warn: #b87a00;
--warn-soft: #fff2d6;
--ok: #2e7b4a;
--ok-soft: #dff0e4;
/* Palette par type d'intervention */
--c-livraison: #2563eb;
--c-livraison-soft: #dbeafe;
/* : récupération en TEAL (turquoise) — distinct des verts de statut. */
--c-recup: #14b8a6;
--c-recup-soft: #ccfbf1;
--c-remplacement: #ea580c;
--c-remplacement-soft: #fed7aa;
--c-incident: #8b5cf6;
--c-incident-soft: #ede9fe;
--c-installation: #2563eb;
--c-installation-soft: #dbeafe;
--c-rollout: #92400e; /* brun */
--c-rollout-soft: #fde68a;
--c-reservation: #f59e0b; /* jaune/ambre */
--c-reservation-soft: #fef3c7;
--c-autre: #6b7280;
--c-autre-soft: #e5e7eb;
/* Statuts clos
- terminated reprend l'ANCIEN vert de récupération (#16a34a) — vert pur
- closed bascule sur un vert plus FONCÉ/bleuté pour bien le distinguer
visuellement de terminated. Les deux restent verts, mais opposés
sur la teinte. */
--c-closed: #047857; /* vert sapin foncé = Clôturé */
--c-closed-soft: #d1fae5;
--c-resolved: #65a30d; /* lime/vert-jaune = Résolu */
--c-resolved-soft: #ecfccb;
--c-terminated: #16a34a; /* vert pur (= ex-recup) = Terminé par tech */
--c-terminated-soft:#dcfce7;
--shadow: 0 1px 3px rgba(20, 30, 50, 0.06), 0 1px 2px rgba(20, 30, 50, 0.04);
--shadow-hover: 0 2px 8px rgba(20, 30, 50, 0.08);
--radius: 8px;
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace;
}
[data-theme="dark"] {
--bg: #16181d;
--bg-elevated: #21242b;
--bg-muted: #1c1f25;
--bg-hover: #2a2e36;
--border: #2e333c;
--border-strong: #414754;
--text: #e6e8ec;
--text-muted: #d0d5de; /* v2026.5.29 : +contraste (était #b8c0cc) — quasi blanc */
--text-faint: #a8b0bc; /* v2026.5.29 : +contraste (était #8b93a0) */
--accent: #5ea8e8;
--accent-soft: #223348;
--danger: #e87878;
--danger-soft: #3b2626;
--warn: #d9a753;
--warn-soft: #3a2e1a;
--ok: #78c59a;
--ok-soft: #1f3a2b;
--c-livraison: #60a5fa;
--c-livraison-soft: #1e3a5f;
/* : récupération en teal (dark mode). */
--c-recup: #2dd4bf;
--c-recup-soft: #134e4a;
--c-remplacement: #fb923c;
--c-remplacement-soft: #4a2512;
--c-incident: #a78bfa;
--c-incident-soft: #2e1065;
--c-installation: #60a5fa;
--c-installation-soft: #1e3a5f;
--c-autre: #9ca3af;
--c-autre-soft: #2a2e36;
/* closed = vert sapin clair, resolved = lime, terminated = vert pur. */
--c-closed: #34d399;
--c-closed-soft: #064e3b;
--c-resolved: #a3e635;
--c-resolved-soft: #1a2e05;
--c-terminated: #4ade80;
--c-terminated-soft:#14432a;
--shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
--shadow-hover: 0 2px 10px rgba(0, 0, 0, 0.4);
}
/* ==========================================================================
Base
========================================================================== */
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: 12px;
line-height: 1.5;
/* v2026.5.45 — coupe la scrollbar horizontale qui apparaissait à
cause du dock translaté en dehors de la viewport (transform sur
position:fixed contribue malgré tout à l'overflow scrollable). */
overflow-x: hidden;
}
.hidden { display: none !important; }
/* ==========================================================================
Topbar
========================================================================== */
/* couleur s'applique uniquement à la topbar, MAIS la police s'applique
à TOUTE la page (--app-font sur body, hérité par cards / popups / tooltips).
Le texte de la topbar utilise --topbar-text pour rester lisible quand l'user
choisit un fond foncé (calculé automatiquement par luminance). */
body {
font-family: var(--app-font, inherit);
}
/* quand l'utilisateur a choisi une couleur de topbar, les boutons et
le bloc Aujourd'hui+horloge adoptent un look translucide qui s'harmonise
avec le fond (au lieu de rester gris/blancs et devenir illisibles sur un
fond vert/bleu/foncé). Le `color-mix` produit une teinte translucide
dérivée de la couleur de texte calculée — donc cohérente et lisible. */
html.has-topbar-color .topbar .btn,
html.has-topbar-color .topbar .btn-today,
html.has-topbar-color .topbar .btn-subtle,
html.has-topbar-color .topbar .btn-refresh,
html.has-topbar-color .topbar .btn-action,
html.has-topbar-color .topbar .btn-icon,
html.has-topbar-color .topbar #refresh-btn,
html.has-topbar-color .topbar #refresh-partial-btn,
html.has-topbar-color .topbar #clear-cache-btn,
html.has-topbar-color .topbar #theme-toggle {
color: var(--topbar-text, var(--text)) !important;
background: color-mix(in srgb, var(--topbar-text, var(--text)) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text, var(--text)) 28%, transparent) !important;
}
html.has-topbar-color .topbar .btn:hover,
html.has-topbar-color .topbar .btn-today:hover,
html.has-topbar-color .topbar .btn-subtle:hover,
html.has-topbar-color .topbar .btn-refresh:hover,
html.has-topbar-color .topbar .btn-action:hover,
html.has-topbar-color .topbar .btn-icon:hover {
background: color-mix(in srgb, var(--topbar-text, var(--text)) 20%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text, var(--text)) 45%, transparent) !important;
}
html.has-topbar-color .today-block {
background: color-mix(in srgb, var(--topbar-text, var(--text)) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text, var(--text)) 28%, transparent) !important;
}
/* La date du planning de la TOPBAR seulement — on l'aligne aussi pour qu'elle
ressorte sans se mélanger au fond. ajout du scope `.topbar` pour
ne PAS toucher la date-custom de la sidebar horizontale (qui doit garder
son look accent / vert d'origine). */
html.has-topbar-color .topbar .date-custom {
background: color-mix(in srgb, var(--topbar-text, var(--text)) 14%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text, var(--text)) 40%, transparent) !important;
color: var(--topbar-text, var(--text)) !important;
}
/* Pastille user-badge + autres icônes textuelles : utiliser le texte topbar. */
html.has-topbar-color .topbar .user-badge {
color: var(--topbar-text, var(--text)) !important;
border-color: color-mix(in srgb, var(--topbar-text, var(--text)) 35%, transparent) !important;
}
/* la sidebar horizontale (= la "barre à gauche") suit la couleur
choisie pour la topbar — fond, texte, boutons et stats globales adaptés
automatiquement pour rester lisibles. */
html.has-topbar-color.view-horizontal .horizontal-sidebar {
background: var(--topbar-bg) !important;
color: var(--topbar-text, var(--text)) !important;
border-right-color: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar .app-clock-date,
html.has-topbar-color.view-horizontal .horizontal-sidebar .app-clock-time,
html.has-topbar-color.view-horizontal .horizontal-sidebar .capture-info,
html.has-topbar-color.view-horizontal .horizontal-sidebar .global-stat,
html.has-topbar-color.view-horizontal .horizontal-sidebar .global-stat b,
html.has-topbar-color.view-horizontal .horizontal-sidebar .global-stat-sub,
html.has-topbar-color.view-horizontal .horizontal-sidebar .global-stat-sep,
html.has-topbar-color.view-horizontal .horizontal-sidebar #app-title {
color: var(--topbar-text, var(--text)) !important;
}
/* on duplique le sélecteur exact (".date-nav .date-custom") pour
battre la spécificité de la règle de base qui force un fond muted neutre. */
html.has-topbar-color.view-horizontal .horizontal-sidebar .today-block,
html.has-topbar-color.view-horizontal .horizontal-sidebar .date-nav .date-custom,
html.has-topbar-color.view-horizontal .horizontal-sidebar .date-custom {
background: color-mix(in srgb, var(--topbar-text) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text) 28%, transparent) !important;
color: var(--topbar-text, var(--text)) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar button.btn,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-today,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-action,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-refresh,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-subtle,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-icon,
html.has-topbar-color.view-horizontal .horizontal-sidebar .btn-nav {
color: var(--topbar-text, var(--text)) !important;
background: color-mix(in srgb, var(--topbar-text) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text) 28%, transparent) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar button.btn:hover {
background: color-mix(in srgb, var(--topbar-text) 20%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text) 45%, transparent) !important;
}
/* icône change-thème en sidebar horizontale — même look translucide
que les autres boutons quand la topbar a une couleur custom. Sans cette
règle l'icône restait blanche/grise et tranchait sur le fond coloré. */
html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle {
color: var(--topbar-text, var(--text)) !important;
background: color-mix(in srgb, var(--topbar-text) 10%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text) 28%, transparent) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle:hover {
background: color-mix(in srgb, var(--topbar-text) 22%, transparent) !important;
border-color: color-mix(in srgb, var(--topbar-text) 50%, transparent) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle #theme-icon {
filter: none;
}
/* séparateurs visibles de la sidebar (border-top/bottom du #stats,
trait au-dessus du 1er bouton, separator-after-clos) prennent une teinte
dérivée du --topbar-text quand l'utilisateur a une couleur custom — sinon
ils restaient gris foncé invisibles sur fond coloré. */
html.has-topbar-color.view-horizontal .horizontal-sidebar #stats {
border-top-color: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important;
border-bottom-color: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar #absence-btn::before {
background: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important;
}
/* barre verticale VERTE à droite des mini-cards UNIQUEMENT quand le
ticket est officiellement CLOS / RÉSOLU (✓✓). Pas pour "Fait" pending,
pas pour Suspendu. */
html.view-horizontal .iv-mini-card.status-closed,
html.view-horizontal .iv-mini-card.status-resolved {
position: relative;
}
html.view-horizontal .iv-mini-card.status-closed::before,
html.view-horizontal .iv-mini-card.status-resolved::before {
content: "";
position: absolute;
right: 0;
top: 4px;
bottom: 4px;
width: 4px;
background: var(--ok, #2e7b4a);
border-radius: 2px;
z-index: 2;
}
/* .iv-done-separator retiré — l'info "X faits" est dans la
barre stats globale au format "X faits / Y clos". */
.topbar {
position: sticky;
top: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: var(--topbar-bg, var(--bg-elevated));
color: var(--topbar-text, var(--text));
border-bottom: 1px solid var(--border);
box-shadow: var(--shadow);
gap: 12px;
flex-wrap: wrap;
}
.topbar-left {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
min-width: 0;
}
/* v2026.5.39 r2 : on verrouille l'ordre des élements de la topbar gauche en
vue classique. Le badge user reste à l'extrême gauche, le titre suit, puis
la nav date et enfin la capture-info. Ce verrou évite que des composants
restaurés depuis la sidebar (vue horizontale → classique) finissent dans
le mauvais ordre. */
/* ordre topbar — initiales | titre | bloc Auj.+horloge | MAJ |
refresh-check | sélecteur date du planning CENTRÉ via marges auto. */
html.view-classic .topbar-left #user-badge { order: 1; }
html.view-classic .topbar-left #app-title { order: 2; }
html.view-classic .topbar-left #today-block { order: 3; }
html.view-classic .topbar-left .capture-info { order: 4; }
html.view-classic .topbar-left #refresh-check { order: 5; }
/* on positionne la nav-date EN ABSOLU au centre de la topbar (et plus
en flex avec marges auto). Sans ça, l'apparition du bouton "Arrêter" dans
la topbar-right faisait rétrécir topbar-left, et le centrage relatif de la
date glissait. Avec position: absolute, la date est ancrée au centre du
viewport ; les éléments left/right peuvent grandir/rétrécir librement
sans déplacer la date. */
html.view-classic .topbar-left .date-nav {
order: 6;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
margin: 0 !important;
z-index: 1;
pointer-events: auto;
}
/* : bascule "compact" déclenchée DYNAMIQUEMENT par le JS dès qu'un
chevauchement est détecté (pas un seuil de viewport fixe). En compact :
- topbar en colonne, sections empilées
- date-nav désancrée du absolute, sur sa propre ligne, centrée
- boutons droite en wrap centré */
html.view-classic.topbar-compact .topbar {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
html.view-classic.topbar-compact .topbar-left {
flex-wrap: wrap;
justify-content: center;
row-gap: 8px;
width: 100%;
}
html.view-classic.topbar-compact .topbar-left .date-nav {
position: static !important;
transform: none !important;
flex: 1 0 100%;
justify-content: center;
margin: 0 !important;
order: 99;
}
html.view-classic.topbar-compact .topbar-right {
flex-wrap: wrap;
justify-content: center;
row-gap: 6px;
width: 100%;
}
html.view-classic.topbar-compact #app-session {
align-self: center;
}
/* encadré qui regroupe le bouton "Aujourd'hui" + l'horloge actuelle. */
.today-block {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 4px 10px 4px 4px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-muted);
}
.today-block #nav-today { margin: 0; }
.today-block #app-clock { margin: 0; }
.topbar h1 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--topbar-text, var(--text));
white-space: nowrap;
}
.capture-info {
font-size: 12px;
color: var(--topbar-text, var(--text-muted));
opacity: 0.85;
white-space: nowrap;
}
.refresh-check {
font-size: 12px;
color: var(--c-recup); /* vert */
font-weight: 700;
opacity: 0;
transform: scale(0.5);
transition: opacity 0.25s ease, transform 0.25s ease;
pointer-events: none;
}
.refresh-check.visible {
opacity: 1;
transform: scale(1);
}
.refresh-check.hidden {
display: none;
}
.topbar-right {
display: flex;
gap: 8px;
flex-shrink: 0;
}
/* Bannière de session expirée (v4.1.12) — sticky sous la topbar, non bloquante.
on utilise --topbar-height (calculé en JS via ResizeObserver) au lieu
d'une valeur fixe — la topbar grandit/rétrécit selon la police, la taille
de la date, le zoom, etc. */
.session-banner {
position: sticky;
top: var(--topbar-height, 56px);
z-index: 8;
display: flex;
align-items: center;
flex-wrap: wrap; /* : permet au sous-bandeau cookies-prompt
de descendre sur sa propre ligne */
gap: 12px;
padding: 12px 18px;
/* v4.2.5 : rouge plus vif + bord plus épais pour visibilité max */
background: linear-gradient(90deg, #c93030, #d84848);
color: #fff;
border-top: 2px solid #ff6060;
border-bottom: 2px solid #7a1515;
font-size: 12px;
font-weight: 500;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
/* petite animation d'apparition pour attirer l'œil */
animation: session-banner-in 0.22s ease-out;
}
/* : sous-bandeau "Multi-onglets EasyVista" — version centrée et
propre. Encart blanc semi-translucide qui se détache du rouge de la
bannière, contenu centré horizontalement, titre + texte court +
bouton, et un lien dismiss discret en dessous. */
.session-banner-cookies-prompt {
flex: 1 0 100%;
margin-top: 10px;
display: flex;
justify-content: center;
}
.session-banner-cookies-inner {
width: 100%;
max-width: 520px;
padding: 14px 18px;
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 10px;
backdrop-filter: blur(2px);
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 8px;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}
.session-banner-cookies-title {
font-size: 14px;
font-weight: 700;
letter-spacing: 0.3px;
}
.session-banner-cookies-explain {
font-size: 12px;
font-weight: 400;
line-height: 1.45;
color: rgba(255, 255, 255, 0.92);
max-width: 460px;
display: flex;
flex-direction: column;
gap: 2px;
}
.session-banner-cookies-explain-sub {
font-size: 11px;
opacity: 0.85;
}
.session-banner-cookies-actions {
margin-top: 4px;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.session-banner-cookies-btn {
background: #fff !important;
color: #c93030 !important;
font-weight: 700 !important;
font-size: 13px !important;
padding: 9px 28px !important;
border-radius: 999px !important;
border: none !important;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.30);
}
.session-banner-cookies-btn:hover {
transform: translateY(-1px);
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.38);
background: #f9f9f9 !important;
}
.session-banner-cookies-btn:disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.session-banner-cookies-dismiss {
background: transparent !important;
color: rgba(255, 255, 255, 0.88) !important;
font-size: 11px !important;
border: none !important;
padding: 2px 6px !important;
cursor: pointer;
text-decoration: underline;
opacity: 0.85;
}
.session-banner-cookies-dismiss:hover { opacity: 1; }
/* variante du sous-bandeau cookies pour les contextes à
fond clair (écran plein "session nécessaire" ET modals d'alerte
"Impossible d'ouvrir la fiche") — encart gris léger, texte normal,
bouton accent, lien dismiss discret. */
.session-needed .session-banner-cookies-prompt,
.modal-card .session-banner-cookies-prompt {
margin-top: 18px;
flex: none;
}
.session-needed .session-banner-cookies-inner,
.modal-card .session-banner-cookies-inner {
background: var(--bg-muted, rgba(0, 0, 0, 0.04));
border: 1px solid var(--border, rgba(0, 0, 0, 0.12));
color: var(--text);
backdrop-filter: none;
}
.session-needed .session-banner-cookies-title,
.modal-card .session-banner-cookies-title {
color: var(--text);
}
.session-needed .session-banner-cookies-explain,
.modal-card .session-banner-cookies-explain {
color: var(--text-muted);
}
.session-needed .session-banner-cookies-btn,
.modal-card .session-banner-cookies-btn {
background: var(--accent, #0f4f8b) !important;
color: #fff !important;
}
.session-needed .session-banner-cookies-btn:hover,
.modal-card .session-banner-cookies-btn:hover {
background: var(--accent-strong, #0a3d6f) !important;
}
.session-needed .session-banner-cookies-dismiss,
.modal-card .session-banner-cookies-dismiss {
color: var(--text-faint, #6b7280) !important;
}
@keyframes session-banner-in {
from { opacity: 0; transform: translateY(-6px); }
to { opacity: 1; transform: translateY(0); }
}
/* v4.2.5 : variante ORANGE pour "EV inaccessible" (distinct de session expirée) */
.session-banner.ev-banner {
background: linear-gradient(90deg, #c77920, #e09a3a);
border-top: 2px solid #ffbb60;
border-bottom: 2px solid #7a4a15;
}
.session-banner.ev-banner .btn-primary {
color: #8a4a10;
}
.session-banner.hidden {
display: none;
}
.session-banner-icon {
font-size: 20px;
flex-shrink: 0;
}
.session-banner-text {
flex: 1;
line-height: 1.4;
}
.session-banner-text strong {
font-weight: 600;
}
.session-banner .btn-primary {
background: #fff;
color: #9a2020;
border: 0;
font-weight: 600;
}
.session-banner .btn-primary:hover {
background: #f0f0f0;
}
.session-banner .btn-sm {
padding: 5px 12px;
font-size: 12px;
/* v4.2.5 : btn-sm non-primary dans la bannière = contour blanc */
background: transparent;
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.5);
font-weight: 500;
}
.session-banner .btn-sm:hover {
background: rgba(255, 255, 255, 0.12);
}
.session-banner .btn-primary.btn-sm {
/* reset : le primary override le style du btn-sm */
background: #fff;
color: #9a2020;
border: 0;
font-weight: 600;
}
.session-banner.ev-banner .btn-primary.btn-sm {
color: #8a4a10;
}
.session-banner .btn-icon {
background: transparent;
color: #fff;
border: 0;
font-size: 20px;
line-height: 1;
padding: 4px 8px;
cursor: pointer;
}
.session-banner .btn-icon:hover {
background: rgba(255,255,255,0.15);
}
/* Barre de progression pendant le rafraichissement — v4.1.12 : texte
toujours lisible, que la zone verte l'ait atteint ou non (utilise
mix-blend-mode:difference pour inverser la couleur du texte là où la
barre verte est dessous). */
.progress-bar {
position: sticky;
/* suit la hauteur réelle de la topbar (calculée en JS). */
top: var(--topbar-height, 56px);
z-index: 9;
height: 22px;
/* v4.1.17 : backdrop-blur sur toute la barre → ce qui défile derrière
est légèrement flouté sur TOUTE la largeur. Pas d'opacité sombre
ajoutée, transparence préservée. */
background: rgba(128, 128, 128, 0.08);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.2));
overflow: hidden;
}
.progress-bar.hidden {
display: none;
}
.progress-bar-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
background: linear-gradient(90deg, #2ea043, #3fb950);
width: 0%;
transition: width 240ms ease-out;
box-shadow: 0 0 8px rgba(63, 185, 80, 0.3);
}
.progress-bar-label {
position: relative;
display: block;
text-align: center;
line-height: 22px;
font-size: 12px;
font-weight: 700;
color: #fff;
pointer-events: none;
letter-spacing: 0.3px;
/* v4.1.14/17 : text-shadow multi-directionnel (halo sombre autour du
texte). Le backdrop-blur est sur toute la barre, plus besoin de pill. */
text-shadow:
0 0 2px rgba(0, 0, 0, 0.95),
0 0 3px rgba(0, 0, 0, 0.85),
0 1px 2px rgba(0, 0, 0, 0.75),
0 -1px 2px rgba(0, 0, 0, 0.75),
1px 0 2px rgba(0, 0, 0, 0.75),
-1px 0 2px rgba(0, 0, 0, 0.75);
z-index: 2;
}
/* Navigation de date */
.date-nav {
display: flex;
align-items: center;
gap: 4px;
flex-wrap: nowrap;
}
/* v2026.5.17 : faux input date custom avec nom du jour */
.date-custom-wrapper {
position: relative;
display: inline-flex;
align-items: center;
}
/* date du planning vue classique — réduite de 10% (31 → 28px). */
.date-custom {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0;
padding: 6px 22px;
line-height: 1;
border: 1px solid var(--border-strong);
border-radius: 18px;
background: var(--bg-muted);
color: var(--topbar-text, var(--text));
font-family: inherit;
font-size: 28px;
font-weight: 700;
cursor: pointer;
white-space: nowrap;
user-select: none;
transition: border-color 0.15s, background 0.15s, transform 0.1s;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.date-custom:hover {
border-color: var(--border-strong);
background: var(--bg-hover);
}
.date-custom:focus {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
/* badge rouge "Auj. JJ.MM" qui apparaît quand la date affichée
n'est PAS aujourd'hui. Cliquable → revient à Auj. */
.today-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
margin-left: 6px;
border: 1px solid var(--danger);
background: var(--danger-soft);
color: var(--danger);
border-radius: 999px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
transition: filter 0.1s;
}
.today-badge:hover { filter: brightness(0.95); }
.today-badge.hidden { display: none; }
.today-badge::before { content: "🔴"; margin-right: 2px; }
.date-input-hidden {
position: absolute;
top: 100%;
left: 0;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
/* v2026.5.38 : ancienne classe .date-picker-day retirée (orpheline depuis le
nouveau picker .date-custom de la v2026.5.17). */
.btn-nav {
padding: 6px 10px;
font-size: 13px;
min-width: 32px;
}
/* bouton "Aujourd'hui" — texte complet, centré, plus visible.
Reste cliquable pour revenir au jour courant à tout moment. */
.btn-today {
padding: 7px 16px;
font-size: 13px;
font-weight: 600;
min-width: 110px;
text-align: center;
justify-content: center;
}
.date-input {
padding: 5px 8px;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg-muted);
color: var(--text);
font-family: inherit;
font-size: 13px;
cursor: pointer;
}
.date-input:hover {
border-color: var(--border-strong);
}
.date-input:focus {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--bg-muted);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 13px;
font-family: inherit;
cursor: pointer;
transition: background 0.1s, border-color 0.1s;
}
.btn:hover {
background: var(--bg-hover);
border-color: var(--border-strong);
}
.btn:active {
transform: translateY(1px);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-icon {
padding: 6px 10px;
font-size: 15px;
}
.btn-subtle {
opacity: 0.75;
font-size: 12px;
}
.btn-subtle:hover {
opacity: 1;
}
/* v4.1.12 : boutons refresh plus clairs visuellement.
- "Vérifier" (partiel) : style discret, icône demi-rotation
- "Tout recharger" (total) : plus affirmé, icône double-flèche circulaire */
.btn-refresh {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
}
.btn-refresh-icon {
width: 15px;
height: 15px;
flex-shrink: 0;
color: currentColor;
}
.btn-refresh-label {
font-size: 12px;
line-height: 1;
}
.btn-refresh-strong {
background: var(--bg-subtle, rgba(63, 185, 80, 0.08));
border-color: var(--border-strong);
}
.btn-refresh-strong:hover {
background: rgba(63, 185, 80, 0.18);
border-color: rgba(63, 185, 80, 0.5);
}
.btn-refresh-icon.spinning {
animation: refresh-spin 0.9s linear infinite;
transform-origin: 50% 50%;
}
@keyframes refresh-spin {
to { transform: rotate(360deg); }
}
.btn-primary {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.btn-primary:hover {
background: var(--accent);
opacity: 0.9;
}
/* Bouton "Arrêter" (apparaît pdt un refresh manuel) */
.btn-abort {
background: var(--danger-soft);
color: var(--danger);
border-color: var(--danger);
}
.btn-abort:hover {
background: var(--danger);
color: white;
border-color: var(--danger);
}
#refresh-icon.spinning {
display: inline-block;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ==========================================================================
Écrans d'état
========================================================================== */
.loading {
padding: 40px 20px;
text-align: center;
color: var(--text-muted);
font-size: 12px;
}
/* error-box — bandeau classique pour les vraies erreurs (rouge). */
.error-box {
margin: 20px;
padding: 16px 20px;
background: var(--danger-soft);
color: var(--danger);
border: 1px solid var(--danger);
border-radius: var(--radius);
font-size: 13px;
line-height: 1.55;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
max-width: 800px;
}
.error-box .btn-primary {
flex: 0 0 auto;
padding: 8px 16px;
font-size: 13px;
font-weight: 600;
background: var(--danger);
color: #fff;
border: 1px solid var(--danger);
border-radius: 8px;
cursor: pointer;
transition: background 0.15s, transform 0.1s, box-shadow 0.15s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.error-box .btn-primary:hover {
background: #8a2424;
border-color: #8a2424;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.error-box .btn-primary:active {
transform: translateY(1px);
}
/* en vue horizontale, .cards est flex:1 — quand il est vide, il
continue de manger toute la largeur, ce qui empêche margin:auto de
centrer la carte d'onboarding. On collapse .cards quand il est vide.
Couvre aussi la vue classique (pas de regression — .cards vide n'est
visible nulle part). */
.cards:empty {
display: none !important;
}
/* positionnement ABSOLU centré pour la carte d'onboarding en
horizontal — approche robuste qui ne dépend pas du flex flow de main et
garantit le centre exact (horizontal + vertical) de la zone main, peu
importe la largeur de la sidebar ou le contenu. */
html.view-horizontal main#main {
position: relative;
min-height: calc(100vh - var(--topbar-height, 56px));
}
html.view-horizontal .error-box.error-box-centered {
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
margin: 0 !important;
}
/* variante "centered notice" — carte centrée, neutre (pas rouge),
layout vertical icon + titre + description + bouton. Utilisée pour les
états d'onboarding (équipe non configurée). Identique en classique et
horizontale (centrée dans la zone main). */
.error-box.error-box-centered {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
width: min(480px, calc(100% - 40px));
max-width: 480px;
/* marge auto horizontale pour centrer en classique (parent main
non-flex). Marge verticale 0 — le centrage vertical est piloté par
align-items: center du parent flex en horizontal (cf. règle :has). */
margin: 0 auto;
padding: 36px 32px;
background: var(--bg-elevated);
color: var(--text);
border: 1px solid var(--border);
border-radius: 14px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
gap: 0;
flex: 0 0 auto;
}
/* en classique, on garde une marge de 80px en haut pour ne pas
coller la carte sous la topbar (qui n'a pas de min-height en classique). */
html.view-classic .error-box.error-box-centered {
margin: 80px auto;
}
.error-box.error-box-centered .error-icon {
font-size: 56px;
line-height: 1;
margin-bottom: 18px;
display: inline-flex;
width: 88px;
height: 88px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: var(--accent-soft);
color: var(--accent);
}
.error-box.error-box-centered .error-title {
font-size: 20px;
font-weight: 700;
color: var(--text);
margin: 0 0 10px 0;
}
.error-box.error-box-centered .error-description {
font-size: 14px;
font-weight: 400;
color: var(--text-muted);
line-height: 1.6;
margin: 0 0 24px 0;
max-width: 380px;
}
.error-box.error-box-centered .btn-primary {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 22px;
font-size: 14px;
font-weight: 600;
background: var(--accent);
color: #fff;
border: 1px solid var(--accent);
border-radius: 10px;
cursor: pointer;
transition: filter 0.15s, transform 0.1s, box-shadow 0.15s;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
}
.error-box.error-box-centered .btn-primary:hover {
filter: brightness(0.92);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
}
.error-box.error-box-centered .btn-primary:active {
transform: translateY(1px);
}
.session-needed {
max-width: 500px;
margin: 60px auto;
padding: 28px 32px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
text-align: center;
}
.session-needed h2 {
margin: 0 0 12px 0;
color: var(--text);
}
.session-needed p {
margin: 10px 0;
color: var(--text-muted);
}
.session-needed code {
background: var(--bg-muted);
padding: 2px 6px;
border-radius: 3px;
font-family: var(--mono);
font-size: 12px;
}
.session-needed button {
margin-top: 14px;
}
/* ==========================================================================
Stats globales
========================================================================== */
.stats {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 8px;
padding: 12px 20px 4px 20px;
font-size: 13px;
color: var(--text-muted);
}
.global-stat b {
color: var(--text);
font-weight: 600;
}
.global-stat-main b {
font-size: 16px;
}
.global-stat-sub {
color: var(--text-faint);
font-size: 12px;
}
.global-stat-sep {
color: var(--text-faint);
opacity: 0.5;
}
/* ==========================================================================
Grille de cartes
========================================================================== */
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 14px;
padding: 14px 20px 40px 20px;
}
.card {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
display: flex;
flex-direction: column;
}
.card-header {
padding: 10px 14px;
border-bottom: 1px solid var(--border);
background: var(--bg-muted);
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
}
.card-tech-name {
font-weight: 600;
font-size: 12px;
color: var(--text);
}
.card-tech-badge {
font-size: 11px;
padding: 2px 8px;
border-radius: 10px;
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 600;
}
/* (issue #1) : séparateur "/" entre badges Pompier + Absence — visible
et bien centré pour qu'il ne se confonde pas avec l'un des deux libellés. */
.card-tech-badge-sep {
display: inline-block;
font-size: 16px;
font-weight: 700;
color: var(--text-muted);
margin: 0 6px;
align-self: center;
line-height: 1;
user-select: none;
}
/* (issue #1) : container des badges collé à droite du header. Le
parent .card-header utilise display:flex + justify-content:space-between
donc ce wrapper se positionne à l'extrémité opposée du nom du tech. */
.card-tech-badge-wrap {
display: inline-flex;
align-items: center;
margin-left: auto;
flex-shrink: 0;
}
.badge-pompier {
background: var(--danger-soft);
color: var(--danger);
}
.badge-absent {
background: var(--bg-muted);
color: var(--text-faint);
border: 1px solid var(--border);
}
.badge-count {
background: var(--accent-soft);
color: var(--accent);
}
.card-body {
padding: 0;
flex: 1;
}
.card-empty {
padding: 14px;
color: var(--text-faint);
font-size: 13px;
font-style: italic;
text-align: center;
}
.card.is-pompier {
border-left: 3px solid var(--danger);
}
.card.is-absent {
opacity: 0.85;
}
.card.is-absent .card-header {
background: var(--bg);
}
/* ==========================================================================
Frise de temps
========================================================================== */
.timeline {
padding: 12px 14px 6px 14px;
background: var(--bg-muted);
border-bottom: 1px solid var(--border);
position: relative;
}
.timeline-pompier {
background: var(--danger-soft);
}
.timeline-bar {
position: relative;
height: 20px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 3px;
overflow: hidden;
}
.timeline-hole {
position: absolute;
top: 0;
bottom: 0;
background: repeating-linear-gradient(
45deg,
transparent 0 4px,
rgba(0, 0, 0, 0.035) 4px 8px
);
cursor: help;
transition: background 0.1s;
}
[data-theme="dark"] .timeline-hole {
background: repeating-linear-gradient(
45deg,
transparent 0 4px,
rgba(255, 255, 255, 0.04) 4px 8px
);
}
.timeline-hole:hover {
background: var(--accent-soft);
outline: 3px solid var(--accent, #0f4f8b);
outline-offset: -3px;
z-index: 6;
}
.timeline-slot {
position: absolute;
top: 0;
bottom: 0;
cursor: help;
transition: filter 0.1s;
border-right: 1px solid var(--bg-elevated);
}
.timeline-slot.color-livraison { background: var(--c-livraison); }
.timeline-slot.color-installation { background: var(--c-installation); }
.timeline-slot.color-recup { background: var(--c-recup); }
.timeline-slot.color-remplacement { background: var(--c-remplacement); }
.timeline-slot.color-incident { background: var(--c-incident); }
.timeline-slot.color-rollout { background: var(--c-rollout); }
.timeline-slot.color-reservation { background: var(--c-reservation); }
.timeline-slot.color-autre { background: var(--c-autre); }
/* : statuts clos/résolu/terminé sur la timeline → on GARDE la couleur
de catégorie en fond et on ajoute un bandeau vert en BAS de la slot pour
signaler que c'est terminé. La couleur de la catégorie reste lisible. */
.timeline-slot.status-closed { box-shadow: inset 0 -4px 0 var(--c-closed); }
.timeline-slot.status-resolved { box-shadow: inset 0 -4px 0 var(--c-resolved); }
.timeline-slot.kind-absence {
/* v5.0.15 : uni gris-noir au lieu de rayé, plus lisible */
background: #2a2f36;
border-right: 1px solid var(--bg-elevated);
}
.timeline-slot:hover,
.timeline-slot.highlight {
filter: brightness(1.12);
outline: 2px solid var(--text);
outline-offset: -2px;
z-index: 2;
}
/* v2026.5.39 r3 : séparateur "midi" — vraie coupure visuelle.
La timeline-bar a overflow:hidden donc les chevrons qui dépassaient en
haut/bas étaient coupés. On reste DANS la bar mais on rend le trait
massif et fortement contrasté + un effet "encoche" via box-shadow latéral
(pour bien détacher du fond), et on superpose une bande à stripes
diagonales sur 6px de large pour signaler la pause de midi.
Z-index élevé pour passer au-dessus des segments. */
.timeline-noon {
position: absolute;
top: 0;
bottom: 0;
width: 6px;
z-index: 3;
pointer-events: none;
transform: translateX(-3px); /* centrer la bande sur 12h */
background:
/* trait central massif : */
linear-gradient(to right,
transparent 0%, transparent 30%,
var(--text) 30%, var(--text) 70%,
transparent 70%, transparent 100%),
/* fond stripes diagonales pour signaler "césure" : */
repeating-linear-gradient(
45deg,
var(--bg-muted) 0 3px,
var(--text-muted) 3px 4px
);
opacity: 0.95;
}
/* En vue horizontale, timeline-bar est plus haute (22px au lieu de 20px),
on garde le même trait mais légèrement plus large pour la visibilité. */
html.view-horizontal .timeline-noon {
width: 7px;
transform: translateX(-3.5px);
}
.timeline-scale {
position: relative;
height: 14px;
margin-top: 4px;
}
.timeline-tick {
position: absolute;
transform: translateX(-50%);
font-size: 12px;
color: var(--text-faint);
font-family: var(--mono);
}
/* Stats par carte */
.card-stats {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 10px 14px;
background: var(--bg-muted);
border-bottom: 1px solid var(--border);
}
.stat-total {
display: flex;
align-items: baseline;
gap: 6px;
}
.stat-total-num {
font-size: 22px;
font-weight: 700;
color: var(--text);
line-height: 1;
}
.stat-total-lbl {
font-size: 12px;
color: var(--text-muted);
}
.stat-split {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-faint);
}
.stat-split-item b {
color: var(--text-muted);
font-weight: 600;
}
.stat-split-sep {
opacity: 0.4;
}
/* Notes de statut */
.card-status-note {
padding: 8px 14px;
font-size: 12px;
font-weight: 500;
text-align: center;
}
.card-status-note.pompier {
background: var(--danger-soft);
color: var(--danger);
border-bottom: 1px solid var(--border);
}
.card-status-note.absent {
background: var(--bg);
color: var(--text-muted);
border-bottom: 1px solid var(--border);
font-style: italic;
}
.card-empty.subtle {
font-size: 12px;
opacity: 0.7;
}
/* v2026.5.29 : highlight visible sur les rows .intervention-v2 quand on
survole le segment timeline correspondant (ou que l'user survole la row) */
.intervention-v2.highlight {
background: var(--accent-soft);
outline: 2px solid var(--accent);
outline-offset: -2px;
box-shadow: 0 0 0 3px var(--accent-soft);
z-index: 2;
position: relative;
}
/* ==========================================================================
v2026.5.41 : Conflit absence/réservation × intervention.
Quand une intervention est planifiée pendant qu'un tech est marqué
"absent" (toute la journée ou demi-journée) ou qu'il a une réservation
sur le même créneau, on signale visuellement le conflit en peignant la
carte de l'intervention en rouge plein. S'applique à la row classique
(.intervention-v2) ET à la mini-card (.iv-mini-card) en vue horizontale.
========================================================================== */
.intervention-v2.intervention-conflict-absence,
.iv-mini-card.intervention-conflict-absence {
background: #b03030 !important;
color: #ffffff !important;
border-color: #7a1f1f !important;
}
.intervention-v2.intervention-conflict-absence::before {
/* Renforce la barre gauche pour rester cohérent avec le rouge plein. */
background: #7a1f1f !important;
}
.intervention-v2.intervention-conflict-absence .intervention-dot,
.intervention-v2.intervention-conflict-absence .iv-status-check,
.intervention-v2.intervention-conflict-absence a,
.iv-mini-card.intervention-conflict-absence .iv-mini-card-bar,
.iv-mini-card.intervention-conflict-absence .iv-mini-time-vertical,
.iv-mini-card.intervention-conflict-absence .iv-mini-card-text {
color: #ffffff !important;
background: transparent !important;
}
/* La barre couleur catégorie à gauche de la mini-card devient un blanc
semi-transparent pour rester visible sur le rouge. */
.iv-mini-card.intervention-conflict-absence .iv-mini-card-bar {
background: rgba(255, 255, 255, 0.45) !important;
}
.intervention-v2.intervention-conflict-absence:hover,
.iv-mini-card.intervention-conflict-absence:hover {
background: #c44040 !important;
}
/* conflit horaire entre deux interventions du même tech → rouge plein
(carte classique + mini-card timeline horizontale). */
.intervention-v2.intervention-conflict-overlap,
.iv-mini-card.intervention-conflict-overlap {
background: #b03030 !important;
color: #ffffff !important;
border-color: #7a1f1f !important;
}
.intervention-v2.intervention-conflict-overlap::before {
background: #7a1f1f !important;
}
.intervention-v2.intervention-conflict-overlap .intervention-dot,
.intervention-v2.intervention-conflict-overlap .iv-status-check,
.intervention-v2.intervention-conflict-overlap a,
.iv-mini-card.intervention-conflict-overlap .iv-mini-card-bar,
.iv-mini-card.intervention-conflict-overlap .iv-mini-time-vertical,
.iv-mini-card.intervention-conflict-overlap .iv-mini-card-text {
color: #ffffff !important;
background: transparent !important;
}
.iv-mini-card.intervention-conflict-overlap .iv-mini-card-bar {
background: rgba(255, 255, 255, 0.45) !important;
}
.intervention-v2.intervention-conflict-overlap:hover,
.iv-mini-card.intervention-conflict-overlap:hover {
background: #c44040 !important;
}
/* ==========================================================================
Interventions — layout v2 (heures verticales)
========================================================================== */
.intervention-v2 {
display: grid;
grid-template-columns: 4px 58px 1fr auto;
grid-template-rows: auto auto;
/* v4.1.17 : la ligne du bas (right) s'étend maintenant sur les 2 colonnes
droite (right + status) pour que la signature aille vraiment jusqu'au
bord droit. Le ✓ status est positionné en absolute par-dessus. */
grid-template-areas:
"dot time ref copy"
"dot time right right";
gap: 2px 10px;
align-items: start;
padding: 10px 12px 12px 8px;
border-top: 1px solid var(--border);
cursor: default;
transition: background 0.08s;
position: relative;
}
.intervention-v2:first-child { border-top: none; }
.intervention-v2:hover { background: var(--bg-hover); }
/* Pastille colorée (barre verticale) */
.intervention-v2 .intervention-dot {
grid-area: dot;
width: 4px;
height: 100%;
border-radius: 2px;
align-self: stretch;
}
.intervention-v2.color-livraison .intervention-dot { background: var(--c-livraison); }
.intervention-v2.color-installation .intervention-dot { background: var(--c-installation); }
.intervention-v2.color-recup .intervention-dot { background: var(--c-recup); }
.intervention-v2.color-remplacement .intervention-dot { background: var(--c-remplacement); }
.intervention-v2.color-incident .intervention-dot { background: var(--c-incident); }
.intervention-v2.color-rollout .intervention-dot { background: var(--c-rollout); }
.intervention-v2.color-reservation .intervention-dot { background: var(--c-reservation); }
.intervention-v2.color-autre .intervention-dot { background: var(--c-autre); }
.intervention-v2.clickable { cursor: pointer; }
.intervention-v2.clickable:active { transform: translateY(1px); }
/* : on GARDE la couleur de catégorie visible (.intervention-dot reste
en couleur de catégorie + fond neutre). Le statut clos/résolu/terminé est
signalé par un bandeau vert sur la DROITE de la carte (inset shadow) +
le ✓✓ ou ✓ existant. */
.intervention-v2.status-closed {
box-shadow: inset -4px 0 0 var(--c-closed);
}
.intervention-v2.status-closed:hover {
filter: brightness(0.96);
}
.intervention-v2.status-resolved {
box-shadow: inset -4px 0 0 var(--c-resolved);
}
.intervention-v2.status-resolved:hover {
filter: brightness(0.96);
}
.intervention-v2.status-terminated {
box-shadow: inset -4px 0 0 var(--c-terminated, #16a34a);
}
.intervention-v2.status-terminated:hover {
filter: brightness(0.96);
}
.intervention-v2.status-terminated .iv-status-check {
color: var(--c-terminated, #16a34a);
}
.timeline-slot.status-terminated { box-shadow: inset 0 -4px 0 var(--c-terminated, #16a34a); }
/* v4.2.5 : carte "en cours d'analyse" (ghost juste disparu, on re-fetch la
fiche pour décider du sort). Opacité réduite + petit spinner discret. */
.intervention-v2._checking {
opacity: 0.6;
position: relative;
}
.intervention-v2._checking::after {
content: "";
position: absolute;
right: 10px;
top: 50%;
width: 12px;
height: 12px;
margin-top: -6px;
border: 2px solid var(--border, #ccc);
border-top-color: var(--text-muted, #666);
border-radius: 50%;
animation: iv-check-spin 0.9s linear infinite;
}
@keyframes iv-check-spin {
to { transform: rotate(360deg); }
}
/* Ligne 1 : REF en titre centré gros gras */
.iv-ref-header {
grid-area: ref;
font-family: var(--mono);
font-size: 15px;
font-weight: 700;
color: var(--text);
letter-spacing: 0.03em;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 2px 0;
}
.iv-ref-header.no-ref {
font-family: var(--font);
font-weight: 500;
color: var(--text-faint);
}
.iv-status-check {
/* v4.1.17 : absolute en bas à droite (la grid-area "status" a été
fusionnée avec "right" pour étendre la signature jusqu'au bord). */
position: absolute;
right: 10px;
bottom: 10px;
font-size: 16px;
font-weight: 700;
color: var(--c-closed);
pointer-events: none;
/* Au-dessus de la signature, mais discret */
z-index: 1;
}
.intervention-v2.status-resolved .iv-status-check { color: var(--c-resolved); }
/* ✓ jaune pour les iv "terminated-suspended" (Suspendu côté EV).
Pas de fond vert sur la carte — juste un check coloré jaune ambre. */
.iv-status-check.suspended { color: #d39c00; }
.intervention-v2.status-suspended .iv-status-check { color: #d39c00; }
.iv-mini-status-check.suspended { color: #d39c00; }
/* ⚠ jaune en haut-droite quand la fiche est tronquée / session morte. */
.iv-fetch-warning {
position: absolute;
top: 6px;
right: 8px;
font-size: 12px;
color: #d39c00;
pointer-events: auto;
cursor: help;
z-index: 2;
text-shadow: 0 0 2px rgba(0,0,0,0.4);
}
/* v4.2.5 : ✓✓ double check (clôturé/résolu) — un peu plus petit pour tenir
les 2 caractères. Espacement négatif pour les rapprocher. */
.iv-status-check.double {
font-size: 12px;
letter-spacing: -3px;
padding-right: 3px; /* compenser le letter-spacing côté droit */
}
.intervention-copy {
grid-area: copy;
align-self: start;
padding: 2px 6px;
background: transparent;
color: var(--text-faint);
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
font-size: 11px;
opacity: 0;
transition: opacity 0.1s, background 0.1s, color 0.1s;
font-family: inherit;
/* v2026.5.17 : figer largeur/hauteur pour que le changement 📋 → ✓ pendant
la copie ne fasse pas bouger le titre centré dans la grid */
min-width: 28px;
min-height: 22px;
text-align: center;
box-sizing: border-box;
}
.intervention-v2:hover .intervention-copy { opacity: 1; }
.intervention-copy:hover {
background: var(--bg-muted);
color: var(--text);
border-color: var(--border);
}
.intervention-copy.copied {
color: var(--ok);
background: var(--ok-soft);
opacity: 1;
}
/* Ligne 2 GAUCHE : heures VERTICALES, centrées par rapport au bloc droit */
.iv-time-vertical {
grid-area: time;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center; /* centrage vertical */
align-self: center; /* centrage dans la cellule grille */
gap: 1px;
font-family: var(--mono);
font-size: 12px;
color: var(--text);
height: 100%;
}
.iv-time-start, .iv-time-end {
font-weight: 600;
letter-spacing: 0.02em;
}
.iv-time-arrow {
font-size: 11px;
color: var(--text-muted);
line-height: 1;
}
.intervention-v2.is-pompier-line .iv-time-start,
.intervention-v2.is-pompier-line .iv-time-end {
color: var(--danger);
font-weight: 700;
}
/* Ligne 2 DROITE : lieu / contact+tél / bas (catégorie + signature) */
.iv-right {
grid-area: right;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
/* Lieu : ville (MAJ gras) + adresse (italique noir) */
.iv-lieu-block {
display: flex;
flex-direction: column;
gap: 0;
}
.iv-lieu-ville {
font-size: 13px;
font-weight: 700;
color: var(--text);
letter-spacing: 0.04em;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.iv-lieu-adresse {
font-size: 12.5px;
font-style: italic;
font-weight: 400;
color: var(--text); /* noir, pas gris */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Contact + tél — wrap intelligent (pas de coupure de mot) */
.iv-contact-line {
font-size: 13px;
word-break: normal;
overflow-wrap: break-word;
white-space: normal;
line-height: 1.35;
}
.iv-contact {
font-weight: 600;
color: var(--text);
}
.iv-sep {
color: var(--text-faint);
font-weight: 400;
margin: 0 2px;
}
.iv-phone {
font-family: var(--mono);
color: var(--text-muted);
font-weight: 400;
font-size: 12px;
white-space: nowrap; /* numéro pas coupé */
}
/* Bas : catégorie à gauche + signature à droite */
.iv-bottom-line {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 8px;
font-size: 12px;
/* v4.1.14 : forcer la ligne à occuper 100% de largeur du parent */
width: 100%;
}
.iv-category {
color: var(--text-muted);
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* v4.1.15 : taille naturelle (pas de flex:1 qui étirait le texte et
rendait la signature juste à côté). Sans flex, la catégorie reste à
son contenu + justify-content:space-between pousse la signature à
l'extrême droite du parent. */
min-width: 0;
flex: 0 1 auto;
max-width: calc(100% - 70px);
}
.iv-signature {
color: var(--text-faint);
font-size: 11px;
font-family: var(--mono);
flex-shrink: 0;
letter-spacing: 0.02em;
text-align: right;
/* v4.1.15/17 : margin-left: auto pour collage garanti à droite */
margin-left: auto;
white-space: nowrap;
}
/* v4.1.17 : si statut clos/résolu, le ✓✓ est à droite en absolute → décaler
la signature.
✓✓ double prend plus de place → 32px ; ✓ simple → 22px. */
.intervention-v2.status-closed .iv-signature,
.intervention-v2.status-resolved .iv-signature {
padding-right: 32px;
}
.intervention-v2.has-pending-check .iv-signature,
.intervention-v2.status-terminated .iv-signature {
padding-right: 22px;
}
/* ✓ / ✓✓ dans la mini-card de la timeline horizontale */
.iv-mini-card {
position: relative; /* pour positionner le check absolute */
}
.iv-mini-status-check {
position: absolute;
top: 4px;
right: 6px;
font-size: 12px;
font-weight: 700;
line-height: 1;
color: var(--c-closed);
pointer-events: none;
z-index: 1;
}
/* en vue horizontale, la barre verte verticale est à right:0 + 4px.
Sans décalage, le ✓✓ chevauche la barre. On le pousse à droite +14px pour
qu'il reste visible et lisible à côté de la barre. */
html.view-horizontal .iv-mini-card.status-closed .iv-mini-status-check,
html.view-horizontal .iv-mini-card.status-resolved .iv-mini-status-check {
right: 14px;
}
.iv-mini-card.status-resolved .iv-mini-status-check { color: var(--c-resolved); }
.iv-mini-status-check.double {
letter-spacing: -2px;
font-size: 11px;
}
/* Réservation (créneau bloqué par un coordinateur) */
.iv-ref-header.is-reservation-title {
color: var(--c-reservation);
font-family: var(--font);
letter-spacing: 0.02em;
/* v5.0.15 : étendre le titre sur toute la largeur de la carte pour le
vrai centrage (sinon il n'est centré que dans sa colonne grid) */
grid-column: 1 / -1;
text-align: center;
padding-left: 62px; /* compense la colonne time (58px + gap) */
padding-right: 0;
}
/* v5.0.15 : absence partielle (demi-journée) affichée comme une row */
.iv-ref-header.is-absence-title {
color: var(--c-absence, #a0a8b2);
font-family: var(--font);
letter-spacing: 0.02em;
grid-column: 1 / -1;
text-align: center;
padding-left: 62px;
padding-right: 0;
}
.intervention-v2.color-absence .intervention-dot {
background: var(--c-absence, #2a2f36);
}
.iv-reservation-par {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.iv-reservation-sujet {
font-size: 12.5px;
color: var(--text-muted);
font-style: italic;
}
/* v2026.5.38 : anciens styles .intervention (layout v1) supprimés — le HTML
ne génère plus que .intervention-v2 depuis longtemps. */
/* ==========================================================================
Tooltip
========================================================================== */
/* v2026.5.45 : on aligne les dimensions du tooltip live sur celles du
pinned-popup (padding-top 28px qui réserve la place de la dragbar, border
2px). Ainsi, passer de live → épinglé → désépinglé ne change RIEN à la
taille de la boîte — seule la couleur de la bordure varie pour signaler
l'état. */
.tooltip {
position: fixed !important;
isolation: isolate;
contain: layout;
z-index: 100;
max-width: 620px;
max-height: calc(100vh - 40px);
overflow-y: auto;
/* : padding-top 28px d'office pour réserver la place de la dragbar. */
padding: 28px 14px 12px 14px;
background: var(--bg-elevated);
color: var(--text);
/* : border 2px d'office (transparent en mode live), pour ne pas
redimensionner la boîte au passage en épinglé. */
border: 2px solid transparent;
border-radius: 8px;
box-shadow: var(--shadow-hover);
font-size: 13px;
line-height: 1.5;
pointer-events: none;
opacity: 0;
transition: opacity 0.1s;
user-select: text;
-webkit-user-select: text;
}
.tooltip.visible:not(.pinned-popup):not(.soft-unpinned) {
/* : en mode live (hover), bordure discrète mais bien 2px de large. */
border-color: var(--border-strong);
}
.tooltip.visible {
opacity: 1;
/* v4.1.10 : permet à la souris d'entrer dans la bulle pour la garder
visible (persistance au hover) et, en mode pinned, pour sélectionner. */
pointer-events: auto;
/* v4.2 : curseur texte par défaut (pour signaler que c'est sélectionnable) */
cursor: text;
}
.tooltip.pinned {
/* Bulle épinglée : bordure verte pour indiquer le mode */
border-color: var(--c-accent, #3fb950);
box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.15), var(--shadow-hover);
}
/* v4.1.13/14 : barre d'actions en haut à droite de la bulle
(recharger cette iv + épingler) */
.tooltip-actions {
position: absolute;
top: 6px;
right: 6px;
display: flex;
gap: 2px;
z-index: 5;
}
.tooltip-actionbtn {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
font-size: 13px;
opacity: 0.55;
transition: opacity 0.15s, background 0.15s, transform 0.15s;
user-select: none;
color: var(--text-muted);
}
.tooltip-actionbtn svg {
width: 14px;
height: 14px;
}
.tooltip-actionbtn:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.08);
color: var(--text);
}
.tooltip-actionbtn.spinning svg {
animation: refresh-spin 0.8s linear infinite;
transform-origin: 50% 50%;
}
/* L'ancien .tooltip-pinbtn garde ses variantes */
.tooltip-pinbtn {
filter: grayscale(100%);
}
.tooltip-pinbtn:hover {
filter: grayscale(0%);
}
.tooltip.pinned .tooltip-pinbtn {
opacity: 1;
filter: grayscale(0%);
transform: rotate(-30deg);
background: rgba(63, 185, 80, 0.15);
}
/* v4.1.15 : référence dans la bulle avec bouton copier inline */
.tt-ref-cell {
display: inline-flex;
align-items: center;
gap: 8px;
}
.tt-ref-val {
font-family: var(--mono, monospace);
}
/* v2026.5.40 r18 : référence cliquable → ouvre la fiche EV. Style "lien"
immédiatement reconnaissable : couleur accent, soulignée, cursor pointer. */
.tt-ref-link {
font-family: var(--mono, monospace);
color: var(--accent);
text-decoration: underline;
text-underline-offset: 2px;
cursor: pointer;
font-weight: 600;
transition: color 0.1s, text-shadow 0.1s;
pointer-events: auto;
}
.tt-ref-link:hover {
color: var(--accent);
text-shadow: 0 0 6px rgba(59, 130, 246, 0.4);
}
/* flèche " ↗" retirée de la référence cliquable. */
.tt-copy-btn {
background: transparent;
border: 1px solid var(--border);
color: var(--text-muted);
width: 26px;
height: 22px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.tt-copy-btn:hover {
background: var(--bg-hover);
color: var(--text);
border-color: var(--border-strong);
}
.tt-copy-btn.copied {
background: rgba(63, 185, 80, 0.2);
border-color: #3fb950;
color: #3fb950;
}
.tooltip dl {
margin: 0;
display: grid;
grid-template-columns: auto 1fr;
column-gap: 10px;
row-gap: 4px;
}
.tooltip dt {
color: var(--text-muted);
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.tooltip dd {
margin: 0;
color: var(--text);
font-size: 13px;
word-break: break-word;
}
.tooltip dd.description,
.tooltip dd.commentaire {
white-space: pre-wrap;
}
.tooltip dd.commentaire {
padding: 6px 8px;
background: var(--bg-muted);
border-radius: 4px;
border-left: 2px solid var(--c-closed);
font-style: italic;
}
.tooltip hr {
border: none;
border-top: 1px solid var(--border);
margin: 8px 0;
grid-column: 1 / -1;
}
/* Badge de statut dans le tooltip */
.status-pill {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
}
.status-pill.closed {
background: var(--c-closed-soft);
color: var(--c-closed);
}
.status-pill.resolved {
background: var(--c-resolved-soft);
color: var(--c-resolved);
}
.status-pill.ongoing {
background: var(--accent-soft);
color: var(--accent);
}
.status-pill.other {
background: var(--bg-muted);
color: var(--text-muted);
}
/* ==========================================================================
Toasts de notification (ouverture d'intervention)
========================================================================== */
.toast-stack {
position: fixed;
bottom: 20px;
right: 20px;
/* v2026.5.41 : au-dessus du modal-overlay du panel admin (z-index 10000)
pour que les toasts de feedback restent visibles avec le flou en arrière. */
z-index: 11000;
display: flex;
flex-direction: column-reverse; /* les nouveaux en bas, les anciens au-dessus */
gap: 8px;
pointer-events: none;
}
.toast {
min-width: 200px;
max-width: 340px;
padding: 10px 14px;
background: var(--bg-elevated);
color: var(--text);
border: 1px solid var(--border-strong);
border-left: 3px solid var(--c-livraison);
border-radius: 6px;
box-shadow: var(--shadow-hover);
font-size: 13px;
opacity: 0;
transform: translateX(40px);
transition: opacity 0.18s ease-out, transform 0.18s ease-out;
pointer-events: auto;
}
.toast.visible {
opacity: 1;
transform: translateX(0);
}
.toast.leaving {
opacity: 0;
transform: translateX(40px);
}
.toast-label {
color: var(--text-muted);
font-size: 11px;
margin-right: 6px;
}
.toast-ref {
font-family: var(--mono);
font-weight: 700;
letter-spacing: 0.02em;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.1.20 : Modal central de confirmation (vider cache)
───────────────────────────────────────────────────────────────────────── */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
/* Flou + assombrissement léger de l'arrière-plan */
background: rgba(0, 0, 0, 0.35);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
animation: modal-fade-in 0.15s ease-out;
}
@keyframes modal-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-card {
background: var(--bg, #ffffff);
color: var(--text, #111);
border: 1px solid var(--border, rgba(128, 128, 128, 0.25));
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25),
0 2px 8px rgba(0, 0, 0, 0.15);
padding: 24px 24px 20px;
width: min(440px, 92vw);
max-height: 90vh;
overflow-y: auto;
animation: modal-card-in 0.18s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes modal-card-in {
from { opacity: 0; transform: translateY(8px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-title {
margin: 0 0 12px 0;
font-size: 18px;
font-weight: 700;
color: var(--text, #111);
}
.modal-message {
margin: 0 0 20px 0;
font-size: 13px;
line-height: 1.5;
color: var(--text-muted, #555);
}
.modal-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.modal-actions .btn {
width: 100%;
padding: 10px 14px;
font-size: 13px;
font-weight: 600;
text-align: center;
border-radius: 8px;
cursor: pointer;
transition: background 0.12s, transform 0.06s;
border: 1px solid transparent;
}
.modal-actions .btn:active { transform: translateY(1px); }
/* Vider le cache du jour : danger modéré (orange) */
.btn-modal-danger {
background: rgba(234, 128, 38, 0.12);
color: #c85a00;
border-color: rgba(234, 128, 38, 0.3);
}
.btn-modal-danger:hover {
background: rgba(234, 128, 38, 0.22);
}
/* Vider tout le cache : danger fort (rouge) */
.btn-modal-danger-strong {
background: rgba(220, 60, 60, 0.12);
color: #c03030;
border-color: rgba(220, 60, 60, 0.3);
}
.btn-modal-danger-strong:hover {
background: rgba(220, 60, 60, 0.22);
}
/* Annuler : neutre */
.btn-modal-cancel {
background: transparent;
color: var(--text-muted, #666);
border-color: var(--border, rgba(128, 128, 128, 0.3));
margin-top: 4px;
}
.btn-modal-cancel:hover {
background: var(--bg-hover, rgba(128, 128, 128, 0.08));
}
/* v4.2.5 : bouton primaire (action principale) pour modals d'alerte */
.btn-modal-primary {
background: var(--c-accent, #3fb950);
color: #fff;
border-color: var(--c-accent, #3fb950);
}
.btn-modal-primary:hover {
filter: brightness(1.08);
}
/* ─────────────────────────────────────────────────────────────────────────
v4.1.20 : Message d'absence récurrente (configurée par tech)
───────────────────────────────────────────────────────────────────────── */
.tech-absence-recurring {
padding: 14px 12px;
text-align: center;
font-size: 13px;
font-style: italic;
color: var(--text-faint, #888);
background: rgba(128, 128, 128, 0.04);
border-top: 1px solid var(--border, rgba(128, 128, 128, 0.15));
border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.15));
}
/* v4.2 : contact en rouge quand anomalie détectée (Contact + Personne de
contact présents tous les deux dans l'action = situation suspecte).
On signale visuellement pour que l'user aille vérifier dans la fiche. */
.iv-contact-line.iv-contact-anomalie {
color: #dc3030;
}
.iv-contact-line.iv-contact-anomalie .iv-contact,
.iv-contact-line.iv-contact-anomalie .iv-phone {
color: #dc3030;
}
/* v4.2 : badge utilisateur EasyVista connecté (en haut à droite de la topbar) */
.current-user {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
font-size: 12px;
font-weight: 500;
color: var(--text-muted, #666);
background: rgba(128, 128, 128, 0.08);
border: 1px solid var(--border, rgba(128, 128, 128, 0.2));
border-radius: 999px;
margin-right: 8px;
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.current-user::before {
content: "👤";
font-size: 11px;
opacity: 0.7;
}
.current-user.hidden {
display: none;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.2.3 : pastille utilisateur (initiales) dans la topbar gauche
───────────────────────────────────────────────────────────────────────── */
.user-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
margin-right: 10px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.5px;
color: #fff;
background: var(--user-badge-color, #5b6372);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 50%;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.12s;
flex-shrink: 0;
user-select: none;
}
.user-badge:hover {
transform: scale(1.06);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.user-badge:active {
transform: scale(0.97);
}
.user-badge.hidden {
display: none;
}
.user-badge.open {
/* Indication visuelle quand la popup nom est ouverte */
box-shadow: 0 0 0 2px var(--user-badge-color, #5b6372) inset,
0 0 0 2px rgba(255, 255, 255, 0.15);
}
/* Popup du nom complet, affichée juste sous la pastille au clic */
.user-name-popup {
position: fixed;
z-index: 10050;
padding: 8px 14px;
background: var(--bg, #ffffff);
color: var(--text, #111);
border: 1px solid var(--border, rgba(128, 128, 128, 0.25));
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18),
0 2px 6px rgba(0, 0, 0, 0.12);
font-size: 13px;
font-weight: 500;
white-space: nowrap;
animation: user-popup-in 0.12s ease-out;
}
.user-name-popup.hidden {
display: none;
}
@keyframes user-popup-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* ─────────────────────────────────────────────────────────────────────────
v4.2.6 : boutons d'action topbar (Absence, Douchette)
───────────────────────────────────────────────────────────────────────── */
.btn-action {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
background: transparent;
color: var(--text, #e0e0e0);
border: 1px solid var(--border, rgba(128, 128, 128, 0.3));
border-radius: 6px;
cursor: pointer;
transition: background 0.12s, border-color 0.12s;
}
.btn-action:hover {
background: var(--bg-hover, rgba(128, 128, 128, 0.12));
border-color: var(--border-strong, rgba(128, 128, 128, 0.5));
}
.btn-action:active {
transform: translateY(1px);
}
.btn-action-icon {
width: 16px;
height: 16px;
flex-shrink: 0;
}
.btn-action-emoji {
font-size: 12px;
line-height: 1;
}
.btn-action-label {
white-space: nowrap;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.2.6 : modals Absence et Douchette
───────────────────────────────────────────────────────────────────────── */
.modal-card.modal-wide {
width: min(520px, 92vw);
}
.modal-form-group {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 14px;
}
.modal-form-row {
display: flex;
gap: 8px;
align-items: center;
}
.modal-form-row > * {
flex: 1;
}
.modal-form-label {
font-size: 12px;
font-weight: 500;
color: var(--text-muted, #888);
text-transform: uppercase;
letter-spacing: 0.3px;
}
.modal-form-input,
.modal-form-select {
padding: 8px 10px;
font-size: 13px;
background: var(--bg, #fff);
color: var(--text, #111);
border: 1px solid var(--border, rgba(128, 128, 128, 0.3));
border-radius: 6px;
font-family: inherit;
}
.modal-form-input:focus,
.modal-form-select:focus {
outline: none;
border-color: var(--c-accent, #3fb950);
box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.15);
}
/* Liste checkboxes techniciens */
.modal-tech-list {
display: flex;
flex-direction: column;
gap: 4px;
/* v4.2.8 : plus de max-height → tous les techs (max 8 + "Tout") visibles
d'un coup sans avoir à scroller dans la liste. */
padding: 6px;
background: var(--bg-muted, rgba(128, 128, 128, 0.06));
border: 1px solid var(--border, rgba(128, 128, 128, 0.2));
border-radius: 6px;
}
.modal-tech-item {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: background 0.1s;
}
.modal-tech-item:hover {
background: var(--bg-hover, rgba(128, 128, 128, 0.12));
}
.modal-tech-item input[type="checkbox"] {
width: 15px;
height: 15px;
cursor: pointer;
accent-color: var(--c-accent, #3fb950);
}
.modal-tech-item.tech-selectall {
font-weight: 600;
border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.2));
padding-bottom: 8px;
margin-bottom: 2px;
}
.modal-tech-item.tech-selectall:hover {
background: var(--bg-hover, rgba(128, 128, 128, 0.12));
}
/* Boutons Appliquer/Envoyer/Annuler côte à côte */
.modal-actions.horizontal {
flex-direction: row;
gap: 8px;
}
.modal-actions.horizontal .btn {
flex: 1;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.2.9 : blocage du scroll arrière quand une modal est ouverte.
La classe body.modal-open est ajoutée/retirée automatiquement par
initModalScrollLock() dans viewer.js dès qu'un .modal-overlay existe.
───────────────────────────────────────────────────────────────────────── */
body.modal-open {
overflow: hidden;
}
/* v4.2.9 : pied de page discret en bas à droite — affiche auteur + date + version
v2026.5.29 : agrandi + plus contrasté */
.app-footer {
position: fixed;
right: 10px;
bottom: 6px;
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
opacity: 0.85;
pointer-events: none;
user-select: none;
font-variant-numeric: tabular-nums;
letter-spacing: 0.3px;
z-index: 1;
padding: 3px 8px;
background: var(--bg-muted);
border: 1px solid var(--border);
border-radius: 6px;
}
.app-footer:hover {
opacity: 1;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.3.0 : conflit d'horaire entre 2 interventions d'un même tech.
Les heures s'affichent en rouge + icône ⚠ à côté.
───────────────────────────────────────────────────────────────────────── */
.iv-time-vertical.iv-time-overlap .iv-time-start,
.iv-time-vertical.iv-time-overlap .iv-time-end,
.iv-time-vertical.iv-time-overlap .iv-time-arrow {
color: var(--danger, #b03030) !important;
font-weight: 700;
}
.iv-time-overlap-warn {
color: var(--danger, #b03030);
font-size: 12px;
font-weight: 700;
line-height: 1;
margin-top: 2px;
cursor: help;
text-align: center;
}
/* ─────────────────────────────────────────────────────────────────────────
v4.3.0 : popups épinglés détachés
Ancrés au contenu (position:absolute coord document) → scrollent avec
la page. Persistent jusqu'à fermeture explicite.
───────────────────────────────────────────────────────────────────────── */
/* v2026.5.45 : pinned-popup hérite la même taille que le tooltip live.
Seule la couleur du border change. Pas d'animation scale (saut visuel),
juste un fade opacity court. */
.tooltip.pinned-popup {
position: absolute !important;
z-index: 5 !important;
opacity: 1 !important;
pointer-events: auto !important;
border-color: var(--accent, #0f4f8b) !important;
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
/* : resize natif depuis le coin bas-droite (drag pour redimensionner). */
resize: both;
overflow: auto;
min-width: 280px;
min-height: 120px;
}
/* : animation d'unpin réduite à un fade opacity (pas de scale). */
.tooltip.pinned-popup.unpinning,
.tooltip.soft-unpinned.unpinning {
animation: pinned-popup-out 0.18s ease-in forwards !important;
}
@keyframes pinned-popup-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* v2026.5.45 : soft-unpinned reprend exactement les mêmes dimensions que
les autres états — seule la couleur de bordure change (gris au lieu d'accent). */
.tooltip.soft-unpinned {
position: absolute !important;
z-index: 5 !important;
opacity: 1 !important;
pointer-events: auto !important;
border-color: var(--border-strong) !important;
box-shadow: var(--shadow-hover) !important;
animation: none !important;
}
/* v4.3.3 : Barre de drag en haut de la popup épinglée, permet de la
déplacer (le contenu lui-même garde la sélection de texte possible). */
.pinned-popup-dragbar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 22px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(
to bottom,
var(--bg-muted, rgba(128,128,128,0.08)) 0%,
transparent 100%
);
border-bottom: 1px solid var(--border, rgba(128,128,128,0.15));
border-radius: 6px 6px 0 0;
cursor: grab;
user-select: none;
-webkit-user-select: none;
}
.pinned-popup-dragbar:active,
.pinned-popup.dragging .pinned-popup-dragbar {
cursor: grabbing;
}
/* Petite grippe visuelle au milieu pour signaler que c'est déplaçable */
.pinned-popup-dragbar::before {
content: "";
width: 32px;
height: 3px;
border-radius: 3px;
background: var(--border-strong, rgba(128,128,128,0.35));
}
/* Pendant le drag, on fige l'animation pour éviter les tremblements */
.pinned-popup.dragging {
animation: none !important;
transition: none !important;
cursor: grabbing !important;
box-shadow: 0 12px 32px rgba(0,0,0,0.28);
}
/* Bouton × de fermeture du popup épinglé */
.pinned-popup-close {
position: absolute;
top: 3px;
right: 6px;
width: 22px;
height: 22px;
padding: 0;
line-height: 1;
font-size: 18px;
font-weight: 400;
color: var(--text-muted, #888);
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.1s, color 0.1s;
z-index: 2; /* au-dessus de la dragbar */
}
.pinned-popup-close:hover {
background: var(--danger-soft, #fbe6e6);
color: var(--danger, #b03030);
}
/* ─────────────────────────────────────────────────────────────────────────
v5.0.0 : horloge au milieu de la topbar (HH:MM, pas de secondes)
───────────────────────────────────────────────────────────────────────── */
/* app-clock +15% (12 → 14px). */
.app-clock {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 5px;
line-height: 1;
color: var(--topbar-text, var(--text-faint));
user-select: none;
white-space: nowrap;
font-size: 14px;
font-weight: 500;
opacity: 0.8;
margin-left: 2px;
}
.app-clock-date {
font-size: 14px;
font-weight: 500;
color: var(--topbar-text, var(--text-muted));
letter-spacing: 0.15px;
font-variant-numeric: tabular-nums;
}
.app-clock-date::after {
content: "•";
margin-left: 5px;
color: var(--topbar-text, var(--text-faint));
font-size: 14px;
}
.app-clock-time {
font-size: 14px;
font-weight: 600;
color: var(--topbar-text, var(--text-muted));
font-variant-numeric: tabular-nums;
letter-spacing: 0.3px;
}
.topbar { position: sticky; /* déja défini plus haut */ }
/* topbar doit être en position: relative parent pour que .app-clock absolute
se positionne par rapport à elle */
header.topbar { position: sticky !important; }
header.topbar::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
/* ─────────────────────────────────────────────────────────────────────────
v5.0.0 : ligne rouge "heure actuelle" sur la timeline (uniquement si on
affiche la date d'aujourd'hui). v5.0.1 : plus visible.
───────────────────────────────────────────────────────────────────────── */
.timeline-now-line {
position: absolute;
top: -2px;
bottom: -2px;
width: 4px;
background: #ff3030;
z-index: 5;
pointer-events: none;
box-shadow: 0 0 6px rgba(255, 48, 48, 0.8),
0 0 2px rgba(255, 48, 48, 1);
border-radius: 2px;
margin-left: -2px; /* centre la barre sur la position exacte */
}
.timeline-now-line::after {
content: "";
position: absolute;
top: -4px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: #ff3030;
border-radius: 50%;
box-shadow: 0 0 8px rgba(255, 48, 48, 0.9);
}
/* ─────────────────────────────────────────────────────────────────────────
v5.0.0 : Panel admin (menu caché 5 clics sur titre)
───────────────────────────────────────────────────────────────────────── */
.admin-overlay {
/* hérite de .modal-overlay */
align-items: flex-start;
padding: 30px 20px;
}
.admin-panel-card {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 8px;
width: 100%;
max-width: 1100px;
height: calc(100vh - 60px);
display: flex;
flex-direction: column;
box-shadow: 0 12px 40px rgba(0,0,0,0.3);
overflow: hidden;
}
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.admin-title {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.admin-close-btn {
background: transparent;
border: none;
font-size: 24px;
line-height: 1;
cursor: pointer;
padding: 4px 10px;
color: var(--text-muted);
border-radius: 4px;
}
.admin-close-btn:hover {
background: var(--danger-soft);
color: var(--danger);
}
.admin-body {
display: flex;
flex: 1;
min-height: 0;
}
.admin-sidebar {
width: 180px;
background: var(--bg);
border-right: 1px solid var(--border);
padding: 10px 0;
display: flex;
flex-direction: column;
gap: 2px;
flex-shrink: 0;
}
.admin-nav-btn {
text-align: left;
padding: 10px 18px;
background: transparent;
border: none;
cursor: pointer;
font-size: 12px;
color: var(--text);
border-left: 3px solid transparent;
transition: background 0.12s, border-color 0.12s;
}
.admin-nav-btn:hover {
background: var(--bg-hover);
}
.admin-nav-btn.active {
background: var(--bg-elevated);
border-left-color: var(--accent);
font-weight: 600;
}
.admin-content {
flex: 1;
padding: 20px 24px;
overflow-y: auto;
}
.admin-section-title {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
}
.admin-section-desc {
margin: 0 0 16px 0;
color: var(--text-muted);
font-size: 13px;
}
.admin-team-table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
.admin-team-table th,
.admin-team-table td {
padding: 8px 10px;
border-bottom: 1px solid var(--border);
text-align: left;
vertical-align: middle;
}
.admin-team-table th {
background: var(--bg);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
color: var(--text-muted);
}
.admin-input {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg);
color: var(--text);
font-size: 13px;
box-sizing: border-box;
}
.admin-input-id {
font-family: var(--mono);
max-width: 100px;
}
.admin-day-cb {
display: inline-flex;
align-items: center;
gap: 2px;
margin-right: 6px;
font-size: 11px;
cursor: pointer;
user-select: none;
}
.admin-day-cb input[type="checkbox"] {
margin: 0 2px 0 0;
}
.admin-del-btn {
background: transparent;
border: none;
cursor: pointer;
font-size: 16px;
color: var(--text-muted);
padding: 4px 8px;
border-radius: 4px;
}
.admin-del-btn:hover {
background: var(--danger-soft);
color: var(--danger);
}
.admin-readonly {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
padding: 12px;
font-family: var(--mono);
font-size: 12px;
overflow-x: auto;
}
.admin-diag-grid {
display: grid;
grid-template-columns: 200px 1fr;
gap: 8px 16px;
margin: 16px 0;
font-size: 13px;
}
.admin-diag-grid > div {
padding: 4px 0;
}
/* ─────────────────────────────────────────────────────────────────────────
v5.0.0 : bouton supprimer dans le tooltip (absence / réservation)
───────────────────────────────────────────────────────────────────────── */
.tooltip-delete-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 10px;
background: var(--danger-soft, #fbe6e6);
border: 1px solid var(--danger, #b03030);
color: var(--danger, #b03030);
border-radius: 4px;
cursor: pointer;
font-size: 12px;
font-weight: 500;
margin-top: 4px;
}
.tooltip-delete-btn:hover:not(:disabled) {
background: var(--danger, #b03030);
color: #fff;
}
.tooltip-delete-btn:disabled {
opacity: 0.6;
cursor: wait;
}
/* Bouton danger dans les modals */
.btn-danger,
.modal-btn-danger {
background: var(--danger, #b03030);
color: #fff;
border: 1px solid var(--danger, #b03030);
}
.btn-danger:hover,
.modal-btn-danger:hover {
background: #8e2020;
}
/* v5.0.1 : ligne d'équipe exclue (pas cochée) - apparaît grisée */
.admin-team-table tr.admin-row-excluded {
opacity: 0.45;
}
.admin-team-table tr.admin-row-excluded input[type="text"] {
background: var(--bg);
}
/* v5.0.1 : bouton supprimer sur la carte "Absent toute la journée" */
.absence-delete-wrap {
margin-top: 8px;
text-align: center;
}
.absence-delete-wrap .tooltip-delete-btn {
font-size: 11px;
padding: 4px 8px;
}
/* v5.0.4 : boutons preset matin / après-midi / journée dans modal absence */
.modal-preset-row {
gap: 8px;
flex-wrap: wrap;
}
.modal-preset-btn {
flex: 1;
min-width: 100px;
padding: 8px 10px;
font-size: 13px;
cursor: pointer;
}
/* ==========================================================================
v5.0.9 : Compteur de session EasyVista (topbar)
========================================================================== */
/* v2026.5.45 (issue #7) : compteur session repositionné SOUS la pastille
des initiales (top-gauche), à environ 8px sous la topbar. Avant : centré au
milieu de la topbar (`top: 50%; left: calc(50% + 60px)`), peu intuitif. */
.app-session {
position: fixed;
top: calc(var(--topbar-height, 56px) + 8px);
left: 16px;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 10px;
border-radius: 14px;
font-size: 13px;
font-weight: 500;
font-variant-numeric: tabular-nums;
z-index: 9;
background: rgba(0, 0, 0, 0.05);
transition: background 0.3s, color 0.3s;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.app-session.hidden {
display: none;
}
.app-session .session-icon {
font-size: 12px;
}
.app-session .session-time {
font-weight: 600;
}
.app-session .session-extend-btn {
margin-left: 4px;
padding: 3px 8px;
font-size: 11px;
border-radius: 10px;
border: 1px solid currentColor;
background: transparent;
color: inherit;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.app-session .session-extend-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
.app-session .session-extend-btn:disabled {
opacity: 0.6;
cursor: default;
}
/* État warning (2-5 min) : jaune */
.app-session.session-warn {
background: #f5c518;
color: #2a2100;
}
.app-session.session-warn .session-extend-btn {
border-color: #2a2100;
}
/* État critical (< 2 min) : rouge + pulse */
.app-session.session-critical {
background: #e74c3c;
color: #fff;
animation: session-pulse 1s infinite;
}
.app-session.session-critical .session-extend-btn {
border-color: #fff;
background: rgba(255, 255, 255, 0.15);
font-weight: 600;
}
.app-session.session-critical .session-extend-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
@keyframes session-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.5); }
50% { box-shadow: 0 0 0 6px rgba(231, 76, 60, 0); }
}
/* Bouton "Me reconnecter" dans la bannière session expirée */
.session-expired-reconnect-btn {
margin-left: 12px;
padding: 6px 14px;
border-radius: 4px;
background: #fff;
color: #c0392b;
border: none;
font-weight: 600;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
}
.session-expired-reconnect-btn:hover {
background: #f8d7da;
}
/* Bannière "Reconnexion en cours" */
.banner-reconnecting {
background: #3498db;
color: #fff;
padding: 10px 20px;
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
font-weight: 500;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.banner-reconnecting.hidden {
display: none;
}
.banner-reconnecting .banner-spinner {
font-size: 16px;
animation: spin-slow 2s linear infinite;
}
@keyframes spin-slow {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ==========================================================================
v5.0.11 : bannières reconnect avec boutons Annuler + choix réseau
========================================================================== */
.banner-reconnecting .banner-cancel-btn,
.banner-reconnect-failed .banner-cancel-btn {
margin-left: auto;
padding: 5px 12px;
background: transparent;
color: inherit;
border: 1px solid currentColor;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.banner-reconnecting .banner-cancel-btn:hover,
.banner-reconnect-failed .banner-cancel-btn:hover {
background: rgba(255, 255, 255, 0.15);
}
.banner-reconnect-failed {
background: #e67e22;
color: #fff;
padding: 10px 20px;
display: flex;
align-items: center;
gap: 12px;
font-size: 12px;
font-weight: 500;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.banner-reconnect-failed.hidden {
display: none;
}
.banner-reconnect-failed .banner-icon {
font-size: 18px;
}
.banner-reconnect-failed .banner-btn-primary {
padding: 6px 14px;
border-radius: 4px;
background: #fff;
color: #c0392b;
border: none;
font-weight: 600;
cursor: pointer;
font-size: 13px;
transition: background 0.2s;
}
.banner-reconnect-failed .banner-btn-primary:hover {
background: #f8d7da;
}
/* ==========================================================================
v2026.5.16 : responsive topbar
========================================================================== */
/* Breakpoint medium : entre 1000 et 1300px, on compacte un peu */
@media (max-width: 1300px) {
/* horloge garde sa petite taille à tous les breakpoints. */
.app-clock-date { font-size: 14px; }
.app-clock-time { font-size: 14px; }
.app-clock-date::after { font-size: 14px; }
.topbar-right .btn-action .btn-action-label,
.topbar-right .btn-refresh .btn-refresh-label {
font-size: 13px;
}
}
/* Breakpoint small : moins de 1000px, on masque les labels de boutons action
et on réduit encore l'horloge. Les icônes restent, titres restent. */
@media (max-width: 1000px) {
.topbar { padding: 8px 14px; gap: 8px; }
.topbar h1 { font-size: 18px; }
/* horloge ne doit pas grossir aux breakpoints, elle reste minuscule. */
.app-clock-date { font-size: 14px; }
.app-clock-time { font-size: 14px; }
.app-clock-date::after { font-size: 14px; }
.btn-action .btn-action-label,
.btn-refresh .btn-refresh-label {
display: none;
}
.btn-action, .btn-refresh {
padding: 6px 10px;
}
.capture-info { display: none; }
}
/* Breakpoint très petit : moins de 720px, on cache la date complète (garde
juste l'heure) et on autorise le wrap total */
@media (max-width: 720px) {
.topbar {
flex-wrap: wrap;
padding: 6px 10px;
}
.app-clock {
position: static;
transform: none;
margin: 0 auto;
}
.app-clock-date { display: none; }
.topbar-left { flex-wrap: wrap; }
.date-nav { margin-top: 4px; }
.topbar-right { flex-wrap: wrap; justify-content: flex-end; }
}
/* Breakpoint minuscule : masque aussi les labels de refresh, boutons deviennent
vraiment iconifiés */
@media (max-width: 520px) {
.app-clock-time { font-size: 14px; }
.topbar h1 { font-size: 12px; }
.btn-today { padding: 4px 6px; font-size: 11px; }
.btn-nav { min-width: 26px; padding: 4px 6px; }
}
/* ==========================================================================
v2026.5.17 : topbar des popups épinglés (3 boutons : _ ▭ 📍)
========================================================================== */
.pinned-popup {
/* v2026.5.45 : padding-top harmonisé à 28px avec .tooltip pour qu'il
n'y ait aucun saut de boîte entre live / pinned / soft-unpinned. La
topbar du popup épinglé se place en absolute sur cet espace. */
padding-top: 28px !important;
}
/* v2026.5.18 : masquer le conteneur d'actions d'origine (↻ reload + 📌 pin)
dans les popups épinglés — leur place est reprise par notre .pinned-popup-topbar */
.pinned-popup .tooltip-actions {
display: none !important;
}
.pinned-popup-topbar {
position: absolute;
top: 4px;
right: 4px;
display: flex;
gap: 2px;
align-items: center;
z-index: 10;
}
.pinned-popup-btn {
width: 26px;
height: 22px;
padding: 0;
font-size: 13px;
line-height: 1;
background: transparent;
color: var(--text-muted);
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;
transition: background 0.1s, color 0.1s, border-color 0.1s;
font-family: inherit;
display: inline-flex;
align-items: center;
justify-content: center;
}
.pinned-popup-btn:hover {
background: var(--bg-muted);
color: var(--text);
border-color: var(--border);
}
.pinned-popup-unpin {
font-size: 12px;
}
/* ==========================================================================
v2026.5.17 : mode Minimisé (popup flottant compact, juste la ref)
v2026.5.19 : refonte — élément .pinned-popup-minref créé à la volée
v2026.5.21 : agrandi pour que la ref tienne sans déborder
v2026.5.22 : encore agrandi + plus d'espace entre dragbar et topbar
v2026.5.23 : refonte complète en style "onglet" single-line, compact
========================================================================== */
.pinned-popup.pinned-popup-minimized {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
min-width: 300px !important;
max-width: 400px !important;
width: auto !important;
height: 36px !important;
min-height: 36px !important;
padding: 0 6px 0 4px !important;
overflow: visible;
background: var(--bg-elevated) !important;
border: 1px solid var(--border) !important;
border-radius: 6px !important;
}
/* Dans le mode minimisé, la topbar n'est plus en absolute : elle se pose en fin
de ligne à droite, après la ref */
.pinned-popup.pinned-popup-minimized .pinned-popup-topbar {
position: static !important;
top: auto !important;
right: auto !important;
margin-left: auto !important;
order: 3;
flex-shrink: 0;
padding: 0 2px;
}
/* La dragbar devient un simple "handle" à gauche (≡) */
.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar {
position: static !important;
top: auto !important;
left: auto !important;
right: auto !important;
order: 1;
flex-shrink: 0;
width: 18px !important;
height: 22px !important;
background: transparent !important;
border: none !important;
cursor: grab;
display: flex !important;
align-items: center;
justify-content: center;
color: var(--text-faint);
opacity: 0.6;
transition: opacity 0.12s, color 0.12s;
}
.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar::before {
content: "≡" !important;
font-size: 15px;
line-height: 1;
/* v2026.5.24 : annuler les propriétés du ::before normal (barre grise) */
width: auto !important;
height: auto !important;
background: transparent !important;
border-radius: 0 !important;
}
.pinned-popup.pinned-popup-minimized .pinned-popup-dragbar:hover {
opacity: 1;
color: var(--text);
}
.pinned-popup.pinned-popup-minimized.dragging .pinned-popup-dragbar {
cursor: grabbing;
}
/* Masquer tous les enfants directs SAUF topbar, dragbar et minref */
.pinned-popup.pinned-popup-minimized > *:not(.pinned-popup-topbar):not(.pinned-popup-dragbar):not(.pinned-popup-minref) {
display: none !important;
}
/* La ref au centre, cliquable pour agrandir */
.pinned-popup-minref {
display: flex;
align-items: center;
justify-content: center; /* v2026.5.24 : centrer horizontalement la ref */
flex: 1;
min-width: 0;
padding: 0 10px;
font-family: var(--mono, monospace);
font-size: 12px;
font-weight: 700;
color: var(--text);
cursor: pointer;
user-select: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
order: 2;
text-align: center; /* v2026.5.24 : centrer le texte */
}
.pinned-popup-minref:hover {
color: var(--accent, #3b82f6);
}
/* Boutons plus petits en mode minimisé */
.pinned-popup.pinned-popup-minimized .pinned-popup-btn {
width: 22px !important;
height: 22px !important;
font-size: 12px !important;
}
.pinned-popup.pinned-popup-minimized .pinned-popup-refresh svg {
width: 12px;
height: 12px;
}
/* ==========================================================================
v2026.5.17 : mode Réduit (docké en bas de l'écran) + taskbar
========================================================================== */
.pinned-popup.pinned-popup-reduced {
display: none !important;
}
.pinned-popups-dock {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: none;
flex-wrap: wrap;
gap: 6px;
padding: 6px 10px;
background: var(--bg-elevated);
border-top: 1px solid var(--border);
box-shadow: 0 -2px 10px rgba(0,0,0,0.2);
align-items: center;
}
.pinned-popups-dock.visible {
display: flex;
}
.pinned-popup-dock-pill {
display: inline-flex;
align-items: center;
padding: 6px 14px;
background: var(--bg-muted);
color: var(--text);
border: 1px solid var(--border);
border-radius: 16px;
font-family: var(--mono, monospace);
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: background 0.15s, transform 0.15s, filter 0.15s;
white-space: nowrap;
}
.pinned-popup-dock-pill:hover {
transform: translateY(-1px);
filter: brightness(1.1);
}
/* v2026.5.18 : couleurs par catégorie (fond = couleur, texte blanc) */
/* v2026.5.26 : les anciennes règles color-XXX qui mettaient un fond coloré vif
sont remplacées par une simple barre verticale à gauche de la pastille.
Les styles pour ::before sont plus bas dans le fichier. */
/* v2026.5.18 : bouton "Fermer tous" à droite du dock */
.pinned-popups-close-all {
margin-left: auto;
padding: 6px 12px;
background: transparent;
color: var(--text-muted);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.pinned-popups-close-all:hover {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border-color: #ef4444;
}
/* ==========================================================================
v2026.5.17 : popup user-badge avec ligne session
========================================================================== */
.user-name-popup-name {
font-weight: 600;
margin-bottom: 4px;
}
.user-name-popup-session {
font-size: 12px;
font-variant-numeric: tabular-nums;
padding-top: 4px;
border-top: 1px solid var(--border);
}
.user-name-popup-session.session-ok { color: var(--text-muted); }
.user-name-popup-session.session-warn { color: #f59e0b; font-weight: 600; }
.user-name-popup-session.session-critical { color: #ef4444; font-weight: 700; }
/* ==========================================================================
v2026.5.17 : popup alerte session qui glisse depuis haut-gauche
========================================================================== */
.session-slide-alert {
position: fixed;
/* suit la hauteur dynamique de la topbar (--topbar-height calculé
en JS). +8px de respiration sous le badge user. */
top: calc(var(--topbar-height, 56px) + 8px);
left: -420px; /* hors écran au départ */
width: 380px;
max-width: calc(100vw - 40px);
padding: 14px 18px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-left: 4px solid #f59e0b;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
z-index: 1000;
transition: left 0.28s ease-out, opacity 0.28s;
opacity: 0;
}
/* v2026.5.40 r12 : alignée avec le bord gauche de la topbar (à hauteur
des initiales — left: 14px = padding gauche de la topbar). */
.session-slide-alert.visible {
left: 14px;
opacity: 1;
}
/* En vue horizontale, l'alerte vient sous le badge user dans la sidebar
gauche, donc on laisse left: 14px aussi (la sidebar a son propre padding). */
html.view-horizontal .session-slide-alert.visible {
left: 14px;
}
.session-slide-alert.urgent {
border-left-color: #ef4444;
animation: session-pulse 1.4s ease-in-out infinite;
}
@keyframes session-pulse {
0%, 100% { box-shadow: 0 8px 24px rgba(0,0,0,0.25); }
50% { box-shadow: 0 8px 24px rgba(239,68,68,0.5); }
}
.session-slide-alert-title {
font-size: 12px;
font-weight: 600;
color: var(--text);
margin-bottom: 12px;
}
.session-slide-alert-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.session-slide-alert-extend,
.session-slide-alert-later {
padding: 6px 14px;
font-size: 13px;
border-radius: 6px;
border: 1px solid var(--border);
cursor: pointer;
font-family: inherit;
}
.session-slide-alert-extend {
background: #10b981;
color: white;
border-color: #10b981;
font-weight: 600;
}
.session-slide-alert-extend:hover { background: #059669; }
.session-slide-alert-extend:disabled { opacity: 0.6; cursor: wait; }
.session-slide-alert-later {
background: transparent;
color: var(--text-muted);
}
.session-slide-alert-later:hover {
background: var(--bg-muted);
color: var(--text);
}
/* ==========================================================================
v2026.5.19 : nouveaux éléments
========================================================================== */
/* Bouton Actualiser (↻) dans la topbar du popup épinglé — animation spin */
.pinned-popup-refresh {
font-size: 12px;
line-height: 1;
}
.pinned-popup-refresh svg {
width: 14px;
height: 14px;
}
.pinned-popup-refresh.spinning svg {
animation: pinned-popup-refresh-spin 0.6s linear infinite;
transform-origin: 50% 50%;
}
.pinned-popup-refresh.spinning {
pointer-events: none;
}
@keyframes pinned-popup-refresh-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Pendant le drag d'un popup, ignorer les hover sur les cartes pour ne pas
ouvrir des tooltips parasites */
body.popup-dragging .intervention-v2,
body.popup-dragging .card {
pointer-events: none;
}
/* Mais garder les popups épinglés cliquables */
body.popup-dragging .pinned-popup {
pointer-events: auto;
}
/* Pastille dock à 2 lignes : ref (gras) + date (petit) */
.pinned-popup-dock-pill {
flex-direction: column !important;
align-items: center !important;
padding: 4px 14px !important;
line-height: 1.1;
gap: 1px !important;
}
.pinned-popup-dock-pill-ref {
display: block;
font-size: 13px;
font-weight: 700;
font-family: var(--mono, monospace);
}
.pinned-popup-dock-pill-date {
display: block;
font-size: 12px;
font-weight: 500;
opacity: 0.85;
font-family: var(--mono, monospace);
}
/* ==========================================================================
v2026.5.20 : mini-menu au survol d'une pastille dock
========================================================================== */
.pill-hover-menu {
position: fixed;
z-index: 60;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
padding: 4px;
display: flex;
flex-direction: column;
gap: 2px;
min-width: 130px;
animation: pill-hover-menu-appear 0.12s ease-out;
}
/* v2026.5.43 : pas de transform dans cette animation — Firefox inclut les
transforms dans getBoundingClientRect ce qui fausse le calcul de position
du menu juste après son insertion dans le DOM. Animation en opacité seule. */
@keyframes pill-hover-menu-appear {
from { opacity: 0; }
to { opacity: 1; }
}
.pill-hover-menu-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: transparent;
color: var(--text);
border: none;
border-radius: 4px;
font-family: inherit;
font-size: 13px;
font-weight: 500;
cursor: pointer;
text-align: left;
transition: background 0.12s;
}
.pill-hover-menu-btn:hover {
background: var(--bg-muted);
}
.pill-hover-menu-btn.pill-hover-menu-close:hover {
background: rgba(239, 68, 68, 0.15);
color: #ef4444;
}
.pill-menu-ico {
font-size: 12px;
width: 16px;
text-align: center;
}
/* ==========================================================================
v2026.5.21 : icône 📍 "active" dans le tooltip hover = déjà épinglée
========================================================================== */
.tooltip-pinbtn.tooltip-pinbtn-active {
opacity: 1 !important;
filter: none !important;
background: rgba(239, 68, 68, 0.15);
border-radius: 4px;
}
/* ==========================================================================
v2026.5.23 : drag & drop des pastilles du dock
========================================================================== */
.pinned-popup-dock-pill {
cursor: grab;
user-select: none;
transition: opacity 0.15s, transform 0.12s;
}
.pinned-popup-dock-pill:active {
cursor: grabbing;
}
.pinned-popup-dock-pill.pill-dragging {
opacity: 0.3 !important;
pointer-events: none;
}
.pill-dragging-ghost {
animation: pill-ghost-bounce 0.2s ease-out;
transform: scale(1.05);
box-shadow: 0 10px 24px rgba(0,0,0,0.4);
}
@keyframes pill-ghost-bounce {
from { transform: scale(1); }
to { transform: scale(1.05); }
}
/* ==========================================================================
v2026.5.25 : pastille dock enrichie (lieu + service + date) +
bouton Paramètres dans popup user-badge +
ref dans mini-menu pill
v2026.5.26 : couleurs sobres (fond sombre + barre colorée à gauche) +
contenu centré
========================================================================== */
/* Pastille dock : 3 lignes centrées, fond sombre, barre colorée à gauche */
.pinned-popup-dock-pill {
flex-direction: column !important;
align-items: center !important;
justify-content: center !important;
padding: 6px 14px 6px 18px !important;
gap: 2px !important;
line-height: 1.2 !important;
text-align: center;
min-width: 200px;
max-width: 300px;
/* Fond sobre et texte bien lisible, peu importe la catégorie */
background: var(--bg-muted) !important;
color: var(--text) !important;
border: 1px solid var(--border) !important;
position: relative;
overflow: hidden;
}
/* Barre verticale colorée à gauche = indicateur de catégorie */
.pinned-popup-dock-pill::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: var(--c-autre);
}
.pinned-popup-dock-pill.color-livraison::before { background: var(--c-livraison); }
.pinned-popup-dock-pill.color-installation::before { background: var(--c-installation); }
.pinned-popup-dock-pill.color-recup::before { background: var(--c-recup); }
.pinned-popup-dock-pill.color-remplacement::before { background: var(--c-remplacement); }
.pinned-popup-dock-pill.color-incident::before { background: var(--c-incident); }
.pinned-popup-dock-pill.color-rollout::before { background: var(--c-rollout); }
.pinned-popup-dock-pill.color-reservation::before { background: var(--c-reservation); }
.pinned-popup-dock-pill.color-absence::before { background: #666; }
.pinned-popup-dock-pill.color-autre::before { background: var(--c-autre); }
.pinned-popup-dock-pill-lieu {
display: block;
font-size: 13px;
font-weight: 700;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text);
text-align: center;
}
.pinned-popup-dock-pill-service {
display: block;
font-size: 11px;
font-weight: 500;
color: var(--text-muted);
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.pinned-popup-dock-pill-date {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--text-faint);
font-family: var(--mono, monospace);
text-align: center;
}
/* Ref dans le mini-menu hover de la pastille */
.pill-hover-menu-ref {
padding: 6px 12px;
text-align: center;
font-family: var(--mono, monospace);
font-size: 13px;
font-weight: 700;
color: var(--text);
border-bottom: 1px solid var(--border);
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Bouton Paramètres dans popup user-badge */
.user-name-popup-settings {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
padding: 6px 10px;
background: var(--bg-muted);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
font-family: inherit;
font-size: 13px;
font-weight: 500;
cursor: pointer;
width: 100%;
justify-content: center;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.user-name-popup-settings:hover {
background: var(--accent, #3b82f6);
color: white;
border-color: var(--accent, #3b82f6);
}
.user-name-popup-settings .settings-ico {
font-size: 15px;
}
/* ==========================================================================
v2026.5.26 : rond gris avec "?" quand user inconnu
========================================================================== */
.user-badge.user-badge-unknown {
--user-badge-color: #6b7280 !important;
opacity: 0.75;
font-weight: 500;
}
.user-badge.user-badge-unknown:hover {
opacity: 1;
}
/* ==========================================================================
v2026.5.27 : agrandir +20% les textes topbar + stats bar pour la lisibilité
========================================================================== */
/* Labels boutons topbar */
.btn-action-label,
.btn-refresh-label {
font-size: 14px !important; /* +20% depuis 12px */
}
.btn-today {
font-size: 14px !important;
padding: 7px 12px !important;
}
.btn-subtle {
font-size: 14px !important;
}
.capture-info {
font-size: 14px !important; /* +20% depuis 12px */
}
.topbar h1 {
font-size: 21px !important; /* +20% depuis 18px */
}
/* date du planning — 28px en vue classique (-10%). En vue horizontale,
la sidebar a son propre override (12px) plus bas. */
#date-custom-label {
font-size: 28px !important;
line-height: 1 !important;
}
.date-custom {
font-size: 28px !important;
}
/* Stats bar */
.stats-bar,
.stats-bar * {
font-size: 14px !important;
}
.stats-bar strong {
font-size: 16px !important;
}
/* v2026.5.27 : icône thème plus contrastée avec bordure + fond visible */
#theme-toggle {
border: 1.5px solid var(--border-strong, rgba(128,128,128,0.5)) !important;
background: var(--bg-muted) !important;
width: 38px;
height: 38px;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s, border-color 0.15s;
}
#theme-toggle:hover {
background: var(--accent-soft, rgba(59, 130, 246, 0.15)) !important;
border-color: var(--accent, #3b82f6) !important;
}
#theme-icon {
font-size: 20px;
line-height: 1;
filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3));
}
/* ==========================================================================
v2026.5.27 : classification visuelle des absences (Maladie, Congé, Pompier)
========================================================================== */
/* Variables de couleurs pour les catégories d'absence */
:root {
--c-maladie: #4338ca; /* Indigo foncé */
--c-maladie-soft: #e0e7ff; /* Indigo très clair */
--c-conge: #06b6d4; /* Cyan */
--c-conge-soft: #cffafe; /* Cyan très clair */
}
html.theme-dark {
--c-maladie: #818cf8; /* Indigo plus clair pour dark mode */
--c-maladie-soft: #2a1e66; /* Indigo foncé pour fonds */
--c-conge: #67e8f9; /* Cyan plus clair pour dark mode */
--c-conge-soft: #0e3e4a; /* Cyan foncé pour fonds */
}
/* Badge "Maladie" à côté du nom */
.badge-maladie {
background: var(--c-maladie-soft);
color: var(--c-maladie);
font-weight: 600;
}
/* Badge "Congé" / "Congés" à côté du nom */
.badge-conge {
background: var(--c-conge-soft);
color: var(--c-conge);
font-weight: 600;
}
/* Carte entière : couleur de fond + barre gauche épaisse pour absence */
.card.absence-cat-maladie {
border-left: 4px solid var(--c-maladie);
background: linear-gradient(to bottom, var(--c-maladie-soft) 0%, var(--bg-elevated) 40%);
}
html.theme-dark .card.absence-cat-maladie {
background: linear-gradient(to bottom, rgba(67, 56, 202, 0.12) 0%, var(--bg-elevated) 40%);
}
.card.absence-cat-conge {
border-left: 4px solid var(--c-conge);
background: linear-gradient(to bottom, var(--c-conge-soft) 0%, var(--bg-elevated) 40%);
}
html.theme-dark .card.absence-cat-conge {
background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--bg-elevated) 40%);
}
/* Pompier — on reprend le style existant mais on accentue le border-left */
.card.absence-cat-pompier {
border-left: 4px solid var(--danger);
}
/* Pastille ronde à côté du nom */
.tech-name-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
flex-shrink: 0;
vertical-align: middle;
}
.tech-name-dot.absence-dot-maladie { background: var(--c-maladie); }
.tech-name-dot.absence-dot-conge { background: var(--c-conge); }
.tech-name-dot.absence-dot-pompier { background: var(--danger); }
/* Note statut (banner texte sur la zone interventions) */
.card-status-note {
padding: 14px 16px;
font-size: 15px;
font-weight: 500;
text-align: center;
border-radius: 6px;
margin: 10px;
}
.card-status-note.absent-maladie {
background: var(--c-maladie-soft);
color: var(--c-maladie);
}
.card-status-note.absent-conge {
background: var(--c-conge-soft);
color: var(--c-conge);
}
.card-status-note.pompier {
background: var(--danger-soft);
color: var(--danger);
}
/* ==========================================================================
v2026.5.28 : popups épinglés gardent une taille standard au resize de la
fenêtre. Le max-width 620px du tooltip de base les contraignait quand on
réduisait la largeur depuis le côté droit — désormais ils restent à leur
taille créée et sont simplement repositionnés dans la safe area.
========================================================================== */
/* v2026.5.40 r17 : on retire le `!important` sur width pour permettre à
pinTooltip d'imposer la même largeur que le tooltip live au moment du
clic (via style.width inline). 520px reste la valeur par défaut si le
JS ne définit rien. */
.pinned-popup:not(.pinned-popup-minimized):not(.pinned-popup-reduced) {
max-width: none !important;
width: 520px; /* défaut, peut être override par pinTooltip */
min-width: 320px;
}
/* Sur les très petits écrans (< 600px), on laisse le clamp naturel */
@media (max-width: 600px) {
.pinned-popup:not(.pinned-popup-minimized):not(.pinned-popup-reduced) {
width: calc(100vw - 20px) !important;
min-width: 0;
}
}
/* ==========================================================================
v2026.5.30 : absences récurrentes (configurées par tech) en cyan
(même couleur que Congé mais texte distinct "Absent le vendredi")
========================================================================== */
/* Badge "Absent" cyan pour récurrent */
.badge-recurring {
background: var(--c-conge-soft);
color: var(--c-conge);
font-weight: 600;
}
/* Carte entière : bordure gauche cyan + fond dégradé */
.card.absence-cat-recurring {
border-left: 4px solid var(--c-conge);
background: linear-gradient(to bottom, var(--c-conge-soft) 0%, var(--bg-elevated) 40%);
}
html.theme-dark .card.absence-cat-recurring {
background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--bg-elevated) 40%);
}
/* Message "Absent le vendredi" (ou autre récurrence) en cyan */
.tech-absence-recurring {
padding: 14px 12px;
text-align: center;
font-size: 15px;
font-weight: 500;
font-style: normal;
color: var(--c-conge);
background: var(--c-conge-soft);
border-radius: 6px;
margin: 10px;
}
html.theme-dark .tech-absence-recurring {
background: rgba(6, 182, 212, 0.12);
}
/* ==========================================================================
v2026.5.30 : mode compact pour écrans 24" Full HD (1920×1080) ou plus petits
Objectif : réduire les paddings et tailles sans casser la lisibilité.
========================================================================== */
@media (max-width: 1920px) {
/* Topbar légèrement compactée */
.topbar {
padding: 8px 14px !important;
gap: 10px;
}
.topbar h1 {
font-size: 18px !important;
}
/* horloge minuscule à tous les breakpoints. */
.app-clock-date,
.app-clock-time {
font-size: 14px !important;
}
.app-clock-date::after {
font-size: 14px !important;
}
/* Stats bar plus dense */
.stats-bar {
padding: 6px 12px !important;
}
.stats-bar,
.stats-bar * {
font-size: 13px !important;
}
.stats-bar strong {
font-size: 14px !important;
}
/* Cartes : padding réduit */
.card-header {
padding: 8px 12px !important;
}
.card-tech-name {
font-size: 14px !important;
}
.card-tech-badge {
font-size: 12px !important;
padding: 2px 6px !important;
}
/* Interventions : padding vertical réduit */
.intervention-v2 {
padding: 7px 10px 9px 6px !important;
}
/* Timeline plus fine */
.timeline {
padding: 10px 12px 6px 12px !important;
}
.timeline-bar {
height: 18px !important;
}
.timeline-label {
font-size: 12px !important;
}
/* Boutons topbar un peu plus compacts */
.btn-action,
.btn-refresh {
padding: 6px 10px !important;
}
.btn-action-label,
.btn-refresh-label {
font-size: 13px !important;
}
.btn-today {
padding: 6px 10px !important;
font-size: 13px !important;
}
.btn-subtle {
padding: 6px 10px !important;
font-size: 13px !important;
}
/* Grid cartes : colonnes légèrement plus étroites pour en caser 1-2 de + */
.main-grid {
gap: 10px !important;
}
}
/* Breakpoint encore plus étroit (tablette / laptop 13-14") */
@media (max-width: 1400px) {
.topbar {
padding: 6px 10px !important;
}
.topbar h1 {
font-size: 17px !important;
}
/* horloge minuscule à tous les breakpoints. */
.app-clock-date,
.app-clock-time {
font-size: 14px !important;
}
.intervention-v2 {
padding: 6px 8px 7px 5px !important;
}
.card-header {
padding: 7px 10px !important;
}
.stats-bar,
.stats-bar * {
font-size: 12px !important;
}
}
/* ==========================================================================
v2026.5.32 : Vue horizontale — chaque tech = 1 ligne fine empilée
Active seulement quand <html class="view-horizontal">.
But : voir les 8 techs d'un coup sur un 24" Full HD.
========================================================================== */
/* Mode horizontal : la grille devient un simple stack vertical */
html.view-horizontal .cards {
display: flex !important;
flex-direction: column !important;
gap: 6px !important;
padding: 8px 12px 24px 12px !important;
}
/* Chaque carte devient une ligne horizontale compacte */
html.view-horizontal .card {
flex-direction: row !important;
align-items: stretch !important;
height: auto !important;
min-height: 0 !important;
max-height: none !important;
overflow: visible !important;
}
/* Header devient une barre latérale gauche fixe */
/* v2026.5.40 r15 : 180px pour loger "Maladie/Accident" en entier sans
tronquer (libellé le plus long parmi tous les badges). */
html.view-horizontal .card-header {
flex-direction: column !important;
align-items: flex-start !important;
justify-content: center !important;
min-width: 180px !important;
max-width: 180px !important;
border-bottom: none !important;
border-right: 1px solid var(--border) !important;
padding: 6px 10px !important;
gap: 3px !important;
flex: 0 0 auto;
}
/* v2026.5.40 r8 : nom du tech complet (jusqu'à 3 lignes). Si vraiment
trop long, .card-tech-name-tight est appliqué dynamiquement par le JS
pour réduire légèrement la font-size (12px → 11.5px). */
html.view-horizontal .card-tech-name {
font-size: calc(13px * var(--text-scale)) !important;
font-weight: 600;
line-height: 1.2 !important;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
max-width: 100%;
word-break: break-word;
hyphens: auto;
}
html.view-horizontal .card-tech-name.card-tech-name-tight {
font-size: calc(11.5px * var(--text-scale)) !important;
letter-spacing: -0.1px;
}
/* v2026.5.40 r16 : badge tech compact, sidebar 180px. Le texte est décalé
un peu vers la gauche via padding-right > padding-left (effet visuel
demandé). */
html.view-horizontal .card-tech-badge {
font-size: calc(8.5px * var(--text-scale)) !important;
padding: 2px 14px 2px 4px !important;
line-height: 1.3 !important;
white-space: nowrap;
text-align: center;
align-self: flex-start;
max-width: 100%;
letter-spacing: 0.02em;
}
/* Le body prend le reste de la ligne, scroll horizontal si trop d'interv */
html.view-horizontal .card-body {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Timeline visible, un peu plus fine */
html.view-horizontal .timeline {
padding: 6px 10px 4px 10px !important;
background: transparent !important;
border-bottom: none !important;
}
html.view-horizontal .timeline-bar {
height: 10px !important; /* v2026.5.40 r4 : timeline très fine */
}
/* Timeline elle-même : padding réduit pour gagner de la hauteur */
html.view-horizontal .timeline {
padding: 4px 14px 2px 14px !important;
}
/* Échelle d'heures plus compacte */
html.view-horizontal .timeline-scale {
height: 11px !important;
}
html.view-horizontal .timeline-tick {
font-size: calc(14px * var(--text-scale)) !important;
}
/* Liste interventions en mode "chips" (défilement horizontal) */
html.view-horizontal .card-body > .intervention-v2,
html.view-horizontal .card-body > .intervention {
display: none !important; /* masquer la liste détaillée en vue horiz */
}
/* Messages "Pas d'intervention planifiée" / "Absent" tiennent sur la ligne */
html.view-horizontal .card-empty,
html.view-horizontal .card-status-note,
html.view-horizontal .tech-absence-recurring {
padding: 8px 12px !important;
font-size: 13px !important;
margin: 4px 8px !important;
text-align: left !important;
}
/* Stats quick pour chaque tech (nb interv etc.) affichées dans le header */
html.view-horizontal .card-header::after {
content: "";
}
html.view-horizontal .tech-row-stats {
display: flex;
flex-wrap: wrap; /* libellés complets → permet wrap */
gap: 4px;
font-size: calc(11px * var(--text-scale));
color: var(--text-muted);
margin-top: 4px;
width: 100%;
justify-content: flex-start;
}
html.view-horizontal .tech-row-stats .stat-pill {
padding: 2px 8px;
background: var(--bg-muted);
border: 1px solid var(--border);
border-radius: 10px;
font-variant-numeric: tabular-nums;
font-size: calc(10.5px * var(--text-scale));
white-space: nowrap;
}
/* En vue classique, on cache les éléments spécifiques horizontal */
html.view-classic .tech-row-stats {
display: none !important;
}
/* ==========================================================================
v2026.5.35 : en vue horizontale, stats globales sur le CÔTÉ GAUCHE
v2026.5.36 : REFONTE — les stats (et tout le reste) sont maintenant déplacés
physiquement dans .horizontal-sidebar (via JS _moveElementsToSidebar).
Le CSS ci-dessous est conservé au cas où une ancienne instance
ait encore .stats dans <main>, mais il ne devrait plus s'appliquer.
========================================================================== */
html.view-horizontal main#main.legacy-stats-layout {
display: flex;
flex-direction: row;
align-items: stretch;
}
/* ==========================================================================
v2026.5.36 : Vue horizontale — sidebar verticale à gauche
Contient (haut → bas) : nav date, horloge, stats, boutons actions.
Seuls restent en haut : user-badge, titre "Planification", bouton thème.
========================================================================== */
/* Topbar en vue horizontale : minimaliste */
html.view-horizontal .topbar {
padding: 6px 12px !important;
gap: 8px;
}
/* Cacher la zone centrale (déplacée vers sidebar) */
html.view-horizontal .topbar .app-clock,
html.view-horizontal .topbar .capture-info,
html.view-horizontal .topbar .app-session {
display: none !important;
}
/* topbar-left ne contient plus que user-badge + titre */
html.view-horizontal .topbar-left {
gap: 10px;
}
/* topbar-right ne contient plus que le theme-toggle */
html.view-horizontal .topbar-right {
gap: 4px;
}
/* Sidebar verticale à gauche */
.horizontal-sidebar {
flex: 0 0 auto;
width: 200px;
min-width: 200px;
max-width: 200px;
background: var(--bg-muted);
border-right: 1px solid var(--border);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
overflow-y: auto;
position: sticky;
top: 0;
align-self: flex-start;
max-height: calc(100vh - 48px); /* topbar ~48px */
font-size: 12px;
box-sizing: border-box;
}
/* Cacher la sidebar en vue classique (au cas où elle existe encore) */
html.view-classic .horizontal-sidebar {
display: none !important;
}
/* Layout main avec sidebar */
html.view-horizontal main#main {
display: flex;
flex-direction: row;
align-items: stretch;
}
/* Groupe navigation date dans la sidebar — v2026.5.36 (OBSOLÈTE v2026.5.37)
v2026.5.37 : date-nav devient display: contents pour que ses enfants
soient directement positionnables dans la sidebar (btn-today
en haut, app-clock intercalé, flèches groupées dans wrapper).
Les anciennes règles ci-dessous sont conservées pour compat
mais neutralisées par les overrides v5.37 plus bas. */
html.view-horizontal .horizontal-sidebar .date-nav {
/* v5.37 override via display: contents ci-dessous */
}
html.view-horizontal .horizontal-sidebar .date-nav .btn-nav {
/* v5.37 : ces règles sont conservées au cas où flèches restent dans date-nav
(pas censé arriver puisque JS les sort dans #sidebar-arrows). */
padding: 4px 8px;
}
/* date sélectionnée en sidebar horizontale — -5% (18 → 17px). */
html.view-horizontal .horizontal-sidebar .date-nav .date-custom {
width: 100%;
justify-content: flex-start;
padding: 6px 8px;
font-size: 17px !important;
font-weight: 700 !important;
border: 1px solid var(--border-strong) !important;
background: var(--bg-muted) !important;
color: var(--text) !important;
border-radius: 6px !important;
}
html.view-horizontal .horizontal-sidebar .date-nav .date-custom #date-custom-label {
font-size: 17px !important;
font-weight: 700 !important;
color: inherit;
}
html.view-horizontal .horizontal-sidebar .date-nav .btn-today {
padding: 4px 10px;
font-size: 12px !important;
}
/* Horloge dans la sidebar */
html.view-horizontal .horizontal-sidebar .app-clock {
position: static !important;
transform: none !important;
left: auto !important;
top: auto !important;
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
gap: 2px;
padding: 6px 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
}
html.view-horizontal .horizontal-sidebar .app-clock-date {
font-size: 11px !important;
color: var(--text-muted) !important;
font-weight: 500 !important;
}
html.view-horizontal .horizontal-sidebar .app-clock-date::after {
content: "" !important; /* supprimer le gros point · */
display: none !important;
}
html.view-horizontal .horizontal-sidebar .app-clock-time {
font-size: 20px !important;
font-weight: 700 !important;
color: var(--text) !important;
font-variant-numeric: tabular-nums;
}
/* Info capture (Synchronisé à...) */
html.view-horizontal .horizontal-sidebar .capture-info {
display: block !important;
font-size: 11px !important;
color: var(--text-muted);
padding: 2px 4px;
}
/* Stats globales dans la sidebar (séparateurs cachés) */
html.view-horizontal .horizontal-sidebar #stats {
display: flex !important;
flex-direction: column !important;
gap: 4px !important;
padding: 6px 4px !important;
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
margin: 2px 0;
background: transparent !important;
width: 100%;
box-sizing: border-box;
}
html.view-horizontal .horizontal-sidebar #stats .global-stat {
display: block;
width: 100%;
padding: 2px 0;
font-size: 11px;
color: var(--text-muted);
}
html.view-horizontal .horizontal-sidebar #stats .global-stat b {
color: var(--text);
font-weight: 700;
font-size: 13px;
}
html.view-horizontal .horizontal-sidebar #stats .global-stat-main b {
font-size: 15px !important;
}
/* v2026.5.40 r19 : en sidebar horizontale, on cache tous les séparateurs
sauf le `+` (classe .global-stat-sep-keep) qui s'affiche en bloc CENTRÉ
entre "tech. dispo" et "pompier". */
html.view-horizontal .horizontal-sidebar #stats .global-stat-sep {
display: none !important;
}
html.view-horizontal .horizontal-sidebar #stats .global-stat-sep.global-stat-sep-keep {
display: block !important;
width: 100% !important;
text-align: center !important;
color: var(--text-faint);
font-size: 12px;
font-weight: 700;
padding: 0;
/* Compenser le gap parent (#stats a gap: 4px) pour que le `+` soit serré
entre "tech. dispo" et "pompier" — pas d'espace artificiel autour. */
margin: -3px auto -3px auto;
line-height: 1;
align-self: center;
}
/* séparateur après "clos" — en CLASSIQUE on garde le "//" en texte
inline ; en HORIZONTAL on transforme cette span en barre HORIZONTALE
pleine largeur (le texte "//" est masqué). */
html.view-horizontal .horizontal-sidebar #stats .global-stat-sep-after-clos {
display: block !important;
width: 90% !important;
height: 0 !important;
border-top: 1px solid var(--border-strong, var(--border)) !important;
margin: 6px auto !important;
padding: 0 !important;
font-size: 0 !important;
line-height: 0 !important;
color: transparent !important;
}
html.has-topbar-color.view-horizontal .horizontal-sidebar #stats .global-stat-sep-after-clos {
border-top-color: color-mix(in srgb, var(--topbar-text) 35%, transparent) !important;
}
html.view-horizontal .horizontal-sidebar #stats .global-stat-sub {
display: block !important;
font-size: 12px;
color: var(--text-faint);
padding-left: 6px;
}
/* Boutons d'action dans la sidebar : pleine largeur, empilés */
html.view-horizontal .horizontal-sidebar button.btn {
width: 100%;
justify-content: flex-start !important;
padding: 6px 10px !important;
font-size: 12px !important;
gap: 6px;
flex: 0 0 auto;
}
html.view-horizontal .horizontal-sidebar .btn-action-label,
html.view-horizontal .horizontal-sidebar .btn-refresh-label {
display: inline !important;
font-size: 12px !important;
}
html.view-horizontal .horizontal-sidebar .btn-action-icon,
html.view-horizontal .horizontal-sidebar .btn-refresh-icon {
flex: 0 0 16px;
width: 16px;
height: 16px;
}
/* Grille cards prend le reste de la largeur */
html.view-horizontal .cards {
flex: 1 1 auto;
min-width: 0;
}
/* v2026.5.36 : zone nom tech encore plus petite (140 → 120px) */
html.view-horizontal .card-header {
min-width: 120px !important;
max-width: 120px !important;
}
/* Annuler les règles plus anciennes v2026.5.35 qui mettaient les stats à gauche
de manière inline (elles sont désormais dans la sidebar unifiée) */
html.view-horizontal main .stats {
/* Les stats sont maintenant DANS la sidebar, pas dans main. Si pour une
raison quelconque elles y restent, on les cache. */
}
/* Breakpoint étroit : sidebar plus fine */
@media (max-width: 1400px) {
.horizontal-sidebar {
width: 170px;
min-width: 170px;
max-width: 170px;
padding: 8px 10px;
font-size: 11px;
}
html.view-horizontal .card-header {
min-width: 160px !important; /* v2026.5.40 r15 : un peu plus serré sur petit écran */
max-width: 160px !important;
}
}
/* ==========================================================================
v2026.5.36 : layout global du body en vue horizontale
La topbar reste en haut (pleine largeur), puis body devient flex-row :
[sidebar] + [main]
========================================================================== */
html.view-horizontal body {
display: flex;
flex-direction: column;
}
html.view-horizontal body > header.topbar {
flex: 0 0 auto;
}
html.view-horizontal body {
/* Le body doit permettre à sidebar + main de se placer côte à côte.
On met un wrapper flex via un pseudo-selecteur impossible à hacker
proprement sans JS, donc on utilise display:flex sur body et
flex-direction: row pour tout sauf la topbar et quelques éléments
absolument positionnés (toasts, modals...). Plus simple :
on rend .horizontal-sidebar et main voisins via flex sur un wrapper
JS-créé (plus bas). À défaut, positioning absolute fallback. */
}
/* Fallback : si pas de wrapper, on positionne .horizontal-sidebar en sticky
dans le flux normal. <main> n'est pas juste à côté mais en dessous —
ce n'est pas l'idéal, d'où le wrapper JS. */
/* v2026.5.36 : wrapper flex-row pour sidebar + main en vue horizontale */
.horizontal-wrapper {
display: flex;
flex-direction: row;
align-items: stretch;
width: 100%;
}
html.view-horizontal .horizontal-wrapper > main#main {
flex: 1 1 auto;
min-width: 0;
}
/* ==========================================================================
v2026.5.37 : refonte layout vue horizontale
- Topbar en haut : masquée complètement (user-badge + titre descendent
dans la sidebar)
- Sidebar : user-badge + titre en haut, date/heure sous le bouton Auj.,
stats puis espace libre puis boutons en bas (direction colonne-inverse)
- Progress-bar : overlay par-dessus user-badge + titre
- Banderole "En pompier du..." : masquée en vue horizontale
========================================================================== */
/* 1. Topbar masquée complètement en vue horizontale */
html.view-horizontal body > header.topbar {
display: none !important;
}
/* : la session-banner (sticky avec top: var(--topbar-height, 56px))
laissait une "barre invisible" de 56 px au-dessus en vue horizontale
parce que la topbar est en display:none mais --topbar-height conserve
sa dernière valeur. En vue horizontale on force top: 0 pour qu'elle
soit collée au sommet. */
html.view-horizontal .session-banner {
top: 0 !important;
}
/* rendu absence + réservation dans les mini-cards horizontales —
juste le mot ("Congé" / "Réservation") centré. Les infos détaillées
restent dans le tooltip au survol. */
.iv-mini-card-absence .iv-mini-time-vertical,
.iv-mini-card-reservation .iv-mini-time-vertical { display: none; }
.iv-mini-card-absence .iv-mini-card-text,
.iv-mini-card-reservation .iv-mini-card-text {
align-items: center;
justify-content: center;
text-align: center;
flex: 1 1 auto;
}
.iv-mini-absence-label,
.iv-mini-reservation-label {
font-weight: 600;
font-size: 13px;
text-transform: none !important; /* garantir l'accent sur "Congé" */
}
.iv-mini-absence-label { color: var(--text-muted, #98a2b3); }
/* : seul le texte "Réservation" est en jaune ambré (comme la vue
classique) — pas de fond coloré, on garde la barre couleur à gauche
qui est déjà jaune via .color-reservation .iv-mini-card-bar. */
.iv-mini-reservation-label {
color: var(--c-reservation, #f59e0b) !important;
}
/* 2. Sidebar : structure verticale avec section fixe en haut (user+titre+date)
et section "boutons" en bas poussée via margin-top: auto.
v2026.5.39 r8 : max-height retiré (était en conflit avec min-height
compensé par --zoom-inv quand le user dézoom le texte). */
html.view-horizontal .horizontal-sidebar {
padding-top: 12px !important;
}
/* 3. User-badge et titre dans la sidebar, côte à côte en haut */
html.view-horizontal .horizontal-sidebar #user-badge {
position: relative;
margin: 0 auto 2px auto;
display: flex;
align-items: center;
justify-content: center;
}
html.view-horizontal .horizontal-sidebar #app-title {
text-align: center;
font-size: 15px !important;
font-weight: 700 !important;
margin: 0 0 8px 0 !important;
padding: 0 !important;
color: var(--text);
}
/* 4. Bouton "Aujourd'hui" en pleine largeur, avant date/heure */
/* v2026.5.37 : on utilise un DOM wrapper #sidebar-arrows créé en JS pour
les 2 flèches côte à côte. date-nav est décomposé en : [Auj.] + [clock
intercalé] + [date-custom] + [arrows-wrapper]. Le JS s'en occupe. */
html.view-horizontal .horizontal-sidebar .date-nav {
display: contents;
}
/* today-block (nav-today + app-clock) déplacé en bloc en sidebar,
empilé verticalement comme la vue classique. */
html.view-horizontal .horizontal-sidebar .today-block {
order: 1;
display: flex !important;
flex-direction: column !important;
align-items: stretch !important;
width: 100%;
gap: 6px;
padding: 6px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg-muted);
margin: 0;
}
/* Bouton Aujourd'hui en haut du today-block en sidebar. */
html.view-horizontal .horizontal-sidebar .today-block .btn-today,
html.view-horizontal .horizontal-sidebar .btn-today {
width: 100% !important;
justify-content: center !important;
text-align: center !important;
}
/* 5. App-clock (date + heure) centré sous le bouton "Aujourd'hui" */
html.view-horizontal .horizontal-sidebar .today-block .app-clock,
html.view-horizontal .horizontal-sidebar .app-clock {
align-items: center !important;
text-align: center !important;
padding: 4px 4px !important;
background: transparent !important;
border: none !important;
margin: 0 !important;
width: 100%;
}
/* date du jour et heure même taille (12px) plus petites en sidebar
horizontale, pour que la date sélectionnée (14px gras) reste l'info dominante. */
html.view-horizontal .horizontal-sidebar .app-clock-date {
text-align: center !important;
width: 100%;
font-size: 12px !important;
font-weight: 500 !important;
color: var(--text-muted) !important;
}
html.view-horizontal .horizontal-sidebar .app-clock-time {
text-align: center !important;
width: 100%;
font-size: 12px !important;
font-weight: 600 !important;
color: var(--text-muted) !important;
}
/* 6. Séparateur visuel après date/heure (avant sélecteur date)
v2026.5.37 : on override order:-1 qui venait de v5.36 */
html.view-horizontal .horizontal-sidebar .date-nav .date-custom-wrapper {
order: 3 !important;
border-top: 1px solid var(--border);
padding-top: 8px;
margin-top: 4px;
width: 100%;
}
/* 7. Flèches ◀ ▶ côte à côte via wrapper JS #sidebar-arrows */
html.view-horizontal .horizontal-sidebar #sidebar-arrows {
order: 4;
display: flex;
flex-direction: row;
gap: 4px;
width: 100%;
}
html.view-horizontal .horizontal-sidebar #sidebar-arrows .btn-nav {
flex: 1 1 0 !important;
min-width: 0 !important;
justify-content: center !important;
}
/* 8. Stats empilées avec order pour venir après les flèches */
html.view-horizontal .horizontal-sidebar #stats {
order: 5;
width: 100%;
margin-top: 8px;
}
/* 9. Capture-info (Synchronisé à HH:MM) sous les stats */
html.view-horizontal .horizontal-sidebar .capture-info {
order: 6;
margin-top: 4px;
text-align: center;
}
/* 10. Boutons poussés en bas via margin-top: auto sur le premier d'entre eux
(Absence, qui a order:7).
v2026.5.39 r2 : ajout d'une fine bordure top + padding pour visualiser
clairement la séparation entre la zone "info" (capture-info, stats) et
la zone "actions" (boutons). margin-top: auto pousse en bas. */
html.view-horizontal .horizontal-sidebar #absence-btn {
order: 7;
margin-top: auto !important; /* pousse tout ce qui suit en bas */
padding-top: 8px !important; /* respiration au-dessus du bouton */
position: relative;
}
html.view-horizontal .horizontal-sidebar #absence-btn::before {
content: "";
position: absolute;
top: 0;
left: 4px;
right: 4px;
height: 1px;
background: var(--border);
}
html.view-horizontal .horizontal-sidebar #douchette-btn { order: 8; }
html.view-horizontal .horizontal-sidebar #refresh-partial-btn { order: 9; }
html.view-horizontal .horizontal-sidebar #refresh-btn { order: 10; }
html.view-horizontal .horizontal-sidebar #clear-cache-btn { order: 11; }
html.view-horizontal .horizontal-sidebar #theme-toggle {
order: 12;
margin-top: 4px !important;
}
html.view-horizontal .horizontal-sidebar #abort-btn {
order: 8; /* avec douchette-btn (qui est aussi 8) — ne devrait pas
être visible en même temps (un seul actif à la fois) */
}
/* 11. Theme-toggle en bas : pleine largeur centrée */
html.view-horizontal .horizontal-sidebar #theme-toggle {
width: 100% !important;
padding: 6px !important;
justify-content: center !important;
}
/* 12. Sidebar doit être flex column pour que margin-top:auto fonctionne.
v2026.5.39 r3 : on force min-height: 100vh.
v2026.5.39 r5 : tout est centré horizontalement (text-align + align-items).
v2026.5.39 r8 : on compense le body.zoom via --zoom-inv. À 75% de zoom,
100vh ne couvre que 75% de la viewport effective ; on multiplie donc par
l'inverse du zoom pour que la sidebar atteigne TOUJOURS le bas de l'écran. */
html.view-horizontal .horizontal-sidebar {
display: flex !important;
flex-direction: column !important;
align-items: center !important; /* centrage horizontal des enfants */
text-align: center !important; /* centrage des textes */
gap: 6px !important;
min-height: calc(100vh * var(--zoom-inv, 1)) !important;
box-sizing: border-box !important;
}
/* Tous les enfants directs reçoivent text-align: center par héritage,
mais on force aussi sur les boutons (qui ont parfois justify-content:
flex-start) et les blocs de stats. */
html.view-horizontal .horizontal-sidebar > * {
text-align: center !important;
}
html.view-horizontal .horizontal-sidebar button.btn {
justify-content: center !important;
}
html.view-horizontal .horizontal-sidebar #stats .global-stat {
text-align: center !important;
}
html.view-horizontal .horizontal-sidebar .app-clock {
align-items: center !important;
text-align: center !important;
}
html.view-horizontal .horizontal-sidebar .date-custom {
justify-content: center !important;
}
/* 13. Barre de rafraîchissement en vue horizontale : overlay par-dessus
user-badge + titre (zone haut de sidebar). */
html.view-horizontal #progress-bar {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 200px !important; /* largeur de la sidebar */
z-index: 9999 !important;
border-radius: 0 !important;
margin: 0 !important;
pointer-events: none;
}
/* Sur breakpoint étroit (sidebar 170px) */
@media (max-width: 1400px) {
html.view-horizontal #progress-bar {
width: 170px !important;
}
}
/* v2026.5.40 r17 : banderole "En pompier du..." en vue HORIZONTALE
uniquement — pleine largeur, hauteur modérée (2 fois plus que r14d),
texte centré. */
html.view-horizontal .card-status-note.pompier {
padding: 4px 14px !important;
text-align: center !important;
line-height: 1.3 !important;
font-size: calc(11.5px * var(--text-scale)) !important;
margin: 0 !important;
border-radius: 0 !important;
width: auto !important;
display: block !important;
}
/* ==========================================================================
v2026.5.38 : Signature auteur en bas du popup user-badge
Style cohérent avec le footer "QRO/vX.Y.Z" en bas à droite : petit, gris
atténué, séparé par une fine ligne supérieure.
========================================================================== */
.user-name-popup-author {
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--border);
font-size: 11px;
color: var(--text-faint);
text-align: center;
font-style: italic;
letter-spacing: 0.2px;
user-select: none;
}
/* ==========================================================================
v2026.5.39 : Séparateur Matin / Après-midi entre les interventions
Affiché entre les rows .intervention-v2 dans la vue classique. La ligne
est marquée (pas un simple text), avec un label dans une "pill" centrée
sur une fine barre horizontale qui traverse la card.
Caché automatiquement en vue horizontale (les rows .intervention-v2 sont
masquées dans cette vue, donc le séparateur le serait visuellement seul).
========================================================================== */
.day-period-sep {
display: flex;
align-items: center;
gap: 10px;
margin: 14px 0 8px 0;
padding: 0 6px;
position: relative;
user-select: none;
}
/* v2026.5.39 r2 : ligne plus épaisse (3px), continue, sans dégradé. */
.day-period-sep::before,
.day-period-sep::after {
content: "";
flex: 1 1 auto;
height: 3px;
background: var(--border-strong);
border-radius: 2px;
}
/* v2026.5.39 r2 : style neutre — pas de couleur ambre/bleu. */
.day-period-sep .day-period-label {
flex: 0 0 auto;
font-size: 12px;
font-weight: 700;
letter-spacing: 1.4px;
text-transform: uppercase;
color: var(--text-muted);
background: var(--bg-muted);
border: 1px solid var(--border-strong);
border-radius: 14px;
padding: 4px 14px;
white-space: nowrap;
}
/* Premier séparateur : pas de marge top excessive (juste après les stats) */
.card-stats + .day-period-sep {
margin-top: 8px;
}
/* Vue horizontale : la liste détaillée est masquée donc le séparateur aussi */
html.view-horizontal .day-period-sep {
display: none !important;
}
/* : séparateur "vide" (la période n'a aucune iv) — on garde le
même design que le séparateur normal d'une période avec iv, pour
l'homogénéité. Aucun override visuel. */
/* Option "Afficher les pauses" — quand inactive, on cache les
.gap-placeholder SAUF .gap-placeholder-full (le bloc Matin/Aprèm
complètement vide reste toujours visible, peu importe l'option).
La détection bidirectionnelle hover du timeline-hole reste
fonctionnelle (le hole existe toujours dans le DOM). */
html:not(.show-iv-gaps) .gap-placeholder:not(.gap-placeholder-full) {
display: none;
}
/* : "carte vide" entre 2 iv qui ne s'enchainent pas — même rendu
que le placeholder Matin/Aprèm vide. */
.gap-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
margin: 6px 0;
font-size: 16px;
font-weight: 700;
color: var(--text-faint);
border: 1px dashed var(--border);
border-radius: 6px;
background: transparent;
cursor: help;
user-select: none;
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.gap-placeholder.gap-placeholder-mini {
/* : flex défini inline par JS — flex-grow proportionnel,
flex-shrink 10 (compresse 10× plus que l'iv en cas de manque
de place), basis 30 px. min-width 0 : peut disparaître visuelle-
ment quand l'espace manque, pour laisser la place aux iv. */
flex: 1 10 30px;
min-width: 0;
min-height: 0;
align-self: stretch;
margin: 0;
font-size: 14px;
overflow: hidden;
}
/* : variante "full" — quand le bloc Matin/Aprèm est complètement
vide en vue horizontale, le gap prend TOUTE la largeur du bloc
(au lieu des 32 px fixes), comme l'ancien .iv-mini-block-empty. */
.gap-placeholder.gap-placeholder-mini.gap-placeholder-full {
flex: 1 1 auto;
min-width: 60px;
}
/* : en vue horizontale, on cache le grand .gap-placeholder (vue
classique) qui apparaissait en bas de la card-body — les rows
.intervention-v2 sont déjà cachées, le rect entre rows traînait
en plus en bas. Seuls les .gap-placeholder-mini (entre les
mini-cards horizontales) restent visibles. */
html.view-horizontal .gap-placeholder:not(.gap-placeholder-mini) {
display: none !important;
}
/* Surbrillance individuelle (CSS pur, toujours active) :
- hover direct du rectangle → le rectangle s'éclaire
- hover direct du timeline-hole → le hole s'éclaire
PLUS l'effet symétrique via la classe .gap-hover-active appliquée par
JS sur l'élément lié. Du coup les 2 côtés se highlight ensemble même
si le bind JS échoue à trouver le partenaire. */
.gap-placeholder:hover,
.gap-placeholder.gap-hover-active {
background: var(--accent-soft);
border-color: var(--accent, #0f4f8b);
border-style: solid;
color: var(--accent, #0f4f8b);
}
.timeline-hole.gap-hover-active {
background: var(--accent-soft);
outline: 3px solid var(--accent, #0f4f8b);
outline-offset: -3px;
z-index: 6;
}
/* : placeholder "—" sous le séparateur d'une période vide, dans
la vue classique. Reprend le même design que .iv-mini-block-empty
en vue horizontale : encadré dashed, "—" centré, hauteur fixe. */
.day-period-empty-placeholder {
display: flex;
align-items: center;
justify-content: center;
min-height: 44px;
margin: 6px 0;
font-size: 16px;
font-weight: 700;
color: var(--text-faint);
border: 1px dashed var(--border);
border-radius: 6px;
background: transparent;
user-select: none;
pointer-events: none;
}
html.view-horizontal .day-period-empty-placeholder {
display: none !important;
}
/* ==========================================================================
v2026.5.39 : Admin — section Apparence (rows label/control + select +
groupe boutons zoom).
========================================================================== */
.admin-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 14px 0;
border-bottom: 1px solid var(--border);
}
.admin-row:last-child {
border-bottom: none;
}
.admin-row-label {
flex: 1 1 auto;
min-width: 0;
}
.admin-row-label strong {
font-size: 12px;
color: var(--text);
}
.admin-row-desc {
font-size: 12px;
color: var(--text-faint);
margin-top: 4px;
line-height: 1.4;
}
.admin-row-control {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: 6px;
}
.admin-select {
padding: 6px 10px;
font-size: 13px;
background: var(--bg-elevated);
color: var(--text);
border: 1px solid var(--border-strong);
border-radius: 4px;
cursor: pointer;
min-width: 200px;
}
.admin-input-num {
width: 80px !important;
text-align: center;
}
.admin-zoom-group {
display: flex;
gap: 4px;
}
.btn-zoom {
min-width: 56px;
padding: 6px 10px;
font-size: 12px;
font-weight: 500;
background: var(--bg-elevated);
color: var(--text-muted);
border: 1px solid var(--border-strong);
border-radius: 4px;
cursor: pointer;
transition: background 0.1s, color 0.1s, border-color 0.1s;
}
.btn-zoom:hover {
background: var(--bg-hover);
color: var(--text);
}
.btn-zoom.active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
/* ==========================================================================
v2026.5.39 r5 : zoom texte via variable --text-scale (0.8 à 1.2).
Modifie uniquement les font-size, pas le layout. Stockée par JS dans
admin_config.textZoom et appliquée sur <html> au boot.
On utilise un selecteur `*` ciblant tous les éléments qui ont
`font-size` défini en `px` ou `rem` — nope c'est impossible. Donc on
applique sur :root un font-size inheritable, et on bump les tailles
spécifiques via calc(). Pour simplicité on cible juste body+composants
principaux. Les éléments restants (avec font-size en px hardcodé) seront
inchangés, mais comme la majorité des textes hérite de body, ça suffit
en pratique.
========================================================================== */
/* v2026.5.39 r7 : zoom GLOBAL sur le body — fait scaler TOUS les textes
visibles (interventions, popups, absences, réservations, tooltips, cards,
sidebar, etc.). Application via body.style.zoom = "<pct>" depuis JS.
On garde aussi --text-scale pour la date/heure (qui restent à la même
taille entre elles, même règle calc()). */
:root {
--text-scale: 1;
--zoom-factor: 1;
--zoom-inv: 1;
}
/* base minuscule (9px) — l'horloge contextuelle ne doit pas dominer. */
.app-clock-time,
.app-clock-date { font-size: calc(14px * var(--text-scale)) !important; }
/* v2026.5.39 r12 : le panel admin suit le zoom comme tout le reste.
Pas d'effet yo-yo car le zoom n'est plus appliqué pendant le drag du
slider (seulement au release) — voir _applyTextZoom dans viewer.js. */
/* Slider taille texte dans Apparence — v2026.5.39 r6 : 5 dots sur la piste */
.admin-zoom-slider-wrap {
display: flex;
align-items: center;
gap: 12px;
}
.admin-zoom-slider {
width: 220px;
height: 26px;
cursor: pointer;
-webkit-appearance: none;
appearance: none;
background: transparent;
}
.admin-zoom-slider::-webkit-slider-runnable-track {
height: 4px;
background:
radial-gradient(circle at 0% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 25% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 50% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 75% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 100% 50%, var(--text-muted) 0 4px, transparent 5px),
var(--border-strong);
border-radius: 2px;
}
.admin-zoom-slider::-moz-range-track {
height: 4px;
background:
radial-gradient(circle at 0% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 25% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 50% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 75% 50%, var(--text-muted) 0 4px, transparent 5px),
radial-gradient(circle at 100% 50%, var(--text-muted) 0 4px, transparent 5px),
var(--border-strong);
border-radius: 2px;
}
.admin-zoom-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--accent);
border: 2px solid var(--bg-elevated);
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
cursor: pointer;
margin-top: -7px;
}
.admin-zoom-slider::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--accent);
border: 2px solid var(--bg-elevated);
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
cursor: pointer;
}
.admin-zoom-value {
min-width: 48px;
text-align: right;
font-size: 13px;
font-weight: 600;
color: var(--text);
font-variant-numeric: tabular-nums;
}
/* ==========================================================================
v2026.5.39 r6 : section "À propos" du panel admin
========================================================================== */
.admin-about-card {
display: flex;
flex-direction: column;
gap: 0;
background: var(--bg-muted);
border: 1px solid var(--border);
border-radius: 6px;
padding: 16px 20px;
margin-top: 8px;
}
.admin-about-row {
display: flex;
gap: 16px;
padding: 8px 0;
border-bottom: 1px solid var(--border);
}
.admin-about-row:last-child {
border-bottom: none;
}
.admin-about-label {
flex: 0 0 130px;
font-size: 13px;
color: var(--text-muted);
font-weight: 500;
}
.admin-about-value {
flex: 1 1 auto;
font-size: 13px;
color: var(--text);
word-break: break-word;
}
.admin-about-value a {
color: var(--accent);
text-decoration: none;
}
.admin-about-value a:hover {
text-decoration: underline;
}
.admin-about-desc {
margin: 12px 0;
padding: 12px 14px;
background: var(--bg-elevated);
border-left: 3px solid var(--accent);
border-radius: 0 4px 4px 0;
font-size: 13px;
line-height: 1.55;
color: var(--text);
}
/* ==========================================================================
v2026.5.40 : segments timeline enrichis (vue horizontale uniquement)
- Barre verticale couleur catégorie à gauche (comme les cards classique)
- Référence + ville lisibles dans le segment (si assez large)
- Cachés en vue classique (pas la place dans une timeline 20px de haut)
========================================================================== */
/* (retiré v2026.5.40 r3 : le contenu enrichi est maintenant dans
.iv-mini-cards en-dessous de la timeline) */
.timeline-slot-content { display: none; }
/* v2026.5.40 r3 : la timeline reste comme la vue classique (segments colorés
compacts). Les infos détaillées sont dans .iv-mini-cards juste en-dessous. */
/* ==========================================================================
v2026.5.40 r3 : mini-cartes d'intervention sous la timeline (vue horizontale
uniquement). Chaque carte contient ref / heure / ville / adresse, alignée
sur l'ordre temporel des interventions. Largeur proportionnelle à la durée
(flex-grow), avec min-width pour rester lisible. Trous représentés par des
espaces vides qui préservent l'alignement avec la timeline du dessus.
========================================================================== */
/* Par défaut (vue classique) : caché */
.iv-mini-cards { display: none; }
/* En vue horizontale : flex row qui prend toute la largeur — toutes les
mini-cartes équidistantes. Si vraiment trop d'interv pour la largeur
(rare), elles se compriment via flex-shrink. */
html.view-horizontal .iv-mini-cards {
display: flex;
flex-direction: row;
align-items: stretch;
width: 100%;
gap: 3px;
padding: 2px 14px 4px 14px;
background: transparent;
overflow: hidden;
}
/* Carte mini d'intervention : v2026.5.40 r5 — toutes les cartes ont la
MÊME largeur (flex: 1 1 0) pour qu'on puisse les voir toutes d'un coup
sans scroll. La position temporelle est donnée par la timeline au-dessus. */
.iv-mini-card {
flex: 1 1 0;
display: flex;
flex-direction: row;
align-items: stretch;
min-width: 0; /* permet au flex de bien rétrécir */
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
cursor: pointer;
transition: background 0.1s, border-color 0.1s, box-shadow 0.1s;
}
.iv-mini-card:hover {
background: var(--bg-hover);
border-color: var(--border-strong);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.iv-mini-card.highlight {
outline: 2px solid var(--accent);
outline-offset: -1px;
z-index: 2;
}
/* Barre verticale couleur catégorie à gauche de chaque carte (4 px) */
.iv-mini-card-bar {
flex: 0 0 4px;
background: var(--c-autre);
}
.iv-mini-card.color-livraison .iv-mini-card-bar { background: var(--c-livraison); }
.iv-mini-card.color-installation .iv-mini-card-bar { background: var(--c-installation); }
.iv-mini-card.color-recup .iv-mini-card-bar { background: var(--c-recup); }
.iv-mini-card.color-remplacement .iv-mini-card-bar { background: var(--c-remplacement); }
.iv-mini-card.color-incident .iv-mini-card-bar { background: var(--c-incident); }
.iv-mini-card.color-rollout .iv-mini-card-bar { background: var(--c-rollout); }
.iv-mini-card.color-reservation .iv-mini-card-bar { background: var(--c-reservation); }
.iv-mini-card.color-autre .iv-mini-card-bar { background: var(--c-autre); }
/* v2026.5.40 r7 : bloc heure VERTICAL (09:00 / ↓ / 10:00) */
.iv-mini-time-vertical {
flex: 0 0 auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0;
padding: 4px 6px;
border-right: 1px solid var(--border);
font-variant-numeric: tabular-nums;
min-width: 44px;
}
.iv-mini-time-start,
.iv-mini-time-end {
font-size: calc(11px * var(--text-scale));
font-weight: 600;
color: var(--text);
line-height: 1.1;
}
.iv-mini-time-arrow {
font-size: calc(10px * var(--text-scale));
color: var(--text-faint);
line-height: 1;
margin: 1px 0;
}
/* Bloc texte (3 lignes) à droite — v2026.5.40 r7 : centré horizontalement */
.iv-mini-card-text {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center; /* centre les lignes horizontalement */
gap: 1px;
padding: 4px 8px;
min-width: 0;
text-align: center;
}
.iv-mini-line {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
text-align: center;
}
.iv-mini-ref {
font-weight: 700;
font-size: calc(11px * var(--text-scale));
color: var(--text);
letter-spacing: 0.2px;
}
.iv-mini-ville {
font-size: calc(10.5px * var(--text-scale));
font-weight: 600;
color: var(--text);
}
.iv-mini-adresse {
font-size: calc(10px * var(--text-scale));
font-style: italic;
color: var(--text-faint);
}
/* ==========================================================================
v2026.5.40 r4 : compactage vertical des lignes tech en vue horizontale
- Card globale : pas de padding inutile en haut/bas
- Body : padding nul, gap minimal
- Card-stats (X interv. matin/apm) cachées (info redondante avec mini-cards)
========================================================================== */
html.view-horizontal .card {
margin-bottom: 6px !important;
border-radius: 6px;
}
html.view-horizontal .card-body {
padding: 0 !important;
gap: 0 !important;
}
/* v2026.5.40 r6 : la card-stats du bas est CACHÉE en horizontal — l'info est
désormais dans les pills (.tech-row-stats) du header tech, libellés complets. */
html.view-horizontal .card-body > .card-stats {
display: none !important;
}
/* Header tech plus compact (centrage vertical, hauteur auto) */
html.view-horizontal .card-header {
padding: 4px 10px !important;
}
/* v2026.5.40 r7 : chiffre du nombre d'interventions mis en évidence dans
la pill du header tech (ex: "4 interventions" → le 4 plus gros). */
html.view-horizontal .tech-row-stats .stat-pill .stat-pill-big-num {
font-size: calc(15px * var(--text-scale));
font-weight: 800;
color: var(--text);
margin-right: 2px;
letter-spacing: 0;
}
html.view-horizontal .tech-row-stats .stat-pill {
display: inline-flex;
align-items: baseline;
gap: 0;
}
/* v2026.5.40 r8 : en vue horizontale, le dock des popups épinglés réduits
commence APRÈS la sidebar (200px). Sinon les pills étaient cachées sous
la sidebar fixe à gauche. */
html.view-horizontal .pinned-popups-dock {
left: 200px !important;
}
@media (max-width: 1400px) {
html.view-horizontal .pinned-popups-dock {
left: 170px !important;
}
}
/* v2026.5.45 : 2 blocs MATIN / APRÈS-MIDI toujours présents (même vides),
séparés par une vraie barre verticale dédiée (.iv-mini-sep). Garantit la
symétrie d'une carte tech à l'autre — l'œil retrouve toujours les 2 demi-
journées au même endroit horizontal. */
.iv-mini-block {
flex: 1 1 0;
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.iv-mini-sep {
flex: 0 0 12px;
align-self: stretch;
position: relative;
margin: 0 6px;
/* Trait vertical centré dans la zone réservée. */
background:
linear-gradient(to bottom,
transparent 0,
transparent 14%,
var(--border-strong) 14%,
var(--border-strong) 86%,
transparent 86%,
transparent 100%) no-repeat center / 2px 100%;
}
.iv-mini-sep::before {
/* Petit "•" centré pour signaler la coupure 12h. */
content: "12h";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: calc(9px * var(--text-scale));
font-weight: 700;
color: var(--text-muted);
background: var(--bg-elevated);
padding: 1px 2px;
border-radius: 3px;
letter-spacing: 0.4px;
}
.iv-mini-block-label {
font-size: calc(10px * var(--text-scale));
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
color: var(--text-muted);
text-align: center;
padding: 1px 0;
}
.iv-mini-block-cards {
display: flex;
flex-direction: row;
gap: 3px;
flex: 1 1 auto;
min-width: 0;
}
.iv-mini-block-cards .iv-mini-card {
/* : flex inline (proportionnel à la durée + basis 100 px).
min-width 80 px : seuil ABSOLU sous lequel l'iv ne descend pas,
même si le bloc est très étroit — garantit que la ref / l'heure
restent lisibles. Quand l'espace manque, c'est les gaps qui
compressent en premier (flex-shrink 10 vs 1). */
flex: 1 1 100px;
min-width: 80px;
}
/* : placeholder pour un bloc vide — garde la place réservée et
indique visuellement qu'il n'y a rien sur cette demi-journée. */
.iv-mini-block-empty {
flex: 1 1 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 36px;
font-size: calc(14px * var(--text-scale));
font-weight: 700;
color: var(--text-faint);
border: 1px dashed var(--border);
border-radius: 6px;
background: transparent;
user-select: none;
pointer-events: none;
}
/* v2026.5.40 r18 : ordre fixe des boutons en vue classique topbar-right.
Absence → Douchette → Actualiser → Tout recharger → Vider cache → Thème */
html.view-classic .topbar-right #absence-btn { order: 1; }
html.view-classic .topbar-right #douchette-btn { order: 2; }
html.view-classic .topbar-right #refresh-partial-btn { order: 3; }
html.view-classic .topbar-right #refresh-btn { order: 4; }
html.view-classic .topbar-right #abort-btn { order: 4; }
html.view-classic .topbar-right #clear-cache-btn { order: 5; }
html.view-classic .topbar-right #theme-toggle { order: 6; }
/* ==========================================================================
v2026.5.40 r18 : refonte visuelle onglet Équipe — plus moderne, cohérent
avec le reste du panel admin (Apparence, À propos).
========================================================================== */
.admin-team-table {
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
margin-top: 14px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.admin-team-table th {
background: var(--bg-muted) !important;
border-bottom: 1px solid var(--border-strong) !important;
font-size: 11.5px !important;
letter-spacing: 0.6px !important;
padding: 10px 14px !important;
}
.admin-team-table td {
padding: 10px 14px !important;
border-bottom: 1px solid var(--border) !important;
}
.admin-team-table tr:last-child td {
border-bottom: none !important;
}
.admin-team-table tr:hover td {
background: var(--bg-hover);
}
.admin-team-table .admin-input {
background: var(--bg);
transition: border-color 0.12s, box-shadow 0.12s;
}
.admin-team-table .admin-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.admin-team-table .admin-input-id {
font-weight: 600;
color: var(--text-muted);
}
/* Checkbox + label "Exclure" / jours d'absence : style pill */
.admin-team-table label {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 8px;
background: var(--bg-muted);
border: 1px solid var(--border);
border-radius: 12px;
font-size: 12px;
cursor: pointer;
margin-right: 4px;
transition: background 0.12s, border-color 0.12s;
user-select: none;
}
.admin-team-table label:hover {
background: var(--bg-hover);
border-color: var(--border-strong);
}
.admin-team-table label input[type="checkbox"] {
margin: 0;
cursor: pointer;
}
.admin-team-table label:has(input:checked) {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent);
font-weight: 600;
}
/* v2026.5.40 r20 : onglet Équipe — colonne "Inclure" plus étroite, ID +
nom en lecture seule (texte clair, pas d'input). */
.admin-team-table th:first-child,
.admin-team-table td:first-child {
width: 60px !important;
text-align: center !important;
padding-left: 8px !important;
padding-right: 8px !important;
}
.admin-team-table th:first-child {
font-size: 12px !important;
letter-spacing: 0.4px !important;
}
.admin-team-id-readonly {
font-family: var(--mono);
font-weight: 600;
color: var(--text-muted);
font-size: 12.5px;
}
.admin-team-name-readonly {
font-weight: 600;
color: var(--text);
font-size: 13px;
}
/* v2026.5.40 r21b : seul le `/` prend la couleur faint d'absent.
Le `+` reste en couleur neutre (héritée du parent stats). */
.global-stat-sep.sep-absent {
color: var(--text-faint) !important;
font-weight: 700;
}
/* ==========================================================================
— Feature reschedule (modifier horaire / déplacer iv)
- Bloc heure éditable : curseur grab + petit hover halo
- Ghost qui suit la souris pendant le drag
- Preview rectangle vert sur la timeline du tech survolé
- Modal d'édition + confirmation (réutilise les styles modal existants)
========================================================================== */
.iv-time-vertical.editable-time,
.iv-mini-time-vertical.editable-time {
cursor: grab;
position: relative;
user-select: none;
border-radius: 6px;
transition: background 0.1s, box-shadow 0.1s;
}
.iv-time-vertical.editable-time:hover,
.iv-mini-time-vertical.editable-time:hover {
background: rgba(63, 185, 80, 0.10);
box-shadow: inset 0 0 0 1px rgba(63, 185, 80, 0.45);
}
html.reschedule-dragging .iv-time-vertical.editable-time,
html.reschedule-dragging .iv-mini-time-vertical.editable-time {
cursor: grabbing;
}
html.reschedule-dragging,
html.reschedule-dragging * {
cursor: grabbing !important;
user-select: none !important;
}
/* Ghost flottant attaché au curseur pendant le drag */
.reschedule-ghost {
position: fixed;
z-index: 9999;
pointer-events: none;
background: var(--bg-elevated);
border: 2px solid var(--ok, #2e7b4a);
border-radius: 8px;
padding: 6px;
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35);
font-size: 12px;
line-height: 1.3;
min-width: 140px;
max-width: 360px;
opacity: 0.95;
transition: opacity 0.1s;
transform: rotate(-1.5deg);
}
/* : clone visuel de la card source intégré dans le ghost flottant */
.reschedule-ghost .reschedule-ghost-visual {
pointer-events: none !important;
margin: 0 !important;
cursor: grabbing !important;
filter: none !important;
/* on annule les hover-effects résiduels du clone (.intervention-v2:hover) */
background: var(--bg-card, var(--bg-elevated)) !important;
}
.reschedule-ghost .reschedule-ghost-visual::before,
.reschedule-ghost .reschedule-ghost-visual::after { display: none !important; }
.reschedule-ghost .rg-meta {
margin-top: 6px;
padding: 4px 6px 2px;
border-top: 1px dashed var(--border-faint, rgba(255,255,255,0.12));
}
.reschedule-ghost.on-target {
border-color: var(--ok, #2e7b4a);
background: var(--ok-soft, #dff0e4);
}
.reschedule-ghost:not(.on-target):not(.on-blocked) {
border-color: var(--text-faint, #50596a);
opacity: 0.7;
}
/* : tech cible absent toute la journée → drop refusé visuellement */
.reschedule-ghost.on-blocked {
border-color: var(--err, #d6443d) !important;
background: rgba(214, 68, 61, 0.12) !important;
opacity: 0.85;
}
.reschedule-ghost.on-blocked::before {
content: "🚫 absent";
position: absolute;
top: -10px;
right: 8px;
background: var(--err, #d6443d);
color: #fff;
font-size: 10px;
font-weight: 700;
padding: 1px 8px;
border-radius: 10px;
letter-spacing: 0.3px;
pointer-events: none;
}
.reschedule-ghost .rg-title {
font-weight: 700;
color: var(--text);
font-family: var(--mono, monospace);
}
.reschedule-ghost .rg-times {
margin-top: 2px;
color: var(--text-muted);
}
.reschedule-ghost .rg-times .rg-dur {
color: var(--text-faint);
font-size: 11px;
}
.reschedule-ghost .rg-tech {
margin-top: 2px;
color: var(--text-muted);
font-size: 11px;
font-style: italic;
}
/* : drop-ghost = clone visuel grisé de la carte (row classique ou
mini-card horizontale) inséré dans la card-body / bloc Matin-Aprèm
du tech cible à la position chronologique correspondant aux heures
projetées. C'est ce que voit le user pour repérer où sa carte se
loge — un vrai fac-similé grisé de la carte. */
.reschedule-drop-ghost {
opacity: 0.55 !important;
filter: grayscale(0.5);
border: 2px dashed var(--ok, #2e7b4a) !important;
pointer-events: none !important;
background: var(--bg-card, var(--bg-elevated)) !important;
transition: margin 0.12s ease;
}
.reschedule-drop-ghost.reschedule-drop-ghost-mini {
/* Mini-card horizontale — mêmes codes mais on évite le filter qui
écraserait les couleurs de catégorie de la mini-card. */
filter: none;
opacity: 0.55 !important;
}
/* Petite barre verte sur la timeline-bar pour repérer la position
horaire précise (en plus du clone in-card). Volontairement minimaliste
(pas de label, pas de pulsation) pour ne pas concurrencer le clone. */
.reschedule-timeline-mark {
position: absolute;
top: 0;
bottom: 0;
background: rgba(46, 123, 74, 0.55);
border-left: 2px solid var(--ok, #2e7b4a);
border-right: 2px solid var(--ok, #2e7b4a);
pointer-events: none;
z-index: 5;
box-sizing: border-box;
}
/* Modal d'édition reschedule — réutilise modal-card mais avec un style
plus compact pour le formulaire à 4 champs. */
.modal-card.reschedule-modal {
min-width: 380px;
max-width: 460px;
}
/* Pendant le drag, on supprime tous les hover handlers concurrents :
tooltip live, popovers timeline, hover des mini-cards et iv-rows.
Comme ça elementFromPoint trouve toujours la timeline-bar / card pour
un drop précis, et aucune popup parasite ne s'ouvre. */
html.reschedule-dragging .timeline-slot,
html.reschedule-dragging .iv-mini-card,
html.reschedule-dragging .intervention-v2,
html.reschedule-dragging .timeline-popover,
html.reschedule-dragging .tooltip,
html.reschedule-dragging .pinned-popup {
pointer-events: none !important;
}
html.reschedule-dragging .tooltip:not(.pinned-popup):not(.soft-unpinned) {
display: none !important;
}
/* Texte projeté du ghost : vert vif quand on est sur une cible valide. */
.reschedule-ghost .rg-times {
font-family: var(--mono, monospace);
font-size: 13px;
font-weight: 700;
color: var(--text-muted);
margin-top: 4px;
}
.reschedule-ghost.on-target .rg-times {
color: var(--ok, #2e7b4a);
font-size: 14px;
}
.reschedule-ghost .rg-tech.rg-tech-changed {
color: var(--accent, #0f4f8b);
font-weight: 700;
font-style: normal;
}
.reschedule-ghost .rg-tech.rg-tech-changed::before {
content: "→ ";
}
/* : la source est retirée du flux pendant le drag (display:none) —
la place est immédiatement reprise par le clone à destination inséré
au même endroit dès l'activation du drag, donc la card ne rétrécit
pas visuellement. */
.reschedule-source-hidden {
display: none !important;
}
/* ============================================================================
Dock latéral des interventions mises de côté
───────────────────────────────────────────────────────────────────────────
Conteneur = simple carré flouté à droite (backdrop-filter), pas de chrome.
Cartes = exactement la même structure que le ghost de drag (.reschedule-
ghost) : clone .intervention-v2 / .iv-mini-card avec heure à gauche, ref
et meta à droite. L'utilisateur retrouve sous le dock ce qu'il voyait
sous son curseur pendant le drag.
──────────────────────────────────────────────────────────────────────────── */
.iv-dock {
position: fixed;
top: 80px;
right: 8px;
bottom: 12px;
width: 200px;
z-index: 9000;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
pointer-events: none;
background: rgba(255, 255, 255, 0.10);
-webkit-backdrop-filter: blur(14px) saturate(120%);
backdrop-filter: blur(14px) saturate(120%);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 12px;
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18);
transition: transform 140ms ease-out, opacity 140ms ease-out;
}
html.dark .iv-dock,
html[data-theme="dark"] .iv-dock {
background: rgba(20, 25, 35, 0.32);
border-color: rgba(255, 255, 255, 0.10);
}
body:has(.pinned-popups-dock.visible) .iv-dock {
bottom: 64px;
}
.iv-dock.iv-dock--hidden {
opacity: 0;
transform: translateX(110%);
pointer-events: none;
}
/* Drag commencé, dock vide : juste un petit onglet pour signaler la cible
sans rien masquer du planning (~32 px visibles sur 260 px). */
.iv-dock.iv-dock--peep-min {
transform: translateX(88%);
}
/* Cartes dockées (drag ou pas) : intermédiaire, le numéro DS reste lisible
à droite. v2026.5.45 — décalé un peu plus à droite (50% au lieu de
30%) pour libérer du planning. */
.iv-dock.iv-dock--peep {
/* v2026.5.45 — léger décalage supplémentaire vers la gauche (8 px de
plus visibles que 50%) pour libérer un peu plus le numéro DS. */
transform: translateX(calc(50% - 8px));
}
.iv-dock.iv-dock--expanded {
transform: translateX(0);
}
.iv-dock-list {
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
/* v2026.5.45 — sans overflow-x explicite, "overflow-y: auto" force aussi
l'axe X en auto (spec CSS), d'où la scrollbar horizontale qui apparaissait
en bas du dock dès qu'une carte dépassait d'1 px. On la supprime. */
overflow-x: hidden;
flex: 1;
pointer-events: auto;
}
/* Dock vide (peep-min pendant un drag, ou sans rien) : on ne capte aucun
clic, sinon le carré flouté bloque les mousedown sur les cartes du
planning qui sont sous le dock — ce qui cassait le drag&drop standard. */
.iv-dock-list:empty {
pointer-events: none;
}
.iv-dock.iv-dock--peep-min,
.iv-dock.iv-dock--peep-min .iv-dock-list {
pointer-events: none;
}
/* v2026.5.45 — quand le dock est visible avec du contenu (peep ou
expanded), il capte les événements souris : empêche les clics et le hover
de traverser jusqu'au planning derrière (ex : zone autour du bouton
« Tout annuler » qui sélectionnait l'iv en dessous). */
.iv-dock.iv-dock--peep,
.iv-dock.iv-dock--expanded {
pointer-events: auto;
}
/* Carte du dock = juste ref + date sur fond teinté de la couleur de la
catégorie. Drag handle = la carte entière. Pas d'heure, pas de barre,
pas de chrome. */
.iv-dock-card.reschedule-ghost {
/* v2026.5.45 — repart de zéro : aucun fond, aucune bordure sur la
carte. La couleur de catégorie sera ajoutée UNIQUEMENT par les règles
.iv-dock-card.color-* ci-dessous, qui posent un border-left de 4 px et
RIEN d'autre. */
position: relative !important;
z-index: auto !important;
transform: rotate(-1.5deg) !important;
transition: transform 140ms ease, box-shadow 120ms ease;
pointer-events: auto;
cursor: grab;
opacity: 1;
margin: 0;
min-width: 0;
max-width: none;
padding: 0 !important;
display: block;
font-family: var(--mono, monospace);
background: transparent;
border: none;
/* v2026.5.45 — overflow visible pour que le bouton × (positionné en
-8px/-8px) ne soit pas tronqué par les bords de la card. */
overflow: visible;
user-select: none;
}
.iv-dock-card.reschedule-ghost:hover {
transform: rotate(0deg) scale(1.02) !important;
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.30);
z-index: 1 !important;
}
.iv-dock-card.reschedule-ghost:active {
cursor: grabbing;
}
/* v2026.5.45 — couleur du type d'intervention sur la GAUCHE seulement.
Aucun fond, aucune autre bordure. Juste un trait vertical de 4 px. */
.iv-dock-card.color-livraison { border-left: 4px solid var(--c-livraison); }
.iv-dock-card.color-installation { border-left: 4px solid var(--c-installation); }
.iv-dock-card.color-recup { border-left: 4px solid var(--c-recup); }
.iv-dock-card.color-remplacement { border-left: 4px solid var(--c-remplacement); }
.iv-dock-card.color-incident { border-left: 4px solid var(--c-incident); }
.iv-dock-card.color-rollout { border-left: 4px solid var(--c-rollout); }
.iv-dock-card.color-reservation { border-left: 4px solid var(--c-reservation); }
.iv-dock-card.color-autre { border-left: 4px solid var(--c-autre); }
/* Bloc info : ref + date, texte collé aux bords (juste 2 px latéraux
pour éviter de coller pile à la bordure de la carte). */
.iv-dock-card-info {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0;
}
.iv-dock-card-ref {
font-weight: 700;
font-size: 13px;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
padding: 0 2px;
}
.iv-dock-card-meta {
font-size: 10px;
color: var(--text-muted, #6c7280);
font-style: italic;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.1;
padding: 0 2px;
}
.iv-dock-card-fallback {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
font-size: 12px;
}
.iv-dock-card-fallback .iv-dock-card-time {
font-weight: 700;
font-family: var(--mono, monospace);
}
.iv-dock-card-remove {
/* v2026.5.45 — repositionné DANS la card (top:2px / right:2px) au lieu
de déborder en -8/-8 : le parent .iv-dock-list a overflow-x: hidden
pour supprimer la scrollbar horizontale, donc tout ce qui sort à droite
est tronqué. Le bouton tient en 22 px dans le coin supérieur droit. */
position: absolute;
top: 2px;
right: 2px;
width: 22px;
height: 22px;
border: none;
background: var(--err, #d6443d);
color: #fff;
border-radius: 999px;
cursor: pointer;
display: none;
font-size: 14px;
line-height: 1;
font-weight: 700;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
z-index: 2;
}
.iv-dock-card:hover .iv-dock-card-remove {
display: block;
}
/* v2026.5.45 — pendant l'appui long sur ×, on signale visuellement la
progression : un anneau conic-gradient se remplit en 2 s. À la fin, le
handler JS retire l'iv. Si l'utilisateur lâche avant, la classe est
retirée → l'anim s'annule, rien n'est supprimé. */
.iv-dock-card-remove.holding {
background:
conic-gradient(rgba(255,255,255,0.55) var(--hold, 0%), transparent 0)
content-box,
var(--err, #d6443d);
animation: iv-dock-remove-hold 2000ms linear forwards;
}
@property --hold {
syntax: '<percentage>';
initial-value: 0%;
inherits: false;
}
@keyframes iv-dock-remove-hold {
from { --hold: 0%; }
to { --hold: 100%; }
}
.iv-dock-clear-all {
pointer-events: auto;
align-self: flex-end;
border: 1px solid var(--border, #d4d8e0);
background: var(--bg-elevated, #fff);
color: var(--text-muted, #6c7280);
font-size: 10px;
letter-spacing: 0.4px;
padding: 3px 10px;
border-radius: 999px;
cursor: pointer;
display: none;
text-transform: uppercase;
}
.iv-dock-clear-all:hover {
color: var(--err, #d6443d);
border-color: var(--err, #d6443d);
}
.iv-dock--expanded .iv-dock-clear-all {
display: inline-block;
}