Theming
O sistema de temas usa Tailwind CSS v3 com tokens organizados em objetos aninhados por secao. Forks
sobrescrevem os arquivos de app/config/theme/ (substituicao de arquivo inteiro) para personalizar
cores, fonte e dimensoes.
app/config/theme/ — o que existe
| Arquivo | Export | Papel |
|---|---|---|
colors.ts | themeColors | Paleta completa: 15 tokens globais + 12 secoes aninhadas |
fonts.ts | themeFonts | Familia da fonte + fallback com metricas ajustadas |
sizes.ts | layoutConfig | contentMaxWidth (→ max-w-content) e dimensoes de logo por area |
font-preloads.ts | fontPreloads | URLs dos woff2 a precarregar (<link rel="preload">) |
header.ts | headerStyle | Chrome do header: divisoria inferior, classes dos botoes de auth, alinhamento e aparencia da nav |
mobile-bottom-nav.ts | mobileBottomNavStyle | Superficies da barra de navegacao mobile |
search-input.ts | searchInputStyle | Superficie visual (borda, fundo, raio, sombra) dos inputs de busca, por surface |
Os tres ultimos seguem a mesma divisao de responsabilidade: layout estrutural fica no componente, superficie visual fica no config — assim uma brand troca a aparencia sem forkar o componente.
colors.ts
A estrutura usa objetos aninhados, nao chaves flat com prefixo. A convencao de nome de classe e
{utility}-{secao}-{token}:
// app/config/theme/colors.ts
export const themeColors = {
// ── Default (globais) ──
primary: "#a3d712",
"bg-primary": "#212425",
secondary: "#f18825",
"bg-secondary": "#323637",
texts: "#fdffff",
links: "#fdffff",
titles: "#fdffff",
"button-bg": "#a3d712",
"button-text": "#212425",
accent: "#a3d712",
success: "#22bb33",
money: "#beeb0a", // verde "cor de dinheiro" — valores de ganho
error: "#f1416c",
warning: "#ff9f43",
info: "#0096ff",
game: { /* … */ },
header: { /* … */ },
sidebar: { /* … */ },
footer: { /* … */ },
auth: { /* … */ },
payments: { /* … */ },
topbar: { /* … */ },
bottomNotification: { /* … */ },
loader: { /* … */ },
"ftd-cashback": { /* … */ },
"ftd-offer": { /* … */ },
manifest: { /* … */ },
} as const;
export type ThemeColors = typeof themeColors;
Tokens globais
| Token | Classes | Uso |
|---|---|---|
primary | bg-primary, text-primary | Cor primaria da marca |
bg-primary | bg-bg-primary | Fundo principal do app |
secondary | text-secondary | Cor secundaria |
bg-secondary | bg-bg-secondary | Fundo secundario |
texts | text-texts | Texto padrao |
links | text-links | Links |
titles | text-titles | Titulos |
button-bg / button-text | bg-button-bg / text-button-text | Botoes globais |
accent | bg-accent, text-accent | Destaque adicional |
success / error / warning / info | bg-*, text-* | Estados |
money | text-money | Valores monetarios de ganho. Mais amarelado que success; cada brand deriva do proprio verde |
button-text tambem e exposto como primary-foreground.
:::caution money tem fallback, os outros nao
No tailwind.config.js, money: themeColors.money ?? themeColors.success — override que ainda nao
propagou o token nao quebra o build. Nenhum outro token tem essa rede. Ver a regra de propagacao
mais abaixo.
:::
Secoes aninhadas
| Secao | Prefixo da classe | Tokens |
|---|---|---|
game | *-game-* | title, subtitle, button-bg, button-text, overlay, icon, online-bg, online-text, online-dot, balloon-bg, balloon-text, balloon-value, balloon-icon |
header | *-header-* | bg, links, texts, active-icon, promo-icon, nav-link, nav-link-active, register-bg/text/border, login-bg/text/border, deposit-bg/text/border, balance-bg, balance-text, avatar-bg, top-bg, top-text, main-bg, glow |
sidebar | *-sidebar-* | bg, links, titles, icon, button-bg, button-text, cta-bg, cta-text |
footer | *-footer-* | bg-primary, bg-secondary, links, titles, texts, button-bg, button-text, app-button-bg |
auth | *-auth-* | bg-primary, bg-inputs, text-inputs, bg-close, icon-close, links, titles, texts, button-bg, button-text, backdrop |
payments | *-payments-* | bg-primary, bg-inputs, text-inputs, titles, texts, button-bg, button-text |
topbar | *-topbar-* | bg, text, icon-bg, icon-text, cta-bg, cta-text |
bottomNotification | *-bottom-notification-* | bg, border, text, icon-bg, icon-text, cta-bg, cta-text |
loader | *-loader-* | bar, spinner |
ftd-cashback | *-ftd-cashback-* | first-bg-from/to, first-text-accent, prize-bg-from/to, prize-amount-color, prize-glow-color, prize-coin-color |
ftd-offer | *-ftd-offer-* | Modal + countdown + thumb + template + toast (~26 tokens) |
manifest | — | theme-color, background-color. Nao gera classe Tailwind: e consumido direto por routes/manifest[.]json.ts |
:::info Um mapeamento de nome que confunde
A chave em colors.ts e bottomNotification (camelCase), mas o nome no Tailwind e
bottom-notification (kebab-case) — a traducao acontece no tailwind.config.js. As classes sao
bg-bottom-notification-bg, text-bottom-notification-text, etc.
:::
Todos os modais de validacao (ValidationBlockerOverlay, ValidationStepsModal,
PasswordValidationModal) e o Modal base usam exclusivamente tokens auth.* — trocar essas cores
no fork adapta todos os modais de uma vez.
tailwind.config.js
:::danger E .js (ESM), nao .ts, e nao importa o config — ele o PARSEIA
O arquivo e tailwind.config.js. Ele nao faz import { themeColors } from "./app/config/theme/…":
o Tailwind roda fora do pipeline de aliases do Vite, entao o config le e avalia o arquivo .ts como
texto em build time, via readExportFromTs + resolveBrandFile(projectRoot, getBrandKey(), …). O
brand key vem de process.env.ORIGIN_DOMAIN ou do ORIGIN_DOMAIN lido do .dev.vars.
:::
// tailwind.config.js (trechos reais)
import formsPlugin from "@tailwindcss/forms";
import defaultTheme from "tailwindcss/defaultTheme";
import { resolveBrandFile, toBrandOverrideKey } from "./vite-plugins/brand-resolver.mjs";
const themeColors = readExportFromTs(resolveBrandFile(root, brandKey, "app/config/theme/colors.ts"), "themeColors");
const layoutConfig = readExportFromTs(resolveBrandFile(root, brandKey, "app/config/theme/sizes.ts"), "layoutConfig");
const themeFonts = readExportFromTs(resolveBrandFile(root, brandKey, "app/config/theme/fonts.ts"), "themeFonts");
const sansFontStack = [themeFonts.family, themeFonts.fallback, ...defaultTheme.fontFamily.sans];
export default {
content: ["./app/**/*.{js,jsx,ts,tsx}", "./overrides/**/app/**/*.{js,jsx,ts,tsx}"],
darkMode: "class",
theme: {
colors: {
transparent: "transparent",
current: "currentColor",
white: "#ffffff",
black: "#000000",
primary: themeColors.primary,
// … tokens globais e secoes
},
extend: {
fontFamily: { sans: sansFontStack },
maxWidth: { content: layoutConfig.contentMaxWidth },
screens: { xs: "368px", xxs: "390px" },
// animation, keyframes, fontSize, boxShadow, transitionDuration, gridTemplateRows
},
},
plugins: [formsPlugin, ({ addBase }) => { /* custom properties em :root */ }],
};
O contrato de parse (footgun real)
Porque os arquivos sao parseados, nao importados, eles precisam de uma forma literal exata:
export const <nome> = { … } as const;— o regex procura literalmente poras const;- Somente valores primitivos. Sem
import, sem spread, sem valor computado, sem template string com interpolacao, sem referencia a outra variavel - Vale para os tres arquivos parseados:
colors.ts,sizes.tsefonts.ts
Quebrar isso nao produz um erro sutil: o readExportFromTs lanca
Could not parse <export> from <file> e o build para.
Paleta
theme.colors substitui a paleta default do Tailwind (nao usa extend), entao text-gray-500,
bg-blue-600, bg-zinc-* e afins nao existem.
:::caution bg-white existe, sim
theme.colors declara explicitamente transparent, current, white e black. Esses quatro sao a
lista de excecoes: bg-white, text-black, bg-transparent, text-current funcionam. O que nao
existe e o resto da paleta numerada do Tailwind.
:::
Sem important
O config nao tem important: true — a chave foi removida em 2026-04-29
(perf(tailwind): drop important: true — CSS -23.5KB raw (-14.4%)). Qualquer orientacao no sentido
de "as classes do Tailwind ganham de style inline" esta invertida hoje. Para vencer especificidade,
use ! por classe (!rounded-3xl) — e o padrao adotado nos configs de chrome como headerStyle.
Plugins
| Plugin | Situacao |
|---|---|
@tailwindcss/forms | O unico plugin de terceiro instalado |
addBase inline | Plugin local que injeta custom properties em :root: --auth-text-inputs, --auth-bg-inputs, --color-loader-bar, --font-sans |
@tailwindcss/typography, @tailwindcss/aspect-ratio e tailwind-textfill nao estao no
package.json — prose e aspect-w-* nao existem no projeto.
content inclui os overrides
content: ["./app/**/*.{js,jsx,ts,tsx}", "./overrides/**/app/**/*.{js,jsx,ts,tsx}"]
Sem escanear overrides/<brand>/**, uma classe usada apenas por um componente de override seria
purgada do build daquela brand e a pagina renderizaria sem estilo.
Custom screens e outros extends
| Extend | Valor |
|---|---|
screens.xs | 368px |
screens.xxs | 390px |
maxWidth.content | layoutConfig.contentMaxWidth (base: 1400px) |
fontSize.2xs | 0.675rem |
fontSize.sm | 13px / 18px de line-height — o default do Tailwind (14px) foi sobrescrito globalmente, para todas as brands |
boxShadow.input | 0 0 0 100px <auth.bg-inputs> inset (mata o autofill amarelo do Chrome) |
transitionDuration.350 | 350ms |
animation / keyframes | fadeIn, slide-up, arrow-bounce-x, shake, bell-shake, ping-slow |
Fonte
A fonte e brand-overridable por arquivo, nao hardcoded no config do Tailwind.
// app/config/theme/fonts.ts
export const themeFonts = {
family: "Montserrat",
fallback: "Montserrat Fallback",
} as const;
O tailwind.config.js monta sansFontStack = [family, fallback, ...defaultTheme.fontFamily.sans] e
o usa em dois lugares: theme.extend.fontFamily.sans (utility font-sans) e a custom property
--font-sans em :root — que body e os placeholders de input leem no app/tailwind.css. Trocar o
arquivo troca a fonte de toda a UI sem tocar em CSS de componente.
Os @font-face / @import ficam em app/tailwind.css, apenas com os subsets latin:
@import "@fontsource/montserrat/latin-400.css";
@import "@fontsource/montserrat/latin-600.css";
@import "@fontsource/montserrat/latin-700.css";
Ha tambem um @font-face de fallback com metricas ajustadas (Montserrat Fallback, local("Arial")
com size-adjust / ascent-override / descent-override) — existe para evitar CLS no swap de
fonte. O nome da familia declarado em fonts.ts precisa casar com um @font-face real.
Trocar de fonte numa brand exige tres pecas coerentes:
overrides/<brand>/app/config/theme/fonts.ts— nomes das familiasoverrides/<brand>/app/config/theme/font-preloads.ts— URLs doswoff2daquela familia (assim os arquivos de fonte so entram no bundle da brand que os usa)- Os
@font-facecorrespondentes — fontes especificas de brand vivem emapp/styles/brand-fonts.css, importado com?inlinee injetado num<style data-brand-fonts>noapp/root.tsx(brand-resolved), para que oswoff2de uma fonte so entrem no bundle da brand que a usa
Como personalizar em um fork
- Copie o arquivo para
overrides/<brand-key>/app/config/theme/<arquivo>.ts - Altere os valores mantendo a estrutura e a forma literal (
export const … as const;) - Rode o dev server com o
ORIGIN_DOMAINda brand — o Tailwind recompila com os tokens novos - Para uma secao nova, adicione o objeto aninhado e mapeie a chave em
tailwind.config.js(theme.colors) — o mapeamento e explicito, campo por campo
:::danger Override e substituicao de arquivo INTEIRO
Nao ha deep-merge. Ao adicionar um token novo em app/config/theme/colors.ts (ou em sizes.ts /
fonts.ts), propague para todos os overrides existentes — senao a brand recebe undefined no
token e o build quebra ou a cor sai vazia. O money e a unica excecao, com fallback explicito no
config. Ver Forking — Override Files.
:::