@zvndev/darkmown 1.4.0 → 2.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.
Files changed (106) hide show
  1. package/AGENTS.md +17 -3
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +114 -17
  4. package/SECURITY.md +21 -18
  5. package/docs/cli.md +5 -1
  6. package/docs/spec-alignment.md +31 -4
  7. package/package.json +1 -1
  8. package/site/_/base.skin +239 -18
  9. package/site/_/base.wd +4 -0
  10. package/site/_/feature-card.wd +4 -0
  11. package/site/_/footer.wd +4 -0
  12. package/site/_/nav.wd +5 -1
  13. package/site/_/scoped-promo.wd +4 -0
  14. package/site/_/scoped-stat.wd +4 -0
  15. package/site/pages/404.wd +1 -0
  16. package/site/pages/app.skin +27 -5
  17. package/site/pages/app.wd +1 -0
  18. package/site/pages/blog/_schema.wd +15 -0
  19. package/site/pages/blog/collections-and-pagination.md +38 -0
  20. package/site/pages/blog/collections-and-pagination.skin +127 -0
  21. package/site/pages/blog/drafts-that-never-leak.md +2 -0
  22. package/site/pages/blog/drafts-that-never-leak.skin +127 -0
  23. package/site/pages/blog/index.skin +144 -0
  24. package/site/pages/blog/index.wd +74 -0
  25. package/site/pages/blog/shipping-feeds.md +1 -0
  26. package/site/pages/blog/shipping-feeds.skin +127 -0
  27. package/site/pages/blog/typed-frontmatter.md +45 -0
  28. package/site/pages/blog/typed-frontmatter.skin +127 -0
  29. package/site/pages/blog/unfinished-thoughts.skin +127 -0
  30. package/site/pages/blog/zero-js-by-default.md +31 -0
  31. package/site/pages/blog/zero-js-by-default.skin +127 -0
  32. package/site/pages/carousel/index.wd +1 -0
  33. package/site/pages/compass/index.wd +1 -0
  34. package/site/pages/data.skin +50 -9
  35. package/site/pages/data.wd +3 -0
  36. package/site/pages/docs/-relative-note.wd +4 -0
  37. package/site/pages/docs/index.skin +27 -0
  38. package/site/pages/docs/index.wd +115 -11
  39. package/site/pages/folio/-style.wd +4 -0
  40. package/site/pages/folio/cart.wd +1 -0
  41. package/site/pages/folio/index.wd +1 -0
  42. package/site/pages/forge/index.wd +1 -0
  43. package/site/pages/highlight/index.wd +1 -0
  44. package/site/pages/index.wd +1 -0
  45. package/site/pages/interactions/index.wd +1 -0
  46. package/site/pages/ledger/index.wd +1 -0
  47. package/site/pages/loops.skin +43 -6
  48. package/site/pages/loops.wd +1 -0
  49. package/site/pages/media/index.wd +1 -0
  50. package/site/pages/pulse/index.wd +1 -0
  51. package/site/pages/reactive.skin +38 -6
  52. package/site/pages/reactive.wd +1 -0
  53. package/site/pages/scoped/index.wd +1 -0
  54. package/site/pages/showcase/index.wd +1 -0
  55. package/site/pages/store.skin +43 -6
  56. package/site/pages/store.wd +1 -0
  57. package/site/pages/theme.wd +1 -0
  58. package/src/builder.js +535 -48
  59. package/src/cli.js +96 -18
  60. package/src/compiler/actions.js +303 -0
  61. package/src/compiler/body.js +36 -24
  62. package/src/compiler/collections.js +316 -0
  63. package/src/compiler/context.js +83 -6
  64. package/src/compiler/directives.js +31 -1470
  65. package/src/compiler/fetch.js +159 -0
  66. package/src/compiler/format.js +17 -4
  67. package/src/compiler/forms.js +456 -0
  68. package/src/compiler/frontmatter.js +10 -5
  69. package/src/compiler/includes.js +66 -1
  70. package/src/compiler/index.js +15 -3
  71. package/src/compiler/loops.js +181 -31
  72. package/src/compiler/markdown.js +65 -15
  73. package/src/compiler/media.js +133 -0
  74. package/src/compiler/page.js +78 -41
  75. package/src/compiler/state.js +168 -0
  76. package/src/compiler/structure.js +377 -0
  77. package/src/dev.js +76 -0
  78. package/src/headers.js +56 -17
  79. package/src/router.js +12 -1
  80. package/src/runtime.js +5 -3
  81. package/src/statics.js +63 -12
  82. package/src/templates/blog/README.md +13 -4
  83. package/src/templates/blog/site/_/nav.wd +4 -0
  84. package/src/templates/blog/site/pages/index.skin +87 -0
  85. package/src/templates/blog/site/pages/index.wd +10 -4
  86. package/src/templates/blog/site/pages/posts/_schema.wd +16 -0
  87. package/src/templates/blog/site/pages/posts/hello-darkmown.md +2 -0
  88. package/src/templates/blog/site/pages/posts/hello-darkmown.skin +59 -0
  89. package/src/templates/blog/site/pages/posts/markdown-native.md +9 -4
  90. package/src/templates/blog/site/pages/posts/markdown-native.skin +59 -0
  91. package/src/templates/dashboard/site/_/nav.wd +4 -0
  92. package/src/templates/dashboard/site/pages/index.skin +84 -0
  93. package/src/templates/dashboard/site/pages/index.wd +1 -0
  94. package/src/templates/landing/site/_/nav.wd +4 -0
  95. package/src/templates/landing/site/pages/index.skin +113 -6
  96. package/src/templates/landing/site/pages/index.wd +1 -0
  97. package/src/templates/starter/site/_/nav.wd +4 -0
  98. package/src/templates/starter/site/pages/about.md +18 -1
  99. package/src/templates/starter/site/pages/about.skin +69 -0
  100. package/src/templates/starter/site/pages/index.skin +81 -3
  101. package/src/templates/starter/site/pages/index.wd +1 -0
  102. package/src/templates/store/site/_/nav.wd +4 -0
  103. package/src/templates/store/site/pages/index.skin +93 -11
  104. package/src/templates/store/site/pages/index.wd +1 -0
  105. package/site/pages/blog/index.md +0 -13
  106. package/src/templates/blog/site/_/posts.json +0 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvndev/darkmown",
3
- "version": "1.4.0",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/site/_/base.skin CHANGED
@@ -37,6 +37,13 @@ tokens
37
37
  radius 12px
38
38
  radius-sm 8px
39
39
  radius-lg 20px
40
+ field-bg #ffffff
41
+ field-line #d9d2c4
42
+ field-line-strong #c4baa8
43
+ control-shadow 0 1px 2px rgba(28,24,20,.06)
44
+ control-shadow-hover 0 6px 18px -8px rgba(28,24,20,.45)
45
+ accent-press #094339
46
+ on-accent #ffffff
40
47
 
41
48
  tokens dark
42
49
  ink #ece6db
@@ -66,6 +73,13 @@ tokens dark
66
73
  code-number #f2bb8a
67
74
  code-punctuation #b6c2bc
68
75
  ring #5ccab4
76
+ field-bg #1a1612
77
+ field-line #38301f
78
+ field-line-strong #4a3f2c
79
+ control-shadow 0 1px 2px rgba(0,0,0,.4)
80
+ control-shadow-hover 0 8px 22px -10px rgba(0,0,0,.7)
81
+ accent-press #b6ecdc
82
+ on-accent #0c1310
69
83
 
70
84
  /* ── Reset & base ───────────────────────────────────────────────── */
71
85
 
@@ -156,45 +170,242 @@ main
156
170
  color $accent
157
171
  margin 0 0 .35rem
158
172
 
159
- /* ── Buttons ────────────────────────────────────────────────────── */
160
-
161
- .btn
173
+ /* ── Buttons ────────────────────────────────────────────────────────
174
+ Two ways to get a button, ONE consistent look:
175
+ 1. The `.btn` class (for link-styled buttons: <a class="btn">).
176
+ 2. A bare <button> — what the `:button`/`:submit` directives emit with
177
+ no class. Both share the same sizing, states, and tokens so a demo,
178
+ a scaffold, or a docs page looks designed without per-page styling.
179
+ The bare-button default is the ACCENT (filled) treatment, because a
180
+ directive button is almost always the page's primary action. Opt down
181
+ to a quieter look with class="secondary" / class="ghost".
182
+ ─────────────────────────────────────────────────────────────────── */
183
+
184
+ /* Shared geometry + motion for every button-shaped thing. One-line comma
185
+ selector (multi-line comma lists silently drop all but the last). */
186
+ .btn, button, button[data-wd-action], button[data-wd-actions], [type=submit], [type=button], [type=reset]
162
187
  display inline-flex
163
188
  align-items center
189
+ justify-content center
164
190
  gap .5rem
165
191
  margin .4rem .5rem .4rem 0
166
- padding .7rem 1.15rem
167
- font-size .95rem
168
- font-weight 550
169
- line-height 1
192
+ padding .62rem 1.15rem
193
+ font-family system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif
194
+ font-size .94rem
195
+ font-weight 560
196
+ line-height 1.15
197
+ letter-spacing -.005em
170
198
  color $ink
171
199
  bg $panel
172
200
  border 1px solid $line
173
201
  radius 999px
174
202
  text-decoration none
175
203
  cursor pointer
176
- transition transform .12s ease, box-shadow .12s ease, background .12s ease
177
-
178
- .btn:hover
179
- transform translateY(-1px)
180
- box-shadow 0 6px 18px -8px rgba(28,24,20,.35)
204
+ -webkit-appearance none
205
+ appearance none
206
+ transition transform .14s cubic-bezier(.2,.7,.3,1), box-shadow .14s ease, background .14s ease, border-color .14s ease, color .14s ease
181
207
 
182
- .btn.primary
208
+ /* The filled accent default — applies to bare directive buttons and to
209
+ the opt-in `.btn.primary` / `.btn.warm` link variants. One-line list. */
210
+ button, button[data-wd-action], button[data-wd-actions], [type=submit], .btn.primary
183
211
  bg $accent
184
- color #fff
212
+ color $on-accent
185
213
  border-color $accent
214
+ box-shadow $control-shadow
186
215
 
187
- .btn.primary:hover
216
+ button:hover, button[data-wd-action]:hover, button[data-wd-actions]:hover, [type=submit]:hover, .btn.primary:hover
188
217
  bg $accent-deep
189
- color #fff
218
+ border-color $accent-deep
219
+ color $on-accent
220
+ transform translateY(-1px)
221
+ box-shadow $control-shadow-hover
222
+
223
+ button:active, button[data-wd-action]:active, button[data-wd-actions]:active, [type=submit]:active, .btn.primary:active
224
+ bg $accent-press
225
+ border-color $accent-press
226
+ transform translateY(0)
227
+ box-shadow $control-shadow
228
+
229
+ /* Visible keyboard focus on every interactive control — accessibility. */
230
+ .btn:focus-visible, button:focus-visible, [type=submit]:focus-visible, [type=button]:focus-visible, [type=reset]:focus-visible
231
+ outline 2px solid $ring
232
+ outline-offset 2px
233
+
234
+ /* Skip-to-content link (emitted by the page shell) — themed reveal on focus. */
235
+ .wd-skip-link:focus
236
+ bg $accent
237
+ color $on-accent
238
+ border-radius 0 0 $radius-sm 0
239
+ outline 2px solid $ring
240
+ outline-offset 2px
241
+
242
+ button:disabled, [type=submit]:disabled, .btn[aria-disabled=true], [type=button]:disabled
243
+ opacity .5
244
+ cursor not-allowed
245
+ box-shadow none
246
+ transform none
247
+
248
+ /* Quieter, neutral button — a secondary/cancel action. One-line list. */
249
+ .btn, button.secondary, button.ghost, .btn.secondary, .btn.ghost
250
+ bg $panel
251
+ color $ink
252
+ border-color $line
253
+
254
+ .btn:hover
255
+ transform translateY(-1px)
256
+ box-shadow $control-shadow-hover
257
+
258
+ button.secondary:hover, .btn.secondary:hover
259
+ bg $sunk
260
+ border-color $line
261
+ color $ink
262
+ transform translateY(-1px)
263
+ box-shadow $control-shadow-hover
264
+
265
+ button.ghost, .btn.ghost
266
+ bg transparent
267
+ border-color transparent
268
+ box-shadow none
269
+
270
+ button.ghost:hover, .btn.ghost:hover
271
+ bg $sunk
272
+ box-shadow none
273
+ transform none
190
274
 
191
275
  .btn.warm
192
276
  bg $warm
193
277
  color #fff
194
278
  border-color $warm
195
279
 
196
- .btn.ghost
197
- bg transparent
280
+ .btn.warm:hover
281
+ bg $warm
282
+ color #fff
283
+
284
+ /* ── Form controls ──────────────────────────────────────────────────
285
+ `:input`/`:bind`/`:textarea`/`:select` emit bare <input>/<textarea>/
286
+ <select> — style them here so forms look designed by default. ─────── */
287
+
288
+ input, textarea, select
289
+ font-family system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif
290
+ font-size .95rem
291
+ line-height 1.4
292
+ color $ink
293
+ bg $field-bg
294
+ border 1px solid $field-line
295
+ radius $radius-sm
296
+ padding .58rem .8rem
297
+ margin .35rem 0
298
+ width 100%
299
+ max-width 28rem
300
+ -webkit-appearance none
301
+ appearance none
302
+ transition border-color .14s ease, box-shadow .14s ease, background .14s ease
303
+
304
+ input::placeholder, textarea::placeholder
305
+ color $faint
306
+
307
+ input:hover, textarea:hover, select:hover
308
+ border-color $field-line-strong
309
+
310
+ input:focus-visible, textarea:focus-visible, select:focus-visible
311
+ outline none
312
+ border-color $accent
313
+ box-shadow 0 0 0 3px $accent-soft
314
+
315
+ input:disabled, textarea:disabled, select:disabled
316
+ opacity .55
317
+ cursor not-allowed
318
+ bg $sunk
319
+
320
+ textarea
321
+ min-height 6.5rem
322
+ resize vertical
323
+
324
+ /* Native select chevron (the appearance reset hides the default arrow). */
325
+ select
326
+ background-image url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23756a5d' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E")
327
+ background-repeat no-repeat
328
+ background-position right .75rem center
329
+ padding-right 2.25rem
330
+ cursor pointer
331
+
332
+ /* Checkbox / radio — accent-tinted, comfortable hit target. The directive
333
+ emits <label><input type=checkbox|radio> Text</label> in .wd-checkboxes /
334
+ .wd-radios groups. One-line comma list. */
335
+ .wd-checkboxes, .wd-radios
336
+ display flex
337
+ flex-direction column
338
+ gap .55rem
339
+ margin .5rem 0
340
+
341
+ .wd-checkboxes label, .wd-radios label
342
+ display flex
343
+ align-items center
344
+ gap .55rem
345
+ cursor pointer
346
+
347
+ [type=checkbox], [type=radio]
348
+ width 1.1rem
349
+ height 1.1rem
350
+ min-width 1.1rem
351
+ margin 0
352
+ max-width none
353
+ padding 0
354
+ accent-color $accent
355
+ cursor pointer
356
+
357
+ [type=checkbox]:focus-visible, [type=radio]:focus-visible
358
+ outline 2px solid $ring
359
+ outline-offset 2px
360
+
361
+ label
362
+ font-size .95rem
363
+ color $ink-soft
364
+
365
+ /* The range slider (`:slider`) — tint the track to the brand accent. */
366
+ [type=range]
367
+ accent-color $accent
368
+ cursor pointer
369
+ padding 0
370
+
371
+ /* Framework control buttons (`:carousel` injects these at runtime). They
372
+ must NOT inherit the big accent pill above — reset them to a compact,
373
+ neutral round control. Page skins can still override. One-line lists. */
374
+ .wd-carousel-prev, .wd-carousel-next
375
+ width 2.25rem
376
+ height 2.25rem
377
+ padding 0
378
+ margin 0
379
+ bg $panel
380
+ color $ink
381
+ border 1px solid $line
382
+ radius 999px
383
+ box-shadow $control-shadow
384
+
385
+ .wd-carousel-prev:hover, .wd-carousel-next:hover
386
+ bg $panel
387
+ border-color $accent
388
+ color $accent
389
+ transform none
390
+ box-shadow $control-shadow-hover
391
+
392
+ .wd-carousel-dot
393
+ width .6rem
394
+ height .6rem
395
+ padding 0
396
+ margin 0
397
+ border 0
398
+ radius 999px
399
+ bg $line
400
+ box-shadow none
401
+
402
+ .wd-carousel-dot:hover
403
+ bg $faint
404
+ transform none
405
+ box-shadow none
406
+
407
+ .wd-carousel-dot[aria-current=true]
408
+ bg $accent
198
409
 
199
410
  /* ── Cards & surfaces ───────────────────────────────────────────── */
200
411
 
@@ -359,6 +570,16 @@ blockquote
359
570
  margin 0
360
571
  radius 0
361
572
 
573
+ /* ── Motion preference ──────────────────────────────────────────────
574
+ Honour users who ask for less motion: drop the button lift/transition.
575
+ (@media must be indented in a .skin — its rules wrap automatically.) ── */
576
+
577
+ @media (prefers-reduced-motion: reduce)
578
+ .btn, button, [type=submit], [type=button], input, textarea, select
579
+ transition none
580
+ .btn:hover, button:hover, [type=submit]:hover
581
+ transform none
582
+
362
583
  /* ── Footer ─────────────────────────────────────────────────────── */
363
584
 
364
585
  .sitefooter
package/site/_/base.wd CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <!-- Shared design-system carrier. Including this file injects base.skin (the
2
6
  refreshed Darkmown brand foundation: tokens, typography, components, dark
3
7
  mode) into the page. It renders no markup of its own. Pair with /nav.wd at
@@ -1,3 +1,7 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <section class="feature-card">
2
6
 
3
7
  **{ card.title }**
package/site/_/footer.wd CHANGED
@@ -1,3 +1,7 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <footer class="sitefooter">
2
6
  <div class="foot-inner">
3
7
  <div>
package/site/_/nav.wd CHANGED
@@ -1,11 +1,15 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <nav class="topnav">
2
6
  <a class="brand" href="/">Darkmown</a>
3
7
  <span class="nav-links">
4
8
  <a href="/docs/">Docs</a>
9
+ <a href="/blog/">Blog</a>
5
10
  <a href="/showcase/">Showcase</a>
6
11
  <a href="/reactive/">Reactive</a>
7
12
  <a href="/data/">Data</a>
8
- <a href="/highlight/">Highlight</a>
9
13
  </span>
10
14
  <a class="nav-gh" href="https://github.com/ZVN-DEV/darkmown">GitHub ↗</a>
11
15
  </nav>
@@ -1,3 +1,7 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <!-- Scoped component A. Its colocated scoped-promo.skin opts into scoping, so the
2
6
  `.card` here gets its OWN look — a warm, accent-bordered promo — without
3
7
  clashing with the identically-named `.card` in scoped-stat.wd. handleInclude
@@ -1,3 +1,7 @@
1
+ ---
2
+ html: true
3
+ ---
4
+
1
5
  <!-- Scoped component B. Reuses the SAME class names as scoped-promo.wd
2
6
  (.card / .title / .body) but its colocated scoped-stat.skin scopes them to a
3
7
  dark, centered stat tile. Proof the leak is gone: two `.card` definitions on
package/site/pages/404.wd CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ html: true
2
3
  title: Page not found — Darkmown
3
4
  description: That route does not exist. Head back to the Darkmown homepage.
4
5
  ---
@@ -57,20 +57,42 @@ h1
57
57
  color #9a2a2a
58
58
 
59
59
  button
60
+ display inline-flex
61
+ align-items center
62
+ justify-content center
63
+ gap .4rem
60
64
  background $accent
61
65
  color white
62
- border 0
63
- padding .55rem .9rem
66
+ border 1px solid $accent
67
+ padding .55rem 1rem
64
68
  margin .25rem .4rem .25rem 0
65
- border-radius 8px
66
- font-size .95rem
69
+ border-radius 999px
70
+ font-size .92rem
71
+ font-weight 560
67
72
  cursor pointer
73
+ appearance none
74
+ transition transform .14s cubic-bezier(.2,.7,.3,1), box-shadow .14s ease
75
+
76
+ button:hover
77
+ transform translateY(-1px)
78
+ box-shadow 0 8px 18px -8px rgba(15,107,94,.5)
79
+
80
+ button:focus-visible
81
+ outline 2px solid $accent
82
+ outline-offset 2px
68
83
 
69
84
  input
70
85
  font-size 1rem
71
- padding .6rem .7rem
86
+ padding .55rem .75rem
72
87
  border 1px solid $line
73
88
  border-radius 8px
74
89
  width 100%
75
90
  max-width 22rem
76
91
  margin .3rem 0
92
+ appearance none
93
+ transition border-color .14s ease, box-shadow .14s ease
94
+
95
+ input:focus-visible
96
+ outline none
97
+ border-color $accent
98
+ box-shadow 0 0 0 3px rgba(15,107,94,.18)
package/site/pages/app.wd CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ html: true
2
3
  title: A live app in one Markdown file
3
4
  description: One .wd file — static prose, fetched data, looped results with nested conditionals, a persistent counter, and a form that round-trips to a real server. No JavaScript written, no components, no build config.
4
5
  ---
@@ -0,0 +1,15 @@
1
+ ---
2
+ # Typed schema for the blog collection. Every post under site/pages/blog/ is
3
+ # validated against these rules at build time — a missing required field, a
4
+ # wrong type, or an unknown extra field fails the build with a file:line. Append
5
+ # "?" to a type to make the field optional. Remove this file to turn validation
6
+ # off (it is opt-in).
7
+ title: string
8
+ date: date
9
+ description: string
10
+ excerpt: string?
11
+ tags: string[]?
12
+ ---
13
+
14
+ The blog collection schema. Posts are plain `.md` files next to this file;
15
+ their frontmatter must match the rules above.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: Collections and pagination, with zero ceremony
3
+ description: Any folder under site/pages is a queryable collection. Loop it by name, sort it, paginate it — all at build time, all zero-JS.
4
+ date: 2026-06-28
5
+ excerpt: This very page is an entry in a collection. The list you came from is one @loop over a folder — no content root, no marker file, no config.
6
+ tags: [collections, content]
7
+ ---
8
+
9
+ # Collections and pagination, with zero ceremony
10
+
11
+ The page you came from is not a hand-maintained list of links. It is one
12
+ `@loop` over a *collection* — and a collection is just a folder. Every `.md`
13
+ and `.wd` file under `site/pages/blog/` is an entry, queryable by the folder's
14
+ bare name:
15
+
16
+ ```wd
17
+ @loop blog where draft == false sort by date desc paginate 3 into post
18
+ - [{ post.title }]({ post.url }) — { post.date }
19
+ @endloop
20
+ ```
21
+
22
+ No `content/` root to opt into, no marker file to drop in, no config object.
23
+ A folder of posts *is* the collection. Each entry's frontmatter becomes a row,
24
+ plus three fields the framework derives for you: `url` (the entry's route),
25
+ `slug` (its filename), and `excerpt` (the post's first paragraph when you don't
26
+ write one).
27
+
28
+ ## It is the same loop you already know
29
+
30
+ There is no second loop syntax. `where`, `sort by`, `reverse`, `offset`,
31
+ `limit`, and the format pipes all work over a collection exactly as they do over
32
+ a JSON file — because under the hood a collection resolves to the same array of
33
+ rows. The only new word is `paginate N`, which slices the listing into static
34
+ pages: page one keeps this listing's clean route, and pages two and up live at
35
+ `/blog/page/2/`, `/blog/page/3/`, and so on. Every one is plain HTML.
36
+
37
+ A pure listing like this ships **zero** framework JavaScript. The pager links —
38
+ newer, older, numbered — are just `<a href>`s to the generated routes.
@@ -0,0 +1,127 @@
1
+ /* Colocated post styling — a .skin next to a .md attaches automatically, so
2
+ these prose pages stay static (zero JS) yet still read on-brand. A .md can't
3
+ @include /base.wd, so this reading skin carries the site's tokens itself.
4
+ Kept in sync with site/_/base.skin; copy it beside each new post. */
5
+
6
+ tokens
7
+ ink #1c1814
8
+ ink-soft #4b4239
9
+ muted #756a5d
10
+ paper #faf8f3
11
+ panel #ffffff
12
+ sunk #efe9dc
13
+ line #e7e1d4
14
+ accent #0f6b5e
15
+ accent-deep #0a4f45
16
+ accent-soft #e3f0ec
17
+ code-bg #1b2420
18
+ code-ink #e9efe7
19
+ code-line #2c3b34
20
+ radius 12px
21
+
22
+ tokens dark
23
+ ink #ece6db
24
+ ink-soft #c3bbab
25
+ muted #9a9081
26
+ paper #14110d
27
+ panel #1e1913
28
+ sunk #221d17
29
+ line #2f281f
30
+ accent #5ccab4
31
+ accent-deep #8fe0cf
32
+ accent-soft #16302a
33
+ code-bg #100d0a
34
+ code-ink #e4ebe2
35
+ code-line #2a231b
36
+
37
+ html
38
+ bg $paper
39
+
40
+ page
41
+ max-width 720px
42
+ margin 0 auto
43
+ padding 3rem 1.5rem 5rem
44
+ font-family ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, Cambria, serif
45
+ font-size 18px
46
+ line-height 1.7
47
+ color $ink
48
+ bg $paper
49
+ -webkit-font-smoothing antialiased
50
+ text-rendering optimizeLegibility
51
+
52
+ h1
53
+ font-size clamp(2rem, 5vw, 2.8rem)
54
+ line-height 1.12
55
+ letter-spacing -.022em
56
+ margin 1rem 0 1.25rem
57
+ color $ink
58
+
59
+ h2
60
+ font-size 1.5rem
61
+ line-height 1.2
62
+ letter-spacing -.012em
63
+ margin 2.4rem 0 .6rem
64
+ color $ink
65
+
66
+ h3
67
+ font-size 1.2rem
68
+ margin 1.8rem 0 .4rem
69
+ color $ink
70
+
71
+ p
72
+ margin 1.1rem 0
73
+
74
+ a
75
+ color $accent
76
+ text-underline-offset 2px
77
+
78
+ a:hover
79
+ color $accent-deep
80
+
81
+ strong
82
+ font-weight 650
83
+ color $ink
84
+
85
+ hr
86
+ border 0
87
+ border-top 1px solid $line
88
+ margin 2.5rem 0
89
+
90
+ ul, ol
91
+ margin 1.1rem 0
92
+ padding-left 1.4rem
93
+
94
+ li
95
+ margin .35rem 0
96
+
97
+ code
98
+ font-family ui-monospace, "SF Mono", Menlo, Consolas, monospace
99
+ font-size .88em
100
+ bg $sunk
101
+ color $accent-deep
102
+ padding .12rem .36rem
103
+ radius 6px
104
+
105
+ pre
106
+ bg $code-bg
107
+ color $code-ink
108
+ padding 1.2rem 1.35rem
109
+ margin 1.5rem 0
110
+ radius $radius
111
+ overflow-x auto
112
+ line-height 1.62
113
+ font-size .87rem
114
+ box-shadow inset 0 0 0 1px $code-line
115
+
116
+ pre code
117
+ bg transparent
118
+ color inherit
119
+ padding 0
120
+ font-size 1em
121
+
122
+ blockquote
123
+ margin 1.6rem 0
124
+ padding .6rem 1.3rem
125
+ border-left 3px solid $accent
126
+ color $ink-soft
127
+ font-style italic
@@ -2,6 +2,8 @@
2
2
  title: Drafts that never leak
3
3
  description: draft true excludes a page everywhere in a production build — dist, routes.json, sitemap, and rss.
4
4
  date: 2026-06-27
5
+ excerpt: Set draft true on a post and it vanishes from a production build — dist, routes.json, the sitemap, and the feed — even with a date.
6
+ tags: [drafts, workflow]
5
7
  ---
6
8
 
7
9
  # Drafts that never leak