56cc2bd814
Optional skill: scroll-as-timeline landing pages on a deterministic CSS/JS engine, with interview → page grammar → signature move workflow and screenshot-based scroll verification. Engine and scripts vendored verbatim; asset generation re-anchored on image_generate with the upstream kie.ai flow kept as an optional path.
53 lines
2.0 KiB
JavaScript
53 lines
2.0 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* scrollcraft static server.
|
|
*
|
|
* A scrollcraft page cannot be verified from file://. The engine fetches each
|
|
* clip as a Blob, and file:// fetches are blocked by CORS in every browser, so
|
|
* the page silently falls back to posters and looks fine while proving nothing.
|
|
* Serve it.
|
|
*
|
|
* node serve.mjs --root builds/perkform --port 4500
|
|
*/
|
|
import http from "node:http";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const argv = process.argv.slice(2);
|
|
const arg = (n, d) => { const i = argv.indexOf(n); return i > -1 && argv[i + 1] ? argv[i + 1] : d; };
|
|
|
|
const ROOT = path.resolve(arg("--root", "."));
|
|
const PORT = parseInt(arg("--port", "4500"), 10);
|
|
|
|
const TYPES = {
|
|
".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8",
|
|
".js": "text/javascript; charset=utf-8", ".json": "application/json",
|
|
".mp4": "video/mp4", ".webm": "video/webm",
|
|
".webp": "image/webp", ".png": "image/png", ".jpg": "image/jpeg",
|
|
".svg": "image/svg+xml", ".woff2": "font/woff2",
|
|
};
|
|
|
|
http.createServer((req, res) => {
|
|
const url = decodeURIComponent(req.url.split("?")[0]);
|
|
let file = path.join(ROOT, url === "/" ? "/index.html" : url);
|
|
|
|
// Refuse to serve outside the root even if the path walks up.
|
|
if (!file.startsWith(ROOT)) { res.writeHead(403).end("forbidden"); return; }
|
|
if (fs.existsSync(file) && fs.statSync(file).isDirectory()) file = path.join(file, "index.html");
|
|
if (!fs.existsSync(file)) { res.writeHead(404).end("not found"); return; }
|
|
|
|
const ext = path.extname(file).toLowerCase();
|
|
const stat = fs.statSync(file);
|
|
res.writeHead(200, {
|
|
"Content-Type": TYPES[ext] || "application/octet-stream",
|
|
"Content-Length": stat.size,
|
|
// No caching: verification loops re-shoot the same URLs after edits, and a
|
|
// cached clip or stylesheet makes you screenshot the previous build.
|
|
"Cache-Control": "no-store",
|
|
"Accept-Ranges": "bytes",
|
|
});
|
|
fs.createReadStream(file).pipe(res);
|
|
}).listen(PORT, () => {
|
|
console.log(`scrollcraft: ${ROOT}\n http://localhost:${PORT}`);
|
|
});
|