Ir para o conteúdo principal

Devlog · Fase 0 · 6/7

Um design system Catppuccin, glass-first

Vinte e seis tokens Catppuccin por sabor, um glassmorphism a 62% de transparência e três famílias tipográficas self-hosted: a pele de aubia.dev, do token CSS ao reduced-motion.

Publicado em 17 agosto 20265 min de leitura
  • design-system
  • catppuccin
  • react

Uma vez o backend arrumado em módulos, foi preciso dar um rosto a aubia.dev. Eu queria uma identidade concisa, escura por padrão, que respire cuidado sem gritar. O resultado se resume a quatro escolhas: uma paleta Catppuccin, um theming que se ajusta sozinho, uma linguagem visual em vidro, e um movimento comedido. O código real do repo apoia cada uma delas.

Catppuccin: quatro sabores, dois por padrão

A paleta vem do Catppuccin, uma família de cores bem conhecida no ecossistema dev. Aubia empacota quatro sabores, mas a landing expõe apenas dois: Mocha no escuro, Latte no claro. Os vinte e seis tokens de cada sabor são declarados como variáveis CSS no bloco @theme de resources/css/app.css. O Tailwind v4 se configura aqui inteiramente em CSS, sem arquivo tailwind.config.js.

@theme {
    --color-ctp-base: #1e1e2e;
    --color-ctp-mantle: #181825;
    --color-ctp-text: #cdd6f4;
    --color-ctp-mauve: #cba6f7;
    /* ... vingt-six tokens pour Mocha */
}

O mauve é meu acento primário. Os dois outros sabores, Frappé e Macchiato, são empacotados como variantes escuras para a Fase 1, mas permanecem fora do switcher público enquanto a landing mantém sua identidade concisa. O ponto-chave: nenhum componente referencia um hexadecimal fixo. Uma classe como bg-ctp-mauve muda de valor com o sabor ativo, sem que o JSX saiba nada disso.

O theming se ajusta sozinho

A escolha de tema segue hoje uma regra simples: escuro por padrão. Em abril, a landing partia do inverso, o sistema primeiro, lendo prefers-color-scheme na montagem para aplicar Mocha ou Latte conforme a configuração do SO. Inverti esse padrão para escuro no mês de junho. Agora, um visitante chega em Mocha aconteça o que acontecer, e só segue a configuração do seu SO se escolher explicitamente a opção "sistema" no switcher. O sabor ativo é posto em <html data-theme="...">, e os valores de Latte sobrescrevem os de Mocha por meio desse atributo. Qualquer escolha explícita, claro, escuro ou sistema, é persistida no localStorage e prevalece sobre o padrão no retorno seguinte.

Essa inversão não é cosmética. O alvo de Aubia são desenvolvedores que vivem em dark mode: melhor acolhê-los no seu ambiente natural do que infligir-lhes um flash branco na abertura da página.

Glass-first: a transparência como partido

A linguagem visual da landing repousa sobre cartões em glassmorphism: fundo semitransparente, borda fina, desfoque de fundo. É isso que deixa entrever o gradiente da página através de cada superfície e dá aquela impressão de profundidade abafada. Em vez de repetir a mesma pilha de classes em cada cartão, a receita vive em uma utility Tailwind v4 declarada em CSS:

@utility glass-card {
    background: color-mix(in oklch, var(--glass-bg-card) 62%, transparent);
    backdrop-filter: blur(40px) saturate(1.6) brightness(1.02);
    border: 1px solid color-mix(in oklch, var(--glass-stroke) 12%, transparent);
    box-shadow: inset 0 1px 0 color-mix(in oklch, var(--glass-highlight) 8%, transparent);
}

O detalhe que faz ou desfaz o efeito é a transparência. Um fundo opaco anula o backdrop-filter: sem os 62% do color-mix, o desfoque não tem mais nada para desfocar e o cartão vira um simples retângulo. O saturate e o brightness devolvem ao vidro um pouco de vida que o desfoque sozinho apagaria, e a sombra é interna, um simples fio de luz na aresta superior. A disciplina é estrita: fundo translúcido, borda mal marcada, nunca uma sombra projetada pesada que quebraria a ilusão.

Três famílias tipográficas self-hosted

A tipografia repousa sobre três famílias, todas hospedadas localmente: Geist para o corpo e a interface, Geist Mono para o código e os caminhos de arquivos, Chakra Petch para os grandes títulos de display.

Nenhuma chamada de rede a uma fundição de terceiros: as declarações @font-face são geradas no build pelo plugin de fontes do Laravel, a partir dos pacotes instalados. Em junho, mudei o carregamento para a diretiva @fonts, que injeta essas declarações no <head> antes da folha de estilo principal.

Hospedar localmente ultrapassa a performance: chamar uma fundição de terceiros vazaria o IP de cada visitante para um subprocessador fora da UE, o que recuso para uma vitrine que se quer irrepreensível quanto à vida privada. Compus esse trio de propósito. O wordmark Aubia exige Chakra Petch, e manter um Inter para o corpo teria criado uma divergência permanente com esse título. Geist e Geist Mono vêm da mesma família, o alinhamento vertical delas entre prosa e código é nativo.

O bloco @theme permanece a fonte de verdade das três pilhas de fontes:

--font-display: 'Chakra Petch', 'Chakra Petch fallback', system-ui, sans-serif;
--font-sans: 'Geist', 'Geist fallback', system-ui, sans-serif;
--font-mono: 'Geist Mono', 'Geist Mono fallback', ui-monospace, monospace;

A fonte fallback que segue cada família neutraliza o deslocamento de layout no momento do swap. Apenas Chakra Petch é pré-carregada, porque carrega o título principal e pesa no rendering percebido.

O movimento, com uma trava de segurança

As animações passam pelo Motion. Centralizei os variants reutilizáveis em um único arquivo, motion-presets.ts, em vez de redefini-los em cada componente. O aparecimento padrão das seções é um fade ascendente, no easing signature Luma:

export const fadeUpSpring: Variants = {
    hidden: { opacity: 0, y: 24 },
    visible: {
        opacity: 1,
        y: 0,
        transition: { duration: 0.6, ease: EASE_LUMA },
    },
};

A trava de segurança é a acessibilidade. Um visitante que pediu menos movimento no nível do sistema não deve ser submetido a fades. O Motion respeita prefers-reduced-motion nativamente, e eu normalizo o seu valor com um hook useSafeReducedMotion() que sempre retorna um booleano, mesmo durante o rendering do servidor, onde a media query ainda não é avaliada. O orçamento de animação é deliberadamente apertado: raramente mais de dois elementos animados por vez no campo visível, para hierarquizar a atenção em vez de dispersá-la.

Essas escolhas visuais servem a uma convicção de produto que detalho na seção Visão da landing. O último artigo da série olha para o que os próprios motores veem de tudo isso: o SEO multilíngue e a extração pelos motores de resposta de IA. Se o espírito de Aubia fala com você, você pode entrar na lista de espera.

Esta construção é contada aqui, artigo após artigo. Embarque: o seu feedback desenhará o que vem a seguir.

Entrar na lista de espera