/* Writes the site's plain-text docs for agents from site/api.ts, into site/public, which the site serves at its root: /llms.txt index, in the llms.txt format /llms-full.txt everything in one file /agents.md how to use the package, for coding agents /docs/api.md the whole reference /docs/options/.md one file per option, geometry field and method /docs/examples/.md copy-paste examples /options.json the reference as JSON Run `npm run docs` after changing site/api.ts; `npm test` fails while the files are out of date. */ import { mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { EXAMPLES, GROUPS, INSTALL, NPM_URL, PACKAGE, RULES, SITE_URL, SOURCE_URL, SUMMARY, VERSION, type ApiEntry, type ApiGroup, type Example, } from "../site/api.ts"; import { entryPath } from "../site/slug.ts"; const fence = (language: string, code: string) => `\`\`\`${language}\n${code}\n\`\`\``; function entryText(group: ApiGroup, entry: ApiEntry) { const lines = [`### ${entry.name}`, "", `- Type: \`${entry.type}\``]; if (entry.default) lines.push(`- Default: ${entry.default.startsWith("\"") || /^[\d.]+$|^true$|^false$|^null$/.test(entry.default) ? `\`${entry.default}\`` : entry.default}`); lines.push(`- Group: ${group.title}`, "", entry.description); if (entry.example) lines.push("", fence(entry.example.startsWith("<") ? "tsx" : "ts", entry.example)); return lines.join("\n"); } function groupText(group: ApiGroup) { return [`## ${group.title}`, "", group.intro, "", ...group.entries.map(entry => entryText(group, entry))].join("\n\n").replace(/\n{3,}/g, "\n\n"); } function exampleText(example: Example, level = 2) { return [`${"#".repeat(level)} ${example.title}`, "", example.description, "", fence(example.language, example.code)].join("\n"); } const header = [ `# ${PACKAGE}`, "", `> ${SUMMARY}`, "", `Version ${VERSION}. Install: \`${INSTALL}\`. npm: ${NPM_URL}. Source: ${SOURCE_URL}. Playground: ${SITE_URL}.`, ].join("\n"); const quickstart = [ "## Quick start", "", fence("sh", INSTALL), "", fence("tsx", `import { LiquidGlass } from "${PACKAGE}/react"; import "${PACKAGE}/styles.css"; `), "", "## Rules", "", ...RULES.map(rule => `- ${rule}`), ].join("\n"); export function render() { const files = new Map(); const optionFiles: { path: string; title: string; summary: string }[] = []; for (const group of GROUPS) { for (const entry of group.entries) { const path = entryPath(group.id, entry.name); files.set(path, `${header}\n\n${entryText(group, entry)}\n`); optionFiles.push({ path, title: `${group.title}: ${entry.name}`, summary: entry.description.split(". ")[0]?.replace(/\.$/, "") ?? "" }); } } for (const example of EXAMPLES) { files.set(`docs/examples/${example.id}.md`, `${header}\n\n${exampleText(example)}\n`); } const reference = GROUPS.map(groupText).join("\n\n"); const examples = EXAMPLES.map(example => exampleText(example)).join("\n\n"); files.set("docs/api.md", `${header}\n\n${quickstart}\n\n${reference}\n`); files.set( "llms.txt", [ header, "", "Liquid Glass is a material, not a component library: put `` first inside any positioned, rounded element and it becomes glass. Every option below has its own plain-text page.", "", "## Docs", "", `- [Agents guide](${SITE_URL}/agents.md): how to use the package correctly, with the rules that keep the glass from blanking`, `- [Full reference](${SITE_URL}/docs/api.md): every option, geometry field, method and export`, `- [Everything in one file](${SITE_URL}/llms-full.txt)`, `- [Reference as JSON](${SITE_URL}/options.json)`, "", "## Examples", "", ...EXAMPLES.map(example => `- [${example.title}](${SITE_URL}/docs/examples/${example.id}.md): ${example.description}`), "", "## Options", "", ...optionFiles.map(file => `- [${file.title}](${SITE_URL}/${file.path}): ${file.summary}`), "", ].join("\n"), ); files.set("llms-full.txt", `${header}\n\n${quickstart}\n\n${reference}\n\n# Examples\n\n${examples}\n`); files.set( "agents.md", [ `# Using ${PACKAGE}`, "", `For coding agents adding Liquid Glass to a web app. ${SUMMARY}`, "", `Version ${VERSION}. Every option has a plain-text page under ${SITE_URL}/docs/options/; the index is ${SITE_URL}/llms.txt.`, "", "## Install", "", fence("sh", INSTALL), "", "Import the stylesheet once, at the app's root:", "", fence("ts", `import "${PACKAGE}/styles.css";`), "", "## Pick the entry point", "", "- React: `import { LiquidGlass, useLiquidGlass, useLiquidGlassTabs } from \"@colineapp/liquid-glass/react\"`.", "- Anything else: `import { createLiquidGlass, attachLiquidGlassTabs } from \"@colineapp/liquid-glass\"`.", "- With a class-based dark mode (Tailwind `dark:`, next-themes), pass `appearance=\"class\"`. Otherwise the glass follows prefers-color-scheme.", "", "## Pick the options", "", "| Surface | Options |", "| --- | --- |", "| Nav bar, toolbar, card | none, or `interactive` if it holds controls |", "| Search field, button you press | `interactive swell` |", "| Menu, popover | `floating`, and `materialize()` on open |", "| Dialog, sheet | `floating=\"dialog\"`, `fit=\"scroll\"` if it scrolls itself |", "| Glass over a photo or video | `variant=\"clear\"` |", "| Tinted glass | `hue=\"#3d6bff\"` |", "| Segmented control, tab bar | `useLiquidGlassTabs(trackRef)` on your own tabs |", "", "## Rules that keep the glass working", "", ...RULES.map(rule => `- ${rule}`), "", "## Checking your work", "", "- The surface element has `data-liquid-glass=\"dark\"` or `\"light\"` once the glass is attached, and its glass child has `data-ready` once the rim is built.", "- In Chromium the glass child also has `data-refraction`. If the glass looks flat grey or empty, an ancestor has opacity, a filter, a mask or a backdrop-filter.", "- Don't put a background colour on the surface to make it look glassy: the glass is the background.", "", "## Examples", "", EXAMPLES.map(example => exampleText(example, 3)).join("\n\n"), "", ].join("\n"), ); files.set( "options.json", `${JSON.stringify( { package: PACKAGE, version: VERSION, install: INSTALL, summary: SUMMARY, rules: RULES, groups: GROUPS, examples: EXAMPLES, }, null, 2, )}\n`, ); return files; } const publicDir = join(dirname(fileURLToPath(import.meta.url)), "..", "site", "public"); export function write() { for (const dir of ["docs"]) rmSync(join(publicDir, dir), { recursive: true, force: true }); for (const [path, content] of render()) { const target = join(publicDir, path); mkdirSync(dirname(target), { recursive: true }); writeFileSync(target, content); } } /** Generated files on disk, for the freshness test. */ export function listWritten() { const found: string[] = []; const walk = (dir: string, prefix: string) => { for (const entry of readdirSync(dir, { withFileTypes: true })) { if (entry.isDirectory()) walk(join(dir, entry.name), `${prefix}${entry.name}/`); else found.push(`${prefix}${entry.name}`); } }; walk(join(publicDir, "docs"), "docs/"); return found; } export { publicDir }; if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { write(); console.log(`Wrote ${render().size} files to site/public.`); }