Skip to content

Commit 54e4e4b

Browse files
Merge pull request #2 from mindthemath/feat/math
math and reformatting for scales
2 parents 2d4965b + 9d77c65 commit 54e4e4b

55 files changed

Lines changed: 1776 additions & 498 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/capture.yml

Lines changed: 21 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,29 @@ on:
1010
type: choice
1111
options:
1212
- '0.5'
13+
- '0.75'
1314
- '1'
15+
- '1.25'
1416
- '1.5'
15-
- '3'
17+
- '2'
18+
num_workers:
19+
description: Number of Chromium workers to run in parallel
20+
required: false
21+
default: '8'
22+
type: choice
23+
options:
24+
- '1'
25+
- '2'
26+
- '4'
27+
- '6'
28+
- '8'
1629

1730
permissions:
1831
contents: write
1932

2033
jobs:
2134
screenshot:
22-
runs-on: ubuntu-latest
35+
runs-on: blacksmith-8vcpu-ubuntu-2404
2336
steps:
2437
- name: Checkout
2538
uses: actions/checkout@v6
@@ -47,201 +60,19 @@ jobs:
4760
- name: Start server and take screenshots
4861
env:
4962
SCREENSHOT_ZOOM_LEVEL: ${{ inputs.zoom_level || '1' }}
63+
SCREENSHOT_NUM_WORKERS: ${{ inputs.num_workers || '4' }}
64+
SCREENSHOT_OUT_DIR: ${{ github.workspace }}/.screenshots
5065
run: |
51-
# Start static server in background
52-
bunx --bun serve . -p 1313 > /dev/null 2>&1 &
53-
SERVER_PID=$!
54-
55-
# Wait for server to be ready
56-
echo "Waiting for server to start..."
57-
for i in {1..30}; do
58-
if curl -s http://localhost:1313/ > /dev/null 2>&1; then
59-
echo "Server is ready!"
60-
break
61-
fi
62-
sleep 1
63-
done
64-
65-
# Create screenshot directories and capture slides
66-
node << 'EOF'
67-
const { chromium } = require('playwright');
68-
const fs = require('fs');
69-
const path = require('path');
70-
const zoomLevel = Number(process.env.SCREENSHOT_ZOOM_LEVEL || '1');
71-
72-
if (!Number.isFinite(zoomLevel) || zoomLevel <= 0) {
73-
throw new Error(`Invalid SCREENSHOT_ZOOM_LEVEL: ${process.env.SCREENSHOT_ZOOM_LEVEL}`);
74-
}
75-
76-
// URLs to capture: [url, folderName] or [url, folderName, waitTimeMs]
77-
// Optional waitTimeMs: wait after load before screenshot (for animated slides)
78-
// Main slides 1-17, plus basement slides at 8 and 16
79-
const urls = [
80-
['/', 'slide-01'],
81-
['/#/2', 'slide-02'],
82-
['/#/3', 'slide-03'],
83-
['/#/4', 'slide-04'],
84-
// ['/#/5', 'slide-05'],
85-
// ['/#/6', 'slide-06'],
86-
// ['/#/7', 'slide-07'],
87-
['/#/8', 'slide-08-01'],
88-
['/#/8/2', 'slide-08-02'],
89-
// ['/#/9', 'slide-09'],
90-
['/#/10', 'slide-10'],
91-
['/#/11', 'slide-11'],
92-
['/#/12', 'slide-12'],
93-
// ['/#/13', 'slide-13'],
94-
// ['/#/14', 'slide-14'],
95-
// ['/#/15', 'slide-15'],
96-
['/#/16', 'slide-16-01'],
97-
['/#/16/2', 'slide-16-02'],
98-
['/#/16/3', 'slide-16-03'],
99-
// ['/#/17', 'slide-17'],
100-
];
101-
102-
// Define resolutions: [width, height, label]
103-
const resolutions = [
104-
// Mobile
105-
[375, 667, 'iphone-se'],
106-
[390, 844, 'iphone-12-13'],
107-
[430, 932, 'iphone-14-15-16-17-pro-max'],
108-
// Mobile Landscape
109-
[667, 375, 'iphone-se-landscape'],
110-
[844, 390, 'iphone-12-13-landscape'],
111-
[932, 430, 'iphone-14-15-16-17-pro-max-landscape'],
112-
// Tablet
113-
[768, 1024, 'ipad-portrait'],
114-
[1024, 768, 'ipad-landscape'],
115-
// Desktop
116-
[1280, 720, 'desktop-1280x720'],
117-
[1366, 768, 'desktop-1366x768'],
118-
[1440, 900, 'desktop-1440x900'],
119-
[1920, 1080, 'desktop-1920x1080'],
120-
[2560, 1440, 'desktop-2560x1440'],
121-
];
122-
123-
async function takeScreenshots(url, folder, waitTime = 0) {
124-
const browser = await chromium.launch();
125-
const screenshots = [];
126-
const fullUrl = `http://localhost:1313${url}`;
127-
128-
if (waitTime > 0) {
129-
// Load once, wait for animation, then resize for each resolution
130-
console.log(`Loading ${url} at ${zoomLevel}x zoom and waiting ${waitTime}ms for animation...`);
131-
const page = await browser.newPage();
132-
const [firstWidth, firstHeight, firstLabel] = resolutions[0];
133-
await page.setViewportSize({ width: firstWidth, height: firstHeight });
134-
await page.goto(fullUrl, { waitUntil: 'networkidle' });
135-
await page.waitForTimeout(500);
136-
await page.evaluate((zoom) => {
137-
document.documentElement.style.zoom = String(zoom);
138-
}, zoomLevel);
139-
await page.waitForTimeout(100);
140-
await page.waitForTimeout(waitTime);
141-
142-
// Now take screenshots at all resolutions by resizing
143-
for (const [width, height, label] of resolutions) {
144-
console.log(`Taking ${width}x${height} (${label}) for ${url} at ${zoomLevel}x zoom`);
145-
await page.setViewportSize({ width, height });
146-
await page.evaluate((zoom) => {
147-
document.documentElement.style.zoom = String(zoom);
148-
}, zoomLevel);
149-
// Small delay to let layout adjust after resize
150-
await page.waitForTimeout(100);
151-
152-
const filename = `${width}x${height}-${label}.png`;
153-
const filepath = path.join(folder, filename);
154-
await page.screenshot({ path: filepath, fullPage: false });
155-
screenshots.push({ filename, width, height, label });
156-
}
157-
158-
await page.close();
159-
} else {
160-
// Reload for each resolution
161-
for (const [width, height, label] of resolutions) {
162-
console.log(`Taking ${width}x${height} (${label}) for ${url} at ${zoomLevel}x zoom`);
163-
const page = await browser.newPage();
164-
await page.setViewportSize({ width, height });
165-
await page.goto(fullUrl, { waitUntil: 'networkidle' });
166-
await page.waitForTimeout(500);
167-
await page.evaluate((zoom) => {
168-
document.documentElement.style.zoom = String(zoom);
169-
}, zoomLevel);
170-
await page.waitForTimeout(100);
171-
172-
const filename = `${width}x${height}-${label}.png`;
173-
const filepath = path.join(folder, filename);
174-
await page.screenshot({ path: filepath, fullPage: false });
175-
await page.close();
176-
177-
screenshots.push({ filename, width, height, label });
178-
}
179-
}
180-
181-
await browser.close();
182-
return screenshots;
183-
}
184-
185-
function getFolderTitle(folderName) {
186-
return folderName
187-
.split('-')
188-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
189-
.join(' ');
190-
}
191-
192-
async function generateReadme(folder, screenshots) {
193-
const timestamp = new Date().toISOString().replace(/:/g, '-').split('.')[0] + 'Z';
194-
const readmePath = path.join(folder, 'README.md');
195-
196-
let content = `# ${getFolderTitle(folder)}\n\n`;
197-
content += `Generated: ${new Date().toISOString()}\n\n`;
198-
content += `| Resolution | Device | Screenshot |\n`;
199-
content += `|------------|--------|------------|\n`;
200-
201-
for (const { filename, width, height, label } of screenshots) {
202-
content += `| ${width} × ${height} | ${label} | ![${label}](${filename}?${timestamp}) |\n`;
203-
}
204-
205-
fs.writeFileSync(readmePath, content);
206-
}
207-
208-
(async () => {
209-
try {
210-
const allFolders = [];
211-
console.log(`Using screenshot zoom level: ${zoomLevel}x`);
212-
213-
for (const entry of urls) {
214-
const [url, folderName, waitTime = 0] = entry;
215-
fs.mkdirSync(folderName, { recursive: true });
216-
allFolders.push(folderName);
217-
218-
const waitMsg = waitTime > 0 ? ` (wait ${waitTime}ms)` : '';
219-
console.log(`=== Capturing ${folderName} for ${url}${waitMsg} ===`);
220-
const screenshots = await takeScreenshots(url, folderName, waitTime);
221-
await generateReadme(folderName, screenshots);
222-
console.log(`Captured ${screenshots.length} screenshots for ${folderName}`);
223-
}
224-
225-
console.log('All screenshots saved successfully');
226-
fs.writeFileSync('_screenshot_folders.json', JSON.stringify(allFolders));
227-
} catch (error) {
228-
console.error('Error taking screenshots:', error);
229-
process.exit(1);
230-
}
231-
})();
232-
EOF
233-
234-
# Stop server
235-
kill $SERVER_PID 2>/dev/null || true
66+
./scripts/capture-screenshots.sh
23667
23768
- name: Create orphan branch and commit screenshots
23869
run: |
23970
git config --global user.name "github-actions[bot]"
24071
git config --global user.email "github-actions[bot]@users.noreply.github.com"
24172
242-
FOLDERS=$(node -e "console.log(require('./_screenshot_folders.json').join(' '))")
243-
rm _screenshot_folders.json
244-
mv $FOLDERS /tmp/
73+
FOLDERS=$(bun -e "console.log(JSON.parse(require('fs').readFileSync('.screenshots/_screenshot_folders.json', 'utf8')).join(' '))")
74+
rm .screenshots/_screenshot_folders.json
75+
mv .screenshots/slide-* /tmp/
24576
24677
git checkout main || git checkout master || true
24778
git branch -D screenshots 2>/dev/null || true

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.screenshots/
2+
node_modules
3+
bun.lock
4+
package.json
5+
*.cjs

README.md

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ Reveal.js runtime assets are vendored in-repo for offline use.
99
- `index.html`: Reveal.js bootstrap, markdown loading, and custom slide behavior.
1010
- `custom.css`: presentation theme and layout styling.
1111
- `vendor/reveal.js/`: local Reveal.js CSS/JS/plugin assets used by `index.html`.
12+
- `vendor/katex/`: slim local KaTeX runtime used for offline math rendering.
13+
- `scripts/vendor-katex.sh`: idempotent vendoring script for the KaTeX runtime subset.
14+
- `scripts/capture-screenshots.sh`: local multi-resolution screenshot capture for visual validation.
1215

1316
## Run In Dev
1417

@@ -20,17 +23,60 @@ bunx --bun serve . -p 1313
2023

2124
Then open the local URL printed by `serve` in your browser.
2225

23-
## Refresh Vendored Reveal Assets
26+
## Capture Screenshots Locally
27+
28+
To validate typography/layout changes across the same resolution set used in CI:
2429

2530
```bash
26-
mkdir -p vendor/reveal.js/dist/theme vendor/reveal.js/plugin/markdown vendor/reveal.js/plugin/highlight
27-
wget -q -O vendor/reveal.js/dist/reveal.css https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css
28-
wget -q -O vendor/reveal.js/dist/theme/black.css https://cdn.jsdelivr.net/npm/reveal.js@5/dist/theme/black.css
29-
wget -q -O vendor/reveal.js/dist/reveal.js https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.js
30-
wget -q -O vendor/reveal.js/plugin/markdown/markdown.js https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/markdown/markdown.js
31-
wget -q -O vendor/reveal.js/plugin/highlight/highlight.js https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/highlight.js
31+
SCREENSHOT_ZOOM_LEVEL=1 ./scripts/capture-screenshots.sh
32+
```
33+
34+
This writes slide image folders plus `_screenshot_folders.json` into `.screenshots/` by default.
35+
The script aligns with CI by ensuring the `playwright` package is installed locally before capture, then installing Chromium.
36+
Override `SCREENSHOT_OUT_DIR` to capture elsewhere, and `SCREENSHOT_PORT` if `1313` is already in use.
37+
38+
## Authoring Math
39+
40+
Display math can be written using fenced `math` blocks in `present.md`:
41+
42+
````md
43+
```math
44+
\int_0^\infty e^{-x^2} \, dx = \frac{\sqrt{\pi}}{2}
3245
```
46+
````
47+
48+
`index.html` rewrites these fences to KaTeX display-math delimiters before Reveal parses the deck, so slide authoring stays markdown-native while rendering still uses Reveal's math plugin.
49+
50+
Inline math also works with standard KaTeX delimiters such as `$x^2$`, `\(...\)`, and `\[...\]`.
51+
52+
## Refresh Vendored Assets
53+
54+
```bash
55+
mkdir -p vendor/reveal.js/dist/theme vendor/reveal.js/plugin/markdown vendor/reveal.js/plugin/highlight vendor/reveal.js/plugin/math
56+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/dist/reveal.css -o vendor/reveal.js/dist/reveal.css
57+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/dist/theme/black.css -o vendor/reveal.js/dist/theme/black.css
58+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/dist/reveal.js -o vendor/reveal.js/dist/reveal.js
59+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/plugin/markdown/markdown.js -o vendor/reveal.js/plugin/markdown/markdown.js
60+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/plugin/highlight/highlight.js -o vendor/reveal.js/plugin/highlight/highlight.js
61+
curl -fsSL https://cdn.jsdelivr.net/npm/reveal.js@5.2.1/plugin/math/math.js -o vendor/reveal.js/plugin/math/math.js
62+
./scripts/vendor-katex.sh
63+
```
64+
65+
The KaTeX script vendors only the runtime files Reveal needs:
66+
67+
- `dist/katex.min.js`
68+
- `dist/katex.min.css`
69+
- `dist/contrib/auto-render.min.js`
70+
- `dist/fonts/*.woff2`
71+
- `dist/fonts/*.woff`
72+
- `LICENSE`
73+
74+
Set `KATEX_VERSION` to override the pinned default when refreshing, for example:
75+
76+
```bash
77+
KATEX_VERSION=0.16.37 ./scripts/vendor-katex.sh
78+
```
79+
80+
## Footer Text
3381

34-
# Footer Text
35-
Line 294 of `index.html` can be edited to change the footer text from `present.md` to anything else.
36-
It is there that the styling of the pagination is also handled.
82+
Edit the `counter.textContent = \`${current} / ${total} | present.md\`;` line in `index.html` to change the footer text from `present.md` to something else.

0 commit comments

Comments
 (0)