diff --git a/TODO.md b/TODO.md index 82e789d..d5825a5 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,13 @@ > Last updated: 2026-06-24 > Context: Security audit — fix open redirects, fragment auth, dead code, CSRF gaps +## Deferred / Blocked +- [ ] #tighten-csp Tighten CSP to remove 'unsafe-inline' after inline JS extraction + +## Pending +- [ ] #rep-student-touch Replace hover student popover with tap-to-open drawer for mobile `(repertoire.php, repertoire.css, repertoire-student-popover.js)` +- [x] #rep-polish Polish: scroll-position memory on HTMX swap, animation tuning `(repertoire.css)` ✓ + ## Completed - [x] #icon-color-verify Verify icon colors render correctly across all pages (header, admin tables, forms, dialogs, cleanup modal) ✓ - [x] #sec-open-redirect Fix open redirect in tag.php + language.php (protocol-relative URL bypass via str_starts_with) ✓ @@ -18,12 +25,6 @@ - [x] #sec-fragments-auth Gate partagé fragments on share_active session (read-only fragment renderers — no CSRF needed) ✓ - [x] #sec-retry-csrf Add CSRF check to partage/retry-email.php POST ✓ - [x] #sec-cleanup-dead-code Remove dead App::verifyCsrf() or refactor action handlers to use it ✓ - -## Pending -- [ ] #rep-student-touch Replace hover student popover with tap-to-open drawer for mobile `(repertoire.php, repertoire.css, repertoire-student-popover.js)` -- [ ] #rep-polish Polish: scroll-position memory on HTMX swap, animation tuning `(repertoire.css)` - -## Completed (before this session) - [x] #gzip-nginx Enable gzip compression in nginx config `(nginx/xamxam.conf)` ✓ - [x] #extract-inline-js Move inline JS to external files across 17 templates → 15 new JS files created `(app/public/assets/js/app/*.js)` ✓ - [x] #inline-icon-helper Create `icon()` PHP helper + auto-load in bootstrap `(src/icon.php, bootstrap.php)` ✓ @@ -77,5 +78,3 @@ - [x] #split-form-css Split `form.css` into `form-base.css` and `form-admin.css` ✓ - [x] #extra-css-admin Update `head.php` to support `$extraCssAdmin` for admin-only stylesheets `(head.php)` ✓ -## Deferred / Blocked -- [ ] #tighten-csp Tighten CSP to remove 'unsafe-inline' after inline JS extraction diff --git a/app/public/assets/css/repertoire.css b/app/public/assets/css/repertoire.css index c71eb7c..7d0345b 100644 --- a/app/public/assets/css/repertoire.css +++ b/app/public/assets/css/repertoire.css @@ -290,6 +290,17 @@ color: var(--accent-primary); } +/* ── Swap transition ────────────────────────────────────────────────── */ + +.repertoire-index.htmx-swapping { + opacity: 0.5; + transition: opacity 0.1s ease-out; +} + +.repertoire-index { + transition: opacity 0.15s ease-in; +} + /* Link variant (students col) — no underline by default */ .rep-entry--link { text-decoration: none; @@ -333,7 +344,7 @@ height: 2px; background: var(--accent-primary); opacity: 0; - transition: opacity 0.15s; + transition: opacity 0.1s; z-index: 100; pointer-events: none; } diff --git a/app/public/assets/js/app/public-entry.js b/app/public/assets/js/app/public-entry.js index ed72497..0108e19 100644 --- a/app/public/assets/js/app/public-entry.js +++ b/app/public/assets/js/app/public-entry.js @@ -14,6 +14,7 @@ import "./htmx-global-setup.js"; // Public page features import "./repertoire-accordion.js"; +import "./repertoire-scroll-restore.js"; import "./repertoire-student-popover.js"; import "./access-request.js"; import "./acces-password.js"; diff --git a/app/public/assets/js/app/repertoire-scroll-restore.js b/app/public/assets/js/app/repertoire-scroll-restore.js new file mode 100644 index 0000000..b5d2ac0 --- /dev/null +++ b/app/public/assets/js/app/repertoire-scroll-restore.js @@ -0,0 +1,70 @@ +/** + * repertoire-scroll-restore.js — Preserve column scroll positions on HTMX swap. + * + * When a filter button triggers an HTMX swap that replaces #repertoire-index, + * the new markup replaces the old, resetting all column scroll positions to 0. + * This module captures scrollTop of each scrollable