fb-slides 0.1.2

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 (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/bin/fb-slides.mjs +111 -0
  4. package/lib/build.mjs +98 -0
  5. package/lib/config.mjs +90 -0
  6. package/lib/create.mjs +59 -0
  7. package/lib/decks.mjs +13 -0
  8. package/lib/dev.mjs +94 -0
  9. package/lib/render.mjs +52 -0
  10. package/lib/server.mjs +168 -0
  11. package/lib/vendor.mjs +32 -0
  12. package/package.json +51 -0
  13. package/runtime/annotate.js +458 -0
  14. package/runtime/deck.js +299 -0
  15. package/runtime/index.html +37 -0
  16. package/runtime/outline.js +354 -0
  17. package/runtime/shortcuts.js +53 -0
  18. package/runtime/spotlight.js +293 -0
  19. package/runtime/theme.base.css +879 -0
  20. package/templates/starter/README.md +27 -0
  21. package/templates/starter/_gitignore +4 -0
  22. package/templates/starter/_package.json +14 -0
  23. package/templates/starter/assets/.gitkeep +0 -0
  24. package/templates/starter/decks/01-intro.md +44 -0
  25. package/templates/starter/decks/02-demos.md +34 -0
  26. package/templates/starter/demo/angular-hello/README.md +15 -0
  27. package/templates/starter/demo/angular-hello/_package.json +24 -0
  28. package/templates/starter/demo/angular-hello/angular.json +34 -0
  29. package/templates/starter/demo/angular-hello/src/index.html +12 -0
  30. package/templates/starter/demo/angular-hello/src/main.ts +18 -0
  31. package/templates/starter/demo/angular-hello/src/styles.css +22 -0
  32. package/templates/starter/demo/angular-hello/tsconfig.app.json +5 -0
  33. package/templates/starter/demo/angular-hello/tsconfig.json +17 -0
  34. package/templates/starter/demo/counter/index.html +34 -0
  35. package/templates/starter/slides.config.js +34 -0
  36. package/templates/starter/theme.css +14 -0
@@ -0,0 +1,879 @@
1
+ /* ---------------------------------------------------------------------------
2
+ The base theme. reveal.js ships only its core CSS; the look below is this
3
+ package's, and every colour in it comes from the tokens in `:root`.
4
+
5
+ A project overrides it by dropping a `theme.css` next to its decks/ — it is
6
+ loaded after this file, so redefining a token there is enough to reskin the
7
+ whole deck. Do not edit this file in a project: it is replaced on every update.
8
+ --------------------------------------------------------------------------- */
9
+
10
+ :root {
11
+ --bg: #0e1116;
12
+ --bg-2: #12151b;
13
+ --card: #1a1f27;
14
+ --line: #2c333f;
15
+ --text: #e8ebf0;
16
+ --muted: #97a1b0;
17
+ --accent: #6ea8fe;
18
+ --accent-2: #7ee2b8;
19
+ /* The outline sidebar: how wide it is, and how far one level of nesting
20
+ steps in. Both are tokens so a project can widen the list for a deck with
21
+ long titles. */
22
+ --outline-w: 300px;
23
+ --outline-step: 15px;
24
+ /* Its scrollbar, at rest and under the pointer: tokens so a project that
25
+ reskins the panel does not have to restate the WebKit rules. */
26
+ --outline-bar: color-mix(in srgb, var(--text) 16%, transparent);
27
+ --outline-bar-hover: color-mix(in srgb, var(--text) 30%, transparent);
28
+ --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
29
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
30
+ }
31
+
32
+ /* reveal.js 5 paints the page background on `.reveal-viewport` (the <html>
33
+ element), so setting it on <body> would sit behind it and never show. */
34
+ html,
35
+ body,
36
+ .reveal-viewport {
37
+ background:
38
+ radial-gradient(1200px 700px at 78% -10%, #1b2740 0%, transparent 60%),
39
+ radial-gradient(900px 600px at -10% 110%, #16261f 0%, transparent 55%),
40
+ var(--bg);
41
+ background-attachment: fixed;
42
+ }
43
+
44
+ .reveal {
45
+ font-family: var(--sans);
46
+ font-size: 26px;
47
+ font-weight: 400;
48
+ color: var(--text);
49
+ -webkit-font-smoothing: antialiased;
50
+ }
51
+
52
+ /* Content is read, not admired: left-aligned by default. Vertical centring is
53
+ reveal's own (`center: true`), so slides must NOT be forced to full height —
54
+ only the two layouts below, which have to fill it. */
55
+ .reveal .slides { text-align: left; }
56
+
57
+ /* --- headings ------------------------------------------------------------ */
58
+
59
+ .reveal h1,
60
+ .reveal h2,
61
+ .reveal h3 {
62
+ font-family: var(--sans);
63
+ color: var(--text);
64
+ text-transform: none;
65
+ letter-spacing: -0.02em;
66
+ line-height: 1.15;
67
+ margin: 0 0 0.7em;
68
+ text-shadow: none;
69
+ }
70
+ .reveal h1 { font-size: 2.15em; font-weight: 700; }
71
+ .reveal h2 { font-size: 1.36em; font-weight: 650; }
72
+ .reveal h3 { font-size: 1.05em; font-weight: 600; color: var(--muted); }
73
+
74
+ /* A rule under the slide title, in the accent. */
75
+ .reveal section:not(.deck-title) > h2:first-child {
76
+ padding-bottom: 0.36em;
77
+ border-bottom: 2px solid color-mix(in srgb, var(--accent) 40%, transparent);
78
+ }
79
+
80
+ /* --- deck dividers ------------------------------------------------------- */
81
+
82
+ .reveal .slides section.deck-title { text-align: left; }
83
+ .reveal .slides section.deck-title h1 {
84
+ font-size: 2.6em;
85
+ background: linear-gradient(96deg, var(--text) 30%, var(--accent) 100%);
86
+ -webkit-background-clip: text;
87
+ background-clip: text;
88
+ color: transparent;
89
+ margin-bottom: 0.15em;
90
+ }
91
+ .reveal .slides section.deck-title h2 {
92
+ color: var(--muted);
93
+ font-weight: 500;
94
+ font-size: 1.25em;
95
+ border: 0;
96
+ margin-bottom: 0.8em;
97
+ }
98
+ .reveal .slides section.deck-title h3 { color: var(--accent); font-size: 1.1em; }
99
+ .reveal .slides section.deck-title p { color: var(--muted); }
100
+ .reveal .slides section.deck-title code {
101
+ color: var(--accent-2);
102
+ background: none;
103
+ border: 0;
104
+ padding: 0;
105
+ font-size: 1em;
106
+ }
107
+
108
+ /* --- text ---------------------------------------------------------------- */
109
+
110
+ .reveal p { line-height: 1.5; margin: 0 0 0.7em; }
111
+ /* Trailing margin would push centred content off-centre. */
112
+ .reveal .slides section > :last-child { margin-bottom: 0; }
113
+ .reveal strong { color: #fff; font-weight: 650; }
114
+ .reveal em { color: var(--accent-2); font-style: italic; }
115
+ .reveal a { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); }
116
+
117
+ .reveal ul,
118
+ .reveal ol { display: block; margin: 0 0 0.7em; padding-left: 1.1em; }
119
+ .reveal li { margin: 0 0 0.5em; line-height: 1.45; }
120
+ .reveal ul li::marker { color: var(--accent); }
121
+ .reveal ol li::marker { color: var(--accent); font-variant-numeric: tabular-nums; }
122
+ .reveal li > ul { margin-top: 0.4em; }
123
+
124
+ .reveal blockquote {
125
+ width: auto;
126
+ margin: 0.9em 0;
127
+ padding: 0.55em 1em;
128
+ background: color-mix(in srgb, var(--accent) 9%, transparent);
129
+ border-left: 3px solid var(--accent);
130
+ border-radius: 0 8px 8px 0;
131
+ box-shadow: none;
132
+ font-style: normal;
133
+ color: var(--text);
134
+ }
135
+ .reveal blockquote p { margin: 0; }
136
+
137
+ /* Inline code. */
138
+ .reveal :not(pre) > code {
139
+ font-family: var(--mono);
140
+ font-size: 0.88em;
141
+ padding: 0.12em 0.36em;
142
+ border-radius: 5px;
143
+ background: var(--card);
144
+ border: 1px solid var(--line);
145
+ color: var(--accent-2);
146
+ white-space: nowrap;
147
+ }
148
+
149
+ /* --- code blocks --------------------------------------------------------- */
150
+
151
+ .reveal pre {
152
+ display: block;
153
+ width: 100%;
154
+ margin: 0.5em 0 0.7em;
155
+ font-size: 0.62em;
156
+ line-height: 1.45;
157
+ box-shadow: 0 12px 30px rgb(0 0 0 / 0.35);
158
+ border-radius: 10px;
159
+ border: 1px solid var(--line);
160
+ overflow: hidden;
161
+ background: #14181f;
162
+ /* Stepped line highlighting stacks one absolutely positioned <code> per step
163
+ inside this <pre>; without a positioned ancestor they escape the block. */
164
+ position: relative;
165
+ }
166
+ .reveal pre code {
167
+ font-family: var(--mono);
168
+ padding: 1.05em 1.2em;
169
+ max-height: none;
170
+ /* Opaque, not transparent: each step paints over the one underneath. */
171
+ background: #14181f;
172
+ border-radius: 10px;
173
+ overflow-x: auto;
174
+ -moz-tab-size: 2;
175
+ tab-size: 2;
176
+ }
177
+ .reveal pre code.fragment { width: 100%; box-sizing: border-box; }
178
+ .reveal pre.pre--dense { font-size: 0.55em; }
179
+
180
+ /* Line highlighting (```js [1-2|4]) */
181
+ .reveal .hljs .highlight-line { background: color-mix(in srgb, var(--accent) 16%, transparent); }
182
+ .reveal .hljs-ln-numbers { color: var(--muted); opacity: 0.45; }
183
+
184
+ /* --- highlight.js palette patches ----------------------------------------
185
+ The monokai that ships with reveal's highlight plugin paints strings,
186
+ functions, built-ins, variables *and* template interpolations the same green
187
+ — so `Counter is now ${addToCounter(amount)}` comes out as one flat blob.
188
+ Strings get monokai's own yellow back, and `${…}` is coloured as what it is:
189
+ code inside a string. deck.js re-tokenises what sits between the braces. */
190
+ .reveal .hljs-string,
191
+ .reveal .hljs-regexp { color: #e6db74; }
192
+ .reveal .hljs-subst { color: var(--text); }
193
+ /* The `${` and `}` themselves — the seam between string and expression. */
194
+ .reveal .hljs-subst-mark { color: #f92672; opacity: 0.85; }
195
+ .reveal .hljs-built_in { color: #66d9ef; font-style: italic; }
196
+ .reveal .hljs-variable,
197
+ .reveal .hljs-property { color: var(--text); }
198
+ .reveal .hljs-variable.language_,
199
+ .reveal .hljs-params { color: #fd971f; font-style: italic; }
200
+ .reveal .hljs-number,
201
+ .reveal .hljs-variable.constant_ { color: #ae81ff; }
202
+
203
+ /* --- tables -------------------------------------------------------------- */
204
+
205
+ .reveal .slides section > table {
206
+ width: 100%;
207
+ margin: 0.4em 0 0.6em;
208
+ border-collapse: collapse;
209
+ font-size: 0.82em;
210
+ border: 1px solid var(--line);
211
+ border-radius: 10px;
212
+ overflow: hidden;
213
+ }
214
+ .reveal .slides section > table th {
215
+ background: var(--card);
216
+ color: var(--text);
217
+ font-weight: 650;
218
+ text-align: left;
219
+ padding: 0.5em 0.75em;
220
+ border-bottom: 1px solid var(--line);
221
+ }
222
+ .reveal .slides section > table td {
223
+ padding: 0.45em 0.75em;
224
+ border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent);
225
+ color: var(--text);
226
+ vertical-align: top;
227
+ }
228
+ .reveal .slides section > table tr:last-child td { border-bottom: 0; }
229
+ .reveal .slides section > table tbody tr:nth-child(even) { background: rgb(255 255 255 / 0.018); }
230
+ .reveal .slides section > table td:first-child { color: var(--muted); white-space: nowrap; }
231
+ .reveal .slides section > table th:first-child:empty { min-width: 8ch; }
232
+
233
+ /* --- fragments ----------------------------------------------------------- */
234
+
235
+ .reveal .fragment:not(.visible) { opacity: 0; }
236
+ .reveal .fragment.visible { opacity: 1; }
237
+ .reveal li.fragment { transition: opacity 0.18s ease, transform 0.18s ease; }
238
+ .reveal li.fragment:not(.visible) { transform: translateY(2px); }
239
+
240
+ /* --- mermaid ------------------------------------------------------------- */
241
+
242
+ .reveal .slides section .mermaid {
243
+ display: flex;
244
+ justify-content: center;
245
+ margin: 0.4em 0 0.6em;
246
+ }
247
+ /* mermaid emits a px-sized SVG (see `useMaxWidth: false`); scale it down to fit. */
248
+ .reveal .slides section .mermaid svg {
249
+ width: 100%;
250
+ height: auto;
251
+ max-height: 430px;
252
+ }
253
+ /* SVG edge labels sit on top of the arrows; knock the line out behind the text
254
+ by painting a background-coloured stroke under the glyphs. */
255
+ .reveal .slides section .mermaid .edgeLabel text,
256
+ .reveal .slides section .mermaid .edgeLabel tspan,
257
+ .reveal .slides section .mermaid .edgeLabels text {
258
+ fill: var(--muted) !important;
259
+ paint-order: stroke;
260
+ stroke: var(--bg);
261
+ stroke-width: 7px;
262
+ stroke-linejoin: round;
263
+ }
264
+ .reveal .slides section .mermaid .node rect { rx: 8; ry: 8; }
265
+
266
+ /* --- the live-demo slides ------------------------------------------------ */
267
+
268
+ .reveal .slides section.demo-slide {
269
+ display: flex !important;
270
+ flex-direction: column;
271
+ gap: 0.55em;
272
+ height: 100%;
273
+ /* Full-bleed: this one is the demo, not a slide about the demo. */
274
+ margin: 0;
275
+ }
276
+ .reveal .slides section.demo-slide header {
277
+ display: flex;
278
+ align-items: center;
279
+ gap: 0.6em;
280
+ font-size: 0.7em;
281
+ color: var(--muted);
282
+ flex: 0 0 auto;
283
+ }
284
+ .reveal .slides section.demo-slide header code {
285
+ font-size: 1em;
286
+ white-space: nowrap;
287
+ }
288
+ .reveal .slides section.demo-slide header a { margin-left: auto; font-size: 0.9em; }
289
+ .reveal .slides section.demo-slide .tag {
290
+ font: 600 0.8em/1 var(--sans);
291
+ letter-spacing: 0.08em;
292
+ text-transform: uppercase;
293
+ color: #06210f;
294
+ background: var(--accent-2);
295
+ padding: 0.35em 0.55em;
296
+ border-radius: 999px;
297
+ }
298
+ .reveal .slides section.demo-slide iframe {
299
+ flex: 1 1 auto;
300
+ width: 100%;
301
+ min-height: 0;
302
+ border: 1px solid var(--line);
303
+ border-radius: 12px;
304
+ background: var(--bg-2);
305
+ box-shadow: 0 18px 45px rgb(0 0 0 / 0.45);
306
+ }
307
+
308
+ /* --- chrome -------------------------------------------------------------- */
309
+
310
+ .reveal .progress { color: var(--accent); height: 3px; }
311
+ /* Reveal parks its arrows `--r-controls-spacing` from the corner; lifting them
312
+ clears the strip the slide number now sits in. */
313
+ .reveal-viewport { --r-controls-spacing: 30px; }
314
+
315
+ .reveal .slide-number {
316
+ background: color-mix(in srgb, var(--card) 80%, transparent);
317
+ color: var(--muted);
318
+ font-family: var(--mono);
319
+ font-size: 15px;
320
+ border-radius: 6px;
321
+ padding: 3px 8px;
322
+ /* Centred under the arrows: the pair spans 100px inwards from the spacing
323
+ edge, so its middle is spacing + 50px in from the left. */
324
+ right: auto;
325
+ left: calc(var(--r-controls-spacing, 12px) + 50px);
326
+ transform: translateX(-50%);
327
+ bottom: 12px;
328
+ }
329
+ /* Reveal renders the number as a link, which would pick up the underline
330
+ `.reveal a` gives every other link on a slide. */
331
+ .reveal .slide-number a { color: inherit; border-bottom: 0; }
332
+ /* Arrows move to the left with the slide number, leaving the bottom-right
333
+ corner to the signature. Reveal anchors each arrow to the *right* edge of this
334
+ box, which has no width of its own — so give it the 100px the cluster needs,
335
+ or the arrows hang off the left of the screen. */
336
+ .reveal .controls {
337
+ color: var(--accent);
338
+ right: auto;
339
+ left: var(--r-controls-spacing, 12px);
340
+ width: 100px;
341
+ }
342
+
343
+ #deck-badge {
344
+ position: fixed;
345
+ top: 16px;
346
+ right: 18px;
347
+ z-index: 30;
348
+ font: 600 12px/1 var(--sans);
349
+ letter-spacing: 0.1em;
350
+ text-transform: uppercase;
351
+ color: var(--muted);
352
+ background: color-mix(in srgb, var(--card) 75%, transparent);
353
+ border: 1px solid var(--line);
354
+ border-radius: 999px;
355
+ padding: 6px 11px;
356
+ pointer-events: none;
357
+ backdrop-filter: blur(6px);
358
+ }
359
+ #deck-badge[hidden] { display: none; }
360
+
361
+ /* Bottom right, opposite the slide number: the sticker overlaps a pill that runs
362
+ out from under it, so the pill's right end never shows. `row-reverse` puts the
363
+ sticker on the outside, with the pill running back in towards the slide. */
364
+ #deck-sig {
365
+ position: fixed;
366
+ right: 14px;
367
+ bottom: 10px;
368
+ z-index: 30;
369
+ display: flex;
370
+ flex-direction: row-reverse;
371
+ align-items: flex-end;
372
+ pointer-events: none;
373
+ }
374
+ #deck-sig a { pointer-events: auto; }
375
+ #deck-sig .sig-logo {
376
+ display: block;
377
+ position: relative;
378
+ z-index: 1;
379
+ }
380
+ #deck-sig img {
381
+ display: block;
382
+ height: 80px;
383
+ width: auto;
384
+ }
385
+ #deck-sig .sig-name {
386
+ /* Tucked under the sticker, which is why the padding on that side is deeper.
387
+ Sitting half its own height above the baseline, it reads as part of the
388
+ sticker rather than as something resting under it. */
389
+ margin: 0 -30px 14px 0;
390
+ padding: 5px 36px 5px 13px;
391
+ font: 600 12px/1 var(--sans);
392
+ letter-spacing: 0.04em;
393
+ color: var(--muted);
394
+ text-decoration: none;
395
+ background: color-mix(in srgb, var(--card) 75%, transparent);
396
+ border: 1px solid var(--line);
397
+ border-radius: 999px;
398
+ backdrop-filter: blur(6px);
399
+ }
400
+
401
+ /* --- the drawing overlay (annotate.js) ----------------------------------- */
402
+
403
+ /* Above the slides and the corner chrome, below the toolbar that drives it.
404
+ Transparent to the pointer until a tool is armed, so a deck nobody is drawing
405
+ on behaves exactly as it did before. */
406
+ #deck-ink {
407
+ position: fixed;
408
+ inset: 0;
409
+ z-index: 35;
410
+ pointer-events: none;
411
+ }
412
+ #deck-ink.is-live {
413
+ pointer-events: auto;
414
+ cursor: crosshair;
415
+ }
416
+
417
+ /* Faded back until it is wanted: on stage the slide is the point, and the
418
+ toolbar sits in the one place the eye keeps returning to. */
419
+ #deck-tools {
420
+ position: fixed;
421
+ top: 14px;
422
+ left: 50%;
423
+ transform: translateX(-50%);
424
+ z-index: 40;
425
+ display: flex;
426
+ gap: 4px;
427
+ padding: 5px;
428
+ background: color-mix(in srgb, var(--card) 75%, transparent);
429
+ border: 1px solid var(--line);
430
+ border-radius: 999px;
431
+ backdrop-filter: blur(6px);
432
+ opacity: 0.4;
433
+ transition: opacity 0.18s ease;
434
+ }
435
+ #deck-tools:hover,
436
+ #deck-tools:focus-within,
437
+ #deck-tools.is-armed { opacity: 1; }
438
+
439
+ .deck-tool {
440
+ display: grid;
441
+ place-items: center;
442
+ width: 34px;
443
+ height: 34px;
444
+ padding: 0;
445
+ border: 0;
446
+ border-radius: 999px;
447
+ background: transparent;
448
+ color: var(--muted);
449
+ cursor: pointer;
450
+ transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
451
+ }
452
+ .deck-tool svg {
453
+ display: block;
454
+ width: 19px;
455
+ height: 19px;
456
+ }
457
+ .deck-tool:hover {
458
+ background: color-mix(in srgb, var(--text) 10%, transparent);
459
+ color: var(--text);
460
+ }
461
+ /* An armed tool wears the colour it draws in. annotate.js owns the pen's
462
+ palette — it is a list the toolbar enumerates, not a theme token — and sets
463
+ `--pen` and its readable foreground on #deck-tools as the colour changes. */
464
+ .deck-tool[aria-pressed='true'] {
465
+ background: var(--pen, #ff8c1a);
466
+ color: var(--pen-ink, #17110a);
467
+ /* The first ring keeps the chip's edge readable when the ink is black or
468
+ white and the fill alone would vanish into the toolbar. */
469
+ box-shadow:
470
+ 0 0 0 1px color-mix(in srgb, var(--text) 30%, transparent),
471
+ 0 0 16px color-mix(in srgb, var(--pen, #ff8c1a) 55%, transparent);
472
+ }
473
+
474
+ /* The colours, hanging under the toolbar while the pen is armed. */
475
+ #deck-swatches {
476
+ position: absolute;
477
+ top: calc(100% + 9px);
478
+ left: 50%;
479
+ transform: translateX(-50%);
480
+ display: flex;
481
+ gap: 9px;
482
+ padding: 7px 9px;
483
+ background: color-mix(in srgb, var(--card) 82%, transparent);
484
+ border: 1px solid var(--line);
485
+ border-radius: 999px;
486
+ backdrop-filter: blur(6px);
487
+ }
488
+ #deck-swatches[hidden] { display: none; }
489
+
490
+ .deck-swatch {
491
+ width: 19px;
492
+ height: 19px;
493
+ padding: 0;
494
+ border: 1px solid color-mix(in srgb, var(--text) 22%, transparent);
495
+ border-radius: 999px;
496
+ background: var(--ink);
497
+ cursor: pointer;
498
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
499
+ }
500
+ .deck-swatch:hover { transform: scale(1.18); }
501
+ /* The current colour has to be unmistakable across the whole palette, white and
502
+ black included — so the ring is a fixed bright one held off the swatch by a
503
+ dark gap, rather than anything tinted by the ink itself. */
504
+ .deck-swatch[aria-checked='true'] {
505
+ transform: scale(1.2);
506
+ box-shadow:
507
+ 0 0 0 2px var(--bg-2),
508
+ 0 0 0 4px var(--text),
509
+ 0 0 14px color-mix(in srgb, var(--ink) 65%, transparent);
510
+ }
511
+
512
+ /* --- the outline sidebar (outline.js) ------------------------------------ */
513
+
514
+ /* The panel does not float over the deck, it takes room from it: the slide
515
+ stays whole and readable while you look down the list for the next one.
516
+ Direct child only: the preview thumbnails inside the panel wear the .reveal
517
+ class too, to inherit the theme, and must not be pushed around with it. */
518
+ body.outline-open > .reveal {
519
+ left: var(--outline-w);
520
+ width: calc(100% - var(--outline-w));
521
+ }
522
+ /* Matches SLIDE_MS in outline.js, which drives reveal's rescale for as long as
523
+ this runs. */
524
+ body > .reveal { transition: left 0.26s ease, width 0.26s ease; }
525
+ /* Fixed chrome sits outside .reveal, so it does not move with the deck. Only
526
+ the toolbar has to: the other two are anchored to the right edge. */
527
+ body.outline-open #deck-tools { left: calc(50% + var(--outline-w) / 2); }
528
+
529
+ /* Faded back until it is wanted, like the pen's toolbar. Open, the panel
530
+ covers it, and the panel has its own close button. */
531
+ #deck-menu {
532
+ position: fixed;
533
+ top: 14px;
534
+ left: 14px;
535
+ z-index: 40;
536
+ display: grid;
537
+ place-items: center;
538
+ width: 34px;
539
+ height: 34px;
540
+ padding: 0;
541
+ color: var(--muted);
542
+ background: color-mix(in srgb, var(--card) 75%, transparent);
543
+ border: 1px solid var(--line);
544
+ border-radius: 999px;
545
+ backdrop-filter: blur(6px);
546
+ cursor: pointer;
547
+ opacity: 0.4;
548
+ transition: opacity 0.18s ease, color 0.15s ease;
549
+ }
550
+ #deck-menu:hover { opacity: 1; color: var(--text); }
551
+ #deck-menu svg { display: block; width: 19px; height: 19px; }
552
+
553
+ #deck-outline {
554
+ position: fixed;
555
+ inset: 0 auto 0 0;
556
+ z-index: 45;
557
+ display: flex;
558
+ flex-direction: column;
559
+ width: var(--outline-w);
560
+ background: color-mix(in srgb, var(--bg-2) 92%, transparent);
561
+ border-right: 1px solid var(--line);
562
+ backdrop-filter: blur(10px);
563
+ transform: translateX(-100%);
564
+ transition: transform 0.26s ease;
565
+ }
566
+ body.outline-open #deck-outline { transform: none; }
567
+
568
+ #deck-outline > header {
569
+ display: flex;
570
+ align-items: center;
571
+ justify-content: space-between;
572
+ gap: 8px;
573
+ padding: 14px 10px 12px 16px;
574
+ border-bottom: 1px solid var(--line);
575
+ }
576
+ #deck-outline > header span {
577
+ flex: 1;
578
+ font: 600 12px/1 var(--sans);
579
+ letter-spacing: 0.1em;
580
+ text-transform: uppercase;
581
+ color: var(--muted);
582
+ }
583
+ .deck-outline-close,
584
+ .deck-outline-view {
585
+ display: grid;
586
+ place-items: center;
587
+ width: 28px;
588
+ height: 28px;
589
+ padding: 0;
590
+ border: 0;
591
+ border-radius: 999px;
592
+ background: transparent;
593
+ color: var(--muted);
594
+ cursor: pointer;
595
+ }
596
+ .deck-outline-close:hover,
597
+ .deck-outline-view:hover { background: color-mix(in srgb, var(--text) 10%, transparent); color: var(--text); }
598
+ .deck-outline-close svg,
599
+ .deck-outline-view svg { display: block; width: 16px; height: 16px; }
600
+
601
+ .deck-outline-list {
602
+ flex: 1;
603
+ overflow-y: auto;
604
+ /* The deck must not scroll along when the list reaches its end. */
605
+ overscroll-behavior: contain;
606
+ padding: 8px 10px 20px;
607
+ }
608
+
609
+ /* A hairline, not chrome. macOS hides overlay scrollbars until you scroll,
610
+ which costs the panel the one hint that a fifty-slide deck continues below;
611
+ this keeps that hint permanently, quiet enough to ignore. It darkens while
612
+ the panel is under the pointer, and again on the bar itself, so the thing
613
+ you are about to drag is the thing that answers. */
614
+ .deck-outline-list::-webkit-scrollbar { width: 5px; }
615
+ .deck-outline-list::-webkit-scrollbar-track { background: transparent; }
616
+ .deck-outline-list::-webkit-scrollbar-thumb {
617
+ border-radius: 999px;
618
+ background: var(--outline-bar);
619
+ }
620
+ #deck-outline:hover .deck-outline-list::-webkit-scrollbar-thumb { background: var(--outline-bar-hover); }
621
+ .deck-outline-list::-webkit-scrollbar-thumb:hover,
622
+ #deck-outline:hover .deck-outline-list::-webkit-scrollbar-thumb:hover {
623
+ background: color-mix(in srgb, var(--text) 45%, transparent);
624
+ }
625
+
626
+ /* Firefox only — it has no ::-webkit-scrollbar, and Chrome ignores every rule
627
+ above the moment `scrollbar-width` is set on the element, snapping the bar
628
+ back to its own 11px `thin`. So the standard pair is fenced off to the one
629
+ engine that needs it: `thin` there is already a hairline. */
630
+ @supports not selector(::-webkit-scrollbar) {
631
+ .deck-outline-list {
632
+ scrollbar-width: thin;
633
+ scrollbar-color: var(--outline-bar) transparent;
634
+ }
635
+ #deck-outline:hover .deck-outline-list { scrollbar-color: var(--outline-bar-hover) transparent; }
636
+ }
637
+
638
+ /* One per deck, where it starts: the `section:` from its front matter.
639
+ Direct child only — in preview mode the rows hold cloned slides, and a
640
+ slide's own h2 must keep the theme's look, not the list header's. */
641
+ .deck-outline-list > h2 {
642
+ position: sticky;
643
+ top: 0;
644
+ /* A clear break above: the gap is what says "another deck starts here",
645
+ well before the header's own label is read. */
646
+ margin: 30px 0 6px;
647
+ padding: 6px 6px 5px;
648
+ font: 600 10px/1 var(--sans);
649
+ letter-spacing: 0.12em;
650
+ text-transform: uppercase;
651
+ color: var(--muted);
652
+ background: var(--bg-2);
653
+ border: 0;
654
+ }
655
+ .deck-outline-list > h2:first-child { margin-top: 0; }
656
+
657
+ .deck-outline-item {
658
+ display: flex;
659
+ align-items: baseline;
660
+ gap: 8px;
661
+ width: 100%;
662
+ padding: 7px 9px;
663
+ border: 0;
664
+ border-radius: 7px;
665
+ background: transparent;
666
+ color: var(--muted);
667
+ font: 400 13.5px/1.35 var(--sans);
668
+ text-align: left;
669
+ cursor: pointer;
670
+ transition: background 0.13s ease, color 0.13s ease;
671
+ }
672
+ /* Depth is set by outline.js: a deck divider is 0, the slides it introduces 1,
673
+ a vertical slide 2. Enough of a step to read as nesting, not as a margin. */
674
+ .deck-outline-item[data-depth='1'] { padding-left: calc(9px + var(--outline-step)); }
675
+ .deck-outline-item[data-depth='2'] { padding-left: calc(9px + var(--outline-step) * 2); }
676
+ .deck-outline-item.is-divider { color: var(--text); font-weight: 600; }
677
+ .deck-outline-item:hover { background: color-mix(in srgb, var(--text) 8%, transparent); color: var(--text); }
678
+
679
+ /* Where the deck is now — the one row the eye has to find on the way back. */
680
+ .deck-outline-item.is-current {
681
+ color: var(--text);
682
+ background: color-mix(in srgb, var(--accent) 16%, transparent);
683
+ box-shadow: inset 2px 0 0 var(--accent);
684
+ }
685
+
686
+ .deck-outline-title {
687
+ flex: 1;
688
+ /* Two lines of title, then an ellipsis: enough for any heading worth the
689
+ name, and the list stays scannable. */
690
+ display: -webkit-box;
691
+ -webkit-box-orient: vertical;
692
+ -webkit-line-clamp: 2;
693
+ overflow: hidden;
694
+ }
695
+ .deck-outline-n {
696
+ font: 500 11px/1 var(--mono);
697
+ font-variant-numeric: tabular-nums;
698
+ color: var(--muted);
699
+ opacity: 0.55;
700
+ }
701
+
702
+ /* --- the outline's preview mode ------------------------------------------ */
703
+
704
+ /* Same rows, but each one is the slide itself. Depth indents carry over from
705
+ the list view — a step costs thumbnail width, but that is what makes the
706
+ nesting read. Each indented thumb is narrower, so syncThumbScale gives every
707
+ stage its own scale. Two kinds of nesting, two shapes: a divider's slides
708
+ step in from the left like the titles do, while a vertical slide is inset
709
+ from both sides — stacked under its slide, not opening a new section. */
710
+ #deck-outline[data-view='preview'] .deck-outline-item { display: block; position: relative; padding: 5px; }
711
+ #deck-outline[data-view='preview'] .deck-outline-item[data-depth='1'] { padding-left: calc(5px + var(--outline-step)); }
712
+ #deck-outline[data-view='preview'] .deck-outline-item[data-depth='2'] { padding-left: calc(5px + var(--outline-step) * 2); }
713
+ #deck-outline[data-view='preview'] .deck-outline-item.is-vertical { padding-right: calc(5px + var(--outline-step)); }
714
+ /* A divider slide starts a new section: a break above it says so before its
715
+ thumb is read. Not when it opens its deck — the header's gap already did. */
716
+ #deck-outline[data-view='preview'] .deck-outline-item.is-divider { margin-top: 24px; }
717
+ #deck-outline[data-view='preview'] .deck-outline-list > h2 + .deck-outline-item.is-divider { margin-top: 0; }
718
+ /* The number hangs off the item's right edge — keep it on the inset thumb. */
719
+ #deck-outline[data-view='preview'] .deck-outline-item.is-vertical .deck-outline-n { right: calc(10px + var(--outline-step)); }
720
+ #deck-outline[data-view='preview'] .deck-outline-n {
721
+ position: absolute;
722
+ right: 10px;
723
+ bottom: 10px;
724
+ padding: 2px 5px;
725
+ border-radius: 5px;
726
+ color: var(--text);
727
+ background: color-mix(in srgb, var(--bg) 72%, transparent);
728
+ opacity: 1;
729
+ }
730
+
731
+ /* A deck-sized stage, scaled to the row: the theme lays the clone out exactly
732
+ as it laid out the slide, and one transform does the shrinking. The stage
733
+ wears .reveal for the theme's sake — keep any body-level .reveal rule to
734
+ `body > .reveal` so the main deck's chrome does not leak in here. */
735
+ .deck-outline-thumb {
736
+ position: relative;
737
+ display: block;
738
+ width: 100%;
739
+ aspect-ratio: var(--deck-ratio, 16 / 9);
740
+ overflow: hidden;
741
+ border: 1px solid var(--line);
742
+ border-radius: 6px;
743
+ background: var(--bg);
744
+ }
745
+ .deck-outline-item:hover .deck-outline-thumb { border-color: color-mix(in srgb, var(--text) 25%, transparent); }
746
+ .deck-outline-item.is-current .deck-outline-thumb { border-color: var(--accent); }
747
+
748
+ .deck-outline-thumb .slides {
749
+ position: absolute;
750
+ /* reveal.css says `inset: 0; margin: auto`, and auto margins on a deck-sized
751
+ box in a thumb-sized block solve to large negatives — pin the corner. */
752
+ inset: 0 auto auto 0;
753
+ margin: 0;
754
+ display: flex;
755
+ align-items: center;
756
+ width: var(--deck-w, 1280px);
757
+ height: var(--deck-h, 720px);
758
+ padding: 0 45px;
759
+ transform: scale(var(--thumb-scale, 0.2));
760
+ transform-origin: top left;
761
+ text-align: left;
762
+ pointer-events: none;
763
+ }
764
+ /* The clone arrives stripped of reveal's inline hiding; these outweigh what
765
+ reveal.css says about sections so it lays out as ordinary flow. */
766
+ .deck-outline-thumb .slides > section {
767
+ position: static !important;
768
+ display: block !important;
769
+ visibility: visible !important;
770
+ opacity: 1 !important;
771
+ width: 100%;
772
+ max-height: 100%;
773
+ transform: none !important;
774
+ }
775
+ /* A fragment not yet stepped through is still part of the slide up here. */
776
+ .deck-outline-thumb .fragment {
777
+ visibility: visible !important;
778
+ opacity: 1 !important;
779
+ transform: none !important;
780
+ }
781
+ /* Stands in for a demo's iframe: same footprint, no live page per row. */
782
+ .deck-outline-thumb-frame {
783
+ flex: 1 1 auto;
784
+ min-height: 0;
785
+ border: 1px dashed var(--line);
786
+ border-radius: 12px;
787
+ background: color-mix(in srgb, var(--text) 5%, transparent);
788
+ }
789
+
790
+ /* --- the spotlight (spotlight.js) ---------------------------------------- */
791
+
792
+ /* One veil over the slides, under the ink: spotlight.js punches the lit
793
+ rectangle out of it with a clip-path, and the backdrop-filter only applies
794
+ to what the veil still covers. The fade runs on the filter and the
795
+ background themselves rather than on opacity — an element's opacity does not
796
+ reliably fade its backdrop-filter along with it. */
797
+ #deck-spot {
798
+ position: fixed;
799
+ inset: 0;
800
+ z-index: 30;
801
+ pointer-events: none;
802
+ background: rgba(5, 8, 13, 0);
803
+ backdrop-filter: blur(0px) saturate(1);
804
+ transition: background 0.3s ease, backdrop-filter 0.3s ease;
805
+ }
806
+ #deck-spot.is-live {
807
+ pointer-events: auto;
808
+ cursor: crosshair;
809
+ }
810
+ #deck-spot.has-hole {
811
+ background: rgba(5, 8, 13, 0.45);
812
+ backdrop-filter: blur(5px) saturate(0.75);
813
+ }
814
+
815
+ /* The hole's edge: the clip-path clips the veil's own border away, so the rim
816
+ is its own element, sized by spotlight.js to sit exactly on the rectangle. */
817
+ #deck-spot-ring {
818
+ position: fixed;
819
+ z-index: 31;
820
+ pointer-events: none;
821
+ border: 1px solid color-mix(in srgb, var(--text) 40%, transparent);
822
+ box-shadow: 0 0 22px rgba(0, 0, 0, 0.35);
823
+ opacity: 0;
824
+ transition: opacity 0.3s ease;
825
+ }
826
+ #deck-spot-ring.has-hole { opacity: 1; }
827
+ /* The hole is clipped out of the veil's hit-testing along with its paint, so
828
+ the ring's box is what the pointer finds inside the light — grab it to move
829
+ the spotlight. Live only while a rectangle is up; the rest of the time it
830
+ must not sit between a click and the slide. */
831
+ #deck-spot-ring.is-live {
832
+ pointer-events: auto;
833
+ cursor: grab;
834
+ }
835
+ #deck-spot-ring.is-live:active { cursor: grabbing; }
836
+
837
+ /* --- print (?print-pdf) -------------------------------------------------- */
838
+
839
+ @media print {
840
+ body { background: #fff; }
841
+ #deck-badge { display: none; }
842
+ /* Annotations are a live-talk thing; neither survives the export. Nor does
843
+ navigation: a PDF has its own. */
844
+ #deck-ink,
845
+ #deck-spot,
846
+ #deck-spot-ring,
847
+ #deck-tools,
848
+ #deck-menu,
849
+ #deck-outline { display: none; }
850
+ /* The signature stays on the exported PDF — but the pill behind it does not
851
+ survive a white page. */
852
+ #deck-sig .sig-name { background: none; border-color: transparent; backdrop-filter: none; color: #444; }
853
+ }
854
+
855
+ /* ---------------------------------------------------------------------------
856
+ `​```js [1-2|4]` — the highlight plugin builds a <table> of line numbers and one
857
+ copy per step. Keep it flush; the generic table styling above must not apply.
858
+ --------------------------------------------------------------------------- */
859
+
860
+ .reveal pre code table {
861
+ width: 100%;
862
+ border: 0;
863
+ margin: 0;
864
+ border-collapse: collapse;
865
+ font-size: 1em;
866
+ }
867
+ .reveal pre code table td { border: 0; padding: 0; }
868
+ .reveal pre code .hljs-ln-numbers {
869
+ padding-right: 1.1em;
870
+ text-align: right;
871
+ user-select: none;
872
+ color: var(--muted);
873
+ opacity: 0.4;
874
+ }
875
+ .reveal pre code .hljs-ln-code { padding-left: 0; }
876
+ /* Dim what is not highlighted, rather than tinting what is. */
877
+ .reveal pre code[data-line-numbers] tr:not(.highlight-line) { opacity: 0.32; transition: opacity 0.2s ease; }
878
+ .reveal pre code[data-line-numbers] tr.highlight-line { opacity: 1; }
879
+ .reveal pre code[data-line-numbers] .highlight-line { background: color-mix(in srgb, var(--accent) 13%, transparent); }