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,371 @@
1
+ var isPresenter = window.name === 'decktile-presenter';
2
+ if (isPresenter) document.documentElement.classList.add('presenter');
3
+
4
+ var slideNotes = [{% for slide in slides %}{{ slide.notes|tojson }}{% if not loop.last %},{% endif %}{% endfor %}];
5
+
6
+ var KEYS_FWD = ['ArrowRight', 'ArrowDown', 'PageDown', ' '];
7
+ var KEYS_BACK = ['ArrowLeft', 'ArrowUp', 'PageUp', 'Backspace'];
8
+ var autoplaySpeed = {{ autoplay }};
9
+ var _autoAdvancing = false;
10
+
11
+ // ===== VIDEO SYNC (shared) =====
12
+ // Presenter and main windows are separate documents (window.open), each
13
+ // with its own independent <video> element for the same slide -- playing
14
+ // one never touched the other. Wire play/pause/seek to broadcast over
15
+ // the same BroadcastChannel already used for slide-position sync, keyed
16
+ // by the element's own data-decktile-id so the remote window finds its
17
+ // matching (separate DOM) video.
18
+ function _wireVideoSync(scopeSelector, bc) {
19
+ var figs = document.querySelectorAll(scopeSelector + " figure.video[data-decktile-id]");
20
+ figs.forEach(function(fig) {
21
+ var vid = fig.querySelector("video");
22
+ if (!vid) return;
23
+ var id = fig.getAttribute("data-decktile-id");
24
+ var applying = false;
25
+ vid.addEventListener("play", function() {
26
+ if (!applying) bc.postMessage({ video: { id: id, action: "play", time: vid.currentTime } });
27
+ });
28
+ vid.addEventListener("pause", function() {
29
+ if (!applying) bc.postMessage({ video: { id: id, action: "pause", time: vid.currentTime } });
30
+ });
31
+ vid.addEventListener("seeked", function() {
32
+ if (!applying) bc.postMessage({ video: { id: id, action: "seek", time: vid.currentTime } });
33
+ });
34
+ vid._decktileApply = function(action, time) {
35
+ applying = true;
36
+ if (typeof time === "number" && Math.abs(vid.currentTime - time) > 0.3) vid.currentTime = time;
37
+ if (action === "play") vid.play().catch(function() {});
38
+ else if (action === "pause") vid.pause();
39
+ applying = false;
40
+ };
41
+ });
42
+ }
43
+
44
+ function _applyRemoteVideo(scopeSelector, video) {
45
+ var fig = document.querySelector(scopeSelector + ' figure.video[data-decktile-id="' + video.id + '"]');
46
+ var vid = fig && fig.querySelector("video");
47
+ if (vid && vid._decktileApply) vid._decktileApply(video.action, video.time);
48
+ }
49
+
50
+ if (isPresenter) {
51
+ // ===== PRESENTER MODE =====
52
+ var ms = document.querySelectorAll("#pres-main section");
53
+ var ns = document.querySelectorAll("#pres-next section");
54
+ var nd = document.getElementById("pres-notes-text");
55
+ var current = 0;
56
+ var autoplayRunning = false;
57
+ var autoplayTimer = null;
58
+ var autoplayBtn = document.getElementById('pres-autoplay');
59
+ var speedValEl = document.getElementById('pres-speed-val');
60
+ var speedDecBtn = document.getElementById('pres-speed-dec');
61
+ var speedIncBtn = document.getElementById('pres-speed-inc');
62
+
63
+ function _rawShow(n) {
64
+ if (n < 0 || n >= ms.length) return;
65
+ for (var i = 0; i < ms.length; i++) ms[i].classList.toggle("active", i === n);
66
+ for (var i = 0; i < ns.length; i++) ns[i].classList.toggle("active", i === n + 1);
67
+ nd.textContent = slideNotes[n] || "No notes";
68
+ current = n;
69
+ }
70
+
71
+ function show(n) {
72
+ if (!_autoAdvancing) stopAutoplay();
73
+ _rawShow(n);
74
+ bc.postMessage({ slide: n });
75
+ }
76
+
77
+ function advance(delta) {
78
+ var n = current + delta;
79
+ if (n < 0 || n >= ms.length) return;
80
+ show(n);
81
+ }
82
+
83
+ function startAutoplay() {
84
+ if (autoplaySpeed <= 0) return;
85
+ autoplayRunning = true;
86
+ if (autoplayBtn) autoplayBtn.textContent = '⏸';
87
+ clearInterval(autoplayTimer);
88
+ autoplayTimer = setInterval(function() { _autoAdvance(); }, autoplaySpeed * 1000);
89
+ }
90
+
91
+ function setSpeed(v) {
92
+ autoplaySpeed = Math.max(1, v);
93
+ if (speedValEl) speedValEl.textContent = autoplaySpeed + 's';
94
+ if (autoplayRunning) startAutoplay();
95
+ bc.postMessage({ command: 'autoplay-speed', value: autoplaySpeed });
96
+ }
97
+
98
+ function stopAutoplay() {
99
+ autoplayRunning = false;
100
+ if (autoplayBtn) autoplayBtn.textContent = '⏯';
101
+ clearInterval(autoplayTimer);
102
+ }
103
+
104
+ function toggleAutoplay() {
105
+ if (autoplayRunning) { stopAutoplay(); bc.postMessage({ command: 'autoplay-stop' }); }
106
+ else { startAutoplay(); bc.postMessage({ command: 'autoplay-start' }); }
107
+ }
108
+
109
+ function _autoAdvance() {
110
+ if (current === ms.length - 1) {
111
+ stopAutoplay();
112
+ document.body.classList.add('autoplay-looping');
113
+ setTimeout(function() {
114
+ _autoAdvancing = true;
115
+ show(0);
116
+ _autoAdvancing = false;
117
+ document.body.classList.remove('autoplay-looping');
118
+ startAutoplay();
119
+ }, 400);
120
+ return;
121
+ }
122
+ _autoAdvancing = true;
123
+ advance(1);
124
+ _autoAdvancing = false;
125
+ }
126
+
127
+ if (autoplayBtn) autoplayBtn.addEventListener('click', toggleAutoplay);
128
+ if (speedDecBtn) speedDecBtn.addEventListener('click', function() { setSpeed(autoplaySpeed - 1); });
129
+ if (speedIncBtn) speedIncBtn.addEventListener('click', function() { setSpeed(autoplaySpeed + 1); });
130
+
131
+ var bc = new BroadcastChannel('decktile-' + document.title);
132
+ bc.onmessage = function(e) {
133
+ if (e.data.slide !== undefined && e.data.slide !== current) { _rawShow(e.data.slide); }
134
+ if (e.data.command === 'autoplay-start') { startAutoplay(); }
135
+ if (e.data.command === 'autoplay-stop') { stopAutoplay(); }
136
+ if (e.data.command === 'autoplay-speed') {
137
+ autoplaySpeed = e.data.value;
138
+ if (speedValEl) speedValEl.textContent = autoplaySpeed + 's';
139
+ if (autoplayRunning) startAutoplay();
140
+ }
141
+ if (e.data.video) { _applyRemoteVideo('#pres-main', e.data.video); }
142
+ };
143
+ _wireVideoSync('#pres-main', bc);
144
+ show(0);
145
+
146
+ document.getElementById('pres-main').addEventListener('click', function() { advance(1); });
147
+ document.getElementById('pres-main').addEventListener('contextmenu', function(e) {
148
+ e.preventDefault(); advance(-1);
149
+ });
150
+
151
+ var wheelCooldown = 0;
152
+ document.getElementById('pres-main').addEventListener('wheel', function(e) {
153
+ if (Math.abs(e.deltaY) < 10) return;
154
+ var now = Date.now();
155
+ if (now - wheelCooldown < 250) return;
156
+ wheelCooldown = now;
157
+ advance(e.deltaY > 0 ? 1 : -1);
158
+ }, { passive: true });
159
+
160
+ document.addEventListener('keydown', function(e) {
161
+ if (KEYS_FWD.includes(e.key)) {
162
+ e.preventDefault(); advance(1);
163
+ } else if (KEYS_BACK.includes(e.key)) {
164
+ e.preventDefault(); advance(-1);
165
+ } else if (e.key === 'Home') {
166
+ e.preventDefault(); show(0);
167
+ } else if (e.key === 'End') {
168
+ e.preventDefault(); show(ms.length - 1);
169
+ } else if (e.key === 'q') {
170
+ window.close();
171
+ }
172
+ });
173
+
174
+ } else {
175
+ // ===== MAIN VIEW MODE =====
176
+ var slides = document.querySelectorAll('body > section');
177
+ var total = slides.length;
178
+ if (total === 0) throw new Error('no slides found');
179
+ var current = 0;
180
+ var counter = document.getElementById('decktile-counter');
181
+ var prevBtn = document.getElementById('decktile-prev');
182
+ var nextBtn = document.getElementById('decktile-next');
183
+ var presenterWindow = null;
184
+ var autoplayRunning = false;
185
+ var autoplayTimer = null;
186
+ var autoplayBtn = document.getElementById('decktile-autoplay');
187
+
188
+ function setScale() {
189
+ var sw = {{ slide_w }}, sh = {{ slide_h }};
190
+ var scale = Math.min(window.innerWidth / sw, window.innerHeight / sh);
191
+ document.documentElement.style.setProperty('--s', scale);
192
+ }
193
+ setScale();
194
+ window.addEventListener('resize', setScale);
195
+
196
+ var _outTimer = 0;
197
+ function _rawShow(n) {
198
+ if (n < 0 || n >= total) return;
199
+ var prev = slides[current];
200
+ for (var i = 0; i < slides.length; i++) slides[i].classList.remove('outgoing');
201
+ clearTimeout(_outTimer);
202
+ if (prev && prev !== slides[n] && prev.hasAttribute('data-transition') && prev.getAttribute('data-transition') !== 'none') {
203
+ prev.classList.add('outgoing');
204
+ }
205
+ prev && prev.classList.remove('active');
206
+ current = n;
207
+ if (slides[current].classList.contains('pre-render')) {
208
+ slides[current].style.cssText = '';
209
+ }
210
+ slides[current].classList.add('active');
211
+ slides[current].offsetHeight;
212
+ var next = slides[current + 1];
213
+ if (next && !next.classList.contains('pre-render')) {
214
+ next.classList.add('pre-render');
215
+ next.style.cssText = 'display:flex!important;opacity:0!important;pointer-events:none!important;z-index:-2!important';
216
+ }
217
+ if (prev && prev.classList.contains('outgoing')) {
218
+ _outTimer = setTimeout(function() { prev.classList.remove('outgoing'); }, 400);
219
+ }
220
+ if (counter) counter.textContent = (current + 1) + ' / ' + total;
221
+ if (prevBtn) prevBtn.disabled = current === 0;
222
+ if (nextBtn) nextBtn.disabled = current === total - 1;
223
+ try { localStorage.setItem('decktile-slide-' + document.title, current); } catch(e) {}
224
+ }
225
+
226
+ var show = function(n) {
227
+ if (!_autoAdvancing) stopAutoplay();
228
+ _rawShow(n);
229
+ bc.postMessage({ slide: n });
230
+ };
231
+
232
+ for (var i = 0; i < slides.length; i++) {
233
+ var imgs = slides[i].querySelectorAll('img');
234
+ for (var j = 0; j < imgs.length; j++) {
235
+ var src = imgs[j].getAttribute('src');
236
+ if (src) (new Image()).src = src;
237
+ }
238
+ }
239
+
240
+ var bc = new BroadcastChannel('decktile-' + document.title);
241
+ bc.onmessage = function(e) {
242
+ if (e.data.slide !== undefined && e.data.slide !== current) { stopAutoplay(); _rawShow(e.data.slide); }
243
+ if (e.data.command === 'autoplay-start') { startAutoplay(); }
244
+ if (e.data.command === 'autoplay-stop') { stopAutoplay(); }
245
+ if (e.data.command === 'autoplay-speed') {
246
+ autoplaySpeed = e.data.value;
247
+ if (autoplayRunning) startAutoplay();
248
+ }
249
+ if (e.data.video) { _applyRemoteVideo('body > section', e.data.video); }
250
+ };
251
+ _wireVideoSync('body > section', bc);
252
+
253
+ function startAutoplay() {
254
+ if (autoplaySpeed <= 0) return;
255
+ autoplayRunning = true;
256
+ if (autoplayBtn) autoplayBtn.textContent = '⏸';
257
+ clearInterval(autoplayTimer);
258
+ autoplayTimer = setInterval(function() { _autoAdvance(); }, autoplaySpeed * 1000);
259
+ }
260
+
261
+
262
+ function stopAutoplay() {
263
+ autoplayRunning = false;
264
+ if (autoplayBtn) autoplayBtn.textContent = '⏯';
265
+ clearInterval(autoplayTimer);
266
+ }
267
+
268
+ function toggleAutoplay() {
269
+ if (autoplayRunning) { stopAutoplay(); bc.postMessage({ command: 'autoplay-stop' }); }
270
+ else { startAutoplay(); bc.postMessage({ command: 'autoplay-start' }); }
271
+ }
272
+
273
+ function _autoAdvance() {
274
+ if (current === total - 1) {
275
+ stopAutoplay();
276
+ document.body.classList.add('autoplay-looping');
277
+ setTimeout(function() {
278
+ _autoAdvancing = true;
279
+ show(0);
280
+ _autoAdvancing = false;
281
+ document.body.classList.remove('autoplay-looping');
282
+ startAutoplay();
283
+ }, 400);
284
+ return;
285
+ }
286
+ _autoAdvancing = true;
287
+ show(current + 1);
288
+ _autoAdvancing = false;
289
+ }
290
+
291
+ if (autoplayBtn) autoplayBtn.addEventListener('click', toggleAutoplay);
292
+
293
+ var stored = null;
294
+ try { stored = parseInt(localStorage.getItem('decktile-slide-' + document.title), 10); } catch(e) {}
295
+ show(isNaN(stored) ? 0 : Math.min(stored, total - 1));
296
+
297
+ if (prevBtn) prevBtn.addEventListener('click', function() { show(current - 1); });
298
+ if (nextBtn) nextBtn.addEventListener('click', function() { show(current + 1); });
299
+
300
+ var fsBtn = document.getElementById('decktile-fullscreen');
301
+ if (fsBtn) fsBtn.addEventListener('click', function() {
302
+ if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); }
303
+ else { document.exitFullscreen(); }
304
+ });
305
+
306
+ document.addEventListener('keydown', function(e) {
307
+ if (e.ctrlKey || e.metaKey) return;
308
+ if (KEYS_BACK.includes(e.key)) {
309
+ e.preventDefault(); show(current - 1);
310
+ } else if (KEYS_FWD.includes(e.key)) {
311
+ e.preventDefault(); show(current + 1);
312
+ } else if (e.key === 'Home') {
313
+ e.preventDefault(); show(0);
314
+ } else if (e.key === 'End') {
315
+ e.preventDefault(); show(total - 1);
316
+ } else if (e.key === 'f') {
317
+ if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); }
318
+ else { document.exitFullscreen(); }
319
+ } else if (e.key === 'q') {
320
+ if (presenterWindow && !presenterWindow.closed) presenterWindow.close();
321
+ } else if (e.key === 'p') {
322
+ e.preventDefault(); openPresenter();
323
+ }
324
+ });
325
+
326
+ document.addEventListener('click', function(e) {
327
+ var t = e.target;
328
+ if (t.closest('button, a, input, textarea, select, #decktile-nav, #presenter-panel')) return;
329
+ if (window.getSelection().toString()) return;
330
+ show(current + 1);
331
+ });
332
+
333
+ document.addEventListener('contextmenu', function(e) {
334
+ if (window.getSelection().toString()) return;
335
+ e.preventDefault();
336
+ show(current - 1);
337
+ });
338
+
339
+ var wheelCooldown = 0;
340
+ document.addEventListener('wheel', function(e) {
341
+ if (Math.abs(e.deltaY) < 10) return;
342
+ var now = Date.now();
343
+ if (now - wheelCooldown < 250) return;
344
+ wheelCooldown = now;
345
+ show(e.deltaY > 0 ? current + 1 : current - 1);
346
+ }, { passive: true });
347
+
348
+ var navBar = document.getElementById('decktile-nav');
349
+ var hideTimer = null;
350
+ function resetHideTimer() {
351
+ if (navBar) navBar.classList.remove('hidden');
352
+ clearTimeout(hideTimer);
353
+ if (document.fullscreenElement) {
354
+ hideTimer = setTimeout(function() { if (navBar) navBar.classList.add('hidden'); }, 1000);
355
+ }
356
+ }
357
+ document.addEventListener('fullscreenchange', function() {
358
+ if (document.fullscreenElement) { resetHideTimer(); }
359
+ else { if (navBar) navBar.classList.remove('hidden'); clearTimeout(hideTimer); }
360
+ });
361
+ document.addEventListener('mousemove', resetHideTimer);
362
+
363
+ function openPresenter() {
364
+ if (presenterWindow && !presenterWindow.closed) { presenterWindow.focus(); return; }
365
+ presenterWindow = window.open(window.location.href, 'decktile-presenter', 'width={{ slide_w }},height={{ slide_h }}');
366
+ setTimeout(function() { if (presenterWindow) presenterWindow.focus(); }, 500);
367
+ }
368
+
369
+ var presenterBtn = document.getElementById('decktile-presenter');
370
+ if (presenterBtn) presenterBtn.addEventListener('click', openPresenter);
371
+ }
@@ -0,0 +1,106 @@
1
+ <!DOCTYPE html>
2
+ <html data-theme="{{ variant }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{{ title }}</title>
7
+ <style>
8
+ {{ css }}
9
+
10
+ /* ---- presenter mode ---- */
11
+ html.presenter { overflow: hidden; }
12
+ html.presenter body > section { display: none !important; }
13
+ html.presenter #decktile-nav { display: none !important; }
14
+ html.presenter #presenter-panel {
15
+ display: grid !important;
16
+ grid-template-columns: 1fr 0.5fr;
17
+ grid-template-rows: 3fr 7fr;
18
+ height: 100vh;
19
+ background: #111;
20
+ color: #eee;
21
+ font-family: sans-serif;
22
+ font-size: 13px;
23
+ position: fixed;
24
+ top: 0; left: 0; right: 0; bottom: 0;
25
+ z-index: 2000;
26
+ }
27
+ #presenter-panel { display: none; }
28
+ #pres-main {
29
+ grid-column: 1;
30
+ grid-row: 1 / 3;
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ overflow: hidden;
35
+ }
36
+ #pres-main section { display: none !important; width: 100%; aspect-ratio: {{ slide_w }} / {{ slide_h }}; height: auto; max-height: 100%; overflow: hidden; position: relative; }
37
+ #pres-main section.active { display: block !important; zoom: 0.9; }
38
+ #pres-notes {
39
+ grid-column: 2;
40
+ grid-row: 2;
41
+ padding: 1em;
42
+ color: #aaa;
43
+ border-top: 1px solid #333;
44
+ overflow-y: auto;
45
+ white-space: pre-wrap;
46
+ font-size: 20pt;
47
+ line-height: 1.5;
48
+ }
49
+ #pres-next {
50
+ grid-column: 2;
51
+ grid-row: 1;
52
+ display: flex;
53
+ align-items: flex-start;
54
+ justify-content: center;
55
+ overflow: hidden;
56
+ border-left: 1px solid #333;
57
+ padding: 1em;
58
+ }
59
+ #pres-next section { display: none !important; width: 100%; aspect-ratio: {{ slide_w }} / {{ slide_h }}; height: auto; overflow: hidden; position: relative; }
60
+ #pres-next section.active { display: block !important; zoom: 0.3; }
61
+ /* Hide logo overlay in presenter panel */
62
+ #presenter-panel section::before { display: none !important; }
63
+ #presenter-panel section::after { display: none !important; }
64
+
65
+ /* ---- autoplay loop transition ---- */
66
+ body { transition: opacity 0.3s ease; }
67
+ body.autoplay-looping { opacity: 0; }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ {% for slide in slides %}
72
+ <section class="slide layout-{{ slide.layout }}" data-decktile-id="{{ slide.id }}"{% if slide.variant %} data-theme="{{ slide.variant }}"{% endif %}{% if slide.transition %} data-transition="{{ slide.transition }}"{% endif %}>
73
+ {{ slide.children }}
74
+ {% if slide.footer %}<footer>{{ slide.footer }}</footer>{% endif %}
75
+ {% if slide.paginate %}<span class="slide-num">{{ slide.num }}</span>{% endif %}
76
+ </section>
77
+ {% endfor %}
78
+
79
+ <div id="decktile-nav">
80
+ <button id="decktile-prev" title="Previous (Left arrow)">&larr;</button>
81
+ <span id="decktile-counter">1 / {{ slides|length }}</span>
82
+ <button id="decktile-next" title="Next (Right arrow)">&rarr;</button>
83
+ <button id="decktile-autoplay" title="Toggle autoplay">⏯</button>
84
+ <button id="decktile-fullscreen" title="Toggle fullscreen (f)">&#9974;</button>
85
+ <button id="decktile-presenter" title="Open presenter view (p)">&#9654;</button>
86
+ </div>
87
+
88
+ <div id="presenter-panel">
89
+ <div id="pres-main">
90
+ {% for slide in slides %}
91
+ <section class="slide layout-{{ slide.layout }}"{% if slide.variant %} data-theme="{{ slide.variant }}"{% endif %}>{{ slide.children }}</section>
92
+ {% endfor %}
93
+ </div>
94
+ <div id="pres-next">
95
+ {% for slide in slides %}
96
+ <section class="slide layout-{{ slide.layout }}"{% if slide.variant %} data-theme="{{ slide.variant }}"{% endif %}>{{ slide.children }}</section>
97
+ {% endfor %}
98
+ </div>
99
+ <div id="pres-notes"><button id="pres-autoplay" title="Toggle autoplay" style="font-size:18px;background:#333;border:1px solid #555;color:#eee;padding:4px 12px;cursor:pointer;margin-bottom:8px;border-radius:4px">⏯</button> <button id="pres-speed-dec" title="Faster" style="font-size:16px;background:#333;border:1px solid #555;color:#eee;padding:4px 10px;cursor:pointer;margin-bottom:8px;border-radius:4px">&minus;</button> <span id="pres-speed-val" style="display:inline-block;min-width:3em;text-align:center">{{ autoplay }}s</span> <button id="pres-speed-inc" title="Slower" style="font-size:16px;background:#333;border:1px solid #555;color:#eee;padding:4px 10px;cursor:pointer;margin-bottom:8px;border-radius:4px">+</button><br><span id="pres-notes-text"></span></div>
100
+ </div>
101
+
102
+ <script>
103
+ {% include "decktile.js" %}
104
+ </script>
105
+ </body>
106
+ </html>
@@ -0,0 +1,5 @@
1
+ """Seaborn/matplotlib style modules for decktile themes.
2
+
3
+ Each module exports a STYLE dict of matplotlib rcParams.
4
+ Named to match the CSS theme it pairs with (e.g. kcd_vietnam.css -> kcd_vietnam).
5
+ """
@@ -0,0 +1,30 @@
1
+ """Seaborn/matplotlib style for the Open Source Summit Europe theme.
2
+
3
+ Brand palette: coral #E05845, green #0DA89D, teal #0A5560,
4
+ light coral #E58570, dark navy #003841. Background #ffffff.
5
+ """
6
+
7
+ PALETTE = [
8
+ "#E05845", # coral, primary accent
9
+ "#0DA89D", # green, secondary accent
10
+ "#0A5560", # teal
11
+ "#E58570", # light coral, gradient end
12
+ "#003841", # dark navy, contrast
13
+ ]
14
+
15
+ STYLE = {
16
+ "seaborn_palette": PALETTE,
17
+ "font.family": "sans-serif",
18
+ "font.sans-serif": ["Roboto", "Arial", "Helvetica Neue", "sans-serif"],
19
+ "axes.facecolor": "#ffffff",
20
+ "axes.edgecolor": "#003841",
21
+ "axes.labelcolor": "#003841",
22
+ "axes.titlecolor": "#003841",
23
+ "text.color": "#003841",
24
+ "figure.facecolor": "#ffffff",
25
+ "xtick.color": "#4F6F73",
26
+ "ytick.color": "#4F6F73",
27
+ "grid.color": "#EDF5F4",
28
+ "patch.edgecolor": "#ffffff",
29
+ "patch.force_edgecolor": True,
30
+ }
File without changes
@@ -0,0 +1,53 @@
1
+ """Extract theme colors from CSS using tinycss2."""
2
+
3
+ import tinycss2
4
+
5
+
6
+ def parse_theme(css: str) -> dict[str, str]:
7
+ """Extract CSS custom properties into a flat dict.
8
+ Returns dict like {"--bg": "#07110c", "--ink": "#eef7f0", ...}
9
+ """
10
+ vars_: dict[str, str] = {}
11
+ rules = tinycss2.parse_stylesheet(css, skip_comments=True, skip_whitespace=True)
12
+
13
+ for rule in rules:
14
+ if rule.type != "qualified-rule":
15
+ continue
16
+
17
+ # Only process :root rules
18
+ prelude = tinycss2.serialize(rule.prelude).strip()
19
+ if prelude != ":root":
20
+ continue
21
+
22
+ # Parse declarations
23
+ decls = tinycss2.parse_declaration_list(rule.content)
24
+ for decl in decls:
25
+ if decl.type == "declaration" and decl.name.startswith("--"):
26
+ var_value = tinycss2.serialize(decl.value).strip()
27
+ vars_[decl.name] = var_value
28
+
29
+ return vars_
30
+
31
+
32
+ def to_rgb(value: str) -> tuple[int, int, int]:
33
+ """Convert a CSS color value to an (R, G, B) tuple."""
34
+ value = value.strip()
35
+ # #RRGGBB or #RGB
36
+ if value.startswith("#"):
37
+ value = value.lstrip("#")
38
+ if len(value) == 3:
39
+ value = "".join(c * 2 for c in value)
40
+ if len(value) == 6:
41
+ return (
42
+ int(value[0:2], 16),
43
+ int(value[2:4], 16),
44
+ int(value[4:6], 16),
45
+ )
46
+ # rgb(r, g, b) or rgba(r, g, b, a)
47
+ if value.startswith("rgb"):
48
+ import re
49
+ m = re.findall(r"(\d+)", value)
50
+ if len(m) >= 3:
51
+ return (int(m[0]), int(m[1]), int(m[2]))
52
+
53
+ return (0xFF, 0xFF, 0xFF)
@@ -0,0 +1,22 @@
1
+ """Theme loader -- raw CSS passthrough with variable extraction."""
2
+
3
+ import re
4
+ from dataclasses import dataclass, field
5
+
6
+
7
+ @dataclass
8
+ class Theme:
9
+ raw: str
10
+ vars: dict[str, str] = field(default_factory=dict)
11
+
12
+ def get(self, name: str) -> str:
13
+ return self.vars.get(name, "")
14
+
15
+
16
+ def load(raw_css: str) -> Theme:
17
+ """Parse raw CSS and extract CSS variables."""
18
+ theme = Theme(raw=raw_css)
19
+ # Extract --name: value; patterns
20
+ for m in re.finditer(r"(--[\w-]+)\s*:\s*([^;]+);", raw_css):
21
+ theme.vars[m.group(1)] = m.group(2).strip()
22
+ return theme