/** * The page, rendered on the server: the first paint is complete HTML, * `static/live.js` keeps the board fresh afterwards, and `static/glyph.js` * adds what only a script can (copy, the palette swapped in place). When a second page * arrives, move `layout` to `src/pages/layout.ts` and give each page a file. */ import { canDelete, svgsOf, type Drawing } from "./actions.ts"; import { COUNT, inPalette, isPalette, KINDS, nameOf, PALETTES, PROMPT_MAX, type Palette, } from "./glyph.ts"; import { commit, description, env, publicOrigin, ssh, title, where, } from "./env.ts"; import { asset, escape } from "./http.ts"; import { token, tokensHref } from "./tokens.ts"; import type { Who } from "./who.ts"; /** * The frame every page shares: the tokens and the look, who you are, and * what a link to the page says when it is shared — a title, a sentence * and the address, for Open Graph and the card a chat app unfurls. */ export function layout(body: string, who: Who, path = "/"): string { const door = who.role === "anon" ? `Sign in` : `${escape(who.name)}${who.role === "admin" ? " · admin" : ""} Sign out`; return ` ${escape(title)}

${escape(title)}

${escape(description)}

${body}
`; } export function home(input: { who: Who; drawings: readonly Drawing[]; palette?: string | null; notice?: string | undefined; }): string { const { who, drawings, notice } = input; const palette: Palette = isPalette(input.palette) ? input.palette : "sage"; const composer = who.role === "anon" ? `

Drawings are made for whoever asks. Sign in to draw.

` : `
${Object.entries(KINDS) .map( ([k, label], i) => ``, ) .join("")}
`; return layout( `
${composer}
${palettes(palette)}
${board(drawings, who, palette)}
${notice === undefined ? "" : `

${escape(notice)}

`}`, who, ); } const EXAMPLES = [ "a hex chip with a circuit b", "a lighthouse for a monitoring app", "a rocket leaving a git branch", "a fox made of circuit traces", ]; /** The palette switch: links that work without JavaScript, swapped in place with it. */ const palettes = (current: Palette): string => ``; function board(drawings: readonly Drawing[], who: Who, palette: Palette): string { if (drawings.length === 0) return `

Nothing drawn yet. Describe something above: Claude draws ${COUNT} different takes in about a minute.

`; return ``; } function card(d: Drawing, who: Who, palette: Palette): string { const { ground } = PALETTES[palette]!; const svgs = svgsOf(d); const body = d.status === "drawing" ? `
${Array.from({ length: COUNT }, () => `
`).join("")}

Claude is drawing…

` : d.status === "failed" ? `

${escape(d.error ?? "Something went wrong.")}

` : `
${svgs .map((svg, n) => { const name = nameOf(svg); const href = `/drawings/${d.id}/${n}.svg?palette=${palette}`; return `
${escape(name)}
${escape(name)} Save
`; }) .join("")}
`; return `
  • ${escape(d.prompt)}

    ${escape(KINDS[d.kind] ?? d.kind)}${d.author_id === (who.role === "anon" ? "" : who.id) ? "" : ` · ${escape(d.author_name)}`} ·
    ${ canDelete(who, d) && d.status !== "drawing" ? `
    ` : "" }
    ${body}
  • `; } const ago = (iso: string): string => { const s = Math.max(0, (Date.now() - Date.parse(iso)) / 1000); if (s < 60) return "just now"; if (s < 3600) return `${Math.floor(s / 60)}m ago`; if (s < 86400) return `${Math.floor(s / 3600)}h ago`; return new Date(iso).toLocaleDateString("en", { month: "short", day: "numeric", }); };