From 1a6d59b9fcf07a8db226991b206c8ff696dabb19 Mon Sep 17 00:00:00 2001 From: Marcelo Santos <117441129+marcelo-m7@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:27:37 +0000 Subject: [PATCH] Refactor Monynha site strategy and QA pipeline --- .github/workflows/ci.yml | 110 +--- AGENTS.md | 21 + TODO.md | 19 + eslint.config.js | 31 +- package.json | 10 +- pnpm-lock.yaml | 24 + public/sitemap.xml | 12 +- src/App.tsx | 704 ++++++++++++++++++------- test-results/.last-run.json | 4 - tests/e2e/site-flows.spec.ts | 24 + tests/integration/contactFlow.test.tsx | 20 + tests/unit/appContent.test.tsx | 15 + tsconfig.json | 5 +- 13 files changed, 694 insertions(+), 305 deletions(-) create mode 100644 AGENTS.md create mode 100644 TODO.md delete mode 100644 test-results/.last-run.json create mode 100644 tests/e2e/site-flows.spec.ts create mode 100644 tests/integration/contactFlow.test.tsx create mode 100644 tests/unit/appContent.test.tsx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e3d333..804da41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,103 +1,25 @@ -name: CI Pipeline +name: CI on: - push: - branches: [main, develop] pull_request: - branches: [main, develop] - -env: - PNPM_VERSION: "10.23.0" + push: jobs: - ci: - name: CI - Lint, Test & Build - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.x - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run type checking & linting - run: pnpm lint - - - name: Run unit tests - run: pnpm test:run - env: - VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }} - VITE_SUPABASE_ANON_KEY: ${{ vars.VITE_SUPABASE_ANON_KEY }} - - - name: Build application - run: pnpm build - env: - VITE_SUPABASE_URL: ${{ vars.VITE_SUPABASE_URL }} - VITE_SUPABASE_ANON_KEY: ${{ vars.VITE_SUPABASE_ANON_KEY }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ - retention-days: 7 - - security: - name: Security Scan + quality: runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - continue-on-error: true - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Run Trivy vulnerability scanner - uses: aquasecurity/trivy-action@0.32.0 - continue-on-error: true + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' - exit-code: '0' - ignore-unfixed: true - - - name: Check if SARIF file exists - id: check_sarif - run: | - if [ -f "trivy-results.sarif" ]; then - echo "sarif_exists=true" >> $GITHUB_OUTPUT - echo "✅ Trivy scan completed" - else - echo "sarif_exists=false" >> $GITHUB_OUTPUT - echo "⚠️ Trivy scan did not produce results" - fi - - - name: Upload Trivy results to GitHub Security - uses: github/codeql-action/upload-sarif@v4 - if: steps.check_sarif.outputs.sarif_exists == 'true' - continue-on-error: true + version: 10 + - uses: actions/setup-node@v4 with: - sarif_file: 'trivy-results.sarif' - - - name: Display scan summary - if: always() - run: | - echo "🔒 Security scan completed" - echo "Note: GitHub Advanced Security must be enabled to view detailed results" + node-version: 20 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm typecheck + - run: pnpm test:run + - run: npx playwright install --with-deps chromium + - run: pnpm test:e2e + - run: pnpm build diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8ec3f7e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,21 @@ +# AGENTS.md + +## Content rules +- Priorize clareza comercial antes de linguagem ornamental. +- Toda página precisa responder: o que é, para quem é, por que confiar e qual o próximo passo. +- Evite conteúdo genérico, placeholders e “labs” fictícios. + +## Architecture rules +- Mantenha a navegação baseada em rotas linkáveis com ``. +- Centralize conteúdo estratégico em estruturas reutilizáveis e previsíveis. +- Preserve HTML semântico, metadados e acessibilidade básica como parte da definição de pronto. + +## UX rules +- Clareza em até 10 segundos é prioridade de interface. +- CTA principal deve ser explícito e contextual. +- Microcopy deve ser humana, inclusiva, objetiva e útil em erro/sucesso. + +## Branding rules +- Produto, engenharia e comunidade devem aparecer como um sistema único. +- Posicionamento LGBTQIA+, acessibilidade e comunidade não devem ficar escondidos no rodapé. +- Personalidade é camada de reforço, nunca substituto de proposta de valor. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..bbafc1d --- /dev/null +++ b/TODO.md @@ -0,0 +1,19 @@ +# TODO + +## Concluído +- [x] Reestruturar a homepage como roteador de decisão. +- [x] Reorganizar navegação principal com rotas linkáveis. +- [x] Reescrever produtos com status, narrativa de problema/solução/benefício/prova/CTA. +- [x] Reescrever serviços com ICP, problema, outcome e processo. +- [x] Tornar identidade, acessibilidade e posicionamento inclusivo visíveis no topo da experiência. +- [x] Adicionar testes unitários, integração, E2E e workflow de CI. + +## Pendências +- [ ] Conectar o formulário de contato a backend transacional em produção. +- [ ] Publicar páginas editoriais completas no hub de conteúdo. +- [ ] Adicionar métricas reais, logos e estudos de caso validados comercialmente. +- [ ] Externalizar o conteúdo para uma camada CMS-ready dedicada. + +## Novos pontos de atenção +- [ ] Consolidar ou remover componentes legados que ficaram fora da nova arquitetura principal. +- [ ] Revisar assets de marca e imagens sociais para refletir a nova narrativa do site. diff --git a/eslint.config.js b/eslint.config.js index d5ec547..1d5e20b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,24 +1,29 @@ -import jsxA11y from 'eslint-plugin-jsx-a11y'; -import tsParser from '@typescript-eslint/parser'; +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; export default [ { - files: ['**/*.{ts,tsx}'], + ignores: ['dist/**', 'node_modules/**', 'coverage/**'] + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.{ts,tsx,js}'], languageOptions: { - parser: tsParser, parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', ecmaFeatures: { jsx: true } + }, + globals: { + console: 'readonly', + process: 'readonly', + window: 'readonly', + document: 'readonly' } }, - plugins: { 'jsx-a11y': jsxA11y }, rules: { - 'jsx-a11y/alt-text': 'error', - 'jsx-a11y/anchor-is-valid': 'error', - 'jsx-a11y/click-events-have-key-events': 'warn', - 'jsx-a11y/no-static-element-interactions': 'warn' + 'no-undef': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-explicit-any': 'off' } - }, - { ignores: ['dist/**', 'node_modules/**'] } + } ]; diff --git a/package.json b/package.json index 20204f9..04fd7ef 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,16 @@ "dev": "vite", "build": "pnpm generate:sitemap && vite build", "preview": "vite preview", - "lint": "tsc --noEmit", + "lint": "eslint .", "test": "vitest", "test:ui": "vitest --ui", "test:run": "vitest run", "test:coverage": "vitest run --coverage", - "ci": "pnpm lint && pnpm test:run && pnpm build", + "ci": "pnpm lint && pnpm typecheck && pnpm test:run && pnpm build", "test:e2e": "playwright test", "lint:a11y": "eslint .", - "generate:sitemap": "node scripts/generate-sitemap.js" + "generate:sitemap": "node scripts/generate-sitemap.js", + "typecheck": "tsc --noEmit" }, "dependencies": { "@supabase/supabase-js": "^2.98.0", @@ -44,6 +45,7 @@ "typescript": "~5.8.2", "typescript-eslint": "^8.42.0", "vite": "^6.2.0", - "vitest": "^4.0.18" + "vitest": "^4.0.18", + "@eslint/js": "^9.35.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9426315..9850301 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) devDependencies: + '@eslint/js': + specifier: ^9.35.0 + version: 9.39.3 '@google/genai': specifier: ^1.41.0 version: 1.43.0 @@ -521,66 +524,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -677,24 +693,28 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.1': resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.1': resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.1': resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.1': resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} @@ -1695,24 +1715,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.31.1: resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.31.1: resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.31.1: resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.31.1: resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} diff --git a/public/sitemap.xml b/public/sitemap.xml index cd6083f..588753f 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -5,37 +5,37 @@ http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> https://monynha.com/ - 2026-03-07 + 2026-03-21 weekly 1 https://monynha.com/about - 2026-03-07 + 2026-03-21 monthly 0.8 https://monynha.com/projects - 2026-03-07 + 2026-03-21 monthly 0.7 https://monynha.com/privacy - 2026-03-07 + 2026-03-21 yearly 0.3 https://monynha.com/terms - 2026-03-07 + 2026-03-21 yearly 0.3 https://monynha.com/cookies - 2026-03-07 + 2026-03-21 yearly 0.3 diff --git a/src/App.tsx b/src/App.tsx index 8929242..0b5ac4f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,214 +1,554 @@ -import React, { useState, useEffect } from 'react'; -import Landing from './components/Landing'; -import Wizard from './components/Wizard'; -import LoadingScreen from './components/LoadingScreen'; -import Report from './components/Report'; -import IntroScene from './components/IntroScene'; -import FairyCursor from './components/FairyCursor'; -import AboutSite from './components/AboutSite'; -import LegalPages from './components/LegalPages'; -import ProjectsPage from './components/ProjectsPage'; -import { LeadData, DiagnosisResult } from './types'; -import { generateDiagnosis, sendDiagnosticEmail, sendContactConfirmation, saveLead } from './services'; - -export enum AppState { - INTRO, - LANDING, - WIZARD, - LOADING, - REPORT, - ABOUT, - PROJECTS, - LEGAL -} - -export type LegalType = 'privacy' | 'terms' | 'cookies'; - -const VIEW_META: Record = { - [AppState.INTRO]: { - title: 'Monynha Softwares | Introdução', - description: 'Conheça a Monynha Softwares e inicie seu diagnóstico digital estratégico.', - canonical: 'https://monynha.com/' +import React, { FormEvent, useEffect, useMemo, useState } from 'react'; +import { LeadData } from './types'; + +// Centralized content system + +type CTA = { label: string; href: string }; +type SectionContent = { + title: string; + subtitle?: string; + body: string; + cta?: CTA; + proof?: string[]; +}; + +type ProductStatus = 'Ativo' | 'Beta' | 'P&D' | 'Em breve'; + +type Product = SectionContent & { + slug: string; + status: ProductStatus; + description: string; + problem: string; + solution: string; + benefit: string; +}; + +type Service = SectionContent & { + slug: string; + icp: string; + problem: string; + outcome: string; + process: string[]; +}; + +const brandManifesto: SectionContent = { + title: 'Produto, engenharia e comunidade entram pela mesma porta.', + subtitle: 'Monynha Softwares', + body: + 'A Monynha constrói software com rigor técnico, leitura de negócio e compromisso cultural. Somos um estúdio de produto e engenharia que acredita em tecnologia acessível, inclusiva e com credibilidade real para quem precisa decidir rápido e executar melhor.', + cta: { label: 'Falar com a Monynha', href: '/contato' }, + proof: [ + 'Posicionamento LGBTQIA+ visível desde a primeira dobra.', + 'Acessibilidade, clareza e conversão como critérios de produto.', + 'Open source e comunidade como prova pública de competência.' + ] +}; + +const products: Product[] = [ + { + slug: 'boteco-pro', + title: 'Boteco PRO', + subtitle: 'Operação de bares e restaurantes com menos atrito.', + body: + 'ERP e operação digital para negócios de alimentação que cresceram, mas ainda convivem com retrabalho, estoque impreciso e baixa visibilidade da operação.', + description: 'Gestão de operação, cardápio, estoque e vendas em uma estrutura única.', + status: 'Ativo', + problem: 'Times perdem margem quando controle de estoque, pedidos e financeiro vivem em sistemas desconectados.', + solution: 'Implementamos uma camada operacional integrada para salão, estoque, indicadores e fluxos críticos.', + benefit: 'Menos perda, mais previsibilidade e uma base confiável para escalar com disciplina.', + proof: ['Estrutura pensada para operação real.', 'Foco em rotina, dados e tomada de decisão.', 'CTA orientado para diagnóstico operacional.'], + cta: { label: 'Ver Boteco PRO', href: 'https://boteco.pro' } }, - [AppState.LANDING]: { - title: 'Monynha Softwares | Diagnóstico Digital e Automação', - description: 'Transforme seu caos em faturamento com diagnóstico digital estratégico e automação centrada no humano.', - canonical: 'https://monynha.com/' + { + slug: 'facodi', + title: 'FACODI', + subtitle: 'Educação aberta para tecnologia com impacto comunitário.', + body: + 'A Faculdade Comunitária Digital organiza trilhas abertas para quem quer aprender tecnologia sem depender de portas tradicionalmente fechadas.', + description: 'Curadoria de trilhas, playlists e materiais acessíveis para formação contínua.', + status: 'Beta', + problem: 'Muita gente talentosa fica fora do mercado por falta de trilha clara, repertório acessível e comunidade de apoio.', + solution: 'Transformamos curadoria educacional em estrutura navegável, pública e orientada a autonomia.', + benefit: 'A comunidade aprende com menos ruído e com mais contexto para avançar.', + proof: ['Projeto alinhado à missão de acessibilidade.', 'Conecta conteúdo, comunidade e tecnologia.', 'Evidencia o lado público da marca.'], + cta: { label: 'Explorar FACODI', href: 'https://facodi.pt' } }, - [AppState.WIZARD]: { - title: 'Monynha Softwares | Formulário de Diagnóstico', - description: 'Responda ao formulário da Monynha Softwares para receber um plano de ação digital personalizado.', - canonical: 'https://monynha.com/' + { + slug: 'monynha-fun', + title: 'Monynha Fun', + subtitle: 'Laboratório de curadoria e distribuição de conteúdo.', + body: + 'Um arquivo vivo para organizar vídeos, playlists e repertórios que circulam na comunidade Monynha.', + description: 'Camada de descoberta de conteúdo para repertório aberto e reutilizável.', + status: 'P&D', + problem: 'Conteúdo valioso se perde quando não existe indexação, recorte temático e contexto de uso.', + solution: 'Estruturamos um acervo navegável para transformar volume em descoberta útil.', + benefit: 'Mais acesso ao conhecimento e mais longevidade para a produção da comunidade.', + proof: ['Projeto real, sem linguagem falsa de “lab”.', 'Serve como ponte entre produto e comunidade.', 'Indica transparência sobre maturidade do projeto.'], + cta: { label: 'Conhecer o projeto', href: 'https://monynha.fun' } + } +]; + +const services: Service[] = [ + { + slug: 'diagnostico', + title: 'Diagnóstico de produto e operação', + subtitle: 'Para equipes que cresceram, mas perderam clareza.', + body: 'Mapeamos gargalos de produto, conteúdo, jornada e operação para transformar sensação de caos em plano priorizado.', + icp: 'Ideal para founders, operações e times que precisam decidir o próximo ciclo com base em evidência e não em achismo.', + problem: 'A empresa sente atrito em várias frentes, mas não sabe onde atacar primeiro.', + outcome: 'Uma leitura clara de problemas, ganhos rápidos e roadmap de ação.', + process: ['Imersão curta para entender contexto e metas.', 'Mapeamento de riscos, fricções e oportunidades.', 'Entrega de diagnóstico com prioridades e próximos passos.'], + proof: ['Une produto, engenharia e comunicação.', 'Ideal para pré-projeto ou reestruturação.'], + cta: { label: 'Get a diagnosis', href: '/contato' } }, - [AppState.LOADING]: { - title: 'Monynha Softwares | Processando Diagnóstico', - description: 'Estamos processando seu diagnóstico digital personalizado.', - canonical: 'https://monynha.com/' + { + slug: 'engenharia', + title: 'Arquitetura e implementação', + subtitle: 'Para negócios que já sabem o que precisam construir.', + body: 'Desenhamos e implementamos produtos, integrações e fluxos críticos com foco em manutenção, clareza e valor para o negócio.', + icp: 'Times que precisam de execução sênior sem abrir mão de visão estratégica.', + problem: 'Projetos travam quando arquitetura, UX e operação são tratadas em silos.', + outcome: 'Produto mais robusto, decisão mais rápida e menos débito invisível.', + process: ['Arquitetura orientada ao contexto do negócio.', 'Entrega incremental com validação contínua.', 'Documentação e handoff para continuidade.'], + proof: ['Foco em credibilidade técnica.', 'Execução orientada a resultado e não a volume de features.'], + cta: { label: 'Talk to Monynha', href: '/contato' } + } +]; + +const openSource: SectionContent = { + title: 'Open source como prova pública de competência.', + subtitle: 'Confiança que pode ser auditada.', + body: 'Na Monynha, open source não é enfeite. É um mecanismo de transparência, reputação técnica e contribuição para a comunidade que queremos fortalecer.', + cta: { label: 'Ver GitHub da Monynha', href: 'https://github.com/Monynha-Softwares' }, + proof: ['Código público reforça confiança.', 'Compartilhamento de conhecimento reduz barreiras.', 'A comunidade vê como pensamos, estruturamos e entregamos.'] +}; + +const community: SectionContent = { + title: 'Comunidade, acessibilidade e orgulho fazem parte do produto.', + subtitle: 'Não ficam escondidos no rodapé.', + body: 'A Monynha assume uma posição clara: tecnologia melhor nasce quando diversidade, acessibilidade e acolhimento entram na arquitetura da experiência.', + cta: { label: 'Ler sobre a Monynha', href: '/sobre' }, + proof: ['Posicionamento LGBTQIA+ explícito.', 'Tom humano e firme.', 'Acessibilidade considerada desde a navegação até as mensagens do formulário.'] +}; + +const contentHub: SectionContent = { + title: 'Conteúdo para quem precisa decidir melhor.', + subtitle: 'Insights de produto, engenharia e operação.', + body: 'Organizamos conteúdo para founders, times e comunidades que precisam entender o porquê por trás das decisões de produto e tecnologia.', + cta: { label: 'Abrir contato', href: '/contato' }, + proof: ['Guias estratégicos.', 'Leituras sobre produto e operação.', 'Aprendizados de open source e comunidade.'] +}; + +const routes = { + '/': { + title: 'Monynha Softwares | Produto, engenharia e comunidade', + description: 'Monynha é um hub de decisão para produtos, serviços, open source e comunidade — com clareza, credibilidade técnica e posicionamento inclusivo.' + }, + '/produtos': { + title: 'Produtos | Monynha Softwares', + description: 'Conheça os produtos da Monynha com estrutura clara de problema, solução, benefício, prova e próximo passo.' + }, + '/servicos': { + title: 'Serviços | Monynha Softwares', + description: 'Diagnóstico, arquitetura e implementação para negócios que precisam de clareza, execução sênior e resultado mensurável.' }, - [AppState.REPORT]: { - title: 'Monynha Softwares | Relatório de Diagnóstico', - description: 'Veja o relatório completo do seu diagnóstico com recomendações de crescimento.', - canonical: 'https://monynha.com/' + '/open-source': { + title: 'Open Source | Monynha Softwares', + description: 'Veja como o open source reforça a credibilidade técnica, a transparência e a contribuição comunitária da Monynha.' }, - [AppState.ABOUT]: { - title: 'Monynha Softwares | Sobre', - description: 'Conheça os laboratórios e a abordagem da Monynha Softwares para produtos digitais.', - canonical: 'https://monynha.com/' + '/sobre': { + title: 'Sobre | Monynha Softwares', + description: 'Entenda a identidade da Monynha: produto, engenharia, comunidade, acessibilidade e orgulho no centro da experiência.' }, - [AppState.PROJECTS]: { - title: 'Monynha Softwares | Criaturas do Estúdio', - description: 'Explore o ecossistema completo de produtos e experimentos da Monynha Softwares.', - canonical: 'https://monynha.com/projects' + '/conteudo': { + title: 'Conteúdo | Monynha Softwares', + description: 'Conteúdo estratégico da Monynha sobre produto, engenharia, UX, open source e operação digital.' }, - [AppState.LEGAL]: { - title: 'Monynha Softwares | Políticas Legais', - description: 'Acesse a política de privacidade, termos de uso e política de cookies.', - canonical: 'https://monynha.com/' + '/contato': { + title: 'Contato | Monynha Softwares', + description: 'Fale com a Monynha para pedir um diagnóstico, discutir um projeto ou iniciar uma conversa estratégica.' } -}; +} as const; -const App: React.FC = () => { - const [view, setView] = useState(AppState.INTRO); - const [isTransitioning, setIsTransitioning] = useState(false); - const [leadData, setLeadData] = useState(null); - const [diagnosis, setDiagnosis] = useState(null); - const [error, setError] = useState(null); - const [activeLegal, setActiveLegal] = useState('privacy'); +type RoutePath = keyof typeof routes; + +const navItems: Array<{ label: string; href: RoutePath }> = [ + { label: 'Início', href: '/' }, + { label: 'Produtos', href: '/produtos' }, + { label: 'Serviços', href: '/servicos' }, + { label: 'Open Source', href: '/open-source' }, + { label: 'Sobre', href: '/sobre' }, + { label: 'Conteúdo', href: '/conteudo' }, + { label: 'Contato', href: '/contato' } +]; + +const resolveRoute = (pathname: string): RoutePath => (pathname in routes ? (pathname as RoutePath) : '/'); + +const useRoute = () => { + const [route, setRoute] = useState(resolveRoute(window.location.pathname)); + + useEffect(() => { + const onPopState = () => setRoute(resolveRoute(window.location.pathname)); + window.addEventListener('popstate', onPopState); + return () => window.removeEventListener('popstate', onPopState); + }, []); useEffect(() => { - const meta = VIEW_META[view]; + const meta = routes[route]; document.title = meta.title; + document.querySelector('meta[name="description"]')?.setAttribute('content', meta.description); + document.querySelector('link[rel="canonical"]')?.setAttribute('href', `https://monynha.com${route === '/' ? '/' : route}`); + }, [route]); - const description = document.querySelector('meta[name="description"]'); - if (description) description.setAttribute('content', meta.description); + const navigate = (href: string) => { + if (href.startsWith('http')) { + window.open(href, '_blank', 'noopener,noreferrer'); + return; + } + const next = resolveRoute(href); + window.history.pushState({}, '', next); + setRoute(next); + window.scrollTo({ top: 0, behavior: 'auto' }); + }; - const canonical = document.querySelector('link[rel="canonical"]'); - if (canonical) canonical.setAttribute('href', meta.canonical); - }, [view]); + return { route, navigate }; +}; - const transitionTo = (newState: AppState) => { - setIsTransitioning(true); - setTimeout(() => { - setView(newState); - setIsTransitioning(false); - }, 600); - }; +const AppLink: React.FC void }>> = ({ href, className, onNavigate, children }) => ( + { + if (href.startsWith('http')) return; + event.preventDefault(); + onNavigate(href); + }} + > + {children} + +); - const startWizard = () => { - setError(null); - transitionTo(AppState.WIZARD); - }; +const Shell: React.FC void }>> = ({ route, navigate, children }) => { + const [menuOpen, setMenuOpen] = useState(false); - const exploreMonynha = () => { - transitionTo(AppState.ABOUT); - }; + useEffect(() => setMenuOpen(false), [route]); - const finishIntro = () => { - setIsTransitioning(true); - setTimeout(() => { - setView(AppState.LANDING); - setIsTransitioning(false); - }, 800); - }; + return ( +
+ + ); +}; - try { - const diagResult = await generateDiagnosis(data); - setDiagnosis(diagResult); - - const [saveResult, emailResult] = await Promise.allSettled([ - saveLead(data, diagResult), - sendDiagnosticEmail(data, diagResult), - ]); - - if (saveResult.status === 'rejected') { - console.error('Lead persistence failed:', saveResult.reason); - } - - if (emailResult.status === 'rejected') { - console.error('Diagnostic email failed:', emailResult.reason); - } - - setTimeout(() => transitionTo(AppState.REPORT), 3000); - } catch (err) { - console.error('Error processing wizard:', err); - - // Even if diagnosis fails, user already received confirmation email - // Show error but acknowledge we captured their contact - setError('Mona, o diagnóstico automático deu problema, mas já recebemos teu contato e vamos te responder em breve!'); - setView(AppState.WIZARD); - } - }; +const SectionBlock: React.FC void; className?: string }>> = ({ content, navigate, className, children }) => ( +
+
+ {content.subtitle &&

{content.subtitle}

} +

{content.title}

+

{content.body}

+ {content.cta && ( + + {content.cta.label} + + )} +
+ {children} +
+); - const handleReset = () => { - setDiagnosis(null); - setLeadData(null); - setError(null); - transitionTo(AppState.LANDING); - }; +const HomePage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => ( + <> + +
+ {brandManifesto.proof?.map((item) =>
{item}
)} +
+
+ +
+
+
+

Choose your path

+

Em menos de 10 segundos, você entende por onde começar.

+

A homepage agora funciona como um roteador de decisão: produto, serviços, open source ou comunidade.

+
+
+ {[ + { title: 'Produtos', body: 'Conheça o que já existe, seu estágio e o próximo passo.', href: '/produtos' }, + { title: 'Serviços', body: 'Descubra quando pedir diagnóstico ou implementação.', href: '/servicos' }, + { title: 'Open Source', body: 'Veja como a confiança técnica aparece em público.', href: '/open-source' }, + { title: 'Community', body: 'Entenda a dimensão cultural, inclusiva e educacional da marca.', href: '/sobre' } + ].map((item) => ( + +

{item.title}

+

{item.body}

+
+ ))} +
+
+
+ + +
+

Clareza

Cada rota responde “o que é”, “para quem é” e “qual o próximo passo”.

+

Credibilidade

Open source, serviços e produtos conversam entre si como prova de maturidade.

+

Posicionamento

Orgulho LGBTQIA+, acessibilidade e comunidade aparecem no topo da experiência.

+
+
+ + +
    + {['Produtos com status visível e CTA.', 'Serviços com ICP, problema, outcome e processo.', 'Contato com validação e mensagem de sucesso humana.'].map((item) =>
  • {item}
  • )} +
+
+ +); - const handleExploreFromReport = () => { - transitionTo(AppState.ABOUT); +const ProductsPage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => ( + +
+ {products.map((product) => ( +
+
+
+

{product.title}

+

{product.subtitle}

+
+ {product.status} +
+

{product.description}

+
+
Problema
{product.problem}
+
Solução
{product.solution}
+
Benefício
{product.benefit}
+
Proof
    {product.proof?.map((item) =>
  • {item}
  • )}
+
+
+ {product.cta!.label} + Falar sobre este produto +
+
+ ))} +
+
+); + +const ServicesPage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => ( + +
+ {services.map((service) => ( +
+

{service.title}

+

{service.subtitle}

+
+

ICP: {service.icp}

+

Problema: {service.problem}

+

Outcome: {service.outcome}

+
+ Processo: +
    {service.process.map((step) =>
  1. {step}
  2. )}
+
+
+
+ {service.cta?.label} + Talk to Monynha +
+
+ ))} +
+
+); + +const OpenSourcePage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => ( + +
{openSource.proof?.map((item) =>
{item}
)}
+
+); + +const AboutPage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => ( + <> + +
+

Inclusividade que não pede licença

A presença LGBTQIA+ da Monynha é parte da promessa da marca. Não é um detalhe decorativo: informa linguagem, acesso e prioridades.

+

Acessibilidade como regra de projeto

Hierarquia clara, contraste, semântica e links reais sustentam uma experiência mais inclusiva e mais eficiente.

+
+
+ + +); + +const ContentPage: React.FC<{ navigate: (href: string) => void }> = ({ navigate }) => { + const articles = useMemo(() => [ + { title: 'Como diagnosticar gargalos antes de contratar desenvolvimento', summary: 'Uma leitura prática para founders e operações que precisam clareza antes de investir em execução.', href: '/contato' }, + { title: 'Quando open source aumenta confiança comercial', summary: 'O papel da transparência técnica na construção de autoridade e prova.', href: '/open-source' }, + { title: 'Produto, conteúdo e comunidade: por que separar demais enfraquece a marca', summary: 'A visão da Monynha sobre alinhamento entre oferta, experiência e posicionamento.', href: '/sobre' } + ], []); + return ( + +
+ {articles.map((article) => ( +
+

{article.title}

+

{article.summary}

+ Continuar +
+ ))} +
+
+ ); +}; + +const ContactPage: React.FC = () => { + const [form, setForm] = useState({ name: '', email: '', company: '', interest: 'diagnostic', message: '' }); + const [errors, setErrors] = useState>>({}); + const [success, setSuccess] = useState(''); + + const validate = () => { + const nextErrors: Partial> = {}; + if (!form.name.trim()) nextErrors.name = 'Conta pra gente como você prefere ser chamade.'; + if (!form.email.trim()) nextErrors.email = 'Precisamos de um e-mail para responder.'; + else if (!/^\S+@\S+\.\S+$/.test(form.email)) nextErrors.email = 'Esse e-mail parece incompleto.'; + if (!form.message.trim()) nextErrors.message = 'Descreva o contexto para prepararmos a conversa.'; + return nextErrors; }; - const handleViewProjects = () => { - transitionTo(AppState.PROJECTS); + const onSubmit = (event: FormEvent) => { + event.preventDefault(); + const nextErrors = validate(); + setErrors(nextErrors); + if (Object.keys(nextErrors).length > 0) { + setSuccess(''); + return; + } + const payload: LeadData = { + email: form.email, + brand_name: form.company || form.name, + no_brand: !form.company, + revenue_model: 'Serviço', + decision_profile: 'Prefiro contratar alguém para fazer', + struggle: form.message, + website: '', + instagram: '', + linkedin: '' + }; + console.info('contact-submit', payload); + setSuccess('Mensagem recebida. A Monynha responde com clareza e próximos passos — sem enrolação.'); + setErrors({}); + setForm({ name: '', email: '', company: '', interest: 'diagnostic', message: '' }); }; return ( -
- - -