ca122ad723
Deploy / deploy (push) Successful in 30s
- Unlinked, noindex page listing marker PNGs from public/set/markers (discovered at build time; filenames encode device/resolution/orientation) - Detects the current screen and offers the closest match first - One-tap full-screen viewer: wake lock, hidden controls, full grid/edges toggle, fill vs fit by aspect, back button closes, deep links via hash - Offline service worker + home-screen manifest for iPhone full screen - Marketing chrome moved into an (site) route group so /set has none Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import fs from "node:fs";
|
||
import path from "node:path";
|
||
|
||
// Screen-tracking marker images for on-set use (served at /set).
|
||
// Files live in public/set/markers and are discovered at build time, so adding
|
||
// a device is just dropping in a PNG named like:
|
||
// markers_phone_iPhone-Pro_1179x2556.png
|
||
// edge-markers_phone_iPhone-Pro_2556x1179_landscape.png
|
||
// markers_laptop_1920x1080.png (no device name)
|
||
// plain_grey_128.png (anything else = "extra")
|
||
|
||
export type Kind = "phone" | "tablet" | "laptop" | "monitor";
|
||
|
||
export type MarkerScreen = {
|
||
id: string; // stable key, e.g. "phone-iphone-pro-1179x2556"
|
||
kind: Kind;
|
||
name: string; // "iPhone Pro"
|
||
width: number;
|
||
height: number;
|
||
landscape: boolean;
|
||
full?: string; // /set/markers/markers_….png
|
||
edge?: string; // /set/markers/edge-markers_….png
|
||
};
|
||
|
||
export type MarkerExtra = { id: string; name: string; src: string };
|
||
|
||
const DIR = path.join(process.cwd(), "public", "set", "markers");
|
||
const URL_BASE = "/set/markers/";
|
||
const ORDER: Kind[] = ["phone", "tablet", "laptop", "monitor"];
|
||
|
||
const PATTERN = /^(edge-markers|markers)_(phone|tablet|laptop|monitor)_(?:(.+?)_)?(\d+)x(\d+)(_landscape)?\.png$/i;
|
||
|
||
function prettyName(raw: string | undefined, kind: Kind, w: number, h: number) {
|
||
if (!raw) {
|
||
if (kind === "laptop" && Math.max(w, h) === 1920) return "Laptop Full HD";
|
||
return `${kind[0].toUpperCase()}${kind.slice(1)} ${w}×${h}`;
|
||
}
|
||
return raw
|
||
.replace(/-/g, " ")
|
||
.replace(/ProMax/g, "Pro Max")
|
||
.replace(/(\d+(\.\d+)?)$/, "$1″")
|
||
.replace(/^4K$/, "4K Monitor");
|
||
}
|
||
|
||
export function loadMarkers(): { screens: MarkerScreen[]; extras: MarkerExtra[] } {
|
||
const files = fs.existsSync(DIR) ? fs.readdirSync(DIR).filter((f) => f.toLowerCase().endsWith(".png")) : [];
|
||
const screens = new Map<string, MarkerScreen>();
|
||
const extras: MarkerExtra[] = [];
|
||
|
||
for (const file of files.sort()) {
|
||
const m = file.match(PATTERN);
|
||
if (!m) {
|
||
const name = file
|
||
.replace(/\.png$/i, "")
|
||
.replace(/[_-]+/g, " ")
|
||
.replace(/\b\w/g, (c) => c.toUpperCase());
|
||
extras.push({ id: file.replace(/\.png$/i, "").toLowerCase(), name, src: URL_BASE + file });
|
||
continue;
|
||
}
|
||
const [, variant, kindRaw, rawName, w, h, land] = m;
|
||
const kind = kindRaw.toLowerCase() as Kind;
|
||
const width = Number(w);
|
||
const height = Number(h);
|
||
const id = `${kind}-${(rawName ?? "generic").toLowerCase()}-${width}x${height}`;
|
||
const screen = screens.get(id) ?? {
|
||
id,
|
||
kind,
|
||
name: prettyName(rawName, kind, width, height),
|
||
width,
|
||
height,
|
||
landscape: Boolean(land) || width > height,
|
||
};
|
||
if (variant.toLowerCase() === "edge-markers") screen.edge = URL_BASE + file;
|
||
else screen.full = URL_BASE + file;
|
||
screens.set(id, screen);
|
||
}
|
||
|
||
const sorted = [...screens.values()].sort(
|
||
(a, b) =>
|
||
ORDER.indexOf(a.kind) - ORDER.indexOf(b.kind) ||
|
||
a.name.localeCompare(b.name) ||
|
||
Number(a.landscape) - Number(b.landscape),
|
||
);
|
||
return { screens: sorted, extras };
|
||
}
|