Files
pointeuse-optimisator/app/templates/base.html
Jacquin Antoine e9983a28c7 Refonte de l'interface : sauvegarde automatique, menu d'état unique et panneau aujourd'hui
- Chaque heure validée enregistre le jour (plus de bouton ✓), avec un témoin
  « enregistré » et conservation du focus entre les champs
- Les boutons MA/AM/J/F/AS deviennent un bouton d'état unique à pastilles
  ouvrant un menu déroulant (congés, férié, astreinte)
- Panneau Aujourd'hui : sortie cible en grand et progression travaillé/dû
  de la semaine, rafraîchis à chaque saisie
- Polish général : coins arrondis, ombres douces, hiérarchie des soldes,
  états de focus visibles

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-09 20:34:27 +02:00

1594 lines
49 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;
--r-s: 5px;
--r-m: 8px;
--shadow-s: 0 1px 2px rgba(28,23,18,.05), 0 6px 20px -12px rgba(28,23,18,.12);
--shadow-m: 0 2px 6px rgba(28,23,18,.07), 0 16px 40px -18px rgba(28,23,18,.20);
--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;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* ── 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);
box-shadow: 0 2px 10px rgba(28,23,18,.18);
}
.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;
border-radius: var(--r-s);
transition: border-color .15s, color .15s, background .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);
border-radius: var(--r-s);
padding: .4rem .75rem;
transition: border-color .15s, color .15s;
white-space: nowrap;
}
.export-btn:hover { border-color: var(--accent); color: var(--accent); }
/* ── Soldes ── */
.soldes-wrap {
border: 1.5px solid var(--rule);
border-radius: var(--r-m);
background: var(--surface);
box-shadow: var(--shadow-s);
overflow: hidden;
}
.soldes {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
@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; }
@media (max-width: 600px) {
.solde-item:nth-child(2n) { border-right: none; }
.solde-item:nth-child(-n+2) { border-bottom: 1px solid var(--rule); }
}
.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-item:last-child .solde-num { font-size: 1.9rem; }
.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); }
/* ── Panneau aujourd'hui ── */
.today-panel {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .9rem 2.25rem;
border: 1.5px solid var(--rule);
border-left: 4px solid var(--accent);
border-radius: var(--r-m);
background: var(--surface);
box-shadow: var(--shadow-s);
padding: .9rem 1.25rem;
}
.today-eyebrow {
font-family: var(--mono);
font-size: 9px;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: .3rem;
}
.today-cible {
font-family: var(--mono);
font-size: 1.9rem;
font-weight: 500;
letter-spacing: -.03em;
line-height: 1;
color: var(--accent);
}
.today-cible.today-neutral { color: var(--text); font-size: 1.1rem; }
.today-sub { font-size: 11px; color: var(--muted); margin-top: .3rem; }
.today-prog { flex: 1; min-width: 220px; }
.today-prog-head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: .75rem;
font-family: var(--mono);
font-size: 10px;
color: var(--muted);
margin-bottom: .35rem;
}
.today-prog-head b { color: var(--text); font-weight: 500; font-size: 12px; }
.today-bar {
height: 8px;
border-radius: 999px;
background: var(--ground);
border: 1px solid var(--rule-l);
overflow: hidden;
}
.today-bar-fill {
height: 100%;
background: var(--accent);
border-radius: 999px;
transition: width .4s ease;
}
.today-bar-fill.full-pos { background: var(--pos); }
.today-bar-fill.over { background: var(--accent-2); }
.today-bar-fill.short { background: var(--neg); }
@media (max-width: 700px) {
.today-panel { gap: .6rem; }
.today-prog { flex-basis: 100%; min-width: 0; }
.today-cible { font-size: 1.6rem; }
}
/* ── Table ── */
.table-section {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Rayons sur la table desktop : border-collapse separate, sinon le radius
est ignoré. Le menu d'état s'ouvre en position fixe (voir semaine.html)
pour ne pas être rogné par le scroll horizontal du conteneur. */
@media (min-width: 701px) {
.pointage-table {
border: 1.5px solid var(--rule);
border-radius: var(--r-m);
border-collapse: separate;
border-spacing: 0;
box-shadow: var(--shadow-s);
}
.pointage-table tbody tr:last-child td { border-bottom: none; }
}
table {
width: 100%;
border-collapse: collapse;
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: var(--r-s);
font-family: var(--mono);
font-size: 12px;
color: var(--text);
background: transparent;
text-align: left;
transition: border-color .15s, background .15s, box-shadow .15s;
}
.time-input:hover { border-color: var(--muted); }
.time-input:focus { outline: none; border-color: var(--accent); background: #fff; box-shadow: 0 0 0 3px rgba(47,95,160,.12); }
/* 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; }
/* Auto-enregistrement : déclenché au changement d'un champ du jour, la
sauvegarde part dès que le champ est validé (blur/picker). Le petit
témoin dans la colonne Jour suit l'état de la requête. */
.autosave {
display: inline-flex;
align-items: center;
font-family: var(--mono);
font-size: 9px;
letter-spacing: .06em;
color: var(--muted);
margin-left: .45rem;
vertical-align: middle;
opacity: 0;
transition: opacity .2s;
pointer-events: none;
}
.autosave.busy, .autosave.ok { opacity: 1; }
.autosave.busy::after { content: 'enregistrement…'; }
.autosave.ok::after { content: '✓ enregistré'; color: var(--pos); }
.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; }
/* ── Menu congé / férié / astreinte ──
Un seul bouton par ligne résume l'état (pastilles colorées) et ouvre
le menu des cinq bascules : congé matin / après-midi / journée, férié,
jour d'astreinte. */
.col-conge { text-align: center; }
.cg-cell { position: relative; display: inline-block; }
.cg-menu { position: relative; }
.cg-trigger {
display: inline-flex;
align-items: center;
gap: .3rem;
border: 1.5px solid var(--rule);
border-radius: var(--r-s);
background: transparent;
cursor: pointer;
padding: .22rem .45rem;
min-height: 26px;
transition: border-color .15s, background .15s;
list-style: none;
}
.cg-trigger::-webkit-details-marker { display: none; }
.cg-trigger:hover { border-color: var(--muted); background: var(--ground); }
.cg-state { display: inline-flex; gap: 3px; align-items: center; justify-content: center; min-width: 20px; }
.cg-chip {
font-family: var(--mono);
font-size: 8.5px;
font-weight: 500;
letter-spacing: .04em;
border-radius: 3px;
padding: .12rem .24rem;
line-height: 1;
}
.cg-chip.st-ma, .cg-chip.st-am { background: var(--conge-bg); border: 1px solid var(--conge-bd); color: var(--conge-txt); }
.cg-chip.st-jour { background: var(--accent-2); color: #fff; }
.cg-chip.st-ferie { background: #7B5EA7; color: #fff; }
.cg-chip.st-as { background: var(--accent); color: #fff; }
.cg-none { color: var(--rule); font-family: var(--mono); font-size: 10px; }
.cg-caret { color: var(--muted); font-size: 9px; transition: transform .15s; }
.cg-menu[open] > .cg-trigger { border-color: var(--accent); }
.cg-menu[open] .cg-caret { transform: rotate(180deg); }
.cg-pop {
position: absolute;
right: 0;
top: calc(100% + .35rem);
z-index: 150;
background: var(--surface);
border: 1.5px solid var(--rule);
border-radius: var(--r-m);
box-shadow: var(--shadow-m);
padding: .35rem;
min-width: 178px;
display: flex;
flex-direction: column;
gap: 2px;
text-align: left;
}
.cg-opt {
display: flex;
align-items: center;
gap: .5rem;
border: none;
background: transparent;
border-radius: var(--r-s);
padding: .38rem .5rem;
font-family: var(--sans);
font-size: 12px;
color: var(--text);
cursor: pointer;
white-space: nowrap;
transition: background .12s;
}
.cg-opt:hover { background: var(--ground); }
.cg-opt-chip {
font-family: var(--mono);
font-size: 8.5px;
font-weight: 500;
letter-spacing: .04em;
border-radius: 3px;
padding: .12rem .26rem;
border: 1px solid var(--rule);
color: var(--muted);
min-width: 22px;
text-align: center;
flex-shrink: 0;
line-height: 1;
}
.cg-opt.on { background: var(--conge-bg); }
.cg-opt.on .cg-opt-chip.ma, .cg-opt.on .cg-opt-chip.am { background: var(--conge-bg); border-color: var(--conge-bd); color: var(--conge-txt); }
.cg-opt.on .cg-opt-chip.jour { background: var(--accent-2); border-color: var(--accent-2); color: #fff; }
.cg-opt.on .cg-opt-chip.ferie { background: #7B5EA7; border-color: #7B5EA7; color: #fff; }
.cg-opt.on .cg-opt-chip.as { background: var(--accent); border-color: var(--accent); 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);
border-radius: var(--r-s);
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: var(--r-s);
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);
border-radius: 4px;
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);
border-radius: var(--r-m);
background: var(--surface);
box-shadow: var(--shadow-s);
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 .12rem .6rem;
margin: .2rem 0 .2rem .25rem;
border-left: 2px solid var(--rule-l);
}
.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 { padding: .35rem 0; border-top: 1px dashed var(--rule-l); }
.as-jour:first-of-type { border-top: none; padding-top: .1rem; }
.as-jour-head { display: flex; flex-wrap: wrap; gap: .5rem; align-items: baseline; }
.as-jour-date { font-family: var(--mono); font-weight: 500; }
.as-jour-total { font-family: var(--mono); color: var(--muted); font-size: 11px; margin-left: auto; }
.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-conge { text-align: right; padding-right: .75rem; }
.pointage-table .cg-trigger { min-height: 36px; padding: .3rem .6rem; }
.pointage-table .cg-chip { font-size: 9.5px; padding: .18rem .3rem; }
.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);
border-radius: 4px;
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);
border-radius: var(--r-m);
box-shadow: var(--shadow-m);
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);
border-radius: var(--r-s);
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);
border-radius: var(--r-m);
background: var(--surface);
box-shadow: var(--shadow-s);
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);
border-radius: var(--r-m);
overflow: hidden;
box-shadow: var(--shadow-s);
}
@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);
border-radius: var(--r-s);
overflow: hidden;
}
.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);
border-radius: var(--r-m);
overflow: hidden;
box-shadow: var(--shadow-s);
}
@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);
border-radius: var(--r-m);
box-shadow: var(--shadow-s);
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;
border-radius: var(--r-m);
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);
border-radius: var(--r-m);
box-shadow: var(--shadow-m);
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);
border-radius: var(--r-s);
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;
border-radius: var(--r-s);
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);
border-radius: var(--r-m);
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>