// The public clip gallery: authored screenplay scenes recorded through // the reel, each in two takes โ through the wizard's eyes and from the // board above. Standalone pages, no app bundle: a clip link must play // anywhere a browser lands, and unfurl anywhere it's pasted. import type { ClipMeta } from "./store.js"; const esc = (s: string) => s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); const PAGE_CSS = ` * { box-sizing: border-box; } body { margin: 0; background: #171a20; color: #d8d2c0; font-family: "Archivo Narrow", sans-serif; } a { color: #e0b34a; } .wrap { max-width: 1100px; margin: 0 auto; padding: 1.4rem 1.25rem 3rem; } .mast { display: flex; align-items: baseline; gap: 1rem; margin-bottom: 1.6rem; border-bottom: 1px solid rgba(233, 225, 203, 0.18); padding-bottom: 0.7rem; } .mast a { font-family: "Oswald", sans-serif; text-transform: uppercase; letter-spacing: 0.18em; font-size: 0.95rem; color: #e9e1cb; text-decoration: none; } .mast .crumb { color: #8d8672; font-size: 0.85rem; letter-spacing: 0.06em; } .mast .crumb a { font: inherit; letter-spacing: inherit; text-transform: none; color: inherit; } .mast .deal { margin-left: auto; font-size: 0.8rem; letter-spacing: 0.12em; color: #e0b34a; } h1 { font-family: "Oswald", sans-serif; font-weight: 500; font-size: 1.7rem; letter-spacing: 0.06em; text-transform: uppercase; margin: 0 0 0.3rem; color: #e9e1cb; } h1 a { color: inherit; text-decoration: none; } p.sub { color: #a49c86; margin: 0 0 1.6rem; font-size: 1.05rem; max-width: 46rem; } .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1.4rem; } .card { background: #1f232d; border: 1px solid rgba(233, 225, 203, 0.18); border-radius: 8px; overflow: hidden; text-decoration: none; color: inherit; display: block; } .card:hover { border-color: #e0b34a; } .card img { width: 100%; display: block; aspect-ratio: 736 / 577; object-fit: cover; } .card .meta { padding: 0.7rem 0.9rem 0.9rem; } .card .t { font-family: "Oswald", sans-serif; color: #e0b34a; letter-spacing: 0.06em; text-transform: uppercase; font-size: 0.95rem; } .card .b { font-size: 0.95rem; line-height: 1.4; margin-top: 0.3rem; color: #b5b0a1; } .card .s { float: right; color: #8d8672; font-size: 0.85rem; font-family: "Courier Prime", monospace; } video { width: 100%; border-radius: 6px; display: block; background: #000; } .takes { display: grid; gap: 1.5rem; margin-top: 1.4rem; } .take h2 { font-family: "Oswald", sans-serif; font-size: 0.9rem; color: #e0b34a; font-weight: 500; letter-spacing: 0.14em; text-transform: uppercase; margin: 0 0 0.5rem; } .foot { margin-top: 2.2rem; color: #8d8672; font-size: 0.95rem; } .share { font: inherit; font-size: 0.85rem; letter-spacing: 0.12em; text-transform: uppercase; cursor: pointer; font-family: "Oswald", sans-serif; color: #43331f; background: #e9e1cb; border: 1.5px solid #43331f; border-radius: 3px; padding: 0.4rem 1rem; margin: -0.6rem 0 0.4rem; } .share:hover { background: #f2d27a; } .share.done { background: #d8d2c0; } `; // The share button: the phone's own share sheet where there is one, // the clipboard everywhere else. The page URL is the canonical link. const SHARE_JS = ` var b = document.querySelector(".share"); b.addEventListener("click", function () { var data = { title: b.dataset.title, text: b.dataset.text, url: location.origin + location.pathname }; if (navigator.share) { navigator.share(data).catch(function () {}); return; } navigator.clipboard.writeText(data.url).then(function () { b.textContent = "Link copied"; b.classList.add("done"); setTimeout(function () { b.textContent = "Share this clip"; b.classList.remove("done"); }, 1800); }); }); `; const shell = (metas: string[], body: string) => `
${metas.join("\n")}
`).join("\n");
const body = `
Scenes of Wiz-War, played by the rules and filmed through the wizards' own eyes. Each clip has a first-person take and a board take of the same moves.
Wiz-War is Tom Jolly's classic of magical combat. This is a fan-built table, and you can deal yourself in.
`; return shell(metas, body); } export function clipPageHtml(clip: ClipMeta, base: string): string { const metas = [ `${esc(clip.blurb)}
Played by the real rules engine, so every move on screen is a legal move. Deal yourself in.
`; return shell(metas, body); }