fix(a11y): lot P2 — segments stats cliquables ≥ 24 px, bouton Renommer hors tabulation, <main> sur le login, sheets en role=dialog, toast en role=status, titres H2 dans ParamètresUnverified
Claude-Session: https://claude.ai/code/session_01FPKHKGfqvnvXKY9tNhYmkh
8a5670d parent: d60d720 modified
assets/style.css +3 -1 | @@ -385,7 +385,9 @@ a.team-pill:hover { filter: brightness(.96); } | ||
| 385 | 385 | width: 100%; max-width: 64px; display: flex; flex-direction: column; |
| 386 | 386 | border-radius: var(--radius-md); overflow: hidden; background: var(--color-neutral-200); |
| 387 | 387 | } |
| 388 | -.col-bar a { display: block; min-height: 3px; width: 100%; } | |
| 388 | +/* 24px : cible tactile minimale (WCAG 2.5.8) — la hauteur cliquable prime | |
| 389 | + légèrement sur la proportion visuelle pour les petits segments. */ | |
| 390 | +.col-bar a { display: block; min-height: 24px; width: 100%; } | |
| 389 | 391 | |
| 390 | 392 | /* — bottom sheet (zéro JS : :target) — */ |
| 391 | 393 | .sheet-wrap { position: absolute; inset: 0; display: none; z-index: 10; } |
| @@ -385,7 +385,9 @@ a.team-pill:hover { filter: brightness(.96); } | |||
| 385 | width: 100%; max-width: 64px; display: flex; flex-direction: column; | 385 | width: 100%; max-width: 64px; display: flex; flex-direction: column; |
| 386 | border-radius: var(--radius-md); overflow: hidden; background: var(--color-neutral-200); | 386 | border-radius: var(--radius-md); overflow: hidden; background: var(--color-neutral-200); |
| 387 | } | 387 | } |
| 388 | -.col-bar a { display: block; min-height: 3px; width: 100%; } | 388 | +/* 24px : cible tactile minimale (WCAG 2.5.8) — la hauteur cliquable prime |
| 389 | + légèrement sur la proportion visuelle pour les petits segments. */ | ||
| 390 | +.col-bar a { display: block; min-height: 24px; width: 100%; } | ||
| 389 | 391 | ||
| 390 | /* — bottom sheet (zéro JS : :target) — */ | 392 | /* — bottom sheet (zéro JS : :target) — */ |
| 391 | .sheet-wrap { position: absolute; inset: 0; display: none; z-index: 10; } | 393 | .sheet-wrap { position: absolute; inset: 0; display: none; z-index: 10; } |
modified
templates/app.html +2 -2 | @@ -60,7 +60,7 @@ | ||
| 60 | 60 | </form> |
| 61 | 61 | {% endif %} |
| 62 | 62 | {% if !shell.teams.is_empty() %} |
| 63 | -<div class="sheet-wrap" id="equipes"> | |
| 63 | +<div class="sheet-wrap" id="equipes" role="dialog" aria-label="Changer d'équipe"> | |
| 64 | 64 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> |
| 65 | 65 | <div class="sheet"> |
| 66 | 66 | <div class="sheet-head"> |
| @@ -87,7 +87,7 @@ | ||
| 87 | 87 | </div> |
| 88 | 88 | </div> |
| 89 | 89 | {% endif %} |
| 90 | -{% if let Some(t) = shell.toast %}<div class="toast">{{ t }}</div>{% endif %} | |
| 90 | +{% if let Some(t) = shell.toast %}<div class="toast" role="status">{{ t }}</div>{% endif %} | |
| 91 | 91 | <nav class="tabbar"> |
| 92 | 92 | <a class="tab" href="/temps/nouveau" {% if shell.tab == "add" %}aria-current="page"{% endif %}> |
| 93 | 93 | <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 8v8M8 12h8"></path></svg> |
| @@ -60,7 +60,7 @@ | |||
| 60 | </form> | 60 | </form> |
| 61 | {% endif %} | 61 | {% endif %} |
| 62 | {% if !shell.teams.is_empty() %} | 62 | {% if !shell.teams.is_empty() %} |
| 63 | -<div class="sheet-wrap" id="equipes"> | 63 | +<div class="sheet-wrap" id="equipes" role="dialog" aria-label="Changer d'équipe"> |
| 64 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> | 64 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> |
| 65 | <div class="sheet"> | 65 | <div class="sheet"> |
| 66 | <div class="sheet-head"> | 66 | <div class="sheet-head"> |
| @@ -87,7 +87,7 @@ | |||
| 87 | </div> | 87 | </div> |
| 88 | </div> | 88 | </div> |
| 89 | {% endif %} | 89 | {% endif %} |
| 90 | -{% if let Some(t) = shell.toast %}<div class="toast">{{ t }}</div>{% endif %} | 90 | +{% if let Some(t) = shell.toast %}<div class="toast" role="status">{{ t }}</div>{% endif %} |
| 91 | <nav class="tabbar"> | 91 | <nav class="tabbar"> |
| 92 | <a class="tab" href="/temps/nouveau" {% if shell.tab == "add" %}aria-current="page"{% endif %}> | 92 | <a class="tab" href="/temps/nouveau" {% if shell.tab == "add" %}aria-current="page"{% endif %}> |
| 93 | <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 8v8M8 12h8"></path></svg> | 93 | <svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 8v8M8 12h8"></path></svg> |
modified
templates/login.html +2 -2 | @@ -1,7 +1,7 @@ | ||
| 1 | 1 | {% extends "base.html" %} |
| 2 | 2 | {% block title %}Connexion · time-time{% endblock %} |
| 3 | 3 | {% block body %} |
| 4 | -<div class="login"> | |
| 4 | +<main class="login"> | |
| 5 | 5 | <div class="login-icon"> |
| 6 | 6 | <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="#f5ead8" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg> |
| 7 | 7 | </div> |
| @@ -20,5 +20,5 @@ | ||
| 20 | 20 | {% if error %}<p class="form-error">Identifiants incorrects.</p>{% endif %} |
| 21 | 21 | <button type="submit" class="btn btn-primary btn-block" style="min-height:48px;font-size:16px">Se connecter</button> |
| 22 | 22 | </form> |
| 23 | -</div> | |
| 23 | +</main> | |
| 24 | 24 | {% endblock %} |
| @@ -1,7 +1,7 @@ | |||
| 1 | {% extends "base.html" %} | 1 | {% extends "base.html" %} |
| 2 | {% block title %}Connexion · time-time{% endblock %} | 2 | {% block title %}Connexion · time-time{% endblock %} |
| 3 | {% block body %} | 3 | {% block body %} |
| 4 | -<div class="login"> | 4 | +<main class="login"> |
| 5 | <div class="login-icon"> | 5 | <div class="login-icon"> |
| 6 | <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="#f5ead8" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg> | 6 | <svg width="34" height="34" viewBox="0 0 24 24" fill="none" stroke="#f5ead8" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg> |
| 7 | </div> | 7 | </div> |
| @@ -20,5 +20,5 @@ | |||
| 20 | {% if error %}<p class="form-error">Identifiants incorrects.</p>{% endif %} | 20 | {% if error %}<p class="form-error">Identifiants incorrects.</p>{% endif %} |
| 21 | <button type="submit" class="btn btn-primary btn-block" style="min-height:48px;font-size:16px">Se connecter</button> | 21 | <button type="submit" class="btn btn-primary btn-block" style="min-height:48px;font-size:16px">Se connecter</button> |
| 22 | </form> | 22 | </form> |
| 23 | -</div> | 23 | +</main> |
| 24 | {% endblock %} | 24 | {% endblock %} |
modified
templates/settings.html +4 -4 | @@ -39,7 +39,7 @@ | ||
| 39 | 39 | </form> |
| 40 | 40 | <p style="font-size:12px;opacity:.7;margin:0 0 var(--space-6)">Le nouveau nom est visible par tous les membres.</p> |
| 41 | 41 | |
| 42 | -<h4 style="margin:0 0 var(--space-2)">Membres de {{ shell.team_name }}</h4> | |
| 42 | +<h2 style="font-size:16px;margin:0 0 var(--space-2)">Membres de {{ shell.team_name }}</h2> | |
| 43 | 43 | <div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4)"> |
| 44 | 44 | {% for m in members %}<span class="tag tag-neutral">{{ m }}</span>{% endfor %} |
| 45 | 45 | </div> |
| @@ -58,7 +58,7 @@ | ||
| 58 | 58 | <p style="font-size:12px;opacity:.7;margin:0">L'utilisateur doit déjà avoir un compte. Il reçoit une invitation à accepter de son côté.</p> |
| 59 | 59 | |
| 60 | 60 | {% if !sent.is_empty() %} |
| 61 | -<h4 style="margin:var(--space-6) 0 var(--space-2)">Invitations en attente</h4> | |
| 61 | +<h2 style="font-size:16px;margin:var(--space-6) 0 var(--space-2)">Invitations en attente</h2> | |
| 62 | 62 | {% for si in sent %} |
| 63 | 63 | <div class="sent-row"> |
| 64 | 64 | <div style="flex:1;min-width:0"> |
| @@ -73,7 +73,7 @@ | ||
| 73 | 73 | {% endfor %} |
| 74 | 74 | {% endif %} |
| 75 | 75 | |
| 76 | -<h4 style="margin:var(--space-8) 0 var(--space-2)">Créer une équipe</h4> | |
| 76 | +<h2 style="font-size:16px;margin:var(--space-8) 0 var(--space-2)">Créer une équipe</h2> | |
| 77 | 77 | <form method="post" action="/equipes"> |
| 78 | 78 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 79 | 79 | <div style="display:flex;gap:var(--space-2)"> |
| @@ -92,7 +92,7 @@ | ||
| 92 | 92 | <p style="font-size:11px;opacity:.7;margin-top:var(--space-2);text-align:center">Tes temps déjà saisis restent dans les stats de l'équipe.</p> |
| 93 | 93 | {% endif %} |
| 94 | 94 | |
| 95 | -<h4 style="margin:var(--space-8) 0 var(--space-3)">Session</h4> | |
| 95 | +<h2 style="font-size:16px;margin:var(--space-8) 0 var(--space-3)">Session</h2> | |
| 96 | 96 | <form method="post" action="/parametres/mot-de-passe"> |
| 97 | 97 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 98 | 98 | <div class="field" style="margin-bottom:var(--space-3)"> |
| @@ -39,7 +39,7 @@ | |||
| 39 | </form> | 39 | </form> |
| 40 | <p style="font-size:12px;opacity:.7;margin:0 0 var(--space-6)">Le nouveau nom est visible par tous les membres.</p> | 40 | <p style="font-size:12px;opacity:.7;margin:0 0 var(--space-6)">Le nouveau nom est visible par tous les membres.</p> |
| 41 | 41 | ||
| 42 | -<h4 style="margin:0 0 var(--space-2)">Membres de {{ shell.team_name }}</h4> | 42 | +<h2 style="font-size:16px;margin:0 0 var(--space-2)">Membres de {{ shell.team_name }}</h2> |
| 43 | <div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4)"> | 43 | <div style="display:flex;flex-wrap:wrap;gap:var(--space-2);margin-bottom:var(--space-4)"> |
| 44 | {% for m in members %}<span class="tag tag-neutral">{{ m }}</span>{% endfor %} | 44 | {% for m in members %}<span class="tag tag-neutral">{{ m }}</span>{% endfor %} |
| 45 | </div> | 45 | </div> |
| @@ -58,7 +58,7 @@ | |||
| 58 | <p style="font-size:12px;opacity:.7;margin:0">L'utilisateur doit déjà avoir un compte. Il reçoit une invitation à accepter de son côté.</p> | 58 | <p style="font-size:12px;opacity:.7;margin:0">L'utilisateur doit déjà avoir un compte. Il reçoit une invitation à accepter de son côté.</p> |
| 59 | 59 | ||
| 60 | {% if !sent.is_empty() %} | 60 | {% if !sent.is_empty() %} |
| 61 | -<h4 style="margin:var(--space-6) 0 var(--space-2)">Invitations en attente</h4> | 61 | +<h2 style="font-size:16px;margin:var(--space-6) 0 var(--space-2)">Invitations en attente</h2> |
| 62 | {% for si in sent %} | 62 | {% for si in sent %} |
| 63 | <div class="sent-row"> | 63 | <div class="sent-row"> |
| 64 | <div style="flex:1;min-width:0"> | 64 | <div style="flex:1;min-width:0"> |
| @@ -73,7 +73,7 @@ | |||
| 73 | {% endfor %} | 73 | {% endfor %} |
| 74 | {% endif %} | 74 | {% endif %} |
| 75 | 75 | ||
| 76 | -<h4 style="margin:var(--space-8) 0 var(--space-2)">Créer une équipe</h4> | 76 | +<h2 style="font-size:16px;margin:var(--space-8) 0 var(--space-2)">Créer une équipe</h2> |
| 77 | <form method="post" action="/equipes"> | 77 | <form method="post" action="/equipes"> |
| 78 | <input type="hidden" name="csrf" value="{{ csrf }}"> | 78 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 79 | <div style="display:flex;gap:var(--space-2)"> | 79 | <div style="display:flex;gap:var(--space-2)"> |
| @@ -92,7 +92,7 @@ | |||
| 92 | <p style="font-size:11px;opacity:.7;margin-top:var(--space-2);text-align:center">Tes temps déjà saisis restent dans les stats de l'équipe.</p> | 92 | <p style="font-size:11px;opacity:.7;margin-top:var(--space-2);text-align:center">Tes temps déjà saisis restent dans les stats de l'équipe.</p> |
| 93 | {% endif %} | 93 | {% endif %} |
| 94 | 94 | ||
| 95 | -<h4 style="margin:var(--space-8) 0 var(--space-3)">Session</h4> | 95 | +<h2 style="font-size:16px;margin:var(--space-8) 0 var(--space-3)">Session</h2> |
| 96 | <form method="post" action="/parametres/mot-de-passe"> | 96 | <form method="post" action="/parametres/mot-de-passe"> |
| 97 | <input type="hidden" name="csrf" value="{{ csrf }}"> | 97 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 98 | <div class="field" style="margin-bottom:var(--space-3)"> | 98 | <div class="field" style="margin-bottom:var(--space-3)"> |
modified
templates/stats.html +1 -1 | @@ -53,7 +53,7 @@ | ||
| 53 | 53 | {% endfor %} |
| 54 | 54 | |
| 55 | 55 | {% for s in sheets %} |
| 56 | -<div class="sheet-wrap" id="{{ s.id }}"> | |
| 56 | +<div class="sheet-wrap" id="{{ s.id }}" role="dialog" aria-label="Détail — {{ s.task }}"> | |
| 57 | 57 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> |
| 58 | 58 | <div class="sheet"> |
| 59 | 59 | <div class="sheet-head"> |
| @@ -53,7 +53,7 @@ | |||
| 53 | {% endfor %} | 53 | {% endfor %} |
| 54 | 54 | ||
| 55 | {% for s in sheets %} | 55 | {% for s in sheets %} |
| 56 | -<div class="sheet-wrap" id="{{ s.id }}"> | 56 | +<div class="sheet-wrap" id="{{ s.id }}" role="dialog" aria-label="Détail — {{ s.task }}"> |
| 57 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> | 57 | <a class="sheet-backdrop" href="#fermer" aria-label="Fermer"></a> |
| 58 | <div class="sheet"> | 58 | <div class="sheet"> |
| 59 | <div class="sheet-head"> | 59 | <div class="sheet-head"> |
modified
templates/tasks.html +3 -1 | @@ -12,7 +12,9 @@ | ||
| 12 | 12 | <form method="post" action="{{ t.rename_action }}" class="rename"> |
| 13 | 13 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 14 | 14 | <input class="input" name="name" value="{{ t.name }}" required aria-label="Renommer la tâche (Entrée pour valider)"> |
| 15 | - <button type="submit" class="visually-hidden">Renommer</button> | |
| 15 | + {# tabindex=-1 : la soumission clavier passe par Entrée dans l'input ; | |
| 16 | + sans ça, ce bouton invisible est un stop de tabulation sans focus visible. #} | |
| 17 | + <button type="submit" class="visually-hidden" tabindex="-1">Renommer</button> | |
| 16 | 18 | </form> |
| 17 | 19 | <form method="post" action="{{ t.toggle_action }}"> |
| 18 | 20 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| @@ -12,7 +12,9 @@ | |||
| 12 | <form method="post" action="{{ t.rename_action }}" class="rename"> | 12 | <form method="post" action="{{ t.rename_action }}" class="rename"> |
| 13 | <input type="hidden" name="csrf" value="{{ csrf }}"> | 13 | <input type="hidden" name="csrf" value="{{ csrf }}"> |
| 14 | <input class="input" name="name" value="{{ t.name }}" required aria-label="Renommer la tâche (Entrée pour valider)"> | 14 | <input class="input" name="name" value="{{ t.name }}" required aria-label="Renommer la tâche (Entrée pour valider)"> |
| 15 | - <button type="submit" class="visually-hidden">Renommer</button> | 15 | + {# tabindex=-1 : la soumission clavier passe par Entrée dans l'input ; |
| 16 | + sans ça, ce bouton invisible est un stop de tabulation sans focus visible. #} | ||
| 17 | + <button type="submit" class="visually-hidden" tabindex="-1">Renommer</button> | ||
| 16 | </form> | 18 | </form> |
| 17 | <form method="post" action="{{ t.toggle_action }}"> | 19 | <form method="post" action="{{ t.toggle_action }}"> |
| 18 | <input type="hidden" name="csrf" value="{{ csrf }}"> | 20 | <input type="hidden" name="csrf" value="{{ csrf }}"> |