CARONTE v1.0 - Plataforma de Gestão Social

This commit is contained in:
2026-02-08 23:10:32 -03:00
commit c98c806865
60 changed files with 9450 additions and 0 deletions

6
frontend/.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "next/core-web-vitals",
"rules": {
"react/no-unescaped-entities": "off"
}
}

36
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

36
frontend/README.md Normal file
View File

@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

7
frontend/jsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}

4
frontend/next.config.mjs Normal file
View File

@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;

5910
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
frontend/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"lucide-react": "^0.563.0",
"next": "14.2.35",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"eslint": "^8",
"eslint-config-next": "14.2.35",
"postcss": "^8",
"tailwindcss": "^3.4.1"
}
}

View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

View File

@@ -0,0 +1,108 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import Sidebar from '@/components/Sidebar'
import { ArrowRight, Clock, AlertTriangle } from 'lucide-react'
export default function Dashboard() {
const router = useRouter()
const [loading, setLoading] = useState(true)
useEffect(() => {
if (!localStorage.getItem('token')) { router.push('/login'); return }
setLoading(false)
}, [router])
if (loading) return <div className="flex items-center justify-center h-screen bg-[var(--void)]"><span className="text-5xl boat-float">🚣</span></div>
const cards = [
{ label: 'Famílias', value: 3, emoji: '⚱️' },
{ label: 'Pendências', value: 12, emoji: '📜' },
{ label: 'Benefícios', value: 8, emoji: '🔮' },
{ label: 'Pergaminhos', value: 5, emoji: '📄' },
]
const familias = [
{ id: 1, nome: 'Família Silva', falecido: 'José Carlos Silva', progresso: 35, pendentes: 8, data: '15/01/2026' },
{ id: 2, nome: 'Família Oliveira', falecido: 'Maria Oliveira', progresso: 60, pendentes: 4, data: '20/01/2026' },
{ id: 3, nome: 'Família Santos', falecido: 'Antônio Santos', progresso: 15, pendentes: 12, data: '01/02/2026' },
]
return (
<div className="flex min-h-screen bg-[var(--void)]">
<Sidebar />
<main className="ml-60 flex-1 p-8">
{/* Header */}
<div className="mb-10 fade-in">
<p className="text-[10px] tracking-[0.4em] text-[var(--gold-dim)] uppercase font-myth mb-2">🏛 Ágora</p>
<h1 className="font-myth text-2xl font-bold text-[var(--marble)] tracking-wider">Visão do Submundo</h1>
<div className="ornament-line-simple max-w-[200px] mt-3" />
</div>
{/* Stats */}
<div className="grid grid-cols-4 gap-4 mb-8">
{cards.map((c, i) => (
<div key={i} className="card-hades p-5 text-center group hover:scale-[1.02] transition-all duration-300">
<span className="text-2xl block mb-2">{c.emoji}</span>
<p className="font-myth-decorative text-2xl gold-text">{c.value}</p>
<p className="text-[var(--smoke)] text-[10px] tracking-[0.15em] font-myth uppercase mt-1">{c.label}</p>
</div>
))}
</div>
{/* Treasure */}
<div className="card-hades p-6 mb-8 relative overflow-hidden group">
<div className="absolute inset-0 bg-gradient-to-r from-[var(--ember)]/[0.03] to-transparent" />
<div className="flex items-center gap-5 relative z-10">
<span className="text-4xl group-hover:scale-110 transition-transform">🔱</span>
<div>
<p className="text-[10px] tracking-[0.3em] text-[var(--gold-dim)] uppercase font-myth">Tesouro a Recuperar</p>
<p className="text-3xl font-myth-decorative gold-text mt-1">R$ 45.000</p>
<p className="text-[var(--smoke)] text-xs mt-1">em benefícios identificados para suas famílias</p>
</div>
</div>
</div>
{/* Families header */}
<div className="flex justify-between items-center mb-4">
<div>
<p className="text-[10px] tracking-[0.4em] text-[var(--gold-dim)] uppercase font-myth mb-1"> Núcleos</p>
<h2 className="font-myth text-lg text-[var(--marble)] tracking-wider">Famílias</h2>
</div>
<button className="btn-outline px-4 py-2 text-[10px]">+ NOVA FAMÍLIA</button>
</div>
{/* Families list */}
<div className="space-y-3">
{familias.map((f) => (
<div key={f.id} onClick={() => router.push(`/familia/${f.id}`)}
className="card-hades p-5 cursor-pointer group hover:scale-[1.005] transition-all duration-300">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-3">
<span className="text-lg"></span>
<h3 className="font-myth text-sm font-semibold text-[var(--marble)] tracking-wider">{f.nome}</h3>
{f.pendentes > 5 && (
<span className="flex items-center gap-1 text-[9px] text-[var(--ember)] bg-[var(--ember)]/10 px-2 py-0.5 font-myth tracking-wider">
<AlertTriangle size={9} /> {f.pendentes}
</span>
)}
</div>
<p className="text-[var(--ash)] text-xs ml-8 mt-1">Falecido(a): {f.falecido}</p>
<div className="flex items-center gap-4 mt-3 ml-8">
<div className="flex-1 max-w-[200px] progress-styx h-1.5">
<div className="progress-styx-fill h-full transition-all" style={{ width: `${f.progresso}%` }} />
</div>
<span className="text-xs gold-text font-myth font-bold">{f.progresso}%</span>
<span className="text-[10px] text-[var(--smoke)] flex items-center gap-1"><Clock size={9} /> {f.data}</span>
</div>
</div>
<ArrowRight className="text-[var(--smoke)] group-hover:text-[var(--gold)] transition" size={16} />
</div>
</div>
))}
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,104 @@
'use client'
import { useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Sidebar from '@/components/Sidebar'
import { Search, DollarSign, Clock, CheckCircle2, AlertTriangle, Loader2 } from 'lucide-react'
const beneficiosData = [
{ id: 1, tipo: 'FGTS', instituicao: 'Caixa Econômica Federal', valor_estimado: 18500, valor_sacado: 0, status: 'identificado', prazo: 'Sem prazo', icon: '🏦', desc: 'Saldo de FGTS do último vínculo empregatício' },
{ id: 2, tipo: 'PIS/PASEP', instituicao: 'Caixa Econômica Federal', valor_estimado: 3200, valor_sacado: 0, status: 'identificado', prazo: '5 anos', icon: '💳', desc: 'Abono salarial e cotas do PIS acumuladas' },
{ id: 3, tipo: 'Pensão por Morte', instituicao: 'INSS', valor_estimado: 2800, valor_sacado: 0, status: 'em_processo', prazo: '90 dias (retroativa)', icon: '🏛️', desc: 'Benefício mensal para dependentes — R$2.800/mês' },
{ id: 4, tipo: 'Seguro de Vida', instituicao: 'Bradesco Seguros', valor_estimado: 15000, valor_sacado: 0, status: 'identificado', prazo: '3 anos', icon: '🛡️', desc: 'Apólice de seguro de vida em grupo (empregador)' },
{ id: 5, tipo: 'Restituição IR', instituicao: 'Receita Federal', valor_estimado: 4300, valor_sacado: 4300, status: 'sacado', prazo: '5 anos', icon: '📄', desc: 'Restituição de imposto de renda pendente' },
{ id: 6, tipo: 'DPVAT', instituicao: 'Seguradora Líder', valor_estimado: 0, valor_sacado: 0, status: 'nao_aplicavel', prazo: '3 anos', icon: '🚗', desc: 'Não aplicável — causa da morte não foi acidente de trânsito' },
]
const statusConfig = {
identificado: { label: 'Identificado', color: 'text-blue-400', bg: 'bg-blue-500/10 border-blue-500/30' },
em_processo: { label: 'Em Processo', color: 'text-yellow-400', bg: 'bg-yellow-500/10 border-yellow-500/30' },
sacado: { label: 'Sacado ✓', color: 'text-green-400', bg: 'bg-green-500/10 border-green-500/30' },
nao_aplicavel: { label: 'N/A', color: 'text-gray-500', bg: 'bg-white/5 border-white/10' },
}
export default function BeneficiosPage() {
const router = useRouter()
const params = useParams()
const [beneficios, setBeneficios] = useState(beneficiosData)
const [scanning, setScanning] = useState(false)
const totalEstimado = beneficios.filter(b => b.status !== 'nao_aplicavel').reduce((a, b) => a + b.valor_estimado, 0)
const totalSacado = beneficios.reduce((a, b) => a + b.valor_sacado, 0)
const aProcurar = totalEstimado - totalSacado
const scan = () => {
setScanning(true)
setTimeout(() => {
setScanning(false)
alert('✅ Scan completo! Nenhum novo benefício encontrado.')
}, 3000)
}
return (
<div className="flex min-h-screen bg-[#0a0a0f]">
<Sidebar />
<main className="ml-64 flex-1 p-8">
<button onClick={() => router.push(`/familia/${params.id}`)} className="text-gray-400 hover:text-white text-sm mb-6"> Voltar à Família</button>
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold text-white">Scanner de Benefícios</h1>
<p className="text-gray-400 mt-1">Benefícios identificados para os herdeiros</p>
</div>
<button onClick={scan} disabled={scanning}
className="flex items-center gap-2 px-6 py-3 bg-gradient-to-r from-purple-600 to-purple-500 text-white rounded-xl hover:from-purple-500 hover:to-purple-400 transition disabled:opacity-50 font-medium">
{scanning ? <><Loader2 size={18} className="animate-spin" /> Escaneando...</> : <><Search size={18} /> Escanear Benefícios</>}
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div className="glass bg-gradient-to-br from-blue-500/10 to-blue-600/5 border-blue-500/20 p-6">
<p className="text-gray-400 text-sm">Total Estimado</p>
<p className="text-2xl font-bold text-blue-400 mt-1">R$ {totalEstimado.toLocaleString('pt-BR')}</p>
</div>
<div className="glass bg-gradient-to-br from-green-500/10 to-green-600/5 border-green-500/20 p-6">
<p className="text-gray-400 text-sm"> Sacado</p>
<p className="text-2xl font-bold text-green-400 mt-1">R$ {totalSacado.toLocaleString('pt-BR')}</p>
</div>
<div className="glass bg-gradient-to-br from-yellow-500/10 to-yellow-600/5 border-yellow-500/20 p-6">
<p className="text-gray-400 text-sm">A Recuperar</p>
<p className="text-2xl font-bold text-yellow-400 mt-1">R$ {aProcurar.toLocaleString('pt-BR')}</p>
</div>
</div>
<div className="space-y-4">
{beneficios.map((b) => {
const cfg = statusConfig[b.status]
return (
<div key={b.id} className={`glass p-6 ${b.status === 'nao_aplicavel' ? 'opacity-40' : ''}`}>
<div className="flex items-start justify-between">
<div className="flex items-start gap-4">
<span className="text-3xl">{b.icon}</span>
<div>
<h3 className="text-lg font-semibold text-white">{b.tipo}</h3>
<p className="text-gray-400 text-sm">{b.instituicao}</p>
<p className="text-gray-500 text-xs mt-1">{b.desc}</p>
<div className="flex items-center gap-4 mt-3">
{b.valor_estimado > 0 && (
<span className="flex items-center gap-1 text-sm text-yellow-400">
<DollarSign size={14} /> R$ {b.valor_estimado.toLocaleString('pt-BR')}
</span>
)}
<span className="flex items-center gap-1 text-xs text-gray-500"><Clock size={12} /> Prazo: {b.prazo}</span>
</div>
</div>
</div>
<span className={`text-xs px-3 py-1 rounded-full border ${cfg.bg} ${cfg.color}`}>{cfg.label}</span>
</div>
</div>
)
})}
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,115 @@
'use client'
import { useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Sidebar from '@/components/Sidebar'
import { CheckCircle2, Circle, Clock, AlertTriangle, Lock, Star } from 'lucide-react'
const checklistData = [
{ fase: '⚡ Imediato (24-48h)', items: [
{ id: 1, titulo: 'Obter Certidão de Óbito', desc: 'Registrar no cartório mais próximo', status: 'concluido', prazo: null },
{ id: 2, titulo: 'Comunicar ao empregador', desc: 'Solicitar rescisão e verbas trabalhistas', status: 'concluido', prazo: null },
{ id: 3, titulo: 'Comunicar ao banco', desc: 'Bloquear contas e informar o falecimento', status: 'em_andamento', prazo: '2026-02-15' },
{ id: 4, titulo: 'Cancelar assinaturas recorrentes', desc: 'Streaming, seguros, planos de saúde', status: 'pendente', prazo: null },
]},
{ fase: '📋 1ª Semana', items: [
{ id: 5, titulo: 'Requerer Pensão por Morte (INSS)', desc: 'Prazo de 90 dias para retroatividade', status: 'em_andamento', prazo: '2026-04-10', urgente: true },
{ id: 6, titulo: 'Sacar FGTS do falecido', desc: 'Levar certidão de óbito à Caixa', status: 'pendente', prazo: null },
{ id: 7, titulo: 'Sacar PIS/PASEP', desc: 'Caixa (PIS) ou Banco do Brasil (PASEP)', status: 'pendente', prazo: null },
{ id: 8, titulo: 'Consultar seguros de vida', desc: 'Verificar apólices ativas na SUSEP', status: 'pendente', prazo: null },
]},
{ fase: '📅 30 Dias', items: [
{ id: 9, titulo: 'Transferência de veículos', desc: 'DETRAN - transferir para herdeiros', status: 'bloqueado', prazo: null, depende: 'Inventário' },
{ id: 10, titulo: 'Comunicar Receita Federal', desc: 'Declaração final de espólio', status: 'pendente', prazo: '2026-04-30' },
{ id: 11, titulo: 'Consultar restituição IR', desc: 'Verificar se há restituição pendente', status: 'pendente', prazo: null },
]},
{ fase: '⚖️ 60 Dias (Inventário)', items: [
{ id: 12, titulo: 'Iniciar inventário', desc: 'Judicial ou extrajudicial (cartório)', status: 'pendente', prazo: '2026-03-10', urgente: true },
{ id: 13, titulo: 'Avaliação de bens', desc: 'Imóveis, veículos, investimentos', status: 'bloqueado', prazo: null, depende: 'Inventário iniciado' },
{ id: 14, titulo: 'Cálculo ITCMD', desc: 'Imposto sobre herança (varia por UF)', status: 'bloqueado', prazo: null, depende: 'Avaliação' },
{ id: 15, titulo: 'Partilha de bens', desc: 'Divisão entre herdeiros', status: 'bloqueado', prazo: null, depende: 'ITCMD pago' },
]}
]
const statusConfig = {
concluido: { icon: <CheckCircle2 size={20} />, color: 'text-green-400', bg: 'bg-green-500/10', label: 'Concluído' },
em_andamento: { icon: <Clock size={20} />, color: 'text-yellow-400', bg: 'bg-yellow-500/10', label: 'Em andamento' },
pendente: { icon: <Circle size={20} />, color: 'text-gray-400', bg: 'bg-white/5', label: 'Pendente' },
bloqueado: { icon: <Lock size={20} />, color: 'text-red-400', bg: 'bg-red-500/10', label: 'Bloqueado' },
}
export default function ChecklistPage() {
const router = useRouter()
const params = useParams()
const [checklist, setChecklist] = useState(checklistData)
const toggleItem = (faseIdx, itemIdx) => {
const updated = [...checklist]
const item = updated[faseIdx].items[itemIdx]
if (item.status === 'bloqueado') return
const cycle = { pendente: 'em_andamento', em_andamento: 'concluido', concluido: 'pendente' }
item.status = cycle[item.status] || 'pendente'
setChecklist(updated)
}
const total = checklist.reduce((a, f) => a + f.items.length, 0)
const done = checklist.reduce((a, f) => a + f.items.filter(i => i.status === 'concluido').length, 0)
const pct = Math.round((done / total) * 100)
const nextStep = checklist.flatMap(f => f.items).find(i => i.status === 'pendente' || i.status === 'em_andamento')
return (
<div className="flex min-h-screen bg-[#0a0a0f]">
<Sidebar />
<main className="ml-64 flex-1 p-8">
<button onClick={() => router.push(`/familia/${params.id}`)} className="text-gray-400 hover:text-white text-sm mb-6"> Voltar à Família</button>
<h1 className="text-3xl font-bold text-white mb-2">Checklist Burocrático</h1>
<p className="text-gray-400 mb-6">{done} de {total} itens concluídos ({pct}%)</p>
<div className="w-full bg-white/5 rounded-full h-3 mb-8">
<div className="bg-gradient-to-r from-purple-600 to-green-400 h-3 rounded-full transition-all" style={{ width: `${pct}%` }} />
</div>
{nextStep && (
<div className="glass bg-gradient-to-r from-purple-500/10 to-blue-500/10 border-purple-500/20 p-6 mb-8">
<div className="flex items-center gap-3 mb-2">
<Star className="text-yellow-400" size={20} />
<span className="text-sm text-yellow-400 font-medium">PRÓXIMO PASSO SUGERIDO</span>
</div>
<p className="text-white text-lg font-semibold">{nextStep.titulo}</p>
<p className="text-gray-400 text-sm mt-1">{nextStep.desc}</p>
</div>
)}
<div className="space-y-8">
{checklist.map((fase, fi) => (
<div key={fi}>
<h2 className="text-lg font-bold text-white mb-4">{fase.fase}</h2>
<div className="space-y-3">
{fase.items.map((item, ii) => {
const cfg = statusConfig[item.status]
return (
<div key={item.id} onClick={() => toggleItem(fi, ii)}
className={`glass p-4 flex items-center gap-4 cursor-pointer hover:bg-white/10 transition ${item.status === 'bloqueado' ? 'opacity-50 cursor-not-allowed' : ''}`}>
<div className={cfg.color}>{cfg.icon}</div>
<div className="flex-1">
<p className={`font-medium ${item.status === 'concluido' ? 'text-gray-500 line-through' : 'text-white'}`}>{item.titulo}</p>
<p className="text-gray-500 text-sm">{item.desc}</p>
{item.depende && <p className="text-red-400/60 text-xs mt-1">🔒 Depende de: {item.depende}</p>}
</div>
<div className="flex items-center gap-3">
{item.urgente && <span className="flex items-center gap-1 text-xs text-yellow-400 bg-yellow-500/10 px-2 py-1 rounded-full"><AlertTriangle size={12} /> Urgente</span>}
{item.prazo && <span className="text-xs text-gray-500 flex items-center gap-1"><Clock size={12} /> {new Date(item.prazo).toLocaleDateString('pt-BR')}</span>}
<span className={`text-xs px-2 py-1 rounded-full ${cfg.bg} ${cfg.color}`}>{cfg.label}</span>
</div>
</div>
)
})}
</div>
</div>
))}
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,110 @@
'use client'
import { useState } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Sidebar from '@/components/Sidebar'
import { FileText, Download, Upload, Plus, Clock, CheckCircle2, File } from 'lucide-react'
const docsData = [
{ id: 1, nome: 'Procuração para Inventariante', tipo: 'gerado', categoria: 'Procuração', data: '2026-02-01', tamanho: '45 KB' },
{ id: 2, nome: 'Requerimento Saque FGTS', tipo: 'gerado', categoria: 'Requerimento', data: '2026-02-03', tamanho: '38 KB' },
{ id: 3, nome: 'Requerimento Pensão por Morte', tipo: 'gerado', categoria: 'Requerimento', data: '2026-02-05', tamanho: '52 KB' },
{ id: 4, nome: 'Certidão de Óbito', tipo: 'upload', categoria: 'Certidão', data: '2026-01-12', tamanho: '1.2 MB' },
{ id: 5, nome: 'RG do Falecido', tipo: 'upload', categoria: 'Documento Pessoal', data: '2026-01-12', tamanho: '890 KB' },
]
const templatesDocs = [
{ id: 'procuracao', nome: 'Procuração para Herdeiro', desc: 'Procuração para representar a família no inventário' },
{ id: 'fgts', nome: 'Requerimento Saque FGTS', desc: 'Formulário para saque do FGTS por falecimento' },
{ id: 'pensao', nome: 'Requerimento Pensão por Morte', desc: 'Pedido de pensão por morte ao INSS' },
{ id: 'alvara', nome: 'Petição de Alvará Judicial', desc: 'Para liberação de valores sem inventário (Lei 6.858/80)' },
{ id: 'comunicacao', nome: 'Comunicação de Óbito ao Banco', desc: 'Carta formal para instituição financeira' },
]
export default function DocumentosPage() {
const router = useRouter()
const params = useParams()
const [docs, setDocs] = useState(docsData)
const [showGerador, setShowGerador] = useState(false)
return (
<div className="flex min-h-screen bg-[#0a0a0f]">
<Sidebar />
<main className="ml-64 flex-1 p-8">
<button onClick={() => router.push(`/familia/${params.id}`)} className="text-gray-400 hover:text-white text-sm mb-6"> Voltar à Família</button>
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold text-white">Documentos</h1>
<p className="text-gray-400 mt-1">{docs.length} documentos</p>
</div>
<div className="flex gap-3">
<button className="flex items-center gap-2 px-4 py-2 bg-white/5 text-gray-300 rounded-xl hover:bg-white/10 transition border border-white/10">
<Upload size={16} /> Upload
</button>
<button onClick={() => setShowGerador(!showGerador)} className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-purple-600 to-purple-500 text-white rounded-xl hover:from-purple-500 hover:to-purple-400 transition font-medium">
<Plus size={16} /> Gerar Documento
</button>
</div>
</div>
{showGerador && (
<div className="glass bg-gradient-to-r from-purple-500/5 to-blue-500/5 border-purple-500/20 p-6 mb-8">
<h2 className="text-lg font-semibold text-white mb-4">📄 Gerador de Documentos</h2>
<p className="text-gray-400 text-sm mb-4">Selecione um modelo para gerar automaticamente:</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{templatesDocs.map((t) => (
<div key={t.id} onClick={() => { alert(`✅ Documento "${t.nome}" gerado com sucesso!`); setShowGerador(false) }}
className="p-4 bg-white/5 rounded-xl cursor-pointer hover:bg-purple-500/10 hover:border-purple-500/30 border border-white/5 transition">
<p className="text-white font-medium">{t.nome}</p>
<p className="text-gray-500 text-xs mt-1">{t.desc}</p>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-8">
<div className="glass p-4 flex items-center gap-3">
<div className="w-10 h-10 bg-purple-500/20 rounded-xl flex items-center justify-center text-purple-400"><FileText size={20} /></div>
<div>
<p className="text-2xl font-bold text-white">{docs.filter(d => d.tipo === 'gerado').length}</p>
<p className="text-gray-500 text-xs">Gerados pelo sistema</p>
</div>
</div>
<div className="glass p-4 flex items-center gap-3">
<div className="w-10 h-10 bg-blue-500/20 rounded-xl flex items-center justify-center text-blue-400"><Upload size={20} /></div>
<div>
<p className="text-2xl font-bold text-white">{docs.filter(d => d.tipo === 'upload').length}</p>
<p className="text-gray-500 text-xs">Enviados por você</p>
</div>
</div>
</div>
<div className="space-y-3">
{docs.map((doc) => (
<div key={doc.id} className="glass p-4 flex items-center justify-between hover:bg-white/10 transition">
<div className="flex items-center gap-4">
<div className={`w-10 h-10 rounded-xl flex items-center justify-center ${doc.tipo === 'gerado' ? 'bg-purple-500/20 text-purple-400' : 'bg-blue-500/20 text-blue-400'}`}>
{doc.tipo === 'gerado' ? <FileText size={20} /> : <File size={20} />}
</div>
<div>
<p className="text-white font-medium">{doc.nome}</p>
<div className="flex items-center gap-3 mt-1">
<span className="text-xs text-gray-500">{doc.categoria}</span>
<span className="text-xs text-gray-600"></span>
<span className="text-xs text-gray-500 flex items-center gap-1"><Clock size={10} /> {new Date(doc.data).toLocaleDateString('pt-BR')}</span>
<span className="text-xs text-gray-600"></span>
<span className="text-xs text-gray-500">{doc.tamanho}</span>
</div>
</div>
</div>
<button className="flex items-center gap-2 px-3 py-2 bg-white/5 text-gray-300 rounded-lg hover:bg-white/10 transition text-sm">
<Download size={14} /> Baixar
</button>
</div>
))}
</div>
</main>
</div>
)
}

View File

@@ -0,0 +1,96 @@
'use client'
import { useState, useEffect } from 'react'
import { useRouter, useParams } from 'next/navigation'
import Sidebar from '@/components/Sidebar'
import { User, CheckSquare, Search, FileText, ArrowRight, Clock, MapPin, Briefcase } from 'lucide-react'
const mockFamilias = {
1: { nome: 'Família Silva', falecido: { nome: 'José Carlos Silva', cpf: '***.***.***-45', nascimento: '1955-03-12', obito: '2026-01-10', emprego: true, veiculo: true, imovel: true, previdencia: false }, membros: [{ nome: 'Ana Silva', parentesco: 'Cônjuge', papel: 'Inventariante' }, { nome: 'Pedro Silva', parentesco: 'Filho', papel: 'Herdeiro' }, { nome: 'Carla Silva', parentesco: 'Filha', papel: 'Herdeira' }], progresso: 35 },
2: { nome: 'Família Oliveira', falecido: { nome: 'Maria Oliveira', cpf: '***.***.***-78', nascimento: '1948-07-22', obito: '2026-01-18', emprego: false, veiculo: false, imovel: true, previdencia: true }, membros: [{ nome: 'Carlos Oliveira', parentesco: 'Cônjuge', papel: 'Inventariante' }, { nome: 'Lucia Oliveira', parentesco: 'Filha', papel: 'Herdeira' }], progresso: 60 },
3: { nome: 'Família Santos', falecido: { nome: 'Antônio Santos', cpf: '***.***.***-12', nascimento: '1960-11-05', obito: '2026-01-28', emprego: true, veiculo: true, imovel: true, previdencia: true }, membros: [{ nome: 'Rosa Santos', parentesco: 'Cônjuge', papel: 'Inventariante' }, { nome: 'Marcos Santos', parentesco: 'Filho', papel: 'Herdeiro' }, { nome: 'Julia Santos', parentesco: 'Filha', papel: 'Herdeira' }, { nome: 'Dr. Roberto Lima', parentesco: '-', papel: 'Advogado' }], progresso: 15 }
}
export default function FamiliaPage() {
const router = useRouter()
const params = useParams()
const id = params.id
const familia = mockFamilias[id] || mockFamilias[1]
const f = familia.falecido
const tabs = [
{ label: 'Checklist', icon: <CheckSquare size={18} />, href: `/familia/${id}/checklist` },
{ label: 'Benefícios', icon: <Search size={18} />, href: `/familia/${id}/beneficios` },
{ label: 'Documentos', icon: <FileText size={18} />, href: `/familia/${id}/documentos` },
]
return (
<div className="flex min-h-screen bg-[#0a0a0f]">
<Sidebar />
<main className="ml-64 flex-1 p-8">
<button onClick={() => router.push('/dashboard')} className="text-gray-400 hover:text-white text-sm mb-6 flex items-center gap-1"> Voltar ao Dashboard</button>
<div className="flex items-center gap-4 mb-8">
<div className="w-16 h-16 bg-purple-500/20 rounded-2xl flex items-center justify-center text-3xl">🚣</div>
<div>
<h1 className="text-3xl font-bold text-white">{familia.nome}</h1>
<p className="text-gray-400">Progresso geral: <span className="text-purple-400 font-semibold">{familia.progresso}%</span></p>
</div>
</div>
<div className="w-full bg-white/5 rounded-full h-3 mb-8">
<div className="bg-gradient-to-r from-purple-600 to-purple-400 h-3 rounded-full transition-all" style={{ width: `${familia.progresso}%` }} />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div className="glass p-6">
<h2 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"><User size={20} className="text-purple-400" /> Dados do Falecido</h2>
<div className="space-y-3 text-sm">
<div className="flex justify-between"><span className="text-gray-400">Nome</span><span className="text-white">{f.nome}</span></div>
<div className="flex justify-between"><span className="text-gray-400">CPF</span><span className="text-white font-mono">{f.cpf}</span></div>
<div className="flex justify-between"><span className="text-gray-400">Nascimento</span><span className="text-white">{new Date(f.nascimento).toLocaleDateString('pt-BR')}</span></div>
<div className="flex justify-between"><span className="text-gray-400">Óbito</span><span className="text-white">{new Date(f.obito).toLocaleDateString('pt-BR')}</span></div>
<div className="border-t border-white/5 pt-3 mt-3">
<p className="text-gray-400 mb-2">Situação:</p>
<div className="flex flex-wrap gap-2">
{f.emprego && <span className="px-3 py-1 bg-blue-500/10 text-blue-400 rounded-full text-xs flex items-center gap-1"><Briefcase size={12} /> Empregado</span>}
{f.veiculo && <span className="px-3 py-1 bg-green-500/10 text-green-400 rounded-full text-xs">🚗 Veículo</span>}
{f.imovel && <span className="px-3 py-1 bg-yellow-500/10 text-yellow-400 rounded-full text-xs">🏠 Imóvel</span>}
{f.previdencia && <span className="px-3 py-1 bg-purple-500/10 text-purple-400 rounded-full text-xs">💰 Previdência</span>}
</div>
</div>
</div>
</div>
<div className="glass p-6">
<h2 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"><User size={20} className="text-purple-400" /> Membros da Família</h2>
<div className="space-y-3">
{familia.membros.map((m, i) => (
<div key={i} className="flex items-center justify-between p-3 bg-white/5 rounded-xl">
<div>
<p className="text-white font-medium">{m.nome}</p>
<p className="text-gray-500 text-xs">{m.parentesco}</p>
</div>
<span className={`text-xs px-3 py-1 rounded-full ${m.papel === 'Inventariante' ? 'bg-purple-500/20 text-purple-400' : m.papel === 'Advogado' ? 'bg-blue-500/20 text-blue-400' : 'bg-white/10 text-gray-300'}`}>{m.papel}</span>
</div>
))}
</div>
<button className="mt-4 w-full py-2 border border-dashed border-white/10 rounded-xl text-gray-500 hover:text-white hover:border-purple-500/30 transition text-sm">+ Convidar Membro</button>
</div>
</div>
<h2 className="text-xl font-bold text-white mb-4">Módulos</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{tabs.map((t, i) => (
<div key={i} onClick={() => router.push(t.href)} className="glass p-6 cursor-pointer hover:bg-white/10 transition group flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="text-purple-400">{t.icon}</span>
<span className="text-white font-medium">{t.label}</span>
</div>
<ArrowRight className="text-gray-600 group-hover:text-purple-400 transition" size={18} />
</div>
))}
</div>
</main>
</div>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,239 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@400;700;900&family=Cinzel:wght@400;500;600;700;800;900&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400&family=Inter:wght@300;400;500;600;700&display=swap');
:root {
--void: #050508;
--abyss: #08080f;
--obsidian: #0c0c14;
--onyx: #12121c;
--gold: #b8943f;
--gold-bright: #d4ad4a;
--gold-glow: #e8c55a;
--gold-dim: #7a6228;
--ember: #c44b1a;
--ember-glow: #ff6b2b;
--soul: #4a7aff;
--soul-dim: #2a4a9a;
--bone: #c8c0b0;
--ash: #6a645a;
--smoke: #3a3630;
--marble: #e0dcd4;
}
* { box-sizing: border-box; }
body {
background: var(--void);
color: var(--bone);
font-family: 'Inter', sans-serif;
overflow-x: hidden;
}
.font-myth { font-family: 'Cinzel', serif; }
.font-myth-decorative { font-family: 'Cinzel Decorative', serif; }
.font-elegant { font-family: 'Cormorant Garamond', serif; }
/* === GOLD TEXT === */
.gold-text {
background: linear-gradient(135deg, var(--gold-dim) 0%, var(--gold) 30%, var(--gold-glow) 50%, var(--gold) 70%, var(--gold-dim) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* === UNDERWORLD BACKGROUNDS === */
.bg-underworld {
background:
radial-gradient(ellipse 120% 60% at 50% 110%, rgba(74, 40, 10, 0.08) 0%, transparent 70%),
radial-gradient(ellipse 80% 40% at 20% 80%, rgba(196, 75, 26, 0.04) 0%, transparent 60%),
radial-gradient(ellipse 80% 40% at 80% 80%, rgba(196, 75, 26, 0.04) 0%, transparent 60%),
linear-gradient(180deg, var(--void) 0%, var(--abyss) 40%, #0a0a12 70%, #0d0910 100%);
}
.bg-styx {
background:
radial-gradient(ellipse 100% 30% at 50% 100%, rgba(42, 74, 154, 0.1) 0%, transparent 70%),
linear-gradient(180deg, transparent 60%, rgba(42, 74, 154, 0.03) 100%);
}
/* === EMBER / FIRE GLOW === */
@keyframes emberPulse {
0%, 100% { opacity: 0.4; filter: blur(30px); }
50% { opacity: 0.7; filter: blur(40px); }
}
@keyframes emberFloat {
0% { transform: translateY(0) scale(1); opacity: 0.6; }
50% { transform: translateY(-20px) scale(1.1); opacity: 0.9; }
100% { transform: translateY(-40px) scale(0.8); opacity: 0; }
}
.ember-particle {
position: absolute;
width: 3px;
height: 3px;
background: var(--ember-glow);
border-radius: 50%;
animation: emberFloat 4s ease-out infinite;
}
/* === GREEK ORNAMENTS === */
.ornament-line {
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold-dim), var(--gold), var(--gold-dim), transparent);
position: relative;
}
.ornament-line::after {
content: '◆';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--gold);
font-size: 8px;
background: var(--void);
padding: 0 12px;
}
.ornament-line-simple {
height: 1px;
background: linear-gradient(90deg, transparent, rgba(184, 148, 63, 0.3), transparent);
}
/* === CARDS === */
.card-hades {
background: linear-gradient(180deg, rgba(18, 18, 28, 0.95), rgba(8, 8, 15, 0.98));
border: 1px solid rgba(184, 148, 63, 0.08);
position: relative;
overflow: hidden;
}
.card-hades::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(184, 148, 63, 0.3), transparent);
}
.card-hades:hover {
border-color: rgba(184, 148, 63, 0.2);
box-shadow: 0 0 40px rgba(184, 148, 63, 0.05), inset 0 1px 0 rgba(184, 148, 63, 0.1);
}
.card-shrine {
background: rgba(12, 12, 20, 0.9);
border: 1px solid rgba(184, 148, 63, 0.06);
backdrop-filter: blur(20px);
}
/* === BUTTONS === */
.btn-gold {
background: linear-gradient(135deg, var(--gold-dim), var(--gold), var(--gold-bright));
color: #0a0a0f;
font-family: 'Cinzel', serif;
letter-spacing: 0.15em;
font-weight: 700;
position: relative;
overflow: hidden;
transition: all 0.3s;
}
.btn-gold::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.btn-gold:hover::before { left: 100%; }
.btn-gold:hover { box-shadow: 0 0 30px rgba(184, 148, 63, 0.3); }
.btn-outline {
border: 1px solid rgba(184, 148, 63, 0.25);
color: var(--gold);
font-family: 'Cinzel', serif;
letter-spacing: 0.15em;
transition: all 0.3s;
}
.btn-outline:hover {
background: rgba(184, 148, 63, 0.08);
border-color: rgba(184, 148, 63, 0.4);
}
/* === ANIMATIONS === */
@keyframes boatFloat {
0%, 100% { transform: translateY(0px) rotate(0deg); }
33% { transform: translateY(-6px) rotate(0.8deg); }
66% { transform: translateY(3px) rotate(-0.5deg); }
}
@keyframes fogDrift {
0% { transform: translateX(-10%) scaleX(1); opacity: 0.3; }
50% { transform: translateX(5%) scaleX(1.05); opacity: 0.5; }
100% { transform: translateX(-10%) scaleX(1); opacity: 0.3; }
}
@keyframes soulGlow {
0%, 100% { opacity: 0.2; filter: blur(20px); }
50% { opacity: 0.4; filter: blur(25px); }
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes twinkle {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.8; }
}
.boat-float { animation: boatFloat 6s ease-in-out infinite; }
.fade-in { animation: fadeInUp 1s ease-out forwards; }
.fade-in-delay { animation: fadeInUp 1s ease-out 0.3s forwards; opacity: 0; }
.fade-in-delay-2 { animation: fadeInUp 1s ease-out 0.6s forwards; opacity: 0; }
/* === INPUTS === */
.input-hades {
background: rgba(5, 5, 8, 0.8);
border: 1px solid rgba(184, 148, 63, 0.1);
color: var(--bone);
transition: all 0.3s;
}
.input-hades:focus {
border-color: rgba(184, 148, 63, 0.35);
box-shadow: 0 0 20px rgba(184, 148, 63, 0.05);
outline: none;
}
.input-hades::placeholder { color: var(--smoke); }
/* === PROGRESS === */
.progress-styx {
background: rgba(5, 5, 8, 0.6);
border: 1px solid rgba(184, 148, 63, 0.06);
}
.progress-styx-fill {
background: linear-gradient(90deg, var(--gold-dim), var(--gold), var(--gold-bright));
box-shadow: 0 0 10px rgba(184, 148, 63, 0.3);
}
/* === SCROLLBAR === */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: var(--void); }
::-webkit-scrollbar-thumb { background: rgba(184, 148, 63, 0.2); }
::-webkit-scrollbar-thumb:hover { background: rgba(184, 148, 63, 0.4); }

View File

@@ -0,0 +1,17 @@
import { Inter } from 'next/font/google'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'CARONTE - Guia Pós-Óbito',
description: 'O barqueiro que guia famílias pelo rio burocrático pós-óbito no Brasil',
}
export default function RootLayout({ children }) {
return (
<html lang="pt-BR">
<body className={inter.className}>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,78 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { Eye, EyeOff } from 'lucide-react'
export default function LoginPage() {
const router = useRouter()
const [email, setEmail] = useState('')
const [senha, setSenha] = useState('')
const [show, setShow] = useState(false)
const [loading, setLoading] = useState(false)
const login = async (e) => {
e.preventDefault()
setLoading(true)
setTimeout(() => {
localStorage.setItem('token', 'demo-token')
router.push('/dashboard')
}, 1200)
}
return (
<div className="min-h-screen bg-underworld flex items-center justify-center px-4 relative">
{/* Stars */}
<div className="fixed inset-0 pointer-events-none">
{[...Array(40)].map((_, i) => (
<div key={i} className="absolute rounded-full bg-white"
style={{ width: `${1 + Math.random()}px`, height: `${1 + Math.random()}px`,
top: `${Math.random() * 100}%`, left: `${Math.random() * 100}%`, opacity: 0.15,
animation: `twinkle ${4 + Math.random() * 6}s ease-in-out ${Math.random() * 5}s infinite` }} />
))}
</div>
<div className="w-full max-w-sm relative z-10 fade-in">
<div className="text-center mb-12">
<span className="text-6xl boat-float block mb-6 filter drop-shadow-xl">🚣</span>
<h1 className="font-myth-decorative text-2xl gold-text tracking-[0.2em]">CARONTE</h1>
<div className="ornament-line-simple max-w-[60px] mx-auto my-4" />
<p className="font-elegant italic text-[var(--ash)] text-lg">"A travessia começa aqui."</p>
</div>
<form onSubmit={login} className="card-hades p-8">
<h2 className="font-myth text-sm text-center text-[var(--gold-dim)] mb-8 tracking-[0.3em] uppercase">Entrar no Submundo</h2>
<div className="space-y-6">
<div>
<label className="text-[10px] tracking-[0.2em] text-[var(--gold-dim)] uppercase font-myth block mb-2">Email</label>
<input type="email" value={email} onChange={e => setEmail(e.target.value)}
className="w-full px-4 py-3 input-hades text-sm" placeholder="seu@email.com" required />
</div>
<div>
<label className="text-[10px] tracking-[0.2em] text-[var(--gold-dim)] uppercase font-myth block mb-2">Senha</label>
<div className="relative">
<input type={show ? 'text' : 'password'} value={senha} onChange={e => setSenha(e.target.value)}
className="w-full px-4 py-3 input-hades text-sm pr-12" placeholder="••••••••" required />
<button type="button" onClick={() => setShow(!show)} className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--smoke)] hover:text-[var(--ash)] transition">
{show ? <EyeOff size={14} /> : <Eye size={14} />}
</button>
</div>
</div>
<button type="submit" disabled={loading} className="w-full py-3.5 btn-gold text-xs disabled:opacity-50">
{loading ? '⏳ Cruzando o Styx...' : 'ENTRAR'}
</button>
</div>
<div className="ornament-line-simple my-6" />
<p className="text-center text-[var(--smoke)] text-sm">
Primeira travessia? <Link href="/registro" className="text-[var(--gold)] hover:text-[var(--gold-bright)] transition">Criar conta</Link>
</p>
</form>
</div>
</div>
)
}

199
frontend/src/app/page.js Normal file
View File

@@ -0,0 +1,199 @@
'use client'
import Link from 'next/link'
import { ArrowRight, Shield } from 'lucide-react'
export default function Home() {
return (
<div className="min-h-screen bg-underworld relative">
{/* Fog layers */}
<div className="fixed inset-0 pointer-events-none z-0">
<div className="absolute bottom-0 left-0 right-0 h-[400px] bg-gradient-to-t from-[rgba(196,75,26,0.03)] to-transparent" style={{ animation: 'fogDrift 20s ease-in-out infinite' }} />
<div className="absolute bottom-0 left-0 right-0 h-[300px] bg-gradient-to-t from-[rgba(42,74,154,0.04)] to-transparent" style={{ animation: 'fogDrift 15s ease-in-out infinite reverse' }} />
</div>
{/* Stars */}
<div className="fixed inset-0 pointer-events-none z-0">
{[...Array(80)].map((_, i) => (
<div key={i} className="absolute rounded-full bg-white"
style={{
width: `${1 + Math.random() * 1.5}px`, height: `${1 + Math.random() * 1.5}px`,
top: `${Math.random() * 50}%`, left: `${Math.random() * 100}%`,
opacity: 0.15 + Math.random() * 0.3,
animation: `twinkle ${4 + Math.random() * 6}s ease-in-out ${Math.random() * 5}s infinite`
}} />
))}
</div>
{/* Soul orbs */}
<div className="fixed inset-0 pointer-events-none z-0">
{[...Array(5)].map((_, i) => (
<div key={i} className="absolute rounded-full"
style={{
width: `${60 + Math.random() * 100}px`, height: `${60 + Math.random() * 100}px`,
top: `${30 + Math.random() * 50}%`, left: `${Math.random() * 100}%`,
background: `radial-gradient(circle, ${i % 2 === 0 ? 'rgba(196,75,26,0.06)' : 'rgba(74,122,255,0.04)'}, transparent 70%)`,
animation: `soulGlow ${6 + Math.random() * 4}s ease-in-out ${Math.random() * 3}s infinite`
}} />
))}
</div>
{/* Nav */}
<nav className="relative z-20 flex items-center justify-between px-10 py-7">
<div className="flex items-center gap-4">
<span className="text-4xl boat-float filter drop-shadow-lg">🚣</span>
<div>
<span className="font-myth-decorative text-xl gold-text tracking-[0.2em]">CARONTE</span>
</div>
</div>
<div className="flex gap-3">
<Link href="/login" className="btn-outline px-6 py-2.5 text-xs tracking-[0.2em]">ENTRAR</Link>
<Link href="/registro" className="btn-gold px-6 py-2.5 text-xs">COMEÇAR</Link>
</div>
</nav>
{/* Hero */}
<section className="relative z-10 max-w-4xl mx-auto px-8 pt-20 pb-12 text-center">
<div className="fade-in">
<div className="ornament-line max-w-[200px] mx-auto mb-16" />
<p className="font-myth text-[var(--gold-dim)] text-[11px] tracking-[0.5em] uppercase mb-8"> Guia para famílias em luto</p>
<h1 className="font-myth-decorative text-5xl md:text-7xl font-bold leading-[1.15] mb-8 tracking-wide">
<span className="gold-text">Perdeu</span>
<span className="text-[var(--marble)]"> alguém</span>
<span className="gold-text">?</span><br />
<span className="text-[var(--bone)] text-4xl md:text-5xl font-myth font-normal tracking-wider">Nós guiamos a travessia.</span>
</h1>
</div>
<div className="fade-in-delay">
<p className="font-elegant text-2xl md:text-3xl text-[var(--ash)] max-w-2xl mx-auto mb-6 italic leading-relaxed">
"Assim como o barqueiro guiava as almas<br />pelo Rio Styx até o descanso eterno —<br />nós guiamos sua família pela burocracia."
</p>
</div>
<div className="fade-in-delay-2">
<p className="text-[var(--smoke)] max-w-lg mx-auto mb-14 text-sm leading-relaxed">
Checklist inteligente · Scanner de benefícios esquecidos · Gerador de documentos.<br />
Tudo que precisa ser feito após a perda em um lugar.
</p>
<Link href="/registro" className="group inline-flex items-center gap-3 btn-gold px-10 py-4 text-sm">
INICIAR A TRAVESSIA <ArrowRight className="group-hover:translate-x-1 transition" size={18} />
</Link>
<p className="text-[var(--smoke)] text-xs mt-4 tracking-wider">Gratuito · Sem cartão de crédito</p>
</div>
</section>
{/* Divider with ember */}
<div className="relative z-10 max-w-4xl mx-auto px-8 py-16">
<div className="ornament-line" />
</div>
{/* Numbers */}
<section className="relative z-10 max-w-4xl mx-auto px-8 pb-20">
<div className="grid grid-cols-3 gap-6">
{[
{ num: '1.4M', label: 'óbitos por ano', sub: 'no Brasil', icon: '💀' },
{ num: '500h', label: 'de burocracia', sub: 'por família enlutada', icon: '⏳' },
{ num: 'R$ Bi', label: 'em benefícios', sub: 'esquecidos todo ano', icon: '🔱' },
].map((s, i) => (
<div key={i} className="card-hades p-8 text-center group hover:scale-[1.02] transition-all duration-500">
<span className="text-3xl block mb-4 group-hover:scale-110 transition-transform">{s.icon}</span>
<p className="font-myth-decorative text-3xl md:text-4xl font-bold gold-text mb-2">{s.num}</p>
<p className="text-[var(--bone)] text-sm font-myth tracking-wider">{s.label}</p>
<p className="text-[var(--smoke)] text-xs mt-1">{s.sub}</p>
</div>
))}
</div>
</section>
{/* Features - The Three Pillars */}
<section className="relative z-10 max-w-5xl mx-auto px-8 pb-24">
<div className="text-center mb-16">
<p className="font-myth text-[var(--gold-dim)] text-[11px] tracking-[0.5em] uppercase mb-4">🏛 Os Três Pilares do Submundo</p>
<h2 className="font-myth text-3xl md:text-4xl font-bold text-[var(--marble)] tracking-wide">As armas de Caronte</h2>
<div className="ornament-line-simple max-w-[300px] mx-auto mt-6" />
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{[
{
icon: '🗺️', title: 'O Mapa do', titleGold: 'Submundo',
desc: 'Checklist completo de cada passo após o óbito. Certidões, INSS, bancos, inventário — com prazos, dependências e prioridades. Como um mapa que revela cada caminho no labirinto burocrático.',
detail: '15 etapas · 4 fases · Prazos automáticos'
},
{
icon: '🔮', title: 'O Oráculo dos', titleGold: 'Tesouros',
desc: 'Nossa IA vasculha benefícios esquecidos: FGTS, PIS/PASEP, seguros de vida, pensões, restituições. Milhares de reais que famílias nunca souberam que tinham direito.',
detail: '7 tipos de benefício · Média: R$15.000 recuperados'
},
{
icon: '📜', title: 'Os Pergaminhos', titleGold: 'Sagrados',
desc: 'Procurações, requerimentos e petições gerados automaticamente. Documentos jurídicos prontos para uso — sem precisar de advogado para as tarefas mais simples.',
detail: '5 modelos · PDF pronto para imprimir'
},
].map((f, i) => (
<div key={i} className="card-hades p-8 group hover:scale-[1.02] transition-all duration-500 flex flex-col">
<span className="text-4xl block mb-6 group-hover:scale-110 transition-transform">{f.icon}</span>
<h3 className="font-myth text-lg text-[var(--bone)] mb-1 tracking-wider">
{f.title} <span className="gold-text">{f.titleGold}</span>
</h3>
<p className="text-[var(--ash)] text-sm leading-relaxed mt-3 flex-1">{f.desc}</p>
<div className="ornament-line-simple mt-6 mb-4" />
<p className="text-[var(--gold-dim)] text-[10px] tracking-[0.2em] font-myth uppercase">{f.detail}</p>
</div>
))}
</div>
</section>
{/* Testimonial / Quote */}
<section className="relative z-10 py-20">
<div className="max-w-3xl mx-auto px-8 text-center">
<div className="card-hades p-12 relative">
<span className="absolute -top-6 left-1/2 -translate-x-1/2 text-5xl">🔥</span>
<p className="font-elegant text-2xl md:text-3xl text-[var(--bone)] italic leading-relaxed mt-4">
"Quando meu pai faleceu, eu não sabia por onde começar. Foram meses de agonia em filas, cartórios e telefones. Se o Caronte existisse naquele momento, teria mudado tudo."
</p>
<div className="ornament-line-simple max-w-[100px] mx-auto my-6" />
<p className="text-[var(--gold-dim)] font-myth text-xs tracking-[0.2em] uppercase"> A dor que nos inspirou a criar</p>
</div>
</div>
</section>
{/* Final CTA */}
<section className="relative z-10 py-24">
<div className="max-w-2xl mx-auto text-center px-8">
<span className="text-7xl boat-float block mb-8 filter drop-shadow-2xl">🚣</span>
<h2 className="font-myth text-3xl md:text-4xl font-bold text-[var(--marble)] mb-4 tracking-wide">A travessia não precisa<br />ser solitária</h2>
<p className="font-elegant text-xl text-[var(--ash)] italic mb-10">"Não deixe a burocracia transformar o luto em pesadelo."</p>
<Link href="/registro" className="inline-flex items-center gap-3 btn-gold px-10 py-4 text-sm">
COMEÇAR AGORA GRATUITO
</Link>
</div>
</section>
{/* Footer */}
<footer className="relative z-10 border-t border-[var(--gold)]/5 py-8 px-10">
<div className="max-w-5xl mx-auto flex justify-between items-center">
<div className="flex items-center gap-3">
<span className="text-lg">🚣</span>
<span className="font-myth text-xs gold-text tracking-[0.2em]">CARONTE</span>
</div>
<p className="text-[var(--smoke)] text-[11px] tracking-wider">
© 2026 Nenhum benefício esquecido. Nenhum prazo perdido.
</p>
<div className="flex items-center gap-2">
<Shield size={12} className="text-[var(--gold-dim)]" />
<span className="text-[var(--smoke)] text-[11px]">LGPD</span>
</div>
</div>
</footer>
{/* River Styx glow at bottom */}
<div className="fixed bottom-0 left-0 right-0 h-[2px] bg-gradient-to-r from-transparent via-[var(--gold-dim)] to-transparent opacity-20 z-30" />
</div>
)
}

View File

@@ -0,0 +1,89 @@
'use client'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { UserPlus, Eye, EyeOff } from 'lucide-react'
export default function RegistroPage() {
const router = useRouter()
const [nome, setNome] = useState('')
const [email, setEmail] = useState('')
const [senha, setSenha] = useState('')
const [show, setShow] = useState(false)
const [loading, setLoading] = useState(false)
const registro = async (e) => {
e.preventDefault()
setLoading(true)
setTimeout(() => {
localStorage.setItem('token', 'demo-token')
router.push('/dashboard')
}, 1500)
}
return (
<div className="min-h-screen styx-bg flex items-center justify-center px-4 relative">
<div className="absolute inset-0 overflow-hidden pointer-events-none">
{[...Array(30)].map((_, i) => (
<div key={i} className="absolute w-[2px] h-[2px] bg-white/20 rounded-full"
style={{ top: `${Math.random() * 100}%`, left: `${Math.random() * 100}%`, animation: `twinkle ${3 + Math.random() * 4}s ease-in-out infinite` }} />
))}
</div>
<div className="w-full max-w-md relative z-10">
<div className="text-center mb-10">
<span className="text-5xl boat-float block mb-4">🚣</span>
<h1 className="font-myth text-3xl font-bold gold-text tracking-wider">CARONTE</h1>
<p className="font-elegant italic text-[var(--text-muted)] mt-2">"Toda travessia começa com o primeiro passo."</p>
</div>
<div className="greek-line mb-8 max-w-[100px] mx-auto" />
<form onSubmit={registro} className="card-temple p-8 glow-gold">
<h2 className="font-myth text-xl text-center text-[var(--marble)] mb-6 tracking-wider">CRIAR CONTA</h2>
<div className="space-y-5">
<div>
<label className="text-[10px] tracking-[0.2em] text-[var(--gold)] uppercase font-myth block mb-2">Nome Completo</label>
<input type="text" value={nome} onChange={e => setNome(e.target.value)}
className="w-full px-4 py-3 bg-black/30 border border-[var(--gold)]/20 text-[var(--text)] focus:border-[var(--gold)]/50 focus:outline-none transition text-sm"
placeholder="Seu nome" required />
</div>
<div>
<label className="text-[10px] tracking-[0.2em] text-[var(--gold)] uppercase font-myth block mb-2">Email</label>
<input type="email" value={email} onChange={e => setEmail(e.target.value)}
className="w-full px-4 py-3 bg-black/30 border border-[var(--gold)]/20 text-[var(--text)] focus:border-[var(--gold)]/50 focus:outline-none transition text-sm"
placeholder="seu@email.com" required />
</div>
<div>
<label className="text-[10px] tracking-[0.2em] text-[var(--gold)] uppercase font-myth block mb-2">Senha</label>
<div className="relative">
<input type={show ? 'text' : 'password'} value={senha} onChange={e => setSenha(e.target.value)}
className="w-full px-4 py-3 bg-black/30 border border-[var(--gold)]/20 text-[var(--text)] focus:border-[var(--gold)]/50 focus:outline-none transition text-sm pr-12"
placeholder="••••••••" required />
<button type="button" onClick={() => setShow(!show)} className="absolute right-3 top-1/2 -translate-y-1/2 text-[var(--text-muted)]">
{show ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
<button type="submit" disabled={loading}
className="w-full py-3 bg-gradient-to-r from-[var(--gold-dark)] to-[var(--gold)] text-black font-bold font-myth tracking-wider hover:from-[var(--gold)] hover:to-[var(--gold-light)] transition disabled:opacity-50 flex items-center justify-center gap-2">
{loading ? '⏳ Preparando a travessia...' : <><UserPlus size={16} /> INICIAR TRAVESSIA</>}
</button>
</div>
<div className="greek-line mt-6 mb-4" />
<p className="text-center text-[var(--text-muted)] text-sm">
tem conta? <Link href="/login" className="text-[var(--gold)] hover:text-[var(--gold-light)] font-medium">Entrar</Link>
</p>
</form>
</div>
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-[var(--water)]/20 to-transparent pointer-events-none" />
</div>
)
}

View File

@@ -0,0 +1,59 @@
'use client'
import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation'
import { LayoutDashboard, Users, LogOut } from 'lucide-react'
export default function Sidebar() {
const pathname = usePathname()
const router = useRouter()
const logout = () => { localStorage.removeItem('token'); router.push('/login') }
const links = [
{ href: '/dashboard', label: 'Ágora', icon: <LayoutDashboard size={16} />, emoji: '🏛️' },
{ href: '/dashboard', label: 'Famílias', icon: <Users size={16} />, emoji: '⚱️' },
]
return (
<aside className="fixed left-0 top-0 h-screen w-60 bg-[var(--void)]/98 backdrop-blur-xl flex flex-col z-40 border-r border-[var(--gold)]/5">
{/* Logo */}
<div className="p-6 pb-4">
<Link href="/dashboard" className="flex items-center gap-3">
<span className="text-2xl boat-float">🚣</span>
<div>
<div className="font-myth-decorative text-sm gold-text tracking-[0.15em]">CARONTE</div>
<div className="text-[9px] tracking-[0.15em] text-[var(--smoke)] uppercase">Guia do Submundo</div>
</div>
</Link>
</div>
<div className="ornament-line-simple mx-4" />
{/* Nav */}
<nav className="flex-1 px-3 py-4 space-y-0.5">
<p className="text-[9px] tracking-[0.3em] text-[var(--smoke)] uppercase font-myth px-3 mb-3">Navegação</p>
{links.map((l, i) => {
const active = pathname === l.href
return (
<Link key={i} href={l.href}
className={`flex items-center gap-3 px-3 py-2.5 text-sm transition-all ${active ? 'text-[var(--gold)] bg-[var(--gold)]/5 border-l-2 border-[var(--gold)]' : 'text-[var(--ash)] hover:text-[var(--bone)] hover:bg-white/[0.02]'}`}>
<span className="text-base">{l.emoji}</span>
<span className="font-myth text-xs tracking-wider">{l.label}</span>
</Link>
)
})}
</nav>
<div className="p-4">
<div className="card-shrine p-3 mb-3 text-center">
<p className="font-elegant italic text-[11px] text-[var(--smoke)] leading-relaxed">"A travessia é mais leve<br/>quando não se está só."</p>
</div>
<div className="ornament-line-simple mb-3" />
<button onClick={logout} className="flex items-center gap-2 px-3 py-2 text-[var(--smoke)] hover:text-red-400/80 transition w-full text-xs">
<LogOut size={13} />
<span>Encerrar</span>
</button>
</div>
</aside>
)
}

49
frontend/src/lib/api.js Normal file
View File

@@ -0,0 +1,49 @@
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8070';
async function request(path, options = {}) {
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null;
const headers = { ...options.headers };
if (token) headers['Authorization'] = `Bearer ${token}`;
if (options.body && typeof options.body === 'object' && !(options.body instanceof FormData)) {
headers['Content-Type'] = 'application/json';
options.body = JSON.stringify(options.body);
}
const res = await fetch(`${API_URL}${path}`, { ...options, headers });
if (res.status === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('token');
window.location.href = '/login';
}
throw new Error('Não autorizado');
}
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.detail || 'Erro na requisição');
}
return res.json();
}
export const api = {
login: (email, senha) => {
const form = new URLSearchParams();
form.append('username', email);
form.append('password', senha);
return request('/api/v1/auth/login', { method: 'POST', body: form, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
},
registro: (data) => request('/api/v1/auth/registro', { method: 'POST', body: data }),
me: () => request('/api/v1/auth/me'),
dashboard: () => request('/api/v1/dashboard/'),
familias: () => request('/api/v1/familias/'),
familia: (id) => request(`/api/v1/familias/${id}`),
criarFamilia: (data) => request('/api/v1/familias/', { method: 'POST', body: data }),
membros: (famId) => request(`/api/v1/familias/${famId}/membros`),
falecidos: (famId) => request(`/api/v1/familias/${famId}/falecidos`),
checklist: (famId) => request(`/api/v1/familias/${famId}/checklist/`),
updateChecklist: (famId, itemId, status) => request(`/api/v1/familias/${famId}/checklist/${itemId}`, { method: 'PUT', body: { status } }),
proximoPasso: (famId) => request(`/api/v1/familias/${famId}/checklist/proximo`),
beneficios: (famId) => request(`/api/v1/familias/${famId}/beneficios/`),
scanBeneficios: (famId) => request(`/api/v1/familias/${famId}/beneficios/scan`, { method: 'POST' }),
documentos: (famId) => request(`/api/v1/familias/${famId}/documentos/`),
gerarDocumento: (famId, tipo, falecidoId) => request(`/api/v1/familias/${famId}/documentos/gerar`, { method: 'POST', body: { tipo, falecido_id: falecidoId } }),
downloadUrl: (famId, docId) => `${API_URL}/api/v1/familias/${famId}/documentos/${docId}/download`,
};

View File

@@ -0,0 +1,17 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
};