fractalstyler2 0.0.1 → 0.0.2
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 +81 -65
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +108 -0
- package/dist/{components → styles}/_blocks.sass +1 -1
- package/dist/{fractals/index.sass → styles/_fractals.sass} +3 -1
- package/dist/{components → styles}/_layouts.sass +1 -1
- package/dist/styles/index.sass +11 -10
- package/package.json +12 -5
- package/templates/_atoms.sass +155 -0
- package/templates/_base.sass +43 -0
- package/templates/_blocks.sass +88 -0
- package/templates/_config.sass +78 -0
- package/templates/_fractals.sass +13 -0
- package/templates/_layouts.sass +108 -0
- package/templates/_molecules.sass +93 -0
- package/templates/_responsive.sass +43 -0
- package/templates/_tokens.sass +127 -0
- package/templates/_utilities.sass +125 -0
- package/templates/index.sass +20 -0
- /package/dist/{fractals → styles}/_atoms.sass +0 -0
- /package/dist/{fractals → styles}/_base.sass +0 -0
- /package/dist/{fractals → styles}/_config.sass +0 -0
- /package/dist/{fractals → styles}/_molecules.sass +0 -0
- /package/dist/{fractals → styles}/_responsive.sass +0 -0
- /package/dist/{fractals → styles}/_tokens.sass +0 -0
- /package/dist/{fractals → styles}/_utilities.sass +0 -0
package/README.md
CHANGED
|
@@ -3,62 +3,35 @@
|
|
|
3
3
|
A fractal-composition styling system for SvelteKit. **Every style unit is a SASS
|
|
4
4
|
mixin ("a fractal"), and components and layouts are recipes of smaller fractals.**
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
product is framework-agnostic SASS surfaced through a SvelteKit library; the JS
|
|
8
|
-
runtime is just a tiny mode helper.
|
|
6
|
+
Use it either as a **shadcn-style scaffolder** (copying editable SASS into your project's `src/lib/styles`) or as a **direct package dependency**.
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
---
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
fractals. They form four self-similar tiers — each tier is "a recipe of the one
|
|
14
|
-
below":
|
|
10
|
+
## Quickstart (shadcn-style Scaffolding)
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
| --- | --- | --- |
|
|
18
|
-
| **Config** | scales-as-data + resolvers | `space()`, `radius()`, `align()`, `$breakpoints` |
|
|
19
|
-
| **Atoms** | one decision each | `+box` `+row` `+gap` `+pad` `+border` `+radius` `+bg` `+ink` `+type` `+sticky` |
|
|
20
|
-
| **Molecules** | graphs of atoms | `+stack` `+cluster` `+cover` `+frame` `+surface` `+cols` `+auto-grid` |
|
|
21
|
-
| **Components / Layouts** | graphs of molecules | `.card` `.button` · `.grid-3` `.hero` `.holy-grail` `.docs` `.app-shell` |
|
|
22
|
-
|
|
23
|
-
The one idea that makes it click:
|
|
24
|
-
|
|
25
|
-
> **A utility class and a semantic component are the same fractal, consumed two ways.**
|
|
26
|
-
> `.gap-m { +gap(m) }` binds the fractal to markup; `.hero { +cover; +stack(m) }`
|
|
27
|
-
> composes it into a component. One vocabulary, you pick where each is used.
|
|
28
|
-
|
|
29
|
-
## Install
|
|
12
|
+
Scaffold the complete, customizable SASS design system directly into your project's `src/lib/styles`:
|
|
30
13
|
|
|
31
14
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
15
|
+
# Scaffold into src/lib/styles
|
|
16
|
+
npx fractalstyler2 init
|
|
34
17
|
|
|
35
|
-
|
|
18
|
+
# Or using pnpm
|
|
19
|
+
pnpm dlx fractalstyler2 init
|
|
20
|
+
```
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
blocks, layouts). Import once, globally (e.g. in your root `+layout.svelte`):
|
|
22
|
+
### Next Steps in Your Project
|
|
39
23
|
|
|
24
|
+
**1. Import the stylesheet in your root `+layout.svelte`:**
|
|
40
25
|
```svelte
|
|
41
26
|
<script>
|
|
42
|
-
import '
|
|
27
|
+
import '$lib/styles/index.sass';
|
|
43
28
|
</script>
|
|
44
29
|
```
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```svelte
|
|
49
|
-
<div class="grid-3">
|
|
50
|
-
<article class="card"><h3 class="text-lg">Composable</h3></article>
|
|
51
|
-
<article class="card" data-elevated><h3 class="text-lg">Elevated</h3></article>
|
|
52
|
-
<article class="card"><h3 class="text-lg">Semantic</h3></article>
|
|
53
|
-
</div>
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**2 — Compose your own components from the fractals.** Import the pure API
|
|
57
|
-
(emits nothing until called) inside any component's SASS:
|
|
58
|
-
|
|
31
|
+
**2. Compose your components using the fractals:**
|
|
59
32
|
```svelte
|
|
60
33
|
<style lang="sass">
|
|
61
|
-
@use '
|
|
34
|
+
@use '$lib/styles/fractals' as *
|
|
62
35
|
|
|
63
36
|
.pricing-card
|
|
64
37
|
+surface(surface, l, 16, md) // bg + border + radius + pad + shadow
|
|
@@ -67,48 +40,91 @@ Then write thin, semantic markup:
|
|
|
67
40
|
</style>
|
|
68
41
|
```
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Alternative: Direct Package Dependency
|
|
72
46
|
|
|
73
|
-
|
|
47
|
+
If you prefer importing from `node_modules` instead of scaffolding local files:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install fractalstyler2
|
|
51
|
+
# or
|
|
52
|
+
pnpm add fractalstyler2
|
|
53
|
+
```
|
|
74
54
|
|
|
55
|
+
Your app also needs `sass`:
|
|
56
|
+
```bash
|
|
57
|
+
npm install -D sass
|
|
58
|
+
# or
|
|
59
|
+
pnpm add -D sass
|
|
75
60
|
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
61
|
+
|
|
62
|
+
### Use from `node_modules`:
|
|
63
|
+
|
|
64
|
+
```svelte
|
|
65
|
+
<script>
|
|
66
|
+
import 'fractalstyler2/styles';
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<style lang="sass">
|
|
70
|
+
@use 'fractalstyler2/fractals' as *
|
|
71
|
+
|
|
72
|
+
.card
|
|
73
|
+
+surface(surface, s, 12)
|
|
74
|
+
+stack(s)
|
|
75
|
+
</style>
|
|
81
76
|
```
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
molecules, utilities), `src/lib/components/` (blocks, layouts),
|
|
85
|
-
`src/lib/styles/` (the emit entry).
|
|
78
|
+
---
|
|
86
79
|
|
|
87
|
-
##
|
|
80
|
+
## The Model
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
A fractal is a mixin: a tiny reusable styling decision that composes other
|
|
83
|
+
fractals. They form four self-similar tiers — each tier is "a recipe of the one
|
|
84
|
+
below":
|
|
85
|
+
|
|
86
|
+
| Tier | What | Examples |
|
|
87
|
+
| --- | --- | --- |
|
|
88
|
+
| **Config** | scales-as-data + resolvers | `space()`, `radius()`, `align()`, `$breakpoints` |
|
|
89
|
+
| **Atoms** | one decision each | `+box` `+row` `+gap` `+pad` `+border` `+radius` `+bg` `+ink` `+type` `+sticky` |
|
|
90
|
+
| **Molecules** | graphs of atoms | `+stack` `+cluster` `+cover` `+frame` `+surface` `+cols` `+auto-grid` |
|
|
91
|
+
| **Components / Layouts** | graphs of molecules | `.card` `.button` · `.grid-3` `.hero` `.holy-grail` `.docs` `.app-shell` |
|
|
92
|
+
|
|
93
|
+
The one idea that makes it click:
|
|
94
|
+
|
|
95
|
+
> **A utility class and a semantic component are the same fractal, consumed two ways.**
|
|
96
|
+
> `.gap-m { +gap(m) }` binds the fractal to markup; `.hero { +cover; +stack(m) }`
|
|
97
|
+
> composes it into a component. One vocabulary, you pick where each is used.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Color Mode
|
|
102
|
+
|
|
103
|
+
Light is the marker-free default (SSR-safe). Dark comes from `prefers-color-scheme` and from an explicit `data-mode="dark"` on `<html>`:
|
|
91
104
|
|
|
92
105
|
```js
|
|
93
106
|
import { toggleMode, setMode } from 'fractalstyler2';
|
|
94
107
|
toggleMode(); // flip light/dark
|
|
95
|
-
setMode('dark'); // force
|
|
108
|
+
setMode('dark'); // force mode
|
|
96
109
|
```
|
|
97
110
|
|
|
98
|
-
|
|
111
|
+
---
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
`.holy-grail`, `.docs`, `.app-shell` — each a few fractal calls in
|
|
102
|
-
`src/lib/components/_layouts.sass`.
|
|
103
|
-
|
|
104
|
-
## Develop
|
|
113
|
+
## CLI Options
|
|
105
114
|
|
|
106
115
|
```bash
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
fractalstyler2 init [dest] [options]
|
|
117
|
+
|
|
118
|
+
Arguments:
|
|
119
|
+
dest Target directory for SASS partials (default: src/lib/styles)
|
|
120
|
+
|
|
121
|
+
Options:
|
|
122
|
+
-f, --force Overwrite files if they already exist
|
|
123
|
+
-h, --help Show help message
|
|
110
124
|
```
|
|
111
125
|
|
|
126
|
+
---
|
|
127
|
+
|
|
112
128
|
## License
|
|
113
129
|
|
|
114
130
|
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, copyFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve, relative } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
function getTemplatesDir() {
|
|
7
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const candidates = [
|
|
9
|
+
join(HERE, '..', 'templates'),
|
|
10
|
+
join(HERE, '..', '..', 'templates'),
|
|
11
|
+
join(HERE, 'templates')
|
|
12
|
+
];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
if (existsSync(candidate))
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Templates directory not found. Searched in: ${candidates.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
function printUsage() {
|
|
20
|
+
console.log(`
|
|
21
|
+
fractalstyler2 — SASS fractal design system scaffolder
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
npx fractalstyler2 init [dest] [options]
|
|
25
|
+
|
|
26
|
+
Arguments:
|
|
27
|
+
dest Target directory for SASS partials (default: src/lib/styles)
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
-f, --force Overwrite files if they already exist
|
|
31
|
+
-h, --help Show this help message
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
npx fractalstyler2 init
|
|
35
|
+
npx fractalstyler2 init src/lib/styles --force
|
|
36
|
+
npx fractalstyler2 init src/styles
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
function init(destArg, force) {
|
|
40
|
+
const dest = destArg ?? 'src/lib/styles';
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
const targetDir = resolve(cwd, dest);
|
|
43
|
+
console.log(`\n▲ fractalstyler2 init\n`);
|
|
44
|
+
console.log(`Scaffolding SASS design system into: ${dest}\n`);
|
|
45
|
+
mkdirSync(targetDir, { recursive: true });
|
|
46
|
+
const templatesDir = getTemplatesDir();
|
|
47
|
+
const files = readdirSync(templatesDir).filter((f) => f.endsWith('.sass'));
|
|
48
|
+
let created = 0;
|
|
49
|
+
let overwritten = 0;
|
|
50
|
+
let skipped = 0;
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const targetFile = join(targetDir, file);
|
|
53
|
+
const relPath = relative(cwd, targetFile);
|
|
54
|
+
if (existsSync(targetFile)) {
|
|
55
|
+
if (force) {
|
|
56
|
+
copyFileSync(join(templatesDir, file), targetFile);
|
|
57
|
+
console.log(` \x1b[33moverwrite\x1b[0m ${relPath}`);
|
|
58
|
+
overwritten++;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log(` \x1b[90mskip\x1b[0m ${relPath} (already exists, use --force to overwrite)`);
|
|
62
|
+
skipped++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
copyFileSync(join(templatesDir, file), targetFile);
|
|
67
|
+
console.log(` \x1b[32mcreate\x1b[0m ${relPath}`);
|
|
68
|
+
created++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
console.log(`\nDone: ${created} created, ${overwritten} overwritten, ${skipped} skipped.\n`);
|
|
72
|
+
const importPath = dest === 'src/lib/styles' ? '$lib/styles/index.sass' : `${dest}/index.sass`;
|
|
73
|
+
const fractalsPath = dest === 'src/lib/styles' ? '$lib/styles/fractals' : `${dest}/fractals`;
|
|
74
|
+
console.log(`Next steps:
|
|
75
|
+
1. Import the stylesheet once globally (e.g. in src/routes/+layout.svelte):
|
|
76
|
+
<script>
|
|
77
|
+
import '${importPath}';
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
2. Compose your components with fractal mixins:
|
|
81
|
+
<style lang="sass">
|
|
82
|
+
@use '${fractalsPath}' as *
|
|
83
|
+
|
|
84
|
+
.card
|
|
85
|
+
+surface(surface, s, 12)
|
|
86
|
+
+stack(s)
|
|
87
|
+
</style>
|
|
88
|
+
`);
|
|
89
|
+
}
|
|
90
|
+
const args = process.argv.slice(2);
|
|
91
|
+
const showHelp = args.includes('-h') || args.includes('--help');
|
|
92
|
+
const force = args.includes('-f') || args.includes('--force');
|
|
93
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
94
|
+
const command = positional[0];
|
|
95
|
+
if (showHelp || (command && command !== 'init')) {
|
|
96
|
+
if (command && command !== 'init') {
|
|
97
|
+
console.error(`Unknown command: ${command}`);
|
|
98
|
+
}
|
|
99
|
+
printUsage();
|
|
100
|
+
process.exit(command && command !== 'init' ? 1 : 0);
|
|
101
|
+
}
|
|
102
|
+
else if (command === 'init') {
|
|
103
|
+
init(positional[1], force);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
// Default to init if no subcommand given (e.g., `npx fractalstyler2`)
|
|
107
|
+
init(positional[0], force);
|
|
108
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// =============================================================================
|
|
2
2
|
// FRACTALS — the pure API barrel. Emits NO css on its own.
|
|
3
3
|
// -----------------------------------------------------------------------------
|
|
4
|
-
// In any component partial
|
|
4
|
+
// In any component partial or Svelte component:
|
|
5
|
+
// @use '$lib/styles/fractals' as *
|
|
6
|
+
// (or @use './fractals' as *)
|
|
5
7
|
// then call fractals freely: +surface(...) +stack(...) space(m) ...
|
|
6
8
|
// =============================================================================
|
|
7
9
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// hero, a holy-grail, a docs template. Each is a short composition of fractals.
|
|
5
5
|
// =============================================================================
|
|
6
6
|
|
|
7
|
-
@use '
|
|
7
|
+
@use 'fractals' as *
|
|
8
8
|
|
|
9
9
|
// 1. Responsive grid of 3 columns — 1 → 2 → 3 across breakpoints.
|
|
10
10
|
.grid-3
|
package/dist/styles/index.sass
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
// STYLES ENTRY — the full emitted stylesheet in cascade order.
|
|
3
3
|
// -----------------------------------------------------------------------------
|
|
4
4
|
// 1. tokens the CSS custom properties (only literal values)
|
|
5
|
-
// 2.
|
|
6
|
-
// 3.
|
|
7
|
-
// 4.
|
|
5
|
+
// 2. base fluid reset
|
|
6
|
+
// 3. utilities atom fractals projected to markup classes (optional layer)
|
|
7
|
+
// 4. blocks components composed from fractals
|
|
8
|
+
// 5. layouts page templates composed from fractals
|
|
8
9
|
//
|
|
9
|
-
// Consume from a SvelteKit app: import '
|
|
10
|
-
// or
|
|
10
|
+
// Consume from a SvelteKit app: import '$lib/styles/index.sass'
|
|
11
|
+
// or from package: import 'fractalstyler2/styles'
|
|
11
12
|
// The fractal mixins/functions themselves emit nothing until called, so output
|
|
12
13
|
// contains only what you use.
|
|
13
14
|
// =============================================================================
|
|
14
15
|
|
|
15
|
-
@use '
|
|
16
|
-
@use '
|
|
17
|
-
@use '
|
|
18
|
-
@use '
|
|
19
|
-
@use '
|
|
16
|
+
@use './tokens'
|
|
17
|
+
@use './base'
|
|
18
|
+
@use './utilities'
|
|
19
|
+
@use './blocks'
|
|
20
|
+
@use './layouts'
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fractalstyler2",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "A fractal-composition styling system for SvelteKit — every style unit is a SASS mixin, and components and layouts are recipes of smaller fractals.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fractalstyler2": "./dist/cli.js",
|
|
7
|
+
"fractalstyler": "./dist/cli.js"
|
|
8
|
+
},
|
|
5
9
|
"scripts": {
|
|
6
10
|
"dev": "vite dev",
|
|
7
11
|
"build": "vite build && npm run prepack",
|
|
8
12
|
"preview": "vite preview",
|
|
9
13
|
"prepare": "svelte-kit sync || echo ''",
|
|
10
|
-
"prepack": "svelte-kit sync && svelte-package && publint",
|
|
14
|
+
"prepack": "svelte-kit sync && svelte-package && chmod +x dist/cli.js && publint",
|
|
11
15
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
12
16
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
15
19
|
"dist",
|
|
16
20
|
"!dist/**/*.test.*",
|
|
17
|
-
"!dist/**/*.spec.*"
|
|
21
|
+
"!dist/**/*.spec.*",
|
|
22
|
+
"templates"
|
|
18
23
|
],
|
|
19
24
|
"sideEffects": [
|
|
20
25
|
"**/*.css"
|
|
@@ -28,8 +33,9 @@
|
|
|
28
33
|
"svelte": "./dist/index.js"
|
|
29
34
|
},
|
|
30
35
|
"./styles": "./dist/styles/index.sass",
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
36
|
+
"./styles/*": "./dist/styles/*",
|
|
37
|
+
"./fractals": "./dist/styles/_fractals.sass",
|
|
38
|
+
"./tokens": "./dist/styles/_tokens.sass",
|
|
33
39
|
"./package.json": "./package.json"
|
|
34
40
|
},
|
|
35
41
|
"peerDependencies": {
|
|
@@ -40,6 +46,7 @@
|
|
|
40
46
|
"@sveltejs/kit": "^2.63.0",
|
|
41
47
|
"@sveltejs/package": "^2.5.8",
|
|
42
48
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
49
|
+
"@types/node": "^26.2.0",
|
|
43
50
|
"publint": "^0.3.21",
|
|
44
51
|
"sass": "^1.102.0",
|
|
45
52
|
"svelte": "^5.56.1",
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// ATOM FRACTALS — the indivisible units. One visual decision each.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Every atom is a mixin. Call it inside a class to compose, or let
|
|
5
|
+
// _utilities.sass project it to a utility class. Same fractal, two consumers.
|
|
6
|
+
// =============================================================================
|
|
7
|
+
|
|
8
|
+
@use 'config' as *
|
|
9
|
+
|
|
10
|
+
// --- Flow: the two axes of layout ------------------------------------------
|
|
11
|
+
// A box is a vertical stack, a row is horizontal. Optional semantic alignment:
|
|
12
|
+
// +box(center) → items centered on the cross axis
|
|
13
|
+
// +row(between, center) → spread on main axis, centered on cross axis
|
|
14
|
+
=box($x: null, $y: null)
|
|
15
|
+
display: flex
|
|
16
|
+
flex-direction: column
|
|
17
|
+
@if $x
|
|
18
|
+
align-items: align($x)
|
|
19
|
+
@if $y
|
|
20
|
+
justify-content: align($y)
|
|
21
|
+
|
|
22
|
+
=row($x: null, $y: null)
|
|
23
|
+
display: flex
|
|
24
|
+
flex-direction: row
|
|
25
|
+
@if $x
|
|
26
|
+
justify-content: align($x)
|
|
27
|
+
@if $y
|
|
28
|
+
align-items: align($y)
|
|
29
|
+
|
|
30
|
+
=wrap
|
|
31
|
+
flex-wrap: wrap
|
|
32
|
+
|
|
33
|
+
=grid($cols: 1)
|
|
34
|
+
display: grid
|
|
35
|
+
grid-template-columns: repeat($cols, minmax(0, 1fr))
|
|
36
|
+
|
|
37
|
+
// A grid that reflows on its own — no breakpoints, no counting columns.
|
|
38
|
+
=auto-grid($min: 15rem, $gap: s)
|
|
39
|
+
display: grid
|
|
40
|
+
grid-template-columns: repeat(auto-fit, minmax(min(#{$min}, 100%), 1fr))
|
|
41
|
+
gap: space($gap)
|
|
42
|
+
|
|
43
|
+
// Dead-center anything (grid is the one-liner that works for a single child).
|
|
44
|
+
=center
|
|
45
|
+
display: grid
|
|
46
|
+
place-items: center
|
|
47
|
+
|
|
48
|
+
// --- Spacing ---------------------------------------------------------------
|
|
49
|
+
=gap($v: s)
|
|
50
|
+
gap: space($v)
|
|
51
|
+
|
|
52
|
+
=pad($v: s)
|
|
53
|
+
padding: space($v)
|
|
54
|
+
|
|
55
|
+
=px($v: s)
|
|
56
|
+
padding-inline: space($v)
|
|
57
|
+
|
|
58
|
+
=py($v: s)
|
|
59
|
+
padding-block: space($v)
|
|
60
|
+
|
|
61
|
+
=mx-auto
|
|
62
|
+
margin-inline: auto
|
|
63
|
+
|
|
64
|
+
=my-auto
|
|
65
|
+
margin-block: auto
|
|
66
|
+
|
|
67
|
+
// --- Size ------------------------------------------------------------------
|
|
68
|
+
=w($v: 100%)
|
|
69
|
+
width: $v
|
|
70
|
+
|
|
71
|
+
=h($v: 100%)
|
|
72
|
+
height: $v
|
|
73
|
+
|
|
74
|
+
=full
|
|
75
|
+
width: 100%
|
|
76
|
+
height: 100%
|
|
77
|
+
|
|
78
|
+
=square($v)
|
|
79
|
+
width: $v
|
|
80
|
+
height: $v
|
|
81
|
+
|
|
82
|
+
=grow($n: 1)
|
|
83
|
+
flex-grow: $n
|
|
84
|
+
|
|
85
|
+
=shrink($n: 0)
|
|
86
|
+
flex-shrink: $n
|
|
87
|
+
|
|
88
|
+
=min0
|
|
89
|
+
min-width: 0
|
|
90
|
+
min-height: 0
|
|
91
|
+
|
|
92
|
+
// --- Surface & skin --------------------------------------------------------
|
|
93
|
+
=bg($role: surface)
|
|
94
|
+
background-color: surface($role)
|
|
95
|
+
|
|
96
|
+
=ink($role: primary)
|
|
97
|
+
color: ink($role)
|
|
98
|
+
|
|
99
|
+
=border($side: all, $color: var(--border))
|
|
100
|
+
@if $side == all
|
|
101
|
+
border: 1px solid $color
|
|
102
|
+
@else
|
|
103
|
+
border-#{$side}: 1px solid $color
|
|
104
|
+
|
|
105
|
+
=radius($v: 12)
|
|
106
|
+
border-radius: radius($v)
|
|
107
|
+
|
|
108
|
+
=shadow($v: md)
|
|
109
|
+
box-shadow: shadow($v)
|
|
110
|
+
|
|
111
|
+
// --- Position --------------------------------------------------------------
|
|
112
|
+
=relative
|
|
113
|
+
position: relative
|
|
114
|
+
|
|
115
|
+
=absolute($inset: null)
|
|
116
|
+
position: absolute
|
|
117
|
+
@if $inset
|
|
118
|
+
inset: $inset
|
|
119
|
+
|
|
120
|
+
=sticky($top: 0)
|
|
121
|
+
position: sticky
|
|
122
|
+
top: $top
|
|
123
|
+
|
|
124
|
+
=fill
|
|
125
|
+
position: absolute
|
|
126
|
+
inset: 0
|
|
127
|
+
|
|
128
|
+
// --- Type ------------------------------------------------------------------
|
|
129
|
+
=type($v)
|
|
130
|
+
font-size: text-size($v)
|
|
131
|
+
|
|
132
|
+
=weight($w: 500)
|
|
133
|
+
font-weight: $w
|
|
134
|
+
|
|
135
|
+
=leading($lh: 1.5)
|
|
136
|
+
line-height: $lh
|
|
137
|
+
|
|
138
|
+
=truncate
|
|
139
|
+
overflow: hidden
|
|
140
|
+
white-space: nowrap
|
|
141
|
+
text-overflow: ellipsis
|
|
142
|
+
|
|
143
|
+
=clamp-lines($n: 2)
|
|
144
|
+
display: -webkit-box
|
|
145
|
+
-webkit-line-clamp: $n
|
|
146
|
+
-webkit-box-orient: vertical
|
|
147
|
+
overflow: hidden
|
|
148
|
+
|
|
149
|
+
// --- Motion ----------------------------------------------------------------
|
|
150
|
+
=transition($props: all, $dur: 150ms, $ease: ease)
|
|
151
|
+
transition: $props $dur $ease
|
|
152
|
+
|
|
153
|
+
=ring($color: var(--ring))
|
|
154
|
+
outline: 2px solid $color
|
|
155
|
+
outline-offset: 1px
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// BASE — a minimal, opinionated reset so the tokens actually reach the page.
|
|
3
|
+
// Kept tiny: box model, sane media defaults, and wiring body to the palette.
|
|
4
|
+
// =============================================================================
|
|
5
|
+
|
|
6
|
+
*, *::before, *::after
|
|
7
|
+
box-sizing: border-box
|
|
8
|
+
|
|
9
|
+
html
|
|
10
|
+
-webkit-text-size-adjust: 100%
|
|
11
|
+
|
|
12
|
+
body
|
|
13
|
+
margin: 0
|
|
14
|
+
min-height: 100vh
|
|
15
|
+
font-family: var(--font-sans)
|
|
16
|
+
font-size: var(--text-md)
|
|
17
|
+
line-height: 1.5
|
|
18
|
+
color: var(--text-primary)
|
|
19
|
+
background-color: var(--bg)
|
|
20
|
+
-webkit-font-smoothing: antialiased
|
|
21
|
+
|
|
22
|
+
:where(h1, h2, h3, h4, h5, h6, p, figure, blockquote)
|
|
23
|
+
margin: 0
|
|
24
|
+
|
|
25
|
+
:where(h1, h2, h3, h4)
|
|
26
|
+
line-height: 1.15
|
|
27
|
+
text-wrap: balance
|
|
28
|
+
|
|
29
|
+
:where(img, picture, video, canvas, svg)
|
|
30
|
+
display: block
|
|
31
|
+
max-width: 100%
|
|
32
|
+
|
|
33
|
+
:where(a)
|
|
34
|
+
color: inherit
|
|
35
|
+
text-decoration-color: var(--border-strong)
|
|
36
|
+
|
|
37
|
+
:where(button, input, select, textarea)
|
|
38
|
+
font: inherit
|
|
39
|
+
color: inherit
|
|
40
|
+
|
|
41
|
+
:where(:focus-visible)
|
|
42
|
+
outline: 2px solid var(--ring)
|
|
43
|
+
outline-offset: 2px
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// BLOCKS — components as recipes of fractals. Note how little CSS is here:
|
|
3
|
+
// each block is a handful of fractal calls plus its own genuinely-unique lines.
|
|
4
|
+
// State is expressed with data-*/aria-*, never modifier classes.
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
@use 'fractals' as *
|
|
8
|
+
|
|
9
|
+
.card
|
|
10
|
+
+surface(surface, s, 12)
|
|
11
|
+
+stack(s)
|
|
12
|
+
&[data-elevated]
|
|
13
|
+
+shadow(md)
|
|
14
|
+
|
|
15
|
+
.panel
|
|
16
|
+
+surface(raised, m, 16)
|
|
17
|
+
+stack(m)
|
|
18
|
+
|
|
19
|
+
.badge
|
|
20
|
+
+row(center, center)
|
|
21
|
+
display: inline-flex
|
|
22
|
+
+px(2xs)
|
|
23
|
+
+type(xs)
|
|
24
|
+
+weight(500)
|
|
25
|
+
+radius(full)
|
|
26
|
+
+bg(raised)
|
|
27
|
+
+ink(secondary)
|
|
28
|
+
padding-block: 2px
|
|
29
|
+
|
|
30
|
+
.avatar
|
|
31
|
+
+center
|
|
32
|
+
+square(var(--avatar-size, 40px))
|
|
33
|
+
+radius(full)
|
|
34
|
+
+bg(raised)
|
|
35
|
+
+ink(secondary)
|
|
36
|
+
overflow: hidden
|
|
37
|
+
|
|
38
|
+
.input
|
|
39
|
+
display: block
|
|
40
|
+
+w(100%)
|
|
41
|
+
+px(2xs)
|
|
42
|
+
+type(md)
|
|
43
|
+
+leading(1.5)
|
|
44
|
+
+border
|
|
45
|
+
+radius(6)
|
|
46
|
+
+bg(bg)
|
|
47
|
+
+ink(primary)
|
|
48
|
+
padding-block: 8px
|
|
49
|
+
&:focus-visible
|
|
50
|
+
+ring
|
|
51
|
+
|
|
52
|
+
.button
|
|
53
|
+
+row(center, center)
|
|
54
|
+
+gap(2xs)
|
|
55
|
+
+px(s)
|
|
56
|
+
+radius(6)
|
|
57
|
+
+weight(500)
|
|
58
|
+
+transition(background-color)
|
|
59
|
+
cursor: pointer
|
|
60
|
+
padding-block: 8px
|
|
61
|
+
user-select: none
|
|
62
|
+
// Exceptions: variants live on data-variant, not in the class name.
|
|
63
|
+
&[data-variant='primary']
|
|
64
|
+
background: var(--theme)
|
|
65
|
+
color: var(--text-inverse)
|
|
66
|
+
&:hover
|
|
67
|
+
background: var(--theme-hover)
|
|
68
|
+
&[data-variant='ghost']
|
|
69
|
+
background: transparent
|
|
70
|
+
+ink(secondary)
|
|
71
|
+
&:hover
|
|
72
|
+
+bg(raised)
|
|
73
|
+
|
|
74
|
+
.divider
|
|
75
|
+
border: none
|
|
76
|
+
+border(top)
|
|
77
|
+
margin-block: space(s)
|
|
78
|
+
|
|
79
|
+
.kbd
|
|
80
|
+
display: inline-block
|
|
81
|
+
+px(3xs)
|
|
82
|
+
+type(xs)
|
|
83
|
+
+leading(1)
|
|
84
|
+
+radius(4)
|
|
85
|
+
+bg(raised)
|
|
86
|
+
border: 1px solid var(--border-strong)
|
|
87
|
+
font-family: var(--font-mono)
|
|
88
|
+
padding-block: 2px
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// CONFIG — the finite vocabulary every fractal draws from.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// Scales live here as Sass data; resolvers turn a keyword-or-raw value into the
|
|
5
|
+
// right token var() or unit. Fractals never hardcode a px or a token name —
|
|
6
|
+
// they route through space(), radius(), etc. so the system speaks one language.
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
@use 'sass:map'
|
|
10
|
+
@use 'sass:math'
|
|
11
|
+
@use 'sass:meta'
|
|
12
|
+
@use 'sass:list'
|
|
13
|
+
|
|
14
|
+
// --- Scales (mirror the custom properties in _tokens.sass) --------------------
|
|
15
|
+
$breakpoints: (sm: 640px, md: 768px, lg: 1024px, xl: 1240px)
|
|
16
|
+
|
|
17
|
+
$space-steps: 3xs 2xs xs s m l xl 2xl 3xl s-l
|
|
18
|
+
$radius-steps: 0 2 4 6 8 12 16 24 full
|
|
19
|
+
$text-steps: xs sm md lg xl 2xl 3xl 4xl
|
|
20
|
+
$shadow-steps: sm md lg
|
|
21
|
+
$surface-roles: bg surface raised
|
|
22
|
+
$ink-roles: primary secondary muted inverse
|
|
23
|
+
|
|
24
|
+
$align-map: (start: flex-start, end: flex-end, center: center, between: space-between, around: space-around, evenly: space-evenly, stretch: stretch, baseline: baseline)
|
|
25
|
+
|
|
26
|
+
// --- Resolvers ----------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
// space(m) → var(--space-m) | space(18) → 18px | space(2rem) → 2rem
|
|
29
|
+
@function space($v)
|
|
30
|
+
@if $v == null
|
|
31
|
+
@return null
|
|
32
|
+
@else if list.index($space-steps, $v)
|
|
33
|
+
@return var(--space-#{$v})
|
|
34
|
+
@else if meta.type-of($v) == number and math.is-unitless($v)
|
|
35
|
+
@return #{$v}px
|
|
36
|
+
@else
|
|
37
|
+
@return $v
|
|
38
|
+
|
|
39
|
+
@function radius($v)
|
|
40
|
+
@if list.index($radius-steps, $v)
|
|
41
|
+
@return var(--radius-#{$v})
|
|
42
|
+
@else if meta.type-of($v) == number and math.is-unitless($v)
|
|
43
|
+
@return #{$v}px
|
|
44
|
+
@else
|
|
45
|
+
@return $v
|
|
46
|
+
|
|
47
|
+
@function text-size($v)
|
|
48
|
+
@if list.index($text-steps, $v)
|
|
49
|
+
@return var(--text-#{$v})
|
|
50
|
+
@else
|
|
51
|
+
@return $v
|
|
52
|
+
|
|
53
|
+
@function shadow($v)
|
|
54
|
+
@if list.index($shadow-steps, $v)
|
|
55
|
+
@return var(--shadow-#{$v})
|
|
56
|
+
@else
|
|
57
|
+
@return $v
|
|
58
|
+
|
|
59
|
+
@function surface($role)
|
|
60
|
+
@if $role == bg
|
|
61
|
+
@return var(--bg)
|
|
62
|
+
@else
|
|
63
|
+
@return var(--bg-#{$role})
|
|
64
|
+
|
|
65
|
+
@function ink($role)
|
|
66
|
+
@if $role == primary
|
|
67
|
+
@return var(--text-primary)
|
|
68
|
+
@else
|
|
69
|
+
@return var(--text-#{$role})
|
|
70
|
+
|
|
71
|
+
@function align($v)
|
|
72
|
+
$m: map.get($align-map, $v)
|
|
73
|
+
@if $m
|
|
74
|
+
@return $m
|
|
75
|
+
@return $v
|
|
76
|
+
|
|
77
|
+
@function bp($name)
|
|
78
|
+
@return map.get($breakpoints, $name)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// FRACTALS — the pure API barrel. Emits NO css on its own.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// In any component partial or Svelte component:
|
|
5
|
+
// @use '$lib/styles/fractals' as *
|
|
6
|
+
// (or @use './fractals' as *)
|
|
7
|
+
// then call fractals freely: +surface(...) +stack(...) space(m) ...
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
@forward 'config'
|
|
11
|
+
@forward 'responsive'
|
|
12
|
+
@forward 'atoms'
|
|
13
|
+
@forward 'molecules'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// LAYOUTS — the largest fractals. Same recipe move, page scale.
|
|
3
|
+
// These are the concrete deliverables you named: a responsive 3-col grid, a
|
|
4
|
+
// hero, a holy-grail, a docs template. Each is a short composition of fractals.
|
|
5
|
+
// =============================================================================
|
|
6
|
+
|
|
7
|
+
@use 'fractals' as *
|
|
8
|
+
|
|
9
|
+
// 1. Responsive grid of 3 columns — 1 → 2 → 3 across breakpoints.
|
|
10
|
+
.grid-3
|
|
11
|
+
+cols((base: 1, sm: 2, lg: 3), m)
|
|
12
|
+
|
|
13
|
+
// A grid that never needs breakpoints: it fits as many columns as will hold
|
|
14
|
+
// the min track. Reach for this before .grid-3 when column count is negotiable.
|
|
15
|
+
.card-grid
|
|
16
|
+
+auto-grid(16rem, m)
|
|
17
|
+
|
|
18
|
+
// 2. Hero — full-viewport cover with a centered, measured message.
|
|
19
|
+
.hero
|
|
20
|
+
+cover(80vh, xl)
|
|
21
|
+
> .center
|
|
22
|
+
+stack(m, center)
|
|
23
|
+
text-align: center
|
|
24
|
+
max-width: 45ch
|
|
25
|
+
|
|
26
|
+
// 3. Holy grail — header / (nav · main · aside) / footer.
|
|
27
|
+
.holy-grail
|
|
28
|
+
display: grid
|
|
29
|
+
min-height: 100vh
|
|
30
|
+
grid-template-rows: auto 1fr auto
|
|
31
|
+
grid-template-areas: 'header' 'main' 'footer'
|
|
32
|
+
> .hg-header
|
|
33
|
+
grid-area: header
|
|
34
|
+
> .hg-footer
|
|
35
|
+
grid-area: footer
|
|
36
|
+
> .hg-body
|
|
37
|
+
grid-area: main
|
|
38
|
+
display: grid
|
|
39
|
+
grid-template-columns: 1fr
|
|
40
|
+
+gap(m)
|
|
41
|
+
+pad(m)
|
|
42
|
+
+at(lg)
|
|
43
|
+
> .hg-body
|
|
44
|
+
grid-template-columns: var(--nav-w, 220px) minmax(0, 1fr) var(--aside-w, 260px)
|
|
45
|
+
> .hg-nav
|
|
46
|
+
+sticky(var(--space-m))
|
|
47
|
+
align-self: start
|
|
48
|
+
> .hg-aside
|
|
49
|
+
+sticky(var(--space-m))
|
|
50
|
+
align-self: start
|
|
51
|
+
|
|
52
|
+
// 4. Docs template — sidebar nav + reading column + on-page TOC.
|
|
53
|
+
.docs
|
|
54
|
+
display: grid
|
|
55
|
+
min-height: 100vh
|
|
56
|
+
grid-template-columns: 1fr
|
|
57
|
+
> .docs-nav
|
|
58
|
+
display: none
|
|
59
|
+
> .docs-toc
|
|
60
|
+
display: none
|
|
61
|
+
> .docs-main
|
|
62
|
+
+min0
|
|
63
|
+
+stack(m)
|
|
64
|
+
+center-column(72ch, s-l)
|
|
65
|
+
+py(l)
|
|
66
|
+
+at(md)
|
|
67
|
+
grid-template-columns: var(--docs-nav-w, 240px) minmax(0, 1fr)
|
|
68
|
+
> .docs-nav
|
|
69
|
+
+box
|
|
70
|
+
+sticky(0)
|
|
71
|
+
+py(l)
|
|
72
|
+
+px(s)
|
|
73
|
+
height: 100vh
|
|
74
|
+
overflow-y: auto
|
|
75
|
+
+border(right)
|
|
76
|
+
+at(xl)
|
|
77
|
+
grid-template-columns: var(--docs-nav-w, 240px) minmax(0, 1fr) var(--docs-toc-w, 220px)
|
|
78
|
+
> .docs-toc
|
|
79
|
+
+box
|
|
80
|
+
+sticky(0)
|
|
81
|
+
+py(l)
|
|
82
|
+
+px(s)
|
|
83
|
+
height: 100vh
|
|
84
|
+
overflow-y: auto
|
|
85
|
+
+ink(secondary)
|
|
86
|
+
|
|
87
|
+
// 5. App shell — sticky header, fluid body, footer. The frame most apps live in.
|
|
88
|
+
.app-shell
|
|
89
|
+
+box
|
|
90
|
+
+relative
|
|
91
|
+
isolation: isolate
|
|
92
|
+
min-height: 100vh
|
|
93
|
+
> .app-header
|
|
94
|
+
+row(null, center)
|
|
95
|
+
+sticky(0)
|
|
96
|
+
+px(m)
|
|
97
|
+
z-index: var(--z-sticky)
|
|
98
|
+
height: var(--header-height)
|
|
99
|
+
+bg(surface)
|
|
100
|
+
+border(bottom)
|
|
101
|
+
> .app-main
|
|
102
|
+
+grow
|
|
103
|
+
+min0
|
|
104
|
+
> .app-footer
|
|
105
|
+
+row(null, center)
|
|
106
|
+
+px(m)
|
|
107
|
+
min-height: var(--footer-height)
|
|
108
|
+
+border(top)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// MOLECULE FRACTALS — self-similar: each is a small graph of atom fractals.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// This is the payoff of the model. A molecule reads like a recipe of atoms,
|
|
5
|
+
// and a component (next layer up) reads like a recipe of molecules. Same move
|
|
6
|
+
// at every scale — that is the "fractal" in fractalstyler2.
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
@use 'config' as *
|
|
10
|
+
@use 'atoms' as *
|
|
11
|
+
@use 'responsive' as *
|
|
12
|
+
|
|
13
|
+
// Stack — vertical rhythm with one shared gap.
|
|
14
|
+
=stack($gap: xs, $x: null)
|
|
15
|
+
+box($x)
|
|
16
|
+
+gap($gap)
|
|
17
|
+
|
|
18
|
+
// Cluster — wrapping row of chips/tags/buttons.
|
|
19
|
+
=cluster($gap: xs, $x: start, $y: center)
|
|
20
|
+
+row($x, $y)
|
|
21
|
+
+wrap
|
|
22
|
+
+gap($gap)
|
|
23
|
+
|
|
24
|
+
// Center — a centered reading column bounded by a measure.
|
|
25
|
+
=center-column($max: var(--measure, 60ch), $pad: s)
|
|
26
|
+
max-width: $max
|
|
27
|
+
+mx-auto
|
|
28
|
+
+px($pad)
|
|
29
|
+
|
|
30
|
+
// Cover — fills a min-height with a centered focal child.
|
|
31
|
+
=cover($min: 100vh, $pad: s)
|
|
32
|
+
+box
|
|
33
|
+
min-height: $min
|
|
34
|
+
+pad($pad)
|
|
35
|
+
> *
|
|
36
|
+
margin-block: 0
|
|
37
|
+
> .center
|
|
38
|
+
+my-auto
|
|
39
|
+
|
|
40
|
+
// Frame — fixed aspect-ratio media box.
|
|
41
|
+
=frame($ratio: 16 / 9)
|
|
42
|
+
aspect-ratio: $ratio
|
|
43
|
+
overflow: hidden
|
|
44
|
+
> :is(img, video, iframe)
|
|
45
|
+
+full
|
|
46
|
+
object-fit: cover
|
|
47
|
+
|
|
48
|
+
// Reel — horizontal scroll-snap rail.
|
|
49
|
+
=reel($gap: xs)
|
|
50
|
+
+row
|
|
51
|
+
+gap($gap)
|
|
52
|
+
overflow-x: auto
|
|
53
|
+
overscroll-behavior-inline: contain
|
|
54
|
+
scroll-snap-type: inline mandatory
|
|
55
|
+
> *
|
|
56
|
+
scroll-snap-align: start
|
|
57
|
+
flex: 0 0 auto
|
|
58
|
+
|
|
59
|
+
// Sidebar — an intrinsic rail beside a fluid main; wraps when tight.
|
|
60
|
+
=with-sidebar($rail: 240px, $gap: s, $min: 60%)
|
|
61
|
+
+row
|
|
62
|
+
+wrap
|
|
63
|
+
+gap($gap)
|
|
64
|
+
> .rail
|
|
65
|
+
flex-basis: $rail
|
|
66
|
+
flex-grow: 1
|
|
67
|
+
> .flow
|
|
68
|
+
flex-basis: 0
|
|
69
|
+
flex-grow: 999
|
|
70
|
+
min-width: $min
|
|
71
|
+
|
|
72
|
+
// Surface — the "material" fractal: skin + elevation in one call.
|
|
73
|
+
// The building block of every card/panel/popover.
|
|
74
|
+
=surface($bg: surface, $pad: s, $radius: 12, $elevation: none)
|
|
75
|
+
+bg($bg)
|
|
76
|
+
+border
|
|
77
|
+
+radius($radius)
|
|
78
|
+
+pad($pad)
|
|
79
|
+
@if $elevation != none
|
|
80
|
+
+shadow($elevation)
|
|
81
|
+
|
|
82
|
+
// Responsive column grid from a per-breakpoint map:
|
|
83
|
+
// +cols((base: 1, sm: 2, lg: 3))
|
|
84
|
+
=cols($map, $gap: s)
|
|
85
|
+
display: grid
|
|
86
|
+
grid-auto-flow: row
|
|
87
|
+
+gap($gap)
|
|
88
|
+
@each $bp, $n in $map
|
|
89
|
+
@if $bp == base
|
|
90
|
+
grid-template-columns: repeat($n, minmax(0, 1fr))
|
|
91
|
+
@else
|
|
92
|
+
+at($bp)
|
|
93
|
+
grid-template-columns: repeat($n, minmax(0, 1fr))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// RESPONSIVE FRACTALS — the units that let any other fractal grow with the page.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// These are self-similar too: +at(md) wraps any block of fractal calls in a
|
|
5
|
+
// breakpoint, so a "responsive component" is just a component whose fractals
|
|
6
|
+
// are re-stated inside +at(...).
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
@use 'sass:map'
|
|
10
|
+
@use 'config' as *
|
|
11
|
+
|
|
12
|
+
// +at(md) — mobile-first min-width query by name (sm/md/lg/xl) or raw length.
|
|
13
|
+
=at($name)
|
|
14
|
+
$edge: $name
|
|
15
|
+
@if map.has-key($breakpoints, $name)
|
|
16
|
+
$edge: bp($name)
|
|
17
|
+
@media (min-width: #{$edge})
|
|
18
|
+
@content
|
|
19
|
+
|
|
20
|
+
// +until(md) — max-width query (one below the named edge).
|
|
21
|
+
=until($name)
|
|
22
|
+
$edge: $name
|
|
23
|
+
@if map.has-key($breakpoints, $name)
|
|
24
|
+
$edge: bp($name)
|
|
25
|
+
@media (max-width: #{$edge})
|
|
26
|
+
@content
|
|
27
|
+
|
|
28
|
+
// Container-query fractals — component-scoped responsiveness.
|
|
29
|
+
=cq
|
|
30
|
+
container-type: inline-size
|
|
31
|
+
|
|
32
|
+
=cq-at($width)
|
|
33
|
+
@container (min-width: #{$width})
|
|
34
|
+
@content
|
|
35
|
+
|
|
36
|
+
=cq-until($width)
|
|
37
|
+
@container (max-width: #{$width})
|
|
38
|
+
@content
|
|
39
|
+
|
|
40
|
+
// Motion / preference-aware fractal.
|
|
41
|
+
=reduce-motion
|
|
42
|
+
@media (prefers-reduced-motion: reduce)
|
|
43
|
+
@content
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// TOKENS — the only place literal values live. Everything else is a fractal
|
|
3
|
+
// that consumes these custom properties. Light is the marker-free default so
|
|
4
|
+
// tokens stay defined during SSR and with JS disabled; dark comes from
|
|
5
|
+
// prefers-color-scheme and from an explicit [data-mode='dark'].
|
|
6
|
+
// =============================================================================
|
|
7
|
+
|
|
8
|
+
:root
|
|
9
|
+
color-scheme: light
|
|
10
|
+
|
|
11
|
+
// Fonts
|
|
12
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif
|
|
13
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace
|
|
14
|
+
|
|
15
|
+
// Fluid type scale (Utopia 360→1240)
|
|
16
|
+
--text-xs: 0.75rem
|
|
17
|
+
--text-sm: clamp(0.9375rem, 0.9119rem + 0.1136vw, 1rem)
|
|
18
|
+
--text-md: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem)
|
|
19
|
+
--text-lg: clamp(1.35rem, 1.2631rem + 0.3864vw, 1.5625rem)
|
|
20
|
+
--text-xl: clamp(1.62rem, 1.4837rem + 0.6057vw, 1.9531rem)
|
|
21
|
+
--text-2xl: clamp(1.944rem, 1.7405rem + 0.9044vw, 2.4414rem)
|
|
22
|
+
--text-3xl: clamp(2.3328rem, 2.0387rem + 1.3072vw, 3.0518rem)
|
|
23
|
+
--text-4xl: clamp(2.7994rem, 2.384rem + 1.8461vw, 3.8147rem)
|
|
24
|
+
|
|
25
|
+
// Fluid space scale
|
|
26
|
+
--space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem)
|
|
27
|
+
--space-2xs: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem)
|
|
28
|
+
--space-xs: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem)
|
|
29
|
+
--space-s: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem)
|
|
30
|
+
--space-m: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem)
|
|
31
|
+
--space-l: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem)
|
|
32
|
+
--space-xl: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem)
|
|
33
|
+
--space-2xl: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem)
|
|
34
|
+
--space-3xl: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem)
|
|
35
|
+
--space-s-l: clamp(1.125rem, 0.5625rem + 2.5vw, 2.5rem)
|
|
36
|
+
|
|
37
|
+
// Radius
|
|
38
|
+
--radius-0: 0
|
|
39
|
+
--radius-2: 2px
|
|
40
|
+
--radius-4: 4px
|
|
41
|
+
--radius-6: 6px
|
|
42
|
+
--radius-8: 8px
|
|
43
|
+
--radius-12: 12px
|
|
44
|
+
--radius-16: 16px
|
|
45
|
+
--radius-24: 24px
|
|
46
|
+
--radius-full: 9999px
|
|
47
|
+
|
|
48
|
+
// Shadow
|
|
49
|
+
--shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06)
|
|
50
|
+
--shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08)
|
|
51
|
+
--shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.12)
|
|
52
|
+
|
|
53
|
+
// Layering
|
|
54
|
+
--z-base: 0
|
|
55
|
+
--z-raised: 10
|
|
56
|
+
--z-sticky: 100
|
|
57
|
+
--z-modal: 200
|
|
58
|
+
--z-toast: 300
|
|
59
|
+
|
|
60
|
+
// Layout
|
|
61
|
+
--header-height: 64px
|
|
62
|
+
--footer-height: 56px
|
|
63
|
+
--measure: 65ch
|
|
64
|
+
|
|
65
|
+
// Light palette (marker-free default)
|
|
66
|
+
--bg: #fdfefe
|
|
67
|
+
--bg-surface: #f8f7f7
|
|
68
|
+
--bg-raised: #f1f5f9
|
|
69
|
+
--text-primary: #0f172a
|
|
70
|
+
--text-secondary: #5b6472
|
|
71
|
+
--text-muted: #929497
|
|
72
|
+
--text-inverse: #ffffff
|
|
73
|
+
--border: #e2e8f0
|
|
74
|
+
--border-strong: #cbd5e1
|
|
75
|
+
--theme: #007f4e
|
|
76
|
+
--theme-hover: #00663f
|
|
77
|
+
--theme-active: #005031
|
|
78
|
+
--ring: rgba(0, 127, 78, 0.35)
|
|
79
|
+
|
|
80
|
+
@media (prefers-color-scheme: dark)
|
|
81
|
+
:root:not([data-mode='light'])
|
|
82
|
+
color-scheme: dark
|
|
83
|
+
--bg: #131313
|
|
84
|
+
--bg-surface: #1e1e1e
|
|
85
|
+
--bg-raised: #2b2b2b
|
|
86
|
+
--text-primary: #f9fafb
|
|
87
|
+
--text-secondary: #a7adb7
|
|
88
|
+
--text-muted: #6b7280
|
|
89
|
+
--text-inverse: #0b0f17
|
|
90
|
+
--border: #2f3030
|
|
91
|
+
--border-strong: #4a4b4b
|
|
92
|
+
--theme: #10b981
|
|
93
|
+
--theme-hover: #34d399
|
|
94
|
+
--theme-active: #059669
|
|
95
|
+
--ring: rgba(16, 185, 129, 0.4)
|
|
96
|
+
|
|
97
|
+
[data-mode='dark']
|
|
98
|
+
color-scheme: dark
|
|
99
|
+
--bg: #131313
|
|
100
|
+
--bg-surface: #1e1e1e
|
|
101
|
+
--bg-raised: #2b2b2b
|
|
102
|
+
--text-primary: #f9fafb
|
|
103
|
+
--text-secondary: #a7adb7
|
|
104
|
+
--text-muted: #6b7280
|
|
105
|
+
--text-inverse: #0b0f17
|
|
106
|
+
--border: #2f3030
|
|
107
|
+
--border-strong: #4a4b4b
|
|
108
|
+
--theme: #10b981
|
|
109
|
+
--theme-hover: #34d399
|
|
110
|
+
--theme-active: #059669
|
|
111
|
+
--ring: rgba(16, 185, 129, 0.4)
|
|
112
|
+
|
|
113
|
+
[data-mode='light']
|
|
114
|
+
color-scheme: light
|
|
115
|
+
--bg: #fdfefe
|
|
116
|
+
--bg-surface: #f8f7f7
|
|
117
|
+
--bg-raised: #f1f5f9
|
|
118
|
+
--text-primary: #0f172a
|
|
119
|
+
--text-secondary: #5b6472
|
|
120
|
+
--text-muted: #929497
|
|
121
|
+
--text-inverse: #ffffff
|
|
122
|
+
--border: #e2e8f0
|
|
123
|
+
--border-strong: #cbd5e1
|
|
124
|
+
--theme: #007f4e
|
|
125
|
+
--theme-hover: #00663f
|
|
126
|
+
--theme-active: #005031
|
|
127
|
+
--ring: rgba(0, 127, 78, 0.35)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// UTILITY PROJECTION — the *other* way to consume a fractal.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// A fractal is a mixin. Here we bind a subset of them to class names so they
|
|
5
|
+
// stay usable straight from markup (`class="box gap-m pad-l"`). This is the
|
|
6
|
+
// key idea: utility classes are not a separate system — they are a generated
|
|
7
|
+
// projection of the exact same fractals your components compose. Emit only the
|
|
8
|
+
// atoms you want in markup; delete this whole file if you go SASS-only.
|
|
9
|
+
// =============================================================================
|
|
10
|
+
|
|
11
|
+
@use 'config' as *
|
|
12
|
+
@use 'atoms' as *
|
|
13
|
+
@use 'molecules' as *
|
|
14
|
+
@use 'responsive' as *
|
|
15
|
+
|
|
16
|
+
// --- Flow ------------------------------------------------------------------
|
|
17
|
+
.box
|
|
18
|
+
+box
|
|
19
|
+
.row
|
|
20
|
+
+row
|
|
21
|
+
&.wrap
|
|
22
|
+
+wrap
|
|
23
|
+
|
|
24
|
+
// Alignment modifiers, semantic and axis-aware.
|
|
25
|
+
.box
|
|
26
|
+
&.xcenter
|
|
27
|
+
align-items: center
|
|
28
|
+
&.xleft
|
|
29
|
+
align-items: flex-start
|
|
30
|
+
&.xright
|
|
31
|
+
align-items: flex-end
|
|
32
|
+
&.ycenter
|
|
33
|
+
justify-content: center
|
|
34
|
+
&.ytop
|
|
35
|
+
justify-content: flex-start
|
|
36
|
+
&.ybot
|
|
37
|
+
justify-content: flex-end
|
|
38
|
+
|
|
39
|
+
.row
|
|
40
|
+
&.ycenter
|
|
41
|
+
align-items: center
|
|
42
|
+
&.ytop
|
|
43
|
+
align-items: flex-start
|
|
44
|
+
&.ybot
|
|
45
|
+
align-items: flex-end
|
|
46
|
+
&.xbetween
|
|
47
|
+
justify-content: space-between
|
|
48
|
+
&.xevenly
|
|
49
|
+
justify-content: space-evenly
|
|
50
|
+
&.xleft
|
|
51
|
+
justify-content: flex-start
|
|
52
|
+
&.xright
|
|
53
|
+
justify-content: flex-end
|
|
54
|
+
|
|
55
|
+
// Fixed column grids.
|
|
56
|
+
@for $n from 1 through 6
|
|
57
|
+
.grid-cols-#{$n}
|
|
58
|
+
+grid($n)
|
|
59
|
+
|
|
60
|
+
.center
|
|
61
|
+
+center
|
|
62
|
+
|
|
63
|
+
// --- Spacing scale → gap-* / pad-* -----------------------------------------
|
|
64
|
+
@each $s in $space-steps
|
|
65
|
+
.gap-#{$s}
|
|
66
|
+
+gap($s)
|
|
67
|
+
.pad-#{$s}
|
|
68
|
+
+pad($s)
|
|
69
|
+
.px-#{$s}
|
|
70
|
+
+px($s)
|
|
71
|
+
.py-#{$s}
|
|
72
|
+
+py($s)
|
|
73
|
+
|
|
74
|
+
// --- Radius / shadow / surface ---------------------------------------------
|
|
75
|
+
@each $r in $radius-steps
|
|
76
|
+
.radius-#{$r}
|
|
77
|
+
+radius($r)
|
|
78
|
+
|
|
79
|
+
@each $s in $shadow-steps
|
|
80
|
+
.shadow-#{$s}
|
|
81
|
+
+shadow($s)
|
|
82
|
+
|
|
83
|
+
@each $role in $surface-roles
|
|
84
|
+
.bg-#{$role}
|
|
85
|
+
+bg($role)
|
|
86
|
+
|
|
87
|
+
.border
|
|
88
|
+
+border
|
|
89
|
+
@each $side in top right bottom left
|
|
90
|
+
.border-#{$side}
|
|
91
|
+
+border($side)
|
|
92
|
+
|
|
93
|
+
// --- Type ------------------------------------------------------------------
|
|
94
|
+
@each $t in $text-steps
|
|
95
|
+
.text-#{$t}
|
|
96
|
+
+type($t)
|
|
97
|
+
|
|
98
|
+
.truncate
|
|
99
|
+
+truncate
|
|
100
|
+
|
|
101
|
+
// Semantic type primitives — so you stop retyping `.text-sm .lh15 .muted`.
|
|
102
|
+
.body
|
|
103
|
+
+type(md)
|
|
104
|
+
+leading(1.6)
|
|
105
|
+
+ink(primary)
|
|
106
|
+
.muted
|
|
107
|
+
+ink(secondary)
|
|
108
|
+
.eyebrow
|
|
109
|
+
+type(sm)
|
|
110
|
+
+ink(muted)
|
|
111
|
+
text-transform: uppercase
|
|
112
|
+
letter-spacing: 0.06em
|
|
113
|
+
font-weight: 500
|
|
114
|
+
|
|
115
|
+
// --- Sizing shortcuts ------------------------------------------------------
|
|
116
|
+
.wfull
|
|
117
|
+
+w(100%)
|
|
118
|
+
.hfull
|
|
119
|
+
+h(100%)
|
|
120
|
+
.full
|
|
121
|
+
+full
|
|
122
|
+
.grow
|
|
123
|
+
+grow
|
|
124
|
+
.min0
|
|
125
|
+
+min0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// STYLES ENTRY — the full emitted stylesheet in cascade order.
|
|
3
|
+
// -----------------------------------------------------------------------------
|
|
4
|
+
// 1. tokens the CSS custom properties (only literal values)
|
|
5
|
+
// 2. base fluid reset
|
|
6
|
+
// 3. utilities atom fractals projected to markup classes (optional layer)
|
|
7
|
+
// 4. blocks components composed from fractals
|
|
8
|
+
// 5. layouts page templates composed from fractals
|
|
9
|
+
//
|
|
10
|
+
// Consume from a SvelteKit app: import '$lib/styles/index.sass'
|
|
11
|
+
// or from package: import 'fractalstyler2/styles'
|
|
12
|
+
// The fractal mixins/functions themselves emit nothing until called, so output
|
|
13
|
+
// contains only what you use.
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
@use './tokens'
|
|
17
|
+
@use './base'
|
|
18
|
+
@use './utilities'
|
|
19
|
+
@use './blocks'
|
|
20
|
+
@use './layouts'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|