Skip to content

Landing Pages — Produtos Koder

landing-pages specs/landing-pages/products.kmd

Estrutura, seções, OG image e deploy de landing pages de produtos Koder. HTML monolítico, sem deps externas, 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.

When this spec applies

Primary triggers

All triggers

Specification body

Padrão de Landing Pages — Produtos Koder

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: inglês (en-US).

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)
  • Botão Login: Incluir somente em landing pages de produtos que possuem autenticação/conta 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.dev/ui/login. Todos os produtos devem apontar para essa URL, sem exceção. Nunca usar URLs relativas como /user/oauth2/... (as landing pages são estáticas e não passam pelo backend do produto). Nunca apontar para https://account.koder.dev (essa é a página de gestão de conta, não a tela de login). Nunca apontar para https://id.koder.dev sem o path /ui/login (essa é a landing page do produto Koder ID, não a tela de login). Sempre passar os parâmetros OAuth do produto chamador: client_id (do registro OAuth do produto no Koder ID), redirect_uri (URL-encoded, https://<produto>.koder.dev/auth/callback), response_type=code, scope=openid+profile+email. O /ui/login forwarda esses params pro /oauth/v2/authorize (handler services/foundation/id/engine/services/gateway/internal/handler/proxy.go::uiLoginHandler). Sem esses params, o /ui/login faz fallback pro cliente default koder-platform com redirect_uri=https://id.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://id.koder.dev/ui/login?client_id=<CLIENT_ID>&amp;redirect_uri=https%3A%2F%2F<produto>.koder.dev%2Fauth%2Fcallback&amp;response_type=code&amp;scope=openid+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)
    • 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, Twitter/X, Slack, Discord, iMessage, LinkedIn ou qualquer outro app/rede 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:

/* ERRADO — prende em touch */
.card:hover { transform: translateY(-2px); box-shadow: ...; }

/* CORRETO */
@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:

URLProdutoServe na raiz
hub.koder.devKoder HubWeb app (loja aberta)
flow.koder.devKoder FlowWeb app (git forge)
mail.koder.devKmailWeb app (webmail)
kode.koder.devKodeWeb 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 (Free/Pro/Enterprise) — 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.

References