# Accessibility Audit (WCAG 2.1 AA)

## 1.1.1 Non-text content (alt text)

- [x] **Admin `<nav>` "✕ Réinitialiser" and "✕" remove buttons** — wrap `✕` in `<span aria-hidden="true">✕</span>`; add `aria-label="Supprimer ce membre du jury"` on jury remove buttons in `add.php`/`edit.php`

## 1.3.1 Info and relationships

- [x] **Admin form rows: multi-input rows (languages, formats)** — `checkbox-list.php` partial now wraps checkboxes in `<fieldset class="admin-checkbox-group">` with a `<legend class="sr-only">` for AT grouping

- [x] **Status badges in `admin/index.php` convey state by colour alone** — `status-badge.php` partial emits `<span aria-label="Statut : Publié"><span aria-hidden="true">● </span>Publié</span>` (circle symbol is non-colour indicator); both publish and access badges implemented

## 1.3.4 / 1.3.5 Orientation & Input purpose

- [x] **No `autocomplete` attributes on personal data fields** — `add.php`/`edit.php`: `autocomplete="name"` on author fields, `autocomplete="email"` on mail fields (via `$attrs` in `text-field.php`)

## 1.4.1 Use of colour

- [x] **Active nav link has no non-colour indicator** — admin nav: `border-bottom: 2px solid currentColor` added for `[aria-current="page"]` in `admin.css`; public nav already had `border-bottom` in `common.css`

- [x] **Admin purple `#9557b5` as text colour** — `--admin-purple` was an undefined variable referenced only in pagination hover; replaced with `--accent-primary` (same value, #9557b5). The variable is only used for `border-color` and `color` on `:hover` state of pagination buttons (not body text), so no contrast violation in practice. Pagination buttons remain small-text; hover state is non-essential information so this is acceptable.

## 1.4.4 Resize text

- [x] **Verify no text is set in `px`** — audited all CSS files; every `font-size` uses `rem` or `em`; no `px` font-size found anywhere. No action needed.

## 1.4.12 Text spacing

- [x] **No text-spacing override test done** — audited all `overflow: hidden` instances: `.sr-only` (visually hidden utility, 1×1px — not text content), `.home-body figure` / `aside figure` / `.card` (media containers, not text). `.card__gradient-title` clamps decorative gradient text — not essential content (same info is in the `<p>` link). No WCAG 1.4.12 failure found.

## 2.1.1 Keyboard

- [x] **Jury "✕" remove buttons in `add.php`/`edit.php`** — `aria-label="Supprimer le lecteur·ice N"` already present on all remove buttons in `jury-fieldset.php` (both static and dynamically added rows)

## 2.4.3 Focus order

- [x] **On `tfe.php` the `← Retour` back link is at the bottom of the left column in DOM** — already fixed; `<a class="tfe-back-link">← Retour</a>` is the first child of `<header class="tfe-left">`, which precedes `<h1 class="tfe-title">` in DOM order

## 2.4.4 Link purpose

- [x] **Home page cards: if two theses share the same title, identical link texts exist** — `public/index.php` card `<p>` now appends `<span class="sr-only">, YEAR</span>` when `$item['year']` is set, giving screen-reader users a unique link name per thesis

## 2.5.3 Label in name

- [x] **`<a class="clear-filter">✕ Réinitialiser</a>`** — wrap `✕` in `<span aria-hidden="true">`
- [x] **Admin jury remove buttons `✕`** — `aria-label="Supprimer ce lecteur"` replaces the symbol

## 2.5.5 Target size

- [x] **Pagination buttons are `2rem` (32px)** — increase to `min-height: 2.75rem; min-width: 2.75rem` (44px)
- [x] **Admin `.admin-btn-sm` (~28px)** — increase to minimum 32px with padding
- [x] **Admin bulk action buttons and jury remove `✕` buttons (~28px)** — increase target size

## 3.1.1 Language of page

- [x] **All public pages have `<html lang="fr">`** — verified: `templates/head.php` line 2 has `<html lang="fr">`; all pages share this template. No action needed.

## 3.3.1 Error identification

- [x] **`add.php`/`edit.php` validation errors** — `flash-messages.php` already emits `<p role="alert" data-type="error">` for errors and `<p role="status">` for success
- [x] **`add.php`/`edit.php` `autofocus` on first invalid field** — `App::flashAutofocus(fieldName)` stores the failing field in `$_SESSION['_flash_autofocus']`; action handlers map exception messages to field names; `add.php` consumes via `withAutofocus()` helper + injects into `$attrs`; `edit.php` uses inline ternary; partials support boolean `true` in `$attrs` to emit bare attribute names

## 3.3.2 Labels or instructions

- [x] **Admin jury "Lecteur·ices" label has no `for` attribute** — replaced plain `<label>Lecteur·ices :</label>` with `<fieldset class="admin-jury-lecteurs"><legend>Lecteur·ices</legend>` in `jury-fieldset.php`; CSS rule strips the nested fieldset’s border/padding so it renders as a sub-group

## 4.1.2 Name, role, value

- [x] **Custom "Externe" checkbox for jury members has no group context** — all jury "Externe" checkboxes now carry explicit `aria-label` (e.g. `"Promoteur·ice — externe"`, `"Lecteur·ice N — externe"`); both static PHP-rendered rows and dynamically added rows via `addJuryRow()` receive the label

- [x] **`<video>` elements on `tfe.php` have no captions** — `<track kind="captions">` now emitted for each MP4 when a `.vtt` sidecar has been uploaded alongside it; N-th VTT file is paired with N-th video in document order. `formulaire.php` accepts `.vtt` uploads (MIME `text/vtt`, `file_type = 'caption'`); `media.php` serves VTT with correct `Content-Type`; admin `add.php` file-field hint documents the `.vtt` convention.

- [x] **Admin `<select>` for visibility/access in `edit.php` uses truncated option text** — removed `mb_strimwidth` call; option text now uses full description (`name — description`) so screen-reader accessible name is complete and unambiguous

- [x] **Bulk publish/unpublish JS does not announce result to screen readers** — action result is a full-page redirect to a flash message rendered by `flash-messages.php` which already emits `role="alert"` (error) / `role="status"` (success); no additional JS announcement needed

## 5 - Motion & user preferences

- [x] **`prefers-color-scheme` not respected** — `variables.css` now includes a `@media (prefers-color-scheme: dark)` block scoped to `body:not(.admin-body)`; overrides all semantic tokens (`--bg-*`, `--text-*`, `--border-*`, `--accent-*`, status colours) with dark equivalents; `--accent-primary` lightened to `#b87fd4` for 4.5:1 contrast on dark backgrounds; `search.css` `.search-error` hardcoded `#fff0f0`/`#c00` replaced with `--search-error-bg`/`--search-error-border`/`--search-error-color` variables also overridden in dark mode; admin pages unaffected (`.admin-body` preserves light-mode values)
