{"_id":"@anthonyworks/brochure-kit","_rev":"12-5f920935545b6a34a4fdf50547ef0bbe","name":"@anthonyworks/brochure-kit","dist-tags":{"latest":"0.2.10"},"versions":{"0.1.0":{"name":"@anthonyworks/brochure-kit","version":"0.1.0","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.1.0","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"ae9f3919b26c255e6dbaca769f868b499e12e302","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.1.0.tgz","fileCount":17,"integrity":"sha512-megphM92trRvJ+93bdvtIHW86UQwasSYW+AJ/sqsC0l/YXnXIBcr6x0rVIcKIXurKoekzNQ6/Q4/R+2qu9ZdlA==","signatures":[{"sig":"MEQCIG4YLxDn9o7CmfhxgyyRsaZ4SUerbcrxU7H27jyWtUdYAiA4RvbrfsicQVBAx9Mtp3BFld7q/DFpuPx72kuEeAHQYg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":308711},"type":"module","gitHead":"e349c50181930ece9ed8972be529e1d53b4d2d87","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.1.0_1786572556450_0.9380454359889814","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@anthonyworks/brochure-kit","version":"0.2.0","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.0","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"f17ed22cedf199448cfcc979eb4fcc3776c52334","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.0.tgz","fileCount":18,"integrity":"sha512-WT1cKbN7KD/tr3xoxnTIzgePrvUkdoaCIjf5DvUOV24LRfJjERM6s1z9M3cxJnMJMLMOezaBAMgrqCkjSZzNtA==","signatures":[{"sig":"MEUCIFt35BEQ20YYdj0t8SEd4qELqoCMFgPqnZB9SbHPdmN3AiEAp9xODx/6J96c8qh79PlTjbEbfmGD5+KznYVGz1f5gjM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":323677},"type":"module","gitHead":"8bcf836c3bb3b9e50970a257c5b0ed766da6071e","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.0_1786661735443_0.7218174152843744","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@anthonyworks/brochure-kit","version":"0.2.1","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.1","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"b4549b4eb4846e244240ea92c61478e9550a10f8","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.1.tgz","fileCount":18,"integrity":"sha512-NU29ozQv1RwWzKfmj4pQm82Xf/lpiKPtiyf3uRqdUNDv19nxnlE5KRdWcn6q1Ngk05c1RU4RJAzLdSG+ScwIVA==","signatures":[{"sig":"MEUCIQC/DCvSi5gkifX9aT51kknQWexv04728SeW8XUsuZ1j5wIgFUVmtTDUYAI/ekXrRf9ee/6tiNIj+9wGv6ePfJfBlq8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":326643},"type":"module","gitHead":"86f72be25eff190909536bdc36cbfd779dca865f","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.1_1786751807919_0.7100858418863818","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@anthonyworks/brochure-kit","version":"0.2.2","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.2","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"79eae6c812cfc9c5eb3e493489795eac94ca376b","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.2.tgz","fileCount":19,"integrity":"sha512-NX5OCgUCi/LkPOZ//uN0IrzS8dUcl+I92C5Jqe2LIQDbtkQgzKs9y/BkPoV8yDEjoMRvISuGMF32m/68rAUR/Q==","signatures":[{"sig":"MEQCIBvLO+MExEq3iQa9Au5IIjVCh1x8TstPnsxKMrwWkI+RAiASCr8Qoev2NBSztOnI2pEW9ToKcDEeEKXsFloULzSgzw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":341712},"type":"module","gitHead":"ee6ea7ee524f9bafe0c8ee9e73d5326f234bde0b","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.2_1786756422911_0.5733456314849044","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@anthonyworks/brochure-kit","version":"0.2.3","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.3","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"a5c92332a4598d29c7e4d88ce7e9026e9529cdeb","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.3.tgz","fileCount":19,"integrity":"sha512-DlRtjvlW5N3Anc8+i7JfcPFrkM4zWQKFE+ihT0YlPBNfNS/r0h8kMWRGry7vmZlqAiM83XavJDKjZ5LuBpBs7Q==","signatures":[{"sig":"MEUCIF+OWNyJibXf4c9McGfbqJxMcxu43jGm2O37AS7zPv+ZAiEAtmMZSD+XvqnMmJBx1LPexNkbd04+E+22jswdIxB4Cfg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":342113},"type":"module","gitHead":"4b88d1bda2ff6c642b6709d875d1801299f62e70","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.3_1786836926060_0.9354690032273492","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@anthonyworks/brochure-kit","version":"0.2.4","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.4","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"b9d73b07358255bcdd812dfa755256c00e731019","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.4.tgz","fileCount":19,"integrity":"sha512-xf0UErngoqRxjmJdmckryGkJ3GQcijJil96Phd9mO8biYV/tT0OM10F4tkjTcsnMiaYUEIxccCeLd9a5uuCrQA==","signatures":[{"sig":"MEYCIQC1mR8Y6TjFcVPfSq5YBOvMrGUaW58W31k8rChn196QwAIhAPsQCeSARd3Svjxm+7RFM1rmGWBmzfEURjRJoCswCYsz","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":342042},"type":"module","gitHead":"4c82d5c30be57f7d57328d856ab25bfbd853daf0","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.4_1786865949562_0.6572953690040406","host":"s3://npm-registry-packages-npm-production"}},"0.2.5":{"name":"@anthonyworks/brochure-kit","version":"0.2.5","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.5","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"430f65250ee7db0d1f83f3f1ff9024ce2b262331","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.5.tgz","fileCount":19,"integrity":"sha512-YosfAsTiXw6mgabhFE6jvdMvkOhV/astMVtr2gqFjcXgOzN5f9/bZPnq5QuKpQCJj3hzmToFz9Us7i9DB/glcg==","signatures":[{"sig":"MEUCIQDqP3IEu8ixdbKWVD1blblsOjh41qU61kS4npFcWv8UdwIgNthCfDwXyQWGRkmwBkHXMNw7KRF7QyjyYwQf95xNRyE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":346646},"type":"module","gitHead":"c0b84991c37339b59b8f1ccd966555bdb4e7a3e6","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.5_1786915651536_0.5075669439524109","host":"s3://npm-registry-packages-npm-production"}},"0.2.6":{"name":"@anthonyworks/brochure-kit","version":"0.2.6","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.6","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"50fef6e5b9db7bacc51b6417d7ab70f16460806e","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.6.tgz","fileCount":19,"integrity":"sha512-U/RaxYmQLwX2z8hTVL1n45PWw1OZIeEpLa4ahvgEbmBLWOgaOpLK1cEaRqptu8U7q0yeiqUHL/f7bib8PCacLw==","signatures":[{"sig":"MEYCIQD5+AQzpqJRUSh0OsPt8phJsi6K8qRfeEAdyCWvhdS4mgIhAPVLpRfmDdXAVkd29ShwhJ90YywxcBLiDQPtcAAlp24G","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":348439},"type":"module","gitHead":"f3b1d143d69a5a20105bcc1412f0628e123011e4","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.6_1786944237253_0.8935640133942397","host":"s3://npm-registry-packages-npm-production"}},"0.2.7":{"name":"@anthonyworks/brochure-kit","version":"0.2.7","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.7","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"32c7bf52c87907211a8609217f5ad549f19ba39f","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.7.tgz","fileCount":19,"integrity":"sha512-tRD1elyi0HHhp+kul+jx9oijJ4Rihq8vU2zDlFGOIbnXv9dj7G0wV4E0D6kD9Be/wAWVdImaxdRvfdi/6I++iA==","signatures":[{"sig":"MEQCIFCp9YVg+ebtJo/tQ2rksYv9uyNQNZmBUuJJ4PF26xiLAiBashT/k8DfvlZjubFXG0k7FMkpSA4603I7lTmjyoyg7w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":350090},"type":"module","gitHead":"57dd83e186c10b0ac3aee68b499c6c6a47129c4f","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.7_1787012660928_0.7546091187142638","host":"s3://npm-registry-packages-npm-production"}},"0.2.8":{"name":"@anthonyworks/brochure-kit","version":"0.2.8","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.8","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"b5237d422df95a2ec5a3e50ab6f07eee8980263f","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.8.tgz","fileCount":19,"integrity":"sha512-vEOoH6JVIbCZWAyeHwxGQ9IU5cskgpE/PQ/hkoq1LgSnmbm56v9+nuRuY1w7a8B18nbfV4+1m2J5lCeYV8lLoQ==","signatures":[{"sig":"MEUCIG5rC3dSDZbfOTxjyX/HcnYz4wuRC+YqbZ4ZHCRiO9sbAiEA7fwK5YipCOxbnSlsWhtT6FbE+sMz9d0copjQu+KOeRU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":482685},"type":"module","gitHead":"ee904b5900a34a2b1019437ad95852b4d80acc3f","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.8_1787053365608_0.11502729682773882","host":"s3://npm-registry-packages-npm-production"}},"0.2.9":{"name":"@anthonyworks/brochure-kit","version":"0.2.9","license":"UNLICENSED","_id":"@anthonyworks/brochure-kit@0.2.9","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"dist":{"shasum":"389c17e4187c16dbecdeb56daf544ba8ac8063c9","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.9.tgz","fileCount":19,"integrity":"sha512-d5HhttDcqwfGUJxbQxRZXZmjYR+JZop7WkP7zu3C0S1UN17T+8BQL2y45qLKbR+AbTfMgQUoON3CMNK/5YNxfw==","signatures":[{"sig":"MEQCIHRGryOieZLOlc/fHxOvYHGSiLM+w2aUeLunb0s5oGOhAiBM5iSwtBQHRarBfnvYbvIDalaDUEhaTQPaH//SOgmLHA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":483499},"type":"module","gitHead":"5f217fc5d7426af2a61aadad20d3860085528c91","private":false,"scripts":{"publish:check":"npm pack --dry-run"},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"_npmVersion":"11.16.0","description":"Build tooling and layout bundle for Brochure Kit static sites.","directories":{},"_nodeVersion":"24.18.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"sharp":"0.35.3"},"_npmOperationalInternal":{"tmp":"tmp/brochure-kit_0.2.9_1787285440260_0.3016904023965652","host":"s3://npm-registry-packages-npm-production"}},"0.2.10":{"name":"@anthonyworks/brochure-kit","version":"0.2.10","description":"Build tooling and layout bundle for Brochure Kit static sites.","private":false,"type":"module","license":"UNLICENSED","publishConfig":{"access":"public"},"scripts":{"publish:check":"npm pack --dry-run"},"devDependencies":{"sharp":"0.35.3"},"gitHead":"728241408606048c8a00f67aee6523d547c18bbc","_id":"@anthonyworks/brochure-kit@0.2.10","_nodeVersion":"24.18.1","_npmVersion":"11.16.0","dist":{"integrity":"sha512-q6lWhCglkeKId0/ccevoCzKFTIme/el7SDYk0KJ7mao00ItuBF1DONqdEfbHeOVw/jczY7wLwryEjrJAXeqfCg==","shasum":"5c309a125a08c1c45c81165f1a99909822ea6eb8","tarball":"https://registry.npmjs.org/@anthonyworks/brochure-kit/-/brochure-kit-0.2.10.tgz","fileCount":19,"unpackedSize":484486,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD4u0V3Sv0eKuM0tueyqYXdGp5iUljMbMsdGKDeC1n/ywIgD9FZ4ZA/xhvWCBeZggdwnVUg7Cl+n66TyKiKAti+YS0="}]},"_npmUser":{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"},"directories":{},"maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/brochure-kit_0.2.10_1787610817303_0.9803666613011957"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-12T22:09:16.221Z","modified":"2026-08-24T22:33:37.690Z","0.1.0":"2026-08-12T22:09:16.599Z","0.2.0":"2026-08-13T22:55:35.601Z","0.2.1":"2026-08-14T23:56:48.123Z","0.2.2":"2026-08-15T01:13:43.055Z","0.2.3":"2026-08-15T23:35:26.207Z","0.2.4":"2026-08-16T07:39:09.702Z","0.2.5":"2026-08-16T21:27:31.680Z","0.2.6":"2026-08-17T05:23:57.401Z","0.2.7":"2026-08-18T00:24:21.101Z","0.2.8":"2026-08-18T11:42:45.764Z","0.2.9":"2026-08-21T04:10:40.393Z","0.2.10":"2026-08-24T22:33:37.564Z"},"license":"UNLICENSED","description":"Build tooling and layout bundle for Brochure Kit static sites.","maintainers":[{"name":"anthonyworks","email":"anthony.sitebuilder@gmail.com"}],"readme":"# Local Business Brochure Kit — design system\n\nA reusable design system for **five-page brochure websites** built for small-town American\nservice businesses: rural event venues, custom fabrication and welding shops, landscaping and\nhardscaping contractors. Clients are owner-operators in towns of 2,000 to 60,000 people. Their\ncustomers are homeowners, engaged couples and facility managers, arriving mostly on a phone\nfrom a Google search.\n\nThe site has one job: **get a phone call, a form submission, or a visit.** Not to sell online,\nnot to entertain.\n\n## Sources\n\nNone were provided. This system was authored from the written brief only — there was no\ncodebase, Figma file, deck or brand kit attached, and no client identity to draw from. Anything\nin here that looks like a brand decision (the palette, the two typefaces) is a deliberate\nneutral default, documented as such below, meant to be replaced per client.\n\n**There is no logo.** No mark was supplied and none was invented. Wherever a mark would go, the\nbusiness name renders in the display face as plain type (`SiteHeader`, `SiteFooter`,\n`thumbnail.html`). `SiteHeader` takes a `logo` slot for the client's own mark when one exists.\n\n## Brand-agnostic by design\n\nThe system supplies structure; each project supplies the brand. A reskin is **six colors and\ntwo typefaces** — nothing else varies per client, and no component hardcodes a color, font or\nimage path. Every visual value resolves to a token.\n\n```css\n:root {\n  --ink: #1c2124; --surface: #ffffff; --surface-alt: #f1f2f3;\n  --accent: #35566b; --accent-dark: #223a49; --border: #d7dadc;\n  --font-display: \"Archivo\"; --font-body: \"Source Sans 3\";\n}\n```\n\nDerived values (`--ink-muted`, `--accent-tint`, `--accent-tint-strong`, `--overlay-scrim`,\n`--overlay-scrim-soft`) are mixed from those six with `color-mix()`, so they follow a reskin\nautomatically. They are mixes, not new inputs — do not add a seventh client color.\n\n### Verified contrast\n\nMeasured from actual relative luminance, not estimated. Every text pair clears WCAG 2.1 AA\n(4.5:1 body, 3:1 large); every control boundary clears 1.4.11 non-text contrast (3:1). Resolved\nvalues of the mixes: `--ink-muted` `#5c6062`, `--accent-tint` `#eef0f3`, `--accent-tint-strong`\n`#dce2e6`, `--border-control` `#7c8083`.\n\n| Pair | Ratio | Use |\n| --- | --- | --- |\n| `--ink` on `--surface` | 16.25:1 | all body and heading text |\n| `--ink` on `--surface-alt` | 14.50:1 | text in alternating bands |\n| `--ink` on `--accent-tint` | 14.26:1 | text on hovered cards, no-photo panels |\n| `--ink` on `--accent-tint-strong` | 12.45:1 | text on emphasised panels |\n| `--ink-muted` on `--surface` | 6.36:1 | secondary text, captions, roles |\n| `--ink-muted` on `--surface-alt` | 5.68:1 | secondary text inside bands |\n| `--ink-muted` on `--accent-tint` | 5.58:1 | secondary text on tinted panels |\n| `--accent` on `--surface` | 7.79:1 | links, active nav |\n| `--accent` on `--surface-alt` | 6.95:1 | links inside bands |\n| `--accent-dark` on `--surface` | 11.87:1 | focus ring, hover text |\n| `--surface` on `--accent` | 7.79:1 | primary button label |\n| `--surface` on `--accent-dark` | 11.87:1 | button hover, sample banner |\n| `--border-control` on `--surface` | 3.98:1 | field and control boundaries |\n| `--border-control` on `--surface-alt` | 3.55:1 | fields inside bands |\n| `--border` on `--surface` | 1.40:1 | **decorative hairlines only** |\n\n`--border` is the one pair that does not clear 3:1, and that is deliberate: it draws section edges\nand table rules, which carry no information. Anything bounding a *control* uses `--border-control`\ninstead — that token exists because the original single border color was failing 1.4.11 on form\nfields. When a reskin changes `--ink`, `--accent` or `--border`, re-measure these pairs: the\nderived tokens follow the mix, the ratios do not follow automatically.\n\n## Sample content is marked, loudly\n\nThe three `ui_kits/` businesses do not exist. Their capacities, prices, hours, FAQ answers and\nreviews were invented, and they read convincingly enough to be mistaken for real, so every value\ncarrying a claim is marked `SAMPLE` inline and each kit `content.json` opens with a machine-readable\n`_sample` block. Each kit shell also renders a persistent banner from `_sample.notice`. The markers\nare load-bearing — the full convention is in `content.schema.md`.\n\n**Client builds start from `templates/`, never from `ui_kits/`.** Template `content.json` files are\nblank and carry no `_sample` key.\n\n## Build modes\n\nThere is one site, not a demo and then a rebuild. What changes is completeness state, set with\n`data-lb-build` on `<html>` (or `window.LB_BUILD`). **There is no default.** An absent or\nmisspelled value is a build error that fails visibly and names what it found — a typo like\n`provisonal` must never be the thing that quietly ships `placehold.co` frames to a live site.\nSample has to be asked for by name, which is why every kit page, specimen card and template\ndeclares it explicitly.\n\n| Mode | What it is | Photos | SAMPLE text | Signal on the page |\n| --- | --- | --- | --- | --- |\n| `sample` | the three `ui_kits/`. Fictional businesses, reference only | placeholder frames allowed | rendered with markers visible | persistent SAMPLE banner |\n| `provisional` | the site sent to a prospect | **must be real** — a missing or `placehold.co` photo fails visibly | rendered naturally, markers stripped | **none** |\n| `client` | live | must be real | **fails visibly**, listing every remaining SAMPLE path | none if clean |\n| absent / typo | a build error | — | — | **fails visibly**, naming the value found |\n\n`provisional` is the cold-outreach state: real photos and researched facts, plus a plausible guess\nfor every unknown, each prefixed `SAMPLE` in `content.json` but rendering as finished copy. Nothing\nmay look unfinished — no empty sections, no \"TBD\", no visible placeholder text.\n\nImagery is the one thing `provisional` does not relax. Guessed *text* is recoverable on a phone\ncall; a grey box in the hero is the reason the prospect does not call. `PhotoFrame` therefore\napplies the same failure block in `provisional` as in `client`. Deliberate empty slots still opt in\nwith `optional`, which is how the editorial direction ships with zero photos in every mode.\n\nA provisional page carries no in-page signal at all — no banner, no badge, no marker. The\nunconfirmed values are reported out of band, in `owner-questions.md`.\n\n    node templates/tools/owner-questions.mjs sites/<client>/content.json\n\nEvery SAMPLE and VERIFY field becomes a question grouped by page, in two labelled sections: what\nwe invented and needs an answer, and what we found published and needs a yes.\n\nThe sites are static files with no build step, so nothing runs the generator on its own. Hang it\non a per-site npm script instead — `templates/tools/prepublish.mjs` regenerates the questions file\nand exits non-zero if it is missing or older than `content.json`, which stops the publish:\n\n    \"scripts\": { \"publish:check\": \"node ../../templates/tools/prepublish.mjs content.json\" }\n\nDo not name that script `prepublish`, `prepare`, `prepublishOnly`, or `pre` + the name of any\nother script in the file. Those are npm lifecycle names and npm runs them on `npm install`,\nunasked. On a host that installs before it builds, a `prepublish` script means the site is built\ntwice per deploy: once by the install with whatever flags that script carries, once by the real\nbuild command. Found on the first live staging deploy, 6 August. `publish:check` has a colon in\nit and is not a lifecycle name, which is the whole reason for the colon.\n\nThat file is the kickoff call script and the\nhonest disclosure that values were guessed — and it is the reason a cold prospect replies at all.\n\"I built your site, it needs twelve answers from you\" invites a response that a finished site does\nnot.\n\nDemo to paid site is: fill in the real values, flip the attribute.\n\n`PhotoFrame` messages name the `content.json` key to fix — pass `slot=\"images.hero\"`.\n`GalleryGrid`, `BeforeAfter` and `OwnerIntro` forward `optional` and `slot` to the frames they own.\n\n## Unrecognised prop values are reported, never aliased\n\nSame failure shape as a misspelled build mode: a value that looks set and does nothing. A\nconsuming project passed `size=\"large\"` to `Button` — the kit accepts `\"lg\"` — on four primary\ncall buttons of a venue site where the phone call is the conversion. Nothing matched, nothing\nwarned, and the buttons rendered at the default 44px instead of 52px.\n\n`lib/props.js` validates the closed enums (`Button` variant/size, `Hero` variant/align,\n`SectionHeading` align, `PhotoFrame` fit, `BeforeAfter` layout, `columns` on `ServiceGrid`,\n`SpecTable` and `GalleryGrid`) and `Icon` validates its open name set by whether the glyph\nactually loads. A miss does three things: warns once with the accepted values, marks the rendered\nelement `data-lb-bad-prop=\"prop=value\"`, and — in provisional and client builds — raises a rollup\n`console.error` from the shell, which calls `Props.enablePropAssert(mode)` beside\n`assertClientClean`. `Props.assertPropsClean()` is the synchronous scan for tests.\n\n**No aliases.** Accepting `large` would fix one spelling and leave `big`, `l` and `xl` silent, and\nwould make the `.d.ts` unions untrue. Two behaviour changes come with this: an unknown `Button`\nvariant now falls back to `primary` rather than rendering a class that does not exist (previously\na button with no background at all), and an unknown `Hero` variant falls back to `split`\nexplicitly instead of by if-chain fallthrough. `columns` accepts `\"3\"` as well as `3`; what it\nrejects is out of range.\n\nEvery template's contact page carries a standalone primary call button, because the absence of one\nis what caused the original guess — `size=\"lg\"` previously only ever appeared inside `Hero` and\n`ContactForm`, where nobody building a page from a template would find it.\n\nThe check is a runtime one and needs a rendered page, so `prepublish.mjs` does not cover it. Open\nthe site once in provisional mode before sending it.\n\n## Static-ready\n\nEvery site used to need the same three patches applied by hand after the build — content inlined,\nReact vendored, JSON-LD written into the source — and nothing detected a site that skipped them.\n`build.mjs` now emits them; `static-ready.mjs` is the detector that catches a folder where one is\nmissing or has gone stale. It runs first from `prepublish.mjs`, fails the publish and names the\nfix, per rule:\n\n    [content] content.json is fetched at runtime and no page inlines it.\n    [stale]   index.html was generated from content 372a8a82…, but ../content.json is now 883fb3ad…\n    [react]   index.html loads https://unpkg.com/react@18.3.1/... from a remote origin.\n    [babel]   index.html compiles ./Site.jsx in the browser via type=\"text/babel\".\n    [jsonld]  LocalBusinessSchema is mounted but no page carries a static ld+json script.\n\n**No site takes a waiver.** `--allow=<rules>` exists in the tool, but it is refused unless the\ntarget directory resolves inside this kit's own `ui_kits/` — and nothing runs it there: the demo\npages are authoring surfaces, not deployables, so a deployment-readiness check does not apply to\nthem. A client site that copies the flag is refused by location, so the waiver cannot travel. In\npractice `--allow` is unreachable, which is the intent.\n\nThe kit does not yet *emit* static-ready output — the patches are still applied per site; this only\nguarantees a skipped one is caught.\n\n### Why inlining exists\n\nNot for search. A crawler that does not run scripts sees `<div id=\"root\"></div>` whether the\ncontent is inlined or fetched — the visible copy is React output either way. Inlining is the\n`file://` win: Chrome refuses `fetch()` on `file:` URLs, so a fetch-only folder renders nothing\nwhen a prospect double-clicks it. The SEO artifact is the **static JSON-LD**, which is why that is\na separate emitter. Real per-page HTML is the other half of the SEO story and is not built yet —\nthe five pages are still JS state, not addresses.\n\n## The build step\n\n    node ../../templates/tools/build.mjs [siteDir] [--out=dist] [--skip-app] [--skip-vendor]\n\n**`content.json` is authoritative and is the only file anyone edits.** Everything in `dist/` is\nderived, and `dist/` deliberately contains **no copy of `content.json`** — a loose file sitting\nnext to the page that the page does not read is what made an edit look like it worked and change\nnothing on screen. `dist/` is a build artifact and is not committed.\n\nThe inline block records the hash of the content it came from:\n\n    <script type=\"application/json\" id=\"lb-content\"\n            data-generated-from=\"content.json\" data-content-hash=\"372a8a828e41db96\">…</script>\n\n`static-ready` recomputes that hash from the source and fails on a mismatch. Editing the content\nand forgetting to rebuild is the failure this catches — without it the page and the file disagree\nsilently, and the page is what ships. The hash is plain FNV-1a arithmetic in `lib/sample.js` so\nNode and the browser compute it identically, and `static-ready` imports that one implementation\nrather than carrying a second that could disagree.\n\nAn inline block with **no** `data-content-hash`, sitting beside a `content.json`, also fails: that\nis a hand-patched block from the old workflow, and nothing will ever tell you when it drifts.\n\n| Emitter | Does | Needs |\n| --- | --- | --- |\n| `content` | inline `#lb-content` + hash | — |\n| `schema` | static `ld+json` via `lib/schema.js`, markers resolved for the page's build mode | — |\n| `paths` | rewrites `../../styles.css` and `../../_ds_bundle.js` to `./`, copies them and anything the CSS references | — |\n| `app` | precompiles the `text/babel` `.jsx` to `./app.js` | Babel (cached from unpkg on first run) |\n| `vendor` | downloads React and ReactDOM to `./vendor/`, repoints the tags | network on first run |\n\n`--skip-app` / `--skip-vendor` emit everything else and leave those tags alone; `static-ready` then\nreports `babel` and `react` as still failing, which is the honest answer for a folder that is not\nfinished.\n\n**The JSON-LD mapping moved to `lib/schema.js`.** `LocalBusinessSchema` renders what that function\nreturns and `build.mjs` stringifies what it returns, so the rendered schema and the emitted schema\ncannot drift. The component stays because DC previews and unbuilt folders still need a runtime copy.\n\n**Shells read the inline block first.** All three `ui_kits/*Site.jsx` and all three template DCs now\ncall a local `loadRaw()` — inline if present, `fetch` otherwise. The fetch fallback is what keeps\nthe DC previews working inside this project, which has no build step. It is local to each shell\nrather than read off `Sample` on purpose: it has to work when the bundle is absent, because it feeds\nthe bundle-absent failure branch.\n\n### Verified\n\nA `provisional` build of `sites/east-ridge` emits the inline block and the JSON-LD, resolves 15\nSAMPLE and 3 VERIFY values out of the structured data, and passes `content`, `stale` and `jsonld`.\nEditing `content.json` without rebuilding makes `stale` fire with both hashes named.\n\nThe `app` and `vendor` emitters are **proven** as of 2 Aug 2026 — full build, Node v24.18.1 on\nWindows with network, against both fixtures. Babel precompiled the JSX to `app.js` (63\n`createElement` calls), React and ReactDOM vendored locally, 15 images copied for `fixture-venue`,\n`verify-dist.mjs` 11/11 ok, `static-ready.mjs` exit 0, and `dist/index.html` rendered fully from\n`file://` by double-click. That run found and fixed one real bug: `loadBabel()` fetched Babel\ncorrectly then looked for it on `globalThis.Babel`, where a CJS-evaluated UMD bundle never puts it —\n`pickBabel()` now reads the module object, then `.default`, then the global. The `react` and `babel`\nrules in `static-ready.mjs` had never been observed passing before this run; they have now.\n\n### Test bed\n\n`sites/east-ridge/` is a permanent fixture, not a prospect. `npm run build`, `npm run\nbuild:offline`, `npm run publish:check`. Its content carries SAMPLE and VERIFY markers on purpose.\n\n### Not emitted yet\n\nPer-page HTML, photo resizing on commit, and the CMS. `assertClientClean` still fails a build\nvisibly on a leftover marker — correct for an operator, and a trap for a self-managed owner, so\nmarkers must be zero at handover before any site gets a login.\n\n## Three layout directions\n\nEvery client picks one, so the same structure produces visibly different sites and the portfolio\ndoes not read as a template farm. All three consume the **same `content.json` and the same\ncomponents** — only arrangement, scale and emphasis change.\n\n| Direction | For | What changes |\n| --- | --- | --- |\n| **Photo-forward** | Venues, landscaping — the work sells itself | Full-bleed `Hero variant=\"photo\"`, featured gallery frame, spare copy, generous whitespace |\n| **Structured** | Fabrication and trades — capability sells | `Hero variant=\"split\"`, spec strip above the fold, `ServiceGrid dense`, four-up gallery |\n| **Editorial** | Clients with few usable photos | `Hero variant=\"type\"`, narrow measure, process as a spec table, plain testimonials, designed empty photo slots |\n\nRegister shifts by vertical (venues emotional and imagery-led; trades matter-of-fact and\nspecific; landscaping outcome-driven with proof of finished work), but **the tone is not built\ninto the components** — they are designed to read correctly under any of the three.\n\n## Non-negotiables\n\n- **Mobile-first.** Every layout is authored at phone width first; type and spacing scale up\n  with `clamp()`.\n- **Tap-to-call from anywhere without scrolling.** `CallBar` is fixed to the bottom on mobile;\n  `SiteHeader` carries the call button on desktop. For most of these businesses a phone call\n  *is* the conversion.\n- **Lazy-load all imagery.** `PhotoFrame` sets `loading=\"lazy\"` (only the hero opts out) and\n  reserves aspect ratio so rural connections do not reflow.\n- **Minimal JavaScript.** Three interactive components only: gallery lightbox, FAQ accordion,\n  mobile nav drawer. No animation library, no scroll effects, no carousel.\n- **Two web fonts, one optional utility face.** Nothing else loads.\n- **`LocalBusinessSchema` on every page** — name, address, phone, hours, geo, area served,\n  generated from `content.json` so it cannot drift from the visible page.\n- **WCAG 2.1 AA.** Visible 2px focus ring on everything focusable, full keyboard operation,\n  4.5:1 minimum text contrast (see the Colors cards), a real `<label>` on every form control,\n  and state never conveyed by color alone.\n- **Never stock photography.** Client photos only. Empty photo slots are designed states, not\n  gaps.\n\n## CONTENT FUNDAMENTALS\n\n**Voice.** Confident and grounded — work that speaks for itself. The register is that of an\nowner-operator explaining the job across a kitchen table, not a marketer.\n\n**Person.** First person plural for the business (\"We build retaining walls\"), second person for\nthe reader (\"Tell us the property address\"). The About page shifts to first person singular,\nbecause it is the owner talking: *\"My father opened this shop in 1986 doing hitch repair out of\na two-bay building.\"*\n\n**Casing.** Sentence case everywhere — headlines, buttons, labels, nav. Uppercase appears only\nin the utility face, at label size, for eyebrows, photo tags and spec keys.\n\n**Specificity beats adjectives.** Numbers, materials, distances, hours, names. Not \"spacious\nvenue\" but \"220 seated, 300 standing, 40 ft × 90 ft\". Not \"fast turnaround\" but \"four working\ndays average, 24 hours on breakdowns\".\n\n**Never used:** exclamation points, emoji, urgency banners, countdown timers, \"limited time\",\n\"act now\", \"world-class\", \"passionate\", \"solutions\", \"premier\", title-case headlines, invented\nawards or certifications.\n\nExamples in the right register:\n\n- Hero: *\"A working barn, kept for one weekend at a time.\"*\n- Hero subhead: *\"Two hundred and twenty seated under the original 1948 timber frame,\n  twenty-five minutes south of Rochester.\"*\n- Service bullets: *\"Drain tile and washed rock\" · \"Geogrid reinforcement\" · \"Permitted where\n  required\"*\n- FAQ, written the way a customer actually asks: *\"Can we bring our own beer?\"* → *\"Beer and\n  wine, yes, with a licensed server. Spirits need a bartender from our list of three. No\n  self-serve bar.\"*\n- Owner intro closing line: *\"I meet every couple myself and I am the person who answers the\n  phone on a Saturday.\"*\n- Honest limits, stated plainly: *\"The kitchen is prep only — there is no range.\"*\n- Empty-state copy: *\"Photos are being collected.\"* Never *\"Coming soon!\"*\n\n**Button labels** name the action and, when it is a call, the number:\n*\"Call (507) 555-0142\"*, *\"Request a quote\"*, *\"Book a tour\"*, *\"Send quote request\"*.\n\n**Alt text** describes the job shown: *\"Grain auger flighting rebuilt and hardfaced\"*, never\n*\"image of metal work\"*.\n\n## VISUAL FOUNDATIONS\n\n**Palette.** A deliberately quiet cool neutral: near-black ink, white surface, a light grey\nalternating band, a desaturated slate-blue accent. It reads as *unbranded* rather than as a\nchoice, so a client's real colors take over cleanly. Three defaults are explicitly avoided as\nhouse looks: cream + high-contrast serif + terracotta; near-black + acid accent; broadsheet\nhairlines with zero radius. Any of them is fine *if a client's brand calls for it* — none is the\ndefault.\n\n**Type.** Two faces. **Archivo** (display, 600/700) for headlines: a sturdy grotesque that holds\nup at hero size without shouting. **Source Sans 3** (body, 400/600) for everything read in\nsentences. **IBM Plex Mono** (utility, 500) at label size only, uppercase with 0.08em tracking,\nfor eyebrows, photo tags and spec-table keys — it is what gives the trades side its\nspecification feel without a single hairline rule. Body copy is 17px / 1.55 with a 68ch measure;\nhero subheads cap at 46ch. Headline tracking tightens as size grows (−0.012em → −0.022em).\n\n**Spacing.** A 4px-based scale (4 → 128). Section rhythm is `--section-y`\n(`clamp(40px, 6vw, 96px)`), gutters `clamp(20px, 5vw, 40px)`, container 1160px (760px for\neditorial). Whitespace is the photo-forward variant's main tool; the structured variant spends\nit on density instead.\n\n**Backgrounds.** Flat color only — `--surface` and `--surface-alt` alternating in bands with a\nhairline between. **No gradients** anywhere except one: the `--overlay-scrim` bottom-up scrim\nthat keeps hero text legible over a photo. No textures, no patterns, no hand-drawn\nillustration, no background imagery behind text.\n\n**Imagery.** Client photos exclusively, presented straight — no duotone, no filter, no grain, no\nforced warm or cool grade. Real photos of real work are inconsistent by nature and that\ninconsistency is credibility. Ratios: 4/3 for gallery and owner (4/5), 3/2 for service cards,\n16/9 for the featured frame, full-bleed for the photo hero. Everything lazy-loads with reserved\nratio. **No-photo states** are an accent-tint panel with a 32px accent rule and an uppercase\nutility-face label naming what belongs there (\"Owner photo\", \"Client hero photo\").\n\n**Cards.** 1px `--border` hairline, 5px radius, `--surface` fill, **no shadow**. The dense\nvariant drops the individual borders and uses a 1px grid gap over a `--border` background so a\nservice grid reads as a table. Cards that link get an `--accent` border and `--accent-tint` fill\non hover — never a lift.\n\n**Corner radii.** 3px (chips, tags, focus ring), 5px (buttons, cards, fields, photos), 8px\n(reserved, rarely used). Small and consistent: pill shapes read consumer-app, zero radius reads\nbroadsheet.\n\n**Borders and rules.** 1px hairlines carry nearly all structure — section edges, table rows,\nfield outlines, footer divisions. `--border-strong` marks confirmation panels. A 3px `--accent`\nleft rule is used in exactly one place: `Testimonial`.\n\n**Shadows.** Two, both functional: `--shadow-bar` under the fixed call bar (so it separates from\ncontent) and `--shadow-lift` under the lightbox image. Cards and buttons never cast.\n\n**Transparency and blur.** No blur, ever — it costs paint time on cheap phones. Transparency\nappears only in the hero scrim, the lightbox backdrop (`--ink` at 92%), and lightbox control\nborders.\n\n**Animation.** One easing curve (`cubic-bezier(0.2, 0, 0.2, 1)`), three durations\n(120 / 200 / 320ms). Fades and 1–2px shifts only: button background, link thickness, field\nborder, the FAQ plus rotating to a minus, the lightbox fade. No bounce, no spring, no\nscroll-triggered reveals, no parallax. `prefers-reduced-motion` zeroes all three durations.\n\n**Hover.** Buttons darken to `--accent-dark`; secondary buttons pick up `--accent-tint`; links\nthicken their underline from 1px to 2px; linked cards take an accent border and tint; gallery\nthumbnails drop to 92% opacity. Never a scale-up.\n\n**Press.** `translateY(1px)`. No color change beyond the hover state, no shrink.\n\n**Focus.** 2px solid `--accent-dark` outline, 2px offset, 3px radius, on every focusable element\n— visible on both light surfaces. Never removed.\n\n**Fixed elements.** Two: the sticky header (top, hairline bottom edge) and the mobile call bar\n(bottom, 60px, safe-area padded). The footer reserves the call bar's height so nothing is\ncovered.\n\n**Tap targets.** 44px floor, 52px on large buttons, 60px on the call bar. Nav drawer rows are\nfull-width 44px.\n\n## ICONOGRAPHY\n\n- **Set:** [Lucide](https://lucide.dev) — 24px grid, 2px stroke, round caps, outline only.\n  **This is a substitution and it is flagged:** no icon set was supplied with the brief. Lucide\n  was chosen because its stroke weight sits comfortably beside Archivo and it is CDN-available.\n  If you have a preferred set, replace the CDN base in `components/core/Icon.jsx` — nothing else\n  references icons.\n- **Delivery:** loaded per glyph from `unpkg.com/lucide-static@0.470.0/icons/<name>.svg` and\n  recolored with a CSS `mask`, so every icon inherits `currentColor` and no icon binaries ship\n  with the system. No icon font, no sprite sheet, no inline path data.\n- **Vocabulary is deliberately tiny** — icons label actions, never decorate sections:\n  `phone`, `map-pin`, `mail`, `clock`, `menu`, `x`, `chevron-left`, `chevron-right`,\n  `alert-circle`, `message-square`, `file-text`.\n- **Never:** icons beside headings, icon-per-service-card, decorative badges, filled/duotone\n  styles, mixed sets, or an icon carrying meaning without a text label beside it.\n- **Emoji: never.** Not in copy, not in labels, not in commit-adjacent content.\n- **Unicode as iconography:** only the middle dot `·` as a separator in metadata lines\n  (\"Wedding, June 2025 · Google review\") and the en dash in time ranges (\"7:00 am–5:00 pm\").\n  The FAQ plus/minus is drawn with two 2px CSS bars, not a glyph.\n- **No hand-drawn SVG illustration exists in this system** and none should be added; there is no\n  illustration style to be consistent with.\n\n## Index\n\nRoot\n- `readme.md` — this file.\n- `SKILL.md` — Agent Skills wrapper so the system can be used from Claude Code.\n- `styles.css` — the only entry point consumers link. `@import` lines only.\n- `content.schema.md` — the `content.json` contract every build reads from.\n- `lib/sample.js` — marker convention (SAMPLE and VERIFY), mode detection, `resolveContent`, page\n  grouping. Single source of truth for what counts as unconfirmed. Also exported as the\n  capitalized `Sample` object, which is how it reaches consuming sites: the compiled bundle\n  inlines this file but only exposes capital-leading names, so a consumer with no ESM import\n  reads `window.<Namespace>.Sample.resolveContent(...)` rather than keeping a private copy.\n- `templates/tools/prepublish.mjs` — publish guard: runs `static-ready.mjs`, then regenerates\n  `owner-questions.md` and fails if it is stale.\n- `templates/tools/static-ready.mjs` — fails a site that cannot open from `file://`: runtime\n  `content.json` fetch with no inlined `<script type=\"application/json\" id=\"lb-content\">`, remotely\n  loaded React/Babel, browser-compiled `.jsx`, or JSON-LD that only exists once scripts run. A text\n  scan of the site's own files — it never reads `_ds/`, so a stale bind cannot change the answer.\n- `components/core/BuildNotice` — the sample banner and the client failure block in one\n  component, so a shell cannot render the wrong signal. Renders nothing in `provisional`.\n- `thumbnail.html` — homepage tile.\n\nTokens (`tokens/`) — `typography.css`, `colors.css`, `spacing.css`, `shape.css`, `motion.css`,\n`base.css` (element defaults + `.ds-container` / `.ds-section` / `.ds-eyebrow` helpers).\nComponent styling lives in `components/components.css`, imported from `styles.css`.\n\nFoundation cards (`guidelines/`) — 18 specimen cards in four groups: Colors (six tokens, derived\nvalues, contrast pairs, state-never-color-alone), Type (display, body, utility, measure),\nSpacing (scale, section rhythm, tap targets), Shape (radii, rules, shadow, motion), Brand\n(no-photo states, six-token reskin demo, three layout directions).\n\nComponents — 22 exports, grouped by concern:\n\n| Group | Components |\n| --- | --- |\n| `components/core/` | `Button`, `BuildNotice`, `Icon`, `PhotoFrame`, `SectionHeading` |\n| `components/layout/` | `SiteHeader`, `Hero`, `SiteFooter`, `CallBar` |\n| `components/content/` | `ServiceCard`, `ServiceGrid`, `SpecTable`, `HoursTable`, `FaqAccordion`, `Testimonial`, `OwnerIntro`, `ServiceArea` |\n| `components/media/` | `GalleryGrid`, `BeforeAfter`, `MapFrame` |\n| `components/forms/` | `ContactForm` |\n| `components/meta/` | `LocalBusinessSchema` |\n\nEach has a sibling `.d.ts` (props contract) and `.prompt.md` (what & when, usage example), and\neach directory has one `@dsCard` HTML showing its states.\n\nTemplates (`templates/`) — where a client build starts. One folder per layout direction, each\nholding a Design Component entry, a blank `content.json`, `ds-base.js` and a README:\n`photo-forward/PhotoForward.dc.html`, `structured/Structured.dc.html`,\n`editorial/Editorial.dc.html`. Copy the folder, point `ds-base.js` at the bound design system,\nfill `content.json`, declare `data-lb-build` on `<html>`. Layout is never rebuilt per client.\nThat attribute is the only mode mechanism. No file writes a default: `ds-base.js` logs an error\nwhen nothing is declared and leaves the mode `invalid`, which renders the failure block. Specimen\ncards declare `sample` explicitly.\n`templates/tools/owner-questions.mjs` emits `owner-questions.md` from a provisional\n`content.json` (it lives under `templates/` because it is a Node script, excluded from the\nbrowser bundle).\n\nUI kits (`ui_kits/`) — one complete five-page site per layout direction, each with its own\n`content.json`, a `*Site.jsx` and `README.md`. **All three carry SAMPLE content** — reference\nonly, never a starting point:\n- `photo-forward/` — Hollow Creek Barn, event venue (Chatfield, MN)\n- `structured/` — Vollmer Iron Works, custom fabrication (Beloit, KS)\n- `editorial/` — Ketterling Landscape, landscaping (Cadillac, MI) — ships **zero photos on\n  purpose**, as the reference for the no-imagery path\n\n### Intentional additions\n\nThe brief listed fifteen components. Six more were added, each because the five-page structure\ncannot be built without it:\n\n- **`SiteHeader`** — a five-page site needs navigation; the brief named the footer and call bar\n  but not the header.\n- **`Button`** — the hero, form, cards and call bar all need one action element; without it each\n  would hardcode its own.\n- **`PhotoFrame`** — the single place lazy-loading, ratio reservation and the designed no-photo\n  state live. Every image in the system goes through it.\n- **`Icon`** — thin wrapper over the Lucide CDN so no component embeds path data or a color.\n- **`SectionHeading`** — keeps the eyebrow/heading/description rhythm identical across all three\n  layout directions.\n- **`LocalBusinessSchema`** — the brief requires schema markup on every build; making it a\n  component is what keeps hours and phone from drifting from the visible page.\n\n### Known substitutions\n\n- **Fonts.** No font files were supplied. Archivo, Source Sans 3 and IBM Plex Mono are loaded\n  from Google Fonts via an `@import` at the top of `tokens/typography.css`; no binaries are\n  shipped. If the intended faces differ, replace that one `@import` and the three\n  `--font-*` tokens.\n- **Icons.** Lucide, as described above.\n- **Photography.** The photo-forward and structured kits use grey `placehold.co` frames so\n  composition reads. They are placeholders, not stock photography, and must not ship.\n","readmeFilename":"readme.md"}