Communitygithub.com

OpenLabs-so/oa-design

A linguagem de design do [Open Analytics](https://getopen.so) em um único arquivo.

O que é oa-design?

Este skill define a linguagem de design completa do Open Analytics em um único arquivo SKILL.md, projetado para que agentes de IA e desenvolvedores possam aplicar o estilo visual da plataforma de forma consistente. Ele compila todas as regras de design, tokens de estilo, componentes e padrões de animação em um formato portátil e legível por máquina. O skill é particularmente útil para gerar telas, componentes ou protótipos que sigam o 'OA style', garantindo que cada elemento — das cores e cantos aos movimentos e hierarquia — corresponda exatamente aos valores usados no produto. Projetado para plataformas como Claude Code, Cursor e Codex, ele pode ser colado no contexto do agente ou usado como um arquivo de regras no repositório. O arquivo único é complementado por uma skill mais completa no repositório (`skills/oa-design/`) e uma CLI (`npx getopen-design add <component>`), que oferecem receitas por componente com código-fonte verificado por tipo. Ao seguir as 'dez regras' e a hierarquia de tokens, os desenvolvedores podem criar interfaces que parecem nativas ao Open Analytics, com foco em cantos contínuos, um sistema de cores minimalista e animações baseadas em molas.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/OpenLabs-so/oa-design/tree/HEAD

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

DESIGN-SKILL.md

A linguagem de design do Open Analytics em um único arquivo, escrita para agentes de IA e para os humanos que os orientam. Cada valor aqui é enviado no produto: códigos hexadecimais, constantes de mola, raios, atenuações. Cole este arquivo no contexto do seu agente (ou coloque-o no seu repositório como um arquivo de regras) e peça telas "no estilo OA".

Esta é a edição compilada em um único arquivo, feita para colar. A mesma linguagem também está disponível neste repositório como uma skill de agente adequada (skills/oa-design/: receitas por componente com o código-fonte completo verificado por tipo embutido) e como uma CLI (npx getopen-design add <componente>); prefira estas para o Claude Code e outros agentes com suporte a skills.

A aparência em uma frase: superfícies brancas com cantos de curvatura contínua, repousando sobre um palco cinza silencioso, desenhadas em uma única tinta, movidas por uma única mola.


1. As dez regras

  1. Uma tinta, tudo derivado. Todo o sistema neutro é uma cor misturada em transparência em percentagens fixas. Nunca escolha um segundo cinza.
  2. Duas camadas, e o espaço é a página. As superfícies são um quadro branco segurando uma inserção cinza rebaixada; as seções são placas e o fundo da página entre elas é o único divisor. Sem linhas horizontais.
  3. Squircle para superfícies, pílulas para ações. Cards têm cantos de curvatura contínua; tudo que é clicável que não é um card é uma pílula.
  4. Uma família de molas. Sete molas nomeadas cobrem todo o produto. Não invente uma oitava.
  5. O Chrome nunca espera. Layout e títulos renderizam instantaneamente; apenas as trocas de dados vêm de um esqueleto com pixels correspondentes, chegando por desfoque, não por pop.
  6. O peso para em 500. Sem negrito em nenhum lugar. A hierarquia vem do tamanho, cor e espaçamento.
  7. Um acento, gasto em um só lugar. Um único azul para ações primárias e a linha primária do gráfico; as cores semânticas são matizes de texto, não preenchimentos.
  8. Estados recebem pílulas, eventos terminam a si mesmos. Condições permanentes renderizam faixas não dispensáveis que vivem exatamente enquanto o estado dura; resultados únicos são toasts que se aposentam sozinhos.
  9. O texto é parte do design. Letra inicial maiúscula em frases; botões dizem o que acontece; erros nomeiam a causa e a saída, sem culpa.
  10. Piso de qualidade, sempre. Anéis de foco, role="status", decorações aria-hidden, prefers-reduced-motion e sem rolagem horizontal na página.

2. Tokens

Claro

:root {
  --ink: #292929;

  --background: #f6f6f6;     /* o palco cinza entre as placas */
  --card: #ffffff;
  --popover: #ffffff;

  /* neutros derivados da tinta: as percentagens SÃO o sistema */
  --border: color-mix(in srgb, var(--ink) 12%, transparent);
  --input:  color-mix(in srgb, var(--ink) 14%, transparent);
  --accent: color-mix(in srgb, var(--ink) 5%, transparent);  /* lavagem hover */
  --muted:  color-mix(in srgb, var(--ink) 5%, transparent);
  --muted-foreground: #6d6d6d;
  --foreground: var(--ink);

  --primary: #305dde;             /* o único acento */
  --primary-foreground: #ffffff;
  --ring: #3ba6f1;

  --secondary: #e9e9e9;           /* controles cinza planos, sem borda */
  --secondary-foreground: var(--ink);

  /* semântico, como matizes de TEXTO em claro (escala Tailwind, linha 700) */
  --destructive: var(--color-red-500);
  --destructive-foreground: var(--color-red-700);
  --success-foreground: var(--color-emerald-700);
  --warning-foreground: var(--color-amber-700);
  --info-foreground: var(--color-blue-700);

  --chart-1: #296FF0;             /* linha primária do gráfico */
  --radius: 0.625rem;
}

Precisa de um divisor mais forte? Aumente a percentagem de tinta (o produto usa apenas 4, 5, 10, 12, 14). As cores semânticas vão em palavras e pontos de 8px, nunca em preenchimentos de painel.

Escuro

Mesma receita, retintada, com duas mudanças sistemáticas: as percentagens de linha fina sobem em 2 pontos, e o texto semântico passa da linha 700 para a linha 400.

.dark {
  --ink: #ededed;
  --background: #191919;
  --card: #212121;
  --popover: #242424;
  --border: color-mix(in srgb, var(--ink) 14%, transparent);
  --input:  color-mix(in srgb, var(--ink) 16%, transparent);
  --muted-foreground: #969696;
  --secondary: #2e2e2e;
  --primary: #296FF0;
  --destructive-foreground: var(--color-red-400);
  --success-foreground: var(--color-emerald-400);
}

Raio

Uma base, multiplicada; nunca um raio personalizado:

--radius: 0.625rem;  /* sm ×0.6, md ×0.8, lg ×1, xl ×1.4,
                        2xl ×1.8, 3xl ×2.2, 4xl ×2.6 */

rounded-full é reservado para pílulas. Superfícies squircle têm seus próprios raios maiores (seção 4).

Tipografia

  • Inter Tight, variável, pesos 300 a 500, auto-hospedada (CDNs de fonte quebram builds; mantenha o woff2 no repositório).
  • Geist Mono para code, kbd, samp, pre, teclas e trechos.
  • O teto de peso é a identidade: nada renderiza mais pesado que 500. A hierarquia vem do tamanho, text-foreground/80 vs text-muted-foreground, e espaçamento.
  • Cabeçalhos: font-medium tracking-tight; títulos de marketing adicionam text-wrap: balance.
  • Dados: tabular-nums em qualquer número em uma coluna ou atualizando no lugar.
  • A voz padrão da UI é 14px; linhas secundárias 12px, com leading-5/leading-6.

Sombras

A elevação é binária:

/* card em repouso */    box-shadow: 0 1px 2px rgba(0,0,0,0.06);
/* chrome flutuante */ box-shadow: 0 1px 2px rgba(0,0,0,0.08),
                                  0 8px 24px rgba(0,0,0,0.08);

Disciplina de fundo

body é branco (o overscroll mostra o body; cinza aparecendo atrás de uma tela branca parece uma página diferente). Telas que querem o palco cinza pintam seu próprio fundo. A raiz recebe overflow-x: clip.


3. Layout

Placas. As seções são painéis autocontidos; o fundo da página aparecendo entre elas é o único limite. Espaços verticais generosos (gap-12 sm:gap-20 marketing, gap-6 in-app), padding lateral mais apertado (px-4 sm:px-6). Irmãos são espaçados pelo gap do pai, nunca por margens por filho. Sem <hr>, sem border-b entre seções.

Larguras por escopo: marketing 76rem (6.5xl), páginas do site do app max-w-6xl, telas de lista/seletor max-w-5xl, superfícies de leitura ~65ch. Centralizado, mx-auto.

Anatomia da página do app:

cabeçalho fixo (h-14) → container pt-14
  pílula flutuante opcional (fixed top-16, centralizado)
  <main class="px-4 pb-36 pt-8 sm:px-6 bg-white">
    linha de título: h1 text-xl font-medium tracking-tight + UM controle à direita
    faixa(s) de aviso opcional(is)
    placas: flex flex-col gap-6

A linha de título contém exatamente um controle. pb-36 mantém o conteúdo longe da borda da viewport.

Grades: grid gap-3, colunas responsivas (grid-cols-2 sm:grid-cols-4 para estatísticas). Cards compartilham uma altura de conteúdo fixa e rolam internamente; a linha de base da grade nunca quebra.

Responsivo: design em 390px e 1280px. Pares horizontais empilham em sm: com a ação por último. Conteúdo largo rola em seu próprio contêiner overflow-x-auto; a página nunca rola lateralmente.

Configurações: trilho de abas estreito + coluna de conteúdo com painéis de seção; cada seção abre com um bloco de cabeçalho (título + uma frase cinza, ação única opcional à direita). Abas com link profundo via ?tab=<id>.


4. Componentes

A superfície squircle

Cantos de curvatura contínua via um clip-path shape() CSS parametrizado por duas variáveis, com corner-shape: squircle onde houver suporte:

.squircle {
  border-radius: 26px;
  --card-clip-radius: 14px;
  --card-clip-handle: 2.25px;
  clip-path: shape(
    from var(--card-clip-radius) 0px,
    line to calc(100% - var(--card-clip-radius)) 0px,
    curve to 100% var(--card-clip-radius)
      with calc(100% - var(--card-clip-handle)) 0px / 100% var(--card-clip-handle),
    line to 100% calc(100% - var(--card-clip-radius)),
    curve to calc(100% - var(--card-clip-radius)) 100%
      with 100% calc(100% - var(--card-clip-handle)) / calc(100% - var(--card-clip-handle)) 100%,
    line to var(--card-clip-radius) 100%,
    curve to 0px calc(100% - var(--card-clip-radius))
      with var(--card-clip-handle) 100% / 0px calc(100% - var(--card-clip-handle)),
    line to 0px var(--card-clip-radius),
    curve to var(--card-clip-radius) 0px
      with 0px var(--card-clip-handle) / var(--card-clip-handle) 0px,
    close
  );
  corner-shape: squircle;
}
@media (min-width: 640px) {
  .squircle { border-radius: 50px; --card-clip-radius: 20px; --card-clip-handle: 3px; }
}

O mini card

squircle externo: bg-card, border, p-1, sombra de repouso
├── faixa de cabeçalho (pb-2 pl-3.5 pr-2 pt-1.5):
│     ícone muted de 16px + título text-sm font-medium text-foreground/80
│     chip opcional na linha central do título
│     "Ver tudo ›" (text-xs muted; hover: lavagem accent, seta desliza 2px)
└── squircle interno: bg #f6f6f6, border, rounded-[22px] sm:rounded-[44px],
      altura FIXA (h-44 = cinco linhas de 32px), overflow hidden

Cards nunca crescem com dados; listas mais longas rolam dentro. Overflow mostra um chip de chevron de 24px no canto inferior direito, balançando y:[0,2.5,0] (loop 1.4s easeInOut), desaparecendo (0.18s, escala 0.7) no final da rolagem. Linhas: 32px, lavagem accent de sangria total no hover, valores tabular-nums.

Botões

Pílulas, font-medium, pressionados fisicamente: active:translate-y-px active:scale-[0.98].

  • primary: azul de acento com um bisel (background/border color-mix em direção a um índigo profundo, inset 0 1px 0 rgba(255,255,255,0.22) topo, inset escuro fundo); hover clareia para primary puro.
  • secondary: --secondary plano, borda transparente, hover mistura 5% de tinta. O burro de carga.
  • ghost: apenas texto muted. destructive: preenchimento vermelho, apenas etapa de confirmação.
  • Tamanhos h-9 / h-8 / h-7; o chrome do app usa principalmente os pequenos.
  • Carregando mantém o rótulo e adiciona um spinner de anel de 14px (border-2 border-current border-t-transparent); largura nunca pula.
  • focus-visible:ring-[3px] em --ring/50. Nunca removido.

Painéis de dropdown (menus, seletores, chaveadores)

Gatilho pílula → painel flutuante (sombra flutuante, borda, branco, rounded-2xl+), entrada com mola em PANEL (550/38) com pequeno deslocamento y/scale, saída mais rápida. Itens rounded-lg de 32px com lavagem accent no hover; o destaque ativo é um elemento compartilhado que viaja (layoutId ou molas de width/height), nunca repaints por item. Fecha em pointerdown fora e Escape.

Modais

Backdrop desbota ~0,15s; painel entra com mola em POP (400/26) a partir de opacity 0, scale .96, sai em 380/28. Diálogos de várias etapas mantêm um painel cuja altura anima para o tamanho medido de cada etapa (LAYOUT 550/40 + ResizeObserver) enquanto as etapas deslizam (x: ±12px, popLayout, 0,16s easeOut fades). Rodapé: faixa h-12, Back (secondary xs) + primary xs, alinhado à direita.

Barra de abas

A pílula ativa é uma janela fixa; os rótulos deslizam atrás dela. O rótulo que chega entra da direção de viagem a 110% de sua própria largura; o que sai sai a 130px fixos no outro sentido (uma percentagem falha quando a pílula cresce em direção a uma palavra mais longa). Largura/altura da pílula seguem o rótulo medido em molas LAYOUT. Opacidade total durante todo o tempo; a máscara faz o trabalho.

Esqueletos

  1. O Chrome nunca espera; apenas a área de dados é esqueleto.
  2. Correspondência de pixels: uma barra fica dentro da mesma linha de altura que o texto que representa (flex h-6 items-center envolvendo uma barra h-3); a troca não move nada.
  3. Os dados chegam por foco: uma animação única de opacity ~0.4 + blur(4px) para nítido. Uma animação, não uma transição (o conteúdo monta já final; uma transição não tem valor anterior). Mantenha filter: blur(0) depois.

Barra: rounded bg-muted-foreground/15, varredura lenta de posição de fundo (2s linear infinito, deslocamento inicial -1s para que as barras fiquem fora de fase).

Avisos, estados vazios, a pílula flutuante

Faixa de aviso acima do conteúdo que explica:

<div class="flex flex-col gap-2 rounded-2xl border border-border bg-card
            px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
     role="status">
  <p class="text-sm leading-6 text-muted-foreground">
    <span class="font-medium text-foreground">Nenhum evento ainda.</span>
    Se o snippet estiver ativo, navegue pelo seu site e pegaremos a primeira
    pageview em segundos.
  </p>
  <button class="…secondary pill…">Verificar a instalação</button>
</div>

Uma afirmação em negrito, uma frase cinza, uma ação secundária; empilha em sm:; vive exatamente enquanto o estado dura; sem dispensar.

Pílula flutuante para condições permanentes em todo o app: fixed top-16, centralizada, rounded-full bg-card py-1.5 pl-3 pr-4 text-xs font-medium, sombra flutuante, ponto de status de 8px (pulsando quando urgente), entrada com mola em BANNER (400/30) a partir de y:-8, saída 0.15s. A pílula inteira linka para onde o estado é resolvido. Pílulas de estado nunca se dispensam automaticamente; pílulas de evento se aposentam após ~6s.

Toasts

Abaixo de 0,35s, atenuação cubic-bezier(0.5, 1, 0.89, 1):

  • Sucesso: pulso scale 1 → 1.025 → 0.99 → 1 (0,32s).
  • Erro: tremor x 0 → -3 → 3 → -3 → 0 px (0,28s).

Cópias de keyframe ímpares/pares alternadas para que as repetições recomecem.


5. Movimento

NomeValorUsado para
PANEL550 / 38dropdowns, menus, painéis, alternadores
LAYOUT550 / 40altura/largura medida, pílulas deslizantes
POP400 / 26entrada de modal
POP_EXIT380 / 28saída de modal
BANNER400 / 30pílulas flutuantes, banners de página
FLICK900 / 50micromovimentos de ícone
CHART300 / 25-30tooltips e crosshair de gráfico

Micro fades: 0,1s a 0,18s easeOut. Nada no chrome do app faz tween por mais de 0,2s; se parecer lento, diminua o amortecimento, não aumente a duração.

Movimentos característicos: coreografia de altura medida (um painel constante, etapas deslizando), o destaque viajante (um elemento ativo compartilhado, nunca repaints por item), assimetria de entrada/saída (saídas sempre mais rápidas), hover como apenas cor (mais no máximo um deslize de seta de 2px; cards nunca levantam ou escalam), e no máximo um loop ambiente minúsculo por tela.

Regras: AnimatePresence para tudo que desmonta; respeite prefers-reduced-motion (revelações colapsam para visível, loops param); nunca anime layout não medido; nunca leia o relógio de parede durante a renderização (inscreva-se em uma store de ticks em intervalos de minuto).


6. Páginas de destino

Mesmos tokens, s

…

[truncado para tradução]

Habilidades Relacionadas