@zvndev/darkmown 0.19.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +115 -5
- package/docs/spec-alignment.md +16 -3
- package/package.json +22 -4
- package/site/_/base.skin +372 -0
- package/site/_/base.wd +4 -0
- package/site/_/footer.wd +14 -0
- package/site/_/metrics.json +13 -0
- package/site/_/nav.skin +35 -12
- package/site/_/nav.wd +8 -10
- package/site/_/products.json +10 -0
- package/site/pages/app.wd +1 -1
- package/site/pages/carousel/index.js +82 -0
- package/site/pages/carousel/index.skin +185 -0
- package/site/pages/carousel/index.wd +109 -0
- package/site/pages/carousel/slide-1.jpg +0 -0
- package/site/pages/carousel/slide-2.jpg +0 -0
- package/site/pages/carousel/slide-3.jpg +0 -0
- package/site/pages/carousel/slide-4.jpg +0 -0
- package/site/pages/carousel/slide-5.jpg +0 -0
- package/site/pages/compass/index.skin +247 -0
- package/site/pages/compass/index.wd +194 -0
- package/site/pages/docs/index.skin +48 -28
- package/site/pages/docs/index.wd +71 -5
- package/site/pages/folio/-style.skin +375 -0
- package/site/pages/folio/-style.wd +6 -0
- package/site/pages/folio/cart.skin +309 -0
- package/site/pages/folio/cart.wd +201 -0
- package/site/pages/folio/index.skin +24 -0
- package/site/pages/folio/index.wd +167 -0
- package/site/pages/forge/index.skin +676 -0
- package/site/pages/forge/index.wd +330 -0
- package/site/pages/index.skin +135 -144
- package/site/pages/index.wd +17 -5
- package/site/pages/ledger/index.skin +257 -0
- package/site/pages/ledger/index.wd +174 -0
- package/site/pages/media/clip-poster.jpg +0 -0
- package/site/pages/media/clip.mp4 +0 -0
- package/site/pages/media/index.skin +82 -0
- package/site/pages/media/index.wd +84 -0
- package/site/pages/media/tone.mp3 +0 -0
- package/site/pages/pulse/index.skin +434 -0
- package/site/pages/pulse/index.wd +178 -0
- package/site/pages/showcase/index.skin +130 -0
- package/site/pages/showcase/index.wd +168 -0
- package/src/builder.js +20 -14
- package/src/cli.js +216 -77
- package/src/compiler/body.js +457 -0
- package/src/compiler/context.js +160 -0
- package/src/compiler/directives.js +1345 -0
- package/src/compiler/format.js +244 -0
- package/src/compiler/frontmatter.js +117 -0
- package/src/compiler/includes.js +116 -0
- package/src/compiler/index.js +22 -0
- package/src/compiler/interpolation.js +223 -0
- package/src/compiler/loops.js +715 -0
- package/src/compiler/markdown.js +242 -0
- package/src/compiler/page.js +267 -0
- package/src/compiler/predicates.js +314 -0
- package/src/compiler.js +26 -2932
- package/src/dev.js +3 -1
- package/src/headers.js +8 -4
- package/src/index.js +22 -0
- package/src/router.js +3 -1
- package/src/runtime.js +168 -14
- package/src/scaffold.js +158 -131
- package/src/skin.js +11 -2
- package/site/_/og.png +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass — find your match
|
|
3
|
+
description: A guided product-finder quiz built in one Markdown file. Four questions advance a state machine with :if branching, accumulate scores with :button actions, and a :computed recommendation lands the perfect pick. Zero hand-written JavaScript.
|
|
4
|
+
image: https://darkmown.com/__wd/media/og-compass.png
|
|
5
|
+
transitions: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
@include /nav.wd
|
|
9
|
+
@include /base.wd
|
|
10
|
+
|
|
11
|
+
<style>@keyframes pop { 0% { transform: scale(0) rotate(-18deg); opacity: 0 } 100% { transform: scale(1) rotate(0); opacity: 1 } }</style>
|
|
12
|
+
|
|
13
|
+
<main class="app-compass">
|
|
14
|
+
|
|
15
|
+
:state step = 1
|
|
16
|
+
:state calm = 0
|
|
17
|
+
:state bold = 0
|
|
18
|
+
:state budget = 0
|
|
19
|
+
|
|
20
|
+
::: section #quiz .compass-stage
|
|
21
|
+
|
|
22
|
+
::: compass-card .compass-card
|
|
23
|
+
|
|
24
|
+
:if step <= 4
|
|
25
|
+
|
|
26
|
+
::: compass-top .compass-top
|
|
27
|
+
|
|
28
|
+
::: eyebrow .compass-eyebrow
|
|
29
|
+
Question { step } of 4
|
|
30
|
+
:::
|
|
31
|
+
|
|
32
|
+
::: compass-rail .compass-rail .at-1 when step == 1 .at-2 when step == 2 .at-3 when step == 3 .at-4 when step == 4
|
|
33
|
+
:::
|
|
34
|
+
|
|
35
|
+
:::
|
|
36
|
+
|
|
37
|
+
:endif
|
|
38
|
+
|
|
39
|
+
:if step == 1
|
|
40
|
+
|
|
41
|
+
## Where should it live?
|
|
42
|
+
|
|
43
|
+
::: lead .compass-lead
|
|
44
|
+
Picture the spot you'd reach for it most. That tells us a lot.
|
|
45
|
+
:::
|
|
46
|
+
|
|
47
|
+
::: answers .compass-answers
|
|
48
|
+
:button "🛋️ A cozy nook to unwind" -> calm += 2; step++
|
|
49
|
+
:button "🎉 The room where people gather" -> bold += 2; step++
|
|
50
|
+
:button "🧺 Wherever — it should just be useful" -> budget += 2; step++
|
|
51
|
+
:::
|
|
52
|
+
|
|
53
|
+
:else if step == 2
|
|
54
|
+
|
|
55
|
+
## What's the mood you're after?
|
|
56
|
+
|
|
57
|
+
::: lead .compass-lead
|
|
58
|
+
No wrong answers. Go with your gut.
|
|
59
|
+
:::
|
|
60
|
+
|
|
61
|
+
::: answers .compass-answers
|
|
62
|
+
:button "🌙 Soft, calm, a little dreamy" -> calm += 2; step++
|
|
63
|
+
:button "⚡ Lively, bold, turn it up" -> bold += 2; step++
|
|
64
|
+
:button "🧘 Fresh and easy — no fuss" -> calm += 1; budget += 1; step++
|
|
65
|
+
:::
|
|
66
|
+
|
|
67
|
+
:else if step == 3
|
|
68
|
+
|
|
69
|
+
## How does it earn its keep?
|
|
70
|
+
|
|
71
|
+
::: lead .compass-lead
|
|
72
|
+
The thing it has to nail before anything else.
|
|
73
|
+
:::
|
|
74
|
+
|
|
75
|
+
::: answers .compass-answers
|
|
76
|
+
:button "✨ It sets the whole vibe" -> calm += 2; step++
|
|
77
|
+
:button "🔊 It fills the room" -> bold += 2; step++
|
|
78
|
+
:button "💧 It quietly makes the day nicer" -> calm += 1; budget += 1; step++
|
|
79
|
+
:::
|
|
80
|
+
|
|
81
|
+
:else if step == 4
|
|
82
|
+
|
|
83
|
+
## And the budget?
|
|
84
|
+
|
|
85
|
+
::: lead .compass-lead
|
|
86
|
+
Last one — then we'll find your match.
|
|
87
|
+
:::
|
|
88
|
+
|
|
89
|
+
::: answers .compass-answers
|
|
90
|
+
:button "🌱 Keep it gentle" -> budget += 3; step++
|
|
91
|
+
:button "⚖️ A fair middle" -> budget += 1; step++
|
|
92
|
+
:button "💎 Splurge for the right one" -> bold += 2; step++
|
|
93
|
+
:::
|
|
94
|
+
|
|
95
|
+
:else
|
|
96
|
+
|
|
97
|
+
::: result .compass-result
|
|
98
|
+
|
|
99
|
+
::: burst .compass-burst
|
|
100
|
+
🎉
|
|
101
|
+
:::
|
|
102
|
+
|
|
103
|
+
::: eyebrow .compass-eyebrow .is-result
|
|
104
|
+
Your match
|
|
105
|
+
:::
|
|
106
|
+
|
|
107
|
+
:if bold > calm and bold > budget
|
|
108
|
+
|
|
109
|
+
# The Cove Speaker
|
|
110
|
+
|
|
111
|
+
::: why .compass-why
|
|
112
|
+
You're here to fill the room. The **Cove Speaker** throws warm, room-filling 360° sound out of a palm-sized shell — the heart of every gathering, turned all the way up.
|
|
113
|
+
:::
|
|
114
|
+
|
|
115
|
+
[Shop this →](/folio/){.compass-cta}
|
|
116
|
+
|
|
117
|
+
:else if calm > bold and calm > budget
|
|
118
|
+
|
|
119
|
+
# The Aurora Lamp
|
|
120
|
+
|
|
121
|
+
::: why .compass-why
|
|
122
|
+
You want a space that *feels* like something. The **Aurora Lamp** pours a warm, dimmable glow from a hand-turned oak base — the quiet centerpiece of your cozy nook.
|
|
123
|
+
:::
|
|
124
|
+
|
|
125
|
+
[Shop this →](/folio/){.compass-cta}
|
|
126
|
+
|
|
127
|
+
:else if budget > calm and budget > bold
|
|
128
|
+
|
|
129
|
+
# The Haven Diffuser
|
|
130
|
+
|
|
131
|
+
::: why .compass-why
|
|
132
|
+
You like things that quietly make the day nicer — without the splurge. The **Haven Diffuser** breathes a soft cedar-scented mist into the room, refillable and easy to love.
|
|
133
|
+
:::
|
|
134
|
+
|
|
135
|
+
[Shop this →](/folio/){.compass-cta}
|
|
136
|
+
|
|
137
|
+
:else
|
|
138
|
+
|
|
139
|
+
# The Isle Earbuds
|
|
140
|
+
|
|
141
|
+
::: why .compass-why
|
|
142
|
+
You're a little of everything — calm, bold, and practical. The **Isle Earbuds** go anywhere you do: active noise cancelling, a 30-hour case, and tap controls for whatever the day brings.
|
|
143
|
+
:::
|
|
144
|
+
|
|
145
|
+
[Shop this →](/folio/){.compass-cta}
|
|
146
|
+
|
|
147
|
+
:endif
|
|
148
|
+
|
|
149
|
+
::: restart .compass-restart
|
|
150
|
+
:button "↺ Start over" -> step reset; calm reset; bold reset; budget reset
|
|
151
|
+
:::
|
|
152
|
+
|
|
153
|
+
:::
|
|
154
|
+
|
|
155
|
+
:endif
|
|
156
|
+
|
|
157
|
+
:::
|
|
158
|
+
|
|
159
|
+
:::
|
|
160
|
+
|
|
161
|
+
<details class="source-reveal">
|
|
162
|
+
<summary>View source — it's Markdown</summary>
|
|
163
|
+
|
|
164
|
+
```wd
|
|
165
|
+
:state step = 1
|
|
166
|
+
:state calm = 0
|
|
167
|
+
:state bold = 0
|
|
168
|
+
:state budget = 0
|
|
169
|
+
|
|
170
|
+
:if step <= 4
|
|
171
|
+
Question { step } of 4
|
|
172
|
+
:endif
|
|
173
|
+
|
|
174
|
+
:if step == 1
|
|
175
|
+
## Where should it live?
|
|
176
|
+
:button "🛋️ A cozy nook to unwind" -> calm += 2; step++
|
|
177
|
+
:button "🎉 The room where people gather" -> bold += 2; step++
|
|
178
|
+
:else if step == 2
|
|
179
|
+
## What's the mood you're after?
|
|
180
|
+
:button "🌙 Soft, calm, a little dreamy" -> calm += 2; step++
|
|
181
|
+
... three more questions ...
|
|
182
|
+
:else
|
|
183
|
+
:if bold > calm and bold > budget
|
|
184
|
+
# The Cove Speaker
|
|
185
|
+
:else if calm > bold and calm > budget
|
|
186
|
+
# The Aurora Lamp
|
|
187
|
+
:endif
|
|
188
|
+
:button "↺ Start over" -> step reset; calm reset; bold reset; budget reset
|
|
189
|
+
:endif
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
</details>
|
|
193
|
+
|
|
194
|
+
</main>
|
|
@@ -1,39 +1,59 @@
|
|
|
1
|
-
tokens
|
|
2
|
-
|
|
3
|
-
paper #fbfaf7
|
|
4
|
-
panel #ffffff
|
|
5
|
-
line #dedbd3
|
|
6
|
-
accent #16645a
|
|
7
|
-
muted #666158
|
|
8
|
-
radius 8px
|
|
9
|
-
|
|
10
|
-
page
|
|
11
|
-
margin 0
|
|
12
|
-
font ui-serif, Georgia, Cambria, serif
|
|
13
|
-
color $ink
|
|
14
|
-
bg $paper
|
|
15
|
-
line-height 1.7
|
|
1
|
+
/* Docs — layers on /base.wd (tokens, type, code, dark mode). Refined,
|
|
2
|
+
readable single-column reference. Only docs-specific layout here. */
|
|
16
3
|
|
|
17
|
-
main
|
|
18
|
-
max-width
|
|
19
|
-
margin 0 auto
|
|
20
|
-
padding 3rem 2rem 5rem
|
|
4
|
+
main.docs
|
|
5
|
+
max-width 760px
|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
7
|
+
.docs-hero
|
|
8
|
+
padding 2rem 0 1.5rem
|
|
9
|
+
margin-bottom 1rem
|
|
10
|
+
border-bottom 1px solid $line
|
|
11
|
+
|
|
12
|
+
.docs-hero h1
|
|
13
|
+
margin .2rem 0 .5rem
|
|
14
|
+
|
|
15
|
+
.docs-hero p
|
|
16
|
+
font-size 1.12rem
|
|
17
|
+
color $ink-soft
|
|
18
|
+
max-width 60ch
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
/* Section headings get a quiet rule above them so the long page scans. */
|
|
21
|
+
main.docs h2
|
|
22
|
+
margin-top 3rem
|
|
23
|
+
padding-top 1.4rem
|
|
30
24
|
border-top 1px solid $line
|
|
25
|
+
font-size 1.55rem
|
|
31
26
|
|
|
32
|
-
|
|
27
|
+
main.docs h3
|
|
28
|
+
margin-top 2rem
|
|
29
|
+
color $accent-deep
|
|
30
|
+
|
|
31
|
+
main.docs p, main.docs li
|
|
32
|
+
color $ink-soft
|
|
33
|
+
|
|
34
|
+
main.docs strong
|
|
35
|
+
color $ink
|
|
36
|
+
|
|
37
|
+
main.docs a
|
|
33
38
|
color $accent
|
|
39
|
+
font-weight 550
|
|
40
|
+
|
|
41
|
+
/* Tight, legible lists for the rule-heavy reference sections. */
|
|
42
|
+
main.docs ul
|
|
43
|
+
padding-left 1.2rem
|
|
44
|
+
|
|
45
|
+
main.docs li
|
|
46
|
+
margin .35rem 0
|
|
47
|
+
|
|
48
|
+
main.docs li code
|
|
49
|
+
white-space nowrap
|
|
34
50
|
|
|
35
51
|
.feature-card
|
|
36
52
|
border 1px solid $line
|
|
37
53
|
bg $panel
|
|
38
|
-
padding 1rem
|
|
54
|
+
padding 1.1rem 1.25rem
|
|
39
55
|
radius $radius
|
|
56
|
+
margin 1rem 0
|
|
57
|
+
|
|
58
|
+
.feature-card strong
|
|
59
|
+
color $warm
|
package/site/pages/docs/index.wd
CHANGED
|
@@ -5,12 +5,17 @@ transitions: true
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
@include /nav.wd
|
|
8
|
+
@include /base.wd
|
|
8
9
|
|
|
9
|
-
<main>
|
|
10
|
+
<main class="docs">
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
<header class="docs-hero">
|
|
13
|
+
<span class="eyebrow">Documentation</span>
|
|
14
|
+
|
|
15
|
+
# The Darkmown docs
|
|
12
16
|
|
|
13
17
|
Darkmown is a Markdown-native framework for mostly-static sites with tiny reactive islands. Pages are plain files: `.md` stays pure CommonMark, and `.wd` ("whateverdown") is the same Markdown plus first-party directives.
|
|
18
|
+
</header>
|
|
14
19
|
|
|
15
20
|
@include ./-relative-note.wd
|
|
16
21
|
|
|
@@ -152,6 +157,12 @@ One syntax everywhere: `{ name }` or `{ name.path }`.
|
|
|
152
157
|
- The page's frontmatter is in scope as `meta` — `{ meta.title }` prints a field.
|
|
153
158
|
- Otherwise the text is left exactly as written — braces in prose never break a page or pull in the runtime.
|
|
154
159
|
|
|
160
|
+
### Format pipes — `{ value | name:arg }`
|
|
161
|
+
|
|
162
|
+
Shape a value for display with a pipe: `{ price | money }`, `{ joinedAt | date:"medium" }`, `{ ratio | percent }`. Pipes chain (`{ name | trim | capitalize }`) and take literal arguments (`{ total | money:"EUR" }`, `{ bio | truncate:80 }`). The same syntax works on static values (folded at build time, zero-JS) and live bindings (re-applied on render).
|
|
163
|
+
|
|
164
|
+
The whitelist: `money`, `number`, `percent`, `round`, `date` / `time` / `datetime` (Intl-backed; `short` / `medium` / `long`), `upper` / `lower` / `capitalize`, `truncate:n` / `trim`, `pluralize:"item"`, and `default:"—"`. The five aggregates double as list pipes — `{ cart | count }`, `{ cart | sum:"price" | money }`, plus `avg` / `min` / `max`. No custom functions, nothing `eval`'d; an unknown pipe is a compile error. There is deliberately no relative "time ago" formatter, so builds stay reproducible.
|
|
165
|
+
|
|
155
166
|
## Frontmatter
|
|
156
167
|
|
|
157
168
|
Frontmatter sits between `---` fences. Values are strings, plus inline arrays:
|
|
@@ -179,7 +190,7 @@ You don't size images by hand. Every `<img>` the compiler emits gets its intrins
|
|
|
179
190
|
|
|
180
191
|
## Reactive directives
|
|
181
192
|
|
|
182
|
-
Reactive pages opt into `/__wd/runtime.js` (~
|
|
193
|
+
Reactive pages opt into `/__wd/runtime.js` (~7.4 KB gzipped, under a CI-enforced 8 KB budget). Static pages do not.
|
|
183
194
|
|
|
184
195
|
```wd
|
|
185
196
|
:state count = 0
|
|
@@ -205,6 +216,32 @@ Count is still zero.
|
|
|
205
216
|
|
|
206
217
|
Directive actions are intentionally narrow and compile-time checked. Arbitrary JavaScript belongs in a colocated `.js` file.
|
|
207
218
|
|
|
219
|
+
### Computed values and aggregates
|
|
220
|
+
|
|
221
|
+
`:computed name = expr` derives state from state — arithmetic, comparisons, and the five list aggregates `sum` / `avg` / `min` / `max` / `count`. It recomputes when an input changes and reads like any binding, so it pairs with format pipes:
|
|
222
|
+
|
|
223
|
+
```wd
|
|
224
|
+
:store cart = [{"price": 89}, {"price": 12}]
|
|
225
|
+
:computed subtotal = sum(cart, price)
|
|
226
|
+
:computed total = subtotal * 1.08
|
|
227
|
+
|
|
228
|
+
Total: { total | money }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The aggregate's field is a bare row key (`price`, not `item.price`).
|
|
232
|
+
|
|
233
|
+
### Timers — `:every`
|
|
234
|
+
|
|
235
|
+
`:every <duration> -> <actions>` runs a `:button`-style action on an interval (`ms` / `s` / `m`). Intervals pause while the tab is hidden and resume on return.
|
|
236
|
+
|
|
237
|
+
```wd
|
|
238
|
+
:fetch board from "/status.json"
|
|
239
|
+
:every 10s -> board refetch
|
|
240
|
+
|
|
241
|
+
:state secs = 0
|
|
242
|
+
:every 1s -> secs++
|
|
243
|
+
```
|
|
244
|
+
|
|
208
245
|
### Multi-branch conditionals
|
|
209
246
|
|
|
210
247
|
A condition reads the same predicate grammar as `.class when` — a bare path (truthy), or the comparisons `==` `!=` `<` `<=` `>` `>=` `contains`, joined with `and`, `or`, and `not`. (`@loop … where` is the comparison-only subset.) Chain with `:else if` (any number; a bare `:else` must come last):
|
|
@@ -320,10 +357,35 @@ You have { cart } items.
|
|
|
320
357
|
- `:input field placeholder="…" required` and `:submit "Label"` build the form body.
|
|
321
358
|
- `:state cart = [] persist` survives reloads via localStorage on a single page; use `:store` for state shared across pages and tabs.
|
|
322
359
|
- `:if item.path` works inside reactive loops for per-row branches, and conditionals nest — an inner `:if` resolves after the outer branch, staying reactive.
|
|
323
|
-
- Reactive pages expose `window.wd` (`get`, `set`, `state`, `render`) so colocated `.js` can do anything directives can't. Section state is addressed as `sectionId:name`.
|
|
360
|
+
- Reactive pages expose `window.wd` (`get`, `set`, `subscribe`, `state`, `render`) so colocated `.js` can do anything directives can't. Section state is addressed as `sectionId:name`.
|
|
324
361
|
|
|
325
362
|
See it all live on the [Data & Forms page](/data/).
|
|
326
363
|
|
|
364
|
+
## Theme toggle — `:theme`
|
|
365
|
+
|
|
366
|
+
For an explicit light/dark switch alongside the OS preference, declare `:theme` once and drive it with buttons. It registers a durable `theme` store and reflects onto `<html data-theme>`:
|
|
367
|
+
|
|
368
|
+
```wd
|
|
369
|
+
:theme
|
|
370
|
+
:button "Auto" -> theme = "auto"
|
|
371
|
+
:button "Light" -> theme = "light"
|
|
372
|
+
:button "Dark" -> theme = "dark"
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
A single `tokens dark` block in your `.skin` already powers both the OS `prefers-color-scheme` query and this toggle — no extra block needed. `"auto"` clears the attribute and follows the OS again. See it on the [Ledger demo](/ledger/).
|
|
376
|
+
|
|
377
|
+
## Media — `:video`, `:audio`, `:embed`
|
|
378
|
+
|
|
379
|
+
Three compile-time-only directives (they emit no `data-wd-*`, so a media page stays zero-JS):
|
|
380
|
+
|
|
381
|
+
```wd
|
|
382
|
+
:video /clip.mp4 poster=/clip.jpg controls
|
|
383
|
+
:audio /track.mp3 controls
|
|
384
|
+
:embed https://youtu.be/aqz-KE-bpKQ title="Big Buck Bunny"
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
`:video`/`:audio` emit a hardened HTML5 player — `preload="metadata"` and `controls` by default, whitelisted flags (`autoplay` implies `muted`), and the `:fetch` URL scheme guard. `:embed` rewrites a YouTube/Vimeo link to its no-cookie/player form in a lazy, responsive `16/9` iframe. See the zero-JS [Media demo](/media/).
|
|
388
|
+
|
|
327
389
|
## Trust boundary
|
|
328
390
|
|
|
329
391
|
Darkmown is a trusted-author site generator: you compile content you wrote, the way you trust your own source. Three things define that boundary.
|
|
@@ -337,7 +399,7 @@ See [SECURITY.md](https://github.com/ZVN-DEV/darkmown/blob/main/SECURITY.md) in
|
|
|
337
399
|
## Colocation
|
|
338
400
|
|
|
339
401
|
- A matching `.skin` file attaches CSS to the page (indentation-based, compiles to real CSS).
|
|
340
|
-
- A matching `.js` file attaches page behavior.
|
|
402
|
+
- A matching `.js` file attaches page behavior. It loads **after** the runtime, so `window.wd` is ready: `wd.get(key)`, `wd.set(key, value)`, and `wd.subscribe(key, cb)` (runs now and on every settled change) bridge declarative state to imperative widgets — the gestures the framework leaves out (keyboard, drag/touch, canvas). The [Swiper demo](/carousel/) is a draggable carousel built this way.
|
|
341
403
|
- Both work for included fragments too, by basename.
|
|
342
404
|
- Any other file under `site/pages/` (an image, font, PDF — anything that isn't a `.md`/`.wd` route or a basename-matched `.skin`/`.js`) copies to `dist/` at its own path with the right content-type. So `site/pages/logo.svg` is served at `/logo.svg`. Assets below hidden path segments (`.`, `-`, or `_`) and symlinked page assets are skipped, matching the routing privacy rule. Use the `site/_/` shelf for assets shared across pages; colocate the ones a single page owns.
|
|
343
405
|
|
|
@@ -345,4 +407,8 @@ See [SECURITY.md](https://github.com/ZVN-DEV/darkmown/blob/main/SECURITY.md) in
|
|
|
345
407
|
|
|
346
408
|
The implementation is faithful to the original core thesis: Markdown-first authoring, no component ceremony, zero runtime on static pages, and tiny direct-DOM reactivity only when declared. `:store`, the full `:fetch` lifecycle (loading/error/empty states, dynamic URLs, `refetch`, dotted loop sources, authenticated `headers=` + `refresh=` token renewal), `:form` (including server round-trips), loop ergonomics (`sort by`/`limit`/`offset`/`reverse`/`@empty` and `$index`/`$number`/`$first`/`$last`/`$count`), `:if … :else if … :else`, reactive `.class when …`, `:effect`, `:computed`, and `persist` are all shipped and live — try them on the [Data & Forms page](/data/) and the [Reactive page](/reactive/). Still on the roadmap: a first-party server runtime (`site/api/`) and HTML-fragment swaps. See `docs/spec-alignment.md` in the package for the full audit.
|
|
347
409
|
|
|
410
|
+
See these directives composed into real apps on the [showcase](/showcase/).
|
|
411
|
+
|
|
348
412
|
</main>
|
|
413
|
+
|
|
414
|
+
@include /footer.wd
|