Files
pointeuse-optimisator/app/templates/base.html
Jacquin Antoine 8e0f2c453f Instance locale sans config : mail et lien email optionnels
L'app tourne désormais pour soi sans rien configurer. Sans relais
SMTP, le lien de création de compte s'affiche directement à l'écran,
construit depuis l'URL en cours — aucune config d'URL nécessaire,
même en naviguant par IP sur le LAN (le contrôle anti-open-redirect
du Host ne concerne que le chemin par email). allowed_email_domain
vide accepte tous les emails : l'adresse ne sert que d'identifiant.

Le fichier d'exemple de config était périmé : il datait d'avant les
plages, force_secure_cookies et login_rate_limit_per_min, et
laissait croire le SMTP obligatoire. Il reflète maintenant les clés
réelles, toutes optionnelles et vides par défaut.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-09-11 16:05:32 +02:00

1665 lines
52 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); }
/* Crédit d'heures de présence couvertes par le repos de 11h (retour décalé
après astreinte) : affiché sous le total travaillé, détail au survol. */
.calc-couvert {
display: block;
font-family: var(--mono);
font-size: 10px;
color: var(--accent-2);
margin-top: .12rem;
margin-left: auto;
width: fit-content;
cursor: help;
border-bottom: 1px dotted rgba(184,106,8,.45);
white-space: normal;
}
.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-link { margin: .6rem 0 0; font-size: 13px; word-break: break-all; }
.login-link a { color: var(--accent); }
.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; }
/* ── Semaine parfaite : célébration kitsch (+0h00 pile, rien à remplir) ── */
#perfect-week {
position: fixed; inset: 0; z-index: 400;
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: .6rem; cursor: pointer;
background:
radial-gradient(circle at 20% 20%, rgba(255,0,128,.45), transparent 40%),
radial-gradient(circle at 80% 30%, rgba(0,201,167,.45), transparent 45%),
radial-gradient(circle at 30% 80%, rgba(255,215,0,.45), transparent 40%),
radial-gradient(circle at 75% 85%, rgba(90,80,255,.45), transparent 45%),
rgba(20, 16, 28, .88);
animation: perfect-disco 1.6s linear infinite;
}
.perfect-trophy { font-size: 4.2rem; line-height: 1; animation: perfect-bounce .7s ease-in-out infinite alternate; }
.perfect-title {
font-size: clamp(1.9rem, 6vw, 3.6rem); font-weight: 800; letter-spacing: .04em;
background: linear-gradient(90deg, #ffd700, #ff5ec4, #7af5ff, #ffd700);
background-size: 300% 100%;
-webkit-background-clip: text; background-clip: text; color: transparent;
animation: perfect-shine 2.2s linear infinite, perfect-pop .8s cubic-bezier(.2,1.6,.4,1) 1;
text-align: center; padding: 0 1rem;
}
.perfect-sub { color: #fff; font-size: 1.05rem; text-align: center; padding: 0 1rem; }
.perfect-sub b { font-family: var(--mono); color: #ffd700; }
.perfect-close { color: rgba(255,255,255,.75); font-size: .85rem; margin-top: 1.2rem; }
.perfect-confetti {
position: absolute; top: -8vh; user-select: none; pointer-events: none;
animation: perfect-fall linear forwards;
}
@keyframes perfect-disco {
0% { filter: hue-rotate(0deg) saturate(1.2); }
100% { filter: hue-rotate(360deg) saturate(1.2); }
}
@keyframes perfect-shine {
0% { background-position: 0% 50%; }
100% { background-position: 300% 50%; }
}
@keyframes perfect-pop {
0% { transform: scale(.2) rotate(-6deg); opacity: 0; }
70% { transform: scale(1.15) rotate(2deg); opacity: 1; }
100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
@keyframes perfect-bounce {
0% { transform: translateY(0) scale(1); }
100% { transform: translateY(-14px) scale(1.12); }
}
@keyframes perfect-fall {
0% { transform: translateY(0) rotate(0deg); opacity: 1; }
100% { transform: translateY(118vh) rotate(720deg); opacity: .85; }
}
@media (prefers-reduced-motion: reduce) {
#perfect-week, .perfect-trophy, .perfect-title, .perfect-confetti { animation: none; }
.perfect-confetti { display: none; }
}
</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>