Files
horizon-airdef/website/scripts/assets.mjs
T
Ludovic Marie 189119a861 Website: a cinematic one-page site in the launcher's design language, on playhorizon.dev
The launcher's palette, violet accent, Instrument Serif and Geist, without its sidebar
and settings rows: a full-viewport hero over Horizon's own sunset (slow drift,
parallax, grain), then sections for what it is (a statement that lights up as it
scrolls), a screenshot mosaic, the parity count and a grid of the 14 subsystems, the
launcher beside Horizon's own menus, the latest patch notes, the roadmap, community
and the footer. Reveals, staggered letters and magnetic buttons stop under
prefers-reduced-motion; a translucent top bar and a full-screen phone menu replace
the sidebar.

playhorizon.dev: canonical, og:url, absolute og/twitter images, sitemap.xml and its
robots.txt line, and a 301 from *.up.railway.app (Railway's healthcheck host is still
served).

The full-page screenshots are no longer committed (about 17 MB): `npm run shots`
writes them to the gitignored shots/. The unused blurred background is gone; LEGAL.md
lists the hero and the settings screenshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:46:19 +02:00

68 lines
3.2 KiB
JavaScript

// Copies the launcher's fonts and the repo's screenshots into public/,
// converted for the web (woff2, webp in two widths). Run it after any of them change:
// `npm run assets`. Its output is committed, because Railway builds only website/.
import { mkdir, readFile, writeFile, copyFile } from "node:fs/promises";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import sharp from "sharp";
import wawoff2 from "wawoff2";
const site = join(dirname(fileURLToPath(import.meta.url)), "..");
const repo = join(site, "..");
const pub = join(site, "public");
const fonts = [
"Geist-Regular.ttf",
"Geist-Medium.ttf",
"Geist-SemiBold.ttf",
"GeistMono-Regular.ttf",
"InstrumentSerif-Regular.ttf",
];
// The launcher's screenshots are left out: they show its background, whose licence is
// unconfirmed (see LEGAL.md). [source, name]; every image becomes media/<name>.webp and media/<name>-800.webp.
export const images = [
["docs/media/sunset.jpg", "sunset"],
["docs/media/village.jpg", "village"],
["docs/media/survival.jpg", "survival"],
["docs/media/nether.jpg", "nether"],
["docs/media/night.jpg", "night"],
["docs/media/underwater.jpg", "underwater"],
["docs/media/menu-title.png", "menu-title"],
["docs/media/menu-worlds.png", "menu-worlds"],
["docs/media/menu-options.png", "menu-options"],
["docs/media/menu-pause.png", "menu-pause"],
["docs/media/settings-simple.png", "settings"],
];
await mkdir(join(pub, "fonts"), { recursive: true });
await mkdir(join(pub, "media"), { recursive: true });
for (const font of fonts) {
const ttf = await readFile(join(repo, "crates/horizon-theme/fonts", font));
await writeFile(join(pub, "fonts", font.replace(".ttf", ".woff2")), await wawoff2.compress(ttf));
}
for (const licence of ["OFL-Geist.txt", "OFL-InstrumentSerif.txt"]) {
await copyFile(join(repo, "crates/horizon-theme/fonts", licence), join(pub, "fonts", licence));
}
for (const [source, name] of images) {
const input = join(repo, source);
await sharp(input).resize({ width: 1600, withoutEnlargement: true }).webp({ quality: 82 }).toFile(join(pub, "media", `${name}.webp`));
await sharp(input).resize({ width: 800 }).webp({ quality: 78 }).toFile(join(pub, "media", `${name}-800.webp`));
}
// The hero: Horizon's sunset over a savanna river valley, darkened and cooled a little so the
// wordmark and the violet accent sit on it, in a landscape and a portrait crop.
const hero = () => sharp(join(repo, "docs/media/sunset.jpg")).modulate({ brightness: 0.82, saturation: 0.86 }).linear(1.04, -6);
await hero().webp({ quality: 74 }).toFile(join(pub, "media/hero.webp"));
await hero().resize({ width: 1200 }).webp({ quality: 72 }).toFile(join(pub, "media/hero-1200.webp"));
await hero().extract({ left: 470, top: 0, width: 840, height: 1080 }).resize({ width: 720 }).webp({ quality: 72 }).toFile(join(pub, "media/hero-portrait.webp"));
// Icons from the project's logo.
await copyFile(join(repo, "docs/media/logo.svg"), join(pub, "favicon.svg"));
await sharp(join(repo, "docs/media/logo.svg")).resize(180).png().toFile(join(pub, "apple-touch-icon.png"));
await sharp(join(repo, "docs/media/logo.svg")).resize(32).png().toFile(join(pub, "favicon-32.png"));
console.log("assets written to public/");