@felloinnovations/brand-kit 0.4.0 → 0.5.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.md +198 -24
- package/CHANGELOG.md +197 -0
- package/README.md +34 -7
- package/design-system/01-how-claude-decides.md +26 -17
- package/design-system/02-principles.md +11 -6
- package/design-system/03-colour.md +134 -47
- package/design-system/04-type.md +45 -23
- package/design-system/05-space-and-layout.md +175 -52
- package/design-system/06-sizing-profiles.md +4 -4
- package/design-system/07-scrolling.md +66 -20
- package/design-system/08-choosing-components.md +50 -22
- package/design-system/09-charts.md +71 -28
- package/design-system/10-patterns.md +33 -10
- package/design-system/11-page-recipes.md +110 -23
- package/design-system/12-content.md +2 -1
- package/design-system/13-motion.md +330 -47
- package/design-system/14-accessibility.md +47 -24
- package/design-system/15-ai-experiences.md +11 -6
- package/design-system/16-creativity.md +1 -1
- package/design-system/17-decisions.md +357 -1
- package/design-system/18-checklist.md +79 -16
- package/design-system/19-public-pages.md +6 -4
- package/design-system/20-palettes.md +275 -0
- package/design-system/README.md +30 -17
- package/design-system/components/ActionList/README.md +96 -10
- package/design-system/components/ActionList/preview.html +52 -7
- package/design-system/components/ActionList/style.css +64 -0
- package/design-system/components/ActivityGrid/README.md +38 -2
- package/design-system/components/ActivityGrid/preview.html +1 -1
- package/design-system/components/ActivityGrid/style.css +35 -0
- package/design-system/components/AppShell/README.md +205 -75
- package/design-system/components/AppShell/preview.html +339 -58
- package/design-system/components/AppShell/style.css +154 -0
- package/design-system/components/Avatar/README.md +37 -10
- package/design-system/components/Avatar/preview.html +13 -4
- package/design-system/components/Avatar/style.css +14 -0
- package/design-system/components/Badge/README.md +41 -19
- package/design-system/components/Badge/preview.html +13 -10
- package/design-system/components/Badge/style.css +23 -0
- package/design-system/components/Banner/README.md +30 -9
- package/design-system/components/Banner/preview.html +1 -1
- package/design-system/components/Banner/style.css +19 -0
- package/design-system/components/Breadcrumb/README.md +58 -46
- package/design-system/components/Breadcrumb/preview.html +16 -16
- package/design-system/components/Breadcrumb/style.css +17 -0
- package/design-system/components/Button/README.md +113 -55
- package/design-system/components/Button/preview.html +98 -25
- package/design-system/components/Button/style.css +86 -0
- package/design-system/components/Card/README.md +158 -49
- package/design-system/components/Card/preview.html +141 -19
- package/design-system/components/Card/style.css +115 -0
- package/design-system/components/CardBoard/README.md +58 -37
- package/design-system/components/CardBoard/preview.html +26 -29
- package/design-system/components/ChatCard/README.md +115 -29
- package/design-system/components/ChatCard/preview.html +149 -32
- package/design-system/components/ChatCard/style.css +49 -0
- package/design-system/components/ChatShell/README.md +116 -0
- package/design-system/components/ChatShell/preview.html +201 -0
- package/design-system/components/ChatShell/style.css +86 -0
- package/design-system/components/ChatTurn/README.md +78 -35
- package/design-system/components/ChatTurn/preview.html +60 -28
- package/design-system/components/ChatTurn/style.css +50 -0
- package/design-system/components/CheckRow/README.md +141 -0
- package/design-system/components/CheckRow/preview.html +140 -0
- package/design-system/components/CheckRow/style.css +88 -0
- package/design-system/components/Checkbox/README.md +58 -22
- package/design-system/components/Checkbox/preview.html +112 -50
- package/design-system/components/Checkbox/style.css +45 -0
- package/design-system/components/Chip/README.md +78 -47
- package/design-system/components/Chip/preview.html +29 -4
- package/design-system/components/Chip/style.css +17 -0
- package/design-system/components/Combobox/README.md +44 -11
- package/design-system/components/Combobox/preview.html +35 -12
- package/design-system/components/Combobox/style.css +54 -0
- package/design-system/components/Composer/README.md +137 -35
- package/design-system/components/Composer/preview.html +115 -40
- package/design-system/components/Composer/style.css +81 -0
- package/design-system/components/ConfirmDialog/README.md +73 -52
- package/design-system/components/ConfirmDialog/preview.html +70 -26
- package/design-system/components/ConfirmDialog/style.css +5 -0
- package/design-system/components/Cover/preview.html +3 -3
- package/design-system/components/Cover/style.css +19 -0
- package/design-system/components/DateInput/README.md +77 -12
- package/design-system/components/DateInput/preview.html +27 -12
- package/design-system/components/DateInput/style.css +38 -0
- package/design-system/components/Dialog/README.md +112 -44
- package/design-system/components/Dialog/preview.html +141 -18
- package/design-system/components/Dialog/style.css +74 -0
- package/design-system/components/EmptyState/README.md +42 -12
- package/design-system/components/EmptyState/preview.html +11 -11
- package/design-system/components/EmptyState/style.css +9 -0
- package/design-system/components/EntryShell/README.md +73 -48
- package/design-system/components/EntryShell/preview.html +60 -27
- package/design-system/components/EntryShell/style.css +19 -0
- package/design-system/components/FeatureGrid/README.md +61 -39
- package/design-system/components/FeatureGrid/preview.html +2 -2
- package/design-system/components/FeatureGrid/style.css +20 -0
- package/design-system/components/FileUpload/README.md +33 -3
- package/design-system/components/FileUpload/preview.html +18 -4
- package/design-system/components/FileUpload/style.css +23 -0
- package/design-system/components/FilterBar/README.md +114 -0
- package/design-system/components/FilterBar/preview.html +82 -0
- package/design-system/components/FilterBar/style.css +22 -0
- package/design-system/components/Fold/README.md +84 -26
- package/design-system/components/Fold/preview.html +77 -34
- package/design-system/components/Fold/style.css +48 -0
- package/design-system/components/Footer/README.md +62 -39
- package/design-system/components/Footer/preview.html +5 -5
- package/design-system/components/Footer/style.css +30 -0
- package/design-system/components/FunnelBars/README.md +24 -3
- package/design-system/components/FunnelBars/preview.html +5 -5
- package/design-system/components/FunnelBars/style.css +13 -0
- package/design-system/components/GoalBars/README.md +54 -10
- package/design-system/components/GoalBars/preview.html +69 -28
- package/design-system/components/GoalBars/style.css +51 -0
- package/design-system/components/GoalTicks/README.md +74 -58
- package/design-system/components/GoalTicks/preview.html +41 -118
- package/design-system/components/GoalTicks/style.css +37 -0
- package/design-system/components/GoalTubes/README.md +29 -3
- package/design-system/components/GoalTubes/preview.html +6 -6
- package/design-system/components/GoalTubes/style.css +23 -0
- package/design-system/components/Heatmap/README.md +133 -0
- package/design-system/components/Heatmap/preview.html +168 -0
- package/design-system/components/Heatmap/style.css +119 -0
- package/design-system/components/Hero/README.md +58 -40
- package/design-system/components/Hero/preview.html +1 -1
- package/design-system/components/Hero/style.css +19 -0
- package/design-system/components/HoverCard/README.md +114 -0
- package/design-system/components/HoverCard/preview.html +107 -0
- package/design-system/components/HoverCard/style.css +59 -0
- package/design-system/components/IconButton/README.md +69 -38
- package/design-system/components/IconButton/preview.html +77 -43
- package/design-system/components/IconButton/style.css +45 -0
- package/design-system/components/Input/README.md +36 -11
- package/design-system/components/Input/preview.html +1 -1
- package/design-system/components/Input/style.css +23 -0
- package/design-system/components/KanbanBoard/README.md +93 -45
- package/design-system/components/KanbanBoard/preview.html +159 -43
- package/design-system/components/KanbanBoard/style.css +53 -0
- package/design-system/components/Kbd/README.md +25 -9
- package/design-system/components/Kbd/preview.html +6 -2
- package/design-system/components/Kbd/style.css +9 -0
- package/design-system/components/KitButton/README.md +81 -47
- package/design-system/components/KitButton/preview.html +35 -13
- package/design-system/components/KitButton/style.css +46 -0
- package/design-system/components/KitCard/README.md +75 -44
- package/design-system/components/KitCard/preview.html +18 -10
- package/design-system/components/KitCard/style.css +15 -0
- package/design-system/components/KitInput/README.md +54 -39
- package/design-system/components/KitInput/preview.html +7 -7
- package/design-system/components/KitInput/style.css +23 -0
- package/design-system/components/Leaderboard/README.md +76 -49
- package/design-system/components/Leaderboard/preview.html +61 -26
- package/design-system/components/Leaderboard/style.css +44 -0
- package/design-system/components/ListDetail/README.md +222 -0
- package/design-system/components/ListDetail/preview.html +213 -0
- package/design-system/components/ListDetail/style.css +148 -0
- package/design-system/components/LoadBar/README.md +79 -0
- package/design-system/components/LoadBar/preview.html +40 -0
- package/design-system/components/LoadBar/style.css +17 -0
- package/design-system/components/MarketingNav/README.md +76 -42
- package/design-system/components/MarketingNav/preview.html +37 -8
- package/design-system/components/MarketingNav/style.css +45 -0
- package/design-system/components/Matrix/README.md +129 -0
- package/design-system/components/Matrix/preview.html +99 -0
- package/design-system/components/Matrix/style.css +84 -0
- package/design-system/components/Menu/README.md +117 -41
- package/design-system/components/Menu/preview.html +85 -23
- package/design-system/components/Menu/style.css +61 -0
- package/design-system/components/Meter/README.md +39 -4
- package/design-system/components/Meter/preview.html +18 -1
- package/design-system/components/Meter/style.css +23 -0
- package/design-system/components/MetricTile/README.md +237 -0
- package/design-system/components/MetricTile/preview.html +241 -0
- package/design-system/components/MetricTile/style.css +129 -0
- package/design-system/components/Morph/README.md +106 -0
- package/design-system/components/Morph/preview.html +103 -0
- package/design-system/components/Morph/style.css +31 -0
- package/design-system/components/Motion/README.md +164 -0
- package/design-system/components/Motion/preview.html +2537 -0
- package/design-system/components/NumberInput/README.md +27 -8
- package/design-system/components/NumberInput/preview.html +21 -5
- package/design-system/components/NumberInput/style.css +34 -0
- package/design-system/components/PaceBar/README.md +75 -0
- package/design-system/components/PaceBar/preview.html +67 -0
- package/design-system/components/PaceBar/style.css +25 -0
- package/design-system/components/Page/README.md +57 -18
- package/design-system/components/Page/preview.html +15 -17
- package/design-system/components/Page/style.css +7 -0
- package/design-system/components/PageBar/README.md +90 -34
- package/design-system/components/PageBar/preview.html +60 -19
- package/design-system/components/PageBar/style.css +35 -0
- package/design-system/components/PageHero/README.md +87 -51
- package/design-system/components/PageHero/preview.html +35 -6
- package/design-system/components/PageHero/style.css +31 -0
- package/design-system/components/Pagination/README.md +70 -49
- package/design-system/components/Pagination/preview.html +24 -15
- package/design-system/components/Pagination/style.css +12 -0
- package/design-system/components/Panel/README.md +109 -51
- package/design-system/components/Panel/preview.html +118 -20
- package/design-system/components/Panel/style.css +51 -0
- package/design-system/components/PasswordInput/README.md +50 -18
- package/design-system/components/PasswordInput/preview.html +71 -21
- package/design-system/components/PasswordInput/style.css +31 -0
- package/design-system/components/Plate/README.md +75 -17
- package/design-system/components/Plate/preview.html +40 -7
- package/design-system/components/Plate/style.css +37 -0
- package/design-system/components/Popover/README.md +70 -49
- package/design-system/components/Popover/preview.html +44 -22
- package/design-system/components/Popover/style.css +41 -0
- package/design-system/components/PricingTable/README.md +69 -49
- package/design-system/components/PricingTable/preview.html +21 -21
- package/design-system/components/PricingTable/style.css +21 -0
- package/design-system/components/ProgressPetals/README.md +31 -3
- package/design-system/components/ProgressPetals/preview.html +14 -15
- package/design-system/components/ProgressPetals/style.css +27 -0
- package/design-system/components/README.md +151 -0
- package/design-system/components/Radio/README.md +40 -17
- package/design-system/components/Radio/preview.html +16 -1
- package/design-system/components/Radio/style.css +21 -0
- package/design-system/components/RankBar/README.md +29 -2
- package/design-system/components/RankBar/preview.html +14 -12
- package/design-system/components/RankBar/style.css +18 -0
- package/design-system/components/Ring/README.md +61 -11
- package/design-system/components/Ring/preview.html +55 -10
- package/design-system/components/Ring/style.css +49 -0
- package/design-system/components/ScoreHero/README.md +91 -0
- package/design-system/components/ScoreHero/preview.html +104 -0
- package/design-system/components/ScoreHero/style.css +32 -0
- package/design-system/components/Section/README.md +72 -33
- package/design-system/components/Section/preview.html +46 -16
- package/design-system/components/Section/style.css +18 -0
- package/design-system/components/Segmented/README.md +100 -55
- package/design-system/components/Segmented/preview.html +107 -26
- package/design-system/components/Segmented/style.css +94 -0
- package/design-system/components/SegmentedProgress/README.md +64 -0
- package/design-system/components/SegmentedProgress/preview.html +69 -0
- package/design-system/components/SegmentedProgress/style.css +11 -0
- package/design-system/components/Select/README.md +47 -12
- package/design-system/components/Select/preview.html +28 -2
- package/design-system/components/Select/style.css +22 -0
- package/design-system/components/ShareBar/README.md +69 -0
- package/design-system/components/ShareBar/preview.html +72 -0
- package/design-system/components/ShareBar/style.css +25 -0
- package/design-system/components/SideList/README.md +147 -0
- package/design-system/components/SideList/preview.html +139 -0
- package/design-system/components/SideList/style.css +94 -0
- package/design-system/components/Skeleton/README.md +52 -16
- package/design-system/components/Skeleton/preview.html +35 -23
- package/design-system/components/Skeleton/style.css +22 -0
- package/design-system/components/Slider/README.md +95 -0
- package/design-system/components/Slider/preview.html +49 -0
- package/design-system/components/Slider/style.css +13 -0
- package/design-system/components/Split/README.md +112 -0
- package/design-system/components/Split/preview.html +105 -0
- package/design-system/components/Split/style.css +30 -0
- package/design-system/components/Starter/README.md +21 -9
- package/design-system/components/Starter/preview.html +5 -5
- package/design-system/components/Stat/README.md +64 -43
- package/design-system/components/Stat/preview.html +3 -3
- package/design-system/components/Stat/style.css +15 -0
- package/design-system/components/StatTiles/README.md +88 -44
- package/design-system/components/StatTiles/preview.html +142 -71
- package/design-system/components/StatTiles/style.css +64 -0
- package/design-system/components/StatusLine/README.md +85 -0
- package/design-system/components/StatusLine/preview.html +49 -0
- package/design-system/components/StatusLine/style.css +14 -0
- package/design-system/components/StatusScreen/README.md +49 -35
- package/design-system/components/StatusScreen/preview.html +20 -5
- package/design-system/components/StatusScreen/style.css +9 -0
- package/design-system/components/StepLine/README.md +97 -0
- package/design-system/components/StepLine/preview.html +108 -0
- package/design-system/components/StepLine/style.css +62 -0
- package/design-system/components/Stepper/README.md +35 -13
- package/design-system/components/Stepper/style.css +13 -0
- package/design-system/components/StreakCalendar/README.md +22 -1
- package/design-system/components/StreakCalendar/preview.html +10 -10
- package/design-system/components/StreakCalendar/style.css +12 -0
- package/design-system/components/SuggestionChip/README.md +87 -0
- package/design-system/components/SuggestionChip/preview.html +42 -0
- package/design-system/components/SuggestionChip/style.css +20 -0
- package/design-system/components/Switch/README.md +44 -24
- package/design-system/components/Switch/preview.html +23 -8
- package/design-system/components/Switch/style.css +31 -0
- package/design-system/components/Table/README.md +129 -58
- package/design-system/components/Table/preview.html +106 -72
- package/design-system/components/Table/style.css +104 -0
- package/design-system/components/Tabs/README.md +71 -46
- package/design-system/components/Tabs/preview.html +56 -24
- package/design-system/components/Tabs/style.css +44 -0
- package/design-system/components/Testimonial/README.md +48 -32
- package/design-system/components/Testimonial/preview.html +14 -14
- package/design-system/components/Testimonial/style.css +12 -0
- package/design-system/components/TextArea/README.md +27 -11
- package/design-system/components/TextArea/preview.html +8 -2
- package/design-system/components/TextArea/style.css +12 -0
- package/design-system/components/ThemeSwitch/README.md +116 -0
- package/design-system/components/ThemeSwitch/preview.html +57 -0
- package/design-system/components/ThemeSwitch/style.css +6 -0
- package/design-system/components/TickRow/README.md +52 -36
- package/design-system/components/TickRow/preview.html +64 -41
- package/design-system/components/TickRow/style.css +23 -0
- package/design-system/components/TickStrip/README.md +95 -0
- package/design-system/components/TickStrip/preview.html +88 -0
- package/design-system/components/TickStrip/style.css +42 -0
- package/design-system/components/Tile/README.md +79 -18
- package/design-system/components/Tile/preview.html +91 -40
- package/design-system/components/Tile/style.css +36 -0
- package/design-system/components/Timeline/README.md +68 -48
- package/design-system/components/Timeline/preview.html +27 -15
- package/design-system/components/Timeline/style.css +25 -0
- package/design-system/components/Toast/README.md +70 -38
- package/design-system/components/Toast/preview.html +43 -8
- package/design-system/components/Toast/style.css +25 -0
- package/design-system/components/Tooltip/README.md +93 -35
- package/design-system/components/Tooltip/preview.html +69 -15
- package/design-system/components/Tooltip/style.css +78 -0
- package/design-system/components/TopBar/README.md +67 -53
- package/design-system/components/TopBar/preview.html +14 -27
- package/design-system/components/TopBar/style.css +35 -0
- package/design-system/components/TopicRail/README.md +108 -0
- package/design-system/components/TopicRail/preview.html +58 -0
- package/design-system/components/TopicRail/style.css +48 -0
- package/design-system/components/TrendBars/README.md +52 -14
- package/design-system/components/TrendBars/preview.html +65 -32
- package/design-system/components/TrendBars/style.css +25 -0
- package/design-system/components/TrendLine/README.md +164 -0
- package/design-system/components/TrendLine/preview.html +373 -0
- package/design-system/components/TrendLine/style.css +155 -0
- package/design-system/components/Well/README.md +64 -22
- package/design-system/components/Well/preview.html +14 -14
- package/design-system/components/Well/style.css +22 -0
- package/design-system/components/_house.css +324 -0
- package/design-system/components/bundle.css +4091 -744
- package/design-system/tokens.json +1314 -124
- package/dist/fello-core.css +2 -2
- package/dist/fello.css +6 -1369
- package/dist/fello.scoped.css +7 -0
- package/dist/index.d.ts +110 -11
- package/dist/index.js +781 -96
- package/dist/palettes.d.ts +464 -0
- package/dist/palettes.js +497 -0
- package/dist/scoped.d.ts +3 -0
- package/dist/scoped.js +16 -0
- package/dist/toast.d.ts +14 -0
- package/dist/toast.js +33 -29
- package/dist/tokens.d.ts +1686 -62
- package/dist/tokens.js +1668 -61
- package/package.json +20 -4
- package/scripts/check-palette.mjs +295 -0
- package/scripts/token-model.mjs +552 -0
- package/tokens/_dark.css +174 -0
- package/tokens/_palettes.css +46 -0
- package/tokens/_product.css +266 -53
- package/tokens/_source.hubl.css +39 -19
- package/tokens/design-tokens.json +319 -0
- package/tokens/palettes.ts +504 -0
- package/tokens/shadcn-compat.css +19 -19
- package/tokens/shadcn-compat.hsl.css +76 -0
- package/tokens/tokens.css +1292 -62
- package/tokens/tokens.md +588 -61
- package/tokens/tokens.root.css +1298 -62
- package/tokens/tokens.ts +1677 -64
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* ---- MetricTile: North's key metric, a rimmed tile that opens across its row into its chart and a side column ---- */
|
|
2
|
+
/* The set: 16 apart; one across under 30rem, two from 30rem, four from 60rem (it measures itself). */
|
|
3
|
+
.ws-metric-tiles { container-type: inline-size; }
|
|
4
|
+
.ws-metric-tiles__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-md); }
|
|
5
|
+
@container (min-width: 30rem) { .ws-metric-tiles__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
6
|
+
@container (min-width: 60rem) { .ws-metric-tiles__grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
|
7
|
+
/* Closed: the tray rim (8 outside, 7 inside), a cream surface 16 inside with 12 between label, figure and bar, and a
|
|
8
|
+
foot on the tray. The whole tile is one button that opens it; hover and focus darken the rim (Seth's well hover). */
|
|
9
|
+
.ws-metric-tile { display: flex; min-width: 0; flex-direction: column; border: 0; border-radius: var(--ws-radius-card); background: var(--ws-tray); padding: var(--ws-rim); color: inherit; text-align: left; text-decoration: none; cursor: pointer; }
|
|
10
|
+
.ws-metric-tile:focus-visible, .ws-metric-tile--selected { background: var(--ws-well-hover); }
|
|
11
|
+
@media (hover: hover) { .ws-metric-tile:not(.ws-metric-tile--open):hover { background: var(--ws-well-hover); } }
|
|
12
|
+
.ws-metric-tile__surface { display: flex; flex: 1; min-width: 0; flex-direction: column; gap: var(--space-sm); padding: var(--space-md); border-radius: var(--ws-radius-card-inner); background: var(--color-bg-page); }
|
|
13
|
+
.ws-metric-tile__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-xs); min-width: 0; }
|
|
14
|
+
.ws-metric-tile__label { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--ws-text-control); font-weight: 500; line-height: var(--ws-leading-control); color: var(--color-text-body); }
|
|
15
|
+
/* The go arrow rests 4 px back and steps forward under the pointer or the focus. On touch, where nothing hovers, it
|
|
16
|
+
sits on the content edge. */
|
|
17
|
+
.ws-metric-tile__go { flex-shrink: 0; width: var(--ws-icon); height: var(--ws-icon); color: var(--color-text-heading); }
|
|
18
|
+
@media (hover: hover) {
|
|
19
|
+
.ws-metric-tile__go { transform: translateX(calc(var(--ws-nudge) * -1)); }
|
|
20
|
+
.ws-metric-tile:hover .ws-metric-tile__go, .ws-metric-tile:focus-visible .ws-metric-tile__go { transform: none; }
|
|
21
|
+
}
|
|
22
|
+
.ws-metric-tile__value { white-space: nowrap; font-size: var(--font-size-3xl); font-weight: 400; line-height: var(--space-3xl); letter-spacing: var(--ws-tracking-display); color: var(--color-text-heading); font-variant-numeric: tabular-nums; }
|
|
23
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
24
|
+
.ws-metric-tile { transition: background-color var(--ws-duration-hover) var(--ws-ease-soft); }
|
|
25
|
+
.ws-metric-tile__go { transition: transform var(--ws-duration-settle) var(--ws-ease-soft); }
|
|
26
|
+
.ws-metric-tile__preview { transition: filter var(--ws-duration-shrink) ease; }
|
|
27
|
+
}
|
|
28
|
+
/* A closed preview chart is greyed and wakes to its colours under the pointer or the focus (North). */
|
|
29
|
+
.ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: grayscale(1) contrast(0.55) brightness(1.32); }
|
|
30
|
+
.ws-metric-tile:focus-visible .ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: none; }
|
|
31
|
+
@media (hover: hover) { .ws-metric-tile:hover .ws-metric-tile__preview { display: flex; align-items: center; height: var(--space-lg); filter: none; } }
|
|
32
|
+
/* The mini charts (North's Home tiles), in the bar's place: a column strip (__cols, one __col a period, the period in
|
|
33
|
+
progress __col--now, lighter) or a line with dots (__line, an inline SVG: one <line> a step, one <circle> a point,
|
|
34
|
+
the last __now and larger). Each is 36 tall and reaches up into the 12 above it, so its baseline sits where a
|
|
35
|
+
PaceBar's 24 slot ends and the tile keeps its height. Columns 8 apart and stopping 12 short of the right edge, 2 px
|
|
36
|
+
top corners, cut every --step of the height by a fine line in the ground's colour (North's scale lines through the
|
|
37
|
+
bars); the baseline runs the full width. The line's points run from 4 in to 12 short, over a 0.5 px mid rule. Grey
|
|
38
|
+
at rest (the heading navy at 20% over the ground whatever the tone, the period in progress 11%, so every tile rests
|
|
39
|
+
the same grey); under the pointer or the focus they wake to the tone (the period in progress 55%) over 400 ms, as
|
|
40
|
+
a preview does. --tone on the chart, navy by default. A zero column is a 1 px stub on the baseline, never left out. */
|
|
41
|
+
.ws-metric-tile { --woke: 0; }
|
|
42
|
+
.ws-metric-tile:focus-visible { --woke: 1; }
|
|
43
|
+
@media (hover: hover) { .ws-metric-tile:hover { --woke: 1; } }
|
|
44
|
+
.ws-metric-tile__cols, .ws-metric-tile__line { --mark-tone: var(--tone, var(--color-text-heading)); --mark-hue: color-mix(in srgb, var(--mark-tone) calc(var(--woke, 1) * 100%), var(--color-text-heading)); --mark: color-mix(in srgb, var(--mark-hue) calc(20% + var(--woke, 1) * 80%), var(--color-bg-page)); position: relative; flex-shrink: 0; height: calc(var(--space-lg) + var(--space-sm)); margin-top: calc(var(--space-sm) * -1); }
|
|
45
|
+
.ws-metric-tile__cols { display: flex; align-items: flex-end; gap: var(--space-xs); padding-right: var(--space-sm); border-bottom: var(--ws-line) solid color-mix(in srgb, var(--ws-edge-card) calc(var(--woke, 1) * 100%), var(--ws-chart-foot)); }
|
|
46
|
+
.ws-metric-tile__cols::after { content: ""; position: absolute; inset: 0 var(--space-sm) 0 0; background: repeating-linear-gradient(to top, transparent 0 calc(var(--step) - var(--ws-hairline) * 1.5), var(--color-bg-page) 0 var(--step)); pointer-events: none; }
|
|
47
|
+
.ws-metric-tile__col { flex: 1; min-width: 0; height: max(var(--ws-line), var(--v, 0%)); border-radius: var(--ws-radius-chip) var(--ws-radius-chip) 0 0; background: var(--mark); transform-origin: bottom; }
|
|
48
|
+
.ws-metric-tile__col--now { background: color-mix(in srgb, var(--mark-hue) calc(11% + var(--woke, 1) * 44%), var(--color-bg-page)); }
|
|
49
|
+
.ws-metric-tile__line::before { content: ""; position: absolute; top: 50%; left: 0; right: 0; border-top: var(--ws-hairline) solid color-mix(in srgb, var(--ws-edge-slot) calc(var(--woke, 1) * 100%), var(--ws-chart-foot)); }
|
|
50
|
+
.ws-metric-tile__line > svg { position: absolute; top: 0; left: var(--space-xxs); width: calc(100% - var(--space-xxs) - var(--space-sm)); height: 100%; overflow: visible; }
|
|
51
|
+
.ws-metric-tile__line line { stroke: var(--mark); stroke-width: calc(var(--ws-hairline) * 2.5); stroke-linecap: round; }
|
|
52
|
+
.ws-metric-tile__line circle { r: calc(var(--ws-hairline) * 5); fill: var(--mark); stroke: var(--color-bg-page); stroke-width: calc(var(--ws-hairline) * 3); }
|
|
53
|
+
.ws-metric-tile__line .ws-metric-tile__now { r: calc(var(--ws-hairline) * 9); }
|
|
54
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
55
|
+
.ws-metric-tile__cols, .ws-metric-tile__col { transition: background-color var(--ws-duration-shrink) ease, border-color var(--ws-duration-shrink) ease; }
|
|
56
|
+
.ws-metric-tile__line::before { transition: border-color var(--ws-duration-shrink) ease; }
|
|
57
|
+
.ws-metric-tile__line :is(line, circle) { transition: stroke var(--ws-duration-shrink) ease, fill var(--ws-duration-shrink) ease; }
|
|
58
|
+
/* The draw (North's Home intro, "Metric + goal bars fill"): with --draw on the tile its chart draws over the card's
|
|
59
|
+
share of the page fill (--ws-duration-fill-page x 0.79: North's .21 card stagger leaves each card 0.79 of it).
|
|
60
|
+
Column i of --n starts at its share of the first half and rises from the baseline over the second half on the
|
|
61
|
+
settle curve. The line draws left to right at an even pace, step i (--i) after step i - 1 (each <line> wears
|
|
62
|
+
pathLength="1"), over the first three quarters; each dot pops as the line reaches it. Then the foot's figure rises
|
|
63
|
+
in. In a .ws-build card it all starts at the card's --ws-build-at. */
|
|
64
|
+
.ws-metric-tile--draw { --ws-tile-draw: calc(var(--ws-duration-fill-page) * 0.79); }
|
|
65
|
+
.ws-metric-tile--draw .ws-metric-tile__col { animation: ws-grow-up calc(var(--ws-tile-draw) * 0.5) var(--ws-ease-settle) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.5 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
|
|
66
|
+
.ws-metric-tile--draw .ws-metric-tile__line line { stroke-dasharray: 1 1; animation: ws-draw calc(var(--ws-tile-draw) * 0.75 / max(var(--n, 2) - 1, 1)) var(--ws-ease-steady) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.75 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
|
|
67
|
+
.ws-metric-tile--draw .ws-metric-tile__line circle { transform-box: fill-box; transform-origin: center; animation: ws-bloom var(--ws-duration-settle) var(--ws-ease-pop) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw) * 0.75 * var(--i, 0) / max(var(--n, 2) - 1, 1)); }
|
|
68
|
+
.ws-metric-tile--draw .ws-metric-tile__foot b { animation: ws-rise var(--ws-duration-settle) var(--ws-ease-soft) backwards; animation-delay: calc(var(--ws-build-at, 0s) + var(--ws-tile-draw)); }
|
|
69
|
+
}
|
|
70
|
+
/* The foot on the tray: 32 tall, 16 at the sides, 12/16 words; figures 600 and never cut, the words give way first. */
|
|
71
|
+
.ws-metric-tile__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm); min-height: var(--space-xl); padding: var(--space-xs) var(--space-md); font-size: var(--ws-text-dense); line-height: var(--space-md); color: var(--color-text-body); }
|
|
72
|
+
.ws-metric-tile__foot > span { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
73
|
+
.ws-metric-tile__foot b { flex-shrink: 0; font-weight: 600; color: var(--color-text-heading); }
|
|
74
|
+
/* Dense (North's KPI rail): 12 / 14 inside, 4 apart, a 28/32 figure. */
|
|
75
|
+
.ws-metric-tile--dense .ws-metric-tile__surface { gap: var(--space-xxs); padding: var(--space-sm) var(--space-sm-plus); }
|
|
76
|
+
.ws-metric-tile--dense .ws-metric-tile__label { font-size: var(--ws-text-dense); line-height: var(--space-md); }
|
|
77
|
+
.ws-metric-tile--dense .ws-metric-tile__value { font-size: var(--ws-text-figure-row); line-height: var(--ws-leading-figure-row); }
|
|
78
|
+
/* Selected in a row of tiles: the darker rim and a 2 px navy tab 8 under it, 16 in from each side. */
|
|
79
|
+
.ws-metric-tile--selected { position: relative; }
|
|
80
|
+
.ws-metric-tile--selected::after { content: ""; position: absolute; left: var(--space-md); right: var(--space-md); bottom: calc((var(--space-xs) + var(--ws-ring)) * -1); height: var(--ws-ring); border-radius: var(--ws-radius-chip); background: var(--color-text-heading); }
|
|
81
|
+
|
|
82
|
+
/* Open: the tile takes the whole row (12 outside, 11 inside), the others are gone; its surface is the chart (20 24 8 28)
|
|
83
|
+
beside a 420 side column behind a 0.5 px rule. Under 60rem the side column moves under the chart. */
|
|
84
|
+
.ws-metric-tiles__grid:has(> .ws-metric-tile--open) > :not(.ws-metric-tile--open) { display: none; }
|
|
85
|
+
.ws-metric-tile--open { grid-column: 1 / -1; min-height: calc(var(--space-9xl) + var(--space-8xl) + var(--space-md)); border-radius: var(--ws-radius-drawer); cursor: default; }
|
|
86
|
+
.ws-metric-tile--open > .ws-metric-tile__surface { display: grid; grid-template-columns: minmax(0, 1fr) calc(var(--space-9xl) * 2 + var(--space-md) + var(--space-xxs)); gap: 0; padding: 0; border-radius: var(--ws-radius-drawer-inner); }
|
|
87
|
+
.ws-metric-tile__main { display: flex; min-width: 0; flex-direction: column; gap: var(--space-xxs-plus); padding: var(--ws-card-inset) var(--space-lg) var(--space-xs) var(--ws-band-inset); }
|
|
88
|
+
.ws-metric-tile--open .ws-metric-tile__head { align-items: flex-start; }
|
|
89
|
+
/* The open label names the metric at 20 px; its dotted line says a description opens from it. */
|
|
90
|
+
.ws-metric-tile--open .ws-metric-tile__label { font-size: var(--font-size-lg2); line-height: var(--space-lg); text-decoration: underline dotted; text-decoration-thickness: calc(var(--ws-hairline) * 3); text-underline-offset: var(--space-xxs-plus); }
|
|
91
|
+
.ws-metric-tile--open .ws-metric-tile__value { font-size: calc(var(--space-3xl) + var(--space-xxs)); line-height: calc(var(--space-3xl) + var(--space-xs)); color: var(--tone-word, var(--color-text-heading)); }
|
|
92
|
+
.ws-metric-tile__chart { position: relative; flex: 1; min-height: var(--space-9xl); }
|
|
93
|
+
.ws-metric-tile__chart--segs { display: flex; flex: 0 1 auto; min-height: 0; flex-direction: column; gap: var(--space-lg); padding: var(--space-md) 0 var(--space-xs); }
|
|
94
|
+
/* A segment of the open chart (North's breakdown): name, figure and its share of pace over a PaceBar, 10 under. */
|
|
95
|
+
.ws-metric-tile__seg-line { display: flex; align-items: baseline; gap: var(--space-xs-plus); font-size: var(--ws-text-body); line-height: var(--ws-leading-row); color: var(--color-text-heading); }
|
|
96
|
+
.ws-metric-tile__seg-line > span { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 500; }
|
|
97
|
+
.ws-metric-tile__seg-line > b { font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
98
|
+
.ws-metric-tile__seg-line > em { width: calc(var(--space-2xl) + var(--space-xxs)); font-style: normal; font-size: var(--ws-text-control); text-align: right; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
|
|
99
|
+
.ws-metric-tile__seg .ws-pace-bar { margin-top: var(--space-xxs); }
|
|
100
|
+
.ws-metric-tile__key { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-xxs) var(--space-md); font-size: var(--ws-text-dense); font-weight: 500; line-height: var(--space-md); color: var(--color-text-body); }
|
|
101
|
+
.ws-metric-tile__key > span { display: inline-flex; align-items: center; gap: var(--space-xxs-plus); }
|
|
102
|
+
.ws-metric-tile__key i { width: var(--space-xs); height: var(--space-xs); border-radius: var(--ws-radius-chip); background: var(--tone); }
|
|
103
|
+
.ws-metric-tile__side { display: flex; min-width: 0; flex-direction: column; padding: var(--space-sm) 0 var(--space-md); border-left: var(--ws-divider); }
|
|
104
|
+
.ws-metric-tile__section { display: flex; flex-direction: column; gap: var(--space-xs); padding: var(--space-sm) var(--space-sm-plus) var(--space-sm) var(--space-lg); }
|
|
105
|
+
.ws-metric-tile__section + .ws-metric-tile__section { border-top: var(--ws-divider); }
|
|
106
|
+
.ws-metric-tile__section--grow { flex: 1; }
|
|
107
|
+
.ws-metric-tile__section-title { font-size: var(--ws-text-control); font-weight: 500; line-height: var(--ws-leading-control); color: var(--color-text-heading); }
|
|
108
|
+
.ws-metric-tile__preview > .ws-tick-strip { flex: 1; }
|
|
109
|
+
.ws-metric-tile__tools { display: flex; align-items: center; gap: var(--space-xxs); padding: 0 var(--space-sm-plus) 0 var(--space-lg); }
|
|
110
|
+
.ws-metric-tile__tools > span { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: var(--ws-text-dense); font-weight: 500; color: var(--color-text-muted); }
|
|
111
|
+
.ws-metric-tile__mover { display: grid; grid-template-columns: var(--space-xs) minmax(0, 1fr) auto; align-items: center; column-gap: var(--space-xs-plus); font-size: var(--ws-text-control); line-height: var(--ws-leading-control); }
|
|
112
|
+
.ws-metric-tile__mover > i { width: var(--space-xs); height: var(--space-xs); border-radius: var(--ws-radius-chip); background: var(--tone); }
|
|
113
|
+
.ws-metric-tile__mover > b { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-weight: 500; color: var(--color-text-heading); }
|
|
114
|
+
.ws-metric-tile__mover > em { font-style: normal; font-weight: 600; color: var(--color-text-heading); font-variant-numeric: tabular-nums; text-align: right; }
|
|
115
|
+
.ws-metric-tile__acts { display: flex; flex-wrap: wrap; gap: var(--space-xs); }
|
|
116
|
+
/* Seth's pane pair: the two acts share the row in equal halves, the primary last; each takes the row when the two
|
|
117
|
+
do not fit side by side. */
|
|
118
|
+
.ws-metric-tile__acts > * { flex: 1 1 0; min-width: max-content; }
|
|
119
|
+
.ws-metric-tile--open > .ws-metric-tile__foot { min-height: var(--space-2xl); padding-inline: var(--ws-band-inset); }
|
|
120
|
+
@container (max-width: 60rem) {
|
|
121
|
+
.ws-metric-tile--open > .ws-metric-tile__surface { grid-template-columns: minmax(0, 1fr); }
|
|
122
|
+
.ws-metric-tile__side { border-left: 0; border-top: var(--ws-divider); }
|
|
123
|
+
.ws-metric-tile__chart:not(.ws-metric-tile__chart--segs) { min-height: calc(var(--space-9xl) + var(--space-4xl)); }
|
|
124
|
+
}
|
|
125
|
+
@container (max-width: 30rem) {
|
|
126
|
+
.ws-metric-tile__main { padding: var(--space-md); }
|
|
127
|
+
.ws-metric-tile__section, .ws-metric-tile__tools { padding-inline: var(--space-md); }
|
|
128
|
+
.ws-metric-tile--open > .ws-metric-tile__foot { padding-inline: var(--space-md); }
|
|
129
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
A thing that changes its place or its size goes there as itself: a card flying to the full screen and back, a figure growing open across its row, the chat moving between its modes. The browser's own view transitions, started by `morph()` from the package, on Seth's curves.
|
|
2
|
+
|
|
3
|
+
## Use it when
|
|
4
|
+
|
|
5
|
+
- One element changes place or size because of a person's act and should be seen to go there: a chat card to its full
|
|
6
|
+
screen and back, a tile opening across its row and closing back into its slot, the chat between docked and full
|
|
7
|
+
screen.
|
|
8
|
+
- The change is a class or a state flip the app can make inside one function.
|
|
9
|
+
|
|
10
|
+
## Not for
|
|
11
|
+
|
|
12
|
+
- Something arriving or leaving: that is the component's own entrance and exit (`Menu`, `Dialog`, `Panel`, `Toast`).
|
|
13
|
+
- A height opening in place (a fold, a lane): that is the component's own height motion.
|
|
14
|
+
- A page change: a route's own transition is the app's, not the kit's.
|
|
15
|
+
|
|
16
|
+
## Anatomy
|
|
17
|
+
|
|
18
|
+
- `morph(update, { as, el, close })` from `@felloinnovations/brand-kit`: runs `update` as a same-document view
|
|
19
|
+
transition. It names `el` `ws-morph` for the move, copies its ground, corners and backdrop to the frame
|
|
20
|
+
(`--ws-morph-ground`, `--ws-morph-radius`, `--ws-morph-backdrop`) and marks the root `data-ws-morph="<as>"` while it
|
|
21
|
+
runs, so only the kit's own transitions take these curves. It resolves when the move has finished.
|
|
22
|
+
- The lift (`full` only): when `update` leaves `el` `position: fixed` (a chat card's `ws-chat-card--full`), morph()
|
|
23
|
+
puts it in the top layer as a manual popover (`popover="manual"`, `showPopover()`, marked `data-ws-lifted`), so the
|
|
24
|
+
full screen covers the window wherever the element sits. Without it, any ancestor with a transform, a filter or
|
|
25
|
+
backdrop-filter (AppShell's glass stage, the chat's glass), `contain` or `will-change` becomes the fixed element's
|
|
26
|
+
box, and the card fills only that ancestor, squashed. Lifted, focus moves into it (its `[autofocus]`, else its first
|
|
27
|
+
control, unless it already holds focus) and Escape runs `close` (default: `update` again, right when `update` is a
|
|
28
|
+
toggle); when an update leaves it unfixed it drops back into its slot and focus goes back where it was. The lift
|
|
29
|
+
happens with or without motion.
|
|
30
|
+
- The frame (`::view-transition-group(ws-morph)`): moves and resizes as one shape on the kind's curve, in the
|
|
31
|
+
element's ground with the hairline edge, and cuts what is inside to its corners.
|
|
32
|
+
- The words (`::view-transition-old` and `-new`): never scale (North css:1485). The old view rides in the frame's
|
|
33
|
+
corner at its own size until 55 % of the way, then fades off over .1 s as the new one fades in at its own size over
|
|
34
|
+
.32 s soft (`--ws-duration-settle`, `--ws-ease-soft`), so the frame never flies empty.
|
|
35
|
+
- The page behind cross-fades over .32 s soft (North's "Page behind fades").
|
|
36
|
+
|
|
37
|
+
## Variants and sizes
|
|
38
|
+
|
|
39
|
+
| `as` | Seth's name for it | Time and curve |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `full` (default) | "Full screen: open / close (card, type and chart together)" | `--ws-duration-morph` (.45 s) on `--ws-ease-morph` (.5, 0, .15, 1), 32 ms late, both ways |
|
|
42
|
+
| `grow` | "Card grows open" | `--ws-duration-grow` (.55 s) on `--ws-ease-grow` (.32, .72, 0, 1) |
|
|
43
|
+
| `shrink` | "Card closes back into place" | `--ws-duration-shrink` (.4 s) on `--ws-ease-grow`: faster than it opened |
|
|
44
|
+
| `shift` | "Chat moves between full screen, dock and pop-out" | `--ws-duration-shift` (.55 s) on `--ws-ease-shift` (.25, 1, .5, 1) |
|
|
45
|
+
|
|
46
|
+
## States
|
|
47
|
+
|
|
48
|
+
- **Moving**: the frame on its curve, the old words in its corner, then the new words fading in.
|
|
49
|
+
- **Interrupted**: a second morph before the first ends skips the first to its end and starts from there.
|
|
50
|
+
- **Reduced motion**: no transition; `update` runs at once (and `full` still lifts and drops the element).
|
|
51
|
+
- **No view transitions** (an older browser): `update` runs at once; nothing else changes.
|
|
52
|
+
- **Full**: lifted into the top layer above everything, Escape and its Back button close it, focus inside it.
|
|
53
|
+
|
|
54
|
+
## Behaviour
|
|
55
|
+
|
|
56
|
+
- `el` must still be in the page after `update` (the same node, its class changed), or the page names the new element
|
|
57
|
+
`view-transition-name: ws-morph` itself and leaves `el` out. `full` needs `el`: it is the element lifted.
|
|
58
|
+
- Full screen's way back lives inside the element (a Back button in its head that runs the same morph): the lifted
|
|
59
|
+
element sits above the whole page, so a control outside it is covered. Escape closes it too, unless a menu, popover or
|
|
60
|
+
dialog open inside it takes the Escape first.
|
|
61
|
+
- In React, flush the state inside it, and give Escape the way back:
|
|
62
|
+
`morph(() => flushSync(() => setFull(true)), { el: ref.current, close: () => flushSync(() => setFull(false)) })`; the
|
|
63
|
+
Back button runs `morph(() => flushSync(() => setFull(false)), { el: ref.current })`.
|
|
64
|
+
- The page does not take presses for the length of the move (the browser's own rule while a view transition runs).
|
|
65
|
+
- In the scoped build (`fello.scoped.css`) the root is not `.fello`, so the move takes the browser's default cross-fade
|
|
66
|
+
instead of Seth's curves.
|
|
67
|
+
|
|
68
|
+
## Rules
|
|
69
|
+
|
|
70
|
+
- One element in flight at a time, as itself; never a copy sliding in from elsewhere.
|
|
71
|
+
- Words never scale: they ride, fade off and fade back.
|
|
72
|
+
- Closing is never slower than opening (`shrink` .4 s after `grow` .55 s; `full` is .45 s both ways, as North).
|
|
73
|
+
|
|
74
|
+
## Source
|
|
75
|
+
|
|
76
|
+
**Derived from**: Seth's North DialKit config (app.js@824790: `open.expand` .55 grow, `open.collapse` .4 grow,
|
|
77
|
+
`chat.morph` .45 (.5, 0, .15, 1) after .032 s, `chat.shift` .55 (.25, 1, .5, 1)), the chat card's fly-out (css:1259 to
|
|
78
|
+
1505: the frame in flight, the parts under it fading out, snapping and fading back from 55 % of the way) and the chat's
|
|
79
|
+
mode moves (js@648700). North moves `top`, `left`, `width` and `height` every frame in script; the kit hands the move
|
|
80
|
+
to the browser's view transitions, so nothing runs per frame.
|
|
81
|
+
|
|
82
|
+
## Markup
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { morph } from '@felloinnovations/brand-kit';
|
|
86
|
+
|
|
87
|
+
// The toggle sits in the card's head ("Full screen", then "Back"); Escape runs the same toggle.
|
|
88
|
+
const go = card.querySelector('.ws-chat-card__head .ws-btn');
|
|
89
|
+
go.addEventListener('click', () =>
|
|
90
|
+
morph(() => {
|
|
91
|
+
const full = card.classList.toggle('ws-chat-card--full');
|
|
92
|
+
go.textContent = full ? 'Back' : 'Full screen';
|
|
93
|
+
}, { as: 'full', el: card }));
|
|
94
|
+
|
|
95
|
+
tile.addEventListener('click', () => {
|
|
96
|
+
const opening = tile.getAttribute('aria-expanded') !== 'true';
|
|
97
|
+
morph(() => tile.setAttribute('aria-expanded', String(opening)), { as: opening ? 'grow' : 'shrink', el: tile });
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Styles
|
|
102
|
+
|
|
103
|
+
`Morph/style.css`, built into `bundle.css` and `fello.css`: the `::view-transition-*` rules under
|
|
104
|
+
`:root[data-ws-morph]`, inside `(prefers-reduced-motion: no-preference)`, and the lifted element's reset
|
|
105
|
+
(`[data-ws-lifted]`: the browser's popover box gives way to the element's own). Tokens only. The helper is
|
|
106
|
+
`src/lib/motion.ts`.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
<!-- @dsCard group="Cards" height=980 -->
|
|
2
|
+
<!doctype html>
|
|
3
|
+
<html>
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>Morph</title>
|
|
7
|
+
<link rel="stylesheet" href="../../tokens.css">
|
|
8
|
+
<link rel="stylesheet" href="../bundle.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div class="ws-stage ws-stage--column">
|
|
12
|
+
|
|
13
|
+
<div class="ws-stack">
|
|
14
|
+
<span class="ws-muted">Live: press Full screen. The card flies to the full screen as itself on the morph curve (.45 s), its words fading off and back at their own size; Back carries it home the same way</span>
|
|
15
|
+
<div style="position: relative; contain: layout; height: 460px; padding: var(--space-lg); border-radius: var(--ws-radius-panel); background: var(--ws-frame);">
|
|
16
|
+
<div class="ws-card ws-chat-card" id="morph-card">
|
|
17
|
+
<div class="ws-chat-card__head">
|
|
18
|
+
<span class="ws-eyebrow">Chart</span>
|
|
19
|
+
<button type="button" class="ws-btn ws-btn--secondary" id="morph-card-go">Full screen</button>
|
|
20
|
+
</div>
|
|
21
|
+
<p class="ws-title">Calls this week, by agent</p>
|
|
22
|
+
<p class="ws-body">Sam is three behind the team; Dana is on pace.</p>
|
|
23
|
+
<ul class="ws-chat-card__rows" aria-label="Calls this week">
|
|
24
|
+
<li class="ws-chat-card__row"><span>Dana Okafor</span><b>12</b></li>
|
|
25
|
+
<li class="ws-chat-card__row"><span>Priya Nair</span><b>9</b></li>
|
|
26
|
+
<li class="ws-chat-card__row"><span>Sam Rivera</span><b>4</b></li>
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="ws-stack">
|
|
33
|
+
<span class="ws-muted">Live: press a figure. It grows open across its row on the grow curve (.55 s) while the others step back; pressed again it closes back into its place, faster (.4 s)</span>
|
|
34
|
+
<div id="morph-row" style="display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-sm);">
|
|
35
|
+
<button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Calls</span><span class="ws-section">42</span></button>
|
|
36
|
+
<button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Replies</span><span class="ws-section">17</span></button>
|
|
37
|
+
<button type="button" class="ws-card" aria-expanded="false" style="display: flex; flex-direction: column; align-items: flex-start; padding: var(--space-md); text-align: left;"><span class="ws-meta">Booked</span><span class="ws-section">5</span></button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<script>
|
|
42
|
+
(() => {
|
|
43
|
+
// A copy of morph() from @felloinnovations/brand-kit (src/lib/motion.ts): a preview cannot import the package.
|
|
44
|
+
// `full` lifts the element into the top layer while it is fixed, so no ancestor (a transform, a filter, the glass,
|
|
45
|
+
// contain) can trap it; Escape runs the update again (a toggle) and focus comes back.
|
|
46
|
+
const morph = (() => {
|
|
47
|
+
const lifted = new WeakMap(), root = document.documentElement;
|
|
48
|
+
const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]:not([tabindex="-1"])';
|
|
49
|
+
let last = 0;
|
|
50
|
+
const place = (el, close) => {
|
|
51
|
+
const was = lifted.get(el);
|
|
52
|
+
if (was) { lifted.delete(el); el.removeEventListener('keydown', was.onKey); if (el.matches(':popover-open')) el.hidePopover(); el.removeAttribute('popover'); delete el.dataset.wsLifted; }
|
|
53
|
+
if (getComputedStyle(el).position === 'fixed' && el.showPopover) {
|
|
54
|
+
const onKey = (e) => { if (e.key !== 'Escape' || e.defaultPrevented || el.querySelector(':popover-open, dialog[open]')) return; e.preventDefault(); morph(close, { as: 'full', el, close }); };
|
|
55
|
+
el.dataset.wsLifted = ''; el.popover = 'manual'; el.showPopover(); el.addEventListener('keydown', onKey);
|
|
56
|
+
lifted.set(el, { opener: was ? was.opener : document.activeElement, onKey });
|
|
57
|
+
if (!el.contains(document.activeElement)) { const to = el.querySelector('[autofocus]') || el.querySelector(FOCUSABLE) || el; if (to === el && !el.hasAttribute('tabindex')) el.tabIndex = -1; to.focus({ preventScroll: true }); }
|
|
58
|
+
} else if (was) { const o = was.opener, here = document.activeElement; if (o && o.isConnected && o !== here && (!here || here === document.body || el.contains(here))) o.focus({ preventScroll: true }); }
|
|
59
|
+
};
|
|
60
|
+
const morph = (update, { as = 'full', el, close } = {}) => {
|
|
61
|
+
const run = as === 'full' && el ? async () => { await update(); place(el, close || update); } : update;
|
|
62
|
+
if (!document.startViewTransition || matchMedia('(prefers-reduced-motion: reduce)').matches) return Promise.resolve(run()).then(() => {});
|
|
63
|
+
const id = ++last;
|
|
64
|
+
if (el) {
|
|
65
|
+
const s = getComputedStyle(el);
|
|
66
|
+
root.style.setProperty('--ws-morph-ground', s.backgroundColor);
|
|
67
|
+
root.style.setProperty('--ws-morph-radius', s.borderRadius);
|
|
68
|
+
root.style.setProperty('--ws-morph-backdrop', s.backdropFilter || 'none');
|
|
69
|
+
el.style.viewTransitionName = 'ws-morph';
|
|
70
|
+
}
|
|
71
|
+
root.dataset.wsMorph = as;
|
|
72
|
+
const move = document.startViewTransition(run);
|
|
73
|
+
move.ready.catch(() => {});
|
|
74
|
+
return move.finished.finally(() => {
|
|
75
|
+
if (el) el.style.viewTransitionName = '';
|
|
76
|
+
if (id !== last) return;
|
|
77
|
+
delete root.dataset.wsMorph;
|
|
78
|
+
for (const p of ['--ws-morph-ground', '--ws-morph-radius', '--ws-morph-backdrop']) root.style.removeProperty(p);
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
return morph;
|
|
82
|
+
})();
|
|
83
|
+
const card = document.getElementById('morph-card'), go = document.getElementById('morph-card-go');
|
|
84
|
+
go.addEventListener('click', () => morph(() => {
|
|
85
|
+
const full = card.classList.toggle('ws-chat-card--full');
|
|
86
|
+
go.textContent = full ? 'Back' : 'Full screen';
|
|
87
|
+
}, { as: 'full', el: card }));
|
|
88
|
+
const row = document.getElementById('morph-row');
|
|
89
|
+
for (const tile of row.children) tile.addEventListener('click', () => {
|
|
90
|
+
const open = tile.getAttribute('aria-expanded') !== 'true';
|
|
91
|
+
morph(() => {
|
|
92
|
+
tile.setAttribute('aria-expanded', String(open));
|
|
93
|
+
tile.style.gridColumn = open ? '1 / -1' : '';
|
|
94
|
+
tile.style.minHeight = open ? '160px' : '';
|
|
95
|
+
for (const other of row.children) if (other !== tile) other.style.display = open ? 'none' : '';
|
|
96
|
+
}, { as: open ? 'grow' : 'shrink', el: tile });
|
|
97
|
+
});
|
|
98
|
+
})();
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
</div>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* ---- Morph: a thing that changes its place or size goes there as itself (Seth's shared element in flight), on the
|
|
2
|
+
browser's own view transitions; morph() in the package starts one ---- */
|
|
3
|
+
/* morph(update, { as, el }) names the element ws-morph and marks the root with data-ws-morph (the kind) while it runs, so
|
|
4
|
+
only the kit's own transitions take these curves. The frame (the transition's group) moves and resizes as one shape
|
|
5
|
+
on the kind's curve, in the element's ground and corners (morph() copies them to --ws-morph-ground, --ws-morph-radius
|
|
6
|
+
and --ws-morph-backdrop) with the hairline edge. What is inside never scales (North css:1485): the old view rides in
|
|
7
|
+
the frame's corner at its own size, cut to the frame, until 55 % of the way, where it fades off over .1 s as the new
|
|
8
|
+
one, at its own size, fades in over .32 s soft (css:1489 to 1495), so the frame never flies empty. The page behind
|
|
9
|
+
fades over .32 s soft (North's "Page behind fades").
|
|
10
|
+
The kinds: full, .45 s on the morph curve, 32 ms late (North's "Full screen: open / close"); grow, .55 s on the grow
|
|
11
|
+
curve ("Card grows open"); shrink, .4 s on the grow curve ("Card closes back into place"); shift, .55 s on the shift
|
|
12
|
+
curve ("Chat moves between full screen, dock and pop-out"). A browser without view transitions, and reduced motion,
|
|
13
|
+
get the change at once: morph() then runs the update alone. */
|
|
14
|
+
/* An element morph() lifted into the top layer (`full`, data-ws-lifted) keeps its own look: the browser's popover
|
|
15
|
+
defaults (a fit-content box, auto margins, a medium border, padding, the Canvas colours) give way to anything the
|
|
16
|
+
element sets, and where it sets nothing it is a plain box in the page's words. */
|
|
17
|
+
:where([data-ws-lifted]) { width: auto; height: auto; margin: 0; padding: 0; border: 0; background: none; color: inherit; }
|
|
18
|
+
:root[data-ws-morph] { --ws-morph-for: var(--ws-duration-morph); --ws-morph-ease: var(--ws-ease-morph); --ws-morph-wait: 32ms; /* raw: North's morph waits .032 s */ --ws-morph-out: 100ms; /* raw: North's .1 s fade-out in flight */ }
|
|
19
|
+
:root[data-ws-morph="grow"] { --ws-morph-for: var(--ws-duration-grow); --ws-morph-ease: var(--ws-ease-grow); --ws-morph-wait: 0s; }
|
|
20
|
+
:root[data-ws-morph="shrink"] { --ws-morph-for: var(--ws-duration-shrink); --ws-morph-ease: var(--ws-ease-grow); --ws-morph-wait: 0s; }
|
|
21
|
+
:root[data-ws-morph="shift"] { --ws-morph-for: var(--ws-duration-shift); --ws-morph-ease: var(--ws-ease-shift); --ws-morph-wait: 0s; }
|
|
22
|
+
@keyframes ws-morph-out { to { opacity: 0; } }
|
|
23
|
+
@keyframes ws-morph-in { from { opacity: 0; } }
|
|
24
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
25
|
+
:root[data-ws-morph]::view-transition-group(ws-morph) { animation-duration: var(--ws-morph-for); animation-timing-function: var(--ws-morph-ease); animation-delay: var(--ws-morph-wait); overflow: clip; border-radius: var(--ws-morph-radius, var(--ws-radius-card)); background: var(--ws-morph-ground, var(--ws-raised)); -webkit-backdrop-filter: var(--ws-morph-backdrop, none); backdrop-filter: var(--ws-morph-backdrop, none); box-shadow: var(--ws-shadow-hairline); }
|
|
26
|
+
:root[data-ws-morph]::view-transition-image-pair(ws-morph) { isolation: auto; }
|
|
27
|
+
:root[data-ws-morph]::view-transition-old(ws-morph), :root[data-ws-morph]::view-transition-new(ws-morph) { inline-size: auto; block-size: auto; mix-blend-mode: normal; }
|
|
28
|
+
:root[data-ws-morph]::view-transition-old(ws-morph) { animation: ws-morph-out var(--ws-morph-out) linear calc(var(--ws-morph-wait) + var(--ws-morph-for) * 0.55) both; }
|
|
29
|
+
:root[data-ws-morph]::view-transition-new(ws-morph) { animation: ws-morph-in var(--ws-duration-settle) var(--ws-ease-soft) calc(var(--ws-morph-wait) + var(--ws-morph-for) * 0.55) both; }
|
|
30
|
+
:root[data-ws-morph]::view-transition-old(root), :root[data-ws-morph]::view-transition-new(root) { animation-duration: var(--ws-duration-settle); animation-timing-function: var(--ws-ease-soft); }
|
|
31
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Every move in Seth's North app on one live page: first a product screen to try them on, a numbered hint at each move and a list of what to notice; then, for developers, each behaviour by Seth's own label, North's values, a kit demo, the token or class to build with, and how close the kit is.
|
|
2
|
+
|
|
3
|
+
## Use it when
|
|
4
|
+
|
|
5
|
+
- You want to feel how the kit moves: try the screen at the top, number by number.
|
|
6
|
+
- You need to know how something should move: find North's behaviour by its name in the reference, play it, and copy
|
|
7
|
+
the token or class from its row.
|
|
8
|
+
- You want to see a spring's shape: play it, or turn on Slow everything down 4x.
|
|
9
|
+
- You are checking what the kit does not do yet before you build it in an app: those rows say why.
|
|
10
|
+
|
|
11
|
+
## Not for
|
|
12
|
+
|
|
13
|
+
- Building a page: this is a reference card, not a component. Build with the components each row names.
|
|
14
|
+
- A new spring or curve: add it to `tokens/design-tokens.json` (`motion.springs`) or `tokens/_product.css` and run
|
|
15
|
+
`node scripts/generate-tokens.mjs`; the springs come out as CSS for you.
|
|
16
|
+
|
|
17
|
+
## Anatomy
|
|
18
|
+
|
|
19
|
+
- The head: what the page is and how to use it.
|
|
20
|
+
- Try it, the playground: a list of what to try and the screen beside it (above it on a phone).
|
|
21
|
+
- The list: the Slow motion switch, then each hint's navy number (`Badge`, `ws-badge--navy`), what to do and one
|
|
22
|
+
plain line on what to notice; the reduced-motion note under it.
|
|
23
|
+
- The screen: a real product screen built only from kit components and their README markup, in a 780 px frame that
|
|
24
|
+
stands for the window. `AppShell` with the glass stage and its nav (a group that folds, a `<details>` with a `Fold`
|
|
25
|
+
chevron; the fold button; on a phone the same nav in its drawer), `PageBar` as the bar (Ask Coach, Save), `Tabs`,
|
|
26
|
+
the dashboard that builds in on arrival (a `.ws-build` page: four `MetricTile--draw`s with their mini charts, three
|
|
27
|
+
column strips and a line with dots, beside a `Split` aside of `SideList--draw` goal rows, on track, over and
|
|
28
|
+
behind; Replay the arrival runs it again), a chart card (`ChatCard` with a `Segmented` Bars / Line and Full
|
|
29
|
+
screen around a `TrendLine` that morphs between columns and a line, its weeks pointed at through `HoverCard`, a
|
|
30
|
+
build card that lands after the goals), a `Heatmap`, a card that loads (`Skeleton`, then each line settling in), a list (`SideList`
|
|
31
|
+
picks) sorted from a `Menu`, the lead `Dialog` drawer, `ChatShell` in its bar, docked and full modes over the
|
|
32
|
+
`ws-chat-veil`, and a `Toast`.
|
|
33
|
+
- The hints: the number and a few words above the part they are about; on the bar and in the nav, where there is no
|
|
34
|
+
room, the number alone on the control's corner (`ws-badge-host`), its words in the list.
|
|
35
|
+
- Reference for developers: the whole table below, folded away by default (a `<details>`, its summary the one line
|
|
36
|
+
that opens it). Its head: what a row is, the four counts and the reduced-motion note.
|
|
37
|
+
- A group per section of North's motion notes (north-motion.md 1 to 14, then 17 for the states the others leave out):
|
|
38
|
+
Durations, Curves, Springs, Hover, Press, Focus, Open and close, Panels docking, Loading, Build-ins, Tooltips,
|
|
39
|
+
Stagger, Reduced motion, What never moves, Interaction states.
|
|
40
|
+
- A row (`article.ws-card` > `.ws-head`), one behaviour: the status chip and Seth's label; North's values (duration,
|
|
41
|
+
curve or spring, with the file reference); the kit's token, class or helper; and the live demo. A demo that needs
|
|
42
|
+
room (a heatmap, the chat, a panel, a chart) spans the board's width. A row whose behaviour is played elsewhere on
|
|
43
|
+
the page links to that row.
|
|
44
|
+
- In the playground, every move is the kit's own: CSS (hover, press, fold, the sliding pill and underline, menus,
|
|
45
|
+
the drawer, the toast, the heatmap square, the shimmer, the build-in on arrival, the bars to line morph), `morph()`
|
|
46
|
+
(the chart card to full screen and back, the chat
|
|
47
|
+
between its modes) and `reorder()` (the sorted list). Full screen lifts the card into the top layer, so it covers
|
|
48
|
+
the whole window even from inside the frame; Back or Escape brings it home.
|
|
49
|
+
- Demos are the kit's own components and house classes. The curve demo is a `PaceBar` track whose dot crosses on the
|
|
50
|
+
row's duration and curve: Play sends it to the other end. One-shots (a draw, a fill, a build-in, a stagger) have
|
|
51
|
+
Replay.
|
|
52
|
+
|
|
53
|
+
## Variants and sizes
|
|
54
|
+
|
|
55
|
+
| Status | Chip | Meaning |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Exact | `ws-chip--live` | the kit moves as North does: Seth's value, Seth's curve or spring |
|
|
58
|
+
| Close | `ws-chip--middle` | the kit moves the same way with a difference the row names (a duration 20 to 50 ms off, a timed curve where North has a spring, a step left out) |
|
|
59
|
+
| JS helper | `ws-chip--accent` | CSS cannot start it alone; a small framework-free helper from the package does (`morph()`, `reorder()`, `rollPlaceholder()`) and the CSS moves it |
|
|
60
|
+
| Not yet | `ws-chip` | not in the kit; the row says why (no component for it, an open question for Seth, or a rule the kit keeps instead) |
|
|
61
|
+
|
|
62
|
+
Coverage, 206 behaviours (the page counts them in its head):
|
|
63
|
+
|
|
64
|
+
| Group | Rows | Exact | Close | JS helper | Not yet |
|
|
65
|
+
| --- | --- | --- | --- | --- | --- |
|
|
66
|
+
| 1. Durations | 16 | 11 | 5 | 0 | 0 |
|
|
67
|
+
| 2. Curves | 19 | 13 | 2 | 0 | 4 |
|
|
68
|
+
| 3. Springs | 27 | 26 | 0 | 0 | 1 |
|
|
69
|
+
| 4. Hover | 26 | 7 | 14 | 0 | 5 |
|
|
70
|
+
| 5. Press | 8 | 3 | 1 | 0 | 4 |
|
|
71
|
+
| 6. Focus | 7 | 4 | 2 | 0 | 1 |
|
|
72
|
+
| 7. Open and close | 23 | 10 | 5 | 2 | 6 |
|
|
73
|
+
| 8. Panels docking | 12 | 6 | 1 | 1 | 4 |
|
|
74
|
+
| 9. Loading, working and loops | 11 | 5 | 2 | 0 | 4 |
|
|
75
|
+
| 10. Number and chart build-ins | 13 | 5 | 2 | 0 | 6 |
|
|
76
|
+
| 11. Tooltips and hover cards | 6 | 2 | 4 | 0 | 0 |
|
|
77
|
+
| 12. Stagger | 11 | 6 | 2 | 1 | 2 |
|
|
78
|
+
| 13. Reduced motion | 4 | 4 | 0 | 0 | 0 |
|
|
79
|
+
| 14. What never moves | 10 | 6 | 3 | 0 | 1 |
|
|
80
|
+
| 17. Interaction states | 13 | 7 | 2 | 1 | 3 |
|
|
81
|
+
| All | 206 | 115 | 45 | 5 | 41 |
|
|
82
|
+
|
|
83
|
+
## States
|
|
84
|
+
|
|
85
|
+
- **Rest**: every demo at rest, exactly as its component draws it.
|
|
86
|
+
- **Playing**: hover, press, Tab, click, Play or Replay.
|
|
87
|
+
- **Slowed** (Slow motion, in Try it; this page only): every `--ws-*` duration, delay, stagger and loop token on `<html>` becomes
|
|
88
|
+
`calc(<its value> * var(--ws-motion-slow))` with `--ws-motion-slow: 4`, and the few raws a component keeps for itself
|
|
89
|
+
(the chat and panel exits and blur, the tooltip fade, the dialog's wait) are stretched on their own elements, so CSS
|
|
90
|
+
springs, curves, view transitions and the helpers all run four times slower together. Off, the inline values go.
|
|
91
|
+
Not a kit feature: an app never slows its motion.
|
|
92
|
+
- **Reduced motion**: everything is still (D7): the house net makes every kit transition and animation instant, the
|
|
93
|
+
helpers skip to the end state, the placeholder holds, shimmers stop on their ghost. The page says which the browser
|
|
94
|
+
asks for. The slow switch then has nothing to slow.
|
|
95
|
+
|
|
96
|
+
## Behaviour
|
|
97
|
+
|
|
98
|
+
- Native CSS first: springs are `linear()` curves (`--ws-spring-<name>` over `--ws-spring-<name>-duration`, sampled
|
|
99
|
+
from the motion library's own solver, overshoot kept); sliding indicators ride CSS anchor positioning; heights grow
|
|
100
|
+
with `interpolate-size` and `::details-content`; enters and exits use `@starting-style` and
|
|
101
|
+
`transition-behavior: allow-discrete` where the kit opens the layer itself (Menu and Popover as a native popover, Dialog
|
|
102
|
+
as a native dialog); Toast, HoverCard, Panel and ChatShell play keyframes, and the app sets `data-leaving` and keeps the
|
|
103
|
+
node until the exit has played; moves of place and size use same-document view transitions.
|
|
104
|
+
- A browser without one of these shows the change at once: no broken layout, nothing half-moved.
|
|
105
|
+
- The playground's arrival (Motion's Build-in, a kit default): on first paint the four tiles rise 70 ms apart
|
|
106
|
+
(`--ws-stagger-part`), each tile's columns grow in one after another and its line draws step by step with its dots
|
|
107
|
+
popping, over 0.79 of `--ws-duration-fill-page`, then its foot figure rises in; the goals card rises fifth and its
|
|
108
|
+
bars fill in order; the chart card rises sixth and its columns grow in over `--ws-duration-draw`. Replay the arrival
|
|
109
|
+
runs fresh copies. Bars / Line: the columns melt into the line on `--ws-spring-morph-chart` while the line draws over
|
|
110
|
+
`--ws-duration-draw`; back, they grow down from the dots. Reduced motion: all of it final at once.
|
|
111
|
+
- Motion changes how things move, never where they rest: a resting demo is its component's resting look, pixel for
|
|
112
|
+
pixel.
|
|
113
|
+
- Only transform, opacity, clip and size move where the platform allows; nothing runs a script per frame (the helpers
|
|
114
|
+
hand the move to the browser).
|
|
115
|
+
|
|
116
|
+
## Rules
|
|
117
|
+
|
|
118
|
+
- Build with the token or class in the row, never North's raw numbers.
|
|
119
|
+
- Springs are for what the person drives (a press, a pointer, an indicator, a panel); timed curves are for what the
|
|
120
|
+
system plays (a draw, a build-in, a loop).
|
|
121
|
+
- Exits are shorter than entrances.
|
|
122
|
+
- Overshoot only on marks under 24 px and indicators, never a card, a panel, a field or words.
|
|
123
|
+
- Reduced motion is still: write motion inside `(prefers-reduced-motion: no-preference)`; the house net catches the
|
|
124
|
+
rest.
|
|
125
|
+
|
|
126
|
+
## Source
|
|
127
|
+
|
|
128
|
+
**Derived from**: Seth's North artifact (style1.css and app.js: the DialKit "Motion" config `nn`, app.js@824790 to
|
|
129
|
+
@833500, the intro timeline `eb`, and the springs and tweens in the bundle), as inventoried in the kit-upgrade notes
|
|
130
|
+
(north-motion.md, sections 1 to 19), and the kit's decisions D7 (motion), D8 (focus) and D15 (palettes).
|
|
131
|
+
|
|
132
|
+
## Markup
|
|
133
|
+
|
|
134
|
+
A row:
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<article class="ws-card" aria-label="Toggle pill slide (bars / line, lanes)"><div class="ws-head">
|
|
138
|
+
<div class="ws-stack ws-stack--tight">
|
|
139
|
+
<span class="ws-cluster"><span class="ws-chip ws-chip--live">Exact</span><span class="ws-row-name">Toggle pill slide (bars / line, lanes)</span></span>
|
|
140
|
+
<span class="ws-meta">North: select.pill: spring visualDuration / bounce .38 / .16</span>
|
|
141
|
+
<span class="ws-meta">Kit: --ws-spring-indicator over --ws-spring-indicator-duration (700ms); Segmented</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div class="ws-cluster"><!-- the live demo --></div>
|
|
144
|
+
</div></article>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A spring in CSS, a helper in script:
|
|
148
|
+
|
|
149
|
+
```css
|
|
150
|
+
.thing { transition: transform var(--ws-spring-press-duration) var(--ws-spring-press); }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { morph, reorder, rollPlaceholder } from '@felloinnovations/brand-kit';
|
|
155
|
+
morph(() => card.classList.toggle('ws-chat-card--full'), { as: 'full', el: card });
|
|
156
|
+
reorder(() => list.append(...sorted), list);
|
|
157
|
+
const stop = rollPlaceholder(rollElement, 3400);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Styles
|
|
161
|
+
|
|
162
|
+
None of its own: the page is the house classes and the components' own `style.css` (a few inline styles place the
|
|
163
|
+
playground's frame, the hints and the chat bar). Its script only flips states the way an app does and carries copies of
|
|
164
|
+
`morph()` and `reorder()` (a preview cannot import the package).
|