@zvndev/darkmown 0.20.0 → 1.1.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 +29 -0
- package/README.md +125 -5
- package/docs/spec-alignment.md +23 -3
- package/package.json +3 -3
- 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 +72 -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 +183 -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/compiler/body.js +89 -3
- package/src/compiler/context.js +1 -1
- package/src/compiler/directives.js +199 -3
- package/src/compiler/format.js +244 -0
- package/src/compiler/interpolation.js +36 -1
- package/src/compiler/loops.js +95 -24
- package/src/compiler/markdown.js +16 -7
- package/src/compiler/predicates.js +35 -2
- package/src/headers.js +5 -0
- package/src/runtime.js +168 -14
- package/src/skin.js +9 -1
package/site/_/base.skin
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/* ===================================================================
|
|
2
|
+
Darkmown design system — the refreshed brand foundation.
|
|
3
|
+
Warm, literary, premium. Serif for prose, system sans for chrome,
|
|
4
|
+
a refined mono for code. Ships zero JS; dark mode is OS-driven via
|
|
5
|
+
`tokens dark` (a pure CSS media query — no flash, no toggle script).
|
|
6
|
+
Every refreshed page and demo app includes this via /base.wd.
|
|
7
|
+
=================================================================== */
|
|
8
|
+
|
|
9
|
+
tokens
|
|
10
|
+
ink #1c1814
|
|
11
|
+
ink-soft #4b4239
|
|
12
|
+
muted #756a5d
|
|
13
|
+
faint #9b9082
|
|
14
|
+
paper #faf8f3
|
|
15
|
+
paper-2 #f4f0e7
|
|
16
|
+
sunk #efe9dc
|
|
17
|
+
panel #ffffff
|
|
18
|
+
line #e7e1d4
|
|
19
|
+
line-soft #f0ebe0
|
|
20
|
+
accent #0f6b5e
|
|
21
|
+
accent-deep #0a4f45
|
|
22
|
+
accent-soft #e3f0ec
|
|
23
|
+
warm #b8462e
|
|
24
|
+
warm-soft #f8eae3
|
|
25
|
+
gold #b6822b
|
|
26
|
+
code-bg #1b2420
|
|
27
|
+
code-ink #e9efe7
|
|
28
|
+
code-line #2c3b34
|
|
29
|
+
ring #0f6b5e
|
|
30
|
+
radius 12px
|
|
31
|
+
radius-sm 8px
|
|
32
|
+
radius-lg 20px
|
|
33
|
+
|
|
34
|
+
tokens dark
|
|
35
|
+
ink #ece6db
|
|
36
|
+
ink-soft #c3bbab
|
|
37
|
+
muted #9a9081
|
|
38
|
+
faint #6f6657
|
|
39
|
+
paper #14110d
|
|
40
|
+
paper-2 #1a1612
|
|
41
|
+
sunk #221d17
|
|
42
|
+
panel #1e1913
|
|
43
|
+
line #2f281f
|
|
44
|
+
line-soft #261f18
|
|
45
|
+
accent #5ccab4
|
|
46
|
+
accent-deep #8fe0cf
|
|
47
|
+
accent-soft #16302a
|
|
48
|
+
warm #e88a6f
|
|
49
|
+
warm-soft #2c1f18
|
|
50
|
+
gold #d6a24e
|
|
51
|
+
code-bg #100d0a
|
|
52
|
+
code-ink #e4ebe2
|
|
53
|
+
code-line #2a231b
|
|
54
|
+
ring #5ccab4
|
|
55
|
+
|
|
56
|
+
/* ── Reset & base ───────────────────────────────────────────────── */
|
|
57
|
+
|
|
58
|
+
*
|
|
59
|
+
box-sizing border-box
|
|
60
|
+
|
|
61
|
+
page
|
|
62
|
+
margin 0
|
|
63
|
+
font-family ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, Cambria, serif
|
|
64
|
+
font-size 18px
|
|
65
|
+
line-height 1.65
|
|
66
|
+
color $ink
|
|
67
|
+
bg $paper
|
|
68
|
+
-webkit-font-smoothing antialiased
|
|
69
|
+
text-rendering optimizeLegibility
|
|
70
|
+
|
|
71
|
+
::selection
|
|
72
|
+
bg $accent-soft
|
|
73
|
+
color $accent-deep
|
|
74
|
+
|
|
75
|
+
/* ── Typography ─────────────────────────────────────────────────── */
|
|
76
|
+
|
|
77
|
+
h1, h2, h3, h4
|
|
78
|
+
line-height 1.1
|
|
79
|
+
letter-spacing -.012em
|
|
80
|
+
color $ink
|
|
81
|
+
font-weight 600
|
|
82
|
+
|
|
83
|
+
h1
|
|
84
|
+
font-size clamp(2.5rem, 6vw, 4.5rem)
|
|
85
|
+
letter-spacing -.025em
|
|
86
|
+
margin .2em 0 .35em
|
|
87
|
+
|
|
88
|
+
h2
|
|
89
|
+
font-size clamp(1.6rem, 3vw, 2.25rem)
|
|
90
|
+
margin 2.4em 0 .6em
|
|
91
|
+
|
|
92
|
+
h3
|
|
93
|
+
font-size 1.3rem
|
|
94
|
+
margin 1.8em 0 .4em
|
|
95
|
+
|
|
96
|
+
p
|
|
97
|
+
margin 1em 0
|
|
98
|
+
max-width 68ch
|
|
99
|
+
|
|
100
|
+
a
|
|
101
|
+
color $accent
|
|
102
|
+
text-underline-offset 2px
|
|
103
|
+
text-decoration-thickness 1px
|
|
104
|
+
|
|
105
|
+
a:hover
|
|
106
|
+
color $accent-deep
|
|
107
|
+
|
|
108
|
+
strong
|
|
109
|
+
font-weight 650
|
|
110
|
+
color $ink
|
|
111
|
+
|
|
112
|
+
hr
|
|
113
|
+
border 0
|
|
114
|
+
border-top 1px solid $line
|
|
115
|
+
margin 3rem 0
|
|
116
|
+
|
|
117
|
+
small, .small
|
|
118
|
+
font-size .82rem
|
|
119
|
+
color $muted
|
|
120
|
+
|
|
121
|
+
/* Chrome (nav, buttons, labels, badges) leans on a clean sans for a
|
|
122
|
+
modern editorial contrast against the serif prose. */
|
|
123
|
+
.topnav, .btn, .eyebrow, .pill, .tag, .badge, .kbd, .sitefooter, .source-reveal summary
|
|
124
|
+
font-family system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif
|
|
125
|
+
|
|
126
|
+
/* ── Layout ─────────────────────────────────────────────────────── */
|
|
127
|
+
|
|
128
|
+
main
|
|
129
|
+
max-width 940px
|
|
130
|
+
margin 0 auto
|
|
131
|
+
padding 0 1.5rem 5rem
|
|
132
|
+
|
|
133
|
+
.wide
|
|
134
|
+
max-width 1140px
|
|
135
|
+
|
|
136
|
+
.eyebrow
|
|
137
|
+
display inline-block
|
|
138
|
+
text-transform uppercase
|
|
139
|
+
letter-spacing .14em
|
|
140
|
+
font-size .74rem
|
|
141
|
+
font-weight 600
|
|
142
|
+
color $accent
|
|
143
|
+
margin 0 0 .35rem
|
|
144
|
+
|
|
145
|
+
/* ── Buttons ────────────────────────────────────────────────────── */
|
|
146
|
+
|
|
147
|
+
.btn
|
|
148
|
+
display inline-flex
|
|
149
|
+
align-items center
|
|
150
|
+
gap .5rem
|
|
151
|
+
margin .4rem .5rem .4rem 0
|
|
152
|
+
padding .7rem 1.15rem
|
|
153
|
+
font-size .95rem
|
|
154
|
+
font-weight 550
|
|
155
|
+
line-height 1
|
|
156
|
+
color $ink
|
|
157
|
+
bg $panel
|
|
158
|
+
border 1px solid $line
|
|
159
|
+
radius 999px
|
|
160
|
+
text-decoration none
|
|
161
|
+
cursor pointer
|
|
162
|
+
transition transform .12s ease, box-shadow .12s ease, background .12s ease
|
|
163
|
+
|
|
164
|
+
.btn:hover
|
|
165
|
+
transform translateY(-1px)
|
|
166
|
+
box-shadow 0 6px 18px -8px rgba(28,24,20,.35)
|
|
167
|
+
|
|
168
|
+
.btn.primary
|
|
169
|
+
bg $accent
|
|
170
|
+
color #fff
|
|
171
|
+
border-color $accent
|
|
172
|
+
|
|
173
|
+
.btn.primary:hover
|
|
174
|
+
bg $accent-deep
|
|
175
|
+
color #fff
|
|
176
|
+
|
|
177
|
+
.btn.warm
|
|
178
|
+
bg $warm
|
|
179
|
+
color #fff
|
|
180
|
+
border-color $warm
|
|
181
|
+
|
|
182
|
+
.btn.ghost
|
|
183
|
+
bg transparent
|
|
184
|
+
|
|
185
|
+
/* ── Cards & surfaces ───────────────────────────────────────────── */
|
|
186
|
+
|
|
187
|
+
.card
|
|
188
|
+
bg $panel
|
|
189
|
+
border 1px solid $line
|
|
190
|
+
radius $radius
|
|
191
|
+
padding 1.4rem
|
|
192
|
+
box-shadow 0 1px 2px rgba(28,24,20,.04)
|
|
193
|
+
|
|
194
|
+
.grid
|
|
195
|
+
display grid
|
|
196
|
+
gap 1.1rem
|
|
197
|
+
|
|
198
|
+
.grid.cols-2
|
|
199
|
+
grid-template-columns repeat(2, 1fr)
|
|
200
|
+
|
|
201
|
+
.grid.cols-3
|
|
202
|
+
grid-template-columns repeat(3, 1fr)
|
|
203
|
+
|
|
204
|
+
.grid.auto
|
|
205
|
+
grid-template-columns repeat(auto-fit, minmax(240px, 1fr))
|
|
206
|
+
|
|
207
|
+
@media (max-width: 760px)
|
|
208
|
+
.grid.cols-2, .grid.cols-3
|
|
209
|
+
grid-template-columns 1fr
|
|
210
|
+
|
|
211
|
+
/* ── Pills, tags, badges ────────────────────────────────────────── */
|
|
212
|
+
|
|
213
|
+
.pill, .tag
|
|
214
|
+
display inline-flex
|
|
215
|
+
align-items center
|
|
216
|
+
gap .35rem
|
|
217
|
+
font-size .72rem
|
|
218
|
+
font-weight 600
|
|
219
|
+
text-transform uppercase
|
|
220
|
+
letter-spacing .06em
|
|
221
|
+
padding .28rem .6rem
|
|
222
|
+
radius 999px
|
|
223
|
+
|
|
224
|
+
.pill
|
|
225
|
+
bg $accent-soft
|
|
226
|
+
color $accent-deep
|
|
227
|
+
|
|
228
|
+
.tag
|
|
229
|
+
bg $sunk
|
|
230
|
+
color $muted
|
|
231
|
+
|
|
232
|
+
.kbd
|
|
233
|
+
font-family ui-monospace, "SF Mono", Menlo, monospace
|
|
234
|
+
font-size .8rem
|
|
235
|
+
padding .15rem .4rem
|
|
236
|
+
radius 6px
|
|
237
|
+
bg $panel
|
|
238
|
+
border 1px solid $line
|
|
239
|
+
color $ink-soft
|
|
240
|
+
|
|
241
|
+
/* ── Callout (the demo :note directive renders <aside class="note">) ─ */
|
|
242
|
+
|
|
243
|
+
.note, .callout
|
|
244
|
+
margin 1.6rem 0
|
|
245
|
+
padding 1rem 1.25rem
|
|
246
|
+
border 1px solid $line
|
|
247
|
+
border-left 3px solid $accent
|
|
248
|
+
bg $panel
|
|
249
|
+
radius $radius-sm
|
|
250
|
+
color $ink-soft
|
|
251
|
+
font-size .96rem
|
|
252
|
+
|
|
253
|
+
/* ── Code ───────────────────────────────────────────────────────── */
|
|
254
|
+
|
|
255
|
+
code
|
|
256
|
+
font-family ui-monospace, "SF Mono", Menlo, Consolas, monospace
|
|
257
|
+
font-size .88em
|
|
258
|
+
bg $sunk
|
|
259
|
+
color $accent-deep
|
|
260
|
+
padding .12rem .36rem
|
|
261
|
+
radius 6px
|
|
262
|
+
|
|
263
|
+
h1 code, h2 code, h3 code
|
|
264
|
+
font-size .82em
|
|
265
|
+
|
|
266
|
+
pre
|
|
267
|
+
bg $code-bg
|
|
268
|
+
color $code-ink
|
|
269
|
+
padding 1.2rem 1.35rem
|
|
270
|
+
margin 1.5rem 0
|
|
271
|
+
radius $radius
|
|
272
|
+
overflow-x auto
|
|
273
|
+
line-height 1.62
|
|
274
|
+
font-size .87rem
|
|
275
|
+
box-shadow inset 0 0 0 1px $code-line
|
|
276
|
+
|
|
277
|
+
pre code
|
|
278
|
+
bg transparent
|
|
279
|
+
color inherit
|
|
280
|
+
padding 0
|
|
281
|
+
font-size 1em
|
|
282
|
+
|
|
283
|
+
/* ── Tables ─────────────────────────────────────────────────────── */
|
|
284
|
+
|
|
285
|
+
table
|
|
286
|
+
border-collapse collapse
|
|
287
|
+
width 100%
|
|
288
|
+
margin 1.5rem 0
|
|
289
|
+
font-size .95rem
|
|
290
|
+
|
|
291
|
+
th
|
|
292
|
+
text-align left
|
|
293
|
+
padding .6rem .9rem
|
|
294
|
+
border-bottom 2px solid $line
|
|
295
|
+
color $muted
|
|
296
|
+
font-weight 600
|
|
297
|
+
font-size .82rem
|
|
298
|
+
text-transform uppercase
|
|
299
|
+
letter-spacing .04em
|
|
300
|
+
|
|
301
|
+
td
|
|
302
|
+
padding .6rem .9rem
|
|
303
|
+
border-bottom 1px solid $line-soft
|
|
304
|
+
|
|
305
|
+
blockquote
|
|
306
|
+
margin 1.6rem 0
|
|
307
|
+
padding .6rem 1.3rem
|
|
308
|
+
border-left 3px solid $accent
|
|
309
|
+
color $ink-soft
|
|
310
|
+
font-style italic
|
|
311
|
+
|
|
312
|
+
/* ── "View source" reveal — the two-beat trick: a slick app, then the
|
|
313
|
+
Markdown it's made of. Wrap a fenced ```wd block in <details>. ── */
|
|
314
|
+
|
|
315
|
+
.source-reveal
|
|
316
|
+
margin 1.5rem 0
|
|
317
|
+
border 1px solid $line
|
|
318
|
+
radius $radius
|
|
319
|
+
bg $panel
|
|
320
|
+
overflow hidden
|
|
321
|
+
|
|
322
|
+
.source-reveal summary
|
|
323
|
+
cursor pointer
|
|
324
|
+
padding .85rem 1.1rem
|
|
325
|
+
font-size .9rem
|
|
326
|
+
font-weight 600
|
|
327
|
+
color $accent-deep
|
|
328
|
+
list-style none
|
|
329
|
+
display flex
|
|
330
|
+
align-items center
|
|
331
|
+
gap .5rem
|
|
332
|
+
|
|
333
|
+
.source-reveal summary::-webkit-details-marker
|
|
334
|
+
display none
|
|
335
|
+
|
|
336
|
+
.source-reveal summary::before
|
|
337
|
+
content "›"
|
|
338
|
+
font-size 1.1rem
|
|
339
|
+
transition transform .15s ease
|
|
340
|
+
|
|
341
|
+
.source-reveal[open] summary::before
|
|
342
|
+
transform rotate(90deg)
|
|
343
|
+
|
|
344
|
+
.source-reveal pre
|
|
345
|
+
margin 0
|
|
346
|
+
radius 0
|
|
347
|
+
|
|
348
|
+
/* ── Footer ─────────────────────────────────────────────────────── */
|
|
349
|
+
|
|
350
|
+
.sitefooter
|
|
351
|
+
border-top 1px solid $line
|
|
352
|
+
margin-top 4rem
|
|
353
|
+
padding 2.5rem 1.5rem
|
|
354
|
+
color $muted
|
|
355
|
+
font-size .9rem
|
|
356
|
+
|
|
357
|
+
.sitefooter .foot-inner
|
|
358
|
+
max-width 940px
|
|
359
|
+
margin 0 auto
|
|
360
|
+
display flex
|
|
361
|
+
flex-wrap wrap
|
|
362
|
+
gap 1rem 2rem
|
|
363
|
+
align-items center
|
|
364
|
+
justify-content space-between
|
|
365
|
+
|
|
366
|
+
.sitefooter a
|
|
367
|
+
color $muted
|
|
368
|
+
text-decoration none
|
|
369
|
+
margin-right 1.1rem
|
|
370
|
+
|
|
371
|
+
.sitefooter a:hover
|
|
372
|
+
color $accent
|
package/site/_/base.wd
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- Shared design-system carrier. Including this file injects base.skin (the
|
|
2
|
+
refreshed Darkmown brand foundation: tokens, typography, components, dark
|
|
3
|
+
mode) into the page. It renders no markup of its own. Pair with /nav.wd at
|
|
4
|
+
the top and /footer.wd at the bottom. -->
|
package/site/_/footer.wd
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<footer class="sitefooter">
|
|
2
|
+
<div class="foot-inner">
|
|
3
|
+
<div>
|
|
4
|
+
<strong>Darkmown</strong> — Markdown that runs.
|
|
5
|
+
<div class="small">Zero JS on static pages · one ~7.4 KB runtime when reactive.</div>
|
|
6
|
+
</div>
|
|
7
|
+
<nav class="foot-links">
|
|
8
|
+
<a href="/docs/">Docs</a>
|
|
9
|
+
<a href="/showcase/">Showcase</a>
|
|
10
|
+
<a href="https://github.com/ZVN-DEV/darkmown">GitHub</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@zvndev/darkmown">npm</a>
|
|
12
|
+
</nav>
|
|
13
|
+
</div>
|
|
14
|
+
</footer>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"updated": "2026-06-26T05:00:00Z",
|
|
3
|
+
"services": [
|
|
4
|
+
{ "id": "api", "name": "API Gateway", "status": "ok", "latency": 42, "uptime": 99.98, "region": "iad", "spark": [38,41,40,44,42,39,42] },
|
|
5
|
+
{ "id": "web", "name": "Web Frontend", "status": "ok", "latency": 18, "uptime": 99.99, "region": "global", "spark": [16,18,17,19,18,18,18] },
|
|
6
|
+
{ "id": "db", "name": "Primary DB", "status": "warn", "latency": 137, "uptime": 99.82, "region": "iad", "spark": [90,110,120,150,140,137,137] },
|
|
7
|
+
{ "id": "cache", "name": "Edge Cache", "status": "ok", "latency": 4, "uptime": 100.0, "region": "global", "spark": [4,3,4,5,4,4,4] },
|
|
8
|
+
{ "id": "queue", "name": "Job Queue", "status": "ok", "latency": 61, "uptime": 99.95, "region": "sfo", "spark": [55,60,58,64,61,59,61] },
|
|
9
|
+
{ "id": "search", "name": "Search Index", "status": "down", "latency": 0, "uptime": 97.40, "region": "sfo", "spark": [120,140,160,0,0,0,0] },
|
|
10
|
+
{ "id": "auth", "name": "Auth Service", "status": "ok", "latency": 33, "uptime": 99.97, "region": "iad", "spark": [30,34,32,35,33,33,33] },
|
|
11
|
+
{ "id": "mail", "name": "Mail Relay", "status": "warn", "latency": 210, "uptime": 99.60, "region": "fra", "spark": [180,200,190,230,210,205,210] }
|
|
12
|
+
]
|
|
13
|
+
}
|
package/site/_/nav.skin
CHANGED
|
@@ -9,40 +9,63 @@ tokens
|
|
|
9
9
|
codeink #ecf3ea
|
|
10
10
|
radius 8px
|
|
11
11
|
|
|
12
|
+
/* No `tokens dark` here on purpose. The shared nav loads on EVERY page,
|
|
13
|
+
including the older feature-demo pages (/reactive, /loops, …) whose bespoke
|
|
14
|
+
component styles were never designed for a dark palette — flipping their
|
|
15
|
+
shared tokens made buttons unreadable. Dark mode is therefore opt-in: it
|
|
16
|
+
lives in base.skin, which only the refreshed pages and showcase apps include.
|
|
17
|
+
On those pages base.skin's dark tokens drive the nav too, so it still goes
|
|
18
|
+
dark there. */
|
|
19
|
+
|
|
12
20
|
.topnav
|
|
13
21
|
display flex
|
|
14
22
|
flex-wrap wrap
|
|
15
|
-
gap .
|
|
23
|
+
gap .35rem 1.1rem
|
|
16
24
|
align-items center
|
|
17
|
-
padding
|
|
25
|
+
padding .85rem 1.5rem
|
|
18
26
|
max-width 100%
|
|
19
27
|
box-sizing border-box
|
|
20
28
|
border-bottom 1px solid $line
|
|
21
|
-
|
|
22
|
-
backdrop-filter blur(
|
|
29
|
+
background color-mix(in srgb, $paper 82%, transparent)
|
|
30
|
+
backdrop-filter saturate(1.4) blur(10px)
|
|
23
31
|
position sticky
|
|
24
32
|
top 0
|
|
25
|
-
z-index
|
|
33
|
+
z-index 20
|
|
34
|
+
font-size .92rem
|
|
26
35
|
|
|
27
36
|
@media (max-width: 600px)
|
|
28
37
|
.topnav
|
|
29
|
-
padding .
|
|
30
|
-
gap .
|
|
38
|
+
padding .75rem 1rem
|
|
39
|
+
gap .3rem .8rem
|
|
31
40
|
|
|
32
|
-
.topnav
|
|
41
|
+
.topnav .brand
|
|
42
|
+
font-weight 700
|
|
43
|
+
font-size 1.08rem
|
|
44
|
+
letter-spacing -.02em
|
|
33
45
|
color $warm
|
|
34
|
-
|
|
46
|
+
text-decoration none
|
|
35
47
|
margin-right .5rem
|
|
36
48
|
|
|
49
|
+
.topnav .nav-links
|
|
50
|
+
display inline-flex
|
|
51
|
+
flex-wrap wrap
|
|
52
|
+
gap .35rem 1.1rem
|
|
53
|
+
align-items center
|
|
54
|
+
|
|
37
55
|
.topnav a
|
|
38
56
|
color $accent
|
|
39
57
|
text-decoration none
|
|
40
58
|
|
|
41
59
|
.topnav a:hover
|
|
42
|
-
|
|
60
|
+
color $ink
|
|
61
|
+
|
|
62
|
+
.topnav .nav-gh
|
|
63
|
+
margin-left auto
|
|
64
|
+
color $ink
|
|
65
|
+
font-weight 600
|
|
43
66
|
|
|
44
67
|
code
|
|
45
|
-
|
|
68
|
+
background color-mix(in srgb, $paper 50%, $panel)
|
|
46
69
|
border 1px solid $line
|
|
47
70
|
padding .1rem .32rem
|
|
48
71
|
radius 5px
|
|
@@ -78,7 +101,7 @@ th
|
|
|
78
101
|
text-align left
|
|
79
102
|
border 1px solid $line
|
|
80
103
|
padding .5rem .9rem
|
|
81
|
-
|
|
104
|
+
background color-mix(in srgb, $paper 60%, $line)
|
|
82
105
|
|
|
83
106
|
td
|
|
84
107
|
border 1px solid $line
|
package/site/_/nav.wd
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
<nav class="topnav">
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<a href="/
|
|
10
|
-
<a href="/data/">Data & Forms</a>
|
|
11
|
-
<a href="/app/">Live app</a>
|
|
2
|
+
<a class="brand" href="/">Darkmown</a>
|
|
3
|
+
<span class="nav-links">
|
|
4
|
+
<a href="/docs/">Docs</a>
|
|
5
|
+
<a href="/showcase/">Showcase</a>
|
|
6
|
+
<a href="/reactive/">Reactive</a>
|
|
7
|
+
<a href="/data/">Data</a>
|
|
8
|
+
</span>
|
|
9
|
+
<a class="nav-gh" href="https://github.com/ZVN-DEV/darkmown">GitHub ↗</a>
|
|
12
10
|
</nav>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "id": "aurora", "name": "Aurora Lamp", "price": 149, "category": "Light", "emoji": "🪔", "tint": "#f4d58d", "blurb": "A warm dimmable glow with a hand-turned oak base." },
|
|
3
|
+
{ "id": "briza", "name": "Briza Fan", "price": 89, "category": "Air", "emoji": "🌀", "tint": "#bcd6e8", "blurb": "Whisper-quiet bladeless airflow, USB-C powered." },
|
|
4
|
+
{ "id": "cove", "name": "Cove Speaker", "price": 199, "category": "Sound", "emoji": "🔊", "tint": "#cbbfe6", "blurb": "Room-filling 360° sound in a palm-sized shell." },
|
|
5
|
+
{ "id": "dune", "name": "Dune Mug", "price": 24, "category": "Kitchen", "emoji": "☕", "tint": "#e8c9a8", "blurb": "Double-walled stoneware that keeps coffee hot for hours." },
|
|
6
|
+
{ "id": "fjord", "name": "Fjord Bottle", "price": 39, "category": "Kitchen", "emoji": "🧊", "tint": "#a8d8d0", "blurb": "Vacuum steel, 24h cold, sweat-proof matte finish." },
|
|
7
|
+
{ "id": "glint", "name": "Glint Desk Clock", "price": 69, "category": "Light", "emoji": "🕰️", "tint": "#f0c8c0", "blurb": "E-ink face, ambient backlight, three-month battery." },
|
|
8
|
+
{ "id": "haven", "name": "Haven Diffuser", "price": 59, "category": "Air", "emoji": "🌿", "tint": "#c4e0b4", "blurb": "Ultrasonic mist with a refillable cedar reservoir." },
|
|
9
|
+
{ "id": "isle", "name": "Isle Earbuds", "price": 129, "category": "Sound", "emoji": "🎧", "tint": "#d8c4ec", "blurb": "Active noise cancelling, 30-hour case, tap controls." }
|
|
10
|
+
]
|
package/site/pages/app.wd
CHANGED
|
@@ -19,7 +19,7 @@ This whole section is plain Markdown. The list below is unrolled **at build time
|
|
|
19
19
|
- **{ feature.title }** — { feature.body }
|
|
20
20
|
@endloop
|
|
21
21
|
|
|
22
|
-
<aside class="boundary">If you deleted everything below this line, this page would ship <strong>zero</strong> bytes of JavaScript. Everything above is static HTML. Below is where the one ~
|
|
22
|
+
<aside class="boundary">If you deleted everything below this line, this page would ship <strong>zero</strong> bytes of JavaScript. Everything above is static HTML. Below is where the one ~7.4 KB gzipped runtime loads (CI-enforced under 8 KB) — and only because the document asks for it.</aside>
|
|
23
23
|
|
|
24
24
|
## Fetched data, looped, with nested conditions
|
|
25
25
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Carousel behavior — the escape hatch in action.
|
|
2
|
+
//
|
|
3
|
+
// Darkmown owns the reactive `slide` state, the indicator dots (`.active when`),
|
|
4
|
+
// and the "Slide N of 5" readout. This colocated module adds exactly what a
|
|
5
|
+
// deliberately tiny framework leaves out: arrow + keyboard + pointer-drag navigation
|
|
6
|
+
// and the sliding transition. It never reaches into the framework's internals —
|
|
7
|
+
// it reads and writes the same `slide` value through the public `window.wd` API.
|
|
8
|
+
//
|
|
9
|
+
// Module scripts run after /__wd/runtime.js (which defines window.wd) and in
|
|
10
|
+
// document order, so `wd` is guaranteed ready here.
|
|
11
|
+
|
|
12
|
+
const viewport = document.querySelector("[data-swiper]");
|
|
13
|
+
|
|
14
|
+
if (viewport && window.wd) {
|
|
15
|
+
const track = viewport.querySelector("[data-swiper-track]");
|
|
16
|
+
const slides = [...track.children];
|
|
17
|
+
const N = slides.length;
|
|
18
|
+
const KEY = "slide";
|
|
19
|
+
|
|
20
|
+
const wrap = (i) => ((i % N) + N) % N;
|
|
21
|
+
const current = () => wrap(Number(window.wd.get(KEY)) || 0);
|
|
22
|
+
const go = (i) => window.wd.set(KEY, wrap(i));
|
|
23
|
+
const SETTLE = "transform .42s cubic-bezier(.22,.61,.36,1)";
|
|
24
|
+
|
|
25
|
+
// State → view. Primed immediately, then on every settled change. Because the
|
|
26
|
+
// arrows/keyboard/drag below always write a wrapped value, `slide` never leaves
|
|
27
|
+
// [0, N-1], so the declarative dots and readout stay valid every frame.
|
|
28
|
+
window.wd.subscribe(KEY, (raw) => {
|
|
29
|
+
const i = wrap(raw);
|
|
30
|
+
track.style.transition = SETTLE;
|
|
31
|
+
track.style.transform = `translateX(${-i * 100}%)`;
|
|
32
|
+
slides.forEach((s, k) => s.setAttribute("aria-hidden", k === i ? "false" : "true"));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Arrow buttons (plain <button>s, wired here — not :button directives).
|
|
36
|
+
document.querySelector("[data-swiper-prev]")?.addEventListener("click", () => go(current() - 1));
|
|
37
|
+
document.querySelector("[data-swiper-next]")?.addEventListener("click", () => go(current() + 1));
|
|
38
|
+
|
|
39
|
+
// Keyboard: the viewport is focusable; ← / → step through.
|
|
40
|
+
viewport.addEventListener("keydown", (event) => {
|
|
41
|
+
if (event.key === "ArrowLeft") { event.preventDefault(); go(current() - 1); }
|
|
42
|
+
else if (event.key === "ArrowRight") { event.preventDefault(); go(current() + 1); }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Pointer drag / touch swipe. Follow the finger while dragging (transition off),
|
|
46
|
+
// then either advance or snap back on release.
|
|
47
|
+
let startX = 0;
|
|
48
|
+
let dragging = false;
|
|
49
|
+
let width = 1;
|
|
50
|
+
|
|
51
|
+
viewport.addEventListener("pointerdown", (event) => {
|
|
52
|
+
if (event.button != null && event.button !== 0) return;
|
|
53
|
+
dragging = true;
|
|
54
|
+
startX = event.clientX;
|
|
55
|
+
width = viewport.clientWidth || 1;
|
|
56
|
+
track.style.transition = "none";
|
|
57
|
+
try { viewport.setPointerCapture(event.pointerId); } catch { /* capture is best-effort */ }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
viewport.addEventListener("pointermove", (event) => {
|
|
61
|
+
if (!dragging) return;
|
|
62
|
+
const dx = event.clientX - startX;
|
|
63
|
+
track.style.transform = `translateX(calc(${-current() * 100}% + ${dx}px))`;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const release = (event) => {
|
|
67
|
+
if (!dragging) return;
|
|
68
|
+
dragging = false;
|
|
69
|
+
const dx = event.clientX - startX;
|
|
70
|
+
if (Math.abs(dx) > width * 0.18) {
|
|
71
|
+
go(current() + (dx < 0 ? 1 : -1));
|
|
72
|
+
} else {
|
|
73
|
+
// Below threshold: ease back to the current slide. The value didn't change,
|
|
74
|
+
// so subscribe won't re-fire — reset the transform directly.
|
|
75
|
+
track.style.transition = "transform .3s ease";
|
|
76
|
+
track.style.transform = `translateX(${-current() * 100}%)`;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
viewport.addEventListener("pointerup", release);
|
|
81
|
+
viewport.addEventListener("pointercancel", release);
|
|
82
|
+
}
|