Files
Planification/src/viewer.css
T
FroSteel f1de87ff58 v2026.5.44 — Refonte topbar, personnalisation Apparence, onboarding équipe, fix #1
Refresh / cache / verdicts ghost :
- Rafraîchissement séquentiel (1 fiche à la fois) avec arrêt instantané
  via AbortController.
- Re-fetch checksum frais (basicAutoComplete + redirectHeader).
- Cache merge robuste avec fallback cachedByRef ; cache écrit toutes les
  5 fiches (incrémental).
- Verdicts ghost unifiés : ✓✓ clos/résolu, ✓ Fait (pending), ✓ jaune
  Suspendu, retrait silencieux pour cancelled.
- Statuts EV configurables depuis Paramètres → EasyVista (matching
  insensible à la casse, accents, conjugaisons).
- Mode diagnostic optionnel (Diagnostics) qui logge tout sans rien retirer.

Topbar (vue classique) :
- Sélecteur de date du planning ancré au centre absolu (ne se décale
  plus quand le bouton Arrêter apparaît).
- Bouton Aujourd'hui en toutes lettres.
- Horloge contextuelle réduite à côté.

Personnalisation (Paramètres → Apparence) :
- Couleur de la topbar : 12 presets cliquables + picker custom + champ
  hex. Texte topbar adapté automatiquement (luminance) pour rester lisible.
- Police de l'application : 28 choix (Arial, Helvetica, Verdana, Tahoma,
  Trebuchet, Calibri, Segoe UI, Times New Roman, Georgia, Cambria,
  Garamond, Palatino, Courier, Consolas, Comic Sans, Impact, …) appliquée
  à toute la page (cards, popups, panel admin) avec preview live.
- Export / import du cache et de admin_config.

Vue horizontale :
- Bloc Aujourd'hui + horloge empilé verticalement dans la sidebar.
- Date sélectionnée mise en avant (taille augmentée, gras), date du jour
  + heure réduites à la même petite taille.
- Barre verticale verte à droite des mini-cards clos/résolu (✓✓), avec
  décalage du ✓✓ pour ne pas chevaucher.
- Sidebar adopte la couleur de topbar custom (titre, horloge, today-block,
  date sélectionnée, boutons, theme-toggle, séparateurs translucides
  cohérents via color-mix).

Stats globales :
- Nouveau compteur 'X faits / Y clos' entre (matin · après-midi) et
  tech. dispo.
- Vue classique : séparateur '//' après clos.
- Vue horizontale (sidebar) : barre horizontale 1px de séparation.

Onboarding équipe :
- Carte centrée propre (icône, titre, description, bouton 'Ouvrir
  paramètres') quand aucun technicien n'est sélectionné. Bouton ouvre
  directement la section Équipe du panel admin.

Bugfix :
- Issue #1 (Pompier + Absence) : les deux badges s'affichent désormais
  avec '/' au lieu de masquer l'absence.
- Absences récurrentes restaurées au switch de groupe (étaient invisibles
  alors qu'en storage).
- Barre de progression / bannière session expirée suivent la hauteur
  dynamique de la topbar (--topbar-height via ResizeObserver).
- STATUS_FR regex limite 30 → 200 chars.
- Description action décodée proprement (\u0022, <br>, HTML strippé) ;
  préfixe 'login:' retiré du commentaire technicien.
- Flèche '↗' retirée des références cliquables.
2026-05-01 18:08:11 +02:00

5350 lines
158 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 FroSteel
* Licensed under the MIT License — see LICENSE file in the project root.
*
* @author FroSteel
* @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;
/* R12e : 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
R12e :
- 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;
/* R12e : 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;
/* R12e dark : 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;
}
.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;
}
/* 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;
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;
}
@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(--ok-soft);
}
.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); }
/* R12c : 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); }
/* R12d : 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
========================================================================== */
.tooltip {
position: fixed !important;
/* v4.2.4 : forcer un stacking context propre et l'isolation pour que le
tooltip ne soit pas affecté par un éventuel filter/transform/contain
sur un ancêtre (qui casserait position:fixed). `contain: layout` et
`will-change: transform` garantissent aussi que le navigateur traite
ce tooltip indépendamment. */
isolation: isolate;
contain: layout;
z-index: 100;
max-width: 620px;
max-height: calc(100vh - 40px);
overflow-y: auto;
padding: 12px 14px;
background: var(--bg-elevated);
color: var(--text);
border: 1px solid var(--border-strong);
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 {
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.
───────────────────────────────────────────────────────────────────────── */
.tooltip.pinned-popup {
position: absolute !important; /* override le fixed du .tooltip */
/* v4.3.3 corr : les popups épinglées doivent passer DERRIÈRE la topbar
quand on scrolle (topbar sticky z-index 10). Donc on met 5 : au-dessus
du contenu normal, mais sous la topbar / bannières / modals. */
z-index: 5 !important;
opacity: 1 !important;
pointer-events: auto !important;
/* Bordure plus visible pour distinguer du tooltip live */
border: 2px solid var(--accent, #0f4f8b);
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
/* Pas de contain: layout (hérité) car ça limite le rendu ; on laisse */
animation: pinned-popup-in 0.15s ease-out;
/* Le padding-top est augmenté pour accueillir la barre de drag. */
padding-top: 28px !important;
}
@keyframes pinned-popup-in {
from { opacity: 0; transform: scale(0.96); }
to { opacity: 1; transform: scale(1); }
}
/* v4.3.3 : animation de sortie (symétrique à l'apparition) quand on
désépingle. Appliquée par la classe .unpinning. */
.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; transform: scale(1); }
to { opacity: 0; transform: scale(0.94); }
}
/* v4.3.3 corr : quand une popup est désépinglée "mou", elle perd son look
"épinglé" et redevient un tooltip normal visuellement, tout en gardant
sa position absolute (pour ne pas sauter). */
.tooltip.soft-unpinned {
position: absolute !important;
z-index: 5 !important;
opacity: 1 !important;
pointer-events: auto !important;
/* v2026.5.43 : on conserve les MÊMES dimensions que .pinned-popup
(padding-top 28px, border 2px) pour que la popup ne bouge ni ne change
de taille au softUnpin. La dragbar est juste retirée (l'espace
reste, c'est le tradeoff pour préserver position + taille).
Bordure plus discrète (variable --border-strong au lieu de --accent). */
border: 2px solid var(--border-strong) !important;
box-shadow: var(--shadow-hover) !important;
padding-top: 28px !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)
========================================================================== */
.app-session {
position: absolute;
top: 50%;
left: calc(50% + 60px); /* à droite de l'horloge (~60px de décalage) */
transform: translateY(-50%);
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;
}
.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 {
/* Laisser un peu de place en haut pour la topbar */
padding-top: 30px !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;
}
/* 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 "FRS/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;
}
/* ==========================================================================
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.40 r9 : 2 blocs MATIN / APRÈS-MIDI côte à côte. Chaque bloc a
son label en haut + ses mini-cards en dessous. Séparation visuelle
entre les 2 blocs via un gap plus large + bordure gauche sur l'après-midi. */
.iv-mini-block {
flex: 1 1 0;
display: flex;
flex-direction: column;
min-width: 0;
gap: 2px;
}
.iv-mini-block.period-afternoon {
border-left: 2px solid var(--border-strong);
padding-left: 8px;
margin-left: 8px;
}
.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: 1 1 0;
}
/* 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;
}