Repository navigation
Expand file tree
/
Copy pathdocumentation.html
More file actions
542 lines (504 loc) · 31.8 KB
/
Copy pathdocumentation.html
File metadata and controls
542 lines (504 loc) · 31.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
<!doctype html>
<!--
danieldeusing-design — one-page documentation template ("danieldeusing-docs").
A single self-contained HTML file: the design system loads build-free from the
jsDelivr CDN, so this opens directly in a browser with no build step.
Fill the {{PLACEHOLDERS}}. The CDN URLs are PINNED to a release, and that is not a
style choice — this page ships the design system's own chrome MARKUP (the `ls -l` rail,
the fixed footer), so it is coupled to the release that styles it. The unpinned url
cannot promise a release: jsDelivr serves it `max-age=604800`, SEVEN DAYS in the
browser. Cockpit 2.65.0 shipped rail markup that way and readers kept applying 0.1.6,
which predates the rail — a 613px header, the brand floating mid-page. See the
danieldeusing-design skill, "Pin or unpin". The page is defended twice more: the
`onerror` snapshot below covers a CDN that cannot be reached (SAME release, so the two
paths are the same bytes), and every token in the <style> block carries a literal
fallback so a doc opened over file:// with no CDN still lays out. Keep all three.
Chrome — the standard three pieces (templates/page-chrome.html has the reasoning):
- header.bar : the "danieldeusing-docs" wordmark left, the `ls -l` head right.
- .ls-nav : the sticky right RAIL — OPTIONAL, and most docs delete it. When a
page does navigate somewhere it is always the rail, never a dropdown.
A doc lists only itself, so the rail has one entry, so there is no
rail: keep it only when there is somewhere else to go. Never pad it
with the folder's other docs — access here is per folder, and that
would tell a scoped reader what else exists.
- footer.status: fixed, and the home of EVERY control — bottom-left
[{{FOLDER}}] {{DOC_TITLE}} (e.g. "[pagr] Source Code
documentation"), bottom-right the danieldeusing.de link, the theme
picker and the anim toggle. Nothing operational goes in the header.
Content (you fill): the title, the full-width lede, and the <section> blocks. Give every
section an id — the MINIMAP in the left gutter builds itself from them (one bar each,
scroll-spy included), so there is no table of contents to keep in sync any more.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{DOC_TITLE}}</title>
<meta name="description" content="{{DOC_DESCRIPTION}}" />
<meta name="theme-color" content="#f5efe2" />
<!-- pre-paint: apply the saved theme + animation gate before first paint -->
<script>
(() => {
const bg = { warm: "#f5efe2", green: "#020604", mono: "#050505", paper: "#fafafa" };
let theme = "warm";
try {
const stored = localStorage.getItem("theme");
if (stored && stored in bg) theme = stored;
} catch {}
document.documentElement.dataset.theme = theme;
document.querySelector('meta[name="theme-color"]')?.setAttribute("content", bg[theme]);
let animOff = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
try {
if (localStorage.getItem("anim") === "off") animOff = true;
} catch {}
if (animOff) document.documentElement.classList.add("anim-off");
// The `ls -l` rail is SHOWN by default. A reader who hid it must not watch it
// paint and then jump away on every single load, so the state is applied HERE —
// the runtime module at the end of <body> runs long after first paint.
try {
if (localStorage.getItem("ls-nav") === "off") document.documentElement.dataset.lsNav = "off";
} catch {}
})();
</script>
<!-- The design system, PINNED — and it must be. This page ships the system's own
MARKUP (the `ls -l` rail, the fixed footer), so it is coupled to the release that
styles them, and the unpinned url cannot promise a release: jsDelivr serves it
`max-age=604800` — SEVEN DAYS in the browser (measured 2026-08-06), 12h at the
edge. Cockpit 2.65.0 shipped rail markup on the unpinned url and every browser
that had opened it that week kept applying 0.1.6, which predates the rail: both
nav toggles on screen, a 613px header, the brand floating mid-page. A release
cannot fix that — the url a release publishes to is the one being cached. The
pinned url is served `immutable`, so the bytes always match the markup; bump it in
the same commit as any markup that needs a newer release.
`onerror` swaps in a byte-for-byte snapshot of the SAME release, served from this
site, so a reader with no route to the CDN gets an identical page rather than an
older one. Snapshots live ONCE per version under /_design/, which is served
without a login (a published stylesheet is not a secret, and a folder-scoped
reader has to be able to load it too). NOTE the path is site-absolute: it resolves
on docs.danieldeusing.de, NOT for a doc opened from disk over file://. -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.64.0/dist/danieldeusing-design.min.css"
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.64.0.min.css'"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.64.0/src/fonts.css"
onerror="this.onerror=null;this.href='/_design/danieldeusing-design-0.64.0.fonts.css'"
/>
<style>
/* Page-only layout. The design system ships the visual identity AND the measurements
(--content-w / --content-pad / --fs-*), so every value below is a TOKEN.
Each one carries a literal fallback on purpose. A doc opened over file:// with no
route to the CDN loads no stylesheet at all (the /_design/ fallback is site-absolute),
so a bare `var(--content-w)` would resolve to nothing and the page would go full-bleed
with collapsed type. The literal is the token's own value, so that reader gets the
same column as everyone else.
Note `padding-block` + `padding-inline`, never the `padding` shorthand: the
shorthand resets padding-inline and silently drops the left/right margins. */
/* design-conformance: a TOKEN declaration with literal fallbacks, not a copy of the
system's rule — the sanctioned exception. A doc is meant to open straight off disk
over file://, where no stylesheet loads at all and a bare var() resolves to nothing.
The literals are what that reader sees; the tokens win everywhere else. */
.wrap { max-width: var(--content-w, 90rem); margin-inline: auto;
padding-block: 2.5rem 5rem; padding-inline: var(--content-pad, 1.5rem); }
/* header/footer chrome + nav now ship with the design system (src/chrome.css) */
/* ONE COLUMN, CENTRED — the same shape every other danieldeusing surface has.
`.wrap` above is the whole layout: --content-w wide, `margin-inline: auto`, and the
side margins are whatever is left. Nothing here overrides it, and nothing should.
There is no table-of-contents column any more. A text TOC repeated the headings the
reader was about to scroll past and charged a whole column of the page for it, and on
a wide display that column was the difference between content that fills the page and
content stranded beside a strip of nothing. Its two real jobs — how long is this, and
where am I — moved to the MINIMAP in the left gutter (src/components.css,
initMinimap()), which does them in 2rem and is fixed, so it costs the column nothing.
(`.content` itself is the system's since 0.60.0 — chrome.css — so nothing is declared here.) */
/* The title is the system's `h1.page-title` and the tagline its `.lede` (content.css): nothing
is declared for them here. */
/* design-conformance: spacing BETWEEN doc sections on this page template, not a restyle
of the shared .doc — the system sets its own margin for the standalone case. */
/* No scroll-margin-top: since 0.60.0 the viewport's scroll padding (chrome.css) lands every
anchor a line below the sticky header, and a margin here would ADD to it. */
section.doc { margin-top: 2.5rem; }
.body { margin-top: 0.85rem; }
.body > * + * { margin-top: 0.75rem; }
/* Screenshots side by side, one column on a phone. Layout only: the system draws the
figure, its image border and the caption (base.css). */
.shots { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); }
/* Every content component the generator uses is the system's, so none is declared here:
`ol.steps` and `ul.plain` (content.css), `table.kv` (data.css), and a bare <code> and <pre>
for inline code and a code block (base.css). A local copy of any of them is a fork that
disagrees with the system at the next release. */
/* No table CSS for scrolling or size here on purpose. `initTableScroll()` at the
bottom of this file gives every table a `.tablewrap` parent (overflow-x plus a
right-edge fade), so a table wider than its column scrolls itself instead of
handing the WHOLE PAGE a horizontal scrollbar — the failure that makes a site
read as broken, because the header slides off and body text needs two axes.
The markup contract is therefore nothing: author a plain <table>.
Size is the system's too — it sets `table { font-size: var(--fs-base) }`, so a
local font-size here would only reintroduce the 3px disagreement between pages
it was added to end. Still never `white-space: nowrap` on a cell that can hold
more than one token (a file list): that is what forces the runaway width. */
/* Mermaid diagrams. Before rendering, the <pre> shows the raw diagram source —
so with JS disabled (or if the CDN is unreachable) the page degrades to a
readable text diagram instead of an empty box. */
pre.mermaid { margin: 0; padding: 0.9rem 1rem; overflow-x: auto; font-size: var(--fs-base, 0.75rem); line-height: var(--lh-base, 1.5); background: var(--muted); border: 1px solid var(--border); white-space: pre; }
pre.mermaid[data-processed] { white-space: normal; text-align: center; }
pre.mermaid svg { max-width: 100%; height: auto; }
/* A status node takes its colour from the TOKEN, here, and never from its classDef. Mermaid
does not parse var() in a style at all (11.16.0: with or without a fallback, the whole
diagram fails and shows its source), and a literal hex is right on one theme of four — the
#a02c2c this template used to write is warm's red and wrong on green, mono and paper. So the
classDef keeps only the shape (a width, a dash) and these rules paint it. `!important`
because mermaid's own theme rules sit inside the svg under an id selector, which no class
selector here out-ranks. `warn` is a failure or a gap; `new` is a proposed node. */
:is(pre.mermaid, doc-explainer) .node.warn > :is(rect, polygon, circle, ellipse, path) {
stroke: var(--destructive, #a02c2c) !important;
fill: color-mix(in srgb, var(--destructive, #a02c2c) 13%, transparent) !important;
}
:is(pre.mermaid, doc-explainer) .node.new > :is(rect, polygon, circle, ellipse, path) {
stroke: var(--primary, #8a4516) !important;
fill: color-mix(in srgb, var(--primary, #8a4516) 13%, transparent) !important;
}
/* Interactive explainer: written empty, filled by a page script at load; while it has
content it stands in on screen for the block marked data-static directly before it.
Without JS and in print, the static block shows. */
doc-explainer { display: block; }
@media screen { [data-static]:has(+ doc-explainer:not(:empty)) { display: none; } }
@media print { doc-explainer { display: none !important; } }
</style>
</head>
<body>
<a href="#top" class="skip-link">skip to content</a>
<header class="bar">
<!-- top-left: ALWAYS danieldeusing-docs. The slots are wrappers, never the link. -->
<div class="bar-side">
<a href="#top" class="brand">
<span class="glow">danieldeusing-docs</span>
<span class="cursor-block" aria-hidden="true"></span>
</a>
</div>
<div class="bar-side bar-right">
<!-- top-right: navigation. Desktop = the sticky `ls -l` RAIL; mobile = the burger
toggling a full-width menu with the rail and the footer folded into it (chrome
kit: src/chrome.css + runtime initBurgerNav / initLsNav). Never a dropdown: a
dropdown answers "where can I go?" only while you hold it open, and covers what
you were reading to do it. -->
<button type="button" class="nav-burger" data-nav-toggle aria-label="menu" aria-controls="site-nav" aria-expanded="false">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M3 12h18"/><path d="M3 18h18"/></svg>
</button>
<nav aria-label="site" id="site-nav" class="site-nav">
<!-- `ls -l` lives in the BAR and the rail hangs below it, so the toggle stays in one
place whether the rail is open or closed. The guillemet is GENERATED by chrome.css
from the state (» open, « closed) — never type one in here. -->
<div class="ls-nav-head">
<span class="ls-nav-title prompt">ls -l</span>
<button type="button" class="ls-nav-toggle" data-ls-nav-toggle aria-controls="nav" aria-expanded="true" aria-label="show or hide navigation"></button>
</div>
<!-- MOST DOCS SHOULD DELETE THIS RAIL. A nav whose only entry is the page you are
already on is not navigation — it costs 17rem of width to state the obvious. Keep
it only when there is somewhere else to GO (sibling docs a reader moves between, or
child pages); one entry means delete it, and do NOT pad it out by listing the
folder's other docs, which only tells a folder-scoped reader what else is there.
Deleting means this <div class="ls-nav"> and the <div class="ls-nav-head"> above it,
and NOTHING ELSE. The surrounding <nav class="site-nav">, the .nav-burger and the
.mobile-footer below all stay: footer.status is display:none under 48rem, so that
burger is the only place a phone has the theme picker and the anim toggle. The
layout needs no other edit — the system reads html:has(.ls-nav), so with the rail
gone --ls-nav-inset falls to 0 and the body reclaims the width itself.
One <li> per entry. `ls-row--dir` marks a directory (accent + weight);
`ls-row--sub` / `ls-row--sub2` draw the nesting. Keep labels under ~18 characters:
the rail is 17rem and .dropdown-item sets `white-space: nowrap`, so a longer one is
clipped with nothing to show for it — put the full filename in `data-tip`, never `title`.
On the docs site, list the doc's OWN subtree and nothing above it: access is per
folder, so a rail that enumerated the estate would tell a scoped reader what else
exists. Below 48rem this folds into the burger — one copy, which is why there is no
second <ul class="mobile-nav"> repeating the same links. -->
<div class="ls-nav" id="nav">
<ul class="ls-panel" role="list">
<li><a class="dropdown-item ls-row ls-row--dir" href="{{NAV_1_HREF}}"><span class="ls-perm" aria-hidden="true">drwxr-xr-x</span><span class="ls-name">{{NAV_1_LABEL}}</span></a></li>
<li><a class="dropdown-item ls-row ls-row--sub" href="{{NAV_2_HREF}}" aria-current="page"><span class="ls-perm" aria-hidden="true">-rw-r--r--</span><span class="ls-name">{{NAV_2_LABEL}}</span></a></li>
</ul>
</div>
<!-- The footer folds away below 48rem on a page that has this block, so its controls fold
in here — a phone reader must still be able to switch theme. Keep it in step with the
footer: the same links, the same menus as accordions, the same toggle. -->
<div class="mobile-footer">
<a class="doc-link" href="https://danieldeusing.de">danieldeusing.de</a>
<!-- OPTIONAL: a language accordion, for a doc published in more than one language. Each
row links to this doc in that language; the current one is aria-current="true".
<details class="mobile-theme">
<summary><span class="visually-hidden">language </span><span class="dd-flag dd-flag-en" aria-hidden="true"></span><span>english</span><span class="mf-chev" aria-hidden="true">▾</span></summary>
<div class="mf-panel">
<a class="dropdown-item" href="{{DOC_DE_HREF}}" hreflang="de" lang="de" data-lang-value="de"><span class="dd-flag dd-flag-de" aria-hidden="true"></span>deutsch</a>
<a class="dropdown-item" href="{{DOC_EN_HREF}}" hreflang="en" lang="en" data-lang-value="en" aria-current="true"><span class="dd-flag dd-flag-en" aria-hidden="true"></span>english</a>
</div>
</details>
-->
<details class="mobile-theme">
<summary>
<span class="visually-hidden">theme </span>
<span class="dd-dot" aria-hidden="true"></span>
<span data-theme-label>warm</span>
<span class="mf-chev" aria-hidden="true">▾</span>
</summary>
<div class="mf-panel">
<button type="button" class="dropdown-item" data-theme-value="warm">warm</button>
<button type="button" class="dropdown-item" data-theme-value="green">green</button>
<button type="button" class="dropdown-item" data-theme-value="mono">mono</button>
<button type="button" class="dropdown-item" data-theme-value="paper">paper</button>
</div>
</details>
<button type="button" class="anim-toggle" data-anim-toggle aria-pressed="true" aria-label="Toggle animations">
<span data-anim-box aria-hidden="true">[x]</span>
<span>anim</span>
</button>
</div></nav>
</div>
</header>
<main class="wrap" id="top">
<div class="content">
<p class="prompt">cat {{FOLDER}}/README.md</p>
<h1 class="page-title">{{DOC_TITLE}}</h1>
<p class="lede">{{TAGLINE}}</p>
<!-- one <section> per topic, each with an id (the minimap builds itself from them).
Short bullets and tables, a visual wherever one fits, proof for every claim. -->
<section class="doc" id="overview" data-term>
<p class="prompt">cat in-short.md</p>
<div class="body" data-term-out>
<ul class="plain">
<li><strong>{{TLDR_LABEL_1}}:</strong> {{TLDR_1}}</li>
<li><strong>{{TLDR_LABEL_2}}:</strong> {{TLDR_2}}</li>
<li><strong>{{TLDR_LABEL_3}}:</strong> {{TLDR_3}}</li>
</ul>
</div>
</section>
<section class="doc" id="plain-words" data-term>
<p class="prompt">cat plain-words.md</p>
<div class="body" data-term-out>
<p>{{PLAIN_WORDS}}</p>
</div>
</section>
<section class="doc" id="steps" data-term>
<p class="prompt">cat steps.txt</p>
<div class="body" data-term-out>
<ol class="steps">
<li>{{STEP_1}}</li>
<li>{{STEP_2}}</li>
</ol>
</div>
</section>
<!-- Flow diagrams use Mermaid — never hand-drawn ASCII art.
IMPORTANT: inside a <pre>, write line breaks in node labels as the
ESCAPED entity <br/>. A raw <br/> would be parsed as an HTML
element by the browser, and the renderer (which reads textContent)
would receive a label with the break silently stripped out. -->
<section class="doc" id="flow" data-term>
<p class="prompt">cat flow.mmd</p>
<div class="body" data-term-out>
<pre class="mermaid">
flowchart TD
A["{{FLOW_START}}"] --> B{"{{FLOW_DECISION}}"}
B -- yes --> C["{{FLOW_YES}}"]
B -- no --> D["{{FLOW_NO}}"]
class D warn
%% Shape only. Mermaid parses no var() in a style, and a hex is right on one theme of
%% four: the colour of .node.warn and .node.new comes from the page's CSS, from the
%% tokens. `class X new` marks a proposed node.
classDef warn stroke-width:2px
classDef new stroke-width:2px,stroke-dasharray:4 3
</pre>
</div>
</section>
<!-- Proof: what was seen, not what was concluded. Screenshots are embedded as data URIs
(one file), each caption names the value it shows. Logs are an excerpt in a bare
<pre>, secrets and customer data replaced. Delete what the page has no proof for. -->
<section class="doc" id="proof" data-term>
<p class="prompt">cat proof.txt</p>
<div class="body" data-term-out>
<div class="shots">
<figure><img alt="{{SHOT_1_ALT}}" src="{{SHOT_1_DATA_URI}}" /><figcaption>{{SHOT_1_CAPTION}}</figcaption></figure>
<figure><img alt="{{SHOT_2_ALT}}" src="{{SHOT_2_DATA_URI}}" /><figcaption>{{SHOT_2_CAPTION}}</figcaption></figure>
</div>
<pre>{{LOG_EXCERPT}}</pre>
<table>
<thead><tr><th>{{PROOF_COL_1}}</th><th>{{PROOF_COL_2}}</th></tr></thead>
<tbody><tr><td>{{PROOF_ROW_1A}}</td><td>{{PROOF_ROW_1B}}</td></tr></tbody>
</table>
</div>
</section>
<section class="doc" id="notes" data-term>
<p class="prompt">cat notes.md</p>
<div class="body" data-term-out>
<p>{{NOTES}}</p>
</div>
</section>
</div>
</main>
<footer class="status">
<!-- bottom-left: [folder] + the doc title -->
<span class="status-left"><span class="glow">[{{FOLDER}}]</span> {{DOC_TITLE}}</span>
<!-- bottom-right: danieldeusing.de link + theme (page settings) dropdown -->
<nav class="status-right" aria-label="page settings">
<a class="doc-link" href="https://danieldeusing.de">danieldeusing.de</a>
<span class="sep" aria-hidden="true"></span>
<!-- OPTIONAL: a language menu, for a doc published in more than one language.
<details class="dropdown">
<summary><span class="visually-hidden">language </span><span class="dd-flag dd-flag-en" aria-hidden="true"></span><span>en</span><span aria-hidden="true">▾</span></summary>
<ul class="dropdown-panel">
<li><a class="dropdown-item" href="{{DOC_DE_HREF}}" hreflang="de" lang="de" data-lang-value="de"><span class="dd-flag dd-flag-de" aria-hidden="true"></span>deutsch</a></li>
<li><a class="dropdown-item" href="{{DOC_EN_HREF}}" hreflang="en" lang="en" data-lang-value="en" aria-current="true"><span class="dd-flag dd-flag-en" aria-hidden="true"></span>english</a></li>
</ul>
</details>
<span class="sep" aria-hidden="true"></span>
-->
<!-- The summary reads "theme warm" — the hidden word names the menu, the label says the
value; an aria-label here would replace the value with "theme". -->
<details class="dropdown">
<summary>
<span class="visually-hidden">theme </span>
<span class="dd-dot" aria-hidden="true"></span>
<span data-theme-label>warm</span>
<span aria-hidden="true">▾</span>
</summary>
<ul class="dropdown-panel">
<li><button type="button" class="dropdown-item" data-theme-value="warm">warm</button></li>
<li><button type="button" class="dropdown-item" data-theme-value="green">green</button></li>
<li><button type="button" class="dropdown-item" data-theme-value="mono">mono</button></li>
<li><button type="button" class="dropdown-item" data-theme-value="paper">paper</button></li>
</ul>
</details>
<!-- No data-tip: a tip that repeats the accessible name is announced twice. -->
<button type="button" class="anim-toggle" data-anim-toggle aria-pressed="true" aria-label="Toggle animations">
<span data-anim-box aria-hidden="true">[x]</span>
<span>anim</span>
</button>
</nav>
</footer>
<script type="module">
// dd-runtime: scripts/bake.mjs swaps this import for the runtime it inlines.
import { applyStoredTheme, initThemeSwitcher, initDropdowns, initSelects, initTerminal, initAnimToggle, initBurgerNav, initLsNav, initTableScroll, initDiagramZoom, initMinimap, initTooltips } from
"https://cdn.jsdelivr.net/npm/@danieldeusing/design@0.64.0/runtime/index.js";
applyStoredTheme();
initThemeSwitcher();
initDropdowns();
initTerminal();
initAnimToggle();
initBurgerNav();
// The rail's show/hide, and it measures the real chrome on every page, rail or not:
// --ls-nav-top / --ls-nav-bottom put the rail (and the minimap) BETWEEN the header and
// the footer, --sticky-top parks anything that sticks under the header.
initLsNav();
// Every table gets a .tablewrap parent, so a table wider than its column scrolls
// itself instead of pushing the whole page sideways. Already-wrapped ones are skipped.
initTableScroll();
// Every <select> gets the estate's dropdown instead of the OS one. Called
// unconditionally, beside initTableScroll() and for the same reason: both have no
// markup contract, so both are a no-op on a page that has none of their element —
// and neither can be forgotten on the day a page grows one.
initSelects();
// EVERY DIAGRAM IS ZOOMABLE. A flowchart sized to fit a text column is unreadable at
// exactly the moment someone needs to read it, so the system ships the overlay (click /
// Enter / Space, wheel-zoom about the pointer, drag-pan, + - 0, Escape) and a doc has no
// reason to opt out. Wired HERE and not in the diagram script below, because a baked page
// keeps that script as inert text and has its svgs already. The opener takes the svg at
// the moment it opens: the one the diagram script last drew, or a baked diagram's variant
// of the current theme. A second call wires nothing twice.
initDiagramZoom("pre.mermaid");
// The minimap builds itself from the sections — one bar each, in the left gutter,
// with the scroll-spy built in. No markup and no per-section wiring: add a
// <section class="doc" id="..."> and it appears. It returns null (and renders
// nothing) when there are fewer than two sections, on the same reasoning as a
// one-entry nav.
initMinimap({ sections: "section.doc" });
// The minimap's bars carry `data-tip`, not `title`: the native tooltip waits about a
// second and renders in the OS's chrome, which is useless on a strip meant to be
// scrubbed. This shows the section name instantly, in the page's own type. Delegated,
// so it covers the bars the minimap just built.
initTooltips();
</script>
<!-- Mermaid diagrams, themed from the design tokens and drawn again on a theme switch.
window.ddRenderDiagrams() draws every diagram once more in the current theme and
resolves to one svg per diagram: scripts/bake.mjs calls it once per theme, keeps the
four svgs in the page and turns this script into inert text (type="text/plain").
Every pass ends with a dd:diagrams event, which an explainer listens for.
Delete this script (and the pre.mermaid CSS) if the page has no diagrams. -->
<script type="module" data-dd-diagrams>
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11.16.0/dist/mermaid.esm.min.mjs";
const diagrams = Array.from(document.querySelectorAll("pre.mermaid"));
// capture the sources before the first render replaces them with SVG
for (const node of diagrams) node.dataset.mermaidSource = node.textContent;
// Renders are serialized: applyStoredTheme() writes data-theme on startup, so an
// unguarded observer would kick off a second pass while the first is still
// running — two passes mutating the same nodes stack every diagram into one box.
let renderPass = 0;
let pending = Promise.resolve([]);
const renderDiagrams = () => {
pending = pending.then(async () => {
const pass = (renderPass += 1);
const styles = getComputedStyle(document.documentElement);
const token = (name, fallback) => styles.getPropertyValue(name).trim() || fallback;
mermaid.initialize({
startOnLoad: false,
securityLevel: "strict",
fontFamily: token("--font-mono", "monospace"),
flowchart: { htmlLabels: true, useMaxWidth: true },
themeVariables: {
background: token("--background", "#f5efe2"),
primaryColor: token("--card", "#efe7d4"),
primaryTextColor: token("--foreground", "#43352a"),
primaryBorderColor: token("--border", "#d9cdb6"),
secondaryColor: token("--muted", "#ece3cf"),
tertiaryColor: token("--muted", "#ece3cf"),
lineColor: token("--muted-foreground", "#71614e"),
textColor: token("--foreground", "#43352a"),
mainBkg: token("--card", "#efe7d4"),
nodeBorder: token("--border", "#d9cdb6"),
clusterBkg: token("--muted", "#ece3cf"),
clusterBorder: token("--border", "#d9cdb6"),
edgeLabelBackground: token("--background", "#f5efe2"),
titleColor: token("--primary", "#8a4516"),
},
});
// render ids must be unique per pass, or a re-render collides with itself
const svgs = [];
for (const [index, node] of diagrams.entries()) {
try {
const { svg } = await mermaid.render(`mermaid-svg-${index}-${pass}`, node.dataset.mermaidSource);
node.innerHTML = svg;
node.dataset.processed = "true";
svgs.push(svg);
} catch {
// a broken diagram falls back to its source — it must never blank the box
node.textContent = node.dataset.mermaidSource;
node.removeAttribute("data-processed");
svgs.push(null);
}
}
document.dispatchEvent(new CustomEvent("dd:diagrams"));
return svgs;
});
return pending;
};
window.ddRenderDiagrams = renderDiagrams;
if (diagrams.length) {
// Wait for the terminal reveal: a diagram inside a not-yet-revealed
// [data-term-out] is visibility:hidden, and Mermaid would size it against a
// container it cannot measure properly. With animation off, render at once.
if (document.documentElement.classList.contains("anim-off") || window.termContentDone) {
void renderDiagrams();
} else {
window.addEventListener("term:contentdone", () => void renderDiagrams(), { once: true });
}
// re-render with the new palette, but only when the theme actually changed
let lastTheme = document.documentElement.dataset.theme;
new MutationObserver(() => {
const theme = document.documentElement.dataset.theme;
if (theme === lastTheme) return;
lastTheme = theme;
void renderDiagrams();
}).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
}
</script>
</body>
</html>