nexus-core-v3 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/agents/README.md +133 -0
- package/agents/_protocol.md +107 -0
- package/agents/analyst.md +138 -0
- package/agents/architect.md +146 -0
- package/agents/data-engineer.md +170 -0
- package/agents/dev.md +134 -0
- package/agents/devops.md +141 -0
- package/agents/nexus-master.md +147 -0
- package/agents/pm.md +133 -0
- package/agents/po.md +138 -0
- package/agents/qa.md +192 -0
- package/agents/sm.md +122 -0
- package/agents/squad-creator.md +121 -0
- package/agents/ux-design-expert.md +165 -0
- package/artifact-manifest.json +903 -0
- package/bin/nexus.mjs +37 -0
- package/checklists/README.md +49 -0
- package/checklists/architect-checklist.md +47 -0
- package/checklists/change-checklist.md +61 -0
- package/checklists/db-predeploy-checklist.md +57 -0
- package/checklists/design-quality-checklist.md +57 -0
- package/checklists/discovery-checklist.md +36 -0
- package/checklists/foundation-checklist.md +39 -0
- package/checklists/launch-checklist.md +39 -0
- package/checklists/pm-checklist.md +48 -0
- package/checklists/po-master-checklist.md +64 -0
- package/checklists/reality-check-checklist.md +49 -0
- package/checklists/story-dod-checklist.md +52 -0
- package/checklists/story-draft-checklist.md +36 -0
- package/dist/bin/dashboard.html +279 -0
- package/dist/bin/nexus.mjs +20008 -0
- package/dist/constitution.yaml +76 -0
- package/knowledge/README.md +57 -0
- package/knowledge/architecture/architectural-styles-map.md +182 -0
- package/knowledge/architecture/design-patterns-gof.md +192 -0
- package/knowledge/architecture/distributed-patterns-cheatsheet.md +201 -0
- package/knowledge/architecture/saas-subscription-blueprint.md +355 -0
- package/knowledge/architecture/system-design-tradeoffs.md +231 -0
- package/knowledge/architecture/t3-fullstack-typesafe-stack.md +273 -0
- package/knowledge/copy/landing-copy-that-converts.md +168 -0
- package/knowledge/data/postgres-indexing-and-tuning.md +263 -0
- package/knowledge/data/schema-modeling-decisions.md +273 -0
- package/knowledge/data/supabase-rls-patterns.md +316 -0
- package/knowledge/data/zero-downtime-migrations.md +308 -0
- package/knowledge/devops/cicd-pipeline-best-practices.md +318 -0
- package/knowledge/devops/production-dockerfile.md +283 -0
- package/knowledge/devops/twelve-factor-app.md +398 -0
- package/knowledge/engineering/clean-code-principles.md +429 -0
- package/knowledge/engineering/effective-code-review.md +204 -0
- package/knowledge/engineering/testing-strategy-beyond-unit.md +307 -0
- package/knowledge/governance/risk-matrix.md +56 -0
- package/knowledge/integration/mcp-server-selection-matrix.md +235 -0
- package/knowledge/marketing/copy-que-converte.md +43 -0
- package/knowledge/marketing/funil-e-jornada.md +36 -0
- package/knowledge/negocios/proposta-vencedora.md +38 -0
- package/knowledge/negocios/roi-e-unit-economics.md +46 -0
- package/knowledge/pipeline/1-descobrir.md +26 -0
- package/knowledge/pipeline/2-estrategizar.md +26 -0
- package/knowledge/pipeline/3-estruturar.md +27 -0
- package/knowledge/pipeline/4-construir.md +27 -0
- package/knowledge/pipeline/5-endurecer.md +28 -0
- package/knowledge/pipeline/6-lancar.md +27 -0
- package/knowledge/pipeline/7-operar.md +27 -0
- package/knowledge/security/lgpd-conformidade-basica.md +35 -0
- package/knowledge/security/owasp-secure-coding-gates.md +220 -0
- package/knowledge/security/owasp-top10-threat-assessment.md +287 -0
- package/knowledge/security/threat-modeling-stride.md +34 -0
- package/knowledge/web-craft/a11y-audit-checklist.md +251 -0
- package/knowledge/web-craft/accessible-component-patterns.md +383 -0
- package/knowledge/web-craft/anti-ai-look.md +114 -0
- package/knowledge/web-craft/design-system-from-code.md +195 -0
- package/knowledge/web-craft/intrinsic-css-layout.md +420 -0
- package/knowledge/web-craft/style-cloning.md +185 -0
- package/knowledge/web-craft/visual-polish-review.md +183 -0
- package/package.json +55 -0
- package/runbooks/campanha-de-conteudo.md +36 -0
- package/runbooks/feature-em-projeto-existente.md +37 -0
- package/runbooks/mvp-startup.md +38 -0
- package/runbooks/resposta-a-incidente.md +37 -0
- package/squads/exemplo-conteudo/agents/editor-chefe.md +48 -0
- package/squads/exemplo-conteudo/agents/pesquisador.md +44 -0
- package/squads/exemplo-conteudo/agents/redator.md +45 -0
- package/squads/exemplo-conteudo/knowledge/estilo-editorial.md +21 -0
- package/squads/exemplo-conteudo/squad.yaml +19 -0
- package/squads/exemplo-conteudo/tasks/pesquisar-fontes.md +26 -0
- package/squads/exemplo-conteudo/tasks/planejar-pauta.md +27 -0
- package/squads/exemplo-conteudo/tasks/redigir-artigo.md +26 -0
- package/squads/exemplo-conteudo/tasks/revisar-artigo.md +27 -0
- package/squads/marketing/agents/analista.md +56 -0
- package/squads/marketing/agents/chefe-marketing.md +65 -0
- package/squads/marketing/agents/conteudo.md +55 -0
- package/squads/marketing/agents/copy.md +55 -0
- package/squads/marketing/agents/growth.md +56 -0
- package/squads/marketing/agents/social.md +55 -0
- package/squads/marketing/squad.yaml +17 -0
- package/squads/marketing/tasks/aprovar-campanha.md +43 -0
- package/squads/negocios/agents/chefe-negocios.md +65 -0
- package/squads/negocios/agents/financas-roi.md +55 -0
- package/squads/negocios/agents/suporte.md +55 -0
- package/squads/negocios/agents/vendas-proposta.md +56 -0
- package/squads/negocios/squad.yaml +17 -0
- package/squads/negocios/tasks/aprovar-proposta.md +40 -0
- package/squads/security/agents/appsec-reviewer.md +59 -0
- package/squads/security/agents/chefe-seguranca.md +65 -0
- package/squads/security/agents/compliance-auditor.md +60 -0
- package/squads/security/agents/threat-modeler.md +60 -0
- package/squads/security/squad.yaml +20 -0
- package/squads/security/tasks/aprovar-gate-seguranca.md +42 -0
- package/squads/security/tasks/emitir-parecer-conformidade.md +42 -0
- package/tasks/README.md +72 -0
- package/tasks/accessibility-wcag-checklist.md +69 -0
- package/tasks/advanced-elicitation.md +42 -0
- package/tasks/analyze-performance.md +54 -0
- package/tasks/analyze-project-structure.md +59 -0
- package/tasks/apply-qa-fixes.md +57 -0
- package/tasks/architect-analyze-impact.md +62 -0
- package/tasks/archive-squad.md +52 -0
- package/tasks/audit-codebase.md +53 -0
- package/tasks/build-component.md +61 -0
- package/tasks/calculate-roi.md +63 -0
- package/tasks/ci-cd-configuration.md +51 -0
- package/tasks/collect-visual-evidence.md +62 -0
- package/tasks/compose-molecule.md +57 -0
- package/tasks/consolidate-patterns.md +54 -0
- package/tasks/create-brownfield-prd.md +54 -0
- package/tasks/create-competitor-analysis.md +42 -0
- package/tasks/create-deep-research-prompt.md +62 -0
- package/tasks/create-doc.md +62 -0
- package/tasks/create-epic.md +49 -0
- package/tasks/create-front-end-spec.md +56 -0
- package/tasks/create-migration-plan.md +57 -0
- package/tasks/create-next-story.md +66 -0
- package/tasks/create-prd.md +53 -0
- package/tasks/create-project-brief.md +47 -0
- package/tasks/create-rls-policies.md +59 -0
- package/tasks/create-schema.md +57 -0
- package/tasks/create-service.md +55 -0
- package/tasks/create-squad.md +100 -0
- package/tasks/create-suite.md +62 -0
- package/tasks/db-apply-migration.md +56 -0
- package/tasks/db-domain-modeling.md +57 -0
- package/tasks/db-dry-run.md +50 -0
- package/tasks/db-env-check.md +57 -0
- package/tasks/db-load-csv.md +54 -0
- package/tasks/db-policy-apply.md +58 -0
- package/tasks/db-rollback.md +51 -0
- package/tasks/db-run-sql.md +61 -0
- package/tasks/db-seed.md +52 -0
- package/tasks/db-smoke-test.md +51 -0
- package/tasks/db-snapshot.md +48 -0
- package/tasks/db-verify-order.md +49 -0
- package/tasks/deliberate.md +46 -0
- package/tasks/design-indexes.md +59 -0
- package/tasks/dev-develop-story.md +61 -0
- package/tasks/document-project.md +59 -0
- package/tasks/execute-checklist.md +57 -0
- package/tasks/execute-epic-plan.md +52 -0
- package/tasks/execute-subtask.md +51 -0
- package/tasks/extend-pattern.md +63 -0
- package/tasks/extend-squad.md +60 -0
- package/tasks/extract-patterns.md +64 -0
- package/tasks/extract-tokens.md +59 -0
- package/tasks/facilitate-brainstorming-session.md +42 -0
- package/tasks/generate-ai-frontend-prompt.md +57 -0
- package/tasks/generate-documentation.md +60 -0
- package/tasks/generate-migration-strategy.md +57 -0
- package/tasks/generate-shock-report.md +56 -0
- package/tasks/mcp-management.md +66 -0
- package/tasks/orchestrate.md +50 -0
- package/tasks/perform-market-research.md +42 -0
- package/tasks/plan-create-context.md +57 -0
- package/tasks/plan-create-implementation.md +58 -0
- package/tasks/po-close-story.md +60 -0
- package/tasks/po-manage-story-backlog.md +59 -0
- package/tasks/po-pull-story.md +60 -0
- package/tasks/po-sync-story.md +59 -0
- package/tasks/pr-automation.md +50 -0
- package/tasks/pre-push-quality-gate.md +54 -0
- package/tasks/push.md +53 -0
- package/tasks/qa-browser-console-check.md +52 -0
- package/tasks/qa-create-fix-request.md +58 -0
- package/tasks/qa-evidence-requirements.md +55 -0
- package/tasks/qa-false-positive-detection.md +55 -0
- package/tasks/qa-fix-issues.md +55 -0
- package/tasks/qa-gate.md +53 -0
- package/tasks/qa-migration-validation.md +58 -0
- package/tasks/qa-nfr-assess.md +45 -0
- package/tasks/qa-review-story.md +56 -0
- package/tasks/qa-risk-profile.md +45 -0
- package/tasks/qa-security-checklist.md +64 -0
- package/tasks/qa-test-design.md +47 -0
- package/tasks/qa-trace-requirements.md +48 -0
- package/tasks/release-management.md +53 -0
- package/tasks/repository-cleanup.md +61 -0
- package/tasks/route.md +44 -0
- package/tasks/run-tests.md +50 -0
- package/tasks/security-audit.md +54 -0
- package/tasks/setup-database.md +60 -0
- package/tasks/setup-design-system.md +60 -0
- package/tasks/shard-doc.md +60 -0
- package/tasks/spec-assess-complexity.md +55 -0
- package/tasks/spec-critique.md +64 -0
- package/tasks/spec-gather-requirements.md +48 -0
- package/tasks/spec-research-dependencies.md +42 -0
- package/tasks/spec-write-spec.md +50 -0
- package/tasks/test-as-user.md +52 -0
- package/tasks/ux-create-wireframe.md +54 -0
- package/tasks/ux-user-research.md +55 -0
- package/tasks/validate-next-story.md +61 -0
- package/tasks/validate-squad.md +55 -0
- package/tasks/verify-subtask.md +52 -0
- package/tasks/version-management.md +45 -0
- package/templates/README.md +47 -0
- package/templates/architecture-tmpl.md +115 -0
- package/templates/competitor-analysis-tmpl.md +87 -0
- package/templates/epic-tmpl.md +83 -0
- package/templates/front-end-spec-tmpl.md +110 -0
- package/templates/market-research-tmpl.md +98 -0
- package/templates/migration-plan-tmpl.md +92 -0
- package/templates/prd-tmpl.md +95 -0
- package/templates/project-brief-tmpl.md +100 -0
- package/templates/qa-verdict-tmpl.md +73 -0
- package/templates/rls-policies-tmpl.md +93 -0
- package/templates/schema-design-tmpl.md +107 -0
- package/templates/spec-tmpl.md +88 -0
- package/templates/squad/agent-dna-tmpl.md +72 -0
- package/templates/squad/chief-dna-tmpl.md +98 -0
- package/templates/squad/squad-task-tmpl.md +50 -0
- package/templates/squad/squad-yaml-tmpl.md +47 -0
- package/templates/story-tmpl.md +63 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: design-system-from-code
|
|
3
|
+
domain: web-craft
|
|
4
|
+
agents: [ux-design-expert]
|
|
5
|
+
when: "ao consolidar um design system a partir de um codebase existente"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Design system a partir de código — consolidar o que já existe, sem inventar
|
|
9
|
+
|
|
10
|
+
Codebase real não tem design system. Tem **arqueologia**: três anos de PRs, quatro pessoas com gostos
|
|
11
|
+
diferentes, um copy-paste de Figma que ninguém atualizou, e `#3B82F6` digitado à mão em 47 lugares. O
|
|
12
|
+
trabalho aqui **não é desenhar** — é *auditar, agrupar e migrar* o que está espalhado, transformando
|
|
13
|
+
caos em tokens reutilizáveis **sem quebrar quem consome**. A regra-mãe: **todo token tem que ter
|
|
14
|
+
origem rastreável no código atual.** Você não está criando uma paleta nova — está nomeando a que já
|
|
15
|
+
existe e matando as divergências. Inventar uma cor "mais bonita" no meio da consolidação é trair o
|
|
16
|
+
trabalho: vira redesign disfarçado de auditoria, e ninguém pediu redesign.
|
|
17
|
+
|
|
18
|
+
## O problema / os tells
|
|
19
|
+
|
|
20
|
+
Você está diante de um codebase que precisa de design system se vê 3+ destes:
|
|
21
|
+
|
|
22
|
+
1. **Hex hardcoded espalhado** — `grep -rE '#[0-9a-fA-F]{3,6}'` retorna centenas de hits. O mesmo azul
|
|
23
|
+
aparece como `#3B82F6`, `#3b82f6`, `#3a83f7` (alguém errou de um dígito), `rgb(59,130,246)` e
|
|
24
|
+
`rgba(59,130,246,1)`. São "o mesmo azul" pro olho, cinco valores distintos pro computador.
|
|
25
|
+
2. **Escalas tipográficas paralelas** — um componente usa `text-sm/text-base/text-lg` (Tailwind), outro
|
|
26
|
+
tem `font-size: 15px` cravado, um terceiro herda `1.05rem` de um CSS legado. Três escalas, zero
|
|
27
|
+
intenção. Você conta 11 tamanhos de fonte distintos numa tela só.
|
|
28
|
+
3. **Spacing ad-hoc** — `margin-top: 13px`, `padding: 18px 22px`, `gap: 6px`. Números que não pertencem
|
|
29
|
+
a escala nenhuma, nascidos de "arrastar até parecer certo" no DevTools. O tell clássico: `17px`.
|
|
30
|
+
Ninguém escolhe 17 de propósito.
|
|
31
|
+
4. **Variantes de componente duplicadas** — `Button`, `ButtonNew`, `PrimaryButton`, `CTAButton` e um
|
|
32
|
+
`<button className="btn-primary">` solto. Fazem a mesma coisa, divergem em 3px de padding e um
|
|
33
|
+
shade de hover. Cada um foi "mais rápido criar do novo do que achar o que existia".
|
|
34
|
+
5. **z-index sem sistema** — `z-index: 9999`, `z-index: 99999`, `z-index: 100`, `z-index: 9998` (o
|
|
35
|
+
hack pra "ficar atrás do 9999"). Guerra de empilhamento, não arquitetura de camadas.
|
|
36
|
+
6. **Sombra e raio por gosto do dia** — `box-shadow` com 6 receitas diferentes de blur/spread/opacity;
|
|
37
|
+
`border-radius` variando entre `4px`, `6px`, `8px`, `0.5rem` e `9999px` sem critério de uso.
|
|
38
|
+
7. **Dark mode meia-boca** — metade dos componentes responde a `.dark`, a outra metade tem branco
|
|
39
|
+
cravado e "estoura" no tema escuro. Sintoma de cor hardcoded, não tokenizada.
|
|
40
|
+
8. **O Figma e o código discordam** — o token "primary" no design tool é `#2563EB`, no código rodando
|
|
41
|
+
em produção é `#3B82F6`. A fonte da verdade é o que o usuário vê: **o código em produção**, não o
|
|
42
|
+
arquivo de design. Comece pelo código.
|
|
43
|
+
|
|
44
|
+
Anti-tell (o que NÃO é problema): dois valores próximos podem ser **intencionais** — `border` em `1px`
|
|
45
|
+
e divisor em `1px` mas com cores diferentes é correto. Nem toda repetição é dívida; só a **divergência
|
|
46
|
+
sem razão** é. Não tokenize ruído como se fosse sinal.
|
|
47
|
+
|
|
48
|
+
## Os princípios do craft
|
|
49
|
+
|
|
50
|
+
### 1. Inventário antes de opinião — meça, não ache
|
|
51
|
+
|
|
52
|
+
Não comece propondo paleta. Comece **extraindo a verdade** do código com comandos, não com olho:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Cores: todos os hex, normalizados e contados por frequência
|
|
56
|
+
grep -rohE '#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|rgba?\([^)]+\)' src/ \
|
|
57
|
+
| tr 'A-F' 'a-f' | sort | uniq -c | sort -rn
|
|
58
|
+
|
|
59
|
+
# Font-sizes: toda a escala real em uso
|
|
60
|
+
grep -rohE 'font-size:\s*[0-9.]+(px|rem|em)' src/ | sort | uniq -c | sort -rn
|
|
61
|
+
|
|
62
|
+
# Spacing: padding/margin/gap literais (o que escapou da escala)
|
|
63
|
+
grep -rohE '(margin|padding|gap)[^:]*:\s*[0-9.]+px' src/ | sort | uniq -c | sort -rn
|
|
64
|
+
|
|
65
|
+
# z-index: o mapa da guerra de empilhamento
|
|
66
|
+
grep -rohE 'z-index:\s*-?[0-9]+' src/ | sort | uniq -c | sort -rn
|
|
67
|
+
|
|
68
|
+
# Componentes que parecem o mesmo botão
|
|
69
|
+
grep -rlE 'Button|btn' src/components/ | sort
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
A saída de frequência é ouro: o hex que aparece **89 vezes** é seu primitivo; o que aparece **1 vez** e
|
|
73
|
+
difere por um dígito do primitivo é divergência pra eliminar. Frequência separa o sistema (alto uso,
|
|
74
|
+
intencional) do acidente (uso único, erro de digitação).
|
|
75
|
+
|
|
76
|
+
### 2. Cluster por proximidade perceptual, não por igualdade exata
|
|
77
|
+
|
|
78
|
+
`#3B82F6`, `#3b82f6` e `#3a83f7` são **o mesmo token**. Agrupe por distância de cor (ΔE), não por
|
|
79
|
+
string idêntica. Regra prática sem ferramenta: converta pra HSL e agrupe o que está dentro de ~2% em
|
|
80
|
+
cada canal. Cada cluster vira **um** primitivo; a variante mais frequente do cluster é a canônica, as
|
|
81
|
+
outras são aliases a migrar. Mesma lógica pra spacing: `13px`, `14px`, `15px` provavelmente queriam ser
|
|
82
|
+
`16px` — colapse pro valor da escala mais próximo, não preserve o acidente.
|
|
83
|
+
|
|
84
|
+
### 3. Primitivos → semânticos (duas camadas, nunca uma)
|
|
85
|
+
|
|
86
|
+
O erro fatal é tokenizar direto pra uso: `$button-blue: #3B82F6`. Quando a marca trocar de azul, você
|
|
87
|
+
caça `button-blue` em 200 lugares. Faça **duas camadas**:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
PRIMITIVOS (a paleta crua, sem significado):
|
|
91
|
+
--blue-500: #3B82F6
|
|
92
|
+
--blue-600: #2563EB
|
|
93
|
+
--gray-50: #F9FAFB
|
|
94
|
+
|
|
95
|
+
SEMÂNTICOS (o significado, apontando pro primitivo):
|
|
96
|
+
--color-action: var(--blue-500)
|
|
97
|
+
--color-action-hover: var(--blue-600)
|
|
98
|
+
--color-surface: var(--gray-50)
|
|
99
|
+
--color-text-primary: var(--gray-900)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Componentes consomem **só semânticos** (`--color-action`), nunca primitivos diretos. Trocar a marca =
|
|
103
|
+
mudar um apontamento. Dark mode = remapear semânticos pra outros primitivos, sem tocar em componente.
|
|
104
|
+
Essa indireção é o que separa um design system de uma lista de cores com nome.
|
|
105
|
+
|
|
106
|
+
### 4. Nomeie por função, não por aparência
|
|
107
|
+
|
|
108
|
+
`--color-primary` é fraco (primário de quê?). `--color-danger`, `--color-action`, `--color-surface`,
|
|
109
|
+
`--color-border-subtle` carregam **intenção** — o consumidor sabe quando usar sem ler doc. E nunca
|
|
110
|
+
nomeie pela cor: `--color-green-success` quebra no dia que "sucesso" virar azul. O nome é o contrato;
|
|
111
|
+
a cor é detalhe de implementação.
|
|
112
|
+
|
|
113
|
+
### 5. Meça consistência com um número, priorize a dívida com ele
|
|
114
|
+
|
|
115
|
+
"Está inconsistente" não move ninguém. **Tokenization coverage** move: `% de valores que vêm de token
|
|
116
|
+
vs hardcoded`. Calcule por categoria:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
cor: 38% tokenizado → 62% hardcoded (pior, ataca primeiro)
|
|
120
|
+
spacing: 71% tokenizado
|
|
121
|
+
tipografia: 84% tokenizado
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Priorize por **frequência × visibilidade × esforço**: a cor de ação aparece em todo CTA (alta
|
|
125
|
+
visibilidade), tem 62% solto (alto volume) e é troca mecânica (baixo esforço) → primeira da fila. O
|
|
126
|
+
`z-index` aparece em 6 lugares e mexer arrisca regressão de overlay → depois, com cuidado. Não migre
|
|
127
|
+
em ordem alfabética; migre em ordem de retorno.
|
|
128
|
+
|
|
129
|
+
### 6. Migre com codemod e alias, nunca com find-and-replace cego
|
|
130
|
+
|
|
131
|
+
Trocar `#3B82F6` por `var(--color-action)` no braço pode quebrar onde aquele azul era **coincidência**
|
|
132
|
+
(um azul de ícone de terceiro que não devia seguir a marca). Migre assim:
|
|
133
|
+
|
|
134
|
+
1. **Camada de alias primeiro**: defina o token apontando pro valor atual exato. Nada muda visualmente
|
|
135
|
+
— você só ganhou o nome.
|
|
136
|
+
2. **Codemod scoped**: substitua o literal pelo token **arquivo por arquivo**, rodando o diff visual
|
|
137
|
+
(ou snapshot de teste) a cada lote. `jscodeshift`/`postcss` com escopo, não sed global.
|
|
138
|
+
3. **Deprecação, não deleção**: o componente velho (`ButtonNew`) ganha um `@deprecated` apontando pro
|
|
139
|
+
canônico e continua funcionando. Você remove quando os consumidores migrarem — não no mesmo PR.
|
|
140
|
+
4. **Visual regression como rede**: snapshot antes/depois. Se um pixel mexeu e você não previu, a
|
|
141
|
+
migração não era equivalente — investigue antes de seguir.
|
|
142
|
+
|
|
143
|
+
A migração correta é **invisível ao usuário**. Se a tela mudou, você fez redesign sem querer.
|
|
144
|
+
|
|
145
|
+
### 7. Component canônico = consolidar variantes na de maior cobertura, com props
|
|
146
|
+
|
|
147
|
+
Cinco botões viram **um** `Button` com variantes explícitas — `variant="primary|secondary|ghost"`,
|
|
148
|
+
`size="sm|md|lg"`. A base é a variante de **maior uso real** (a que mais aparece no grep), não a "mais
|
|
149
|
+
nova" nem a "mais bonita". As divergências de 3px que cada um tinha viram ou um token de tamanho, ou
|
|
150
|
+
são apagadas como o acidente que eram. Quem consumia `CTAButton` ganha um alias deprecado
|
|
151
|
+
`CTAButton = (p) => <Button variant="primary" {...p} />` — migra sem PR de breaking change.
|
|
152
|
+
|
|
153
|
+
### 8. A saída é três coisas, não um arquivo de cores
|
|
154
|
+
|
|
155
|
+
Entregue: **(a) tokens** em duas camadas, num formato consumível (CSS vars + JSON/W3C design tokens
|
|
156
|
+
pra ferramentas), **(b) componentes canônicos** com variantes e os antigos deprecados apontando pra
|
|
157
|
+
eles, e **(c) doc viva** que mostra cada token e componente *renderizado* (Storybook ou página de
|
|
158
|
+
estilo), com a regra de uso ("use `--color-action` em qualquer elemento clicável primário; nunca
|
|
159
|
+
hardcode azul"). Doc que é só uma tabela de hex morre na primeira semana; doc que renderiza o token e
|
|
160
|
+
diz quando usar é a que segura a consistência depois que você sai.
|
|
161
|
+
|
|
162
|
+
## Checklist
|
|
163
|
+
|
|
164
|
+
Antes de declarar o design system consolidado — qualquer "não" é trabalho pendente:
|
|
165
|
+
|
|
166
|
+
- [ ] Rodei o inventário por **comando** (grep de cor/fonte/spacing/z-index) e tenho a contagem de
|
|
167
|
+
frequência, não só uma impressão?
|
|
168
|
+
- [ ] Agrupei valores por **proximidade perceptual** (clusters), não por string exata — e cada cluster
|
|
169
|
+
tem um canônico definido pela frequência?
|
|
170
|
+
- [ ] Os tokens têm **duas camadas** (primitivo → semântico) e os componentes consomem só semânticos?
|
|
171
|
+
- [ ] Os semânticos são nomeados por **função** (`--color-action`), nunca por aparência
|
|
172
|
+
(`--color-blue`)?
|
|
173
|
+
- [ ] Tenho um **número de cobertura** por categoria e a fila de migração está ordenada por retorno
|
|
174
|
+
(frequência × visibilidade × esforço), não alfabética?
|
|
175
|
+
- [ ] A migração usa **alias + codemod scoped + deprecação**, com visual regression a cada lote — e
|
|
176
|
+
nenhum consumidor quebrou?
|
|
177
|
+
- [ ] As variantes duplicadas viraram **um componente canônico** com props, e os antigos têm alias
|
|
178
|
+
deprecado funcionando?
|
|
179
|
+
- [ ] A saída tem os **três artefatos** (tokens em formato consumível, componentes canônicos, doc viva
|
|
180
|
+
que renderiza)?
|
|
181
|
+
- [ ] Cada token tem **origem rastreável** no código atual — não inventei cor/spacing "melhor" no meio?
|
|
182
|
+
- [ ] O usuário final **não vê diferença** depois da consolidação (migração invisível)?
|
|
183
|
+
|
|
184
|
+
## Antes → depois
|
|
185
|
+
|
|
186
|
+
| Tell (codebase sem sistema) | Craft (consolidação) |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `#3B82F6` digitado em 47 lugares + 4 variantes por erro de dígito | Um cluster perceptual → `--blue-500` primitivo → `--color-action` semântico; aliases migrados por codemod |
|
|
189
|
+
| `--button-blue: #3B82F6` consumido direto nos componentes | Duas camadas: componente usa `--color-action`, que aponta pro primitivo; trocar marca = um apontamento |
|
|
190
|
+
| `margin-top: 13px`, `padding: 18px`, `gap: 6px` (números do DevTools) | Colapsados pra escala mais próxima (`16px`/`8px`); literais fora de escala viram exceção justificada ou somem |
|
|
191
|
+
| `Button`, `ButtonNew`, `PrimaryButton`, `CTAButton`, `.btn-primary` | Um `<Button variant size>` (base = a de maior uso); os 4 antigos são alias `@deprecated` que ainda funcionam |
|
|
192
|
+
| `z-index: 9999 / 99999 / 9998` (guerra de empilhamento) | Escala semântica de camadas: `--z-dropdown: 10`, `--z-modal: 40`, `--z-toast: 60` |
|
|
193
|
+
| "Está tudo inconsistente" (impressão) | "Cor: 38% tokenizado, 62% solto, 89 ocorrências do azul de ação → primeira da fila" (número) |
|
|
194
|
+
| Entrega: um `colors.ts` com 30 hex nomeados | Entrega: tokens 2 camadas (CSS+JSON) + componentes canônicos + doc que renderiza cada token e diz quando usar |
|
|
195
|
+
| Find-and-replace global de `#3B82F6` → quebra o azul de um ícone de terceiro | Alias primeiro, codemod scoped por arquivo, visual regression a cada lote, migração invisível |
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: intrinsic-css-layout
|
|
3
|
+
domain: web-craft
|
|
4
|
+
agents: [ux-design-expert, dev]
|
|
5
|
+
when: "ao montar layouts CSS resilientes e responsivos sem media queries frágeis"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Intrinsic CSS Layout — os primitives de Every Layout
|
|
9
|
+
|
|
10
|
+
Baseado em **Every Layout** (Heydon Pickering & Andy Bell). A tese central: pare de
|
|
11
|
+
"desenhar telas" em breakpoints fixos e passe a **compor primitivos de layout** que se adaptam
|
|
12
|
+
ao conteúdo e ao espaço disponível por conta própria. O navegador já sabe fazer layout — o
|
|
13
|
+
trabalho é **declarar restrições, não posições**. Layout intrínseco = o componente decide como
|
|
14
|
+
se arranjar com base no seu próprio conteúdo e no espaço do container, não em quanto mede a
|
|
15
|
+
viewport.
|
|
16
|
+
|
|
17
|
+
Isto casa direto com o pack `anti-ai-look`: layout editorial, ritmo de espaço com contraste e
|
|
18
|
+
composição que respira são justamente o que os primitives entregam **sem** cair no grid-de-3-cards
|
|
19
|
+
default.
|
|
20
|
+
|
|
21
|
+
## O problema
|
|
22
|
+
|
|
23
|
+
Layout "feito por IA / por template" é, na prática, layout **extrínseco**: tudo amarrado a
|
|
24
|
+
breakpoints de pixel (`@media (max-width: 768px)`), larguras fixas, e o mesmo `gap-8` em tudo.
|
|
25
|
+
Os tells técnicos:
|
|
26
|
+
|
|
27
|
+
1. **Media queries baseadas na viewport** para componentes que deveriam reagir ao *seu* container.
|
|
28
|
+
O card quebra a 768px mesmo dentro de uma sidebar de 300px — porque olha a tela, não a si mesmo.
|
|
29
|
+
2. **Espaçamento aplicado item a item** (`margin-bottom` em cada elemento, ou `<br>`/divs vazias)
|
|
30
|
+
em vez de um ritmo declarado uma vez no container.
|
|
31
|
+
3. **Larguras fixas em px** que estouram em telas estreitas ou criam linhas de texto longas demais
|
|
32
|
+
(medida ruim) em telas largas.
|
|
33
|
+
4. **Centralização por hack** (`margin-left/right` mágicos, `position: absolute` com `left: 50%`
|
|
34
|
+
sem `transform`, flex aninhado desnecessário).
|
|
35
|
+
5. **Grid rígido de N colunas** que precisa de media query para virar 2, depois 1 — em vez de
|
|
36
|
+
"encaixe quantas couberem".
|
|
37
|
+
6. **Tudo no mesmo múltiplo de 8** — espaçamento uniforme, sem tensão, robótico.
|
|
38
|
+
|
|
39
|
+
A consequência: layout que parece o mesmo template em qualquer projeto, frágil em conteúdo real
|
|
40
|
+
(título com 1 ou 7 palavras, lista com 2 ou 40 itens) e cheio de `@media` remendado.
|
|
41
|
+
|
|
42
|
+
## O conhecimento
|
|
43
|
+
|
|
44
|
+
### Fundamento 1 — A "lobotomized owl" (`* + *`) para ritmo
|
|
45
|
+
|
|
46
|
+
O seletor `* + *` ("coruja lobotomizada") aplica margem **só entre** irmãos adjacentes — nunca no
|
|
47
|
+
primeiro nem no último. É a base do espaçamento intrínseco: você declara o ritmo **uma vez** no
|
|
48
|
+
container e ele vale para qualquer quantidade de filhos.
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
.stack > * + * {
|
|
52
|
+
margin-block-start: var(--space, 1.5rem);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`margin-block-start` (não `margin-top`) respeita modo de escrita / direção. Nada de margem no
|
|
57
|
+
primeiro filho → sem colapso de margem indesejado com o container.
|
|
58
|
+
|
|
59
|
+
### Fundamento 2 — Espaçamento fluido com `clamp()`
|
|
60
|
+
|
|
61
|
+
Em vez de trocar tamanhos em breakpoints, escale **continuamente** entre um mínimo e um máximo,
|
|
62
|
+
com a viewport como variável no meio:
|
|
63
|
+
|
|
64
|
+
```css
|
|
65
|
+
:root {
|
|
66
|
+
/* clamp(MÍNIMO, PREFERIDO-fluido, MÁXIMO) */
|
|
67
|
+
--space-s: clamp(0.75rem, 0.69rem + 0.3vw, 0.94rem);
|
|
68
|
+
--space-m: clamp(1.13rem, 1.04rem + 0.45vw, 1.41rem);
|
|
69
|
+
--space-l: clamp(1.5rem, 1.38rem + 0.6vw, 1.88rem);
|
|
70
|
+
--space-xl: clamp(2.25rem, 2.07rem + 0.9vw, 2.81rem);
|
|
71
|
+
|
|
72
|
+
--step-0: clamp(1rem, 0.95rem + 0.25vw, 1.13rem); /* corpo */
|
|
73
|
+
--step-2: clamp(1.41rem, 1.24rem + 0.85vw, 1.91rem); /* h3 */
|
|
74
|
+
--step-4: clamp(1.95rem, 1.56rem + 1.95vw, 3.05rem); /* h1 */
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A escala fluida resolve o tell de espaçamento uniforme: defina poucos passos (`s`/`m`/`l`/`xl`)
|
|
79
|
+
com **contraste real** entre os extremos e use `--space` para parametrizar cada primitive.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### Os primitives
|
|
84
|
+
|
|
85
|
+
Cada primitive é uma classe utilitária componível, parametrizada por custom properties. Não há
|
|
86
|
+
media queries — a adaptação é intrínseca.
|
|
87
|
+
|
|
88
|
+
#### 1. Stack — ritmo vertical
|
|
89
|
+
|
|
90
|
+
Injeta espaço **entre** elementos empilhados. O bloco mais usado da web.
|
|
91
|
+
|
|
92
|
+
```css
|
|
93
|
+
.stack {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
justify-content: flex-start;
|
|
97
|
+
}
|
|
98
|
+
.stack > * + * {
|
|
99
|
+
margin-block-start: var(--space, 1.5rem);
|
|
100
|
+
}
|
|
101
|
+
/* "split": empurra o que vier depois do 1º filho pro fim do container */
|
|
102
|
+
.stack > :nth-child(2) {
|
|
103
|
+
margin-block-end: auto;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Variantes por escala e exceção local (sem reescrever o seletor):
|
|
108
|
+
|
|
109
|
+
```css
|
|
110
|
+
[class^='stack'] > * { margin-block: 0; } /* reset de margens herdadas */
|
|
111
|
+
.stack-large > * + * { margin-block-start: 3rem; }
|
|
112
|
+
.stack-small > * + * { margin-block-start: 0.5rem; }
|
|
113
|
+
|
|
114
|
+
/* exceção: um espaço maior antes E depois de um elemento específico */
|
|
115
|
+
.stack-exception,
|
|
116
|
+
.stack-exception + * { --space: 3rem; }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Quando:** qualquer fluxo vertical — artigo, formulário, card. O `split` (`margin-block-end: auto`)
|
|
120
|
+
gruda um rodapé/CTA no fim de um card de altura variável.
|
|
121
|
+
|
|
122
|
+
#### 2. Box — a unidade com padding/borda coerentes
|
|
123
|
+
|
|
124
|
+
Caixa que **não vaza** em modo de alto contraste (a borda transparente vira visível em
|
|
125
|
+
`forced-colors`).
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
.box {
|
|
129
|
+
padding: var(--space, 1rem);
|
|
130
|
+
border: var(--border-thin, 1px) solid;
|
|
131
|
+
/* borda transparente vira visível em forced-colors: acessibilidade de graça */
|
|
132
|
+
outline: var(--border-thin, 1px) solid transparent;
|
|
133
|
+
outline-offset: calc(var(--border-thin, 1px) * -1);
|
|
134
|
+
color: var(--color-light, #fff);
|
|
135
|
+
background-color: var(--color-dark, #000);
|
|
136
|
+
}
|
|
137
|
+
.box * { color: inherit; }
|
|
138
|
+
/* inversão herda cor/fundo sem hardcode */
|
|
139
|
+
.box.invert {
|
|
140
|
+
color: var(--color-dark, #000);
|
|
141
|
+
background-color: var(--color-light, #fff);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Quando:** card, alerta, qualquer "superfície". Padding fluido via `--space`. Evite raio uniforme
|
|
146
|
+
em tudo (ver `anti-ai-look`): varie por propósito.
|
|
147
|
+
|
|
148
|
+
#### 3. Center — centraliza e impõe a medida (measure)
|
|
149
|
+
|
|
150
|
+
Centraliza horizontalmente **e** limita a largura da linha de texto (`measure` ~60ch para leitura
|
|
151
|
+
confortável). Sem container artificial.
|
|
152
|
+
|
|
153
|
+
```css
|
|
154
|
+
.center {
|
|
155
|
+
box-sizing: content-box;
|
|
156
|
+
margin-inline: auto;
|
|
157
|
+
max-inline-size: var(--measure, 60ch);
|
|
158
|
+
padding-inline: var(--gutter, 1rem); /* respiro nas laterais */
|
|
159
|
+
}
|
|
160
|
+
/* variante: centraliza também filhos intrinsecamente menores que a measure */
|
|
161
|
+
.center-intrinsic {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
align-items: center;
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Quando:** wrapper de conteúdo editorial/artigo. `--measure` controla o conforto de leitura; é
|
|
169
|
+
o antídoto pra linhas de 200 caracteres em telas largas.
|
|
170
|
+
|
|
171
|
+
#### 4. Cluster — agrupar coisas que quebram juntas
|
|
172
|
+
|
|
173
|
+
Para grupos de itens de tamanhos diferentes que devem **embrulhar** com espaçamento consistente:
|
|
174
|
+
tags, botões de ação, metadados, nav.
|
|
175
|
+
|
|
176
|
+
```css
|
|
177
|
+
.cluster {
|
|
178
|
+
display: flex;
|
|
179
|
+
flex-wrap: wrap;
|
|
180
|
+
gap: var(--space, 1rem);
|
|
181
|
+
justify-content: var(--justify, flex-start);
|
|
182
|
+
align-items: var(--align, center);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Quando:** lista de tags, toolbar, grupo de botões, badges. `gap` cuida do espaço em qualquer
|
|
187
|
+
quebra de linha — nada de margens negativas modernas.
|
|
188
|
+
|
|
189
|
+
#### 5. Sidebar — duas colunas que viram empilhadas sozinhas (sem media query)
|
|
190
|
+
|
|
191
|
+
Conteúdo + barra lateral. A barra mantém uma largura ideal; quando o espaço aperta, **empilha
|
|
192
|
+
automaticamente** via `flex-wrap`. **CSS verbatim de Every Layout:**
|
|
193
|
+
|
|
194
|
+
```css
|
|
195
|
+
.with-sidebar {
|
|
196
|
+
display: flex;
|
|
197
|
+
flex-wrap: wrap;
|
|
198
|
+
gap: 1em;
|
|
199
|
+
}
|
|
200
|
+
.sidebar {
|
|
201
|
+
/* a menor entre 20ch e 33.333%, nunca passando de 100% */
|
|
202
|
+
flex-basis: clamp(20ch, 33.333%, 100%);
|
|
203
|
+
flex-grow: 1;
|
|
204
|
+
}
|
|
205
|
+
.with-sidebar > :last-child {
|
|
206
|
+
flex-basis: 0;
|
|
207
|
+
flex-grow: 999; /* devora o espaço restante = vira "conteúdo principal" */
|
|
208
|
+
min-inline-size: 50%; /* abaixo disto, embrulha e empilha */
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Quando:** doc + índice, produto + filtros, conteúdo + aside. O `min-inline-size: 50%` é o gatilho
|
|
213
|
+
de quebra — ajuste pra controlar quando empilha. Coloque a `.sidebar` antes ou depois conforme a
|
|
214
|
+
ordem visual desejada.
|
|
215
|
+
|
|
216
|
+
#### 6. Switcher — horizontal vira vertical num limiar de container
|
|
217
|
+
|
|
218
|
+
Alterna entre linha e coluna baseado no **container** (não na viewport). **CSS verbatim:**
|
|
219
|
+
|
|
220
|
+
```css
|
|
221
|
+
.switcher {
|
|
222
|
+
display: flex;
|
|
223
|
+
flex-wrap: wrap;
|
|
224
|
+
gap: var(--space, 1rem);
|
|
225
|
+
--threshold: 30rem; /* abaixo disto, empilha */
|
|
226
|
+
}
|
|
227
|
+
.switcher > * {
|
|
228
|
+
flex-grow: 1;
|
|
229
|
+
/* o truque: valor enorme (empilha) quando container < threshold;
|
|
230
|
+
negativo/inválido (lado a lado) quando >= threshold */
|
|
231
|
+
flex-basis: calc((var(--threshold) - 100%) * 999);
|
|
232
|
+
}
|
|
233
|
+
/* quantity query: 5+ itens forçam vertical (não espreme demais) */
|
|
234
|
+
.switcher > :nth-last-child(n+5),
|
|
235
|
+
.switcher > :nth-last-child(n+5) ~ * {
|
|
236
|
+
flex-basis: 100%;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Quando:** 2–4 blocos de peso igual (features, passos, colunas de preço) que devem ir lado a lado
|
|
241
|
+
quando cabe e empilhar quando não. Diferente da Sidebar, aqui os itens têm **peso simétrico**.
|
|
242
|
+
|
|
243
|
+
#### 7. Cover — herói de altura mínima com conteúdo centralizado
|
|
244
|
+
|
|
245
|
+
Ocupa pelo menos a altura da viewport, centra o conteúdo principal vertical e acomoda
|
|
246
|
+
cabeçalho/rodapé opcionais.
|
|
247
|
+
|
|
248
|
+
```css
|
|
249
|
+
.cover {
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-direction: column;
|
|
252
|
+
min-block-size: 100vh; /* prefira 100svh em mobile p/ evitar pulo de barra */
|
|
253
|
+
padding: var(--space, 1rem);
|
|
254
|
+
}
|
|
255
|
+
.cover > * {
|
|
256
|
+
margin-block: var(--space, 1rem);
|
|
257
|
+
}
|
|
258
|
+
/* o elemento principal centraliza-se "empurrando" com auto dos dois lados */
|
|
259
|
+
.cover > .principal {
|
|
260
|
+
margin-block: auto;
|
|
261
|
+
}
|
|
262
|
+
/* header/footer colam nas pontas */
|
|
263
|
+
.cover > :first-child:not(.principal) { margin-block-start: 0; }
|
|
264
|
+
.cover > :last-child:not(.principal) { margin-block-end: 0; }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Quando:** hero de landing, splash, tela de login. Use `100svh`/`100dvh` em vez de `100vh` para
|
|
268
|
+
não pular com a barra do browser mobile.
|
|
269
|
+
|
|
270
|
+
#### 8. Grid — quantas colunas couberem, sem media query
|
|
271
|
+
|
|
272
|
+
O grid auto-responsivo canônico. As colunas se ajustam ao espaço; o `min()` evita estouro em telas
|
|
273
|
+
estreitíssimas.
|
|
274
|
+
|
|
275
|
+
```css
|
|
276
|
+
.grid {
|
|
277
|
+
display: grid;
|
|
278
|
+
gap: var(--space, 1rem);
|
|
279
|
+
/* encaixa quantas colunas de >= --min couberem; cada uma estica até 1fr */
|
|
280
|
+
grid-template-columns: repeat(auto-fit, minmax(min(var(--min, 250px), 100%), 1fr));
|
|
281
|
+
/* alinha alturas e nivela linhas */
|
|
282
|
+
align-content: start;
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Quando:** galeria, lista de cards, dashboard. `auto-fit` colapsa trilhas vazias (cards esticam pra
|
|
287
|
+
preencher); troque por `auto-fill` se quiser trilhas vazias preservadas. `min(var(--min), 100%)` é o
|
|
288
|
+
detalhe que impede a barra de rolagem horizontal em telas menores que `--min`.
|
|
289
|
+
|
|
290
|
+
#### 9. Frame — proporção fixa que recorta o conteúdo
|
|
291
|
+
|
|
292
|
+
Mantém uma razão de aspecto (16:9, 1:1) e recorta a imagem/vídeo dentro com `object-fit: cover`.
|
|
293
|
+
|
|
294
|
+
```css
|
|
295
|
+
.frame {
|
|
296
|
+
aspect-ratio: var(--n, 16) / var(--d, 9);
|
|
297
|
+
overflow: hidden;
|
|
298
|
+
display: flex;
|
|
299
|
+
align-items: center;
|
|
300
|
+
justify-content: center;
|
|
301
|
+
}
|
|
302
|
+
.frame > img,
|
|
303
|
+
.frame > video {
|
|
304
|
+
inline-size: 100%;
|
|
305
|
+
block-size: 100%;
|
|
306
|
+
object-fit: cover;
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Quando:** thumbnail, capa de card, embed de vídeo. `aspect-ratio` é suporte universal hoje — só
|
|
311
|
+
use o velho hack `padding-bottom: 56.25%` se precisar de browsers antigos.
|
|
312
|
+
|
|
313
|
+
#### 10. Reel — lista que rola na horizontal (carrossel honesto)
|
|
314
|
+
|
|
315
|
+
Rolagem horizontal com snap, sem JS. Itens mantêm largura própria e transbordam para o lado.
|
|
316
|
+
|
|
317
|
+
```css
|
|
318
|
+
.reel {
|
|
319
|
+
display: flex;
|
|
320
|
+
gap: var(--space, 1rem);
|
|
321
|
+
overflow-x: auto;
|
|
322
|
+
overflow-y: hidden;
|
|
323
|
+
scroll-snap-type: x mandatory;
|
|
324
|
+
/* esconde a barra mas mantém rolagem por teclado/toque */
|
|
325
|
+
scrollbar-width: thin;
|
|
326
|
+
overscroll-behavior-inline: contain;
|
|
327
|
+
}
|
|
328
|
+
.reel > * {
|
|
329
|
+
flex: 0 0 var(--item-width, auto); /* não encolhe: preserva largura */
|
|
330
|
+
scroll-snap-align: start;
|
|
331
|
+
}
|
|
332
|
+
.reel > img { block-size: 100%; flex-basis: auto; width: auto; }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Quando:** galeria horizontal, "continue assistindo", chips roláveis. Garanta foco/teclado e
|
|
336
|
+
respeite `prefers-reduced-motion` ao animar o scroll.
|
|
337
|
+
|
|
338
|
+
#### 11. Imposter — sobrepor e centralizar sobre outro elemento
|
|
339
|
+
|
|
340
|
+
Centraliza um elemento sobre o pai (modal, badge, "esgotado" sobre um produto).
|
|
341
|
+
|
|
342
|
+
```css
|
|
343
|
+
.imposter {
|
|
344
|
+
position: absolute;
|
|
345
|
+
inset-block-start: 50%;
|
|
346
|
+
inset-inline-start: 50%;
|
|
347
|
+
transform: translate(-50%, -50%);
|
|
348
|
+
}
|
|
349
|
+
/* variante "contida": nunca ultrapassa o pai, rola se for grande */
|
|
350
|
+
.imposter.contain {
|
|
351
|
+
--margin: 0px;
|
|
352
|
+
overflow: auto;
|
|
353
|
+
max-inline-size: calc(100% - (var(--margin, 0px) * 2));
|
|
354
|
+
max-block-size: calc(100% - (var(--margin, 0px) * 2));
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
O pai precisa de `position: relative`. **Quando:** modal/dialog centralizado, selo sobre imagem,
|
|
359
|
+
tooltip. Para conteúdo realmente modal, prefira o elemento `<dialog>` nativo; o Imposter é pra
|
|
360
|
+
sobreposição puramente visual.
|
|
361
|
+
|
|
362
|
+
### Composição — o todo é maior que as partes
|
|
363
|
+
|
|
364
|
+
O ganho real é **aninhar** primitives, cada um cuidando de uma dimensão:
|
|
365
|
+
|
|
366
|
+
```html
|
|
367
|
+
<!-- Center impõe a measure → Stack dá ritmo vertical → Grid encaixa os cards -->
|
|
368
|
+
<div class="center" style="--measure: 70ch;">
|
|
369
|
+
<div class="stack" style="--space: var(--space-xl);">
|
|
370
|
+
<h1>Manifesto</h1>
|
|
371
|
+
<p>…</p>
|
|
372
|
+
<div class="grid" style="--min: 18rem; --space: var(--space-l);">
|
|
373
|
+
<article class="box stack">…</article>
|
|
374
|
+
<article class="box stack">…</article>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Nenhuma media query. Cada primitive resolve uma responsabilidade; o conteúdo dita a quebra. Use o
|
|
381
|
+
**contraste de `--space`** entre seções (uma `--space-xl`, outra `--space-s`) para criar o ritmo
|
|
382
|
+
editorial que o pack `anti-ai-look` exige — em vez do `gap-8` uniforme.
|
|
383
|
+
|
|
384
|
+
## Checklist
|
|
385
|
+
|
|
386
|
+
Antes de entregar um layout, qualquer "não" é um remendo a corrigir:
|
|
387
|
+
|
|
388
|
+
- [ ] O espaçamento vertical vem de **um** `* + *` no container, não de margens item a item?
|
|
389
|
+
- [ ] Há **zero** media queries de viewport para coisas que deveriam reagir ao container (usou
|
|
390
|
+
Sidebar/Switcher/Grid intrínsecos)?
|
|
391
|
+
- [ ] O wrapper de leitura tem `max-inline-size` em `ch` (measure ~60–70ch), não largura em px?
|
|
392
|
+
- [ ] O grid usa `repeat(auto-fit, minmax(min(--min, 100%), 1fr))` — encaixa o que cabe sozinho?
|
|
393
|
+
- [ ] Usei `clamp()` para espaço/tipografia fluida em vez de trocar valores em breakpoints?
|
|
394
|
+
- [ ] As propriedades são lógicas (`margin-block`, `inline-size`, `inset-inline`) e não físicas?
|
|
395
|
+
- [ ] As seções têm **contraste de `--space`** (ritmo), não todas no mesmo múltiplo?
|
|
396
|
+
- [ ] Cover usa `100svh`/`100dvh` (não `100vh`) pra não pular em mobile?
|
|
397
|
+
- [ ] Reel respeita teclado/foco e `prefers-reduced-motion`?
|
|
398
|
+
- [ ] Box mantém a borda transparente via `outline` pra sobreviver a `forced-colors`?
|
|
399
|
+
|
|
400
|
+
## Tabela de decisão
|
|
401
|
+
|
|
402
|
+
| Preciso de… | Primitive | Parâmetro-chave |
|
|
403
|
+
|---|---|---|
|
|
404
|
+
| Ritmo entre elementos empilhados | **Stack** | `--space` (use a escala fluida) |
|
|
405
|
+
| Card / superfície com padding e borda coerentes | **Box** | `--space`, `.invert` |
|
|
406
|
+
| Limitar largura de leitura e centralizar | **Center** | `--measure` (~60–70ch) |
|
|
407
|
+
| Agrupar tags/botões que quebram juntos | **Cluster** | `--space`, `--justify` |
|
|
408
|
+
| Conteúdo + barra lateral que empilha sozinha | **Sidebar** | `flex-basis: clamp(...)`, `min-inline-size` |
|
|
409
|
+
| 2–4 blocos simétricos: lado a lado ou empilhados | **Switcher** | `--threshold` (30rem), quantity query |
|
|
410
|
+
| Hero de altura mínima com conteúdo centrado | **Cover** | `min-block-size: 100svh`, `margin-block: auto` |
|
|
411
|
+
| "Quantas colunas couberem" sem media query | **Grid** | `--min`, `auto-fit`/`auto-fill` |
|
|
412
|
+
| Proporção fixa recortando imagem/vídeo | **Frame** | `aspect-ratio`, `object-fit: cover` |
|
|
413
|
+
| Lista que rola na horizontal (carrossel) | **Reel** | `overflow-x: auto`, `scroll-snap` |
|
|
414
|
+
| Sobrepor/centralizar sobre outro elemento | **Imposter** | `position: absolute` + `translate(-50%,-50%)` |
|
|
415
|
+
|
|
416
|
+
**Sidebar vs Switcher:** Sidebar = pesos **assimétricos** (uma coluna fina fixa, uma elástica).
|
|
417
|
+
Switcher = pesos **simétricos** (todos iguais), tudo lado a lado ou tudo empilhado.
|
|
418
|
+
|
|
419
|
+
**`auto-fit` vs `auto-fill` no Grid:** `auto-fit` colapsa trilhas vazias e estica os itens
|
|
420
|
+
existentes; `auto-fill` mantém trilhas vazias (itens não esticam pra preencher sozinhos).
|