diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7444c31..5a35365 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.40.0 — 2026-10-28 — Navigation : section Administration + bouton Dark/Light (#51, #52)
+
+- **Accordéon (#51)** : la section Administration ne fermait jamais — le JS posait `nav-closed-admin`, mais `style.css` ne définissait les règles de fermeture que pour les 4 autres sections. Les règles admin sont ajoutées (fermeture et rotation du chevron) : l'accordéon redevient exclusif.
+- **Bouton Dark/Light (#52)** : bouton « ☀️ Mode clair / 🌙 Mode sombre » dans l'en-tête applicatif, qui bascule `data-theme` sur `` et mémorise le choix (localStorage, appliqué avant le rendu par l'amorce de `_header.html`). Thème clair sur les jetons draco (fond #f2f2f0, surfaces blanches/grises, accent rose conservé, second ton assombri pour les liens), barre Fusion et panneau Spark qui suivent. Défaut inchangé : sombre. `nav.js` est désormais versionné (`?v=`), comme dashboard.js depuis la 0.38.1.
+
## 0.39.0 — 2026-10-28 — Thème du site : look agent-dev / barre Fusion (#49)
- **Re-thème complet** : `style.css` passe sur les jetons draco-barre (Console v7.2) — fond #101211, surfaces #1a1c1a / #242624 / #20221f, bordure #2e302c, textes #eceee9 / #a6aba2 / #8b9089, accent rose Spark #c2437e (second ton #e078a8). Fond plat, plus de halos ni de dégradés violet→bleu, y compris les valeurs de repli et l'écran de connexion.
diff --git a/app/config.py b/app/config.py
index dc3ed59..2a756a6 100644
--- a/app/config.py
+++ b/app/config.py
@@ -177,7 +177,7 @@ from urllib.parse import urlparse
BASE_DIR = Path(__file__).resolve().parent.parent
-APP_VERSION = "0.39.0"
+APP_VERSION = "0.40.0"
def _env_bool(name: str, default: bool) -> bool:
diff --git a/app/templates/_header.html b/app/templates/_header.html
index 77b64c9..8910a5d 100644
--- a/app/templates/_header.html
+++ b/app/templates/_header.html
@@ -1,3 +1,15 @@
+
{% include "_draco_bar.html" %}
{# ── Cadre applicatif partagé (issue #16, refonte 0.16.0) ─────────
Barre latérale unique pour TOUTES les pages applicatives :
@@ -141,6 +153,7 @@
🔗 Gitea ↗
{% endif %}
+
-
+
diff --git a/static/css/style.css b/static/css/style.css
index fc75eeb..ce2e32b 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -1298,11 +1298,13 @@ body.sidebar-collapsed .side-search { margin-left: 0; }
body.nav-closed-principal [data-group="principal"] .side-acc,
body.nav-closed-rituels [data-group="rituels"] .side-acc,
body.nav-closed-pilotage [data-group="pilotage"] .side-acc,
-body.nav-closed-outils [data-group="outils"] .side-acc { grid-template-rows: 0fr; }
+body.nav-closed-outils [data-group="outils"] .side-acc,
+body.nav-closed-admin [data-group="admin"] .side-acc { grid-template-rows: 0fr; }
body.nav-closed-principal [data-group="principal"] .side-chevron,
body.nav-closed-rituels [data-group="rituels"] .side-chevron,
body.nav-closed-pilotage [data-group="pilotage"] .side-chevron,
-body.nav-closed-outils [data-group="outils"] .side-chevron { transform: rotate(-90deg); }
+body.nav-closed-outils [data-group="outils"] .side-chevron,
+body.nav-closed-admin [data-group="admin"] .side-chevron { transform: rotate(-90deg); }
@media (min-width: 901px) {
/* Rail replié : pas d'accordéon, toutes les icônes visibles. */
body.sidebar-collapsed .side-acc { grid-template-rows: 1fr !important; }
@@ -1791,3 +1793,36 @@ body.sidebar-collapsed .ws-switch { display: none; }
.ms-form input:focus, .ms-form textarea:focus { outline: none; border-color: var(--accent); }
.ms-hint { color: var(--muted, #a6aba2); font-size: 0.78rem; }
.ms-form-actions { display: flex; gap: 8px; }
+
+/* ── Thème clair (#52) : jetons draco-barre (clair par défaut).
+ html[data-theme="light"] prime sur le :root sombre et sur le
+ prefers-color-scheme de la barre Fusion. Le sombre reste le
+ défaut (les gabarits portent data-theme="dark", le JS du
+ thème ne bascule que sur choix mémorisé). */
+html[data-theme="light"] {
+ --bg: #f2f2f0; --surface: #ffffff; --surface-2: #e9eae6; --surface-3: #f6f6f3;
+ --border: #dedfd9; --border-strong: #c9cbc2;
+ --text: #1b1d1a; --text-2: #5c615a; --text-3: #787d75;
+ --accent: #c2437e; --accent-2: #a92e63;
+ --shadow: 0 8px 28px rgba(0, 0, 0, 0.12);
+ --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.08);
+ color-scheme: light;
+}
+html[data-theme="light"] .sidebar { background: rgba(255, 255, 255, 0.96); }
+html[data-theme="light"] .app-header { background: rgba(255, 255, 255, 0.85); }
+/* Lien actif de la barre latérale : fond rose translucide sur
+ fond clair — le texte blanc du thème sombre y serait illisible. */
+html[data-theme="light"] .side-link.active { color: #1b1d1a; }
+/* Menu ⋯ des tuiles : jetons clairs explicites, pour primer sur
+ le prefers-color-scheme sombre du système. */
+html[data-theme="light"] .card-menu {
+ --menu-bg: #ffffff; --menu-bg-2: #e9eae6; --menu-ink: #1b1d1a;
+ --menu-ink-2: #5c615a; --menu-line: #dedfd9;
+}
+/* Barre Fusion : jetons clairs explicites, pour primer sur son
+ @media (prefers-color-scheme: dark). Le panneau Spark suit
+ via son MutationObserver sur data-theme (draco-panneau.js). */
+html[data-theme="light"] .draco-bar {
+ --bg: #f2f2f0; --surface: #ffffff; --surface-2: #e9eae6; --surface-3: #f6f6f3;
+ --ink: #1b1d1a; --ink-2: #5c615a; --ink-3: #787d75; --line: #dedfd9;
+}
diff --git a/static/js/nav.js b/static/js/nav.js
index 29e2622..59bc0d7 100644
--- a/static/js/nav.js
+++ b/static/js/nav.js
@@ -34,6 +34,26 @@
if (window.innerWidth > 900) closeDrawer();
});
+ /* Bascule Dark/Light (#52) : le choix est mémorisé et
+ l'amorce de _header.html l'a déjà appliqué avant le rendu.
+ Ici, on ne fait que synchroniser le libellé et le clic. */
+ var themeBtn = document.getElementById('theme-toggle');
+ function syncThemeBtn(theme) {
+ if (!themeBtn) return;
+ var light = theme === 'light';
+ themeBtn.textContent = light ? '🌙 Mode sombre' : '☀️ Mode clair';
+ themeBtn.setAttribute('aria-pressed', light ? 'true' : 'false');
+ }
+ if (themeBtn) {
+ syncThemeBtn(document.documentElement.getAttribute('data-theme') || 'dark');
+ themeBtn.addEventListener('click', function () {
+ var next = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem('kanbante-theme', next); } catch (e) { /* non-fatal */ }
+ syncThemeBtn(next);
+ });
+ }
+
if (collapseBtn) {
collapseBtn.addEventListener('click', function () {
var collapsed = document.body.classList.toggle('sidebar-collapsed');
diff --git a/tests/test_nav_theme.py b/tests/test_nav_theme.py
new file mode 100644
index 0000000..1c4c29e
--- /dev/null
+++ b/tests/test_nav_theme.py
@@ -0,0 +1,37 @@
+"""Navigation (#51) et bascule Dark/Light (#52).
+
+L'accordéon de la barre latérale omettait la section admin :
+le JS posait nav-closed-admin, mais aucun style ne la suivait.
+Le thème clair, lui, doit être atteignable par un bouton et
+mémorisé, avec les jetons draco clairs.
+"""
+
+
+def test_admin_accordion_closes(client, fake_gitea):
+ css = client.get("/static/css/style.css").text
+ assert 'body.nav-closed-admin [data-group="admin"] .side-acc' in css
+ assert 'body.nav-closed-admin [data-group="admin"] .side-chevron' in css
+
+
+def test_theme_toggle_present_and_versioned(client, fake_gitea):
+ page = client.get("/")
+ assert 'id="theme-toggle"' in page.text
+ assert "Mode clair" in page.text
+ assert "/static/js/nav.js?v=" in page.text
+
+
+def test_theme_js_contract(client, fake_gitea):
+ js = client.get("/static/js/nav.js").text
+ assert "kanbante-theme" in js
+ assert "theme-toggle" in js
+ assert "data-theme" in js
+
+
+def test_light_theme_tokens(client, fake_gitea):
+ css = client.get("/static/css/style.css").text
+ assert 'html[data-theme="light"]' in css
+ assert "--bg: #f2f2f0;" in css
+ assert "--surface: #ffffff;" in css
+ assert "--accent-2: #a92e63;" in css
+ assert 'html[data-theme="light"] .draco-bar' in css
+ assert 'html[data-theme="light"] .card-menu' in css