@nurix/etna 0.19.51 → 0.19.53
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/data/harness-index.json +1 -1
- package/data/harnesses/apollo/.mcp.json +8 -0
- package/data/harnesses/apollo/harness.json +3 -2
- package/data/harnesses/apollo/skills/design/SKILL.md +45 -50
- package/data/harnesses/apollo/skills/design/data/blueprints/browse.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/creation-flow.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/dashboard.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/grid.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/queue.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/record-detail.md +38 -0
- package/data/harnesses/apollo/skills/design/data/blueprints/settings.md +38 -0
- package/data/harnesses/apollo/skills/design/data/charts/census.csv +58 -0
- package/data/harnesses/apollo/skills/design/data/checklists/browse.md +21 -0
- package/data/harnesses/apollo/skills/design/data/checklists/creation-flow.md +20 -0
- package/data/harnesses/apollo/skills/design/data/checklists/dashboard.md +21 -0
- package/data/harnesses/apollo/skills/design/data/checklists/grid.md +21 -0
- package/data/harnesses/apollo/skills/design/data/checklists/queue.md +21 -0
- package/data/harnesses/apollo/skills/design/data/checklists/record-detail.md +21 -0
- package/data/harnesses/apollo/skills/design/data/checklists/settings.md +21 -0
- package/data/harnesses/apollo/skills/design/data/chrome/board-column.md +68 -0
- package/data/harnesses/apollo/skills/design/data/chrome/data-table.md +63 -0
- package/data/harnesses/apollo/skills/design/data/chrome/side-panel.md +60 -0
- package/data/harnesses/apollo/skills/design/data/chrome/sidebar.md +78 -0
- package/data/harnesses/apollo/skills/design/data/chrome/stat-cards.md +62 -0
- package/data/harnesses/apollo/skills/design/data/chrome/topbar.md +65 -0
- package/data/harnesses/apollo/skills/design/data/elevation/ant-design-directional-shadow.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/apple-liquid-glass-material.md +18 -0
- package/data/harnesses/apollo/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +20 -0
- package/data/harnesses/apollo/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +20 -0
- package/data/harnesses/apollo/skills/design/data/elevation/github-primer-sparing-shadow.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/ibm-carbon-layer-and-border.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/linear-dark-surface-ladder.md +17 -0
- package/data/harnesses/apollo/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/material-3-tonal-elevation.md +21 -0
- package/data/harnesses/apollo/skills/design/data/elevation/neobrutalist-hard-offset.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +18 -0
- package/data/harnesses/apollo/skills/design/data/elevation/notion-warm-minimal-hairline.md +17 -0
- package/data/harnesses/apollo/skills/design/data/elevation/polaris-elevation-inset-bevel.md +21 -0
- package/data/harnesses/apollo/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/shadcn-neutral-derived-radius.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +18 -0
- package/data/harnesses/apollo/skills/design/data/elevation/tailwind-default-utility-scale.md +19 -0
- package/data/harnesses/apollo/skills/design/data/elevation/vercel-geist-shadow-as-border.md +20 -0
- package/data/harnesses/apollo/skills/design/data/iconography.md +199 -0
- package/data/harnesses/apollo/skills/design/data/motion/accordion-expand.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/destructive-hold-confirm.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/drag-dismiss-momentum.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/empty-state-arrive.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/hover-lift.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/modal-center-arrive.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/number-ticker-roll.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/onboarding-bounce.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/panel-slide.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/press-feedback.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/progress-linear-loop.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/route-crossfade.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/rubber-band-overscroll.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/skeleton-shimmer.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/stagger-arrive.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/toast-enter-stack.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/tooltip-chain-instant.md +18 -0
- package/data/harnesses/apollo/skills/design/data/motion/trigger-anchored-reveal.md +18 -0
- package/data/harnesses/apollo/skills/design/data/tells.csv +34 -0
- package/data/harnesses/apollo/skills/design/data/voices/bright-collaborator.md +29 -0
- package/data/harnesses/apollo/skills/design/data/voices/direct-operator.md +29 -0
- package/data/harnesses/apollo/skills/design/data/voices/earnest-helper.md +29 -0
- package/data/harnesses/apollo/skills/design/data/voices/editorial-composed.md +29 -0
- package/data/harnesses/apollo/skills/design/data/voices/quiet-concierge.md +29 -0
- package/data/harnesses/apollo/skills/design/data/voices/warm-guide.md +29 -0
- package/data/harnesses/apollo/skills/design/references/compose.md +3 -7
- package/data/harnesses/apollo/skills/design/references/content.md +4 -4
- package/data/harnesses/apollo/skills/design/references/existing-apps.md +1 -1
- package/data/harnesses/apollo/skills/design/references/foundations.md +57 -0
- package/data/harnesses/apollo/skills/design/references/intake.md +11 -11
- package/data/harnesses/apollo/skills/design/references/object-pass.md +1 -1
- package/data/harnesses/apollo/skills/design/references/ship-gate.md +1 -1
- package/data/harnesses/apollo/skills/design/scripts/select-chart.mjs +14 -8
- package/data/harnesses/apollo/skills/design/scripts/validate-design-md.mjs +1 -1
- package/data/harnesses/code/.mcp.json +8 -0
- package/data/harnesses/code/harness.json +4 -2
- package/data/harnesses/code/skills/design/SKILL.md +45 -50
- package/data/harnesses/code/skills/design/data/blueprints/browse.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/creation-flow.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/dashboard.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/grid.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/queue.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/record-detail.md +38 -0
- package/data/harnesses/code/skills/design/data/blueprints/settings.md +38 -0
- package/data/harnesses/code/skills/design/data/charts/census.csv +58 -0
- package/data/harnesses/code/skills/design/data/checklists/browse.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/creation-flow.md +20 -0
- package/data/harnesses/code/skills/design/data/checklists/dashboard.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/grid.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/queue.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/record-detail.md +21 -0
- package/data/harnesses/code/skills/design/data/checklists/settings.md +21 -0
- package/data/harnesses/code/skills/design/data/chrome/board-column.md +68 -0
- package/data/harnesses/code/skills/design/data/chrome/data-table.md +63 -0
- package/data/harnesses/code/skills/design/data/chrome/side-panel.md +60 -0
- package/data/harnesses/code/skills/design/data/chrome/sidebar.md +78 -0
- package/data/harnesses/code/skills/design/data/chrome/stat-cards.md +62 -0
- package/data/harnesses/code/skills/design/data/chrome/topbar.md +65 -0
- package/data/harnesses/code/skills/design/data/elevation/ant-design-directional-shadow.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/apple-liquid-glass-material.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/atlassian-surface-shadow-pairing.md +20 -0
- package/data/harnesses/code/skills/design/data/elevation/fluent-2-key-ambient-ramp.md +20 -0
- package/data/harnesses/code/skills/design/data/elevation/github-primer-sparing-shadow.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/ibm-carbon-layer-and-border.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/linear-dark-surface-ladder.md +17 -0
- package/data/harnesses/code/skills/design/data/elevation/material-2-key-ambient-dp-ramp.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/material-3-tonal-elevation.md +21 -0
- package/data/harnesses/code/skills/design/data/elevation/neobrutalist-hard-offset.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/neumorphic-soft-dual-shadow.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/notion-warm-minimal-hairline.md +17 -0
- package/data/harnesses/code/skills/design/data/elevation/polaris-elevation-inset-bevel.md +21 -0
- package/data/harnesses/code/skills/design/data/elevation/radix-themes-alpha-ring-scale.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/shadcn-neutral-derived-radius.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/stripe-brand-tinted-dual-shadow.md +18 -0
- package/data/harnesses/code/skills/design/data/elevation/tailwind-default-utility-scale.md +19 -0
- package/data/harnesses/code/skills/design/data/elevation/vercel-geist-shadow-as-border.md +20 -0
- package/data/harnesses/code/skills/design/data/iconography.md +199 -0
- package/data/harnesses/code/skills/design/data/motion/accordion-expand.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/destructive-hold-confirm.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/drag-dismiss-momentum.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/empty-state-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/hover-lift.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/modal-center-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/number-ticker-roll.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/onboarding-bounce.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/panel-slide.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/press-feedback.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/progress-linear-loop.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/route-crossfade.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/rubber-band-overscroll.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/skeleton-shimmer.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/stagger-arrive.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/toast-enter-stack.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/tooltip-chain-instant.md +18 -0
- package/data/harnesses/code/skills/design/data/motion/trigger-anchored-reveal.md +18 -0
- package/data/harnesses/code/skills/design/data/tells.csv +34 -0
- package/data/harnesses/code/skills/design/data/voices/bright-collaborator.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/direct-operator.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/earnest-helper.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/editorial-composed.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/quiet-concierge.md +29 -0
- package/data/harnesses/code/skills/design/data/voices/warm-guide.md +29 -0
- package/data/harnesses/code/skills/design/references/compose.md +3 -7
- package/data/harnesses/code/skills/design/references/content.md +4 -4
- package/data/harnesses/code/skills/design/references/existing-apps.md +1 -1
- package/data/harnesses/code/skills/design/references/foundations.md +57 -0
- package/data/harnesses/code/skills/design/references/intake.md +11 -11
- package/data/harnesses/code/skills/design/references/object-pass.md +1 -1
- package/data/harnesses/code/skills/design/references/ship-gate.md +1 -1
- package/data/harnesses/code/skills/design/scripts/select-chart.mjs +14 -8
- package/data/harnesses/code/skills/design/scripts/validate-design-md.mjs +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: iconography
|
|
3
|
+
class: a5
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "distilled from the A5 iconography phase-1 research sweep — Lucide, Tabler Icons, Radix Icons, Heroicons, Phosphor (Regular + Duotone), Material Symbols, IBM Carbon, Octicons, Linear, and Stripe Apps"
|
|
6
|
+
license: mixed — nine of the eleven conventions below are open (OSS icon sets under ISC/MIT/Apache-2.0); two are licensed (Linear, Stripe) because they describe a proprietary product's house convention rather than a redistributable asset — see the `license` line inside each convention's own values block
|
|
7
|
+
feel: "the register this file spans: quiet neutral utility (Lucide, Tabler) through dense-precision (Radix), paired-weight (Heroicons), rounded-friendly and illustrative (Phosphor), mechanically variable (Material Symbols), rectilinear enterprise (Carbon), hand-tuned developer precision (Octicons), to near-monochrome restraint (Linear, Stripe) — pick the register the product needs, not just a glyph shape"
|
|
8
|
+
---
|
|
9
|
+
A small conventions database, not a big catalog — eleven curated iconography conventions, each a coherent bundle of icon set, stroke/fill treatment, size grid, default color role, and type-pairing rule, because real products never choose these independently. The standing complaint every entry below answers is the same one: a bare, icon-less nav reads as unfinished; each convention states plainly where icons are obligatory (nav, status glyphs, empty states) versus optional (body copy, pure decoration).
|
|
10
|
+
|
|
11
|
+
## Convention: lucide-quiet-utility
|
|
12
|
+
|
|
13
|
+
**Feel:** "quiet, neutral utility line-work — reads as structural chrome, not decoration"
|
|
14
|
+
|
|
15
|
+
Lucide (ISC-licensed fork of Feather) is the default pick for stroke-based quiet utility icons in SaaS dashboards. Obligatory in nav rails, sidebar items, and empty-state graphics — the failure mode this convention guards against is exactly a bare, icon-less nav; optional inline in body copy or button labels where the text alone already carries the meaning.
|
|
16
|
+
|
|
17
|
+
```yaml values
|
|
18
|
+
set: Lucide (ISC license)
|
|
19
|
+
stroke: "2px, currentColor, round caps/joins, no fills"
|
|
20
|
+
grid: [24, 24]
|
|
21
|
+
default-color: muted-foreground
|
|
22
|
+
pairing: "absoluteStrokeWidth keeps the 2px line constant across sizes; optically matches a 14-16px UI sans at regular weight"
|
|
23
|
+
obligatory: [nav-rail, sidebar-items, empty-state-graphics]
|
|
24
|
+
optional: [body-copy, button-labels]
|
|
25
|
+
license: open
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Convention: tabler-technical-outline
|
|
29
|
+
|
|
30
|
+
**Feel:** "sharp, technical, exhaustive — the 'there's an icon for everything' catalog reads as engineering completeness, not a curated voice"
|
|
31
|
+
|
|
32
|
+
Tabler's 6,000+ MIT icon catalog is the pick when coverage matters more than a distinct personality — admin panels, internal tools, anywhere a designer needs an icon for a niche concept without commissioning custom art. Obligatory in dense toolbars and table row-actions where recognition beats charm; optional as pure decoration, where its neutrality reads generic rather than intentional.
|
|
33
|
+
|
|
34
|
+
```yaml values
|
|
35
|
+
set: Tabler Icons (MIT license)
|
|
36
|
+
stroke: "2px, adjustable, currentColor"
|
|
37
|
+
grid: [24, 24]
|
|
38
|
+
default-color: "neutral-500 / muted-foreground"
|
|
39
|
+
pairing: "2px stroke reads correctly against 14px UI text; bump to ~2.5px only above 32px display sizes so the line doesn't thin out"
|
|
40
|
+
obligatory: [dense-toolbars, table-row-actions]
|
|
41
|
+
optional: [pure-decoration]
|
|
42
|
+
license: open
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Convention: radix-dense-precision
|
|
46
|
+
|
|
47
|
+
**Feel:** "crisp, hard-edged precision built for the smallest interactive targets — dropdowns, toolbars, table cells, badges"
|
|
48
|
+
|
|
49
|
+
Radix Icons' 15x15 grid (not the usual 24) exists because at the sizes dense UI actually renders icons — inside buttons, selects, table cells — a 24-grid icon scaled down loses pixel-snap crispness, while a native 15px drawing keeps strokes on-pixel. Obligatory wherever the paired control is itself sub-20px (combobox triggers, sort carets, badge glyphs); skip above ~20px, where the fixed grid stops paying off and a 24-grid set reads better — there is no "optional" tier here, only a hard size threshold.
|
|
50
|
+
|
|
51
|
+
```yaml values
|
|
52
|
+
set: "Radix Icons (MIT license, designed by WorkOS)"
|
|
53
|
+
stroke: "single fixed weight per icon — redrawn per size, not algorithmically scaled"
|
|
54
|
+
grid: [15, 15]
|
|
55
|
+
default-color: "gray-11 / muted-foreground"
|
|
56
|
+
pairing: "matches the cap-height of 12-13px UI text used across Radix-based component libraries (Radix Themes, shadcn primitives)"
|
|
57
|
+
obligatory: [combobox-triggers, sort-carets, badge-glyphs]
|
|
58
|
+
optional: []
|
|
59
|
+
skip-above: "~20px — hand off to a 24-grid set past this threshold"
|
|
60
|
+
license: open
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Convention: heroicons-outline-solid-pair
|
|
64
|
+
|
|
65
|
+
**Feel:** "Tailwind-native default — outline for resting state, solid for the moment something becomes true"
|
|
66
|
+
|
|
67
|
+
Heroicons ships two coherent weights meant to be switched, never mixed within one context: 24px/1.5px-stroke outline for default and primary nav, 20px solid (plus a 16px mini and micro variant) for active/selected states and compact controls. Obligatory swap pattern: an outline icon in a nav item becomes its solid twin exactly when that item is current — the fill change is the only state signal, so don't blend outline and solid within the same list.
|
|
68
|
+
|
|
69
|
+
```yaml values
|
|
70
|
+
set: "Heroicons (MIT license, Tailwind Labs)"
|
|
71
|
+
stroke: "1.5px outline / filled solid — paired variants, not a weight slider"
|
|
72
|
+
grid: [24, 24] # outline; solid ships at 20px, mini at 16px, micro at 16px
|
|
73
|
+
default-color: "text-gray-500 (outline, resting) -> text-gray-900 or accent (solid, active)"
|
|
74
|
+
pairing: "built on Tailwind's own h-4/h-5/h-6 scale, so icon size always lands exactly on a text-size step"
|
|
75
|
+
obligatory: [nav-active-state-swap]
|
|
76
|
+
optional: []
|
|
77
|
+
license: open
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Convention: phosphor-rounded-friendly
|
|
81
|
+
|
|
82
|
+
**Feel:** "rounded, approachable, slightly playful without tipping into cartoonish — the friendly-SaaS register between Lucide's neutrality and duotone's illustration"
|
|
83
|
+
|
|
84
|
+
Phosphor's Regular weight is the rounded-friendly middle ground: softer terminals than Lucide or Tabler, but still a line icon, not a shape. Obligatory in consumer-facing or community products (onboarding flows, empty states, nav) where the brand voice is warm rather than clinical; match icon weight to type weight — Regular icon beside regular/medium body text, Bold icon only beside semibold-or-heavier headings, so the icon never visually undershoots its label.
|
|
85
|
+
|
|
86
|
+
```yaml values
|
|
87
|
+
set: "Phosphor Icons — Regular weight (MIT license)"
|
|
88
|
+
stroke: "~1.5px-equivalent stroke, rounded caps and joins"
|
|
89
|
+
grid: [24, 24] # authored on a 256x256 source grid, shipped at 24px
|
|
90
|
+
default-color: "muted-foreground, warms to accent on hover/active"
|
|
91
|
+
pairing: "weight axis tracks type weight: Regular <-> body/UI text, Bold <-> semibold+ headings"
|
|
92
|
+
obligatory: [onboarding-flows, empty-states, nav]
|
|
93
|
+
optional: []
|
|
94
|
+
license: open
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Convention: phosphor-duotone-expressive
|
|
98
|
+
|
|
99
|
+
**Feel:** "filled, two-tone, illustrative — reads as a fragment of an illustration more than a clickable control"
|
|
100
|
+
|
|
101
|
+
Phosphor's Duotone weight layers a full-opacity foreground shape over a 20%-opacity fill of the same color, giving flat icons a sense of depth without a second hue. Obligatory in empty states, onboarding checklists, and marketing-adjacent surfaces where an icon needs to carry emotional weight; never use duotone for functional nav or toolbar icons — the two-tone fill reads as illustration rather than an actionable control, and it undermines click affordance.
|
|
102
|
+
|
|
103
|
+
```yaml values
|
|
104
|
+
set: "Phosphor Icons — Duotone weight (MIT license)"
|
|
105
|
+
stroke: "filled: 100% opacity primary shape + 20% opacity secondary layer, same hue"
|
|
106
|
+
grid: [24, 24]
|
|
107
|
+
default-color: "brand/accent (duotone is inherently expressive, rarely neutral gray)"
|
|
108
|
+
pairing: "sized 1.5-2x the adjacent display headline's cap height; never paired directly with dense UI text"
|
|
109
|
+
obligatory: [empty-states, onboarding-checklists, marketing-adjacent-surfaces]
|
|
110
|
+
optional: []
|
|
111
|
+
never-use: [functional-nav, toolbar-icons]
|
|
112
|
+
license: open
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Convention: material-symbols-variable-axes
|
|
116
|
+
|
|
117
|
+
**Feel:** "mechanically consistent, endlessly adjustable — one variable font file standing in for what other systems ship as five separate icon sets"
|
|
118
|
+
|
|
119
|
+
Material Symbols collapses weight, fill, grade, and optical size into a single variable font (`wght` 100-700, `FILL` 0/1, `GRAD` -50 to 200, `opsz` 20-48), so "outline vs. filled" and "thin vs. bold" become live CSS values instead of separate asset swaps. Obligatory pattern: `FILL 0` for resting nav items, animated to `FILL 1` on select — the fill toggle is the selected-state affordance itself, so a nav that skips it loses its only state signal.
|
|
120
|
+
|
|
121
|
+
```yaml values
|
|
122
|
+
set: "Material Symbols (Apache-2.0 license, Google)"
|
|
123
|
+
stroke: "variable FILL axis: 0 (outlined) to 1 (filled); wght 100-700, GRAD -50 to 200"
|
|
124
|
+
grid: [24, 24] # opsz axis spans 20-48 on the same glyph
|
|
125
|
+
default-color: "on-surface-variant (Material's muted-foreground equivalent)"
|
|
126
|
+
pairing: "opsz should numerically track the paired text's rendered px/pt size 1:1, or stroke weight looks wrong at the range's edges"
|
|
127
|
+
obligatory: [nav-fill-toggle-as-selected-state]
|
|
128
|
+
optional: []
|
|
129
|
+
license: open
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Convention: carbon-enterprise-grid
|
|
133
|
+
|
|
134
|
+
**Feel:** "rectilinear, high-density, unmistakably enterprise — icons read as instrumentation, not ornament"
|
|
135
|
+
|
|
136
|
+
IBM Carbon draws every icon on a 32x32 master grid with 2px padding, then scales linearly down to 24/20/16px rather than redrawing per size, keeping optical alignment exact across a data-dense UI. Obligatory at 16px inside buttons and inputs, where IBM Plex Sans runs at 14px; 24/32px reserved for standalone actions and empty-state illustration slots. Touch targets stay 44px+ regardless of the visual icon size — a Carbon-specific accessibility floor worth keeping alongside the visual convention.
|
|
137
|
+
|
|
138
|
+
```yaml values
|
|
139
|
+
set: "IBM Carbon icons (Apache-2.0 license)"
|
|
140
|
+
stroke: "solid/filled glyph forms scaled from one master grid, not a stroke-weight system"
|
|
141
|
+
grid: [32, 32] # scales down linearly to 24/20/16 for actual component use
|
|
142
|
+
default-color: "icon-primary token (near-black in light theme, near-white in dark)"
|
|
143
|
+
pairing: "16/20px icons pair with 14/16px IBM Plex Sans UI text; 24/32px reserved for standalone or empty-state use"
|
|
144
|
+
obligatory: [buttons-and-inputs-at-16px, standalone-actions-at-24-32px, empty-state-illustration]
|
|
145
|
+
optional: []
|
|
146
|
+
touch-target-floor: "44px+ regardless of visual icon size"
|
|
147
|
+
license: open
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Convention: octicons-dual-precision
|
|
151
|
+
|
|
152
|
+
**Feel:** "hand-tuned developer-tool precision — every icon individually redrawn per size rather than mechanically scaled"
|
|
153
|
+
|
|
154
|
+
GitHub's Octicons draws each icon twice — a 16px version and a separately hand-corrected 24px version, rather than one scaled from the other — with a consistent 1.5px stroke, round caps/joins, and 1px corner radius across both. Obligatory at 16px in repo/PR/issue navigation chrome, where an icon-less nav row reads as visibly broken against GitHub's own conventions; 24px reserved for section headers and empty-state glyphs, never mixed with 16px in the same row.
|
|
155
|
+
|
|
156
|
+
```yaml values
|
|
157
|
+
set: "Octicons — GitHub Primer (MIT license)"
|
|
158
|
+
stroke: "1.5px, round caps and joins, 1px corner radius (0.25px on small filled arrowheads)"
|
|
159
|
+
grid: [16, 16] # plus a separately hand-drawn 24x24 variant
|
|
160
|
+
default-color: "fg.muted (Primer token)"
|
|
161
|
+
pairing: "16px icon pairs with Primer's 14px UI text baseline; 24px pairs with section headers only"
|
|
162
|
+
obligatory: [repo-pr-issue-nav-chrome-at-16px, section-headers-and-empty-states-at-24px]
|
|
163
|
+
optional: []
|
|
164
|
+
license: open
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Convention: linear-icon-restraint
|
|
168
|
+
|
|
169
|
+
**Feel:** "near-monochrome restraint — icons are load-bearing wayfinding, never decoration, and the restraint itself is the brand voice"
|
|
170
|
+
|
|
171
|
+
Linear confines icons almost exclusively to two obligatory slots — the sidebar nav rail and issue/status glyphs (priority, workflow state) — and deliberately withholds them everywhere else (body text, secondary inline actions) to protect density. The one place Linear over-indexes on icons rather than under-indexing is exactly where the standing complaint bites hardest: a nav with missing icons reads as unfinished, so nav iconography is treated as non-negotiable even though icons everywhere else stay optional.
|
|
172
|
+
|
|
173
|
+
```yaml values
|
|
174
|
+
set: "product convention (implementation-agnostic thin outline, Lucide-class)"
|
|
175
|
+
stroke: "~1.5px, single weight, no fill states except the active accent swap"
|
|
176
|
+
grid: [16, 16]
|
|
177
|
+
default-color: "muted-foreground, promotes to the single accent color only on active/selected state"
|
|
178
|
+
pairing: "icon optical size matches the 13px UI sans cap height in the sidebar; icon and label baseline-align, never icon-only in primary nav"
|
|
179
|
+
obligatory: [sidebar-nav-rail, issue-status-glyphs]
|
|
180
|
+
optional: [body-text, secondary-inline-actions]
|
|
181
|
+
license: licensed
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Convention: stripe-restrained-fintech
|
|
185
|
+
|
|
186
|
+
**Feel:** "near-monochromatic, trustworthy, financial-instrument register — icons are quiet infrastructure, color is spent on one accent only"
|
|
187
|
+
|
|
188
|
+
Stripe's Icon component ships a fixed five-step size scale (xsmall through xlarge) rather than free pixel values, keeping every icon on-grid with the dashboard's type scale by construction; icons default to inherit surrounding text color and only break to the brand indigo for strokes that need to signal interactivity. Obligatory in the dashboard nav rail and the app-indicator badge (the colored bar + icon identifying which Stripe App is active); optional-to-discouraged as decoration inside data-dense tables, where Stripe's own guidance limits custom styling to preserve platform consistency.
|
|
189
|
+
|
|
190
|
+
```yaml values
|
|
191
|
+
set: "product convention (Stripe Dashboard / Stripe Apps Icon component)"
|
|
192
|
+
stroke: "thin outline, inherits currentColor by default"
|
|
193
|
+
grid: [16, 20, 24, 28, 32] # xsmall..xlarge step scale, not free px
|
|
194
|
+
default-color: "inherits surrounding text color (currentColor); accent indigo for interactive strokes only"
|
|
195
|
+
pairing: "icon size step always matches the adjacent text's type-scale step, never set independently"
|
|
196
|
+
obligatory: [dashboard-nav-rail, app-indicator-badge]
|
|
197
|
+
optional: [decoration-in-dense-tables]
|
|
198
|
+
license: licensed
|
|
199
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: accordion-expand
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/animation-vocabulary/SKILL.md (Accordion/Collapse term) + review-animations/STANDARDS.md (easing decision order — on-screen morph → strong ease-in-out; interruptibility)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a 240ms reveal on a strong ease-in-out reads smooth; snapping open or drifting past 300ms reads clunky"
|
|
8
|
+
aliases: { clunky: "a plain built-in easing instead of the strong custom ease-in-out curve", "snap-open": "no transition at all — content appears/disappears instantly", janky: "keyframes (or animated height) instead of an interruptible transition on transform/clip-path" }
|
|
9
|
+
---
|
|
10
|
+
Expand/collapse sections, FAQ rows, nested tree rows — an on-screen morph, so ease-in-out, not the entrance curve. Prefer `clip-path`/transform reveals over animated `height` (layout property — off-GPU). Rapid toggling must retarget mid-animation, not restart from collapsed.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 240ms
|
|
14
|
+
easing: cubic-bezier(0.77, 0, 0.175, 1)
|
|
15
|
+
element-class: panel
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { panel: 200-300ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: destructive-hold-confirm
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (asymmetric timing — press slow/release fast example, values verbatim class)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a slow deliberate fill on hold with a fast release reads like the system is making you mean it; a symmetric hold/release reads accidental-prone"
|
|
8
|
+
aliases: { "trigger-happy": "hold and release animating at the same speed, so a brief accidental hold nearly commits", "accidental-prone": "no hold gate at all — a single tap commits the destructive action" }
|
|
9
|
+
---
|
|
10
|
+
Delete/destructive confirmations gated behind a hold, not a second click. The hold phase (user deciding) animates slow and must be abortable mid-hold; release or completion (system responding) snaps back fast.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 2000ms (hold-to-fill) / 200ms (release)
|
|
14
|
+
easing: linear (hold) / cubic-bezier(0.23, 1, 0.32, 1) (release)
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { control: 1500-2500ms hold, 150-250ms release }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: drag-dismiss-momentum
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (gestures & drag — velocity dismissal; springs config) + apple-design/SKILL.md §5 (velocity handoff)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a flick that keeps its exact velocity into the dismiss reads thrown away on purpose; a fixed-duration tween to the edge reads like the drag didn't matter"
|
|
8
|
+
aliases: { disconnected: "velocity discarded at release in favor of a fixed-duration keyframe to the exit point", stuck: "dismissal gated on a distance threshold instead of release velocity (~0.11 px/ms)" }
|
|
9
|
+
---
|
|
10
|
+
Swipe-to-dismiss on toasts, cards, sheets. The spring here is a correctness mechanism (velocity continuity for gesture-driven motion), not styling — it carries the pointer's release velocity into the exit and stays grabbable and reversible mid-flight. Bounce is canon-sanctioned specifically for drag-to-dismiss; it doesn't generalize.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 500ms (spring settle; no fixed duration)
|
|
14
|
+
easing: spring(bounce 0.2, damping ~0.8)
|
|
15
|
+
element-class: overlay
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { overlay: 350-550ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: empty-state-arrive
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (springs — subtle bounce) + improve-animations/AUDIT.md §1 (rare moments); A6 admission law (identity-moment scope)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "an illustration that gently scales and settles in reads intentional, like the empty state was designed on purpose; a plain instant fade reads unfinished"
|
|
8
|
+
aliases: { unfinished: "the empty-state illustration/copy just appearing with no entrance at all", overdone: "spring bounce strong enough to feel jokey rather than reassuring" }
|
|
9
|
+
---
|
|
10
|
+
First-visit or true-zero-data empty states (no rows yet, no results yet). Low-frequency identity moment — springy motion is admissible, but keep bounce subtle since the tone is reassuring, not playful.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 450ms (spring; no fixed duration)
|
|
14
|
+
easing: spring(bounce 0.15)
|
|
15
|
+
element-class: identity-moment
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { identity-moment: 350-550ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hover-lift
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/emil-design-eng/SKILL.md (easing decision — hover → ease) + review-animations/STANDARDS.md (frequency tiers; hover gating); Easing Blueprint agrees as secondary"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a soft `ease` color/elevation shift under 200ms reads responsive; past 250ms or on touch it reads laggy and fake"
|
|
8
|
+
aliases: { laggy: "hover duration pushed past the control budget", "fake-hover": "hover motion firing on a touch tap because it isn't gated to hover:hover and pointer:fine" }
|
|
9
|
+
---
|
|
10
|
+
List rows, nav items, cards, toolbar icons on mouse-hover. Gate behind `@media (hover: hover) and (pointer: fine)` always. Sits in the "tens of times/day — reduce, don't eliminate" frequency tier; drop it first on any list scrolled constantly.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 150ms
|
|
14
|
+
easing: ease
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { control: 120-200ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: modal-center-arrive
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (physicality — modal exemption; duration table, modal row) + improve-animations/AUDIT.md §3; Material emphasized-decelerate agrees as secondary"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a centered scale-from-0.95-and-fade at ~250ms on a strong ease-out reads deliberate and important; scale(0) or a snap-open reads like it teleported in"
|
|
8
|
+
aliases: { teleported: "scale(0) or an opacity-only entrance with no initial transform", "snap-open": "no interpolation at all — the dialog just appears" }
|
|
9
|
+
---
|
|
10
|
+
Dialogs and confirmation modals only — the one place `transform-origin: center` is correct, exempt from the trigger-anchoring rule. Entrance starts from scale 0.9-0.97 + opacity 0, never scale(0); the longer end of the budget is the modal's prominence — don't reuse it on tooltips or dropdowns.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 250ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: overlay
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { overlay: 200-500ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: number-ticker-roll
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/animation-vocabulary/SKILL.md (Number ticker; Tabular numbers) + review-animations/STANDARDS.md (sub-300ms rule; interruptibility)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "digits rolling to the new value at ~250ms read like a live counter; an instant jump-cut reads like the number just glitched"
|
|
8
|
+
aliases: { glitched: "value changes with no roll/interpolation at all", "tabular-drift": "digits set without tabular figures, so the number visibly shifts width as it rolls", janky: "keyframe roll restarting from zero on each rapid update instead of retargeting" }
|
|
9
|
+
---
|
|
10
|
+
Live counters, stat tiles, KPI cards, running totals — system-driven value changes, so the sub-300ms UI rule applies. Requires tabular figures so surrounding layout doesn't shift while digits roll. Rapid successive updates retarget the roll (transition, not keyframes), never restart from zero.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 250ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { control: 200-300ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: onboarding-bounce
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (springs — bounce 0.1-0.3, reserved for playful moments) + improve-animations/AUDIT.md §1 (rare/first-time → delight)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a springy overshoot on a first-run welcome card reads warm and considered; the same bounce on anything seen daily reads gimmicky"
|
|
8
|
+
aliases: { gimmicky: "spring bounce applied outside a rare/first-time identity moment — the same overshoot on a daily-use control", overdone: "bounce pushed above the 0.1-0.3 range into cartoonish territory" }
|
|
9
|
+
---
|
|
10
|
+
Onboarding welcome screens, first-run tours — rare, high-emotion, seen once or a handful of times ever. The scoped exception where springy/physical vocabulary is admissible; it does not license bounce anywhere else.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 500ms (spring; no fixed duration)
|
|
14
|
+
easing: spring(bounce 0.3)
|
|
15
|
+
element-class: identity-moment
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { identity-moment: 400-600ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: panel-slide
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (--ease-drawer curve; duration table, modal/drawer row) + apple-design/SKILL.md §3 (interruption; velocity continuity)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a 260ms slide on the iOS drawer curve reads native and grabbable; a keyframe slide that restarts on repeated taps reads broken"
|
|
8
|
+
aliases: { broken: "keyframes restarting from zero instead of a transition/spring retargeting mid-slide", floaty: "duration pushed past the panel budget" }
|
|
9
|
+
---
|
|
10
|
+
Side panels, drawers, bottom sheets. Must be interruptible and carry velocity on a gesture-driven close — grabbing a closing panel should follow the pointer, not fight it.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 260ms
|
|
14
|
+
easing: cubic-bezier(0.32, 0.72, 0, 1)
|
|
15
|
+
element-class: panel
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { panel: 200-500ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: press-feedback
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (physicality — press feedback; duration table) + improve-animations/AUDIT.md §2-3"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "160ms scale-to-0.97 on pointer-down reads tactile and instant; slower or absent reads dead under the finger"
|
|
8
|
+
aliases: { dead: "no press feedback at all, or duration pushed above the 160ms control budget", mushy: "an easing softer than ease-out, delaying the visible response" }
|
|
9
|
+
---
|
|
10
|
+
Any pressable control — buttons, icon buttons, tap-target rows. The canon prescribes this on every pressable element while forbidding animation on keyboard-initiated actions: it fires on pointer-down only (a tap affordance, not a state transition), and keyboard activation (Enter/Space) never replays it.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 160ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { control: 100-160ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: progress-linear-loop
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (easing decision order — constant motion → linear) + animation-vocabulary/SKILL.md (Loop; Marquee); Material's linear token agrees as secondary"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a constant linear sweep reads honest about an unknown wait; any easing that speeds up and slows down reads like a fake promise"
|
|
8
|
+
aliases: { "fake-promise": "eased (non-linear) motion on an indeterminate progress indicator, implying a completion point that doesn't exist", twitchy: "loop cycle faster than ~1s, reading anxious rather than calm" }
|
|
9
|
+
---
|
|
10
|
+
Indeterminate progress bars, loading spinners, marquees. Constant-speed motion only — the one place `linear` is correct and `ease` curves are wrong.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 1200ms (loop)
|
|
14
|
+
easing: linear
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { control: 1000-1500ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: route-crossfade
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/animation-vocabulary/SKILL.md (Crossfade; Page transition) + review-animations/STANDARDS.md (sub-300ms rule; masking imperfect crossfades; easing decision order — opacity/color → ease)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a single ~250ms crossfade between views reads like one continuous product; a staggered reveal of each panel on arrival reads like a slideshow"
|
|
8
|
+
aliases: { slideshow: "child elements staggering in individually on route arrival instead of one crossfade", "double-exposed": "old and new view both fully visible mid-transition — mask with a subtle blur (~2px, never above 20px)" }
|
|
9
|
+
---
|
|
10
|
+
Navigating between routes or top-level views — a same-place opacity morph, not a spatial entrance, so `ease` rather than the entrance curve. One crossfade only; never an orchestrated sequence of the destination's own elements arriving one by one. A product loads into a task, not a reveal.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 250ms
|
|
14
|
+
easing: ease
|
|
15
|
+
element-class: page
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { page: 200-300ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: rubber-band-overscroll
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/animation-vocabulary/SKILL.md (Rubber-banding) + apple-design/SKILL.md §8 (boundary resistance) and spring cheat-sheet (critically-damped default)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "resistance that grows the further you pull, then a quick snap-back, reads alive and physical; a hard stop at the edge reads frozen"
|
|
8
|
+
aliases: { frozen: "a hard stop at the scroll boundary instead of progressive resistance", overshoot: "snap-back overshoots past the origin instead of settling cleanly" }
|
|
9
|
+
---
|
|
10
|
+
Scrollable containers and pull-to-refresh regions at their boundary. Resistance increases continuously with drag distance past the edge; release animates back with a critically-damped spring, never a linear tween.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 300ms (snap-back)
|
|
14
|
+
easing: spring(damping increases near boundary)
|
|
15
|
+
element-class: page
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { page: 200-400ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: skeleton-shimmer
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/animation-vocabulary/SKILL.md (Skeleton/Shimmer) + review-animations/STANDARDS.md (easing decision order — constant motion → linear)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a slow, continuous sheen reads like content is genuinely on its way; a fast or jittery shimmer reads anxious"
|
|
8
|
+
aliases: { anxious: "shimmer cycle faster than ~1s, reading twitchy instead of calm", stuck: "a static placeholder with no shimmer at all, giving no sense of progress" }
|
|
9
|
+
---
|
|
10
|
+
Loading placeholders for cards, rows, and detail panes before content resolves. Runs as a continuous ambient loop, exempt from the sub-300ms UI budget (constant-motion class); swap to real content with a quick crossfade, not a hard cut.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 1400ms (loop)
|
|
14
|
+
easing: linear
|
|
15
|
+
element-class: list-item
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { list-item: 1200-1600ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: stagger-arrive
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (stagger — 30-80ms, decorative, never blocks interaction)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a ~50ms cascade with a small upward drift reads considered; everything landing at once reads dumped, and gaps above 80ms read slow"
|
|
8
|
+
aliases: { dumped: "no stagger at all on a multi-item entrance", draggy: "per-item stagger delay above the 80ms budget" }
|
|
9
|
+
---
|
|
10
|
+
List and grid entrances for freshly-loaded content within a task (search results, new rows) — never on initial page load or route arrival (no orchestrated load sequences). Purely decorative — must never delay the list becoming interactive; keep the per-item delay inside 30-80ms so a ten-row list finishes well under half a second.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 300ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: list-item
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { list-item: 250-350ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: toast-enter-stack
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (interruptibility — toast transition example; cohesion — the Sonner register)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "a 400ms transition-based stack reads calm even when toasts pile up fast; a keyframe stack reads jumpy every time a new one lands"
|
|
8
|
+
aliases: { jumpy: "@keyframes restarting already-stacked toasts instead of one continuous transition retargeting them", jarring: "no @starting-style/mount fallback, so the first toast snaps in with no entrance" }
|
|
9
|
+
---
|
|
10
|
+
Toast/notification stacks. A new arrival must not restart the position transition of toasts already stacked — everything reflows on one continuous transition, never per-toast keyframes.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 400ms
|
|
14
|
+
easing: ease
|
|
15
|
+
element-class: overlay
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { overlay: 300-450ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tooltip-chain-instant
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/emil-design-eng/SKILL.md (tooltip-chaining example) + review-animations/STANDARDS.md (duration table, tooltip row; perceived performance)"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "the first tooltip's 125ms fade-and-lift reads considered; every later tooltip in the same toolbar should read instant, with zero animation"
|
|
8
|
+
aliases: { "laggy-toolbar": "every tooltip in a group replaying the full delay+animation instead of chaining to instant after the first", floaty: "duration above the small-popover budget" }
|
|
9
|
+
---
|
|
10
|
+
Tooltips, especially adjacent ones in a toolbar. First open: full delay + animation. While the pointer stays within the tooltip group, later tooltips switch to 0ms — this is the frequency law being satisfied, not an exception to it.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 125ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: control
|
|
16
|
+
interruptible: n/a
|
|
17
|
+
budget: { control: 0-200ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: trigger-anchored-reveal
|
|
3
|
+
class: a6
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "emilkowalski.skills/skills/review-animations/STANDARDS.md (physicality — origin-aware popovers; duration table, dropdown row) + improve-animations/AUDIT.md §3"
|
|
6
|
+
license: open
|
|
7
|
+
feel: "growing out of the trigger at ~200ms reads anchored and native; center-scale or scale(0) reads like a random popup"
|
|
8
|
+
aliases: { disconnected: "transform-origin left at center instead of the trigger element", glitchy: "scale(0) start instead of scale(0.9-0.97) + opacity" }
|
|
9
|
+
---
|
|
10
|
+
Popovers, dropdowns, select menus, tooltips. `transform-origin` follows the trigger (Radix/Base UI expose it as a CSS var); never centers. Rapid open/close/open must retarget, not restart.
|
|
11
|
+
|
|
12
|
+
```yaml values
|
|
13
|
+
duration: 200ms
|
|
14
|
+
easing: cubic-bezier(0.23, 1, 0.32, 1)
|
|
15
|
+
element-class: overlay
|
|
16
|
+
interruptible: required
|
|
17
|
+
budget: { overlay: 150-250ms }
|
|
18
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
id,tell,category,provenance,sourced,status
|
|
2
|
+
tell-001,"every shadow set to zero and every border the same 1px gray — structure without a single elevation decision",wireframe-smell,"design-skill 01 §E5; impeccable detector registry",2026-07-12,active
|
|
3
|
+
tell-002,"rounded-square icon tile stacked above the heading on every card — the universal generated feature-card template",template,"impeccable detector registry (icon-tile-stack)",2026-07-12,active
|
|
4
|
+
tell-003,"cards nested inside cards — double borders and stacked radii standing in for hierarchy",card-abuse,"impeccable detector registry (nested-cards); design-skill 01 §E5",2026-07-12,active
|
|
5
|
+
tell-004,"thick colored stripe down one edge of a card as the accent device",accent-abuse,"impeccable detector registry (side-tab); design-skill 01 §E5",2026-07-12,active
|
|
6
|
+
tell-005,"accent border wrapped around a rounded card — the stripe fighting the corner radius",accent-abuse,"impeccable detector registry (border-accent-on-rounded)",2026-07-12,active
|
|
7
|
+
tell-006,"hero-metric template — a row of identical stat tiles each icon + label + big number, with no comparison, trend, state, or door",template,"design-skill 01 §E5; b2b-ux-patterns dashboards chapter",2026-07-12,active
|
|
8
|
+
tell-007,"cream or warm-beige page surface reached by reflex as the tasteful default",palette,"impeccable detector registry (cream-palette); taste-skill §9.A; design-skill 01 §A2 anti-cream guardband",2026-07-12,active
|
|
9
|
+
tell-008,"ghost cards — bordered or shadowed containers wrapping a single short text run or nothing at all",card-abuse,"design-skill 01 §E5",2026-07-12,active
|
|
10
|
+
tell-009,"purple-violet gradients and cyan-on-dark as the default accent scheme",palette,"impeccable detector registry (ai-color-palette)",2026-07-12,active
|
|
11
|
+
tell-010,"dark theme expressed as colored box-shadow glows instead of a surface-lightness ladder",palette,"impeccable detector registry (dark-glow); design-skill 01 §A3 dark ladder",2026-07-12,active
|
|
12
|
+
tell-011,"gradient fills on heading or metric text",typography,"impeccable detector registry (gradient-text)",2026-07-12,active
|
|
13
|
+
tell-012,"flat type ramp — adjacent sizes too close together to carry hierarchy anywhere on the surface",typography,"impeccable detector registry (flat-type-hierarchy)",2026-07-12,active
|
|
14
|
+
tell-013,"one spacing value everywhere — no tight grouping of related controls, no generous seams between sections",layout,"impeccable detector registry (monotonous-spacing)",2026-07-12,active
|
|
15
|
+
tell-014,"hairline border and a wide diffuse shadow on the same container — two elevation systems at once",elevation,"impeccable detector registry (generated-UI signature set)",2026-07-12,active
|
|
16
|
+
tell-015,"decorative two-axis hairline grid background behind a surface that is not a canvas, map, or measurement view",template,"impeccable detector registry (generated-UI signature set)",2026-07-12,active
|
|
17
|
+
tell-016,"numbered section markers (01 / 02 / 03) as structural scaffolding",template,"impeccable detector registry (numbered-section-markers); taste-skill §9.F",2026-07-12,active
|
|
18
|
+
tell-017,"tiny uppercase letter-spaced kicker label repeated above every section heading",template,"impeccable detector registry (repeated-section-kickers)",2026-07-12,active
|
|
19
|
+
tell-018,"colored status dots decorating every nav item, row, or badge with no real state behind them",chrome,"taste-skill §9.F production-observed tells",2026-07-12,active
|
|
20
|
+
tell-019,"middle-dot chains as the default separator for every metadata strip",chrome,"taste-skill §9.F production-observed tells",2026-07-12,active
|
|
21
|
+
tell-020,"top and bottom hairline borders on every row of a long table or spec list",wireframe-smell,"taste-skill §9.F production-observed tells",2026-07-12,active
|
|
22
|
+
tell-021,"fake build or version strings as decoration where no real build data exists",demo-content,"taste-skill §9.F production-observed tells",2026-07-12,active
|
|
23
|
+
tell-022,"placeholder-generic person names in demo data (the John Doe class)",demo-content,"taste-skill §9.D (Jane Doe effect); design-skill 01 §E5",2026-07-12,active
|
|
24
|
+
tell-023,"the same gray egg or user-icon avatar on every person row",demo-content,"taste-skill §9.D production-observed tells",2026-07-12,active
|
|
25
|
+
tell-024,"fake-perfect numbers — round percentages, sequential IDs, totals that reconcile too neatly",demo-content,"taste-skill §9.D; design-skill 01 §E5",2026-07-12,active
|
|
26
|
+
tell-025,"startup-slop company names in demo data (the Acme / Nexus / Cloudly class)",demo-content,"taste-skill §9.D production-observed tells",2026-07-12,active
|
|
27
|
+
tell-026,"marketing buzzwords in interface copy (streamline, empower, supercharge, enterprise-grade)",copy,"impeccable detector registry (marketing-buzzword); taste-skill §9.D",2026-07-12,active
|
|
28
|
+
tell-027,"em-dash-heavy microcopy — the character recurring across labels, empty states, and tooltips",copy,"taste-skill §9.G; impeccable detector registry (em-dash-overuse)",2026-07-12,active
|
|
29
|
+
tell-028,"transition: all on interactive elements",motion,"design-skill 02 ship-gate Tier-2 motion tells",2026-07-12,active
|
|
30
|
+
tell-029,"ease-in easing on an entrance",motion,"design-skill 02 ship-gate Tier-2 motion tells; emilkowalski motion canon",2026-07-12,active
|
|
31
|
+
tell-030,"animation attached to a keyboard-triggered action",motion,"design-skill 02 ship-gate Tier-2 motion tells; emilkowalski frequency law",2026-07-12,active
|
|
32
|
+
tell-031,"bounce or elastic easing on product chrome",motion,"impeccable detector registry (bounce-easing)",2026-07-12,active
|
|
33
|
+
tell-032,"width, height, padding, or margin animated instead of transform and opacity",motion,"impeccable detector registry (layout-transition)",2026-07-12,active
|
|
34
|
+
tell-033,"orchestrated staggered entrance choreography on a work surface — a product loads into a task",motion,"design-skill 01 §A6 admission criteria; emilkowalski motion canon",2026-07-12,active
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: bright-collaborator
|
|
3
|
+
class: c2
|
|
4
|
+
sourced: 2026-07-12
|
|
5
|
+
source: "Distilled from Slack's voice & tone guidance (api.slack.com/best-practices/voice-and-tone) — confident not cocky, witty not silly, clear-concise-human, tone as seasoning not the dish."
|
|
6
|
+
license: open
|
|
7
|
+
---
|
|
8
|
+
# Voice: bright-collaborator
|
|
9
|
+
|
|
10
|
+
**Feel:** Confident and a little playful, but the point always lands first.
|
|
11
|
+
|
|
12
|
+
Conversational rhythm — short sentences mixed with one longer one for cadence, contractions standard, second person throughout. Personality is seasoning: light wit is reserved for confirmations and empty states, never for errors. Actions open with a verb. Numbers are digits. One idea per sentence — never two clauses competing for attention.
|
|
13
|
+
|
|
14
|
+
## Tone map
|
|
15
|
+
|
|
16
|
+
Voice is constant; tone dials by situation.
|
|
17
|
+
|
|
18
|
+
- **routine action:** verb-first with a light conversational lift
|
|
19
|
+
- **error:** personality drops out entirely — plain statement, then the fix
|
|
20
|
+
- **success:** wit is allowed, one line, never stacked
|
|
21
|
+
- **destructive confirmation:** playfulness fully off, states the stakes plainly
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
- **button:** "Invite teammate"
|
|
26
|
+
- **empty state (heading + motivation):** "Quiet in here" / "Invite a teammate and this space starts filling up."
|
|
27
|
+
- **error:** "That invite didn't go through. Check the email and send it again."
|
|
28
|
+
- **tooltip:** "Only workspace admins can see this."
|
|
29
|
+
- **confirmation:** "Teammate invited — welcome aboard."
|