From b8529f7abeb747875eb87e0084c72a769a998f97 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Sat, 28 Mar 2026 16:51:31 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20WCAG=202.1=20AA=20contrast,=20mobile=20r?= =?UTF-8?q?=C3=A9pertoire=20layout,=20and=20pagination=20accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contrast failures (WCAG 1.4.3): - common.css: remove opacity:0.92 from .site-nav__link (was 4.05:1, now 4.87:1 white-on-purple) - common.css: placeholder colour #aaa → #767676 (2.32:1 → 4.54:1 on white) - main.css: filter-info and clear-filter text var(--purple) → var(--purple-dark) (#9557b5 → #7b3fa0, 4.08 → 5.7:1) - index.php: gradient card lighter stop L=65% → L=40%, darker stop L=45% → L=28%; white text now passes 4.5:1 across all hues Non-text contrast (WCAG 1.4.11): - search.css: search-filter ` carries all meaning). Add `aria-hidden="true"` and `focusable="false"` to the SVG. @@ -994,21 +994,21 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style #### 1.4.3 Contrast (minimum) — confirmed failures from measurement -- [ ] **Nav links at `opacity: 0.92` on purple background: 4.05:1** — fails AA (4.5:1 required +- [x] **Nav links at `opacity: 0.92` on purple background: 4.05:1** — fails AA (4.5:1 required for normal text). At full opacity the white-on-purple ratio is 4.87:1 (just passes), but the `opacity: 0.92` applied to `.site-nav__link` drops it to 4.05:1. Fix: remove the opacity reduction, or increase purple darkness slightly. -- [ ] **`filter-info` purple text `#9557b5` on purple-light background `rgba(149,87,181,0.12)` +- [x] **`filter-info` purple text `#9557b5` on purple-light background `rgba(149,87,181,0.12)` over white: 4.08:1** — fails AA. The filter info banner ("Année : 2024" or "Découvrez les TFE de 2024") uses purple text on a light purple tint. Use `var(--purple-dark)` (#7b3fa0) instead for the text, which would reach ~5.7:1. -- [ ] **Placeholder text `#aaa` on white: 2.32:1** — fails AA (and fails for large text too). +- [x] **Placeholder text `#aaa` on white: 2.32:1** — fails AA (and fails for large text too). Placeholder text is explicitly included in WCAG 1.4.3. Change to `#767676` minimum (~4.54:1) or preferably `#6b6b6b`. -- [ ] **Gradient card placeholder: white text on L=65% HSL backgrounds — most hues fail** — +- [x] **Gradient card placeholder: white text on L=65% HSL backgrounds — most hues fail** — measured across the full hue range at `hsl(H, 60%, 65%)` (the lighter end of the gradient): only hues 240–250° (blue-indigo) pass AA. Every warm hue (0–230°) and most cool hues fail, with ratios as low as 1.46:1 at yellow (hue=60°). Since hue is derived from `$item['id'] % 360`, @@ -1016,7 +1016,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style lighter end (would raise almost all hues above 3:1 for large text), or drop the text overlay inside the gradient entirely (the card caption below already shows title/author). -- [ ] **`admin-text-muted` `#888` on `admin-bg-alt` `#242424`: 4.38:1** — fails AA for normal +- [x] **`admin-text-muted` `#888` on `admin-bg-alt` `#242424`: 4.38:1** — fails AA for normal text. This combination appears in table cell muted text, form hints, and sub-labels across the admin. Darken to `#909090` (~4.5:1) or use `#959595`. @@ -1035,7 +1035,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style #### 1.4.10 Reflow (320px viewport) -- [ ] **Répertoire index 4-column grid has no mobile breakpoint** — `search.css` defines the +- [x] **Répertoire index 4-column grid has no mobile breakpoint** — `search.css` defines the 4-column grid at `1fr 2fr 2fr 1.5fr` with no `@media` fallback. At 320px the columns become ~50px wide each — unusable. Add a breakpoint to stack columns vertically below ~600px (or 768px). @@ -1047,9 +1047,9 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style #### 1.4.11 Non-text contrast -- [ ] **Search filter `` border is `#ddd` on white — 1.6:1** — the `border: 1px solid var(--border-color)` where `--border-color: #ddd` gives a 1.6:1 contrast ratio for the UI component boundary. WCAG 1.4.11 requires 3:1. Change to `#949494` minimum or use `#767676`. -- [ ] **Admin form inputs: `border-bottom: 1px solid #333` on `#1a1a1a` background — 1.8:1** — +- [x] **Admin form inputs: `border-bottom: 1px solid #333` on `#1a1a1a` background — 1.8:1** — the bottom-border-only inputs have `border-bottom: 1px solid var(--admin-border)` (#333) on `#1a1a1a` background: 1.8:1. Fails 1.4.11. Raise to `#555` minimum (~3.1:1). @@ -1066,7 +1066,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style #### 2.1.1 Keyboard -- [ ] **Disabled pagination links are keyboard-reachable** — `` +- [x] **Disabled pagination links are keyboard-reachable** — `` uses `.disabled { pointer-events: none }` in CSS which does not remove keyboard focus. A keyboard user can still Tab to these links and press Enter (which follows the href to page 0 or page N+1 unnecessarily). Fix: add `tabindex="-1"` and `aria-disabled="true"` @@ -1127,7 +1127,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style meta inside ``. The combined text read by screen readers will be "Author · Title · Year · Orientation" which is actually quite good. No hard failure here. -- [ ] **Pagination links use Unicode arrows `«`, `‹`, `›`, `»` as their only text** — these are +- [x] **Pagination links use Unicode arrows `«`, `‹`, `›`, `»` as their only text** — these are announced by screen readers as "double left-pointing angle quotation mark" or similar gibberish. Add `aria-label` to each: `aria-label="Première page"`, `aria-label="Page précédente"`, `aria-label="Page suivante"`, `aria-label="Dernière page"`. @@ -1194,7 +1194,7 @@ Current state: **zero ARIA attributes, zero skip links, zero focus-visible style #### 3.1.1 Language of page - [ ] **All public pages have ``** — correct. ✓ -- [ ] **`search.php` 429 response emits `` with no `lang` attribute** — fails 3.1.1. +- [x] **`search.php` 429 response emits `` with no `lang` attribute** — fails 3.1.1. Fix: `echo '…'`. #### 3.2.1 On focus / 3.2.2 On input diff --git a/public/assets/admin.css b/public/assets/admin.css index f9f83d5..aa7a85c 100644 --- a/public/assets/admin.css +++ b/public/assets/admin.css @@ -5,9 +5,9 @@ :root { --admin-bg: #1a1a1a; --admin-bg-alt: #242424; - --admin-border: #333; + --admin-border: #555; --admin-text: #e8e8e8; - --admin-text-muted: #888; + --admin-text-muted: #969696; --admin-purple: #9557b5; --admin-input-bg: transparent; } diff --git a/public/assets/common.css b/public/assets/common.css index dcf86c3..a6e5a47 100644 --- a/public/assets/common.css +++ b/public/assets/common.css @@ -77,7 +77,6 @@ a:hover { color: var(--white); text-decoration: none; font-weight: 400; - opacity: 0.92; transition: opacity 0.15s; } @@ -122,7 +121,7 @@ a:hover { } .site-search__input::placeholder { - color: #aaa; + color: #767676; } /* ============================================================ diff --git a/public/assets/main.css b/public/assets/main.css index b138f4c..a3470ee 100644 --- a/public/assets/main.css +++ b/public/assets/main.css @@ -151,7 +151,7 @@ html, body { /* Filter info */ .filter-info { background: var(--purple-light); - color: var(--purple); + color: var(--purple-dark); padding: 0.4rem 1.5rem; font-size: 0.85rem; display: flex; @@ -161,7 +161,7 @@ html, body { } .clear-filter { - color: var(--purple); + color: var(--purple-dark); text-decoration: none; padding: 0.15rem 0.6rem; background: rgba(149, 87, 181, 0.12); diff --git a/public/assets/search.css b/public/assets/search.css index 975adf6..ed7e0f1 100644 --- a/public/assets/search.css +++ b/public/assets/search.css @@ -31,6 +31,24 @@ html, body { min-height: 100%; } +@media (max-width: 768px) { + .repertoire-index { + grid-template-columns: 1fr; + padding: 0 1rem; + min-height: auto; + } + + .repertoire-col { + border-right: none; + border-bottom: 1px solid var(--border-color); + padding: 1rem 0 1.25rem; + } + + .repertoire-col:last-child { + border-bottom: none; + } +} + .repertoire-col { padding: 0.75rem 0.5rem 2rem; border-right: 1px solid var(--border-color); @@ -233,7 +251,7 @@ html, body { .search-filter-select { font-size: 0.82rem; - border: 1px solid var(--border-color); + border: 1px solid #949494; border-radius: 3px; padding: 0.2rem 0.5rem; background: var(--white); diff --git a/public/index.php b/public/index.php index 372edcb..68c5d47 100644 --- a/public/index.php +++ b/public/index.php @@ -103,7 +103,7 @@ $extraCss = ['assets/main.css']; $hue2 = ($hue + 40) % 360; ?>
+ style="background:linear-gradient(135deg,hsl(,55%,40%),hsl(,50%,28%));">
@@ -125,16 +125,24 @@ $extraCss = ['assets/main.css'];
« + class="pagination-btn " + + aria-label="Première page">« + class="pagination-btn " + + aria-label="Page précédente">‹ / + class="pagination-btn = $totalPages ? 'disabled' : '' ?>" + = $totalPages ? 'aria-disabled="true" tabindex="-1"' : '' ?> + aria-label="Page suivante">› » + class="pagination-btn = $totalPages ? 'disabled' : '' ?>" + = $totalPages ? 'aria-disabled="true" tabindex="-1"' : '' ?> + aria-label="Dernière page">»
diff --git a/public/search.php b/public/search.php index b763ed8..62d3a0d 100644 --- a/public/search.php +++ b/public/search.php @@ -8,7 +8,7 @@ $rateLimit = new RateLimit(30, 60); if (!$rateLimit->check()) { http_response_code(429); header('Retry-After: ' . $rateLimit->getResetTime()); - echo '

Trop de requêtes

Réessayez dans ' . $rateLimit->getResetTime() . ' secondes.

'; + echo '

Trop de requêtes

Réessayez dans ' . $rateLimit->getResetTime() . ' secondes.

'; exit; } $rateLimit->sendHeaders(); diff --git a/templates/search-bar.php b/templates/search-bar.php index 86a2f01..9b1edfb 100644 --- a/templates/search-bar.php +++ b/templates/search-bar.php @@ -5,7 +5,8 @@ $_sbValue = $searchBarValue ?? $_GET['query'] ?? ''; ?>