diff --git a/TODO.md b/TODO.md
index 88fb8b2..8dd73dd 100644
--- a/TODO.md
+++ b/TODO.md
@@ -447,3 +447,118 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis
- [ ] **`__wakeup()` singleton guard throws from a public method** — PHP 8.x deprecates
throwing exceptions from `__wakeup`. Change to `trigger_error(…, E_USER_ERROR)` or implement
`__serialize()`/`__unserialize()` that always throw.
+
+---
+
+## Refactor & Maintenance — Templates & Frontend (audit 2026-03-26)
+
+### D — Template structure / boilerplate duplication
+
+- [ ] **Every public page duplicates its own `
`** — `index.php`, `search.php`, `tfe.php`,
+ `apropos.php`, `licence.php` each contain an identical block: ``,
+ ``, ``, ``, ``,
+ ``, ``, live-reload script. Only `` and one
+ extra CSS `` differ. Extract a `templates/public/head.php` partial accepting
+ `$pageTitle` and `$extraCss` — mirrors the pattern `templates/admin/head.php` already uses.
+
+- [ ] **Live-reload snippet copy-pasted into 6 files** — `index.php`, `search.php`, `tfe.php`,
+ `apropos.php`, `licence.php`, `templates/admin/head.php` all contain the same 6-line
+ `(function poll(){…})()` block. Consolidate into the shared head partials.
+
+- [ ] **`templates/header.php` and `templates/head.php` are dead files** — neither is `include`d
+ anywhere in the codebase. Both contain outdated markup from a previous design iteration
+ (`lang="en"`, empty author meta, a broken nav with double-quoted `href` attributes inside
+ `href`). Delete both to remove confusion.
+
+- [ ] **`public/assets/icons.svg` is dead** — it is the full TrumboWYG editor icon sprite (40+
+ symbols) referenced **nowhere** in the codebase. The only WYSIWYG editor in use (EasyMDE
+ in `pages-edit.php`) loads from CDN. Delete `icons.svg` (~15 KB of noise).
+
+- [ ] **`admin_favicon.svg` used as the public-facing favicon** — every public page links
+ `/assets/admin_favicon.svg`. Rename or create a distinct `favicon.svg` so admin and public
+ can diverge without naming confusion.
+
+### E — CSS architecture
+
+- [ ] **`html, body { margin:0; padding:0; height:100% }` repeated in 4 page stylesheets** —
+ `main.css`, `search.css`, `tfe.css`, `apropos.css` all open with this identical block.
+ Move it to `common.css` once; delete from the four files. Same for the body-level flex-column
+ shell (`display:flex; flex-direction:column; background:var(--white)`) which only differs in
+ the BEM class name applied to ``.
+
+- [ ] **No `font-display` on the `Combinedd.otf` custom font** — `common.css` declares `@font-face`
+ with no `font-display` property; the browser blocks text rendering until the font loads (FOIT).
+ Add `font-display: swap`. Also add a `` for the font file in the shared
+ head partial once it exists.
+
+- [ ] **Search results pagination is fully inline-styled** — `search.php` lines 159–164 apply
+ `style="padding:.25rem .7rem;border:1px solid #ddd;…"` and hardcoded `#ddd`/`#666`. The home
+ page (`index.php`) already has `.pagination-btn` / `.pagination-info` in `main.css`. Reuse
+ those classes in `search.php` and remove the inline styles.
+
+- [ ] **Scattered inline styles in templates** — notable instances that should become named classes:
+ - `tfe.php` line 146: `style="align-items:start;"` → `.tfe-meta-item--top` in `tfe.css`
+ - `tfe.php` lines 148, 170–172, 193: `font-style:italic`, `margin-top:1.5rem`,
+ `font-size:.88rem;color:#666`, `color:#999;font-style:italic` → `.tfe-note-value`,
+ `.tfe-back-link`, `.tfe-restricted` in `tfe.css`
+ - `admin/edit.php`: multiple `style=` on `.admin-form-row` and banner preview → modifier
+ classes in `admin.css`
+ - `index.php` line 146: `style="padding:2rem;color:#666;"` → `.cards-empty` in `main.css`
+
+- [ ] **`.site-nav__right` is a duplicate of `.site-nav__link`** — `common.css` defines both with
+ identical declarations (font-size, letter-spacing, text-transform, color, opacity, transition).
+ The only difference is DOM position. Merge `.site-nav__right` into `.site-nav__link`; let the
+ flex layout position it via `margin-left:auto` or DOM order.
+
+- [ ] **`.site-nav__link--active` is applied in `nav.php` but never defined in CSS** — the class
+ is set conditionally but has no corresponding rule in `common.css`, so the active state is
+ invisible. Add a visible style (e.g. `opacity:1; border-bottom:1px solid rgba(255,255,255,.6)`)
+ or remove the conditional.
+
+### F — Template logic / PHP in templates
+
+- [ ] **Rate-limit 429 response in `search.php` emits unstyled bare HTML** — the early-exit block
+ outputs `
Trop de requêtes
…` with no stylesheet, no lang,
+ no viewport meta. Style it inline-minimally or redirect to a consistent `429.php` page (like
+ `maintenance.php`).
+
+- [ ] **`apropos.php` contacts and credits are hardcoded in the template** — names, roles, emails
+ (Laurent Leprince, Xavier Gorgol, Brigitte Ledune) and credits text live in PHP/HTML and
+ require a code deploy to change. Either move them into the `about` page Markdown (admin-
+ editable) or extract to a config array so they are in one place.
+
+- [ ] **`licence.php` wastes half the viewport with an always-empty right column** — the page
+ reuses the two-column `.apropos-layout` but `` is always
+ empty. Add a `.apropos-layout--single` variant (or just `grid-template-columns:1fr` when
+ the right child is empty) to use the full width for content.
+
+### G — Accessibility & semantics
+
+- [ ] **`