decktile 0.2.0__py3-none-any.whl

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 (46) hide show
  1. decktile/__init__.py +5 -0
  2. decktile/__main__.py +3 -0
  3. decktile/assets/lucide/VENDORED.md +21 -0
  4. decktile/assets/lucide/lucide.css +2193 -0
  5. decktile/assets/lucide/lucide.ttf +3 -0
  6. decktile/cli.py +269 -0
  7. decktile/parser/__init__.py +0 -0
  8. decktile/parser/ast.py +191 -0
  9. decktile/parser/markdown.py +289 -0
  10. decktile/plugins/cards.py +25 -0
  11. decktile/plugins/directives.py +56 -0
  12. decktile/plugins/fenced.py +228 -0
  13. decktile/plugins/layouts.py +69 -0
  14. decktile/plugins/lucide.py +65 -0
  15. decktile/plugins/lucide_font.py +26 -0
  16. decktile/render/__init__.py +0 -0
  17. decktile/render/html.py +287 -0
  18. decktile/render/ir.py +536 -0
  19. decktile/render/odf/__init__.py +4 -0
  20. decktile/render/odf/geometry.py +510 -0
  21. decktile/render/odf/shapes.py +719 -0
  22. decktile/render/odf/styles.py +828 -0
  23. decktile/render/odp.py +380 -0
  24. decktile/render/pdf.py +17 -0
  25. decktile/render/pdg.py +72 -0
  26. decktile/render/seaborn.py +155 -0
  27. decktile/render/templates/base.css +741 -0
  28. decktile/render/templates/decktile.js +371 -0
  29. decktile/render/templates/shell.html +106 -0
  30. decktile/seaborn_styles/__init__.py +5 -0
  31. decktile/seaborn_styles/ossummit_europe.py +30 -0
  32. decktile/theme/__init__.py +0 -0
  33. decktile/theme/colors.py +53 -0
  34. decktile/theme/loader.py +22 -0
  35. decktile/theme/parser.py +264 -0
  36. decktile/themes/default.css +146 -0
  37. decktile/themes/kcd_vietnam.css +159 -0
  38. decktile/themes/kubecon_china.css +213 -0
  39. decktile/themes/kubecon_japan.css +156 -0
  40. decktile/themes/ossummit_europe.css +196 -0
  41. decktile/themes/ossummit_korea.css +196 -0
  42. decktile-0.2.0.dist-info/METADATA +625 -0
  43. decktile-0.2.0.dist-info/RECORD +46 -0
  44. decktile-0.2.0.dist-info/WHEEL +4 -0
  45. decktile-0.2.0.dist-info/entry_points.txt +6 -0
  46. decktile-0.2.0.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,741 @@
1
+ /* decktile base layout -- structural CSS only, no colors */
2
+
3
+ /* ---- reset ---- */
4
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
5
+
6
+ /* ---- variables ---- */
7
+ :root {
8
+ --radius: 0.18em;
9
+ --img-radius: 0.45em;
10
+ --gap: 1em;
11
+ --font-mono: "Noto Sans Mono", Consolas, "SFMono-Regular", Menlo, monospace;
12
+ --section-padding-v: 2.8em;
13
+ --section-padding-h: 3.6em;
14
+ --card-border: 1px solid var(--color-border);
15
+ --color-accent-primary: var(--color-accent);
16
+ --color-accent-secondary: var(--color-accent);
17
+ --color-accent-contrast: var(--color-accent);
18
+ --bg-image: none;
19
+ --bg-overlay: transparent;
20
+ --watermark: none;
21
+ --logo: none;
22
+ --logo-dark: var(--logo);
23
+ }
24
+
25
+
26
+ /* ---- logo (top-right, override via logo: frontmatter) ---- */
27
+ section::before {
28
+ content: "";
29
+ position: absolute;
30
+ top: 4%;
31
+ right: 5%;
32
+ width: 14%;
33
+ height: 0;
34
+ padding-bottom: 6%;
35
+ background: var(--logo) center / contain no-repeat;
36
+ opacity: 0.92;
37
+ z-index: 1;
38
+ }
39
+ [data-theme="dark"] section::before,
40
+ section[data-theme="dark"]::before {
41
+ /* Repeats the full shorthand, not just background-image: weasyprint
42
+ fails to resolve a var()-based background-image longhand override
43
+ applied after an earlier background shorthand -- it resets to
44
+ `none` instead of swapping just the image (confirmed via isolated
45
+ repro, unrelated to CSS custom-property chaining itself, which
46
+ weasyprint handles fine on its own). Real browsers resolve this
47
+ correctly either way; this avoids relying on a longhand override
48
+ weasyprint (both the --pdf output and the ODP renderer's geometry
49
+ capture) gets wrong. */
50
+ background: var(--logo-dark) center / contain no-repeat;
51
+ }
52
+
53
+
54
+ /* ---- watermark (conference logo, brand mark) ---- */
55
+ section::after {
56
+ display: block;
57
+ content: "";
58
+ position: absolute;
59
+ bottom: 4%;
60
+ right: 5%;
61
+ width: 10%;
62
+ height: 10%;
63
+ background: var(--watermark) right bottom / contain no-repeat;
64
+ pointer-events: none;
65
+ z-index: 0;
66
+ }
67
+
68
+ .accent-primary { color: var(--color-accent-primary); stroke: var(--color-accent-primary); }
69
+ .accent-secondary { color: var(--color-accent-secondary); stroke: var(--color-accent-secondary); }
70
+ .accent-contrast { color: var(--color-accent-contrast); stroke: var(--color-accent-contrast); }
71
+ svg.accent-primary, svg.accent-secondary, svg.accent-contrast { fill: none; }
72
+
73
+ /* ---- layout defaults ---- */
74
+ section {
75
+ font-size: 18pt;
76
+ padding: var(--section-padding-v) var(--section-padding-h);
77
+ display: flex;
78
+ flex-direction: column;
79
+ }
80
+ .slide-body {
81
+ flex: 1;
82
+ min-height: 0;
83
+ padding-bottom: 1.8em;
84
+ }
85
+
86
+ h1, h2, h3, h4, h5, h6 { margin: 0; }
87
+ h2 { padding-top: 0; margin-bottom: 0.15em; }
88
+ h4 { margin: 0.4em 0 0.15em; }
89
+ h3::after {
90
+ display: block;
91
+ content: "";
92
+ width: 100%;
93
+ height: 0;
94
+ /* same 1px weight as the card border (--card-border); 2px accent read
95
+ as heavier than the light grey frame around it */
96
+ border-bottom: 1px solid var(--color-accent-primary);
97
+ margin-top: 0.3em;
98
+ opacity: 0.3;
99
+ }
100
+ .card.side-image {
101
+ align-self: center;
102
+ justify-self: center;
103
+ padding: 0.3em;
104
+ background: none;
105
+ border: none;
106
+ }
107
+ .card.side-image img { max-width: 8em; }
108
+ .subtitle { margin-bottom: 0.6em; }
109
+ h2 + .subtitle { font-size: 0.85em; }
110
+ p { margin-bottom: 0.5em; }
111
+ ul { margin: 0 14pt; }
112
+ li { margin: 0.4em 0; line-height: 1.5; overflow-wrap: break-word; }
113
+ ul { list-style: var(--list-style); }
114
+ ul li::marker { color: var(--color-accent); font-weight: bold; }
115
+
116
+ img { max-width: 100%; height: auto; border-radius: var(--img-radius); }
117
+ section img { max-height: IMG_MAX_Hpx; object-fit: contain; display: block; margin-left: auto; margin-right: auto; }
118
+ figure.video { margin: 0; display: flex; align-items: center; justify-content: center; height: 100%; min-height: 0; }
119
+ figure.video video { width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--img-radius); }
120
+ img.video-print { display: none; }
121
+ [data-theme="dark"] section img,
122
+ section[data-theme="dark"] img { max-height: 16em; }
123
+ a { text-decoration: none; }
124
+
125
+ blockquote {
126
+ border-left: 4px solid;
127
+ padding-left: 0.9em;
128
+ margin: 0.5em 0;
129
+ }
130
+
131
+ code {
132
+ font-family: var(--font-mono);
133
+ }
134
+
135
+ .note {
136
+ border-left: 0.25em solid var(--color-accent);
137
+
138
+ padding: 0.5em 0.8em;
139
+ margin: 0.5em 0;
140
+ font-size: 0.8em;
141
+ border-radius: 0 var(--radius) var(--radius) 0;
142
+ }
143
+
144
+ pre {
145
+ overflow: hidden;
146
+ border-radius: var(--radius);
147
+ padding: 0.55em 0.8em;
148
+ margin: 0.5em 0;
149
+ font-size: 0.75em;
150
+ }
151
+ pre code {
152
+ padding: 0;
153
+ white-space: pre-wrap;
154
+ word-break: break-word;
155
+ }
156
+
157
+ table { border-collapse: collapse; width: 100%; margin: 0.5em 0; font-size: 0.75em; }
158
+ th, td { padding: 0.4em 0.6em; text-align: left; vertical-align: top; overflow-wrap: break-word; }
159
+ th { font-weight: 700; }
160
+
161
+ .grid { display: grid; gap: var(--gap); margin: 0 0 var(--gap) 0; }
162
+ .grid > * { min-width: 0; }
163
+
164
+ .card {
165
+ border-radius: var(--radius);
166
+ padding: 1em;
167
+ overflow-wrap: break-word;
168
+ min-width: 0;
169
+ }
170
+ .card h3 { margin-top: 0; margin-bottom: 0.5em; }
171
+ .card p { margin-bottom: 0.5em; }
172
+ .card p:last-child { margin-bottom: 0; }
173
+
174
+ /* ---- metric cards (::: card {metric}) ---- */
175
+ .card.metric {
176
+ text-align: center;
177
+ padding: 0.5em 0.2em;
178
+ }
179
+ .card.metric h3 {
180
+ font-size: 2.4em;
181
+ font-weight: 800;
182
+ line-height: 1.15;
183
+ margin: 0 0 0.15em;
184
+ color: var(--color-accent-primary);
185
+ }
186
+ .card.metric h3::after { display: none; }
187
+ .card.metric p {
188
+ color: var(--color-dimmed);
189
+ line-height: 1.3;
190
+ margin: 0;
191
+ }
192
+ /* metrics-N layout: center grid vertically in slide body */
193
+ [class*="layout-metrics"] .slide-body {
194
+ display: flex;
195
+ flex-direction: column;
196
+ align-items: center;
197
+ justify-content: center;
198
+ }
199
+ [class*="layout-metrics"] .slide-body .grid { width: 100%; margin: 0; }
200
+ [class*="layout-metrics"] .slide-body .row { width: 100%; }
201
+
202
+ .quote {
203
+ border-left: 0.25em solid;
204
+ padding-left: 1em;
205
+ }
206
+
207
+ .kicker { font-family: var(--font-mono); font-size: 0.75em; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }
208
+ .speaker { font-size: 1.05em; font-weight: 700; margin-top: 1.8em; }
209
+ .speaker + .speaker { margin-top: 0.3em; }
210
+ .contributor { font-size: 0.85em; }
211
+ .contributor-label { font-family: var(--font-mono); font-size: 0.7em; font-weight: 800; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.75; }
212
+ .speaker-links { font-weight: 400; font-size: 0.8em; }
213
+ .speaker-links a { margin-right: 0.6em; }
214
+ .speaker-links svg { height: 0.9em; width: auto; vertical-align: middle; }
215
+
216
+ footer {
217
+ position: absolute;
218
+ bottom: 1em;
219
+ left: var(--section-padding-h);
220
+ font-size: 0.6em;
221
+ text-align: left;
222
+ background: var(--footer-bg);
223
+ color: var(--footer-color);
224
+ }
225
+ .slide-num {
226
+ position: absolute;
227
+ bottom: 1em;
228
+ right: var(--section-padding-h);
229
+ font-size: 0.6em;
230
+ text-align: right;
231
+ }
232
+
233
+ /* ---- theme ---- */
234
+ THEME_CSS
235
+
236
+ LOGO_CSS
237
+
238
+ .mermaid, .seaborn-plot, .pdg-plot {
239
+ max-width: 100%;
240
+ max-height: 100%;
241
+ overflow: hidden;
242
+ text-align: center;
243
+ }
244
+ .mermaid svg, .seaborn-plot svg, .pdg-plot svg {
245
+ max-width: 100%;
246
+ height: auto;
247
+ }
248
+
249
+ /* ---- ::: card {blueprint}: engineering-drawing panel for diagrams ----
250
+ Theme-independent on purpose (same blue on light and dark slides); a
251
+ ```pdg fence inside it renders with the matching blueprint palette
252
+ (render/dot.py). Grid = minor 0.6em + major 3em lines over the blue. */
253
+ .card.blueprint {
254
+ background:
255
+ linear-gradient(rgba(143,211,255,0.16) 1px, transparent 1px) 0 0 / 3em 3em,
256
+ linear-gradient(90deg, rgba(143,211,255,0.16) 1px, transparent 1px) 0 0 / 3em 3em,
257
+ linear-gradient(rgba(143,211,255,0.07) 1px, transparent 1px) 0 0 / 0.6em 0.6em,
258
+ linear-gradient(90deg, rgba(143,211,255,0.07) 1px, transparent 1px) 0 0 / 0.6em 0.6em,
259
+ linear-gradient(160deg, #123f78 0%, #0b2f5c 100%);
260
+ border: 1px solid #8fd3ff;
261
+ color: #eaf4ff;
262
+ padding: 0.6em 0.8em;
263
+ }
264
+ .card.blueprint h3 { color: #8fd3ff; }
265
+ .card.blueprint h3::after { border-bottom-color: #8fd3ff; }
266
+ .card.blueprint p { color: #eaf4ff; }
267
+
268
+ /* ---- predefined slide layouts ---- */
269
+ .layout-cols {
270
+ display: grid;
271
+ grid-template-columns: 1fr 1fr;
272
+ gap: var(--gap);
273
+ align-items: start;
274
+ height: 100%;
275
+ }
276
+
277
+ .layout-image-right .col-right,
278
+ .layout-image-left .col-left {
279
+ height: 100%;
280
+ justify-content: center;
281
+ overflow: hidden;
282
+ }
283
+
284
+ .col-right img, .col-left img {
285
+ width: 100%;
286
+ height: 100%;
287
+ object-fit: contain;
288
+ object-position: center;
289
+ }
290
+ .layout-cols img { max-height: none; }
291
+
292
+ .result-full {
293
+ margin-top: 1em;
294
+ width: 100%;
295
+ }
296
+ .result-full img {
297
+ max-height: IMG_MAX_Hpx;
298
+ display: block;
299
+ margin-left: auto;
300
+ margin-right: auto;
301
+ }
302
+
303
+ .col-left, .col-right {
304
+ height: 100%;
305
+ display: flex;
306
+ flex-direction: column;
307
+ justify-content: center;
308
+ min-width: 0;
309
+ overflow-wrap: break-word;
310
+ }
311
+
312
+ .compare {
313
+ grid-template-columns: 1fr auto 1fr !important;
314
+ align-items: center;
315
+ height: 80%;
316
+ margin-bottom: 0;
317
+ }
318
+ .compare .col-left,
319
+ .compare .col-right {
320
+ justify-content: center;
321
+ }
322
+ .card-arrow {
323
+ font-size: 2em;
324
+ padding: 0 0.3em;
325
+ }
326
+ .notes {
327
+ border-radius: var(--radius);
328
+ padding: 1em;
329
+ margin-top: 0.5em;
330
+ font-style: italic;
331
+ }
332
+ .notes.tag-green { background: var(--tag-green-bg); border-left: 0.25em solid var(--tag-green-border); }
333
+ .notes.tag-yellow { background: var(--tag-yellow-bg); border-left: 0.25em solid var(--tag-yellow-border); }
334
+ .notes.tag-red { background: var(--tag-red-bg); border-left: 0.25em solid var(--tag-red-border); }
335
+
336
+ .note-green { border-left-color: var(--tag-green-border); background: var(--tag-green-bg); }
337
+ .note-yellow { border-left-color: var(--tag-yellow-border); background: var(--tag-yellow-bg); }
338
+ .note-red { border-left-color: var(--tag-red-border); background: var(--tag-red-bg); }
339
+
340
+ .tag-compare {
341
+ flex: 1;
342
+ border: 1px solid var(--color-border);
343
+ margin: 0.1em;
344
+ height: 100%;
345
+ min-height: 0;
346
+ }
347
+
348
+ /* ---- ecosystem layout ---- */
349
+ .layout-ecosystem h3 { font-size: 0.7em; margin-bottom: 0.1em; }
350
+ .layout-ecosystem h3::after { display: none; }
351
+ .layout-ecosystem .card { padding: 0.3em 0.5em; margin-bottom: 0.4em; }
352
+ .layout-ecosystem .card p { margin: 0; line-height: 1.2; display: inline; }
353
+ .layout-ecosystem .card.metric p { display: block; }
354
+ .layout-ecosystem .card.metric {
355
+ padding: 0.2em 0.15em;
356
+ display: flex;
357
+ flex-direction: column;
358
+ justify-content: center;
359
+ }
360
+ .layout-ecosystem .card.metric h3 { font-size: 1.3em; margin-bottom: 0.05em; }
361
+ .layout-ecosystem .card.metric p { font-size: 0.6em; }
362
+ .layout-ecosystem .card img {
363
+ height: 0.9em;
364
+ width: 2.5em;
365
+ object-fit: contain;
366
+ display: inline-block;
367
+ margin: 0.08em 0.2em;
368
+ vertical-align: middle;
369
+ }
370
+ .layout-ecosystem .grid { margin: 0; gap: 0.4em; }
371
+ .layout-ecosystem .card.metric h3 {
372
+ font-size: 1.5em;
373
+ }
374
+
375
+ /* ---- timeline layouts ---- */
376
+ /* ::: grid {cols=1} of ::: card blocks, flex (not CSS grid) so any item
377
+ count distributes naturally. The spine spans a fixed length (full
378
+ available height/width); justify-content: space-evenly auto-spaces
379
+ however many milestones there are along it -- no manually-tuned
380
+ gap/margin per item count. Items alternate sides of the spine -- the
381
+ connector stub's direction (left card <- stub -> right card,
382
+ vertical; above <- stub -> below, horizontal) flips every item, the
383
+ literal "line going up and down". {focus} on a card scales it up.
384
+ {label="Q1"} on a card prints a short name directly on the spine,
385
+ next to that card's own milestone dot. */
386
+ .layout-timeline-vertical .grid,
387
+ .layout-timeline-horizontal .grid {
388
+ display: flex;
389
+ position: relative;
390
+ }
391
+ /* grid fills the remaining body height via flex:1, not height:100% --
392
+ weasyprint resolves the percentage against more than the leftover
393
+ space below the heading, pushing the whole grid onto a new page
394
+ (broke --pdf/--odp: cards landed on an extra page). */
395
+ .layout-timeline-vertical .slide-body {
396
+ display: flex;
397
+ flex-direction: column;
398
+ padding-bottom: 0; /* the spine runs to the section padding, not 1.8em
399
+ short of it (sat too high, empty band below) */
400
+ }
401
+ .layout-timeline-vertical .grid {
402
+ flex-direction: column;
403
+ justify-content: space-evenly;
404
+ flex: 1;
405
+ min-height: 0;
406
+ margin: 0.8em 0 0; /* clear the heading: the first card touched it */
407
+ padding: 0 2.5em;
408
+ }
409
+ .layout-timeline-vertical .grid::before {
410
+ content: "";
411
+ position: absolute;
412
+ left: 50%;
413
+ top: 0;
414
+ bottom: 0;
415
+ width: 0.12em;
416
+ background: var(--color-accent-primary);
417
+ opacity: 0.4;
418
+ transform: translateX(-50%);
419
+ }
420
+ .layout-timeline-horizontal .grid {
421
+ flex-direction: row;
422
+ justify-content: space-evenly;
423
+ align-items: flex-start;
424
+ margin-top: 8em; /* reserves room for odd cards shifted above the spine */
425
+ }
426
+ .layout-timeline-horizontal .grid::before {
427
+ content: "";
428
+ position: absolute;
429
+ top: 0;
430
+ left: 0;
431
+ right: 0;
432
+ height: 0.12em;
433
+ background: var(--color-accent-primary);
434
+ opacity: 0.4;
435
+ }
436
+ .layout-timeline-vertical .card {
437
+ position: relative;
438
+ width: calc(50% - 2.5em); /* padding (2.5em) on both sides makes the
439
+ card-to-spine gap exactly 2.5em either way */
440
+ padding: 0.6em 1em;
441
+ }
442
+ .layout-timeline-vertical .card:nth-child(odd) { margin-right: auto; }
443
+ .layout-timeline-vertical .card:nth-child(even) { margin-left: auto; }
444
+ /* consecutive cards sit on opposite sides of the spine, so they may
445
+ overlap vertically: 4 cards stacked end to end overflowed the slide.
446
+ Card i and i+2 (same side) still clear each other while the card
447
+ between them is taller than 3em. */
448
+ .layout-timeline-vertical .card + .card { margin-top: -1.5em; }
449
+ .layout-timeline-horizontal .card {
450
+ position: relative;
451
+ flex: 0 1 11em; /* basis 11em, may shrink: few milestones leave free
452
+ space for space-evenly; 5-6 shrink to fit the
453
+ width instead of overflowing the slide */
454
+ min-width: 0;
455
+ height: 6em; /* fixed so the stub below reliably reaches the spine --
456
+ keep horizontal-timeline card text brief, it won't
457
+ clip (no overflow:hidden -- that would also clip the
458
+ card's own connector dot/stub below) */
459
+ margin-top: 2em; /* stub gap below the spine */
460
+ }
461
+ .layout-timeline-horizontal .card:nth-child(odd) {
462
+ margin-top: -8em; /* 6em card + 2em stub, shifted above the spine */
463
+ }
464
+ /* connector dot on the spine */
465
+ .layout-timeline-vertical .card::before,
466
+ .layout-timeline-horizontal .card::before {
467
+ content: "";
468
+ position: absolute;
469
+ width: 0.6em;
470
+ height: 0.6em;
471
+ border-radius: 50%;
472
+ background: var(--color-accent-primary);
473
+ border: 0.15em solid var(--color-background);
474
+ z-index: 1;
475
+ }
476
+ .layout-timeline-vertical .card:nth-child(odd)::before { right: -2.8em; top: 1em; }
477
+ .layout-timeline-vertical .card:nth-child(even)::before { left: -2.8em; top: 1em; }
478
+ .layout-timeline-horizontal .card::before { left: 50%; transform: translateX(-50%); }
479
+ .layout-timeline-horizontal .card:nth-child(even)::before { top: -2.3em; }
480
+ .layout-timeline-horizontal .card:nth-child(odd)::before { bottom: -2.3em; }
481
+ /* connector stub -- the alternating line itself, not just the dot */
482
+ .layout-timeline-vertical .card::after {
483
+ content: "";
484
+ position: absolute;
485
+ top: 1.3em;
486
+ height: 0.08em;
487
+ width: 2.5em;
488
+ background: var(--color-accent-primary);
489
+ opacity: 0.5;
490
+ }
491
+ .layout-timeline-vertical .card:nth-child(odd)::after { right: -2.5em; }
492
+ .layout-timeline-vertical .card:nth-child(even)::after { left: -2.5em; }
493
+ .layout-timeline-horizontal .card::after {
494
+ content: "";
495
+ position: absolute;
496
+ left: 50%;
497
+ width: 0.08em;
498
+ height: 2em;
499
+ background: var(--color-accent-primary);
500
+ opacity: 0.5;
501
+ transform: translateX(-50%);
502
+ }
503
+ .layout-timeline-horizontal .card:nth-child(even)::after { top: -2em; }
504
+ .layout-timeline-horizontal .card:nth-child(odd)::after { bottom: -2em; }
505
+ /* {label="..."}: short name printed directly on the spine, next to
506
+ this card's own dot -- distinct from the card's fuller ### header */
507
+ .timeline-label {
508
+ position: absolute;
509
+ font-size: 0.6em;
510
+ font-weight: 700;
511
+ color: var(--color-accent-primary);
512
+ white-space: nowrap;
513
+ z-index: 2;
514
+ }
515
+ /* beside the dot, past the spine, vertically centred on it. Offsets are
516
+ in the label's own 0.6em font: 5.5em = 3.3 card-em = dot edge + gap;
517
+ top 1.8em centres it on the dot (top 1em, 0.9em tall). Was above the
518
+ dot, touching the spine. */
519
+ .layout-timeline-vertical .card:nth-child(odd) .timeline-label {
520
+ left: 100%;
521
+ margin-left: 5.5em;
522
+ top: 1.8em;
523
+ }
524
+ .layout-timeline-vertical .card:nth-child(even) .timeline-label {
525
+ right: 100%;
526
+ margin-right: 5.5em;
527
+ top: 1.8em;
528
+ }
529
+ /* beside the dot: centre on it, then shift right. margin-left, not
530
+ translate(calc(-50% + Xem)) -- weasyprint crashes on calc() inside
531
+ transform (AttributeError in its cascade), breaking --pdf/--odp. */
532
+ .layout-timeline-horizontal .card:nth-child(even) .timeline-label {
533
+ left: 50%;
534
+ top: -2.3em;
535
+ margin-left: 1.4em;
536
+ transform: translateX(-50%);
537
+ }
538
+ .layout-timeline-horizontal .card:nth-child(odd) .timeline-label {
539
+ left: 50%;
540
+ bottom: -2.3em;
541
+ margin-left: 1.4em;
542
+ transform: translateX(-50%);
543
+ }
544
+ /* {focus}: bigger, highlighted with the theme's own card colours (accent
545
+ border + accent background) -- not box-shadow, which weasyprint
546
+ ignores, so the highlight vanished in PDF and ODP. Size via
547
+ font-size/padding, not transform: scale(), which would drag the card's
548
+ own ::before/::after connector (positioned in em relative to the card)
549
+ off the spine with it. Vertical gets the font-size bump too;
550
+ horizontal's height/margin are all in em relative to its own
551
+ font-size, so bumping it there would throw off the exact
552
+ spine-alignment math -- padding only. */
553
+ .layout-timeline-vertical .card.focus,
554
+ .layout-timeline-horizontal .card.focus {
555
+ border: 2px solid var(--color-accent-primary);
556
+ background: var(--color-accent-bg);
557
+ z-index: 2;
558
+ }
559
+ .layout-timeline-horizontal .card.focus {
560
+ padding: 1.3em;
561
+ }
562
+ .layout-timeline-vertical .card.focus {
563
+ padding: 0.8em 1.2em;
564
+ font-size: 1.1em;
565
+ }
566
+ .layout-timeline-horizontal .slide-body {
567
+ display: flex;
568
+ flex-direction: column;
569
+ justify-content: center;
570
+ }
571
+
572
+ /* ---- slide transitions ---- */
573
+ section[data-transition]:not([data-transition="none"]).active > .slide-body {
574
+ animation-duration: .4s;
575
+ animation-fill-mode: both;
576
+ }
577
+
578
+ section[data-transition="fade"].active > .slide-body { animation-name: sldr-fade; }
579
+ section[data-transition="slide"].active > .slide-body { animation-name: sldr-slide; }
580
+ section[data-transition="zoom"].active > .slide-body { animation-name: sldr-zoom; }
581
+ section[data-transition="wipe"].active > .slide-body { animation-name: sldr-wipe; }
582
+
583
+ @keyframes sldr-fade {
584
+ from { opacity: 0; }
585
+ }
586
+ @keyframes sldr-slide {
587
+ from { opacity: 0; translate: 3em 0; }
588
+ }
589
+ @keyframes sldr-zoom {
590
+ from { opacity: 0; scale: .85; }
591
+ }
592
+ @keyframes sldr-wipe {
593
+ from { clip-path: inset(0 100% 0 0); }
594
+ }
595
+
596
+ /* ---- outgoing slide crossfade ---- */
597
+ section[data-transition]:not([data-transition="none"]).outgoing {
598
+ display: flex !important;
599
+ z-index: -1;
600
+ }
601
+
602
+ /* ---- screen mode ---- */
603
+ @media screen {
604
+ html, body {
605
+ width: 100vw;
606
+ height: 100vh;
607
+ overflow: hidden;
608
+ background: #111;
609
+ }
610
+ body > section {
611
+ display: none !important;
612
+ position: absolute;
613
+ top: 50%;
614
+ left: 50%;
615
+ width: SLIDE_Wpx;
616
+ height: SLIDE_Hpx;
617
+ transform-origin: center center;
618
+ transform: translate(-50%, -50%) scale(var(--s));
619
+ overflow: hidden !important;
620
+ }
621
+ body > section.active {
622
+ display: flex !important;
623
+ }
624
+ }
625
+
626
+ .layout-title {
627
+ display: flex;
628
+ flex-direction: column;
629
+ justify-content: center;
630
+ padding-top: 3.2em;
631
+ }
632
+ .layout-title h1 {
633
+ font-size: var(--title-h1-size, 3.5em);
634
+ max-width: 62%;
635
+ }
636
+ .layout-title .subtitle {
637
+ margin-bottom: 0.3em;
638
+ font-size: var(--title-subtitle-size, 1.8em);
639
+ max-width: 62%;
640
+ }
641
+ .layout-title .side-image {
642
+ position: absolute;
643
+ right: 8%;
644
+ bottom: 22%;
645
+ max-width: 26%;
646
+ z-index: 1;
647
+ }
648
+ .layout-title .side-image img {
649
+ max-width: 100%;
650
+ max-height: 14em;
651
+ object-fit: contain;
652
+ display: block;
653
+ }
654
+
655
+ /* ---- print mode ---- */
656
+ @media print {
657
+ body > section {
658
+ position: relative !important;
659
+ transform: none !important;
660
+ width: SLIDE_Wpx;
661
+ height: SLIDE_Hpx;
662
+ overflow: hidden;
663
+ page-break-before: always;
664
+ break-before: page;
665
+ }
666
+ body > section:first-child { page-break-before: avoid; }
667
+ body > section:last-child { page-break-after: avoid; }
668
+ #decktile-nav, #presenter-panel { display: none !important; }
669
+ .slide-body {
670
+ flex: 1;
671
+ min-height: auto;
672
+ padding-bottom: 1.8em;
673
+ overflow: hidden;
674
+ }
675
+ pre {
676
+ max-height: none;
677
+ overflow: visible;
678
+ }
679
+ /* video slides: single big screenshot centered. The figure is
680
+ absolutely positioned: weasyprint collapses height:100% through the
681
+ .slide-body flex layout, and a flowed figure would spill to a second
682
+ page. Absolute pinning makes the img size resolvable and keeps it
683
+ off the page flow. */
684
+ figure.video {
685
+ display: block;
686
+ position: absolute;
687
+ top: 0;
688
+ bottom: 0;
689
+ left: 0;
690
+ right: 0;
691
+ }
692
+ figure.video video { display: none; }
693
+ figure.video img.video-print {
694
+ display: block;
695
+ max-height: none;
696
+ height: 100%;
697
+ width: 100%;
698
+ object-fit: contain;
699
+ }
700
+ }
701
+
702
+ @page {
703
+ size: SLIDE_Wpx SLIDE_Hpx;
704
+ margin: 0;
705
+ }
706
+
707
+ /* ---- nav bar (screen only) ---- */
708
+ #decktile-nav { display: none; }
709
+ @media screen {
710
+ #decktile-nav {
711
+ display: flex;
712
+ position: fixed;
713
+ bottom: 0;
714
+ left: 0;
715
+ right: 0;
716
+ height: 27pt;
717
+ align-items: center;
718
+ justify-content: center;
719
+ gap: 12pt;
720
+ background: rgba(0,0,0,0.7);
721
+ color: #aaa;
722
+ font-family: system-ui, sans-serif;
723
+ font-size: 10pt;
724
+ z-index: 1000;
725
+ user-select: none;
726
+ }
727
+ #decktile-nav button {
728
+ background: none;
729
+ border: 1px solid #555;
730
+ color: #ccc;
731
+ padding: 2pt 9pt;
732
+ border-radius: 3pt;
733
+ cursor: pointer;
734
+ font-size: 10pt;
735
+ }
736
+ #decktile-nav button:hover { background: rgba(255,255,255,0.1); color: #fff; }
737
+ #decktile-nav button:disabled { opacity: 0.3; cursor: default; }
738
+ #decktile-nav span { min-width: 45pt; text-align: center; }
739
+ #decktile-nav.hidden { opacity: 0; pointer-events: none; }
740
+ #decktile-nav { transition: opacity 0.4s; }
741
+ }