Brickheart

Dev

Changes

  • Brickheart website overview

    This supplements site-build.md and brand_guide.md. It covers only what was added, changed or corrected during the build, plus the reasoning behind it.

    State

    Both builds are live on Cloudflare Workers, deployed from GitHub. dev.brickheart.org (dev/): complete. brickheart.org (site/): English pages, picker, per-language 404s, hreflang sitemap and the translation pipeline are all working. zh is launched as translations are filled in. app/: not started.

    Repo layout (as built)

    package.json root: workspaces ["dev","site"], markdown-it dependency, wrangler, scripts shared/ assets/ copied wholesale into each dist (css, js, brand, fonts, favicons, manifest, _headers) engine/ build code, never copied: paths, markdown, layout, lists, sitemap (+ .test.mjs) dev/ content/, css/dev.css, config.mjs, scan.mjs, render.mjs, build.mjs, wrangler.jsonc site/ content/, css/site.css, config.mjs, languages.json, ui.json, links.mjs, scan.mjs, blocks.mjs, translate.mjs, localize.mjs, render.mjs, root.mjs, build.mjs, translations/, wrangler.jsonc Single dependency location: markdown-it lives only in the root package.json, because shared/engine/ resolves packages from the root node_modules. dev/package.json and site/package.json hold only a build script (gray-matter was removed). Scripts: npm test runs node --test shared/engine/.test.mjs dev/.test.mjs site/.test.mjs. build:dev and build:site run each build. Run all npm commands from the repo root. Cloudflare: the root directory is the repo root. Build is npm ci && npm run build:site (or build:dev), with CI set (Cloudflare sets it automatically). Each project has its own wrangler.jsonc with assets.directory: ./dist and not_found_handling: "404-page". .gitignore: node_modules/, dev/dist/, site/dist/, site/translations/todo.json. site/translations/translations.json must be committed. Corrections and additions to site-build.md

    Paths

    Assets land at the dist root (css/, js/, brand/, fonts/), not under assets/. Favicon is brand/favicon.svg. 404 pages are the one exception to relative links: they use root-absolute asset paths, because Cloudflare serves one file at any URL depth. Cloudflare serves .html files extensionless, so canonical URLs and sitemap entries have no .html, and index pages end in /. brand_guide.md, and on dev picker.js and page.js, are excluded from the dist copy.

    Brand and naming

    Roboto Slab is self-hosted as two variable .woff2 subsets with @font-face in base.css (the strict CSP forbids external fonts). Quote styling (figure.quote) lives in base.css because 404 pages don’t load the project CSS.

    Theme

    shared/assets/js/theme.js runs in the head, not deferred, on every page including 404s and the picker. It reads a bh-theme cookie, then localStorage, then the system setting. picker.js writes both localStorage and a cookie on .brickheart.org, so the choice carries between the site and dev. page.js now only clears the saved language on “Change settings”. There is no theme toggle on dev yet.

    Content rules

    Link rewriting (site): authors write About. The build rewrites it to the correct relative .html link, and fails on a missing target or a link starting with /. Dev doesn’t use it. Markdown renderer: raw HTML disabled; the h1 is removed from the body and placed by the template; each h2 becomes <section aria-labelledby>; code blocks get tabindex="0". h1 counting uses the parser, not a regex. All errors are collected and reported in one pass. Site page names are limited to a-z 0-9 _ -; 404.md is reserved.

    Dev changes

    Changelog: entries get no pages of their own. The index and per-category pages (app, dev, site, paginated) show full entry bodies, each with an anchor id. There is no changelog archive. Pages collection: dev/content/pages/ holds standalone pages, for example pages/ai.md → ai-transparency.html. AI transparency: every blog post should sit under one of the tag folders ai-none, ai-assisted or ai-generated. The entry page shows the matching note linked to the AI page. A missing tag is a warning, and two tags is an error. tagLabels in dev/config.mjs overrides tag display names. Excerpts come from the first paragraph. The old summary front matter and RSS feed are gone. Reserved slugs: index, archive, tags for blog entries, plus more for pages. Tag folders must be [a-z0-9_-], and a tag named like another tag’s page 2 (x-2 beside x) fails. Blog tag pages are at blog/tags/<name>.html.

    Site additions

    Footer on every page: “Change settings” link, LEGO trademark disclaimer, and the LDraw CC BY credit. These are UI strings, so they are translated. Root picker is generated from languages.json (English plus each language’s own name). There is one 404 per language plus a neutral root one. Header and footer logos: the small wordmark in the header at all widths

    Translation pipeline (site only)

    This replaces steps 1 to 6 of the scope with a simpler design. Blocks: each page is split into heading, paragraph and list-item blocks, keyed page/position. UI strings from ui.json are blocks keyed ui/<name>. Code blocks are skipped. One working file: translations/todo.json is both todo and inbox. Run a build, fill in the blank language fields, and build again. Filled fields merge in, blanks are skipped, and the file is rewritten with only what remains. If a translation is rejected for a fixable reason (changed link target or {placeholder}), your draft stays in the field. If it was rejected because the English changed, it’s dropped. Store is keyed by the hash of the English text, not by position. This was a redesign after an edit shifted positions and wiped every translation. Inserting, moving or reverting English costs nothing, identical English is translated once, and nothing is auto-deleted. The old position-keyed format migrates on read. Checks on merge: link targets and {placeholders} must match the English (this includes {licence} and {language}). Building languages: a page is translated only if all its blocks and all UI strings have translations. Translated pages are rebuilt from the English Markdown with blocks swapped, and carry a “machine translated” notice. Incomplete pages are built in English inside the language folder, with lang="en-GB" and an English canonical, and stay out of the hreflang sitemap. A language with no complete pages isn’t built, and doesn’t appear in the picker, 404s or sitemap. CI is read-only: with CI set, the build only reads translations.json. It never merges, writes or fails on stale translations. Don’t set CI locally, but CI=1 npm run build:site simulates Cloudflare. Workflow: run npm run build:site, fill in todo.json, run it again, then commit translations.json with the content change that needed it.

    Known limits: translatable blocks are top-level paragraphs, headings (# style) and list items. Blockquote text isn’t collected, setext headings aren’t handled, and a multi-paragraph list item only keeps its first paragraph. Site content should avoid these.

    Open items

    Move shared code-block and blockquote styles from dev.css into a shared stylesheet so site articles get them. Add additional languages to languages.json, and translate. A dev theme toggle, an optional prune-translations command, and a build warning for unsupported Markdown in translatable content. The app/ WebGL editor (offline, with its own bundled logos and PWA icons per the brand guide).