From 6b6d47ba8a9a297ee9c35ae8218994aa1865c82a Mon Sep 17 00:00:00 2001 From: gabrielb Date: Mon, 18 May 2026 00:28:40 -0300 Subject: [PATCH] =?UTF-8?q?feat(a11y):=20implementa=20acessibilidade=20WCA?= =?UTF-8?q?G=202.1=20AA=20em=20todo=20o=20portal=20p=C3=BAblico?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - layout.scss: focus-visible ring, skip link, prefers-reduced-motion, touch targets ≥44px, font-size base 16px - App.vue: skip link + aplica tema dinâmico no onMounted (após PrimeVue inicializado) - composables/useMotion.js: detecta prefers-reduced-motion com listener reativo - HomeView: aria-label em seções, carrossel respeita reduced-motion, contraste de texto melhorado - LoginView: h1 correto, label+for+aria-describedby no campo senha, role=alert no erro, touch targets nos botões - DocumentoInput: aria-label, aria-required, autocomplete=username - AppHeader: aria-label no logo e nav, aria-current na rota ativa, aria-hidden nos ícones decorativos - AppFooter: role=contentinfo, nav com aria-label, contraste text-slate-400→500, ícone decorativo aria-hidden - PortalLayout: role=banner, aria-label no nav, aria-current nas rotas, aria-live no nome do usuário - PublicLayout: tabindex=-1 no main para receber foco via skip link Co-Authored-By: Claude Sonnet 4.6 --- src/App.vue | 6 ++ src/assets/layout/layout.scss | 87 +++++++++++++++++++++++++- src/components/auth/DocumentoInput.vue | 3 + src/components/common/AppFooter.vue | 16 ++--- src/components/common/AppHeader.vue | 17 +++-- src/composables/useMotion.js | 28 +++++++++ src/layouts/PortalLayout.vue | 26 +++++--- src/layouts/PublicLayout.vue | 5 +- src/views/public/HomeView.vue | 21 ++++--- src/views/public/LoginView.vue | 70 ++++++++++++--------- 10 files changed, 215 insertions(+), 64 deletions(-) create mode 100644 src/composables/useMotion.js diff --git a/src/App.vue b/src/App.vue index 652ad8b..6f3e9b8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,6 +12,12 @@ onMounted(() => { diff --git a/src/assets/layout/layout.scss b/src/assets/layout/layout.scss index 5b07d53..ab04dad 100644 --- a/src/assets/layout/layout.scss +++ b/src/assets/layout/layout.scss @@ -1,11 +1,10 @@ -/* Estilos globais de layout — sem @apply (regra do projeto) */ - html, body { height: 100%; margin: 0; padding: 0; font-family: 'DM Sans', system-ui, sans-serif; + font-size: 16px; /* base mínimo para leitura confortável */ } #app { @@ -14,7 +13,7 @@ body { flex-direction: column; } -/* Transições de rota */ +/* ── Transições de rota ────────────────────────────────────────────── */ .page-enter-active, .page-leave-active { transition: opacity 0.18s ease; @@ -23,3 +22,85 @@ body { .page-leave-to { opacity: 0; } + +/* ── Acessibilidade global ─────────────────────────────────────────── */ + +/* + * Focus ring visível e de alto contraste. + * Usa currentColor para se adaptar a qualquer tema de cor. + * Nunca remover — usuários de teclado e leitores de tela dependem disso. + */ +:focus-visible { + outline: 3px solid currentColor; + outline-offset: 3px; + border-radius: 4px; +} + +/* + * Skip link — oculto até receber foco via Tab. + * Permite que usuários de teclado pulem a navegação e vão direto ao conteúdo. + */ +.skip-link { + position: fixed; + top: -100%; + left: 1rem; + z-index: 9999; + padding: 0.875rem 1.5rem; + background: #1e293b; + color: #fff; + font-weight: 600; + font-size: 1rem; + border-radius: 0.5rem; + text-decoration: none; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + transition: top 0.15s ease; + + &:focus { + top: 1rem; + } +} + +/* + * Respeitar a preferência de usuário por menos movimento (vestibular, + * epilepsia, sensibilidade visual). Afeta autoplay do carousel e transições. + */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + /* Garante que transições de rota também param */ + .page-enter-active, + .page-leave-active { + transition: none; + } +} + +/* ── Alvo de toque mínimo (WCAG 2.5.5) ────────────────────────────── */ +/* + * Botões e links pequenos precisam de área clicável mínima de 44×44px + * para usuários com dificuldade motora ou em telas touch. + */ +button, +[role='button'], +a { + min-height: 44px; + display: inline-flex; + align-items: center; +} + +/* + * Exceção para elementos inline de parágrafo onde forçar altura quebra + * o layout (links dentro de texto corrido). + */ +p a, +li a, +.inline-link { + min-height: unset; + display: inline; +} diff --git a/src/components/auth/DocumentoInput.vue b/src/components/auth/DocumentoInput.vue index f6eb9f0..be62958 100644 --- a/src/components/auth/DocumentoInput.vue +++ b/src/components/auth/DocumentoInput.vue @@ -44,8 +44,11 @@ function onInput(e) { :value="valorFormatado" :placeholder="placeholder" inputmode="numeric" + autocomplete="username" class="w-full text-lg tracking-wide" size="large" + aria-label="CPF ou CNPJ" + aria-required="true" @input="onInput" />