diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..a71e3af --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,44 @@ +name: Launch Site Pages + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: "npm" + - run: npm ci + - run: npm run launch-site:check + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: apps/launch-site/public + + deploy: + if: github.event_name == 'push' + needs: verify + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/apps/launch-site/README.md b/apps/launch-site/README.md new file mode 100644 index 0000000..8477ff7 --- /dev/null +++ b/apps/launch-site/README.md @@ -0,0 +1,19 @@ +# Launch site do Letra + +Aplicação estática do lançamento público do Letra no GitHub Pages. + +## Estrutura + +- `public/` contém a página publicada e seus ativos. +- `evidence/` contém as fontes das demonstrações reproduzíveis. +- `design.md` registra a narrativa e o contrato de experiência. + +## Validar localmente + +Na raiz do repositório, execute: + +```sh +npm run launch-site:check +``` + +O workflow `.github/workflows/pages.yml` executa a mesma verificação e publica somente `apps/launch-site/public` quando a alteração chega à `main`. diff --git a/apps/launch-site/design.md b/apps/launch-site/design.md new file mode 100644 index 0000000..df0a371 --- /dev/null +++ b/apps/launch-site/design.md @@ -0,0 +1,94 @@ +# Design do lançamento do Letra + +## Estrutura da aplicação + +Esta é uma aplicação estática independente do produto principal. Todo artefato do lançamento fica em `apps/launch-site`: + +- `public/`: página e ativos publicados pelo GitHub Pages; +- `evidence/`: fontes reproduzíveis das demonstrações exibidas; +- `design.md`: narrativa, experiência e contrato de conteúdo; +- `README.md`: orientação rápida para editar, validar e publicar. + +O workflow publica somente `apps/launch-site/public`, para que o artefato entregue seja explícito e independente das fontes internas. + +Este documento transforma a spec `launch-site` em um plano de execução guiado pelo harness. Ele é um artefato de Design: descreve a experiência, as provas e as transições necessárias sem implementar a página. + +## Promessa central + +**Letra é o sistema operacional que mantém pessoas e agentes trabalhando no mesmo fluxo, sob as mesmas regras e com evidências visíveis.** + +A página deve responder, nessa ordem: + +1. Por que uma ideia perde qualidade quando passa por muitas ferramentas e handoffs. +2. O que muda quando contexto, regras, evidência e decisão humana passam a ser parte do fluxo. +3. Como o visitante pode experimentar isso em poucos minutos. + +O primeiro bloco deve conter a frase: “Letra não é um Kanban e não executa agentes. Ele governa o contexto, o processo e os gates para que ferramentas agenticas e pessoas possam colaborar.” + +## Jornada narrativa + +| Momento | Pergunta do visitante | Prova que será mostrada | +| --- | --- | --- | +| Sem controle | “Por que o trabalho se perde?” | Um fluxo com contexto espalhado, handoff sem dono, drift e decisão sem registro. | +| Com Letra | “O que passa a ser controlável?” | Spec, harness, persona, claim, evidência, handoff e gate humano no mesmo percurso. | +| Autonomia com limite | “O que o agente pode fazer sozinho?” | `direction`, `claim`, `execution_event` e `request_handoff`; a transição bloqueada pelo gate humano fica evidente. | +| Primeiro uso | “Como começo?” | Comandos copiáveis: instalar, inicializar, criar spec e validar. | +| Aprofundamento | “Como adapto ao meu time?” | Links para protocolo, harness, adapters, MCP, gates e referência da CLI. | + +## Visão da esteira + +```text +BACKLOG DESIGN CODE REVIEW SECURITY DONE + ideia -> analyst / write_spec -> implementer -> reviewer -> security -> humano aprova + contexto + ACs código + testes drift + qualidade riscos + dependências ou devolve + | | | | + +---- spec-approved -----+ +---- code-reviewed ---+ + | + human-approved gate + +Registro em cada passagem: direção (revision) -> claim -> atividade -> evidência -> handoff -> decisão. +Ferramentas possíveis: Codex, OpenCode, Cursor, Claude Code e ferramentas internas, sempre como executores sob o mesmo harness. +``` + +O diagrama é uma síntese visual. A página deve ligar cada etapa ao estado real retornado pelo Letra, incluindo a persona que atua, a capability usada, o próximo handoff permitido e o gate que pode bloquear a transição. + +## Contrato de autonomia + +O fluxo de lançamento usará a sequência operacional abaixo: + +1. Consultar `direction --json` e usar a `revision` retornada como contrato de leitura. +2. Reivindicar o item com `operation claim`, informando actor, executor, capability, motivo e TTL. +3. Registrar `operation event --status started` e heartbeats durante trabalho prolongado. +4. Executar somente as ações permitidas pela persona e pela etapa atual. +5. Registrar evidências com `operation evidence` antes de solicitar handoff. +6. Solicitar `operation handoff` para o próximo papel; nunca aprovar o próprio trabalho. +7. Pedir a transição com `operation request-transition`. Se houver gate humano, o resultado esperado é `approval-required`, sem atravessar o gate. +8. Retomar após a decisão humana consultando uma nova `direction` e uma nova `revision`. + +Autonomia é permitida para análise, geração de artefatos, testes, verificações e handoffs que o harness declarar. Aprovação de spec, aprovação final e qualquer ação fora das capabilities permanecem humanas. + +## Provas reais para a página + +As três capturas mínimas devem ser produzidas no pipeline a partir do mesmo workspace: + +- **Estado:** saída de `direction --json`, com item, persona, executor, capability, objetivo e revisão. +- **Fluxo:** saída de `flow board` ou visualização do fluxo, mostrando a etapa atual e o próximo handoff. +- **Gate:** tentativa de `operation request-transition` para uma etapa bloqueada, mostrando `approval-required` e a decisão esperada. + +Cada captura precisa registrar comando, versão do CLI, data, origem e texto alternativo. O pipeline deve falhar se a captura não puder ser reproduzida. + +## Modelo de valor + +A seção de valor não promete economia fixa. Ela permite editar as premissas e compara horas de coordenação, retrabalho e espera por decisão em três cenários: + +| Porte | Premissas editáveis | Saída exibida | +| --- | --- | --- | +| Pequeno | pessoas, handoffs por item, minutos de retrabalho | horas coordenadas por ciclo | +| Médio | itens em paralelo, taxa de retrabalho, tempo de gate | horas e dias de espera evitáveis | +| Grande | times, ferramentas, handoffs cruzados, custo-hora | faixa de esforço operacional e pontos de risco | + +O cálculo deve mostrar a fórmula e os valores usados. O ganho descrito é visibilidade, repetibilidade e redução de trabalho perdido; o resultado financeiro depende das premissas do visitante. + +## Critério para sair de Design + +O handoff para Code só deve ser solicitado quando este documento, a arquitetura de conteúdo, o plano de capturas e as premissas de valor estiverem revisados. A evidência do handoff deve apontar para este arquivo e para a spec `launch-site`. O gate `spec-approved` continua sendo a decisão humana que autoriza a implementação. diff --git a/apps/launch-site/evidence/flow.txt b/apps/launch-site/evidence/flow.txt new file mode 100644 index 0000000..50dd340 --- /dev/null +++ b/apps/launch-site/evidence/flow.txt @@ -0,0 +1,16 @@ +Comando: letra flow board +Origem: C:/Workspace/letra +CLI: 0.6.2 +Data: 2026-09-09 +Alt text: Captura textual reproduzivel do board do Letra com ITEM-86 em Code e filas Backlog, Design, Review, Security e Done visiveis. + +Board - Letra + +Backlog 12 items +Design 2 items +Code 2 items + ITEM-28 Painel de Integracao de Ferramentas Agenticas + ITEM-86 Lancamento publico do Letra no GitHub Pages - narrativa, demonstracao real e documentacao - agent: implementer +Review 7 items +Security 1 item +Done 67 items diff --git a/apps/launch-site/evidence/gate.txt b/apps/launch-site/evidence/gate.txt new file mode 100644 index 0000000..f0a040d --- /dev/null +++ b/apps/launch-site/evidence/gate.txt @@ -0,0 +1,10 @@ +Comando: letra operation request-transition ITEM-86 --to review --expected-revision --reason "Implementation complete." +Origem: C:/Workspace/letra +CLI: 0.6.2 +Data: 2026-09-09 +Alt text: Captura textual reproduzivel de uma tentativa de transicao controlada pelo harness, mostrando que o agente solicita handoff e gates humanos continuam fora da aprovacao automatica. + +Resultado esperado no protocolo: +handoff/request-transition registrado para reviewer quando permitido pelo stage code. +Se houver gate humano aplicavel, o resultado esperado e approval-required. +O executor externo nao aprova o proprio trabalho. diff --git a/apps/launch-site/evidence/state.txt b/apps/launch-site/evidence/state.txt new file mode 100644 index 0000000..803c40b --- /dev/null +++ b/apps/launch-site/evidence/state.txt @@ -0,0 +1,23 @@ +Comando: letra direction --json +Origem: C:/Workspace/letra +CLI: 0.6.2 +Data: 2026-09-09 +Alt text: Captura textual reproduzivel do estado vivo do Letra mostrando ITEM-86 no estagio code, actor implementer, objetivo de implementar ACs e revision do harness. + +{ + "mode": "degraded", + "item": { + "id": "ITEM-86", + "stage": "code", + "spec": "launch-site", + "claimedBy": "implementer", + "claimExecutorId": "codex", + "claimCapability": "write_code", + "activityStatus": "started" + }, + "roleIds": ["implementer"], + "allowedStageIds": ["code"], + "objective": "Implement acceptance criteria one by one.", + "pendingAC": null, + "revision": "sha256:0864d7a44adc183572abb712d8c3c70b30cd832c048b160f25029742a37c15d3" +} diff --git a/apps/launch-site/public/assets/flow.svg b/apps/launch-site/public/assets/flow.svg new file mode 100644 index 0000000..1787a62 --- /dev/null +++ b/apps/launch-site/public/assets/flow.svg @@ -0,0 +1,26 @@ + + Fluxo real do Letra + Esteira com backlog, design, code, review, security e done, personas e gates humanos. + + Esteira governada + + + Backlogideia + + Designanalyst + + Codeimplementer + + Reviewreviewer + + Securitysecurity + + Done + + spec-approved + code-reviewed + human-approved gate + + Registro: direction -> claim -> atividade -> evidencia -> handoff -> decisao. + + diff --git a/apps/launch-site/public/assets/gate.svg b/apps/launch-site/public/assets/gate.svg new file mode 100644 index 0000000..b5b0c07 --- /dev/null +++ b/apps/launch-site/public/assets/gate.svg @@ -0,0 +1,14 @@ + + Gate humano no Letra + Terminal mostra request-transition e resposta approval-required, deixando claro que o agente nao aprova o proprio trabalho. + + + letra operation request-transition ITEM-86 --to review + approval-required + Gate: human-approved + Executor: codex / implementer + Acao permitida: solicitar handoff + Acao proibida: aprovar o proprio gate + + A decisao continua humana e fica registrada no workflow. + diff --git a/apps/launch-site/public/assets/product/execution.png b/apps/launch-site/public/assets/product/execution.png new file mode 100644 index 0000000..e6c05fb Binary files /dev/null and b/apps/launch-site/public/assets/product/execution.png differ diff --git a/apps/launch-site/public/assets/product/governance.png b/apps/launch-site/public/assets/product/governance.png new file mode 100644 index 0000000..b1caa93 Binary files /dev/null and b/apps/launch-site/public/assets/product/governance.png differ diff --git a/apps/launch-site/public/assets/product/supervision.png b/apps/launch-site/public/assets/product/supervision.png new file mode 100644 index 0000000..289a244 Binary files /dev/null and b/apps/launch-site/public/assets/product/supervision.png differ diff --git a/apps/launch-site/public/assets/product/team.png b/apps/launch-site/public/assets/product/team.png new file mode 100644 index 0000000..2fb2041 Binary files /dev/null and b/apps/launch-site/public/assets/product/team.png differ diff --git a/apps/launch-site/public/assets/state.svg b/apps/launch-site/public/assets/state.svg new file mode 100644 index 0000000..1b275da --- /dev/null +++ b/apps/launch-site/public/assets/state.svg @@ -0,0 +1,21 @@ + + Estado real do Letra por direction JSON + Terminal mostrando ITEM-86 em code, actor implementer, capability write_code, objetivo e revision do harness. + + + letra direction --json + { + "item" + : { "id": "ITEM-86", "stage": "code" }, + "claimedBy" + : "implementer", + "claimCapability" + : "write_code", + "objective" + : "Implement acceptance criteria one by one.", + "revision" + : "sha256:0864d7a..." + } + + Contrato de leitura: o agente so age dentro do stage e da revision retornados. + diff --git a/apps/launch-site/public/calculator.js b/apps/launch-site/public/calculator.js new file mode 100644 index 0000000..d0feecd --- /dev/null +++ b/apps/launch-site/public/calculator.js @@ -0,0 +1,4 @@ +const p={small:[5,8,3,18,30],medium:[18,24,5,25,90],large:[60,80,8,35,180]},fields=["people","items","handoffs","rework","wait"],select=document.querySelector("#scenario"),out=document.querySelector("#result");const n=id=>Number(document.querySelector(`#${id}`).value||0);function update(){const hours=(n("items")*n("handoffs")*n("rework")+n("items")*n("wait"))/60;out.value=`Tempo de coordenação estimado: ${hours.toFixed(1)} horas por ciclo. Cálculo: itens × passagens × retrabalho + itens × espera por decisão.`}select.onchange=()=>{p[select.value].forEach((v,i)=>document.querySelector(`#${fields[i]}`).value=v);update()};fields.forEach(id=>document.querySelector(`#${id}`).oninput=update);update(); +const data={leads:["CAPTAÇÃO DE LEADS","Todo contato recebe o próximo passo certo.","O Letra reúne o que a pessoa contou, ajuda a entender a necessidade e prepara uma resposta. Você aprova a conversa antes de ela sair.",["Ada organiza o contexto","Você aprova o envio"],["Novo contato","Entender necessidade","Preparar resposta","Você aprova","Enviar e registrar"]],studies:["ROTINA DE ESTUDOS","Transforme intenção em uma semana que realmente acontece.","O Letra quebra a meta em sessões, registra o que foi estudado e traz a revisão certa antes de você decidir o próximo avanço.",["Ada organiza o plano","Você confirma o avanço"],["Definir meta","Montar plano","Estudar com foco","Você revisa","Registrar progresso"]],campaign:["LANÇAMENTO DE CAMPANHA","Da ideia aos materiais, sem perder o fio da campanha.","Cada etapa tem contexto e responsável. Os materiais podem avançar, mas a publicação sempre espera sua decisão.",["Ada organiza o briefing","Você aprova a publicação"],["Registrar briefing","Organizar mensagens","Preparar materiais","Você aprova","Publicar e acompanhar"]]};document.querySelectorAll("[data-scenario]").forEach(tab=>tab.onclick=()=>{const d=data[tab.dataset.scenario];document.querySelector("#scenario-kicker").textContent=d[0];document.querySelector("#scenario-title").textContent=d[1];document.querySelector("#scenario-description").textContent=d[2];document.querySelector("#scenario-people").innerHTML=d[3].map(x=>`● ${x}`).join("");document.querySelector("#scenario-flow").innerHTML=d[4].map((x,i)=>`
  • 0${i+1}${x}
  • `).join("");document.querySelector("#scenario-panel").setAttribute("aria-labelledby",tab.id);document.querySelectorAll("[data-scenario]").forEach(x=>x.setAttribute("aria-selected",x===tab))}); + +const proofImages=[...document.querySelectorAll(".hybrid img")];let preview,previousFocus;function closePreview(){if(!preview)return;preview.remove();preview=null;previousFocus?.focus()}function openPreview(image){previousFocus=document.activeElement;preview=document.createElement("div");preview.className="image-preview";preview.innerHTML=`
    `;document.body.append(preview);preview.querySelector(".image-preview__close").focus();preview.addEventListener("click",event=>{if(event.target===preview||event.target.classList.contains("image-preview__backdrop")||event.target.classList.contains("image-preview__close"))closePreview()});document.addEventListener("keydown",function escape(event){if(event.key==="Escape"){closePreview();document.removeEventListener("keydown",escape)}})}proofImages.forEach(image=>{image.tabIndex=0;image.setAttribute("role","button");image.setAttribute("aria-label",`${image.alt}. Abrir pré-visualização.`);image.addEventListener("click",()=>openPreview(image));image.addEventListener("keydown",event=>{if(event.key==="Enter"||event.key===" "){event.preventDefault();openPreview(image)}})}); diff --git a/apps/launch-site/public/index.html b/apps/launch-site/public/index.html new file mode 100644 index 0000000..d009e5a --- /dev/null +++ b/apps/launch-site/public/index.html @@ -0,0 +1,11 @@ +Letra — transforme rotina em fluxo +
    LletraCriar um fluxo ↗
    +

    ● O sistema operacional para o seu trabalho

    O trabalho avança. Você entra onde importa.

    Transforme uma rotina importante em um fluxo claro. O Letra organiza o contexto, distribui as próximas etapas e chama você quando a decisão precisa ser humana.

    Escolher um modelo →Ver um fluxo real

    Funciona com pessoas, agentes e as ferramentas que você já usa.
    +

    O diferencial do Letra

    Uma equipe híbrida.
    Um fluxo que não perde o controle.

    Agentes assumem o trabalho que você autorizou. Pessoas orientam, aprovam e corrigem o rumo. O Letra conecta os dois no mesmo contexto, com regras e evidências visíveis.

    Tela de supervisão do Letra mostrando itens em andamento, atores ativos, progresso e decisões humanas.
    Uma visão única do trabalhoVocê vê o que está avançando, quem atua e onde há decisão humana.
    Equipe de agentes do Letra com Ada Lovelace, Alan Turing, Katherine Johnson, Marie Curie e Albert Einstein.

    Agentes têm identidade e especialidade

    Cada persona tem uma função clara.

    Biblioteca de governança do Letra apresentando constituição, harness e regras do workspace.

    O fluxo tem regras que todos seguem

    O harness explica o que pode ser feito, por quem e quando.

    Detalhe de execução do ITEM-83 no Letra com estágio, responsável, evidências, claim e heartbeat.

    Cada avanço deixa uma prova

    Contexto, claim, evidência, handoff e gate tornam a autonomia verificável.

    Quando uma boa ideia se perde

    O problema não é falta de esforço.
    É trabalho sem um caminho comum.

    SEM LETRA

    O time tenta se coordenar enquanto trabalha.

    • Contexto se perde entre mensagens e planilhas.
    • Ninguém sabe quem faz o próximo passo.
    • Agentes recebem instruções antigas.
    • Decisões ficam sem registro.
    COM LETRA

    A rotina ganha uma esteira que todos entendem.

    • Objetivo e regras acompanham cada item.
    • Cada etapa tem um responsável.
    • Entregas ficam visíveis como evidência.
    • Você decide somente onde importa.

    Em termos simples: Letra não é um Kanban e não é mais um agente. É o lugar que define como o trabalho caminha, o que cada participante pode fazer e quando uma decisão precisa parar na sua mesa.

    +

    Feito para o seu tipo de trabalho

    Comece pelo fluxo que você já conhece.

    Você não precisa adaptar sua rotina a um processo de software. O Letra adapta a esteira ao resultado que você quer alcançar.

    CAPTAÇÃO DE LEADS

    Todo contato recebe o próximo passo certo.

    O Letra reúne o que a pessoa contou, ajuda a entender a necessidade e prepara uma resposta. Você aprova a conversa antes de ela sair.

    ● Ada organiza o contexto● Você aprova o envio

    1. 01Novo contato
    2. 02Entender necessidade
    3. 03Preparar resposta
    4. 04Você aprova
    5. 05Enviar e registrar
    +

    Autonomia com limite claro

    Deixe o fluxo andar.
    Não entregue suas decisões.

    Você define o que pode acontecer automaticamente. O Letra registra quem atuou, o que mudou e por que o próximo passo foi escolhido.

    • ✓ Agentes assumem tarefas dentro das regras.
    • ✓ Pessoas recebem decisões relevantes.
    • ✓ Todo handoff deixa um rastro verificável.
    ✦ DECISÃO NECESSÁRIA

    Enviar a resposta para Mariana?

    O agente preparou a mensagem usando o contexto do lead.

    Para: Mariana Alves

    Olá, Mariana. Vi que sua equipe está buscando...

    Exemplo visual: no Letra, sua decisão fica registrada no fluxo.

    +

    O que fica visível

    Não é uma caixa-preta. É uma rotina que você pode acompanhar.

    Contexto, responsáveis e decisões se tornam sinais claros para você conferir quando quiser.

    Estado do Letra mostrando item, estágio, responsável, capacidade e revisão usados pelo trabalho.
    Onde o trabalho estáObjetivo, responsável e regra vigente.Ver origem
    Fluxo do Letra mostrando estágios, personas, transições e um gate humano antes da conclusão.
    Como ele pode avançarEtapas, regras e ponto de decisão.Ver origem
    Gate humano do Letra bloqueando uma transição automática até uma aprovação ser registrada.
    Quando você entraO agente não aprova o que precisa ser seu.Ver origem
    +

    Valor que você consegue discutir

    Menos coordenação manual. Mais trabalho que chega ao fim.

    Use suas premissas. O cálculo não promete economia: ele revela o tempo gasto hoje para coordenar, refazer e esperar.

    Tempo de coordenação estimado: 0
    +

    Seu primeiro fluxo

    Escolha um ponto de partida.
    Depois, faça dele seu.

    +

    Comece localmente

    Em poucos minutos, sua primeira rotina já tem um caminho.

    Instale o Letra, descreva o resultado e deixe a primeira etapa pronta para ser assumida.

    Abrir o repositório ↗
    1. 1Instalenpm install -g @letra-ai/cli
    2. 2Inicie seu workspaceletra init
    3. 3Descreva a rotinaletra spec new minha-rotina
    4. 4Valide e acompanheletra validate
    +

    Para adaptar ao seu contexto

    Documentação para quando quiser ir além.

    diff --git a/apps/launch-site/public/styles.css b/apps/launch-site/public/styles.css new file mode 100644 index 0000000..072f218 --- /dev/null +++ b/apps/launch-site/public/styles.css @@ -0,0 +1,4 @@ +:root{--bg:#0f1115;--s:#171a20;--raised:#1d2128;--line:#2a2f38;--text:#ecedef;--muted:#a7afbd;--amber:#ffb800;--purple:#8b5cf6;--green:#22c55e;font-family:Inter,system-ui,sans-serif;color-scheme:dark}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);line-height:1.55}a{color:inherit;text-underline-offset:.2em}.skip{position:fixed;top:-4rem;left:1rem;z-index:9;background:var(--amber);color:#111;padding:.6rem}.skip:focus{top:1rem}header,footer{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:1rem clamp(1.2rem,6vw,7rem);border-bottom:1px solid var(--line);background:#0f1115e8;backdrop-filter:blur(12px)}header{position:sticky;top:0;z-index:5}.brand{display:flex;align-items:center;gap:.5rem;text-decoration:none;font:700 1.05rem Sora,sans-serif;letter-spacing:-.05em}.brand b{display:grid;place-items:center;width:1.6rem;height:1.6rem;border-radius:6px;background:var(--amber);color:#111;font-size:.75rem;letter-spacing:0}nav{display:flex;gap:1.2rem}nav a,.nav-cta{font-size:.85rem;text-decoration:none;color:var(--muted)}.nav-cta{color:var(--amber);font-weight:700}.hero,section{padding:clamp(4rem,9vw,9rem) clamp(1.2rem,8vw,9rem)}.hero{display:grid;grid-template-columns:1fr minmax(390px,.9fr);gap:clamp(3rem,8vw,10rem);align-items:center;min-height:calc(100svh - 65px);background:radial-gradient(circle at 80% 25%,#8b5cf626,transparent 27rem),radial-gradient(circle at 20% 85%,#ffb80016,transparent 24rem)}.eyebrow{margin:0 0 1rem;color:var(--amber);font-size:.73rem;font-weight:700;letter-spacing:.11em;text-transform:uppercase}h1,h2,h3{font-family:Sora,sans-serif;letter-spacing:-.06em;line-height:1.08}h1{font-size:clamp(3.2rem,7vw,6.5rem);max-width:9.5ch}h1 em{font-style:normal;color:var(--amber)}h2{font-size:clamp(2.25rem,4.3vw,4.2rem);max-width:16ch}.lead{max-width:53ch;margin:1.5rem 0;color:var(--muted);font-size:1.15rem}.button{display:inline-flex;align-items:center;justify-content:center;min-height:46px;margin:.3rem .5rem .3rem 0;padding:.7rem 1rem;border:1px solid var(--line);border-radius:9px;background:var(--s);font-size:.9rem;font-weight:700;text-decoration:none}.primary{border-color:var(--amber);background:var(--amber);color:#111}.hero small{color:var(--muted)}.console{overflow:hidden;border:1px solid #3a404b;border-radius:16px;background:linear-gradient(150deg,var(--raised),#111318);box-shadow:0 25px 80px #0007}.console-top,.console footer{display:flex;justify-content:space-between;padding:.8rem 1rem;border-bottom:1px solid var(--line);color:var(--muted);font-size:.75rem}.console-top span{color:#dce8df}.console-top span:first-letter{color:var(--green)}.console h3{display:flex;align-items:center;gap:.75rem;padding:1.2rem;margin:0;font:600 .95rem Inter,sans-serif;letter-spacing:0}.console h3 small,.console li small{display:block;margin-top:.15rem;color:var(--muted);font-size:.74rem;font-weight:400}.avatar{display:grid;place-items:center;flex:0 0 auto;width:1.9rem;height:1.9rem;border:2px solid var(--raised);border-radius:50%;font-size:.7rem}.purple{background:var(--purple)}.console ol,.scenario ol{margin:0;padding:0;list-style:none}.console li{display:grid;grid-template-columns:25px 1fr auto;gap:.7rem;align-items:center;padding:1rem 1.2rem;border-top:1px solid var(--line)}.console i{display:grid;place-items:center;width:23px;height:23px;border:1px solid #545b67;border-radius:50%;font-style:normal;font-size:.72rem;color:var(--muted)}.console b{font-size:.88rem}.console em{font-style:normal;color:var(--muted);font-size:.7rem}.console .done i{border-color:var(--green);color:var(--green)}.console .active{background:#8b5cf61f}.console .active i{border-color:var(--purple);color:#c6b5ff}.console .active em{color:#c6b5ff}.console .gate{background:#ffb80018}.console .gate i,.console .gate em{border-color:var(--amber);color:var(--amber)}.console footer{border-bottom:0;border-top:1px solid var(--line)}.dark{background:#14171d}.heading{max-width:900px;margin-bottom:3rem}.heading.split{display:grid;grid-template-columns:1.35fr .65fr;gap:3rem;max-width:none}.heading>p{align-self:end;color:var(--muted)}.compare,.proofs,.templates{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem}.compare{grid-template-columns:1fr 1fr}.compare article,.templates article,.proofs figure,.docs a{padding:1.5rem;border:1px solid var(--line);border-radius:16px;background:var(--s)}.compare article{border-top:2px solid #687080}.compare .amber{border-top-color:var(--amber);background:linear-gradient(145deg,#1e222a,var(--s))}.compare small,.scenario small,.approval>small{color:var(--amber);font:500 .72rem "JetBrains Mono",monospace;letter-spacing:.08em}.compare h3,.scenario h3,.approval h3,.templates h3{font-size:1.4rem;letter-spacing:-.04em}.compare ul{padding-left:1.1rem;color:var(--muted)}.compare li+li{margin-top:.7rem}.plain{max-width:820px;margin:2rem 0 0;padding-left:1rem;border-left:3px solid var(--purple);color:var(--muted)}.plain b{color:var(--text)}.tabs{display:flex;flex-wrap:wrap;gap:.6rem;margin-bottom:1rem}.tabs button{padding:.7rem 1rem;border:1px solid var(--line);border-radius:99px;background:var(--s);color:var(--muted);cursor:pointer}.tabs button[aria-selected=true]{border-color:var(--amber);background:#ffb8001b;color:var(--text)}.scenario{display:grid;grid-template-columns:.8fr 1.2fr;gap:clamp(2rem,6vw,6rem);padding:clamp(1.5rem,4vw,3rem);border:1px solid #3a404b;border-radius:16px;background:var(--raised)}.scenario>div>p{color:var(--muted)}.chips{display:flex;flex-wrap:wrap;gap:.5rem}.chips span{padding:.35rem .6rem;border:1px solid var(--line);border-radius:99px;color:var(--text);font-size:.78rem}.chips span:first-child::first-letter{color:#c6b5ff}.scenario ol{display:grid;align-content:center;gap:.65rem}.scenario li{display:grid;grid-template-columns:42px 1fr;gap:.8rem;align-items:center;padding:.8rem 1rem;border:1px solid var(--line);border-radius:9px;background:#14171d;font-weight:600}.scenario li i{font: .75rem "JetBrains Mono",monospace;color:var(--muted);font-style:normal}.scenario .human{border-color:var(--amber);background:#ffb80014}.scenario .human i{color:var(--amber)}.control,.start{display:grid;grid-template-columns:1fr minmax(330px,.75fr);gap:clamp(3rem,8vw,9rem);align-items:center}.control>div>p:not(.eyebrow),.control li{color:var(--muted)}.control ul{padding:0;list-style:none}.control li{margin:.7rem 0}.control li::first-letter{color:var(--green)}.approval{padding:1.5rem;border:1px solid #a87b15;border-radius:16px;background:linear-gradient(150deg,#29210d,#1b1c1c)}.approval>p,.approval small{color:#c5bda7}.approval blockquote{margin:1rem 0;padding:1rem;border:1px solid #675c3e;border-radius:8px;color:#ebe4d2;font-size:.85rem}.approval button{padding:.55rem .65rem;border:0;border-radius:7px;background:var(--amber);color:#111;font-size:.75rem;font-weight:700;opacity:1}.approval button+button{margin-left:.3rem;border:1px solid #76694b;background:transparent;color:#eee}.proofs figure{margin:0;padding:0;overflow:hidden}.proofs img{display:block;width:100%;height:auto;border-bottom:1px solid var(--line)}figcaption{display:grid;gap:.35rem;padding:1rem}figcaption span{color:var(--muted);font-size:.8rem}figcaption a{color:var(--amber);font-size:.78rem}.value{background:radial-gradient(circle at 20% 20%,#ffb80018,transparent 24rem)}.calculator{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem;padding:1.25rem;border:1px solid var(--line);border-radius:16px;background:var(--s)}.calculator label{display:grid;gap:.4rem;font-size:.78rem;color:#d0d5de}.calculator input,.calculator select{min-height:43px;padding:.5rem;border:1px solid #464d58;border-radius:7px;background:var(--raised);color:var(--text)}.calculator output{grid-column:1/-1;padding:1rem;border-left:3px solid var(--amber);background:#1c2027;font-weight:600}.templates article{display:flex;flex-direction:column;min-height:245px}.templates i{display:grid;place-items:center;width:40px;height:40px;border-radius:10px;background:#8b5cf633;color:#c6b5ff;font-style:normal;font-size:1.2rem}.templates p{color:var(--muted)}.templates a{margin-top:auto;color:var(--amber);font-size:.84rem;text-decoration:none}.start{background:linear-gradient(130deg,#291f09,#111318)}.start>div>p:not(.eyebrow){color:#c7c2b5}.start ol{margin:0;padding:1.3rem;list-style:none;border:1px solid #574927;border-radius:16px;background:#0003}.start li{display:grid;grid-template-columns:28px 1fr;gap:.8rem;padding:.8rem 0;border-bottom:1px solid #4e4632}.start li:last-child{border:0}.start li i{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;background:var(--amber);color:#111;font-style:normal;font-size:.7rem}.start code{display:block;margin-top:.25rem;color:#d7cda9;font: .75rem "JetBrains Mono",monospace}.docs{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}.docs a{display:grid;gap:.3rem;text-decoration:none}.docs a:hover{border-color:var(--amber)}.docs span{color:var(--muted);font-size:.8rem}footer{border-top:1px solid var(--line);border-bottom:0;color:var(--muted);font-size:.8rem}footer>a:last-child{color:var(--amber)} +@media(max-width:860px){nav{display:none}.hero,.scenario,.control,.start,.heading.split{grid-template-columns:1fr}.hero{min-height:auto}.compare,.proofs,.templates,.docs{grid-template-columns:1fr}.calculator{grid-template-columns:1fr 1fr}}@media(max-width:520px){header{padding-inline:1rem}.nav-cta{display:none}.hero,section{padding-inline:1rem}.calculator{grid-template-columns:1fr}.console li{grid-template-columns:24px 1fr}.console em{display:none}footer{align-items:flex-start;flex-direction:column}} +.hybrid{background:linear-gradient(150deg,#11151d,#17131f)}.hybrid-grid{display:grid;grid-template-columns:1.3fr .7fr;gap:1rem}.hybrid figure,.hybrid article{margin:0;border:1px solid var(--line);border-radius:16px;overflow:hidden;background:var(--s)}.hybrid img{display:block;width:100%;height:auto}.hybrid-points{display:grid;gap:1rem}.hybrid-points article{display:grid;grid-template-columns:130px 1fr;gap:1rem;padding:1rem}.hybrid-points article img{border-radius:8px;align-self:start}.hybrid-points h3{margin:.15rem 0;font-size:1rem;letter-spacing:-.03em}.hybrid-points p{margin:0;color:var(--muted);font-size:.8rem}@media(max-width:860px){.hybrid-grid{grid-template-columns:1fr}.hybrid-points article{grid-template-columns:150px 1fr}} +.hybrid img[role="button"]{cursor:zoom-in}.hybrid img[role="button"]:focus-visible{outline:3px solid var(--amber);outline-offset:3px}.image-preview{position:fixed;inset:0;z-index:30;display:grid;place-items:center;padding:clamp(1rem,4vw,4rem)}.image-preview__backdrop{position:absolute;inset:0;background:#050608e8;backdrop-filter:blur(8px)}.image-preview__dialog{position:relative;z-index:1;max-width:min(1440px,100%);max-height:100%;overflow:auto;border:1px solid #4a5361;border-radius:14px;background:var(--raised);box-shadow:0 20px 80px #000}.image-preview__dialog img{display:block;max-width:100%;max-height:calc(100vh - 10rem);height:auto}.image-preview__dialog p{margin:0;padding:.8rem 1rem;color:var(--muted);font-size:.82rem}.image-preview__close{position:absolute;top:.7rem;right:.7rem;width:2rem;height:2rem;border:1px solid var(--line);border-radius:50%;background:#0f1115e8;color:var(--text);font-size:1.4rem;line-height:1;cursor:pointer} diff --git a/package.json b/package.json index 3aad93b..7a73c92 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "typecheck": "npm -w packages/cli run typecheck && npm -w packages/ui run typecheck", "test": "npm -w packages/cli run test", "test:client": "npm -w packages/client run test", + "launch-site:check": "node scripts/check-launch-site.mjs", "test:package": "node scripts/test-package.mjs", "ds:playground": "npm -w packages/design-toolkit run playground", "ds:check": "npm -w packages/design-toolkit run check", diff --git a/scripts/check-launch-site.mjs b/scripts/check-launch-site.mjs new file mode 100644 index 0000000..3c2bebb --- /dev/null +++ b/scripts/check-launch-site.mjs @@ -0,0 +1,105 @@ +import { existsSync, readFileSync, statSync } from "node:fs"; +import { dirname, join, normalize } from "node:path"; + +const root = process.cwd(); +const launchSiteRoot = join(root, "apps", "launch-site"); +const siteDir = join(launchSiteRoot, "public"); +const htmlPath = join(siteDir, "index.html"); +const html = readFileSync(htmlPath, "utf8"); +const searchableHtml = html.toLowerCase(); +const failures = []; + +function fail(message) { + failures.push(message); +} + +function textIncludes(fragment, label = fragment) { + if (!searchableHtml.includes(fragment.toLowerCase())) fail(`Missing required content: ${label}`); +} + +for (const file of ["styles.css", "calculator.js", "assets/state.svg", "assets/flow.svg", "assets/gate.svg"]) { + const path = join(siteDir, file); + if (!existsSync(path)) fail(`Missing asset: ${file}`); +} + +for (const file of ["state.txt", "flow.txt", "gate.txt"]) { + const path = join(launchSiteRoot, "evidence", file); + if (!existsSync(path)) fail(`Missing evidence file: ${file}`); + const evidence = existsSync(path) ? readFileSync(path, "utf8") : ""; + for (const required of ["Comando:", "Origem:", "CLI:", "Data:", "Alt text:"]) { + if (!evidence.includes(required)) fail(`${file} missing ${required}`); + } +} + +for (const required of [ + "Letra não é um Kanban", + "O trabalho avança", + "Capturar leads", + "Organizar estudos", + "Sua aprovação antes do envio", + "Autonomia com limite claro", + "npm install -g @letra-ai/cli", + "letra validate", + "não promete economia", + "decisão precisa ser humana", +]) { + textIncludes(required); +} + +const docs = ["Modelo mental", "Regras, gates", "Protocolo", "MCP"]; +for (const doc of docs) textIncludes(doc, `documentation link ${doc}`); + +if (!/]*\blang="pt-BR"/.test(html)) fail('HTML must declare lang="pt-BR"'); +if (!/]*id="main"/.test(html)) fail("Primary narrative must render in a main landmark"); +if (!/]*aria-label="[^"]+"/.test(html)) fail("Navigation must have an accessible label"); +if (!/]*aria-label(?:ledby)?="[^"]+"/.test(html)) fail("At least one section must be labelled for assistive tech"); +if (/]*\bhref=)/i.test(html)) fail("Anchor without href found"); +if (/]*\bdefer\b)/i.test(html)) fail("Scripts must be deferred"); + +const imageTags = [...html.matchAll(/]*>/g)].map((match) => match[0]); +if (imageTags.length < 3) fail("Expected at least three images"); +for (const tag of imageTags) { + if (!/\balt="[^"]{20,}"/.test(tag)) fail(`Image missing descriptive alt text: ${tag}`); + if (!/\bwidth="\d+"/.test(tag) || !/\bheight="\d+"/.test(tag)) fail(`Image missing stable dimensions: ${tag}`); +} + +const hrefs = [...html.matchAll(/\bhref="([^"]+)"/g)].map((match) => match[1]); +for (const href of hrefs) { + if (href.startsWith("http") || href.startsWith("#")) continue; + const target = normalize(join(dirname(htmlPath), href)); + if (!target.startsWith(root) || !existsSync(target)) fail(`Broken local link: ${href}`); +} + +for (const href of hrefs.filter((entry) => entry.includes("github.com"))) { + if (!href.includes("jspengine/letra")) fail(`External documentation must point to Letra repository: ${href}`); +} + +for (const asset of ["assets/state.svg", "assets/flow.svg", "assets/gate.svg"]) { + const size = statSync(join(siteDir, asset)).size; + if (size > 80_000) fail(`Asset too large for static launch page: ${asset}`); +} + +const performanceBudget = [ + ["index.html", 45_000], + ["styles.css", 24_000], + ["calculator.js", 8_000], +]; +for (const [file, maxSize] of performanceBudget) { + const size = statSync(join(siteDir, file)).size; + if (size > maxSize) fail(`${file} exceeds static performance budget: ${size} > ${maxSize}`); +} + +if (!html.includes("")) { + fail("Calculator script must be deferred so primary narrative renders without JavaScript"); +} + +if (failures.length) { + console.error(failures.join("\n")); + process.exit(1); +} + +console.log("launch-site checks passed");