lazyslides 0.2.0 → 0.2.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 (48) hide show
  1. package/README.md +25 -3
  2. package/_includes/slides/_nested-list.njk +28 -12
  3. package/_includes/slides/_section-attrs.njk +15 -0
  4. package/_includes/slides/agenda.njk +17 -4
  5. package/_includes/slides/center.njk +4 -1
  6. package/_includes/slides/code.njk +4 -1
  7. package/_includes/slides/columns.njk +6 -3
  8. package/_includes/slides/comparison.njk +5 -2
  9. package/_includes/slides/content.njk +6 -3
  10. package/_includes/slides/funnel.njk +5 -2
  11. package/_includes/slides/hero.njk +3 -2
  12. package/_includes/slides/image-overlay.njk +5 -5
  13. package/_includes/slides/metrics.njk +5 -2
  14. package/_includes/slides/quote.njk +4 -1
  15. package/_includes/slides/section.njk +8 -1
  16. package/_includes/slides/split-wide.njk +6 -3
  17. package/_includes/slides/split.njk +6 -3
  18. package/_includes/slides/table.njk +4 -1
  19. package/_includes/slides/three-columns.njk +48 -0
  20. package/_includes/slides/timeline.njk +5 -2
  21. package/_includes/slides/title.njk +2 -1
  22. package/_layouts/presentation.njk +39 -13
  23. package/assets/css/themes/card.css +63 -0
  24. package/assets/css/themes/corporate.css +25 -0
  25. package/assets/css/themes/default.css +2 -4
  26. package/assets/css/themes/forest.css +23 -0
  27. package/assets/css/themes/midnight.css +98 -0
  28. package/assets/css/themes/sunset.css +23 -0
  29. package/assets/reveal.js/plugin/math/katex.js +96 -0
  30. package/assets/reveal.js/plugin/math/math.esm.js +6 -0
  31. package/assets/reveal.js/plugin/math/math.js +1 -0
  32. package/assets/reveal.js/plugin/math/mathjax2.js +89 -0
  33. package/assets/reveal.js/plugin/math/mathjax3.js +77 -0
  34. package/assets/reveal.js/plugin/math/plugin.js +15 -0
  35. package/assets/reveal.js/plugin/search/plugin.js +243 -0
  36. package/assets/reveal.js/plugin/search/search.esm.js +7 -0
  37. package/assets/reveal.js/plugin/search/search.js +7 -0
  38. package/assets/reveal.js/plugin/zoom/plugin.js +264 -0
  39. package/assets/reveal.js/plugin/zoom/zoom.esm.js +11 -0
  40. package/assets/reveal.js/plugin/zoom/zoom.js +11 -0
  41. package/index.js +27 -11
  42. package/lib/validate.js +74 -9
  43. package/package.json +1 -1
  44. package/scaffold/CLAUDE.md +9 -4
  45. package/scaffold/README.md +1 -0
  46. package/scaffold/claude-commands/add-template.md +74 -0
  47. package/scaffold/claude-commands/add-theme.md +50 -0
  48. package/src/styles.css +280 -121
package/README.md CHANGED
@@ -10,7 +10,7 @@ Leverage your favorite AI agent to build simple, yet meaningful slide decks.
10
10
 
11
11
  Built on [Eleventy](https://www.11ty.dev/), [Reveal.js](https://revealjs.com), and [Tailwind CSS](https://tailwindcss.com).
12
12
 
13
- [Live demo](https://chrisissorry.github.io/lazyslides/)
13
+ [Live demo](https://chrisissorry.github.io/lazyslides/presentations/)
14
14
 
15
15
  ## Why
16
16
 
@@ -148,10 +148,31 @@ Supporting files (outlines, notes, PDFs, images) go alongside `index.md` in the
148
148
  | `table` | Data table |
149
149
  | `code` | Code snippet with syntax highlighting |
150
150
  | `image-overlay` | Full image with positioned text box |
151
- | `agenda` | Clickable table of contents |
151
+ | `agenda` | Clickable table of contents (supports `auto_generate: true`) |
152
152
 
153
153
  See `CLAUDE.md` for the full field reference for each template.
154
154
 
155
+ ## Footer
156
+
157
+ Every slide (except `title`) includes a footer bar with slide numbers, a logo, a brand name, and optional source references.
158
+
159
+ **Visibility** — the footer is shown by default on most templates. Hero and section slides hide it by default.
160
+
161
+ | Level | Field | Effect |
162
+ |-------|-------|--------|
163
+ | Presentation | `show_footer: false` | Hides the footer on all slides |
164
+ | Per slide | `hide_footer: true` | Hides footer on that slide |
165
+ | Per slide (hero/section) | `show_footer: true` | Shows footer on that slide |
166
+
167
+ **Branding** — customize the logo and name via CSS custom properties in a theme file:
168
+
169
+ ```css
170
+ :root {
171
+ --footer-brand-name: 'Your Brand';
172
+ --footer-logo-url: url('/path/to/logo.svg');
173
+ }
174
+ ```
175
+
155
176
  ## Slash commands (Claude Code)
156
177
 
157
178
  The scaffolded project includes Claude Code slash commands for the full workflow:
@@ -164,6 +185,8 @@ The scaffolded project includes Claude Code slash commands for the full workflow
164
185
  | `/create-outline` | Plan structure before writing YAML |
165
186
  | `/refine-slides` | Improve an existing presentation |
166
187
  | `/validate` | Check YAML and fix issues |
188
+ | `/add-template` | Create a custom slide template |
189
+ | `/add-theme` | Create a custom CSS theme |
167
190
 
168
191
  The project ships a `CLAUDE.md` with the complete template schema, so Claude Code knows every field of every template.
169
192
 
@@ -184,7 +207,6 @@ Drop a CSS file into `themes/` to override design tokens:
184
207
  ```css
185
208
  :root {
186
209
  --color-primary-500: #your-brand-color;
187
- --footer-brand-name: 'Your Brand';
188
210
  }
189
211
  ```
190
212
 
@@ -1,22 +1,38 @@
1
- {# Reusable macro for rendering nested lists (map syntax + legacy object syntax) #}
2
- {% macro renderItems(items, ordered) %}
1
+ {# Reusable macro for rendering nested lists (map syntax + legacy object syntax).
2
+ Supports fragment animations and auto-animate data-id attributes.
3
+
4
+ Parameters:
5
+ items — the list items array
6
+ ordered — true for <ol>, false for <ul> (default: false)
7
+ fragment_type — slide-level fragment class applied to all items (e.g. "fade-up")
8
+ #}
9
+ {% macro renderItems(items, ordered, fragment_type) %}
3
10
  <{{ "ol" if ordered else "ul" }}>
4
11
  {% for item in items %}
5
- <li>
6
- {% if item is mapping %}
7
- {% for key, children in item %}
8
- {{ key }}
9
- <ul>
10
- {% for sub in children %}<li>{{ sub }}</li>{% endfor %}
11
- </ul>
12
- {% endfor %}
13
- {% elif item.text %}
12
+ <li
13
+ {%- if item is mapping and item.text is defined %}
14
+ {%- set frag = item.fragment or fragment_type %}
15
+ {%- if frag %} class="fragment {{ frag }}"{% endif %}
16
+ {%- if item.data_id %} data-id="{{ item.data_id }}"{% endif %}
17
+ {%- elif item is mapping %}
18
+ {%- if fragment_type %} class="fragment {{ fragment_type }}"{% endif %}
19
+ {%- else %}
20
+ {%- if fragment_type %} class="fragment {{ fragment_type }}"{% endif %}
21
+ {%- endif %}>
22
+ {% if item is mapping and item.text is defined %}
14
23
  {{ item.text }}
15
24
  {% if item.sub_items %}
16
25
  <ul>
17
- {% for sub in item.sub_items %}<li>{{ sub }}</li>{% endfor %}
26
+ {% for sub in item.sub_items %}<li{% if frag %} class="fragment {{ frag }}"{% endif %}>{{ sub }}</li>{% endfor %}
18
27
  </ul>
19
28
  {% endif %}
29
+ {% elif item is mapping %}
30
+ {% for key, children in item %}
31
+ {{ key }}
32
+ <ul>
33
+ {% for sub in children %}<li{% if fragment_type %} class="fragment {{ fragment_type }}"{% endif %}>{{ sub }}</li>{% endfor %}
34
+ </ul>
35
+ {% endfor %}
20
36
  {% else %}
21
37
  {{ item }}
22
38
  {% endif %}
@@ -0,0 +1,15 @@
1
+ {# Reusable macro for emitting data-* attributes on <section> tags.
2
+ Handles transitions, auto-animate, backgrounds (color, gradient, image, video). #}
3
+ {% macro sectionAttrs(slide) %}
4
+ {%- if slide.transition %} data-transition="{{ slide.transition }}"{% endif %}
5
+ {%- if slide.transition_speed %} data-transition-speed="{{ slide.transition_speed }}"{% endif %}
6
+ {%- if slide.auto_animate %} data-auto-animate{% endif %}
7
+ {%- if slide.auto_animate_easing %} data-auto-animate-easing="{{ slide.auto_animate_easing }}"{% endif %}
8
+ {%- if slide.auto_animate_duration %} data-auto-animate-duration="{{ slide.auto_animate_duration }}"{% endif %}
9
+ {%- if slide.background_color %} data-background-color="{{ slide.background_color }}"{% endif %}
10
+ {%- if slide.background_gradient %} data-background="{{ slide.background_gradient }}"{% endif %}
11
+ {%- if slide.background_image %} data-background-image="{{ slide.background_image }}"{% endif %}
12
+ {%- if slide.background_video %} data-background-video="{{ slide.background_video }}"{% endif %}
13
+ {%- if slide.background_video_loop %} data-background-video-loop{% endif %}
14
+ {%- if slide.background_video_muted %} data-background-video-muted{% endif %}
15
+ {%- endmacro %}
@@ -1,17 +1,30 @@
1
- {# TEMPLATE: agenda — Clickable table of contents for presentation sections. #}
2
- <section class="slide-agenda">
1
+ {# TEMPLATE: agenda — Clickable table of contents for presentation sections.
2
+ Set auto_generate: true to build the agenda automatically from section slides.
3
+ Manual mode: provide sections array with title and slide (0-based index). #}
4
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
5
+ <section class="slide-agenda"{{ sectionAttrs(slide) }}>
3
6
  <div class="slide-body">
4
7
  <div class="slide-header">
5
8
  <h2>{{ slide.title | default("Agenda") }}</h2>
6
9
  </div>
7
10
  <ol class="agenda-list">
8
- {% for item in slide.sections %}
11
+ {% if slide.auto_generate %}
12
+ {% for s in slides %}
13
+ {% if s.template == "section" %}
14
+ <li><a href="#/{{ loop.index0 }}">{{ s.title }}</a></li>
15
+ {% endif %}
16
+ {% endfor %}
17
+ {% else %}
18
+ {% for item in slide.sections %}
9
19
  <li><a href="#/{{ item.slide }}">{{ item.title }}</a></li>
10
- {% endfor %}
20
+ {% endfor %}
21
+ {% endif %}
11
22
  </ol>
12
23
  </div>
24
+ {% if global_footer and not slide.hide_footer %}
13
25
  {% set reference = slide.reference %}
14
26
  {% set reference_link = slide.reference_link %}
15
27
  {% set references = slide.references %}
16
28
  {% include "slides/footer.njk" %}
29
+ {% endif %}
17
30
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: center — Centered statement, image, or both. Image has lightbox on click. #}
2
- <section class="slide-center">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-center"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  {% if slide.title %}<h2>{{ slide.title }}</h2>{% endif %}
5
6
  {% if slide.text %}<p>{{ slide.text }}</p>{% endif %}
@@ -9,8 +10,10 @@
9
10
  {% endif %}
10
11
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
11
12
  </div>
13
+ {% if global_footer and not slide.hide_footer %}
12
14
  {% set reference = slide.reference %}
13
15
  {% set reference_link = slide.reference_link %}
14
16
  {% set references = slide.references %}
15
17
  {% include "slides/footer.njk" %}
18
+ {% endif %}
16
19
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: code — Code snippet display with syntax highlighting. #}
2
- <section class="slide-code">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-code"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -8,8 +9,10 @@
8
9
  {% if slide.caption %}<p class="caption">{{ slide.caption }}</p>{% endif %}
9
10
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
10
11
  </div>
12
+ {% if global_footer and not slide.hide_footer %}
11
13
  {% set reference = slide.reference %}
12
14
  {% set reference_link = slide.reference_link %}
13
15
  {% set references = slide.references %}
14
16
  {% include "slides/footer.njk" %}
17
+ {% endif %}
15
18
  </section>
@@ -1,11 +1,12 @@
1
1
  {# TEMPLATE: columns — Two-column layout for side-by-side content. #}
2
- <section class="slide-columns">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-columns"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
6
7
  </div>
7
8
  <div class="columns">
8
- <div class="column">
9
+ <div class="column{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
9
10
  {% if slide.left_title %}<h3>{{ slide.left_title }}</h3>{% endif %}
10
11
  {% if slide.left_text %}<p>{{ slide.left_text }}</p>{% endif %}
11
12
  {% if slide.left_items %}
@@ -15,7 +16,7 @@
15
16
  </ul>
16
17
  {% endif %}
17
18
  </div>
18
- <div class="column">
19
+ <div class="column{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
19
20
  {% if slide.right_title %}<h3>{{ slide.right_title }}</h3>{% endif %}
20
21
  {% if slide.right_text %}<p>{{ slide.right_text }}</p>{% endif %}
21
22
  {% if slide.right_items %}
@@ -28,8 +29,10 @@
28
29
  </div>
29
30
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
30
31
  </div>
32
+ {% if global_footer and not slide.hide_footer %}
31
33
  {% set reference = slide.reference %}
32
34
  {% set reference_link = slide.reference_link %}
33
35
  {% set references = slide.references %}
34
36
  {% include "slides/footer.njk" %}
37
+ {% endif %}
35
38
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: comparison — Before/after two-column table with optional highlight styling. #}
2
- <section class="slide-comparison{% if slide.highlight == 'left' %} highlight-left{% elif slide.highlight == 'none' %} highlight-none{% endif %}">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-comparison{% if slide.highlight == 'left' %} highlight-left{% elif slide.highlight == 'none' %} highlight-none{% endif %}"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -13,7 +14,7 @@
13
14
  </thead>
14
15
  <tbody>
15
16
  {% for row in slide.rows %}
16
- <tr>
17
+ <tr{% if slide.fragment %} class="fragment {{ slide.fragment }}"{% endif %}>
17
18
  <td>{% if row.left_icon == "bad" %}<span class="icon-bad">✗</span>{% endif %}{{ row.left }}</td>
18
19
  <td>{% if row.right_icon == "good" %}<span class="icon-good">✓</span>{% endif %}{{ row.right }}</td>
19
20
  </tr>
@@ -22,8 +23,10 @@
22
23
  </table>
23
24
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
24
25
  </div>
26
+ {% if global_footer and not slide.hide_footer %}
25
27
  {% set reference = slide.reference %}
26
28
  {% set reference_link = slide.reference_link %}
27
29
  {% set references = slide.references %}
28
30
  {% include "slides/footer.njk" %}
31
+ {% endif %}
29
32
  </section>
@@ -1,6 +1,7 @@
1
1
  {# TEMPLATE: content — Standard slide with headline and bullet points. #}
2
2
  {% from "slides/_nested-list.njk" import renderItems %}
3
- <section class="slide-content">
3
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
4
+ <section class="slide-content"{{ sectionAttrs(slide) }}>
4
5
  <div class="slide-body">
5
6
  <div class="slide-header">
6
7
  <h2>{{ slide.title }}</h2>
@@ -8,15 +9,17 @@
8
9
  </div>
9
10
  {% if slide.text %}<p>{{ slide.text }}</p>{% endif %}
10
11
  {% if slide.items %}
11
- {{ renderItems(slide.items) }}
12
+ {{ renderItems(slide.items, false, slide.fragment) }}
12
13
  {% endif %}
13
14
  {% if slide.ordered_items %}
14
- {{ renderItems(slide.ordered_items, true) }}
15
+ {{ renderItems(slide.ordered_items, true, slide.fragment) }}
15
16
  {% endif %}
16
17
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
17
18
  </div>
19
+ {% if global_footer and not slide.hide_footer %}
18
20
  {% set reference = slide.reference %}
19
21
  {% set reference_link = slide.reference_link %}
20
22
  {% set references = slide.references %}
21
23
  {% include "slides/footer.njk" %}
24
+ {% endif %}
22
25
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: funnel — Progressive narrowing funnel with stage labels and annotations. #}
2
- <section class="slide-funnel">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-funnel"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -7,7 +8,7 @@
7
8
  <div class="funnel-wrapper">
8
9
  <div class="funnel-grid">
9
10
  {% for stage in slide.stages %}
10
- <div class="funnel-row">
11
+ <div class="funnel-row{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
11
12
  <div class="funnel-stage-wrapper">
12
13
  <div class="funnel-stage stage-{{ loop.index }}">
13
14
  <span class="stage-label">{{ stage.label }}</span>
@@ -23,8 +24,10 @@
23
24
  </div>
24
25
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
25
26
  </div>
27
+ {% if global_footer and not slide.hide_footer %}
26
28
  {% set reference = slide.reference %}
27
29
  {% set reference_link = slide.reference_link %}
28
30
  {% set references = slide.references %}
29
31
  {% include "slides/footer.njk" %}
32
+ {% endif %}
30
33
  </section>
@@ -1,6 +1,7 @@
1
1
  {# TEMPLATE: hero — Full-bleed background image with dark overlay and optional text box. #}
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
2
3
  {% if slide.image %}{% set img_src = slide.image | resolveImage %}{% endif %}
3
- <section class="slide-hero"{% if slide.image %} style="background-image: url('{{ img_src }}'); background-size: cover; background-position: center;"{% elif slide.color %} style="background-color: {{ slide.color }};"{% endif %}>
4
+ <section class="slide-hero"{% if slide.image %} data-background-image="{{ img_src }}" data-background-size="cover" data-background-position="center"{% elif slide.color %} data-background-color="{{ slide.color }}"{% endif %}{{ sectionAttrs(slide) }}>
4
5
  <div class="slide-body">
5
6
  <h1>{{ slide.title }}</h1>
6
7
  {% if slide.text %}<p>{{ slide.text }}</p>{% endif %}
@@ -18,7 +19,7 @@
18
19
  {% endif %}
19
20
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
20
21
  </div>
21
- {% if not slide.hide_footer %}
22
+ {% if global_footer and slide.show_footer %}
22
23
  {% set reference = slide.reference %}
23
24
  {% set reference_link = slide.reference_link %}
24
25
  {% set references = slide.references %}
@@ -1,9 +1,7 @@
1
- {# TEMPLATE: image-overlay — Full background image with positioned text box overlay.
2
- NOTE: Use inline style for background-image, NOT data-background-image.
3
- Reveal.js data-background-* renders in the full viewport (including theater margins),
4
- while inline style keeps the image within the 960×540 slide bounds. #}
1
+ {# TEMPLATE: image-overlay — Full background image with positioned text box overlay. #}
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
5
3
  {% set img_src = slide.image | resolveImage %}
6
- <section class="slide-image-overlay" style="background-image: url('{{ img_src }}'); background-size: cover; background-position: center;">
4
+ <section class="slide-image-overlay" data-background-image="{{ img_src }}" data-background-size="cover" data-background-position="center"{{ sectionAttrs(slide) }}>
7
5
  <div class="slide-body">
8
6
  <div class="overlay-box {{ slide.position | default('bottom-left') }}">
9
7
  {% if slide.title %}<h3>{{ slide.title }}</h3>{% endif %}
@@ -17,8 +15,10 @@
17
15
  </div>
18
16
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
19
17
  </div>
18
+ {% if global_footer and not slide.hide_footer %}
20
19
  {% set reference = slide.reference %}
21
20
  {% set reference_link = slide.reference_link %}
22
21
  {% set references = slide.references %}
23
22
  {% include "slides/footer.njk" %}
23
+ {% endif %}
24
24
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: metrics — Key statistics display with 2-4 metric cards. #}
2
- <section class="slide-metrics">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-metrics"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -11,7 +12,7 @@
11
12
  {% else %}{% set cols_class = "cols-3" %}{% endif %}
12
13
  <div class="metrics-grid {{ cols_class }}">
13
14
  {% for metric in slide.metrics %}
14
- <div class="metric-card">
15
+ <div class="metric-card{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
15
16
  <div class="value{% if metric.color %} {{ metric.color }}{% endif %}">{{ metric.value }}</div>
16
17
  <div class="label">{{ metric.label }}</div>
17
18
  {% if metric.context %}<div class="context">{{ metric.context }}</div>{% endif %}
@@ -20,8 +21,10 @@
20
21
  </div>
21
22
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
22
23
  </div>
24
+ {% if global_footer and not slide.hide_footer %}
23
25
  {% set reference = slide.reference %}
24
26
  {% set reference_link = slide.reference_link %}
25
27
  {% set references = slide.references %}
26
28
  {% include "slides/footer.njk" %}
29
+ {% endif %}
27
30
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: quote — Testimonial or statement with attribution. #}
2
- <section class="slide-quote">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-quote"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="quote-mark">"</div>
5
6
  <blockquote>{{ slide.quote }}</blockquote>
@@ -10,8 +11,10 @@
10
11
  {% endif %}
11
12
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
12
13
  </div>
14
+ {% if global_footer and not slide.hide_footer %}
13
15
  {% set reference = slide.reference %}
14
16
  {% set reference_link = slide.reference_link %}
15
17
  {% set references = slide.references %}
16
18
  {% include "slides/footer.njk" %}
19
+ {% endif %}
17
20
  </section>
@@ -1,6 +1,13 @@
1
1
  {# TEMPLATE: section — Chapter divider slide for separating presentation sections. #}
2
- <section class="slide-section">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-section"{{ sectionAttrs(slide) }}>
3
4
  <h2>{{ slide.title }}</h2>
4
5
  {% if slide.subtitle %}<p class="subtitle">{{ slide.subtitle }}</p>{% endif %}
5
6
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
7
+ {% if global_footer and slide.show_footer %}
8
+ {% set reference = slide.reference %}
9
+ {% set reference_link = slide.reference_link %}
10
+ {% set references = slide.references %}
11
+ {% include "slides/footer.njk" %}
12
+ {% endif %}
6
13
  </section>
@@ -1,6 +1,7 @@
1
1
  {# TEMPLATE: split-wide — 1/3 content + 2/3 image layout (inverse of split). #}
2
2
  {% from "slides/_nested-list.njk" import renderItems %}
3
- <section class="slide-split-wide">
3
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
4
+ <section class="slide-split-wide"{{ sectionAttrs(slide) }}>
4
5
  <div class="slide-body">
5
6
  <div class="split-wide-container">
6
7
  <div class="split-wide-content">
@@ -9,11 +10,11 @@
9
10
  </div>
10
11
  {% if slide.text %}<p>{{ slide.text }}</p>{% endif %}
11
12
  {% if slide.items %}
12
- {{ renderItems(slide.items) }}
13
+ {{ renderItems(slide.items, false, slide.fragment) }}
13
14
  {% endif %}
14
15
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
15
16
  </div>
16
- <div class="split-wide-image">
17
+ <div class="split-wide-image{% if slide.image_fit == 'contain' %} image-contain{% endif %}">
17
18
  {% if slide.image %}
18
19
  {% set img_src = slide.image | resolveImage %}
19
20
  <a href="{{ img_src }}" class="glightbox">
@@ -23,8 +24,10 @@
23
24
  </div>
24
25
  </div>
25
26
  </div>
27
+ {% if global_footer and not slide.hide_footer %}
26
28
  {% set reference = slide.reference %}
27
29
  {% set reference_link = slide.reference_link %}
28
30
  {% set references = slide.references %}
29
31
  {% include "slides/footer.njk" %}
32
+ {% endif %}
30
33
  </section>
@@ -1,9 +1,10 @@
1
1
  {# TEMPLATE: split — 1/3 image + 2/3 content layout. Image has lightbox on click. #}
2
2
  {% from "slides/_nested-list.njk" import renderItems %}
3
- <section class="slide-split">
3
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
4
+ <section class="slide-split"{{ sectionAttrs(slide) }}>
4
5
  <div class="slide-body">
5
6
  <div class="split-container">
6
- <div class="split-image">
7
+ <div class="split-image{% if slide.image_fit == 'contain' %} image-contain{% endif %}">
7
8
  {% if slide.image %}
8
9
  {% set img_src = slide.image | resolveImage %}
9
10
  <a href="{{ img_src }}" class="glightbox">
@@ -17,14 +18,16 @@
17
18
  </div>
18
19
  {% if slide.text %}<p>{{ slide.text }}</p>{% endif %}
19
20
  {% if slide.items %}
20
- {{ renderItems(slide.items) }}
21
+ {{ renderItems(slide.items, false, slide.fragment) }}
21
22
  {% endif %}
22
23
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
23
24
  </div>
24
25
  </div>
25
26
  </div>
27
+ {% if global_footer and not slide.hide_footer %}
26
28
  {% set reference = slide.reference %}
27
29
  {% set reference_link = slide.reference_link %}
28
30
  {% set references = slide.references %}
29
31
  {% include "slides/footer.njk" %}
32
+ {% endif %}
30
33
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: table — Multi-column data table with neutral styling and zebra striping. #}
2
- <section class="slide-table">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-table"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -24,8 +25,10 @@
24
25
  </table>
25
26
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
26
27
  </div>
28
+ {% if global_footer and not slide.hide_footer %}
27
29
  {% set reference = slide.reference %}
28
30
  {% set reference_link = slide.reference_link %}
29
31
  {% set references = slide.references %}
30
32
  {% include "slides/footer.njk" %}
33
+ {% endif %}
31
34
  </section>
@@ -0,0 +1,48 @@
1
+ {# TEMPLATE: three-columns — Three equal-width columns for side-by-side content. #}
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-three-columns"{{ sectionAttrs(slide) }}>
4
+ <div class="slide-body">
5
+ <div class="slide-header">
6
+ <h2>{{ slide.title }}</h2>
7
+ </div>
8
+ <div class="three-columns">
9
+ <div class="column{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
10
+ {% if slide.col1_title %}<h3>{{ slide.col1_title }}</h3>{% endif %}
11
+ {% if slide.col1_text %}<p>{{ slide.col1_text }}</p>{% endif %}
12
+ {% if slide.col1_items %}
13
+ <ul>
14
+ {% for item in slide.col1_items %}<li>{{ item }}</li>
15
+ {% endfor %}
16
+ </ul>
17
+ {% endif %}
18
+ </div>
19
+ <div class="column{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
20
+ {% if slide.col2_title %}<h3>{{ slide.col2_title }}</h3>{% endif %}
21
+ {% if slide.col2_text %}<p>{{ slide.col2_text }}</p>{% endif %}
22
+ {% if slide.col2_items %}
23
+ <ul>
24
+ {% for item in slide.col2_items %}<li>{{ item }}</li>
25
+ {% endfor %}
26
+ </ul>
27
+ {% endif %}
28
+ </div>
29
+ <div class="column{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
30
+ {% if slide.col3_title %}<h3>{{ slide.col3_title }}</h3>{% endif %}
31
+ {% if slide.col3_text %}<p>{{ slide.col3_text }}</p>{% endif %}
32
+ {% if slide.col3_items %}
33
+ <ul>
34
+ {% for item in slide.col3_items %}<li>{{ item }}</li>
35
+ {% endfor %}
36
+ </ul>
37
+ {% endif %}
38
+ </div>
39
+ </div>
40
+ {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
41
+ </div>
42
+ {% if global_footer and not slide.hide_footer %}
43
+ {% set reference = slide.reference %}
44
+ {% set reference_link = slide.reference_link %}
45
+ {% set references = slide.references %}
46
+ {% include "slides/footer.njk" %}
47
+ {% endif %}
48
+ </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: timeline — Horizontal timeline with alternating above/below event cards. #}
2
- <section class="slide-timeline">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-timeline"{{ sectionAttrs(slide) }}>
3
4
  <div class="slide-body">
4
5
  <div class="slide-header">
5
6
  <h2>{{ slide.title }}</h2>
@@ -9,7 +10,7 @@
9
10
  <div class="timeline-events">
10
11
  {% for event in slide.events %}
11
12
  {% set remainder = loop.index % 2 %}
12
- <div class="timeline-event {% if remainder == 1 %}above{% else %}below{% endif %}">
13
+ <div class="timeline-event {% if remainder == 1 %}above{% else %}below{% endif %}{% if slide.fragment %} fragment {{ slide.fragment }}{% endif %}">
13
14
  <div class="event-dot"></div>
14
15
  <div class="event-connector"></div>
15
16
  <div class="event-card">
@@ -23,8 +24,10 @@
23
24
  </div>
24
25
  {% if slide.notes %}<aside class="notes">{{ slide.notes }}</aside>{% endif %}
25
26
  </div>
27
+ {% if global_footer and not slide.hide_footer %}
26
28
  {% set reference = slide.reference %}
27
29
  {% set reference_link = slide.reference_link %}
28
30
  {% set references = slide.references %}
29
31
  {% include "slides/footer.njk" %}
32
+ {% endif %}
30
33
  </section>
@@ -1,5 +1,6 @@
1
1
  {# TEMPLATE: title — Opening slide with logo, title, subtitle, and author. #}
2
- <section class="slide-title">
2
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
3
+ <section class="slide-title"{{ sectionAttrs(slide) }}>
3
4
  {% if slide.logo != false %}
4
5
  <div class="logo">
5
6
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">