@xiee/utils 1.9.5 → 1.10.1
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/.github/workflows/npm-publish.yml +2 -2
- package/README.md +7 -2
- package/css/snap.css +98 -0
- package/css/snap.min.css +1 -0
- package/js/number-captions.js +1 -1
- package/js/snap.js +193 -0
- package/js/snap.min.js +1 -0
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ jobs:
|
|
|
6
6
|
runs-on: ubuntu-latest
|
|
7
7
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
|
8
8
|
steps:
|
|
9
|
-
- uses: actions/checkout@
|
|
10
|
-
- uses: actions/setup-node@
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- uses: actions/setup-node@v4
|
|
11
11
|
with:
|
|
12
12
|
registry-url: 'https://registry.npmjs.org'
|
|
13
13
|
- run: ./build.sh
|
package/README.md
CHANGED
|
@@ -128,8 +128,8 @@ This requires highlight.js to be loaded in advance.
|
|
|
128
128
|
|
|
129
129
|
## math-code.js
|
|
130
130
|
|
|
131
|
-
Write LaTeX math expressions (`$\alpha$`) in `<code></code
|
|
132
|
-
backticks in Markdown (which will be rendered to `<code>` in HTML), and this
|
|
131
|
+
Write LaTeX math expressions (`$\alpha$`) in `<code></code>` in HTML or a pair
|
|
132
|
+
of backticks in Markdown (which will be rendered to `<code>` in HTML), and this
|
|
133
133
|
script will remove the `<code>` tag, so that MathJax can recognize the math
|
|
134
134
|
expressions (by default, MathJax ignores math in `<code>`).
|
|
135
135
|
|
|
@@ -163,6 +163,11 @@ KaTeX's auto-render extension.
|
|
|
163
163
|
Right-align a `<blockquote>` footer if the footer is a `<p>` that starts with
|
|
164
164
|
the em-dash.
|
|
165
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
|
+
|
|
166
171
|
## tabsets.js
|
|
167
172
|
|
|
168
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 { opacity: 0; }
|
|
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 { opacity: initial; }
|
|
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{opacity:0}.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{opacity:initial}}@media (min-width:1400px){:root{--slide-width:33%}}@media (min-width:1800px){:root{--slide-width:24.67%}}
|
package/js/number-captions.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// config options via data-foo attributes of this <script>:
|
|
4
4
|
// * data-colon: the colon character (':' by default);
|
|
5
5
|
// * data-fig-label: label for figure captions ('Figure ' by default)
|
|
6
|
-
// * data-tab-label:
|
|
6
|
+
// * data-tab-label: label for table captions ('Table ' by default)
|
|
7
7
|
(d => {
|
|
8
8
|
const cfg = d.currentScript?.dataset, colon = cfg?.colon || ':';
|
|
9
9
|
function NUM(target, label) {
|
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.opacity = Math.ceiling(t/1000) % 2;
|
|
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.opacity=Math.ceiling(e/1e3)%2)}))}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);
|