{usuario?.nome ? iniciais(usuario.nome) : '?'}
diff --git a/frontend/src/index.css b/frontend/src/index.css
index beac79d..958a4b5 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -75,6 +75,15 @@ body {
.delay-225 { animation-delay: 225ms; }
.delay-300 { animation-delay: 300ms; }
+/* Landing page floating elements */
+@keyframes landingFloat {
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
+ 50% { transform: translateY(-20px) rotate(3deg); }
+}
+.landing-float-slow { animation: landingFloat 8s ease-in-out infinite; }
+.landing-float-med { animation: landingFloat 6s ease-in-out infinite 1s; }
+.landing-float-fast { animation: landingFloat 4s ease-in-out infinite 0.5s; }
+
/* ============================================================
MODO CLARO — sobrescreve utilitários Tailwind via seletor
de maior especificidade (fora de @layer, sempre vence)
diff --git a/frontend/src/pages/Apresentacao.tsx b/frontend/src/pages/Apresentacao.tsx
index e0bc67e..7b559f5 100644
--- a/frontend/src/pages/Apresentacao.tsx
+++ b/frontend/src/pages/Apresentacao.tsx
@@ -1,142 +1,533 @@
+import { useState, useEffect } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
-const recursos = [
+const NAV_ITEMS = [
+ { id: 'inicio', label: 'Início' },
+ { id: 'funcionalidades', label: 'Funcionalidades' },
+ { id: 'beneficios', label: 'Benefícios' },
+ { id: 'seguranca', label: 'Segurança' },
+ { id: 'contato', label: 'Contato' },
+];
+
+const etapas = [
+ { numero: '01', titulo: 'Cadastre-se', descricao: 'Crie sua conta como estudante em poucos segundos.' },
+ { numero: '02', titulo: 'Envie', descricao: 'Anexe seus certificados e documentos acadêmicos.' },
+ { numero: '03', titulo: 'Validação', descricao: 'O coordenador analisa e valida cada documento.' },
+ { numero: '04', titulo: 'Compartilhe', descricao: 'Receba um link único com QR Code verificável.' },
+];
+
+const funcionalidades = [
+ {
+ titulo: 'Envio de Certificados',
+ descricao: 'Anexe certificados de cursos, eventos, estágios e atividades complementares em poucos cliques.',
+ icone: 'M9 12h6m-6 4h4m-7 5h10a2 2 0 002-2V7.414a1 1 0 00-.293-.707l-4.414-4.414A1 1 0 0012.586 2H6a2 2 0 00-2 2v14a2 2 0 002 2z',
+ },
+ {
+ titulo: 'Validação por Coordenadores',
+ descricao: 'Coordenadores analisam, aprovam ou solicitam ajustes nos documentos enviados.',
+ icone: 'M9 12.75l1.5 1.5 3-3.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z',
+ },
{
- titulo: 'Envie seus certificados',
- descricao:
- 'Anexe certificados de cursos, eventos, estágios e atividades complementares em poucos cliques, direto pelo computador ou celular.',
- icone: (
-
- ),
+ titulo: 'Verificação Pública',
+ descricao: 'Cada certificado validado recebe um link único com QR Code para comprovar autenticidade.',
+ icone: 'M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244',
},
{
- titulo: 'Validação por coordenadores',
- descricao:
- 'Coordenadores de cada curso analisam, aprovam ou solicitam ajustes nos documentos enviados pelos estudantes.',
- icone: (
-
- ),
+ titulo: 'Multi-Instituição',
+ descricao: 'Suporte a múltiplas instituições com cursos e usuários organizados de forma independente.',
+ icone: 'M3.75 21h16.5M4.5 3h15M5.25 3v18m13.5-18v18M9 6.75h1.5m-1.5 3h1.5m-1.5 3h1.5m3-6H15m-1.5 3H15m-1.5 3H15',
+ },
+ {
+ titulo: 'Dashboard Inteligente',
+ descricao: 'Painel com visão geral de documentos, filtros e estatísticas em tempo real.',
+ icone: 'M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z',
+ },
+ {
+ titulo: 'Geração de Certificados',
+ descricao: 'Gere certificados com QR Code de verificação automática e assinatura do coordenador.',
+ icone: 'M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z',
+ },
+];
+
+const beneficios = [
+ {
+ titulo: 'Para Estudantes',
+ items: [
+ 'Envie certificados de qualquer lugar, a qualquer hora',
+ 'Acompanhe o status de validação em tempo real',
+ 'Compartilhe certificados validados com link verificável',
+ 'Histórico completo de todas as atividades complementares',
+ ],
+ icone: 'M4.26 10.147a60.438 60.438 0 00-.491 6.347A48.62 48.62 0 0112 20.904a48.62 48.62 0 018.232-4.41 60.46 60.46 0 00-.491-6.347m-15.482 0a50.636 50.636 0 00-2.658-.813A59.906 59.906 0 0112 3.493a59.903 59.903 0 0110.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0112 13.489a50.702 50.702 0 017.74-3.342M6.75 15a.75.75 0 100-1.5.75.75 0 000 1.5zm0 0v-3.675A55.378 55.378 0 0112 8.443m-7.007 11.55A5.981 5.981 0 006.75 15.75v-1.5',
+ cor: '#618C7C',
},
{
- titulo: 'Verificação pública',
- descricao:
- 'Cada certificado validado recebe um link único, que pode ser compartilhado para comprovar sua autenticidade a qualquer momento.',
- icone: (
-
- ),
+ titulo: 'Para Coordenadores',
+ items: [
+ 'Fila organizada de documentos para análise',
+ 'Aprovação ou rejeição com feedback detalhado',
+ 'Visão completa dos alunos e seus certificados',
+ 'Assinatura digital automática nos certificados gerados',
+ ],
+ icone: 'M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z',
+ cor: '#5B8DB8',
},
{
- titulo: 'Várias instituições, um só lugar',
- descricao:
- 'Suporte a múltiplas instituições de ensino, com cursos e usuários organizados de forma independente e segura.',
- icone: (
-
- ),
+ titulo: 'Para Instituições',
+ items: [
+ 'Gestão centralizada de cursos e coordenadores',
+ 'Redução de processos burocráticos e uso de papel',
+ 'Rastreabilidade completa de todos os documentos',
+ 'Relatórios e métricas de atividades complementares',
+ ],
+ icone: 'M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0012 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75z',
+ cor: '#8B6BAE',
},
];
+const segurancaItems = [
+ { titulo: 'Criptografia TLS 1.2/1.3', descricao: 'Comunicações protegidas com criptografia de ponta a ponta.', icone: 'M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z' },
+ { titulo: 'Autenticação Segura', descricao: 'JWT tokens, bcrypt e suporte a Microsoft OAuth.', icone: 'M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z' },
+ { titulo: 'Controle de Acesso', descricao: 'Três perfis com permissões granulares.', icone: 'M18 18.72a9.094 9.094 0 003.741-.479 3 3 0 00-4.682-2.72m.94 3.198l.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0112 21c-2.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 016 18.719m12 0a5.971 5.971 0 00-.941-3.197m0 0A5.995 5.995 0 0012 12.75a5.995 5.995 0 00-5.058 2.772m0 0a3 3 0 00-4.681 2.72 8.986 8.986 0 003.74.477m.94-3.197a5.971 5.971 0 00-.94 3.197M15 6.75a3 3 0 11-6 0 3 3 0 016 0zm6 3a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm-13.5 0a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z' },
+ { titulo: 'QR Code Verificável', descricao: 'Verificação instantânea da autenticidade por qualquer pessoa.', icone: 'M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z' },
+ { titulo: 'Headers de Segurança', descricao: 'HSTS, Helmet.js e proteção contra ataques comuns.', icone: 'M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z' },
+ { titulo: 'Armazenamento S3', descricao: 'Documentos na AWS S3 com URLs assinadas temporárias.', icone: 'M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125' },
+];
+
+function GridPattern({ className = '' }: { className?: string }) {
+ return (
+
+ );
+}
+
+function FloatingShapes() {
+ return (
+
+
+
+
+
+
+ {/* Corner accents */}
+
+
+
+ );
+}
+
+function Icon({ d, className = 'h-5 w-5' }: { d: string; className?: string }) {
+ return (
+
+ );
+}
+
+function useActiveSection() {
+ const [active, setActive] = useState('inicio');
+
+ useEffect(() => {
+ if (typeof IntersectionObserver === 'undefined') return;
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ const visible = entries.filter((e) => e.isIntersecting);
+ if (visible.length > 0) {
+ const top = visible.reduce((a, b) =>
+ a.boundingClientRect.top < b.boundingClientRect.top ? a : b
+ );
+ setActive(top.target.id);
+ }
+ },
+ { rootMargin: '-20% 0px -60% 0px', threshold: 0 }
+ );
+
+ NAV_ITEMS.forEach(({ id }) => {
+ const el = document.getElementById(id);
+ if (el) observer.observe(el);
+ });
+
+ return () => observer.disconnect();
+ }, []);
+
+ return active;
+}
+
+function SectionHeading({ badge, title, subtitle }: { badge: string; title: string; subtitle: string }) {
+ return (
+
+
+
+ {badge}
+
+
{title}
+
{subtitle}
+
+ );
+}
+
+function Divider() {
+ return (
+
+ );
+}
+
export function Apresentacao() {
const { isAuthenticated } = useAuth();
+ const activeSection = useActiveSection();
+ const [menuOpen, setMenuOpen] = useState(false);
if (isAuthenticated) return
;
+ const scrollTo = (id: string) => {
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
+ setMenuOpen(false);
+ };
+
return (
-
- {/* Topo */}
-
-
-
+ {/* ── Navbar ── */}
+
-
- Entrar
-
-
+ {/* ── Hero ── */}
+
+
+
- {/* Hero */}
-
+
-
- Captação e validação de documentos universitários
+ {/* Badge animado */}
+
+
+
+
+
+ Plataforma de validação acadêmica
-
- Centralize e valide os certificados acadêmicos da sua instituição
+
+ Centralize e valide os{' '}
+
+ certificados acadêmicos
+
+ {' '}
+ da sua instituição
-
- O ValidaAI conecta estudantes e coordenadores em um único lugar: estudantes enviam
- certificados de cursos, eventos e atividades complementares, e coordenadores
- validam cada documento com segurança e rastreabilidade.
+
+ O ValidaAI conecta estudantes e coordenadores em um único lugar: envie certificados
+ de cursos, eventos e atividades complementares, e tenha cada documento
+ validado com segurança e rastreabilidade.
-
+
- Criar conta de estudante
+ Começar agora
+
-
+
Já tenho uma conta
- {/* Recursos */}
-
- {recursos.map((recurso) => (
+ {/* Stats com ícones */}
+
+ {[
+ { value: '100%', label: 'Digital', icon: 'M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25' },
+ { value: 'QR Code', label: 'Verificável', icon: 'M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z' },
+ { value: '24/7', label: 'Disponível', icon: 'M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z' },
+ ].map((stat) => (
+
+
+
{stat.value}
+
{stat.label}
+
+ ))}
+
+
+
+ {/* Scroll indicator */}
+
+
+
+
+
+ {/* ── Como Funciona ── */}
+
+
+
+
+
+ {/* Linha conectora vertical (mobile) / horizontal (desktop) */}
+
+
+
+ {etapas.map((etapa, i) => (
+
+
+ {etapa.numero}
+
+
+
{etapa.titulo}
+
{etapa.descricao}
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* ── Funcionalidades ── */}
+
+
+
+
+
+
+ {funcionalidades.map((item, i) => (
-
-
+ {/* Número decorativo */}
+
+ {String(i + 1).padStart(2, '0')}
+
+
+
-
{recurso.titulo}
-
{recurso.descricao}
+
{item.titulo}
+
{item.descricao}
))}
-
+
+
+
+
+
+ {/* ── Benefícios ── */}
+
+
+
+
+
+ {beneficios.map((grupo, i) => (
+
+ {/* Top accent bar */}
+
+
+
+
+ {grupo.items.map((item) => (
+ -
+
+ {item}
+
+ ))}
+
+
+ ))}
+
+
+
+
+
+
+ {/* ── Segurança ── */}
+
+ {/* Shield background decorativo */}
+
+
+
+
+
+
+ {segurancaItems.map((item, i) => (
+
+
+
+
+
{item.titulo}
+
{item.descricao}
+
+ ))}
+
+
+
+
+
+
+ {/* ── Contato ── */}
+
+
+
+
+
+
+
+
+ O ValidaAI é um projeto acadêmico desenvolvido como
+ Trabalho de Conclusão de Curso (TCC). Para dúvidas, sugestões ou interesse em
+ colaborar, entre em contato pelo GitHub:
+
+
+
+
+ github.com/icrcode
+
+
+
+
+
+ {/* ── Footer ── */}
+
+
);
}
diff --git a/frontend/src/test/App.test.tsx b/frontend/src/test/App.test.tsx
index 85d8e17..fb1195d 100644
--- a/frontend/src/test/App.test.tsx
+++ b/frontend/src/test/App.test.tsx
@@ -40,7 +40,7 @@ describe('App', () => {
it('exibe convite para criar conta na tela de apresentação quando não autenticado', () => {
render(
);
- const link = screen.queryByRole('link', { name: /criar conta de estudante/i });
+ const link = screen.queryByRole('link', { name: /começar agora/i });
expect(link).toBeInTheDocument();
});
});
diff --git a/frontend/src/test/pages/Apresentacao.test.tsx b/frontend/src/test/pages/Apresentacao.test.tsx
new file mode 100644
index 0000000..be7c6ce
--- /dev/null
+++ b/frontend/src/test/pages/Apresentacao.test.tsx
@@ -0,0 +1,224 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { screen, fireEvent } from '@testing-library/react';
+import { renderWithProviders } from '../helpers/renderWithProviders';
+import { Apresentacao } from '../../pages/Apresentacao';
+
+vi.mock('../../services/api', () => ({
+ default: {
+ get: vi.fn().mockResolvedValue({ data: [] }),
+ post: vi.fn().mockResolvedValue({ data: {} }),
+ interceptors: {
+ request: { use: vi.fn() },
+ response: { use: vi.fn() },
+ },
+ },
+}));
+
+beforeEach(() => {
+ localStorage.clear();
+});
+
+function renderPage() {
+ return renderWithProviders(
);
+}
+
+describe('Apresentacao', () => {
+ it('renderiza sem erros', () => {
+ const { container } = renderPage();
+ expect(container).toBeTruthy();
+ });
+
+ it('exibe o logo ValidaAI', () => {
+ renderPage();
+ const logos = screen.getAllByText(/ValidaAI/i);
+ expect(logos.length).toBeGreaterThan(0);
+ });
+
+ it('exibe o título principal do hero', () => {
+ renderPage();
+ expect(screen.getByText(/centralize e valide os/i)).toBeInTheDocument();
+ expect(screen.getByText(/certificados acadêmicos/i)).toBeInTheDocument();
+ });
+
+ it('exibe o badge de plataforma', () => {
+ renderPage();
+ expect(screen.getByText(/plataforma de validação acadêmica/i)).toBeInTheDocument();
+ });
+
+ it('exibe o CTA "Começar agora"', () => {
+ renderPage();
+ const links = screen.getAllByRole('link', { name: /começar agora/i });
+ expect(links.length).toBeGreaterThan(0);
+ });
+
+ it('exibe o link "Já tenho uma conta"', () => {
+ renderPage();
+ const links = screen.getAllByRole('link', { name: /já tenho uma conta/i });
+ expect(links.length).toBeGreaterThan(0);
+ });
+
+ it('exibe os stats do hero', () => {
+ renderPage();
+ expect(screen.getByText('100%')).toBeInTheDocument();
+ expect(screen.getByText('QR Code')).toBeInTheDocument();
+ expect(screen.getByText('24/7')).toBeInTheDocument();
+ });
+
+ it('exibe os labels dos stats', () => {
+ renderPage();
+ expect(screen.getByText('Digital')).toBeInTheDocument();
+ expect(screen.getByText('Verificável')).toBeInTheDocument();
+ expect(screen.getByText('Disponível')).toBeInTheDocument();
+ });
+
+ // Navbar
+ it('exibe os itens de navegação no desktop', () => {
+ renderPage();
+ expect(screen.getAllByText('Início').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('Funcionalidades').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('Benefícios').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('Segurança').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('Contato').length).toBeGreaterThan(0);
+ });
+
+ it('exibe os botões Entrar e Criar conta na navbar', () => {
+ renderPage();
+ expect(screen.getAllByText('Entrar')[0]).toBeInTheDocument();
+ expect(screen.getAllByText('Criar conta')[0]).toBeInTheDocument();
+ });
+
+ it('abre o menu mobile ao clicar no hamburger', () => {
+ renderPage();
+ const hamburgers = screen.getAllByRole('button');
+ const hamburger = hamburgers.find((b) => b.querySelector('path[d*="3.75 6.75"]'));
+ expect(hamburger).toBeTruthy();
+ fireEvent.click(hamburger!);
+ // Após abrir, o botão muda para o X (close icon)
+ const closeBtn = screen.getAllByRole('button').find((b) => b.querySelector('path[d*="M6 18"]'));
+ expect(closeBtn).toBeTruthy();
+ });
+
+ // Seção "Como funciona"
+ it('exibe as 4 etapas do fluxo', () => {
+ renderPage();
+ expect(screen.getByText('Cadastre-se')).toBeInTheDocument();
+ expect(screen.getByText('Envie')).toBeInTheDocument();
+ expect(screen.getByText('Validação')).toBeInTheDocument();
+ expect(screen.getByText('Compartilhe')).toBeInTheDocument();
+ });
+
+ it('exibe os números das etapas', () => {
+ renderPage();
+ expect(screen.getAllByText('01').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('02').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('03').length).toBeGreaterThan(0);
+ expect(screen.getAllByText('04').length).toBeGreaterThan(0);
+ });
+
+ // Seção Funcionalidades
+ it('exibe os cards de funcionalidades', () => {
+ renderPage();
+ expect(screen.getByText('Envio de Certificados')).toBeInTheDocument();
+ expect(screen.getByText('Validação por Coordenadores')).toBeInTheDocument();
+ expect(screen.getByText('Verificação Pública')).toBeInTheDocument();
+ expect(screen.getByText('Multi-Instituição')).toBeInTheDocument();
+ expect(screen.getByText('Dashboard Inteligente')).toBeInTheDocument();
+ expect(screen.getByText('Geração de Certificados')).toBeInTheDocument();
+ });
+
+ // Seção Benefícios
+ it('exibe os três perfis de benefícios', () => {
+ renderPage();
+ expect(screen.getByText('Para Estudantes')).toBeInTheDocument();
+ expect(screen.getByText('Para Coordenadores')).toBeInTheDocument();
+ expect(screen.getByText('Para Instituições')).toBeInTheDocument();
+ });
+
+ it('exibe os itens de benefícios dos estudantes', () => {
+ renderPage();
+ expect(screen.getByText(/envie certificados de qualquer lugar/i)).toBeInTheDocument();
+ expect(screen.getByText(/acompanhe o status/i)).toBeInTheDocument();
+ });
+
+ // Seção Segurança
+ it('exibe os cards de segurança', () => {
+ renderPage();
+ expect(screen.getByText('Criptografia TLS 1.2/1.3')).toBeInTheDocument();
+ expect(screen.getByText('Autenticação Segura')).toBeInTheDocument();
+ expect(screen.getByText('Controle de Acesso')).toBeInTheDocument();
+ expect(screen.getByText('QR Code Verificável')).toBeInTheDocument();
+ expect(screen.getByText('Headers de Segurança')).toBeInTheDocument();
+ expect(screen.getByText('Armazenamento S3')).toBeInTheDocument();
+ });
+
+ // Seção Contato
+ it('exibe a seção de contato com menção ao TCC', () => {
+ renderPage();
+ const tccTexts = screen.getAllByText(/trabalho de conclusão de curso/i);
+ expect(tccTexts.length).toBeGreaterThan(0);
+ });
+
+ it('exibe o link para o GitHub', () => {
+ renderPage();
+ const ghLink = screen.getByRole('link', { name: /github\.com\/icrcode/i });
+ expect(ghLink).toBeInTheDocument();
+ expect(ghLink).toHaveAttribute('href', 'https://github.com/icrcode');
+ expect(ghLink).toHaveAttribute('target', '_blank');
+ });
+
+ // Footer
+ it('exibe o footer com copyright', () => {
+ renderPage();
+ const year = new Date().getFullYear().toString();
+ expect(screen.getByText(new RegExp(year))).toBeInTheDocument();
+ });
+
+ it('exibe links de navegação no footer', () => {
+ renderPage();
+ // Footer duplica os nav items
+ const inicioButtons = screen.getAllByText('Início');
+ expect(inicioButtons.length).toBeGreaterThanOrEqual(2);
+ });
+
+ // Seções heading
+ it('exibe os headings das seções', () => {
+ renderPage();
+ expect(screen.getByText('Tudo que você precisa em um só lugar')).toBeInTheDocument();
+ expect(screen.getByText('Vantagens para cada perfil')).toBeInTheDocument();
+ expect(screen.getByText('Seus dados estão protegidos')).toBeInTheDocument();
+ expect(screen.getAllByText('Entre em contato').length).toBeGreaterThan(0);
+ });
+
+ // Redirect
+ it('redireciona para /dashboard quando autenticado', () => {
+ renderWithProviders(
, {
+ token: 'tok-test',
+ usuario: {
+ id: 'u-1', nome: 'Teste', email: 'teste@test.com', perfil: 'estudante',
+ matricula: '2021001', cpf: null, endereco: null, curso_id: 'c-1',
+ instituicao_id: 'i-1', instituicao_nome: 'Universidade Teste', ativo: true,
+ },
+ });
+ expect(screen.queryByText(/plataforma de validação acadêmica/i)).not.toBeInTheDocument();
+ });
+
+ // Elementos gráficos
+ it('renderiza o grid pattern SVG', () => {
+ const { container } = renderPage();
+ const patterns = container.querySelectorAll('pattern#grid');
+ expect(patterns.length).toBeGreaterThan(0);
+ });
+
+ it('renderiza as formas flutuantes', () => {
+ const { container } = renderPage();
+ const floats = container.querySelectorAll('.landing-float-slow, .landing-float-med, .landing-float-fast');
+ expect(floats.length).toBeGreaterThan(0);
+ });
+
+ it('renderiza os dividers entre seções', () => {
+ const { container } = renderPage();
+ // Dividers have the gradient line + dot pattern
+ const dots = container.querySelectorAll('.rounded-full.bg-\\[\\#618C7C\\]\\/25');
+ expect(dots.length).toBeGreaterThan(0);
+ });
+});
diff --git a/infra/nginx/default.conf.template b/infra/nginx/default.conf.template
index 43bbda5..8e568c8 100644
--- a/infra/nginx/default.conf.template
+++ b/infra/nginx/default.conf.template
@@ -1,7 +1,7 @@
# ── HTTP: challenge do Certbot + redirect para HTTPS ──────────────
server {
listen 80;
- server_name ${DOMAIN} www.${DOMAIN};
+ server_name ${DOMAIN} www.${DOMAIN} ${DOMAIN_ALT} www.${DOMAIN_ALT};
location /.well-known/acme-challenge/ {
root /var/www/certbot;
@@ -15,7 +15,7 @@ server {
# ── HTTPS ──────────────────────────────────────────────────────────
server {
listen 443 ssl;
- server_name ${DOMAIN} www.${DOMAIN};
+ server_name ${DOMAIN} www.${DOMAIN} ${DOMAIN_ALT} www.${DOMAIN_ALT};
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;