/** * Planification — Extension navigateur EasyVista (Canton de Vaud / DGNSI) * * Copyright (c) 2026 Quentin Rouiller * Licensed under the MIT License — see LICENSE file in the project root. * * @author Quentin Rouiller * @repository https://gitea.netaplaid.ch/FroSteel/Planification */ /* ========================================================================== Thème clair (défaut) ========================================================================== */ :root { --bg: #f4f5f7; --bg-elevated: #ffffff; --bg-muted: #f0f1f3; --bg-hover: #f7f8fa; --border: #e2e4e8; --border-strong: #cfd3da; --text: #1a1f2b; --text-muted: #2e3642; /* v2026.5.29 : +contraste (était #4a5260) */ --text-faint: #50596a; /* v2026.5.29 : +contraste (était #6c7583) */ --accent: #0f4f8b; --accent-soft: #e1ecf7; --danger: #b03030; --danger-soft: #fbe6e6; --warn: #b87a00; --warn-soft: #fff2d6; --ok: #2e7b4a; --ok-soft: #dff0e4; /* Palette par type d'intervention */ --c-livraison: #2563eb; --c-livraison-soft: #dbeafe; --c-recup: #16a34a; --c-recup-soft: #dcfce7; --c-remplacement: #ea580c; --c-remplacement-soft: #fed7aa; --c-incident: #8b5cf6; --c-incident-soft: #ede9fe; --c-installation: #2563eb; --c-installation-soft: #dbeafe; --c-rollout: #92400e; /* brun */ --c-rollout-soft: #fde68a; --c-reservation: #f59e0b; /* jaune/ambre */ --c-reservation-soft: #fef3c7; --c-autre: #6b7280; --c-autre-soft: #e5e7eb; /* Statuts clos */ --c-closed: #15803d; /* vert foncé = Clôturé */ --c-closed-soft: #bbf7d0; --c-resolved: #4ade80; /* vert clair = Résolu */ --c-resolved-soft: #dcfce7; --shadow: 0 1px 3px rgba(20, 30, 50, 0.06), 0 1px 2px rgba(20, 30, 50, 0.04); --shadow-hover: 0 2px 8px rgba(20, 30, 50, 0.08); --radius: 8px; --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; --mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace; } [data-theme="dark"] { --bg: #16181d; --bg-elevated: #21242b; --bg-muted: #1c1f25; --bg-hover: #2a2e36; --border: #2e333c; --border-strong: #414754; --text: #e6e8ec; --text-muted: #d0d5de; /* v2026.5.29 : +contraste (était #b8c0cc) — quasi blanc */ --text-faint: #a8b0bc; /* v2026.5.29 : +contraste (était #8b93a0) */ --accent: #5ea8e8; --accent-soft: #223348; --danger: #e87878; --danger-soft: #3b2626; --warn: #d9a753; --warn-soft: #3a2e1a; --ok: #78c59a; --ok-soft: #1f3a2b; --c-livraison: #60a5fa; --c-livraison-soft: #1e3a5f; --c-recup: #4ade80; --c-recup-soft: #14432a; --c-remplacement: #fb923c; --c-remplacement-soft: #4a2512; --c-incident: #a78bfa; --c-incident-soft: #2e1065; --c-installation: #60a5fa; --c-installation-soft: #1e3a5f; --c-autre: #9ca3af; --c-autre-soft: #2a2e36; --c-closed: #22c55e; --c-closed-soft: #14432a; --c-resolved: #86efac; --c-resolved-soft: #0f3320; --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); --shadow-hover: 0 2px 10px rgba(0, 0, 0, 0.4); } /* ========================================================================== Base ========================================================================== */ * { box-sizing: border-box; } html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--font); font-size: 14px; line-height: 1.5; } .hidden { display: none !important; } /* ========================================================================== Topbar ========================================================================== */ .topbar { position: sticky; top: 0; z-index: 10; display: flex; justify-content: space-between; align-items: center; padding: 10px 20px; background: var(--bg-elevated); 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. */ html.view-classic .topbar-left #user-badge { order: 1; } html.view-classic .topbar-left #app-title { order: 2; } html.view-classic .topbar-left .date-nav { order: 3; } html.view-classic .topbar-left .capture-info { order: 4; } html.view-classic .topbar-left #refresh-check { order: 5; } .topbar h1 { margin: 0; font-size: 18px; font-weight: 600; color: var(--text); white-space: nowrap; } .capture-info { font-size: 12px; color: var(--text-muted); white-space: nowrap; } .refresh-check { font-size: 14px; 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 */ .session-banner { position: sticky; top: 56px; z-index: 8; display: flex; align-items: center; gap: 12px; padding: 12px 18px; /* v4.2.5 : rouge plus vif + bord plus épais pour visibilité max */ background: linear-gradient(90deg, #c93030, #d84848); color: #fff; border-top: 2px solid #ff6060; border-bottom: 2px solid #7a1515; font-size: 14px; font-weight: 500; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); /* petite animation d'apparition pour attirer l'œil */ animation: session-banner-in 0.22s ease-out; } @keyframes session-banner-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } } /* v4.2.5 : variante ORANGE pour "EV inaccessible" (distinct de session expirée) */ .session-banner.ev-banner { background: linear-gradient(90deg, #c77920, #e09a3a); border-top: 2px solid #ffbb60; border-bottom: 2px solid #7a4a15; } .session-banner.ev-banner .btn-primary { color: #8a4a10; } .session-banner.hidden { display: none; } .session-banner-icon { font-size: 20px; flex-shrink: 0; } .session-banner-text { flex: 1; line-height: 1.4; } .session-banner-text strong { font-weight: 600; } .session-banner .btn-primary { background: #fff; color: #9a2020; border: 0; font-weight: 600; } .session-banner .btn-primary:hover { background: #f0f0f0; } .session-banner .btn-sm { padding: 5px 12px; font-size: 12px; /* v4.2.5 : btn-sm non-primary dans la bannière = contour blanc */ background: transparent; color: #fff; border: 1px solid rgba(255, 255, 255, 0.5); font-weight: 500; } .session-banner .btn-sm:hover { background: rgba(255, 255, 255, 0.12); } .session-banner .btn-primary.btn-sm { /* reset : le primary override le style du btn-sm */ background: #fff; color: #9a2020; border: 0; font-weight: 600; } .session-banner.ev-banner .btn-primary.btn-sm { color: #8a4a10; } .session-banner .btn-icon { background: transparent; color: #fff; border: 0; font-size: 20px; line-height: 1; padding: 4px 8px; cursor: pointer; } .session-banner .btn-icon:hover { background: rgba(255,255,255,0.15); } /* Barre de progression pendant le rafraichissement — v4.1.12 : texte toujours lisible, que la zone verte l'ait atteint ou non (utilise mix-blend-mode:difference pour inverser la couleur du texte là où la barre verte est dessous). */ .progress-bar { position: sticky; top: 56px; z-index: 9; height: 22px; /* v4.1.17 : backdrop-blur sur toute la barre → ce qui défile derrière est légèrement flouté sur TOUTE la largeur. Pas d'opacité sombre ajoutée, transparence préservée. */ background: rgba(128, 128, 128, 0.08); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.2)); overflow: hidden; } .progress-bar.hidden { display: none; } .progress-bar-fill { position: absolute; left: 0; top: 0; bottom: 0; background: linear-gradient(90deg, #2ea043, #3fb950); width: 0%; transition: width 240ms ease-out; box-shadow: 0 0 8px rgba(63, 185, 80, 0.3); } .progress-bar-label { position: relative; display: block; text-align: center; line-height: 22px; font-size: 12px; font-weight: 700; color: #fff; pointer-events: none; letter-spacing: 0.3px; /* v4.1.14/17 : text-shadow multi-directionnel (halo sombre autour du texte). Le backdrop-blur est sur toute la barre, plus besoin de pill. */ text-shadow: 0 0 2px rgba(0, 0, 0, 0.95), 0 0 3px rgba(0, 0, 0, 0.85), 0 1px 2px rgba(0, 0, 0, 0.75), 0 -1px 2px rgba(0, 0, 0, 0.75), 1px 0 2px rgba(0, 0, 0, 0.75), -1px 0 2px rgba(0, 0, 0, 0.75); z-index: 2; } /* Navigation de date */ .date-nav { display: flex; align-items: center; gap: 4px; flex-wrap: nowrap; } /* v2026.5.17 : faux input date custom avec nom du jour */ .date-custom-wrapper { position: relative; display: inline-flex; align-items: center; } .date-custom { display: inline-flex; align-items: center; gap: 8px; padding: 5px 10px 5px 12px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-muted); color: var(--text); font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer; white-space: nowrap; user-select: none; transition: border-color 0.15s, background 0.15s; } .date-custom:hover { border-color: var(--border-strong); background: var(--bg-hover); } .date-custom:focus { outline: 2px solid var(--accent); outline-offset: -1px; } .date-custom-icon { font-size: 13px; opacity: 0.7; } .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; } .btn-today { padding: 6px 10px; font-size: 12px; } .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: 14px; } .error-box { margin: 20px; padding: 14px 18px; background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger); border-radius: var(--radius); font-size: 14px; line-height: 1.55; } .session-needed { max-width: 500px; margin: 60px auto; padding: 28px 32px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); text-align: center; } .session-needed h2 { margin: 0 0 12px 0; color: var(--text); } .session-needed p { margin: 10px 0; color: var(--text-muted); } .session-needed code { background: var(--bg-muted); padding: 2px 6px; border-radius: 3px; font-family: var(--mono); font-size: 12px; } .session-needed button { margin-top: 14px; } /* ========================================================================== Stats globales ========================================================================== */ .stats { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; padding: 12px 20px 4px 20px; font-size: 13px; color: var(--text-muted); } .global-stat b { color: var(--text); font-weight: 600; } .global-stat-main b { font-size: 16px; } .global-stat-sub { color: var(--text-faint); font-size: 12px; } .global-stat-sep { color: var(--text-faint); opacity: 0.5; } /* ========================================================================== Grille de cartes ========================================================================== */ .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 14px; padding: 14px 20px 40px 20px; } .card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; display: flex; flex-direction: column; } .card-header { padding: 10px 14px; border-bottom: 1px solid var(--border); background: var(--bg-muted); display: flex; justify-content: space-between; align-items: center; gap: 10px; } .card-tech-name { font-weight: 600; font-size: 14px; 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; } .badge-pompier { background: var(--danger-soft); color: var(--danger); } .badge-absent { background: var(--bg-muted); color: var(--text-faint); border: 1px solid var(--border); } .badge-count { background: var(--accent-soft); color: var(--accent); } .card-body { padding: 0; flex: 1; } .card-empty { padding: 14px; color: var(--text-faint); font-size: 13px; font-style: italic; text-align: center; } .card.is-pompier { border-left: 3px solid var(--danger); } .card.is-absent { opacity: 0.85; } .card.is-absent .card-header { background: var(--bg); } /* ========================================================================== Frise de temps ========================================================================== */ .timeline { padding: 12px 14px 6px 14px; background: var(--bg-muted); border-bottom: 1px solid var(--border); position: relative; } .timeline-pompier { background: var(--danger-soft); } .timeline-bar { position: relative; height: 20px; background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 3px; overflow: hidden; } .timeline-hole { position: absolute; top: 0; bottom: 0; background: repeating-linear-gradient( 45deg, transparent 0 4px, rgba(0, 0, 0, 0.035) 4px 8px ); cursor: help; transition: background 0.1s; } [data-theme="dark"] .timeline-hole { background: repeating-linear-gradient( 45deg, transparent 0 4px, rgba(255, 255, 255, 0.04) 4px 8px ); } .timeline-hole:hover { background: var(--ok-soft); } .timeline-slot { position: absolute; top: 0; bottom: 0; cursor: help; transition: filter 0.1s; border-right: 1px solid var(--bg-elevated); } .timeline-slot.color-livraison { background: var(--c-livraison); } .timeline-slot.color-installation { background: var(--c-installation); } .timeline-slot.color-recup { background: var(--c-recup); } .timeline-slot.color-remplacement { background: var(--c-remplacement); } .timeline-slot.color-incident { background: var(--c-incident); } .timeline-slot.color-rollout { background: var(--c-rollout); } .timeline-slot.color-reservation { background: var(--c-reservation); } .timeline-slot.color-autre { background: var(--c-autre); } /* Statuts clos sur la timeline */ .timeline-slot.status-closed { background: var(--c-closed); } .timeline-slot.status-resolved { background: 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: 10px; 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; } /* ========================================================================== Interventions — layout v2 (heures verticales) ========================================================================== */ .intervention-v2 { display: grid; grid-template-columns: 4px 58px 1fr auto; grid-template-rows: auto auto; /* v4.1.17 : la ligne du bas (right) s'étend maintenant sur les 2 colonnes droite (right + status) pour que la signature aille vraiment jusqu'au bord droit. Le ✓ status est positionné en absolute par-dessus. */ grid-template-areas: "dot time ref copy" "dot time right right"; gap: 2px 10px; align-items: start; padding: 10px 12px 12px 8px; border-top: 1px solid var(--border); cursor: default; transition: background 0.08s; position: relative; } .intervention-v2:first-child { border-top: none; } .intervention-v2:hover { background: var(--bg-hover); } /* Pastille colorée (barre verticale) */ .intervention-v2 .intervention-dot { grid-area: dot; width: 4px; height: 100%; border-radius: 2px; align-self: stretch; } .intervention-v2.color-livraison .intervention-dot { background: var(--c-livraison); } .intervention-v2.color-installation .intervention-dot { background: var(--c-installation); } .intervention-v2.color-recup .intervention-dot { background: var(--c-recup); } .intervention-v2.color-remplacement .intervention-dot { background: var(--c-remplacement); } .intervention-v2.color-incident .intervention-dot { background: var(--c-incident); } .intervention-v2.color-rollout .intervention-dot { background: var(--c-rollout); } .intervention-v2.color-reservation .intervention-dot { background: var(--c-reservation); } .intervention-v2.color-autre .intervention-dot { background: var(--c-autre); } .intervention-v2.clickable { cursor: pointer; } .intervention-v2.clickable:active { transform: translateY(1px); } .intervention-v2.status-closed { background: var(--c-closed-soft); box-shadow: inset 4px 0 0 var(--c-closed); } .intervention-v2.status-closed:hover { background: var(--c-closed-soft); filter: brightness(0.96); } .intervention-v2.status-closed .intervention-dot { background: var(--c-closed); width: 5px; } .intervention-v2.status-resolved { background: var(--c-resolved-soft); box-shadow: inset 4px 0 0 var(--c-resolved); } .intervention-v2.status-resolved:hover { background: var(--c-resolved-soft); filter: brightness(0.96); } .intervention-v2.status-resolved .intervention-dot { background: var(--c-resolved); width: 5px; } /* v4.2.5 : statut "terminée par le tech" (commentaire LOGIN: détecté). Vert PLUS CLAIR que status-closed (distinction visuelle du ✓ simple vs ✓✓ double). */ .intervention-v2.status-terminated { background: var(--c-recup-soft, rgba(63, 185, 80, 0.12)); box-shadow: inset 4px 0 0 var(--c-recup, #3fb950); } .intervention-v2.status-terminated:hover { background: var(--c-recup-soft, rgba(63, 185, 80, 0.12)); filter: brightness(0.96); } .intervention-v2.status-terminated .intervention-dot { background: var(--c-recup, #3fb950); width: 5px; } .intervention-v2.status-terminated .iv-status-check { color: var(--c-recup, #3fb950); } .timeline-slot.status-terminated { background: var(--c-recup, #3fb950); } /* 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); } /* 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: 14px; 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 pour ne pas se chevaucher */ .intervention-v2.status-closed .iv-signature, .intervention-v2.status-resolved .iv-signature { padding-right: 22px; } /* Réservation (créneau bloqué par un coordinateur) */ .iv-ref-header.is-reservation-title { color: var(--c-reservation); font-family: var(--font); letter-spacing: 0.02em; /* v5.0.15 : étendre le titre sur toute la largeur de la carte pour le vrai centrage (sinon il n'est centré que dans sa colonne grid) */ grid-column: 1 / -1; text-align: center; padding-left: 62px; /* compense la colonne time (58px + gap) */ padding-right: 0; } /* v5.0.15 : absence partielle (demi-journée) affichée comme une row */ .iv-ref-header.is-absence-title { color: var(--c-absence, #a0a8b2); font-family: var(--font); letter-spacing: 0.02em; grid-column: 1 / -1; text-align: center; padding-left: 62px; padding-right: 0; } .intervention-v2.color-absence .intervention-dot { background: var(--c-absence, #2a2f36); } .iv-reservation-par { font-size: 13px; font-weight: 600; color: var(--text); } .iv-reservation-sujet { font-size: 12.5px; color: var(--text-muted); font-style: italic; } /* v2026.5.38 : anciens styles .intervention (layout v1) supprimés — le HTML ne génère plus que .intervention-v2 depuis longtemps. */ /* ========================================================================== Tooltip ========================================================================== */ .tooltip { position: fixed !important; /* v4.2.4 : forcer un stacking context propre et l'isolation pour que le tooltip ne soit pas affecté par un éventuel filter/transform/contain sur un ancêtre (qui casserait position:fixed). `contain: layout` et `will-change: transform` garantissent aussi que le navigateur traite ce tooltip indépendamment. */ isolation: isolate; contain: layout; z-index: 100; max-width: 620px; max-height: calc(100vh - 40px); overflow-y: auto; padding: 12px 14px; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border-strong); border-radius: 8px; box-shadow: var(--shadow-hover); font-size: 13px; line-height: 1.5; pointer-events: none; opacity: 0; transition: opacity 0.1s; user-select: text; -webkit-user-select: text; } .tooltip.visible { opacity: 1; /* v4.1.10 : permet à la souris d'entrer dans la bulle pour la garder visible (persistance au hover) et, en mode pinned, pour sélectionner. */ pointer-events: auto; /* v4.2 : curseur texte par défaut (pour signaler que c'est sélectionnable) */ cursor: text; } .tooltip.pinned { /* Bulle épinglée : bordure verte pour indiquer le mode */ border-color: var(--c-accent, #3fb950); box-shadow: 0 0 0 2px rgba(63, 185, 80, 0.15), var(--shadow-hover); } /* v4.1.13/14 : barre d'actions en haut à droite de la bulle (recharger cette iv + épingler) */ .tooltip-actions { position: absolute; top: 6px; right: 6px; display: flex; gap: 2px; z-index: 5; } .tooltip-actionbtn { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 4px; font-size: 13px; opacity: 0.55; transition: opacity 0.15s, background 0.15s, transform 0.15s; user-select: none; color: var(--text-muted); } .tooltip-actionbtn svg { width: 14px; height: 14px; } .tooltip-actionbtn:hover { opacity: 1; background: rgba(255, 255, 255, 0.08); color: var(--text); } .tooltip-actionbtn.spinning svg { animation: refresh-spin 0.8s linear infinite; transform-origin: 50% 50%; } /* L'ancien .tooltip-pinbtn garde ses variantes */ .tooltip-pinbtn { filter: grayscale(100%); } .tooltip-pinbtn:hover { filter: grayscale(0%); } .tooltip.pinned .tooltip-pinbtn { opacity: 1; filter: grayscale(0%); transform: rotate(-30deg); background: rgba(63, 185, 80, 0.15); } /* v4.1.15 : référence dans la bulle avec bouton copier inline */ .tt-ref-cell { display: inline-flex; align-items: center; gap: 8px; } .tt-ref-val { font-family: var(--mono, monospace); } .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; z-index: 200; display: flex; flex-direction: column-reverse; /* les nouveaux en bas, les anciens au-dessus */ gap: 8px; pointer-events: none; } .toast { min-width: 200px; max-width: 340px; padding: 10px 14px; background: var(--bg-elevated); color: var(--text); border: 1px solid var(--border-strong); border-left: 3px solid var(--c-livraison); border-radius: 6px; box-shadow: var(--shadow-hover); font-size: 13px; opacity: 0; transform: translateX(40px); transition: opacity 0.18s ease-out, transform 0.18s ease-out; pointer-events: auto; } .toast.visible { opacity: 1; transform: translateX(0); } .toast.leaving { opacity: 0; transform: translateX(40px); } .toast-label { color: var(--text-muted); font-size: 11px; margin-right: 6px; } .toast-ref { font-family: var(--mono); font-weight: 700; letter-spacing: 0.02em; } /* ───────────────────────────────────────────────────────────────────────── v4.1.20 : Modal central de confirmation (vider cache) ───────────────────────────────────────────────────────────────────────── */ .modal-overlay { position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center; /* Flou + assombrissement léger de l'arrière-plan */ background: rgba(0, 0, 0, 0.35); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); animation: modal-fade-in 0.15s ease-out; } @keyframes modal-fade-in { from { opacity: 0; } to { opacity: 1; } } .modal-card { background: var(--bg, #ffffff); color: var(--text, #111); border: 1px solid var(--border, rgba(128, 128, 128, 0.25)); border-radius: 12px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25), 0 2px 8px rgba(0, 0, 0, 0.15); padding: 24px 24px 20px; width: min(440px, 92vw); max-height: 90vh; overflow-y: auto; animation: modal-card-in 0.18s cubic-bezier(0.16, 1, 0.3, 1); } @keyframes modal-card-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } } .modal-title { margin: 0 0 12px 0; font-size: 18px; font-weight: 700; color: var(--text, #111); } .modal-message { margin: 0 0 20px 0; font-size: 13px; line-height: 1.5; color: var(--text-muted, #555); } .modal-actions { display: flex; flex-direction: column; gap: 8px; } .modal-actions .btn { width: 100%; padding: 10px 14px; font-size: 13px; font-weight: 600; text-align: center; border-radius: 8px; cursor: pointer; transition: background 0.12s, transform 0.06s; border: 1px solid transparent; } .modal-actions .btn:active { transform: translateY(1px); } /* Vider le cache du jour : danger modéré (orange) */ .btn-modal-danger { background: rgba(234, 128, 38, 0.12); color: #c85a00; border-color: rgba(234, 128, 38, 0.3); } .btn-modal-danger:hover { background: rgba(234, 128, 38, 0.22); } /* Vider tout le cache : danger fort (rouge) */ .btn-modal-danger-strong { background: rgba(220, 60, 60, 0.12); color: #c03030; border-color: rgba(220, 60, 60, 0.3); } .btn-modal-danger-strong:hover { background: rgba(220, 60, 60, 0.22); } /* Annuler : neutre */ .btn-modal-cancel { background: transparent; color: var(--text-muted, #666); border-color: var(--border, rgba(128, 128, 128, 0.3)); margin-top: 4px; } .btn-modal-cancel:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.08)); } /* v4.2.5 : bouton primaire (action principale) pour modals d'alerte */ .btn-modal-primary { background: var(--c-accent, #3fb950); color: #fff; border-color: var(--c-accent, #3fb950); } .btn-modal-primary:hover { filter: brightness(1.08); } /* ───────────────────────────────────────────────────────────────────────── v4.1.20 : Message d'absence récurrente (Pillonel vendredi) ───────────────────────────────────────────────────────────────────────── */ .tech-absence-recurring { padding: 14px 12px; text-align: center; font-size: 13px; font-style: italic; color: var(--text-faint, #888); background: rgba(128, 128, 128, 0.04); border-top: 1px solid var(--border, rgba(128, 128, 128, 0.15)); border-bottom: 1px solid var(--border, rgba(128, 128, 128, 0.15)); } /* v4.2 : contact en rouge quand anomalie détectée (Contact + Personne de contact présents tous les deux dans l'action = situation suspecte). On signale visuellement pour que l'user aille vérifier dans la fiche. */ .iv-contact-line.iv-contact-anomalie { color: #dc3030; } .iv-contact-line.iv-contact-anomalie .iv-contact, .iv-contact-line.iv-contact-anomalie .iv-phone { color: #dc3030; } /* v4.2 : badge utilisateur EasyVista connecté (en haut à droite de la topbar) */ .current-user { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; font-size: 12px; font-weight: 500; color: var(--text-muted, #666); background: rgba(128, 128, 128, 0.08); border: 1px solid var(--border, rgba(128, 128, 128, 0.2)); border-radius: 999px; margin-right: 8px; max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .current-user::before { content: "👤"; font-size: 11px; opacity: 0.7; } .current-user.hidden { display: none; } /* ───────────────────────────────────────────────────────────────────────── v4.2.3 : pastille utilisateur (initiales) dans la topbar gauche ───────────────────────────────────────────────────────────────────────── */ .user-badge { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; padding: 0; margin-right: 10px; font-size: 11px; font-weight: 700; letter-spacing: 0.5px; color: #fff; background: var(--user-badge-color, #5b6372); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 50%; cursor: pointer; transition: transform 0.1s, box-shadow 0.12s; flex-shrink: 0; user-select: none; } .user-badge:hover { transform: scale(1.06); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .user-badge:active { transform: scale(0.97); } .user-badge.hidden { display: none; } .user-badge.open { /* Indication visuelle quand la popup nom est ouverte */ box-shadow: 0 0 0 2px var(--user-badge-color, #5b6372) inset, 0 0 0 2px rgba(255, 255, 255, 0.15); } /* Popup du nom complet, affichée juste sous la pastille au clic */ .user-name-popup { position: fixed; z-index: 10050; padding: 8px 14px; background: var(--bg, #ffffff); color: var(--text, #111); border: 1px solid var(--border, rgba(128, 128, 128, 0.25)); border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18), 0 2px 6px rgba(0, 0, 0, 0.12); font-size: 13px; font-weight: 500; white-space: nowrap; animation: user-popup-in 0.12s ease-out; } .user-name-popup.hidden { display: none; } @keyframes user-popup-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } } /* ───────────────────────────────────────────────────────────────────────── v4.2.6 : boutons d'action topbar (Absence, Douchette) ───────────────────────────────────────────────────────────────────────── */ .btn-action { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; font-size: 13px; font-weight: 500; background: transparent; color: var(--text, #e0e0e0); border: 1px solid var(--border, rgba(128, 128, 128, 0.3)); border-radius: 6px; cursor: pointer; transition: background 0.12s, border-color 0.12s; } .btn-action:hover { background: var(--bg-hover, rgba(128, 128, 128, 0.12)); border-color: var(--border-strong, rgba(128, 128, 128, 0.5)); } .btn-action:active { transform: translateY(1px); } .btn-action-icon { width: 16px; height: 16px; flex-shrink: 0; } .btn-action-emoji { font-size: 14px; 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: 14px; font-weight: 700; line-height: 1; margin-top: 2px; cursor: help; text-align: center; } /* ───────────────────────────────────────────────────────────────────────── v4.3.0 : popups épinglés détachés Ancrés au contenu (position:absolute coord document) → scrollent avec la page. Persistent jusqu'à fermeture explicite. ───────────────────────────────────────────────────────────────────────── */ .tooltip.pinned-popup { position: absolute !important; /* override le fixed du .tooltip */ /* v4.3.3 corr : les popups épinglées doivent passer DERRIÈRE la topbar quand on scrolle (topbar sticky z-index 10). Donc on met 5 : au-dessus du contenu normal, mais sous la topbar / bannières / modals. */ z-index: 5 !important; opacity: 1 !important; pointer-events: auto !important; /* Bordure plus visible pour distinguer du tooltip live */ border: 2px solid var(--accent, #0f4f8b); box-shadow: 0 8px 24px rgba(0,0,0,0.18); /* Pas de contain: layout (hérité) car ça limite le rendu ; on laisse */ animation: pinned-popup-in 0.15s ease-out; /* Le padding-top est augmenté pour accueillir la barre de drag. */ padding-top: 28px !important; } @keyframes pinned-popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } } /* v4.3.3 : animation de sortie (symétrique à l'apparition) quand on désépingle. Appliquée par la classe .unpinning. */ .tooltip.pinned-popup.unpinning, .tooltip.soft-unpinned.unpinning { animation: pinned-popup-out 0.18s ease-in forwards !important; } @keyframes pinned-popup-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.94); } } /* v4.3.3 corr : quand une popup est désépinglée "mou", elle perd son look "épinglé" et redevient un tooltip normal visuellement, tout en gardant sa position absolute (pour ne pas sauter). */ .tooltip.soft-unpinned { position: absolute !important; z-index: 5 !important; opacity: 1 !important; pointer-events: auto !important; /* Pas de bordure bleue, pas de padding-top (plus de dragbar), juste les styles de base du tooltip (hérités de .tooltip). */ border: 1px solid var(--border-strong) !important; box-shadow: var(--shadow-hover) !important; padding-top: 12px !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) ───────────────────────────────────────────────────────────────────────── */ /* v2026.5.27 : app-clock sur UNE seule ligne : "Jeudi 23.04.26 • 21:55" Même taille pour la date et l'heure, gros point au milieu. */ .app-clock { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 12px; line-height: 1.1; color: var(--text); pointer-events: none; user-select: none; white-space: nowrap; } .app-clock-date { font-size: 22px; font-weight: 600; color: var(--text); letter-spacing: 0.5px; font-variant-numeric: tabular-nums; } .app-clock-date::after { content: "•"; margin-left: 12px; color: var(--text-muted); font-size: 26px; line-height: 0.8; vertical-align: middle; } .app-clock-time { font-size: 22px; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 1px; } .topbar { position: sticky; /* déja défini plus haut */ } /* topbar doit être en position: relative parent pour que .app-clock absolute se positionne par rapport à elle */ header.topbar { position: sticky !important; } header.topbar::before { content: ""; position: absolute; inset: 0; pointer-events: none; } /* ───────────────────────────────────────────────────────────────────────── v5.0.0 : ligne rouge "heure actuelle" sur la timeline (uniquement si on affiche la date d'aujourd'hui). v5.0.1 : plus visible. ───────────────────────────────────────────────────────────────────────── */ .timeline-now-line { position: absolute; top: -2px; bottom: -2px; width: 4px; background: #ff3030; z-index: 5; pointer-events: none; box-shadow: 0 0 6px rgba(255, 48, 48, 0.8), 0 0 2px rgba(255, 48, 48, 1); border-radius: 2px; margin-left: -2px; /* centre la barre sur la position exacte */ } .timeline-now-line::after { content: ""; position: absolute; top: -4px; left: 50%; transform: translateX(-50%); width: 12px; height: 12px; background: #ff3030; border-radius: 50%; box-shadow: 0 0 8px rgba(255, 48, 48, 0.9); } /* ───────────────────────────────────────────────────────────────────────── v5.0.0 : Panel admin (menu caché 5 clics sur titre) ───────────────────────────────────────────────────────────────────────── */ .admin-overlay { /* hérite de .modal-overlay */ align-items: flex-start; padding: 30px 20px; } .admin-panel-card { background: var(--bg-elevated); border: 1px solid var(--border); border-radius: 8px; width: 100%; max-width: 1100px; height: calc(100vh - 60px); display: flex; flex-direction: column; box-shadow: 0 12px 40px rgba(0,0,0,0.3); overflow: hidden; } .admin-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; border-bottom: 1px solid var(--border); background: var(--bg); } .admin-title { margin: 0; font-size: 18px; font-weight: 600; } .admin-close-btn { background: transparent; border: none; font-size: 24px; line-height: 1; cursor: pointer; padding: 4px 10px; color: var(--text-muted); border-radius: 4px; } .admin-close-btn:hover { background: var(--danger-soft); color: var(--danger); } .admin-body { display: flex; flex: 1; min-height: 0; } .admin-sidebar { width: 180px; background: var(--bg); border-right: 1px solid var(--border); padding: 10px 0; display: flex; flex-direction: column; gap: 2px; flex-shrink: 0; } .admin-nav-btn { text-align: left; padding: 10px 18px; background: transparent; border: none; cursor: pointer; font-size: 14px; color: var(--text); border-left: 3px solid transparent; transition: background 0.12s, border-color 0.12s; } .admin-nav-btn:hover { background: var(--bg-hover); } .admin-nav-btn.active { background: var(--bg-elevated); border-left-color: var(--accent); font-weight: 600; } .admin-content { flex: 1; padding: 20px 24px; overflow-y: auto; } .admin-section-title { margin: 0 0 8px 0; font-size: 20px; font-weight: 600; } .admin-section-desc { margin: 0 0 16px 0; color: var(--text-muted); font-size: 13px; } .admin-team-table { width: 100%; border-collapse: collapse; margin-top: 10px; } .admin-team-table th, .admin-team-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); text-align: left; vertical-align: middle; } .admin-team-table th { background: var(--bg); font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; color: var(--text-muted); } .admin-input { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 4px; background: var(--bg); color: var(--text); font-size: 13px; box-sizing: border-box; } .admin-input-id { font-family: var(--mono); max-width: 100px; } .admin-day-cb { display: inline-flex; align-items: center; gap: 2px; margin-right: 6px; font-size: 11px; cursor: pointer; user-select: none; } .admin-day-cb input[type="checkbox"] { margin: 0 2px 0 0; } .admin-del-btn { background: transparent; border: none; cursor: pointer; font-size: 16px; color: var(--text-muted); padding: 4px 8px; border-radius: 4px; } .admin-del-btn:hover { background: var(--danger-soft); color: var(--danger); } .admin-readonly { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 12px; font-family: var(--mono); font-size: 12px; overflow-x: auto; } .admin-diag-grid { display: grid; grid-template-columns: 200px 1fr; gap: 8px 16px; margin: 16px 0; font-size: 13px; } .admin-diag-grid > div { padding: 4px 0; } /* ───────────────────────────────────────────────────────────────────────── v5.0.0 : bouton supprimer dans le tooltip (absence / réservation) ───────────────────────────────────────────────────────────────────────── */ .tooltip-delete-btn { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; background: var(--danger-soft, #fbe6e6); border: 1px solid var(--danger, #b03030); color: var(--danger, #b03030); border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; margin-top: 4px; } .tooltip-delete-btn:hover:not(:disabled) { background: var(--danger, #b03030); color: #fff; } .tooltip-delete-btn:disabled { opacity: 0.6; cursor: wait; } /* Bouton danger dans les modals */ .btn-danger, .modal-btn-danger { background: var(--danger, #b03030); color: #fff; border: 1px solid var(--danger, #b03030); } .btn-danger:hover, .modal-btn-danger:hover { background: #8e2020; } /* v5.0.1 : ligne d'équipe exclue (pas cochée) - apparaît grisée */ .admin-team-table tr.admin-row-excluded { opacity: 0.45; } .admin-team-table tr.admin-row-excluded input[type="text"] { background: var(--bg); } /* v5.0.1 : bouton supprimer sur la carte "Absent toute la journée" */ .absence-delete-wrap { margin-top: 8px; text-align: center; } .absence-delete-wrap .tooltip-delete-btn { font-size: 11px; padding: 4px 8px; } /* v5.0.4 : boutons preset matin / après-midi / journée dans modal absence */ .modal-preset-row { gap: 8px; flex-wrap: wrap; } .modal-preset-btn { flex: 1; min-width: 100px; padding: 8px 10px; font-size: 13px; cursor: pointer; } /* ========================================================================== v5.0.9 : Compteur de session EasyVista (topbar) ========================================================================== */ .app-session { position: absolute; top: 50%; left: calc(50% + 60px); /* à droite de l'horloge (~60px de décalage) */ transform: translateY(-50%); display: flex; align-items: center; gap: 8px; padding: 4px 10px; border-radius: 14px; font-size: 13px; font-weight: 500; font-variant-numeric: tabular-nums; z-index: 9; background: rgba(0, 0, 0, 0.05); transition: background 0.3s, color 0.3s; } .app-session.hidden { display: none; } .app-session .session-icon { font-size: 14px; } .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: 14px; 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: 14px; 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) { .app-clock-date { font-size: 18px; } .app-clock-time { font-size: 18px; } .app-clock-date::after { font-size: 20px; } .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; } .app-clock-date { font-size: 16px; } .app-clock-time { font-size: 16px; } .app-clock-date::after { font-size: 18px; } .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: 16px; } .topbar h1 { font-size: 14px; } .btn-today { padding: 4px 6px; font-size: 11px; } .btn-nav { min-width: 26px; padding: 4px 6px; } } /* ========================================================================== v2026.5.17 : topbar des popups épinglés (3 boutons : _ ▭ 📍) ========================================================================== */ .pinned-popup { /* Laisser un peu de place en haut pour la topbar */ padding-top: 30px !important; } /* v2026.5.18 : masquer le conteneur d'actions d'origine (↻ reload + 📌 pin) dans les popups épinglés — leur place est reprise par notre .pinned-popup-topbar */ .pinned-popup .tooltip-actions { display: none !important; } .pinned-popup-topbar { position: absolute; top: 4px; right: 4px; display: flex; gap: 2px; align-items: center; z-index: 10; } .pinned-popup-btn { width: 26px; height: 22px; padding: 0; font-size: 13px; line-height: 1; background: transparent; color: var(--text-muted); border: 1px solid transparent; border-radius: 4px; cursor: pointer; transition: background 0.1s, color 0.1s, border-color 0.1s; font-family: inherit; display: inline-flex; align-items: center; justify-content: center; } .pinned-popup-btn:hover { background: var(--bg-muted); color: var(--text); border-color: var(--border); } .pinned-popup-unpin { font-size: 14px; } /* ========================================================================== 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: 14px; font-weight: 700; color: var(--text); cursor: pointer; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; order: 2; text-align: center; /* v2026.5.24 : centrer le texte */ } .pinned-popup-minref:hover { color: var(--accent, #3b82f6); } /* Boutons plus petits en mode minimisé */ .pinned-popup.pinned-popup-minimized .pinned-popup-btn { width: 22px !important; height: 22px !important; font-size: 12px !important; } .pinned-popup.pinned-popup-minimized .pinned-popup-refresh svg { width: 12px; height: 12px; } /* ========================================================================== v2026.5.17 : mode Réduit (docké en bas de l'écran) + taskbar ========================================================================== */ .pinned-popup.pinned-popup-reduced { display: none !important; } .pinned-popups-dock { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; display: none; flex-wrap: wrap; gap: 6px; padding: 6px 10px; background: var(--bg-elevated); border-top: 1px solid var(--border); box-shadow: 0 -2px 10px rgba(0,0,0,0.2); align-items: center; } .pinned-popups-dock.visible { display: flex; } .pinned-popup-dock-pill { display: inline-flex; align-items: center; padding: 6px 14px; background: var(--bg-muted); color: var(--text); border: 1px solid var(--border); border-radius: 16px; font-family: var(--mono, monospace); font-size: 13px; font-weight: 700; cursor: pointer; transition: background 0.15s, transform 0.15s, filter 0.15s; white-space: nowrap; } .pinned-popup-dock-pill:hover { transform: translateY(-1px); filter: brightness(1.1); } /* v2026.5.18 : couleurs par catégorie (fond = couleur, texte blanc) */ /* v2026.5.26 : les anciennes règles color-XXX qui mettaient un fond coloré vif sont remplacées par une simple barre verticale à gauche de la pastille. Les styles pour ::before sont plus bas dans le fichier. */ /* v2026.5.18 : bouton "Fermer tous" à droite du dock */ .pinned-popups-close-all { margin-left: auto; padding: 6px 12px; background: transparent; color: var(--text-muted); border: 1px solid var(--border); border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; font-family: inherit; transition: background 0.15s, color 0.15s, border-color 0.15s; } .pinned-popups-close-all:hover { background: rgba(239, 68, 68, 0.1); color: #ef4444; border-color: #ef4444; } /* ========================================================================== v2026.5.17 : popup user-badge avec ligne session ========================================================================== */ .user-name-popup-name { font-weight: 600; margin-bottom: 4px; } .user-name-popup-session { font-size: 12px; font-variant-numeric: tabular-nums; padding-top: 4px; border-top: 1px solid var(--border); } .user-name-popup-session.session-ok { color: var(--text-muted); } .user-name-popup-session.session-warn { color: #f59e0b; font-weight: 600; } .user-name-popup-session.session-critical { color: #ef4444; font-weight: 700; } /* ========================================================================== v2026.5.17 : popup alerte session qui glisse depuis haut-gauche ========================================================================== */ .session-slide-alert { position: fixed; top: 60px; left: -420px; /* hors écran au départ */ width: 380px; max-width: calc(100vw - 40px); padding: 14px 18px; background: var(--bg-elevated); border: 1px solid var(--border); border-left: 4px solid #f59e0b; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.25); z-index: 1000; transition: left 0.28s ease-out, opacity 0.28s; opacity: 0; } .session-slide-alert.visible { left: 20px; opacity: 1; } .session-slide-alert.urgent { border-left-color: #ef4444; animation: session-pulse 1.4s ease-in-out infinite; } @keyframes session-pulse { 0%, 100% { box-shadow: 0 8px 24px rgba(0,0,0,0.25); } 50% { box-shadow: 0 8px 24px rgba(239,68,68,0.5); } } .session-slide-alert-title { font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 12px; } .session-slide-alert-actions { display: flex; gap: 8px; justify-content: flex-end; } .session-slide-alert-extend, .session-slide-alert-later { padding: 6px 14px; font-size: 13px; border-radius: 6px; border: 1px solid var(--border); cursor: pointer; font-family: inherit; } .session-slide-alert-extend { background: #10b981; color: white; border-color: #10b981; font-weight: 600; } .session-slide-alert-extend:hover { background: #059669; } .session-slide-alert-extend:disabled { opacity: 0.6; cursor: wait; } .session-slide-alert-later { background: transparent; color: var(--text-muted); } .session-slide-alert-later:hover { background: var(--bg-muted); color: var(--text); } /* ========================================================================== v2026.5.19 : nouveaux éléments ========================================================================== */ /* Bouton Actualiser (↻) dans la topbar du popup épinglé — animation spin */ .pinned-popup-refresh { font-size: 14px; line-height: 1; } .pinned-popup-refresh svg { width: 14px; height: 14px; } .pinned-popup-refresh.spinning svg { animation: pinned-popup-refresh-spin 0.6s linear infinite; transform-origin: 50% 50%; } .pinned-popup-refresh.spinning { pointer-events: none; } @keyframes pinned-popup-refresh-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Pendant le drag d'un popup, ignorer les hover sur les cartes pour ne pas ouvrir des tooltips parasites */ body.popup-dragging .intervention-v2, body.popup-dragging .card { pointer-events: none; } /* Mais garder les popups épinglés cliquables */ body.popup-dragging .pinned-popup { pointer-events: auto; } /* Pastille dock à 2 lignes : ref (gras) + date (petit) */ .pinned-popup-dock-pill { flex-direction: column !important; align-items: center !important; padding: 4px 14px !important; line-height: 1.1; gap: 1px !important; } .pinned-popup-dock-pill-ref { display: block; font-size: 13px; font-weight: 700; font-family: var(--mono, monospace); } .pinned-popup-dock-pill-date { display: block; font-size: 10px; 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; } @keyframes pill-hover-menu-appear { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .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: 14px; width: 16px; text-align: center; } /* ========================================================================== v2026.5.21 : icône 📍 "active" dans le tooltip hover = déjà épinglée ========================================================================== */ .tooltip-pinbtn.tooltip-pinbtn-active { opacity: 1 !important; filter: none !important; background: rgba(239, 68, 68, 0.15); border-radius: 4px; } /* ========================================================================== v2026.5.23 : drag & drop des pastilles du dock ========================================================================== */ .pinned-popup-dock-pill { cursor: grab; user-select: none; transition: opacity 0.15s, transform 0.12s; } .pinned-popup-dock-pill:active { cursor: grabbing; } .pinned-popup-dock-pill.pill-dragging { opacity: 0.3 !important; pointer-events: none; } .pill-dragging-ghost { animation: pill-ghost-bounce 0.2s ease-out; transform: scale(1.05); box-shadow: 0 10px 24px rgba(0,0,0,0.4); } @keyframes pill-ghost-bounce { from { transform: scale(1); } to { transform: scale(1.05); } } /* ========================================================================== v2026.5.25 : pastille dock enrichie (lieu + service + date) + bouton Paramètres dans popup user-badge + ref dans mini-menu pill v2026.5.26 : couleurs sobres (fond sombre + barre colorée à gauche) + contenu centré ========================================================================== */ /* Pastille dock : 3 lignes centrées, fond sombre, barre colorée à gauche */ .pinned-popup-dock-pill { flex-direction: column !important; align-items: center !important; justify-content: center !important; padding: 6px 14px 6px 18px !important; gap: 2px !important; line-height: 1.2 !important; text-align: center; min-width: 200px; max-width: 300px; /* Fond sobre et texte bien lisible, peu importe la catégorie */ background: var(--bg-muted) !important; color: var(--text) !important; border: 1px solid var(--border) !important; position: relative; overflow: hidden; } /* Barre verticale colorée à gauche = indicateur de catégorie */ .pinned-popup-dock-pill::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; background: var(--c-autre); } .pinned-popup-dock-pill.color-livraison::before { background: var(--c-livraison); } .pinned-popup-dock-pill.color-installation::before { background: var(--c-installation); } .pinned-popup-dock-pill.color-recup::before { background: var(--c-recup); } .pinned-popup-dock-pill.color-remplacement::before { background: var(--c-remplacement); } .pinned-popup-dock-pill.color-incident::before { background: var(--c-incident); } .pinned-popup-dock-pill.color-rollout::before { background: var(--c-rollout); } .pinned-popup-dock-pill.color-reservation::before { background: var(--c-reservation); } .pinned-popup-dock-pill.color-absence::before { background: #666; } .pinned-popup-dock-pill.color-autre::before { background: var(--c-autre); } .pinned-popup-dock-pill-lieu { display: block; font-size: 13px; font-weight: 700; max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); text-align: center; } .pinned-popup-dock-pill-service { display: block; font-size: 11px; font-weight: 500; color: var(--text-muted); max-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; } .pinned-popup-dock-pill-date { display: block; font-size: 10px; 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-custom label (Vendredi 24.04.2026) */ #date-custom-label { font-size: 14px !important; /* +20% depuis 12px */ } .date-custom { font-size: 14px !important; } /* Stats bar */ .stats-bar, .stats-bar * { font-size: 14px !important; } .stats-bar strong { font-size: 16px !important; } /* v2026.5.27 : icône thème plus contrastée avec bordure + fond visible */ #theme-toggle { border: 1.5px solid var(--border-strong, rgba(128,128,128,0.5)) !important; background: var(--bg-muted) !important; width: 38px; height: 38px; padding: 0 !important; display: flex; align-items: center; justify-content: center; transition: background 0.15s, border-color 0.15s; } #theme-toggle:hover { background: var(--accent-soft, rgba(59, 130, 246, 0.15)) !important; border-color: var(--accent, #3b82f6) !important; } #theme-icon { font-size: 20px; line-height: 1; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); } /* ========================================================================== v2026.5.27 : classification visuelle des absences (Maladie, Congé, Pompier) ========================================================================== */ /* Variables de couleurs pour les catégories d'absence */ :root { --c-maladie: #4338ca; /* Indigo foncé */ --c-maladie-soft: #e0e7ff; /* Indigo très clair */ --c-conge: #06b6d4; /* Cyan */ --c-conge-soft: #cffafe; /* Cyan très clair */ } html.theme-dark { --c-maladie: #818cf8; /* Indigo plus clair pour dark mode */ --c-maladie-soft: #2a1e66; /* Indigo foncé pour fonds */ --c-conge: #67e8f9; /* Cyan plus clair pour dark mode */ --c-conge-soft: #0e3e4a; /* Cyan foncé pour fonds */ } /* Badge "Maladie" à côté du nom */ .badge-maladie { background: var(--c-maladie-soft); color: var(--c-maladie); font-weight: 600; } /* Badge "Congé" / "Congés" à côté du nom */ .badge-conge { background: var(--c-conge-soft); color: var(--c-conge); font-weight: 600; } /* Carte entière : couleur de fond + barre gauche épaisse pour absence */ .card.absence-cat-maladie { border-left: 4px solid var(--c-maladie); background: linear-gradient(to bottom, var(--c-maladie-soft) 0%, var(--bg-elevated) 40%); } html.theme-dark .card.absence-cat-maladie { background: linear-gradient(to bottom, rgba(67, 56, 202, 0.12) 0%, var(--bg-elevated) 40%); } .card.absence-cat-conge { border-left: 4px solid var(--c-conge); background: linear-gradient(to bottom, var(--c-conge-soft) 0%, var(--bg-elevated) 40%); } html.theme-dark .card.absence-cat-conge { background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--bg-elevated) 40%); } /* Pompier — on reprend le style existant mais on accentue le border-left */ .card.absence-cat-pompier { border-left: 4px solid var(--danger); } /* Pastille ronde à côté du nom */ .tech-name-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; flex-shrink: 0; vertical-align: middle; } .tech-name-dot.absence-dot-maladie { background: var(--c-maladie); } .tech-name-dot.absence-dot-conge { background: var(--c-conge); } .tech-name-dot.absence-dot-pompier { background: var(--danger); } /* Note statut (banner texte sur la zone interventions) */ .card-status-note { padding: 14px 16px; font-size: 15px; font-weight: 500; text-align: center; border-radius: 6px; margin: 10px; } .card-status-note.absent-maladie { background: var(--c-maladie-soft); color: var(--c-maladie); } .card-status-note.absent-conge { background: var(--c-conge-soft); color: var(--c-conge); } .card-status-note.pompier { background: var(--danger-soft); color: var(--danger); } /* ========================================================================== v2026.5.28 : popups épinglés gardent une taille standard au resize de la fenêtre. Le max-width 620px du tooltip de base les contraignait quand on réduisait la largeur depuis le côté droit — désormais ils restent à leur taille créée et sont simplement repositionnés dans la safe area. ========================================================================== */ .pinned-popup:not(.pinned-popup-minimized):not(.pinned-popup-reduced) { max-width: none !important; width: 520px !important; /* largeur standard fixe */ min-width: 380px; } /* 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 (Pillonel vendredi) en cyan (même couleur que Congé mais texte distinct "Absent le vendredi") ========================================================================== */ /* Badge "Absent" cyan pour récurrent */ .badge-recurring { background: var(--c-conge-soft); color: var(--c-conge); font-weight: 600; } /* Carte entière : bordure gauche cyan + fond dégradé */ .card.absence-cat-recurring { border-left: 4px solid var(--c-conge); background: linear-gradient(to bottom, var(--c-conge-soft) 0%, var(--bg-elevated) 40%); } html.theme-dark .card.absence-cat-recurring { background: linear-gradient(to bottom, rgba(6, 182, 212, 0.12) 0%, var(--bg-elevated) 40%); } /* Message "Absent le vendredi" (ou autre récurrence) en cyan */ .tech-absence-recurring { padding: 14px 12px; text-align: center; font-size: 15px; font-weight: 500; font-style: normal; color: var(--c-conge); background: var(--c-conge-soft); border-radius: 6px; margin: 10px; } html.theme-dark .tech-absence-recurring { background: rgba(6, 182, 212, 0.12); } /* ========================================================================== v2026.5.30 : mode compact pour écrans 24" Full HD (1920×1080) ou plus petits Objectif : réduire les paddings et tailles sans casser la lisibilité. ========================================================================== */ @media (max-width: 1920px) { /* Topbar légèrement compactée */ .topbar { padding: 8px 14px !important; gap: 10px; } .topbar h1 { font-size: 18px !important; } .app-clock-date, .app-clock-time { font-size: 19px !important; } .app-clock-date::after { font-size: 22px !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: 10px !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: 10px !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; } .app-clock-date, .app-clock-time { font-size: 17px !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.35 : réduit à 140px (au lieu de 200px) pour donner plus de place à la timeline */ html.view-horizontal .card-header { flex-direction: column !important; align-items: flex-start !important; justify-content: center !important; min-width: 140px !important; max-width: 140px !important; border-bottom: none !important; border-right: 1px solid var(--border) !important; padding: 6px 10px !important; gap: 3px !important; flex: 0 0 auto; } html.view-horizontal .card-tech-name { font-size: 13px !important; font-weight: 600; line-height: 1.2 !important; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; max-width: 100%; } html.view-horizontal .card-tech-badge { font-size: 10px !important; padding: 2px 6px !important; white-space: nowrap; } /* 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: 22px !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; gap: 8px; font-size: 11px; color: var(--text-muted); margin-top: 2px; } html.view-horizontal .tech-row-stats .stat-pill { padding: 1px 6px; background: var(--bg-muted); border: 1px solid var(--border); border-radius: 3px; font-variant-numeric: tabular-nums; } /* 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; } html.view-horizontal .horizontal-sidebar .date-nav .date-custom { width: 100%; justify-content: flex-start; padding: 6px 8px; font-size: 12px !important; } 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; } html.view-horizontal .horizontal-sidebar #stats .global-stat-sep { display: none !important; } html.view-horizontal .horizontal-sidebar #stats .global-stat-sub { display: block !important; font-size: 10px; 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: 110px !important; max-width: 110px !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; } /* 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; } /* Bouton Aujourd'hui — v2026.5.39 r5 : MÊME style que Absence/Douchette en sidebar. Hérite de la règle générique button.btn (padding/font-size). On force juste centrage du texte. */ html.view-horizontal .horizontal-sidebar .btn-today { order: 1; /* tout en haut après titre */ width: 100% !important; justify-content: center !important; /* centrage du label */ text-align: center !important; } /* 5. App-clock (date + heure) centré sous le bouton "Aujourd'hui" */ html.view-horizontal .horizontal-sidebar .app-clock { order: 2; align-items: center !important; text-align: center !important; padding: 6px 4px !important; background: transparent !important; border: none !important; margin-bottom: 4px; } html.view-horizontal .horizontal-sidebar .app-clock-date { text-align: center !important; width: 100%; } html.view-horizontal .horizontal-sidebar .app-clock-time { text-align: center !important; width: 100%; font-size: 22px !important; } /* 6. Séparateur visuel après date/heure (avant sélecteur date) v2026.5.37 : on override order:-1 qui venait de v5.36 */ html.view-horizontal .horizontal-sidebar .date-nav .date-custom-wrapper { order: 3 !important; border-top: 1px solid var(--border); padding-top: 8px; margin-top: 4px; width: 100%; } /* 7. Flèches ◀ ▶ côte à côte via wrapper JS #sidebar-arrows */ html.view-horizontal .horizontal-sidebar #sidebar-arrows { order: 4; display: flex; flex-direction: row; gap: 4px; width: 100%; } html.view-horizontal .horizontal-sidebar #sidebar-arrows .btn-nav { flex: 1 1 0 !important; min-width: 0 !important; justify-content: center !important; } /* 8. Stats empilées avec order pour venir après les flèches */ html.view-horizontal .horizontal-sidebar #stats { order: 5; width: 100%; margin-top: 8px; } /* 9. Capture-info (Synchronisé à HH:MM) sous les stats */ html.view-horizontal .horizontal-sidebar .capture-info { order: 6; margin-top: 4px; text-align: center; } /* 10. Boutons poussés en bas via margin-top: auto sur le premier d'entre eux (Absence, qui a order:7). v2026.5.39 r2 : ajout d'une fine bordure top + padding pour visualiser clairement la séparation entre la zone "info" (capture-info, stats) et la zone "actions" (boutons). margin-top: auto pousse en bas. */ html.view-horizontal .horizontal-sidebar #absence-btn { order: 7; margin-top: auto !important; /* pousse tout ce qui suit en bas */ padding-top: 8px !important; /* respiration au-dessus du bouton */ position: relative; } html.view-horizontal .horizontal-sidebar #absence-btn::before { content: ""; position: absolute; top: 0; left: 4px; right: 4px; height: 1px; background: var(--border); } html.view-horizontal .horizontal-sidebar #douchette-btn { order: 8; } html.view-horizontal .horizontal-sidebar #refresh-partial-btn { order: 9; } html.view-horizontal .horizontal-sidebar #refresh-btn { order: 10; } html.view-horizontal .horizontal-sidebar #clear-cache-btn { order: 11; } html.view-horizontal .horizontal-sidebar #theme-toggle { order: 12; margin-top: 4px !important; } html.view-horizontal .horizontal-sidebar #abort-btn { order: 8; /* avec douchette-btn (qui est aussi 8) — ne devrait pas être visible en même temps (un seul actif à la fois) */ } /* 11. Theme-toggle en bas : pleine largeur centrée */ html.view-horizontal .horizontal-sidebar #theme-toggle { width: 100% !important; padding: 6px !important; justify-content: center !important; } /* 12. Sidebar doit être flex column pour que margin-top:auto fonctionne. v2026.5.39 r3 : on force min-height: 100vh. v2026.5.39 r5 : tout est centré horizontalement (text-align + align-items). v2026.5.39 r8 : on compense le body.zoom via --zoom-inv. À 75% de zoom, 100vh ne couvre que 75% de la viewport effective ; on multiplie donc par l'inverse du zoom pour que la sidebar atteigne TOUJOURS le bas de l'écran. */ html.view-horizontal .horizontal-sidebar { display: flex !important; flex-direction: column !important; align-items: center !important; /* centrage horizontal des enfants */ text-align: center !important; /* centrage des textes */ gap: 6px !important; min-height: calc(100vh * var(--zoom-inv, 1)) !important; box-sizing: border-box !important; } /* Tous les enfants directs reçoivent text-align: center par héritage, mais on force aussi sur les boutons (qui ont parfois justify-content: flex-start) et les blocs de stats. */ html.view-horizontal .horizontal-sidebar > * { text-align: center !important; } html.view-horizontal .horizontal-sidebar button.btn { justify-content: center !important; } html.view-horizontal .horizontal-sidebar #stats .global-stat { text-align: center !important; } html.view-horizontal .horizontal-sidebar .app-clock { align-items: center !important; text-align: center !important; } html.view-horizontal .horizontal-sidebar .date-custom { justify-content: center !important; } /* 13. Barre de rafraîchissement en vue horizontale : overlay par-dessus user-badge + titre (zone haut de sidebar). */ html.view-horizontal #progress-bar { position: fixed !important; top: 0 !important; left: 0 !important; width: 200px !important; /* largeur de la sidebar */ z-index: 9999 !important; border-radius: 0 !important; margin: 0 !important; pointer-events: none; } /* Sur breakpoint étroit (sidebar 170px) */ @media (max-width: 1400px) { html.view-horizontal #progress-bar { width: 170px !important; } } /* 14. Banderole "En pompier du..." : masquée en vue horizontale uniquement (la barre rouge à gauche et le badge POMPIER restent visibles). */ html.view-horizontal .card-status-note.pompier { display: none !important; } /* ========================================================================== v2026.5.38 : Signature auteur en bas du popup user-badge Style cohérent avec le footer "QRO/vX.Y.Z" en bas à droite : petit, gris atténué, séparé par une fine ligne supérieure. ========================================================================== */ .user-name-popup-author { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); font-size: 11px; color: var(--text-faint); text-align: center; font-style: italic; letter-spacing: 0.2px; user-select: none; } /* ========================================================================== v2026.5.39 : Séparateur Matin / Après-midi entre les interventions Affiché entre les rows .intervention-v2 dans la vue classique. La ligne est marquée (pas un simple text), avec un label dans une "pill" centrée sur une fine barre horizontale qui traverse la card. Caché automatiquement en vue horizontale (les rows .intervention-v2 sont masquées dans cette vue, donc le séparateur le serait visuellement seul). ========================================================================== */ .day-period-sep { display: flex; align-items: center; gap: 10px; margin: 14px 0 8px 0; padding: 0 6px; position: relative; user-select: none; } /* v2026.5.39 r2 : ligne plus épaisse (3px), continue, sans dégradé. */ .day-period-sep::before, .day-period-sep::after { content: ""; flex: 1 1 auto; height: 3px; background: var(--border-strong); border-radius: 2px; } /* v2026.5.39 r2 : style neutre — pas de couleur ambre/bleu. */ .day-period-sep .day-period-label { flex: 0 0 auto; font-size: 12px; font-weight: 700; letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-muted); background: var(--bg-muted); border: 1px solid var(--border-strong); border-radius: 14px; padding: 4px 14px; white-space: nowrap; } /* Premier séparateur : pas de marge top excessive (juste après les stats) */ .card-stats + .day-period-sep { margin-top: 8px; } /* Vue horizontale : la liste détaillée est masquée donc le séparateur aussi */ html.view-horizontal .day-period-sep { display: none !important; } /* ========================================================================== 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: 14px; 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; } .app-clock-time, .app-clock-date { font-size: calc(18px * 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); }