Files
ttvfx/lib/markers.ts
T
twotalesanimation ca122ad723
Deploy / deploy (push) Successful in 30s
Add /set: on-set screen tracking markers for crews
- 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>
2026-10-06 09:11:36 +02:00

86 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 };
}