From 6ecd3d4540efda9cb2c5af43e40cd658ebeb3822 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Wed, 24 Jun 2026 13:21:04 +0200 Subject: [PATCH] Fix biome lint errors: remove duplicate CSS properties, apply safe auto-fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSS: - Remove duplicate 'background' fallbacks in base.css, header.css, search.css (solid color declared before gradient — gradient always wins) - Remove duplicate 'padding' in admin.css .admin-import-log JS (biome --write safe fixes applied): - function() → arrow functions in all IIFEs and callbacks - forEach/callback → arrow functions - evaluePtrn → parseInt(x, 10) in admin-contacts-form.js - Cleaned label text in build.mjs lint step Remaining warnings are intentional: !important overrides, descending specificity (admin.css cascade), noUnusedVariables (functions exported to window/onclick), useTemplate style preference. --- app/public/assets/css/admin.css | 51 ++-- app/public/assets/css/base.css | 1 - app/public/assets/css/components/header.css | 1 - app/public/assets/css/components/search.css | 1 - app/public/assets/css/components/toc.css | 141 +++++++++++ app/public/assets/css/content-page.css | 233 +++--------------- app/public/assets/css/style.css | 1 + .../assets/js/app/admin-acces-sharelink.js | 8 +- app/public/assets/js/app/admin-acces.js | 18 +- .../assets/js/app/admin-contacts-form.js | 8 +- .../assets/js/app/admin-contenus-langues.js | 16 +- .../assets/js/app/admin-contenus-motscles.js | 16 +- app/public/assets/js/app/admin-index-bulk.js | 28 ++- app/public/assets/js/app/admin-tags.js | 10 +- app/public/assets/js/app/admin-toc.js | 42 ++-- .../assets/js/app/file-upload-filepond.js | 4 +- .../assets/js/app/form-duration-toggle.js | 2 +- app/public/assets/js/app/form-jury-fields.js | 8 +- .../assets/js/app/form-language-asterisk.js | 2 +- app/public/assets/js/app/htmx-global-setup.js | 8 +- .../assets/js/app/repertoire-accordion.js | 16 +- .../js/app/repertoire-student-popover.js | 10 +- .../assets/js/app/sidebar-links-editor.js | 10 +- app/public/assets/js/app/smtp-error-focus.js | 2 +- app/templates/admin/partials/admin-toc.php | 16 +- app/templates/public/about.php | 4 +- app/templates/public/charte.php | 6 +- app/templates/public/licence.php | 6 +- biome.json | 7 +- justfile | 4 + scripts/build.mjs | 4 + 31 files changed, 336 insertions(+), 348 deletions(-) create mode 100644 app/public/assets/css/components/toc.css diff --git a/app/public/assets/css/admin.css b/app/public/assets/css/admin.css index 8c94190..7509613 100644 --- a/app/public/assets/css/admin.css +++ b/app/public/assets/css/admin.css @@ -1198,7 +1198,6 @@ th.admin-ap-col { .admin-import-log { list-style: none; margin: 0; - padding: 0; display: flex; flex-direction: column; gap: var(--space-3xs); @@ -2158,18 +2157,23 @@ th.admin-ap-col { 100% { transform: scaleX(0); transform-origin: right; } } -/* ── Sidebar TOC ───────────────────────────────────────────────────────────── */ +/* ── Sidebar TOC (matches public .page-content alignment pattern) ────────── */ .admin-main--toc { display: flex; - gap: var(--space-xs); - align-items: flex-start; + gap: var(--space-2xl); + align-items: start; + padding: var(--space-xl) var(--space-m) var(--space-2xl); } .admin-main--toc > article { flex: 1; min-width: 0; - padding-top: var(--space-m); +} + +/* Align first child of article with TOC heading (same as public) */ +.admin-main--toc > article > :first-child { + margin-top: 2.2rem; } .admin-main--toc > article > section { @@ -2180,41 +2184,14 @@ th.admin-ap-col { margin-bottom: var(--space-m); } -.admin-toc { - position: sticky; - top: var(--space-xs); - width: 160px; +/* Admin TOC: same
as public pages, positioned sticky */ +#admin-toc { + width: 180px; flex-shrink: 0; - padding-top: var(--space-m); } -.admin-toc-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 2px; -} - -.admin-toc-list a { - display: block; - padding: var(--space-3xs) var(--space-2xs); - font-size: var(--step--2); - color: var(--text-secondary); - text-decoration: none; - border-left: 2px solid transparent; - transition: color 0.15s, border-color 0.15s; -} - -.admin-toc-list a:hover { - color: var(--text-primary); -} - -.admin-toc-list a.admin-toc-active { - color: var(--text-primary); - font-weight: 600; - border-left-color: var(--accent, var(--color-primary)); +#admin-toc .toc-list a { + font-size: var(--step--1); } /* ── Lazy-load transition ─────────────────────────────────────────────────── */ diff --git a/app/public/assets/css/base.css b/app/public/assets/css/base.css index 8de1e5d..aaf70f7 100644 --- a/app/public/assets/css/base.css +++ b/app/public/assets/css/base.css @@ -39,7 +39,6 @@ html, body { body { font-family: var(--font-body); font-weight: 300; - background: var(--bg-primary); color: var(--text-primary); background: linear-gradient( 180deg, diff --git a/app/public/assets/css/components/header.css b/app/public/assets/css/components/header.css index efef98c..b1ab464 100644 --- a/app/public/assets/css/components/header.css +++ b/app/public/assets/css/components/header.css @@ -8,7 +8,6 @@ header { vertical-align: center; flex-shrink: 0; - background: #c05de1; background: linear-gradient( 0deg, rgba(192, 93, 225, 1) 0%, diff --git a/app/public/assets/css/components/search.css b/app/public/assets/css/components/search.css index 85153fc..5da6c48 100644 --- a/app/public/assets/css/components/search.css +++ b/app/public/assets/css/components/search.css @@ -21,7 +21,6 @@ form[role="search"]:not(.header-search-form) { .header-search-wrap { padding: 0; flex-shrink: 0; - background: #C05DE1; background: linear-gradient(180deg, rgba(192, 93, 225, 1) 0%, rgba(255, 255, 255, 1) 100%); } diff --git a/app/public/assets/css/components/toc.css b/app/public/assets/css/components/toc.css new file mode 100644 index 0000000..d8707b4 --- /dev/null +++ b/app/public/assets/css/components/toc.css @@ -0,0 +1,141 @@ +/* ============================================================ + SHARED TOC — sidebar table-of-contents for admin + public pages. + Both use
markup. + Public: server-rendered links (about, charte, licence) + Admin: JS-populated links (contenus, accès, paramètres — admin-toc.js) + ============================================================ */ + +/* ── Shared list styles (.toc-list) ─────────────────────────────────────── */ +.toc-list, +.toc ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: var(--space-3xs); +} + +.toc-list a, +.toc ul a { + display: block; + font-family: var(--font-body); + font-size: var(--step-0); + font-weight: 300; + color: var(--text-primary); + text-decoration: none; + padding: var(--space-3xs) 0; + transition: color 0.15s; +} + +.toc-list a:hover { + color: var(--accent-primary); +} + +.toc-list a.toc-active, +.toc ul a.toc-active { + color: var(--accent-primary); + font-weight: 400; +} + +/* ── Public TOC (details/summary block) ─────────────────────────────────── */ +.toc { + border: none; + border-radius: 0; + background: transparent; + overflow: visible; +} + +.toc[open] { + padding-bottom: 0; +} + +.toc > :not(summary) { + padding-left: 0; + padding-right: 0; +} + +.toc summary { + font-family: var(--font-display); + font-size: var(--step-1); + font-weight: 400; + color: var(--text-primary); + padding: 0 0 var(--space-2xs) 0; + border-bottom: 1px solid var(--text-primary); + display: flex; + align-items: center; + gap: var(--space-2xs); + background: transparent; + cursor: pointer; + list-style: none; +} + +.toc summary::-webkit-details-marker { + display: none; +} + +.toc summary:hover { + color: inherit; + background: transparent; +} + +/* Caret icon inside summary — visible only on mobile */ +.toc-caret { + flex-shrink: 0; + transition: transform 0.2s ease; +} + +.toc[open] > summary .toc-caret { + transform: rotate(180deg); +} + +/* Sidebar links (about page — custom links below TOC) */ +.toc-sidebar-link:first-of-type { + padding-top: var(--space-s); + border-top: 1px solid var(--border-primary); +} + +.toc-sidebar-link + .toc-sidebar-link { + padding-top: var(--space-xs); +} + +.toc-sidebar-link a { + font-size: var(--step--2); + color: var(--accent-primary); + text-decoration: none; + transition: opacity 0.15s; +} + +.toc-sidebar-link a:hover { + opacity: 0.8; +} + +/* ── Desktop: sticky sidebar ───────────────────────────────────────────── */ +@media (min-width: 768px) { + .toc { + position: sticky; + top: var(--space-l); + grid-column: 1; + } + + .toc summary { + pointer-events: none; + } + + .toc-caret { + display: none; + } +} + +/* ── Mobile: collapsible TOC ────────────────────────────────────────────── */ +@media (max-width: 767px) { + .toc { + position: static; + grid-column: 1; + } + + .toc summary { + cursor: pointer; + list-style: revert; + } +} diff --git a/app/public/assets/css/content-page.css b/app/public/assets/css/content-page.css index 3ef455d..d850332 100644 --- a/app/public/assets/css/content-page.css +++ b/app/public/assets/css/content-page.css @@ -21,142 +21,17 @@ align-items: start; } -/* ------------------------------------------------------------------ */ -/* Table of contents — details/summary block */ -/* ------------------------------------------------------------------ */ - -.toc { - border: none; - border-radius: 0; - background: transparent; - overflow: visible; -} - -.toc[open] { - padding-bottom: 0; -} - -.toc > :not(summary) { - padding-left: 0; - padding-right: 0; -} - -.toc summary { - font-family: var(--font-display); - font-size: var(--step-1); - font-weight: 400; - color: var(--text-primary); - padding: 0 0 var(--space-2xs) 0; - border-bottom: 1px solid var(--text-primary); - display: flex; - align-items: center; - gap: var(--space-2xs); - background: transparent; - cursor: pointer; - list-style: none; -} - -.toc summary::-webkit-details-marker { - display: none; -} - -.toc summary:hover { - color: inherit; - background: transparent; -} - -/* Caret icon inside summary — visible only on mobile */ -.toc-caret { - flex-shrink: 0; - transition: transform 0.2s ease; -} - -.toc[open] > summary .toc-caret { - transform: rotate(180deg); -} - -.toc ul { - list-style: none; - margin: var(--space-xs) 0 0 0; - padding: 0; - display: flex; - flex-direction: column; - gap: var(--space-3xs); -} - -.toc ul a { - font-family: var(--font-body); - font-size: var(--step-0); - font-weight: 300; - color: var(--text-primary); - text-decoration: none; - display: block; - padding: var(--space-3xs) 0; - transition: color 0.15s; -} - -.toc ul a:hover { - color: var(--accent-primary); -} - -/* Sidebar links (about page) */ -.toc-sidebar-link:first-of-type { - padding-top: var(--space-s); - border-top: 1px solid var(--border-primary); -} - -.toc-sidebar-link + .toc-sidebar-link { - padding-top: var(--space-xs); -} - -.toc-sidebar-link a { - font-size: var(--step--2); - color: var(--accent-primary); - text-decoration: none; - transition: opacity 0.15s; -} - -.toc-sidebar-link a:hover { - color: var(--accent-primary); - opacity: 1; -} - -/* ------------------------------------------------------------------ */ -/* Desktop: force-open TOC, hide caret, sticky sidebar */ -/* ------------------------------------------------------------------ */ +/* ── TOC styles → components/toc.css (shared with admin) ───────────────── */ +/* Grid column positioning for content pages with sidebar TOC */ @media (min-width: 768px) { - .toc { - position: sticky; - top: var(--space-l); - grid-column: 1; - } - - .toc summary { - pointer-events: none; - list-style: none; - } - - .toc summary::-webkit-details-marker { - display: none; - } - - .toc-caret { - display: none; - } - - .page-content > .content, - .page-content > .content-section { + .page-content > article { grid-column: 2; min-width: 0; max-width: 100%; } } -/* ------------------------------------------------------------------ */ -/* Mobile: collapsible TOC */ -/* ------------------------------------------------------------------ */ - @media (max-width: 767px) { .page-content { grid-template-columns: 1fr; @@ -164,30 +39,25 @@ padding: var(--space-m) var(--space-s) var(--space-xl); } + .page-content > article { + grid-column: 1; + } + .toc { margin-top: var(--space-s); - position: static; - grid-column: 1; - } - - .page-content > .content, - .page-content > .content-section { - grid-column: 1; - } - - .toc summary { - cursor: pointer; - list-style: revert; } } +/* ── First content child aligns with TOC heading top ───────────────────── */ +.page-content > article > :first-child { + margin-top: 2.2rem; +} + /* ------------------------------------------------------------------ */ -/* Main content area */ +/* Article content typography */ /* ------------------------------------------------------------------ */ -/* Shared typography for about-page sections and charte/licence content */ -.content, -.content-section { +.page-content > article { display: block; max-width: 100%; font-family: var(--font-body); @@ -195,77 +65,57 @@ line-height: 1.6; color: var(--text-primary); font-weight: 300; + padding-bottom: var(--space-xl); } -.content *, -.content-section * { +.page-content > article * { max-width: 100%; overflow-wrap: anywhere; word-break: break-word; } -.content { - padding-bottom: var(--space-xl); -} - -.content p, -.content-section p { +.page-content > article p { margin: 0 0 1em 0; } -.content p:last-child, -.content-section p:last-child { +.page-content > article p:last-child { margin-bottom: 0; } -.content :where(h1, h2, h3), -.content-section :where(h1, h2, h3) { +.page-content > article :where(h1, h2, h3) { margin: 1.5em 0 0.5em 0; } -.content :where(h1, h2, h3):first-child, -.content-section :where(h1, h2, h3):first-child { - margin-top: 2.2rem; -} - -.content a, -.content-section a { +.page-content > article a { color: inherit; text-decoration: none; font-weight: 700; } -.content a:hover, -.content-section a:hover { +.page-content > article a:hover { color: var(--accent-primary); text-decoration: none; } -.content ul, -.content ol, -.content-section ul, -.content-section ol { +.page-content > article ul, +.page-content > article ol { padding-left: var(--space-m); margin-bottom: var(--space-s); } -.content li, -.content-section li { +.page-content > article li { margin-bottom: 0.3em; } -.content strong, -.content-section strong { +.page-content > article strong { font-weight: 700; } -.content em, -.content-section em { +.page-content > article em { font-style: italic; } -.content :where(pre, pre code, code), -.content-section :where(pre, pre code, code) { +.page-content > article :where(pre, pre code, code) { display: block; max-width: 100%; overflow-x: auto; @@ -288,19 +138,29 @@ max-width: 100%; } -/* Section separators (about page only — .content-section adds dividers) */ -.page-content > .content-section { +/* Section separators (about page only) */ +.page-content > article > section { padding-bottom: var(--space-xl); border-bottom: 1px solid var(--border-primary); margin-bottom: var(--space-xl); } -.page-content > .content-section:last-child { +.page-content > article > section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: var(--space-xl); } +/* Scroll margin so anchor links account for the sticky header */ +.page-content > article :where(h1, h2, h3) { + scroll-margin-top: var(--space-l); +} + +/* Hide CommonMark heading permalink anchors */ +.heading-permalink { + display: none; +} + /* ------------------------------------------------------------------ */ /* Section titles */ /* ------------------------------------------------------------------ */ @@ -314,17 +174,6 @@ line-height: 1.1; } -/* Scroll margin so anchor links account for the sticky header */ -.content :where(h1, h2, h3), -.content-section :where(h1, h2, h3) { - scroll-margin-top: var(--space-l); -} - -/* Hide CommonMark heading permalink anchors (id now lives on the heading itself) */ -.heading-permalink { - display: none; -} - /* ------------------------------------------------------------------ */ /* Contacts grid */ /* ------------------------------------------------------------------ */ @@ -431,7 +280,7 @@ padding: var(--space-m) var(--space-s) var(--space-xl); } - .content-section { + .page-content > article { font-size: var(--step-0); } diff --git a/app/public/assets/css/style.css b/app/public/assets/css/style.css index 5ced664..0c9bde1 100644 --- a/app/public/assets/css/style.css +++ b/app/public/assets/css/style.css @@ -23,5 +23,6 @@ @import "components/pagination.css"; @import "components/header.css"; @import "components/search.css"; +@import "components/toc.css"; @import "utilities.css"; diff --git a/app/public/assets/js/app/admin-acces-sharelink.js b/app/public/assets/js/app/admin-acces-sharelink.js index 7c96068..5e95b47 100644 --- a/app/public/assets/js/app/admin-acces-sharelink.js +++ b/app/public/assets/js/app/admin-acces-sharelink.js @@ -3,10 +3,10 @@ * * Provides: dialog openers, clipboard copy, password dialog. */ -(function () { +(() => { var createBtn = document.getElementById('open-create-dialog'); if (createBtn) { - createBtn.addEventListener('click', function () { + createBtn.addEventListener('click', () => { document.getElementById('create-dialog').showModal(); }); } @@ -26,11 +26,11 @@ function _executeDeleteLink() { function copyUrl(id) { var input = document.getElementById('url-' + id); - navigator.clipboard.writeText(input.value).then(function () { + navigator.clipboard.writeText(input.value).then(() => { var btn = event.target.closest('button'); var orig = btn.textContent; btn.textContent = '✓ Copié'; - setTimeout(function () { + setTimeout(() => { btn.textContent = orig; }, 1200); }); diff --git a/app/public/assets/js/app/admin-acces.js b/app/public/assets/js/app/admin-acces.js index f4f3a3b..a9936ab 100644 --- a/app/public/assets/js/app/admin-acces.js +++ b/app/public/assets/js/app/admin-acces.js @@ -6,7 +6,7 @@ * * */ -(function () { +(() => { var baseUrlMeta = document.querySelector('meta[name="acces-base-url"]'); var passwordMeta = document.querySelector('meta[name="acces-new-link-password"]'); var slugMeta = document.querySelector('meta[name="acces-new-link-slug"]'); @@ -22,7 +22,7 @@ // Create dialog opener var createBtn = document.getElementById('open-create-dialog'); if (createBtn) { - createBtn.addEventListener('click', function () { + createBtn.addEventListener('click', () => { document.getElementById('create-dialog').showModal(); }); } @@ -30,12 +30,12 @@ function copyUrl(id) { var input = document.getElementById('url-' + id); - navigator.clipboard.writeText(input.value).then(function () { + navigator.clipboard.writeText(input.value).then(() => { var btn = event.target.closest('button'); if (btn) { var orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); - setTimeout(function () { + setTimeout(() => { btn.setAttribute('title', orig); }, 1200); } @@ -43,12 +43,12 @@ function copyUrl(id) { } function copyUrlFrom(el) { - navigator.clipboard.writeText(el.value).then(function () { + navigator.clipboard.writeText(el.value).then(() => { var btn = el.nextElementSibling; if (btn) { var orig = btn.textContent; btn.textContent = '✓ Copié'; - setTimeout(function () { + setTimeout(() => { btn.textContent = orig; }, 1200); } @@ -57,17 +57,17 @@ function copyUrlFrom(el) { function copyTextToClipboard(text) { navigator.clipboard.writeText(text) - .then(function () { + .then(() => { var btn = event && event.target ? event.target.closest('button') : null; if (btn) { var orig = btn.getAttribute('title') || ''; btn.setAttribute('title', '✓ Copié'); - setTimeout(function () { + setTimeout(() => { btn.setAttribute('title', orig); }, 1200); } }) - .catch(function () {}); + .catch(() => {}); } function openEditDialog(id, name, hasPassword, expiresVal) { diff --git a/app/public/assets/js/app/admin-contacts-form.js b/app/public/assets/js/app/admin-contacts-form.js index 1216b60..19ec0b7 100644 --- a/app/public/assets/js/app/admin-contacts-form.js +++ b/app/public/assets/js/app/admin-contacts-form.js @@ -4,7 +4,7 @@ * Reads `data-apropos-key` and `data-apropos-group-count` from the form element. * Expects templates with ids `entry-template-f-{key}` and `group-template-f-{key}`. */ -(function () { +(() => { var form = document.querySelector('form[data-apropos-key]'); if (!form) return; @@ -16,12 +16,12 @@ function reindexGroups() { var fieldsets = form.querySelectorAll('fieldset.apropos-group'); groupCount = fieldsets.length; - fieldsets.forEach(function (fs, i) { + fieldsets.forEach((fs, i) => { var newIdx = i; var legend = fs.querySelector('legend'); if (legend) legend.textContent = 'Contact ' + (newIdx + 1); - fs.querySelectorAll('input').forEach(function (inp) { + fs.querySelectorAll('input').forEach((inp) => { if (inp.name) { inp.name = inp.name.replace(/groups\[\d+\]/, 'groups[' + newIdx + ']'); } @@ -33,7 +33,7 @@ } }); - fs.querySelectorAll('label[for]').forEach(function (lbl) { + fs.querySelectorAll('label[for]').forEach((lbl) => { lbl.setAttribute( 'for', lbl.getAttribute('for').replace( diff --git a/app/public/assets/js/app/admin-contenus-langues.js b/app/public/assets/js/app/admin-contenus-langues.js index 3187afc..59efbd9 100644 --- a/app/public/assets/js/app/admin-contenus-langues.js +++ b/app/public/assets/js/app/admin-contenus-langues.js @@ -18,7 +18,7 @@ function languesSubmitPending() { } function languesToggleAll(src) { - document.querySelectorAll('input[name="selected_langs[]"]').forEach(function (cb) { + document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => { cb.checked = src.checked; }); languesUpdateBulk(); @@ -32,7 +32,7 @@ function languesUpdateBulk() { var visible = n > 1; bar.style.display = visible ? 'flex' : 'none'; if (visible) { - requestAnimationFrame(function () { + requestAnimationFrame(() => { wrap.style.setProperty('--sticky-top', bar.offsetHeight + 'px'); }); } else { @@ -41,7 +41,7 @@ function languesUpdateBulk() { } function languesCancelSelection() { - document.querySelectorAll('input[name="selected_langs[]"]').forEach(function (cb) { + document.querySelectorAll('input[name="selected_langs[]"]').forEach((cb) => { cb.checked = false; }); languesUpdateBulk(); @@ -57,7 +57,7 @@ function languesConfirmBulkDelete() { function languesExecBulkDelete() { var container = document.getElementById('langues-bulk-checkboxes'); container.innerHTML = ''; - document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach(function (cb) { + document.querySelectorAll('input[name="selected_langs[]"]:checked').forEach((cb) => { var inp = document.createElement('input'); inp.type = 'hidden'; inp.name = 'selected_langs[]'; @@ -76,7 +76,7 @@ function languesConfirmBulkMerge() { document.getElementById('langues-bulk-merge-count').textContent = checked.length; var sel = document.getElementById('langues-bulk-merge-target-select'); sel.innerHTML = ''; - checked.forEach(function (cb) { + checked.forEach((cb) => { var tr = cb.closest('tr'); sel.innerHTML += ''; - checked.forEach(function (cb) { + checked.forEach((cb) => { var tr = cb.closest('tr'); sel.innerHTML += ''; - checked.forEach(function (cb) { + checked.forEach((cb) => { var tr = cb.closest('tr'); sel.innerHTML += '