Files
pointeuse-optimisator/app/templates/base.html
Jacquin Antoine 7d1601371c Récapitulatif d'astreinte : indique le forfait de chaque jour
Chaque jour d'astreinte déclaré affiche son forfait dans le récapitulatif,
même sans intervention saisie, et les totaux rappellent la somme des
forfaits — sans la mêler aux compteurs pondérés d'astreinte.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-08 23:35:58 +02:00

1389 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Décompte Horaire</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600&display=swap" rel="stylesheet">
<script src="/static/htmx.min.js"></script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--ground: #F4F0E8;
--surface: #FDFAF4;
--text: #1C1712;
--muted: #6B6152;
--rule: #D8D0C0;
--rule-l: #EAE5DA;
--accent: #2F5FA0;
--accent-2: #B86A08;
--pos: #2A5E42;
--pos-bg: #EBF5EE;
--neg: #9B3528;
--neg-bg: #F9EDEB;
--conge-bg: #FEF6E4;
--conge-bd: #E8C97A;
--conge-txt: #7A5300;
--nav-bg: #1C1712;
--mono: 'DM Mono', 'Cascadia Code', ui-monospace, monospace;
--sans: 'DM Sans', system-ui, -apple-system, sans-serif;
}
body {
font-family: var(--sans);
background: var(--ground);
color: var(--text);
font-size: 14px;
line-height: 1.5;
min-height: 100vh;
}
/* ── Nav ── */
nav {
position: relative;
background: var(--nav-bg);
color: var(--ground);
height: 50px;
padding: 0 2rem;
display: flex;
align-items: center;
gap: 2rem;
border-bottom: 3px solid var(--accent-2);
}
.nav-brand {
font-family: var(--mono);
font-size: 12px;
font-weight: 500;
color: var(--ground);
text-decoration: none;
letter-spacing: .1em;
text-transform: uppercase;
display: flex;
align-items: center;
gap: .55rem;
}
.nav-dot {
width: 7px; height: 7px;
border-radius: 50%;
background: var(--accent-2);
display: inline-block;
flex-shrink: 0;
}
nav .spacer { flex: 1; }
nav a.nav-link {
color: rgba(244,240,232,.55);
text-decoration: none;
font-size: 12px;
letter-spacing: .02em;
transition: color .15s;
}
nav a.nav-link:hover { color: var(--ground); }
.nav-user {
font-family: var(--mono);
font-size: 11px;
color: var(--accent-2);
letter-spacing: .05em;
}
.nav-logout { opacity: .6; font-size: 11px; }
/* Sur desktop les liens restent des enfants flex directs de <nav> (display:contents
les rend "transparents" au layout) ; sur mobile ils deviennent un panneau
déroulant contrôlé par le bouton hamburger. */
.nav-links { display: contents; }
.nav-burger {
display: none;
flex-direction: column;
justify-content: center;
gap: 4px;
width: 32px;
height: 32px;
padding: 0;
background: none;
border: none;
cursor: pointer;
flex-shrink: 0;
}
.nav-burger-bar {
display: block;
width: 20px;
height: 2px;
background: var(--ground);
transition: transform .15s, opacity .15s;
}
.nav-burger[aria-expanded="true"] .nav-burger-bar:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-burger[aria-expanded="true"] .nav-burger-bar:nth-child(2) { opacity: 0; }
.nav-burger[aria-expanded="true"] .nav-burger-bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
@media (max-width: 700px) {
nav { padding: 0 1rem; }
.nav-burger { display: flex; }
.nav-links {
display: none;
position: absolute;
top: 100%;
left: 0;
right: 0;
flex-direction: column;
align-items: stretch;
gap: 0;
background: var(--nav-bg);
border-bottom: 3px solid var(--accent-2);
padding: .5rem 0;
z-index: 200;
}
.nav-links.open { display: flex; }
.nav-links a.nav-link, .nav-links .nav-user {
padding: .75rem 1.25rem;
width: 100%;
}
.nav-links .nav-user { display: block; }
}
/* ── Layout ── */
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1.75rem;
}
@media (max-width: 700px) {
.container { padding: 1rem .75rem; gap: 1.25rem; }
}
/* ── Week header ── */
.week-header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: .6rem 1rem;
border-bottom: 2px solid var(--text);
padding-bottom: .75rem;
}
@media (max-width: 700px) {
.week-header-spacer { display: none; }
.export-btn { order: 10; flex-basis: 100%; text-align: center; }
}
.week-nav-arrows { display: flex; gap: .4rem; }
.arrow-btn {
width: 28px; height: 28px;
display: flex; align-items: center; justify-content: center;
border: 1.5px solid var(--rule);
background: transparent;
cursor: pointer;
font-size: 14px;
color: var(--muted);
text-decoration: none;
transition: border-color .15s, color .15s;
}
.arrow-btn:hover { border-color: var(--text); color: var(--text); }
.week-id {
font-family: var(--mono);
font-size: 1.75rem;
font-weight: 500;
letter-spacing: -.02em;
line-height: 1;
}
.week-id .wk { color: var(--muted); font-size: 1rem; font-weight: 300; }
.week-id .slash { color: var(--rule); margin: 0 .2em; }
.week-header-spacer { flex: 1; }
.export-btn {
font-family: var(--mono);
font-size: 11px;
letter-spacing: .04em;
color: var(--muted);
text-decoration: none;
border: 1.5px solid var(--rule);
padding: .4rem .75rem;
transition: border-color .15s, color .15s;
white-space: nowrap;
}
.export-btn:hover { border-color: var(--accent); color: var(--accent); }
/* ── Soldes ── */
.soldes {
display: grid;
grid-template-columns: repeat(4, 1fr);
border: 1.5px solid var(--rule);
background: var(--surface);
}
@media (max-width: 600px) { .soldes { grid-template-columns: 1fr 1fr; } }
.solde-item {
padding: 1rem 1.25rem;
border-right: 1px solid var(--rule);
}
.solde-item:last-child { border-right: none; }
.solde-item.item-pos { background: var(--pos-bg); }
.solde-item.item-neg { background: var(--neg-bg); }
.solde-eyebrow {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: .45rem;
}
.solde-num {
font-family: var(--mono);
font-size: 1.5rem;
font-weight: 500;
letter-spacing: -.03em;
line-height: 1;
}
.solde-num.pos { color: var(--pos); }
.solde-num.neg { color: var(--neg); }
.solde-num.neutral { color: var(--text); }
.reste-info {
padding: .6rem 1.25rem;
border-top: 1px solid var(--rule);
background: var(--ground);
font-size: 12px;
color: var(--muted);
}
.reste-info strong { color: var(--text); font-family: var(--mono); font-weight: 500; }
.reste-info strong.reste-extra { color: var(--neg); }
/* ── Table ── */
.table-section { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table {
width: 100%;
border-collapse: collapse;
border: 1.5px solid var(--rule);
background: var(--surface);
white-space: nowrap;
}
.th-group th {
padding: .45rem .65rem .25rem;
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
font-weight: 500;
color: var(--muted);
border-bottom: 1px solid var(--rule-l);
background: var(--ground);
white-space: nowrap;
}
.th-group .g-matin {
background: rgba(47,95,160,.07);
color: var(--accent);
border-left: 3px solid var(--accent);
text-align: center;
}
.th-group .g-aprem {
background: rgba(184,106,8,.07);
color: var(--accent-2);
border-left: 3px solid var(--accent-2);
text-align: center;
}
.th-group .g-calc { text-align: right; }
.th-group .g-conge { text-align: center; }
.th-sub th {
padding: .25rem .65rem .5rem;
font-size: 10px;
color: rgba(107,97,82,.65);
border-bottom: 2px solid var(--text);
font-weight: 400;
background: var(--ground);
white-space: nowrap;
}
.th-sub .s-matin {
background: rgba(47,95,160,.04);
border-left: 3px solid var(--accent);
color: rgba(47,95,160,.75);
text-align: center;
font-family: var(--mono);
font-size: 9px;
letter-spacing: .04em;
}
.th-sub .s-aprem {
background: rgba(184,106,8,.04);
border-left: 3px solid var(--accent-2);
color: rgba(184,106,8,.85);
text-align: center;
font-family: var(--mono);
font-size: 9px;
letter-spacing: .04em;
}
.th-sub .s-calc { text-align: right; }
.th-sub .s-conge { text-align: center; }
tbody tr { transition: background .1s; }
tbody tr:hover td { background: rgba(47,95,160,.025); }
tbody tr:hover .td-fill { background: rgba(47,95,160,.025); }
tbody tr:hover .td-fill.cg { background: var(--conge-bg); }
tbody tr:not(:last-child) td { border-bottom: 1px solid var(--rule-l); }
/* Week-end : affiché pour déclarer des astreintes, mais hors heures dues */
tbody tr.we td { background: var(--ground); }
tbody tr.we:hover td, tbody tr.we:hover .td-fill { background: var(--ground); }
tbody tr.we .jour-nom { color: var(--muted); font-weight: 400; }
tbody tr.we .calc-num, tbody tr.we .delta-zero { color: var(--rule); }
td {
padding: .5rem .65rem;
vertical-align: middle;
}
/* Matin / aprem cells: padding moved to inner .td-fill so OOB can target it */
td.c-matin, td.c-aprem { padding: 0; }
.td-fill {
display: flex;
align-items: center;
padding: .5rem .65rem;
}
td.c-matin .td-fill { border-left: 3px solid rgba(47,95,160,.2); }
td.c-aprem .td-fill { border-left: 3px solid rgba(184,106,8,.2); }
/* Congé state — applied to the inner div, not the <td> */
.td-fill.cg {
background: var(--conge-bg);
}
td.c-matin .td-fill.cg,
td.c-aprem .td-fill.cg { border-left-color: var(--conge-bd); }
.td-fill.cg .time-input { background: var(--conge-bg); }
.col-jour { white-space: nowrap; }
.jour-nom { display: block; font-weight: 500; font-size: 13px; }
.jour-date { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.time-pair { display: flex; align-items: center; gap: 3px; justify-content: center; }
.time-sep { color: var(--rule); font-size: 11px; flex-shrink: 0; }
.time-input {
width: 78px;
padding: .28rem .3rem;
border: 1px solid var(--rule);
border-radius: 0;
font-family: var(--mono);
font-size: 12px;
color: var(--text);
background: transparent;
text-align: left;
transition: border-color .15s;
}
.time-input:hover { border-color: var(--muted); }
.time-input:focus { outline: none; border-color: var(--accent); background: #fff; }
/* L'icône d'horloge native a une largeur fixe qui, avec un champ centré trop
étroit, empiète sur le dernier chiffre : on aligne le texte à gauche et on
resserre le padding de l'icône plutôt que de lui laisser sa marge par défaut. */
.time-input::-webkit-calendar-picker-indicator {
margin-left: .2rem;
padding: 1px;
}
tr.htmx-request .time-input { opacity: .4; }
.col-calc { text-align: right; padding-left: .5rem; }
.calc-num { font-family: var(--mono); font-size: 13px; color: var(--muted); }
.delta-pos { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--pos); }
.delta-neg { font-family: var(--mono); font-size: 13px; font-weight: 500; color: var(--neg); }
.delta-zero{ font-family: var(--mono); font-size: 13px; color: var(--rule); }
/* Warn state: time input violates minimum directive */
td.c-matin .td-fill.warn,
td.c-aprem .td-fill.warn { border-left-color: var(--neg); background: var(--neg-bg); }
.td-fill.warn .time-input { background: transparent; }
tbody tr:hover .td-fill.warn { background: var(--neg-bg); }
/* Cumulative delta and sortie cible columns */
.col-cumul { text-align: right; padding-left: .5rem; }
.col-cible { text-align: right; padding-left: .5rem; white-space: normal; min-width: 110px; }
.g-cumul { text-align: right; }
.g-cible { text-align: right; }
.cible-num { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 500; display: inline-block; }
.col-save { text-align: center; padding: .4rem .3rem; }
.g-save { background: var(--ground); }
.btn-valider {
display: inline-flex;
align-items: center;
justify-content: center;
width: 26px; height: 26px;
border: 1.5px solid var(--accent);
background: transparent;
cursor: pointer;
font-size: 13px;
color: var(--accent);
transition: background .15s, color .15s;
}
.btn-valider:hover { background: var(--accent); color: #fff; }
.btn-valider.htmx-request { opacity: .4; pointer-events: none; }
.col-conge { text-align: center; }
.conge-btns { display: flex; gap: 3px; justify-content: center; }
.btn-cg {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px; height: 24px;
border: 1.5px solid var(--rule);
background: transparent;
cursor: pointer;
font-family: var(--mono);
font-size: 9px;
font-weight: 500;
letter-spacing: .04em;
color: var(--muted);
transition: all .15s;
}
.btn-cg:hover { border-color: var(--conge-txt); color: var(--conge-txt); background: var(--conge-bg); }
.btn-cg.on { background: var(--conge-bg); border-color: var(--conge-bd); color: var(--conge-txt); }
.btn-cg.btn-jour { border-radius: 50%; width: 22px; height: 22px; font-size: 9px; }
.btn-cg.jour-on { background: var(--accent-2); border-color: #955505; color: #fff; }
.btn-cg.btn-as { border-radius: 4px; }
.btn-cg.as-on { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-cg.btn-ferie { border-radius: 4px; }
.btn-cg.ferie-on { background: #7B5EA7; border-color: #7B5EA7; color: #fff; }
.ferie-badge {
display: inline-block;
font-family: var(--mono);
font-size: 9px;
letter-spacing: .08em;
text-transform: uppercase;
color: #7B5EA7;
border: 1px solid #7B5EA7;
border-radius: 3px;
padding: 0 .25rem;
margin-left: .3rem;
}
/* Astreinte : sous-ligne pleine largeur sous la ligne du jour (hors colonne
après-midi), heures d'intervention (début/fin/description) repliées dans un
menu dépliant fermé par défaut ; retour 11h et total restent visibles.
La sous-ligne se masque toute seule tant que le bloc est vide. */
.pointage-table tr.as-row > td { padding: 0; }
.pointage-table tr.as-row:has(.as-block:empty) { display: none; }
.as-block {
display: flex;
flex-direction: column;
gap: .3rem;
padding: .3rem .65rem .45rem;
}
.as-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.as-toggle {
display: inline-flex;
align-items: center;
gap: .35rem;
border: 1px solid var(--rule);
background: transparent;
padding: .15rem .5rem;
cursor: pointer;
font-family: var(--mono);
font-size: 9px;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--accent-2);
white-space: nowrap;
transition: border-color .15s;
}
.as-toggle:hover { border-color: var(--accent-2); }
.as-caret { display: inline-block; transition: transform .15s; }
.as-block.open .as-caret { transform: rotate(90deg); }
.as-body { display: none; flex-direction: column; gap: .35rem; }
.as-block.open .as-body { display: flex; }
.as-grid {
display: grid;
grid-template-columns: 84px 84px minmax(110px, 1fr);
gap: 3px 6px;
align-items: center;
}
.as-grid .as-th {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--muted);
}
.as-grid .time-input { width: 100%; }
.as-event { display: contents; }
.as-desc-cell { display: flex; align-items: center; gap: 4px; min-width: 0; }
.as-hint {
font-family: var(--mono);
font-size: 10px;
color: var(--accent);
white-space: nowrap;
}
.as-desc {
flex: 1;
min-width: 0;
border: 1px solid var(--rule);
border-radius: 0;
padding: .25rem .3rem;
font-size: 11px;
font-family: var(--sans);
color: var(--text);
background: transparent;
transition: border-color .15s;
}
.as-desc:hover { border-color: var(--muted); }
.as-desc:focus { outline: none; border-color: var(--accent); background: #fff; }
.as-del {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px; height: 18px;
border: 1px solid var(--rule);
background: transparent;
cursor: pointer;
font-family: var(--mono);
font-size: 10px;
color: var(--muted);
flex-shrink: 0;
transition: border-color .15s, color .15s;
}
.as-del:hover { border-color: var(--neg); color: var(--neg); }
.as-add {
align-self: flex-start;
border: none;
background: none;
padding: 0;
cursor: pointer;
font-family: var(--mono);
font-size: 9px;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--accent-2);
white-space: nowrap;
}
.as-add:hover { text-decoration: underline; }
.as-total {
font-family: var(--mono);
font-size: 10px;
color: var(--accent-2);
white-space: nowrap;
}
/* ── Décompte astreinte (section soldes) ── */
.as-decompte {
border: 1px solid var(--rule);
background: var(--surface);
margin-top: .6rem;
padding: .55rem .75rem;
font-size: 12px;
}
.as-decompte-title {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: .35rem;
}
.as-decompte-help {
color: var(--muted);
font-size: 11px;
line-height: 1.55;
margin: -.15rem 0 .45rem;
}
.as-decompte-help b { color: var(--text); font-weight: 500; }
.as-ev { display: flex; flex-wrap: wrap; gap: .45rem; align-items: baseline; padding: .12rem 0; }
.as-ev-h { font-family: var(--mono); }
.as-ev-desc { color: var(--muted); font-style: italic; }
.as-ev-q {
font-family: var(--mono);
color: var(--text);
cursor: help;
border-bottom: 1px dotted var(--rule);
}
.as-ev-cat, .as-cat { color: var(--accent); font-family: var(--mono); font-size: 11px; }
.as-forfait { font-family: var(--mono); color: var(--muted); font-size: 11px; }
.as-ev-pond { font-family: var(--mono); color: var(--accent-2); }
.as-jour-date { font-family: var(--mono); font-weight: 500; margin-right: .4rem; }
.as-totaux {
border-top: 1px dashed var(--rule);
margin-top: .4rem;
padding-top: .4rem;
display: flex;
flex-wrap: wrap;
gap: .75rem;
}
.as-grand { font-family: var(--mono); font-weight: 500; }
/* ── Tableau hebdo en cartes empilées sous 700px (évite le scroll horizontal) ── */
@media (max-width: 700px) {
.pointage-table thead { display: none; }
.pointage-table, .pointage-table tbody, .pointage-table tr, .pointage-table td {
display: block;
width: 100%;
}
.pointage-table { border: none; background: none; }
.pointage-table tbody tr {
border: 1.5px solid var(--rule);
background: var(--surface);
margin-bottom: .75rem;
}
/* La sous-ligne astreinte prolonge la carte du jour : pas de bord haut,
marge haute négative pour absorber celle du jour. */
.pointage-table tbody tr.as-row {
border-top: none;
margin: -.75rem 0 .75rem 0;
}
.pointage-table tbody tr.as-row > td { padding: 0; }
.pointage-table tbody tr:not(:last-child) td { border-bottom: none; }
.pointage-table td {
border-bottom: 1px solid var(--rule-l) !important;
padding: .55rem .75rem;
}
.pointage-table td:last-child { border-bottom: none !important; }
.pointage-table td.col-jour {
background: var(--ground);
border-bottom: 2px solid var(--text) !important;
}
.pointage-table .jour-nom, .pointage-table .jour-date { display: inline; }
.pointage-table .jour-date::before { content: ' · '; }
.pointage-table [data-label] {
display: flex;
align-items: center;
justify-content: space-between;
gap: .75rem;
}
.pointage-table [data-label]::before {
content: attr(data-label);
font-family: var(--mono);
font-size: 9px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
flex-shrink: 0;
}
.pointage-table td.c-matin, .pointage-table td.c-aprem { padding: 0; }
.pointage-table td.c-matin .td-fill, .pointage-table td.c-aprem .td-fill {
padding: .55rem .75rem;
}
.pointage-table .time-input { width: 98px; font-size: 14px; padding: .45rem .4rem; }
.pointage-table td.col-calc,
.pointage-table td.col-cumul,
.pointage-table td.col-cible {
text-align: left;
padding-left: .75rem;
min-width: 0;
}
.pointage-table td.col-save { text-align: right; }
.pointage-table .btn-valider { width: 40px; height: 40px; font-size: 16px; }
.pointage-table .btn-cg { width: 34px; height: 34px; font-size: 11px; }
.pointage-table .btn-cg.btn-jour { width: 32px; height: 32px; }
.pointage-table .as-block { padding: .4rem .75rem .55rem; align-items: stretch; }
.as-grid { grid-template-columns: 82px 82px minmax(90px, 1fr); }
.as-desc { font-size: 13px; padding: .4rem .4rem; }
}
/* ── Historique ── */
.history {
display: flex;
flex-direction: column;
gap: .75rem;
border-top: 1px solid var(--rule);
padding-top: 1.25rem;
}
.hist-label {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.hist-body { display: flex; flex-direction: column; gap: .6rem; }
.hist-year-group { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.hist-year {
font-family: var(--mono);
font-size: 11px;
font-weight: 500;
color: var(--muted);
width: 36px;
flex-shrink: 0;
}
.hist-chips { display: flex; flex-wrap: wrap; gap: .3rem; }
.chip {
font-family: var(--mono);
font-size: 11px;
padding: .18rem .5rem;
border: 1px solid var(--rule);
color: var(--muted);
text-decoration: none;
letter-spacing: .02em;
transition: all .15s;
}
.chip:hover { border-color: var(--text); color: var(--text); }
.chip.here { background: var(--text); border-color: var(--text); color: var(--ground); }
.chip.chip-export { color: var(--accent); border-color: rgba(47,95,160,.35); margin-left: .3rem; }
.chip.chip-export:hover { border-color: var(--accent); background: rgba(47,95,160,.06); }
/* ── Calendrier sélecteur ── */
.cal-trigger-wrap { position: relative; }
.cal-trigger {
display: flex;
align-items: baseline;
gap: .1em;
background: none;
border: none;
cursor: pointer;
font-family: var(--mono);
font-size: 1.75rem;
font-weight: 500;
letter-spacing: -.02em;
color: var(--text);
padding: 0;
line-height: 1;
}
.cal-trigger .wk { color: var(--muted); font-size: 1rem; font-weight: 300; }
.cal-trigger .slash { color: var(--rule); margin: 0 .2em; }
.cal-trigger .cal-caret {
font-size: .85rem;
color: var(--muted);
margin-left: .35em;
transition: transform .15s;
}
.cal-trigger[aria-expanded="true"] .cal-caret { transform: rotate(180deg); }
.cal-trigger:hover .cal-caret { color: var(--text); }
.cal-popup {
position: absolute;
top: calc(100% + .5rem);
left: 0;
z-index: 100;
background: var(--surface);
border: 1.5px solid var(--rule);
box-shadow: 0 4px 20px rgba(28,23,18,.12);
padding: .75rem;
min-width: 260px;
max-width: calc(100vw - 2rem);
}
.cal-popup[hidden] { display: none; }
.cal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: .6rem;
}
.cal-month-label {
font-family: var(--mono);
font-size: 12px;
font-weight: 500;
letter-spacing: .04em;
text-transform: uppercase;
color: var(--text);
}
.cal-nav {
background: none;
border: 1px solid var(--rule);
width: 24px; height: 24px;
cursor: pointer;
font-size: 12px;
color: var(--muted);
display: flex; align-items: center; justify-content: center;
transition: border-color .15s, color .15s;
}
.cal-nav:hover { border-color: var(--text); color: var(--text); }
.cal-grid { width: 100%; border-collapse: collapse; }
.cal-grid thead th {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
padding: .2rem .3rem;
text-align: center;
border-bottom: 1px solid var(--rule);
}
.cal-grid thead th.cal-wn {
color: var(--accent);
border-right: 1px solid var(--rule-l);
}
.cal-grid thead th.cal-we { color: var(--neg); }
.cal-grid .cal-row td {
font-family: var(--mono);
font-size: 12px;
text-align: center;
padding: .3rem .25rem;
color: var(--text);
transition: background .1s;
}
.cal-grid .cal-row:hover td { background: var(--ground); }
.cal-grid .cal-row:hover td.cal-wn { background: rgba(47,95,160,.1); }
.cal-grid .cal-row td.cal-wn {
color: var(--accent);
font-weight: 500;
font-size: 11px;
border-right: 1px solid var(--rule-l);
padding-right: .4rem;
}
.cal-grid .cal-row td.cal-out { color: var(--rule); }
.cal-grid .cal-row td.cal-we { color: var(--muted); }
.cal-grid .cal-current td {
background: var(--ground);
font-weight: 500;
}
.cal-grid .cal-current td.cal-wn {
background: var(--accent);
color: #fff;
}
.cal-grid .cal-current:hover td { background: rgba(47,95,160,.06); }
.cal-grid .cal-current:hover td.cal-wn { background: var(--accent); }
/* ── Stats page ── */
.stats-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1.5rem;
border-bottom: 2px solid var(--text);
padding-bottom: .75rem;
}
.stats-title {
font-family: var(--mono);
font-size: 1.75rem;
font-weight: 500;
letter-spacing: -.02em;
line-height: 1;
margin-bottom: .4rem;
}
.stats-meta {
font-size: 12px;
color: var(--muted);
font-family: var(--mono);
}
.stats-sep { margin: 0 .35em; color: var(--rule); }
.stat-pos { color: var(--pos); font-weight: 500; }
.stat-neg { color: var(--neg); font-weight: 500; }
.stats-overall {
text-align: center;
padding: .75rem 1.1rem;
border: 1.5px solid var(--rule);
background: var(--surface);
flex-shrink: 0;
}
.stats-overall-num {
font-family: var(--mono);
font-size: 2rem;
font-weight: 500;
letter-spacing: -.04em;
line-height: 1;
}
.stats-overall-pct { font-size: 1.1rem; font-weight: 300; }
.stats-overall-label {
font-size: 9px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
margin-top: .35rem;
line-height: 1.4;
}
.comp-ok { color: var(--pos); border-color: var(--pos) !important; background: var(--pos-bg) !important; }
.comp-warn { color: #7A5300; border-color: var(--conge-bd) !important; background: var(--conge-bg) !important; }
.comp-bad { color: var(--neg); border-color: var(--neg) !important; background: var(--neg-bg) !important; }
/* ── 3 period-compliance cards ── */
.comp-periods {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--rule);
border: 1.5px solid var(--rule);
}
@media (max-width: 520px) { .comp-periods { grid-template-columns: 1fr; } }
.comp-period-card {
background: var(--surface);
padding: 1rem 1.25rem 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: .2rem;
text-align: center;
}
.cp-eyebrow {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.cp-sub {
font-family: var(--mono);
font-size: 10px;
color: var(--text);
font-weight: 500;
margin-bottom: .35rem;
}
.cp-donut-wrap { margin: .25rem 0; }
.cp-donut { display: block; }
.cp-n {
font-family: var(--mono);
font-size: 10px;
color: var(--muted);
margin-top: .2rem;
}
.cp-n.comp-ok { color: var(--pos); }
.cp-n.comp-warn { color: #7A5300; }
.cp-n.comp-bad { color: var(--neg); }
.cp-nodata { color: var(--rule); }
/* ── Period tabs ── */
.period-tabs-wrap {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.period-tabs-label {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
flex-shrink: 0;
}
.period-tabs {
display: flex;
gap: 1px;
background: var(--rule);
border: 1.5px solid var(--rule);
}
.ptab {
padding: .38rem .9rem;
font-family: var(--mono);
font-size: 11px;
background: var(--surface);
border: none;
cursor: pointer;
color: var(--muted);
letter-spacing: .04em;
transition: background .12s, color .12s;
white-space: nowrap;
}
.ptab:hover { background: var(--ground); color: var(--text); }
.ptab.active { background: var(--text); color: var(--ground); }
.ptab:focus { outline: 2px solid var(--accent); outline-offset: -2px; }
/* 2×2 grid, 4-in-a-row on wide screens */
.ts-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
background: var(--rule);
border: 1.5px solid var(--rule);
}
@media (min-width: 800px) { .ts-grid { grid-template-columns: repeat(4, 1fr); } }
.ts-card {
background: var(--surface);
padding: 1rem 1.2rem .9rem;
display: flex;
flex-direction: column;
gap: .3rem;
min-width: 0;
}
.ts-card.ts-matin { border-top: 3px solid var(--accent); }
.ts-card.ts-aprem { border-top: 3px solid var(--accent-2); }
.ts-eyebrow {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.ts-mean {
font-family: var(--mono);
font-size: 2rem;
font-weight: 500;
letter-spacing: -.04em;
line-height: 1;
color: var(--text);
margin-top: .15rem;
}
.ts-mean.ts-empty { color: var(--rule); }
.ts-sigma {
font-family: var(--mono);
font-size: 10px;
color: var(--muted);
margin-bottom: .25rem;
}
.ts-mean-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: .5rem;
}
.donut-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: .15rem;
flex-shrink: 0;
}
.donut-canvas { display: block; }
.donut-label {
font-family: var(--mono);
font-size: 8px;
letter-spacing: .04em;
color: var(--muted);
text-align: center;
}
.dist-canvas {
width: 100%;
display: block;
margin: .25rem 0 .2rem;
background: transparent;
}
.dist-legend {
display: flex;
flex-wrap: wrap;
gap: .5rem .75rem;
font-family: var(--mono);
font-size: 9px;
color: var(--muted);
margin-bottom: .35rem;
}
.dl-item { display: flex; align-items: center; gap: .28rem; white-space: nowrap; }
.dl-mean-line {
display: inline-block;
width: 14px; height: 1.5px;
background: var(--text);
flex-shrink: 0;
}
.dl-target-line {
display: inline-block;
width: 14px; height: 0;
border-top: 1.5px dashed;
flex-shrink: 0;
}
.dl-target-line.dl-neg { border-color: var(--neg); }
.dl-target-line.dl-pos { border-color: var(--pos); }
.dl-band {
display: inline-block;
width: 10px; height: 10px;
flex-shrink: 0;
}
.dl-band.ts-matin { background: rgba(47,95,160,.12); border: 1px solid rgba(47,95,160,.25); }
.dl-band.ts-aprem { background: rgba(184,106,8,.12); border: 1px solid rgba(184,106,8,.25); }
.dl-n { color: var(--muted); }
/* Weekly balance chart */
.wkly-section {
background: var(--surface);
border: 1.5px solid var(--rule);
padding: 1rem 1.25rem;
}
.wkly-eyebrow {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: .75rem;
}
.wkly-wrap {
position: relative;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
#c-weekly { display: block; width: 100%; }
.wkly-note { font-weight: 300; color: var(--muted); letter-spacing: 0; text-transform: none; font-size: 9px; }
.wkly-legend {
margin-top: .5rem;
font-size: 10px;
color: var(--muted);
font-family: var(--mono);
display: flex;
align-items: center;
gap: .4rem;
}
.legend-dot {
width: 10px; height: 10px;
display: inline-block;
flex-shrink: 0;
}
.partial-dot { background: repeating-linear-gradient(45deg, var(--muted) 0, var(--muted) 2px, transparent 2px, transparent 4px); }
/* Zoomable canvas hint */
canvas.zoomable { cursor: zoom-in; }
/* ── Alerte départ ── */
#alerte-depart {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 9999;
background: var(--neg);
color: #fff;
border-left: 4px solid #ff6b5b;
box-shadow: 0 6px 28px rgba(0,0,0,.3);
min-width: 240px;
animation: alerte-slide .35s cubic-bezier(.2,.8,.3,1) both;
}
.alerte-inner {
display: flex;
align-items: center;
gap: .75rem;
padding: .85rem 1rem;
}
.alerte-icon { font-size: 1.5rem; flex-shrink: 0; animation: alerte-pulse 1s ease-in-out 3; }
.alerte-body { flex: 1; }
.alerte-label {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
opacity: .7;
margin-bottom: .2rem;
}
.alerte-time {
font-family: var(--mono);
font-size: 1.75rem;
font-weight: 500;
letter-spacing: -.03em;
line-height: 1;
}
.alerte-close {
background: none;
border: 1px solid rgba(255,255,255,.3);
color: #fff;
width: 24px; height: 24px;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 12px;
flex-shrink: 0;
align-self: flex-start;
transition: background .15s;
}
.alerte-close:hover { background: rgba(255,255,255,.15); }
.alerte-supp {
font-family: var(--mono);
font-size: 11px;
opacity: .8;
margin-top: .25rem;
letter-spacing: .02em;
}
@keyframes alerte-slide {
from { opacity: 0; transform: translateX(120%); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes alerte-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.25); }
}
/* ── Login / set-password ── */
.login-wrap {
display: flex;
justify-content: center;
align-items: flex-start;
padding-top: 4rem;
}
.login-card {
background: var(--surface);
border: 1.5px solid var(--rule);
padding: 2.5rem 3rem;
width: 380px;
max-width: 100%;
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.login-header { display: flex; flex-direction: column; gap: .35rem; }
.login-eyebrow {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.login-title {
font-family: var(--mono);
font-size: 22px;
font-weight: 500;
color: var(--text);
letter-spacing: -.01em;
}
.login-error {
background: var(--neg-bg);
border-left: 3px solid var(--neg);
color: var(--neg);
font-size: 12px;
padding: .6rem .9rem;
font-family: var(--mono);
}
.login-form { display: flex; flex-direction: column; gap: .75rem; }
.login-label {
font-size: 11px;
font-weight: 500;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--muted);
}
.login-input {
font-family: var(--mono);
font-size: 15px;
padding: .6rem .75rem;
border: 1.5px solid var(--rule);
background: var(--ground);
color: var(--text);
outline: none;
transition: border-color .15s;
}
.login-input:focus { border-color: var(--accent); }
.login-hint { font-size: 11px; color: var(--muted); }
.login-btn {
margin-top: .5rem;
font-family: var(--mono);
font-size: 13px;
font-weight: 500;
letter-spacing: .08em;
text-transform: uppercase;
padding: .7rem 1.5rem;
background: var(--text);
color: var(--surface);
border: none;
cursor: pointer;
transition: background .15s;
}
.login-btn:hover { background: var(--accent); }
/* ── Chart zoom modal ── */
.chart-modal {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
}
.chart-modal[hidden] { display: none; }
.chart-modal-bd {
position: absolute;
inset: 0;
background: rgba(28,23,18,.55);
backdrop-filter: blur(3px);
}
.chart-modal-inner {
position: relative;
background: var(--surface);
border: 1.5px solid var(--rule);
max-width: 92vw;
max-height: 90vh;
overflow: auto;
display: flex;
flex-direction: column;
}
.chart-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: .65rem 1rem;
border-bottom: 1px solid var(--rule);
background: var(--ground);
}
.chart-modal-title {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--muted);
}
.chart-modal-close {
background: none;
border: 1.5px solid var(--rule);
width: 26px; height: 26px;
display: flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 12px;
color: var(--muted);
flex-shrink: 0;
transition: border-color .15s, color .15s;
}
.chart-modal-close:hover { border-color: var(--text); color: var(--text); }
.chart-modal-close:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.chart-modal-body {
position: relative;
padding: 1.25rem;
overflow-x: auto;
}
#chart-modal-canvas { display: block; }
.chart-tooltip {
position: absolute;
pointer-events: none;
background: var(--surface);
border: 1.5px solid var(--rule);
box-shadow: 0 2px 8px rgba(28,23,18,.12);
padding: .35rem .55rem;
font-family: var(--mono);
font-size: 11px;
color: var(--text);
white-space: nowrap;
opacity: 0;
transform: translate(-50%, -100%);
transition: opacity .1s;
z-index: 5;
}
.chart-tooltip.visible { opacity: 1; }
.chart-tooltip .ct-val { font-weight: 500; }
.chart-tooltip .ct-label { color: var(--muted); margin-left: .35em; }
</style>
</head>
<body>
<nav>
<a href="/" class="nav-brand"><span class="nav-dot"></span>Décompte Horaire</a>
<span class="spacer"></span>
{% if current_user is defined and current_user %}
<button type="button" class="nav-burger" id="nav-burger" aria-expanded="false"
aria-controls="nav-links" aria-label="Menu" onclick="toggleNavMenu()">
<span class="nav-burger-bar"></span><span class="nav-burger-bar"></span><span class="nav-burger-bar"></span>
</button>
<div class="nav-links" id="nav-links">
<a href="/" class="nav-link">Semaine courante</a>
<a href="/stats" class="nav-link">Statistiques</a>
<a href="/logs" class="nav-link">Journaux</a>
<a href="/settings" class="nav-link">Réglages</a>
<a href="/aide" class="nav-link">Aide</a>
<span class="nav-user">{{ current_user }}</span>
<a href="/logout" class="nav-link nav-logout">Déconnexion</a>
</div>
{% endif %}
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script>
function toggleNavMenu() {
var links = document.getElementById('nav-links');
var btn = document.getElementById('nav-burger');
var open = !links.classList.contains('open');
links.classList.toggle('open', open);
btn.setAttribute('aria-expanded', String(open));
}
document.addEventListener('click', function (e) {
var nav = document.querySelector('nav');
if (nav && !nav.contains(e.target)) {
var links = document.getElementById('nav-links');
var btn = document.getElementById('nav-burger');
if (links) links.classList.remove('open');
if (btn) btn.setAttribute('aria-expanded', 'false');
}
});
</script>
</body>
</html>