diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml
new file mode 100644
index 0000000..62cee5c
--- /dev/null
+++ b/.gitea/workflows/ci.yml
@@ -0,0 +1,107 @@
+# CI e deploy num arquivo só: o Gitea lê .gitea/workflows e ignora .github/workflows quando esta pasta existe.
+# - verify: lint, tipos, testes e build. O deploy só roda se ele passar.
+# - audit: alertas de segurança das dependências (fica vermelho sozinho, sem travar o deploy).
+# - e2e: testes de ponta a ponta na build de produção (informativo).
+# - deploy: Vercel CLI. Branch padrão -> produção; demais branches -> preview; pull requests não publicam.
+# Token: secret VERCEL_TOKEN de nível de usuário no Gitea. Projeto na Vercel = nome do repo
+# (ou a variável VERCEL_PROJECT do repo). O build roda na Vercel; o runner só envia o código.
+name: CI
+
+"on":
+ push:
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ verify:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm lint
+ - run: pnpm typecheck
+ - run: pnpm test -- --runInBand
+ - run: pnpm build
+
+ audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm audit --prod --audit-level=high
+ - run: pnpm audit --dev --audit-level=high
+
+ e2e:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm exec playwright install --with-deps chromium
+ - run: pnpm build
+ - name: Subir o servidor de produção
+ run: pnpm start -p 3001 &
+ - name: Esperar a porta 3001
+ run: |
+ for attempt in $(seq 1 60); do
+ if curl --silent --fail --output /dev/null http://localhost:3001/; then
+ exit 0
+ fi
+ sleep 2
+ done
+ echo "O servidor não respondeu na porta 3001 em 120 s" >&2
+ exit 1
+ - name: Testes de ponta a ponta
+ run: pnpm test:e2e
+ env:
+ BASE_URL: http://localhost:3001
+ - name: Publicar test-results/
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-test-results
+ path: test-results/
+ if-no-files-found: ignore
+ retention-days: 7
+
+ deploy:
+ needs: verify
+ if: github.event_name != 'pull_request'
+ runs-on: ubuntu-latest
+ env:
+ VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
+ VERCEL_SCOPE: asemlk54-1928s-projects
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ # versão fixa do CLI: um pacote sem versão num pipeline de produção é risco de cadeia de suprimentos
+ - run: npm install --global vercel@61.1.0
+ - name: Deploy
+ run: |
+ PROJECT=$(echo "${{ vars.VERCEL_PROJECT || github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')
+ if [ "${{ github.ref_name }}" = "${{ github.event.repository.default_branch }}" ]; then
+ PROD=--prod
+ else
+ PROD=
+ fi
+ vercel link --yes --project "$PROJECT" --scope "$VERCEL_SCOPE" --token "$VERCEL_TOKEN"
+ vercel deploy $PROD --token "$VERCEL_TOKEN"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index db405ea..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-name: CI
-
-"on":
- push:
- pull_request:
-
-permissions:
- contents: read
-
-jobs:
- verify:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v6
- - uses: pnpm/action-setup@v6
- - uses: actions/setup-node@v6
- with:
- node-version: 22
- cache: pnpm
- - run: pnpm install --frozen-lockfile
- - run: pnpm lint
- - run: pnpm typecheck
- - run: pnpm test -- --runInBand
- - run: pnpm build
- - run: pnpm audit --prod --audit-level=high
- - run: pnpm audit --dev --audit-level=high
-
- e2e:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v6
- - uses: pnpm/action-setup@v6
- - uses: actions/setup-node@v6
- with:
- node-version: 22
- cache: pnpm
- - run: pnpm install --frozen-lockfile
- - run: pnpm exec playwright install --with-deps chromium
- - run: pnpm build
- - name: Subir o servidor de produção
- run: pnpm start -p 3001 &
- - name: Esperar a porta 3001
- run: |
- for attempt in $(seq 1 60); do
- if curl --silent --fail --output /dev/null http://localhost:3001/; then
- exit 0
- fi
- sleep 2
- done
- echo "O servidor não respondeu na porta 3001 em 120 s" >&2
- exit 1
- - name: Testes de ponta a ponta
- run: pnpm test:e2e
- env:
- BASE_URL: http://localhost:3001
- - name: Publicar test-results/
- if: ${{ !cancelled() }}
- uses: actions/upload-artifact@v4
- with:
- name: e2e-test-results
- path: test-results/
- if-no-files-found: ignore
- retention-days: 7
diff --git a/README.md b/README.md
index c6ceec7..9869826 100644
--- a/README.md
+++ b/README.md
@@ -1,188 +1,142 @@
-# Evandro.dev.br: software sob medida
+

-Site comercial de Evandro Fonseca Junior para projetos de software sob medida: sistemas empresariais, aplicativos e integrações, com cases e contato direto por WhatsApp.
+# Evandro.dev.br — portfólio
+
+**Portfólio profissional de Evandro Fonseca Junior: projetos, cases, serviços,
+currículo e contato por WhatsApp — com hero e notebook 3D progressivos que caem
+para HTML puro quando o dispositivo não comporta.**
+
+
+
+
+
+[Rotas](#o-que-o-site-entrega) · [Como funciona](#como-funciona) · [Execução local](#execu%C3%A7%C3%A3o-local) · [CI/CD](#cicd)
-## Requisitos
+> [!NOTE]
+> **Estado do projeto:** em desenvolvimento ativo (último commit: "refine portfolio
+> showcase, services and quality lab"); o site de produção é `https://evandro.dev.br`,
+> que é o host declarado em `app/sitemap.ts` e `app/robots.ts`. As ações externas
+> de SEO/GEO (Search Console, Bing, Cloudflare, CrUX) estão descritas como runbook e
+> **não executadas** em [`docs/seo-geo-operations.md`](docs/seo-geo-operations.md).
-- Node.js `22.x`
-- pnpm `9.15.9`, fixado em `package.json#packageManager`
+> [!IMPORTANT]
+> **Next.js `16.3.0` é intencional.** A `16.2.11` ainda mantinha três advisories
+> transitivos de severidade alta; a `16.3.0` foi a menor versão estável da mesma
+> major que deixou as auditorias atuais sem vulnerabilidades high. Não faça
+> downgrade sem repetir `pnpm audit`.
-O projeto usa Next.js `16.3.3`, patch de segurança que corrige [GHSA-p293-qw3h-jr36](https://github.com/advisories/GHSA-p293-qw3h-jr36) e [GHSA-2xp9-vwfh-vxw4](https://github.com/advisories/GHSA-2xp9-vwfh-vxw4). A atualização substitui `16.3.0`, afetada por esses avisos. As auditorias de produção e desenvolvimento continuam sendo verificadas na CI.
-
-## Início rápido
-
-```bash
-corepack pnpm@9.15.9 install --frozen-lockfile
-corepack pnpm@9.15.9 dev
-```
-
-O servidor de desenvolvimento usa Turbopack. Para executar o artefato de produção:
-
-```bash
-corepack pnpm@9.15.9 build
-corepack pnpm@9.15.9 start
-```
-
-## Scripts
-
-| Comando | Comportamento |
-| --- | --- |
-| `pnpm dev` | Inicia o Next.js em desenvolvimento com Turbopack. |
-| `pnpm build` | Gera o build de produção com Turbopack. |
-| `pnpm start` | Serve um build já gerado. |
-| `pnpm lint` | Executa `biome check`. |
-| `pnpm typecheck` | Gera os tipos de rota com `next typegen` e executa TypeScript sem emitir arquivos. |
-| `pnpm format` | Formata o repositório com Biome. |
-| `pnpm test` | Executa a suíte Jest. |
-| `pnpm test:watch` | Executa Jest em modo watch. |
-| `pnpm check:projects` | Verifica URLs de demonstração e substitui atomicamente `data/project-health.json`. |
-
-## Vitrine de vídeos dos projetos
-
-Cada projeto com site gravado aparece de três formas na seção Projetos da home. Tudo mora em `app/components/showcase/`, os dados em `lib/project-showcase.ts` e os estilos em `showcase.css`.
-
-- **Cards.** O vídeo de rolagem fica numa moldura de navegador com o endereço real do site. Só um vídeo toca por vez: o do card sob o mouse (ou com foco) e, sem mouse, o mais perto do centro da tela. Nada é baixado até o card tocar pela primeira vez, e um botão pausa os vídeos que se mexem sozinhos. Clicar na moldura abre o vídeo ampliado (`