fractalstyler2 0.4.0 → 0.7.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/README.md +114 -95
- package/dist/cli.js +105 -25
- package/dist/components/Accordion.svelte +44 -0
- package/dist/components/Accordion.svelte.d.ts +23 -0
- package/dist/components/AccordionItem.svelte +44 -0
- package/dist/components/AccordionItem.svelte.d.ts +21 -0
- package/dist/components/AppShell.svelte +86 -0
- package/dist/components/AppShell.svelte.d.ts +39 -0
- package/dist/components/ColorPicker.svelte +38 -0
- package/dist/components/ColorPicker.svelte.d.ts +6 -0
- package/dist/components/Hero.svelte +14 -0
- package/dist/components/Hero.svelte.d.ts +9 -0
- package/dist/components/LayoutPicker.svelte +34 -0
- package/dist/components/LayoutPicker.svelte.d.ts +6 -0
- package/dist/components/MotionPicker.svelte +34 -0
- package/dist/components/MotionPicker.svelte.d.ts +6 -0
- package/dist/components/PageShell.svelte +14 -0
- package/dist/components/PageShell.svelte.d.ts +9 -0
- package/dist/components/PageSplit.svelte +26 -0
- package/dist/components/PageSplit.svelte.d.ts +18 -0
- package/dist/components/ShapePicker.svelte +37 -0
- package/dist/components/ShapePicker.svelte.d.ts +6 -0
- package/dist/css/fractalstyler.css +10643 -0
- package/dist/css/fractalstyler.min.css +2 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +30 -12
- package/dist/mcp/schemas/compile_fractals.json +3 -3
- package/dist/mcp/schemas/css_to_fractals.json +1 -1
- package/dist/mcp/schemas/generate_component.json +2 -2
- package/dist/mcp/schemas/instructions.md +41 -14
- package/dist/mcp/schemas/list_fractals.json +9 -7
- package/dist/mcp/schemas/validate_recipe.json +2 -2
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +334 -237
- package/dist/presets.core.d.ts +42 -0
- package/dist/presets.core.js +190 -0
- package/dist/presets.svelte.d.ts +6 -0
- package/dist/presets.svelte.js +21 -0
- package/dist/styles/_00_presets.sass +111 -0
- package/dist/styles/_00_themes.sass +1061 -0
- package/dist/styles/_00_tokens.sass +57 -17
- package/dist/styles/_01_config.sass +77 -70
- package/dist/styles/_02_dimensions.sass +174 -0
- package/dist/styles/_03_containers.sass +105 -0
- package/dist/styles/_04_layouts.sass +100 -0
- package/dist/styles/_05_shells.sass +374 -0
- package/dist/styles/_06_visuals.sass +236 -0
- package/dist/styles/_07_interactions.sass +112 -0
- package/dist/styles/_08_own.sass +4 -0
- package/dist/styles/canonical-markups.md +160 -0
- package/dist/styles/index.sass +11 -18
- package/dist/themes.d.ts +7 -0
- package/dist/themes.js +45 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/package.json +26 -15
- package/plugin.json +11 -10
- package/registry.json +2002 -0
- package/skills/fractal-styler/SKILL.md +137 -50
- package/skills/fractal-styler/references/fractals.md +416 -28
- package/skills/fractal-styler/references/tokens.md +110 -36
- package/dist/styles/_02_fonts.sass +0 -13
- package/dist/styles/_03_responsive.sass +0 -31
- package/dist/styles/_04_atoms.sass +0 -155
- package/dist/styles/_05_molecules.sass +0 -97
- package/dist/styles/_06_recipes.sass +0 -189
- package/dist/styles/_07_base.sass +0 -44
- package/dist/styles/_08_blocks.sass +0 -433
- package/dist/styles/_09_utilities.sass +0 -208
- package/dist/styles/_10_layouts.sass +0 -373
- package/dist/styles/_11_own.sass +0 -21
- package/dist/styles/_fractals.sass +0 -14
- package/skills/style-migration/SKILL.md +0 -45
- package/templates/_00_tokens.sass +0 -136
- package/templates/_01_config.sass +0 -66
- package/templates/_02_fonts.sass +0 -13
- package/templates/_03_responsive.sass +0 -31
- package/templates/_04_atoms.sass +0 -155
- package/templates/_05_molecules.sass +0 -97
- package/templates/_06_recipes.sass +0 -189
- package/templates/_07_base.sass +0 -44
- package/templates/_08_blocks.sass +0 -433
- package/templates/_09_utilities.sass +0 -207
- package/templates/_10_layouts.sass +0 -373
- package/templates/_11_own.sass +0 -21
- package/templates/_fractals.sass +0 -14
- package/templates/index.sass +0 -18
package/README.md
CHANGED
|
@@ -1,145 +1,164 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fractalstyler
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
> **A composition styling system — plain CSS or editable SASS — with a fluid design token engine and a runtime preset architecture.**
|
|
4
|
+
>
|
|
5
|
+
> The class registry is the public API. You compose in markup; you do not write a stylesheet.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/fractalstyler2)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Every styling decision in `Fractalstyler` is a modular fractal: unitary tokens, dimensions, and container primitives that compose into higher layers while retaining mathematical harmony, responsiveness, and zero-runtime bloat.
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
Ships two ways, from one source:
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
| | | |
|
|
15
|
+
|:--|:--|:--|
|
|
16
|
+
| **Plain CSS** | one file, no toolchain | `npx fractalstyler2 init --css` |
|
|
17
|
+
| **SASS** | editable partials, retunable generators | `npx fractalstyler2 init` |
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
The compiled CSS is emitted from the same partials the SASS path scaffolds, so
|
|
20
|
+
the two are byte-identical. SASS buys exactly one thing: the ability to retune
|
|
21
|
+
the generators — the literal ladder and the responsive seam — before compiling.
|
|
22
|
+
|
|
23
|
+
---
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
## The Fractal Mental Model
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Tokens (L0) ──► Dimensions (L1) ──► Containers (L2) ──► Layouts (L3) ──► Shells (L4) ──► Visuals (L5)
|
|
19
29
|
```
|
|
20
30
|
|
|
21
|
-
|
|
31
|
+
1. **Tokens (L0)**: 31 semantic colour tokens, 41 themes, and fluid Utopia typography/space scales.
|
|
32
|
+
2. **Dimensions (L1)**: 17 space families (`.gap-sm`, `.pad-md`, `.marg-xs`), negative margins (`.marg--sm`), literal pixel utilities (`.w-120`, `.radius-8`), and `-mob` / `-desk` bands.
|
|
33
|
+
3. **Containers (L2)**: Flexible flow primitives (`.box`, `.row`, `.grid`) with strict physical X/Y axis alignment (`.xcenter`, `.ycenter`, `.xbetween`).
|
|
34
|
+
4. **Layouts (L3)**: Gridding Golden Rules ($3\to1$, $4\to2\to1$, $6\to3\to2\to1$), `.card-grid`, reading measure `.prose`, and scroll-snap `.reel`.
|
|
35
|
+
5. **Shells (L4)**: Full application frames (`.app-shell`, `.app-header`, `.app-main`, `.sidebar-left`, `.sidebar-right`, `.page-split`, `.drawer`, `.dialog`, `.popover`, `.accordion`).
|
|
36
|
+
6. **Visuals & Interactions (L5)**: Bare surfaces (`.bg`, `.surface`, `.raised`), text inks, 1px hairline borders, the button quartet (`.primary`, `.ghost`, `.active`, `.is-icon`), and form controls (`.input`, `.select`).
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
### Plain CSS — no build step
|
|
43
|
+
|
|
44
|
+
Most projects want this. One stylesheet, nothing to configure:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx fractalstyler2 init --css
|
|
28
48
|
```
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<div class="pricing-card">
|
|
33
|
-
<span class="badge" data-status="released">Pro Plan</span>
|
|
34
|
-
<h3>$29/mo</h3>
|
|
35
|
-
<p>Full access to all fractal components and layouts.</p>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<style lang="sass">
|
|
39
|
-
@use '$lib/styles/fractals' as *
|
|
40
|
-
|
|
41
|
-
.pricing-card
|
|
42
|
-
+surface(surface, m, 6, md) // bg + border + radius(6px) + padding + shadow
|
|
43
|
-
+stack(s, start) // flex-column + gap(s) + top-anchored flow
|
|
44
|
-
</style>
|
|
50
|
+
```html
|
|
51
|
+
<link rel="stylesheet" href="/src/styles/fractalstyler.css" />
|
|
45
52
|
```
|
|
46
53
|
|
|
47
|
-
|
|
54
|
+
Themes and the four preset axes are classes and attributes, so they work with
|
|
55
|
+
JavaScript disabled:
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
```html
|
|
58
|
+
<html class="theme-night-dark" data-mode="dark" data-shape="sharp">
|
|
59
|
+
```
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
For runtime switching and zero-flicker restore, there is a framework-free
|
|
62
|
+
runtime at `fractalstyler2/presets`:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { initPresets, setPreset, getPresetScript } from 'fractalstyler2/presets';
|
|
66
|
+
initPresets();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### SASS — if you want to retune the generators
|
|
52
70
|
|
|
53
71
|
```bash
|
|
54
|
-
pnpm add fractalstyler2
|
|
55
|
-
|
|
72
|
+
pnpm add -D sass fractalstyler2
|
|
73
|
+
npx fractalstyler2 init
|
|
56
74
|
```
|
|
57
75
|
|
|
58
|
-
|
|
76
|
+
In your root layout (`src/routes/+layout.svelte`):
|
|
59
77
|
|
|
60
78
|
```svelte
|
|
61
|
-
<script>
|
|
62
|
-
|
|
79
|
+
<script lang="ts">
|
|
80
|
+
import 'fractalstyler2/styles';
|
|
81
|
+
import { initPresets, getPresetScript } from 'fractalstyler2';
|
|
82
|
+
import { onMount } from 'svelte';
|
|
83
|
+
|
|
84
|
+
let { children } = $props();
|
|
85
|
+
|
|
86
|
+
onMount(() => {
|
|
87
|
+
initPresets();
|
|
88
|
+
});
|
|
63
89
|
</script>
|
|
64
90
|
|
|
65
|
-
<
|
|
66
|
-
|
|
91
|
+
<svelte:head>
|
|
92
|
+
{@html `<script>${getPresetScript()}</script>`}
|
|
93
|
+
</svelte:head>
|
|
67
94
|
|
|
68
|
-
|
|
69
|
-
+surface(surface, s, 6)
|
|
70
|
-
+stack(s)
|
|
71
|
-
</style>
|
|
95
|
+
{@render children()}
|
|
72
96
|
```
|
|
73
97
|
|
|
74
98
|
---
|
|
75
99
|
|
|
76
|
-
## The
|
|
100
|
+
## The Four Preset Tuning Axes
|
|
77
101
|
|
|
78
|
-
|
|
102
|
+
Fractalstyler includes a runtime tuning engine governing four fundamental aesthetic axes:
|
|
79
103
|
|
|
104
|
+
```html
|
|
105
|
+
<!-- HTML root attribute contract -->
|
|
106
|
+
<html data-layout="tight" data-shape="sharp" data-color="clean" data-motion="active">
|
|
80
107
|
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
_03_responsive.sass ──► Breakpoint helpers (+at, +below, +between)
|
|
89
|
-
_04_atoms.sass ──► Single-decision primitives (+box, +row, +bg, +ink, +pad, +gap)
|
|
90
|
-
_05_molecules.sass ──► Compositions of atoms (+stack, +cluster, +surface, +cols)
|
|
91
|
-
_06_recipes.sass ──► Macro component archetypes (=control, =select, =card, =partition)
|
|
92
|
-
|
|
93
|
-
── Phase 2: CSS Cascade Emitters (Loaded once globally by index.sass) ─────────
|
|
94
|
-
_07_base.sass ──► Global HTML element resets
|
|
95
|
-
_08_blocks.sass ──► Semantic component classes (.surface, .panel, .select, .badge)
|
|
96
|
-
_09_utilities.sass ──► 1:1 atomic markup projections (.pad-*, .pad-top-*, .gap-*, .hide-desktop)
|
|
97
|
-
_10_layouts.sass ──► Page layout templates (.docs, .card-grid, .hero, .holy-grail)
|
|
98
|
-
_11_own.sass ──► Bespoke local project overrides
|
|
99
|
-
```
|
|
108
|
+
|
|
109
|
+
| Axis | Options | Description |
|
|
110
|
+
|:---|:---|:---|
|
|
111
|
+
| **Layout** (`data-layout`) | `tight` · `comfortable` *(default)* · `sprawling` | Scales gap and padding density factors independently. |
|
|
112
|
+
| **Shape** (`data-shape`) | `round` · `curved` *(default)* · `pro` · `sharp` | Controls corner radii scale down to strict $0\text{px}$ sharp edges. |
|
|
113
|
+
| **Color** (`data-color`) | `clean` · `general` *(default)* · `vibrant` | Adjusts background contrasts and surface elevations. |
|
|
114
|
+
| **Motion** (`data-motion`) | `reduced` · `heavy` · `active` *(default)* · `springy` | Sets transition durations and spring easing physics. |
|
|
100
115
|
|
|
101
116
|
---
|
|
102
117
|
|
|
103
|
-
## The
|
|
118
|
+
## The Complete Grepable Registry
|
|
104
119
|
|
|
105
|
-
|
|
120
|
+
The master class dictionary is documented in [**`REGISTRY.md`**](./REGISTRY.md) and programmatically exposed via [`registry.json`](./registry.json).
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
- **Text & Ink**: `--text-primary`, `--text-secondary`, `--text-muted`, `--text-inverse`
|
|
109
|
-
- **States & Feedback**: `--state-hover`, `--state-hover-subtle`, `--state-selected`
|
|
110
|
-
- **Borders & Accents**: `--border`, `--border-subtle`, `--theme-color`, `--theme-color-alt` *(aliased to `--theme`)*
|
|
122
|
+
To regenerate the registry after editing stylesheet files:
|
|
111
123
|
|
|
112
|
-
|
|
124
|
+
```bash
|
|
125
|
+
pnpm registry
|
|
126
|
+
```
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
---
|
|
115
129
|
|
|
116
|
-
|
|
130
|
+
## Documentation Index
|
|
117
131
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
| Chapter | Document | Scope & Contents |
|
|
133
|
+
|:---|:---|:---|
|
|
134
|
+
| **01** | [**`01-introduction.md`**](./docs/01-introduction.md) | Philosophy, $L0 \to L5$ mental model, and design invariants. |
|
|
135
|
+
| **02** | [**`02-getting-started.md`**](./docs/02-getting-started.md) | Installation (CSS or SASS), CLI init, SvelteKit setup, and the 41 built-in themes. |
|
|
136
|
+
| **03** | [**`03-structure.md`**](./docs/03-structure.md) | Numbered physical scale (`_00` through `_08`) and cascade order. |
|
|
137
|
+
| **04** | [**`04-tokens.md`**](./docs/04-tokens.md) | 30 semantic colors, fluid Utopia scales, and dark/light modes. |
|
|
138
|
+
| **05** | [**`05-dimensions.md`**](./docs/05-dimensions.md) | 17 space families, the literal px ladder, and `-mob`/`-desk` bands. |
|
|
139
|
+
| **06** | [**`06-containers.md`**](./docs/06-containers.md) | `.box`, `.row`, `.grid`, and physical X/Y axis alignment. |
|
|
140
|
+
| **07** | [**`07-layouts.md`**](./docs/07-layouts.md) | Gridding Golden Rules, `.card-grid`, `.prose`, and `.reel`. |
|
|
141
|
+
| **08** | [**`08-shells-and-markups.md`**](./docs/08-shells-and-markups.md) | Canonical App Shell, role-bound sidebars, mobile disclosures, overlays. |
|
|
142
|
+
| **09** | [**`09-visuals-and-interactions.md`**](./docs/09-visuals-and-interactions.md) | Surfaces, inks, hairline borders, buttons, and form inputs. |
|
|
143
|
+
| **10** | [**`10-presets.md`**](./docs/10-presets.md) | Presets runtime, `presets.svelte.ts` API, and Svelte UI pickers. |
|
|
144
|
+
| **11** | [**`11-mcp-server.md`**](./docs/11-mcp-server.md) | Model Context Protocol server tools and agent connection configs. |
|
|
145
|
+
| **12** | [**`12-agent-plugin.md`**](./docs/12-agent-plugin.md) | `agent-plugins.org` spec, the bundled `fractal-styler` skill. |
|
|
146
|
+
| **13** | [**`13-cookbook.md`**](./docs/13-cookbook.md) | The Zero-SASS Recipe Gallery: real-world UI patterns composed in HTML. |
|
|
147
|
+
| **Registry** | [**`REGISTRY.md`**](./REGISTRY.md) | Comprehensive, grepable class dictionary and token table. |
|
|
123
148
|
|
|
124
149
|
---
|
|
125
150
|
|
|
126
|
-
##
|
|
151
|
+
## AI Agent Integration & MCP
|
|
127
152
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
| [05. Tokens & Theming](docs/05-tokens-and-theming.md) | Fluid scales, the 21-variable contract, and dark mode |
|
|
135
|
-
| [06. Utilities Reference](docs/06-utilities.md) | Generated atomic classes, directional padding, and visibility |
|
|
136
|
-
| [07. Components & Layouts](docs/07-components-and-layouts.md) | Shipped blocks, cards, and page layout templates |
|
|
137
|
-
| [08. Recipes & Patterns](docs/08-recipes.md) | Worked examples: pricing card, responsive docs shell, dialog |
|
|
138
|
-
| [09. Migration from v1](docs/09-migration-from-v1.md) | Upgrading from older unnumbered stylesheets |
|
|
139
|
-
| [DESIGN.md](DESIGN.md) | Golden UI invariants, card containment laws, and defect prevention |
|
|
153
|
+
Fractalstyler2 includes a native MCP server (`fractalstyler2-mcp`) providing 7 tools for AI coding agents:
|
|
154
|
+
- `compile_fractals`: Compiles a SASS snippet to CSS, for the rare declaration that genuinely does not compose.
|
|
155
|
+
- `get_design_tokens`: Structured token queries by category.
|
|
156
|
+
- `snap_to_tokens`: Snaps raw pixel measurements to nearest token steps.
|
|
157
|
+
- `css_to_fractals`: Converts raw CSS declarations to idiomatic markup classes.
|
|
158
|
+
- `validate_recipe`: Lints snippets against design system rules.
|
|
140
159
|
|
|
141
160
|
---
|
|
142
161
|
|
|
143
162
|
## License
|
|
144
163
|
|
|
145
|
-
MIT
|
|
164
|
+
MIT © [Fractal Mandala](https://github.com/fractalmandala)
|
package/dist/cli.js
CHANGED
|
@@ -3,22 +3,45 @@ import { existsSync, mkdirSync, readdirSync, copyFileSync } from 'node:fs';
|
|
|
3
3
|
import { dirname, join, resolve, relative } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import process from 'node:process';
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Where the scaffoldable partials live.
|
|
8
|
+
*
|
|
9
|
+
* These ARE the published stylesheet — `svelte-package` copies
|
|
10
|
+
* src/lib/styles into dist/styles, so `init` hands over the same files the
|
|
11
|
+
* package imports. There is deliberately no separate templates/ copy to
|
|
12
|
+
* drift out of sync.
|
|
13
|
+
*
|
|
14
|
+
* Resolves for both layouts: dist/cli.js -> dist/styles, and the in-repo
|
|
15
|
+
* src/lib/cli.ts -> src/lib/styles.
|
|
16
|
+
*/
|
|
17
|
+
function getStylesDir() {
|
|
7
18
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
8
19
|
const candidates = [
|
|
9
|
-
join(HERE, '
|
|
10
|
-
join(HERE, '..', '
|
|
11
|
-
join(HERE, '
|
|
20
|
+
join(HERE, 'styles'),
|
|
21
|
+
join(HERE, '..', 'styles'),
|
|
22
|
+
join(HERE, '..', 'lib', 'styles')
|
|
12
23
|
];
|
|
13
24
|
for (const candidate of candidates) {
|
|
14
|
-
|
|
25
|
+
// index.sass is the marker: an empty or unrelated directory is not it.
|
|
26
|
+
if (existsSync(join(candidate, 'index.sass')))
|
|
15
27
|
return candidate;
|
|
16
28
|
}
|
|
17
|
-
throw new Error(`
|
|
29
|
+
throw new Error(`fractalstyler2 stylesheet source not found. Searched in:\n ${candidates.join('\n ')}`);
|
|
30
|
+
}
|
|
31
|
+
/** The compiled CSS distribution, emitted by scripts/build-css.js at pack time. */
|
|
32
|
+
function getCssDir() {
|
|
33
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
34
|
+
const candidates = [join(HERE, 'css'), join(HERE, '..', 'css'), join(HERE, '..', 'dist', 'css')];
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
if (existsSync(join(candidate, 'fractalstyler.css')))
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`fractalstyler2 CSS distribution not found. Searched in:\n ${candidates.join('\n ')}\n` +
|
|
40
|
+
`If you are working from a clone, run \`npm run css\` first.`);
|
|
18
41
|
}
|
|
19
42
|
function printUsage() {
|
|
20
43
|
console.log(`
|
|
21
|
-
fractalstyler2 —
|
|
44
|
+
fractalstyler2 — composition styling system scaffolder & MCP server
|
|
22
45
|
|
|
23
46
|
Usage:
|
|
24
47
|
npx fractalstyler2 init [dest] [options]
|
|
@@ -27,31 +50,41 @@ Usage:
|
|
|
27
50
|
npx fractalstyler2 mcp:export [dest]
|
|
28
51
|
|
|
29
52
|
Commands:
|
|
30
|
-
init [dest] Scaffold
|
|
53
|
+
init [dest] Scaffold the design system into a target directory
|
|
54
|
+
(default: src/lib/styles)
|
|
31
55
|
mcp Start the Model Context Protocol (MCP) server for OpenDesign, Claude, etc.
|
|
32
56
|
mcp:install Automatically install MCP schemas & config into Antigravity & OpenCode
|
|
33
57
|
mcp:export [dest] Export static MCP tool schema JSON files to target directory (default: .mcp)
|
|
34
58
|
|
|
35
59
|
Options:
|
|
60
|
+
--css Scaffold the compiled stylesheet (no toolchain needed)
|
|
61
|
+
--sass Scaffold the editable SASS partials (the default)
|
|
36
62
|
-f, --force Overwrite files if they already exist
|
|
37
63
|
-h, --help Show this help message
|
|
38
64
|
|
|
39
65
|
Examples:
|
|
40
66
|
npx fractalstyler2 init
|
|
67
|
+
npx fractalstyler2 init --css
|
|
68
|
+
npx fractalstyler2 init src/styles --css
|
|
41
69
|
npx fractalstyler2 mcp
|
|
42
70
|
npx fractalstyler2 mcp:install
|
|
43
71
|
npx fractalstyler2 mcp:export ~/.gemini/antigravity/mcp/fractalstyler2
|
|
44
72
|
`);
|
|
45
73
|
}
|
|
46
|
-
function init(destArg, force) {
|
|
47
|
-
const dest = destArg ?? 'src/lib/styles';
|
|
74
|
+
function init(destArg, force, flavour) {
|
|
75
|
+
const dest = destArg ?? (flavour === 'css' ? 'src/styles' : 'src/lib/styles');
|
|
48
76
|
const cwd = process.cwd();
|
|
49
77
|
const targetDir = resolve(cwd, dest);
|
|
50
78
|
console.log(`\n▲ fractalstyler2 init\n`);
|
|
51
|
-
console.log(`Scaffolding
|
|
79
|
+
console.log(`Scaffolding ${flavour === 'css' ? 'compiled CSS' : 'SASS'} into: ${dest}\n`);
|
|
52
80
|
mkdirSync(targetDir, { recursive: true });
|
|
53
|
-
const
|
|
54
|
-
const files =
|
|
81
|
+
const stylesDir = flavour === 'css' ? getCssDir() : getStylesDir();
|
|
82
|
+
const files = flavour === 'css'
|
|
83
|
+
? ['fractalstyler.css', 'fractalstyler.min.css']
|
|
84
|
+
: readdirSync(stylesDir).filter((f) => f.endsWith('.sass'));
|
|
85
|
+
// The L4 shells are only half a definition without their markup, so the
|
|
86
|
+
// contract ships alongside the stylesheet in both flavours.
|
|
87
|
+
const markups = join(getStylesDir(), 'canonical-markups.md');
|
|
55
88
|
let created = 0;
|
|
56
89
|
let overwritten = 0;
|
|
57
90
|
let skipped = 0;
|
|
@@ -60,7 +93,7 @@ function init(destArg, force) {
|
|
|
60
93
|
const relPath = relative(cwd, targetFile);
|
|
61
94
|
if (existsSync(targetFile)) {
|
|
62
95
|
if (force) {
|
|
63
|
-
copyFileSync(join(
|
|
96
|
+
copyFileSync(join(stylesDir, file), targetFile);
|
|
64
97
|
console.log(` \x1b[33moverwrite\x1b[0m ${relPath}`);
|
|
65
98
|
overwritten++;
|
|
66
99
|
}
|
|
@@ -70,33 +103,80 @@ function init(destArg, force) {
|
|
|
70
103
|
}
|
|
71
104
|
}
|
|
72
105
|
else {
|
|
73
|
-
copyFileSync(join(
|
|
106
|
+
copyFileSync(join(stylesDir, file), targetFile);
|
|
74
107
|
console.log(` \x1b[32mcreate\x1b[0m ${relPath}`);
|
|
75
108
|
created++;
|
|
76
109
|
}
|
|
77
110
|
}
|
|
111
|
+
if (existsSync(markups)) {
|
|
112
|
+
const target = join(targetDir, 'canonical-markups.md');
|
|
113
|
+
if (!existsSync(target) || force) {
|
|
114
|
+
copyFileSync(markups, target);
|
|
115
|
+
console.log(` \x1b[32mcreate\x1b[0m ${relative(cwd, target)}`);
|
|
116
|
+
created++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
78
119
|
console.log(`\nDone: ${created} created, ${overwritten} overwritten, ${skipped} skipped.\n`);
|
|
120
|
+
if (flavour === 'css') {
|
|
121
|
+
const cssPath = `${dest}/fractalstyler.css`;
|
|
122
|
+
console.log(`Next steps:
|
|
123
|
+
1. Link the stylesheet once, in your document head:
|
|
124
|
+
<link rel="stylesheet" href="/${cssPath}" />
|
|
125
|
+
|
|
126
|
+
Or import it, if you have a bundler:
|
|
127
|
+
import '${cssPath}';
|
|
128
|
+
|
|
129
|
+
2. Compose in markup. The registry is the API — .box, .row and .grid
|
|
130
|
+
carry most layouts, with .gap-* / .pad-* for space and .surface /
|
|
131
|
+
.border for the dress:
|
|
132
|
+
<div class="row ycenter xbetween gap-sm pad-md surface border">
|
|
133
|
+
<span class="text-md weight-600">Title</span>
|
|
134
|
+
<button class="button primary">Continue</button>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
3. Themes and presets need no JavaScript to work:
|
|
138
|
+
<html class="theme-night-dark" data-mode="dark" data-shape="sharp">
|
|
139
|
+
|
|
140
|
+
To let people change them at runtime, and to avoid a flash of the
|
|
141
|
+
wrong theme on load:
|
|
142
|
+
import { initPresets, setPreset, getPresetScript } from 'fractalstyler2/presets';
|
|
143
|
+
|
|
144
|
+
4. For page and application shells, copy the structures in the scaffolded
|
|
145
|
+
canonical-markups.md verbatim. The responsive behaviour — rails that
|
|
146
|
+
retract, drawers that open — follows from the markup, so an
|
|
147
|
+
approximation of it will not behave.
|
|
148
|
+
|
|
149
|
+
5. Before writing any custom CSS, check docs/13-cookbook.md. Nearly every
|
|
150
|
+
common pattern is already composable.
|
|
151
|
+
`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
79
154
|
const importPath = dest === 'src/lib/styles' ? '$lib/styles/index.sass' : `${dest}/index.sass`;
|
|
80
|
-
const
|
|
155
|
+
const ownPath = dest === 'src/lib/styles' ? '$lib/styles/_08_own.sass' : `${dest}/_08_own.sass`;
|
|
81
156
|
console.log(`Next steps:
|
|
82
157
|
1. Import the stylesheet once globally (e.g. in src/routes/+layout.svelte):
|
|
83
158
|
<script>
|
|
84
159
|
import '${importPath}';
|
|
85
160
|
</script>
|
|
86
161
|
|
|
87
|
-
2. Compose
|
|
88
|
-
|
|
89
|
-
|
|
162
|
+
2. Compose in markup. The registry is the API — .box, .row and .grid
|
|
163
|
+
carry most layouts, with .gap-* / .pad-* for space and .surface /
|
|
164
|
+
.border for the dress:
|
|
165
|
+
<div class="row ycenter xbetween gap-sm pad-md surface border">
|
|
166
|
+
<span class="text-md weight-600">Title</span>
|
|
167
|
+
<button class="button primary">Continue</button>
|
|
168
|
+
</div>
|
|
90
169
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
170
|
+
3. Before writing any custom class, check docs/13-cookbook.md — nearly
|
|
171
|
+
every common pattern is already composable. Keep
|
|
172
|
+
${ownPath}
|
|
173
|
+
for what genuinely is not (third-party widget overrides, mostly).
|
|
95
174
|
`);
|
|
96
175
|
}
|
|
97
176
|
const args = process.argv.slice(2);
|
|
98
177
|
const showHelp = args.includes('-h') || args.includes('--help');
|
|
99
178
|
const force = args.includes('-f') || args.includes('--force');
|
|
179
|
+
const flavour = args.includes('--css') ? 'css' : 'sass';
|
|
100
180
|
const positional = args.filter((a) => !a.startsWith('-'));
|
|
101
181
|
const command = positional[0];
|
|
102
182
|
const VALID_COMMANDS = ['init', 'mcp', 'mcp:export', 'mcp:install'];
|
|
@@ -143,9 +223,9 @@ else if (command === 'mcp:install') {
|
|
|
143
223
|
});
|
|
144
224
|
}
|
|
145
225
|
else if (command === 'init') {
|
|
146
|
-
init(positional[1], force);
|
|
226
|
+
init(positional[1], force, flavour);
|
|
147
227
|
}
|
|
148
228
|
else {
|
|
149
229
|
// Default to init if no subcommand given (e.g., `npx fractalstyler2`)
|
|
150
|
-
init(positional[0], force);
|
|
230
|
+
init(positional[0], force, flavour);
|
|
151
231
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export interface AccordionCtx {
|
|
3
|
+
/** Called by an item when it opens, so the group can close the others. */
|
|
4
|
+
claim: (id: symbol) => void;
|
|
5
|
+
register: (id: symbol, close: () => void) => () => void;
|
|
6
|
+
}
|
|
7
|
+
export const ACCORDION = Symbol('fs2.accordion');
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { setContext, type Snippet } from 'svelte';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Accordion group, from canonical-markups.md §3.
|
|
15
|
+
*
|
|
16
|
+
* `single` is the "accordion parent that regulates what closes when what
|
|
17
|
+
* opens" the introduction describes: opening one item closes its siblings.
|
|
18
|
+
* Default is independent items, which is what most navigation trees want.
|
|
19
|
+
*/
|
|
20
|
+
interface Props {
|
|
21
|
+
children: Snippet;
|
|
22
|
+
/** Only one item open at a time. */
|
|
23
|
+
single?: boolean;
|
|
24
|
+
class?: string;
|
|
25
|
+
}
|
|
26
|
+
let { children, single = false, class: className = '' }: Props = $props();
|
|
27
|
+
|
|
28
|
+
const items = new Map<symbol, () => void>();
|
|
29
|
+
|
|
30
|
+
setContext<AccordionCtx>(ACCORDION, {
|
|
31
|
+
register(id, close) {
|
|
32
|
+
items.set(id, close);
|
|
33
|
+
return () => items.delete(id);
|
|
34
|
+
},
|
|
35
|
+
claim(id) {
|
|
36
|
+
if (!single) return;
|
|
37
|
+
for (const [other, close] of items) if (other !== id) close();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div class="accordion {className}">
|
|
43
|
+
{@render children()}
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface AccordionCtx {
|
|
2
|
+
/** Called by an item when it opens, so the group can close the others. */
|
|
3
|
+
claim: (id: symbol) => void;
|
|
4
|
+
register: (id: symbol, close: () => void) => () => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const ACCORDION: unique symbol;
|
|
7
|
+
import { type Snippet } from 'svelte';
|
|
8
|
+
/**
|
|
9
|
+
* Accordion group, from canonical-markups.md §3.
|
|
10
|
+
*
|
|
11
|
+
* `single` is the "accordion parent that regulates what closes when what
|
|
12
|
+
* opens" the introduction describes: opening one item closes its siblings.
|
|
13
|
+
* Default is independent items, which is what most navigation trees want.
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
children: Snippet;
|
|
17
|
+
/** Only one item open at a time. */
|
|
18
|
+
single?: boolean;
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
declare const Accordion: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type Accordion = ReturnType<typeof Accordion>;
|
|
23
|
+
export default Accordion;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, onMount, type Snippet } from 'svelte';
|
|
3
|
+
import { ACCORDION, type AccordionCtx } from './Accordion.svelte';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One disclosure, from canonical-markups.md §3.
|
|
7
|
+
*
|
|
8
|
+
* Emits all three parts of the contract: `.accordion-item` carries `.open`,
|
|
9
|
+
* `.accordion-content` is the grid that animates, and `.accordion-panel` is
|
|
10
|
+
* the element that actually clips. The panel is the easiest to omit by hand
|
|
11
|
+
* and the failure is silent — the content simply never collapses.
|
|
12
|
+
*
|
|
13
|
+
* State stays on native attributes: the trigger carries `aria-expanded`.
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
/** The always-visible trigger row. */
|
|
17
|
+
trigger: Snippet;
|
|
18
|
+
children: Snippet;
|
|
19
|
+
open?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
}
|
|
22
|
+
let { trigger, children, open = $bindable(false), class: className = '' }: Props = $props();
|
|
23
|
+
|
|
24
|
+
const id = Symbol();
|
|
25
|
+
const group = getContext<AccordionCtx | undefined>(ACCORDION);
|
|
26
|
+
|
|
27
|
+
onMount(() => group?.register(id, () => (open = false)));
|
|
28
|
+
|
|
29
|
+
function toggle(): void {
|
|
30
|
+
open = !open;
|
|
31
|
+
if (open) group?.claim(id);
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="accordion-item {className}" class:open>
|
|
36
|
+
<button type="button" class="accordion-trigger" aria-expanded={open} onclick={toggle}>
|
|
37
|
+
{@render trigger()}
|
|
38
|
+
</button>
|
|
39
|
+
<div class="accordion-content">
|
|
40
|
+
<div class="accordion-panel box gap-2xs pad-x-xs">
|
|
41
|
+
{@render children()}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
/**
|
|
3
|
+
* One disclosure, from canonical-markups.md §3.
|
|
4
|
+
*
|
|
5
|
+
* Emits all three parts of the contract: `.accordion-item` carries `.open`,
|
|
6
|
+
* `.accordion-content` is the grid that animates, and `.accordion-panel` is
|
|
7
|
+
* the element that actually clips. The panel is the easiest to omit by hand
|
|
8
|
+
* and the failure is silent — the content simply never collapses.
|
|
9
|
+
*
|
|
10
|
+
* State stays on native attributes: the trigger carries `aria-expanded`.
|
|
11
|
+
*/
|
|
12
|
+
interface Props {
|
|
13
|
+
/** The always-visible trigger row. */
|
|
14
|
+
trigger: Snippet;
|
|
15
|
+
children: Snippet;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const AccordionItem: import("svelte").Component<Props, {}, "open">;
|
|
20
|
+
type AccordionItem = ReturnType<typeof AccordionItem>;
|
|
21
|
+
export default AccordionItem;
|