// Render the Beyond 2 spec sheet PDF from src/site/spec-sheet.html. // // node scripts/build-spec-sheet.mjs // -> public/landing/press/assets/beyond-2-spec-sheet.pdf // // Uses the repo's playwright dependency (headless Chromium print-to-PDF), so // the sheet is real HTML/CSS on the design system, with the brand fonts // embedded. Fonts: the full Neue Haas family from the local design-system // folder when present (true Medium weight), else the shipped webfonts. import fs from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { chromium } from "playwright"; const OUT = "public/landing/press/assets/beyond-2-spec-sheet.pdf"; const SRC = "src/site/spec-sheet.html"; const DS = "Bigscreen Design System/fonts"; const url = (p) => pathToFileURL(path.resolve(p)).href; let fontsCss; if (fs.existsSync(path.join(DS, "NeueHaasDisplayRoman.ttf"))) { fontsCss = ` @font-face { font-family: "Neue Haas Grotesk Display Pro"; font-weight: 400; src: url("${url(path.join(DS, "NeueHaasDisplayRoman.ttf"))}"); } @font-face { font-family: "Neue Haas Grotesk Display Pro"; font-weight: 500; src: url("${url(path.join(DS, "NeueHaasDisplayMediu.ttf"))}"); } @font-face { font-family: "Fragment Mono"; font-weight: 400; src: url("${url(path.join(DS, "FragmentMono-Regular.ttf"))}"); }`; } else { // fallback: the webfonts shipped in the repo (Roman doubles for Medium) fontsCss = ` @font-face { font-family: "Neue Haas Grotesk Display Pro"; font-weight: 400; src: url("${url("public/landing/assets/052-asset.otf")}"); } @font-face { font-family: "Neue Haas Grotesk Display Pro"; font-weight: 500; src: url("${url("public/landing/assets/052-asset.otf")}"); } @font-face { font-family: "Fragment Mono"; font-weight: 400; src: url("${url("public/landing/assets/056-asset.ttf")}"); }`; console.warn("design-system fonts not found; using shipped webfonts (no true Medium)"); } // logo inlined as a data URI so it can't fail to resolve in the print context const logo = "data:image/svg+xml;base64," + fs.readFileSync("public/landing/press/assets/bigscreen-logo-black.svg").toString("base64"); const html = fs .readFileSync(SRC, "utf8") .replace("{{FONTS_CSS}}", fontsCss) .split("{{LOGO}}") .join(logo); // write to a temp file and load via file:// (a real file origin, so the // file-URL fonts are loadable; setContent's about:blank origin blocks them) const tmp = path.resolve(".spec-sheet.tmp.html"); fs.writeFileSync(tmp, html); // channel: "chrome" = the installed system Chrome, so no playwright browser // download is needed on a fresh machine const browser = await chromium.launch({ channel: "chrome" }); const page = await browser.newPage(); await page.goto(pathToFileURL(tmp).href, { waitUntil: "networkidle" }); await page.evaluate(() => document.fonts.ready); // hard fail on layout overflow instead of shipping a clipped page const overflow = await page.evaluate(() => [...document.querySelectorAll(".page")].map((p, i) => ({ page: i + 1, over: Math.max(0, p.scrollHeight - p.clientHeight), })).filter((r) => r.over > 0), ); if (overflow.length) { await browser.close(); throw new Error("page overflow: " + JSON.stringify(overflow) + " (px past the sheet)"); } await page.pdf({ path: OUT, format: "Letter", printBackground: true, margin: { top: 0, right: 0, bottom: 0, left: 0 }, }); // --png: also dump per-page screenshots next to OUT for visual review if (process.argv.includes("--png")) { const sheets = await page.locator(".page").all(); for (let i = 0; i < sheets.length; i++) { await sheets[i].screenshot({ path: OUT.replace(/\.pdf$/, `-p${i + 1}.png`) }); console.log(` review png: ${OUT.replace(/\.pdf$/, `-p${i + 1}.png`)}`); } } await browser.close(); fs.unlinkSync(tmp); const kb = Math.round(fs.statSync(OUT).size / 1024); console.log(`spec sheet: ${OUT} (${kb} KB)`);