simplex-web 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.
- simplex/README.md +32 -0
- simplex/cli/README.md +13 -0
- simplex/cli/__init__.py +5 -0
- simplex/cli/commands.py +384 -0
- simplex/deck/README.md +19 -0
- simplex/deck/__init__.py +7 -0
- simplex/deck/_template/assets/.gitkeep +0 -0
- simplex/deck/_template/assets/code/.gitkeep +0 -0
- simplex/deck/_template/assets/figures/.gitkeep +0 -0
- simplex/deck/_template/deck.toml +11 -0
- simplex/deck/_template/manim.cfg +3 -0
- simplex/deck/_template/notes.md +27 -0
- simplex/deck/_template/refs.bib +12 -0
- simplex/deck/_template/slides/__init__.py +7 -0
- simplex/deck/_template/slides/intro.py +21 -0
- simplex/deck/config.py +207 -0
- simplex/deck/registry.py +110 -0
- simplex/deck/scaffold.py +86 -0
- simplex/deck/section.py +40 -0
- simplex/engine/README.md +9 -0
- simplex/render/README.md +46 -0
- simplex/render/__init__.py +1 -0
- simplex/render/html.py +132 -0
- simplex/render/pdf.py +32 -0
- simplex/render/pptx.py +32 -0
- simplex/render/reconcile.py +350 -0
- simplex/render/runner.py +116 -0
- simplex/render/thumbnail.py +374 -0
- simplex/slides/README.md +9 -0
- simplex/slides/components/README.md +9 -0
- simplex/theme/README.md +9 -0
- simplex/web/README.md +33 -0
- simplex/web/__init__.py +1 -0
- simplex/web/bibliography.py +248 -0
- simplex/web/bibtex.py +129 -0
- simplex/web/builder.py +321 -0
- simplex/web/callouts.py +134 -0
- simplex/web/citations.py +118 -0
- simplex/web/equations.py +79 -0
- simplex/web/notes.py +135 -0
- simplex/web/refs.py +60 -0
- simplex/web/sidenotes.py +76 -0
- simplex/web/site_config.py +71 -0
- simplex/web/slide_ref.py +54 -0
- simplex/web/static/.gitkeep +0 -0
- simplex/web/static/README.md +23 -0
- simplex/web/static/fonts/lato/lato-latin-400-italic.woff2 +0 -0
- simplex/web/static/fonts/lato/lato-latin-400-normal.woff2 +0 -0
- simplex/web/static/fonts/lato/lato-latin-700-italic.woff2 +0 -0
- simplex/web/static/fonts/lato/lato-latin-700-normal.woff2 +0 -0
- simplex/web/static/fonts/lato/lato-latin-900-normal.woff2 +0 -0
- simplex/web/static/fonts/merriweather/merriweather-latin-400-italic.woff2 +0 -0
- simplex/web/static/fonts/merriweather/merriweather-latin-400-normal.woff2 +0 -0
- simplex/web/static/fonts/merriweather/merriweather-latin-700-italic.woff2 +0 -0
- simplex/web/static/fonts/merriweather/merriweather-latin-700-normal.woff2 +0 -0
- simplex/web/static/fonts/merriweather/merriweather-latin-900-normal.woff2 +0 -0
- simplex/web/static/htmx.min.js +1 -0
- simplex/web/static/katex/auto-render.min.js +1 -0
- simplex/web/static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- simplex/web/static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- simplex/web/static/katex/katex.min.css +1 -0
- simplex/web/static/katex/katex.min.js +1 -0
- simplex/web/static/lucide/README.md +7 -0
- simplex/web/static/lucide/lucide.min.js +12 -0
- simplex/web/static/notes.js +68 -0
- simplex/web/static/reveal.js/reset.css +30 -0
- simplex/web/static/reveal.js/reveal.css +8 -0
- simplex/web/static/reveal.js/reveal.js +9 -0
- simplex/web/static/simplex.css +1870 -0
- simplex/web/static/tailwind.js +64 -0
- simplex/web/static/viewer.js +428 -0
- simplex/web/templates/README.md +19 -0
- simplex/web/templates/_carousel.html +117 -0
- simplex/web/templates/base.html +110 -0
- simplex/web/templates/deck.html +149 -0
- simplex/web/templates/index.html +20 -0
- simplex/web/templates/revealjs.html.j2 +374 -0
- simplex/web/templates/section.html +74 -0
- simplex/web/vendor.py +148 -0
- simplex_web-0.2.0.dist-info/METADATA +166 -0
- simplex_web-0.2.0.dist-info/RECORD +91 -0
- simplex_web-0.2.0.dist-info/WHEEL +4 -0
- simplex_web-0.2.0.dist-info/entry_points.txt +2 -0
- simplex_web-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
{% block title %}{{ deck.title }} -- {{ site.brand }}{% endblock %}
|
|
3
|
+
{% block nav_actions %}
|
|
4
|
+
{% if has_pdf %}
|
|
5
|
+
<a
|
|
6
|
+
href="{{ deck.slug }}.pdf"
|
|
7
|
+
class="site-resource-link"
|
|
8
|
+
download
|
|
9
|
+
>
|
|
10
|
+
<span class="pdf-file-icon" aria-hidden="true"><span>PDF</span></span>
|
|
11
|
+
<span>ppt</span>
|
|
12
|
+
</a>
|
|
13
|
+
{% endif %}
|
|
14
|
+
{% if has_notes_pdf %}
|
|
15
|
+
<a
|
|
16
|
+
href="notes.pdf"
|
|
17
|
+
class="site-resource-link"
|
|
18
|
+
download
|
|
19
|
+
>
|
|
20
|
+
<i data-lucide="file-text" aria-hidden="true"></i>
|
|
21
|
+
<span class="icon-fallback" aria-hidden="true">N</span>
|
|
22
|
+
<span>Notes PDF</span>
|
|
23
|
+
</a>
|
|
24
|
+
{% endif %}
|
|
25
|
+
{% endblock %}
|
|
26
|
+
{% block main_class %}deck-main{% endblock %}
|
|
27
|
+
{% block content %}
|
|
28
|
+
<section
|
|
29
|
+
class="deck-grid"
|
|
30
|
+
data-deck-slug="{{ deck.slug }}"
|
|
31
|
+
data-slide-count="{{ slide_count }}"
|
|
32
|
+
data-default-slide-number="{% if deck.web.show_slide_number %}true{% else %}false{% endif %}"
|
|
33
|
+
data-default-clock="{% if deck.web.show_clock %}true{% else %}false{% endif %}"
|
|
34
|
+
>
|
|
35
|
+
<div class="deck-viewer">
|
|
36
|
+
<div class="deck-viewer-frame">
|
|
37
|
+
<iframe
|
|
38
|
+
src="slides.html"
|
|
39
|
+
title="{{ deck.title }} presentation"
|
|
40
|
+
allowfullscreen
|
|
41
|
+
allow="autoplay; fullscreen"
|
|
42
|
+
loading="lazy"
|
|
43
|
+
class="deck-iframe"
|
|
44
|
+
></iframe>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="deck-controls" role="toolbar" aria-label="Slide controls">
|
|
47
|
+
<div class="deck-control-group" aria-label="Playback">
|
|
48
|
+
<button data-control="prev" class="deck-ctl-btn" aria-label="Previous slide" title="Previous" type="button">
|
|
49
|
+
<i data-lucide="chevron-left" aria-hidden="true"></i>
|
|
50
|
+
<span class="icon-fallback" aria-hidden="true">‹</span>
|
|
51
|
+
</button>
|
|
52
|
+
<button data-control="restart" class="deck-ctl-btn" aria-label="Restart slide" title="Restart" type="button">
|
|
53
|
+
<i data-lucide="rotate-ccw" aria-hidden="true"></i>
|
|
54
|
+
<span class="icon-fallback" aria-hidden="true">↺</span>
|
|
55
|
+
</button>
|
|
56
|
+
<button data-control="toggle-play" class="deck-ctl-btn deck-ctl-play" aria-label="Play" title="Play" data-state="paused" type="button">
|
|
57
|
+
<i data-lucide="play" class="deck-ctl-icon-play" aria-hidden="true"></i>
|
|
58
|
+
<i data-lucide="pause" class="deck-ctl-icon-pause" aria-hidden="true"></i>
|
|
59
|
+
<span class="icon-fallback deck-ctl-icon-play" aria-hidden="true">▶</span>
|
|
60
|
+
<span class="icon-fallback deck-ctl-icon-pause" aria-hidden="true">Ⅱ</span>
|
|
61
|
+
</button>
|
|
62
|
+
<button data-control="next" class="deck-ctl-btn" aria-label="Next slide" title="Next" type="button">
|
|
63
|
+
<i data-lucide="chevron-right" aria-hidden="true"></i>
|
|
64
|
+
<span class="icon-fallback" aria-hidden="true">›</span>
|
|
65
|
+
</button>
|
|
66
|
+
<button data-control="fullscreen" class="deck-ctl-btn" aria-label="Fullscreen" title="Fullscreen" type="button">
|
|
67
|
+
<i data-lucide="maximize" aria-hidden="true"></i>
|
|
68
|
+
<span class="icon-fallback" aria-hidden="true">⛶</span>
|
|
69
|
+
</button>
|
|
70
|
+
<span class="deck-counter" data-counter aria-live="polite">1 / {{ slide_count }}</span>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="deck-settings" data-settings>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="deck-settings-trigger"
|
|
76
|
+
data-settings-toggle
|
|
77
|
+
aria-expanded="false"
|
|
78
|
+
aria-haspopup="true"
|
|
79
|
+
>
|
|
80
|
+
<i data-lucide="settings" aria-hidden="true"></i>
|
|
81
|
+
<span class="icon-fallback" aria-hidden="true">⚙</span>
|
|
82
|
+
<span>Player Settings</span>
|
|
83
|
+
</button>
|
|
84
|
+
<div class="deck-settings-panel" data-settings-panel hidden>
|
|
85
|
+
<label class="deck-setting-row">
|
|
86
|
+
<span><i data-lucide="palette" aria-hidden="true"></i>Slide Color</span>
|
|
87
|
+
<input type="checkbox" data-setting="slide-color" checked />
|
|
88
|
+
</label>
|
|
89
|
+
<label class="deck-setting-row">
|
|
90
|
+
<span><i data-lucide="list-ordered" aria-hidden="true"></i>Enumeration</span>
|
|
91
|
+
<input
|
|
92
|
+
type="checkbox"
|
|
93
|
+
data-setting="slide-number"
|
|
94
|
+
{% if deck.web.show_slide_number %}checked{% endif %}
|
|
95
|
+
/>
|
|
96
|
+
</label>
|
|
97
|
+
<label class="deck-setting-row">
|
|
98
|
+
<span><i data-lucide="clock" aria-hidden="true"></i>Clock</span>
|
|
99
|
+
<input type="checkbox" data-setting="clock" {% if deck.web.show_clock %}checked{% endif %} />
|
|
100
|
+
</label>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<aside class="deck-sidebar" aria-label="Slide list">
|
|
107
|
+
<ol class="deck-slide-list" role="list">
|
|
108
|
+
{% for slide in slides %}
|
|
109
|
+
<li>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
class="deck-slide-card"
|
|
113
|
+
data-slide-target="{{ slide.index }}"
|
|
114
|
+
{% if loop.first %}aria-current="true"{% endif %}
|
|
115
|
+
>
|
|
116
|
+
<div class="deck-slide-thumb">
|
|
117
|
+
{% if slide.thumbnail %}
|
|
118
|
+
<img
|
|
119
|
+
src="{{ slide.thumbnail }}"
|
|
120
|
+
alt=""
|
|
121
|
+
loading="lazy"
|
|
122
|
+
decoding="async"
|
|
123
|
+
/>
|
|
124
|
+
{% else %}
|
|
125
|
+
<div class="deck-slide-thumb-empty" aria-hidden="true"></div>
|
|
126
|
+
{% endif %}
|
|
127
|
+
<div class="deck-slide-overlay">
|
|
128
|
+
<span class="deck-slide-title">
|
|
129
|
+
{{ slide.name or slide.scene }}
|
|
130
|
+
</span>
|
|
131
|
+
{% set duration_seconds = slide.duration_s|round(0, "ceil")|int %}
|
|
132
|
+
<span class="deck-slide-duration">
|
|
133
|
+
{{ "%d:%02d"|format(duration_seconds // 60, duration_seconds % 60) }}
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
</button>
|
|
138
|
+
</li>
|
|
139
|
+
{% endfor %}
|
|
140
|
+
</ol>
|
|
141
|
+
</aside>
|
|
142
|
+
|
|
143
|
+
{% if notes_html %}
|
|
144
|
+
<article class="deck-notes prose prose-invert max-w-none">
|
|
145
|
+
{{ notes_html|safe }}
|
|
146
|
+
</article>
|
|
147
|
+
{% endif %}
|
|
148
|
+
</section>
|
|
149
|
+
{% endblock %}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
{% block title %}{{ site.brand }} -- Decks{% endblock %}
|
|
3
|
+
{% block content %}
|
|
4
|
+
{% if not registry.sections %}
|
|
5
|
+
<p class="text-neutral-400">
|
|
6
|
+
No decks yet. Run <code class="px-1 bg-neutral-800 rounded">simplex new my-deck</code>
|
|
7
|
+
to create one.
|
|
8
|
+
</p>
|
|
9
|
+
{% else %}
|
|
10
|
+
{% if latest_section %}
|
|
11
|
+
{% set section = latest_section %}
|
|
12
|
+
{% set carousel_variant = "latest" %}
|
|
13
|
+
{% include "_carousel.html" %}
|
|
14
|
+
{% endif %}
|
|
15
|
+
{% set carousel_variant = "section" %}
|
|
16
|
+
{% for section in registry.sections %}
|
|
17
|
+
{% include "_carousel.html" %}
|
|
18
|
+
{% endfor %}
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% endblock %}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5" />
|
|
6
|
+
<title>{{ deck.title }} -- slides</title>
|
|
7
|
+
<link rel="stylesheet" href="{{ static_prefix }}/reveal.js/reset.css" />
|
|
8
|
+
<link rel="stylesheet" href="{{ static_prefix }}/reveal.js/reveal.css" />
|
|
9
|
+
<style>
|
|
10
|
+
{{ palette_css|safe }}
|
|
11
|
+
</style>
|
|
12
|
+
<style>
|
|
13
|
+
:root {
|
|
14
|
+
--simplex-stage-bg: #2b2b2b;
|
|
15
|
+
--simplex-stage-progress: rgba(255, 255, 255, 0.08);
|
|
16
|
+
}
|
|
17
|
+
:root[data-theme="light"] {
|
|
18
|
+
--simplex-stage-bg: #f3f5f7;
|
|
19
|
+
--simplex-stage-progress: rgba(21, 31, 40, 0.14);
|
|
20
|
+
}
|
|
21
|
+
html, body {
|
|
22
|
+
background: var(--simplex-stage-bg);
|
|
23
|
+
color: var(--simplex-text, #f8f8f8);
|
|
24
|
+
height: 100%;
|
|
25
|
+
width: 100%;
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
touch-action: manipulation;
|
|
30
|
+
}
|
|
31
|
+
.reveal, .reveal .slides {
|
|
32
|
+
background: var(--simplex-stage-bg);
|
|
33
|
+
width: 100% !important;
|
|
34
|
+
height: 100% !important;
|
|
35
|
+
inset: 0;
|
|
36
|
+
}
|
|
37
|
+
/* Override reveal's transform-based scaling: we want the video to fill
|
|
38
|
+
the viewer box natively. Reveal still drives slide changes; only the
|
|
39
|
+
layout math is ours. */
|
|
40
|
+
.reveal .slides {
|
|
41
|
+
top: 0 !important;
|
|
42
|
+
left: 0 !important;
|
|
43
|
+
transform: none !important;
|
|
44
|
+
zoom: 1 !important;
|
|
45
|
+
}
|
|
46
|
+
.reveal .slides > section,
|
|
47
|
+
.reveal .slides > section > section {
|
|
48
|
+
position: absolute;
|
|
49
|
+
inset: 0;
|
|
50
|
+
width: 100% !important;
|
|
51
|
+
height: 100% !important;
|
|
52
|
+
padding: 0;
|
|
53
|
+
margin: 0;
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: center;
|
|
57
|
+
background: var(--simplex-stage-bg);
|
|
58
|
+
}
|
|
59
|
+
.reveal video {
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
max-height: 100%;
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
object-fit: contain;
|
|
65
|
+
background: var(--simplex-stage-bg);
|
|
66
|
+
display: block;
|
|
67
|
+
}
|
|
68
|
+
.reveal .controls { display: none !important; }
|
|
69
|
+
.reveal .progress {
|
|
70
|
+
background: var(--simplex-stage-progress);
|
|
71
|
+
height: 3px;
|
|
72
|
+
color: var(--simplex-accent, #ffd700);
|
|
73
|
+
}
|
|
74
|
+
.reveal .progress span { background: var(--simplex-accent, #ffd700); }
|
|
75
|
+
.tap-zone {
|
|
76
|
+
position: fixed;
|
|
77
|
+
top: 0;
|
|
78
|
+
bottom: 0;
|
|
79
|
+
width: 33vw;
|
|
80
|
+
z-index: 30;
|
|
81
|
+
background: transparent;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
-webkit-tap-highlight-color: transparent;
|
|
84
|
+
}
|
|
85
|
+
.tap-zone.next { right: 0; width: 67vw; }
|
|
86
|
+
.tap-zone.prev { left: 0; }
|
|
87
|
+
@media (prefers-reduced-motion: reduce) {
|
|
88
|
+
.reveal .slides section { transition: none !important; }
|
|
89
|
+
}
|
|
90
|
+
.empty-slide {
|
|
91
|
+
font-family: var(--simplex-font-sans, ui-sans-serif, system-ui, sans-serif);
|
|
92
|
+
font-size: 1.25rem;
|
|
93
|
+
color: rgba(255,255,255,.65);
|
|
94
|
+
padding: 2rem;
|
|
95
|
+
text-align: center;
|
|
96
|
+
}
|
|
97
|
+
.simplex-chrome {
|
|
98
|
+
position: fixed;
|
|
99
|
+
z-index: 40;
|
|
100
|
+
font-family: var(--simplex-font-sans, ui-sans-serif, system-ui, sans-serif);
|
|
101
|
+
font-size: 1rem;
|
|
102
|
+
color: var(--simplex-text-muted, rgba(255,255,255,.65));
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
105
|
+
.simplex-chrome.clock { bottom: 10px; left: 10px; }
|
|
106
|
+
.simplex-chrome.slide-number { bottom: 10px; right: 10px; }
|
|
107
|
+
</style>
|
|
108
|
+
{% if deck_custom_css %}
|
|
109
|
+
<style>
|
|
110
|
+
{{ deck_custom_css|safe }}
|
|
111
|
+
</style>
|
|
112
|
+
{% endif %}
|
|
113
|
+
</head>
|
|
114
|
+
<body>
|
|
115
|
+
<div class="reveal" role="region" aria-label="{{ deck.title }} slide viewer">
|
|
116
|
+
<div class="slides">
|
|
117
|
+
{% for main in main_slides %}
|
|
118
|
+
<section
|
|
119
|
+
data-main-index="{{ main.index }}"
|
|
120
|
+
data-scene="{{ main.scene }}"
|
|
121
|
+
data-name="{{ main.name }}"
|
|
122
|
+
>
|
|
123
|
+
{% for sub in main.subsections %}
|
|
124
|
+
<section
|
|
125
|
+
data-sub-name="{{ sub.name }}"
|
|
126
|
+
{% if "loop" in sub.section_type %}data-background-video-loop{% endif %}
|
|
127
|
+
>
|
|
128
|
+
{% if sub.video_href %}
|
|
129
|
+
<video
|
|
130
|
+
data-autoplay
|
|
131
|
+
muted
|
|
132
|
+
playsinline
|
|
133
|
+
preload="metadata"
|
|
134
|
+
aria-label="{{ main.name }} - {{ sub.name }}"
|
|
135
|
+
>
|
|
136
|
+
<source src="{{ sub.video_href }}" type="video/mp4" />
|
|
137
|
+
</video>
|
|
138
|
+
{% else %}
|
|
139
|
+
<div class="empty-slide">
|
|
140
|
+
<p>{{ main.name }} - {{ sub.name }}</p>
|
|
141
|
+
<p style="font-size:.85rem;opacity:.6">no video segment available</p>
|
|
142
|
+
</div>
|
|
143
|
+
{% endif %}
|
|
144
|
+
</section>
|
|
145
|
+
{% endfor %}
|
|
146
|
+
{% if not main.subsections %}
|
|
147
|
+
<section data-sub-name="empty">
|
|
148
|
+
<div class="empty-slide">
|
|
149
|
+
<p>{{ main.name }}</p>
|
|
150
|
+
<p style="font-size:.85rem;opacity:.6">no rendered video for this slide yet</p>
|
|
151
|
+
</div>
|
|
152
|
+
</section>
|
|
153
|
+
{% endif %}
|
|
154
|
+
</section>
|
|
155
|
+
{% endfor %}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<button class="tap-zone prev" aria-hidden="true" tabindex="-1"
|
|
160
|
+
data-tap="prev"></button>
|
|
161
|
+
<button class="tap-zone next" aria-hidden="true" tabindex="-1"
|
|
162
|
+
data-tap="next"></button>
|
|
163
|
+
|
|
164
|
+
<div
|
|
165
|
+
class="simplex-chrome clock"
|
|
166
|
+
id="simplex-clock"
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
{% if not show_clock %}hidden{% endif %}
|
|
169
|
+
></div>
|
|
170
|
+
<div
|
|
171
|
+
class="simplex-chrome slide-number"
|
|
172
|
+
id="simplex-slide-number"
|
|
173
|
+
aria-hidden="true"
|
|
174
|
+
{% if not show_slide_number %}hidden{% endif %}
|
|
175
|
+
></div>
|
|
176
|
+
|
|
177
|
+
<script src="{{ static_prefix }}/reveal.js/reveal.js"></script>
|
|
178
|
+
<script>
|
|
179
|
+
(function () {
|
|
180
|
+
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
181
|
+
const isTouch = window.matchMedia('(hover: none) and (pointer: coarse)').matches;
|
|
182
|
+
const parentOrigin = (function () {
|
|
183
|
+
try { return window.parent.location.origin || '*'; } catch (_) { return '*'; }
|
|
184
|
+
})();
|
|
185
|
+
const chromeState = {
|
|
186
|
+
clock: {% if show_clock %}true{% else %}false{% endif %},
|
|
187
|
+
slideNumber: {% if show_slide_number %}true{% else %}false{% endif %},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
Reveal.initialize({
|
|
191
|
+
embedded: true,
|
|
192
|
+
controls: false,
|
|
193
|
+
controlsTutorial: false,
|
|
194
|
+
progress: true,
|
|
195
|
+
hash: false,
|
|
196
|
+
// Remap all arrow keys to flat next/prev so direction never matters,
|
|
197
|
+
// even when the slide grid has vertical sub-stops. Keys: 37 left,
|
|
198
|
+
// 38 up, 39 right, 40 down, 32 space, 33 PageUp, 34 PageDown.
|
|
199
|
+
keyboard: {
|
|
200
|
+
37: 'prev', 38: 'prev', 33: 'prev',
|
|
201
|
+
39: 'next', 40: 'next', 32: 'next', 34: 'next',
|
|
202
|
+
},
|
|
203
|
+
// Disable Reveal's built-in swipe gestures. Mobile navigation goes
|
|
204
|
+
// through the .tap-zone elements below; this keeps tap-right = next
|
|
205
|
+
// and tap-left = prev regardless of horizontal/vertical slide layout.
|
|
206
|
+
touch: false,
|
|
207
|
+
transition: reduce ? 'none' : '{{ transition }}',
|
|
208
|
+
backgroundTransition: 'none',
|
|
209
|
+
autoPlayMedia: true,
|
|
210
|
+
slideNumber: false,
|
|
211
|
+
loop: false,
|
|
212
|
+
center: false,
|
|
213
|
+
// Reveal's scale-to-fit math fights us when the video already knows
|
|
214
|
+
// its aspect; disableLayout: true means our CSS sizes every slide.
|
|
215
|
+
disableLayout: true,
|
|
216
|
+
viewDistance: 2,
|
|
217
|
+
}).then(function () {
|
|
218
|
+
function currentMainIndex() {
|
|
219
|
+
// Resolve the active 1-based main-slide index from the closest
|
|
220
|
+
// ancestor ``<section data-main-index>``. Works for both top-level
|
|
221
|
+
// mains and their vertical sub-stops -- on a sub-slide the
|
|
222
|
+
// closest() walk still lands on the surrounding main, so the
|
|
223
|
+
// sidebar stays highlighted on the right thumbnail card while the
|
|
224
|
+
// user scrubs through sub-stops.
|
|
225
|
+
const slide = Reveal.getCurrentSlide();
|
|
226
|
+
if (!slide) return 0;
|
|
227
|
+
const main = slide.closest('[data-main-index]') || slide;
|
|
228
|
+
const raw = main.getAttribute('data-main-index');
|
|
229
|
+
return raw ? parseInt(raw, 10) || 0 : 0;
|
|
230
|
+
}
|
|
231
|
+
function emit() {
|
|
232
|
+
const idx = Reveal.getIndices();
|
|
233
|
+
const total = {{ main_slide_count }};
|
|
234
|
+
renderSlideNumber();
|
|
235
|
+
try {
|
|
236
|
+
window.parent.postMessage(
|
|
237
|
+
{
|
|
238
|
+
type: 'simplex.slide',
|
|
239
|
+
idx: currentMainIndex(),
|
|
240
|
+
sub: idx.v || 0,
|
|
241
|
+
total: total,
|
|
242
|
+
},
|
|
243
|
+
parentOrigin
|
|
244
|
+
);
|
|
245
|
+
} catch (_) { /* parent gone or different origin */ }
|
|
246
|
+
}
|
|
247
|
+
function renderSlideNumber() {
|
|
248
|
+
const el = document.getElementById('simplex-slide-number');
|
|
249
|
+
if (!el) return;
|
|
250
|
+
el.textContent = currentMainIndex() + ' / ' + {{ main_slide_count }};
|
|
251
|
+
}
|
|
252
|
+
function applyChrome() {
|
|
253
|
+
const clock = document.getElementById('simplex-clock');
|
|
254
|
+
const slideNumber = document.getElementById('simplex-slide-number');
|
|
255
|
+
if (clock) clock.hidden = !chromeState.clock;
|
|
256
|
+
if (slideNumber) slideNumber.hidden = !chromeState.slideNumber;
|
|
257
|
+
renderSlideNumber();
|
|
258
|
+
}
|
|
259
|
+
Reveal.on('slidechanged', emit);
|
|
260
|
+
Reveal.on('ready', emit);
|
|
261
|
+
applyChrome();
|
|
262
|
+
|
|
263
|
+
window.addEventListener('message', function (e) {
|
|
264
|
+
const data = e.data || {};
|
|
265
|
+
if (typeof data !== 'object') return;
|
|
266
|
+
if (data.type === 'simplex.goto' && Number.isInteger(data.idx)) {
|
|
267
|
+
// ``data.idx`` is the 1-based main-slide number from the sidebar
|
|
268
|
+
// (``data-slide-target``). Reveal's horizontal index is 0-based,
|
|
269
|
+
// so subtract one before dispatching.
|
|
270
|
+
Reveal.slide(data.idx - 1, data.sub || 0);
|
|
271
|
+
} else if (data.type === 'simplex.next') {
|
|
272
|
+
Reveal.next();
|
|
273
|
+
} else if (data.type === 'simplex.prev') {
|
|
274
|
+
Reveal.prev();
|
|
275
|
+
} else if (data.type === 'simplex.restart') {
|
|
276
|
+
const v = document.querySelector('.reveal .slides .present video');
|
|
277
|
+
if (v) {
|
|
278
|
+
try { v.currentTime = 0; } catch (_) { /* */ }
|
|
279
|
+
const p = v.play();
|
|
280
|
+
if (p && typeof p.catch === 'function') p.catch(function () {});
|
|
281
|
+
}
|
|
282
|
+
} else if (data.type === 'simplex.toggle-play') {
|
|
283
|
+
const v = document.querySelector('.reveal .slides .present video');
|
|
284
|
+
if (v) {
|
|
285
|
+
if (v.paused) {
|
|
286
|
+
if (v.ended || v.currentTime >= Math.max(0, v.duration - 0.05)) {
|
|
287
|
+
try { v.currentTime = 0; } catch (_) { /* */ }
|
|
288
|
+
}
|
|
289
|
+
const p = v.play();
|
|
290
|
+
if (p && typeof p.catch === 'function') p.catch(function () {});
|
|
291
|
+
} else {
|
|
292
|
+
v.pause();
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
window.parent.postMessage(
|
|
296
|
+
{ type: 'simplex.play-state', playing: !v.paused },
|
|
297
|
+
parentOrigin
|
|
298
|
+
);
|
|
299
|
+
} catch (_) { /* */ }
|
|
300
|
+
}
|
|
301
|
+
} else if (data.type === 'simplex.fullscreen') {
|
|
302
|
+
const el = document.documentElement;
|
|
303
|
+
const fn = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen;
|
|
304
|
+
const exit = document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen;
|
|
305
|
+
const inFs = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement;
|
|
306
|
+
try {
|
|
307
|
+
if (inFs) { if (exit) { exit.call(document); } }
|
|
308
|
+
else if (fn) { fn.call(el); }
|
|
309
|
+
} catch (_) { /* */ }
|
|
310
|
+
} else if (data.type === 'simplex.set-chrome') {
|
|
311
|
+
if (typeof data.clock === 'boolean') chromeState.clock = data.clock;
|
|
312
|
+
if (typeof data.slideNumber === 'boolean') chromeState.slideNumber = data.slideNumber;
|
|
313
|
+
applyChrome();
|
|
314
|
+
} else if (data.type === 'simplex.set-theme' && typeof data.theme === 'string') {
|
|
315
|
+
document.documentElement.dataset.theme = data.theme;
|
|
316
|
+
document.documentElement.style.colorScheme = data.theme;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
document.addEventListener('play', function (e) {
|
|
321
|
+
if (!e.target || e.target.tagName !== 'VIDEO') return;
|
|
322
|
+
try {
|
|
323
|
+
window.parent.postMessage(
|
|
324
|
+
{ type: 'simplex.play-state', playing: true }, parentOrigin
|
|
325
|
+
);
|
|
326
|
+
} catch (_) { /* */ }
|
|
327
|
+
}, true);
|
|
328
|
+
document.addEventListener('pause', function (e) {
|
|
329
|
+
if (!e.target || e.target.tagName !== 'VIDEO') return;
|
|
330
|
+
try {
|
|
331
|
+
window.parent.postMessage(
|
|
332
|
+
{ type: 'simplex.play-state', playing: false }, parentOrigin
|
|
333
|
+
);
|
|
334
|
+
} catch (_) { /* */ }
|
|
335
|
+
}, true);
|
|
336
|
+
|
|
337
|
+
if (isTouch) {
|
|
338
|
+
document.querySelectorAll('.tap-zone').forEach(function (z) {
|
|
339
|
+
z.addEventListener('click', function (ev) {
|
|
340
|
+
ev.preventDefault();
|
|
341
|
+
if (z.dataset.tap === 'next') { Reveal.next(); }
|
|
342
|
+
else { Reveal.prev(); }
|
|
343
|
+
}, { passive: false });
|
|
344
|
+
});
|
|
345
|
+
} else {
|
|
346
|
+
document.querySelectorAll('.tap-zone').forEach(function (z) {
|
|
347
|
+
z.style.display = 'none';
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Wall clock in the corner. Updates once per second; pad single
|
|
353
|
+
// digits so the layout doesn't reflow.
|
|
354
|
+
(function () {
|
|
355
|
+
const el = document.getElementById('simplex-clock');
|
|
356
|
+
if (!el) return;
|
|
357
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
358
|
+
const tick = () => {
|
|
359
|
+
const now = new Date();
|
|
360
|
+
el.textContent = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds());
|
|
361
|
+
};
|
|
362
|
+
tick();
|
|
363
|
+
setInterval(tick, 1000);
|
|
364
|
+
})();
|
|
365
|
+
|
|
366
|
+
{% if watch %}
|
|
367
|
+
// --watch mode: subscribe to SSE for live reload on render.
|
|
368
|
+
const es = new EventSource('/_simplex/events');
|
|
369
|
+
es.addEventListener('reload', () => window.location.reload());
|
|
370
|
+
{% endif %}
|
|
371
|
+
})();
|
|
372
|
+
</script>
|
|
373
|
+
</body>
|
|
374
|
+
</html>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
{% block title %}{{ section.config.title }} -- {{ site.brand }}{% endblock %}
|
|
3
|
+
{% block content %}
|
|
4
|
+
<section class="section-page-heading">
|
|
5
|
+
<div class="carousel-heading-line">
|
|
6
|
+
<h1>{{ section.config.title }}</h1>
|
|
7
|
+
</div>
|
|
8
|
+
{% if section.config.blurb %}
|
|
9
|
+
<p>{{ section.config.blurb }}</p>
|
|
10
|
+
{% endif %}
|
|
11
|
+
</section>
|
|
12
|
+
|
|
13
|
+
<ol class="section-deck-grid" role="list">
|
|
14
|
+
{% for deck in section.decks %}
|
|
15
|
+
{% set thumb = thumbs.get(deck.slug) %}
|
|
16
|
+
{% set gif = preview_gifs.get(deck.slug) %}
|
|
17
|
+
{% set created = deck_dates.get(deck.slug) %}
|
|
18
|
+
<li>
|
|
19
|
+
<a
|
|
20
|
+
href="{{ site.url('decks/' + deck.slug + '/') }}"
|
|
21
|
+
class="carousel-card"
|
|
22
|
+
aria-label="Open {{ deck.title }}"
|
|
23
|
+
>
|
|
24
|
+
<div class="carousel-thumb">
|
|
25
|
+
{% if thumb %}
|
|
26
|
+
<img
|
|
27
|
+
src="{{ site.url('decks/' + deck.slug + '/' + thumb) }}"
|
|
28
|
+
{% if gif %}data-preview-gif="{{ site.url('decks/' + deck.slug + '/' + gif) }}"{% endif %}
|
|
29
|
+
alt=""
|
|
30
|
+
loading="lazy"
|
|
31
|
+
decoding="async"
|
|
32
|
+
width="480"
|
|
33
|
+
height="270"
|
|
34
|
+
/>
|
|
35
|
+
{% else %}
|
|
36
|
+
<div class="carousel-thumb-empty" aria-hidden="true">
|
|
37
|
+
<span>{{ deck.title[:1] }}</span>
|
|
38
|
+
</div>
|
|
39
|
+
{% endif %}
|
|
40
|
+
<div class="carousel-scrim" aria-hidden="true"></div>
|
|
41
|
+
<div class="carousel-card-content">
|
|
42
|
+
<div class="carousel-card-top">
|
|
43
|
+
{% if deck.category %}
|
|
44
|
+
<span class="carousel-badge">{{ deck.category }}</span>
|
|
45
|
+
{% endif %}
|
|
46
|
+
{% if created %}
|
|
47
|
+
<span class="carousel-created">
|
|
48
|
+
<i data-lucide="calendar" aria-hidden="true"></i>
|
|
49
|
+
{{ created }}
|
|
50
|
+
</span>
|
|
51
|
+
{% endif %}
|
|
52
|
+
</div>
|
|
53
|
+
<div class="carousel-card-bottom">
|
|
54
|
+
<h2>{{ deck.title }}</h2>
|
|
55
|
+
{% if deck.summary %}
|
|
56
|
+
<p>{{ deck.summary }}</p>
|
|
57
|
+
{% endif %}
|
|
58
|
+
<div class="carousel-card-actions">
|
|
59
|
+
<span class="carousel-play">
|
|
60
|
+
<i data-lucide="play" aria-hidden="true"></i>
|
|
61
|
+
Play now
|
|
62
|
+
</span>
|
|
63
|
+
{% if deck.duration_minutes %}
|
|
64
|
+
<span class="carousel-duration">{{ deck.duration_minutes }} min</span>
|
|
65
|
+
{% endif %}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</a>
|
|
71
|
+
</li>
|
|
72
|
+
{% endfor %}
|
|
73
|
+
</ol>
|
|
74
|
+
{% endblock %}
|