@lugom.io/hefesto 0.3.0 → 1.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/agents/hefesto-argos.md +51 -237
- package/agents/hefesto-athena.md +59 -339
- package/agents/hefesto-hermes.md +39 -71
- package/bin/install.js +105 -69
- package/hooks/hefesto-check-update.cjs +32 -11
- package/hooks/hefesto-statusline.cjs +8 -17
- package/hooks/hefesto-workflow.cjs +68 -0
- package/package.json +12 -2
- package/skills/hefesto-context/SKILL.md +59 -26
- package/skills/hefesto-debug/SKILL.md +54 -0
- package/skills/hefesto-design/SKILL.md +133 -143
- package/skills/hefesto-execute/SKILL.md +133 -0
- package/skills/hefesto-init/SKILL.md +94 -59
- package/skills/hefesto-init/references/api.md +116 -0
- package/skills/hefesto-init/references/cli.md +91 -0
- package/skills/hefesto-init/references/mobile.md +69 -0
- package/skills/hefesto-init/references/web.md +246 -0
- package/skills/hefesto-new-feature/SKILL.md +75 -41
- package/skills/hefesto-security/SKILL.md +89 -0
- package/skills/hefesto-security/references/boundaries-and-bypasses.md +152 -0
- package/skills/hefesto-security/references/secrets-detection.md +121 -0
- package/skills/hefesto-security/references/severity-and-judgment.md +176 -0
- package/skills/hefesto-simplify/SKILL.md +82 -0
- package/templates/TPL-CLAUDE.md +54 -0
- package/templates/TPL-CONFIG.json +19 -0
- package/templates/TPL-DESIGN.md +305 -0
- package/templates/{FEATURE.md → TPL-FEATURE.md} +13 -6
- package/templates/TPL-PROJECT.md +50 -0
- package/templates/{RECON.md → TPL-RECON.md} +10 -4
- package/templates/{RESEARCH.md → TPL-RESEARCH.md} +15 -15
- package/templates/TPL-SECURITY.md +42 -0
- package/templates/TPL-SIMPLIFY.md +40 -0
- package/templates/{STATE.md → TPL-STATE.md} +0 -6
- package/templates/TPL-VERDICT.md +34 -0
- package/skills/hefesto-design/data/animations.csv +0 -21
- package/skills/hefesto-design/data/anti-patterns.csv +0 -41
- package/skills/hefesto-design/data/charts.csv +0 -26
- package/skills/hefesto-design/data/colors.csv +0 -108
- package/skills/hefesto-design/data/components.csv +0 -31
- package/skills/hefesto-design/data/google-fonts.csv +0 -56
- package/skills/hefesto-design/data/icons.csv +0 -23
- package/skills/hefesto-design/data/landing-pages.csv +0 -28
- package/skills/hefesto-design/data/products.csv +0 -46
- package/skills/hefesto-design/data/spacing.csv +0 -16
- package/skills/hefesto-design/data/styles.csv +0 -53
- package/skills/hefesto-design/data/typography.csv +0 -41
- package/skills/hefesto-design/data/ux-rules.csv +0 -61
- package/skills/hefesto-design/references/accessibility.md +0 -335
- package/skills/hefesto-design/references/aesthetics.md +0 -343
- package/skills/hefesto-design/references/anti-patterns.md +0 -107
- package/skills/hefesto-design/references/checklist.md +0 -66
- package/skills/hefesto-design/references/color-psychology.md +0 -203
- package/skills/hefesto-design/references/component-specs.md +0 -318
- package/skills/hefesto-design/references/polish.md +0 -339
- package/skills/hefesto-design/references/token-architecture.md +0 -394
- package/skills/hefesto-design/references/ux-rules.md +0 -349
- package/skills/hefesto-design/scripts/__pycache__/audit.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/contrast.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/search.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/__pycache__/validate_tokens.cpython-314.pyc +0 -0
- package/skills/hefesto-design/scripts/audit.py +0 -450
- package/skills/hefesto-design/scripts/contrast.py +0 -195
- package/skills/hefesto-design/scripts/core.py +0 -155
- package/skills/hefesto-design/scripts/design_system.py +0 -311
- package/skills/hefesto-design/scripts/search.py +0 -235
- package/skills/hefesto-design/scripts/validate_tokens.py +0 -274
- package/skills/hefesto-update/SKILL.md +0 -34
- package/templates/DESIGN.md +0 -137
- package/templates/PROJECT.md +0 -28
- package/templates/ROADMAP.md +0 -23
- package/templates/VERDICT.md +0 -52
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
# Micro-Polish — 16 Princípios de Refinamento Visual
|
|
2
|
-
|
|
3
|
-
Detalhes imperceptíveis isoladamente, mas que juntos separam interfaces amadoras de interfaces profissionais. Cada princípio inclui a regra, o valor exato e CSS pronto para copiar. Aplicar sistematicamente após a estrutura base estar funcional.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Superfícies
|
|
8
|
-
|
|
9
|
-
**POL-01 — Border Radius Concêntrico**
|
|
10
|
-
Raio externo = raio interno + padding. Card com padding 16px e inner radius 8px deve ter outer radius 24px. Raios que não seguem essa regra criam uma sensação "errada" difícil de identificar.
|
|
11
|
-
- Regra: `outer-radius = inner-radius + padding`
|
|
12
|
-
- Valor: calculado por contexto (ex: inner 8px + padding 16px = outer 24px)
|
|
13
|
-
```css
|
|
14
|
-
.card {
|
|
15
|
-
border-radius: 24px;
|
|
16
|
-
padding: 16px;
|
|
17
|
-
}
|
|
18
|
-
.card-inner {
|
|
19
|
-
border-radius: 8px;
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
- Por quê: raios não-concêntricos criam espessura visual irregular nas bordas, quebrando a percepção de profundidade uniforme
|
|
23
|
-
|
|
24
|
-
**POL-02 — Alinhamento Óptico vs Geométrico**
|
|
25
|
-
Ícones de play (triângulos), elementos assimétricos e alguns glyphs precisam de ajuste manual. Centralizar geometricamente nem sempre é centralizar visualmente.
|
|
26
|
-
- Regra: ajustar 1-2px para compensar peso visual assimétrico
|
|
27
|
-
- Valor: `translate(1px, 0)` a `translate(2px, 0)` para triângulos/play icons
|
|
28
|
-
```css
|
|
29
|
-
.play-icon {
|
|
30
|
-
display: flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
}
|
|
34
|
-
.play-icon svg {
|
|
35
|
-
transform: translateX(1px); /* compensação óptica */
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
- Por quê: o olho humano percebe centro de massa, não centro geométrico — triângulos parecem deslocados à esquerda quando centrados matematicamente
|
|
39
|
-
|
|
40
|
-
**POL-03 — Sombras > Bordas**
|
|
41
|
-
Usar múltiplas `box-shadow` com transparência em vez de `border: 1px solid`. Sombras se adaptam a qualquer fundo sem criar linhas duras.
|
|
42
|
-
- Regra: substituir bordas sólidas por sombras em camadas
|
|
43
|
-
- Valor: 2-3 camadas de shadow com opacidade entre 0.04 e 0.12
|
|
44
|
-
```css
|
|
45
|
-
/* Em vez de: border: 1px solid #e5e5e5 */
|
|
46
|
-
.card {
|
|
47
|
-
box-shadow:
|
|
48
|
-
0 1px 2px rgba(0, 0, 0, 0.05),
|
|
49
|
-
0 4px 12px rgba(0, 0, 0, 0.08);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/* Elevated state */
|
|
53
|
-
.card-elevated {
|
|
54
|
-
box-shadow:
|
|
55
|
-
0 2px 4px rgba(0, 0, 0, 0.04),
|
|
56
|
-
0 8px 24px rgba(0, 0, 0, 0.12);
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
- Por quê: bordas sólidas criam contraste abrupto e não escalam entre temas claro/escuro — sombras com alpha se adaptam naturalmente
|
|
60
|
-
|
|
61
|
-
**POL-04 — Outlines em Imagens**
|
|
62
|
-
Adicionar contorno sutil em imagens para criar separação contra fundos claros sem borda visível.
|
|
63
|
-
- Regra: aplicar inset shadow ou outline com opacidade 0.06 em toda imagem
|
|
64
|
-
- Valor: `rgba(0, 0, 0, 0.06)` com 1px
|
|
65
|
-
```css
|
|
66
|
-
.image {
|
|
67
|
-
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
|
|
68
|
-
border-radius: 8px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/* Alternativa com outline (não afeta layout) */
|
|
72
|
-
.image-alt {
|
|
73
|
-
outline: 1px solid rgba(0, 0, 0, 0.06);
|
|
74
|
-
outline-offset: -1px;
|
|
75
|
-
border-radius: 8px;
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
- Por quê: imagens com áreas claras nas bordas "sangram" no fundo branco — o outline sutil define a fronteira sem parecer uma moldura
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Animação
|
|
83
|
-
|
|
84
|
-
**POL-05 — Animações Interrompíveis**
|
|
85
|
-
Usar CSS transitions (não keyframes) para estados interativos (hover, active, focus). Transitions podem ser interrompidas mid-animation; keyframes completam o ciclo.
|
|
86
|
-
- Regra: states interativos = transition, sequências = keyframes
|
|
87
|
-
- Valor: `transition` para hover/active/focus, `@keyframes` apenas para loaders/decorativos
|
|
88
|
-
```css
|
|
89
|
-
/* Correto: transition interrompível */
|
|
90
|
-
.button {
|
|
91
|
-
background: var(--color-primary);
|
|
92
|
-
transition: background-color 150ms ease, transform 100ms ease;
|
|
93
|
-
}
|
|
94
|
-
.button:hover {
|
|
95
|
-
background: var(--color-primary-hover);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/* Errado: keyframe não-interrompível */
|
|
99
|
-
.button:hover {
|
|
100
|
-
animation: highlight 200ms forwards; /* completa mesmo se mouse sair */
|
|
101
|
-
}
|
|
102
|
-
```
|
|
103
|
-
- Por quê: quando o usuário move o mouse rapidamente, transitions revertem suavemente — keyframes criam "flicker" ao completar antes de reiniciar
|
|
104
|
-
|
|
105
|
-
**POL-06 — Split & Stagger Enters**
|
|
106
|
-
Separar conteúdo em chunks semânticos e aplicar delay incremental entre eles. Nunca animar um container inteiro como bloco único.
|
|
107
|
-
- Regra: ~100ms de delay entre chunks, máximo 5-6 itens staggered
|
|
108
|
-
- Valor: `transition-delay` de 0ms, 100ms, 200ms, 300ms...
|
|
109
|
-
```css
|
|
110
|
-
.stagger-item {
|
|
111
|
-
opacity: 0;
|
|
112
|
-
transform: translateY(8px);
|
|
113
|
-
transition: opacity 300ms ease, transform 300ms ease;
|
|
114
|
-
}
|
|
115
|
-
.stagger-item.visible {
|
|
116
|
-
opacity: 1;
|
|
117
|
-
transform: translateY(0);
|
|
118
|
-
}
|
|
119
|
-
.stagger-item:nth-child(1) { transition-delay: 0ms; }
|
|
120
|
-
.stagger-item:nth-child(2) { transition-delay: 100ms; }
|
|
121
|
-
.stagger-item:nth-child(3) { transition-delay: 200ms; }
|
|
122
|
-
.stagger-item:nth-child(4) { transition-delay: 300ms; }
|
|
123
|
-
```
|
|
124
|
-
- Por quê: entradas em bloco parecem "flash" — stagger cria narrativa visual e dá tempo para o olho processar cada elemento
|
|
125
|
-
|
|
126
|
-
**POL-07 — Exits Sutis**
|
|
127
|
-
Usar `translateY(-12px)` fixo para saídas, nunca a altura total do elemento. Saídas devem ser menores e mais rápidas que entradas.
|
|
128
|
-
- Regra: exit = metade da duração do enter, distância fixa de 12px
|
|
129
|
-
- Valor: enter 300ms, exit 150ms; enter `translateY(8px)`, exit `translateY(-12px)`
|
|
130
|
-
```css
|
|
131
|
-
/* Enter */
|
|
132
|
-
.panel-enter {
|
|
133
|
-
animation: fadeIn 300ms ease forwards;
|
|
134
|
-
}
|
|
135
|
-
@keyframes fadeIn {
|
|
136
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
137
|
-
to { opacity: 1; transform: translateY(0); }
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* Exit */
|
|
141
|
-
.panel-exit {
|
|
142
|
-
animation: fadeOut 150ms ease forwards;
|
|
143
|
-
}
|
|
144
|
-
@keyframes fadeOut {
|
|
145
|
-
from { opacity: 1; transform: translateY(0); }
|
|
146
|
-
to { opacity: 0; transform: translateY(-12px); }
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
- Por quê: atenção do usuário já está no próximo elemento — exits longos ou distantes distraem do novo conteúdo
|
|
150
|
-
|
|
151
|
-
**POL-08 — Animação Contextual de Ícones**
|
|
152
|
-
Ícones que aparecem devem ter animação própria, não herdar a animação do container.
|
|
153
|
-
- Regra: ícones entram com scale + blur + opacity simultâneos
|
|
154
|
-
- Valor: `scale: 0.25→1`, `opacity: 0→1`, `filter: blur(4px)→blur(0)`
|
|
155
|
-
```css
|
|
156
|
-
.icon-enter {
|
|
157
|
-
animation: iconReveal 250ms ease forwards;
|
|
158
|
-
}
|
|
159
|
-
@keyframes iconReveal {
|
|
160
|
-
from {
|
|
161
|
-
opacity: 0;
|
|
162
|
-
transform: scale(0.25);
|
|
163
|
-
filter: blur(4px);
|
|
164
|
-
}
|
|
165
|
-
to {
|
|
166
|
-
opacity: 1;
|
|
167
|
-
transform: scale(1);
|
|
168
|
-
filter: blur(0);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
- Por quê: ícones são pequenos e de alta densidade visual — sem animação própria, parecem "pop" abrupto quando o container anima
|
|
173
|
-
|
|
174
|
-
**POL-09 — Nunca `transition: all`**
|
|
175
|
-
Sempre especificar propriedades explicitamente. `all` causa re-renders desnecessários e pode animar propriedades inesperadas.
|
|
176
|
-
- Regra: listar cada propriedade animada individualmente
|
|
177
|
-
- Valor: `transition-property: transform, opacity` (nunca `all`)
|
|
178
|
-
```css
|
|
179
|
-
/* Errado */
|
|
180
|
-
.element {
|
|
181
|
-
transition: all 200ms ease;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/* Correto */
|
|
185
|
-
.element {
|
|
186
|
-
transition:
|
|
187
|
-
transform 200ms ease,
|
|
188
|
-
opacity 200ms ease,
|
|
189
|
-
background-color 150ms ease;
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
- Por quê: `transition: all` anima padding, margin, border-radius, width, height — causando layout thrashing e efeitos visuais indesejados
|
|
193
|
-
|
|
194
|
-
**POL-10 — `will-change` com Moderação**
|
|
195
|
-
Só usar para propriedades GPU-compositable e apenas quando houver stutter observável.
|
|
196
|
-
- Regra: aplicar apenas em `transform`, `opacity`, `filter`; nunca preventivamente
|
|
197
|
-
- Valor: `will-change: transform` apenas quando necessário
|
|
198
|
-
```css
|
|
199
|
-
/* Aplicar apenas se observar stutter no primeiro frame */
|
|
200
|
-
.heavy-animation {
|
|
201
|
-
will-change: transform, opacity;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/* Remover após animação (em JS) */
|
|
205
|
-
element.addEventListener('transitionend', () => {
|
|
206
|
-
element.style.willChange = 'auto';
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
/* NUNCA fazer: */
|
|
210
|
-
* { will-change: transform; } /* desperdiça memória GPU */
|
|
211
|
-
```
|
|
212
|
-
- Por quê: `will-change` cria uma camada compositing dedicada — usar em tudo consome memória GPU e pode causar o stutter que tentava evitar
|
|
213
|
-
|
|
214
|
-
**POL-11 — Skip Animation on Page Load**
|
|
215
|
-
Evitar animações de entrada no primeiro render. Conteúdo deve aparecer instantaneamente no carregamento.
|
|
216
|
-
- Regra: desabilitar enter animations no mount inicial
|
|
217
|
-
- Valor: `initial={false}` (Framer Motion) ou classe `.loaded` via JS
|
|
218
|
-
```css
|
|
219
|
-
/* CSS: desabilitar transitions até página carregar */
|
|
220
|
-
.no-transition * {
|
|
221
|
-
transition: none !important;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/* JS: remover classe após load */
|
|
225
|
-
/* document.addEventListener('DOMContentLoaded', () => {
|
|
226
|
-
document.body.classList.remove('no-transition');
|
|
227
|
-
}); */
|
|
228
|
-
|
|
229
|
-
/* Framer Motion / Motion */
|
|
230
|
-
/* <AnimatePresence initial={false}> */
|
|
231
|
-
```
|
|
232
|
-
- Por quê: animações no load atrasam o tempo percebido de carregamento e fazem a UI parecer lenta — conteúdo deve estar "lá" imediatamente
|
|
233
|
-
|
|
234
|
-
**POL-12 — Scale on Press**
|
|
235
|
-
Botões devem ter feedback tátil via scale no estado `:active`.
|
|
236
|
-
- Regra: exatamente `scale(0.96)` no active, nunca menor que 0.95
|
|
237
|
-
- Valor: `transform: scale(0.96)`, `transition: transform 100ms ease`
|
|
238
|
-
```css
|
|
239
|
-
.button {
|
|
240
|
-
transition: transform 100ms ease;
|
|
241
|
-
}
|
|
242
|
-
.button:active {
|
|
243
|
-
transform: scale(0.96);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/* Com hover combinado */
|
|
247
|
-
.button:hover {
|
|
248
|
-
transform: scale(1.02);
|
|
249
|
-
}
|
|
250
|
-
.button:hover:active {
|
|
251
|
-
transform: scale(0.96);
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
- Por quê: scale simula a física de um botão real sendo pressionado — abaixo de 0.95 parece "borracha", acima de 0.98 é imperceptível
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Tipografia
|
|
259
|
-
|
|
260
|
-
**POL-13 — Font Smoothing**
|
|
261
|
-
Sempre aplicar antialiasing subpixel no body. Texto fica mais nítido e com peso visual consistente no macOS.
|
|
262
|
-
- Regra: aplicar no `body` ou `:root`, uma vez
|
|
263
|
-
- Valor: `-webkit-font-smoothing: antialiased` + `-moz-osx-font-smoothing: grayscale`
|
|
264
|
-
```css
|
|
265
|
-
body {
|
|
266
|
-
-webkit-font-smoothing: antialiased;
|
|
267
|
-
-moz-osx-font-smoothing: grayscale;
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
- Por quê: sem antialiasing, fontes ficam mais "gordas" no macOS (subpixel rendering padrão adiciona peso visual), especialmente em fontes claras sobre fundo escuro
|
|
271
|
-
|
|
272
|
-
**POL-14 — Tabular Numbers**
|
|
273
|
-
Usar `tabular-nums` em qualquer número que mude dinamicamente. Evita shift de layout quando dígitos têm larguras diferentes.
|
|
274
|
-
- Regra: aplicar em preços, contadores, timers, colunas numéricas de tabelas
|
|
275
|
-
- Valor: `font-variant-numeric: tabular-nums`
|
|
276
|
-
```css
|
|
277
|
-
.price,
|
|
278
|
-
.counter,
|
|
279
|
-
.timer,
|
|
280
|
-
.table-number {
|
|
281
|
-
font-variant-numeric: tabular-nums;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/* Alternativa via font-feature-settings */
|
|
285
|
-
.number {
|
|
286
|
-
font-feature-settings: "tnum";
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
- Por quê: fontes proporcionais têm "1" mais estreito que "0" — sem tabular nums, o layout "pula" a cada atualização de valor
|
|
290
|
-
|
|
291
|
-
**POL-15 — Text Wrapping**
|
|
292
|
-
Usar `balance` em headings e `pretty` em parágrafos para distribuição inteligente de texto entre linhas.
|
|
293
|
-
- Regra: `balance` para headings (distribui palavras igualmente), `pretty` para body (evita orphans)
|
|
294
|
-
- Valor: `text-wrap: balance` e `text-wrap: pretty`
|
|
295
|
-
```css
|
|
296
|
-
h1, h2, h3, h4, h5, h6 {
|
|
297
|
-
text-wrap: balance;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
p, li, blockquote {
|
|
301
|
-
text-wrap: pretty;
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
- Por quê: `balance` evita headings com uma linha cheia e outra com uma palavra; `pretty` evita palavras órfãs (sozinhas) na última linha de parágrafos
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
## Touch & Hit Areas
|
|
309
|
-
|
|
310
|
-
**POL-16 — Hit Area Mínimo 40x40px**
|
|
311
|
-
Área interativa mínima de 40x40px em desktop, 44x44px em mobile. Se o elemento visual é menor, estender a área clicável com pseudo-element.
|
|
312
|
-
- Regra: todo alvo de toque/clique deve ter pelo menos 40x40px de área interativa
|
|
313
|
-
- Valor: 40x40px desktop, 44x44px mobile (Apple HIG)
|
|
314
|
-
```css
|
|
315
|
-
/* Elemento visual pequeno com hit area expandida */
|
|
316
|
-
.icon-button {
|
|
317
|
-
position: relative;
|
|
318
|
-
width: 24px;
|
|
319
|
-
height: 24px;
|
|
320
|
-
}
|
|
321
|
-
.icon-button::before {
|
|
322
|
-
content: '';
|
|
323
|
-
position: absolute;
|
|
324
|
-
top: 50%;
|
|
325
|
-
left: 50%;
|
|
326
|
-
transform: translate(-50%, -50%);
|
|
327
|
-
width: 40px;
|
|
328
|
-
height: 40px;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/* Mobile: expandir para 44px */
|
|
332
|
-
@media (pointer: coarse) {
|
|
333
|
-
.icon-button::before {
|
|
334
|
-
width: 44px;
|
|
335
|
-
height: 44px;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
```
|
|
339
|
-
- Por quê: dedos são imprecisos (~7mm de contato) — alvos menores que 40px causam taps errados e frustração, especialmente em mobile
|