Add 3D loose-leaf chronicle of human civilisation
A Three.js site presenting 20 world history milestones as double-sided loose-leaf pages, with three views: a leather binder with page-flip animation, a golden timeline, and a dotted globe with great-circle arcs. All textures (paper, leather, registration cards, and 18 hand-drawn sketches) are generated procedurally via Canvas 2D, so no external image assets are needed beyond the Earth map. Includes dev helper scripts for screenshots, ASCII previews, and headless verification.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import puppeteer from 'puppeteer-core';
|
||||
|
||||
const CHROME = 'C:/Program Files/Google/Chrome/Application/chrome.exe';
|
||||
const URL = process.argv[2] || 'http://localhost:5199/';
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: CHROME,
|
||||
headless: 'new',
|
||||
args: ['--no-sandbox', '--disable-gpu', '--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--window-size=1440,900'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1440, height: 900 });
|
||||
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => errors.push('pageerror: ' + e.message));
|
||||
page.on('console', (m) => { if (m.type() === 'error') errors.push('console: ' + m.text()); });
|
||||
|
||||
await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||
await new Promise((r) => setTimeout(r, 6000));
|
||||
|
||||
async function snapshot(label) {
|
||||
const info = await page.evaluate(() => {
|
||||
const app = window.__app;
|
||||
const gl = app.renderer.getContext();
|
||||
const w = gl.drawingBufferWidth, h = gl.drawingBufferHeight;
|
||||
const buf = new Uint8Array(w * h * 4);
|
||||
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, buf);
|
||||
let sum = 0, n = 0, bright = 0;
|
||||
for (let i = 0; i < buf.length; i += 4) {
|
||||
const lum = 0.299 * buf[i] + 0.587 * buf[i + 1] + 0.114 * buf[i + 2];
|
||||
sum += lum; n++;
|
||||
if (lum > 40) bright++;
|
||||
}
|
||||
return {
|
||||
view: app.view,
|
||||
selected: app.selected,
|
||||
binderCurrent: app.binder.current,
|
||||
pages: app.binder.pages.length,
|
||||
timelineCards: app.timeline.cards.length,
|
||||
globeMarkers: app.globe.markers.length,
|
||||
detailName: document.getElementById('detail-name')?.textContent,
|
||||
detailNo: document.getElementById('detail-no')?.textContent,
|
||||
avgLum: +(sum / n).toFixed(2),
|
||||
brightPct: +((bright / n) * 100).toFixed(2),
|
||||
};
|
||||
});
|
||||
console.log(label, JSON.stringify(info, null, 0));
|
||||
}
|
||||
|
||||
await snapshot('binder ');
|
||||
|
||||
await page.click('.view-btn[data-view="timeline"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await snapshot('timeline');
|
||||
|
||||
await page.click('.view-btn[data-view="globe"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await snapshot('globe ');
|
||||
|
||||
await page.click('.view-btn[data-view="binder"]');
|
||||
await new Promise((r) => setTimeout(r, 2400));
|
||||
await page.click('#btn-next');
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
await snapshot('flipped ');
|
||||
|
||||
await page.click('#btn-prev');
|
||||
await new Promise((r) => setTimeout(r, 1500));
|
||||
await snapshot('back ');
|
||||
|
||||
console.log('ERRORS:', errors.length ? errors.join('\n') : '(none)');
|
||||
await browser.close();
|
||||
Reference in New Issue
Block a user