fractalstyler2 0.0.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/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # fractalstyler2
2
+
3
+ A fractal-composition styling system for SvelteKit. **Every style unit is a SASS
4
+ mixin ("a fractal"), and components and layouts are recipes of smaller fractals.**
5
+
6
+ This is a standalone package with no relationship to any earlier styler. The
7
+ product is framework-agnostic SASS surfaced through a SvelteKit library; the JS
8
+ runtime is just a tiny mode helper.
9
+
10
+ ## The idea
11
+
12
+ A fractal is a mixin: a tiny reusable styling decision that composes other
13
+ fractals. They form four self-similar tiers — each tier is "a recipe of the one
14
+ below":
15
+
16
+ | Tier | What | Examples |
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
30
+
31
+ ```bash
32
+ pnpm add fractalstyler2
33
+ ```
34
+
35
+ ## Use
36
+
37
+ **1 — Emit the ready-made stylesheet** (tokens, base reset, utility classes,
38
+ blocks, layouts). Import once, globally (e.g. in your root `+layout.svelte`):
39
+
40
+ ```svelte
41
+ <script>
42
+ import 'fractalstyler2/styles';
43
+ </script>
44
+ ```
45
+
46
+ Then write thin, semantic markup:
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
+
59
+ ```svelte
60
+ <style lang="sass">
61
+ @use 'fractalstyler2/fractals' as *
62
+
63
+ .pricing-card
64
+ +surface(surface, l, 16, md) // bg + border + radius + pad + shadow
65
+ +stack(m, center) // flex-column + gap + centered
66
+ text-align: center
67
+ </style>
68
+ ```
69
+
70
+ Values prefer the finite token scale and fall back to raw units in the same
71
+ call: `+gap(m)` → `var(--space-m)`, `+gap(16)` → `16px`. No JIT class explosion.
72
+
73
+ ## What ships
74
+
75
+ ```
76
+ fractalstyler2/
77
+ ├─ styles → the full emitted stylesheet (import 'fractalstyler2/styles')
78
+ ├─ fractals → the mixin/function API (@use 'fractalstyler2/fractals' as *)
79
+ ├─ tokens → just the custom properties (@use 'fractalstyler2/tokens')
80
+ └─ (default) → { version, setMode, toggleMode }
81
+ ```
82
+
83
+ Source layout: `src/lib/fractals/` (config, tokens, base, responsive, atoms,
84
+ molecules, utilities), `src/lib/components/` (blocks, layouts),
85
+ `src/lib/styles/` (the emit entry).
86
+
87
+ ## Color mode
88
+
89
+ Light is the marker-free default (SSR-safe). Dark comes from
90
+ `prefers-color-scheme` and from an explicit `data-mode="dark"` on `<html>`:
91
+
92
+ ```js
93
+ import { toggleMode, setMode } from 'fractalstyler2';
94
+ toggleMode(); // flip light/dark
95
+ setMode('dark'); // force
96
+ ```
97
+
98
+ ## Included layouts
99
+
100
+ `.grid-3` (responsive 1→2→3), `.card-grid` (intrinsic auto-fit), `.hero`,
101
+ `.holy-grail`, `.docs`, `.app-shell` — each a few fractal calls in
102
+ `src/lib/components/_layouts.sass`.
103
+
104
+ ## Develop
105
+
106
+ ```bash
107
+ pnpm install
108
+ pnpm dev # demo showcase at /
109
+ pnpm run prepack # build the distributable package into dist/
110
+ ```
111
+
112
+ ## License
113
+
114
+ MIT
@@ -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,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,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,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,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,11 @@
1
+ // =============================================================================
2
+ // FRACTALS — the pure API barrel. Emits NO css on its own.
3
+ // -----------------------------------------------------------------------------
4
+ // In any component partial: @use '../fractals' as *
5
+ // then call fractals freely: +surface(...) +stack(...) space(m) ...
6
+ // =============================================================================
7
+
8
+ @forward 'config'
9
+ @forward 'responsive'
10
+ @forward 'atoms'
11
+ @forward 'molecules'
@@ -0,0 +1,6 @@
1
+ export declare const version = "0.0.1";
2
+ export type Mode = 'light' | 'dark';
3
+ /** Set the color mode by writing the [data-mode] marker on <html>. */
4
+ export declare function setMode(mode: Mode): void;
5
+ /** Toggle between light and dark, returning the new mode. */
6
+ export declare function toggleMode(): Mode;
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ // fractalstyler2 — a fractal-composition styling system for SvelteKit.
2
+ //
3
+ // The product is the SASS in ./fractals and ./components, surfaced as:
4
+ // import 'fractalstyler2/styles' // emit the full stylesheet
5
+ // @use 'fractalstyler2/fractals' as * // compose your own with the mixins
6
+ //
7
+ // This entry ships only tiny runtime helpers. There is deliberately no CSS-in-JS
8
+ // and no component coupling — the styling layer is framework-agnostic SASS.
9
+ export const version = '0.0.1';
10
+ /** Set the color mode by writing the [data-mode] marker on <html>. */
11
+ export function setMode(mode) {
12
+ if (typeof document === 'undefined')
13
+ return;
14
+ document.documentElement.setAttribute('data-mode', mode);
15
+ }
16
+ /** Toggle between light and dark, returning the new mode. */
17
+ export function toggleMode() {
18
+ const current = typeof document !== 'undefined' ? document.documentElement.getAttribute('data-mode') : null;
19
+ const next = current === 'dark' ? 'light' : 'dark';
20
+ setMode(next);
21
+ return next;
22
+ }
@@ -0,0 +1,19 @@
1
+ // =============================================================================
2
+ // STYLES ENTRY — the full emitted stylesheet in cascade order.
3
+ // -----------------------------------------------------------------------------
4
+ // 1. tokens the CSS custom properties (only literal values)
5
+ // 2. utilities atom fractals projected to markup classes (optional layer)
6
+ // 3. blocks components composed from fractals
7
+ // 4. layouts page templates composed from fractals
8
+ //
9
+ // Consume from a SvelteKit app: import 'fractalstyler2/styles'
10
+ // or, from SASS: @use 'fractalstyler2/styles'
11
+ // The fractal mixins/functions themselves emit nothing until called, so output
12
+ // contains only what you use.
13
+ // =============================================================================
14
+
15
+ @use '../fractals/tokens'
16
+ @use '../fractals/base'
17
+ @use '../fractals/utilities'
18
+ @use '../components/blocks'
19
+ @use '../components/layouts'
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "fractalstyler2",
3
+ "version": "0.0.1",
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
+ "scripts": {
6
+ "dev": "vite dev",
7
+ "build": "vite build && npm run prepack",
8
+ "preview": "vite preview",
9
+ "prepare": "svelte-kit sync || echo ''",
10
+ "prepack": "svelte-kit sync && svelte-package && publint",
11
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
12
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "!dist/**/*.test.*",
17
+ "!dist/**/*.spec.*"
18
+ ],
19
+ "sideEffects": [
20
+ "**/*.css"
21
+ ],
22
+ "svelte": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "svelte": "./dist/index.js"
29
+ },
30
+ "./styles": "./dist/styles/index.sass",
31
+ "./fractals": "./dist/fractals/index.sass",
32
+ "./tokens": "./dist/fractals/_tokens.sass",
33
+ "./package.json": "./package.json"
34
+ },
35
+ "peerDependencies": {
36
+ "svelte": "^5.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@sveltejs/adapter-auto": "^7.0.1",
40
+ "@sveltejs/kit": "^2.63.0",
41
+ "@sveltejs/package": "^2.5.8",
42
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
43
+ "publint": "^0.3.21",
44
+ "sass": "^1.102.0",
45
+ "svelte": "^5.56.1",
46
+ "svelte-check": "^4.6.0",
47
+ "typescript": "^6.0.3",
48
+ "vite": "^8.0.16"
49
+ },
50
+ "keywords": [
51
+ "svelte",
52
+ "sveltekit",
53
+ "sass",
54
+ "css",
55
+ "design-system",
56
+ "mixins",
57
+ "fractal"
58
+ ]
59
+ }