@syncedco/flow 0.3.1 → 0.4.1
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/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +18 -5
- package/README.md +32 -9
- package/base.css +2 -1
- package/bin/synced-flow.mjs +446 -58
- package/components.css +440 -9
- package/dist/index.d.ts +69 -0
- package/dist/index.js +69 -0
- package/docs/accessibility-css.md +14 -3
- package/docs/ai-usage.md +22 -1
- package/docs/api-contract.md +10 -2
- package/docs/audit-2026-06.md +13 -12
- package/docs/base-styling.md +5 -0
- package/docs/cli-reference.md +9 -6
- package/docs/config-reference.md +2 -2
- package/docs/css-optimisation.md +10 -10
- package/docs/native-components.md +121 -0
- package/docs/patterns.md +18 -0
- package/docs/quick-start.md +6 -4
- package/docs/recipes.md +12 -0
- package/docs/release-readiness.md +32 -7
- package/docs/system-primitives.md +10 -3
- package/docs/tokens.md +10 -2
- package/examples/README.md +2 -2
- package/examples/astro/src/styles/synced-flow.generated.css +14 -0
- package/examples/next/app/synced-flow.generated.css +19 -5
- package/examples/plain-html/synced-flow.generated.css +19 -5
- package/examples/templates/README.md +2 -0
- package/examples/templates/app-settings-workflow.html +81 -0
- package/examples/vite/src/synced-flow.generated.css +19 -5
- package/examples/wordpress/assets/css/synced-flow.css +542 -9
- package/package.json +14 -3
- package/reset.css +1 -1
- package/skills/synced-flow/SKILL.md +20 -3
- package/src/index.ts +69 -0
- package/src/presets.mjs +5 -5
- package/src/tokens.mjs +10 -8
- package/styles.css +456 -21
- package/tokens.css +13 -10
package/dist/index.js
CHANGED
|
@@ -42,10 +42,79 @@ export const fluidSystem = {
|
|
|
42
42
|
},
|
|
43
43
|
components: {
|
|
44
44
|
button: 'sf-button',
|
|
45
|
+
buttonGroup: 'sf-button-group',
|
|
46
|
+
icon: 'sf-icon',
|
|
47
|
+
iconButton: 'sf-icon-button',
|
|
45
48
|
card: 'sf-card',
|
|
49
|
+
surface: 'sf-surface',
|
|
50
|
+
hero: 'sf-hero',
|
|
51
|
+
logoCloud: 'sf-logo-cloud',
|
|
52
|
+
feature: 'sf-feature',
|
|
53
|
+
stats: 'sf-stats',
|
|
54
|
+
testimonial: 'sf-testimonial',
|
|
55
|
+
pricingGrid: 'sf-pricing-grid',
|
|
56
|
+
faq: 'sf-faq',
|
|
57
|
+
callToAction: 'sf-cta',
|
|
58
|
+
footer: 'sf-footer',
|
|
46
59
|
badge: 'sf-badge',
|
|
60
|
+
avatar: 'sf-avatar',
|
|
61
|
+
alert: 'sf-alert',
|
|
62
|
+
status: 'sf-status',
|
|
63
|
+
sectionHeader: 'sf-section-header',
|
|
64
|
+
kicker: 'sf-kicker',
|
|
65
|
+
nav: 'sf-nav',
|
|
66
|
+
form: 'sf-form',
|
|
67
|
+
fieldset: 'sf-fieldset',
|
|
47
68
|
field: 'sf-field',
|
|
69
|
+
label: 'sf-label',
|
|
70
|
+
help: 'sf-help',
|
|
71
|
+
error: 'sf-error',
|
|
48
72
|
input: 'sf-input',
|
|
73
|
+
inputGroup: 'sf-input-group',
|
|
74
|
+
select: 'sf-select',
|
|
75
|
+
textarea: 'sf-textarea',
|
|
76
|
+
check: 'sf-check',
|
|
77
|
+
switch: 'sf-switch',
|
|
78
|
+
fileInput: 'sf-file-input',
|
|
79
|
+
range: 'sf-range',
|
|
80
|
+
segmentedControl: 'sf-segmented-control',
|
|
81
|
+
spinner: 'sf-spinner',
|
|
82
|
+
search: 'sf-search',
|
|
83
|
+
combobox: 'sf-combobox',
|
|
84
|
+
filterBar: 'sf-filter-bar',
|
|
85
|
+
bulkActions: 'sf-bulk-actions',
|
|
86
|
+
dataTable: 'sf-data-table',
|
|
87
|
+
tableSort: 'sf-table-sort',
|
|
88
|
+
dataList: 'sf-data-list',
|
|
89
|
+
emptyState: 'sf-empty-state',
|
|
90
|
+
settingsSection: 'sf-settings-section',
|
|
91
|
+
detailPanel: 'sf-detail-panel',
|
|
92
|
+
dialog: 'sf-dialog',
|
|
93
|
+
popover: 'sf-popover',
|
|
94
|
+
drawer: 'sf-drawer',
|
|
95
|
+
tooltip: 'sf-tooltip',
|
|
96
|
+
toast: 'sf-toast',
|
|
97
|
+
toastStack: 'sf-toast-stack',
|
|
98
|
+
banner: 'sf-banner',
|
|
99
|
+
disclosure: 'sf-disclosure',
|
|
100
|
+
accordion: 'sf-accordion',
|
|
101
|
+
tabs: 'sf-tabs',
|
|
102
|
+
tab: 'sf-tab',
|
|
103
|
+
menu: 'sf-menu',
|
|
104
|
+
breadcrumb: 'sf-breadcrumb',
|
|
105
|
+
pagination: 'sf-pagination',
|
|
106
|
+
stepper: 'sf-stepper',
|
|
107
|
+
wizardActions: 'sf-wizard-actions',
|
|
108
|
+
progress: 'sf-progress',
|
|
109
|
+
skeleton: 'sf-skeleton',
|
|
110
|
+
chart: 'sf-chart',
|
|
111
|
+
meter: 'sf-meter',
|
|
112
|
+
codeWindow: 'sf-code-window',
|
|
113
|
+
codeLines: 'sf-code-lines',
|
|
114
|
+
tokenStrip: 'sf-token-strip',
|
|
115
|
+
marquee: 'sf-marquee',
|
|
116
|
+
commandList: 'sf-command-list',
|
|
117
|
+
platformCard: 'sf-platform-card',
|
|
49
118
|
},
|
|
50
119
|
utilities: {
|
|
51
120
|
visuallyHidden: 'sf-visually-hidden',
|
|
@@ -27,6 +27,8 @@ semantic HTML and ARIA states are visible, consistent, and easy to compose.
|
|
|
27
27
|
| `[aria-disabled="true"]`, `:disabled` | Disabled affordance for buttons, links, cards, and form controls. |
|
|
28
28
|
| `[aria-busy="true"]`, `[data-loading="true"]` | Busy/loading affordance for buttons and form controls. |
|
|
29
29
|
| `.sf-field[data-invalid="true"]`, `[aria-invalid="true"]` | Invalid field styling. |
|
|
30
|
+
| `.sf-switch input:checked`, `.sf-segmented-control input:checked` | Native checkbox/radio state shown without replacing the input semantics. |
|
|
31
|
+
| `.sf-spinner` | Presentational loading indicator with a reduced-motion fallback; text status remains required. |
|
|
30
32
|
| `.sf-required`, `.sf-label[aria-required="true"]`, `label:has(+ :required)` | Required-field marker styling. |
|
|
31
33
|
| `@media (forced-colors: active)` | High contrast mode border, focus, and button fallbacks. |
|
|
32
34
|
|
|
@@ -111,13 +113,22 @@ messages that need immediate announcement.
|
|
|
111
113
|
When an action is in progress, keep the accessible name visible and add state:
|
|
112
114
|
|
|
113
115
|
```html
|
|
114
|
-
<button class="sf-button" type="button" aria-busy="true">
|
|
115
|
-
|
|
116
|
+
<button class="sf-button" type="button" aria-busy="true" aria-disabled="true">
|
|
117
|
+
<span class="sf-spinner" aria-hidden="true"></span>
|
|
118
|
+
<span>Saving…</span>
|
|
116
119
|
</button>
|
|
120
|
+
<p class="sr-only" role="status" aria-live="polite">Saving changes</p>
|
|
117
121
|
```
|
|
118
122
|
|
|
119
123
|
Synced Flow styles this as a busy state, but the consuming app should still
|
|
120
|
-
manage focus, state changes, and completion messaging.
|
|
124
|
+
manage focus, state changes, and completion messaging. `aria-disabled` keeps
|
|
125
|
+
focus stable but does not block activation, so the app must guard its handler.
|
|
126
|
+
Native `disabled` is also valid when removing the action from focus order is
|
|
127
|
+
preferable.
|
|
128
|
+
|
|
129
|
+
For a searchable select, the `sf-combobox` classes only style the shell. The
|
|
130
|
+
app must implement the full combobox keyboard and ARIA contract. A static
|
|
131
|
+
`role="combobox"` without synchronized state is not production-ready.
|
|
121
132
|
|
|
122
133
|
## Confidence Checklist
|
|
123
134
|
|
package/docs/ai-usage.md
CHANGED
|
@@ -53,7 +53,9 @@ before finalising the theme.
|
|
|
53
53
|
`sf-split`, and `sf-sidebar` before writing custom layout CSS.
|
|
54
54
|
- Use semantic colours: `bg-background`, `text-foreground`, `bg-primary`,
|
|
55
55
|
`text-primary-foreground`, `border-border`, `bg-surface`.
|
|
56
|
-
- Use `sf-button`, `sf-card`, `sf-badge`, `sf-field`,
|
|
56
|
+
- Use `sf-button`, `sf-card`, `sf-badge`, `sf-field`, `sf-input`, `sf-switch`,
|
|
57
|
+
`sf-input-group`, `sf-file-input`, `sf-range`, and `sf-segmented-control` for
|
|
58
|
+
common UI.
|
|
57
59
|
- Keep browser affordances unless the UI intentionally replaces them: body
|
|
58
60
|
links stay underlined, content lists keep markers, and focus states remain
|
|
59
61
|
visible.
|
|
@@ -78,6 +80,9 @@ before finalising the theme.
|
|
|
78
80
|
- Choose `saas-landing` for public SaaS marketing pages. Choose
|
|
79
81
|
`saas-dashboard` for authenticated app UI, admin panels, portals, CRMs,
|
|
80
82
|
analytics dashboards, metrics, tables, account menus, and login state.
|
|
83
|
+
- Choose `app-settings-workflow` for richer forms. Use the
|
|
84
|
+
`searchable-select`, `bulk-actions-toolbar`, `complete-pagination`, and
|
|
85
|
+
`multi-step-form` patterns for app-owned interactive behavior.
|
|
81
86
|
- Treat auth recipe markup as UI only; sessions, providers, permissions, and
|
|
82
87
|
sign-out logic belong to the consuming app.
|
|
83
88
|
- Keep class names complete in source files. Do not build classes from fragments.
|
|
@@ -110,3 +115,19 @@ pnpm flow:doctor
|
|
|
110
115
|
```
|
|
111
116
|
|
|
112
117
|
If `doctor` warns about stale CSS, run `pnpm flow:build`.
|
|
118
|
+
|
|
119
|
+
## Machine-Readable Integration
|
|
120
|
+
|
|
121
|
+
Synced Flow does not ship an MCP server. AI and external tool integrations
|
|
122
|
+
should use the local CLI as the canonical discovery surface:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pnpm exec synced-flow tokens --json
|
|
126
|
+
pnpm exec synced-flow catalog --json
|
|
127
|
+
pnpm exec synced-flow suggest "searchable select settings" --json
|
|
128
|
+
pnpm exec synced-flow pattern searchable-select --json
|
|
129
|
+
pnpm exec synced-flow recipe app-settings-workflow --json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This keeps component names, app-owned behavior notes, accessibility guidance,
|
|
133
|
+
and copy-ready markup in one versioned contract.
|
package/docs/api-contract.md
CHANGED
|
@@ -14,10 +14,12 @@ These are intended for application code and examples.
|
|
|
14
14
|
| Tokens | Unlayered `--sf-*` custom properties emitted by `tokens.css` |
|
|
15
15
|
| Theme config | `theme.fonts`, `theme.colours`, `theme.darkColours`, `theme.radii`, `theme.layout`, `theme.components` |
|
|
16
16
|
| Layout classes | `sf-container`, `sf-section`, `sf-stack`, `sf-flow`, `sf-cluster`, `sf-repel`, `sf-toolbar`, `sf-app-shell`, `sf-app-header`, `sf-app-sidebar`, `sf-app-backdrop`, `sf-app-main`, `sf-auto-grid`, `sf-switcher`, `sf-sidebar`, `sf-split`, `sf-frame`, `sf-cover`, `sf-metric-grid`, `sf-pipeline` |
|
|
17
|
-
| Components | `sf-button`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-search`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
|
|
18
|
-
| Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-tab__count`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-toast-stack` |
|
|
17
|
+
| Components | `sf-button`, `sf-spinner`, `sf-icon`, `sf-icon-button`, `sf-avatar`, `sf-chart`, `sf-meter`, `sf-progress`, `sf-skeleton`, `sf-filter-bar`, `sf-search`, `sf-input-group`, `sf-file-input`, `sf-range`, `sf-switch`, `sf-segmented-control`, `sf-combobox`, `sf-bulk-actions`, `sf-stepper`, `sf-wizard-actions`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel`, `sf-card`, `sf-surface`, `sf-hero`, `sf-nav`, `sf-form`, `sf-field`, `sf-input`, `sf-select`, `sf-textarea`, `sf-check`, `sf-alert`, `sf-badge`, `sf-section-header`, `sf-kicker` |
|
|
18
|
+
| Native components | `sf-dialog`, `sf-popover`, `sf-tooltip`, `sf-drawer`, `sf-drawer--stack`, `sf-disclosure`, `sf-accordion`, `sf-tabs`, `sf-tab__count`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-pagination__*`, `sf-toast-stack` |
|
|
19
19
|
| Website patterns | `sf-logo-cloud`, `sf-feature`, `sf-stats`, `sf-testimonial`, `sf-pricing-grid`, `sf-price-card`, `sf-faq`, `sf-cta`, `sf-footer` |
|
|
20
|
+
| Technical presentation | `sf-code-window`, `sf-code-lines`, `sf-code-block`, `sf-code-token--*`, `sf-token-strip`, `sf-marquee`, `sf-command-list`, `sf-platform-card` |
|
|
20
21
|
| Utilities | `sf-prose`, `sf-link`, `sf-link-subtle`, `sf-link-plain`, `sf-list-*`, `sf-push-*`, `sf-focus-ring`, `sf-touch-target`, `sf-skip-link`, `sr-only`, `not-sr-only` |
|
|
22
|
+
| TypeScript runtime | `cx()` and the typed `fluidSystem.layout`, `fluidSystem.components`, and `fluidSystem.utilities` class shortcuts exported from `@syncedco/flow` |
|
|
21
23
|
| CLI | `synced-flow init`, `agents install`, `agents status`, `skill`, `add defaults`, `build`, `watch`, `lint`, `doctor`, `tokens`, `catalog`, `suggest`, `pattern`, `recipe`, `theme init`, `theme validate` |
|
|
22
24
|
|
|
23
25
|
## AI Agent Contract
|
|
@@ -35,6 +37,12 @@ The project-level AI setup commands are public in 0.x:
|
|
|
35
37
|
`catalog --json` includes `patterns[]` with copy-ready interaction metadata:
|
|
36
38
|
classes, markup, JS requirement notes, accessibility notes, and gotchas.
|
|
37
39
|
|
|
40
|
+
The package does not own an MCP server. Its supported machine-readable
|
|
41
|
+
integration is the local CLI: `tokens --json`, `catalog --json`, `suggest
|
|
42
|
+
--json`, and `pattern`/`recipe` JSON or markup output. External MCP tools may
|
|
43
|
+
invoke those commands, but they should treat the CLI output as the package
|
|
44
|
+
contract rather than maintaining a separate component inventory.
|
|
45
|
+
|
|
38
46
|
## Internal Or Compatibility Surface
|
|
39
47
|
|
|
40
48
|
These can change more freely.
|
package/docs/audit-2026-06.md
CHANGED
|
@@ -68,13 +68,14 @@ than implying runtime AI integration.
|
|
|
68
68
|
|
|
69
69
|
## App-tier coverage gaps (from real CRM adoption)
|
|
70
70
|
|
|
71
|
-
Shipped and adopted: app-shell, data-table, empty-state,
|
|
72
|
-
chips, tabs, toast skin, form field shells.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
Shipped and adopted at audit time: app-shell, data-table, empty-state,
|
|
72
|
+
filter-bar, status chips, tabs, toast skin, and form field shells. The later
|
|
73
|
+
component-completeness batch adds the missing searchable-select presentation
|
|
74
|
+
shell, page-size/jump pagination, bulk-actions toolbar, multi-step/wizard
|
|
75
|
+
structure, switch, grouped/file/range inputs, segmented choice, and compact
|
|
76
|
+
loading button. JavaScript state and keyboard behavior remain app-owned where
|
|
77
|
+
the browser has no complete native primitive. Kanban/timesheet/invoice-doc
|
|
78
|
+
remain deliberately consumer-specific and out of scope.
|
|
78
79
|
|
|
79
80
|
## Strengths (keep doing)
|
|
80
81
|
|
|
@@ -97,13 +98,13 @@ deliberately consumer-specific and out of scope.
|
|
|
97
98
|
**0.4.0 — structural gaps:**
|
|
98
99
|
6. Continue dark-mode validation with a real consumer using Flow's built-in
|
|
99
100
|
theme selectors.
|
|
100
|
-
7.
|
|
101
|
-
|
|
101
|
+
7. Add app-tier depth: combobox presentation, pagination, modal-with-form,
|
|
102
|
+
bulk-actions, and wizard structure. ✅ Implemented after the audit.
|
|
102
103
|
|
|
103
104
|
**Docs / positioning / CI (cheap, high-trust):**
|
|
104
|
-
8. CI:
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
8. CI: the component-completeness batch adds a regression assertion that every
|
|
106
|
+
catalogued component selector ships in `styles.css`. Example smoke builds,
|
|
107
|
+
CSS parsing, and broader visual regression remain useful future additions.
|
|
107
108
|
|
|
108
109
|
## Meta-observation
|
|
109
110
|
|
package/docs/base-styling.md
CHANGED
|
@@ -111,3 +111,8 @@ The base layer uses cascade layers, logical properties, low-specificity
|
|
|
111
111
|
`prefers-reduced-motion`. Utopia informs the fluid type, space, and grid
|
|
112
112
|
tokens; Synced Flow owns the reset, accessibility helpers, and component
|
|
113
113
|
defaults.
|
|
114
|
+
|
|
115
|
+
The root element is kept at `font-size: 100%` rather than a fixed pixel value so
|
|
116
|
+
browser and user font-size settings remain respected. Body copy starts from the
|
|
117
|
+
fluid text token with `body { font-size: var(--sf-text-base); }`, where
|
|
118
|
+
`--sf-text-base` maps to the existing body type scale.
|
package/docs/cli-reference.md
CHANGED
|
@@ -189,9 +189,11 @@ pnpm exec synced-flow pattern mobile-nav-drawer --framework next --markup
|
|
|
189
189
|
pnpm exec synced-flow pattern scroll-viewport-sections --json
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
Current interaction patterns include
|
|
193
|
-
`
|
|
194
|
-
`native-
|
|
192
|
+
Current interaction patterns include native navigation and overlays, app shell
|
|
193
|
+
and data patterns, plus `switch-control`, `input-group`,
|
|
194
|
+
`native-file-and-range`, `segmented-control`, `loading-button`,
|
|
195
|
+
`searchable-select`, `bulk-actions-toolbar`, `complete-pagination`, and
|
|
196
|
+
`multi-step-form`. Pattern JSON includes
|
|
195
197
|
classes, framework markup, JS requirement notes, accessibility notes, and
|
|
196
198
|
implementation gotchas.
|
|
197
199
|
|
|
@@ -210,9 +212,10 @@ pnpm exec synced-flow recipe coming-soon --json
|
|
|
210
212
|
|
|
211
213
|
Current recipes include SaaS landing, scroll portfolio, agency homepage, blog
|
|
212
214
|
index, article page, about timeline, team grid, contact page, 404, coming soon,
|
|
213
|
-
and
|
|
214
|
-
`saas-dashboard` for authenticated app UI
|
|
215
|
-
|
|
215
|
+
SaaS dashboard, and application settings workflow. Use `saas-landing` for
|
|
216
|
+
public product marketing, `saas-dashboard` for authenticated app UI, and
|
|
217
|
+
`app-settings-workflow` for the richer form-control surface. Recipes are
|
|
218
|
+
composed from public `sf-*` classes and
|
|
216
219
|
are intended as copy-paste starting points rather than new utility APIs.
|
|
217
220
|
|
|
218
221
|
Use `--framework html`, `--framework next`, `--framework react`, or
|
package/docs/config-reference.md
CHANGED
|
@@ -52,8 +52,8 @@ theme: {
|
|
|
52
52
|
colours: {
|
|
53
53
|
background: 'oklch(98.6% 0.006 80)',
|
|
54
54
|
foreground: 'oklch(18% 0.026 250)',
|
|
55
|
-
primary: 'oklch(
|
|
56
|
-
primaryHover: 'oklch(
|
|
55
|
+
primary: 'oklch(50% 0.16 40)',
|
|
56
|
+
primaryHover: 'oklch(44% 0.14 40)',
|
|
57
57
|
primaryForeground: 'oklch(100% 0 0)',
|
|
58
58
|
border: 'oklch(18% 0.026 250 / 0.12)',
|
|
59
59
|
},
|
package/docs/css-optimisation.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CSS Optimisation Notes
|
|
2
2
|
|
|
3
|
-
Current measurements from `pnpm build` on 2026-
|
|
3
|
+
Current measurements from `pnpm build` on 2026-09-09.
|
|
4
4
|
|
|
5
5
|
## Developer Notes
|
|
6
6
|
|
|
@@ -33,14 +33,14 @@ Sizes are raw bytes and gzip bytes from `gzip -c`.
|
|
|
33
33
|
|
|
34
34
|
| File | Raw | Gzip | Use |
|
|
35
35
|
| --- | ---: | ---: | --- |
|
|
36
|
-
| `tokens.css` |
|
|
37
|
-
| `reset.css` |
|
|
38
|
-
| `base.css` | 3,
|
|
39
|
-
| `defaults.css` |
|
|
40
|
-
| `layout.css` |
|
|
41
|
-
| `components.css` |
|
|
42
|
-
| `utilities.css` | 7,
|
|
43
|
-
| `styles.css` |
|
|
36
|
+
| `tokens.css` | 11,953 B | 2,563 B | Design tokens only. |
|
|
37
|
+
| `reset.css` | 731 B | 435 B | Reset layer only. |
|
|
38
|
+
| `base.css` | 3,477 B | 1,163 B | Base element styles. |
|
|
39
|
+
| `defaults.css` | 506 B | 302 B | Optional site/UI defaults for raw links, lists, and controls. |
|
|
40
|
+
| `layout.css` | 12,195 B | 2,820 B | Layout primitives such as container, stack, grid, app shell, sidebar, scroll snap, sticky, media object, and split. |
|
|
41
|
+
| `components.css` | 62,874 B | 9,319 B | Component primitives including complete forms, app data controls, native overlays, website patterns, loading states, and technical presentation. |
|
|
42
|
+
| `utilities.css` | 7,550 B | 1,902 B | Static type, prose, content, positioning, motion, accessibility, link, list, colour, border, and shadow helpers. |
|
|
43
|
+
| `styles.css` | 97,950 B | 15,776 B | Full core stylesheet with tokens, reset, base, layout, components, and utilities. |
|
|
44
44
|
|
|
45
45
|
Example generated project CSS with tokens plus one scanned `text-primary`
|
|
46
46
|
utility measured 7,031 B raw and 1,943 B gzip.
|
|
@@ -91,7 +91,7 @@ Safe claims:
|
|
|
91
91
|
|
|
92
92
|
- Modern CSS-first: Synced Flow uses cascade layers, custom properties,
|
|
93
93
|
`clamp()`, logical properties, OKLCH colour, and container-aware primitives.
|
|
94
|
-
- Compact by default: the full core stylesheet is currently about
|
|
94
|
+
- Compact by default: the full core stylesheet is currently about 15.8 KB gzip.
|
|
95
95
|
- Flexible loading: developers can import only the CSS layers their project
|
|
96
96
|
uses.
|
|
97
97
|
- Source-scanned utilities: project utility CSS is generated from actual class
|
|
@@ -25,6 +25,127 @@ Synced Flow sets `color-scheme` for native controls, forms, dialogs, and
|
|
|
25
25
|
popover-backed UI. Projects can toggle the attribute however they like; the
|
|
26
26
|
package does not ship a JavaScript theme switcher.
|
|
27
27
|
|
|
28
|
+
## Switches And Segmented Choices
|
|
29
|
+
|
|
30
|
+
Use `sf-switch` only for a strict binary setting. The native checkbox owns the
|
|
31
|
+
checked, keyboard, disabled, and form states. Keep the visible label unchanged
|
|
32
|
+
when the value changes, and do not use `indeterminate` or `aria-checked="mixed"`
|
|
33
|
+
for this pattern.
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<label class="sf-switch">
|
|
37
|
+
<input type="checkbox" role="switch" name="notifications" checked>
|
|
38
|
+
<span class="sf-switch__control" aria-hidden="true"></span>
|
|
39
|
+
<span class="sf-switch__label">Email notifications</span>
|
|
40
|
+
</label>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Checkboxes do not have a native read-only state. Use `disabled` when a setting
|
|
44
|
+
is unavailable. If a product needs a focusable but non-editable explanation,
|
|
45
|
+
the app must implement and test that behavior rather than adding `readonly` to
|
|
46
|
+
the checkbox.
|
|
47
|
+
|
|
48
|
+
State mapping is deliberately small: omit `checked` for off, add `checked` for
|
|
49
|
+
on, rely on native `:focus-visible` for keyboard focus, and add `disabled` to
|
|
50
|
+
the input when unavailable. The visible label must stay the same in every
|
|
51
|
+
state.
|
|
52
|
+
|
|
53
|
+
Use the radio-backed segmented control for one choice from a small set. It is a
|
|
54
|
+
form control, not a tab list and not a group of independent switches.
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<fieldset class="sf-fieldset">
|
|
58
|
+
<legend>Billing cycle</legend>
|
|
59
|
+
<div class="sf-segmented-control">
|
|
60
|
+
<label class="sf-segmented-control__option">
|
|
61
|
+
<input type="radio" name="cycle" value="monthly" checked>
|
|
62
|
+
<span class="sf-segmented-control__label">Monthly</span>
|
|
63
|
+
</label>
|
|
64
|
+
<label class="sf-segmented-control__option">
|
|
65
|
+
<input type="radio" name="cycle" value="annual">
|
|
66
|
+
<span class="sf-segmented-control__label">Annual</span>
|
|
67
|
+
</label>
|
|
68
|
+
</div>
|
|
69
|
+
</fieldset>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Both controls include light/dark token behavior, visible keyboard focus,
|
|
73
|
+
disabled styling, forced-colors fallbacks, and reduced-motion handling.
|
|
74
|
+
|
|
75
|
+
## Input Groups, File Inputs, And Range Inputs
|
|
76
|
+
|
|
77
|
+
`sf-input-group` composes one flexible input with leading/trailing content or
|
|
78
|
+
an inline action. Prefixes that repeat visible label context should be hidden
|
|
79
|
+
from assistive technology. Use `sf-search` for a normal search field.
|
|
80
|
+
|
|
81
|
+
```html
|
|
82
|
+
<label class="sf-field">
|
|
83
|
+
<span class="sf-label">Monthly budget</span>
|
|
84
|
+
<span class="sf-input-group">
|
|
85
|
+
<span class="sf-input-group__leading" aria-hidden="true">£</span>
|
|
86
|
+
<input class="sf-input" name="budget" inputmode="decimal">
|
|
87
|
+
<span class="sf-input-group__trailing" aria-hidden="true">GBP</span>
|
|
88
|
+
</span>
|
|
89
|
+
</label>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use `sf-file-input` and `sf-range` on their native input types. File previews,
|
|
93
|
+
upload limits, live range output, and persistence belong to the app.
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<label class="sf-field">
|
|
97
|
+
<span class="sf-label">Supporting document</span>
|
|
98
|
+
<input class="sf-file-input" type="file" accept=".pdf,.doc,.docx">
|
|
99
|
+
<span class="sf-help">PDF or Word, up to 10 MB.</span>
|
|
100
|
+
</label>
|
|
101
|
+
|
|
102
|
+
<label class="sf-field">
|
|
103
|
+
<span class="sf-label">Team size: <output for="team-size">12</output></span>
|
|
104
|
+
<input class="sf-range" id="team-size" type="range" min="1" max="50" value="12">
|
|
105
|
+
</label>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Loading Buttons
|
|
109
|
+
|
|
110
|
+
`sf-spinner` is a presentational indicator. A pending action must also expose a
|
|
111
|
+
text status and use app state to prevent duplicate submission.
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<button class="sf-button" type="submit" aria-busy="true" aria-disabled="true">
|
|
115
|
+
<span class="sf-spinner" aria-hidden="true"></span>
|
|
116
|
+
<span>Saving…</span>
|
|
117
|
+
</button>
|
|
118
|
+
<p class="sr-only" role="status" aria-live="polite">Saving changes</p>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The spinner stops rotating under reduced motion while remaining visible.
|
|
122
|
+
`aria-disabled` keeps an in-flight button in the focus order, but the app must
|
|
123
|
+
prevent click and submit activation while it is set. Use native `disabled`
|
|
124
|
+
instead when removing the action from the focus order is the better tradeoff.
|
|
125
|
+
|
|
126
|
+
## App-Owned Interactive Shells
|
|
127
|
+
|
|
128
|
+
Synced Flow includes presentation classes for searchable selects, selection
|
|
129
|
+
toolbars, complete pagination, and multi-step forms. The copy-ready contracts
|
|
130
|
+
live in the CLI:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm exec synced-flow pattern searchable-select --markup
|
|
134
|
+
pnpm exec synced-flow pattern bulk-actions-toolbar --markup
|
|
135
|
+
pnpm exec synced-flow pattern complete-pagination --markup
|
|
136
|
+
pnpm exec synced-flow pattern multi-step-form --markup
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The combobox shell does not implement filtering or keyboard behavior. The app
|
|
140
|
+
must synchronize `aria-expanded`, `aria-controls`, `aria-activedescendant`, and
|
|
141
|
+
`aria-selected`, and test the complete interaction with keyboard and assistive
|
|
142
|
+
technology. Bulk selection, client-side pagination state, step validation,
|
|
143
|
+
focus movement, persistence, and history are also app-owned.
|
|
144
|
+
|
|
145
|
+
For a completed wizard step, include hidden text such as
|
|
146
|
+
`<span class="sr-only">Completed: </span>` before the visible label. The tick
|
|
147
|
+
or marker may remain `aria-hidden`; completion must still be available as text.
|
|
148
|
+
|
|
28
149
|
## Dialog
|
|
29
150
|
|
|
30
151
|
Use `<dialog>` for modal UI. Style the dialog with `sf-dialog` and structure it
|
package/docs/patterns.md
CHANGED
|
@@ -31,6 +31,15 @@ pnpm exec synced-flow pattern scroll-viewport-sections --json
|
|
|
31
31
|
| `data-table` | Native responsive table markup with status pills. |
|
|
32
32
|
| `empty-state` | No-results and first-run states with clear next actions. |
|
|
33
33
|
| `settings-section` | Settings/detail sections for account, team, billing, and security screens. |
|
|
34
|
+
| `switch-control` | Native checkbox-backed binary setting switch. |
|
|
35
|
+
| `input-group` | Prefix, suffix, unit, or inline-action input composition. |
|
|
36
|
+
| `native-file-and-range` | Styled native file and bounded range inputs. |
|
|
37
|
+
| `segmented-control` | Native radio-backed mutually exclusive choice. |
|
|
38
|
+
| `loading-button` | Busy action with spinner and text/live-region guidance. |
|
|
39
|
+
| `searchable-select` | Presentation shell and complete app-owned combobox contract. |
|
|
40
|
+
| `bulk-actions-toolbar` | Selection count and list/table actions. |
|
|
41
|
+
| `complete-pagination` | Result context, page links, page size, and direct page entry. |
|
|
42
|
+
| `multi-step-form` | Step progress and form navigation structure with textual current and completed states. |
|
|
34
43
|
|
|
35
44
|
Pattern JSON includes:
|
|
36
45
|
|
|
@@ -43,3 +52,12 @@ Pattern JSON includes:
|
|
|
43
52
|
Use patterns before hand-rolling interaction markup. Use recipes for full-page
|
|
44
53
|
composition and patterns for the tricky native interaction details inside those
|
|
45
54
|
pages.
|
|
55
|
+
|
|
56
|
+
Patterns with `requiresJs: true` are presentation and markup contracts. Synced
|
|
57
|
+
Flow deliberately does not pretend that CSS supplies combobox keyboard
|
|
58
|
+
behavior, async state, row selection, or wizard validation. The consuming app
|
|
59
|
+
owns those behaviors and their browser/assistive-technology tests.
|
|
60
|
+
|
|
61
|
+
Wizard completion cannot rely on the tick marker alone. Keep that decorative
|
|
62
|
+
marker hidden from assistive technology and prefix completed labels with
|
|
63
|
+
screen-reader text such as `Completed:`.
|
package/docs/quick-start.md
CHANGED
|
@@ -50,10 +50,11 @@ theme: {
|
|
|
50
50
|
display: 'Fraunces, Georgia, serif',
|
|
51
51
|
},
|
|
52
52
|
colours: {
|
|
53
|
-
primary: 'oklch(
|
|
53
|
+
primary: 'oklch(50% 0.16 40)',
|
|
54
|
+
primaryHover: 'oklch(44% 0.14 40)',
|
|
54
55
|
primaryForeground: 'oklch(100% 0 0)',
|
|
55
56
|
accent: 'oklch(70% 0.12 205)',
|
|
56
|
-
ring: 'oklch(
|
|
57
|
+
ring: 'oklch(50% 0.16 40)',
|
|
57
58
|
},
|
|
58
59
|
components: {
|
|
59
60
|
button: { radius: '0.5rem' },
|
|
@@ -153,7 +154,8 @@ export default defineConfig({
|
|
|
153
154
|
display: 'Fraunces, Georgia, serif',
|
|
154
155
|
},
|
|
155
156
|
colours: {
|
|
156
|
-
primary: 'oklch(
|
|
157
|
+
primary: 'oklch(50% 0.16 40)',
|
|
158
|
+
primaryHover: 'oklch(44% 0.14 40)',
|
|
157
159
|
secondary: 'oklch(62% 0.12 205)',
|
|
158
160
|
tertiary: 'oklch(64% 0.14 185)',
|
|
159
161
|
primaryForeground: 'oklch(100% 0 0)',
|
|
@@ -174,7 +176,7 @@ Use the CSS entry file for one-off local overrides.
|
|
|
174
176
|
|
|
175
177
|
:root {
|
|
176
178
|
--sf-font-sans: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
177
|
-
--sf-colour-primary: oklch(
|
|
179
|
+
--sf-colour-primary: oklch(50% 0.16 40);
|
|
178
180
|
}
|
|
179
181
|
```
|
|
180
182
|
|
package/docs/recipes.md
CHANGED
|
@@ -21,6 +21,7 @@ Available recipe ids:
|
|
|
21
21
|
|
|
22
22
|
- `saas-landing`
|
|
23
23
|
- `saas-dashboard`
|
|
24
|
+
- `app-settings-workflow`
|
|
24
25
|
- `portfolio-scroll`
|
|
25
26
|
- `agency-home`
|
|
26
27
|
- `blog-index`
|
|
@@ -31,6 +32,17 @@ Available recipe ids:
|
|
|
31
32
|
- `not-found`
|
|
32
33
|
- `coming-soon`
|
|
33
34
|
|
|
35
|
+
`app-settings-workflow` composes the switch, grouped input, file and range
|
|
36
|
+
inputs, segmented choice, combobox presentation shell, stepper, wizard actions,
|
|
37
|
+
and loading-button structure. Generate the complete copy-ready markup with:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm exec synced-flow recipe app-settings-workflow --markup
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The consuming app still owns async state, upload handling, live range output,
|
|
44
|
+
combobox keyboard behavior, step validation, persistence, and focus movement.
|
|
45
|
+
|
|
34
46
|
## Marketing Homepage
|
|
35
47
|
|
|
36
48
|
```html
|
|
@@ -34,10 +34,19 @@ keep the CSS affordances in place.
|
|
|
34
34
|
the system provides matching primitives.
|
|
35
35
|
- Native disabled, required, invalid, `details[open]`, and `:target` states are
|
|
36
36
|
styled.
|
|
37
|
+
- Checkbox-backed switches remain strictly binary and radio-backed segmented
|
|
38
|
+
controls preserve native keyboard behavior.
|
|
39
|
+
- Loading indicators have a text status; reduced motion leaves a visible
|
|
40
|
+
non-rotating indicator.
|
|
41
|
+
- App-owned combobox, bulk-selection, pagination, and wizard examples clearly
|
|
42
|
+
name the JavaScript and focus behavior the consumer must implement.
|
|
37
43
|
- Forced-colors fallbacks keep borders and focus outlines visible.
|
|
38
44
|
- `prefers-reduced-motion` is respected by the base styles.
|
|
39
45
|
- Examples use semantic landmarks, real buttons, real links, labels, help text,
|
|
40
46
|
and native disclosure controls.
|
|
47
|
+
- Every catalogue pattern and recipe has valid IDs and ID references and passes
|
|
48
|
+
automated WCAG A/AA checks in light and dark themes.
|
|
49
|
+
- Native component behaviour passes in Chromium, Firefox, and WebKit.
|
|
41
50
|
|
|
42
51
|
## Documentation Checks
|
|
43
52
|
|
|
@@ -51,23 +60,39 @@ keep the CSS affordances in place.
|
|
|
51
60
|
## Commands
|
|
52
61
|
|
|
53
62
|
```bash
|
|
54
|
-
pnpm
|
|
63
|
+
pnpm install --ignore-scripts
|
|
64
|
+
pnpm build:types
|
|
65
|
+
pnpm check:generated
|
|
55
66
|
pnpm check
|
|
56
67
|
pnpm test
|
|
68
|
+
pnpm test:browser:all
|
|
69
|
+
npm pack --dry-run
|
|
57
70
|
node bin/synced-flow.mjs tokens --json
|
|
58
71
|
node bin/synced-flow.mjs catalog --json
|
|
72
|
+
node bin/synced-flow.mjs pattern switch-control --markup
|
|
73
|
+
node bin/synced-flow.mjs pattern searchable-select --json
|
|
74
|
+
node bin/synced-flow.mjs recipe app-settings-workflow --markup
|
|
59
75
|
node bin/synced-flow.mjs doctor --cwd examples/plain-html
|
|
60
76
|
```
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
Run freshness checks before `pnpm build`, `pnpm install` without
|
|
79
|
+
`--ignore-scripts`, or another lifecycle command can regenerate tracked CSS.
|
|
80
|
+
The freshness gate covers the eight package layer/bundle files plus plain HTML,
|
|
81
|
+
Vite, Next, Astro, and WordPress generated outputs. The unit suite semantically
|
|
82
|
+
compiles all 35 patterns and 12 recipes for React and Next (94 generated TSX
|
|
83
|
+
files), and exercises the generated React scroll-spy lifecycle. Browser tests
|
|
84
|
+
use shipped CSS and generated markup to cover native keyboard/form behaviour,
|
|
85
|
+
focus, reduced motion, forced colours, dialogs, responsive overflow, IDs, and
|
|
86
|
+
light/dark Axe checks.
|
|
64
87
|
|
|
65
88
|
## npm Trusted Publishing
|
|
66
89
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
Pull requests and `main` use `.github/workflows/ci.yml` to run the freshness,
|
|
91
|
+
static, unit, package dry-run, and Chromium gates. Publishing is handled by
|
|
92
|
+
`.github/workflows/npm-publish.yml` when a `v*` tag is pushed. Its release gate
|
|
93
|
+
checks that the tag matches `package.json` and runs the same checks with
|
|
94
|
+
Chromium, Firefox, and WebKit. The publish job can start only after that gate
|
|
95
|
+
passes and is the only job granted npm trusted-publishing identity permission.
|
|
71
96
|
|
|
72
97
|
Configure npm package trusted publishing for `@syncedco/flow` with:
|
|
73
98
|
|
|
@@ -10,7 +10,7 @@ Use the `--sf-*` variables as the stable CSS foundation.
|
|
|
10
10
|
| Layer | Examples | Use for |
|
|
11
11
|
| --- | --- | --- |
|
|
12
12
|
| Font | `--sf-font-sans`, `--sf-font-display`, `--sf-font-mono` | Site typography families. |
|
|
13
|
-
| Type | `--sf-type-body`, `--sf-type-lead`, `--sf-type-h1`, `--sf-type-display` | Fluid text sizes. |
|
|
13
|
+
| Type | `--sf-text-base`, `--sf-type-body`, `--sf-type-lead`, `--sf-type-h1`, `--sf-type-display` | Fluid text sizes. |
|
|
14
14
|
| Space | `--sf-space-s`, `--sf-space-m-l`, `--sf-space-xl-2xl` | Fluid padding, margin, and gaps. |
|
|
15
15
|
| Radius | `--sf-radius-control`, `--sf-radius-panel`, `--sf-radius-full` | Controls, panels, pills. |
|
|
16
16
|
| Colour | `--sf-colour-background`, `--sf-colour-surface`, `--sf-colour-primary` | Semantic UI colour roles. |
|
|
@@ -63,18 +63,25 @@ These cover the common UI elements needed for a simple site.
|
|
|
63
63
|
| `sf-surface`, `sf-surface--alt`, `sf-surface--raised` | Generic reusable panels. |
|
|
64
64
|
| `sf-logo-cloud`, `sf-feature`, `sf-stats`, `sf-testimonial`, `sf-pricing-grid`, `sf-faq`, `sf-cta`, `sf-footer` | Common website patterns. |
|
|
65
65
|
| `sf-nav`, `sf-nav__list`, `sf-nav__link` | Navigation basics. |
|
|
66
|
-
| `sf-nav--mobile`, `sf-menu`, `sf-breadcrumb`, `sf-pagination` | Mobile, menu, breadcrumb, and paginated navigation. |
|
|
66
|
+
| `sf-nav--mobile`, `sf-menu`, `sf-breadcrumb`, `sf-pagination`, `sf-pagination__summary`, `sf-pagination__controls`, `sf-pagination__pages`, `sf-pagination__jump` | Mobile, menu, breadcrumb, and complete paginated navigation. |
|
|
67
67
|
| `sf-dialog`, `sf-dialog__header`, `sf-dialog__body`, `sf-dialog__footer` | Native `<dialog>` styling. |
|
|
68
68
|
| `sf-popover`, `sf-tooltip`, `sf-tooltip-trigger`, `sf-menu-popover`, `sf-toast`, `sf-toast-stack`, `sf-banner`, `sf-drawer` | Popover-backed native overlays and app feedback stacks. |
|
|
69
69
|
| `sf-disclosure`, `sf-accordion` | Native `details`/`summary` disclosure patterns. |
|
|
70
70
|
| `sf-tabs`, `sf-tab-list`, `sf-tab`, `sf-tab__count`, `sf-tab-panel` | HTML/CSS-first tab styling, including counted pill tabs. |
|
|
71
71
|
| `sf-form`, `sf-fieldset`, `sf-field`, `sf-label`, `sf-help`, `sf-error` | Form structure and messaging. |
|
|
72
|
-
| `sf-input`, `sf-select`, `sf-textarea`, `sf-search`, `sf-check` |
|
|
72
|
+
| `sf-input`, `sf-select`, `sf-textarea`, `sf-search`, `sf-input-group`, `sf-file-input`, `sf-range`, `sf-check` | Native form controls, search, grouped prefixes/suffixes/actions, file selection, and range input. |
|
|
73
|
+
| `sf-switch`, `sf-switch__control`, `sf-switch__label` | Native checkbox-backed binary setting switch. |
|
|
74
|
+
| `sf-segmented-control`, `sf-segmented-control__option`, `sf-segmented-control__label` | Native radio-backed single-choice control. |
|
|
75
|
+
| `sf-spinner` | Compact loading indicator for buttons and small async surfaces. |
|
|
73
76
|
| `sf-alert`, `sf-alert--info`, `sf-alert--success`, `sf-alert--warning`, `sf-alert--danger`, `sf-alert__title` | Notices and feedback. |
|
|
74
77
|
| `sf-section-header`, `sf-kicker`, `sf-badge`, `sf-avatar` | Common marketing/content and account patterns. |
|
|
75
78
|
| `sf-chart`, `sf-chart__plot`, `sf-chart__svg`, `sf-chart__legend`, `sf-meter-list`, `sf-meter` | Lightweight chart shells for app-owned SVG charts and native meter bars. |
|
|
76
79
|
| `sf-progress`, `sf-skeleton` | Native progress bars and CSS-only loading placeholders for async app states. |
|
|
77
80
|
| `sf-filter-bar`, `sf-data-table`, `sf-table-sort`, `sf-status`, `sf-data-list`, `sf-empty-state`, `sf-settings-section`, `sf-detail-panel` | Lean application primitives for filters, operational data, status, empty states, and settings/detail screens. |
|
|
81
|
+
| `sf-combobox`, `sf-combobox__listbox`, `sf-combobox__option`, `sf-combobox__empty` | Presentation shell for an app-owned accessible searchable select. |
|
|
82
|
+
| `sf-bulk-actions`, `sf-bulk-actions__count`, `sf-bulk-actions__controls` | Selection count and actions above a list or table. |
|
|
83
|
+
| `sf-stepper`, `sf-stepper__step`, `sf-stepper__marker`, `sf-stepper__label`, `sf-wizard-actions` | Multi-step progress and form navigation structure. |
|
|
84
|
+
| `sf-code-window`, `sf-code-lines`, `sf-code-block`, `sf-token-strip`, `sf-marquee`, `sf-command-list`, `sf-platform-card` | Public technical/product presentation primitives used by the catalogue and examples. |
|
|
78
85
|
|
|
79
86
|
## Utility Classes
|
|
80
87
|
|