@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/pages/index.wd
CHANGED
|
@@ -6,18 +6,28 @@ transitions: true
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
@include /nav.wd
|
|
9
|
+
@include /base.wd
|
|
9
10
|
|
|
10
11
|
<main>
|
|
11
12
|
|
|
12
13
|
<section class="hero">
|
|
13
|
-
<
|
|
14
|
+
<span class="eyebrow">Darkmown vs. Markdoc</span>
|
|
14
15
|
|
|
15
|
-
# Markdown that runs
|
|
16
|
+
# Markdown that *runs.* Not Markdown that needs React.
|
|
16
17
|
|
|
17
18
|
The left file is Markdown. It is also a running app — a live search filter, add-to-cart, and a running total. The right file is the closest Markdoc can get on its own: a static list. To make it do what the left one already does, you add a React component, hydration, and a build step.
|
|
18
19
|
|
|
19
|
-
<
|
|
20
|
+
<div class="hero-cta">
|
|
21
|
+
<a class="btn primary" href="/showcase/">See the showcase</a>
|
|
20
22
|
<a class="btn" href="/docs/">Read the docs</a>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<ul class="hero-stats">
|
|
26
|
+
<li><strong>0 KB</strong><span>framework JS on static pages</span></li>
|
|
27
|
+
<li><strong>~7.4 KB</strong><span>one runtime, CI-enforced < 8 KB</span></li>
|
|
28
|
+
<li><strong>1</strong><span>loop and one binding to learn</span></li>
|
|
29
|
+
<li><strong>MIT</strong><span>zero runtime dependencies</span></li>
|
|
30
|
+
</ul>
|
|
21
31
|
</section>
|
|
22
32
|
|
|
23
33
|
<section class="versus">
|
|
@@ -98,14 +108,14 @@ Markdoc.renderers.react(content, React, {
|
|
|
98
108
|
|
|
99
109
|
</section>
|
|
100
110
|
|
|
101
|
-
:note "Static pages ship zero JavaScript. The moment a page asks for reactivity — like this very homepage — one ~
|
|
111
|
+
:note "Static pages ship zero JavaScript. The moment a page asks for reactivity — like this very homepage — one ~7.4 KB gzipped runtime loads (CI-enforced under 8 KB), never before. JS optional, CSS optional, always."
|
|
102
112
|
|
|
103
113
|
<section class="props">
|
|
104
114
|
|
|
105
115
|
## Three rules, no escape hatch
|
|
106
116
|
|
|
107
117
|
- **Zero-JS static.** A page with no state ships no framework JavaScript. `/markdown/` and `/docs/` prove it — open them and check the network tab.
|
|
108
|
-
- **One runtime.** Every reactive page shares the same ~
|
|
118
|
+
- **One runtime.** Every reactive page shares the same ~7.4 KB gzipped runtime, CI-enforced under 8 KB. No per-component bundles, no hydration tax that scales with your UI.
|
|
109
119
|
- **One loop, one binding.** `@loop … into …` is the only loop; `{ name }` is the only interpolation. Nothing else to learn, nothing else to misuse.
|
|
110
120
|
|
|
111
121
|
</section>
|
|
@@ -160,3 +170,5 @@ This button is powered by `site/pages/index.js`, discovered by matching the page
|
|
|
160
170
|
<a class="btn" href="https://github.com/ZVN-DEV/darkmown">Star on GitHub</a>
|
|
161
171
|
|
|
162
172
|
</main>
|
|
173
|
+
|
|
174
|
+
@include /footer.wd
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/* ── Ledger — a sortable expense table. Reuses base tokens ($ink/$paper/…), so
|
|
2
|
+
the :theme toggle re-themes it through base.skin's `tokens dark` (now wired to
|
|
3
|
+
[data-theme] as well as the OS media query). ───────────────────────────── */
|
|
4
|
+
|
|
5
|
+
.app-ledger
|
|
6
|
+
max-width 60rem
|
|
7
|
+
margin 0 auto
|
|
8
|
+
padding 2.5rem 1.25rem 4rem
|
|
9
|
+
|
|
10
|
+
/* Header: title + theme switch */
|
|
11
|
+
.app-ledger .ledger-head
|
|
12
|
+
display flex
|
|
13
|
+
align-items flex-end
|
|
14
|
+
justify-content space-between
|
|
15
|
+
flex-wrap wrap
|
|
16
|
+
gap 1.25rem
|
|
17
|
+
margin-bottom 1.75rem
|
|
18
|
+
|
|
19
|
+
.app-ledger .ledger-eyebrow
|
|
20
|
+
font-size .72rem
|
|
21
|
+
font-weight 700
|
|
22
|
+
letter-spacing .14em
|
|
23
|
+
text-transform uppercase
|
|
24
|
+
color $accent
|
|
25
|
+
|
|
26
|
+
.app-ledger h1
|
|
27
|
+
margin .35rem 0 0
|
|
28
|
+
font-size 2.4rem
|
|
29
|
+
letter-spacing -.02em
|
|
30
|
+
color $ink
|
|
31
|
+
|
|
32
|
+
.app-ledger .ledger-tagline
|
|
33
|
+
margin .6rem 0 0
|
|
34
|
+
max-width 34rem
|
|
35
|
+
color $muted
|
|
36
|
+
font-size .95rem
|
|
37
|
+
line-height 1.5
|
|
38
|
+
|
|
39
|
+
.app-ledger .ledger-tagline code
|
|
40
|
+
bg $code-bg
|
|
41
|
+
color $code-ink
|
|
42
|
+
padding .05rem .3rem
|
|
43
|
+
radius 5px
|
|
44
|
+
font-size .85em
|
|
45
|
+
|
|
46
|
+
/* Theme switch */
|
|
47
|
+
.app-ledger .theme-switch
|
|
48
|
+
display inline-flex
|
|
49
|
+
align-items center
|
|
50
|
+
gap .4rem
|
|
51
|
+
font-size .75rem
|
|
52
|
+
font-weight 600
|
|
53
|
+
letter-spacing .04em
|
|
54
|
+
text-transform uppercase
|
|
55
|
+
color $muted
|
|
56
|
+
|
|
57
|
+
.app-ledger .theme-switch button[data-wd-action]
|
|
58
|
+
border 1px solid $line
|
|
59
|
+
bg $panel
|
|
60
|
+
color $ink-soft
|
|
61
|
+
font-size .8rem
|
|
62
|
+
font-weight 600
|
|
63
|
+
padding .4rem .8rem
|
|
64
|
+
radius 999px
|
|
65
|
+
cursor pointer
|
|
66
|
+
transition color .14s ease, border-color .14s ease, background .14s ease
|
|
67
|
+
|
|
68
|
+
.app-ledger .theme-switch button[data-wd-action]:hover
|
|
69
|
+
color $ink
|
|
70
|
+
border-color $accent
|
|
71
|
+
|
|
72
|
+
/* Stat cards */
|
|
73
|
+
.app-ledger .stat-grid
|
|
74
|
+
display grid
|
|
75
|
+
grid-template-columns repeat(4, 1fr)
|
|
76
|
+
gap .85rem
|
|
77
|
+
margin-bottom 1.5rem
|
|
78
|
+
|
|
79
|
+
.app-ledger .stat
|
|
80
|
+
padding 1rem 1.15rem
|
|
81
|
+
border 1px solid $line-soft
|
|
82
|
+
radius 14px
|
|
83
|
+
bg $panel
|
|
84
|
+
|
|
85
|
+
.app-ledger .stat-label
|
|
86
|
+
font-size .72rem
|
|
87
|
+
font-weight 600
|
|
88
|
+
letter-spacing .06em
|
|
89
|
+
text-transform uppercase
|
|
90
|
+
color $muted
|
|
91
|
+
|
|
92
|
+
.app-ledger .stat-num
|
|
93
|
+
margin-top .3rem
|
|
94
|
+
font-size 1.5rem
|
|
95
|
+
font-weight 700
|
|
96
|
+
letter-spacing -.01em
|
|
97
|
+
color $ink
|
|
98
|
+
font-variant-numeric tabular-nums
|
|
99
|
+
|
|
100
|
+
.app-ledger .stat p, .app-ledger .stat-num p, .app-ledger .stat-label p
|
|
101
|
+
margin 0
|
|
102
|
+
|
|
103
|
+
/* Controls: filter + live ticker */
|
|
104
|
+
.app-ledger .ledger-controls
|
|
105
|
+
display flex
|
|
106
|
+
align-items center
|
|
107
|
+
justify-content space-between
|
|
108
|
+
flex-wrap wrap
|
|
109
|
+
gap 1rem
|
|
110
|
+
margin-bottom 1rem
|
|
111
|
+
|
|
112
|
+
.app-ledger .ledger-controls input[data-wd-bind-input]
|
|
113
|
+
flex 1 1 16rem
|
|
114
|
+
border 1px solid $line
|
|
115
|
+
bg $paper-2
|
|
116
|
+
color $ink
|
|
117
|
+
font-size .95rem
|
|
118
|
+
padding .6rem .9rem
|
|
119
|
+
radius 10px
|
|
120
|
+
|
|
121
|
+
.app-ledger .synced
|
|
122
|
+
font-size .8rem
|
|
123
|
+
color $muted
|
|
124
|
+
white-space nowrap
|
|
125
|
+
|
|
126
|
+
.app-ledger .synced p
|
|
127
|
+
margin 0
|
|
128
|
+
|
|
129
|
+
/* The table (CSS grid rows so it stays one readable Markdown file) */
|
|
130
|
+
.app-ledger .ledger-table
|
|
131
|
+
padding 0
|
|
132
|
+
border 1px solid $line-soft
|
|
133
|
+
radius 16px
|
|
134
|
+
overflow hidden
|
|
135
|
+
bg $panel
|
|
136
|
+
|
|
137
|
+
.app-ledger .thead, .app-ledger .trow
|
|
138
|
+
display grid
|
|
139
|
+
grid-template-columns 9rem 1fr 9rem 7rem 4.5rem
|
|
140
|
+
align-items center
|
|
141
|
+
|
|
142
|
+
.app-ledger .thead
|
|
143
|
+
border-bottom 1px solid $line-soft
|
|
144
|
+
bg $paper-2
|
|
145
|
+
|
|
146
|
+
.app-ledger .th
|
|
147
|
+
padding 0
|
|
148
|
+
|
|
149
|
+
.app-ledger .th button[data-wd-action]
|
|
150
|
+
width 100%
|
|
151
|
+
text-align left
|
|
152
|
+
border 0
|
|
153
|
+
bg transparent
|
|
154
|
+
color $muted
|
|
155
|
+
font-size .72rem
|
|
156
|
+
font-weight 700
|
|
157
|
+
letter-spacing .06em
|
|
158
|
+
text-transform uppercase
|
|
159
|
+
padding .8rem .9rem
|
|
160
|
+
cursor pointer
|
|
161
|
+
transition color .14s ease
|
|
162
|
+
|
|
163
|
+
.app-ledger .th button[data-wd-action]:hover
|
|
164
|
+
color $ink
|
|
165
|
+
|
|
166
|
+
.app-ledger .th.active button[data-wd-action]
|
|
167
|
+
color $accent
|
|
168
|
+
|
|
169
|
+
.app-ledger .th.num button[data-wd-action]
|
|
170
|
+
text-align right
|
|
171
|
+
|
|
172
|
+
.app-ledger .th.dir
|
|
173
|
+
display flex
|
|
174
|
+
gap .15rem
|
|
175
|
+
justify-content center
|
|
176
|
+
|
|
177
|
+
.app-ledger .th.dir button[data-wd-action]
|
|
178
|
+
width auto
|
|
179
|
+
padding .8rem .35rem
|
|
180
|
+
font-size .7rem
|
|
181
|
+
|
|
182
|
+
.app-ledger .trow
|
|
183
|
+
border-top 1px solid $line-soft
|
|
184
|
+
font-size .92rem
|
|
185
|
+
|
|
186
|
+
.app-ledger .trow:first-of-type
|
|
187
|
+
border-top 0
|
|
188
|
+
|
|
189
|
+
.app-ledger .td
|
|
190
|
+
padding .7rem .9rem
|
|
191
|
+
color $ink-soft
|
|
192
|
+
|
|
193
|
+
.app-ledger .td p
|
|
194
|
+
margin 0
|
|
195
|
+
|
|
196
|
+
.app-ledger .td.muted
|
|
197
|
+
color $muted
|
|
198
|
+
font-variant-numeric tabular-nums
|
|
199
|
+
|
|
200
|
+
.app-ledger .td.strong
|
|
201
|
+
color $ink
|
|
202
|
+
font-weight 600
|
|
203
|
+
|
|
204
|
+
.app-ledger .td.num
|
|
205
|
+
text-align right
|
|
206
|
+
font-weight 600
|
|
207
|
+
color $ink
|
|
208
|
+
font-variant-numeric tabular-nums
|
|
209
|
+
|
|
210
|
+
.app-ledger .pill
|
|
211
|
+
display inline-block
|
|
212
|
+
font-size .72rem
|
|
213
|
+
font-weight 600
|
|
214
|
+
padding .15rem .55rem
|
|
215
|
+
radius 999px
|
|
216
|
+
bg $accent-soft
|
|
217
|
+
color $accent-deep
|
|
218
|
+
|
|
219
|
+
.app-ledger .td.dir
|
|
220
|
+
text-align center
|
|
221
|
+
|
|
222
|
+
.app-ledger .td.dir button[data-wd-action]
|
|
223
|
+
border 1px solid transparent
|
|
224
|
+
bg transparent
|
|
225
|
+
color $muted
|
|
226
|
+
font-size .8rem
|
|
227
|
+
padding .25rem .5rem
|
|
228
|
+
radius 8px
|
|
229
|
+
cursor pointer
|
|
230
|
+
transition color .14s ease, border-color .14s ease
|
|
231
|
+
|
|
232
|
+
.app-ledger .td.dir button[data-wd-action]:hover
|
|
233
|
+
color $warm
|
|
234
|
+
border-color $line
|
|
235
|
+
|
|
236
|
+
.app-ledger .trow.blank
|
|
237
|
+
display block
|
|
238
|
+
padding 1.4rem .9rem
|
|
239
|
+
color $muted
|
|
240
|
+
text-align center
|
|
241
|
+
|
|
242
|
+
.app-ledger .ledger-foot
|
|
243
|
+
margin-top 1.1rem
|
|
244
|
+
font-size .85rem
|
|
245
|
+
color $muted
|
|
246
|
+
text-align center
|
|
247
|
+
|
|
248
|
+
.app-ledger .ledger-foot strong
|
|
249
|
+
color $ink-soft
|
|
250
|
+
|
|
251
|
+
@media (max-width: 640px)
|
|
252
|
+
.app-ledger .stat-grid
|
|
253
|
+
grid-template-columns repeat(2, 1fr)
|
|
254
|
+
.app-ledger .thead, .app-ledger .trow
|
|
255
|
+
grid-template-columns 1fr 5.5rem 3.5rem
|
|
256
|
+
.app-ledger .th.cat, .app-ledger .td.cat
|
|
257
|
+
display none
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ledger — a sortable expense table in Markdown
|
|
3
|
+
description: A real data table written as one Markdown file — clickable-header sorting, a live filter, running totals, and a light/dark toggle. Format pipes (| money, | date), aggregates (sum, avg, max), reactive sort, and :theme — all declarative, no hand-written JavaScript.
|
|
4
|
+
image: https://darkmown.com/__wd/media/og-showcase.png
|
|
5
|
+
transitions: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
@include /nav.wd
|
|
9
|
+
@include /base.wd
|
|
10
|
+
|
|
11
|
+
<main class="wide app-ledger">
|
|
12
|
+
|
|
13
|
+
::: section #ledger
|
|
14
|
+
|
|
15
|
+
:theme
|
|
16
|
+
:store expenses = [
|
|
17
|
+
{"id":1,"date":"2026-06-22","merchant":"Aurora Studio","category":"Software","amount":149},
|
|
18
|
+
{"id":2,"date":"2026-06-20","merchant":"Briza Coffee","category":"Meals","amount":18.4},
|
|
19
|
+
{"id":3,"date":"2026-06-19","merchant":"Cobalt Cloud","category":"Hosting","amount":312.5},
|
|
20
|
+
{"id":4,"date":"2026-06-18","merchant":"Dune Print Co","category":"Office","amount":64},
|
|
21
|
+
{"id":5,"date":"2026-06-16","merchant":"Aurora Studio","category":"Software","amount":29},
|
|
22
|
+
{"id":6,"date":"2026-06-15","merchant":"Ember Transit","category":"Travel","amount":212.75},
|
|
23
|
+
{"id":7,"date":"2026-06-13","merchant":"Briza Coffee","category":"Meals","amount":11.2},
|
|
24
|
+
{"id":8,"date":"2026-06-11","merchant":"Cobalt Cloud","category":"Hosting","amount":312.5}
|
|
25
|
+
]
|
|
26
|
+
:state q = ""
|
|
27
|
+
:state sortKey = "amount"
|
|
28
|
+
:state sortDir = "desc"
|
|
29
|
+
:state secs = 0
|
|
30
|
+
:every 1s -> secs++
|
|
31
|
+
|
|
32
|
+
:computed total = sum(expenses, amount)
|
|
33
|
+
:computed average = avg(expenses, amount)
|
|
34
|
+
:computed largest = max(expenses, amount)
|
|
35
|
+
|
|
36
|
+
::: ledger-head
|
|
37
|
+
::: ledger-mark
|
|
38
|
+
::: eyebrow .ledger-eyebrow
|
|
39
|
+
A real data table
|
|
40
|
+
:::
|
|
41
|
+
|
|
42
|
+
# *Ledger*
|
|
43
|
+
|
|
44
|
+
::: ledger-tagline
|
|
45
|
+
Clickable-header sorting, a live filter, and running totals — a spreadsheet-grade table written as one Markdown file. The numbers below are `{ … | money }`; the dates are `{ … | date }`; the totals are `sum()` / `avg()` / `max()`.
|
|
46
|
+
:::
|
|
47
|
+
:::
|
|
48
|
+
|
|
49
|
+
::: theme-switch
|
|
50
|
+
Theme
|
|
51
|
+
:button "Auto" -> theme = "auto"
|
|
52
|
+
:button "Light" -> theme = "light"
|
|
53
|
+
:button "Dark" -> theme = "dark"
|
|
54
|
+
:::
|
|
55
|
+
:::
|
|
56
|
+
|
|
57
|
+
::: stat-grid
|
|
58
|
+
::: card .stat
|
|
59
|
+
::: stat-label
|
|
60
|
+
Total spend
|
|
61
|
+
:::
|
|
62
|
+
::: stat-num
|
|
63
|
+
{ total | money }
|
|
64
|
+
:::
|
|
65
|
+
:::
|
|
66
|
+
::: card .stat
|
|
67
|
+
::: stat-label
|
|
68
|
+
Average
|
|
69
|
+
:::
|
|
70
|
+
::: stat-num
|
|
71
|
+
{ average | money }
|
|
72
|
+
:::
|
|
73
|
+
:::
|
|
74
|
+
::: card .stat
|
|
75
|
+
::: stat-label
|
|
76
|
+
Largest
|
|
77
|
+
:::
|
|
78
|
+
::: stat-num
|
|
79
|
+
{ largest | money }
|
|
80
|
+
:::
|
|
81
|
+
:::
|
|
82
|
+
::: card .stat
|
|
83
|
+
::: stat-label
|
|
84
|
+
Entries
|
|
85
|
+
:::
|
|
86
|
+
::: stat-num
|
|
87
|
+
{ expenses | count }
|
|
88
|
+
:::
|
|
89
|
+
:::
|
|
90
|
+
:::
|
|
91
|
+
|
|
92
|
+
::: ledger-controls
|
|
93
|
+
:bind q placeholder="Filter by merchant…"
|
|
94
|
+
::: synced
|
|
95
|
+
↻ live · { secs | pluralize:"second" } on this page
|
|
96
|
+
:::
|
|
97
|
+
:::
|
|
98
|
+
|
|
99
|
+
::: card .ledger-table
|
|
100
|
+
|
|
101
|
+
::: thead
|
|
102
|
+
::: th .active when sortKey == "date"
|
|
103
|
+
:button "Date" -> sortKey = "date" ; sortDir = "asc"
|
|
104
|
+
:::
|
|
105
|
+
::: th .active when sortKey == "merchant"
|
|
106
|
+
:button "Merchant" -> sortKey = "merchant" ; sortDir = "asc"
|
|
107
|
+
:::
|
|
108
|
+
::: th .active when sortKey == "category"
|
|
109
|
+
:button "Category" -> sortKey = "category" ; sortDir = "asc"
|
|
110
|
+
:::
|
|
111
|
+
::: th .num .active when sortKey == "amount"
|
|
112
|
+
:button "Amount" -> sortKey = "amount" ; sortDir = "desc"
|
|
113
|
+
:::
|
|
114
|
+
::: th .dir
|
|
115
|
+
:button "▲" -> sortDir = "asc"
|
|
116
|
+
:button "▼" -> sortDir = "desc"
|
|
117
|
+
:::
|
|
118
|
+
:::
|
|
119
|
+
|
|
120
|
+
@loop expenses into e where e.merchant contains q sort by { sortKey } { sortDir }
|
|
121
|
+
::: trow
|
|
122
|
+
::: td .muted
|
|
123
|
+
{ e.date | date:"medium" }
|
|
124
|
+
:::
|
|
125
|
+
::: td .strong
|
|
126
|
+
{ e.merchant }
|
|
127
|
+
:::
|
|
128
|
+
::: td
|
|
129
|
+
::: pill
|
|
130
|
+
{ e.category }
|
|
131
|
+
:::
|
|
132
|
+
:::
|
|
133
|
+
::: td .num
|
|
134
|
+
{ e.amount | money }
|
|
135
|
+
:::
|
|
136
|
+
::: td .dir
|
|
137
|
+
:button "✕" -> expenses remove e
|
|
138
|
+
:::
|
|
139
|
+
:::
|
|
140
|
+
@empty
|
|
141
|
+
::: trow .blank
|
|
142
|
+
No expenses match "**{ q }**".
|
|
143
|
+
:::
|
|
144
|
+
@endloop
|
|
145
|
+
|
|
146
|
+
:::
|
|
147
|
+
|
|
148
|
+
::: ledger-foot
|
|
149
|
+
{ expenses | count } entries · totalling **{ total | money }** · sorted by **{ sortKey }** ({ sortDir })
|
|
150
|
+
:::
|
|
151
|
+
|
|
152
|
+
<details class="source-reveal">
|
|
153
|
+
<summary>View source — it's Markdown</summary>
|
|
154
|
+
|
|
155
|
+
```wd
|
|
156
|
+
:store expenses = [ … ]
|
|
157
|
+
:state sortKey = "amount"
|
|
158
|
+
:state sortDir = "desc"
|
|
159
|
+
:state q = ""
|
|
160
|
+
|
|
161
|
+
:computed total = sum(expenses, amount)
|
|
162
|
+
|
|
163
|
+
# clickable headers set the sort column + direction
|
|
164
|
+
:button "Amount" -> sortKey = "amount" ; sortDir = "desc"
|
|
165
|
+
:button "▲" -> sortDir = "asc"
|
|
166
|
+
|
|
167
|
+
:bind q placeholder="Filter by merchant…"
|
|
168
|
+
|
|
169
|
+
@loop expenses into e where e.merchant contains q sort by { sortKey } { sortDir }
|
|
170
|
+
| { e.date | date:"medium" } | { e.merchant } | { e.amount | money } |
|
|
171
|
+
:button "✕" -> expenses remove e
|
|
172
|
+
@endloop
|
|
173
|
+
|
|
174
|
+
Total: { total | money }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
</details>
|
|
178
|
+
|
|
179
|
+
:::
|
|
180
|
+
|
|
181
|
+
</main>
|
|
182
|
+
|
|
183
|
+
@include /footer.wd
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/* ── Media demo — layers on /base.wd. Pure presentation; the page itself ships
|
|
2
|
+
no framework JS, so nothing here is reactive. Reuses base tokens. ───────── */
|
|
3
|
+
|
|
4
|
+
.app-media
|
|
5
|
+
max-width 52rem
|
|
6
|
+
margin 0 auto
|
|
7
|
+
padding 2.5rem 1.25rem 4rem
|
|
8
|
+
|
|
9
|
+
.app-media .media-head
|
|
10
|
+
margin-bottom 2rem
|
|
11
|
+
|
|
12
|
+
.app-media .media-head .eyebrow
|
|
13
|
+
color $accent
|
|
14
|
+
|
|
15
|
+
.app-media h1
|
|
16
|
+
margin .5rem 0 0
|
|
17
|
+
font-size 2.5rem
|
|
18
|
+
letter-spacing -.02em
|
|
19
|
+
color $ink
|
|
20
|
+
|
|
21
|
+
.app-media .media-head p
|
|
22
|
+
margin 1rem 0 0
|
|
23
|
+
max-width 42rem
|
|
24
|
+
color $ink-soft
|
|
25
|
+
font-size 1.02rem
|
|
26
|
+
line-height 1.6
|
|
27
|
+
|
|
28
|
+
.app-media .media-head code, .app-media .media-label code, .app-media .media-note code
|
|
29
|
+
bg $code-bg
|
|
30
|
+
color $code-ink
|
|
31
|
+
padding .08rem .35rem
|
|
32
|
+
radius 6px
|
|
33
|
+
font-size .85em
|
|
34
|
+
|
|
35
|
+
/* Each player sits in its own labelled card */
|
|
36
|
+
.app-media .media-panel
|
|
37
|
+
padding 1.25rem 1.25rem 1.1rem
|
|
38
|
+
margin 1.25rem 0
|
|
39
|
+
border 1px solid $line-soft
|
|
40
|
+
radius 16px
|
|
41
|
+
bg $panel
|
|
42
|
+
|
|
43
|
+
.app-media .media-label
|
|
44
|
+
font-size .8rem
|
|
45
|
+
font-weight 600
|
|
46
|
+
letter-spacing .02em
|
|
47
|
+
color $muted
|
|
48
|
+
margin-bottom .85rem
|
|
49
|
+
|
|
50
|
+
.app-media .media-label p
|
|
51
|
+
margin 0
|
|
52
|
+
|
|
53
|
+
/* The compiled <video>/<audio>/<iframe> wrapper */
|
|
54
|
+
.app-media video
|
|
55
|
+
width 100%
|
|
56
|
+
height auto
|
|
57
|
+
display block
|
|
58
|
+
border 1px solid $line-soft
|
|
59
|
+
radius 12px
|
|
60
|
+
background $sunk
|
|
61
|
+
|
|
62
|
+
.app-media audio
|
|
63
|
+
width 100%
|
|
64
|
+
display block
|
|
65
|
+
|
|
66
|
+
.app-media .wd-embed
|
|
67
|
+
border 1px solid $line-soft
|
|
68
|
+
radius 12px
|
|
69
|
+
overflow hidden
|
|
70
|
+
background $sunk
|
|
71
|
+
|
|
72
|
+
.app-media .media-note
|
|
73
|
+
margin .9rem 0 0
|
|
74
|
+
font-size .88rem
|
|
75
|
+
line-height 1.55
|
|
76
|
+
color $muted
|
|
77
|
+
|
|
78
|
+
.app-media .media-note p
|
|
79
|
+
margin 0
|
|
80
|
+
|
|
81
|
+
.app-media .source-reveal
|
|
82
|
+
margin-top 2rem
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Media — video, audio, and embeds in Markdown
|
|
3
|
+
description: Three one-line directives — :video, :audio, :embed — compile to hardened, accessible, privacy-friendly players. No framework JavaScript ships: a media page stays zero-JS, just like static Markdown.
|
|
4
|
+
image: https://darkmown.com/__wd/media/og-showcase.png
|
|
5
|
+
transitions: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
@include /nav.wd
|
|
9
|
+
@include /base.wd
|
|
10
|
+
|
|
11
|
+
<main class="wide app-media">
|
|
12
|
+
|
|
13
|
+
<header class="media-head">
|
|
14
|
+
<span class="eyebrow">New in 1.0</span>
|
|
15
|
+
|
|
16
|
+
# *Media*, the Markdown way
|
|
17
|
+
|
|
18
|
+
Video, audio, and third-party embeds used to be the one place a Markdown page fell
|
|
19
|
+
back to raw HTML — `<video>` tags, `<iframe>` soup, privacy footguns, and
|
|
20
|
+
copy-pasted attributes. Darkmown gives you three one-line directives instead.
|
|
21
|
+
Each compiles to a **hardened, accessible, lazy** player, and — because they emit
|
|
22
|
+
no `data-wd-*` — *this entire page ships zero framework JavaScript.*
|
|
23
|
+
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
::: card .media-panel
|
|
27
|
+
::: media-label
|
|
28
|
+
`:video` — a hardened HTML5 player
|
|
29
|
+
:::
|
|
30
|
+
|
|
31
|
+
:video /media/clip.mp4 poster=/media/clip-poster.jpg controls
|
|
32
|
+
|
|
33
|
+
::: media-note
|
|
34
|
+
`preload="metadata"` and `controls` are added for you; an `autoplay` clip silently
|
|
35
|
+
implies `muted`, because browsers block sound-on autoplay. The source URL runs
|
|
36
|
+
through the same scheme guard as `:fetch` — only relative paths and `http(s)` get
|
|
37
|
+
through.
|
|
38
|
+
:::
|
|
39
|
+
:::
|
|
40
|
+
|
|
41
|
+
::: card .media-panel
|
|
42
|
+
::: media-label
|
|
43
|
+
`:audio` — same guarantees, for sound
|
|
44
|
+
:::
|
|
45
|
+
|
|
46
|
+
:audio /media/tone.mp3 controls
|
|
47
|
+
|
|
48
|
+
::: media-note
|
|
49
|
+
A 440 Hz reference tone. `:audio` shares the `:video` defaults and validation; flags
|
|
50
|
+
you don't set never appear in the markup, so there are no surprise behaviors.
|
|
51
|
+
:::
|
|
52
|
+
:::
|
|
53
|
+
|
|
54
|
+
::: card .media-panel
|
|
55
|
+
::: media-label
|
|
56
|
+
`:embed` — privacy-friendly, responsive iframes
|
|
57
|
+
:::
|
|
58
|
+
|
|
59
|
+
:embed https://youtu.be/aqz-KE-bpKQ title="Big Buck Bunny (open movie)"
|
|
60
|
+
|
|
61
|
+
::: media-note
|
|
62
|
+
A YouTube or Vimeo URL is rewritten to its **no-cookie / player** form, wrapped in a
|
|
63
|
+
`16/9` box, and marked `loading="lazy"` with a locked-down `referrerpolicy`. Paste a
|
|
64
|
+
share link; get a responsive, tracking-light embed.
|
|
65
|
+
:::
|
|
66
|
+
:::
|
|
67
|
+
|
|
68
|
+
<details class="source-reveal">
|
|
69
|
+
<summary>View source — three lines</summary>
|
|
70
|
+
|
|
71
|
+
```wd
|
|
72
|
+
:video /media/clip.mp4 poster=/media/clip-poster.jpg controls
|
|
73
|
+
:audio /media/tone.mp3 controls
|
|
74
|
+
:embed https://youtu.be/aqz-KE-bpKQ title="Big Buck Bunny (open movie)"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
That's the whole page's interactivity. No `<script>`, no runtime — open DevTools and
|
|
78
|
+
watch the Network tab stay empty of framework JS.
|
|
79
|
+
|
|
80
|
+
</details>
|
|
81
|
+
|
|
82
|
+
</main>
|
|
83
|
+
|
|
84
|
+
@include /footer.wd
|
|
Binary file
|