@xiee/utils 1.9.4 → 1.10.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.
- package/README.md +11 -0
- package/css/snap.css +98 -0
- package/css/snap.min.css +1 -0
- package/js/fold-output.js +24 -0
- package/js/fold-output.min.js +1 -0
- package/js/snap.js +193 -0
- package/js/snap.min.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,12 @@ blocks (`<pre>`) are folded. Other elements can also be folded via custom
|
|
|
83
83
|
options. See [this post](https://yihui.org/en/2023/09/code-folding/) for more
|
|
84
84
|
information.
|
|
85
85
|
|
|
86
|
+
## fold-output.js
|
|
87
|
+
|
|
88
|
+
Click on a code block of the class `language-*` to toggle the visibility of its
|
|
89
|
+
siblings on the page before the next `language-*` block. Click while holding the
|
|
90
|
+
`Alt` key to toggle siblings of all `language-*` blocks on the page.
|
|
91
|
+
|
|
86
92
|
## fullwidth.js
|
|
87
93
|
|
|
88
94
|
Find `<pre>`, `<table>`, and TOC (with ID `TableOfContents`) elements and add
|
|
@@ -157,6 +163,11 @@ KaTeX's auto-render extension.
|
|
|
157
163
|
Right-align a `<blockquote>` footer if the footer is a `<p>` that starts with
|
|
158
164
|
the em-dash.
|
|
159
165
|
|
|
166
|
+
## snap.js
|
|
167
|
+
|
|
168
|
+
Create HTML slides using [the CSS Scroll Snap
|
|
169
|
+
technique](https://yihui.org/en/2023/09/snap-slides/).
|
|
170
|
+
|
|
160
171
|
## tabsets.js
|
|
161
172
|
|
|
162
173
|
Create tabsets from section headings and their content. See [this
|
package/css/snap.css
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
:root { --slide-width: 100%; }
|
|
2
|
+
html { scroll-snap-type: y mandatory; }
|
|
3
|
+
th, td { padding: .2em .5em; }
|
|
4
|
+
.slide { padding: 0 1em; }
|
|
5
|
+
.slide, .frontmatter .main, .middle .main {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
}
|
|
9
|
+
.slide > .main { flex-grow: 1; }
|
|
10
|
+
.slide > .header { margin-bottom: 1em; }
|
|
11
|
+
.slide h2, .slide h3 { margin-top: unset; }
|
|
12
|
+
body {
|
|
13
|
+
max-width: fit-content;
|
|
14
|
+
padding: 0;
|
|
15
|
+
}
|
|
16
|
+
a { color: #eb4a47; }
|
|
17
|
+
:not(pre) > code { background-color: #fdfded; }
|
|
18
|
+
#TOC { columns: 2; }
|
|
19
|
+
#TOC::before {
|
|
20
|
+
font-size: 1.3em;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
display: block;
|
|
23
|
+
border-bottom: 1px solid #666;
|
|
24
|
+
}
|
|
25
|
+
.frontmatter .main, .middle .main {
|
|
26
|
+
justify-content: center;
|
|
27
|
+
}
|
|
28
|
+
.footer {
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
opacity: .5;
|
|
32
|
+
font: .7em monospace;
|
|
33
|
+
}
|
|
34
|
+
.inverse {
|
|
35
|
+
background-color: #eee;
|
|
36
|
+
filter: invert(1);
|
|
37
|
+
}
|
|
38
|
+
.fade {
|
|
39
|
+
background: repeating-linear-gradient(135deg, white, white 30px, #ddd 32px, #ddd 32px);
|
|
40
|
+
opacity: 0.6;
|
|
41
|
+
}
|
|
42
|
+
.center { text-align: center; }
|
|
43
|
+
.slide-container h2 .section-number {
|
|
44
|
+
display: inline-block;
|
|
45
|
+
background-color: #666;
|
|
46
|
+
color: white;
|
|
47
|
+
padding: 0 .1em;
|
|
48
|
+
margin-right: .3em;
|
|
49
|
+
}
|
|
50
|
+
.overview { font-size: .8em; }
|
|
51
|
+
.overview .slide-container {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
justify-content: space-evenly;
|
|
55
|
+
}
|
|
56
|
+
.overview .slide-container .slide {
|
|
57
|
+
width: var(--slide-width);
|
|
58
|
+
border: 1px dotted #ccc;
|
|
59
|
+
margin-bottom: 0.5em;
|
|
60
|
+
}
|
|
61
|
+
.mirrored { transform: scale(-1, 1); }
|
|
62
|
+
.timer { visibility: hidden; }
|
|
63
|
+
html:fullscreen::-webkit-scrollbar, .spacer { display: none; }
|
|
64
|
+
html:fullscreen {
|
|
65
|
+
-ms-overflow-style: none;
|
|
66
|
+
scrollbar-width: none;
|
|
67
|
+
}
|
|
68
|
+
@media screen and (min-width: 992px) {
|
|
69
|
+
:root {
|
|
70
|
+
--slide-width: 49%;
|
|
71
|
+
--slide-scale: 1;
|
|
72
|
+
--slide-ratio: 0.75;
|
|
73
|
+
--slide-top: auto;
|
|
74
|
+
}
|
|
75
|
+
.slide-mode {
|
|
76
|
+
font-size: 2em;
|
|
77
|
+
background-color: #d7d8d2;
|
|
78
|
+
scale: var(--slide-scale);
|
|
79
|
+
margin-top: var(--slide-top);
|
|
80
|
+
}
|
|
81
|
+
.slide-mode .slide {
|
|
82
|
+
min-height: calc(100vh / var(--slide-scale));
|
|
83
|
+
width: calc(100vh / var(--slide-ratio) / var(--slide-scale));
|
|
84
|
+
box-shadow: 0 0 2em #888;
|
|
85
|
+
clip-path: inset(0 -2em 0 -2em);
|
|
86
|
+
background-color: white;
|
|
87
|
+
scroll-snap-align: start;
|
|
88
|
+
}
|
|
89
|
+
li li { font-size: .9em; }
|
|
90
|
+
.slide-mode .spacer { display: block; }
|
|
91
|
+
.slide-mode .timer { visibility: visible; }
|
|
92
|
+
}
|
|
93
|
+
@media (min-width: 1400px) {
|
|
94
|
+
:root { --slide-width: 33%; }
|
|
95
|
+
}
|
|
96
|
+
@media (min-width: 1800px) {
|
|
97
|
+
:root { --slide-width: 24.67%; }
|
|
98
|
+
}
|
package/css/snap.min.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--slide-width:100%}html{scroll-snap-type:y mandatory}td,th{padding:.2em .5em}.slide{padding:0 1em}.frontmatter .main,.middle .main,.slide{display:flex;flex-direction:column}.slide>.main{flex-grow:1}.slide>.header{margin-bottom:1em}.slide h2,.slide h3{margin-top:unset}body{max-width:fit-content;padding:0}a{color:#eb4a47}:not(pre)>code{background-color:#fdfded}#TOC{columns:2}#TOC::before{font-size:1.3em;font-weight:700;display:block;border-bottom:1px solid #666}.frontmatter .main,.middle .main{justify-content:center}.footer{display:flex;justify-content:space-between;opacity:.5;font:.7em monospace}.inverse{background-color:#eee;filter:invert(1)}.fade{background:repeating-linear-gradient(135deg,#fff,#fff 30px,#ddd 32px,#ddd 32px);opacity:.6}.center{text-align:center}.slide-container h2 .section-number{display:inline-block;background-color:#666;color:#fff;padding:0 .1em;margin-right:.3em}.overview{font-size:.8em}.overview .slide-container{display:flex;flex-wrap:wrap;justify-content:space-evenly}.overview .slide-container .slide{width:var(--slide-width);border:1px dotted #ccc;margin-bottom:.5em}.mirrored{transform:scale(-1,1)}.timer{visibility:hidden}.spacer,html:fullscreen::-webkit-scrollbar{display:none}html:fullscreen{-ms-overflow-style:none;scrollbar-width:none}@media screen and (min-width:992px){:root{--slide-width:49%;--slide-scale:1;--slide-ratio:0.75;--slide-top:auto}.slide-mode{font-size:2em;background-color:#d7d8d2;scale:var(--slide-scale);margin-top:var(--slide-top)}.slide-mode .slide{min-height:calc(100vh / var(--slide-scale));width:calc(100vh / var(--slide-ratio)/ var(--slide-scale));box-shadow:0 0 2em #888;clip-path:inset(0 -2em 0 -2em);background-color:#fff;scroll-snap-align:start}li li{font-size:.9em}.slide-mode .spacer{display:block}.slide-mode .timer{visibility:visible}}@media (min-width:1400px){:root{--slide-width:33%}}@media (min-width:1800px){:root{--slide-width:24.67%}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// toggle output of source code by clicking on source code
|
|
2
|
+
(() => {
|
|
3
|
+
const blocks = [];
|
|
4
|
+
document.querySelectorAll('pre>code[class^=language-],pre[class^=language-]').forEach(el => {
|
|
5
|
+
// if <code> is selected, use its parent
|
|
6
|
+
if (el.tagName === 'CODE' && el.parentNode.tagName === 'PRE') el = el.parentNode;
|
|
7
|
+
blocks.indexOf(el) < 0 && blocks.push(el);
|
|
8
|
+
});
|
|
9
|
+
let s1, s2; // local and global show/hide status
|
|
10
|
+
blocks.forEach(el => {
|
|
11
|
+
el.onclick = e => {
|
|
12
|
+
let sb = el.nextElementSibling;
|
|
13
|
+
while (sb && blocks.indexOf(sb) < 0) {
|
|
14
|
+
s1 = s2; // use global status if exists
|
|
15
|
+
if (s1 === undefined) s1 = sb.style.display === '';
|
|
16
|
+
sb.style.display = s1 ? 'none' : '';
|
|
17
|
+
sb = sb.nextElementSibling;
|
|
18
|
+
}
|
|
19
|
+
// Alt + Click to toggle all output
|
|
20
|
+
e.altKey && (s2 = s1, blocks.forEach(b => b !== el && b.click()), s2 = undefined);
|
|
21
|
+
s1 = undefined;
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{const e=[];let l,a;document.querySelectorAll("pre>code[class^=language-],pre[class^=language-]").forEach((l=>{"CODE"===l.tagName&&"PRE"===l.parentNode.tagName&&(l=l.parentNode),e.indexOf(l)<0&&e.push(l)})),e.forEach((n=>{n.onclick=t=>{let o=n.nextElementSibling;for(;o&&e.indexOf(o)<0;)l=a,void 0===l&&(l=""===o.style.display),o.style.display=l?"none":"",o=o.nextElementSibling;t.altKey&&(a=l,e.forEach((e=>e!==n&&e.click())),a=void 0),l=void 0}}))})();
|
package/js/snap.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
(function(d) {
|
|
2
|
+
let p = d.body; // container of slides; assume <body> for now
|
|
3
|
+
const s1 = ':scope > hr:not([class])', s2 = ':scope > h2';
|
|
4
|
+
// find a container that has at least n "slides"
|
|
5
|
+
function findContainer(s, n = 1) {
|
|
6
|
+
if (p.querySelectorAll(s).length >= n) return true;
|
|
7
|
+
// if body doesn't contain headings or <hr>s, look into children
|
|
8
|
+
for (let i = 0; i < p.children.length; i++) {
|
|
9
|
+
if (p.children[i].querySelectorAll(s).length >= n) {
|
|
10
|
+
p = p.children[i]; break;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function newEl(tag, cls) {
|
|
16
|
+
const el = d.createElement(tag);
|
|
17
|
+
if (cls) el.className = cls;
|
|
18
|
+
return el;
|
|
19
|
+
}
|
|
20
|
+
if (!findContainer(s1, 3)) {
|
|
21
|
+
// if not enough <hr>s found in children; look for <h2> instead
|
|
22
|
+
if (p.tagName === 'BODY') {
|
|
23
|
+
// not enough h2 found, this page is not appropriate for slides
|
|
24
|
+
if (!findContainer(s2) && p.tagName === 'BODY') return;
|
|
25
|
+
p.querySelectorAll(s2).forEach(h2 => h2.before(newEl('hr')));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
p.classList.add('slide-container');
|
|
29
|
+
// add 'slide' class to the frontmatter div and toc
|
|
30
|
+
['.frontmatter', '#TOC'].forEach(sel => {
|
|
31
|
+
const el = d.body.querySelector(sel);
|
|
32
|
+
if (!el) return;
|
|
33
|
+
if (sel === '.frontmatter') {
|
|
34
|
+
el.classList.add('slide');
|
|
35
|
+
} else {
|
|
36
|
+
const s = newSlide(); el.before(s); s.append(el);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function newSlide(s) {
|
|
41
|
+
return (s?.innerText === '') ? s : newEl('div', 'slide');
|
|
42
|
+
}
|
|
43
|
+
function isSep(el) {
|
|
44
|
+
return el.tagName === 'HR' && el.attributes.length === 0;
|
|
45
|
+
}
|
|
46
|
+
let el = p.firstElementChild; isSep(el) && el.remove();
|
|
47
|
+
el = p.firstElementChild; if (!el) return;
|
|
48
|
+
let s = newSlide(); el.before(s);
|
|
49
|
+
while (true) {
|
|
50
|
+
let el = s.nextSibling;
|
|
51
|
+
if (!el) break;
|
|
52
|
+
// remove slide separators (<hr>) and create new slide
|
|
53
|
+
if (isSep(el)) {
|
|
54
|
+
s = newSlide(s);
|
|
55
|
+
el.before(s); el.remove();
|
|
56
|
+
} else if (el.classList?.contains('slide')) {
|
|
57
|
+
s = newSlide(s);
|
|
58
|
+
el.after(s);
|
|
59
|
+
} else {
|
|
60
|
+
s.append(el);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function setAttr(el, attr) {
|
|
64
|
+
const m = newEl('div');
|
|
65
|
+
m.innerHTML = `<div ${attr}></div>`;
|
|
66
|
+
const attrs = m.firstElementChild.attributes;
|
|
67
|
+
for (const a of attrs) {
|
|
68
|
+
el.setAttribute(a.name, a.value);
|
|
69
|
+
}
|
|
70
|
+
m.remove();
|
|
71
|
+
}
|
|
72
|
+
function reveal(el) {
|
|
73
|
+
setTimeout(() => el?.scrollIntoView(), 100);
|
|
74
|
+
}
|
|
75
|
+
const dE = d.documentElement, dC = d.body.classList;
|
|
76
|
+
const slides = d.querySelectorAll('div.slide'), N = slides.length,
|
|
77
|
+
tm = d.querySelector('span.timer'), fn = d.querySelector('.footnotes');
|
|
78
|
+
slides.forEach((s, i) => {
|
|
79
|
+
// slide header, main body, and footer
|
|
80
|
+
const header = newEl('div', 'header'), main = newEl('div', 'main'), footer = newEl('div', 'footer');
|
|
81
|
+
main.append(...s.childNodes);
|
|
82
|
+
s.append(main);
|
|
83
|
+
s.insertAdjacentElement('afterbegin', header);
|
|
84
|
+
s.insertAdjacentElement('beforeend', footer);
|
|
85
|
+
// append footnotes
|
|
86
|
+
if (fn) s.querySelectorAll('.footnote-ref > a[href^="#fn"]').forEach(a => {
|
|
87
|
+
const li = fn.querySelector('li' + a.getAttribute('href'));
|
|
88
|
+
if (!li) return;
|
|
89
|
+
let f = s.querySelector('section.footnotes');
|
|
90
|
+
if (!f) {
|
|
91
|
+
f = newEl('section', 'footnotes'); footer.before(f);
|
|
92
|
+
}
|
|
93
|
+
f.append(li);
|
|
94
|
+
li.firstElementChild?.insertAdjacentHTML('afterbegin', `[${a.innerHTML}] `);
|
|
95
|
+
li.outerHTML = li.innerHTML;
|
|
96
|
+
});
|
|
97
|
+
// add a timer
|
|
98
|
+
footer.append(tm ? tm.cloneNode() : newEl('span', 'timer'));
|
|
99
|
+
// add page numbers
|
|
100
|
+
const n = newEl('span', 'page-number');
|
|
101
|
+
n.innerText = i + 1 + '/' + N;
|
|
102
|
+
n.onclick = e => location.hash = i + 1;
|
|
103
|
+
footer.append(n);
|
|
104
|
+
// apply slide attributes in <!--# -->
|
|
105
|
+
for (const node of main.childNodes) {
|
|
106
|
+
if (node.nodeType !== Node.COMMENT_NODE) continue;
|
|
107
|
+
let t = node.textContent;
|
|
108
|
+
if (!/^#/.test(t)) continue;
|
|
109
|
+
t = t.replace(/^#/, '');
|
|
110
|
+
const r = /[\s\n]class="([^"]+)"/, m = t.match(r);
|
|
111
|
+
if (m) {
|
|
112
|
+
t = t.replace(r, '').trim();
|
|
113
|
+
s.className += ' ' + m[1];
|
|
114
|
+
}
|
|
115
|
+
if (t) setAttr(s, t);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
s.addEventListener('click', e => {
|
|
119
|
+
e.altKey && (toggleView(e), reveal(e.target));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
[...d.querySelectorAll('a.footnote-backref'), fn, tm].forEach(el => el?.remove());
|
|
123
|
+
const tms = d.querySelectorAll('span.timer'), t1 = 1000 * tms[0].dataset.total;
|
|
124
|
+
let t0;
|
|
125
|
+
function startTimers() {
|
|
126
|
+
t0 = new Date();
|
|
127
|
+
setInterval(setTimers, 1000);
|
|
128
|
+
}
|
|
129
|
+
function setTimers() {
|
|
130
|
+
if (!dC.contains('slide-mode')) return; // set timer only in slide mode
|
|
131
|
+
let t = (new Date() - t0);
|
|
132
|
+
if (t1) t = t1 - t;
|
|
133
|
+
const t2 = new Date(Math.abs(t)).toISOString().substr(11, 8).replace(/^00:/, '');
|
|
134
|
+
tms.forEach(el => {
|
|
135
|
+
el.innerText = t2;
|
|
136
|
+
if (t < 0) el.style.visibility = el.style.visibility === 'hidden' ? '' : 'hidden';
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function toggleView(e) {
|
|
140
|
+
(dC.toggle('overview') ? dC.remove('slide-mode') : setScale(e));
|
|
141
|
+
}
|
|
142
|
+
// press f for fullscreen mode
|
|
143
|
+
d.addEventListener('keyup', e => {
|
|
144
|
+
if (e.target !== d.body) return;
|
|
145
|
+
e.key === 'f' && dE.requestFullscreen();
|
|
146
|
+
e.key === 'o' && toggleView(e);
|
|
147
|
+
e.key === 'm' && dC.toggle('mirrored');
|
|
148
|
+
sessionStorage.setItem('body-class', d.body.className);
|
|
149
|
+
});
|
|
150
|
+
// start timer and set scale on fullscreen
|
|
151
|
+
d.onfullscreenchange = e => {
|
|
152
|
+
d.fullscreenElement && (!t0 && startTimers(), setScale(e));
|
|
153
|
+
}
|
|
154
|
+
tms.forEach(el => el.addEventListener('click', e => startTimers()));
|
|
155
|
+
// measure the height of an empty slide
|
|
156
|
+
let H = -1;
|
|
157
|
+
function slideHeight() {
|
|
158
|
+
if (H >= 0) return H;
|
|
159
|
+
const s = newEl('div', 'slide');
|
|
160
|
+
p.querySelector('.slide:last-of-type').after(s);
|
|
161
|
+
H = s.offsetHeight;
|
|
162
|
+
s.remove()
|
|
163
|
+
return H;
|
|
164
|
+
}
|
|
165
|
+
// scale slides according to window height (baseline: 900px)
|
|
166
|
+
const sty = newEl('style'); sty.setAttribute('type', 'text/css');
|
|
167
|
+
d.head.append(sty);
|
|
168
|
+
// default height/width ratio from screen size
|
|
169
|
+
sty.innerHTML = `:root{--slide-ratio:${screen.height / screen.width}}`;
|
|
170
|
+
// read --slide-ratio in case users have set it in their CSS
|
|
171
|
+
const ratio = +getComputedStyle(dE).getPropertyValue('--slide-ratio');
|
|
172
|
+
function setScale(e) {
|
|
173
|
+
// navigate to a slide indicated by the hash if provided
|
|
174
|
+
e.type === 'load' && reveal(slides[location.hash.replace(/^#/, '') - 1]);
|
|
175
|
+
if (dC.contains('overview')) return;
|
|
176
|
+
let h = window.innerHeight, p = h / 900, r2 = h / window.innerWidth, full = d.fullscreenElement;
|
|
177
|
+
// add slide mode if there's enough window width, and remove it in case of scrollbar
|
|
178
|
+
dC.toggle('slide-mode', full || r2 <= ratio) &&
|
|
179
|
+
(!full && (dE.scrollWidth > dE.offsetWidth)) && dC.remove('slide-mode');
|
|
180
|
+
sty.innerHTML = `:root{--slide-ratio:${ratio};--slide-scale:${p};--slide-top:${(p - 1)/2 * d.body.scrollHeight + 'px'};}`;
|
|
181
|
+
// add spacers with enough height on load
|
|
182
|
+
!d.querySelector('.spacer.fade') && dC.contains('slide-mode') && slides.forEach(s => {
|
|
183
|
+
const sp = newEl('div', 'spacer fade'), h = s.offsetHeight, h2 = slideHeight();
|
|
184
|
+
s.append(sp);
|
|
185
|
+
if (h <= h2) return;
|
|
186
|
+
sp.style.height = (h2 - h % h2) * p + 'px';
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
['load', 'resize'].forEach(evt => window.addEventListener(evt, setScale));
|
|
190
|
+
// restore previsouly saved body class
|
|
191
|
+
const bc = sessionStorage.getItem('body-class');
|
|
192
|
+
if (bc) d.body.className += ' ' + bc;
|
|
193
|
+
})(document);
|
package/js/snap.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e){let t=e.body;const n=":scope > h2";function o(e,n=1){if(t.querySelectorAll(e).length>=n)return!0;for(let o=0;o<t.children.length;o++)if(t.children[o].querySelectorAll(e).length>=n){t=t.children[o];break}return!1}function r(t,n){const o=e.createElement(t);return n&&(o.className=n),o}if(!o(":scope > hr:not([class])",3)&&"BODY"===t.tagName){if(!o(n)&&"BODY"===t.tagName)return;t.querySelectorAll(n).forEach((e=>e.before(r("hr"))))}function i(e){return""===e?.innerText?e:r("div","slide")}function s(e){return"HR"===e.tagName&&0===e.attributes.length}t.classList.add("slide-container"),[".frontmatter","#TOC"].forEach((t=>{const n=e.body.querySelector(t);if(n)if(".frontmatter"===t)n.classList.add("slide");else{const e=i();n.before(e),e.append(n)}}));let l=t.firstElementChild;if(s(l)&&l.remove(),l=t.firstElementChild,!l)return;let c=i();for(l.before(c);;){let e=c.nextSibling;if(!e)break;s(e)?(c=i(c),e.before(c),e.remove()):e.classList?.contains("slide")?(c=i(c),e.after(c)):c.append(e)}function a(e,t){const n=r("div");n.innerHTML=`<div ${t}></div>`;const o=n.firstElementChild.attributes;for(const t of o)e.setAttribute(t.name,t.value);n.remove()}function d(e){setTimeout((()=>e?.scrollIntoView()),100)}const f=e.documentElement,u=e.body.classList,h=e.querySelectorAll("div.slide"),m=h.length,p=e.querySelector("span.timer"),y=e.querySelector(".footnotes");h.forEach(((e,t)=>{const n=r("div","header"),o=r("div","main"),i=r("div","footer");o.append(...e.childNodes),e.append(o),e.insertAdjacentElement("afterbegin",n),e.insertAdjacentElement("beforeend",i),y&&e.querySelectorAll('.footnote-ref > a[href^="#fn"]').forEach((t=>{const n=y.querySelector("li"+t.getAttribute("href"));if(!n)return;let o=e.querySelector("section.footnotes");o||(o=r("section","footnotes"),i.before(o)),o.append(n),n.firstElementChild?.insertAdjacentHTML("afterbegin",`[${t.innerHTML}] `),n.outerHTML=n.innerHTML})),i.append(p?p.cloneNode():r("span","timer"));const s=r("span","page-number");s.innerText=t+1+"/"+m,s.onclick=e=>location.hash=t+1,i.append(s);for(const t of o.childNodes){if(t.nodeType!==Node.COMMENT_NODE)continue;let n=t.textContent;if(!/^#/.test(n))continue;n=n.replace(/^#/,"");const o=/[\s\n]class="([^"]+)"/,r=n.match(o);r&&(n=n.replace(o,"").trim(),e.className+=" "+r[1]),n&&a(e,n);break}e.addEventListener("click",(e=>{e.altKey&&(q(e),d(e.target))}))})),[...e.querySelectorAll("a.footnote-backref"),y,p].forEach((e=>e?.remove()));const g=e.querySelectorAll("span.timer"),b=1e3*g[0].dataset.total;let v;function E(){v=new Date,setInterval(S,1e3)}function S(){if(!u.contains("slide-mode"))return;let e=new Date-v;b&&(e=b-e);const t=new Date(Math.abs(e)).toISOString().substr(11,8).replace(/^00:/,"");g.forEach((n=>{n.innerText=t,e<0&&(n.style.visibility="hidden"===n.style.visibility?"":"hidden")}))}function q(e){u.toggle("overview")?u.remove("slide-mode"):N(e)}e.addEventListener("keyup",(t=>{t.target===e.body&&("f"===t.key&&f.requestFullscreen(),"o"===t.key&&q(t),"m"===t.key&&u.toggle("mirrored"),sessionStorage.setItem("body-class",e.body.className))})),e.onfullscreenchange=t=>{e.fullscreenElement&&(!v&&E(),N(t))},g.forEach((e=>e.addEventListener("click",(e=>E()))));let L=-1;function T(){if(L>=0)return L;const e=r("div","slide");return t.querySelector(".slide:last-of-type").after(e),L=e.offsetHeight,e.remove(),L}const w=r("style");w.setAttribute("type","text/css"),e.head.append(w),w.innerHTML=`:root{--slide-ratio:${screen.height/screen.width}}`;const A=+getComputedStyle(f).getPropertyValue("--slide-ratio");function N(t){if("load"===t.type&&d(h[location.hash.replace(/^#/,"")-1]),u.contains("overview"))return;let n=window.innerHeight,o=n/900,i=n/window.innerWidth,s=e.fullscreenElement;u.toggle("slide-mode",s||i<=A)&&!s&&f.scrollWidth>f.offsetWidth&&u.remove("slide-mode"),w.innerHTML=`:root{--slide-ratio:${A};--slide-scale:${o};--slide-top:${(o-1)/2*e.body.scrollHeight+"px"};}`,!e.querySelector(".spacer.fade")&&u.contains("slide-mode")&&h.forEach((e=>{const t=r("div","spacer fade"),n=e.offsetHeight,i=T();e.append(t),n<=i||(t.style.height=(i-n%i)*o+"px")}))}["load","resize"].forEach((e=>window.addEventListener(e,N)));const H=sessionStorage.getItem("body-class");H&&(e.body.className+=" "+H)}(document);
|