lazyslides 0.2.1 → 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 (44) hide show
  1. package/README.md +1 -1
  2. package/_includes/slides/_nested-list.njk +28 -12
  3. package/_includes/slides/_section-attrs.njk +15 -0
  4. package/_includes/slides/agenda.njk +2 -1
  5. package/_includes/slides/center.njk +2 -1
  6. package/_includes/slides/code.njk +2 -1
  7. package/_includes/slides/columns.njk +4 -3
  8. package/_includes/slides/comparison.njk +3 -2
  9. package/_includes/slides/content.njk +4 -3
  10. package/_includes/slides/funnel.njk +3 -2
  11. package/_includes/slides/hero.njk +2 -1
  12. package/_includes/slides/image-overlay.njk +3 -5
  13. package/_includes/slides/metrics.njk +3 -2
  14. package/_includes/slides/quote.njk +2 -1
  15. package/_includes/slides/section.njk +2 -1
  16. package/_includes/slides/split-wide.njk +4 -3
  17. package/_includes/slides/split.njk +4 -3
  18. package/_includes/slides/table.njk +2 -1
  19. package/_includes/slides/three-columns.njk +48 -0
  20. package/_includes/slides/timeline.njk +3 -2
  21. package/_includes/slides/title.njk +2 -1
  22. package/_layouts/presentation.njk +38 -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 +26 -10
  42. package/lib/validate.js +74 -9
  43. package/package.json +1 -1
  44. package/src/styles.css +262 -118
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/presentations/example/)
13
+ [Live demo](https://chrisissorry.github.io/lazyslides/presentations/)
14
14
 
15
15
  ## Why
16
16
 
@@ -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,7 +1,8 @@
1
1
  {# TEMPLATE: agenda — Clickable table of contents for presentation sections.
2
2
  Set auto_generate: true to build the agenda automatically from section slides.
3
3
  Manual mode: provide sections array with title and slide (0-based index). #}
4
- <section class="slide-agenda">
4
+ {% from "slides/_section-attrs.njk" import sectionAttrs %}
5
+ <section class="slide-agenda"{{ sectionAttrs(slide) }}>
5
6
  <div class="slide-body">
6
7
  <div class="slide-header">
7
8
  <h2>{{ slide.title | default("Agenda") }}</h2>
@@ -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 %}
@@ -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>
@@ -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 %}
@@ -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>
@@ -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,10 +9,10 @@
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>
@@ -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>
@@ -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 %}
@@ -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 %}
@@ -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 %}
@@ -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>
@@ -1,5 +1,6 @@
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 %}
@@ -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">
@@ -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,7 +18,7 @@
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>
@@ -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>
@@ -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">
@@ -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">
@@ -17,10 +17,17 @@
17
17
  <!-- Tailwind CSS (structural styles) -->
18
18
  <link rel="stylesheet" href="{{ '/assets/css/styles.css' | url }}">
19
19
 
20
- <!-- Theme CSS (design tokens) -->
20
+ <!-- Theme -->
21
21
  {% set theme_name = theme | default('default') %}
22
22
  <link rel="stylesheet" href="{{ '/assets/css/themes/' | url }}{{ theme_name }}.css">
23
23
 
24
+ {% if footer_brand or footer_logo == false %}
25
+ <style>
26
+ {% if footer_brand %}:root { --footer-brand-name: '{{ footer_brand }}'; }{% endif %}
27
+ {% if footer_logo == false %}.footer-center .logo-small { display: none; }{% endif %}
28
+ </style>
29
+ {% endif %}
30
+
24
31
  <!-- Code highlighting theme -->
25
32
  <link rel="stylesheet" href="{{ '/assets/reveal.js/plugin/highlight/monokai.css' | url }}">
26
33
 
@@ -28,15 +35,6 @@
28
35
  <link rel="stylesheet" href="{{ '/assets/css/vendor/glightbox.min.css' | url }}">
29
36
  </head>
30
37
  <body>
31
- <!-- Exit button to return to presentations index -->
32
- <a href="{{ '/presentations/' | url }}"
33
- class="exit-button"
34
- title="Exit to presentations list">
35
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
36
- <path d="M18 6L6 18M6 6l12 12"/>
37
- </svg>
38
- </a>
39
-
40
38
  <div class="reveal">
41
39
  <div class="slides">
42
40
  {% for slide in slides %}
@@ -52,6 +50,9 @@
52
50
  <div class="section-progress-dots"></div>
53
51
  </div>
54
52
 
53
+ <!-- Viewport footer — fixed at screen bottom, outside Reveal.js canvas -->
54
+ <div id="viewport-footer"></div>
55
+
55
56
  <!-- Reveal.js -->
56
57
  <script src="{{ '/assets/reveal.js/dist/reveal.js' | url }}"></script>
57
58
 
@@ -59,6 +60,11 @@
59
60
  <script src="{{ '/assets/reveal.js/plugin/markdown/markdown.js' | url }}"></script>
60
61
  <script src="{{ '/assets/reveal.js/plugin/highlight/highlight.js' | url }}"></script>
61
62
  <script src="{{ '/assets/reveal.js/plugin/notes/notes.js' | url }}"></script>
63
+ <script src="{{ '/assets/reveal.js/plugin/zoom/zoom.js' | url }}"></script>
64
+ <script src="{{ '/assets/reveal.js/plugin/search/search.js' | url }}"></script>
65
+ {% if math %}
66
+ <script src="{{ '/assets/reveal.js/plugin/math/math.js' | url }}"></script>
67
+ {% endif %}
62
68
 
63
69
  <!-- Convert newlines to <br/> in speaker notes for proper line breaks -->
64
70
  <script>
@@ -81,7 +87,7 @@
81
87
  height: {{ height | default(540) }},
82
88
 
83
89
  // Scale settings
84
- margin: isPdfExport ? 0 : 0.04,
90
+ margin: parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--reveal-margin')) || 0,
85
91
  minScale: 0.2,
86
92
  maxScale: 2.0,
87
93
 
@@ -94,7 +100,7 @@
94
100
  progress: isPdfExport ? false : {{ progress | default(true) }},
95
101
 
96
102
  // Slide number
97
- slideNumber: {{ slideNumber | default(false) }},
103
+ slideNumber: false,
98
104
 
99
105
  // Push each slide to browser history
100
106
  history: {{ history | default(true) }},
@@ -157,7 +163,7 @@
157
163
  mobileViewDistance: {{ mobileViewDistance | default(2) }},
158
164
 
159
165
  // Plugins
160
- plugins: [ RevealMarkdown, RevealHighlight, RevealNotes ]
166
+ plugins: [ RevealMarkdown, RevealHighlight, RevealNotes, RevealZoom, RevealSearch{% if math %}, RevealMath.KaTeX{% endif %} ]
161
167
  });
162
168
  </script>
163
169
 
@@ -237,5 +243,24 @@
237
243
  updateDots(); // Set initial state
238
244
  });
239
245
  </script>
246
+
247
+ <!-- Viewport footer: clone current slide's footer to fixed container -->
248
+ <script>
249
+ function updateViewportFooter() {
250
+ const container = document.getElementById('viewport-footer');
251
+ if (!container || document.body.classList.contains('pdf-export')) return;
252
+
253
+ const currentSlide = Reveal.getCurrentSlide();
254
+ const footer = currentSlide?.querySelector('.slide-footer');
255
+
256
+ container.innerHTML = '';
257
+ if (footer) {
258
+ container.appendChild(footer.cloneNode(true));
259
+ }
260
+ }
261
+
262
+ Reveal.on('ready', updateViewportFooter);
263
+ Reveal.on('slidechanged', updateViewportFooter);
264
+ </script>
240
265
  </body>
241
266
  </html>
@@ -0,0 +1,63 @@
1
+ /* ============================================
2
+ CARD THEME
3
+ Restores the card/theater aesthetic where slides
4
+ appear as floating white cards on a gradient background.
5
+
6
+ Usage: Add `theme: card` to presentation frontmatter
7
+ ============================================ */
8
+
9
+ :root {
10
+ /* Primary Color Scale — same sky blue as default */
11
+ --color-primary-50: #f0f9ff;
12
+ --color-primary-100: #e0f2fe;
13
+ --color-primary-200: #bae6fd;
14
+ --color-primary-400: #38bdf8;
15
+ --color-primary-500: #0ea5e9;
16
+ --color-primary-600: #0284c7;
17
+ --color-primary-700: #0369a1;
18
+
19
+ --reveal-margin: 0.04; /* Theater-style inset for card frame */
20
+ --footer-brand-name: 'lazyslides';
21
+ }
22
+
23
+ /* Theater gradient background */
24
+ html, body {
25
+ background: linear-gradient(135deg, #f1f5f9 0%, var(--color-primary-50) 100%);
26
+ }
27
+
28
+ /* Transparent viewport so theater background shows through */
29
+ .reveal-viewport {
30
+ background: transparent !important;
31
+ }
32
+
33
+ /* White card styling on each slide */
34
+ .reveal .slides > section,
35
+ .reveal .slides > section > section {
36
+ background: #ffffff;
37
+ border-radius: 6px;
38
+ border: 1px solid var(--color-slate-200, #e2e8f0);
39
+ box-shadow:
40
+ 0 2px 3px -1px rgba(0, 0, 0, 0.07),
41
+ 0 5px 10px -3px rgba(0, 0, 0, 0.05);
42
+ }
43
+
44
+ /* Restore footer rounded corners */
45
+ .slide-footer {
46
+ border-radius: 0 0 6px 6px;
47
+ border-top: 1px solid var(--color-slate-200);
48
+ background: var(--color-slate-50);
49
+ }
50
+
51
+ /* Hero and image-overlay get rounded corners too */
52
+ .reveal .slides section.slide-hero,
53
+ .reveal .slides section.slide-image-overlay {
54
+ border-radius: 6px;
55
+ }
56
+
57
+ /* PDF export removes card styling */
58
+ .pdf-export .reveal .slides > section,
59
+ .pdf-export .reveal .slides > section > section {
60
+ border-radius: 0;
61
+ border: none;
62
+ box-shadow: none;
63
+ }
@@ -0,0 +1,25 @@
1
+ /* ============================================
2
+ CORPORATE THEME
3
+ Charcoal and slate, minimal and professional.
4
+
5
+ Usage: Add `theme: corporate` to presentation frontmatter
6
+ ============================================ */
7
+
8
+ :root {
9
+ /* Primary Color Scale — slate/charcoal */
10
+ --color-primary-50: #f8fafc;
11
+ --color-primary-100: #f1f5f9;
12
+ --color-primary-200: #e2e8f0;
13
+ --color-primary-400: #94a3b8;
14
+ --color-primary-500: #64748b;
15
+ --color-primary-600: #475569;
16
+ --color-primary-700: #334155;
17
+
18
+ --font-heading: var(--font-sans);
19
+
20
+ --footer-brand-name: 'lazyslides';
21
+ }
22
+
23
+ html, body {
24
+ background: #ffffff;
25
+ }
@@ -52,11 +52,9 @@
52
52
  }
53
53
 
54
54
  /* ----------------------------------------
55
- THEATER BACKGROUND
56
- The gradient behind the slide cards.
55
+ BACKGROUND
57
56
  ---------------------------------------- */
58
57
 
59
58
  html, body {
60
- /* Default: warm gray to light primary tint */
61
- background: linear-gradient(135deg, #f1f5f9 0%, var(--color-primary-50) 100%);
59
+ background: var(--color-background, #ffffff);
62
60
  }
@@ -0,0 +1,23 @@
1
+ /* ============================================
2
+ FOREST THEME
3
+ Deep greens with warm earth tones.
4
+
5
+ Usage: Add `theme: forest` to presentation frontmatter
6
+ ============================================ */
7
+
8
+ :root {
9
+ /* Primary Color Scale — forest green */
10
+ --color-primary-50: #f0fdf4;
11
+ --color-primary-100: #dcfce7;
12
+ --color-primary-200: #bbf7d0;
13
+ --color-primary-400: #4ade80;
14
+ --color-primary-500: #22c55e;
15
+ --color-primary-600: #16a34a;
16
+ --color-primary-700: #15803d;
17
+
18
+ --footer-brand-name: 'lazyslides';
19
+ }
20
+
21
+ html, body {
22
+ background: #fafdf7;
23
+ }