// Shared HTML builders for the live-preview gallery. Every specimen is built
// from REAL framework classes (.sf-*) plus the thin token-driven pv-* skin
// (see ./skin.ts). Values that need to vary per specimen use inline
// `style="…:var(--sf-*)"` — still 100% token-driven, so configurator overrides
// update them live exactly like a class would. Nothing here re-implements a
// framework component; it only arranges real ones and labels them.
/** Escape text that ends up as HTML content (token/class names, notes). */
export function esc(s: string): string {
return String(s)
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
/** Monospace caption under a specimen (class/token name or scale step). */
export function tag(text: string): string {
return `${esc(text)}`;
}
/** A labelled specimen cell: the rendered thing, then its name underneath. */
export function specimen(label: string, body: string): string {
return `
${body}${tag(label)}
`;
}
/** Like `specimen`, but for primitives whose whole point is filling real
* available width (grid column counts, sidebar, switcher, bento) — the
* `sf-stack--center` in the base variant shrink-wraps its child to
* intrinsic content width (`align-items: center`), which silently
* defeats these: a 4-column grid or a sidebar collapse can't demonstrate
* anything at ~100px. Only the caption is centered here; the demo itself
* stretches to the card's full width, same as it would in real markup. */
export function specimenFull(label: string, body: string): string {
return `${body}${tag(label)}
`;
}
/** A titled section block (eyebrow + optional note + content). */
export function section(eyebrow: string, content: string, note?: string): string {
const noteHtml = note ? `${esc(note)}
` : "";
return `
${esc(eyebrow)}
${noteHtml}
${content}
`;
}
/** The page shell every tab body is wrapped in. `head` is the tab title row. */
export function page(title: string, lead: string, ...sections: string[]): string {
return `
Framework API
${esc(title)}
${esc(lead)}
${sections.join("\n")}
`;
}
export function cluster(...items: string[]): string {
return `${items.join("")}
`;
}
export function stack(gap: "xs" | "s" | "m" | "l", ...items: string[]): string {
return `${items.join("")}
`;
}
/** A responsive auto-fitting grid of specimen cells. `min` in rem. */
export function grid(min: number, ...items: string[]): string {
return `${items.join("")}
`;
}
/** A bordered card used as a neutral backdrop for a specimen row. */
export function well(content: string): string {
return `${content}
`;
}
/** A plain bordered surface backdrop — like `well`, but NOT a `.sf-card`.
* Use for rows of `.sf-btn`: the framework's `.sf-card .sf-btn` rule pins a
* nested button's label to `--sf-text-s`, which would flatten a per-size
* button font ladder; a non-card frame lets each button render its true size. */
export function frame(content: string): string {
return `${content}
`;
}