diff --git a/.agents/product-marketing.md b/.agents/product-marketing.md index 31ee5ff..058e7ac 100644 --- a/.agents/product-marketing.md +++ b/.agents/product-marketing.md @@ -1,7 +1,7 @@ # WebIngressos — Product Marketing Context -**Document version:** 1.1.0 -**Last updated:** 2026-08-05 +**Document version:** 1.2.0 +**Last updated:** 2026-08-09 **Status:** hipótese em validação; produto ainda não lançado ## 1. Product overview @@ -235,7 +235,10 @@ Não inventar nem sugerir essas provas. Interfaces demonstrativas devem ser marc - português-BR; - profissional, direto e acessível; - jovem sem parecer infantil; -- energético sem estética genérica de balada; +- expressivo, com linguagem visual de flyer universitário sem recorrer a + urgência ou tração inventada; +- títulos de marketing podem ser mais jovens e diretos; dinheiro, governança e + prestação de contas permanecem sóbrios e precisos; - confiável para assuntos financeiros; - benefícios concretos acima de adjetivos; - sem exclamações, hipérboles ou urgência artificial. @@ -259,6 +262,15 @@ Lead qualificado de um organizador disposto a conversar, compartilhar o fluxo re ## 13. Changelog +### 1.2.0 — 2026-08-09 + +- Registrada a direção visual aprovada da landing: flyer universitário, Anton, + magenta e verde ácido no marketing, com Archivo e superfícies neutras em + produto, dados e finanças. +- A linguagem do problema passou a nomear o substituto real — Pix no WhatsApp, + comprovantes e planilhas paralelas — sem alterar o status de produto em + validação nem transformar hipóteses em funcionalidades disponíveis. + ### 1.1.0 — 2026-08-05 - Rótulos de superfície dos quatro pilares registrados na seção 8: os cartões da diff --git a/AGENTS.md b/AGENTS.md index cd91c63..ca40b87 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,14 +1,16 @@ # AGENTS.md — WebIngressos Page -## Objetivo do repositório +Este documento tem três níveis, separados de propósito: -Este repositório contém exclusivamente a landing page de validação comercial da WebIngressos. +- **Mecanismo** — como o código funciona. Só muda quando o código muda. +- **Decisões** — escolhas de produto vigentes. Reversíveis por quem decide. +- **Abertas** — questões ainda não resolvidas. Vivem no `TODO.md`, não aqui. -- Site público: `webingressos.com.br`. -- Produto operacional futuro: repositório separado, preferencialmente em `app.webingressos.com.br`. -- A landing não deve acumular autenticação, checkout, pagamentos, emissão de ingressos ou backoffice. +Se uma regra parecer imutável quando é reversível (ou o contrário), ela está no +nível errado: conserte a seção antes de segui-la. Deliberação em aberto neste +documento é bug — foi assim que métricas fictícias sobreviveram a três revisões. -## Contexto obrigatório +## Antes de começar Antes de alterar posicionamento, copy, público ou CTA: @@ -23,45 +25,148 @@ Para atualizar as skills a partir do repositório oficial: pnpm skills:update ``` -## Posicionamento atual +--- -A WebIngressos é apresentada como infraestrutura em validação para venda, operação e prestação de contas de eventos universitários. +# Parte 1 — Mecanismo -Público inicial: +## Onde o design está documentado -- atléticas; -- repúblicas; -- centros acadêmicos e diretórios estudantis; -- produtores de eventos universitários. +`DESIGN.md` na raiz é a especificação do design system vigente: papéis +tipográficos, escala de raio, anatomia de cada componente e as regras de uso. +Esta seção cobre os tokens e o encanamento; `DESIGN.md` cobre os papéis. Os dois +são obrigatórios antes de alterar UI. -CTA principal: +Uma regra de lá que se perde com facilidade: tracking largo pertence só ao +papel _eyebrow_. Títulos de marketing podem usar Anton em caixa alta, mas com +tracking compacto; texto de produto e finanças permanece em Archivo. -> Quero participar do piloto +## Camada de tokens -## Riscos estratégicos conhecidos +- `globals.css` define a paleta em `:root` e a expõe via `@theme inline`: escala + `brand-50..900`, escala `ink-*` e os tokens semânticos do shadcn + (`--primary`, `--border`, …). +- Os primitivos em `src/components/ui/` são shadcn **base-nova sobre + `@base-ui/react`** e leem variáveis _sem prefixo_ (`var(--secondary)`, + `var(--popover)`). **Consequência prática: `:root` é a autoridade única do + tema.** Trocar a paleta inteira é reescrever `:root`, sem tocar em componente. +- Os primitivos também trazem variantes `dark:`. O `@custom-variant dark` no + topo do `globals.css` as redireciona para `.dark`, impedindo que + `prefers-color-scheme` do visitante as ative sozinho. Essa linha serve à + decisão de tema vigente (Parte 2), não é restrição permanente. +- `--focus-ring` é a fonte única da cor de foco: o `outline` de 3px e os anéis + dos componentes leem dela. -Contexto para decisões de posicionamento e copy. Levantado em revisão externa -(2026-08-05) e considerado válido: +## Componentes -- **O concorrente real é caseiro, não o Sympla.** O substituto imediato do - organizador é Google Forms + Pix + planilha: custo zero e já dominado. A copy - precisa ganhar dessa combinação, não de um concorrente enterprise. -- **Risco de comoditização por incumbente.** Sympla/Eventbrite podem adicionar - controle de promoter e comissão como feature. Não ancorar a copy em features - que um incumbente replica rápido; ancorar na operação específica da atlética. -- **Nicho pequeno exige venda direta.** O mercado universitário não sustenta - aquisição paga em escala; a landing serve a venda consultiva e contato - próximo, não a self-service de volume. -- **Preço indefinido.** Não há decisão entre mensalidade e percentual do bruto. - Enquanto isso não estiver resolvido, não prometer previsibilidade de custo - nem citar valores. +- Controles do shadcn vêm dimensionados para dashboard (`h-8`). Em formulários + de marketing use `h-12`; no `SelectTrigger` o override precisa do mesmo + prefixo: `data-[size=default]:h-12`. +- Seções da landing são Server Components. Só `header`, `faq`, `pilot-form` e + `pilot-form-lazy` são `"use client"`. +- Listas de conteúdo e configuração compartilhada vivem em + `src/lib/constants.ts`; títulos estruturais podem permanecer junto ao + componente quando não são reutilizados. -Não confundir com crítica inválida: revisões externas costumam penalizar a -sobriedade da copy e a ausência de números fortes. Isso é consequência -deliberada das regras abaixo (não inventar resultados, não apresentar o produto -como pronto) e não deve ser "corrigido" com claims não comprovadas. +## Formulário `POST /api/subscribe` + +- valida o corpo com o schema de `src/lib/schemas.ts` (Zod); +- na falha de validação, responde `400` com os erros por campo; +- no sucesso, persiste o lead de forma privada no Vercel Blob usando um pathname + determinístico por `submissionId`, preservando consentimento, data e origem; +- reenvios do mesmo `submissionId` são idempotentes e não criam outro registro; +- não registra PII em logs; o armazenamento durável depende de + `BLOB_READ_WRITE_TOKEN` configurado no ambiente de produção; +- limites de payload e proteção contra abuso devem acompanhar o tráfego do piloto; + para volume elevado, adicionar rate limiting distribuído antes da abertura geral. + +## Superfície de compatibilidade + +Preserve compatibilidade retroativa com: + +- estrutura pública de URLs; +- nomes das variáveis de ambiente; +- aliases `@/*`; +- fluxo principal `/#piloto`. + +## Gate de qualidade + +Antes de concluir uma alteração: + +```bash +pnpm check +``` + +Não existe suíte de testes: `pnpm check` (format, lint, typecheck, build) é o +gate completo. Também verifique manualmente: + +- viewport móvel e desktop; +- estados de foco; +- contraste; +- envio, repetição e falha do formulário; +- ausência de links quebrados; +- ausência de promessas não comprovadas; +- nenhuma regressão em metadados, sitemap e robots. + +--- + +# Parte 2 — Decisões vigentes + +## Escopo do repositório + +Este repositório contém exclusivamente a landing page de validação comercial. -## Regras de copy +- Site público: `webingressos.com.br`. +- Produto operacional futuro: repositório separado, preferencialmente em `app.webingressos.com.br`. +- A landing não deve acumular autenticação, checkout, pagamentos, emissão de ingressos ou backoffice. + +## Posicionamento + +A WebIngressos é apresentada como infraestrutura em validação para venda, +operação e prestação de contas de eventos universitários. + +Público inicial: atléticas; repúblicas; centros acadêmicos e diretórios +estudantis; produtores de eventos universitários. + +CTA principal: + +> Quero participar do piloto + +## Tema visual + +- **Decisão atual: tema escuro.** `:root` carrega a paleta violeta-noturna e + `color-scheme: dark`; o `layout.tsx` aplica `.dark` explicitamente para que + as variantes dos primitivos não dependam da preferência do visitante. +- Marketing usa Anton, magenta, verde ácido e composição inspirada em flyers. + Produto, dados e finanças usam Archivo, superfícies neutras e destaques mais + contidos. A mesma página pode mostrar os dois registros, mas não misturá-los + dentro de um componente financeiro. +- **Não usar cores cruas do Tailwind** (`slate-*`, `emerald-*`) nos + componentes — sempre os tokens (`bg-primary`, `text-muted-foreground`, + `border-border`). +- A fonte canônica da identidade pública é a landing `/` e seus tokens em + `globals.css`. Protótipos externos são referência visual, não rota nem + dependência deste repositório. + +## Estética + +Padrão-nega para gradiente decorativo, 3D e sombra pesada. Textura de grão, +grid, blocos planos e tipografia expressiva criam a linguagem de flyer sem +substituir conteúdo por efeito. A exceção é permitida quando o efeito +**codifica informação** — justifique no PR. + +O critério é esse, não a técnica: a perfuração de um ingresso codifica algo +verdadeiro (o canhoto fica com quem organiza); um `rotateY` com brilho varrendo +não codifica nada e sai. + +Exceção declarada, para não ficar implícita: o briefing de 2026-08-05 pede +explicitamente **glow neon sutil no hover do botão primário**. É sombra, o +critério acima não a salva (hover já é codificado pela cor) e ela existe por +pedido do cliente, não por mérito da regra. Fica registrada aqui como exceção +nomeada — se o briefing mudar, ela cai junto. A segunda exceção é a sombra +deslocada do painel demonstrativo, que o separa do hero como janela de produto. +Nenhuma outra sombra é aceita sem passar pelo critério. + +## Copy - Escreva em português-BR. - Priorize clareza, especificidade e linguagem do organizador. @@ -72,81 +177,76 @@ como pronto) e não deve ser "corrigido" com claims não comprovadas. - Evite competir apenas por preço ou taxa. - Evite jargão sem consequência concreta. - Prefira benefício operacional verificável a listas extensas de funcionalidades. - -> O mockup de dashboard no hero (`dashboard-preview.tsx`) é ilustrativo e -> marcado `aria-hidden`, mas é visualmente visível e mostra métricas -> fictícias (faturamento, eventos ativos). Isso está em tensão com a regra -> acima — avaliar antes do lançamento se o rótulo precisa deixar isso mais -> explícito ou se os números devem ficar mais genéricos. - -## Regras técnicas +- Interface demonstrativa precisa de rótulo visível de exemplo ou prévia, no + próprio bloco — não em nota de rodapé. + +## Ética com o comprador final + +Persuasão pode organizar a decisão do organizador; não pode obscurecer o +dinheiro de quem compra o ingresso. + +- Não ocultar valor, taxa ou total em nenhuma etapa visível ao comprador. +- Não usar escassez, contagem regressiva ou prova social sem fato verificável + por trás. +- Viés cognitivo é aceitável para **ordenar informação verdadeira** (hierarquia, + ancoragem em dado real); não para fabricar urgência ou tração inexistente. +- O produto vende prestação de contas auditável. Interface que esconde dinheiro + contradiz a proposta que está sendo vendida. + +## Acessibilidade (pisos) + +- Contraste de texto: ≥ 4,5:1 (WCAG AA). +- Contraste de não-texto (WCAG 1.4.11): ≥ 3:1 para o que **delimita ou + identifica um controle** — borda de botão, de input, estado de foco, ícone + que carrega significado. Divisor puramente decorativo não entra nessa conta. + A distinção importa: onde as camadas de superfície diferem pouco, a borda + passa a ser a única pista da existência do controle, e aí ela é obrigada aos + 3:1 mesmo parecendo "só uma linha". +- Foco sempre visível: `outline` de 3px com `--focus-ring` e offset de 3px. +- `prefers-reduced-motion` respeitado — o guard global em `globals.css` colapsa + animação e transição; não reintroduza movimento fora dele. +- Navegação completa por teclado, incluindo o skip link do `layout.tsx`. +- Alvo de toque confortável em marketing (`h-12`). + +## Técnicas - Next.js com App Router e TypeScript estrito. - Design mobile-first. - Tailwind CSS e componentes no padrão shadcn/ui mantidos no repositório. - Priorize Server Components; use `"use client"` somente quando houver estado, efeitos ou APIs do navegador. - Não adicione dependências sem necessidade demonstrável. -- Preserve acessibilidade, responsividade e navegação por teclado. - Valide entrada no servidor, mesmo quando houver validação no cliente. - Não registre PII em logs. - Nunca versione segredos ou arquivos `.env` reais. - Consulte Context7 ou documentação oficial atual antes de usar APIs suscetíveis a mudança. -## Qualidade obrigatória - -Antes de concluir uma alteração: - -```bash -pnpm check -``` - -Também verifique: - -- viewport móvel e desktop; -- estados de foco; -- contraste; -- envio, repetição e falha do formulário; -- ausência de links quebrados; -- ausência de promessas não comprovadas; -- nenhuma regressão em metadados, sitemap e robots. - -## Formulário +## Riscos estratégicos conhecidos -O endpoint `POST /api/subscribe`: +Contexto para decisões de posicionamento e copy. Levantado em revisão externa +(2026-08-05) e considerado válido: -- valida o corpo com o schema de `src/lib/schemas.ts` (Zod); -- na falha de validação, responde `400` com os erros por campo; -- no sucesso, persiste o lead de forma privada no Vercel Blob usando um pathname - determinístico por `submissionId`, preservando consentimento, data e origem; -- reenvios do mesmo `submissionId` são idempotentes e não criam outro registro; -- não registra PII em logs; o armazenamento durável depende de - `BLOB_READ_WRITE_TOKEN` configurado no ambiente de produção; -- limites de payload e proteção contra abuso devem acompanhar o tráfego do piloto; - para volume elevado, adicionar rate limiting distribuído antes da abertura geral. +- **O concorrente real é caseiro, não o Sympla.** O substituto imediato do + organizador é Google Forms + Pix + planilha: custo zero e já dominado. A copy + precisa ganhar dessa combinação, não de um concorrente enterprise. +- **Risco de comoditização por incumbente.** Sympla/Eventbrite podem adicionar + controle de promoter e comissão como feature. Não ancorar a copy em features + que um incumbente replica rápido; ancorar na operação específica da atlética. +- **Nicho pequeno exige venda direta.** O mercado universitário não sustenta + aquisição paga em escala; a landing serve a venda consultiva e contato + próximo, não a self-service de volume. +- **Preço indefinido.** Não há decisão entre mensalidade e percentual do bruto. + Enquanto isso não estiver resolvido, não prometer previsibilidade de custo + nem citar valores. -## Compatibilidade +Não confundir com crítica inválida: revisões externas costumam penalizar a +sobriedade da copy e a ausência de números fortes. Isso é consequência +deliberada das regras de copy acima e não deve ser "corrigido" com claims não +comprovadas. -Preserve compatibilidade retroativa com: +--- -- estrutura pública de URLs; -- nomes das variáveis de ambiente; -- aliases `@/*`; -- fluxo principal `/#piloto`. +# Parte 3 — Abertas -## Design System - -- Tema **light apenas**. `globals.css` define a paleta em `:root` e a expõe via - `@theme inline`: escala `brand-50..900` (verde institucional `brand-700 = #0e6340`), - escala `ink-*` e os tokens semânticos do shadcn (`--primary`, `--border`, …). -- **Não usar cores cruas do Tailwind** (`slate-*`, `emerald-*`) nos componentes — - sempre os tokens (`bg-brand-700`, `text-ink-500`, `border-border`). -- Os primitivos em `src/components/ui/` são shadcn **base-nova sobre `@base-ui/react`** - e leem variáveis _sem prefixo_ (`var(--secondary)`, `var(--popover)`), por isso a - camada `:root` + `@theme inline` é obrigatória. -- Eles também trazem variantes `dark:`; o `@custom-variant dark` no topo do - `globals.css` as neutraliza. Não remover. -- Controles são dimensionados para dashboard (`h-8`). Em formulários de marketing - use `h-12`, e no `SelectTrigger` o override precisa do mesmo prefixo: - `data-[size=default]:h-12`. -- Seções da landing são Server Components. Só `header`, `faq`, `pilot-form` e - `pilot-form-lazy` são `"use client"`. +Questões em aberto não moram aqui. Estão no `TODO.md`, com prioridade e critério +de conclusão. A que afeta decisões deste documento hoje é o modelo de preço, +que bloqueia qualquer copy sobre custo. diff --git a/DESIGN.md b/DESIGN.md index 65bce27..731d164 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,256 +1,230 @@ --- -version: alpha -name: Campus Ledger +version: beta +name: Campus After Dark description: > - A light, single-hue institutional-green system for a university - event-operations product — a marketing site whose hero contains a live - financial/ops dashboard preview, aimed at student organizations and - event producers who need to be taken seriously about other people's - money. + Sistema visual escuro e expressivo para a landing de uma infraestrutura em + validação para eventos universitários. O marketing tem energia de flyer; a + demonstração de produto mantém legibilidade financeira. colors: - primary: "#0e6340" - primary-strong: "#0a4d32" - primary-container: "#eaf5ef" - accent: "#2f9e68" - accent-soft: "#d3eadf" - accent-softest: "#a9d6c0" - on-primary: "#ffffff" - on-primary-container: "#0e6340" - surface: "#f9fafc" - surface-container: "#ffffff" - surface-container-muted: "#f1f6f5" - outline: "#e9ecf1" - outline-variant: "#d6dbe4" - on-surface: "#1b2740" - on-surface-variant: "#5e6677" - on-surface-faint: "#8a92a3" - error: "#c8394f" - on-error: "#ffffff" + background: "#0a0611" + surface: "#140d20" + surface-raised: "#1a1229" + surface-deep: "#070410" + foreground: "#f7f4ee" + muted-foreground: "#a394b8" + border: "#3c2c50" + control-border: "#806b90" + hot: "#ff2e7e" + lime: "#c6ff3d" + success: "#34e5a0" + warning: "#ffc857" + destructive: "#ff6b7d" typography: - display: - fontFamily: Plus Jakarta Sans - fontSize: 3.1rem - fontWeight: 800 - lineHeight: 1.08 - letterSpacing: -0.028em - headline-lg: - fontFamily: Plus Jakarta Sans - fontSize: 1.875rem - fontWeight: 800 - lineHeight: 1.2 + marketing-display: + fontFamily: Anton + fontWeight: 400 + lineHeight: 0.9 letterSpacing: -0.025em - headline-md: - fontFamily: Plus Jakarta Sans - fontSize: 1.5rem - fontWeight: 800 - lineHeight: 1.3 - letterSpacing: -0.02em - title: - fontFamily: Plus Jakarta Sans - fontSize: 1.125rem - fontWeight: 700 - lineHeight: 1.4 - body-lg: - fontFamily: Plus Jakarta Sans - fontSize: 1.125rem + textTransform: uppercase + marketing-heading: + fontFamily: Anton fontWeight: 400 - lineHeight: 1.625 - body-md: - fontFamily: Plus Jakarta Sans - fontSize: 1rem + lineHeight: 0.94 + letterSpacing: -0.02em + textTransform: uppercase + body: + fontFamily: Archivo fontWeight: 400 - lineHeight: 1.625 - label: - fontFamily: Plus Jakarta Sans - fontSize: 1rem - fontWeight: 700 - lineHeight: 1 - label-sm: - fontFamily: Plus Jakarta Sans - fontSize: 0.875rem + lineHeight: 1.6 + interface: + fontFamily: Archivo fontWeight: 600 - lineHeight: 1.3 + lineHeight: 1.4 eyebrow: - fontFamily: Plus Jakarta Sans + fontFamily: Archivo fontSize: 0.75rem fontWeight: 700 lineHeight: 1 letterSpacing: 0.16em - caption: - fontFamily: Plus Jakarta Sans - fontSize: 0.6875rem - fontWeight: 600 - lineHeight: 1.3 + textTransform: uppercase rounded: - control: 0.625rem - tile: 0.875rem - card: 1.25rem - panel: 1.75rem + control: 0.5rem + card: 0.75rem + panel: 1rem full: 9999px -spacing: - xs: 4px - sm: 8px - md: 16px - lg: 24px - xl: 40px - 2xl: 56px - gutter: 20px motion: hover: 200ms - panel-open: 220ms - panel-close: 200ms + accordion-open: 220ms + accordion-close: 200ms + marquee: 24s easing: ease-out -components: - button-primary: - backgroundColor: "{colors.primary}" - textColor: "{colors.on-primary}" - typography: "{typography.label}" - rounded: "{rounded.control}" - height: 56px - padding: 24px - button-primary-hover: - backgroundColor: "{colors.primary-strong}" - button-secondary: - backgroundColor: "{colors.surface-container}" - textColor: "{colors.primary}" - typography: "{typography.label}" - rounded: "{rounded.control}" - height: 56px - padding: 24px - button-compact: - backgroundColor: "{colors.primary}" - textColor: "{colors.on-primary}" - typography: "{typography.label-sm}" - rounded: "{rounded.control}" - height: 44px - padding: 20px - card: - backgroundColor: "{colors.surface-container}" - rounded: "{rounded.card}" - padding: 24px - card-hover: - backgroundColor: "{colors.surface-container}" - panel: - backgroundColor: "{colors.surface-container-muted}" - rounded: "{rounded.panel}" - padding: 56px - panel-inverse: - backgroundColor: "{colors.primary}" - textColor: "{colors.on-primary}" - rounded: "{rounded.panel}" - padding: 40px - input: - backgroundColor: "{colors.surface-container}" - textColor: "{colors.on-surface}" - typography: "{typography.body-md}" - rounded: "{rounded.control}" - height: 48px - padding: 16px - icon-chip: - backgroundColor: "{colors.primary-container}" - textColor: "{colors.primary-strong}" - rounded: "{rounded.full}" - size: 36px - checkbox: - backgroundColor: "{colors.surface-container}" - textColor: "{colors.primary}" - rounded: 6px - size: 20px --- -## Overview +# Campus After Dark + +## Princípio + +A landing deve parecer feita para quem divulga uma calourada e, horas depois, +precisa explicar cada repasse. Essa tensão define o sistema: + +- marketing: Anton, títulos grandes, magenta, verde ácido, grid e textura de + papel impresso; +- produto, dados e finanças: Archivo, superfícies neutras, alinhamento estável, + números tabulares e cor usada para indicar estado. + +Energia não autoriza promessas. A página continua sendo uma candidatura ao +piloto, não uma demonstração de tração ou um produto em produção. + +## Tokens e autoridade do tema + +`src/app/globals.css` é a fonte única da paleta. Os componentes consomem tokens +semânticos; valores hexadecimais não saem de `:root`. + +- `background`: canvas violeta quase preto. +- `surface`: cards e painel demonstrativo. +- `surface-raised`: bandas que precisam separar um momento da página. +- `surface-deep`: header, footer e planos de fundo de alta densidade. +- `foreground` e `muted-foreground`: texto principal e secundário. +- `border`: separação estrutural decorativa. +- `input`/`control-border`: delimitação de controles com contraste mínimo 3:1. +- `primary`/`hot`: ação principal em magenta. +- `accent`/`lime`: eyebrows, indicadores e destaques de leitura. +- `success`, `warning`, `destructive`: apenas estados correspondentes. +- `focus-ring`: única fonte da cor de foco, sempre verde ácido. -Picture a bank's back-office operations console that got assigned to redesign a flyer for a university costume party — and took the job completely seriously. That tension is the whole system: financial-grade restraint (revenue tiles, settlement figures, progress bars, a "98% valid" check-in stat) rendered in a single confident green, sitting on an almost-white canvas, aimed at 20-year-olds who run their fraternity's or athletic club's event budget and need to trust the thing with real money. +O tema é deliberadamente escuro. `layout.tsx` aplica `.dark` no elemento +`html`; `@custom-variant dark` impede que a preferência do sistema operacional +decida o tema da landing. -The product's own brief states the target feeling as "energetic without a nightclub aesthetic — trustworthy first." That rules out almost everything a consumer-facing events brand reaches for by default: no neon, no glow, no gradient mesh, no glassmorphism, no confetti color. The only color that gets to be loud is the single institutional green, and even that green is closer to a passport stamp or a ledger stripe than a highlighter. Everything else is white paper, hairline rule, and graphite-navy ink. +## Tipografia -The UI reads as calm and slightly formal at rest — cards are flat, bordered, barely elevated — and becomes only _slightly_ more alive on interaction: a card lifts two pixels and gains a soft shadow on hover, a button darkens by one step, nothing bounces or springs. The energy the brief asks for lives in the copy's directness and the confident scale of the headline type, not in motion or saturation. +### Marketing display -## Colors +Anton, caixa alta, tracking compacto e line-height entre `0.9` e `0.95`. Usado +em `h1`, títulos de seção e headline do CTA final. Anton não entra em parágrafo, +formulário, números ou textos financeiros. -A monochromatic system: one green hue carries every interactive and brand moment, and a desaturated blue-gray ink scale carries every word. There is no secondary or tertiary hue — introducing one would immediately read as "consumer event app" rather than "financial infrastructure." +### Body e interface -- **Primary ({colors.primary}):** A deep, slightly blue-leaning forest green — closer to a passport cover or a bank vault door than to anything botanical. It is the only saturated color in the system and appears exclusively on the single most important action in any given view: the primary CTA, the deep-green closing band, filled icon roundels. -- **Primary Strong ({colors.primary-strong}):** The hover/pressed state of primary. It does not lighten on interaction — a common consumer-app tell — it _deepens_, the way a wet ink stamp reads darker than a dry one. This is a deliberate signal of gravity, not liveliness. -- **Primary Container ({colors.primary-container}):** A near-white green wash used only as a quiet background — icon roundels on light surfaces, checklist bullet backdrops, the large "Programa Piloto" band. It should never be mistaken for a second brand color; it is primary diluted almost to nothing. -- **Accent ({colors.accent}):** A mid-toned, slightly more saturated green reserved for data — chart bars, sparkline strokes, checkmarks, progress fills. This is the "the numbers are good" color. It never appears on static UI chrome, only on things that represent a live metric. -- **Surface ({colors.surface}):** Not pure white — a barely-there cool off-white, the visual equivalent of uncoated paper stock rather than a glossy screen. This is the page canvas everything else floats on. -- **Surface Container ({colors.surface-container}):** True white, reserved for anything that needs to read as a discrete object sitting _on_ the canvas — cards, the header bar, form fields, the dashboard panel. -- **Surface Container Muted ({colors.surface-container-muted}):** A pale green-gray, one step warmer than pure white, used for exactly one purpose: the large "we're selecting our first partners" band. It signals "this section is a distinct offer," not decoration. -- **Outline ({colors.outline}):** A hairline neutral gray used for every card and input border in the system. Borders here are informational, not decorative — thin enough to feel like a rule on a ledger page, never a heavy stroke. -- **On Surface ({colors.on-surface}):** A near-black navy — not true black — used for every headline and every important number. It has just enough blue in it to feel considered rather than default. -- **On Surface Variant ({colors.on-surface-variant}):** A cooler mid-gray for all body copy and secondary text. It is dim enough to clearly subordinate itself to headlines, never competing for attention. -- **On Surface Faint ({colors.on-surface-faint}):** The quietest text color in the system — chart axis labels, placeholder text, micro-captions inside the dashboard preview. If a reader has to squint, that's correct; this text is ambient, not addressed to them directly. -- **Error ({colors.error}):** A single desaturated red-pink for form validation only. It never appears anywhere else in the system — no error-colored badges, no destructive-red accents in illustrations. +Archivo em todo corpo, navegação, formulário, card, FAQ e painel. Dados usam +pesos maiores e `tabular-nums` quando o alinhamento entre valores importa. -## Typography +### Eyebrow -One family carries the entire system: a geometric-humanist grotesque with a double-story lowercase "a" and rounded terminals — friendly enough to not feel like enterprise software, but set at weights and tracking tight enough to still feel like it's handling money. There is no second display face and no monospace anywhere; numeral-heavy content (currency, percentages, dashboard stats) is set in the same family at a heavier weight rather than switching to tabular figures in a different face. +Archivo, 12 px, bold, caixa alta e tracking `0.16em`. É o único papel que +combina caixa alta com tracking largo. Botões continuam em capitalização +normal; títulos Anton podem usar caixa alta, mas com tracking apertado. -- **Display ({typography.display}):** The single largest text on the page, used once — the hero headline. It is set extremely tight (`-0.028em` tracking, `1.08` line height), so three short lines of a punchy Portuguese sentence read as one dense, confident block rather than a loose title. -- **Headline Large ({typography.headline-lg}):** Every section title on the page ("Organizar o evento não deveria depender de planilhas…"). Same extra-bold weight as display, same instinct toward tight tracking, but scaled down enough to sit comfortably above a grid of cards without competing with the hero. -- **Headline Medium ({typography.headline-md}):** Sub-page and sub-section titles — a form's own heading, a confirmation page's headline. Same weight and tightness as headline-lg, one step smaller. -- **Title ({typography.title}):** Card and tile titles — a pillar's name, a dashboard stat's headline number. Bold rather than extra-bold; this is where the system starts feeling like data rather than a manifesto. -- **Body Large ({typography.body-lg}):** Sub-headline copy directly under a display or headline-lg — generously leaded (`1.625`) so a two-sentence supporting paragraph never feels cramped against the bold headline above it. -- **Body Medium ({typography.body-md}):** Default paragraph and card-description copy throughout. Same generous leading as body-lg, one step smaller. -- **Label ({typography.label}):** Primary button text. Bold, tight line-height, no letter-spacing — it should read as a single confident word or short phrase, never as a paragraph fragment. -- **Label Small ({typography.label-sm}):** Form field labels, nav links, secondary buttons. Semi-bold rather than bold — present but clearly subordinate to primary labels. -- **Eyebrow ({typography.eyebrow}):** Small kicker text above a section or panel headline ("PROGRAMA PILOTO"). Always set in uppercase with wide tracking (`0.16em`) and always in the accent green — this is the one place tracked-out caps are allowed, and it should never bleed into any other text role. -- **Caption ({typography.caption}):** The smallest text in the system — axis labels and micro-annotations inside the dashboard preview only. It is allowed to be nearly illegible at a glance; its job is texture and plausibility, not communication. +## Cor e contraste + +Magenta identifica a ação principal e momentos de marca. Verde ácido identifica +seção, progresso, confirmação e contraste editorial. Nenhuma cor deve ser +usada como única forma de comunicar estado: texto, ícone ou rótulo acompanha. + +Pisos obrigatórios: + +- texto normal: 4,5:1; +- texto grande: 3:1; +- contorno de controles, foco e ícones funcionais: 3:1; +- foco: outline de 3 px e offset de 3 px. ## Layout -Mobile-first with a hard ceiling: content never exceeds a comfortably narrow desktop measure, keeping even the widest six-column grid from sprawling into "dashboard software" territory. Every section shares one consistent side gutter and one consistent vertical rhythm — spacing compresses uniformly from desktop to mobile rather than sections having their own bespoke rhythm. +- mobile-first, sem largura mínima implícita; +- shell com máximo de 1200 px e gutters de 20/24/32 px; +- hero em uma coluna no mobile e duas no desktop; +- cards passam de uma para duas e quatro colunas conforme o conteúdo; +- títulos nunca dependem de quebra manual no mobile; +- ornamentos permanecem dentro de containers com `overflow-hidden`; +- nenhum elemento decorativo pode aumentar `scrollWidth`. + +## Superfícies e profundidade + +Cards são planos: mudança de superfície e borda fazem a separação. Hover pode +subir 2 px e mudar a cor da borda. Sombra pesada, 3D, glassmorphism e gradiente +decorativo continuam proibidos. + +Exceções declaradas: + +1. o painel demonstrativo pode ter sombra deslocada magenta para se separar do + hero como uma janela de produto; +2. o botão primário pode ganhar glow magenta sutil somente no hover, conforme o + briefing aprovado; +3. grão e grid são texturas planas, sem simular volume. + +## Componentes + +### Header + +Sticky, `surface-deep` translúcido e borda inferior. Não contém link para +`/dashboard` nem ação “Entrar” enquanto a aplicação não estiver disponível. +O CTA repete “Quero participar do piloto”. Menu móvel abre com foco no primeiro +link, fecha com Escape e devolve foco ao botão. + +### Hero + +Comunica público, problema e benefício antes da rolagem. Tem um CTA primário e +um link secundário “Ver por dentro”, que aponta para `#demonstracao`. O painel +fica acompanhado, no próprio bloco, pelos rótulos “Demonstração do produto” e +“Dados fictícios”. + +### Faixa de público + +Faixa verde ácido com audiência conhecida. Pode usar marquee em CSS; com +`prefers-reduced-motion`, torna-se estática. O conteúdo duplicado para o loop é +`aria-hidden`. + +### Cards de problema -The page is built from a strict single-column stack on mobile that progressively splits into two- and multi-column grids as width allows: a two-column hero (copy beside a live dashboard preview) collapses to a single stacked column below desktop width; a six-item problem grid steps down through three columns to two as the viewport narrows; a four-item feature grid does the same through two columns. Nothing reflows into an asymmetric or masonry layout — every grid is a strict, evenly-sized grid at every breakpoint. +Uma dor concreta por card, iconografia magenta e texto Archivo. Não usar +estatística, urgência ou claim de cliente. -Interior padding scales with a component's importance: compact controls (inputs, buttons, nav pills) use the smallest padding step, cards use a step up, and the two large "band" panels (the pale program panel and the deep-green closing panel) use the most generous padding in the system, reinforcing that they are the two moments the page most wants to slow down for. +### Cards da proposta -## Elevation & Depth +Alternam chips magenta e verde ácido. A seção precisa dizer que os fluxos estão +em validação, para que as descrições não pareçam disponibilidade atual. -Depth is almost entirely conveyed through **borders and background-color steps**, not shadow. Every card sits directly on the page canvas with only a 1px hairline outline — the shadow underneath it is so faint (low opacity, tightly clipped) that on a quick glance the card reads as "outlined," not "floating." This is a deliberate choice: heavy drop shadows would read as consumer-app gloss, which is exactly the register this system avoids. +### Fluxo operacional -There are exactly three elevation moments, and each is used for exactly one thing: +Quatro passos: configurar evento, ligar venda e responsável, registrar entrada +e fechar com rastreabilidade. O eyebrow “Fluxo previsto para o piloto” faz parte +do significado do componente e não pode ser removido por motivo cosmético. -1. **Card rest state** — a whisper of a shadow plus a hairline border. Used for every problem tile, pillar tile, and audience pill. -2. **Card hover state** — the same whisper of a shadow, slightly more present, paired with the card's outline shifting from neutral gray to a faint green tint and the card lifting two pixels. This is the _only_ elevation change that responds to interaction anywhere in the system. -3. **Panel elevation** — a much larger, softer, more diffused shadow reserved for exactly one element: the dashboard preview floating beside the hero copy. It is allowed to look like a real "object" in a way nothing else on the page is, because it is the one piece of UI standing in for an actual product screenshot. +### Painel demonstrativo -The deep-green closing band and the pale program-piloto band both sit flush with zero shadow — they're panels of color, not floating cards, and should never pick up a drop shadow no matter how tempting it is to add one for "polish." +Superfície financeira neutra, Archivo e cor funcional. Os valores podem ser +fictícios somente porque o bloco é rotulado antes deles. `aria-hidden` evita que +uma sequência de números sem contexto polua a leitura por tecnologia assistiva. -## Shapes +### Formulário -Corner radius scales with a container's size, not its importance: the biggest, calmest panels get the roundest corners, and the smallest, densest UI (dashboard mini-tiles) gets the tightest ones. Nothing in the system is sharp-cornered — the softest radius still reads as "considered," never "clinical" — but nothing is fully rounded into a pill either, except literal pills and circular icon chips. +Mantém os primitivos base-nova, altura mínima 48 px, borda de controle 3:1, +erros por campo e erro geral. Sucesso só existe após resposta real de +`POST /api/subscribe`; então ocorre navegação para `/obrigado`. Consentimento +liga para `/privacidade`. Honeypot, Zod e idempotência não são detalhes visuais +e não podem ser removidos em uma revisão de UI. -- **Control** ({rounded.control}): buttons, inputs, select triggers, nav pills. This is the workhorse radius — soft enough to feel approachable, tight enough that a row of controls still reads as a grid, not a cluster of capsules. -- **Tile** ({rounded.tile}): the small stat tiles inside the dashboard preview. Slightly softer than a control, since these are meant to feel like miniature cards rather than input chrome. -- **Card** ({rounded.card}): every content card — problem tiles, pillar tiles, the form container, the FAQ items. The signature radius of the system; if a screenshot of this product had to be identified from silhouette alone, this radius is the tell. -- **Panel** ({rounded.panel}): the two largest single-color band panels. Round enough to feel like a soft object rather than a hard-edged banner ad. -- **Full** ({rounded.full}): perfect circles only — icon roundels, small circular badges. Never used for buttons or cards; a pill-shaped button here would read as "consumer app," which this system is deliberately avoiding. +### FAQ e CTA final -Line art (the two small illustrations of a ticket stub and an ID badge) is drawn exclusively in thin, uniform, rounded-cap strokes in the primary or accent green at low opacity — never filled, never multi-color, never photographic. It should read as a diagram sketched on the ledger page, not as marketing illustration. +FAQ usa cards planos e accordion acessível. O CTA final pode inverter para +magenta sólido, com botão claro. Nenhum dos dois introduz uma segunda ação de +mesmo peso. -## Components +## Movimento -- **Button — Primary** ({colors.primary} fill, {colors.on-primary} text): the only filled button in the system. One per view, always the single highest-priority action ("Quero participar do piloto"). Darkens on hover; never lightens, never scales, never glows. -- **Button — Secondary** (white fill, {colors.primary} text, {colors.outline}-tinted border): the "second" action on any screen that has two. Same height and radius as primary so a pair of buttons always aligns as a clean row, never mismatched. -- **Button — Compact**: the same primary treatment at a smaller height, used only inside chrome-constrained contexts like the site header, never in page content. -- **Card**: white fill, hairline border, whisper shadow at rest, lift-plus-tint-plus-slightly-stronger-shadow on hover. The universal content container — every repeating grid item in the system is a Card. -- **Panel**: the large pale-green band. Houses a checklist and supporting illustration; always paired with an eyebrow label above its headline. -- **Panel — Inverse**: the large solid-green closing band. White text on green, paired with a white pill-free secondary-style button (white fill, green text) — the one place a "secondary" button appears on a colored background rather than white. -- **Input**: white fill, hairline border, generous internal padding, focus state shown as a colored border plus a soft ring rather than a color fill change. Every text field, select trigger, and textarea in the form shares identical height and radius so a multi-column form row always lines up. -- **Icon Chip**: a small perfect circle in the palest green, holding a single-color outline icon. Used wherever a list item or stat needs a lightweight visual anchor without competing with the surrounding text. -- **Checkbox**: a small square, white at rest, filling solid primary green with a white check mark once ticked — the only checkbox-style control in the system, used exactly once (consent). +Transições duram até 220 ms. Não usar reveal por scroll, parallax, spring ou +animação em JavaScript. `prefers-reduced-motion` colapsa todas as animações e +transições. A página deve continuar legível e completa sem movimento. -## Do's and Don'ts +## Checklist de revisão -- **Do** treat green as a scarce resource — if more than one element per view is fully saturated green, something has gone wrong. -- **Do** let hover states be quiet: a two-pixel lift, a one-step color shift, nothing that bounces, scales past 100%, or glows. -- **Do** keep every card's border and shadow proportional to its size — bigger panels get softer, larger shadows; small tiles stay nearly flat. -- **Do** set numbers and money amounts in the same type family as everything else, just heavier — never switch to a monospace or tabular-figures face to signal "this is data." -- **Don't** introduce a second brand hue. No blue, no purple, no orange accent "for variety" — the entire palette is one green plus neutral ink. -- **Don't** use neon, gradient, glow, or glassmorphism anywhere. A shadow with more than a whisper of opacity, or a button with a colored glow underneath it, is an immediate tell that the design has drifted into "nightclub event app" territory, which is explicitly what this system exists to avoid. -- **Don't** round anything into a full pill except literal circular icon chips and badges. A pill-shaped button or card reads as consumer-casual, not institutional. -- **Don't** add motion beyond a 200ms color/position transition. No parallax, no scroll-triggered reveals, no spring physics. -- **Don't** let illustrations become filled, multi-color, or photographic. They stay thin-stroke, single-hue line art at all times. -- **Don't** use pure black or pure white for text and backgrounds — the ink is always a navy-tinted near-black, the canvas always a cool near-white. +- Anton aparece apenas em títulos de marketing e marca. +- Archivo aparece em body, interface, dados e finanças. +- Não há hex nem cores cruas do Tailwind nos componentes. +- O único glow está no hover da ação primária. +- Demonstrações estão rotuladas no próprio bloco. +- Recursos em validação não parecem prontos. +- Nenhum link aponta para rota inexistente. +- Não há overflow em 320, 375, 390, 768, 1024 e 1440 px. +- Foco, controles e texto atendem aos pisos de contraste. +- A experiência continua funcional com movimento reduzido e zoom de 200%. diff --git a/README.md b/README.md index f32fddc..a7d0d04 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ Landing page de validação comercial da **WebIngressos**, destinada à captação de organizadores de eventos universitários para o programa piloto. +A identidade pública usa fundo violeta-noturno, magenta e verde ácido com Anton +em títulos de marketing. Prévia de produto, dados e finanças usa Archivo e +superfícies mais neutras; números simulados aparecem apenas em blocos marcados +como demonstração. + ## Escopo Este repositório contém apenas o site público de marketing: diff --git a/TODO.md b/TODO.md index e29b2c7..0a9a106 100644 --- a/TODO.md +++ b/TODO.md @@ -20,14 +20,14 @@ repositório. ## P0 — bloqueadores antes da produção -- [ ] Substituir o armazenamento em `/tmp/webingressos-leads` por banco, KV ou - webhook com persistência durável. +- [x] Substituir o armazenamento em `/tmp/webingressos-leads` por persistência + privada no Vercel Blob, com pathname determinístico por `submissionId`. - [ ] Definir o contrato de recuperação, duplicidade, falha e reprocessamento dos leads; não registrar PII nos logs. - [ ] Testar o `POST /api/subscribe` em produção: sucesso, payload inválido (`400`), JSON malformado, falha do armazenamento e repetição do envio. -- [ ] Adicionar rate limiting, honeypot ou outra proteção antispam antes de - expor o formulário a tráfego público. +- [ ] Adicionar rate limiting distribuído antes de expor o formulário a tráfego + elevado. O honeypot já está implementado e validado localmente. - [ ] Revisar juridicamente a Política de Privacidade, a base legal, o texto de consentimento, o prazo de retenção, a exclusão e os fornecedores que recebem dados. @@ -36,16 +36,35 @@ repositório. - [ ] Executar `pnpm check` no commit de release e guardar o resultado da CI. - [ ] Fazer smoke test no Preview e em Production, incluindo formulário, página de confirmação, política de privacidade, links, sitemap e robots. -- [ ] Revisar todas as claims da landing: mockups e métricas fictícias devem - estar identificados como prévia/conceito ou ser substituídos por conteúdo - genérico; remover promessas não comprovadas. +- [x] Revisar as claims da landing: o status de validação está explícito, + fluxos futuros aparecem como previstos e números fictícios ficam apenas + no bloco demonstrativo rotulado. +- [x] Decidir o mockup de dashboard do hero. `DASHBOARD_DATA` em + `src/lib/constants.ts` mostra faturamento e liquidação inventados + (`R$ 45.870,00`, `R$ 32.650,00`) e `dashboard-preview.tsx` os renderiza + apenas dentro do bloco rotulado visivelmente como **Demonstração do + produto — Dados fictícios**. Os valores continuam `aria-hidden` e não + aparecem como prova social ou resultado da WebIngressos. + +## Decisões de design + +O `AGENTS.md` registra as decisões vigentes; esta seção mantém a conclusão e a +questão que ainda bloqueia copy. + +- [x] Tema escuro promovido para a landing principal com a direção aprovada na + issue #7: fundo violeta, magenta, verde ácido, Anton no marketing e + Archivo em produto/dados. A landing `/` é a implementação canônica e o + protótipo externo não foi incorporado como rota ou dependência. Validação + local concluída nos breakpoints definidos, sem overflow. +- [ ] Definir o modelo de preço (mensalidade ou percentual do bruto). Enquanto + estiver aberto, nenhuma copy pode falar de custo ou previsibilidade. ## P1 — operação do piloto - [ ] Adicionar rate limiting distribuído ao endpoint. -- [ ] Adicionar proteção antispam (honeypot/rate limit) compatível com a - política de privacidade — ainda não implementada no `route.ts`/schema do - formulário. +- [x] Adicionar honeypot compatível com a política de privacidade, sem enviar ou + registrar PII em serviços externos. Rate limiting distribuído permanece + no item anterior. - [x] Restringir o Blob de candidaturas ao modo privado. - [ ] Definir quem recebe, acompanha e responde cada candidatura. - [ ] Criar o pipeline de leads com os estados: novo, qualificado, entrevista, diff --git a/docs/issue-7/implementation-notes.md b/docs/issue-7/implementation-notes.md new file mode 100644 index 0000000..6d8b05c --- /dev/null +++ b/docs/issue-7/implementation-notes.md @@ -0,0 +1,149 @@ +# Issue #7 — registro de implementação e validação + +Este documento registra as decisões verificáveis da migração visual. Ele não +substitui `DESIGN.md`, `AGENTS.md` nem o contexto de produto. + +## Baseline + +- Repositório: `prof-ramos/webingressos-page`. +- Commit de partida: `b4d4f752f4efd7dcac10be0a7d813d3942d58abe` + (`origin/main` em 2026-08-09). +- Instalação: `pnpm install --frozen-lockfile` — concluída. +- Gate: `pnpm check` — Prettier, ESLint, TypeScript e build concluídos. +- Capturas: `screenshots/before-desktop.webp` e + `screenshots/before-mobile.webp`. + +## Inventário anterior à alteração + +Ordem narrativa na `main`: + +1. `Header` e `Hero`, com `DashboardPreview`; +2. `Problems`; +3. `Pillars`; +4. `OperationalFlow`; +5. `TargetAudience`; +6. `PilotBand`; +7. `PilotFormLazy` / `PilotForm` em `#piloto`; +8. `Faq`, `FinalCta` e `Footer`. + +Âncoras e destinos preservados ou corrigidos: + +| Origem | Destino | Decisão | +| ----------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------- | +| CTA principal, header, faixa e fechamento | `#piloto` | Preservado | +| Ação secundária do hero | `#demonstracao` | Corrigido para a demonstração real | +| Navegação | `#problemas`, `#pilares`, `#operacao`, `#programa-piloto`, `#faq` | Preservada e revisada | +| Consentimento e footer | `/privacidade` | Preservado | +| Confirmação após sucesso real | `/obrigado` | Preservado | +| Ação “Entrar” | ausente | Não há ambiente operacional público validado | +| `/dashboard` | ausente | Descartado | + +## Tokens + +| Papel | Baseline | Direção adotada | +| ---------------- | ----------------------------- | --------------------------------------------- | +| Fundo | off-white `#f9fafc` | violeta quase preto `#0a0611` | +| Superfície | branco `#ffffff` | violeta neutro `#140d20` | +| Texto | navy `#1b2740` | marfim `#f7f4ee` | +| Texto secundário | cinza `#5e6677` | lavanda acinzentada `#a394b8` | +| Ação | verde institucional `#0e6340` | magenta `#ff2e7e` | +| Destaque | verde médio `#2f9e68` | verde ácido `#c6ff3d` | +| Sucesso | verde de dados | `#34e5a0` | +| Alerta | não centralizado | `#ffc857` | +| Erro | vermelho rosado | `#ff6b7d` | +| Foco | verde | verde ácido, outline de 3 px e offset de 3 px | + +Os valores ficam centralizados em `src/app/globals.css` e são expostos por +`@theme inline`. Componentes consomem os nomes semânticos. + +## Portado, adaptado e descartado + +| Referência do protótipo | Tratamento na base canônica | +| -------------------------------------- | ----------------------------------------------------------------------------------------- | +| Anton + Archivo | Portado por `next/font`, com `display: "swap"` | +| Violeta, magenta e verde ácido | Portados para tokens semânticos | +| Grid, grão e ritmo de flyer | Adaptados como utilitários CSS discretos | +| Painel do produto no hero | Adaptado no `DashboardPreview` existente | +| Estrutura monolítica da página | Descartada; módulos existentes preservados | +| Formulário local do protótipo | Descartado; formulário e endpoint reais preservados | +| Atraso e sucesso simulados | Descartados | +| `/dashboard` e botão “Entrar” | Descartados | +| Métricas, urgência e eventos do mockup | Descartados como claims; dados ilustrativos ficam rotulados e ocultos da árvore acessível | +| Biblioteca ou dependência nova | Nenhuma adicionada | + +## Matriz de claims + +| Promessa relevante | Classificação | Justificativa | +| --------------------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Infraestrutura para vendas, promoters, check-in e prestação de contas | Reformulada | Aparece sempre como proposta “em validação”, não como produto concluído | +| Pix no WhatsApp e planilhas paralelas como alternativa atual | Mantida | Risco estratégico e problema operacional documentados; a copy não afirma prevalência estatística | +| Comissão ligada a cada responsável | Reformulada | Apresentada como fluxo em validação no piloto | +| Check-in com registro | Reformulada | Apresentado como fluxo em validação, sem prometer offline ou bloqueio automático | +| Fechamento com rastreabilidade | Reformulada | Apresentado como fluxo previsto para o piloto, sem afirmar operação real | +| Atendimento em até 48 horas úteis | Mantida | Compromisso já documentado na base canônica | +| Modo ou sincronização offline | Removida | Não há confirmação de disponibilidade | +| Estorno conciliado, divisão auditável e histórico auditável | Removida como funcionalidade disponível | O protótipo não prova disponibilidade; somente a hipótese de fluxo futuro permanece identificada como tal | +| Lotes ou preços automáticos | Removida | Não há confirmação de disponibilidade | +| Pix ou cartão como meios disponíveis | Removida | Não há confirmação de disponibilidade | +| Ingresso repetido bloqueado | Removida | Não há confirmação de disponibilidade | +| Ranking, metas ou acompanhamento em tempo real | Removida | Não há confirmação de disponibilidade | +| Repasses automáticos | Removida | Não há confirmação de disponibilidade | +| Eventos, receita, vendas e conversão reais | Removida | A demonstração usa dados fictícios, com rótulo permanente e `aria-hidden` | +| Preço, taxa ou previsibilidade de custo | Removida | Modelo comercial continua em aberto | +| Escassez ou urgência artificial | Removida | Sem evidência verificável | + +## Contratos preservados + +- Next.js 16, React 19, TypeScript estrito, Tailwind 4, base-nova, + React Hook Form e Zod, sem atualização de dependências. +- `POST /api/subscribe`, schema, campos, payload, honeypot, limite de 16 KiB, + validação de origem e idempotência. +- Blob privado em `leads/.json`, sem sufixo aleatório e sem + sobrescrita. +- `PilotFormLazy` e sua reserva de espaço. +- Skip link, foco visível, menu por teclado, redução de movimento e mensagens + acessíveis do formulário. +- `/obrigado`, `/privacidade`, 404, sitemap, robots, manifest, Analytics, Speed + Insights, headers de segurança e variáveis de ambiente. + +## Evidências de validação da branch + +Resultados executados em 2026-08-09: + +- [x] `pnpm install --frozen-lockfile` — sem alteração do lockfile. +- [x] `pnpm check` — Prettier, ESLint, TypeScript e build aprovados. +- [x] rotas públicas — `/`, `/obrigado`, `/privacidade`, `robots`, `sitemap` e + `manifest` responderam `200`; rota inexistente respondeu `404`. +- [x] links e âncoras — nenhum hash quebrado e nenhuma referência a + `/dashboard`. +- [x] matriz local do endpoint — JSON malformado `400`, schema inválido `400`, + chave inválida `400`, origem inválida `403`, payload acima de 16 KiB + `413`, honeypot `200` e token ausente `503`. +- [x] formulário — oito erros associados, foco no primeiro campo inválido, + apenas uma tentativa ativa no duplo clique, botão reabilitado após falha + e mesma `Idempotency-Key` reutilizada no retry do mesmo payload. +- [x] teclado — skip link como primeiro foco; menu move foco ao abrir, fecha por + `Escape` e devolve foco ao botão. +- [x] foco — outline verde ácido sólido de 3 px, offset de 3 px. +- [x] 320, 375, 390, 768, 1024 e 1440 px — sem overflow horizontal nem corte + do título, demonstração ou formulário. +- [x] reflow equivalente a zoom de 200% — viewport CSS efetivo de 640 px para + uma janela de 1280 px, sem overflow ou perda de conteúdo. +- [x] movimento reduzido — guard global localizado no CSS compilado e marquee + com fallback `motion-reduce:animate-none`; o navegador de QA não expôs + emulação da preferência para teste visual ativo. +- [x] contraste WCAG AA — texto/fundo 18,27:1; secundário/superfície 6,75:1; + ação/fundo 5,68:1; borda de controle/controle 4,11:1; foco/fundo 16,97:1; + erro/superfície 6,90:1. +- [x] Lighthouse local desktop e mobile — acessibilidade 100, boas práticas + 100, SEO 100 e agentic browsing 100; zero falhas em 53 auditorias. +- [x] trace local desktop — LCP 167 ms e CLS 0,00, sem throttling; sem dados de + campo disponíveis. +- [x] console local — nenhum erro, warning ou issue. +- [x] capturas depois em desktop e mobile. +- [x] comparação de build — `.next/static/chunks` permaneceu em 1.288 KiB; os + arquivos de fonte em `.next/static/media` passaram de 96 para 156 KiB e + `.next/server/app` de 1.264 para 1.304 KiB. O acréscimo vem da dupla + tipográfica Anton + Archivo; não houve aumento no total de chunks JS. +- [ ] Preview Deployment, console e Core Web Vitals +- [ ] persistência real e idempotência no Preview diff --git a/docs/issue-7/screenshots/after-desktop.webp b/docs/issue-7/screenshots/after-desktop.webp new file mode 100644 index 0000000..c8a4d3d Binary files /dev/null and b/docs/issue-7/screenshots/after-desktop.webp differ diff --git a/docs/issue-7/screenshots/after-mobile.webp b/docs/issue-7/screenshots/after-mobile.webp new file mode 100644 index 0000000..f7011a4 Binary files /dev/null and b/docs/issue-7/screenshots/after-mobile.webp differ diff --git a/docs/issue-7/screenshots/before-desktop.webp b/docs/issue-7/screenshots/before-desktop.webp new file mode 100644 index 0000000..0184daf Binary files /dev/null and b/docs/issue-7/screenshots/before-desktop.webp differ diff --git a/docs/issue-7/screenshots/before-mobile.webp b/docs/issue-7/screenshots/before-mobile.webp new file mode 100644 index 0000000..f7d7851 Binary files /dev/null and b/docs/issue-7/screenshots/before-mobile.webp differ diff --git a/src/app/globals.css b/src/app/globals.css index 33a4c9c..aa5b0e9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,62 +1,71 @@ @import "tailwindcss"; @import "tw-animate-css"; -/* Light-only site. Without this, Tailwind v4 resolves `dark:` against - prefers-color-scheme and the base-nova controls (which are full of - `dark:bg-input/30`) render dark fills on this light page. */ +/* Dark mode is an explicit product decision, not a visitor preference. The + root layout owns the `.dark` class so base-nova's variants stay predictable. */ @custom-variant dark (&:where(.dark, .dark *)); :root { - color-scheme: light; - - /* Brand scale — sampled from the reference design. */ - --brand-50: #f1f6f5; - --brand-100: #eaf5ef; - --brand-200: #d3eadf; - --brand-300: #a9d6c0; - --brand-400: #6bbe95; - --brand-500: #2f9e68; - --brand-600: #1b7f52; - --brand-700: #0e6340; - --brand-800: #0a4d32; - --brand-900: #073624; - - /* Neutral scale. */ - --ink-50: #f9fafc; - --ink-100: #f2f4f8; - --ink-200: #e9ecf1; - --ink-300: #d6dbe4; - --ink-400: #8a92a3; - --ink-500: #5e6677; - --ink-700: #39415a; - --ink-800: #1b2740; - --ink-900: #101a2e; - - /* Semantic layer. Bare names on purpose: ui/button.tsx does - color-mix(... var(--secondary), var(--foreground) ...) and ui/sonner.tsx - reads var(--popover)/var(--border)/var(--radius) directly. */ - --background: var(--ink-50); - --foreground: var(--ink-800); - --card: #ffffff; - --card-foreground: var(--ink-800); - --popover: #ffffff; - --popover-foreground: var(--ink-800); - --primary: var(--brand-700); - --primary-foreground: #ffffff; - --secondary: var(--brand-100); - --secondary-foreground: var(--brand-700); - --muted: var(--ink-100); - --muted-foreground: var(--ink-500); - --accent: var(--brand-50); - --accent-foreground: var(--brand-700); - --destructive: #c8394f; - --destructive-foreground: #ffffff; - --border: var(--ink-200); - --input: var(--ink-200); - --ring: var(--brand-600); - /* Single source for focus color: the 3px outline and component rings share it. */ + color-scheme: dark; + + /* Marketing palette. Values live here so components only consume tokens. */ + --brand-50: #fff0f6; + --brand-100: #ffd6e5; + --brand-200: #ffadc9; + --brand-300: #ff7daa; + --brand-400: #ff528f; + --brand-500: #ff2e7e; + --brand-600: #e91f6f; + --brand-700: #c9145d; + --brand-800: #9f104a; + --brand-900: #731139; + + --ink-50: #f7f4ee; + --ink-100: #ded6e8; + --ink-200: #c0b3ce; + --ink-300: #a394b8; + --ink-400: #857693; + --ink-500: #6e5d7d; + --ink-700: #291d3d; + --ink-800: #140d20; + --ink-900: #0a0611; + + --hot: var(--brand-500); + --hot-foreground: var(--ink-900); + --lime: #c6ff3d; + --lime-foreground: var(--ink-900); + --success: #34e5a0; + --warning: #ffc857; + + /* Semantic layer shared by marketing and base-nova primitives. */ + --background: var(--ink-900); + --foreground: var(--ink-50); + --card: var(--ink-800); + --card-foreground: var(--ink-50); + --popover: #181024; + --popover-foreground: var(--ink-50); + --primary: var(--hot); + --primary-foreground: var(--hot-foreground); + --secondary: #1a1229; + --secondary-foreground: var(--ink-50); + --muted: #1a1229; + --muted-foreground: var(--ink-300); + --accent: var(--lime); + --accent-foreground: var(--lime-foreground); + --destructive: #ff6b7d; + --destructive-foreground: var(--ink-900); + --border: #3c2c50; + /* Inputs need >= 3:1 against their surrounding surface (WCAG 1.4.11). */ + --input: #806b90; + --ring: var(--lime); --focus-ring: var(--ring); - --radius: 0.625rem; + + --surface: var(--card); + --surface-raised: #1a1229; + --surface-deep: #070410; + --control: #10091a; + --control-border: var(--input); + --radius: 0.5rem; } @theme inline { @@ -80,6 +89,17 @@ --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); + --color-surface: var(--surface); + --color-surface-raised: var(--surface-raised); + --color-surface-deep: var(--surface-deep); + --color-control: var(--control); + --color-control-border: var(--control-border); + --color-hot: var(--hot); + --color-hot-foreground: var(--hot-foreground); + --color-lime: var(--lime); + --color-lime-foreground: var(--lime-foreground); + --color-success: var(--success); + --color-warning: var(--warning); --color-brand-50: var(--brand-50); --color-brand-100: var(--brand-100); @@ -91,7 +111,6 @@ --color-brand-700: var(--brand-700); --color-brand-800: var(--brand-800); --color-brand-900: var(--brand-900); - --color-ink-50: var(--ink-50); --color-ink-100: var(--ink-100); --color-ink-200: var(--ink-200); @@ -102,26 +121,24 @@ --color-ink-800: var(--ink-800); --color-ink-900: var(--ink-900); - --font-sans: - var(--font-jakarta), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, - "Helvetica Neue", Arial, sans-serif; - --font-heading: var(--font-jakarta), ui-sans-serif, system-ui, sans-serif; + --font-sans: var(--font-archivo), ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --font-heading: var(--font-anton), var(--font-archivo), ui-sans-serif, sans-serif; + --font-display: var(--font-anton), var(--font-archivo), ui-sans-serif, sans-serif; } @theme { - /* lg/xl override the Tailwind defaults so every base-nova control - (input, select, popup) picks up the softer marketing radius. */ - --radius-lg: 0.625rem; - --radius-xl: 0.875rem; - --radius-card: 1.25rem; - --radius-panel: 1.75rem; + --radius-lg: 0.5rem; + --radius-xl: 0.625rem; + --radius-card: 0.75rem; + --radius-panel: 1rem; - --shadow-card: 0 1px 2px 0 rgb(16 24 40 / 0.04), 0 8px 24px -14px rgb(16 24 40 / 0.1); - --shadow-panel: 0 2px 4px 0 rgb(16 24 40 / 0.03), 0 32px 64px -32px rgb(11 39 26 / 0.22); - --shadow-cta: 0 10px 24px -12px rgb(14 99 64 / 0.45); + --shadow-card: 0 14px 32px -28px rgb(0 0 0 / 0.8); + --shadow-panel: 12px 12px 0 rgb(255 46 126 / 0.16); + --shadow-cta: 0 0 30px -6px rgb(255 46 126 / 0.72); --animate-accordion-down: accordion-down 220ms ease-out; --animate-accordion-up: accordion-up 200ms ease-in; + --animate-marquee: marquee 24s linear infinite; } @keyframes accordion-down { @@ -142,6 +159,15 @@ } } +@keyframes marquee { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); + } +} + @layer base { * { border-color: var(--color-border); @@ -159,13 +185,15 @@ } :where(a, button, input, select, textarea, [tabindex]):focus-visible { - outline: 3px solid var(--focus-ring); - outline-offset: 3px; + /* Utilities and base-nova controls may ship `outline-none`; the global + accessibility floor must remain the final authority. */ + outline: 3px solid var(--focus-ring) !important; + outline-offset: 3px !important; } ::selection { - background-color: var(--brand-100); - color: var(--brand-800); + background-color: var(--lime); + color: var(--lime-foreground); } a, @@ -174,9 +202,6 @@ } } -/* Respect prefers-reduced-motion for every transition/animation (accordion - height, header mobile menu slide, card hover-lift) instead of hand-rolling - a reduced variant per component. */ @media (prefers-reduced-motion: reduce) { *, *::before, @@ -188,8 +213,18 @@ } } -/* Decorative dot grid, tinted with `text-*`. Replaces the /grid.svg that never existed. */ -@utility dot-grid { - background-image: radial-gradient(currentColor 1px, transparent 1px); - background-size: 18px 18px; +@utility flyer-grid { + background-image: + linear-gradient(var(--border) 1px, transparent 1px), + linear-gradient(90deg, var(--border) 1px, transparent 1px); + background-size: 44px 44px; +} + +@utility grain { + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitchTiles'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E"); +} + +@utility text-stroke-hot { + color: transparent; + -webkit-text-stroke: 2px var(--hot); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index faf4f7a..33d5cde 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,38 +1,45 @@ import type { Metadata, Viewport } from "next" -import { Plus_Jakarta_Sans } from "next/font/google" +import { Anton, Archivo } from "next/font/google" import { Analytics } from "@vercel/analytics/next" import { SpeedInsights } from "@vercel/speed-insights/next" import "./globals.css" import { Toaster } from "@/components/ui/sonner" import { SITE_CONFIG } from "@/lib/constants" -const jakarta = Plus_Jakarta_Sans({ +const anton = Anton({ subsets: ["latin"], - variable: "--font-jakarta", + weight: "400", + variable: "--font-anton", + display: "swap", +}) + +const archivo = Archivo({ + subsets: ["latin"], + variable: "--font-archivo", display: "swap", }) export const metadata: Metadata = { metadataBase: new URL(SITE_CONFIG.url), - title: "WebIngressos | Gestão de Ingressos para Eventos Universitários", + title: "WebIngressos | Venda ingressos sem perder o controle do evento", description: - "Centralize vendas, promoters, check-in e prestação de contas para atléticas, repúblicas e produtores universitários.", + "Infraestrutura em validação para vendas, promoters, check-in e prestação de contas de eventos universitários.", alternates: { canonical: SITE_CONFIG.url }, openGraph: { type: "website", locale: "pt_BR", url: SITE_CONFIG.url, siteName: SITE_CONFIG.name, - title: "WebIngressos | Gestão de Ingressos para Eventos Universitários", + title: "WebIngressos | Venda ingressos sem perder o controle do evento", description: - "Centralize vendas, promoters, check-in e prestação de contas para atléticas, repúblicas e produtores universitários.", + "Infraestrutura em validação para vendas, promoters, check-in e prestação de contas de eventos universitários.", }, twitter: { card: "summary_large_image" }, } export const viewport: Viewport = { - themeColor: "#0e6340", - colorScheme: "light", + themeColor: "#0a0611", + colorScheme: "dark", } export default function RootLayout({ @@ -41,11 +48,11 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - - + + Pular para o conteúdo diff --git a/src/app/manifest.ts b/src/app/manifest.ts index a76fa95..80e5d08 100644 --- a/src/app/manifest.ts +++ b/src/app/manifest.ts @@ -8,7 +8,7 @@ export default function manifest(): MetadataRoute.Manifest { "Gestão de vendas, promoters, check-in e prestação de contas para eventos universitários.", start_url: "/", display: "standalone", - background_color: "#f9fafc", - theme_color: "#0e6340", + background_color: "#0a0611", + theme_color: "#0a0611", } } diff --git a/src/app/obrigado/page.tsx b/src/app/obrigado/page.tsx index b5d4a5f..0462405 100644 --- a/src/app/obrigado/page.tsx +++ b/src/app/obrigado/page.tsx @@ -7,33 +7,33 @@ export default function ThankYouPage() { return (
-
+
- +
-

- Candidatura recebida! +

+ Candidatura recebida

-

+

Obrigado por se candidatar ao programa piloto da WebIngressos. Nossa equipe entrará em contato pelo e-mail ou WhatsApp informado em até{" "} - 48 horas úteis para dar início ao - seu processo. + 48 horas úteis para dar início + ao seu processo.

Voltar para a página inicial diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx index d418e35..9a520d1 100644 --- a/src/app/opengraph-image.tsx +++ b/src/app/opengraph-image.tsx @@ -8,8 +8,8 @@ export default function OpenGraphImage() { return new ImageResponse(
-
- WEBINGRESSOS +
+ WEBINGRESSOS · PROGRAMA PILOTO
Venda ingressos sem perder o controle do evento.
-
- Gestão para eventos universitários. +
+ Infraestrutura em validação para eventos universitários.
, size, diff --git a/src/app/page.tsx b/src/app/page.tsx index 695892d..1c74296 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,7 +13,7 @@ import { Section } from "@/components/landing/section" export default function Home() { return ( - <> +
@@ -23,7 +23,7 @@ export default function Home() { -
+
@@ -31,6 +31,6 @@ export default function Home() {
- +
) } diff --git a/src/app/privacidade/page.tsx b/src/app/privacidade/page.tsx index f048395..b3e060b 100644 --- a/src/app/privacidade/page.tsx +++ b/src/app/privacidade/page.tsx @@ -3,33 +3,35 @@ import { ArrowLeft } from "lucide-react" export default function PrivacyPolicyPage() { return ( -
+
Voltar para a página inicial
-

+

Política de Privacidade

-

Última atualização: 30 de julho de 2026

+

+ Última atualização: 30 de julho de 2026 +

-

+

A sua privacidade é importante para nós. Esta Política de Privacidade descreve como a WebIngressos coleta, usa e protege as informações fornecidas por você através do nosso formulário de candidatura ao programa piloto.

-

1. Informações coletadas

-

+

1. Informações coletadas

+

Ao preencher nosso formulário de contato, podemos coletar os seguintes dados pessoais:

-
    +
    • Nome completo;
    • Endereço de e-mail;
    • Número de telefone / WhatsApp;
    • @@ -37,39 +39,39 @@ export default function PrivacyPolicyPage() {
    • Público estimado e estado (UF) do evento.
    -

    2. Uso das informações

    -

    +

    2. Uso das informações

    +

    As informações coletadas são utilizadas exclusivamente para os seguintes fins:

    -
      +
      • Entrar em contato com você sobre sua candidatura ao programa piloto;
      • Avaliar se o seu evento se encaixa nos critérios do piloto;
      • Enviar informações relevantes sobre o uso da plataforma WebIngressos.
      -

      3. Compartilhamento de dados

      -

      +

      3. Compartilhamento de dados

      +

      Não compartilhamos suas informações com terceiros, exceto quando exigido por lei, ou quando necessário para a prestação de serviços de infraestrutura de tecnologia que nos auxiliam na operação (por exemplo, hospedagem de dados), sempre sob acordos de confidencialidade rigorosos.

      -

      4. Segurança dos dados

      -

      +

      4. Segurança dos dados

      +

      Empregamos medidas de segurança técnicas e administrativas para proteger suas informações contra acesso não autorizado, alteração, divulgação ou destruição. No entanto, nenhum método de transmissão pela internet ou de armazenamento eletrônico é 100% seguro.

      -

      5. Seus direitos

      -

      +

      5. Seus direitos

      +

      Você tem o direito de acessar, corrigir ou solicitar a exclusão dos seus dados pessoais a qualquer momento, entrando em contato pelo e-mail{" "} contato@webingressos.com.br @@ -79,7 +81,7 @@ export default function PrivacyPolicyPage() {

      Voltar ao site diff --git a/src/components/brand/logo.tsx b/src/components/brand/logo.tsx index c076f16..035a1f7 100644 --- a/src/components/brand/logo.tsx +++ b/src/components/brand/logo.tsx @@ -7,8 +7,8 @@ type Tone = "brand" | "inverse" * split by a perforation. Two tones on the brand version, flat on inverse. */ export function LogoMark({ className, tone = "brand" }: { className?: string; tone?: Tone }) { - const light = tone === "brand" ? "var(--brand-500)" : "rgb(255 255 255 / 0.7)" - const dark = tone === "brand" ? "var(--brand-700)" : "#ffffff" + const light = tone === "brand" ? "var(--lime)" : "var(--ink-100)" + const dark = tone === "brand" ? "var(--hot)" : "var(--ink-50)" return ( diff --git a/src/components/landing/charts.tsx b/src/components/landing/charts.tsx index b9ec9a3..3ea54c1 100644 --- a/src/components/landing/charts.tsx +++ b/src/components/landing/charts.tsx @@ -40,7 +40,7 @@ export function Sparkline({ return (