/** * 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; /* : 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: #6b21a8; /* v2026.5.46 — violet fonce (choix final apres gris/fuchsia/lime/rose ; distinct du violet clair incident) */ --c-autre-soft: #e5e7eb; /* Statuts clos - terminated reprend l'ANCIEN vert de récupération (#16a34a) — vert pur - closed bascule sur un vert plus FONCÉ/bleuté pour bien le distinguer visuellement de terminated. Les deux restent verts, mais opposés sur la teinte. */ --c-closed: #047857; /* vert sapin foncé = Clôturé */ --c-closed-soft: #d1fae5; --c-resolved: #65a30d; /* lime/vert-jaune = Résolu */ --c-resolved-soft: #ecfccb; --c-terminated: #16a34a; /* vert pur (= ex-recup) = Terminé par tech */ --c-terminated-soft:#dcfce7; --shadow: 0 1px 3px rgba(20, 30, 50, 0.06), 0 1px 2px rgba(20, 30, 50, 0.04); --shadow-hover: 0 2px 8px rgba(20, 30, 50, 0.08); /* v2026.5.49 — relief des interventions : elles se posent en surface claire (--bg-elevated) sur le corps de carte, qui recule (--iv-canvas). Ombre un cran au-dessus de --shadow (qui porte les cartes elles-mêmes) pour que la hiérarchie carte < intervention se voie sans devenir tape-à-l'oeil. --iv-canvas est un gris un peu plus soutenu que --bg-muted : sur du blanc, l'écart de --bg-muted se voyait à peine. */ --iv-canvas: #eaecf1; --iv-relief: 0 1px 2px rgba(20, 30, 50, 0.10), 0 2px 5px rgba(20, 30, 50, 0.07); /* v2026.5.49 — fond des plages LIBRES (pauses affichées, créneaux du mode heure exacte). HACHURES claires plutôt qu'un voile uni : un aplat légèrement plus clair se voyait à peine, et monter son contraste le rapprochait des tuiles d'intervention. Le rayé reprend le langage des trous de la timeline (45°) — impossible à confondre avec une surface pleine, et lisible de loin. */ --iv-libre: repeating-linear-gradient(45deg, var(--bg-elevated) 0 7px, var(--iv-canvas) 7px 14px); --radius: 8px; --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; --mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace; } [data-theme="dark"] { --bg: #16181d; --bg-elevated: #21242b; --bg-muted: #1c1f25; --bg-hover: #2a2e36; --border: #2e333c; --border-strong: #414754; --text: #e6e8ec; --text-muted: #d0d5de; /* v2026.5.29 : +contraste (était #b8c0cc) — quasi blanc */ --text-faint: #a8b0bc; /* v2026.5.29 : +contraste (était #8b93a0) */ --accent: #5ea8e8; --accent-soft: #223348; --danger: #e87878; --danger-soft: #3b2626; --warn: #d9a753; --warn-soft: #3a2e1a; --ok: #78c59a; --ok-soft: #1f3a2b; --c-livraison: #60a5fa; --c-livraison-soft: #1e3a5f; /* : récupération en teal (dark mode). */ --c-recup: #2dd4bf; --c-recup-soft: #134e4a; --c-remplacement: #fb923c; --c-remplacement-soft: #4a2512; --c-incident: #a78bfa; --c-incident-soft: #2e1065; --c-installation: #60a5fa; --c-installation-soft: #1e3a5f; --c-autre: #9333ea; /* v2026.5.46 — violet fonce lisible sur theme sombre (incident reste lavande claire) */ --c-autre-soft: #2a2e36; /* closed = vert sapin clair, resolved = lime, terminated = vert pur. */ --c-closed: #34d399; --c-closed-soft: #064e3b; --c-resolved: #a3e635; --c-resolved-soft: #1a2e05; --c-terminated: #4ade80; --c-terminated-soft:#14432a; --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); --shadow-hover: 0 2px 10px rgba(0, 0, 0, 0.4); /* v2026.5.49 — sur fond sombre l'ombre porte moins : elle est plus dense et c'est surtout l'écart de clarté (corps --iv-canvas / intervention --bg-elevated #21242b) qui fait le relief. */ --iv-canvas: #191c21; --iv-relief: 0 1px 2px rgba(0, 0, 0, 0.45), 0 2px 6px rgba(0, 0, 0, 0.30); /* v2026.5.49 — en sombre, éclaircir veut dire monter vers la lumière : les hachures partent de --bg-hover, sinon elles se confondaient avec le fond. */ --iv-libre: repeating-linear-gradient(45deg, var(--bg-hover) 0 7px, var(--iv-canvas) 7px 14px); } /* ========================================================================== Base ========================================================================== */ * { box-sizing: border-box; } html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--font); font-size: 12px; line-height: 1.5; /* v2026.5.45 — coupe la scrollbar horizontale qui apparaissait à cause du dock translaté en dehors de la viewport (transform sur position:fixed contribue malgré tout à l'overflow scrollable). v2026.5.46 — `clip` au lieu de `hidden` : coupe le débordement horizontal SANS créer de conteneur de défilement → ne casse plus `position: sticky` (topbar + ligne stats/recherche restent collées). */ overflow-x: clip; } .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; } /* v2026.5.46 — barre de recherche : même « vitre » translucide que les boutons quand une couleur personnalisée est appliquée à la sidebar. */ html.has-topbar-color.view-horizontal .horizontal-sidebar .search-bar { 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 .search-input { color: var(--topbar-text, var(--text)) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar .search-input::placeholder { color: color-mix(in srgb, var(--topbar-text) 55%, transparent) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar .search-ico, html.has-topbar-color.view-horizontal .horizontal-sidebar .search-clear, html.has-topbar-color.view-horizontal .horizontal-sidebar .search-help { color: var(--topbar-text, var(--text)) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar button.btn:hover { background: color-mix(in srgb, var(--topbar-text) 20%, transparent) !important; border-color: color-mix(in srgb, var(--topbar-text) 45%, transparent) !important; } /* icône change-thème en sidebar horizontale — même look translucide que les autres boutons quand la topbar a une couleur custom. Sans cette règle l'icône restait blanche/grise et tranchait sur le fond coloré. */ html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle { color: var(--topbar-text, var(--text)) !important; background: color-mix(in srgb, var(--topbar-text) 10%, transparent) !important; border-color: color-mix(in srgb, var(--topbar-text) 28%, transparent) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle:hover { background: color-mix(in srgb, var(--topbar-text) 22%, transparent) !important; border-color: color-mix(in srgb, var(--topbar-text) 50%, transparent) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar #theme-toggle #theme-icon { filter: none; } /* séparateurs visibles de la sidebar (border-top/bottom du #stats, trait au-dessus du 1er bouton, separator-after-clos) prennent une teinte dérivée du --topbar-text quand l'utilisateur a une couleur custom — sinon ils restaient gris foncé invisibles sur fond coloré. */ html.has-topbar-color.view-horizontal .horizontal-sidebar #stats { border-top-color: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important; border-bottom-color: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important; } html.has-topbar-color.view-horizontal .horizontal-sidebar #absence-btn::before { background: color-mix(in srgb, var(--topbar-text) 30%, transparent) !important; } /* barre verticale VERTE à droite des mini-cards UNIQUEMENT quand le ticket est officiellement CLOS / RÉSOLU (✓✓). Pas pour "Fait" pending, pas pour Suspendu. */ html.view-horizontal .iv-mini-card.status-closed, html.view-horizontal .iv-mini-card.status-resolved { position: relative; } html.view-horizontal .iv-mini-card.status-closed::before, html.view-horizontal .iv-mini-card.status-resolved::before { content: ""; position: absolute; right: 0; top: 4px; bottom: 4px; width: 4px; background: var(--ok, #2e7b4a); border-radius: 2px; z-index: 2; } /* .iv-done-separator retiré — l'info "X faits" est dans la barre stats globale au format "X faits / Y clos". */ .topbar { position: sticky; top: 0; z-index: 10; display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: var(--topbar-bg, var(--bg-elevated)); color: var(--topbar-text, var(--text)); border-bottom: 1px solid var(--border); box-shadow: var(--shadow); gap: 12px; flex-wrap: wrap; } .topbar-left { display: flex; align-items: center; gap: 14px; flex: 1; min-width: 0; } /* v2026.5.39 r2 : on verrouille l'ordre des élements de la topbar gauche en vue classique. Le badge user reste à l'extrême gauche, le titre suit, puis la nav date et enfin la capture-info. Ce verrou évite que des composants restaurés depuis la sidebar (vue horizontale → classique) finissent dans le mauvais ordre. */ /* ordre topbar — initiales | titre | bloc Auj.+horloge | MAJ | refresh-check | sélecteur date du planning CENTRÉ via marges auto. */ html.view-classic .topbar-left #user-badge { order: 1; } html.view-classic .topbar-left #app-title { order: 2; } html.view-classic .topbar-left #today-block { order: 3; } html.view-classic .topbar-left .capture-info { order: 4; } html.view-classic .topbar-left #refresh-check { order: 5; } /* on positionne la nav-date EN ABSOLU au centre de la topbar (et plus en flex avec marges auto). Sans ça, l'apparition du bouton "Arrêter" dans la topbar-right faisait rétrécir topbar-left, et le centrage relatif de la date glissait. Avec position: absolute, la date est ancrée au centre du viewport ; les éléments left/right peuvent grandir/rétrécir librement sans déplacer la date. */ html.view-classic .topbar-left .date-nav { order: 6; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0 !important; z-index: 1; pointer-events: auto; } /* : bascule "compact" déclenchée DYNAMIQUEMENT par le JS dès qu'un chevauchement est détecté (pas un seuil de viewport fixe). En compact : - topbar en colonne, sections empilées - date-nav désancrée du absolute, sur sa propre ligne, centrée - boutons droite en wrap centré */ html.view-classic.topbar-compact .topbar { flex-direction: column; align-items: stretch; gap: 10px; } html.view-classic.topbar-compact .topbar-left { flex-wrap: wrap; justify-content: center; row-gap: 8px; width: 100%; } html.view-classic.topbar-compact .topbar-left .date-nav { position: static !important; transform: none !important; flex: 1 0 100%; justify-content: center; margin: 0 !important; order: 99; } html.view-classic.topbar-compact .topbar-right { flex-wrap: wrap; justify-content: center; row-gap: 6px; width: 100%; } html.view-classic.topbar-compact #app-session { align-self: center; } /* encadré qui regroupe le bouton "Aujourd'hui" + l'horloge actuelle. */ .today-block { display: inline-flex; align-items: center; gap: 10px; padding: 4px 10px 4px 4px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-muted); } .today-block #nav-today { margin: 0; } .today-block #app-clock { margin: 0; } .topbar h1 { margin: 0; font-size: 18px; font-weight: 600; color: var(--topbar-text, var(--text)); white-space: nowrap; } .capture-info { font-size: 12px; color: var(--topbar-text, var(--text-muted)); opacity: 0.85; white-space: nowrap; } .refresh-check { font-size: 12px; color: var(--c-recup); /* vert */ font-weight: 700; opacity: 0; transform: scale(0.5); transition: opacity 0.25s ease, transform 0.25s ease; pointer-events: none; } .refresh-check.visible { opacity: 1; transform: scale(1); } .refresh-check.hidden { display: none; } .topbar-right { display: flex; gap: 8px; flex-shrink: 0; } /* Bannière de session expirée (v4.1.12) — sticky sous la topbar, non bloquante. on utilise --topbar-height (calculé en JS via ResizeObserver) au lieu d'une valeur fixe — la topbar grandit/rétrécit selon la police, la taille de la date, le zoom, etc. */ .session-banner { position: sticky; top: var(--topbar-height, 56px); z-index: 8; display: flex; align-items: center; flex-wrap: wrap; /* : permet au sous-bandeau cookies-prompt de descendre sur sa propre ligne */ gap: 12px; padding: 12px 18px; /* v4.2.5 : rouge plus vif + bord plus épais pour visibilité max */ background: linear-gradient(90deg, #c93030, #d84848); color: #fff; border-top: 2px solid #ff6060; border-bottom: 2px solid #7a1515; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* petite animation d'apparition pour attirer l'œil */ animation: session-banner-in 0.22s ease-out; } /* : sous-bandeau "Multi-onglets EasyVista" — version centrée et propre. Encart blanc semi-translucide qui se détache du rouge de la bannière, contenu centré horizontalement, titre + texte court + bouton, et un lien dismiss discret en dessous. */ .session-banner-cookies-prompt { flex: 1 0 100%; margin-top: 10px; display: flex; justify-content: center; } .session-banner-cookies-inner { width: 100%; max-width: 520px; padding: 14px 18px; background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.25); border-radius: 10px; backdrop-filter: blur(2px); display: flex; flex-direction: column; align-items: center; text-align: center; gap: 8px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18); } .session-banner-cookies-title { font-size: 14px; font-weight: 700; letter-spacing: 0.3px; } .session-banner-cookies-explain { font-size: 12px; font-weight: 400; line-height: 1.45; color: rgba(255, 255, 255, 0.92); max-width: 460px; display: flex; flex-direction: column; gap: 2px; } .session-banner-cookies-explain-sub { font-size: 11px; opacity: 0.85; } .session-banner-cookies-actions { margin-top: 4px; display: flex; flex-direction: column; align-items: center; gap: 6px; } .session-banner-cookies-btn { background: #fff !important; color: #c93030 !important; font-weight: 700 !important; font-size: 13px !important; padding: 9px 28px !important; border-radius: 999px !important; border: none !important; cursor: pointer; transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s ease; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.30); } .session-banner-cookies-btn:hover { transform: translateY(-1px); box-shadow: 0 6px 14px rgba(0, 0, 0, 0.38); background: #f9f9f9 !important; } .session-banner-cookies-btn:disabled { opacity: 0.55; cursor: not-allowed; transform: none; } .session-banner-cookies-dismiss { background: transparent !important; color: rgba(255, 255, 255, 0.88) !important; font-size: 11px !important; border: none !important; padding: 2px 6px !important; cursor: pointer; text-decoration: underline; opacity: 0.85; } .session-banner-cookies-dismiss:hover { opacity: 1; } /* variante du sous-bandeau cookies pour les contextes à fond clair (écran plein "session nécessaire" ET modals d'alerte "Impossible d'ouvrir la fiche") — encart gris léger, texte normal, bouton accent, lien dismiss discret. */ .session-needed .session-banner-cookies-prompt, .modal-card .session-banner-cookies-prompt { margin-top: 18px; flex: none; } .session-needed .session-banner-cookies-inner, .modal-card .session-banner-cookies-inner { background: var(--bg-muted, rgba(0, 0, 0, 0.04)); border: 1px solid var(--border, rgba(0, 0, 0, 0.12)); color: var(--text); backdrop-filter: none; } .session-needed .session-banner-cookies-title, .modal-card .session-banner-cookies-title { color: var(--text); } .session-needed .session-banner-cookies-explain, .modal-card .session-banner-cookies-explain { color: var(--text-muted); } .session-needed .session-banner-cookies-btn, .modal-card .session-banner-cookies-btn { background: var(--accent, #0f4f8b) !important; color: #fff !important; } .session-needed .session-banner-cookies-btn:hover, .modal-card .session-banner-cookies-btn:hover { background: var(--accent-strong, #0a3d6f) !important; } .session-needed .session-banner-cookies-dismiss, .modal-card .session-banner-cookies-dismiss { color: var(--text-faint, #6b7280) !important; } @keyframes session-banner-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } } /* v4.2.5 : variante ORANGE pour "EV inaccessible" (distinct de session expirée) */ .session-banner.ev-banner { background: linear-gradient(90deg, #c77920, #e09a3a); border-top: 2px solid #ffbb60; border-bottom: 2px solid #7a4a15; } .session-banner.ev-banner .btn-primary { color: #8a4a10; } .session-banner.hidden { display: none; } .session-banner-icon { font-size: 20px; flex-shrink: 0; } .session-banner-text { flex: 1; line-height: 1.4; } .session-banner-text strong { font-weight: 600; } .session-banner .btn-primary { background: #fff; color: #9a2020; border: 0; font-weight: 600; } .session-banner .btn-primary:hover { background: #f0f0f0; } .session-banner .btn-sm { padding: 5px 12px; font-size: 12px; /* v4.2.5 : btn-sm non-primary dans la bannière = contour blanc */ background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.5); font-weight: 500; } .session-banner .btn-sm:hover { background: rgba(255, 255, 255, 0.12); } .session-banner .btn-primary.btn-sm { /* reset : le primary override le style du btn-sm */ background: #fff; color: #9a2020; border: 0; font-weight: 600; } .session-banner.ev-banner .btn-primary.btn-sm { color: #8a4a10; } .session-banner .btn-icon { background: transparent; color: #fff; border: 0; font-size: 20px; line-height: 1; padding: 4px 8px; cursor: pointer; } .session-banner .btn-icon:hover { background: rgba(255,255,255,0.15); } /* Barre de progression pendant le rafraichissement — v4.1.12 : texte toujours lisible, que la zone verte l'ait atteint ou non (utilise mix-blend-mode:difference pour inverser la couleur du texte là où la barre verte est dessous). */ .progress-bar { position: sticky; /* v2026.5.46 — collée JUSTE SOUS la ligne stats+recherche (#topline), pas au-dessus : --stack-top (topbar + bandeaux session) + --topline-h (hauteur de #topline, mesurée en JS ; 0 en vue horizontale). */ top: calc(var(--stack-top, 56px) + var(--topline-h, 0px)); 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; } /* v2026.5.46 — petite barre de chargement indéterminée, affichée sous le n° d'intervention de la modal Réservation pendant le fetch EV des infos. */ .res-loading-bar { position: relative; height: 3px; margin: 6px 0 2px; border-radius: 2px; background: rgba(128, 128, 128, 0.18); overflow: hidden; } .res-loading-bar.hidden { display: none; } .res-loading-bar::before { content: ""; position: absolute; top: 0; bottom: 0; left: -40%; width: 40%; border-radius: 2px; background: linear-gradient(90deg, #2ea043, #3fb950); animation: res-loading-slide 1s ease-in-out infinite; } @keyframes res-loading-slide { 0% { left: -40%; } 100% { left: 100%; } } /* 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; } /* v2026.5.47 — message de reconnexion EasyVista (#session-needed / #ev-unreachable) CENTRÉ dans la zone de contenu en vue horizontale (avant : posé en haut, comme un item flex de main#main). main#main est position:relative en horizontal ; on centre dans le contenu (à droite de la sidebar) via left = 50% + --sidebar-w/2. */ html.view-horizontal .session-needed { position: absolute !important; top: 50% !important; left: calc(50% + var(--sidebar-w, 200px) / 2) !important; transform: translate(-50%, -50%) !important; margin: 0 !important; } .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); /* v2026.5.49 — `clip` au lieu de `hidden` : coupe le débordement (coins arrondis) SANS créer de conteneur de défilement, sinon l'en-tête sticky (ci-dessous) se collerait à la carte au lieu de la fenêtre (même piège que body { overflow-x: clip }). */ overflow: clip; 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; } /* v2026.5.49 — VUE CLASSIQUE : l'en-tête de carte (nom du tech + badges) suit le scroll et reste collé sous l'empilement du haut (topbar + bandeaux session + ligne stats/recherche), tant que sa carte est à l'écran. Sinon le nom disparaissait dès qu'on scrollait une carte haute. Même offset que la barre de progression (--stack-top + --topline-h, mesurés en JS). En vue horizontale, l'en-tête fait partie de la ligne : pas de sticky. */ html:not(.view-horizontal) .card-header { position: sticky; /* v2026.5.49 — pendant un rafraîchissement, la barre de progression est sticky au MÊME offset (--stack-top + --topline-h) avec un z-index supérieur : elle recouvrait le nom du tech collé quand on avait scrollé. On ajoute donc --progress-h (hauteur réelle de la barre, mesurée en JS ; 0 hors refresh) pour caler l'en-tête JUSTE SOUS la barre. */ top: calc(var(--stack-top, 56px) + var(--topline-h, 0px) + var(--progress-h, 0px)); /* au-dessus du contenu de la frise qui défile dessous (slot hover z2, midi z3, trou survolé z6) ; reste sous la banderole session (z8) et la barre de progression (z9), qui sont dans un autre contexte d'empilement. */ z-index: 7; } /* v2026.5.49 — VUE CLASSIQUE : la frise horaire (.timeline) reste collée JUSTE SOUS l'en-tête du tech quand on scrolle, tant que la carte est à l'écran — pour garder le repère horaire ET le total d'interventions visibles (le total vit dans l'en-tête .tech-row-stats, déjà collant). Elle se cale sous l'en-tête via --card-header-h (hauteur RÉELLE de l'en-tête de CETTE carte, mesurée en JS car un nom long fait 2-3 lignes). Fond opaque (--bg-muted) : le contenu défile dessous sans transparence. z-index 6 : sous l'en-tête (7), au-dessus du corps qui défile. */ html:not(.view-horizontal) .card-body > .timeline { position: sticky; top: calc(var(--stack-top, 56px) + var(--topline-h, 0px) + var(--progress-h, 0px) + var(--card-header-h, 0px)); z-index: 6; } .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; /* v2026.5.49 — le corps de la carte RECULE (fond en retrait) pour que les interventions posées dessus ressortent. Avant, corps et interventions partageaient le même blanc : tout le contenu se lisait comme un seul aplat, séparé par des filets de 1 px. */ background: var(--iv-canvas); } /* v2026.5.49 — vue classique, option « Afficher les pauses » ACTIVE : les plages libres REMPLISSENT le vide de la carte au lieu de rester des bandes de 44 px suivies d'un grand aplat gris. Les cartes d'une même ligne ont la hauteur de la plus chargée : tout ce qui dépasse le contenu est du temps disponible, il se lit donc comme tel. Le corps passe en colonne flex et chaque pause reçoit le MÊME flex-grow (posé par _buildGapPlaceholder ; v2026.5.49) : toutes les plages ont la même taille. Les autres éléments gardent leur taille (flex-shrink: 0) — sans cela, un contenu plus haut que la carte comprimerait les interventions. Option inactive ou vue horizontale : rien ne change (les pauses y sont masquées ou gérées par les règles .gap-placeholder-mini). */ html.show-iv-gaps:not(.view-horizontal) .card-body { display: flex; flex-direction: column; } html.show-iv-gaps:not(.view-horizontal) .card-body > *:not(.gap-placeholder) { flex-shrink: 0; } /* v2026.5.49 — cartes d'absence : leur couleur de catégorie est peinte sur la CARTE (dégradé vers --iv-canvas) ; un fond opaque sur le corps la masquerait. Le corps se contente donc de laisser passer, et les interventions éventuelles gardent leur relief (tuile claire sur le dégradé, qui finit sur la même teinte de retrait). */ .card.absence-cat-maladie .card-body, .card.absence-cat-conge .card-body, .card.absence-cat-recurring .card-body { background: transparent; } .card-empty { padding: 14px; color: var(--text-faint); font-size: 13px; font-style: italic; text-align: center; } .card.is-pompier { border-left: 3px solid var(--danger); } .card.is-absent { opacity: 0.85; } .card.is-absent .card-header { background: var(--bg); } /* ========================================================================== Frise de temps ========================================================================== */ .timeline { padding: 12px 14px 6px 14px; background: var(--bg-muted); border-bottom: 1px solid var(--border); position: relative; } .timeline-pompier { background: var(--danger-soft); } .timeline-bar { position: relative; height: 20px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 3px; overflow: hidden; } .timeline-hole { position: absolute; top: 0; bottom: 0; background: repeating-linear-gradient( 45deg, transparent 0 4px, rgba(0, 0, 0, 0.035) 4px 8px ); cursor: help; transition: background 0.1s; } [data-theme="dark"] .timeline-hole { background: repeating-linear-gradient( 45deg, transparent 0 4px, rgba(255, 255, 255, 0.04) 4px 8px ); } .timeline-hole:hover { background: var(--accent-soft); outline: 3px solid var(--accent, #0f4f8b); outline-offset: -3px; z-index: 6; } .timeline-slot { position: absolute; top: 0; bottom: 0; cursor: help; transition: filter 0.1s; border-right: 1px solid var(--bg-elevated); } .timeline-slot.color-livraison { background: var(--c-livraison); } .timeline-slot.color-installation { background: var(--c-installation); } .timeline-slot.color-recup { background: var(--c-recup); } .timeline-slot.color-remplacement { background: var(--c-remplacement); } .timeline-slot.color-incident { background: var(--c-incident); } .timeline-slot.color-rollout { background: var(--c-rollout); } .timeline-slot.color-reservation { background: var(--c-reservation); } .timeline-slot.color-autre { background: var(--c-autre); } /* : statuts clos/résolu/terminé sur la timeline → on GARDE la couleur de catégorie en fond et on ajoute un bandeau vert en BAS de la slot pour signaler que c'est terminé. La couleur de la catégorie reste lisible. */ .timeline-slot.status-closed { box-shadow: inset 0 -4px 0 var(--c-closed); } .timeline-slot.status-resolved { box-shadow: inset 0 -4px 0 var(--c-resolved); } .timeline-slot.kind-absence { /* v5.0.15 : uni gris-noir au lieu de rayé, plus lisible */ background: #2a2f36; border-right: 1px solid var(--bg-elevated); } .timeline-slot:hover, .timeline-slot.highlight { filter: brightness(1.12); outline: 2px solid var(--text); outline-offset: -2px; z-index: 2; } /* v2026.5.39 r3 : séparateur "midi" — vraie coupure visuelle. La timeline-bar a overflow:hidden donc les chevrons qui dépassaient en haut/bas étaient coupés. On reste DANS la bar mais on rend le trait massif et fortement contrasté + un effet "encoche" via box-shadow latéral (pour bien détacher du fond), et on superpose une bande à stripes diagonales sur 6px de large pour signaler la pause de midi. Z-index élevé pour passer au-dessus des segments. */ .timeline-noon { position: absolute; top: 0; bottom: 0; width: 6px; z-index: 3; pointer-events: none; transform: translateX(-3px); /* centrer la bande sur 12h */ background: /* trait central massif : */ linear-gradient(to right, transparent 0%, transparent 30%, var(--text) 30%, var(--text) 70%, transparent 70%, transparent 100%), /* fond stripes diagonales pour signaler "césure" : */ repeating-linear-gradient( 45deg, var(--bg-muted) 0 3px, var(--text-muted) 3px 4px ); opacity: 0.95; } /* En vue horizontale, timeline-bar est plus haute (22px au lieu de 20px), on garde le même trait mais légèrement plus large pour la visibilité. */ html.view-horizontal .timeline-noon { width: 7px; transform: translateX(-3.5px); } .timeline-scale { position: relative; height: 14px; margin-top: 4px; } .timeline-tick { position: absolute; transform: translateX(-50%); font-size: 12px; color: var(--text-faint); font-family: var(--mono); } /* Stats par carte */ .card-stats { display: flex; align-items: baseline; justify-content: space-between; padding: 10px 14px; background: var(--bg-muted); border-bottom: 1px solid var(--border); } .stat-total { display: flex; align-items: baseline; gap: 6px; } .stat-total-num { font-size: 22px; font-weight: 700; color: var(--text); line-height: 1; } .stat-total-lbl { font-size: 12px; color: var(--text-muted); } .stat-split { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-faint); } .stat-split-item b { color: var(--text-muted); font-weight: 600; } .stat-split-sep { opacity: 0.4; } /* Notes de statut */ .card-status-note { padding: 8px 14px; font-size: 12px; font-weight: 500; text-align: center; } .card-status-note.pompier { background: var(--danger-soft); color: var(--danger); border-bottom: 1px solid var(--border); } .card-status-note.absent { background: var(--bg); color: var(--text-muted); border-bottom: 1px solid var(--border); font-style: italic; } .card-empty.subtle { font-size: 12px; opacity: 0.7; } /* v2026.5.29 : highlight visible sur les rows .intervention-v2 quand on survole le segment timeline correspondant (ou que l'user survole la row) */ .intervention-v2.highlight { background: var(--accent-soft); outline: 2px solid var(--accent); outline-offset: -2px; box-shadow: 0 0 0 3px var(--accent-soft); z-index: 2; position: relative; } /* ========================================================================== v2026.5.41 : Conflit absence/réservation × intervention. Quand une intervention est planifiée pendant qu'un tech est marqué "absent" (toute la journée ou demi-journée) ou qu'il a une réservation sur le même créneau, on signale visuellement le conflit en peignant la carte de l'intervention en rouge plein. S'applique à la row classique (.intervention-v2) ET à la mini-card (.iv-mini-card) en vue horizontale. ========================================================================== */ /* conflit ABSENCE/réservation × intervention ET conflit horaire (overlap) partagent exactement le même rendu (rouge plein) → sélecteurs regroupés (avant : deux blocs identiques). Les couleurs (#b03030/#7a1f1f/#c44040) sont des rouges d'alarme volontairement constants (ne pas mapper sur --danger, qui varie en thème sombre). S'applique à la row classique (.intervention-v2) et à la mini-card (.iv-mini-card). */ .intervention-v2.intervention-conflict-absence, .intervention-v2.intervention-conflict-overlap, .iv-mini-card.intervention-conflict-absence, .iv-mini-card.intervention-conflict-overlap { background: #b03030 !important; color: #ffffff !important; border-color: #7a1f1f !important; } /* Renforce la barre gauche pour rester cohérent avec le rouge plein. */ .intervention-v2.intervention-conflict-absence::before, .intervention-v2.intervention-conflict-overlap::before { background: #7a1f1f !important; } .intervention-v2.intervention-conflict-absence .intervention-dot, .intervention-v2.intervention-conflict-absence .iv-status-check, .intervention-v2.intervention-conflict-absence a, .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-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, .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; } /* 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, .iv-mini-card.intervention-conflict-overlap .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, .intervention-v2.intervention-conflict-overlap:hover, .iv-mini-card.intervention-conflict-overlap:hover { background: #c44040 !important; } /* v2026.5.47 — intervention à DURÉE NULLE (début == fin, ex. 10:00→10:00 : erreur de saisie EV). Même rouge plein que les conflits ci-dessus, pour la repérer d'un coup d'oeil dans toutes les vues. */ .iv-mini-card.iv-zero-duration { background: #b03030 !important; color: #ffffff !important; border-color: #7a1f1f !important; } .iv-mini-card.iv-zero-duration .iv-mini-card-bar, .iv-mini-card.iv-zero-duration .iv-mini-time-vertical, .iv-mini-card.iv-zero-duration .iv-mini-card-text { color: #ffffff !important; background: transparent !important; } /* Vue exacte : la bulle est très fine (min-width) → le bord gauche (poignée) doit être rouge lui aussi, sinon on ne verrait que sa couleur de catégorie. */ html.view-horizontal .iv-exact-card.iv-zero-duration .iv-exact-handle { background-color: #7a1f1f !important; background-image: none !important; } /* v2026.5.47 — durée nulle PLUS VISIBLE en vue exacte : un peu plus large que les interventions courtes (11 px vs 6 px) + hachures rouges pour attirer l'oeil sur l'erreur. */ html.view-horizontal .iv-exact-card.iv-zero-duration { min-width: 11px !important; background-image: repeating-linear-gradient( 45deg, #b03030 0 4px, #7a1f1f 4px 8px) !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; cursor: default; transition: background 0.08s; position: relative; /* v2026.5.49 — tuile posée sur le corps de carte en retrait : surface claire, contour et ombre. Remplace le simple filet border-top, qui ne distinguait rien (même fond de part et d'autre du trait). */ background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 6px; box-shadow: var(--iv-relief); margin: 6px 8px; } .intervention-v2:hover { background: var(--bg-hover); } /* Pastille colorée (barre verticale) */ .intervention-v2 .intervention-dot { grid-area: dot; width: 4px; height: 100%; border-radius: 2px; align-self: stretch; } .intervention-v2.color-livraison .intervention-dot { background: var(--c-livraison); } .intervention-v2.color-installation .intervention-dot { background: var(--c-installation); } .intervention-v2.color-recup .intervention-dot { background: var(--c-recup); } .intervention-v2.color-remplacement .intervention-dot { background: var(--c-remplacement); } .intervention-v2.color-incident .intervention-dot { background: var(--c-incident); } .intervention-v2.color-rollout .intervention-dot { background: var(--c-rollout); } .intervention-v2.color-reservation .intervention-dot { background: var(--c-reservation); } .intervention-v2.color-autre .intervention-dot { background: var(--c-autre); } .intervention-v2.clickable { cursor: pointer; } .intervention-v2.clickable:active { transform: translateY(1px); } /* : on GARDE la couleur de catégorie visible (.intervention-dot reste en couleur de catégorie + fond neutre). Le statut clos/résolu/terminé est signalé par un bandeau vert sur la DROITE de la carte (inset shadow) + le ✓✓ ou ✓ existant. v2026.5.49 — ce bandeau se COMPOSE avec le relief de la tuile : une box-shadow unique aurait effacé l'ombre. */ .intervention-v2.status-closed { box-shadow: inset -4px 0 0 var(--c-closed), var(--iv-relief); } .intervention-v2.status-closed:hover { filter: brightness(0.96); } .intervention-v2.status-resolved { box-shadow: inset -4px 0 0 var(--c-resolved), var(--iv-relief); } .intervention-v2.status-resolved:hover { filter: brightness(0.96); } .intervention-v2.status-terminated { box-shadow: inset -4px 0 0 var(--c-terminated, #16a34a), var(--iv-relief); } .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.46 — n° DS de la réservation, sur sa propre ligne sous « Réservé ». */ .iv-reservation-ds { font-size: 13px; font-weight: 700; color: var(--c-reservation); letter-spacing: 0.02em; } /* v2026.5.46 — bouton principal "Ajouter" + menu déroulant (Réservation / Absence / Pompier). */ .add-menu-wrap { position: relative; /* v2026.5.46 — flex pour que le bouton s'étire à la même hauteur que les autres boutons de la topbar (Douchette, etc.). */ display: flex; } .add-menu-wrap > #add-menu-btn { flex: 1; } .add-menu { position: absolute; top: calc(100% + 6px); left: 0; z-index: 50; min-width: 190px; padding: 6px; border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 10px; background: var(--bg, #1b1f24); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); display: flex; flex-direction: column; gap: 2px; } .add-menu.hidden { display: none; } .add-menu-item { display: flex; align-items: center; gap: 9px; width: 100%; padding: 9px 11px; border: none; border-radius: 7px; background: transparent; color: var(--text, #e6e6e6); font-size: 14px; text-align: left; cursor: pointer; } .add-menu-item:hover { background: rgba(128, 128, 128, 0.16); } .add-menu-emoji { font-size: 16px; line-height: 1; } /* v2026.5.46 — menu contextuel (clic droit) sur le nom d'un technicien. Mêmes codes visuels que .add-menu, mais positionné en fixed au curseur. */ .tech-name-ctx { cursor: context-menu; } .tech-context-menu { position: fixed; /* v2026.5.46 — au-dessus des tooltips (1700) : la petite popup des trous ne doit jamais recouvrir le menu clic droit. */ z-index: 1800; min-width: 190px; padding: 6px; border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 10px; background: var(--bg, #1b1f24); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); display: flex; flex-direction: column; gap: 2px; } .tech-context-menu-header { padding: 6px 11px 8px; font-size: 12.5px; font-weight: 700; color: var(--text-faint, #9aa0aa); border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.25)); margin-bottom: 4px; } .tech-context-menu-item { display: flex; align-items: center; gap: 9px; width: 100%; padding: 9px 11px; border: none; border-radius: 7px; background: transparent; color: var(--text, #e6e6e6); font-size: 14px; text-align: left; cursor: pointer; } .tech-context-menu-item:hover { background: rgba(128, 128, 128, 0.16); } /* pompier du mois dans la barre de stats */ .global-stat-pompier-mois { color: var(--c-pompier, #e0533d); font-weight: 600; } /* v2026.5.46 — liste des pompiers actuels/à venir dans la modal pompier. */ .pompier-list { display: flex; flex-direction: column; gap: 4px; margin: 4px 0 2px; max-height: 180px; overflow-y: auto; } .pompier-list-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%; padding: 8px 10px; border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 7px; background: transparent; color: var(--text, #e6e6e6); font-size: 13px; cursor: pointer; text-align: left; } .pompier-list-item:hover { background: rgba(128, 128, 128, 0.14); border-color: var(--c-pompier, #e0533d); } .pompier-list-name { font-weight: 600; } .pompier-list-dates { color: var(--text-muted, #9aa0a6); font-size: 12px; white-space: nowrap; } .pompier-list-now { display: inline-block; margin-left: 6px; padding: 1px 7px; border-radius: 8px; background: var(--c-pompier, #e0533d); color: #fff; font-size: 10.5px; font-weight: 700; } .pompier-list-empty { margin: 2px 0 2px; padding: 8px 10px; border: 1px dashed var(--border, rgba(128, 128, 128, 0.35)); border-radius: 7px; color: var(--text-muted, #9aa0a6); font-size: 12.5px; } /* v2026.5.46 — en-tête repliable de la liste pompier. */ .pompier-list-header { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 4px; background: transparent; border: none; color: var(--text, #e6e6e6); font-size: 13px; font-weight: 600; cursor: pointer; text-align: left; } .pompier-list-header:hover { color: var(--c-pompier, #e0533d); } .pompier-list-caret { display: inline-block; width: 12px; color: var(--c-pompier, #e0533d); } .pompier-list.hidden { display: none; } /* v2026.5.46 — badge "Mes actions" (mode coordinateur) dans la barre de stats. */ .my-actions-badge { display: inline-flex; align-items: center; gap: 5px; padding: 2px 9px; border: 1px solid var(--c-pompier, #e0533d); border-radius: 10px; background: transparent; color: var(--text, #e6e6e6); font: inherit; cursor: pointer; /* v2026.5.46 — positionné à droite via la 3e colonne de #topline (#topline-coord, justify-self:end) ; plus besoin de margin-left:auto. */ } .my-actions-badge:hover { background: rgba(128, 128, 128, 0.14); } /* v2026.5.46 — en vue horizontale, #topline-coord est relocalisé dans la sidebar : le badge prend toute la largeur et se centre comme les autres items. */ html.view-horizontal .horizontal-sidebar #topline-coord { width: 100%; display: flex; justify-content: center; margin: 4px 0; } html.view-horizontal .horizontal-sidebar #topline-coord .my-actions-badge { width: 100%; justify-content: center; } /* page "Mes actions" : table large + scroll. */ .modal-card.my-actions-card { width: min(1000px, 95vw); } .my-actions-table-wrap { max-height: 60vh; overflow: auto; border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 8px; } .my-actions-table { width: 100%; border-collapse: collapse; font-size: 13px; } .my-actions-table th, .my-actions-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.2)); white-space: nowrap; } .my-actions-table thead th { position: sticky; top: 0; background: var(--bg, #16181d); font-weight: 700; z-index: 1; } .my-actions-empty { text-align: center; color: var(--text-muted, #9aa0a6); font-style: italic; padding: 22px 10px; white-space: normal; } .my-actions-num { background: none; border: none; padding: 0; color: var(--accent, #4aa3ff); font: inherit; font-weight: 700; cursor: pointer; text-decoration: underline; } .my-actions-num:hover { opacity: 0.8; } /* v2026.5.46 — page dédiée "Mes actions" (onglet viewer.html?view=myactions). Pleine largeur (le tableau prend toute la page). */ .myactions-page { width: 100%; box-sizing: border-box; padding: 20px 24px; } .myactions-page-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; } .myactions-page-title { margin: 0; font-size: 22px; font-weight: 700; } .myactions-diag { margin-bottom: 12px; padding: 6px 10px; border-radius: 6px; background: rgba(128, 128, 128, 0.10); color: var(--text-muted, #9aa0a6); font-size: 12px; font-family: monospace; } /* le wrap de table prend toute la hauteur dispo dans l'onglet plein écran. */ .myactions-page .my-actions-table-wrap { max-height: calc(100vh - 130px); } /* v2026.5.38 : anciens styles .intervention (layout v1) supprimés — le HTML ne génère plus que .intervention-v2 depuis longtemps. */ /* ========================================================================== Tooltip ========================================================================== */ /* v2026.5.45 : on aligne les dimensions du tooltip live sur celles du pinned-popup (padding-top 28px qui réserve la place de la dragbar, border 2px). Ainsi, passer de live → épinglé → désépinglé ne change RIEN à la taille de la boîte — seule la couleur de la bordure varie pour signaler l'état. */ .tooltip { position: fixed !important; isolation: isolate; contain: layout; z-index: 100; max-width: 620px; max-height: calc(100vh - 40px); overflow-y: auto; /* : padding-top 28px d'office pour réserver la place de la dragbar. */ padding: 28px 14px 12px 14px; background: var(--bg-elevated); color: var(--text); /* : border 2px d'office (transparent en mode live), pour ne pas redimensionner la boîte au passage en épinglé. */ border: 2px solid transparent; border-radius: 8px; box-shadow: var(--shadow-hover); font-size: 13px; line-height: 1.5; pointer-events: none; opacity: 0; transition: opacity 0.1s; user-select: text; -webkit-user-select: text; } .tooltip.visible:not(.pinned-popup):not(.soft-unpinned) { /* : en mode live (hover), bordure discrète mais bien 2px de large. */ border-color: var(--border-strong); } .tooltip.visible { opacity: 1; /* v4.1.10 : permet à la souris d'entrer dans la bulle pour la garder visible (persistance au hover) et, en mode pinned, pour sélectionner. */ pointer-events: auto; /* v4.2 : curseur texte par défaut (pour signaler que c'est sélectionnable) */ cursor: text; } .tooltip.pinned { /* Bulle épinglée : bordure verte pour indiquer le mode */ border-color: var(--c-accent, #3fb950); box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.15), var(--shadow-hover); } /* v4.1.13/14 : barre d'actions en haut à droite de la bulle (recharger cette iv + épingler) */ .tooltip-actions { position: absolute; top: 6px; right: 6px; display: flex; gap: 2px; z-index: 5; } .tooltip-actionbtn { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 4px; font-size: 13px; opacity: 0.55; transition: opacity 0.15s, background 0.15s, transform 0.15s; user-select: none; color: var(--text-muted); } .tooltip-actionbtn svg { width: 14px; height: 14px; } .tooltip-actionbtn:hover { opacity: 1; background: rgba(255, 255, 255, 0.08); color: var(--text); } .tooltip-actionbtn.spinning svg { animation: refresh-spin 0.8s linear infinite; transform-origin: 50% 50%; } /* L'ancien .tooltip-pinbtn garde ses variantes */ .tooltip-pinbtn { filter: grayscale(100%); } .tooltip-pinbtn:hover { filter: grayscale(0%); } .tooltip.pinned .tooltip-pinbtn { opacity: 1; filter: grayscale(0%); transform: rotate(-30deg); background: rgba(63, 185, 80, 0.15); } /* v4.1.15 : référence dans la bulle avec bouton copier inline */ .tt-ref-cell { display: inline-flex; align-items: center; gap: 8px; } .tt-ref-val { font-family: var(--mono, monospace); } /* v2026.5.40 : 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); } /* (Message d'absence récurrente : style consolidé plus bas — .tech-absence-recurring cyan. L'ancien bloc v4.1.20 ne fournissait plus que les bordures, repliées.) */ /* 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. */ /* v2026.5.46 — adresse de la fiche ≠ adresse de l'action : lieu en rouge (classique : bloc lieu ; horizontale : lignes ville/adresse). Le title au survol explique la divergence. */ .iv-lieu-block.iv-lieu-mismatch .iv-lieu-ville, .iv-lieu-block.iv-lieu-mismatch .iv-lieu-adresse { color: #dc3030; } .iv-mini-line.iv-lieu-mismatch-txt { color: #dc3030; font-weight: 700; } .iv-contact-line.iv-contact-anomalie { color: #dc3030; } .iv-contact-line.iv-contact-anomalie .iv-contact, .iv-contact-line.iv-contact-anomalie .iv-phone { color: #dc3030; } /* v4.2 : badge utilisateur EasyVista connecté (en haut à droite de la topbar) */ .current-user { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 12px; font-weight: 500; color: var(--text-muted, #666); background: rgba(128, 128, 128, 0.08); border: 1px solid var(--border, rgba(128, 128, 128, 0.2)); border-radius: 999px; margin-right: 8px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .current-user::before { content: "👤"; font-size: 11px; opacity: 0.7; } .current-user.hidden { display: none; } /* ───────────────────────────────────────────────────────────────────────── v4.2.3 : pastille utilisateur (initiales) dans la topbar gauche ───────────────────────────────────────────────────────────────────────── */ .user-badge { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; margin-right: 10px; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; color: #fff; background: var(--user-badge-color, #5b6372); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 50%; cursor: pointer; transition: transform 0.1s, box-shadow 0.12s; flex-shrink: 0; user-select: none; } .user-badge:hover { transform: scale(1.06); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .user-badge:active { transform: scale(0.97); } .user-badge.hidden { display: none; } .user-badge.open { /* Indication visuelle quand la popup nom est ouverte */ box-shadow: 0 0 0 2px var(--user-badge-color, #5b6372) inset, 0 0 0 2px rgba(255, 255, 255, 0.15); } /* Popup du nom complet, affichée juste sous la pastille au clic */ .user-name-popup { position: fixed; z-index: 10050; padding: 8px 14px; background: var(--bg, #ffffff); color: var(--text, #111); border: 1px solid var(--border, rgba(128, 128, 128, 0.25)); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18), 0 2px 6px rgba(0, 0, 0, 0.12); font-size: 13px; font-weight: 500; white-space: nowrap; animation: user-popup-in 0.12s ease-out; } .user-name-popup.hidden { display: none; } @keyframes user-popup-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } } /* ───────────────────────────────────────────────────────────────────────── v4.2.6 : boutons d'action topbar (Absence, Douchette) ───────────────────────────────────────────────────────────────────────── */ .btn-action { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; font-size: 13px; font-weight: 500; background: transparent; color: var(--text, #e0e0e0); border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 6px; cursor: pointer; transition: background 0.12s, border-color 0.12s; } .btn-action:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.12)); border-color: var(--border-strong, rgba(128, 128, 128, 0.5)); } .btn-action:active { transform: translateY(1px); } .btn-action-icon { width: 16px; height: 16px; flex-shrink: 0; } .btn-action-emoji { font-size: 12px; line-height: 1; } .btn-action-label { white-space: nowrap; } /* ───────────────────────────────────────────────────────────────────────── v4.2.6 : modals Absence et Douchette ───────────────────────────────────────────────────────────────────────── */ .modal-card.modal-wide { width: min(520px, 92vw); } .modal-form-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; } .modal-form-row { display: flex; gap: 8px; align-items: center; } .modal-form-row > * { flex: 1; } .modal-form-label { font-size: 12px; font-weight: 500; color: var(--text-muted, #888); text-transform: uppercase; letter-spacing: 0.3px; } .modal-form-input, .modal-form-select { padding: 8px 10px; font-size: 13px; background: var(--bg, #fff); color: var(--text, #111); border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 6px; font-family: inherit; } .modal-form-input:focus, .modal-form-select:focus { outline: none; border-color: var(--c-accent, #3fb950); box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.15); } /* Liste checkboxes techniciens */ .modal-tech-list { display: flex; flex-direction: column; gap: 4px; /* v4.2.8 : plus de max-height → tous les techs (max 8 + "Tout") visibles d'un coup sans avoir à scroller dans la liste. */ padding: 6px; background: var(--bg-muted, rgba(128, 128, 128, 0.06)); border: 1px solid var(--border, rgba(128, 128, 128, 0.2)); border-radius: 6px; } .modal-tech-item { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background 0.1s; } .modal-tech-item:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.12)); } .modal-tech-item input[type="checkbox"] { width: 15px; height: 15px; cursor: pointer; accent-color: var(--c-accent, #3fb950); } .modal-tech-item.tech-selectall { font-weight: 600; border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.2)); padding-bottom: 8px; margin-bottom: 2px; } .modal-tech-item.tech-selectall:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.12)); } /* Boutons Appliquer/Envoyer/Annuler côte à côte */ .modal-actions.horizontal { flex-direction: row; gap: 8px; } .modal-actions.horizontal .btn { flex: 1; } /* ───────────────────────────────────────────────────────────────────────── v4.2.9 : blocage du scroll arrière quand une modal est ouverte. La classe body.modal-open est ajoutée/retirée automatiquement par initModalScrollLock() dans viewer.js dès qu'un .modal-overlay existe. ───────────────────────────────────────────────────────────────────────── */ body.modal-open { overflow: hidden; } /* v4.2.9 : pied de page discret en bas à droite — affiche auteur + date + version v2026.5.29 : agrandi + plus contrasté */ .app-footer { position: fixed; right: 10px; bottom: 6px; font-size: 13px; font-weight: 500; color: var(--text-muted); opacity: 0.85; pointer-events: none; user-select: none; font-variant-numeric: tabular-nums; letter-spacing: 0.3px; z-index: 1; padding: 3px 8px; background: var(--bg-muted); border: 1px solid var(--border); border-radius: 6px; } .app-footer:hover { opacity: 1; } /* ───────────────────────────────────────────────────────────────────────── v4.3.0 : conflit d'horaire entre 2 interventions d'un même tech. Les heures s'affichent en rouge + icône à côté. ───────────────────────────────────────────────────────────────────────── */ .iv-time-vertical.iv-time-overlap .iv-time-start, .iv-time-vertical.iv-time-overlap .iv-time-end, .iv-time-vertical.iv-time-overlap .iv-time-arrow { color: var(--danger, #b03030) !important; font-weight: 700; } .iv-time-overlap-warn { color: var(--danger, #b03030); font-size: 12px; font-weight: 700; line-height: 1; margin-top: 2px; cursor: help; text-align: center; } /* ───────────────────────────────────────────────────────────────────────── v4.3.0 : popups épinglés détachés Ancrés au contenu (position:absolute coord document) → scrollent avec la page. Persistent jusqu'à fermeture explicite. ───────────────────────────────────────────────────────────────────────── */ /* v2026.5.45 : pinned-popup hérite la même taille que le tooltip live. Seule la couleur du border change. Pas d'animation scale (saut visuel), juste un fade opacity court. */ .tooltip.pinned-popup { position: absolute !important; z-index: 5 !important; opacity: 1 !important; pointer-events: auto !important; border-color: var(--accent, #0f4f8b) !important; box-shadow: 0 8px 24px rgba(0,0,0,0.18); /* : resize natif depuis le coin bas-droite (drag pour redimensionner). */ resize: both; overflow: auto; min-width: 280px; min-height: 120px; } /* : animation d'unpin réduite à un fade opacity (pas de scale). */ .tooltip.pinned-popup.unpinning, .tooltip.soft-unpinned.unpinning { animation: pinned-popup-out 0.18s ease-in forwards !important; } @keyframes pinned-popup-out { from { opacity: 1; } to { opacity: 0; } } /* v2026.5.45 : soft-unpinned reprend exactement les mêmes dimensions que les autres états — seule la couleur de bordure change (gris au lieu d'accent). */ .tooltip.soft-unpinned { position: absolute !important; z-index: 5 !important; opacity: 1 !important; pointer-events: auto !important; border-color: var(--border-strong) !important; box-shadow: var(--shadow-hover) !important; animation: none !important; } /* v4.3.3 : Barre de drag en haut de la popup épinglée, permet de la déplacer (le contenu lui-même garde la sélection de texte possible). */ .pinned-popup-dragbar { position: absolute; top: 0; left: 0; right: 0; height: 22px; display: flex; align-items: center; justify-content: center; background: linear-gradient( to bottom, var(--bg-muted, rgba(128,128,128,0.08)) 0%, transparent 100% ); border-bottom: 1px solid var(--border, rgba(128,128,128,0.15)); border-radius: 6px 6px 0 0; cursor: grab; user-select: none; -webkit-user-select: none; } .pinned-popup-dragbar:active, .pinned-popup.dragging .pinned-popup-dragbar { cursor: grabbing; } /* Petite grippe visuelle au milieu pour signaler que c'est déplaçable */ .pinned-popup-dragbar::before { content: ""; width: 32px; height: 3px; border-radius: 3px; background: var(--border-strong, rgba(128,128,128,0.35)); } /* Pendant le drag, on fige l'animation pour éviter les tremblements */ .pinned-popup.dragging { animation: none !important; transition: none !important; cursor: grabbing !important; box-shadow: 0 12px 32px rgba(0,0,0,0.28); } /* Bouton × de fermeture du popup épinglé */ .pinned-popup-close { position: absolute; top: 3px; right: 6px; width: 22px; height: 22px; padding: 0; line-height: 1; font-size: 18px; font-weight: 400; color: var(--text-muted, #888); background: transparent; border: none; border-radius: 4px; cursor: pointer; transition: background 0.1s, color 0.1s; z-index: 2; /* au-dessus de la dragbar */ } .pinned-popup-close:hover { background: var(--danger-soft, #fbe6e6); color: var(--danger, #b03030); } /* ───────────────────────────────────────────────────────────────────────── v5.0.0 : horloge au milieu de la topbar (HH:MM, pas de secondes) ───────────────────────────────────────────────────────────────────────── */ /* app-clock +15% (12 → 14px). */ .app-clock { display: inline-flex; flex-direction: row; align-items: center; gap: 5px; line-height: 1; color: var(--topbar-text, var(--text-faint)); user-select: none; white-space: nowrap; font-size: 14px; font-weight: 500; opacity: 0.8; margin-left: 2px; } .app-clock-date { font-size: 14px; font-weight: 500; color: var(--topbar-text, var(--text-muted)); letter-spacing: 0.15px; font-variant-numeric: tabular-nums; } .app-clock-date::after { content: "•"; margin-left: 5px; color: var(--topbar-text, var(--text-faint)); font-size: 14px; } .app-clock-time { font-size: 14px; font-weight: 600; color: var(--topbar-text, var(--text-muted)); font-variant-numeric: tabular-nums; letter-spacing: 0.3px; } /* (.topbar { position: sticky } était re-déclaré ici inutilement — déjà dans la règle .topbar de base ; retiré.) */ /* 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; } /* v2026.5.49 — chaque type d'email (déplacement / annulation) est une SECTION visuellement délimitée : encadré léger + liseré d'accent à gauche, pour qu'on distingue d'un coup d'oeil les deux zones. */ .admin-email-section { border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: 8px; padding: 12px 14px 14px; margin: 18px 0 0; } .admin-email-section .admin-email-section-titre { margin-top: 0; font-size: 17px; } /* v2026.5.49 — aperçu en direct des adresses en COPIE (CC) sous le champ : une puce par adresse retenue, pour VOIR ce qui sera ajouté ; les saisies invalides sont barrées en rouge. */ .cc-apercu { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; font-size: 12px; } .cc-apercu-intro { color: var(--text-muted); } .cc-apercu-vide { color: var(--text-faint); font-style: italic; } .cc-apercu-chip { display: inline-block; padding: 2px 8px; border-radius: 10px; background: var(--accent-soft); color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); font-weight: 600; } .cc-apercu-chip-invalide { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); text-decoration: line-through; } .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; } /* v2026.5.46 — bouton "Modifier" dans le tooltip réservation (neutre). */ .tooltip-edit-btn { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; background: transparent; border: 1px solid var(--border, rgba(128, 128, 128, 0.4)); color: var(--text, #e0e0e0); border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; margin-top: 4px; } .tooltip-edit-btn:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.14)); border-color: var(--border-strong, rgba(128, 128, 128, 0.6)); } /* Bouton danger dans les modals */ .btn-danger, .modal-btn-danger { background: var(--danger, #b03030); color: #fff; border: 1px solid var(--danger, #b03030); } .btn-danger:hover, .modal-btn-danger:hover { background: #8e2020; } /* v5.0.1 : ligne d'équipe exclue (pas cochée) - apparaît grisée */ .admin-team-table tr.admin-row-excluded { opacity: 0.45; } .admin-team-table tr.admin-row-excluded input[type="text"] { background: var(--bg); } /* v5.0.1 : bouton supprimer sur la carte "Absent toute la journée" */ .absence-delete-wrap { margin-top: 8px; text-align: center; } .absence-delete-wrap .tooltip-delete-btn { font-size: 11px; padding: 4px 8px; } /* v5.0.4 : boutons preset matin / après-midi / journée dans modal absence */ .modal-preset-row { gap: 8px; flex-wrap: wrap; } .modal-preset-btn { flex: 1; min-width: 100px; padding: 8px 10px; font-size: 13px; cursor: pointer; } /* ========================================================================== v5.0.9 : Compteur de session EasyVista (topbar) ========================================================================== */ /* v2026.5.45 (issue #7) : compteur session repositionné SOUS la pastille des initiales (top-gauche), à environ 8px sous la topbar. Avant : centré au milieu de la topbar (`top: 50%; left: calc(50% + 60px)`), peu intuitif. */ .app-session { position: fixed; top: calc(var(--topbar-height, 56px) + 8px); left: 16px; display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 14px; font-size: 13px; font-weight: 500; font-variant-numeric: tabular-nums; z-index: 9; background: rgba(0, 0, 0, 0.05); transition: background 0.3s, color 0.3s; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); } .app-session.hidden { display: none; } .app-session .session-icon { font-size: 12px; } .app-session .session-time { font-weight: 600; } .app-session .session-extend-btn { margin-left: 4px; padding: 3px 8px; font-size: 11px; border-radius: 10px; border: 1px solid currentColor; background: transparent; color: inherit; cursor: pointer; font-weight: 500; transition: background 0.2s; } .app-session .session-extend-btn:hover { background: rgba(255, 255, 255, 0.2); } .app-session .session-extend-btn:disabled { opacity: 0.6; cursor: default; } /* État warning (2-5 min) : jaune */ .app-session.session-warn { background: #f5c518; color: #2a2100; } .app-session.session-warn .session-extend-btn { border-color: #2a2100; } /* État critical (< 2 min) : rouge + pulse */ .app-session.session-critical { background: #e74c3c; color: #fff; animation: session-pulse 1s infinite; } .app-session.session-critical .session-extend-btn { border-color: #fff; background: rgba(255, 255, 255, 0.15); font-weight: 600; } .app-session.session-critical .session-extend-btn:hover { background: rgba(255, 255, 255, 0.3); } @keyframes session-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(231, 76, 60, 0.5); } 50% { box-shadow: 0 0 0 6px rgba(231, 76, 60, 0); } } /* Bouton "Me reconnecter" dans la bannière session expirée */ .session-expired-reconnect-btn { margin-left: 12px; padding: 6px 14px; border-radius: 4px; background: #fff; color: #c0392b; border: none; font-weight: 600; cursor: pointer; font-size: 13px; transition: background 0.2s; } .session-expired-reconnect-btn:hover { background: #f8d7da; } /* Bannière "Reconnexion en cours" */ .banner-reconnecting { background: #3498db; color: #fff; padding: 10px 20px; display: flex; align-items: center; gap: 10px; font-size: 12px; font-weight: 500; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .banner-reconnecting.hidden { display: none; } .banner-reconnecting .banner-spinner { font-size: 16px; animation: spin-slow 2s linear infinite; } @keyframes spin-slow { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* ========================================================================== v5.0.11 : bannières reconnect avec boutons Annuler + choix réseau ========================================================================== */ .banner-reconnecting .banner-cancel-btn, .banner-reconnect-failed .banner-cancel-btn { margin-left: auto; padding: 5px 12px; background: transparent; color: inherit; border: 1px solid currentColor; border-radius: 4px; font-size: 13px; cursor: pointer; font-weight: 500; transition: background 0.2s; } .banner-reconnecting .banner-cancel-btn:hover, .banner-reconnect-failed .banner-cancel-btn:hover { background: rgba(255, 255, 255, 0.15); } .banner-reconnect-failed { background: #e67e22; color: #fff; padding: 10px 20px; display: flex; align-items: center; gap: 12px; font-size: 12px; font-weight: 500; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } .banner-reconnect-failed.hidden { display: none; } .banner-reconnect-failed .banner-icon { font-size: 18px; } .banner-reconnect-failed .banner-btn-primary { padding: 6px 14px; border-radius: 4px; background: #fff; color: #c0392b; border: none; font-weight: 600; cursor: pointer; font-size: 13px; transition: background 0.2s; } .banner-reconnect-failed .banner-btn-primary:hover { background: #f8d7da; } /* ========================================================================== v2026.5.16 : responsive topbar ========================================================================== */ /* Breakpoint medium : entre 1000 et 1300px, on compacte un peu */ @media (max-width: 1300px) { /* horloge garde sa petite taille à tous les breakpoints. */ .app-clock-date { font-size: 14px; } .app-clock-time { font-size: 14px; } .app-clock-date::after { font-size: 14px; } .topbar-right .btn-action .btn-action-label, .topbar-right .btn-refresh .btn-refresh-label { font-size: 13px; } } /* Breakpoint small : moins de 1000px, on masque les labels de boutons action et on réduit encore l'horloge. Les icônes restent, titres restent. */ @media (max-width: 1000px) { .topbar { padding: 8px 14px; gap: 8px; } .topbar h1 { font-size: 18px; } /* horloge ne doit pas grossir aux breakpoints, elle reste minuscule. */ .app-clock-date { font-size: 14px; } .app-clock-time { font-size: 14px; } .app-clock-date::after { font-size: 14px; } .btn-action .btn-action-label, .btn-refresh .btn-refresh-label { display: none; } .btn-action, .btn-refresh { padding: 6px 10px; } .capture-info { display: none; } } /* Breakpoint très petit : moins de 720px, on cache la date complète (garde juste l'heure) et on autorise le wrap total */ @media (max-width: 720px) { .topbar { flex-wrap: wrap; padding: 6px 10px; } .app-clock { position: static; transform: none; margin: 0 auto; } .app-clock-date { display: none; } .topbar-left { flex-wrap: wrap; } .date-nav { margin-top: 4px; } .topbar-right { flex-wrap: wrap; justify-content: flex-end; } } /* Breakpoint minuscule : masque aussi les labels de refresh, boutons deviennent vraiment iconifiés */ @media (max-width: 520px) { .app-clock-time { font-size: 14px; } .topbar h1 { font-size: 12px; } .btn-today { padding: 4px 6px; font-size: 11px; } .btn-nav { min-width: 26px; padding: 4px 6px; } } /* ========================================================================== v2026.5.17 : topbar des popups épinglés (3 boutons : _ ▭ ) ========================================================================== */ .pinned-popup { /* v2026.5.45 : padding-top harmonisé à 28px avec .tooltip pour qu'il n'y ait aucun saut de boîte entre live / pinned / soft-unpinned. La topbar du popup épinglé se place en absolute sur cet espace. */ padding-top: 28px !important; } /* v2026.5.18 : masquer le conteneur d'actions d'origine (↻ reload + pin) dans les popups épinglés — leur place est reprise par notre .pinned-popup-topbar */ .pinned-popup .tooltip-actions { display: none !important; } .pinned-popup-topbar { position: absolute; top: 4px; right: 4px; display: flex; gap: 2px; align-items: center; z-index: 10; } .pinned-popup-btn { width: 26px; height: 22px; padding: 0; font-size: 13px; line-height: 1; background: transparent; color: var(--text-muted); border: 1px solid transparent; border-radius: 4px; cursor: pointer; transition: background 0.1s, color 0.1s, border-color 0.1s; font-family: inherit; display: inline-flex; align-items: center; justify-content: center; } .pinned-popup-btn:hover { background: var(--bg-muted); color: var(--text); border-color: var(--border); } .pinned-popup-unpin { font-size: 12px; } /* ========================================================================== v2026.5.17 : mode Minimisé (popup flottant compact, juste la ref) v2026.5.19 : refonte — élément .pinned-popup-minref créé à la volée v2026.5.21 : agrandi pour que la ref tienne sans déborder v2026.5.22 : encore agrandi + plus d'espace entre dragbar et topbar v2026.5.23 : refonte complète en style "onglet" single-line, compact ========================================================================== */ .pinned-popup.pinned-popup-minimized { display: flex !important; flex-direction: row !important; align-items: center !important; min-width: 300px !important; max-width: 400px !important; width: auto !important; height: 36px !important; min-height: 36px !important; padding: 0 6px 0 4px !important; overflow: visible; background: var(--bg-elevated) !important; border: 1px solid var(--border) !important; border-radius: 6px !important; } /* Dans le mode minimisé, la topbar n'est plus en absolute : elle se pose en fin de ligne à droite, après la ref */ .pinned-popup.pinned-popup-minimized .pinned-popup-topbar { position: static !important; top: auto !important; right: auto !important; margin-left: auto !important; order: 3; flex-shrink: 0; padding: 0 2px; } /* La dragbar devient un simple "handle" à gauche (≡) */ .pinned-popup.pinned-popup-minimized .pinned-popup-dragbar { position: static !important; top: auto !important; left: auto !important; right: auto !important; order: 1; flex-shrink: 0; width: 18px !important; height: 22px !important; background: transparent !important; border: none !important; cursor: grab; display: flex !important; align-items: center; justify-content: center; color: var(--text-faint); opacity: 0.6; transition: opacity 0.12s, color 0.12s; } .pinned-popup.pinned-popup-minimized .pinned-popup-dragbar::before { content: "≡" !important; font-size: 15px; line-height: 1; /* v2026.5.24 : annuler les propriétés du ::before normal (barre grise) */ width: auto !important; height: auto !important; background: transparent !important; border-radius: 0 !important; } .pinned-popup.pinned-popup-minimized .pinned-popup-dragbar:hover { opacity: 1; color: var(--text); } .pinned-popup.pinned-popup-minimized.dragging .pinned-popup-dragbar { cursor: grabbing; } /* Masquer tous les enfants directs SAUF topbar, dragbar et minref */ .pinned-popup.pinned-popup-minimized > *:not(.pinned-popup-topbar):not(.pinned-popup-dragbar):not(.pinned-popup-minref) { display: none !important; } /* La ref au centre, cliquable pour agrandir */ .pinned-popup-minref { display: flex; align-items: center; justify-content: center; /* v2026.5.24 : centrer horizontalement la ref */ flex: 1; min-width: 0; padding: 0 10px; font-family: var(--mono, monospace); font-size: 12px; font-weight: 700; color: var(--text); cursor: pointer; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; order: 2; text-align: center; /* v2026.5.24 : centrer le texte */ } .pinned-popup-minref:hover { color: var(--accent, #3b82f6); } /* Boutons plus petits en mode minimisé */ .pinned-popup.pinned-popup-minimized .pinned-popup-btn { width: 22px !important; height: 22px !important; font-size: 12px !important; } .pinned-popup.pinned-popup-minimized .pinned-popup-refresh svg { width: 12px; height: 12px; } /* ========================================================================== v2026.5.17 : mode Réduit (docké en bas de l'écran) + taskbar ========================================================================== */ .pinned-popup.pinned-popup-reduced { display: none !important; } .pinned-popups-dock { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; display: none; flex-wrap: wrap; gap: 6px; padding: 6px 10px; background: var(--bg-elevated); border-top: 1px solid var(--border); box-shadow: 0 -2px 10px rgba(0,0,0,0.2); align-items: center; } .pinned-popups-dock.visible { display: flex; } /* Pastille du dock (popup réduit) : 3 lignes centrées, fond sobre, barre de catégorie à gauche (::before), draggable. Règle de base UNIQUE — avant, 4 définitions empilées (base v5.18 + 2-lignes + drag v5.23 + refonte 3-lignes v5.25/26) avec des !important pour se surcharger l'une l'autre. État final reproduit à l'identique, sans !important (plus aucun conflit intra-sélecteur). Les modificateurs (:hover, :active, .pill-dragging, ::before, -ref/-date/-lieu/-service) sont inchangés. */ .pinned-popup-dock-pill { display: inline-flex; flex-direction: column; align-items: center; justify-content: center; padding: 6px 14px 6px 18px; gap: 2px; line-height: 1.2; text-align: center; min-width: 200px; max-width: 300px; 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; white-space: nowrap; cursor: grab; user-select: none; transition: opacity 0.15s, transform 0.12s; position: relative; overflow: hidden; } .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.46 : badge « plusieurs jours » VERTICAL — icône au-dessus, date en dessous : tient dans la colonne des heures sans jamais être coupé */ .iv-multiday-badge { display: inline-flex; flex-direction: column; align-items: center; margin-top: 3px; padding: 2px 6px; border-radius: 8px; font-weight: 700; background: rgba(99, 102, 241, 0.14); color: #4f46e5; border: 1px solid rgba(99, 102, 241, 0.45); } .iv-multiday-ico { font-size: 11px; line-height: 1.15; } .iv-multiday-date { font-size: 9px; line-height: 1.2; white-space: nowrap; } [data-theme="dark"] .iv-multiday-badge { color: #a5b4fc; background: rgba(99, 102, 241, 0.22); border-color: rgba(165, 180, 252, 0.5); } .iv-multiday-badge-mini { padding: 1px 4px; margin-top: 2px; } .iv-multiday-badge-mini .iv-multiday-ico { font-size: 10px; } .iv-multiday-badge-mini .iv-multiday-date { font-size: 8px; } /* v2026.5.46 : ligne « Connexion : … » du popup badge */ .user-name-popup-conn { font-size: 11.5px; color: var(--text-muted); padding-top: 2px; } /* ========================================================================== 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; } /* Sous-éléments texte de la pastille dock (le bloc de base est consolidé plus haut). */ .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: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é ========================================================================== */ /* Barre verticale colorée à gauche = indicateur de catégorie (le bloc de base de la pastille est consolidé plus haut). */ .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(--iv-canvas) 40%); } html.theme-dark .card.absence-cat-maladie { background: linear-gradient(to bottom, rgba(67, 56, 202, 0.12) 0%, var(--iv-canvas) 40%); } .card.absence-cat-conge { border-left: 4px solid var(--c-conge); background: linear-gradient(to bottom, var(--c-conge-soft) 0%, var(--iv-canvas) 40%); } html.theme-dark .card.absence-cat-conge { background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--iv-canvas) 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 : 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(--iv-canvas) 40%); } html.theme-dark .card.absence-cat-recurring { background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--iv-canvas) 40%); } /* v2026.5.46 — vue horizontale : en échelle exacte, la carte d'un tech absent est HAUTE (elle s'étire sur toute la frise) alors que son contenu est court. Le dégradé « couleur → neutre à 40 % » (pensé pour les cartes courtes de la vue classique) laissait tout le bas de la carte en neutre. En vue horizontale on remplit donc TOUTE la carte de la couleur douce (theme-aware via --c-*-soft) ; la vue classique garde le dégradé. */ html.view-horizontal .card.absence-cat-maladie { background: var(--c-maladie-soft); } html.view-horizontal .card.absence-cat-conge, html.view-horizontal .card.absence-cat-recurring { background: var(--c-conge-soft); } /* Message "Absent le vendredi" (ou autre récurrence) en cyan. Règle UNIQUE — les bordures haut/bas (qui venaient d'un 2e bloc plus haut, v4.1.20) sont repliées ici ; le bloc dupliqué a été retiré. */ .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-top: 1px solid var(--border, rgba(128, 128, 128, 0.15)); border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.15)); 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 . 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 : 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.47 — le fond de la zone nom remplit TOUTE la hauteur de la ligne (nom toujours centré via justify-content:center). En mode normal le parent .card est déjà align-items:stretch, mais en mode heure exacte il passe à align-items:center (pour centrer les bulles du corps), ce qui laissait le header en bandeau fixe plus court que la ligne, avec du fond de carte visible au-dessus/en dessous. align-self:stretch force le header à s'étirer dans les deux modes ; le corps garde son propre centrage. */ align-self: stretch !important; } /* v2026.5.40 : 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 : 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 : 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
, 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 : 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 : 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.
n'est pas juste à côté mais en dessous — ce n'est pas l'idéal, d'où le wrapper JS. */ /* v2026.5.36 : wrapper flex-row pour sidebar + main en vue horizontale */ .horizontal-wrapper { display: flex; flex-direction: row; align-items: stretch; width: 100%; } html.view-horizontal .horizontal-wrapper > main#main { flex: 1 1 auto; min-width: 0; } /* ========================================================================== v2026.5.37 : refonte layout vue horizontale - Topbar en haut : masquée complètement (user-badge + titre descendent dans la sidebar) - Sidebar : user-badge + titre en haut, date/heure sous le bouton Auj., stats puis espace libre puis boutons en bas (direction colonne-inverse) - Progress-bar : overlay par-dessus user-badge + titre - Banderole "En pompier du..." : masquée en vue horizontale ========================================================================== */ /* 1. Topbar masquée complètement en vue horizontale */ html.view-horizontal body > header.topbar { display: none !important; } /* : la session-banner (sticky avec top: var(--topbar-height, 56px)) laissait une "barre invisible" de 56 px au-dessus en vue horizontale parce que la topbar est en display:none mais --topbar-height conserve sa dernière valeur. En vue horizontale on force top: 0 pour qu'elle soit collée au sommet. */ html.view-horizontal .session-banner { top: 0 !important; } /* rendu absence + réservation dans les mini-cards horizontales — juste le mot ("Congé" / "Réservation") centré. Les infos détaillées restent dans le tooltip au survol. */ .iv-mini-card-absence .iv-mini-time-vertical, .iv-mini-card-reservation .iv-mini-time-vertical { display: none; } .iv-mini-card-absence .iv-mini-card-text, .iv-mini-card-reservation .iv-mini-card-text { align-items: center; justify-content: center; text-align: center; flex: 1 1 auto; } .iv-mini-absence-label, .iv-mini-reservation-label { font-weight: 600; font-size: 13px; text-transform: none !important; /* garantir l'accent sur "Congé" */ } .iv-mini-absence-label { color: var(--text-muted, #98a2b3); } /* : seul le texte "Réservation" est en jaune ambré (comme la vue classique) — pas de fond coloré, on garde la barre couleur à gauche qui est déjà jaune via .color-reservation .iv-mini-card-bar. */ .iv-mini-reservation-label { color: var(--c-reservation, #f59e0b) !important; } /* 2. Sidebar : structure verticale avec section fixe en haut (user+titre+date) et section "boutons" en bas poussée via margin-top: auto. v2026.5.39 r8 : max-height retiré (était en conflit avec min-height compensé par --zoom-inv quand le user dézoom le texte). */ html.view-horizontal .horizontal-sidebar { padding-top: 12px !important; } /* 3. User-badge et titre dans la sidebar, côte à côte en haut */ html.view-horizontal .horizontal-sidebar #user-badge { position: relative; margin: 0 auto 2px auto; display: flex; align-items: center; justify-content: center; } html.view-horizontal .horizontal-sidebar #app-title { text-align: center; font-size: 15px !important; font-weight: 700 !important; margin: 0 0 8px 0 !important; padding: 0 !important; color: var(--text); } /* 4. Bouton "Aujourd'hui" en pleine largeur, avant date/heure */ /* v2026.5.37 : on utilise un DOM wrapper #sidebar-arrows créé en JS pour les 2 flèches côte à côte. date-nav est décomposé en : [Auj.] + [clock intercalé] + [date-custom] + [arrows-wrapper]. Le JS s'en occupe. */ html.view-horizontal .horizontal-sidebar .date-nav { display: contents; } /* today-block (nav-today + app-clock) déplacé en bloc en sidebar, empilé verticalement comme la vue classique. */ html.view-horizontal .horizontal-sidebar .today-block { order: 1; display: flex !important; flex-direction: column !important; align-items: stretch !important; width: 100%; gap: 6px; padding: 6px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-muted); margin: 0; } /* Bouton Aujourd'hui en haut du today-block en sidebar. */ html.view-horizontal .horizontal-sidebar .today-block .btn-today, html.view-horizontal .horizontal-sidebar .btn-today { width: 100% !important; justify-content: center !important; text-align: center !important; } /* 5. App-clock (date + heure) centré sous le bouton "Aujourd'hui" */ html.view-horizontal .horizontal-sidebar .today-block .app-clock, html.view-horizontal .horizontal-sidebar .app-clock { align-items: center !important; text-align: center !important; padding: 4px 4px !important; background: transparent !important; border: none !important; margin: 0 !important; width: 100%; } /* date du jour et heure même taille (12px) plus petites en sidebar horizontale, pour que la date sélectionnée (14px gras) reste l'info dominante. */ html.view-horizontal .horizontal-sidebar .app-clock-date { text-align: center !important; width: 100%; font-size: 12px !important; font-weight: 500 !important; color: var(--text-muted) !important; } html.view-horizontal .horizontal-sidebar .app-clock-time { text-align: center !important; width: 100%; font-size: 12px !important; font-weight: 600 !important; color: var(--text-muted) !important; } /* 6. Séparateur visuel après date/heure (avant sélecteur date) v2026.5.37 : on override order:-1 qui venait de v5.36 */ html.view-horizontal .horizontal-sidebar .date-nav .date-custom-wrapper { order: 3 !important; border-top: 1px solid var(--border); padding-top: 8px; margin-top: 4px; width: 100%; } /* 7. Flèches ◀ ▶ côte à côte via wrapper JS #sidebar-arrows */ html.view-horizontal .horizontal-sidebar #sidebar-arrows { order: 4; display: flex; flex-direction: row; gap: 4px; width: 100%; } html.view-horizontal .horizontal-sidebar #sidebar-arrows .btn-nav { flex: 1 1 0 !important; min-width: 0 !important; justify-content: center !important; } /* 8. Stats empilées avec order pour venir après les flèches */ html.view-horizontal .horizontal-sidebar #stats { order: 5; width: 100%; margin-top: 8px; } /* 9. Capture-info (Synchronisé à HH:MM) sous les stats */ html.view-horizontal .horizontal-sidebar .capture-info { order: 6; margin-top: 4px; text-align: center; } /* 10. Boutons poussés en bas via margin-top: auto sur le premier d'entre eux (Absence, qui a order:7). v2026.5.39 r2 : ajout d'une fine bordure top + padding pour visualiser clairement la séparation entre la zone "info" (capture-info, stats) et la zone "actions" (boutons). margin-top: auto pousse en bas. */ /* v2026.5.46 — « + Ajouter » (#add-menu-wrap) a REMPLACÉ l'ancien #absence-btn comme 1er bouton d'action. Sans order ni largeur, il remontait tout en haut (order:0), étroit et centré. On le replace dans le groupe d'actions en bas (order:7), poussé en bas via margin-top:auto + séparateur, et en PLEINE LARGEUR comme Douchette / Actualiser. */ html.view-horizontal .horizontal-sidebar #add-menu-wrap { order: 7; margin-top: auto !important; /* pousse le groupe d'actions tout en bas */ padding-top: 8px !important; /* espace au-dessus, sépare de l'info/horloge */ width: 100%; position: relative; } html.view-horizontal .horizontal-sidebar #add-menu-wrap::before { content: ""; position: absolute; top: 0; left: 4px; right: 4px; height: 1px; background: var(--border); } html.view-horizontal .horizontal-sidebar #add-menu-wrap > #add-menu-btn { width: 100%; } 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. v2026.5.47 — la barre doit RESTER dans la barre de gauche, AU-DESSUS des initiales — pas dans le contenu où elle couvrait les heures du mode « échelle exacte ». Elle repasse donc en overlay sur la SIDEBAR (left:0, width = --sidebar-w) ; top suit --stack-top (sous la banderole de session/reco si présente). Pour NE PAS recouvrir les initiales, le contenu de la sidebar est poussé vers le bas pendant le rafraîchissement (classe .planif-refreshing sur , cf. showProgressBar). */ html.view-horizontal #progress-bar { position: fixed !important; top: var(--stack-top, 0px) !important; left: 0 !important; width: var(--sidebar-w, 200px) !important; right: auto !important; z-index: 9999 !important; border-radius: 0 !important; margin: 0 !important; pointer-events: none; } /* Réserve la hauteur de la barre en haut de la sidebar pendant un rafraîchissement → les initiales / le titre passent SOUS la barre. */ html.planif-refreshing.view-horizontal .horizontal-sidebar { padding-top: 34px !important; } /* v2026.5.40 : 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; } /* : séparateur "vide" (la période n'a aucune iv) — on garde le même design que le séparateur normal d'une période avec iv, pour l'homogénéité. Aucun override visuel. */ /* Option "Afficher les pauses" — quand inactive, on cache les .gap-placeholder SAUF .gap-placeholder-full (le bloc Matin/Aprèm complètement vide reste toujours visible, peu importe l'option). La détection bidirectionnelle hover du timeline-hole reste fonctionnelle (le hole existe toujours dans le DOM). */ html:not(.show-iv-gaps) .gap-placeholder:not(.gap-placeholder-full) { display: none; } /* : "carte vide" entre 2 iv qui ne s'enchainent pas — même rendu que le placeholder Matin/Aprèm vide. */ .gap-placeholder { display: flex; align-items: center; justify-content: center; min-height: 44px; margin: 6px 0; font-size: 16px; font-weight: 700; color: var(--text-muted); border: 1px dashed var(--border-strong); border-radius: 6px; /* v2026.5.49 — plage libre VISIBLE : sur le fond de retrait des cartes (--iv-canvas), un rectangle transparent au contour pâle se voyait à peine. Voile clair + contour tireté = « emplacement disponible », sans atteindre la surface pleine d'une intervention. */ background: var(--iv-libre); cursor: help; user-select: none; transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; } /* v2026.5.46 — menu clic droit : raison de blocage + entrées grisées */ .tech-context-menu-reason { padding: 2px 12px 8px; font-size: calc(11.5px * var(--text-scale, 1)); font-style: italic; color: var(--warn); max-width: 230px; } .tech-context-menu-item.is-disabled { opacity: .45; cursor: not-allowed; } .tech-context-menu-item.is-disabled:hover { background: transparent; } .gap-placeholder.gap-placeholder-mini { /* : flex défini inline par JS — flex-grow proportionnel, flex-shrink 10 (compresse 10× plus que l'iv en cas de manque de place), basis 30 px. min-width 0 : peut disparaître visuelle- ment quand l'espace manque, pour laisser la place aux iv. */ flex: 1 10 30px; min-width: 0; min-height: 0; align-self: stretch; margin: 0; font-size: 14px; overflow: hidden; } /* : variante "full" — quand le bloc Matin/Aprèm est complètement vide en vue horizontale, le gap prend TOUTE la largeur du bloc (au lieu des 32 px fixes), comme l'ancien .iv-mini-block-empty. */ .gap-placeholder.gap-placeholder-mini.gap-placeholder-full { flex: 1 1 auto; min-width: 60px; } /* : en vue horizontale, on cache le grand .gap-placeholder (vue classique) qui apparaissait en bas de la card-body — les rows .intervention-v2 sont déjà cachées, le rect entre rows traînait en plus en bas. Seuls les .gap-placeholder-mini (entre les mini-cards horizontales) restent visibles. */ html.view-horizontal .gap-placeholder:not(.gap-placeholder-mini) { display: none !important; } /* Surbrillance individuelle (CSS pur, toujours active) : - hover direct du rectangle → le rectangle s'éclaire - hover direct du timeline-hole → le hole s'éclaire PLUS l'effet symétrique via la classe .gap-hover-active appliquée par JS sur l'élément lié. Du coup les 2 côtés se highlight ensemble même si le bind JS échoue à trouver le partenaire. */ .gap-placeholder:hover, .gap-placeholder.gap-hover-active { background: var(--accent-soft); border-color: var(--accent, #0f4f8b); border-style: solid; color: var(--accent, #0f4f8b); } .timeline-hole.gap-hover-active { background: var(--accent-soft); outline: 3px solid var(--accent, #0f4f8b); outline-offset: -3px; z-index: 6; } /* v2026.5.49 — survol d'une PAUSE : on fane (désature + assombrit) les interventions de la FRISE (.timeline-slot) uniquement, pour que la plage libre survolée — qui garde sa couleur vive et son contour accent — ressorte, même collée à une installation bleue (les deux se confondaient). La frise existe dans les deux vues (classique et horizontale), donc l'effet vaut pour les deux. On NE fane PAS les cartes d'intervention en dessous (.iv-mini-card, .intervention-v2) : seule la timeline s'assombrit. La pause elle-même (.timeline-hole / .gap-placeholder) n'est jamais ciblée. .gap-dimming est posée par highlightGap sur la carte (classique) ou la ligne (horizontal). */ .gap-dimming .timeline-slot { filter: saturate(0.3) brightness(0.82); opacity: 0.45; transition: filter 0.12s ease, opacity 0.12s ease; } /* : placeholder "—" sous le séparateur d'une période vide, dans la vue classique. Reprend le même design que .iv-mini-block-empty en vue horizontale : encadré dashed, "—" centré, hauteur fixe. */ .day-period-empty-placeholder { display: flex; align-items: center; justify-content: center; min-height: 44px; margin: 6px 0; font-size: 16px; font-weight: 700; color: var(--text-muted); border: 1px dashed var(--border-strong); border-radius: 6px; /* v2026.5.49 — même éclaircissement que .gap-placeholder. */ background: var(--iv-libre); user-select: none; pointer-events: none; } html.view-horizontal .day-period-empty-placeholder { display: none !important; } /* ========================================================================== v2026.5.39 : Admin — section Apparence (rows label/control + select + groupe boutons zoom). ========================================================================== */ .admin-row { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 14px 0; border-bottom: 1px solid var(--border); } .admin-row:last-child { border-bottom: none; } .admin-row-label { flex: 1 1 auto; min-width: 0; } .admin-row-label strong { font-size: 12px; color: var(--text); } .admin-row-desc { font-size: 12px; color: var(--text-faint); margin-top: 4px; line-height: 1.4; } .admin-row-control { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; } .admin-select { padding: 6px 10px; font-size: 13px; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border-strong); border-radius: 4px; cursor: pointer; min-width: 200px; } .admin-input-num { width: 80px !important; text-align: center; } .admin-zoom-group { display: flex; gap: 4px; } .btn-zoom { min-width: 56px; padding: 6px 10px; font-size: 12px; font-weight: 500; background: var(--bg-elevated); color: var(--text-muted); border: 1px solid var(--border-strong); border-radius: 4px; cursor: pointer; transition: background 0.1s, color 0.1s, border-color 0.1s; } .btn-zoom:hover { background: var(--bg-hover); color: var(--text); } .btn-zoom.active { background: var(--accent); color: #fff; border-color: var(--accent); } /* ========================================================================== v2026.5.39 r5 : zoom texte via variable --text-scale (0.8 à 1.2). Modifie uniquement les font-size, pas le layout. Stockée par JS dans admin_config.textZoom et appliquée sur 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 = "" 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 : le contenu enrichi est maintenant dans .iv-mini-cards en-dessous de la timeline) */ .timeline-slot-content { display: none; } /* v2026.5.40 : 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 : 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 — 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; /* v2026.5.49 — même relief qu'en vue classique : la mini-carte est posée sur le corps de carte, qui est en retrait. */ box-shadow: var(--iv-relief); } .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 : 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 : 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 : 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 : 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 : 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 : en vue horizontale, le dock des popups épinglés réduits commence APRÈS la sidebar (200px). Sinon les pills étaient cachées sous la sidebar fixe à gauche. */ html.view-horizontal .pinned-popups-dock { left: 200px !important; } @media (max-width: 1400px) { html.view-horizontal .pinned-popups-dock { left: 170px !important; } } /* v2026.5.45 : 2 blocs MATIN / APRÈS-MIDI toujours présents (même vides), séparés par une vraie barre verticale dédiée (.iv-mini-sep). Garantit la symétrie d'une carte tech à l'autre — l'œil retrouve toujours les 2 demi- journées au même endroit horizontal. */ .iv-mini-block { flex: 1 1 0; display: flex; flex-direction: column; min-width: 0; gap: 2px; } .iv-mini-sep { flex: 0 0 12px; align-self: stretch; position: relative; margin: 0 6px; /* Trait vertical centré dans la zone réservée. */ background: linear-gradient(to bottom, transparent 0, transparent 14%, var(--border-strong) 14%, var(--border-strong) 86%, transparent 86%, transparent 100%) no-repeat center / 2px 100%; } .iv-mini-sep::before { /* Petit "•" centré pour signaler la coupure 12h. */ content: "12h"; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: calc(9px * var(--text-scale)); font-weight: 700; color: var(--text-muted); background: var(--bg-elevated); padding: 1px 2px; border-radius: 3px; letter-spacing: 0.4px; } .iv-mini-block-label { font-size: calc(10px * var(--text-scale)); font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--text-muted); text-align: center; padding: 1px 0; } .iv-mini-block-cards { display: flex; flex-direction: row; gap: 3px; flex: 1 1 auto; min-width: 0; } .iv-mini-block-cards .iv-mini-card { /* : flex inline (proportionnel à la durée + basis 100 px). min-width 80 px : seuil ABSOLU sous lequel l'iv ne descend pas, même si le bloc est très étroit — garantit que la ref / l'heure restent lisibles. Quand l'espace manque, c'est les gaps qui compressent en premier (flex-shrink 10 vs 1). */ flex: 1 1 100px; min-width: 80px; } /* : placeholder pour un bloc vide — garde la place réservée et indique visuellement qu'il n'y a rien sur cette demi-journée. */ .iv-mini-block-empty { flex: 1 1 0; display: flex; align-items: center; justify-content: center; min-height: 36px; font-size: calc(14px * var(--text-scale)); font-weight: 700; color: var(--text-muted); border: 1px dashed var(--border-strong); border-radius: 6px; /* v2026.5.49 — même éclaircissement que .gap-placeholder. */ background: var(--iv-libre); user-select: none; pointer-events: none; } /* v2026.5.40 : 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 : 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 : onglet Équipe — colonne "Inclure" plus étroite, ID + nom en lecture seule (texte clair, pas d'input). */ .admin-team-table th:first-child, .admin-team-table td:first-child { width: 60px !important; text-align: center !important; padding-left: 8px !important; padding-right: 8px !important; } .admin-team-table th:first-child { font-size: 12px !important; letter-spacing: 0.4px !important; } .admin-team-id-readonly { font-family: var(--mono); font-weight: 600; color: var(--text-muted); font-size: 12.5px; } .admin-team-name-readonly { font-weight: 600; color: var(--text); font-size: 13px; } /* v2026.5.40 r21b : seul le `/` prend la couleur faint d'absent. Le `+` reste en couleur neutre (héritée du parent stats). */ .global-stat-sep.sep-absent { color: var(--text-faint) !important; font-weight: 700; } /* ========================================================================== — Feature reschedule (modifier horaire / déplacer iv) - Bloc heure éditable : curseur grab + petit hover halo - Ghost qui suit la souris pendant le drag - Preview rectangle vert sur la timeline du tech survolé - Modal d'édition + confirmation (réutilise les styles modal existants) ========================================================================== */ .iv-time-vertical.editable-time, .iv-mini-time-vertical.editable-time { cursor: grab; position: relative; user-select: none; border-radius: 6px; transition: background 0.1s, box-shadow 0.1s; } .iv-time-vertical.editable-time:hover, .iv-mini-time-vertical.editable-time:hover { background: rgba(63, 185, 80, 0.10); box-shadow: inset 0 0 0 1px rgba(63, 185, 80, 0.45); } html.reschedule-dragging .iv-time-vertical.editable-time, html.reschedule-dragging .iv-mini-time-vertical.editable-time { cursor: grabbing; } html.reschedule-dragging, html.reschedule-dragging * { cursor: grabbing !important; user-select: none !important; } /* Ghost flottant attaché au curseur pendant le drag */ .reschedule-ghost { position: fixed; z-index: 9999; pointer-events: none; background: var(--bg-elevated); border: 2px solid var(--ok, #2e7b4a); border-radius: 8px; padding: 6px; box-shadow: 0 10px 28px rgba(0, 0, 0, 0.35); font-size: 12px; line-height: 1.3; min-width: 140px; max-width: 360px; opacity: 0.95; transition: opacity 0.1s; transform: rotate(-1.5deg); } /* : clone visuel de la card source intégré dans le ghost flottant */ .reschedule-ghost .reschedule-ghost-visual { pointer-events: none !important; margin: 0 !important; cursor: grabbing !important; filter: none !important; /* on annule les hover-effects résiduels du clone (.intervention-v2:hover) */ background: var(--bg-card, var(--bg-elevated)) !important; } .reschedule-ghost .reschedule-ghost-visual::before, .reschedule-ghost .reschedule-ghost-visual::after { display: none !important; } .reschedule-ghost .rg-meta { margin-top: 6px; padding: 4px 6px 2px; border-top: 1px dashed var(--border-faint, rgba(255,255,255,0.12)); } .reschedule-ghost.on-target { border-color: var(--ok, #2e7b4a); background: var(--ok-soft, #dff0e4); } .reschedule-ghost:not(.on-target):not(.on-blocked) { border-color: var(--text-faint, #50596a); opacity: 0.7; } /* : tech cible absent toute la journée → drop refusé visuellement */ .reschedule-ghost.on-blocked { border-color: var(--err, #d6443d) !important; background: rgba(214, 68, 61, 0.12) !important; opacity: 0.85; } .reschedule-ghost.on-blocked::before { content: "🚫 absent"; position: absolute; top: -10px; right: 8px; background: var(--err, #d6443d); color: #fff; font-size: 10px; font-weight: 700; padding: 1px 8px; border-radius: 10px; letter-spacing: 0.3px; pointer-events: none; } .reschedule-ghost .rg-title { font-weight: 700; color: var(--text); font-family: var(--mono, monospace); } .reschedule-ghost .rg-times { margin-top: 2px; color: var(--text-muted); } .reschedule-ghost .rg-times .rg-dur { color: var(--text-faint); font-size: 11px; } .reschedule-ghost .rg-tech { margin-top: 2px; color: var(--text-muted); font-size: 11px; font-style: italic; } /* : drop-ghost = clone visuel grisé de la carte (row classique ou mini-card horizontale) inséré dans la card-body / bloc Matin-Aprèm du tech cible à la position chronologique correspondant aux heures projetées. C'est ce que voit le user pour repérer où sa carte se loge — un vrai fac-similé grisé de la carte. */ .reschedule-drop-ghost { opacity: 0.55 !important; filter: grayscale(0.5); border: 2px dashed var(--ok, #2e7b4a) !important; pointer-events: none !important; background: var(--bg-card, var(--bg-elevated)) !important; transition: margin 0.12s ease; } .reschedule-drop-ghost.reschedule-drop-ghost-mini { /* Mini-card horizontale — mêmes codes mais on évite le filter qui écraserait les couleurs de catégorie de la mini-card. */ filter: none; opacity: 0.55 !important; } /* Petite barre verte sur la timeline-bar pour repérer la position horaire précise (en plus du clone in-card). Volontairement minimaliste (pas de label, pas de pulsation) pour ne pas concurrencer le clone. */ .reschedule-timeline-mark { position: absolute; top: 0; bottom: 0; background: rgba(46, 123, 74, 0.55); border-left: 2px solid var(--ok, #2e7b4a); border-right: 2px solid var(--ok, #2e7b4a); pointer-events: none; z-index: 5; box-sizing: border-box; } /* Modal d'édition reschedule — réutilise modal-card mais avec un style plus compact pour le formulaire à 4 champs. */ .modal-card.reschedule-modal { min-width: 380px; max-width: 460px; } /* Pendant le drag, on supprime tous les hover handlers concurrents : tooltip live, popovers timeline, hover des mini-cards et iv-rows. Comme ça elementFromPoint trouve toujours la timeline-bar / card pour un drop précis, et aucune popup parasite ne s'ouvre. */ html.reschedule-dragging .timeline-slot, html.reschedule-dragging .iv-mini-card, html.reschedule-dragging .intervention-v2, html.reschedule-dragging .timeline-popover, html.reschedule-dragging .tooltip, html.reschedule-dragging .pinned-popup { pointer-events: none !important; } html.reschedule-dragging .tooltip:not(.pinned-popup):not(.soft-unpinned) { display: none !important; } /* Texte projeté du ghost : vert vif quand on est sur une cible valide. */ .reschedule-ghost .rg-times { font-family: var(--mono, monospace); font-size: 13px; font-weight: 700; color: var(--text-muted); margin-top: 4px; } .reschedule-ghost.on-target .rg-times { color: var(--ok, #2e7b4a); font-size: 14px; } .reschedule-ghost .rg-tech.rg-tech-changed { color: var(--accent, #0f4f8b); font-weight: 700; font-style: normal; } .reschedule-ghost .rg-tech.rg-tech-changed::before { content: "→ "; } /* : la source est retirée du flux pendant le drag (display:none) — la place est immédiatement reprise par le clone à destination inséré au même endroit dès l'activation du drag, donc la card ne rétrécit pas visuellement. */ .reschedule-source-hidden { display: none !important; } /* ============================================================================ Dock latéral des interventions mises de côté ─────────────────────────────────────────────────────────────────────────── Conteneur = simple carré flouté à droite (backdrop-filter), pas de chrome. Cartes = exactement la même structure que le ghost de drag (.reschedule- ghost) : clone .intervention-v2 / .iv-mini-card avec heure à gauche, ref et meta à droite. L'utilisateur retrouve sous le dock ce qu'il voyait sous son curseur pendant le drag. ──────────────────────────────────────────────────────────────────────────── */ .iv-dock { position: fixed; top: 80px; right: 8px; bottom: 12px; width: 200px; z-index: 9000; display: flex; flex-direction: column; gap: 8px; padding: 12px; pointer-events: none; background: rgba(255, 255, 255, 0.10); -webkit-backdrop-filter: blur(14px) saturate(120%); backdrop-filter: blur(14px) saturate(120%); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 12px; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.18); transition: transform 140ms ease-out, opacity 140ms ease-out; } html.dark .iv-dock, html[data-theme="dark"] .iv-dock { background: rgba(20, 25, 35, 0.32); border-color: rgba(255, 255, 255, 0.10); } body:has(.pinned-popups-dock.visible) .iv-dock { bottom: 64px; } .iv-dock.iv-dock--hidden { opacity: 0; transform: translateX(110%); pointer-events: none; } /* Drag commencé, dock vide : juste un petit onglet pour signaler la cible sans rien masquer du planning (~32 px visibles sur 260 px). */ .iv-dock.iv-dock--peep-min { transform: translateX(88%); } /* Cartes dockées (drag ou pas) : intermédiaire, le numéro DS reste lisible à droite. v2026.5.45 — décalé un peu plus à droite (50% au lieu de 30%) pour libérer du planning. */ .iv-dock.iv-dock--peep { /* v2026.5.45 — léger décalage supplémentaire vers la gauche (8 px de plus visibles que 50%) pour libérer un peu plus le numéro DS. */ transform: translateX(calc(50% - 8px)); } .iv-dock.iv-dock--expanded { transform: translateX(0); } .iv-dock-list { display: flex; flex-direction: column; gap: 10px; overflow-y: auto; /* v2026.5.45 — sans overflow-x explicite, "overflow-y: auto" force aussi l'axe X en auto (spec CSS), d'où la scrollbar horizontale qui apparaissait en bas du dock dès qu'une carte dépassait d'1 px. On la supprime. */ overflow-x: hidden; flex: 1; pointer-events: auto; } /* Dock vide (peep-min pendant un drag, ou sans rien) : on ne capte aucun clic, sinon le carré flouté bloque les mousedown sur les cartes du planning qui sont sous le dock — ce qui cassait le drag&drop standard. */ .iv-dock-list:empty { pointer-events: none; } .iv-dock.iv-dock--peep-min, .iv-dock.iv-dock--peep-min .iv-dock-list { pointer-events: none; } /* v2026.5.45 — quand le dock est visible avec du contenu (peep ou expanded), il capte les événements souris : empêche les clics et le hover de traverser jusqu'au planning derrière (ex : zone autour du bouton « Tout annuler » qui sélectionnait l'iv en dessous). */ .iv-dock.iv-dock--peep, .iv-dock.iv-dock--expanded { pointer-events: auto; } /* Carte du dock = juste ref + date sur fond teinté de la couleur de la catégorie. Drag handle = la carte entière. Pas d'heure, pas de barre, pas de chrome. */ .iv-dock-card.reschedule-ghost { /* v2026.5.45 — repart de zéro : aucun fond, aucune bordure sur la carte. La couleur de catégorie sera ajoutée UNIQUEMENT par les règles .iv-dock-card.color-* ci-dessous, qui posent un border-left de 4 px et RIEN d'autre. */ position: relative !important; z-index: auto !important; transform: rotate(-1.5deg) !important; transition: transform 140ms ease, box-shadow 120ms ease; pointer-events: auto; cursor: grab; opacity: 1; margin: 0; min-width: 0; max-width: none; padding: 0 !important; display: block; font-family: var(--mono, monospace); background: transparent; border: none; /* v2026.5.45 — overflow visible pour que le bouton × (positionné en -8px/-8px) ne soit pas tronqué par les bords de la card. */ overflow: visible; user-select: none; } .iv-dock-card.reschedule-ghost:hover { transform: rotate(0deg) scale(1.02) !important; box-shadow: 0 8px 22px rgba(0, 0, 0, 0.30); z-index: 1 !important; } .iv-dock-card.reschedule-ghost:active { cursor: grabbing; } /* v2026.5.45 — couleur du type d'intervention sur la GAUCHE seulement. Aucun fond, aucune autre bordure. Juste un trait vertical de 4 px. */ .iv-dock-card.color-livraison { border-left: 4px solid var(--c-livraison); } .iv-dock-card.color-installation { border-left: 4px solid var(--c-installation); } .iv-dock-card.color-recup { border-left: 4px solid var(--c-recup); } .iv-dock-card.color-remplacement { border-left: 4px solid var(--c-remplacement); } .iv-dock-card.color-incident { border-left: 4px solid var(--c-incident); } .iv-dock-card.color-rollout { border-left: 4px solid var(--c-rollout); } .iv-dock-card.color-reservation { border-left: 4px solid var(--c-reservation); } .iv-dock-card.color-autre { border-left: 4px solid var(--c-autre); } /* Bloc info : ref + date, texte collé aux bords (juste 2 px latéraux pour éviter de coller pile à la bordure de la carte). */ .iv-dock-card-info { display: flex; flex-direction: column; justify-content: center; padding: 0; } .iv-dock-card-ref { font-weight: 700; font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2; padding: 0 2px; } .iv-dock-card-meta { font-size: 10px; color: var(--text-muted, #6c7280); font-style: italic; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.1; padding: 0 2px; } .iv-dock-card-fallback { display: flex; align-items: center; gap: 8px; padding: 4px 8px; font-size: 12px; } .iv-dock-card-fallback .iv-dock-card-time { font-weight: 700; font-family: var(--mono, monospace); } .iv-dock-card-remove { /* v2026.5.45 — repositionné DANS la card (top:2px / right:2px) au lieu de déborder en -8/-8 : le parent .iv-dock-list a overflow-x: hidden pour supprimer la scrollbar horizontale, donc tout ce qui sort à droite est tronqué. Le bouton tient en 22 px dans le coin supérieur droit. */ position: absolute; top: 2px; right: 2px; width: 22px; height: 22px; border: none; background: var(--err, #d6443d); color: #fff; border-radius: 999px; cursor: pointer; display: none; font-size: 14px; line-height: 1; font-weight: 700; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); z-index: 2; } .iv-dock-card:hover .iv-dock-card-remove { display: block; } /* v2026.5.45 — pendant l'appui long sur ×, on signale visuellement la progression : un anneau conic-gradient se remplit en 2 s. À la fin, le handler JS retire l'iv. Si l'utilisateur lâche avant, la classe est retirée → l'anim s'annule, rien n'est supprimé. */ .iv-dock-card-remove.holding { background: conic-gradient(rgba(255,255,255,0.55) var(--hold, 0%), transparent 0) content-box, var(--err, #d6443d); animation: iv-dock-remove-hold 2000ms linear forwards; } @property --hold { syntax: ''; initial-value: 0%; inherits: false; } @keyframes iv-dock-remove-hold { from { --hold: 0%; } to { --hold: 100%; } } .iv-dock-clear-all { pointer-events: auto; align-self: flex-end; border: 1px solid var(--border, #d4d8e0); background: var(--bg-elevated, #fff); color: var(--text-muted, #6c7280); font-size: 10px; letter-spacing: 0.4px; padding: 3px 10px; border-radius: 999px; cursor: pointer; display: none; text-transform: uppercase; } .iv-dock-clear-all:hover { color: var(--err, #d6443d); border-color: var(--err, #d6443d); } .iv-dock--expanded .iv-dock-clear-all { display: inline-block; } /* =========================================================================== v2026.5.46 — Barre de recherche EV + cache (réf / personne / matériel / lieu) Centrée, sous le sélecteur de date, à la hauteur des stats. Résultats dans un popup ancré qui s'adapte à l'espace écran (max-height piloté en JS). =========================================================================== */ .search-zone { position: relative; width: min(92vw, 560px); margin: 10px auto 2px; z-index: 40; } .search-bar { display: flex; align-items: center; gap: 8px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 999px; padding: 7px 14px; box-shadow: var(--shadow); transition: border-color .12s, box-shadow .12s; } .search-bar:focus-within { border-color: var(--accent); box-shadow: var(--shadow-hover); } .search-ico { font-size: 14px; opacity: .7; flex: 0 0 auto; } .search-input { flex: 1 1 auto; min-width: 0; border: none; outline: none; background: transparent; color: var(--text); font-size: calc(14px * var(--text-scale, 1)); line-height: 1.2; } .search-input::placeholder { color: var(--text-faint); } /* v2026.5.46 — le champ est en type="search" : le navigateur ajoute sa PROPRE croix d'effacement (bleue), en doublon avec le bouton × maison (#ev-search-clear). On masque la native, on garde la nôtre. */ .search-input::-webkit-search-cancel-button, .search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; display: none; } .search-clear, .search-spin { flex: 0 0 auto; } .search-clear { border: none; background: transparent; cursor: pointer; color: var(--text-faint); font-size: 18px; line-height: 1; padding: 0 2px; border-radius: 4px; } .search-clear:hover { color: var(--text); background: var(--bg-hover); } .search-spin { width: 14px; height: 14px; border-radius: 50%; border: 2px solid var(--border-strong); border-top-color: var(--accent); animation: search-spin .7s linear infinite; } @keyframes search-spin { to { transform: rotate(360deg); } } .search-results { position: absolute; top: calc(100% + 6px); left: 0; right: 0; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-hover); max-height: 60vh; /* repli ; ajusté dynamiquement en JS */ overflow-y: auto; padding: 6px; z-index: 50; } .search-results .sr-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 8px 4px; font-size: calc(11px * var(--text-scale, 1)); font-weight: 600; text-transform: uppercase; letter-spacing: .03em; color: var(--text-faint); } .search-results .sr-head .sr-src { font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text-faint); opacity: .7; } .search-results .sr-head img.sr-ic { width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px; } .search-results .sr-sep { text-align: center; color: var(--text-faint); font-size: calc(11px * var(--text-scale, 1)); margin: 6px 0 2px; opacity: .8; } .sr-card { position: relative; border: 1px solid var(--border); border-left: 4px solid var(--border-strong); border-radius: 6px; background: var(--bg); padding: 8px 10px; margin: 5px 2px; display: flex; gap: 10px; align-items: flex-start; } .sr-card:hover { background: var(--bg-hover); } .sr-card.acc-fait, .sr-card.acc-clos { border-left-color: var(--ok); } .sr-card.acc-cours { border-left-color: var(--accent); } .sr-card.acc-retard { border-left-color: var(--warn); } .sr-card.acc-annule { border-left-color: var(--text-faint); } .sr-main { flex: 1 1 auto; min-width: 0; } .sr-status-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; } .sr-pill { font-size: calc(11px * var(--text-scale, 1)); padding: 1px 8px; border-radius: 999px; font-weight: 600; border: 1px solid transparent; } .sr-pill.s-fait, .sr-pill.s-clos { background: var(--ok-soft); color: var(--ok); } .sr-pill.s-cours { background: var(--accent-soft); color: var(--accent); } .sr-pill.s-annule { background: var(--bg-muted); color: var(--text-faint); } .sr-pill.s-autre { background: var(--bg-muted); color: var(--text-muted); } .sr-when { font-size: calc(11px * var(--text-scale, 1)); padding: 1px 8px; border-radius: 999px; background: var(--bg-muted); color: var(--text-faint); } .sr-when.w-today { background: var(--warn-soft); color: var(--warn); } .sr-when.w-future { background: var(--accent-soft); color: var(--accent); } /* v2026.5.46 — badge « Jour affiché » (l'iv tombe sur le jour regardé) */ .sr-when.w-here { background: var(--ok-soft); color: var(--ok); font-weight: 600; } /* v2026.5.46 — bouton d'aide sur la syntaxe de recherche */ .search-help { flex: 0 0 auto; border: none; background: transparent; cursor: pointer; color: var(--text-faint); font-size: 13px; line-height: 1; padding: 0 2px; border-radius: 4px; } .search-help:hover { color: var(--text); background: var(--bg-hover); } .sr-help-pop { position: fixed; z-index: 10010; background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,.25); padding: 12px 14px; font-size: calc(12.5px * var(--text-scale, 1)); color: var(--text); } .sr-help-pop .sr-help-title { font-weight: 700; margin-bottom: 8px; } .sr-help-pop .sr-help-table { border-collapse: collapse; width: 100%; } .sr-help-pop .sr-help-table td { padding: 3px 8px 3px 0; vertical-align: top; } .sr-help-pop .sr-help-table td:first-child { white-space: nowrap; color: var(--accent); } .sr-help-pop code { background: var(--bg-muted); border-radius: 4px; padding: 1px 5px; font-family: ui-monospace, monospace; font-size: .95em; } .sr-help-pop .sr-help-foot { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); color: var(--text-faint); line-height: 1.45; } /* v2026.5.46 — en-tête « Planifiée (passé) » repliable */ .search-results .sr-head.sr-past-toggle { cursor: pointer; user-select: none; } .search-results .sr-head.sr-past-toggle:hover { background: var(--bg-hover); } /* v2026.5.46 — pied de liste : « N de plus » + barre de chargement du scroll */ .sr-loadmore { padding: 8px 12px 10px; text-align: center; } .sr-loadmore .sr-loadmore-hint { font-size: calc(11.5px * var(--text-scale, 1)); color: var(--text-faint); } .sr-loadmore .sr-loadmore-bar { position: relative; height: 3px; border-radius: 2px; background: rgba(128, 128, 128, 0.18); overflow: hidden; } .sr-loadmore .sr-loadmore-bar::before { content: ""; position: absolute; top: 0; bottom: 0; left: -40%; width: 40%; border-radius: 2px; background: var(--accent); animation: sr-loadmore-slide .9s linear infinite; } @keyframes sr-loadmore-slide { from { left: -40%; } to { left: 100%; } } .sr-field { display: flex; gap: 6px; font-size: calc(13px * var(--text-scale, 1)); line-height: 1.45; } .sr-field .sr-k { color: var(--text-faint); flex: 0 0 auto; min-width: 78px; } .sr-field .sr-v { color: var(--text); word-break: break-word; } .sr-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 5px; } .sr-btn { border: 1px solid var(--border); background: var(--bg-elevated); color: var(--text); border-radius: 6px; cursor: pointer; padding: 4px 9px; font-size: calc(12px * var(--text-scale, 1)); white-space: nowrap; } .sr-btn:hover { background: var(--accent-soft); border-color: var(--accent); } .sr-empty { padding: 12px 10px; color: var(--text-faint); font-size: calc(13px * var(--text-scale, 1)); text-align: center; } @media (max-width: 640px) { .search-zone { width: 94vw; } .sr-card { flex-direction: column; } .sr-actions { flex-direction: row; } } /* =========================================================================== v2026.5.46 — Ligne collante stats + recherche, vues classique/horizontale =========================================================================== */ /* Ligne stats + recherche, collée sous l'en-tête (topbar + bandeaux = --stack-top, calculé en JS) et visible au scroll. Vue classique : grille STATS (gauche) | RECHERCHE (centre) | colonne vide (équilibre le centrage). Vue horizontale : #topline masqué (stats + recherche relocalisés en sidebar, cf. plus bas). Règle UNIQUE — avant, 5 définitions empilées dont des couches mortes (breakpoint 760px neutralisé par !important) ; cascade finale reproduite à l'identique, sans les !important devenus inutiles. */ #topline { position: sticky; top: var(--stack-top, 56px); z-index: 9; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; justify-content: stretch; gap: 14px; padding: 6px 16px; background: var(--bg); border-bottom: 1px solid var(--border); } #topline > #stats { grid-column: 1; justify-self: start; min-width: 0; } #topline > #search-zone { grid-column: 2; justify-self: center; min-width: 0; } /* v2026.5.46 — bouton « Mes actions » coordinateur : 3e colonne, collé à droite. */ #topline > #topline-coord { grid-column: 3; justify-self: end; align-self: center; min-width: 0; } /* Étroit (≤ 680px) : tout passe en une colonne pleine largeur. */ @media (max-width: 680px) { #topline { grid-template-columns: 1fr; } #topline > #stats, #topline > #search-zone, #topline > #topline-coord { grid-column: 1; justify-self: stretch; } } #topline .stats { padding: 4px 0; } /* La barre n'occupe plus toute la largeur : elle se place à côté des stats. */ .search-zone { position: relative; width: min(46vw, 440px); margin: 0; flex: 0 1 440px; z-index: 40; } @media (max-width: 900px) { .search-zone { width: min(92vw, 440px); flex-basis: 100%; } } /* --- Vue horizontale : la barre vit dans la sidebar gauche --------------- */ /* #topline devient vide (stats + search relocalisés) → on le masque. */ html.view-horizontal #topline { display: none; } /* Dans la sidebar, la barre prend toute la largeur dispo. */ html.view-horizontal #search-zone { /* v2026.5.46 — même gabarit que les autres blocs de la sidebar : la zone est étirée à la largeur du contenu de la sidebar (la sidebar centre ses enfants → sans align-self stretch, l'input débordait du padding et la barre paraissait bord à bord, plus large que le reste). */ width: 100%; max-width: 100%; align-self: stretch; box-sizing: border-box; flex: none; margin: 4px 0; } /* Le popup de résultats s'affiche À DROITE (sur la zone planning), en fixed. La position fine (left/top/width/max-height) est posée en JS. */ html.view-horizontal #ev-search-results { position: fixed; left: auto; right: auto; top: auto; } /* --- Surbrillance d'une carte après clic « Jour ↗ » : règle UNIQUE consolidée plus bas (.iv-search-highlight). Les anciennes définitions, entièrement écrasées, ont été retirées. ------------------- */ /* --- Menu « Ajouter » ouvert à droite (positionné en fixed via JS) : passe au premier plan (z-index 1600, au-dessus du popup de recherche). : règle unique (un 2e bloc identique, sauf z-index, était défini plus bas). -------- */ .add-menu.add-menu--right { position: fixed; right: auto; /* left/top posés en JS */ z-index: 1600; } /* =========================================================================== v2026.5.46 — ajustements recherche (layout classique, barre horizontale, popup overlay, surbrillance persistante) =========================================================================== */ /* (Grille classique stats/recherche : voir la règle #topline consolidée plus haut.) */ /* Vue horizontale : la barre façon bouton de sidebar (rectangulaire, pleine largeur, sans ombre) est définie plus bas (avec box-sizing). Ici on ne garde que la taille de police des champs (non dupliquée). */ html.view-horizontal #search-zone .search-input, html.view-horizontal #search-zone .search-ico { font-size: 12px; } /* Vue horizontale : popup EN OVERLAY au-dessus des noms/interventions (position fine posée en JS : à droite de la sidebar, calé en haut). */ html.view-horizontal #ev-search-results { position: fixed; z-index: 1500; box-shadow: var(--shadow-hover); } /* (Surbrillance carte : voir la règle consolidée .iv-search-highlight.) */ /* =========================================================================== v2026.5.46 — popup recherche PORTALISÉ dans : position fixed + premier plan dans TOUTES les vues (coords posées en JS). Annule l'ancrage absolu de base (qui supposait le popup enfant de #search-zone). =========================================================================== */ #ev-search-results.search-results { position: fixed !important; inset: auto; /* annule left/right:0 + top:100% du style de base */ z-index: 1600; box-shadow: var(--shadow-hover); } /* Vue horizontale : la barre épouse vraiment le style des boutons de sidebar. (doublon exact « #search-zone { width/flex/margin } » retiré — déjà défini plus haut à l'identique.) */ html.view-horizontal #search-zone .search-bar { width: 100%; box-sizing: border-box; /* v2026.5.46 — même style que les autres blocs de la sidebar (.today-block : fond discret + bordure fine + rayon 8) au lieu du grand panneau clair qui tranchait. */ border-radius: 8px; padding: 5px 10px; background: var(--bg-muted); border: 1px solid var(--border); box-shadow: none; } /* (Sidebar horizontale collée sous l'en-tête + bandeaux session/EV/progress : versions finales dans le bloc ci-dessous. : couches mortes retirées — sidebar top écrasé par --stack-top ; bandeau top dupliqué (basé sur une variable jamais posée en JS) ; menu « Ajouter » droite dupliqué.) */ /* =========================================================================== v2026.5.46 — empilement collant propre : topbar → bandeaux → ligne stats+recherche (classique) / sidebar (horizontal). Plus de chevauchement. --stack-top = topbar + bandeaux visibles (calculé en JS). =========================================================================== */ .session-banner { top: var(--topbar-h, 56px) !important; /* juste sous la topbar */ } /* (.progress-bar n'est plus dans cette règle — elle se cale sous #topline, cf. sa règle de base avec top: --stack-top + --topline-h.) */ html.view-horizontal .horizontal-sidebar { top: var(--stack-top, 48px); max-height: calc(100vh - var(--stack-top, 48px)); } /* (Grille classique + breakpoint 680px : voir la règle #topline consolidée plus haut.) */ /* v2026.5.46 — le tooltip de survol passe AU-DESSUS du popup de recherche (z-index 1600) pour que le détail d'une intervention survolée dans les résultats s'affiche bien au premier plan. */ .tooltip { z-index: 1700 !important; } /* v2026.5.46 — mise en avant : le scrollIntoView ne place jamais la ligne sous l'en-tête collant (topbar + bandeaux + ligne stats/recherche). */ .intervention-v2, .iv-mini-card, .card { scroll-margin-top: calc(var(--stack-top, 56px) + var(--topline-h, 0px) + 12px); scroll-margin-bottom: 12px; } /* v2026.5.46 — surbrillance « mise en avant » TRÈS visible (et non rognée par un overflow ancêtre) : bordure + fond teinté + halo + pulse bref. Règle UNIQUE (anciennes définitions mortes retirées). */ .iv-search-highlight { outline: 3px solid var(--accent) !important; outline-offset: 1px; box-shadow: 0 0 0 4px var(--accent-soft), 0 0 16px 2px var(--accent) !important; border-radius: var(--radius); background: var(--accent-soft) !important; position: relative; z-index: 6; animation: iv-hl-pulse 0.9s ease-in-out 0s 3 !important; } @keyframes iv-hl-pulse { 0%, 100% { box-shadow: 0 0 0 4px var(--accent-soft), 0 0 16px 2px var(--accent); } 50% { box-shadow: 0 0 0 7px var(--accent-soft), 0 0 22px 5px var(--accent); } } /* v2026.5.46 — note d'info dans les résultats (ex. troncature à 15). */ .search-results .sr-note { margin: 4px 6px 2px; padding: 5px 8px; font-size: calc(11px * var(--text-scale, 1)); color: var(--text-faint); background: var(--bg-muted); border-radius: 6px; text-align: center; } /* ========================================================================== v2026.5.46 — VUE HORIZONTALE « ÉCHELLE DE TEMPS EXACTE » (Paramètres → Apparence, exclusive avec « Afficher les pauses ») La bande .iv-exact-row remplace les blocs Matin/Après-midi : chaque carte est posée en ABSOLU à son heure réelle (left/width en % de la plage affichée), les chevauchements s'empilent en sous-lignes (.iv-exact-lane, éléments en flux — la bande grandit avec ses sous-lignes). Le bandeau timeline est masqué : les cartes portent le temps elles-mêmes, d'où le fond PLEIN aux couleurs de catégorie (l'info dominante demandée). La règle des heures est rendue une seule fois en tête de liste (sticky), des lignes de grille par heure traversent chaque bande pour l'alignement. ========================================================================== */ /* Le bandeau timeline disparaît : les cartes remplacent ses segments. */ html.view-horizontal.horizontal-exact .timeline { display: none !important; } /* Filet : si un rendu flux traînait encore (le toggle re-rend normalement). */ html.view-horizontal.horizontal-exact .iv-mini-cards { display: none !important; } /* La bande vide + le clic droit remplacent le message « Pas d'intervention ». */ html.view-horizontal.horizontal-exact .card-empty { display: none !important; } /* ── La bande continue ──────────────────────────────────────────────────── */ .iv-exact-row { display: none; } /* invisible hors vue horizontale */ html.view-horizontal .iv-exact-row { display: block; position: relative; /* v2026.5.46 — hauteur AUTO : la bande empile de vraies sous-lignes en flux (une par niveau de chevauchement) → la sous-ligne du dessous est toujours entière, la zone s'agrandit d'elle-même. */ margin: 2px 14px 3px 14px; min-width: 0; } /* Sous-ligne du mode exact : hauteur calée sur la taille de texte configurée. Transparente aux clics (les trous DERRIÈRE restent survolables / cliquables au clic droit), les cartes se réactivent. */ /* v2026.5.46 — mode exact : la carte tech est centrée verticalement, la bande des bulles n'est plus collée en haut avec du vide dessous (le vide venait du header nom+pills, plus haut que la bande). */ html.view-horizontal.horizontal-exact .card { align-items: center !important; } /* v2026.5.46 — cartes POMPIER : la bandelette « En pompier du … » reste FIGÉE EN HAUT (taille inchangée), mais les bulles sont CENTRÉES dans l'espace restant, comme sur les autres cartes (avant : bulles collées en haut avec du vide dessous). Le body est étiré (stretch), la note reste en tête, la bande se centre via margin auto. */ html.view-horizontal.horizontal-exact .card.is-pompier { align-items: stretch !important; } html.view-horizontal.horizontal-exact .card.is-pompier .card-body { justify-content: flex-start; } html.view-horizontal.horizontal-exact .card.is-pompier .iv-exact-row { margin-top: auto !important; margin-bottom: auto !important; } html.view-horizontal.horizontal-exact .card-tech-name { -webkit-line-clamp: 2; } html.view-horizontal .iv-exact-lane { position: relative; /* v2026.5.46 — hauteur JUSTE pour le contenu (réf + ville + adresse ~ 38 px). 60 px laissait trop de vide sur une bulle seule. Toutes les sous-lignes gardent la MÊME hauteur → un conflit empile 2 bulles identiques et n'agrandit que la zone du tech concerné. */ height: calc(74px * var(--text-scale, 1)); pointer-events: none; } html.view-horizontal .iv-exact-lane + .iv-exact-lane { margin-top: 2px; } /* ── Lignes d'heures CONTINUES (v2026.5.46) : calque global posé dans #cards par _layoutExactGrid, top/height en px (inline). Traversent tout le tableau, DEVANT les cartes (z-index 3) mais subtiles ; midi renforcé. ──── */ .exact-grid-vline { position: absolute; width: 1px; background: var(--border); opacity: 0.5; pointer-events: none; /* v2026.5.46 — z-index 0 : DEVANT le fond des cartes (visible dans les zones libres) mais DERRIÈRE les bulles (z-index 1, opaques) → un trait ne barre plus une intervention, il passe dessous. */ z-index: 0; } /* v2026.5.46 — heures pleines = trait NET continu ; demi-heures (h30) = trait POINTILLÉ discret, pour distinguer les deux d'un coup d'oeil. */ .exact-grid-vline { opacity: 0.55; } .exact-grid-vline-half { background: repeating-linear-gradient( to bottom, var(--border) 0 3px, transparent 3px 7px); opacity: 0.7; } /* v2026.5.46 — VRAIE séparation matin/après-midi : même césure que le midi de l'ancienne timeline (trait central + rayures diagonales), pleine hauteur de bande, PAR-DESSUS les cartes qui chevauchent midi (z-index 2, comme la timeline le faisait) mais transparente aux clics. */ .exact-grid-vline-noon { width: 6px; transform: translateX(-3px); /* centrer la bande sur 12h */ background: linear-gradient(to right, transparent 0%, transparent 30%, var(--text) 30%, var(--text) 70%, transparent 70%, transparent 100%), repeating-linear-gradient( 45deg, var(--bg-muted) 0 3px, var(--text-muted) 3px 4px ); /* v2026.5.46 — sur toute la hauteur du tableau (et non plus une bande de 48 px) : opacité réduite pour rester marqué sans faire un mur. */ opacity: 0.6; /* v2026.5.46 — derrière les bulles, comme les autres traits. */ z-index: 0; } /* ── Trous : zones invisibles survolables/cliquables (popover « Libre », clic droit → Réservation/Absence, surbrillance bidirectionnelle) ───── */ .iv-exact-hole { position: absolute; top: 0; bottom: 0; /* v2026.5.49 — le créneau libre se VOIT sans qu'il faille le survoler : voile clair sur la bande en retrait, contour tireté discret. Même langage que les pauses de la vue classique (clair + tireté = disponible). */ background: var(--iv-libre); border: 1px dashed var(--border-strong); border-radius: 4px; cursor: pointer; z-index: 0; } .iv-exact-hole:hover, .iv-exact-hole.highlight { /* v2026.5.49 — le survol reste plus marqué que l'état de repos, qui n'est plus transparent : teinte d'accent au lieu d'un gris. */ background: var(--accent-soft); outline: 1px dashed var(--accent, #3b82f6); outline-offset: -2px; } /* v2026.5.47 — surbrillance de survol : la ZONE (1 h par défaut) qu'un clic droit poserait en rendez-vous/absence, calée sous la souris à l'heure snappée (créneau libre, mode heure exacte). Plus marquée que le survol gris du trou, pour qu'on VOIE clairement la zone visée. Purement visuelle. */ .iv-exact-selpreview { position: absolute; top: 0; bottom: 0; pointer-events: none; z-index: 0; box-sizing: border-box; border-radius: 4px; border: 2px solid var(--accent, #3b82f6); background: rgba(94, 168, 232, 0.28); background: color-mix(in srgb, var(--accent, #3b82f6) 28%, transparent); } /* v2026.5.47 — bulle « temps disponible » FIXE en haut de la bande (elle ne suit PLUS la souris) : plage libre du créneau survolé, ou avertissement rouge si erreur. Seule la zone de sélection (.iv-exact-selpreview) suit le curseur. */ .iv-exact-selinfo { position: absolute; top: 3px; left: 50%; transform: translateX(-50%); white-space: nowrap; text-align: center; line-height: 1.25; font-size: calc(11px * var(--text-scale, 1)); padding: 3px 10px; border-radius: 6px; background: var(--bg-elevated, #21242b); color: var(--text, #e8eaed); border: 1px solid var(--accent, #3b82f6); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); pointer-events: none; z-index: 3; } .iv-exact-selinfo-main { font-weight: 700; } .iv-exact-selinfo-sub { font-size: 0.9em; opacity: 0.8; font-weight: 500; } .iv-exact-selinfo.danger .iv-exact-selinfo-sub { opacity: 0.95; } /* v2026.5.47 — heure choisie (début–fin) affichée DANS le rectangle de sélection mobile ; suit le curseur, lisible sur le fond translucide. v2026.5.47 — placée EN BAS du rectangle (la bulle fixe est en haut) → la bulle ne cache jamais l'heure quand la souris passe dessous. */ .iv-exact-selpreview-hour { position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%); white-space: nowrap; font-size: calc(10.5px * var(--text-scale, 1)); font-weight: 700; color: #ffffff; pointer-events: none; /* v2026.5.49 — la zone est désormais bornée au prochain rendez-vous : sur un créneau de 30 min l'heure déborde du rectangle. Pastille opaque (au lieu d'un simple texte ombré) pour qu'elle reste lisible hors de la zone, sur les hachures d'un créneau libre comme sur le fond de la bande. */ padding: 1px 5px; border-radius: 5px; background: var(--accent, #3b82f6); } .iv-exact-selpreview.danger .iv-exact-selpreview-hour { background: #b03030; } /* v2026.5.47 — un rendez-vous en ERREUR (début == fin) tombe dans la zone qu'on créerait : surbrillance ET bulle passent en rouge (le clic droit est par ailleurs bloqué, cf. disabledReason). */ .iv-exact-selpreview.danger { border-color: #b03030; background: rgba(176, 48, 48, 0.30); background: color-mix(in srgb, #b03030 32%, transparent); } .iv-exact-selinfo.danger { background: #b03030; color: #ffffff; border-color: #7a1f1f; } /* ── Les cartes posées (héritent de .iv-mini-card : anatomie, conflits rouges !important, liseré clos/résolu, tooltips, reschedule) ────────── */ html.view-horizontal .iv-exact-card { /* v2026.5.46 — BLINDÉ (!important) : la règle du liseré des cartes terminées (html.view-horizontal .iv-mini-card.status-closed/.status- resolved { position: relative }) est PLUS spécifique et écrasait le positionnement des cartes ✓✓ du mode exact → cartes en escalier, à la hauteur de leur contenu (petites/énormes), trous recouverts. position, ancres et hauteur sont désormais verrouillées pour TOUTES les cartes du mode exact, quel que soit leur statut. */ position: absolute !important; /* v2026.5.46 — la bulle remplit sa zone (0 px haut/bas) → plus grande ; le léger contour (border 1px) sépare toujours de la voisine. */ top: 0 !important; bottom: 0 !important; height: auto !important; pointer-events: auto; flex: none; /* v2026.5.47 — largeur MINIMALE visible : une intervention très courte (1-5 min) ou à durée nulle (10:00→10:00) a une largeur proportionnelle quasi nulle → elle disparaissait. Ce plancher la garde visible (très fine), sans élargir les bulles normales (plus larges que ce plancher). */ min-width: 6px; z-index: 1; container-type: inline-size; border-radius: 4px; overflow: hidden; /* v2026.5.46 — fond NEUTRE (fini le fond plein couleur) : seul le bord gauche (poignée/barre) porte la couleur de catégorie. Texte et coches repassent donc en couleurs normales, comme les mini-cards. */ background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border); } html.view-horizontal .iv-exact-card:hover { background: var(--bg-hover); border-color: var(--border-strong); } /* v2026.5.46 — BORD GAUCHE = POIGNÉE de déplacement, à la COULEUR de la catégorie : c'est la zone de manipulation (CLIC = fenêtre de modification, GLISSER = drag & drop). Motif de points = « saisissable ». Toujours présente, même sur une carte étroite. */ html.view-horizontal .iv-exact-card .iv-exact-handle { flex: 0 0 14px; align-self: stretch; cursor: grab; background-color: var(--c-autre); background-image: radial-gradient(circle, rgba(0, 0, 0, 0.42) 1px, transparent 1.4px); background-size: 4px 6px; background-position: center; } html.view-horizontal .iv-exact-card.color-livraison .iv-exact-handle { background-color: var(--c-livraison); } html.view-horizontal .iv-exact-card.color-installation .iv-exact-handle { background-color: var(--c-installation); } html.view-horizontal .iv-exact-card.color-recup .iv-exact-handle { background-color: var(--c-recup); } html.view-horizontal .iv-exact-card.color-remplacement .iv-exact-handle { background-color: var(--c-remplacement); } html.view-horizontal .iv-exact-card.color-incident .iv-exact-handle { background-color: var(--c-incident); } html.view-horizontal .iv-exact-card.color-rollout .iv-exact-handle { background-color: var(--c-rollout); } html.view-horizontal .iv-exact-card.color-reservation .iv-exact-handle { background-color: var(--c-reservation); } html.view-horizontal .iv-exact-card.color-autre .iv-exact-handle { background-color: var(--c-autre); } html.view-horizontal .iv-exact-card .iv-exact-handle:hover { filter: brightness(1.15); } html.view-horizontal .iv-exact-card .iv-exact-handle:active { cursor: grabbing; } /* v2026.5.46 — poignée VERROUILLÉE (faites/clôturées, réservations, absences) : même look à points colorés, mais pas de déplacement. */ html.view-horizontal .iv-exact-card .iv-exact-handle-locked { cursor: default; } /* v2026.5.46 — texte resserré verticalement pour des bulles plus basses (padding vertical 4→2 px) sans toucher au contenu. */ html.view-horizontal .iv-exact-card .iv-mini-card-text { padding: 2px 6px; } /* v2026.5.46 — respiration : le n° d'intervention est détaché du bloc ville+adresse (qui, eux, restent collés via le gap serré de la carte). */ html.view-horizontal .iv-exact-card .iv-mini-card-text .iv-mini-ref, html.view-horizontal .iv-exact-card .iv-mini-card-text .iv-mini-reservation-label { margin-bottom: 8px; } /* v2026.5.46 — l'intitulé « Réservation » adopte le même gabarit que le numéro d'intervention (gras, taille, en tête de bulle). */ html.view-horizontal .iv-exact-card .iv-mini-card-text .iv-mini-reservation-label { font-weight: 700; font-size: calc(11px * var(--text-scale)); letter-spacing: 0.2px; } /* Cartes NON déplaçables (faites/closes, réservation, absence) : bord gauche coloré DÉCORATIF (la barre reprend la couleur de catégorie via les règles .iv-mini-card.color-xxx .iv-mini-card-bar), un peu élargi pour l'harmonie. */ html.view-horizontal .iv-exact-card .iv-mini-card-bar { flex: 0 0 6px; } /* Coche ✓/✓✓ : couleur normale (vert clos/résolu) sur le fond neutre. */ html.view-horizontal .iv-exact-card .iv-mini-status-check { top: 2px !important; right: 4px !important; } /* v2026.5.46 — texte COMPACT des cartes étroites : ville en gras + fin d'adresse (« Université 5 »), jamais une carte vide au-dessus de ~36 px. Ellipsis par ligne si ça déborde ; détail complet au survol. */ html.view-horizontal .iv-exact-card .iv-mini-card-text-mini { display: none; flex: 1 1 auto; min-width: 0; flex-direction: column; justify-content: center; align-items: center; gap: 0; padding: 1px 3px; overflow: hidden; } html.view-horizontal .iv-exact-card .iv-mini-card-text-mini .iv-mini-line { font-size: calc(9.5px * var(--text-scale, 1)); line-height: 1.15; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } html.view-horizontal .iv-exact-card .iv-mini-card-text-mini .iv-mini-mini-ville { font-weight: 700; } /* Cartes étroites : bascule texte NORMAL → texte COMPACT → couleur seule (sous ~36 px). La poignée, elle, reste toujours attrapable. */ @container (max-width: 100px) { html.view-horizontal .iv-exact-card .iv-mini-card-text { display: none; } html.view-horizontal .iv-exact-card .iv-mini-card-text-mini { display: flex; } } /* v2026.5.46 — sous ~58 px, on sacrifie la VILLE mais on garde la fin d'adresse (« Mornex 32 ») le plus longtemps possible ; on ne masque tout que sur une carte vraiment minuscule. */ @container (max-width: 58px) { html.view-horizontal .iv-exact-card .iv-mini-card-text-mini .iv-mini-mini-ville { display: none; } } @container (max-width: 22px) { html.view-horizontal .iv-exact-card .iv-mini-card-text-mini { display: none; } } /* ── Règle des heures (une seule, sticky en tête de liste) ──────────────── */ .exact-hours-ruler { display: none; } html.view-horizontal.horizontal-exact .exact-hours-ruler { display: block; position: sticky; /* v2026.5.47 — top suit --stack-top : la règle des heures reste collée en haut au scroll, ET passe SOUS la banderole de session/reconnexion quand il y en a une (au lieu de disparaître derrière), 0 sinon. (--stack-top = topbar masquée en horizontal + hauteur des banderoles visibles ; corrigé depuis des ajustements précédents, il ne sort plus la règle de l'écran.) */ top: var(--stack-top, 0px); z-index: 20; background: var(--bg); /* v2026.5.46 — l'alignement n'est PLUS un calcul théorique (faux au pixel selon zoom/taille de texte/scrollbar) : _alignExactRuler() mesure la première bande au runtime et pose margin-left/right sur la zone des ticks (les marges CSS ci-dessous ne servent qu'au premier paint). */ padding: 4px 0; border-bottom: 1px solid var(--border); } html.view-horizontal.horizontal-exact .exact-ruler-scale { position: relative; height: 16px; margin: 0 15px 0 196px; /* premier paint ; remplacé par la mesure runtime */ } .exact-ruler-tick { position: absolute; top: 0; transform: translateX(-50%); font-size: calc(12px * var(--text-scale, 1)); font-weight: 600; line-height: 16px; color: var(--text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; } .exact-ruler-tick-noon { color: var(--text); font-weight: 700; } /* ── Ligne « maintenant » UNIQUE (v2026.5.46) et marqueur de drop ──── */ /* Une seule ligne continue posée dans #cards (position:relative requis), du haut de la liste au bas de la dernière carte ; la tête ronde vit dans la zone des ticks de la règle (elle suit le sticky au scroll). */ html.view-horizontal.horizontal-exact .cards { position: relative; } .exact-now-global { position: absolute; width: 2px; background: #ff3030; box-shadow: 0 0 4px rgba(255, 48, 48, 0.55); z-index: 4; pointer-events: none; } /* v2026.5.46 — .exact-now-head retirée (plus de point sur la règle). */ .reschedule-timeline-mark.reschedule-exact-mark { border-radius: 4px; } /* ── Puces de variables du modèle d'email (v2026.5.48) ─────────────── */ /* Interactives : grossissent au survol, s'enfoncent au clic. Le style de base vit ici (plus d'inline) pour que :hover et :active puissent surcharger la bordure. */ .admin-var-chip { font-family: monospace; font-size: 12px; padding: 2px 8px; align-self: flex-start; border: 1px solid var(--border, #888); border-radius: 6px; background: transparent; color: inherit; cursor: pointer; transition: transform 0.09s ease, border-color 0.09s ease, background-color 0.09s ease; } .admin-var-chip:hover { transform: scale(1.07); border-color: var(--accent, #4a90d9); background: rgba(127, 127, 127, 0.12); } .admin-var-chip:active { transform: scale(0.92); }