jekyll-slides 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/README.md +56 -0
- data/assets/css/presentation.css +1 -1
- data/doc/CHANGELOG.md +5 -1
- data/doc/Jekyll/Slides/Deck.md +40 -0
- data/doc/Jekyll/Slides/DeckAssembler.md +17 -0
- data/doc/Jekyll/Slides/FrontMatter.md +16 -0
- data/doc/Jekyll/Slides/SlideFile.md +18 -0
- data/doc/Jekyll/Slides/SlideMetadata.md +6 -0
- data/doc/Jekyll/Slides/Support.md +6 -0
- data/doc/Jekyll/Slides.md +5 -1
- data/doc/Jekyll.md +1 -1
- data/doc/README.md +56 -0
- data/doc/index.csv +17 -0
- data/lib/jekyll/slides/deck.rb +151 -0
- data/lib/jekyll/slides/deck_assembler.rb +147 -0
- data/lib/jekyll/slides/front_matter.rb +39 -0
- data/lib/jekyll/slides/jekyll_integration.rb +1 -0
- data/lib/jekyll/slides/renderer.rb +14 -0
- data/lib/jekyll/slides/slide_file.rb +55 -0
- data/lib/jekyll/slides/slide_metadata.rb +26 -18
- data/lib/jekyll/slides/support.rb +14 -0
- data/lib/jekyll/slides/version.rb +1 -1
- data/lib/jekyll/slides.rb +4 -0
- data/llm.txt +5 -1
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa9ab7d728aad367a3cabaf68ebfefc51ba0c694b448932fac52cb1344b8080c
|
|
4
|
+
data.tar.gz: b6f552ba2da1bd283f1cde54d29735de5b293eac7049b4c8bf5183967aa88419
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 86ade6fd5acce8e68260d75e4104b1e4e57650c29dd8482a188473a05b6005758cd7493bf10829def302f797ea74d1d6e8dc2f73a48dbd8665501aa3774d0259
|
|
7
|
+
data.tar.gz: f0243f3d3d115e8c24f90251e791df00a57514000bd99dab7b618a76ea4fb22b9a898f591cc2961e37ac31415bbc8b178e14865cd9190de26ecdc7c3d194d9b1
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Jekyll Slides are documented here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.2.0 — 2026-09-06
|
|
6
6
|
|
|
7
|
+
- Add folder decks: author a presentation as one Markdown file per slide, ordered by a numeric filename prefix and alphabetically otherwise, opted into with `slides:` in the entry file's front matter.
|
|
8
|
+
- Add slide file front matter (`layout`, `background`, `class`) as an alternative to the `<!-- ... -->` metadata comment.
|
|
9
|
+
- Fix slides rendering upside down in Safari: the CSS length-division trick used to scale slides to the viewport made Safari compute a negative scale factor, which is a 180 degree rotation.
|
|
10
|
+
- Fix `slides_count` for decks whose slides are generated by Liquid, which previously reported the pre-Liquid slide count.
|
|
7
11
|
- Add `bin/prepare_release` to verify the gem, regenerate Markdown API documentation and `llm.txt`, and build a versioned package with a SHA-256 checksum.
|
|
8
12
|
- Ship generated documentation while keeping YARD and release tools development-only.
|
|
9
13
|
|
data/README.md
CHANGED
|
@@ -59,6 +59,62 @@ class: keynote
|
|
|
59
59
|
-->
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Deck folders
|
|
63
|
+
|
|
64
|
+
A deck can also be a folder of Markdown files, one file per slide, instead of one file with `---` separators:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
talks/my-talk/
|
|
68
|
+
index.md # deck front matter, opts in with `slides: true`
|
|
69
|
+
01-title.md
|
|
70
|
+
02-the-idea.md
|
|
71
|
+
03-code.md
|
|
72
|
+
diagram.png # ordinary static file, untouched
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The entry file must be named `index.md` and carry the deck's front matter, `layout: presentation`, and `slides: true`:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# talks/my-talk/index.md
|
|
79
|
+
---
|
|
80
|
+
layout: presentation
|
|
81
|
+
title: My talk
|
|
82
|
+
slides: true
|
|
83
|
+
---
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`slides: true` discovers every other Markdown file that is a direct child of the folder (not recursive), skipping names starting with `_` or `.`, and skipping anything Jekyll itself withholds: a file dropped by the site's `exclude:` setting, or one whose front matter sets `published: false`. Naming a withheld file in an explicit list warns and skips it too, so a deck never publishes content the site holds back. Files are ordered by a natural sort of the filename: digit runs compare numerically and sort before letters, so `01-title.md`, `02-the-idea.md`, `10-outro.md`, then any file with no numeric prefix, sorted alphabetically. Each file holds one slide, or several separated by `---`, exactly as in a single-file deck. Body content in `index.md`, if any, becomes the deck's leading slides.
|
|
87
|
+
|
|
88
|
+
A slide file can carry its own YAML front matter with `layout`, `background`, and `class`, as an alternative to the `<!-- ... -->` comment:
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
---
|
|
92
|
+
layout: statement
|
|
93
|
+
background: spotlight
|
|
94
|
+
---
|
|
95
|
+
# A bold claim
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Front matter in the file applies to every slide it holds; a slide's own comment overrides it key by key.
|
|
99
|
+
|
|
100
|
+
Set `slides:` to a list instead of `true` to turn the entry file into a readable running order, in presentation order:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
---
|
|
104
|
+
layout: presentation
|
|
105
|
+
title: My talk
|
|
106
|
+
slides:
|
|
107
|
+
- 01-title.md
|
|
108
|
+
- 02-the-idea.md
|
|
109
|
+
# - 03-detour.md
|
|
110
|
+
- 04-code.md
|
|
111
|
+
---
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Cutting a slide for a shorter time slot is commenting out one line, not moving a file out of the folder.
|
|
115
|
+
|
|
116
|
+
`slides:` is what opts a folder in. Opting in absorbs the whole folder: every Markdown file it absorbs is removed from the site's pages, collection documents, and static files, so none of them is ever published on its own. This holds even for a file commented out of an explicit list; the list controls order and inclusion in the deck, not what gets absorbed. Without `slides:`, a folder holding an `index.md` presentation behaves exactly as it does today, and sibling files are left alone. This matters if the site applies `layout: presentation` through a broad `defaults:` rule, since that alone never triggers absorption.
|
|
117
|
+
|
|
62
118
|
## Editors and terminals
|
|
63
119
|
|
|
64
120
|
Put a kramdown inline attribute list immediately after a fenced block. Attribute values must be quoted; class and ID tokens such as `.editor` and `#order-total` do not need quotes. `.editor` renders a syntax-highlighted editor window; `.terminal` renders a terminal window.
|
data/assets/css/presentation.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-slide-sans:"Atkinson Hyperlegible Next", "Avenir Next", "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;--font-slide-mono:"Atkinson Hyperlegible Mono", "JetBrains Mono", "SFMono-Regular", Consolas, monospace;--slide-canvas-width:1920px;--slide-canvas-height:1080px;--slide-radius:28px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.block{display:block}.grid{display:grid}.hidden{display:none}}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-next.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Next;font-style:italic;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-next-italic.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-mono.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:italic;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-mono-italic.woff2)format("woff2")}:root,[data-theme=midnight]{--slide-bg:#10141d;--slide-fg:#f3f5f8;--slide-muted:#b4bdca;--slide-accent:#ff6b4a;--slide-accent-strong:#ff8a65;--slide-code-surface:#151b26;--slide-code-header:#1c2532;--slide-code-border:#364354;--slide-code-shadow:#0000006b;--slide-syntax-comment:#aab5c5;--slide-syntax-keyword:#ff9e7a;--slide-syntax-string:#b8d98e;--slide-syntax-number:#c6a9ff;--slide-syntax-function:#75c9db;--slide-syntax-operator:#f3f5f8;--slide-syntax-punctuation:#b9c4d2;--slide-syntax-name:#f3f5f8;--slide-syntax-variable:#f3f5f8;--slide-syntax-symbol:#75c9db;--slide-syntax-builtin:#75c9db;--slide-syntax-constant:#c6a9ff;--slide-syntax-error:#ff8675;--slide-highlight:#ff6b4a2b;--slide-focus:#ff9e7a;--slide-selection:#ff6b4a47;--slide-grid-line:#ffffff0e;--slide-spotlight:#ff6b4a26;--slide-gradient-start:#191d2c;--slide-gradient-end:#10141d;--slide-link:#9edbea;--slide-quote:#c8d1dc;--slide-dot-close:#ef806d;--slide-dot-minimize:#e6b566;--slide-dot-maximize:#87c899;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#ff6b4a1a;--slide-code-highlight-border:#ff6b4a;--slide-code-focus-surface:#ff9e7a1a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}[data-theme=minimal-light]{--slide-bg:#f4f1eb;--slide-fg:#1c2027;--slide-muted:#4d5660;--slide-accent:#bc3e27;--slide-accent-strong:#8d2919;--slide-code-surface:#e9e6df;--slide-code-header:#dedbd4;--slide-code-border:#c8c4bb;--slide-code-shadow:#302c242e;--slide-syntax-comment:#50564f;--slide-syntax-keyword:#9d3e28;--slide-syntax-string:#325c35;--slide-syntax-number:#6b4ca0;--slide-syntax-function:#12586a;--slide-syntax-operator:#343b45;--slide-syntax-punctuation:#4d5863;--slide-syntax-name:#1c2027;--slide-syntax-variable:#1c2027;--slide-syntax-symbol:#12586a;--slide-syntax-builtin:#12586a;--slide-syntax-constant:#6b4ca0;--slide-syntax-error:#a52121;--slide-highlight:#bc3e2721;--slide-focus:#9d3e28;--slide-selection:#bc3e2733;--slide-grid-line:#1c202714;--slide-spotlight:#bc3e271f;--slide-gradient-start:#fffdf8;--slide-gradient-end:#e9e5dc;--slide-link:#176d82;--slide-quote:#424a54;--slide-dot-close:#cf6753;--slide-dot-minimize:#c09243;--slide-dot-maximize:#579966;--slide-heading-font-size:72px;--slide-title-font-size:96px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#bc3e2712;--slide-code-highlight-border:#bc3e27;--slide-code-focus-surface:#9d3e2812;--slide-code-inset:#1c202714;--slide-surface-shine:#ffffff0f}[data-theme=minimal-dark]{--slide-bg:#171717;--slide-fg:#f4f1ea;--slide-muted:#bcb8b0;--slide-accent:#e6b566;--slide-accent-strong:#f2c57a;--slide-code-surface:#202020;--slide-code-header:#2b2b2a;--slide-code-border:#454441;--slide-code-shadow:#00000080;--slide-syntax-comment:#b2afa9;--slide-syntax-keyword:#f0bd75;--slide-syntax-string:#b8d094;--slide-syntax-number:#d1a9e8;--slide-syntax-function:#83ced1;--slide-syntax-operator:#f4f1ea;--slide-syntax-punctuation:#c5c1b9;--slide-syntax-name:#f4f1ea;--slide-syntax-variable:#f4f1ea;--slide-syntax-symbol:#83ced1;--slide-syntax-builtin:#83ced1;--slide-syntax-constant:#d1a9e8;--slide-syntax-error:#f28272;--slide-highlight:#e6b56629;--slide-focus:#f0bd75;--slide-selection:#e6b56640;--slide-grid-line:#ffffff0f;--slide-spotlight:#e6b56621;--slide-gradient-start:#25221e;--slide-gradient-end:#171717;--slide-link:#83ced1;--slide-quote:#d3cec4;--slide-dot-close:#d97762;--slide-dot-minimize:#d4a052;--slide-dot-maximize:#70ad7e;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#e6b5661a;--slide-code-highlight-border:#e6b566;--slide-code-focus-surface:#f0bd751a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}[data-theme=ruby]{--slide-bg:#210f18;--slide-fg:#fff4ed;--slide-muted:#dfbcc3;--slide-accent:#ff7a68;--slide-accent-strong:#ffb09a;--slide-code-surface:#2a1520;--slide-code-header:#3a1c2a;--slide-code-border:#613348;--slide-code-shadow:#0a020780;--slide-syntax-comment:#cba8b0;--slide-syntax-keyword:#ff9f80;--slide-syntax-string:#c6e19c;--slide-syntax-number:#d9afff;--slide-syntax-function:#8ed9e0;--slide-syntax-operator:#fff4ed;--slide-syntax-punctuation:#e0bdc3;--slide-syntax-name:#fff4ed;--slide-syntax-variable:#fff4ed;--slide-syntax-symbol:#8ed9e0;--slide-syntax-builtin:#8ed9e0;--slide-syntax-constant:#d9afff;--slide-syntax-error:#ff8176;--slide-highlight:#ff7a682e;--slide-focus:#ffb09a;--slide-selection:#ff7a6847;--slide-grid-line:#ffe5dc11;--slide-spotlight:#ff7a6829;--slide-gradient-start:#3a1828;--slide-gradient-end:#210f18;--slide-link:#8ed9e0;--slide-quote:#f1c7c9;--slide-dot-close:#f07769;--slide-dot-minimize:#dca25a;--slide-dot-maximize:#71b587;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#ff7a681a;--slide-code-highlight-border:#ff7a68;--slide-code-focus-surface:#ffb09a1a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}*{box-sizing:border-box}::selection{background:var(--slide-selection);color:var(--slide-fg)}html,body{min-height:100%;margin:0}body{background:var(--slide-bg);color:var(--slide-fg);font-family:var(--font-slide-sans);font-synthesis:none;text-rendering:optimizelegibility;font-weight:400}.presentation-root{background:var(--slide-bg);min-height:100dvh;color:var(--slide-fg)}.presentation-root:not(.js) .slide-deck{flex-direction:column;align-items:center;gap:2rem;width:100%;height:auto;min-height:0;padding:2rem;display:flex;overflow:visible}.presentation-root:not(.js) .slide{width:min(100%, var(--slide-canvas-width));aspect-ratio:16/9;height:auto;position:relative;overflow:visible}.presentation-root:not(.js) .slide-content{height:auto;min-height:0}.presentation-root.js .slide-deck{flex-direction:column;align-items:center;gap:2rem;width:100%;height:auto;min-height:0;padding:2rem;display:flex;overflow:visible}.presentation-root.js .slide{width:min(100%, var(--slide-canvas-width));aspect-ratio:16/9;height:auto;position:relative;overflow:visible}.presentation-root.js .slide-content{height:auto;min-height:0}.slide-deck{isolation:isolate;width:100vw;height:100dvh;min-height:0;position:relative;container-type:size}.slide{width:var(--slide-canvas-width);height:var(--slide-canvas-height);aspect-ratio:16/9;color:var(--slide-fg);background:var(--slide-bg);position:relative;overflow:hidden}@supports (width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px)))){.presentation-root.js{width:100vw;height:100dvh;min-height:0;overflow:hidden}.presentation-root.js .slide-deck{place-items:center;gap:0;width:100%;height:100%;min-height:0;padding:0;display:grid;overflow:hidden;container-type:size}.presentation-root.js .slide{--slide-scale:min(tan(atan2(100cqw, 1920px)), tan(atan2(100cqh, 1080px)));width:var(--slide-canvas-width);height:var(--slide-canvas-height);transform:translate(-50%, -50%) scale(var(--slide-scale,1));transform-origin:50%;margin:0;position:absolute;inset:50% auto auto 50%;overflow:hidden}.presentation-root.js .slide-content{height:100%}.presentation-root.js:not(.is-overview) .slide:not(.is-active),.presentation-root.js[data-overview-active=false] .slide:not(.is-active){visibility:hidden;pointer-events:none}}.slide-content{z-index:1;flex-direction:column;width:100%;height:100%;padding:108px 132px 100px;display:flex;position:relative}:where(.prose,.layout-split-code .pane){max-width:100%;font-size:var(--slide-body-font-size);letter-spacing:0;line-height:1.4}:where(.prose,.layout-split-code .pane) :where(h1,h2,h3,h4){letter-spacing:-.012em;text-wrap:balance;max-width:16ch;margin:0 0 32px;line-height:1.12}:where(.prose,.layout-split-code .pane) :where(h1,h2){font-size:var(--slide-heading-font-size);font-weight:600}:where(.prose,.layout-split-code .pane) h3{font-size:56px;font-weight:600}:where(.prose,.layout-split-code .pane) h4{font-size:44px;font-weight:600}:where(.prose,.layout-split-code .pane) p{max-width:40ch;margin:0 0 28px}:where(.prose,.layout-split-code .pane) strong{color:var(--slide-accent-strong);font-weight:700}:where(.prose,.layout-split-code .pane) em{color:var(--slide-quote)}:where(.prose,.layout-split-code .pane) a{color:var(--slide-link);text-underline-offset:6px;text-decoration:underline;text-decoration-thickness:3px}:where(.prose,.layout-split-code .pane) :where(ul,ol){max-width:34ch;margin:0 0 28px;padding-inline-start:1.25em}:where(.prose,.layout-split-code .pane) ul{list-style-type:disc}:where(.prose,.layout-split-code .pane) ol{list-style-type:decimal}:where(.prose,.layout-split-code .pane) ul ul{list-style-type:circle}:where(.prose,.layout-split-code .pane) ol ol{list-style-type:lower-alpha}:where(.prose,.layout-split-code .pane) li{margin-block:.25em;padding-inline-start:.2em}:where(.prose,.layout-split-code .pane) li::marker{color:var(--slide-accent)}:where(.prose,.layout-split-code .pane) code:not(pre code){border:1px solid var(--slide-code-border);background:var(--slide-code-surface);color:var(--slide-accent-strong);font-family:var(--font-slide-mono);font-variant-ligatures:none;border-radius:8px;padding:.12em .28em;font-size:.95em}:where(.prose,.layout-split-code .pane) blockquote{border-inline-start:6px solid var(--slide-accent);max-width:34ch;color:var(--slide-quote);margin:0 0 28px;padding:12px 28px;font-style:italic}:where(.prose,.layout-split-code .pane) img{object-fit:contain;border-radius:20px;max-width:100%;max-height:520px;display:block}:where(.prose,.layout-split-code .pane) table{border-collapse:collapse;margin-block:16px 28px;font-size:1em}:where(.prose,.layout-split-code .pane) th,:where(.prose,.layout-split-code .pane) td{border-bottom:2px solid var(--slide-code-border);text-align:start;padding:12px 20px}:where(.prose,.layout-split-code .pane) th{color:var(--slide-accent-strong);font-weight:700}.visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.slide{isolation:isolate}.slide:before,.slide:after{content:none;pointer-events:none;position:absolute}.slide:before{z-index:0;background:var(--slide-bg);inset:0}.bg-plain:before{content:"";background:var(--slide-bg)}.bg-gradient:before{content:"";background:linear-gradient(135deg, var(--slide-gradient-start), var(--slide-gradient-end) 64%)}.bg-grid:before{content:"";background-color:var(--slide-bg);background-image:linear-gradient(var(--slide-grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--slide-grid-line) 1px, transparent 1px);background-size:72px 72px}.bg-spotlight:before{content:"";background:radial-gradient(circle at 70% 18%, var(--slide-spotlight), transparent 42%), var(--slide-bg)}.bg-gradient:after,.bg-grid:after,.bg-spotlight:after{content:"";z-index:0;background:var(--slide-spotlight);filter:blur(90px);opacity:.5;border-radius:50%;width:70%;height:70%;top:-28%;right:-12%}.layout-title .slide-content{justify-content:center;padding-inline:180px}.layout-title .prose h1{max-width:12ch;font-size:var(--slide-title-font-size)}.layout-title .prose p{max-width:28ch;color:var(--slide-muted)}.layout-statement .slide-content{justify-content:center;padding-inline:180px}.layout-statement .prose{font-size:var(--slide-statement-font-size);letter-spacing:-.012em;line-height:1.12}.layout-statement .prose :where(h1,h2){font-size:var(--slide-statement-font-size);margin-block-end:36px;line-height:1.12}.layout-statement .prose p{font-size:var(--slide-body-font-size);letter-spacing:0;max-width:20ch;line-height:1.4}.layout-content .slide-content{justify-content:center}.layout-content .prose{max-width:920px}.layout-code .slide-content{justify-content:center;gap:50px}.layout-code .prose{flex:none}.visual,.stack{flex-direction:column;min-height:0;display:flex}.layout-code .visual{width:100%;max-width:none;min-height:0;margin-inline:auto}.layout-code-left .slide-content,.layout-code-right .slide-content,.layout-split-code .slide-content{justify-content:center}.panes{grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:center;gap:72px;width:100%;min-height:0;display:grid}.layout-code-left .panes{grid-template-columns:minmax(0,1.55fr) minmax(0,1fr)}.layout-code-right .panes{grid-template-columns:minmax(0,1fr) minmax(0,1.55fr)}.pane{min-width:0;min-height:0;max-height:100%}.pane.prose{max-width:640px}.layout-split-code .panes{align-items:stretch}.layout-split-code .pane{flex-direction:column;min-width:0;display:flex}.layout-split-code .pane>ul>li::marker{color:var(--slide-accent)}.layout-split-code .pane>ol>li::marker{color:var(--slide-accent)}.layout-code-result .slide-content{justify-content:center;gap:24px}.layout-code-result .prose{flex:none}.layout-code-result .prose :where(h1,h2){max-width:24ch}.layout-code-result .prose p{margin-bottom:16px}.layout-code-result .stack{flex-direction:column;gap:24px;min-height:0;display:flex}.stack{min-width:0}@media (max-width:900px){.presentation-root:not(.js) .slide-content{padding:clamp(24px,6vw,56px)}.presentation-root:not(.js) :is(.prose,.pane) :where(h1,h2){font-size:clamp(32px,7vw,64px)}.presentation-root:not(.js) :is(.prose,.pane) :where(h3,h4){font-size:clamp(26px,5vw,44px)}.presentation-root:not(.js) .prose{font-size:clamp(22px,4vw,40px)}.presentation-root:not(.js) .layout-code-left .panes,.presentation-root:not(.js) .layout-code-right .panes,.presentation-root:not(.js) .layout-split-code .panes{grid-template-columns:1fr;gap:40px}}@supports not ((width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px))))){@media (max-width:900px){.presentation-root.js .slide-content{padding:56px}.presentation-root.js .prose h1{font-size:clamp(48px,9vw,88px)}.presentation-root.js .prose{font-size:clamp(22px,4vw,40px)}.presentation-root.js .layout-code-left .panes,.presentation-root.js .layout-code-right .panes,.presentation-root.js .layout-split-code .panes{grid-template-columns:1fr;gap:40px}}}@supports (width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px)))){.presentation-root.js.is-overview .slide-deck,.presentation-root.js[data-overview-active=true] .slide-deck{grid-template-columns:repeat(auto-fit,minmax(min(360px,42vw),1fr));grid-auto-rows:max-content;align-content:start;align-items:start;gap:32px;padding:112px 48px 56px;display:grid;overflow:auto}.presentation-root.js.is-overview .slide,.presentation-root.js[data-overview-active=true] .slide{aspect-ratio:16/9;visibility:visible;border:2px solid var(--slide-code-border);width:100%;min-width:0;height:auto;box-shadow:0 12px 28px var(--slide-code-shadow);cursor:pointer;border-radius:16px;margin:0;position:relative;inset:auto;overflow:hidden;transform:none;container-type:inline-size}.presentation-root.js.is-overview .slide.is-active,.presentation-root.js[data-overview-active=true] .slide.is-active{border-color:var(--slide-accent);box-shadow:0 0 0 4px var(--slide-highlight), 0 12px 28px var(--slide-code-shadow)}.presentation-root.js.is-overview .slide-content,.presentation-root.js[data-overview-active=true] .slide-content{--overview-slide-scale:tan(atan2(100cqw, 1920px));width:1920px;height:1080px;transform:scale(var(--overview-slide-scale,1));transform-origin:0 0;padding:108px 132px 100px}.presentation-root.js.is-overview .slide:focus-visible,.presentation-root.js[data-overview-active=true] .slide:focus-visible{outline:5px solid var(--slide-focus);outline-offset:6px}}.code-window,.terminal-window{border:2px solid var(--slide-code-border);min-width:0;min-height:0;max-height:100%;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.code-window,.terminal-window{border:2px solid color-mix(in srgb, var(--slide-code-border), transparent 15%)}}.code-window,.terminal-window{border-radius:var(--slide-radius);background:var(--slide-code-surface);box-shadow:0 24px 60px var(--slide-code-shadow), inset 0 1px 0 var(--slide-code-inset);font-family:var(--font-slide-mono);font-size:var(--slide-code-font-size);font-variant-ligatures:none;flex-direction:column;font-weight:450;line-height:1.45;display:flex}.code-window figcaption,.terminal-window figcaption{z-index:2;max-width:70%;color:var(--slide-muted);font-family:var(--font-slide-sans);letter-spacing:.015em;text-overflow:ellipsis;white-space:nowrap;font-size:28px;font-weight:500;position:absolute;top:16px;left:50%;overflow:hidden;transform:translate(-50%)}.code-window-header,.terminal-window:before{border-bottom:2px solid var(--slide-code-border);background:var(--slide-code-header);flex:0 0 72px;align-items:center;height:72px;padding:0 28px;display:flex}.code-window-controls{gap:10px;display:flex}.code-window-controls i{background:var(--slide-muted);opacity:.65;border-radius:50%;width:14px;height:14px;display:block}.code-window-controls i:first-child{background:var(--slide-dot-close)}.code-window-controls i:nth-child(2){background:var(--slide-dot-minimize)}.code-window-controls i:last-child{background:var(--slide-dot-maximize)}.code-window figcaption{max-width:min(50%,max(0px,100% - 224px))}.code-window-language{max-width:max(0px,25% - 40px);color:var(--slide-muted);letter-spacing:.04em;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap;margin-inline-start:auto;font-size:24px;overflow:hidden}.code-window pre,.terminal-window pre{tab-size:2;scrollbar-width:thin;scrollbar-color:var(--slide-muted) transparent;min-height:0;margin:0;padding:32px 34px 36px;font-family:inherit;overflow:auto}.code-window code,.terminal-window code{color:var(--slide-fg);font:inherit;display:block}.code-window .line{white-space:pre;border-inline-start:4px solid #0000;min-height:1.45em;padding-inline:16px 20px;display:block}.code-window .line:before{content:attr(data-line-number);width:2.2em;color:var(--slide-muted);text-align:end;-webkit-user-select:none;user-select:none;margin-inline-end:.8em;font-size:.8em;display:inline-block}.code-window .line:not([data-line-number]):before{content:none}.code-window .line.is-highlighted{border-inline-start-color:var(--slide-code-highlight-border);background:var(--slide-code-highlight-surface)}.code-window.has-focus .line.is-focused{border-inline-start-color:var(--slide-focus);background:var(--slide-code-focus-surface);opacity:1}.code-window .hll,.code-window .highlight{background:var(--slide-code-highlight-surface)}.code-window .c,.code-window .ch,.code-window .cm,.code-window .c1,.code-window .cs{color:var(--slide-syntax-comment)}.code-window .k,.code-window .kd,.code-window .kn,.code-window .kp,.code-window .kr,.code-window .kt{color:var(--slide-syntax-keyword)}.code-window .s,.code-window .sa,.code-window .sb,.code-window .sc,.code-window .dl,.code-window .sd,.code-window .s1,.code-window .s2{color:var(--slide-syntax-string)}.code-window .m,.code-window .mb,.code-window .mf,.code-window .mh,.code-window .mi,.code-window .mo{color:var(--slide-syntax-number)}.code-window .nf,.code-window .fm,.code-window .nd{color:var(--slide-syntax-function)}:is(.highlighter-rouge,figure.highlight) .c,:is(.highlighter-rouge,figure.highlight) .ch,:is(.highlighter-rouge,figure.highlight) .cm,:is(.highlighter-rouge,figure.highlight) .c1,:is(.highlighter-rouge,figure.highlight) .cs{color:var(--slide-syntax-comment)}:is(.highlighter-rouge,figure.highlight) .k,:is(.highlighter-rouge,figure.highlight) .kd,:is(.highlighter-rouge,figure.highlight) .kn,:is(.highlighter-rouge,figure.highlight) .kp,:is(.highlighter-rouge,figure.highlight) .kr,:is(.highlighter-rouge,figure.highlight) .kt{color:var(--slide-syntax-keyword)}:is(.highlighter-rouge,figure.highlight) .s,:is(.highlighter-rouge,figure.highlight) .sa,:is(.highlighter-rouge,figure.highlight) .sb,:is(.highlighter-rouge,figure.highlight) .sc,:is(.highlighter-rouge,figure.highlight) .dl,:is(.highlighter-rouge,figure.highlight) .sd,:is(.highlighter-rouge,figure.highlight) .s1,:is(.highlighter-rouge,figure.highlight) .s2{color:var(--slide-syntax-string)}:is(.highlighter-rouge,figure.highlight) .m,:is(.highlighter-rouge,figure.highlight) .mb,:is(.highlighter-rouge,figure.highlight) .mf,:is(.highlighter-rouge,figure.highlight) .mh,:is(.highlighter-rouge,figure.highlight) .mi,:is(.highlighter-rouge,figure.highlight) .mo{color:var(--slide-syntax-number)}:is(.highlighter-rouge,figure.highlight) .nf,:is(.highlighter-rouge,figure.highlight) .fm,:is(.highlighter-rouge,figure.highlight) .nd{color:var(--slide-syntax-function)}.code-window .o,.code-window .ow,:is(.highlighter-rouge,figure.highlight) .o,:is(.highlighter-rouge,figure.highlight) .ow{color:var(--slide-syntax-operator)}.code-window .p,:is(.highlighter-rouge,figure.highlight) .p{color:var(--slide-syntax-punctuation)}.code-window .n,.code-window .na,.code-window .nv,.code-window .vc,.code-window .vg,.code-window .vi,.code-window .py,:is(.highlighter-rouge,figure.highlight) .n,:is(.highlighter-rouge,figure.highlight) .na,:is(.highlighter-rouge,figure.highlight) .nv,:is(.highlighter-rouge,figure.highlight) .vc,:is(.highlighter-rouge,figure.highlight) .vg,:is(.highlighter-rouge,figure.highlight) .vi,:is(.highlighter-rouge,figure.highlight) .py{color:var(--slide-syntax-name)}.code-window .nv,.code-window .vc,.code-window .vg,.code-window .vi,:is(.highlighter-rouge,figure.highlight) .nv,:is(.highlighter-rouge,figure.highlight) .vc,:is(.highlighter-rouge,figure.highlight) .vg,:is(.highlighter-rouge,figure.highlight) .vi{color:var(--slide-syntax-variable)}.code-window .x,.code-window .sx,.code-window .ni,.code-window .nl,.code-window .nt,:is(.highlighter-rouge,figure.highlight) .x,:is(.highlighter-rouge,figure.highlight) .sx,:is(.highlighter-rouge,figure.highlight) .ni,:is(.highlighter-rouge,figure.highlight) .nl,:is(.highlighter-rouge,figure.highlight) .nt,.code-window .ss,.code-window .sh,.code-window .si,.code-window .sr,:is(.highlighter-rouge,figure.highlight) .ss,:is(.highlighter-rouge,figure.highlight) .sh,:is(.highlighter-rouge,figure.highlight) .si,:is(.highlighter-rouge,figure.highlight) .sr{color:var(--slide-syntax-symbol)}.code-window .kv,.code-window .kc,.code-window .cp,.code-window .cd,.code-window .pi,:is(.highlighter-rouge,figure.highlight) .kv,:is(.highlighter-rouge,figure.highlight) .kc,:is(.highlighter-rouge,figure.highlight) .cp,:is(.highlighter-rouge,figure.highlight) .cd,:is(.highlighter-rouge,figure.highlight) .pi{color:var(--slide-syntax-keyword)}.code-window .il,:is(.highlighter-rouge,figure.highlight) .il{color:var(--slide-syntax-number)}.code-window .nb,.code-window .bp,:is(.highlighter-rouge,figure.highlight) .nb,:is(.highlighter-rouge,figure.highlight) .bp{color:var(--slide-syntax-builtin)}.code-window .nc,.code-window .no,.code-window .ne,.code-window .nn,.code-window .nx,:is(.highlighter-rouge,figure.highlight) .nc,:is(.highlighter-rouge,figure.highlight) .no,:is(.highlighter-rouge,figure.highlight) .ne,:is(.highlighter-rouge,figure.highlight) .nn,:is(.highlighter-rouge,figure.highlight) .nx{color:var(--slide-syntax-constant)}.code-window .err,:is(.highlighter-rouge,figure.highlight) .err{color:var(--slide-syntax-error);-webkit-text-decoration:wavy underline var(--slide-syntax-error);-webkit-text-decoration:wavy underline var(--slide-syntax-error);text-decoration:wavy underline var(--slide-syntax-error);text-underline-offset:4px}.highlighter-rouge,figure.highlight{min-width:0;font-family:var(--font-slide-mono)}.highlighter-rouge .highlight,figure.highlight{border:1px solid var(--slide-code-border);background:var(--slide-code-surface);box-shadow:inset 0 1px 0 var(--slide-code-inset);font-size:var(--slide-code-font-size);font-variant-ligatures:none;scrollbar-width:thin;scrollbar-color:var(--slide-muted) transparent;border-radius:16px;font-weight:450;line-height:1.45;overflow:auto}.highlighter-rouge pre,figure.highlight pre{margin:0;padding:28px 32px}.highlighter-rouge code,figure.highlight code{color:var(--slide-fg);font:inherit}.code-window.size-sm,.terminal-window.size-sm{font-size:32px}.code-window.size-md,.terminal-window.size-md{font-size:38px}.code-window.size-lg,.terminal-window.size-lg{font-size:42px}.code-window.size-sm .code-window-header,.terminal-window.size-sm:before{flex-basis:62px;height:62px}.code-window.size-lg .code-window-header,.terminal-window.size-lg:before{flex-basis:82px;height:82px}.terminal-window{background:var(--slide-code-surface)}.terminal-window:before{content:"";background:var(--slide-code-header)}.terminal-window pre{padding-top:28px}.terminal-window .tline{white-space:pre-wrap;min-height:1.45em;display:block}.terminal-window .prompt{color:var(--slide-accent-strong);font-weight:700}.terminal-window .cmd{color:var(--slide-link)}.terminal-window .tline:not(:has(.prompt)):not(:has(.cmd)){color:var(--slide-fg)}.presentation-chrome{z-index:20;color:var(--slide-muted);pointer-events:none;position:fixed;inset:0}.presentation-root:not(.js) .presentation-chrome{visibility:hidden;pointer-events:none}.slide-controls{pointer-events:auto;background:0 0;border:1px solid #0000;border-radius:12px;align-items:center;gap:8px;padding:8px;transition:background .2s,border-color .2s;display:flex;position:absolute;bottom:16px;right:24px}.slide-controls:hover,.slide-controls:focus-within{border-color:var(--slide-code-border);background:var(--slide-bg)}@supports (color:color-mix(in lab, red, red)){.slide-controls:hover,.slide-controls:focus-within{background:color-mix(in srgb, var(--slide-bg), transparent 12%)}}.slide-controls:hover,.slide-controls:focus-within{-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.slide-control{border:1px solid var(--slide-code-border);width:46px;height:38px;color:var(--slide-fg);cursor:pointer;font:inherit;opacity:0;pointer-events:none;background:0 0;border-radius:9px;place-items:center;line-height:1;transition:background .2s,color .2s,border-color .2s,opacity .2s,transform .2s;display:inline-grid;transform:translateY(4px)}.slide-controls:hover .slide-control,.slide-controls:focus-within .slide-control{opacity:1;pointer-events:auto;transform:none}.slide-control:hover{border-color:var(--slide-accent);background:var(--slide-highlight);color:var(--slide-accent-strong)}.slide-controls:hover .slide-control:disabled,.slide-controls:focus-within .slide-control:disabled{opacity:.35;cursor:default}.slide-counter{font-family:var(--font-slide-mono);letter-spacing:.04em;margin:0;font-size:17px}.slide-progress{width:100%;height:4px;accent-color:var(--slide-accent);opacity:.58;pointer-events:auto;position:absolute;bottom:0;left:0;right:0}.slide-progress::-webkit-progress-bar{background:var(--slide-code-border);border-radius:99px}.slide-progress::-webkit-progress-value{background:var(--slide-accent);border-radius:99px}.overview-hint{z-index:25;border:1px solid var(--slide-code-border);background:var(--slide-code-surface);color:var(--slide-muted);font-family:var(--font-slide-mono);border-radius:999px;margin:0;padding:10px 18px;font-size:14px;position:fixed;top:82px;left:50%;transform:translate(-50%)}.overview-hint[hidden]{display:none}.presentation-root.js.is-fullscreen .presentation-chrome,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome{opacity:0;transition:opacity .35s}.presentation-root.js.is-fullscreen .presentation-chrome:hover,.presentation-root.js.is-fullscreen .presentation-chrome:focus-within,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome:hover,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome:focus-within{opacity:1}.slide-control:focus-visible,.slide-progress:focus-visible{outline:4px solid var(--slide-focus);outline-offset:4px}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:0s!important;animation-duration:0s!important;animation-iteration-count:1!important}}.presentation-root.is-reduced-motion .presentation-chrome,.presentation-root[data-reduced-motion=true] .presentation-chrome{transition:none}@page{size:13.333in 7.5in;margin:0}@media print{*,:before,:after{transition:none!important;animation:none!important}html,body{width:13.333in;min-width:13.333in;background:var(--slide-bg)!important}body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.presentation-root,.presentation-root.js{width:13.333in!important;height:auto!important;min-height:0!important;overflow:visible!important}.presentation-chrome,.slide-progress,.overview-hint{display:none!important}.slide-deck,.presentation-root.js .slide-deck,.presentation-root.js.is-overview .slide-deck{width:13.333in!important;height:auto!important;min-height:0!important;padding:0!important;display:block!important;position:static!important;overflow:visible!important}.slide,.presentation-root.js .slide,.presentation-root.js.is-overview .slide{break-after:page;page-break-after:always;min-height:0;visibility:visible!important;width:13.333in!important;height:7.5in!important;box-shadow:none!important;cursor:default!important;border:0!important;border-radius:0!important;margin:0!important;display:block!important;position:relative!important;inset:auto!important;overflow:hidden!important;transform:none!important}.slide:last-child{break-after:auto;page-break-after:auto}.slide-content,.presentation-root.js.is-overview .slide-content{transform-origin:0 0;width:1920px!important;height:1080px!important;transform:scale(.66665)!important}.code-window .line,.terminal-window .tline{opacity:1!important}}
|
|
2
|
+
@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--font-slide-sans:"Atkinson Hyperlegible Next", "Avenir Next", "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;--font-slide-mono:"Atkinson Hyperlegible Mono", "JetBrains Mono", "SFMono-Regular", Consolas, monospace;--slide-canvas-width:1920px;--slide-canvas-height:1080px;--slide-radius:28px}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.block{display:block}.grid{display:grid}.hidden{display:none}}@font-face{font-family:Atkinson Hyperlegible Next;font-style:normal;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-next.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Next;font-style:italic;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-next-italic.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:normal;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-mono.woff2)format("woff2")}@font-face{font-family:Atkinson Hyperlegible Mono;font-style:italic;font-weight:200 800;font-display:swap;src:url(../fonts/atkinson-hyperlegible-mono-italic.woff2)format("woff2")}:root,[data-theme=midnight]{--slide-bg:#10141d;--slide-fg:#f3f5f8;--slide-muted:#b4bdca;--slide-accent:#ff6b4a;--slide-accent-strong:#ff8a65;--slide-code-surface:#151b26;--slide-code-header:#1c2532;--slide-code-border:#364354;--slide-code-shadow:#0000006b;--slide-syntax-comment:#aab5c5;--slide-syntax-keyword:#ff9e7a;--slide-syntax-string:#b8d98e;--slide-syntax-number:#c6a9ff;--slide-syntax-function:#75c9db;--slide-syntax-operator:#f3f5f8;--slide-syntax-punctuation:#b9c4d2;--slide-syntax-name:#f3f5f8;--slide-syntax-variable:#f3f5f8;--slide-syntax-symbol:#75c9db;--slide-syntax-builtin:#75c9db;--slide-syntax-constant:#c6a9ff;--slide-syntax-error:#ff8675;--slide-highlight:#ff6b4a2b;--slide-focus:#ff9e7a;--slide-selection:#ff6b4a47;--slide-grid-line:#ffffff0e;--slide-spotlight:#ff6b4a26;--slide-gradient-start:#191d2c;--slide-gradient-end:#10141d;--slide-link:#9edbea;--slide-quote:#c8d1dc;--slide-dot-close:#ef806d;--slide-dot-minimize:#e6b566;--slide-dot-maximize:#87c899;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#ff6b4a1a;--slide-code-highlight-border:#ff6b4a;--slide-code-focus-surface:#ff9e7a1a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}[data-theme=minimal-light]{--slide-bg:#f4f1eb;--slide-fg:#1c2027;--slide-muted:#4d5660;--slide-accent:#bc3e27;--slide-accent-strong:#8d2919;--slide-code-surface:#e9e6df;--slide-code-header:#dedbd4;--slide-code-border:#c8c4bb;--slide-code-shadow:#302c242e;--slide-syntax-comment:#50564f;--slide-syntax-keyword:#9d3e28;--slide-syntax-string:#325c35;--slide-syntax-number:#6b4ca0;--slide-syntax-function:#12586a;--slide-syntax-operator:#343b45;--slide-syntax-punctuation:#4d5863;--slide-syntax-name:#1c2027;--slide-syntax-variable:#1c2027;--slide-syntax-symbol:#12586a;--slide-syntax-builtin:#12586a;--slide-syntax-constant:#6b4ca0;--slide-syntax-error:#a52121;--slide-highlight:#bc3e2721;--slide-focus:#9d3e28;--slide-selection:#bc3e2733;--slide-grid-line:#1c202714;--slide-spotlight:#bc3e271f;--slide-gradient-start:#fffdf8;--slide-gradient-end:#e9e5dc;--slide-link:#176d82;--slide-quote:#424a54;--slide-dot-close:#cf6753;--slide-dot-minimize:#c09243;--slide-dot-maximize:#579966;--slide-heading-font-size:72px;--slide-title-font-size:96px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#bc3e2712;--slide-code-highlight-border:#bc3e27;--slide-code-focus-surface:#9d3e2812;--slide-code-inset:#1c202714;--slide-surface-shine:#ffffff0f}[data-theme=minimal-dark]{--slide-bg:#171717;--slide-fg:#f4f1ea;--slide-muted:#bcb8b0;--slide-accent:#e6b566;--slide-accent-strong:#f2c57a;--slide-code-surface:#202020;--slide-code-header:#2b2b2a;--slide-code-border:#454441;--slide-code-shadow:#00000080;--slide-syntax-comment:#b2afa9;--slide-syntax-keyword:#f0bd75;--slide-syntax-string:#b8d094;--slide-syntax-number:#d1a9e8;--slide-syntax-function:#83ced1;--slide-syntax-operator:#f4f1ea;--slide-syntax-punctuation:#c5c1b9;--slide-syntax-name:#f4f1ea;--slide-syntax-variable:#f4f1ea;--slide-syntax-symbol:#83ced1;--slide-syntax-builtin:#83ced1;--slide-syntax-constant:#d1a9e8;--slide-syntax-error:#f28272;--slide-highlight:#e6b56629;--slide-focus:#f0bd75;--slide-selection:#e6b56640;--slide-grid-line:#ffffff0f;--slide-spotlight:#e6b56621;--slide-gradient-start:#25221e;--slide-gradient-end:#171717;--slide-link:#83ced1;--slide-quote:#d3cec4;--slide-dot-close:#d97762;--slide-dot-minimize:#d4a052;--slide-dot-maximize:#70ad7e;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#e6b5661a;--slide-code-highlight-border:#e6b566;--slide-code-focus-surface:#f0bd751a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}[data-theme=ruby]{--slide-bg:#210f18;--slide-fg:#fff4ed;--slide-muted:#dfbcc3;--slide-accent:#ff7a68;--slide-accent-strong:#ffb09a;--slide-code-surface:#2a1520;--slide-code-header:#3a1c2a;--slide-code-border:#613348;--slide-code-shadow:#0a020780;--slide-syntax-comment:#cba8b0;--slide-syntax-keyword:#ff9f80;--slide-syntax-string:#c6e19c;--slide-syntax-number:#d9afff;--slide-syntax-function:#8ed9e0;--slide-syntax-operator:#fff4ed;--slide-syntax-punctuation:#e0bdc3;--slide-syntax-name:#fff4ed;--slide-syntax-variable:#fff4ed;--slide-syntax-symbol:#8ed9e0;--slide-syntax-builtin:#8ed9e0;--slide-syntax-constant:#d9afff;--slide-syntax-error:#ff8176;--slide-highlight:#ff7a682e;--slide-focus:#ffb09a;--slide-selection:#ff7a6847;--slide-grid-line:#ffe5dc11;--slide-spotlight:#ff7a6829;--slide-gradient-start:#3a1828;--slide-gradient-end:#210f18;--slide-link:#8ed9e0;--slide-quote:#f1c7c9;--slide-dot-close:#f07769;--slide-dot-minimize:#dca25a;--slide-dot-maximize:#71b587;--slide-heading-font-size:72px;--slide-title-font-size:108px;--slide-body-font-size:44px;--slide-statement-font-size:88px;--slide-code-font-size:38px;--slide-code-highlight-surface:#ff7a681a;--slide-code-highlight-border:#ff7a68;--slide-code-focus-surface:#ffb09a1a;--slide-code-inset:#ffffff0f;--slide-surface-shine:#ffffff0f}*{box-sizing:border-box}::selection{background:var(--slide-selection);color:var(--slide-fg)}html,body{min-height:100%;margin:0}body{background:var(--slide-bg);color:var(--slide-fg);font-family:var(--font-slide-sans);font-synthesis:none;text-rendering:optimizelegibility;font-weight:400}.presentation-root{background:var(--slide-bg);min-height:100dvh;color:var(--slide-fg)}.presentation-root:not(.js) .slide-deck{flex-direction:column;align-items:center;gap:2rem;width:100%;height:auto;min-height:0;padding:2rem;display:flex;overflow:visible}.presentation-root:not(.js) .slide{width:min(100%, var(--slide-canvas-width));aspect-ratio:16/9;height:auto;position:relative;overflow:visible}.presentation-root:not(.js) .slide-content{height:auto;min-height:0}.presentation-root.js .slide-deck{flex-direction:column;align-items:center;gap:2rem;width:100%;height:auto;min-height:0;padding:2rem;display:flex;overflow:visible}.presentation-root.js .slide{width:min(100%, var(--slide-canvas-width));aspect-ratio:16/9;height:auto;position:relative;overflow:visible}.presentation-root.js .slide-content{height:auto;min-height:0}.slide-deck{isolation:isolate;width:100vw;height:100dvh;min-height:0;position:relative;container-type:size}.slide{width:var(--slide-canvas-width);height:var(--slide-canvas-height);aspect-ratio:16/9;color:var(--slide-fg);background:var(--slide-bg);position:relative;overflow:hidden}@supports (width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px)))){.presentation-root.js{width:100vw;height:100dvh;min-height:0;overflow:hidden}.presentation-root.js .slide-deck{place-items:center;gap:0;width:100%;height:100%;min-height:0;padding:0;display:grid;overflow:hidden;container-type:size}.presentation-root.js .slide{--slide-scale:min(tan(atan2(100cqw, 1920px)), tan(atan2(100cqh, 1080px)));width:var(--slide-canvas-width);height:var(--slide-canvas-height);transform:translate(-50%, -50%) scale(var(--slide-scale,1));transform-origin:50%;margin:0;position:absolute;inset:50% auto auto 50%;overflow:hidden}.presentation-root.js .slide-content{height:100%}.presentation-root.js:not(.is-overview) .slide:not(.is-active),.presentation-root.js[data-overview-active=false] .slide:not(.is-active){visibility:hidden;pointer-events:none}}@supports (transform:scale(abs(-1))){.presentation-root.js .slide{--slide-scale:min(abs(tan(atan2(100cqw, 1920px))), abs(tan(atan2(100cqh, 1080px))))}}.slide-content{z-index:1;flex-direction:column;width:100%;height:100%;padding:108px 132px 100px;display:flex;position:relative}:where(.prose,.layout-split-code .pane){max-width:100%;font-size:var(--slide-body-font-size);letter-spacing:0;line-height:1.4}:where(.prose,.layout-split-code .pane) :where(h1,h2,h3,h4){letter-spacing:-.012em;text-wrap:balance;max-width:16ch;margin:0 0 32px;line-height:1.12}:where(.prose,.layout-split-code .pane) :where(h1,h2){font-size:var(--slide-heading-font-size);font-weight:600}:where(.prose,.layout-split-code .pane) h3{font-size:56px;font-weight:600}:where(.prose,.layout-split-code .pane) h4{font-size:44px;font-weight:600}:where(.prose,.layout-split-code .pane) p{max-width:40ch;margin:0 0 28px}:where(.prose,.layout-split-code .pane) strong{color:var(--slide-accent-strong);font-weight:700}:where(.prose,.layout-split-code .pane) em{color:var(--slide-quote)}:where(.prose,.layout-split-code .pane) a{color:var(--slide-link);text-underline-offset:6px;text-decoration:underline;text-decoration-thickness:3px}:where(.prose,.layout-split-code .pane) :where(ul,ol){max-width:34ch;margin:0 0 28px;padding-inline-start:1.25em}:where(.prose,.layout-split-code .pane) ul{list-style-type:disc}:where(.prose,.layout-split-code .pane) ol{list-style-type:decimal}:where(.prose,.layout-split-code .pane) ul ul{list-style-type:circle}:where(.prose,.layout-split-code .pane) ol ol{list-style-type:lower-alpha}:where(.prose,.layout-split-code .pane) li{margin-block:.25em;padding-inline-start:.2em}:where(.prose,.layout-split-code .pane) li::marker{color:var(--slide-accent)}:where(.prose,.layout-split-code .pane) code:not(pre code){border:1px solid var(--slide-code-border);background:var(--slide-code-surface);color:var(--slide-accent-strong);font-family:var(--font-slide-mono);font-variant-ligatures:none;border-radius:8px;padding:.12em .28em;font-size:.95em}:where(.prose,.layout-split-code .pane) blockquote{border-inline-start:6px solid var(--slide-accent);max-width:34ch;color:var(--slide-quote);margin:0 0 28px;padding:12px 28px;font-style:italic}:where(.prose,.layout-split-code .pane) img{object-fit:contain;border-radius:20px;max-width:100%;max-height:520px;display:block}:where(.prose,.layout-split-code .pane) table{border-collapse:collapse;margin-block:16px 28px;font-size:1em}:where(.prose,.layout-split-code .pane) th,:where(.prose,.layout-split-code .pane) td{border-bottom:2px solid var(--slide-code-border);text-align:start;padding:12px 20px}:where(.prose,.layout-split-code .pane) th{color:var(--slide-accent-strong);font-weight:700}.visually-hidden{clip:rect(0 0 0 0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.slide{isolation:isolate}.slide:before,.slide:after{content:none;pointer-events:none;position:absolute}.slide:before{z-index:0;background:var(--slide-bg);inset:0}.bg-plain:before{content:"";background:var(--slide-bg)}.bg-gradient:before{content:"";background:linear-gradient(135deg, var(--slide-gradient-start), var(--slide-gradient-end) 64%)}.bg-grid:before{content:"";background-color:var(--slide-bg);background-image:linear-gradient(var(--slide-grid-line) 1px, transparent 1px), linear-gradient(90deg, var(--slide-grid-line) 1px, transparent 1px);background-size:72px 72px}.bg-spotlight:before{content:"";background:radial-gradient(circle at 70% 18%, var(--slide-spotlight), transparent 42%), var(--slide-bg)}.bg-gradient:after,.bg-grid:after,.bg-spotlight:after{content:"";z-index:0;background:var(--slide-spotlight);filter:blur(90px);opacity:.5;border-radius:50%;width:70%;height:70%;top:-28%;right:-12%}.layout-title .slide-content{justify-content:center;padding-inline:180px}.layout-title .prose h1{max-width:12ch;font-size:var(--slide-title-font-size)}.layout-title .prose p{max-width:28ch;color:var(--slide-muted)}.layout-statement .slide-content{justify-content:center;padding-inline:180px}.layout-statement .prose{font-size:var(--slide-statement-font-size);letter-spacing:-.012em;line-height:1.12}.layout-statement .prose :where(h1,h2){font-size:var(--slide-statement-font-size);margin-block-end:36px;line-height:1.12}.layout-statement .prose p{font-size:var(--slide-body-font-size);letter-spacing:0;max-width:20ch;line-height:1.4}.layout-content .slide-content{justify-content:center}.layout-content .prose{max-width:920px}.layout-code .slide-content{justify-content:center;gap:50px}.layout-code .prose{flex:none}.visual,.stack{flex-direction:column;min-height:0;display:flex}.layout-code .visual{width:100%;max-width:none;min-height:0;margin-inline:auto}.layout-code-left .slide-content,.layout-code-right .slide-content,.layout-split-code .slide-content{justify-content:center}.panes{grid-template-rows:minmax(0,1fr);grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:center;gap:72px;width:100%;min-height:0;display:grid}.layout-code-left .panes{grid-template-columns:minmax(0,1.55fr) minmax(0,1fr)}.layout-code-right .panes{grid-template-columns:minmax(0,1fr) minmax(0,1.55fr)}.pane{min-width:0;min-height:0;max-height:100%}.pane.prose{max-width:640px}.layout-split-code .panes{align-items:stretch}.layout-split-code .pane{flex-direction:column;min-width:0;display:flex}.layout-split-code .pane>ul>li::marker{color:var(--slide-accent)}.layout-split-code .pane>ol>li::marker{color:var(--slide-accent)}.layout-code-result .slide-content{justify-content:center;gap:24px}.layout-code-result .prose{flex:none}.layout-code-result .prose :where(h1,h2){max-width:24ch}.layout-code-result .prose p{margin-bottom:16px}.layout-code-result .stack{flex-direction:column;gap:24px;min-height:0;display:flex}.stack{min-width:0}@media (max-width:900px){.presentation-root:not(.js) .slide-content{padding:clamp(24px,6vw,56px)}.presentation-root:not(.js) :is(.prose,.pane) :where(h1,h2){font-size:clamp(32px,7vw,64px)}.presentation-root:not(.js) :is(.prose,.pane) :where(h3,h4){font-size:clamp(26px,5vw,44px)}.presentation-root:not(.js) .prose{font-size:clamp(22px,4vw,40px)}.presentation-root:not(.js) .layout-code-left .panes,.presentation-root:not(.js) .layout-code-right .panes,.presentation-root:not(.js) .layout-split-code .panes{grid-template-columns:1fr;gap:40px}}@supports not ((width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px))))){@media (max-width:900px){.presentation-root.js .slide-content{padding:56px}.presentation-root.js .prose h1{font-size:clamp(48px,9vw,88px)}.presentation-root.js .prose{font-size:clamp(22px,4vw,40px)}.presentation-root.js .layout-code-left .panes,.presentation-root.js .layout-code-right .panes,.presentation-root.js .layout-split-code .panes{grid-template-columns:1fr;gap:40px}}}@supports (width:1cqw) and (height:1cqh) and (transform:scale(tan(atan2(1px, 1px)))){.presentation-root.js.is-overview .slide-deck,.presentation-root.js[data-overview-active=true] .slide-deck{grid-template-columns:repeat(auto-fit,minmax(min(360px,42vw),1fr));grid-auto-rows:max-content;align-content:start;align-items:start;gap:32px;padding:112px 48px 56px;display:grid;overflow:auto}.presentation-root.js.is-overview .slide,.presentation-root.js[data-overview-active=true] .slide{aspect-ratio:16/9;visibility:visible;border:2px solid var(--slide-code-border);width:100%;min-width:0;height:auto;box-shadow:0 12px 28px var(--slide-code-shadow);cursor:pointer;border-radius:16px;margin:0;position:relative;inset:auto;overflow:hidden;transform:none;container-type:inline-size}.presentation-root.js.is-overview .slide.is-active,.presentation-root.js[data-overview-active=true] .slide.is-active{border-color:var(--slide-accent);box-shadow:0 0 0 4px var(--slide-highlight), 0 12px 28px var(--slide-code-shadow)}.presentation-root.js.is-overview .slide-content,.presentation-root.js[data-overview-active=true] .slide-content{--overview-slide-scale:tan(atan2(100cqw, 1920px));width:1920px;height:1080px;transform:scale(var(--overview-slide-scale,1));transform-origin:0 0;padding:108px 132px 100px}.presentation-root.js.is-overview .slide:focus-visible,.presentation-root.js[data-overview-active=true] .slide:focus-visible{outline:5px solid var(--slide-focus);outline-offset:6px}}@supports (transform:scale(abs(-1))){.presentation-root.js.is-overview .slide-content,.presentation-root.js[data-overview-active=true] .slide-content{--overview-slide-scale:abs(tan(atan2(100cqw, 1920px)))}}.code-window,.terminal-window{border:2px solid var(--slide-code-border);min-width:0;min-height:0;max-height:100%;position:relative;overflow:hidden}@supports (color:color-mix(in lab, red, red)){.code-window,.terminal-window{border:2px solid color-mix(in srgb, var(--slide-code-border), transparent 15%)}}.code-window,.terminal-window{border-radius:var(--slide-radius);background:var(--slide-code-surface);box-shadow:0 24px 60px var(--slide-code-shadow), inset 0 1px 0 var(--slide-code-inset);font-family:var(--font-slide-mono);font-size:var(--slide-code-font-size);font-variant-ligatures:none;flex-direction:column;font-weight:450;line-height:1.45;display:flex}.code-window figcaption,.terminal-window figcaption{z-index:2;max-width:70%;color:var(--slide-muted);font-family:var(--font-slide-sans);letter-spacing:.015em;text-overflow:ellipsis;white-space:nowrap;font-size:28px;font-weight:500;position:absolute;top:16px;left:50%;overflow:hidden;transform:translate(-50%)}.code-window-header,.terminal-window:before{border-bottom:2px solid var(--slide-code-border);background:var(--slide-code-header);flex:0 0 72px;align-items:center;height:72px;padding:0 28px;display:flex}.code-window-controls{gap:10px;display:flex}.code-window-controls i{background:var(--slide-muted);opacity:.65;border-radius:50%;width:14px;height:14px;display:block}.code-window-controls i:first-child{background:var(--slide-dot-close)}.code-window-controls i:nth-child(2){background:var(--slide-dot-minimize)}.code-window-controls i:last-child{background:var(--slide-dot-maximize)}.code-window figcaption{max-width:min(50%,max(0px,100% - 224px))}.code-window-language{max-width:max(0px,25% - 40px);color:var(--slide-muted);letter-spacing:.04em;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap;margin-inline-start:auto;font-size:24px;overflow:hidden}.code-window pre,.terminal-window pre{tab-size:2;scrollbar-width:thin;scrollbar-color:var(--slide-muted) transparent;min-height:0;margin:0;padding:32px 34px 36px;font-family:inherit;overflow:auto}.code-window code,.terminal-window code{color:var(--slide-fg);font:inherit;display:block}.code-window .line{white-space:pre;border-inline-start:4px solid #0000;min-height:1.45em;padding-inline:16px 20px;display:block}.code-window .line:before{content:attr(data-line-number);width:2.2em;color:var(--slide-muted);text-align:end;-webkit-user-select:none;user-select:none;margin-inline-end:.8em;font-size:.8em;display:inline-block}.code-window .line:not([data-line-number]):before{content:none}.code-window .line.is-highlighted{border-inline-start-color:var(--slide-code-highlight-border);background:var(--slide-code-highlight-surface)}.code-window.has-focus .line.is-focused{border-inline-start-color:var(--slide-focus);background:var(--slide-code-focus-surface);opacity:1}.code-window .hll,.code-window .highlight{background:var(--slide-code-highlight-surface)}.code-window .c,.code-window .ch,.code-window .cm,.code-window .c1,.code-window .cs{color:var(--slide-syntax-comment)}.code-window .k,.code-window .kd,.code-window .kn,.code-window .kp,.code-window .kr,.code-window .kt{color:var(--slide-syntax-keyword)}.code-window .s,.code-window .sa,.code-window .sb,.code-window .sc,.code-window .dl,.code-window .sd,.code-window .s1,.code-window .s2{color:var(--slide-syntax-string)}.code-window .m,.code-window .mb,.code-window .mf,.code-window .mh,.code-window .mi,.code-window .mo{color:var(--slide-syntax-number)}.code-window .nf,.code-window .fm,.code-window .nd{color:var(--slide-syntax-function)}:is(.highlighter-rouge,figure.highlight) .c,:is(.highlighter-rouge,figure.highlight) .ch,:is(.highlighter-rouge,figure.highlight) .cm,:is(.highlighter-rouge,figure.highlight) .c1,:is(.highlighter-rouge,figure.highlight) .cs{color:var(--slide-syntax-comment)}:is(.highlighter-rouge,figure.highlight) .k,:is(.highlighter-rouge,figure.highlight) .kd,:is(.highlighter-rouge,figure.highlight) .kn,:is(.highlighter-rouge,figure.highlight) .kp,:is(.highlighter-rouge,figure.highlight) .kr,:is(.highlighter-rouge,figure.highlight) .kt{color:var(--slide-syntax-keyword)}:is(.highlighter-rouge,figure.highlight) .s,:is(.highlighter-rouge,figure.highlight) .sa,:is(.highlighter-rouge,figure.highlight) .sb,:is(.highlighter-rouge,figure.highlight) .sc,:is(.highlighter-rouge,figure.highlight) .dl,:is(.highlighter-rouge,figure.highlight) .sd,:is(.highlighter-rouge,figure.highlight) .s1,:is(.highlighter-rouge,figure.highlight) .s2{color:var(--slide-syntax-string)}:is(.highlighter-rouge,figure.highlight) .m,:is(.highlighter-rouge,figure.highlight) .mb,:is(.highlighter-rouge,figure.highlight) .mf,:is(.highlighter-rouge,figure.highlight) .mh,:is(.highlighter-rouge,figure.highlight) .mi,:is(.highlighter-rouge,figure.highlight) .mo{color:var(--slide-syntax-number)}:is(.highlighter-rouge,figure.highlight) .nf,:is(.highlighter-rouge,figure.highlight) .fm,:is(.highlighter-rouge,figure.highlight) .nd{color:var(--slide-syntax-function)}.code-window .o,.code-window .ow,:is(.highlighter-rouge,figure.highlight) .o,:is(.highlighter-rouge,figure.highlight) .ow{color:var(--slide-syntax-operator)}.code-window .p,:is(.highlighter-rouge,figure.highlight) .p{color:var(--slide-syntax-punctuation)}.code-window .n,.code-window .na,.code-window .nv,.code-window .vc,.code-window .vg,.code-window .vi,.code-window .py,:is(.highlighter-rouge,figure.highlight) .n,:is(.highlighter-rouge,figure.highlight) .na,:is(.highlighter-rouge,figure.highlight) .nv,:is(.highlighter-rouge,figure.highlight) .vc,:is(.highlighter-rouge,figure.highlight) .vg,:is(.highlighter-rouge,figure.highlight) .vi,:is(.highlighter-rouge,figure.highlight) .py{color:var(--slide-syntax-name)}.code-window .nv,.code-window .vc,.code-window .vg,.code-window .vi,:is(.highlighter-rouge,figure.highlight) .nv,:is(.highlighter-rouge,figure.highlight) .vc,:is(.highlighter-rouge,figure.highlight) .vg,:is(.highlighter-rouge,figure.highlight) .vi{color:var(--slide-syntax-variable)}.code-window .x,.code-window .sx,.code-window .ni,.code-window .nl,.code-window .nt,:is(.highlighter-rouge,figure.highlight) .x,:is(.highlighter-rouge,figure.highlight) .sx,:is(.highlighter-rouge,figure.highlight) .ni,:is(.highlighter-rouge,figure.highlight) .nl,:is(.highlighter-rouge,figure.highlight) .nt,.code-window .ss,.code-window .sh,.code-window .si,.code-window .sr,:is(.highlighter-rouge,figure.highlight) .ss,:is(.highlighter-rouge,figure.highlight) .sh,:is(.highlighter-rouge,figure.highlight) .si,:is(.highlighter-rouge,figure.highlight) .sr{color:var(--slide-syntax-symbol)}.code-window .kv,.code-window .kc,.code-window .cp,.code-window .cd,.code-window .pi,:is(.highlighter-rouge,figure.highlight) .kv,:is(.highlighter-rouge,figure.highlight) .kc,:is(.highlighter-rouge,figure.highlight) .cp,:is(.highlighter-rouge,figure.highlight) .cd,:is(.highlighter-rouge,figure.highlight) .pi{color:var(--slide-syntax-keyword)}.code-window .il,:is(.highlighter-rouge,figure.highlight) .il{color:var(--slide-syntax-number)}.code-window .nb,.code-window .bp,:is(.highlighter-rouge,figure.highlight) .nb,:is(.highlighter-rouge,figure.highlight) .bp{color:var(--slide-syntax-builtin)}.code-window .nc,.code-window .no,.code-window .ne,.code-window .nn,.code-window .nx,:is(.highlighter-rouge,figure.highlight) .nc,:is(.highlighter-rouge,figure.highlight) .no,:is(.highlighter-rouge,figure.highlight) .ne,:is(.highlighter-rouge,figure.highlight) .nn,:is(.highlighter-rouge,figure.highlight) .nx{color:var(--slide-syntax-constant)}.code-window .err,:is(.highlighter-rouge,figure.highlight) .err{color:var(--slide-syntax-error);-webkit-text-decoration:wavy underline var(--slide-syntax-error);-webkit-text-decoration:wavy underline var(--slide-syntax-error);text-decoration:wavy underline var(--slide-syntax-error);text-underline-offset:4px}.highlighter-rouge,figure.highlight{min-width:0;font-family:var(--font-slide-mono)}.highlighter-rouge .highlight,figure.highlight{border:1px solid var(--slide-code-border);background:var(--slide-code-surface);box-shadow:inset 0 1px 0 var(--slide-code-inset);font-size:var(--slide-code-font-size);font-variant-ligatures:none;scrollbar-width:thin;scrollbar-color:var(--slide-muted) transparent;border-radius:16px;font-weight:450;line-height:1.45;overflow:auto}.highlighter-rouge pre,figure.highlight pre{margin:0;padding:28px 32px}.highlighter-rouge code,figure.highlight code{color:var(--slide-fg);font:inherit}.code-window.size-sm,.terminal-window.size-sm{font-size:32px}.code-window.size-md,.terminal-window.size-md{font-size:38px}.code-window.size-lg,.terminal-window.size-lg{font-size:42px}.code-window.size-sm .code-window-header,.terminal-window.size-sm:before{flex-basis:62px;height:62px}.code-window.size-lg .code-window-header,.terminal-window.size-lg:before{flex-basis:82px;height:82px}.terminal-window{background:var(--slide-code-surface)}.terminal-window:before{content:"";background:var(--slide-code-header)}.terminal-window pre{padding-top:28px}.terminal-window .tline{white-space:pre-wrap;min-height:1.45em;display:block}.terminal-window .prompt{color:var(--slide-accent-strong);font-weight:700}.terminal-window .cmd{color:var(--slide-link)}.terminal-window .tline:not(:has(.prompt)):not(:has(.cmd)){color:var(--slide-fg)}.presentation-chrome{z-index:20;color:var(--slide-muted);pointer-events:none;position:fixed;inset:0}.presentation-root:not(.js) .presentation-chrome{visibility:hidden;pointer-events:none}.slide-controls{pointer-events:auto;background:0 0;border:1px solid #0000;border-radius:12px;align-items:center;gap:8px;padding:8px;transition:background .2s,border-color .2s;display:flex;position:absolute;bottom:16px;right:24px}.slide-controls:hover,.slide-controls:focus-within{border-color:var(--slide-code-border);background:var(--slide-bg)}@supports (color:color-mix(in lab, red, red)){.slide-controls:hover,.slide-controls:focus-within{background:color-mix(in srgb, var(--slide-bg), transparent 12%)}}.slide-controls:hover,.slide-controls:focus-within{-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px)}.slide-control{border:1px solid var(--slide-code-border);width:46px;height:38px;color:var(--slide-fg);cursor:pointer;font:inherit;opacity:0;pointer-events:none;background:0 0;border-radius:9px;place-items:center;line-height:1;transition:background .2s,color .2s,border-color .2s,opacity .2s,transform .2s;display:inline-grid;transform:translateY(4px)}.slide-controls:hover .slide-control,.slide-controls:focus-within .slide-control{opacity:1;pointer-events:auto;transform:none}.slide-control:hover{border-color:var(--slide-accent);background:var(--slide-highlight);color:var(--slide-accent-strong)}.slide-controls:hover .slide-control:disabled,.slide-controls:focus-within .slide-control:disabled{opacity:.35;cursor:default}.slide-counter{font-family:var(--font-slide-mono);letter-spacing:.04em;margin:0;font-size:17px}.slide-progress{width:100%;height:4px;accent-color:var(--slide-accent);opacity:.58;pointer-events:auto;position:absolute;bottom:0;left:0;right:0}.slide-progress::-webkit-progress-bar{background:var(--slide-code-border);border-radius:99px}.slide-progress::-webkit-progress-value{background:var(--slide-accent);border-radius:99px}.overview-hint{z-index:25;border:1px solid var(--slide-code-border);background:var(--slide-code-surface);color:var(--slide-muted);font-family:var(--font-slide-mono);border-radius:999px;margin:0;padding:10px 18px;font-size:14px;position:fixed;top:82px;left:50%;transform:translate(-50%)}.overview-hint[hidden]{display:none}.presentation-root.js.is-fullscreen .presentation-chrome,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome{opacity:0;transition:opacity .35s}.presentation-root.js.is-fullscreen .presentation-chrome:hover,.presentation-root.js.is-fullscreen .presentation-chrome:focus-within,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome:hover,.presentation-root.js[data-fullscreen-active=true] .presentation-chrome:focus-within{opacity:1}.slide-control:focus-visible,.slide-progress:focus-visible{outline:4px solid var(--slide-focus);outline-offset:4px}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:0s!important;animation-duration:0s!important;animation-iteration-count:1!important}}.presentation-root.is-reduced-motion .presentation-chrome,.presentation-root[data-reduced-motion=true] .presentation-chrome{transition:none}@page{size:13.333in 7.5in;margin:0}@media print{*,:before,:after{transition:none!important;animation:none!important}html,body{width:13.333in;min-width:13.333in;background:var(--slide-bg)!important}body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.presentation-root,.presentation-root.js{width:13.333in!important;height:auto!important;min-height:0!important;overflow:visible!important}.presentation-chrome,.slide-progress,.overview-hint{display:none!important}.slide-deck,.presentation-root.js .slide-deck,.presentation-root.js.is-overview .slide-deck{width:13.333in!important;height:auto!important;min-height:0!important;padding:0!important;display:block!important;position:static!important;overflow:visible!important}.slide,.presentation-root.js .slide,.presentation-root.js.is-overview .slide{break-after:page;page-break-after:always;min-height:0;visibility:visible!important;width:13.333in!important;height:7.5in!important;box-shadow:none!important;cursor:default!important;border:0!important;border-radius:0!important;margin:0!important;display:block!important;position:relative!important;inset:auto!important;overflow:hidden!important;transform:none!important}.slide:last-child{break-after:auto;page-break-after:auto}.slide-content,.presentation-root.js.is-overview .slide-content{transform-origin:0 0;width:1920px!important;height:1080px!important;transform:scale(.66665)!important}.code-window .line,.terminal-window .tline{opacity:1!important}}
|
data/doc/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Jekyll Slides are documented here.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.2.0 — 2026-09-06
|
|
6
6
|
|
|
7
|
+
- Add folder decks: author a presentation as one Markdown file per slide, ordered by a numeric filename prefix and alphabetically otherwise, opted into with `slides:` in the entry file's front matter.
|
|
8
|
+
- Add slide file front matter (`layout`, `background`, `class`) as an alternative to the `<!-- ... -->` metadata comment.
|
|
9
|
+
- Fix slides rendering upside down in Safari: the CSS length-division trick used to scale slides to the viewport made Safari compute a negative scale factor, which is a 180 degree rotation.
|
|
10
|
+
- Fix `slides_count` for decks whose slides are generated by Liquid, which previously reported the pre-Liquid slide count.
|
|
7
11
|
- Add `bin/prepare_release` to verify the gem, regenerate Markdown API documentation and `llm.txt`, and build a versioned package with a SHA-256 checksum.
|
|
8
12
|
- Ship generated documentation while keeping YARD and release tools development-only.
|
|
9
13
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Class Jekyll::Slides::Deck <a id="class-Jekyll-Slides-Deck"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Object |
|
|
6
|
+
| **Defined in** | lib/jekyll/slides/deck.rb |
|
|
7
|
+
|
|
8
|
+
Represents one folder deck: an entry file plus the slide files a directory
|
|
9
|
+
holds. Reads slide files from disk and assembles them, with the entry body,
|
|
10
|
+
into one Markdown string ready for the existing `PresentationParser` pipeline.
|
|
11
|
+
Knows nothing about Jekyll page objects.
|
|
12
|
+
|
|
13
|
+
## Constants
|
|
14
|
+
### `OPTION_KEYS` <a id="constant-OPTION_KEYS"></a> <a id="OPTION_KEYS-constant"></a>
|
|
15
|
+
Top-level presentation options an author might mistakenly nest under
|
|
16
|
+
<code>slides:</code> instead of setting directly in front matter.
|
|
17
|
+
|
|
18
|
+
## Public Instance Methods
|
|
19
|
+
### `consumed_paths()` <a id="method-i-consumed_paths"></a> <a id="consumed_paths-instance_method"></a>
|
|
20
|
+
Every slide file the deck folder absorbs, so none is published on its own:
|
|
21
|
+
every discovered direct child, plus any explicitly listed path (which may live
|
|
22
|
+
in a subfolder). Order is not meaningful; this is a set for pruning, not the
|
|
23
|
+
render order. Identical to `slide_paths` when <code>slides:</code> is `true`.
|
|
24
|
+
|
|
25
|
+
### `content()` <a id="method-i-content"></a> <a id="content-instance_method"></a>
|
|
26
|
+
Not documented.
|
|
27
|
+
|
|
28
|
+
### `deck?()` <a id="method-i-deck-3F"></a> <a id="deck?-instance_method"></a>
|
|
29
|
+
rubocop:enable Metrics/ParameterLists
|
|
30
|
+
- **@return** [Boolean]
|
|
31
|
+
|
|
32
|
+
### `initialize(directory:, entry_path:, entry_body:, slides:, markdown_extensions:, warning: = nil, read_options: = {}, published: = nil)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
33
|
+
rubocop:disable Metrics/ParameterLists -- this exact keyword set is the deck's
|
|
34
|
+
public construction contract; the Jekyll integration layer wires it verbatim.
|
|
35
|
+
- **@return** [Deck] a new instance of Deck
|
|
36
|
+
|
|
37
|
+
### `slide_paths()` <a id="method-i-slide_paths"></a> <a id="slide_paths-instance_method"></a>
|
|
38
|
+
The ordered slide file paths that make up `content`: the explicit list in list
|
|
39
|
+
order when <code>slides:</code> names one, otherwise every discovered file in
|
|
40
|
+
natural order.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Module Jekyll::Slides::DeckAssembler <a id="module-Jekyll-Slides-DeckAssembler"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/jekyll/slides/deck_assembler.rb |
|
|
6
|
+
|
|
7
|
+
Finds folder-deck entries across a site's pages and output-collection
|
|
8
|
+
documents, assembles each into one Markdown string via `Deck`, and prunes the
|
|
9
|
+
slide files it consumed so they never appear in the built site. Runs once per
|
|
10
|
+
site, inside the +:site, :post_read+ hook, before Liquid rendering and before
|
|
11
|
+
<code>JekyllIntegration.process</code> computes `slides_count`.
|
|
12
|
+
|
|
13
|
+
## Public Class Methods
|
|
14
|
+
### `assemble(site)` <a id="method-c-assemble"></a> <a id="assemble-class_method"></a>
|
|
15
|
+
Assembles every folder deck the site's pages and output-collection documents
|
|
16
|
+
opt into. Mutates <code>site.pages</code>, each collection's `docs`, and
|
|
17
|
+
<code>site.static_files</code> in place to remove consumed slide files.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Module Jekyll::Slides::FrontMatter <a id="module-Jekyll-Slides-FrontMatter"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Defined in** | lib/jekyll/slides/front_matter.rb |
|
|
6
|
+
|
|
7
|
+
Splits a leading YAML front matter block off a Markdown source string.
|
|
8
|
+
|
|
9
|
+
## Public Class Methods
|
|
10
|
+
### `parse(text)` <a id="method-c-parse"></a> <a id="parse-class_method"></a>
|
|
11
|
+
Not documented.
|
|
12
|
+
|
|
13
|
+
### `split(source)` <a id="method-c-split"></a> <a id="split-class_method"></a>
|
|
14
|
+
Returns [values, body]. `values` is a Hash for a well-formed leading
|
|
15
|
+
<code>---</code> block; otherwise `values` is {} and `body` is `source`
|
|
16
|
+
unchanged.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Class Jekyll::Slides::SlideFile <a id="class-Jekyll-Slides-SlideFile"></a>
|
|
2
|
+
|
|
3
|
+
| | |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| **Inherits** | Object |
|
|
6
|
+
| **Defined in** | lib/jekyll/slides/slide_file.rb |
|
|
7
|
+
|
|
8
|
+
Reads one Markdown file and returns its slide sources. The file's own front
|
|
9
|
+
matter (`layout`, `background`, `class`) becomes a default metadata comment
|
|
10
|
+
applied to every slide the file contains; a slide's own comment wins key by
|
|
11
|
+
key.
|
|
12
|
+
|
|
13
|
+
## Public Instance Methods
|
|
14
|
+
### `initialize(path, warning: = nil, read_options: = {})` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
15
|
+
- **@return** [SlideFile] a new instance of SlideFile
|
|
16
|
+
|
|
17
|
+
### `slides()` <a id="method-i-slides"></a> <a id="slides-instance_method"></a>
|
|
18
|
+
Not documented.
|
|
@@ -35,6 +35,12 @@ Returns the value of attribute values.
|
|
|
35
35
|
### `parse(content, index: = 0, warning: = nil, components: = nil)` <a id="method-c-parse"></a> <a id="parse-class_method"></a>
|
|
36
36
|
Not documented.
|
|
37
37
|
|
|
38
|
+
### `split_comment(body, warning: = nil)` <a id="method-c-split_comment"></a> <a id="split_comment-class_method"></a>
|
|
39
|
+
Parses a leading "<!-- key: value -->" metadata comment off `body`. Returns
|
|
40
|
+
[body_without_comment, values]. A comment whose lines are not all "key:
|
|
41
|
+
value", or that carries no recognized key, is left in the body untouched.
|
|
42
|
+
Unknown keys warn only when `warning` is given.
|
|
43
|
+
|
|
38
44
|
## Public Instance Methods
|
|
39
45
|
### `initialize(content, index: = 0, warning: = nil)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
|
|
40
46
|
- **@return** [SlideMetadata] a new instance of SlideMetadata
|
|
@@ -8,5 +8,11 @@
|
|
|
8
8
|
### `escape(value)` <a id="method-c-escape"></a> <a id="escape-class_method"></a>
|
|
9
9
|
Not documented.
|
|
10
10
|
|
|
11
|
+
### `natural_sort_key(name)` <a id="method-c-natural_sort_key"></a> <a id="natural_sort_key-class_method"></a>
|
|
12
|
+
A comparable Array key for natural filename ordering: digit runs compare
|
|
13
|
+
numerically, text runs compare case-insensitively, and digit runs sort before
|
|
14
|
+
text runs. The name itself is appended as a stable tiebreaker so equal-value
|
|
15
|
+
keys (e.g. "01-a" and "1-a") still order deterministically.
|
|
16
|
+
|
|
11
17
|
### `warn(warning, message)` <a id="method-c-warn"></a> <a id="warn-class_method"></a>
|
|
12
18
|
Not documented.
|
data/doc/Jekyll/Slides.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| | |
|
|
4
4
|
| --- | --- |
|
|
5
|
-
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
|
5
|
+
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/deck.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/slide_file.rb, lib/jekyll/slides/front_matter.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/deck_assembler.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
|
6
6
|
|
|
7
7
|
Markdown presentations for Jekyll, with editor and terminal components.
|
|
8
8
|
|
|
@@ -38,6 +38,9 @@ Not documented.
|
|
|
38
38
|
- [Slides/Assets.md](Slides/Assets.md)
|
|
39
39
|
- [Slides/ComponentRenderer/RenderedDocument.md](Slides/ComponentRenderer/RenderedDocument.md)
|
|
40
40
|
- [Slides/ComponentRenderer.md](Slides/ComponentRenderer.md)
|
|
41
|
+
- [Slides/Deck.md](Slides/Deck.md)
|
|
42
|
+
- [Slides/DeckAssembler.md](Slides/DeckAssembler.md)
|
|
43
|
+
- [Slides/FrontMatter.md](Slides/FrontMatter.md)
|
|
41
44
|
- [Slides/JekyllIntegration.md](Slides/JekyllIntegration.md)
|
|
42
45
|
- [Slides/Layout.md](Slides/Layout.md)
|
|
43
46
|
- [Slides/Presentation.md](Slides/Presentation.md)
|
|
@@ -45,6 +48,7 @@ Not documented.
|
|
|
45
48
|
- [Slides/RangeParser.md](Slides/RangeParser.md)
|
|
46
49
|
- [Slides/Renderer.md](Slides/Renderer.md)
|
|
47
50
|
- [Slides/RougeRenderer.md](Slides/RougeRenderer.md)
|
|
51
|
+
- [Slides/SlideFile.md](Slides/SlideFile.md)
|
|
48
52
|
- [Slides/SlideMetadata.md](Slides/SlideMetadata.md)
|
|
49
53
|
- [Slides/SlideRenderer.md](Slides/SlideRenderer.md)
|
|
50
54
|
- [Slides/Support.md](Slides/Support.md)
|
data/doc/Jekyll.md
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| | |
|
|
4
4
|
| --- | --- |
|
|
5
|
-
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
|
5
|
+
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/deck.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/slide_file.rb, lib/jekyll/slides/front_matter.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/deck_assembler.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
data/doc/README.md
CHANGED
|
@@ -59,6 +59,62 @@ class: keynote
|
|
|
59
59
|
-->
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
## Deck folders
|
|
63
|
+
|
|
64
|
+
A deck can also be a folder of Markdown files, one file per slide, instead of one file with `---` separators:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
talks/my-talk/
|
|
68
|
+
index.md # deck front matter, opts in with `slides: true`
|
|
69
|
+
01-title.md
|
|
70
|
+
02-the-idea.md
|
|
71
|
+
03-code.md
|
|
72
|
+
diagram.png # ordinary static file, untouched
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The entry file must be named `index.md` and carry the deck's front matter, `layout: presentation`, and `slides: true`:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
# talks/my-talk/index.md
|
|
79
|
+
---
|
|
80
|
+
layout: presentation
|
|
81
|
+
title: My talk
|
|
82
|
+
slides: true
|
|
83
|
+
---
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`slides: true` discovers every other Markdown file that is a direct child of the folder (not recursive), skipping names starting with `_` or `.`, and skipping anything Jekyll itself withholds: a file dropped by the site's `exclude:` setting, or one whose front matter sets `published: false`. Naming a withheld file in an explicit list warns and skips it too, so a deck never publishes content the site holds back. Files are ordered by a natural sort of the filename: digit runs compare numerically and sort before letters, so `01-title.md`, `02-the-idea.md`, `10-outro.md`, then any file with no numeric prefix, sorted alphabetically. Each file holds one slide, or several separated by `---`, exactly as in a single-file deck. Body content in `index.md`, if any, becomes the deck's leading slides.
|
|
87
|
+
|
|
88
|
+
A slide file can carry its own YAML front matter with `layout`, `background`, and `class`, as an alternative to the `<!-- ... -->` comment:
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
---
|
|
92
|
+
layout: statement
|
|
93
|
+
background: spotlight
|
|
94
|
+
---
|
|
95
|
+
# A bold claim
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Front matter in the file applies to every slide it holds; a slide's own comment overrides it key by key.
|
|
99
|
+
|
|
100
|
+
Set `slides:` to a list instead of `true` to turn the entry file into a readable running order, in presentation order:
|
|
101
|
+
|
|
102
|
+
```yaml
|
|
103
|
+
---
|
|
104
|
+
layout: presentation
|
|
105
|
+
title: My talk
|
|
106
|
+
slides:
|
|
107
|
+
- 01-title.md
|
|
108
|
+
- 02-the-idea.md
|
|
109
|
+
# - 03-detour.md
|
|
110
|
+
- 04-code.md
|
|
111
|
+
---
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Cutting a slide for a shorter time slot is commenting out one line, not moving a file out of the folder.
|
|
115
|
+
|
|
116
|
+
`slides:` is what opts a folder in. Opting in absorbs the whole folder: every Markdown file it absorbs is removed from the site's pages, collection documents, and static files, so none of them is ever published on its own. This holds even for a file commented out of an explicit list; the list controls order and inclusion in the deck, not what gets absorbed. Without `slides:`, a folder holding an `index.md` presentation behaves exactly as it does today, and sibling files are left alone. This matters if the site applies `layout: presentation` through a broad `defaults:` rule, since that alone never triggers absorption.
|
|
117
|
+
|
|
62
118
|
## Editors and terminals
|
|
63
119
|
|
|
64
120
|
Put a kramdown inline attribute list immediately after a fenced block. Attribute values must be quoted; class and ID tokens such as `.editor` and `#order-total` do not need quotes. `.editor` renders a syntax-highlighted editor window; `.terminal` renders a terminal window.
|
data/doc/index.csv
CHANGED
|
@@ -11,7 +11,13 @@ Jekyll::Slides::Assets.asset_files,Method,Jekyll/Slides/Assets.md#method-c-asset
|
|
|
11
11
|
Jekyll::Slides::Assets.install,Method,Jekyll/Slides/Assets.md#method-c-install
|
|
12
12
|
Jekyll::Slides::Support,Module,Jekyll/Slides/Support.md
|
|
13
13
|
Jekyll::Slides::Support.escape,Method,Jekyll/Slides/Support.md#method-c-escape
|
|
14
|
+
Jekyll::Slides::Support.natural_sort_key,Method,Jekyll/Slides/Support.md#method-c-natural_sort_key
|
|
14
15
|
Jekyll::Slides::Support.warn,Method,Jekyll/Slides/Support.md#method-c-warn
|
|
16
|
+
Jekyll::Slides::FrontMatter,Module,Jekyll/Slides/FrontMatter.md
|
|
17
|
+
Jekyll::Slides::FrontMatter.parse,Method,Jekyll/Slides/FrontMatter.md#method-c-parse
|
|
18
|
+
Jekyll::Slides::FrontMatter.split,Method,Jekyll/Slides/FrontMatter.md#method-c-split
|
|
19
|
+
Jekyll::Slides::DeckAssembler,Module,Jekyll/Slides/DeckAssembler.md
|
|
20
|
+
Jekyll::Slides::DeckAssembler.assemble,Method,Jekyll/Slides/DeckAssembler.md#method-c-assemble
|
|
15
21
|
Jekyll::Slides::JekyllIntegration,Module,Jekyll/Slides/JekyllIntegration.md
|
|
16
22
|
Jekyll::Slides::JekyllIntegration.DEFAULTS,Constant,Jekyll/Slides/JekyllIntegration.md#constant-DEFAULTS
|
|
17
23
|
Jekyll::Slides::JekyllIntegration.THEMES,Constant,Jekyll/Slides/JekyllIntegration.md#constant-THEMES
|
|
@@ -20,12 +26,22 @@ Jekyll::Slides::JekyllIntegration.OPTION_KEYS,Constant,Jekyll/Slides/JekyllInteg
|
|
|
20
26
|
Jekyll::Slides::JekyllIntegration.install!,Method,Jekyll/Slides/JekyllIntegration.md#method-c-install-21
|
|
21
27
|
Jekyll::Slides::JekyllIntegration.process,Method,Jekyll/Slides/JekyllIntegration.md#method-c-process
|
|
22
28
|
Jekyll::Slides::JekyllIntegration.process_site,Method,Jekyll/Slides/JekyllIntegration.md#method-c-process_site
|
|
29
|
+
Jekyll::Slides::Deck,Class,Jekyll/Slides/Deck.md
|
|
30
|
+
Jekyll::Slides::Deck.OPTION_KEYS,Constant,Jekyll/Slides/Deck.md#constant-OPTION_KEYS
|
|
31
|
+
Jekyll::Slides::Deck.consumed_paths,Method,Jekyll/Slides/Deck.md#method-i-consumed_paths
|
|
32
|
+
Jekyll::Slides::Deck.content,Method,Jekyll/Slides/Deck.md#method-i-content
|
|
33
|
+
Jekyll::Slides::Deck.deck?,Method,Jekyll/Slides/Deck.md#method-i-deck-3F
|
|
34
|
+
Jekyll::Slides::Deck.initialize,Method,Jekyll/Slides/Deck.md#method-i-initialize
|
|
35
|
+
Jekyll::Slides::Deck.slide_paths,Method,Jekyll/Slides/Deck.md#method-i-slide_paths
|
|
23
36
|
Jekyll::Slides::Assets::SiteAssetFile,Class,Jekyll/Slides/Assets/SiteAssetFile.md
|
|
24
37
|
Jekyll::Slides::Assets::SiteAssetFile.write,Method,Jekyll/Slides/Assets/SiteAssetFile.md#method-i-write
|
|
25
38
|
Jekyll::Slides::Layout,Class,Jekyll/Slides/Layout.md
|
|
26
39
|
Jekyll::Slides::Layout.initialize,Method,Jekyll/Slides/Layout.md#method-i-initialize
|
|
27
40
|
Jekyll::Slides::Renderer,Class,Jekyll/Slides/Renderer.md
|
|
28
41
|
Jekyll::Slides::Renderer.convert,Method,Jekyll/Slides/Renderer.md#method-i-convert
|
|
42
|
+
Jekyll::Slides::SlideFile,Class,Jekyll/Slides/SlideFile.md
|
|
43
|
+
Jekyll::Slides::SlideFile.initialize,Method,Jekyll/Slides/SlideFile.md#method-i-initialize
|
|
44
|
+
Jekyll::Slides::SlideFile.slides,Method,Jekyll/Slides/SlideFile.md#method-i-slides
|
|
29
45
|
Jekyll::Slides::Presentation,Class,Jekyll/Slides/Presentation.md
|
|
30
46
|
Jekyll::Slides::Presentation.initialize,Method,Jekyll/Slides/Presentation.md#method-i-initialize
|
|
31
47
|
Jekyll::Slides::Presentation.render,Method,Jekyll/Slides/Presentation.md#method-i-render
|
|
@@ -45,6 +61,7 @@ Jekyll::Slides::SlideMetadata.KEYS,Constant,Jekyll/Slides/SlideMetadata.md#const
|
|
|
45
61
|
Jekyll::Slides::SlideMetadata.initialize,Method,Jekyll/Slides/SlideMetadata.md#method-i-initialize
|
|
46
62
|
Jekyll::Slides::SlideMetadata.parse,Method,Jekyll/Slides/SlideMetadata.md#method-i-parse
|
|
47
63
|
Jekyll::Slides::SlideMetadata.parse,Method,Jekyll/Slides/SlideMetadata.md#method-c-parse
|
|
64
|
+
Jekyll::Slides::SlideMetadata.split_comment,Method,Jekyll/Slides/SlideMetadata.md#method-c-split_comment
|
|
48
65
|
Jekyll::Slides::SlideMetadata.background,Attribute,Jekyll/Slides/SlideMetadata.md#attribute-i-background
|
|
49
66
|
Jekyll::Slides::SlideMetadata.content,Attribute,Jekyll/Slides/SlideMetadata.md#attribute-i-content
|
|
50
67
|
Jekyll::Slides::SlideMetadata.custom_class,Attribute,Jekyll/Slides/SlideMetadata.md#attribute-i-custom_class
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Slides
|
|
5
|
+
# Represents one folder deck: an entry file plus the slide files a
|
|
6
|
+
# directory holds. Reads slide files from disk and assembles them, with
|
|
7
|
+
# the entry body, into one Markdown string ready for the existing
|
|
8
|
+
# +PresentationParser+ pipeline. Knows nothing about Jekyll page objects.
|
|
9
|
+
class Deck
|
|
10
|
+
# Top-level presentation options an author might mistakenly nest under
|
|
11
|
+
# +slides:+ instead of setting directly in front matter.
|
|
12
|
+
OPTION_KEYS = %w[theme progress slide_numbers overview fullscreen aspect_ratio].freeze
|
|
13
|
+
|
|
14
|
+
# rubocop:disable Metrics/ParameterLists -- this exact keyword set is the deck's public
|
|
15
|
+
# construction contract; the Jekyll integration layer wires it verbatim.
|
|
16
|
+
def initialize(directory:, entry_path:, entry_body:, slides:, markdown_extensions:, warning: nil, read_options: {}, published: nil)
|
|
17
|
+
@directory = directory
|
|
18
|
+
@entry_path = File.expand_path(entry_path)
|
|
19
|
+
@entry_body = entry_body.to_s
|
|
20
|
+
@slides_option = slides
|
|
21
|
+
@markdown_extensions = Array(markdown_extensions).map { |ext| ext.to_s.downcase }
|
|
22
|
+
@warning = warning
|
|
23
|
+
@read_options = read_options
|
|
24
|
+
@published = published
|
|
25
|
+
@deck = resolve_deck?
|
|
26
|
+
end
|
|
27
|
+
# rubocop:enable Metrics/ParameterLists
|
|
28
|
+
|
|
29
|
+
def deck?
|
|
30
|
+
@deck
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# The ordered slide file paths that make up +content+: the explicit
|
|
34
|
+
# list in list order when +slides:+ names one, otherwise every
|
|
35
|
+
# discovered file in natural order.
|
|
36
|
+
def slide_paths
|
|
37
|
+
@slide_paths ||= deck? ? resolve_slide_paths : []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Every slide file the deck folder absorbs, so none is published on
|
|
41
|
+
# its own: every discovered direct child, plus any explicitly listed
|
|
42
|
+
# path (which may live in a subfolder). Order is not meaningful; this
|
|
43
|
+
# is a set for pruning, not the render order. Identical to
|
|
44
|
+
# +slide_paths+ when +slides:+ is +true+.
|
|
45
|
+
def consumed_paths
|
|
46
|
+
@consumed_paths ||= deck? ? resolve_consumed_paths : []
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def content
|
|
50
|
+
parts = []
|
|
51
|
+
parts << @entry_body unless blank?(@entry_body)
|
|
52
|
+
parts.concat(slide_files.flat_map(&:slides))
|
|
53
|
+
parts.join("\n\n---\n\n")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def blank?(text)
|
|
59
|
+
text.to_s.strip.empty?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def slide_files
|
|
63
|
+
@slide_files ||= slide_paths.map { |path| SlideFile.new(path, warning: @warning, read_options: @read_options) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resolve_deck?
|
|
67
|
+
case @slides_option
|
|
68
|
+
when true, Array
|
|
69
|
+
true
|
|
70
|
+
when false, nil
|
|
71
|
+
false
|
|
72
|
+
when Hash
|
|
73
|
+
Support.warn(@warning, "Invalid slides value #{@slides_option.inspect}; set the top-level slides options " \
|
|
74
|
+
"(#{OPTION_KEYS.join(', ')}) directly instead of nesting them under slides:; " \
|
|
75
|
+
"treating as a single-file deck")
|
|
76
|
+
false
|
|
77
|
+
else
|
|
78
|
+
Support.warn(@warning, "Invalid slides value #{@slides_option.inspect}; expected true, false, or a list of " \
|
|
79
|
+
"file names; treating as a single-file deck")
|
|
80
|
+
false
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_slide_paths
|
|
85
|
+
@slides_option == true ? discover_slide_paths : explicit_slide_paths
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resolve_consumed_paths
|
|
89
|
+
paths = discover_slide_paths
|
|
90
|
+
@slides_option.is_a?(Array) ? paths | explicit_slide_paths : paths
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def discover_slide_paths
|
|
94
|
+
paths = Dir.children(@directory).each_with_object([]) do |basename, found|
|
|
95
|
+
next if basename.start_with?("_", ".")
|
|
96
|
+
|
|
97
|
+
path = File.join(@directory, basename)
|
|
98
|
+
next unless File.file?(path)
|
|
99
|
+
next unless markdown_extension?(basename)
|
|
100
|
+
next if File.expand_path(path) == @entry_path
|
|
101
|
+
next unless published?(path)
|
|
102
|
+
|
|
103
|
+
found << path
|
|
104
|
+
end
|
|
105
|
+
paths.sort_by { |path| Support.natural_sort_key(File.basename(path)) }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def markdown_extension?(basename)
|
|
109
|
+
@markdown_extensions.include?(File.extname(basename).delete_prefix(".").downcase)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Memoized because both the rendering set and the pruning set ask for
|
|
113
|
+
# it, and resolve warns about missing or out-of-bounds names.
|
|
114
|
+
def explicit_slide_paths
|
|
115
|
+
@explicit_slide_paths ||= @slides_option.filter_map { |name| resolve(name) }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def resolve(name)
|
|
119
|
+
root = File.expand_path(@directory) + File::SEPARATOR
|
|
120
|
+
path = File.expand_path(File.join(@directory, name.to_s))
|
|
121
|
+
unless path.start_with?(root)
|
|
122
|
+
Support.warn(@warning, "Slide file #{name.inspect} escapes the deck directory; ignoring")
|
|
123
|
+
return nil
|
|
124
|
+
end
|
|
125
|
+
if path == @entry_path
|
|
126
|
+
Support.warn(@warning, "Slide file #{name.inspect} is the deck entry itself; ignoring")
|
|
127
|
+
return nil
|
|
128
|
+
end
|
|
129
|
+
unless File.file?(path)
|
|
130
|
+
Support.warn(@warning, "Slide file #{name.inspect} does not exist; ignoring")
|
|
131
|
+
return nil
|
|
132
|
+
end
|
|
133
|
+
unless published?(path)
|
|
134
|
+
Support.warn(@warning, "Slide file #{name.inspect} is excluded or not published; ignoring")
|
|
135
|
+
return nil
|
|
136
|
+
end
|
|
137
|
+
path
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Discovery reads the folder straight from disk, which bypasses the
|
|
141
|
+
# filtering Jekyll already applied: files its exclude: config drops and
|
|
142
|
+
# files whose front matter sets published: false are never read. Absorbing
|
|
143
|
+
# one would publish content the author withheld, inside the deck.
|
|
144
|
+
def published?(path)
|
|
145
|
+
return true unless @published
|
|
146
|
+
|
|
147
|
+
@published.call(File.expand_path(path))
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "set"
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module Slides
|
|
8
|
+
# Finds folder-deck entries across a site's pages and output-collection
|
|
9
|
+
# documents, assembles each into one Markdown string via +Deck+, and
|
|
10
|
+
# prunes the slide files it consumed so they never appear in the built
|
|
11
|
+
# site. Runs once per site, inside the +:site, :post_read+ hook, before
|
|
12
|
+
# Liquid rendering and before +JekyllIntegration.process+ computes
|
|
13
|
+
# +slides_count+.
|
|
14
|
+
module DeckAssembler
|
|
15
|
+
class << self
|
|
16
|
+
# Assembles every folder deck the site's pages and output-collection
|
|
17
|
+
# documents opt into. Mutates +site.pages+, each collection's
|
|
18
|
+
# +docs+, and +site.static_files+ in place to remove consumed slide
|
|
19
|
+
# files.
|
|
20
|
+
def assemble(site)
|
|
21
|
+
published = published_paths(site)
|
|
22
|
+
entry_candidates(site).each { |entry| assemble_entry(site, entry, published) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Every source path Jekyll actually read. A file its exclude: config
|
|
28
|
+
# drops, or one whose front matter sets published: false, is absent
|
|
29
|
+
# here, and a deck must not absorb it. Snapshotted before any pruning,
|
|
30
|
+
# since pruning removes the very items this is built from.
|
|
31
|
+
def published_paths(site)
|
|
32
|
+
items = site.pages + site.collections.values.flat_map(&:docs) + site.static_files
|
|
33
|
+
items.to_set { |item| absolute_path(site, item) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def entry_candidates(site)
|
|
37
|
+
collection_documents = site.collections.values.flat_map(&:docs)
|
|
38
|
+
site.pages + collection_documents
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def assemble_entry(site, entry, published)
|
|
42
|
+
return unless presentation_entry?(entry)
|
|
43
|
+
|
|
44
|
+
slides_option = entry.data["slides"]
|
|
45
|
+
return unless slides_option
|
|
46
|
+
|
|
47
|
+
entry_path = absolute_path(site, entry)
|
|
48
|
+
unless index_entry?(entry_path, markdown_extensions(site))
|
|
49
|
+
warn_entry(site, entry_path, "the entry of a deck folder must be named index.<ext>; leaving as a single-file deck")
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
finish_assembly(site, entry, entry_path, build_deck(site, entry, entry_path, published))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def finish_assembly(site, entry, entry_path, deck)
|
|
57
|
+
return unless deck.deck?
|
|
58
|
+
|
|
59
|
+
entry.content = deck.content
|
|
60
|
+
# Pruning does not depend on anything being selected. Opting in
|
|
61
|
+
# absorbs the whole folder, so a file left out of an explicit
|
|
62
|
+
# running order is cut from the deck rather than published alone.
|
|
63
|
+
prune(site, deck.consumed_paths)
|
|
64
|
+
register_dependencies(site, entry_path, deck.slide_paths)
|
|
65
|
+
log_deck(site, entry_path, deck.slide_paths) unless deck.slide_paths.empty?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def presentation_entry?(entry)
|
|
69
|
+
entry.respond_to?(:data) && entry.data.is_a?(Hash) && entry.data["layout"].to_s == "presentation"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def markdown_extensions(site)
|
|
73
|
+
site.config["markdown_ext"].to_s.split(",")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def index_entry?(path, extensions)
|
|
77
|
+
basename = File.basename(path)
|
|
78
|
+
extname = File.extname(basename)
|
|
79
|
+
return false if extname.empty?
|
|
80
|
+
|
|
81
|
+
base = basename.delete_suffix(extname)
|
|
82
|
+
ext = extname.delete_prefix(".").downcase
|
|
83
|
+
base.casecmp("index").zero? && extensions.any? { |candidate| candidate.to_s.downcase == ext }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_deck(site, entry, entry_path, published)
|
|
87
|
+
Deck.new(
|
|
88
|
+
directory: File.dirname(entry_path),
|
|
89
|
+
entry_path: entry_path,
|
|
90
|
+
entry_body: entry.content,
|
|
91
|
+
slides: entry.data["slides"],
|
|
92
|
+
markdown_extensions: markdown_extensions(site),
|
|
93
|
+
warning: ->(message) { warn_entry(site, entry_path, message) },
|
|
94
|
+
read_options: Jekyll::Utils.merged_file_read_opts(site, {}),
|
|
95
|
+
published: ->(path) { published.include?(path) }
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Removes every consumed slide file from site.pages, each
|
|
100
|
+
# collection's docs, and site.static_files, matching on absolute
|
|
101
|
+
# source path. A Markdown file with no front matter is read by
|
|
102
|
+
# Jekyll as a StaticFile rather than a Page, so pruning static
|
|
103
|
+
# files is required, not optional.
|
|
104
|
+
def prune(site, paths)
|
|
105
|
+
return if paths.empty?
|
|
106
|
+
|
|
107
|
+
consumed = paths.map { |path| File.expand_path(path) }
|
|
108
|
+
site.pages.reject! { |page| consumed.include?(absolute_path(site, page)) }
|
|
109
|
+
site.collections.each_value do |collection|
|
|
110
|
+
collection.docs.reject! { |doc| consumed.include?(absolute_path(site, doc)) }
|
|
111
|
+
end
|
|
112
|
+
site.static_files.reject! { |file| consumed.include?(absolute_path(site, file)) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def register_dependencies(site, entry_path, slide_paths)
|
|
116
|
+
return unless site.respond_to?(:regenerator) && site.regenerator.respond_to?(:add_dependency)
|
|
117
|
+
|
|
118
|
+
slide_paths.each { |path| site.regenerator.add_dependency(entry_path, path) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def log_deck(site, entry_path, slide_paths)
|
|
122
|
+
basenames = slide_paths.map { |path| File.basename(path) }
|
|
123
|
+
Jekyll.logger.info(
|
|
124
|
+
"Jekyll Slides:",
|
|
125
|
+
"#{relative_path(site, entry_path)}: #{slide_paths.length} slide files (#{basenames.join(', ')})"
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def warn_entry(site, entry_path, message)
|
|
130
|
+
Jekyll.logger.warn("Jekyll Slides:", "#{relative_path(site, entry_path)}: #{message}")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Jekyll::Page#path is relative to the source; Jekyll::Document#path
|
|
134
|
+
# and Jekyll::StaticFile#path are already absolute. expand_path
|
|
135
|
+
# handles all three, since it ignores the base for a path that is
|
|
136
|
+
# already absolute.
|
|
137
|
+
def absolute_path(site, item)
|
|
138
|
+
File.expand_path(item.path.to_s, site.source)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def relative_path(site, path)
|
|
142
|
+
Pathname.new(path).relative_path_from(Pathname.new(site.source)).to_s
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "date"
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module Slides
|
|
8
|
+
# Splits a leading YAML front matter block off a Markdown source string.
|
|
9
|
+
module FrontMatter
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Returns [values, body]. +values+ is a Hash for a well-formed leading
|
|
13
|
+
# +---+ block; otherwise +values+ is {} and +body+ is +source+ unchanged.
|
|
14
|
+
def split(source)
|
|
15
|
+
source = source.to_s
|
|
16
|
+
return [{}, source] unless source.start_with?("---\n", "---\r\n")
|
|
17
|
+
|
|
18
|
+
lines = source.lines
|
|
19
|
+
closing = lines[1..]&.index { |line| line.chomp == "---" }
|
|
20
|
+
return [{}, source] unless closing
|
|
21
|
+
|
|
22
|
+
closing += 1
|
|
23
|
+
values = parse(lines[1...closing].join)
|
|
24
|
+
return [{}, source] unless values
|
|
25
|
+
|
|
26
|
+
[values, lines[(closing + 1)..]&.join.to_s]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def parse(text)
|
|
30
|
+
return {} if text.strip.empty?
|
|
31
|
+
|
|
32
|
+
parsed = YAML.safe_load(text, permitted_classes: [Date, Time, DateTime])
|
|
33
|
+
parsed.is_a?(Hash) ? parsed : nil
|
|
34
|
+
rescue Psych::Exception
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -4,6 +4,20 @@ module Jekyll
|
|
|
4
4
|
module Slides
|
|
5
5
|
class Renderer < Jekyll::Renderer
|
|
6
6
|
def convert(content)
|
|
7
|
+
# Liquid has already run by the time this fires (Jekyll::Renderer#run
|
|
8
|
+
# converts before place_in_layouts), so this is the true slide
|
|
9
|
+
# count, including slides a {% for %} loop generated.
|
|
10
|
+
#
|
|
11
|
+
# document.data alone is not enough: a Document reaches the layout
|
|
12
|
+
# through a live DocumentDrop that reads document.data on every
|
|
13
|
+
# access, so writing it here is enough. A Page reaches the layout
|
|
14
|
+
# through a plain Hash snapshot that Jekyll::Renderer#assign_pages!
|
|
15
|
+
# already built and stashed in payload["page"] before convert runs,
|
|
16
|
+
# so a Page's layout would keep seeing the old, pre-Liquid count
|
|
17
|
+
# unless payload["page"] itself is updated too.
|
|
18
|
+
count = PresentationParser.new(content).parse.length
|
|
19
|
+
document.data["slides_count"] = count
|
|
20
|
+
payload["page"]["slides_count"] = count
|
|
7
21
|
Presentation.new(
|
|
8
22
|
content,
|
|
9
23
|
warning: method(:warning),
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module Slides
|
|
5
|
+
# Reads one Markdown file and returns its slide sources. The file's own
|
|
6
|
+
# front matter (+layout+, +background+, +class+) becomes a default
|
|
7
|
+
# metadata comment applied to every slide the file contains; a slide's
|
|
8
|
+
# own comment wins key by key.
|
|
9
|
+
class SlideFile
|
|
10
|
+
def initialize(path, warning: nil, read_options: {})
|
|
11
|
+
@path = path
|
|
12
|
+
@warning = warning
|
|
13
|
+
@read_options = read_options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def slides
|
|
17
|
+
@slides ||= build_slides
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def build_slides
|
|
23
|
+
file_values, body = FrontMatter.split(File.read(@path, **@read_options))
|
|
24
|
+
defaults = file_defaults(file_values)
|
|
25
|
+
slide_sources = PresentationParser.new(body).parse
|
|
26
|
+
|
|
27
|
+
if slide_sources.empty?
|
|
28
|
+
Support.warn(@warning, "#{@path} produced no slides; skipping")
|
|
29
|
+
return []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
slide_sources.map { |slide| apply_defaults(slide, defaults) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def file_defaults(file_values)
|
|
36
|
+
file_values.each_with_object({}) do |(key, value), defaults|
|
|
37
|
+
if SlideMetadata::KEYS.include?(key)
|
|
38
|
+
defaults[key] = value
|
|
39
|
+
else
|
|
40
|
+
Support.warn(@warning, "Unknown slide file key #{key.inspect}; ignoring")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def apply_defaults(slide, defaults)
|
|
46
|
+
return slide if defaults.empty?
|
|
47
|
+
|
|
48
|
+
body, own_values = SlideMetadata.split_comment(slide)
|
|
49
|
+
merged = defaults.merge(own_values)
|
|
50
|
+
lines = SlideMetadata::KEYS.filter_map { |key| "#{key}: #{merged[key]}" if merged.key?(key) }
|
|
51
|
+
"<!--\n#{lines.join("\n")}\n-->\n#{body}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -13,6 +13,31 @@ module Jekyll
|
|
|
13
13
|
new(content, index: index, warning: warning).parse(components: components)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
# Parses a leading "<!-- key: value -->" metadata comment off +body+.
|
|
17
|
+
# Returns [body_without_comment, values]. A comment whose lines are not
|
|
18
|
+
# all "key: value", or that carries no recognized key, is left in the
|
|
19
|
+
# body untouched. Unknown keys warn only when +warning+ is given.
|
|
20
|
+
def self.split_comment(body, warning: nil)
|
|
21
|
+
match = body.match(/\A[ \t]*(<!--(.*?)-->)[ \t]*(?:\r?\n)?/m)
|
|
22
|
+
return [body, {}] unless match
|
|
23
|
+
|
|
24
|
+
values = {}
|
|
25
|
+
lines = match[2].to_s.lines.map(&:strip).reject(&:empty?)
|
|
26
|
+
parsed = lines.map { |line| line.match(/\A(\w[\w-]*)\s*:\s*(.*?)\z/) }
|
|
27
|
+
return [body, {}] unless parsed.all?
|
|
28
|
+
return [body, {}] unless parsed.any? { |match_data| KEYS.include?(match_data[1]) }
|
|
29
|
+
|
|
30
|
+
parsed.each do |match_data|
|
|
31
|
+
key = match_data[1]
|
|
32
|
+
if KEYS.include?(key)
|
|
33
|
+
values[key] = match_data[2].strip
|
|
34
|
+
else
|
|
35
|
+
Support.warn(warning, "Unknown slide metadata key #{key.inspect}; ignoring")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
[body[match.end(0)..].to_s, values]
|
|
39
|
+
end
|
|
40
|
+
|
|
16
41
|
def initialize(content, index: 0, warning: nil)
|
|
17
42
|
@original_content = content.to_s
|
|
18
43
|
@index = index
|
|
@@ -40,24 +65,7 @@ module Jekyll
|
|
|
40
65
|
private
|
|
41
66
|
|
|
42
67
|
def extract_comment(body)
|
|
43
|
-
|
|
44
|
-
return [body, {}] unless match
|
|
45
|
-
|
|
46
|
-
values = {}
|
|
47
|
-
lines = match[2].to_s.lines.map(&:strip).reject(&:empty?)
|
|
48
|
-
parsed = lines.map { |line| line.match(/\A(\w[\w-]*)\s*:\s*(.*?)\z/) }
|
|
49
|
-
return [body, {}] unless parsed.all?
|
|
50
|
-
return [body, {}] unless parsed.any? { |match_data| KEYS.include?(match_data[1]) }
|
|
51
|
-
|
|
52
|
-
parsed.each do |match_data|
|
|
53
|
-
key = match_data[1]
|
|
54
|
-
if KEYS.include?(key)
|
|
55
|
-
values[key] = match_data[2].strip
|
|
56
|
-
else
|
|
57
|
-
Support.warn(@warning, "Unknown slide metadata key #{key.inspect}; ignoring")
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
[body[match.end(0)..].to_s, values]
|
|
68
|
+
self.class.split_comment(body, warning: @warning)
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
def inferred_layout(components)
|
|
@@ -20,6 +20,20 @@ module Jekyll
|
|
|
20
20
|
def escape(value)
|
|
21
21
|
CGI.escapeHTML(value.to_s)
|
|
22
22
|
end
|
|
23
|
+
|
|
24
|
+
# A comparable Array key for natural filename ordering: digit runs
|
|
25
|
+
# compare numerically, text runs compare case-insensitively, and digit
|
|
26
|
+
# runs sort before text runs. The name itself is appended as a stable
|
|
27
|
+
# tiebreaker so equal-value keys (e.g. "01-a" and "1-a") still order
|
|
28
|
+
# deterministically.
|
|
29
|
+
def natural_sort_key(name)
|
|
30
|
+
name = name.to_s
|
|
31
|
+
key = name.scan(/\d+|\D+/).map do |run|
|
|
32
|
+
run.match?(/\A\d+\z/) ? [0, run.to_i, ""] : [1, 0, run.downcase]
|
|
33
|
+
end
|
|
34
|
+
key << [2, 0, name]
|
|
35
|
+
key
|
|
36
|
+
end
|
|
23
37
|
end
|
|
24
38
|
end
|
|
25
39
|
end
|
data/lib/jekyll/slides.rb
CHANGED
|
@@ -26,8 +26,12 @@ end
|
|
|
26
26
|
|
|
27
27
|
require_relative "slides/version"
|
|
28
28
|
require_relative "slides/support"
|
|
29
|
+
require_relative "slides/front_matter"
|
|
29
30
|
require_relative "slides/presentation_parser"
|
|
30
31
|
require_relative "slides/slide_metadata"
|
|
32
|
+
require_relative "slides/slide_file"
|
|
33
|
+
require_relative "slides/deck"
|
|
34
|
+
require_relative "slides/deck_assembler"
|
|
31
35
|
require_relative "slides/range_parser"
|
|
32
36
|
require_relative "slides/rouge_renderer"
|
|
33
37
|
require_relative "slides/component_renderer"
|
data/llm.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| | |
|
|
4
4
|
| --- | --- |
|
|
5
|
-
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
|
5
|
+
| **Defined in** | lib/jekyll/slides.rb, lib/jekyll/slides/deck.rb, lib/jekyll/slides/assets.rb, lib/jekyll/slides/layout.rb, lib/jekyll/slides/support.rb, lib/jekyll/slides/version.rb, lib/jekyll/slides/renderer.rb, lib/jekyll/slides/slide_file.rb, lib/jekyll/slides/front_matter.rb, lib/jekyll/slides/presentation.rb, lib/jekyll/slides/range_parser.rb, lib/jekyll/slides/deck_assembler.rb, lib/jekyll/slides/rouge_renderer.rb, lib/jekyll/slides/slide_metadata.rb, lib/jekyll/slides/slide_renderer.rb, lib/jekyll/slides/component_renderer.rb, lib/jekyll/slides/jekyll_integration.rb, lib/jekyll/slides/presentation_parser.rb |
|
|
6
6
|
|
|
7
7
|
Markdown presentations for Jekyll, with editor and terminal components.
|
|
8
8
|
|
|
@@ -38,6 +38,9 @@ Not documented.
|
|
|
38
38
|
- [Slides/Assets.md](doc/Jekyll/Slides/Assets.md)
|
|
39
39
|
- [Slides/ComponentRenderer/RenderedDocument.md](doc/Jekyll/Slides/ComponentRenderer/RenderedDocument.md)
|
|
40
40
|
- [Slides/ComponentRenderer.md](doc/Jekyll/Slides/ComponentRenderer.md)
|
|
41
|
+
- [Slides/Deck.md](doc/Jekyll/Slides/Deck.md)
|
|
42
|
+
- [Slides/DeckAssembler.md](doc/Jekyll/Slides/DeckAssembler.md)
|
|
43
|
+
- [Slides/FrontMatter.md](doc/Jekyll/Slides/FrontMatter.md)
|
|
41
44
|
- [Slides/JekyllIntegration.md](doc/Jekyll/Slides/JekyllIntegration.md)
|
|
42
45
|
- [Slides/Layout.md](doc/Jekyll/Slides/Layout.md)
|
|
43
46
|
- [Slides/Presentation.md](doc/Jekyll/Slides/Presentation.md)
|
|
@@ -45,6 +48,7 @@ Not documented.
|
|
|
45
48
|
- [Slides/RangeParser.md](doc/Jekyll/Slides/RangeParser.md)
|
|
46
49
|
- [Slides/Renderer.md](doc/Jekyll/Slides/Renderer.md)
|
|
47
50
|
- [Slides/RougeRenderer.md](doc/Jekyll/Slides/RougeRenderer.md)
|
|
51
|
+
- [Slides/SlideFile.md](doc/Jekyll/Slides/SlideFile.md)
|
|
48
52
|
- [Slides/SlideMetadata.md](doc/Jekyll/Slides/SlideMetadata.md)
|
|
49
53
|
- [Slides/SlideRenderer.md](doc/Jekyll/Slides/SlideRenderer.md)
|
|
50
54
|
- [Slides/Support.md](doc/Jekyll/Slides/Support.md)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-slides
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lucian Ghinda
|
|
@@ -118,6 +118,9 @@ files:
|
|
|
118
118
|
- doc/Jekyll/Slides/Assets/SiteAssetFile.md
|
|
119
119
|
- doc/Jekyll/Slides/ComponentRenderer.md
|
|
120
120
|
- doc/Jekyll/Slides/ComponentRenderer/RenderedDocument.md
|
|
121
|
+
- doc/Jekyll/Slides/Deck.md
|
|
122
|
+
- doc/Jekyll/Slides/DeckAssembler.md
|
|
123
|
+
- doc/Jekyll/Slides/FrontMatter.md
|
|
121
124
|
- doc/Jekyll/Slides/JekyllIntegration.md
|
|
122
125
|
- doc/Jekyll/Slides/Layout.md
|
|
123
126
|
- doc/Jekyll/Slides/Presentation.md
|
|
@@ -125,6 +128,7 @@ files:
|
|
|
125
128
|
- doc/Jekyll/Slides/RangeParser.md
|
|
126
129
|
- doc/Jekyll/Slides/Renderer.md
|
|
127
130
|
- doc/Jekyll/Slides/RougeRenderer.md
|
|
131
|
+
- doc/Jekyll/Slides/SlideFile.md
|
|
128
132
|
- doc/Jekyll/Slides/SlideMetadata.md
|
|
129
133
|
- doc/Jekyll/Slides/SlideRenderer.md
|
|
130
134
|
- doc/Jekyll/Slides/Support.md
|
|
@@ -134,6 +138,9 @@ files:
|
|
|
134
138
|
- lib/jekyll/slides.rb
|
|
135
139
|
- lib/jekyll/slides/assets.rb
|
|
136
140
|
- lib/jekyll/slides/component_renderer.rb
|
|
141
|
+
- lib/jekyll/slides/deck.rb
|
|
142
|
+
- lib/jekyll/slides/deck_assembler.rb
|
|
143
|
+
- lib/jekyll/slides/front_matter.rb
|
|
137
144
|
- lib/jekyll/slides/jekyll_integration.rb
|
|
138
145
|
- lib/jekyll/slides/layout.rb
|
|
139
146
|
- lib/jekyll/slides/presentation.rb
|
|
@@ -141,6 +148,7 @@ files:
|
|
|
141
148
|
- lib/jekyll/slides/range_parser.rb
|
|
142
149
|
- lib/jekyll/slides/renderer.rb
|
|
143
150
|
- lib/jekyll/slides/rouge_renderer.rb
|
|
151
|
+
- lib/jekyll/slides/slide_file.rb
|
|
144
152
|
- lib/jekyll/slides/slide_metadata.rb
|
|
145
153
|
- lib/jekyll/slides/slide_renderer.rb
|
|
146
154
|
- lib/jekyll/slides/support.rb
|