Landing: a11y, design-system conformance & performance - #3
Conversation
Bundles the uncommitted landing-page work into one coherent change — hunks overlap across page.tsx/layout.tsx/globals.css, so a clean per-area split without interactive staging wasn't possible. Accessibility / UX - Skip link, 3px focus ring, prefers-reduced-motion, fieldset/legend, role="alert" errors, mobile-menu focus handling; eyebrow -> brand-700 for AA contrast. Design-system conformance (DESIGN.md) - Drop the 5 raster 3D .webp ticket assets (off-palette blue/orange, glossy) and the 413 MB docs/uiux-assets Envato archive. - Hero: remove the floating 3D ticket; DashboardPreview is the sole visual. - Operational flow: convert the third dark brand-900 band into a light section with green line-art icons, keeping the 01/02/03 sequence. - Target audience: 3D concert render -> TicketLine line-art; support panel brand-50 -> neutral ink-100 so green stays a scarce resource. Performance - Code-split the below-the-fold pilot form (next/dynamic + ssr:false + IntersectionObserver, height-reserving skeleton): pulls zod + react-hook-form + radix out of the home first-load (338 -> 222 KB gzip). - Mount @vercel/speed-insights + @vercel/analytics for field CWV. Code-review fixes - Remove unused --success/--warning tokens (palette stays single-green). - Single-source the focus color (--focus-ring -> var(--ring), brand-600). - Stop double-announcing submit errors to screen readers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughA landing page recebeu melhorias de acessibilidade, navegação móvel, conteúdo visual, validação do formulário, carregamento sob demanda, observabilidade e documentação técnica. ChangesAcessibilidade e base da aplicação
Landing page e formulário
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant LandingPage
participant PilotFormLazy
participant IntersectionObserver
participant PilotForm
LandingPage->>PilotFormLazy: renderiza o formulário sob demanda
PilotFormLazy->>IntersectionObserver: observa a aproximação da seção
IntersectionObserver->>PilotFormLazy: informa entrada no limite de 600 px
PilotFormLazy->>PilotForm: carrega dinamicamente sem SSR
PilotForm-->>LandingPage: exibe o formulário
Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Biome (2.5.5)src/app/globals.cssFile contains syntax errors that prevent linting: Line 7: Tailwind-specific syntax is disabled.; Line 62: Tailwind-specific syntax is disabled.; Line 111: Tailwind-specific syntax is disabled.; Line 192: Tailwind-specific syntax is disabled. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43641622df
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const FLOW_STEPS = [ | ||
| { | ||
| title: "Ingresso digital", | ||
| description: "A pessoa compra e acessa o ingresso pelo celular, sem fila e sem papel.", |
There was a problem hiding this comment.
Reframe the operational flow as a pilot concept
This new present-tense flow says attendees buy and access mobile tickets and later promises real-time attendance tracking, but .agents/product-marketing.md states that the product has no functionality in production and must not be presented as completed. On the public landing, these statements—including the guaranteed outcome “sem fila”—turn validation hypotheses into availability and result claims, so describe them as proposed pilot behavior until they are verified.
AGENTS.md reference: AGENTS.md:L46-L48
Useful? React with 👍 / 👎.
| <SpeedInsights /> | ||
| <Analytics /> |
There was a problem hiding this comment.
Disclose telemetry before enabling it globally
When deployed, these components collect page-view and performance telemetry on every route, including from visitors who never submit the pilot form, while /privacidade still describes only information supplied through that form and lists only form-related purposes. This introduces undisclosed collection; update the privacy disclosure and applicable legal-basis/consent handling, or leave telemetry disabled until that is done.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/uiux-analysis.md`:
- Around line 8-10: Atualize os trechos introdutórios e a seção que descreve os
assets em docs/uiux-analysis.md para refletirem o estado final registrado
posteriormente: não afirme que nenhuma dependência foi adicionada nem que cinco
arquivos WebP permanecem servidos. Preserve essas informações apenas se
explicitamente marcadas como histórico da primeira rodada; caso contrário,
substitua-as pelo estado final com as duas dependências adicionadas e os assets
removidos, mantendo o restante da análise consistente.
- Around line 96-98: Atualize a política descrita em docs/uiux-analysis.md para
cobrir o Analytics global carregado por Analytics em src/app/layout.tsx,
incluindo finalidade, dados tratados, retenção e formas de recusa. Não afirme
que o Analytics está coberto enquanto essas regras não estiverem incluídas em
src/app/privacidade/page.tsx.
In `@src/app/globals.css`:
- Line 158: Update the text-rendering declaration in the global stylesheet to
use the lowercase optimizelegibility value required by Stylelint’s
value-keyword-case rule, leaving the surrounding styling unchanged.
In `@src/app/layout.tsx`:
- Around line 41-42: Atualize a política de privacidade em
src/app/privacidade/page.tsx para documentar a telemetria de <SpeedInsights /> e
<Analytics />, incluindo dados tratados, finalidade, retenção e opção de recusa;
verifique também as versões instaladas para confirmar o comportamento atual. Se
essas versões exigirem consentimento, ajuste o fluxo em layout.tsx para
renderizar os componentes somente após o usuário consentir.
- Around line 33-35: Atualize o link de pulo no layout, identificado pelo
href="`#main-content`", para não remover o contorno global com
focus-visible:outline-none; alternativamente, substitua o anel sem opacidade por
ring-focus-ring. Preserve os demais estilos e garanta contraste de foco
compatível com WCAG.
In `@src/app/obrigado/page.tsx`:
- Around line 8-11: Enable programmatic focus on every main element with
id="main-content" by adding tabIndex={-1} in src/app/obrigado/page.tsx lines
8-11 and src/app/privacidade/page.tsx line 6; also apply the same change to the
corresponding main element in src/app/page.tsx as requested.
In `@src/components/landing/header.tsx`:
- Around line 76-104: Update the div#menu-principal className to include the
transition-discrete utility, preserving the existing opacity and translate
transitions so closing the menu animates before hidden applies display:none in
modern browsers.
In `@src/components/landing/pilot-band.tsx`:
- Line 9: Keep the scroll-mt-16 and sm:scroll-mt-20 values in the pilot-band
section unchanged; apply the consolidated deduplication adjustment to the
corresponding section wrapper in the Section component instead, reusing the
shared spacing values without altering header-aligned behavior.
In `@src/components/landing/pilot-form.tsx`:
- Around line 94-97: Remova as chamadas a toast.error no fluxo de erro de envio
do formulário, mantendo setSubmitError como único canal de anúncio acessível.
Aplique essa alteração tanto no catch próximo a setSubmitError quanto no fluxo
correspondente indicado na linha 133, preservando o banner role="alert" e o
restante do tratamento de erro.
In `@src/components/landing/section.tsx`:
- Around line 25-28: Centralize the shared scroll-mt-16/sm:scroll-mt-20 values
used by the landing Section component and pilot-band.tsx into a reusable styling
constant or utility. Update Section and pilot-band.tsx to consume that shared
definition, preserving the existing responsive scroll offset behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c9d2a390-2841-40df-866d-5f082e15ccce
📒 Files selected for processing (17)
.prettierignoredocs/uiux-analysis.mdeslint.config.mjssrc/app/globals.csssrc/app/layout.tsxsrc/app/obrigado/page.tsxsrc/app/page.tsxsrc/app/privacidade/page.tsxsrc/components/landing/header.tsxsrc/components/landing/hero.tsxsrc/components/landing/operational-flow.tsxsrc/components/landing/pilot-band.tsxsrc/components/landing/pilot-form-lazy.tsxsrc/components/landing/pilot-form.tsxsrc/components/landing/section.tsxsrc/components/landing/target-audience.tsxsrc/components/ui/form.tsx
📜 Review details
🧰 Additional context used
🪛 LanguageTool
docs/uiux-analysis.md
[locale-violation] ~24-~24: “targets” é um estrangeirismo. É preferível dizer “objetivos” ou “alvos”.
Context: ...pais, labels associados e controles com targets de toque adequados. - O foco global usa...
(PT_BARBARISMS_REPLACE_TARGETS)
[style] ~26-~26: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ...o continuam preservando seus estilos. - O FormControl mantém aria-describedby...
(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)
[style] ~47-~47: Três frases seguidas começam com a mesma palavra. Considere reformular a frase ou use um dicionário para encontrar um sinônimo.
Context: ...ráter de candidatura sem compromisso. - O formulário informa que a candidatura nã...
(PORTUGUESE_WORD_REPEAT_BEGINNING_RULE)
[style] ~47-~47: Pode omitir o pronome ‘que’ para tornar o texto mais fluido.
Context: ...orma que a candidatura não garante vaga e que o retorno ocorre em até 48 horas úteis,...
(SIMPLIFICAR_E_QUE_E)
[locale-violation] ~51-~51: “Performance” é um estrangeirismo. É preferível dizer “desempenho”, “atuação”, “apresentação”, “espetáculo” ou “interpretação”.
Context: ...ssistiva, além do toast existente. ### Performance - Os cinco PNGs selecionados foram con...
(PT_BARBARISMS_REPLACE_PERFORMANCE)
[uncategorized] ~61-~61: Sinal de pontuação isolado.
Context: ...ssets adicionados - ticket-event.webp: assinatura visual do hero. - `mobile-ti...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~62-~62: Sinal de pontuação isolado.
Context: ...a visual do hero. - mobile-ticket.webp, qr-code-ticket.webp e `checkmark-tick...
(UNLIKELY_OPENING_PUNCTUATION)
[misspelling] ~62-~62: Possível erro ortográfico.
Context: ...visual do hero. - mobile-ticket.webp, qr-code-ticket.webp e checkmark-ticket.webp:...
(PT_MULTITOKEN_SPELLING_HYPHEN)
[uncategorized] ~63-~63: Sinal de pontuação isolado.
Context: ...n e confirmação. - concert-ticket.webp: apoio visual da seção de público univer...
(UNLIKELY_OPENING_PUNCTUATION)
[misspelling] ~85-~85: Possível erro ortográfico.
Context: ... painel de apoio deixou de ser verde (bg-brand-50) e passou a neutro (bg-ink-100), ...
(PT_MULTITOKEN_SPELLING_HYPHEN)
[locale-violation] ~90-~90: “Performance” é um estrangeirismo. É preferível dizer “desempenho”, “atuação”, “apresentação”, “espetáculo” ou “interpretação”.
Context: ...ez a exposição à licença pendente. ### Performance e dependências Diferente da primeira r...
(PT_BARBARISMS_REPLACE_PERFORMANCE)
[locale-violation] ~92-~92: “performance” é um estrangeirismo. É preferível dizer “desempenho”, “atuação”, “apresentação”, “espetáculo” ou “interpretação”.
Context: ...dependências de runtime), esta etapa de performance **adicionou duas dependências de runtim...
(PT_BARBARISMS_REPLACE_PERFORMANCE)
[grammar] ~98-~98: Possível erro de concordância de número.
Context: ...caminho para obter Core Web Vitals de campo reais após o deploy. - O formulário do piloto...
(GENERAL_NUMBER_AGREEMENT_ERRORS)
[grammar] ~102-~102: Possível erro de concordância.
Context: ... gzip** no first-load da home (medido no build). ## Validação executada - `pnpm form...
(GENERAL_GENDER_AGREEMENT_ERRORS)
[uncategorized] ~106-~106: Sinal de pontuação isolado.
Context: ...lidação executada - pnpm format:check: passou. - pnpm lint: passou sem erros...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~107-~107: Sinal de pontuação isolado.
Context: ...npm format:check: passou. - pnpm lint`: passou sem erros; existe um warning pre...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~108-~108: Sinal de pontuação isolado.
Context: ...ber/tmp/last-ndc.ts. - pnpm typecheck: passou. - pnpm build`: passou; rotas e...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~109-~109: Sinal de pontuação isolado.
Context: ...pnpm typecheck: passou. - pnpm build: passou; rotas estáticas e `/api/subscri...
(UNLIKELY_OPENING_PUNCTUATION)
[uncategorized] ~110-~110: Sinal de pontuação isolado.
Context: ...piladas. - curl http://localhost:3000/: respondeu 200 com headers de seguranç...
(UNLIKELY_OPENING_PUNCTUATION)
🪛 Stylelint (17.14.1)
src/app/globals.css
[error] 158-158: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)
(value-keyword-case)
🔇 Additional comments (21)
src/app/globals.css (2)
56-58: LGTM!Also applies to: 79-79
150-157: LGTM!Also applies to: 159-175, 177-188
src/app/layout.tsx (2)
3-4: LGTM!
33-38: 🎯 Functional CorrectnessSem alteração necessária.
docs/uiux-analysis.md (1)
1-7: LGTM!Also applies to: 12-50, 67-95, 99-118
.prettierignore (1)
4-5: LGTM!eslint.config.mjs (1)
8-8: LGTM!src/components/landing/header.tsx (1)
13-30: LGTM!Also applies to: 40-40, 62-62
src/components/landing/hero.tsx (1)
13-15: LGTM!Also applies to: 44-53
src/components/landing/pilot-band.tsx (1)
13-13: LGTM!src/components/landing/section.tsx (2)
25-28: Resumo da mudança não bate com o código.O resumo dessa faixa de linhas diz que a classe
lg:scroll-mt-12foi adicionada. No código mostrado, essa classe não aparece — só existescroll-mt-16 py-8 sm:scroll-mt-20 sm:py-10 lg:py-12. Não é um problema funcional: como o header não muda de altura emlg(continuasm:h-20), o valorsm:scroll-mt-20cascateia corretamente atélgsem precisar de override. Reporto só para registro, comandante.
56-56: LGTM!src/components/landing/target-audience.tsx (1)
1-36: LGTM!src/app/page.tsx (2)
7-13: LGTM!Also applies to: 19-27
18-18: 🗄️ Data Integrity & IntegrationSkip link apontado para
#main-content.Tudo certo, Comandante. O link “Pular para o conteúdo” já tem
href="#main-content"e o<main>usaid="main-content".src/components/landing/operational-flow.tsx (1)
1-52: LGTM!src/components/landing/pilot-form-lazy.tsx (1)
1-60: LGTM!src/components/landing/pilot-form.tsx (3)
6-6: LGTM!Also applies to: 60-60, 78-78, 297-297, 340-340
103-108: LGTM!
109-125: LGTM!Also applies to: 134-286
src/components/ui/form.tsx (1)
151-151: LGTM!
| A landing já tinha uma base consistente: Next.js 16, React 19, Tailwind 4, HTML semântico básico, skip link, foco visível, formulário com React Hook Form + Zod, feedback via Sonner e ilustrações SVG leves. | ||
|
|
||
| O foco desta rodada foi conversão da candidatura, sem trocar a identidade visual nem adicionar dependências de runtime. A proposta continua usando evergreen/mint e o motivo de ingresso/perfuração como assinatura própria; cinco assets locais foram adicionados de forma pontual como apoio visual. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Corrija as afirmações sobre o estado final.
Line 8-10 afirma que esta rodada não adicionou dependências. Line 53-65 afirma que cinco WebP foram adicionados e servidos. Porém, Line 77-102 registra a remoção desses arquivos e a adição de duas dependências. Marque os primeiros trechos como histórico da primeira rodada ou substitua-os pelo estado final. Corrija isso antes do merge.
Also applies to: 51-65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/uiux-analysis.md` around lines 8 - 10, Atualize os trechos introdutórios
e a seção que descreve os assets em docs/uiux-analysis.md para refletirem o
estado final registrado posteriormente: não afirme que nenhuma dependência foi
adicionada nem que cinco arquivos WebP permanecem servidos. Preserve essas
informações apenas se explicitamente marcadas como histórico da primeira rodada;
caso contrário, substitua-as pelo estado final com as duas dependências
adicionadas e os assets removidos, mantendo o restante da análise consistente.
| - `@vercel/speed-insights` e `@vercel/analytics`, montados em `src/app/layout.tsx`. Como | ||
| não há Google Chrome nesta máquina para rodar Lighthouse, o Speed Insights é o caminho | ||
| para obter Core Web Vitals de campo reais após o deploy. |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 \
'Analytics|SpeedInsights|cookies?|consentimento|consent|opt.?out|privacidade|Vercel' \
src/app/layout.tsx src/app/privacidade/page.tsx src/componentsRepository: prof-ramos/webingressos-page
Length of output: 3042
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- policy outline ---\n'
wc -l src/app/privacidade/page.tsx
printf '\n--- policy content ---\n'
cat -n src/app/privacidade/page.tsx
printf '\n--- layout content ---\n'
cat -n src/app/layout.tsx | sed -n '1,90p'
printf '\n--- broader privacy/analytics related terms ---\n'
rg -n -i 'analytics|speedinsights|cookie|cookies?|consentimento|consent|recusa|opt.?out|retenção|retencao|tratament|LGPD|Google|Vercel' src docs package.json | sed -n '1,240p'Repository: prof-ramos/webingressos-page
Length of output: 257
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- policy size ---'
wc -l src/app/privacidade/page.tsx
printf '%s\n' ''
printf '%s\n' '--- policy content ---'
cat -n src/app/privacidade/page.tsx
printf '%s\n' ''
printf '%s\n' '--- layout content ---'
cat -n src/app/layout.tsx | sed -n '1,90p'
printf '%s\n' ''
printf '%s\n' '--- broader privacy/analytics related terms ---'
rg -n -i 'analytics|speedinsights|cookie|cookies?|consentimento|consent|recusa|opt.?out|retenção|retencao|tratament|LGPD|Google|Vercel' src docs package.json | sed -n '1,240p'Repository: prof-ramos/webingressos-page
Length of output: 8167
Complete a cobertura do Analytics global na política.
O layout global carrega <Analytics />, mas src/app/privacidade/page.tsx continua restrita ao formulário de candidatura e não aborda finalidade, dados tratados, retenção nem recusa do Analytics; o texto final não deve dizer que o Analytics é coberto até incluir essas regras.
🧰 Tools
🪛 LanguageTool
[grammar] ~98-~98: Possível erro de concordância de número.
Context: ...caminho para obter Core Web Vitals de campo reais após o deploy. - O formulário do piloto...
(GENERAL_NUMBER_AGREEMENT_ERRORS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/uiux-analysis.md` around lines 96 - 98, Atualize a política descrita em
docs/uiux-analysis.md para cobrir o Analytics global carregado por Analytics em
src/app/layout.tsx, incluindo finalidade, dados tratados, retenção e formas de
recusa. Não afirme que o Analytics está coberto enquanto essas regras não
estiverem incluídas em src/app/privacidade/page.tsx.
| background-color: var(--color-background); | ||
| color: var(--color-foreground); | ||
| -webkit-font-smoothing: antialiased; | ||
| text-rendering: optimizeLegibility; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Normalize o valor de text-rendering para passar no lint.
Stylelint sinaliza optimizeLegibility para a regra value-keyword-case. Use optimizelegibility nesta propriedade.
Correção
- text-rendering: optimizeLegibility;
+ text-rendering: optimizelegibility;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| text-rendering: optimizeLegibility; | |
| text-rendering: optimizelegibility; |
🧰 Tools
🪛 Stylelint (17.14.1)
[error] 158-158: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` at line 158, Update the text-rendering declaration in
the global stylesheet to use the lowercase optimizelegibility value required by
Stylelint’s value-keyword-case rule, leaving the surrounding styling unchanged.
Source: Linters/SAST tools
| <a | ||
| href="#main-content" | ||
| className="sr-only focus-visible:not-sr-only focus-visible:fixed focus-visible:top-4 focus-visible:left-4 focus-visible:z-100 focus-visible:rounded-xl focus-visible:bg-brand-700 focus-visible:px-4 focus-visible:py-2 focus-visible:text-sm focus-visible:font-bold focus-visible:text-white focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## candidate files"
git ls-files | rg '(^|/)(globals\.css|tailwind\.config\.(js|ts|cjs|mjs)|package\.json|src/app/layout\.tsx|styles\.css|app.*global.*\.css)$' || true
echo
echo "## layout snippet"
if [ -f src/app/layout.tsx ]; then
nl -ba src/app/layout.tsx | sed -n '1,80p'
fi
echo
echo "## color/ring tailwind and CSS variables"
for f in $(git ls-files | rg '(\.css$|tailwind\.config\.(js|ts|cjs|mjs)$|globals\.css$|layout\.tsx$)'); do
echo "--- $f"
rg -n -- '--(ring|brand|background|primary|secondary|surface|focus|outline)|ring-focus-ring|ring-ring|focus-visible:ring|focus-visible:outline|outline-none|--brand-[0-9]+' "$f" || true
done
echo
echo "## exact class usage"
rg -n "ring-ring/50|ring-focus-ring|outline-none|focus-visible:outline" .Repository: prof-ramos/webingressos-page
Length of output: 297
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## layout snippet"
cat -n src/app/layout.tsx | sed -n '1,80p'
echo
echo "## globals CSS and Tailwind config"
for f in src/app/globals.css package.json; do
echo "--- $f"
cat -n "$f"
done
echo
echo "## exact class usage"
rg -n "ring-ring/50|ring-focus-ring|outline-none|focus-visible:outline|--ring|--brand-[0-9]+" . || trueRepository: prof-ramos/webingressos-page
Length of output: 20130
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## contrast probe for focus styles"
python3 - <<'PY'
import math
def wcag_ratio(c1, c2):
lum = []
for c in [c1, c2]:
srgb = [x / 12.92 if x <= 0.03928 else ((x + 0.055) / 1.055) ** 2.4 for x in c]
lum.append(0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2])
return (max(lum) + 0.05) / (min(lum) + 0.05)
brand600 = (27/255, 127/255, 82/255)
ink50 = (249/255, 250/255, 252/255)
def alpha_over(fg, bg, alpha):
r = fg[0]*alpha + bg[0]*(1-alpha)
g = fg[1]*alpha + bg[1]*(1-alpha)
b = fg[2]*alpha + bg[2]*(1-alpha)
return (r, g, b)
print(f"--ring/--focus-ring = var(--brand-600) = {brand600}")
print(f"light background = var(--ink-50) = {ink50}")
print(f"0d-ring vs light: {wcag_ratio(brand600, ink50):.2f}")
print(f"50%-alpha ring vs light: {wcag_ratio(alpha_over(brand600, ink50, 0.5), ink50):.2f}")
print(f"50%-alpha ring vs white: {wcag_ratio(alpha_over(brand600, (1,1,1), 0.5), (1,1,1)):.2f}")
print(f"ring-focus-ring vs light: {wcag_ratio(brand600, ink50):.2f}")
PY
echo
echo "## nearby generated CSS declarations if files exist"
rg -n "focus-visible:outline-none|focus-visible:ring-3|focus-visible:ring-ring|outline: 3px|focus-ring|focus-visible" node_modules/.vite 2>/dev/null || trueRepository: prof-ramos/webingressos-page
Length of output: 567
Passe o link de pulo para fora antes de esconder o contorno de foco.
focus-visible:outline-none mata a regra global de 3px solid var(--focus-ring). No fundo claro, ring-3 ring-ring/50 reduz o contraste do --brand-600 para cerca de 2:1, abaixo do 3:1 exigido pelo WCAG. Use ring-focus-ring sem opacidade no foco ou remova apenas outline-none para manter o contorno global.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/layout.tsx` around lines 33 - 35, Atualize o link de pulo no layout,
identificado pelo href="`#main-content`", para não remover o contorno global com
focus-visible:outline-none; alternativamente, substitua o anel sem opacidade por
ring-focus-ring. Preserve os demais estilos e garanta contraste de foco
compatível com WCAG.
| <SpeedInsights /> | ||
| <Analytics /> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked files matching layout/privacy/analytics/speedinsights/package:"
git ls-files | rg '(^|/)(layout|page\.tsx|package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$|SpeedInsights|Analytics|speedinsights|analsytics' || true
echo
echo "src/app/layout.tsx around SpeedInsights/Analytics:"
if [ -f src/app/layout.tsx ]; then
nl -ba src/app/layout.tsx | sed -n '1,90p'
fi
echo
echo "Privacy page candidate:"
if [ -f src/app/privacidade/page.tsx ]; then
nl -ba src/app/privacidade/page.tsx | sed -n '1,220p'
fi
if [ -f src/app/privacidade/page.ts ]; then
echo "src/app/privacidade/page.ts exists"
fi
echo
echo "Search for consent / opt / Vercel policy / speed insights mentions:"
rg -n "SpeedInsights|Analytics|Vercel|tracking|privacy|privacidade|consent|consentimento|recusa|opt|analytics|telemetria|cookies" src package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true
echo
echo "Dependency versions from package/lock:"
for f in package.json pnpm-lock.yaml yarn.lock package-lock.json; do
[ -f "$f" ] && echo "--- $f ---" && rg -n "`@vercel/analytics`|next" "$f" -m 20 || true
done
echo
echo "Root files listing package and lock availability:"
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$'Repository: prof-ramos/webingressos-page
Length of output: 430
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking whether Vercel/next package metadata is present in sandbox:"
if [ -f node_modules/@vercel/analytics/package.json ]; then
cat node_modules/@vercel/analytics/package.json | jq '{version, main, repository}'
else
echo "`@vercel/analytics` not installed in node_modules"
fi
if [ -f node_modules/next/package.json ]; then
cat node_modules/next/package.json | jq '{version}'
else
echo "next not installed in node_modules"
fiRepository: prof-ramos/webingressos-page
Length of output: 410
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "src/app/layout.tsx:"
awk '{printf "%5d %s\n", NR, $0}' src/app/layout.tsx
echo
echo "src/app/privacidade/page.tsx:"
awk '{printf "%5d %s\n", NR, $0}' src/app/privacidade/page.tsx
echo
echo "package versions/snippets:"
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
for (const k of ['dependencies','devDependencies']) {
console.log(k, {next: pkg[k]?.next, '`@vercel/analytics`': pkg[k]?.['`@vercel/analytics`']});
}
JS
echo
echo "lock relevant entries:"
awk '/^$/ {next} /(`@vercel/analytics`|next@|next:|specifier): {print; for(i=0;i<8;i++){getline; if(/^$/) break; print}}' pnpm-lock.yamlRepository: prof-ramos/webingressos-page
Length of output: 7182
Atualize a política antes de publicar a telemetria.
<SpeedInsights /> e <Analytics /> estão ativos em todo o site. A página src/app/privacidade/page.tsx só aborda os dados do formulário e não documenta a telemetria da Vercel, os dados tratados, a finalidade, a retenção nem a opção de recusa. Alinhe a política. Se o fluxo exigir consentimento, carregue os componentes somente após a escolha do usuário. Confirme o comportamento das versões instaladas antes do merge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/layout.tsx` around lines 41 - 42, Atualize a política de privacidade
em src/app/privacidade/page.tsx para documentar a telemetria de <SpeedInsights
/> e <Analytics />, incluindo dados tratados, finalidade, retenção e opção de
recusa; verifique também as versões instaladas para confirmar o comportamento
atual. Se essas versões exigirem consentimento, ajuste o fluxo em layout.tsx
para renderizar os componentes somente após o usuário consentir.
| <main | ||
| id="main-content" | ||
| className="flex min-h-screen flex-col items-center justify-center px-5 py-16" | ||
| > |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg '(^|/)src/app/(obrigado|privacidade)/page\.tsx$' || true
echo
echo "Relevant snippets:"
for f in src/app/obrigado/page.tsx src/app/privacidade/page.tsx; do
echo "--- $f"
wc -l "$f"
sed -n '1,80p' "$f" | cat -n
done
echo
echo "Search for skip/main-content implementations:"
rg -n 'main-content|tabIndex=\{-1\}|skip|Skip|pular|Pular' src/app src/components src 2>/dev/null || trueRepository: prof-ramos/webingressos-page
Length of output: 7703
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "src/app/page.tsx:"
sed -n '1,80p' src/app/page.tsx | cat -n
echo
echo "src/app/layout skip-link region:"
sed -n '20,45p' src/app/layout.tsx | cat -n
echo
echo "All main-content usages:"
python3 - <<'PY'
from pathlib import Path
for path in [Path("src/app/page.tsx"), Path("src/app/obrigado/page.tsx"), Path("src/app/privacidade/page.tsx")]:
lines = path.read_text().splitlines()
for i, line in enumerate(lines, 1):
if "main-content" in line or "tabIndex" in line:
print(f"{path}:{i}:{line}")
PYRepository: prof-ramos/webingressos-page
Length of output: 2948
🌐 Web query:
WCAG 2.4.1 Bypass blocks programmatically setting focus jump target tabindex
💡 Result:
To satisfy WCAG 2.4.1 (Bypass Blocks), implementing a "skip to main content" link is a highly effective and recommended technique [1][2]. A common technical challenge with this approach is ensuring that the keyboard focus actually moves to the target content when the link is activated, rather than just scrolling the page [1][3]. Setting tabindex="-1" on the target element is the standard and recommended practice to make this jump reliable [4][5][6]. Why tabindex="-1" is used: Standard HTML elements like
,are not natively focusable [5]. Browsers often fail to move focus to these elements during fragment navigation (when clicking an tag pointing to an ID) [7][6]. Adding tabindex="-1" allows an element to receive programmatic focus via JavaScript or the browser's internal focus management, without including it in the natural tab sequence of the page [1][4][6]. Implementation Best Practices: 1. Skip Link Placement: The skip link must be the very first focusable element in the DOM so that it is the first thing a user encounters when tabbing after page load [1][4][7]. 2. Target Configuration: Assign a unique ID to your main content container and add tabindex="-1" to it [1][5][6]. 3. Programmatic Focus: While many modern browsers handle focus movement to hash links automatically, relying solely on the href can be inconsistent [7][6]. It is best practice to attach a JavaScript click handler to the skip link that explicitly calls the.focus method on the target element [1][3][6]. 4. Visibility: Never hide the skip link using display: none or visibility: hidden, as these methods remove the element from the accessibility tree, making it invisible to screen readers and keyboard users [1][7][5]. Instead, use CSS to hide it visually (e.g., positioning it off-screen) and reveal it when it receives focus [1][4][3]. By following these steps, you ensure that the mechanism is robust, works across different browsers, and satisfies the requirements of WCAG 2.4.1 [5][6].
Citations:
- 1: https://www.webability.io/blog/skip-to-main-content
- 2: https://webaim.org/techniques/skipnav/
- 3: http://tarnoff.info/2025/08/04/a11y-101-2-4-1-bypass-blocks/
- 4: https://accessibility.build/wcag/2-4-1
- 5: https://rocketvalidator.com/accessibility-validation/axe/4.11/skip-link
- 6: https://www.adacompliancepros.com/wcag-guides/skip-links-focusable-target
- 7: https://a11ybot.app/learn/2-4-1-bypass-blocks/
Bora fazer o foco ir para o conteúdo pular o link.
O link “Pular para o conteúdo” só aponta para #main-content, e esses destinos não recebem foco programático. Adicione tabIndex={-1} em todos os <main id="main-content">:
src/app/obrigado/page.tsx#L8-L11src/app/privacidade/page.tsx#L6src/app/page.tsx#L18
📍 Affects 2 files
src/app/obrigado/page.tsx#L8-L11(this comment)src/app/privacidade/page.tsx#L6-L6
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/obrigado/page.tsx` around lines 8 - 11, Enable programmatic focus on
every main element with id="main-content" by adding tabIndex={-1} in
src/app/obrigado/page.tsx lines 8-11 and src/app/privacidade/page.tsx line 6;
also apply the same change to the corresponding main element in src/app/page.tsx
as requested.
| <div | ||
| id="menu-principal" | ||
| hidden={!isMenuOpen} | ||
| className="border-t border-border bg-white transition-[opacity,translate] duration-200 starting:-translate-y-2 starting:opacity-0 lg:hidden" | ||
| > | ||
| <Shell> | ||
| <nav aria-label="Navegação móvel" className="flex flex-col gap-1 py-4"> | ||
| {NAV_LINKS.map((link, index) => ( | ||
| <a | ||
| href={SITE_CONFIG.pilotFormUrl} | ||
| key={link.href} | ||
| ref={index === 0 ? firstMenuLinkRef : undefined} | ||
| href={link.href} | ||
| onClick={() => setIsMenuOpen(false)} | ||
| className="mt-2 inline-flex h-12 items-center justify-center gap-2 rounded-xl bg-brand-700 px-5 text-sm font-bold text-white transition-colors hover:bg-brand-800 sm:hidden" | ||
| className="rounded-xl px-3 py-3 text-base font-medium text-ink-700 transition-colors hover:bg-brand-50 hover:text-brand-700" | ||
| > | ||
| {SITE_CONFIG.ctaLabel} | ||
| <ArrowRight className="size-4" /> | ||
| {link.label} | ||
| </a> | ||
| </nav> | ||
| </Shell> | ||
| </div> | ||
| ) : null} | ||
| ))} | ||
| <a | ||
| href={SITE_CONFIG.pilotFormUrl} | ||
| onClick={() => setIsMenuOpen(false)} | ||
| className="mt-2 inline-flex h-12 items-center justify-center gap-2 rounded-xl bg-brand-700 px-5 text-sm font-bold text-white transition-colors hover:bg-brand-800 sm:hidden" | ||
| > | ||
| {SITE_CONFIG.ctaLabel} | ||
| <ArrowRight className="size-4" /> | ||
| </a> | ||
| </nav> | ||
| </Shell> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
MDN display property CSS transitions visibility hidden discrete discrete transition-discrete allow-discrete browser support
💡 Result:
In modern CSS, the transition-behavior property with the value allow-discrete enables transitions on properties that are typically animatable only in a discrete (all-or-nothing) manner, such as display and content-visibility [1][2][3]. By default, transition-behavior is set to normal, which prevents transitions for these discrete properties [4][2]. Key technical details: 1. Mechanism: When allow-discrete is used, the browser coordinates the discrete property change with other interpolable transitions (like opacity) [4][5]. For example, when animating from display: block to display: none, the browser maintains the block state throughout the animation duration and flips to none only at the end, allowing the other property transitions to complete [1][4][3]. 2. Implementation: You can apply this via the transition shorthand or the transition-behavior property [6][2]: transition: opacity 0.5s, display 0.5s allow-discrete; or transition-behavior: allow-discrete; 3. Starting State: When transitioning an element into the DOM or from display: none to a visible state, the @starting-style at-rule is often required to define the initial state, as transitions are not triggered on an element's first style update [1][7][4]. 4. Browser Support: Support for transition-behavior: allow-discrete is generally available in modern browsers, including Chrome (117+), Edge (117+), Firefox (129+), and Safari (17.4+) [8][9][10]. Unlike display, the visibility property is historically considered an interpolable property (though it animates discretely, switching to visible at 0% and hidden at the end) and does not strictly require allow-discrete to animate, though it is often used in conjunction with these modern techniques to manage accessibility and layout transitions [11]. [1][8][10]
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transition-behavior
- 2: https://developer.chrome.com/blog/entry-exit-animations
- 3: https://css-tricks.com/almanac/properties/t/transition/transition-behavior/
- 4: https://drafts.csswg.org/css-transitions-2/
- 5: https://modern-css.com/animating-display-none-without-workarounds/
- 6: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transition
- 7: https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Transitions
- 8: https://caniuse.com/mdn-css_properties_transition_allow-discrete
- 9: https://web-platform-dx.github.io/web-features-explorer/features/transition-behavior/
- 10: https://caniuse.com/mdn-css_properties_transition-behavior_transitionable_display
- 11: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/visibility
🌐 Web query:
developer.chrome blog exit-animations display transition-discrete allow-display Safari Firefox transition-discrete
💡 Result:
The CSS feature you are referring to is transition-behavior: allow-discrete [1][2]. It was introduced to solve the long-standing challenge of animating elements to and from display: none, which previously required JavaScript to manage the timing of DOM removal or style changes [1][3][4]. While some early documentation or discussions may have informally referred to a desired functionality as "allow-display," the official property name is transition-behavior with the value allow-discrete [1][5]. The term discrete refers to properties that cannot be interpolated (like display, visibility, or mix-blend-mode), meaning they typically flip instantly between values rather than transitioning smoothly [1][6][5]. Setting transition-behavior: allow-discrete allows the browser to perform a transition before switching the discrete property state, typically at the 50% mark of the transition duration [1][5][4]. Browser support for transition-behavior: allow-discrete is now broadly available in modern browsers [7]: - Chrome: Supported since version 117 [8][7]. - Firefox: Supported since version 129 [8][6]. - Safari: Supported since version 17.4 [8][6]. To create smooth entry and exit animations, this property is typically used in conjunction with the @starting-style at-rule [1][3]. The @starting-style rule defines the initial state for entry animations, preventing the browser from skipping the transition when an element is first added to the DOM or changes from display: none [1][2][4]. Without these combined features, animating an element out of display: none typically results in an immediate, jumpy appearance or disappearance [3][2]. For implementation, it is often recommended to use the transition shorthand and place the transition-behavior declaration after it, or to explicitly include allow-discrete within the shorthand transition definition [1][9][5]. Because older browsers will not support these properties, it is best practice to treat them as a progressive enhancement [3][4]. You can detect support using CSS feature queries like @supports (transition-behavior: allow-discrete) {... } [3].
Citations:
- 1: https://developer.chrome.com/blog/entry-exit-animations
- 2: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transition-behavior
- 3: https://github.com/GoogleChrome/modern-web-guidance-src/blob/main/guides/user-experience/animate-element-entry-exit/guide.md
- 4: https://www.pbrumby.com/2025/01/03/animate-to-and-from-display-none/
- 5: https://www.smashingmagazine.com/2025/01/transitioning-top-layer-entries-display-property-css/
- 6: https://web-platform-dx.github.io/web-features-explorer/features/transition-behavior/
- 7: https://docs.w3cub.com/css/transition-behavior
- 8: https://caniuse.com/mdn-css_properties_transition-behavior_allow-discrete
- 9: https://developer.chrome.com/blog/new-in-chrome-117
🌐 Web query:
"transition-discrete" display none Tailwind CSS
💡 Result:
The transition-discrete utility in Tailwind CSS is a wrapper for the CSS property transition-behavior: allow-discrete [1][2]. It is used to enable transitions for properties that are traditionally "discrete" and cannot be animated, most notably display [1][3]. Normally, when an element is toggled to display: none, it disappears instantly because the browser removes it from the layout, preventing any accompanying animations (like opacity) from playing [2][3]. By using transition-discrete, you instruct the browser to defer the display property change until the end of the transition timeline [2]. To effectively transition an element in and out of display: none, you must combine transition-discrete with other Tailwind utilities: 1. Transition Property: Ensure display is included in your transition properties. Tailwind's default transition utility now includes display by default [4][5]. 2. State Management: Toggle the element's visibility state (e.g., using hidden and block, or state-based variants) [1][2]. 3. Starting Style: To animate an element as it first appears (since transitions typically do not run on an element's first render or when switching from display: none to display: block), use the starting: variant [6][7]. This utilizes the CSS @starting-style rule [6][2]. Example pattern:
transition-discrete ensures the exit animation (e.g., opacity fading out) completes before the element is set to display: none [2][3]. The starting: variant ensures a smooth fade-in when the element is first displayed [6][7].
Citations:
- 1: https://tailwindcss.com/docs/transition-behavior
- 2: https://richdynamix.com/articles/tailwind-v4-starting-style-discrete-transitions
- 3: https://tailkits.com/blog/tailwind-transitions-guide/
- 4: Add discrete properties to the default list of
transitionproperties tailwindlabs/tailwindcss#17812 - 5: https://tailwindcss.com/docs/transition-property
- 6: https://tailwindcss.com/blog/tailwindcss-v4
- 7: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/transition-behavior
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate files =="
fd -a 'header\.tsx$|tailwind.*config|package.json|pnpm-lock.yaml|package-lock.json|yarn.lock' . | sed 's#^\./##' | head -80
echo
echo "== package-related tailwind versions =="
for f in $(fd 'package.json$' .); do
echo "-- $f"
rg -n '"tailwindcss"|`@tailwind`|scripts|postcss' "$f" || true
done
echo
echo "== header excerpt =="
f="$(fd 'header\.tsx$' . | head -1)"
if [ -n "$f" ]; then
echo "FILE=$f"
sed -n '1,180p' "$f" | nl -ba | sed -n '60,115p'
fi
echo
echo "== search for transition-discrete and display in Tailwind support =="
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' 'transition-discrete|transition-behavior|display'|head -80 || trueRepository: prof-ramos/webingressos-page
Length of output: 561
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== header excerpt =="
sed -n '60,120p' src/components/landing/header.tsx
echo
echo "== transition/display CSS hints / Tailwind version =="
sed -n '1,70p' package.json
printf '\n'
rg -n 'transition-discrete|transition-behavior|display|starting:opacity|starting:-translate|starting:' src/pnpm-lock.yaml package.json || trueRepository: prof-ramos/webingressos-page
Length of output: 3932
Dê cobertura pra transição de fechamento.
O menu abre via display do atributo hidden, mas esse estado não é tratado como propriedade discreta. Quando isMenuOpen vira false, display: none é aplicado na hora e corta opacity/translate. Adicione transition-discrete no div#menu-principal para fechar com animação em browsers modernos.
🎖️ Ajuste sugerido
<div
id="menu-principal"
hidden={!isMenuOpen}
- className="border-t border-border bg-white transition-[opacity,translate] duration-200 starting:-translate-y-2 starting:opacity-0 lg:hidden"
+ className="border-t border-border bg-white transition-discrete transition-[opacity,translate] duration-200 starting:-translate-y-2 starting:opacity-0 lg:hidden"
>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/landing/header.tsx` around lines 76 - 104, Update the
div#menu-principal className to include the transition-discrete utility,
preserving the existing opacity and translate transitions so closing the menu
animates before hidden applies display:none in modern browsers.
| export function PilotBand() { | ||
| return ( | ||
| <section id="programa-piloto" className="py-6 sm:py-8"> | ||
| <section id="programa-piloto" className="scroll-mt-16 py-6 sm:scroll-mt-20 sm:py-8"> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Valor de scroll-mt duplicado — ver comentário consolidado.
Aqui o scroll-mt-16/sm:scroll-mt-20 está calibrado certinho com a altura do header (h-16/sm:h-20). Missão cumprida nesse ponto. Mas esse mesmo par de números está copiado em src/components/landing/section.tsx (Linha 27). Ver comentário consolidado ao final da revisão para o ajuste sugerido.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/landing/pilot-band.tsx` at line 9, Keep the scroll-mt-16 and
sm:scroll-mt-20 values in the pilot-band section unchanged; apply the
consolidated deduplication adjustment to the corresponding section wrapper in
the Section component instead, reusing the shared spacing values without
altering header-aligned behavior.
| setSubmitError("Não foi possível enviar agora. Tente novamente em alguns minutos.") | ||
| toast.error("Erro ao enviar candidatura", { | ||
| description: "Tente novamente em alguns minutos ou entre em contato diretamente.", | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file =="
fd -a 'pilot-form\.tsx$' . || true
echo "== file excerpt =="
if [ -f src/components/landing/pilot-form.tsx ]; then
nl -ba src/components/landing/pilot-form.tsx | sed -n '1,180p'
fi
echo "== search toast/error usage in file =="
if [ -f src/components/landing/pilot-form.tsx ]; then
rg -n "toast\.error|setSubmitError|aria-live|role=\"alert\"|role='alert'" src/components/landing/pilot-form.tsx || true
fi
echo "== package sonner references =="
rg -n "sonner|`@radix-ui`|toast" package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || trueRepository: prof-ramos/webingressos-page
Length of output: 300
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file excerpt with line numbers =="
awk '{printf "%6d\t%s\n", NR, $0}' src/components/landing/pilot-form.tsx | sed -n '1,180p'
echo "== relevant usages =="
grep -n 'toast\.error\|setSubmitError\|aria-live\|role="alert"\|role='"'"'alert'"'"'' src/components/landing/pilot-form.tsx || true
echo "== package/dependency references =="
grep -RlnE 'sonner|`@radix-ui/react-toast`|toast\.error' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || trueRepository: prof-ramos/webingressos-page
Length of output: 8174
🌐 Web query:
Sonner Toast react announces error notify live region role status browser
💡 Result:
Sonner is designed to handle screen reader announcements using WAI-ARIA live regions, ensuring that toast notifications are accessible [1][2]. To properly announce toasts, the library utilizes a containing element as an ARIA live region rather than applying live region attributes to individual toast items [3]. Key accessibility details regarding Sonner's implementation include: * Live Region Strategy: The Toaster component maintains a persistent containing element (often a section) configured with aria-live, aria-relevant, and aria-atomic attributes [3]. This approach ensures that screen readers are aware of the container and can announce additions to it, solving issues where conditional rendering of individual toast elements caused some screen readers to fail to announce them [3][1]. * Configuration: Recent updates to Sonner have introduced custom ARIA announcement options [4]. Users can now configure aria-live, aria-atomic, and aria-relevant settings per toast, allowing for more granular control over notification urgency [4]. * Urgency Management: While the default behavior for general notifications is typically polite, you can use these custom options to set the aria-live attribute to assertive for urgent notifications, such as error messages, to ensure they are announced immediately [4][2]. * Historical Context: Early versions of Sonner experienced accessibility bugs related to placing role="status" directly on individual toast li elements, which led to inconsistent announcement behavior across different screen readers [3][1]. These issues were resolved by moving the live region attributes to the persistent parent container [3]. When implementing Sonner, ensure you are using a recent version that supports these configurable ARIA options to achieve optimal accessibility for error and status notifications [4].
Citations:
- 1: BUG: Invalid WAI-ARIA setup preventing screen readers from announcing new toasts emilkowalski/sonner#306
- 2: P2: Toast System Standardization — Sonner + aria-live + Rate Limiting ericsocrat/tryvit#40
- 3: fix: turn section into live region #306 emilkowalski/sonner#436
- 4: add custom ARIA announcement options to Toaster component emilkowalski/sonner#765
Escolha só um canal pra anunciar esse erro.
O mesmo catch aciona o banner role="alert" via setSubmitError(...) e dispara um toast.error(...) do Sonner, que também usa uma region viva para anunciar toasts. Isso faz o leitor de tela ouvir o mesmo erro duas vezes. Mantenha o banner e remova o toast.error desse fluxo, ou faça o feedback visual sem papel de status/alerta.
🎯 Ajuste sugerido mantendo só o banner
} catch {
setSubmitError("Não foi possível enviar agora. Tente novamente em alguns minutos.")
- toast.error("Erro ao enviar candidatura", {
- description: "Tente novamente em alguns minutos ou entre em contato diretamente.",
- })
} finally {Também vale pra linha 133.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/landing/pilot-form.tsx` around lines 94 - 97, Remova as
chamadas a toast.error no fluxo de erro de envio do formulário, mantendo
setSubmitError como único canal de anúncio acessível. Aplique essa alteração
tanto no catch próximo a setSubmitError quanto no fluxo correspondente indicado
na linha 133, preservando o banner role="alert" e o restante do tratamento de
erro.
| <section | ||
| id={id} | ||
| className={cn("scroll-mt-16 py-8 sm:scroll-mt-20 sm:py-10 lg:py-12", className)} | ||
| > |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Valor de scroll-mt duplicado entre section.tsx e pilot-band.tsx — ver comentário consolidado.
Esse componente compartilhado (Section) usa scroll-mt-16 sm:scroll-mt-20, calibrado para a altura do header (h-16/sm:h-20). O mesmo par de valores está repetido em src/components/landing/pilot-band.tsx (Linha 9), que não usa Section. Ver comentário consolidado ao final da revisão para a proposta de centralizar esse valor.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/landing/section.tsx` around lines 25 - 28, Centralize the
shared scroll-mt-16/sm:scroll-mt-20 values used by the landing Section component
and pilot-band.tsx into a reusable styling constant or utility. Update Section
and pilot-band.tsx to consume that shared definition, preserving the existing
responsive scroll offset behavior.
Consolida o trabalho não-commitado da landing em uma frente coesa. Hunks se sobrepõem entre
page.tsx/layout.tsx/globals.css, então um split por área sem staging interativo quebraria a buildabilidade por commit.Acessibilidade / UX
Skip link, foco 3px,
prefers-reduced-motion,fieldset/legend, erros comrole="alert", foco do menu mobile; eyebrow →brand-700(contraste AA).Conformidade com o design system (DESIGN.md)
.webp(azul/laranja fora da paleta, glossy) e o acervo Envatodocs/uiux-assets(~413 MB).DashboardPreviewé o visual único.brand-900→ seção clara com ícones line-art verdes, mantendo a numeração 01/02/03.TicketLineline-art; painel de apoiobrand-50→ink-100neutro (verde volta a ser recurso escasso).Performance
next/dynamic+ssr:false+IntersectionObserver, skeleton que reserva altura): tirazod+react-hook-form+radixdo first-load da home (338 → 222 KB gzip).@vercel/speed-insights+@vercel/analyticspara CWV de campo.Fixes do code-review
--success/--warningnão usados (paleta segue mono-verde).--focus-ring→var(--ring),brand-600).Validação
pnpm build✅ ·typecheck✅ ·lint✅ (0 erros) · Prettier ✅🤖 Generated with Claude Code
Summary by CodeRabbit
Novos Recursos
Acessibilidade
Melhorias