Landing Pages — Produtos Koder

mandatory

Estrutura, seções, OG image e deploy de landing pages de produtos Koder. HTML monolítico, sem deps externas, floor de idiomas en-US+pt-BR (toggle EN/PT, default en-US). Inclui padrão canônico de URL para web apps (<produto>.koder.dev — nunca app.<produto>.koder.dev) e fluxo OAuth callback. Regra de privacidade: zero links pra código-fonte.

Visão Geral

Toda landing page de produto koder-* deve ser um arquivo HTML monolítico (site/index.html) dentro do repositório do produto, sem dependências externas (frameworks, CDNs, fontes web). Todo CSS e JS são inline.

Idioma (diretiva do owner — META-DOCS#184): toda landing de produto oferece o floor de idiomas en-US + pt-BRes onde a Área já o adota — ver policies/language.kmd "minimum language floor"), com toggle EN/PT canônico e default en-US. en-US é a fontefallback; o pt-BR está sempre presente como opção. Não auto-detectar locale a ponto de forçar pt-BR como default (o bug que motivou este registro: auto-detect tornou pt-BR default → uma sessão removeu o toggle inteiro como "não-conformidade"). Persistência + fallback chain seguem `specsi18n/contract.kmd`. Nunca remover um toggle de idioma existente como "não-conformidade" — a ausência de toggle é que é o drift.

Regra de privacidade: Nenhuma landing page deve conter links, botões ou referências ao repositório de código-fonte (Koder Flow, GitHub, ou qualquer hospedagem de código). Isso inclui botões "View Source", "View on Flow", links no footer para o repo, links para releases no Flow, etc. O código-fonte é privado e não deve ser exposto nas páginas públicas.

Estrutura do Arquivo

{categoria}/{produto}/site/
├── index.html       ← Landing page (HTML + CSS + JS inline)
├── icon.svg         ← Ícone do produto (cópia ou symlink)
└── og-image.png     ← Imagem OG rasterizada (1200×630px) — gerada por kicon

Geração canônica do og-image.png: rodar kicon generate --module {categoria}/{produto} --targets og (target og introduzido pra unificar a composição entre landing pages, páginas de pacote no Hub e capa de docs). O composer lê name + description do koder.toml/kpkg.toml e o ícone master, e produz o PNG 1200×630 com layout uniforme (ícone à esquerda, nome+descrição+wordmark Koder à direita). Geração manual (og-image.svg editado à mão) está obsoleta — cria inconsistência visual entre produtos e desatualiza quando o ícone muda. Repos legados que ainda têm og-image.svg podem manter o arquivo até a próxima release; o CI vai sobrescrever og-image.png com o output do kicon.

Head — Meta Tags e Setup

Meta Tags Obrigatórias

<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{Produto} — {Tagline curta}</title>
<meta name="description" content="{Descrição em 1-2 frases}">
<meta name="keywords" content="{palavras-chave separadas por vírgula}">

Comprimento máximo do <title>: 60 caracteres incluindo o separador (3 chars). Browsers truncam a aba por volta de 55–60 chars; Google Search trunca por volta de 55 chars. Exemplo dentro do limite: Koder KDB — The database for the age of AI (43 chars).

Open Graph + Twitter Card

<meta property="og:title" content="{Produto} — {Tagline}">
<meta property="og:description" content="{Descrição curta}">
<meta property="og:type" content="website">
<meta property="og:url" content="https://{subdominio}.koder.dev">
<meta property="og:image" content="https://{subdominio}.koder.dev/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="{Produto} — {Tagline}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{Produto} — {Tagline}">
<meta name="twitter:description" content="{Descrição curta}">
<meta name="twitter:image" content="https://{subdominio}.koder.dev/og-image.png">

Favicon

<link rel="icon" type="image/svg+xml" href="icon.svg">

Usar referência relativa icon.svg (o arquivo deve existir em site/). Alternativamente, usar data URI inline.

Script Anti-Flash de Tema (no <head>, antes do CSS)

<script>
  (function(){
    const s = localStorage.getItem('theme');
    const dark = s ? s === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches;
    if (dark) document.documentElement.setAttribute('data-theme','dark');
  })();
</script>

CSS — Design Tokens (Variáveis)

Tema Claro (:root)

:root {
  --bg: #ffffff; --bg2: #f8fafc; --bg3: #f1f5f9;
  --text: #0f172a; --text2: #475569; --text3: #94a3b8;
  --accent: {COR_DOMINANTE}; --accent2: {COR_DOMINANTE_DARK}; --accent-light: {COR_DOMINANTE_BG};
  --border: #e2e8f0;
  --card: #ffffff; --card-shadow: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.04);
  --code-bg: #1e293b; --code-text: #e2e8f0;
  --gradient: linear-gradient(135deg, {COR_DOMINANTE} 0%, {COR_SECUNDARIA} 100%);
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', monospace;
  color-scheme: light;
}

Tema Escuro ([data-theme="dark"])

[data-theme="dark"] {
  --bg: #0b1120; --bg2: #111827; --bg3: #1e293b;
  --text: #f1f5f9; --text2: #94a3b8; --text3: #64748b;
  --accent: {COR_DOMINANTE_LIGHT}; --accent2: {COR_DOMINANTE}; --accent-light: {COR_DOMINANTE_DARK_BG};
  --border: #1e293b;
  --card: #1e293b; --card-shadow: 0 1px 3px rgba(0,0,0,.3);
  --code-bg: #0f172a; --code-text: #e2e8f0;
  color-scheme: dark;
}

Regra: A cor dominante (--accent) varia por produto. Todas as outras variáveis de fundo, texto, borda e card são padronizadas e não devem variar entre produtos.

CSS — Componentes

  • position: fixed; top: 0; z-index: 100
  • Altura: 64px
  • max-width: 1200px centralizado
  • backdrop-filter: blur(12px) + border-bottom: 1px solid var(--border)
  • Atributo data-scrolled adicionado via JS quando scrollY > 20 (muda opacidade do fundo)
  • Regra CSS: nav[data-scrolled] { background: rgba(255,255,255,.85); } (e equivalente dark)
  • Contém: .nav-brand (SVG 32x32 + nome), .nav-links (âncoras para seções), .nav-actions (botão tema + botões CTA). O nome em .nav-brand usa a forma display do componente (Koder <Bare>, ex. Koder Mediant) — nunca o bare nem o slug. Fonte normativa: specs/naming/forms.kmd §7 (R7.1).
  • Clique na marca (.nav-brand): leva à home do próprio produtohref="/" (topo da própria landing). Nunca aponta para um site externo (www.koder.dev, área, catálogo). É a convenção universal "logo → home" (GitHubNotionLinearVercel, os mesmos que esta spec cita em §"SPA routing"); o clique mais proeminente serve seu propósito esperado e não ejeta o visitante do produto que ele está avaliando. A navegação "subir" para o ecossistema Koder (Koder › Área › Produto + grid das áreas) vive exclusivamente no rodapé koder-eco-nav — não no .nav-brand. Se um acesso proeminente ao ecossistema for desejado no topo, ele entra como link de nav nomeado (ex.: Koder ↗ em .nav-links.nav-actions, espelhando as landings de Área em areas.kmd §Nav), nunca sequestrando o clique do logo.
  • Botão Login: Incluir somente em landing pages de produtos que possuem autenticaçãoconta de usuário (SaaS, plataformas web, apps com login). Não incluir em produtos que são apenas ferramentas de linha de comando, bibliotecas, SDKs, ou distros. Quando presente, o botão Login fica em .nav-actions, como btn-outline, antes do botão primário (Download / Get Started). URL do Login: A página de login centralizada da Koder Platform é provida pelo Koder ID em `https:id.koder.devuilogin. **Todos** os produtos devem apontar para essa URL, sem exceção. **Nunca** usar URLs relativas como useroauth2... (as landing pages são estáticas e não passam pelo backend do produto). **Nunca** apontar para https:/ccount.koder.dev (essa é a página de gestão de conta, não a tela de login). **Nunca** apontar para https:/d.koder.dev sem o path uilogin (essa é a landing page do produto Koder ID, não a tela de login). **Sempre** passar os parâmetros OAuth do produto chamador: clientid (do registro OAuth do produto no Koder ID), redirecturi (URL-encoded, https:/produto>.koder.devauthcallback), responsetypecode, scopeopenid+profile+email. O uilogin forwarda esses params pro oauthv2authorize (handler servicesfoundationidengineservicesgatewayinternalhandlerproxy.go::uiLoginHandler). **Sem esses params**, o ui/login faz fallback pro cliente default koder-platform com redirecturihttps:/d.koder.dev/, e o usuário pós-login fica preso na landing do Koder ID em vez de voltar pro produto. Formato: <a href"https:/d.koder.devuilogin?clientid=<CLIENTID>&amp;redirecturi=https%3A%2F%2Fproduto.koder.dev%2Fauth%2Fcallback&amp;responsetypecode&amp;scopeopenid+profile+email" class="btn btn-outline">Log In/a. O client_id é provisionado uma vez via koder-id-cli client register --tenant koder --name 'Produto' --type public --redirect ... (gera ULID até a CLI ganhar --id; a id seed-clients/main.go é a source-of-truth canônica de slugs first-party). Em mobile (max-width: 768px), o botão Login fica oculto (display: none na classe .nav-actions .btn-outline`).
  • Mobile: .nav-toggle (hamburger) exibido em max-width: 768px, .nav-links vira coluna vertical

Botões

.btn { padding: 10px 20px; border-radius: 8px; font-size: 14px; font-weight: 600; }
.btn-primary { background: var(--accent); color: #fff; }
.btn-outline { border: 1px solid var(--border); color: var(--text); }
.btn-lg { padding: 14px 28px; font-size: 16px; border-radius: 10px; }

Hover: translateY(-1px) e cor mais escura.

Animações

@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

Usadas nos elementos do hero e ativadas via IntersectionObserver nos cards.

Seções Obrigatórias (em ordem)

1. Hero (duas colunas — obrigatório)

  • Layout: grid-template-columns: 1fr 1fr com gap: 60px. As duas colunas são obrigatórias para todos os produtos Koder, sem exceção. Não é permitido hero de coluna única no desktop.
  • Coluna esquerda (.hero-text):
    • Badge: pill com ícone + texto curto (ex: "Built in Koder Koda")
    • <h1> com ícone do produto inline (<img class="hero-icon-inline"> com width: 1.2em) + texto com gradient no nome do produto (background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent). O nome no <h1> usa a forma bare ou um nome distintivo de marca — a convenção dominante do fleet é o template Meet <Bare> (ex. Meet Jet, Meet Cron) ou marca lowercase distintiva (kicon, Kodix); a forma display (Koder <Bare>) também é válida. O gradiente cai sobre a palavra distintiva. O display fica ancorado no navbar + <title> na mesma página. Fonte normativa: specs/naming/forms.kmd §7 (R7.2).
    • Parágrafo descritivo (1-3 frases)
    • Botões CTA: primário ("Get Started" / "Download") — não incluir botões de acesso ao repositório (View Source, View on Flow, etc.)
    • Animações: fadeInUp escalonadas (0s, 0.1s, 0.2s, 0.3s)
  • Coluna direita (.hero-right): conteúdo visual que ilustre o produto. Três variantes permitidas, escolher a que melhor representa o produto:
    • Variante A — Bloco de código (<pre>): para produtos técnicos/dev (SDKs, bancos de dados, linguagens, APIs, ferramentas de programação, frameworks, motores de simulação). Exemplo: kdb, jobs, sim.
      • Syntax highlighting via spans com classes: .kw (keyword, roxo), .str (string, verde), .num (número, amarelo), .cm (comentário, cinza), .fn (função, cyan), .op (operador, rosa)
      • Fundo escuro (var(--code-bg)), border-radius: 12px, font-family: var(--mono)
    • Variante B — Mockup de UI (SVG inline ou HTML/CSS estilizado): para apps consumer e SaaS visuais (delivery, bus, ride, learn, hr, jobs, org, health, clinic). Renderize uma janela estilizada (browser, mobile ou desktop) contendo elementos reais e reconhecíveis do produto: cards de busca, listas de itens, mapas com rota, dashboard com gráficos, formulário de agendamento, etc.
      • Janela com sombra (box-shadow ou filter: drop-shadow), bordas arredondadas, barra de título estilizada (3 botões macOS, ou status bar mobile)
      • Conteúdo interno usa as cores do produto (var(--accent))
    • Variante C — Ilustração temática (SVG inline): figura conceitual ilustrando o domínio do produto, quando nem código nem UI são adequados (ex: produto puramente físico, hardware, infraestrutura).
    • Em todos os casos: tudo inline, sem dependências externas (sem <img> para PNG/JPG, sem fontes web). Visual moderno: sombras suaves, gradientes, bordas arredondadas (12-16px). Legível em mobile.
  • Mobile (max-width: 768px): grid-template-columns: 1fr, texto centralizado, coluna direita aparece abaixo da esquerda
  • Background sutil: radial-gradient(circle, rgba({accent},.08) 0%, transparent 70%) posicionado atrás

2. Features / Funcionalidades Principais

  • Classe .alt-bg (fundo var(--bg2))
  • Header centralizado: <h2> + <p> descritivo
  • Grid: grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)) com gap: 24px
  • Cards com: ícone (emoji ou SVG em div 32x32 colorida), título <h4>, descrição <p>
  • Border: 1px solid var(--border), border-radius: 12-16px
  • Hover: border-color: var(--accent), translateY(-2px), sombra

3. Exemplos de Código / How It Works (opcional, mas recomendada)

  • Layout alternado: grid-template-columns: 1fr 1fr
  • Blocos: texto explicativo (esquerda) + código (direita), depois invertido (.reverse)
  • Texto: <h3>, <p>, <ul> com bullets coloridos (.accent)
  • Código: <pre> com syntax highlighting

4. Comparativo com Concorrentes

  • Classe .alt-bg
  • Header centralizado: "How It Compares" + subtítulo
  • <table class="comparison-table">: cabeçalho com nomes (produto Koder + concorrentes)
  • Células: verde (.check { color: #22c55e }) para features presentes, cinza (.cross) para ausentes
  • overflow-x: auto para scroll horizontal em mobile

5. FAQ com Accordion (se aplicável)

  • Perguntas/respostas em acordeão expansível
  • Implementação via <details>/<summary> ou JS toggle
  • Tipografia: pergunta em font-weight: 700, resposta em var(--text2)

6. CTA Final

  • Fundo: var(--gradient) (gradiente da cor dominante), color: #fff
  • <h2> com pergunta motivacional (ex: "Ready to get started?")
  • <p> com subtítulo
  • Botões: fundo branco com texto na cor accent, + botão ghost
  • Padding generoso: 100px 24px
  • border-top: 1px solid var(--border), padding: 40px 24px
  • Layout: flexbox, justify-content: space-between
  • Esquerda: © 2026 Koder. All rights reserved.
  • Direita: links (Docs, API, Architecture) — variam por produto. Nunca incluir links para o repositório ou Koder Flow.
  • Links: color: var(--text2), hover color: var(--accent), font-size: 13px

JavaScript — Comportamentos

Toggle de Tema (2 modos: claro ↔ escuro)

O botão alterna apenas entre claro e escuro. Não há opção "dispositivo" no toggle.

Comportamento padrão (primeiro acesso / cache limpo): Quando não há preferência salva em localStorage, usar a preferência do sistema operacional (prefers-color-scheme). Se o SO preferir escuro, abrir em escuro; caso contrário, abrir em claro.

Persistência: Quando o usuário clica no botão, a escolha é salva em localStorage e prevalece sobre a preferência do SO em acessos futuros.

function toggleTheme() {
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  const next = isDark ? 'light' : 'dark';
  localStorage.setItem('theme', next);
  applyTheme();
}
function applyTheme() {
  const saved = localStorage.getItem('theme');
  const isDark = saved ? saved === 'dark' : matchMedia('(prefers-color-scheme:dark)').matches;
  document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
  const sun = document.getElementById('icon-sun');
  const moon = document.getElementById('icon-moon');
  if (sun) sun.style.display = isDark ? 'none' : 'block';
  if (moon) moon.style.display = isDark ? 'block' : 'none';
}
matchMedia('(prefers-color-scheme:dark)').addEventListener('change', () => {
  if (!localStorage.getItem('theme')) applyTheme();
});
applyTheme();
  • Ícones SVG inline: sol (claro), lua (escuro)
  • Persistência em localStorage (chave theme, valores: light, dark)
  • Sem valor salvo → segue preferência do SO
window.addEventListener('scroll', () => {
  document.getElementById('navbar').toggleAttribute('data-scrolled', window.scrollY > 20);
});

Mobile Nav Toggle

document.querySelector('.nav-toggle').onclick = () => {
  document.querySelector('.nav-links').classList.toggle('open');
};

Fade-In on Scroll (IntersectionObserver)

Opcional. Se usado, aplicar classe .fade-in nos cards e ativar com observer (threshold: 0.1).

OG Image (Imagem para Compartilhamento Social — WhatsApp, Telegram, Twitter, Slack, etc.)

Obrigatório para todas as landing pages de produto. Toda landing deve ser configurada para exibir uma thumb rica quando a URL for compartilhada via WhatsApp, Telegram, TwitterX, Slack, Discord, iMessage, LinkedIn ou qualquer outro apprede social que consuma Open Graph / Twitter Card. Sem isso, o link aparece como texto puro e perde impacto.

Conteúdo obrigatório da thumb

A imagem deve conter, nesta ordem de prioridade:

  1. Ícone do produto — o icon.svg do produto, destacado visualmente (lado esquerdo ou centralizado, com tamanho generoso ≈ 30% da altura).
  2. Slogan do produto — a tagline curta oficial do produto em texto grande e legível (ex: "The database for the age of AI"). É o elemento de leitura principal.
  3. Frase complementar (se couber) — uma linha adicional de apoio (subtítulo, descrição de 1 frase, ou call-to-action) posicionada abaixo do slogan, somente se houver espaço visual sem poluir. Se a thumb já estiver cheia, omitir.
  4. (Opcional) URL do produto ({subdominio}.koder.dev) discreta em um canto.

Evitar: blocos de texto longos, múltiplos parágrafos, tabelas, features listadas, ou qualquer coisa que não seja legível no preview reduzido do WhatsApp (tipicamente ≈ 400px de largura renderizada).

Especificações técnicas

  • Dimensão: 1200×630px (ratio 1.91:1, exigência do Open Graph / WhatsApp)
  • Formato: PNG (rasterizado a partir de SVG fonte)
  • Arquivos: site/og-image.svg (fonte editável) + site/og-image.png (rasterizado para servir)
  • Fundo: escuro (#0b1120) ou gradient da cor accent do produto
  • Rasterização: rsvg-convert -w 1200 -h 630 og-image.svg > og-image.png
  • Peso: manter .png abaixo de 300 KB (WhatsApp pode descartar thumbs muito grandes)

Meta Tags (já descritas na seção Head)

Usar URL absoluta HTTPS: https://{subdominio}.koder.dev/og-image.png. URLs relativas não funcionam no WhatsApp/Telegram — o scraper precisa da URL completa. Incluir sempre og:image:width=1200 e og:image:height=630 para acelerar o render da thumb.

Validação

Após deploy, testar o preview com:

  • WhatsApp: enviar o link para si mesmo em uma conversa de teste
  • OpenGraph.xyz ou metatags.io para debug visual
  • curl -A 'WhatsApp/2.0' -I https://{subdominio}.koder.dev para verificar que a resposta entrega as meta tags corretas

Responsividade

Regra: toda landing page de produto deve funcionar sem problemas em dispositivos móveis. Isso é obrigatório — não opcional.

Onde as breakpoint rules vivem

As regras de responsividade (@media (max-width: 768px) e @media (max-width: 480px)) podem ser declaradas em duas localizações, e o auditor (/k-audit-landings) aceita qualquer uma:

  1. Inline no próprio <style> da landing — pattern original, usado por produtos com landing standalone.
  2. External stylesheet linkado via <link rel="stylesheet"> — pattern usado quando a landing delega a estilo compartilhado (ex.: <link rel="stylesheet" href="/ecosystem.css"> nas Áreas, <link rel="stylesheet" href="/sdk/koder-web-kit.css"> em produtos que herdam o KDS). Nesse caso o arquivo externo já carrega os breakpoints + classes .nav-toggle, .nav-links, hamburger CSS, etc.

O auditor deve verificar: se o HTML linka ecosystem.css OU koder-web-kit.css OU qualquer stylesheet local que contenha as media queries canônicas, considerar OK. Caso contrário, exigir inline.

Breakpoints

  • max-width: 768px: Navbar vira hamburger (.nav-toggle visível, .nav-links colapsado), hero de 2 colunas vira 1 coluna, code sections viram 1 coluna, botão Login oculto (display: none)
  • max-width: 480px: Fontes menores, todos os grids em 1 coluna, botões empilhados verticalmente, padding lateral reduzido (≥ 16px)

Regras obrigatórias

  • Nenhum elemento deve causar overflow horizontal — a página nunca deve ter scrollbar horizontal em mobile
  • Todos os grids 1fr 1fr colapsam para 1fr em max-width: 768px
  • Texto do hero centralizado em mobile
  • Tabela comparativa com overflow-x: auto para scroll horizontal interno
  • Botões com área de toque mínima de 44×44px (padding adequado)
  • Fontes nunca menores que 14px em mobile
  • Imagens e SVGs com max-width: 100% para não vazar o container
  • Nenhum position: fixed (exceto navbar) que interfira com o conteúdo em viewports pequenas

iOS Safari — regras adicionais obrigatórias

iOS Safari tem comportamentos específicos que o Chrome DevTools não simula:

  • 100vh bug: em iOS Safari o 100vh inclui a barra de endereço, causando corte. Usar min-height: 100svh (small viewport height) como fallback, ou min-height: -webkit-fill-available. Nunca depender de 100vh para seções full-screen.
  • Safe areas (notch / barra inferior): adicionar padding via variáveis de ambiente CSS para não sobrepor elementos de navegação do sistema:
    body { padding-bottom: env(safe-area-inset-bottom); }
    nav  { padding-top: env(safe-area-inset-top); }
  • position: fixed em iOS: elementos fixos dentro de elementos com overflow: scroll ficam estáticos. Garantir que a navbar fixa esteja no nível de <body>, nunca dentro de um container com overflow.
  • Meta viewport: usar exatamente content="width=device-width, initial-scale=1". Nunca adicionar user-scalable=no (acessibilidade) nem maximum-scale=1 (impede zoom do usuário).

Hover em touchscreen — regras obrigatórias

Estados :hover puros em CSS "prendem" em dispositivos touch (o estilo persiste após o toque). Regra: todo :hover visual deve usar @media (hover: hover) para ser aplicado apenas quando o dispositivo tem cursor real:


.card:hover { transform: translateY(-2px); box-shadow: ...; }


@media (hover: hover) {
  .card:hover { transform: translateY(-2px); box-shadow: ...; }
}

Exceção: :hover usado apenas para mudança de color em links de texto pode ficar sem wrapper — o impacto visual "preso" é aceitável nesses casos.

Verificação obrigatória antes de deploy

Toda landing deve ser verificada nos seguintes pontos antes de ir ao ar:

  1. Chrome DevTools → modo responsivo → testar em:
    • 375px (iPhone SE / padrão mínimo)
    • 390px (iPhone 14)
    • 768px (tablet / breakpoint de referência)
  2. Sem overflow horizontal: com DevTools aberto, verificar que document.documentElement.scrollWidth === window.innerWidth
  3. Navbar hamburger funcional: menu abre e fecha em 375px
  4. Hero legível: texto do h1 visível sem zoom, sem truncamento
  5. Grids colapsados: nenhuma grade de 2+ colunas visível abaixo de 480px
  6. Tabela responsiva: scroll horizontal interno funciona em mobile
  7. Botões clicáveis: área de toque visualmente adequada (mín. 44×44px)
  8. Fontes legíveis: nenhum texto com fonte < 14px
  9. iOS Safari: testar 100svh / env(safe-area-inset-*) se houver Safari disponível; ou validar via BrowserStack / Xcode Simulator
  10. Hover não prende: verificar que cards e botões não ficam com estilo de hover travado após toque em mobile (Chrome DevTools → Touch emulation)

Padrão de URL para Produtos Koder

A URL de todo produto Koder é <produto>.koder.dev — sem prefixo app., sem path /app.

Nunca usar app.<produto>.koder.dev — esse padrão de duplo-subdomain não existe na Koder Stack e não tem precedente nas principais suites de produtos do mercado (Google, Microsoft, Adobe).

Produtos com web app (padrão)

A maioria dos produtos Koder são web apps onde a URL raiz serve o próprio produto. A mesma URL (<produto>.koder.dev) serve conteúdos distintos dependendo do estado de autenticação — sem mudança de URL visível na barra de endereços:

Usuário acessa produto.koder.dev
         ↓
App (Flutter/JS) carrega o mesmo bundle
         ↓
Verifica token no storage local (client-side)
    ├── sem token → renderiza landing screen (hero, features, botão Login)
    └── token válido → renderiza tela principal do produto

Esse comportamento é análogo ao de GitHub (github.com), Notion (notion.so), Linear (linear.app) e Vercel (vercel.com): a mesma URL, o servidor entrega sempre o mesmo bundle, e o cliente decide o que renderizar.

Exemplos:

URL Produto Serve na raiz
hub.koder.dev Koder Hub Web app (loja aberta)
flow.koder.dev Koder Flow Web app (git forge)
mail.koder.dev Kmail Web app (webmail)
kode.koder.dev Kode Web app (AI assistant)

OAuth callback URL

O redirect_uri registrado no Koder ID para web apps segue o padrão:

https://<produto>.koder.dev/auth/callback

Exemplos:

  • https://kode.koder.dev/auth/callback
  • https://talk.koder.dev/auth/callback
  • https://hub.koder.dev/auth/callback

Nunca registrar https://app.<produto>.koder.dev/... como redirect URI.

Landing page em /about

Como a raiz serve o app, a landing page marketing fica em /about:

  • O og:url da landing aponta para https://{produto}.koder.dev/about
  • O og:image aponta para https://{produto}.koder.dev/about/og-image.png
  • A landing e seus assets são deployados em /var/www/{produto}.koder.dev/about/
  • O servidor tem spa = true no koder-jet; arquivos estáticos em about/ são servidos diretamente sem conflito com o SPA

Fluxo de URLs completo

produto.koder.dev            (não logado → landing screen no app)
    ↓ clica "Log In"
id.koder.dev/oauth/v2/authorize
    ?redirect_uri=https://produto.koder.dev/auth/callback
    ↓ autentica
produto.koder.dev/auth/callback?code=...   (OAuth callback)
    ↓ app processa e salva token
produto.koder.dev            (logado → tela principal do produto)

Produtos sem web app (CLI, SDK, desktop-only)

Produtos que não têm web app (ferramentas de linha de comando, SDKs, bibliotecas, apps desktop-only) seguem o padrão das seções anteriores desta spec: landing page estática em site/index.html deployada na raiz de <produto>.koder.dev.

Deploy

  • O site é deployado no servidor s.forge em /var/www/{subdominio}.koder.dev/ (s.k.lin foi descomissionado em 2026-04-16; ver infrastructure/servers.md)
  • Configurar entrada em /etc/koder-jet/sites.toml
  • DNS: criar registro A em ClouDNS apontando para 177.136.231.237 (s.forge). A API ClouDNS só aceita chamadas vindas desse mesmo IP — sempre rodar o curl via ssh -p 220 rodrigo@177.136.231.237 (ver infrastructure/dns.md).
  • HTTPS automático via koder-jet (ACME/Let's Encrypt)
  • Após deploy, sempre escrever a URL no chat

Seções Opcionais (conforme tipo de produto)

Dependendo do tipo de produto, algumas seções adicionais podem ser incluídas:

  • Pricing: Cards de planos (FreeProEnterprise) — para produtos SaaS
  • Architecture: Diagrama de camadas em bloco de código estilizado
  • Integrations: Cards de outros produtos Koder que se integram
  • Key Specs: Cards com números grandes (para produtos de infraestrutura)
  • Timeline: Para projetos com cronograma (ex: datacenter)
  • Location/Map: Para produtos com componente geográfico

A ordem dessas seções opcionais é flexível, mas devem vir entre Features e Comparativo.

Referência de Implementação

O arquivo platform/kdb/site/index.html (deployado em kdb.koder.dev) é a implementação de referência canônica deste padrão. Em caso de dúvida sobre qualquer aspecto visual ou estrutural, consultar esse arquivo.

Requisitos normativos (gramática RFC-013 — piloto do backfill #185)

Requirement: Mobile responsiveness

Every product landing SHALL declare a device-width viewport meta and SHALL render with at least one @media breakpoint; it SHALL NOT rely on overflow-x: hidden on html/body to mask horizontal overflow.

Scenario: Viewport meta present

  • GIVEN a product landing index.html
  • WHEN the static gate inspects its head
  • THEN a <meta name="viewport"> with width=device-width is found

Scenario: Breakpoints exist

  • GIVEN the landing inline styles and local CSS
  • WHEN the static gate scans them
  • THEN at least one @media query exists

Scenario: Kodix-class regression blocked

  • GIVEN a landing with zero breakpoints
  • WHEN koder-spec-audit landing-responsive runs in CI
  • THEN the run exits non-zero naming LR2 and the release does not publish

Requirement: Self-contained single-file landing

A product landing SHALL be a single monolithic index.html with no external runtime dependencies (no CDN scripts/styles), per the privacy and resilience rules of this spec.

Scenario: No external runtime deps

  • GIVEN a product landing index.html
  • WHEN its <script src> and <link href> URLs are inspected
  • THEN none resolve to a non-Koder external origin