@koehler8/cms 1.0.0-beta.15 → 1.0.0-beta.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/bin/cms-create-site.js +7 -0
  2. package/package.json +3 -2
  3. package/scripts/create-site.mjs +11 -0
  4. package/scripts/scaffold.mjs +55 -19
  5. package/src/components/ComingSoonModal.vue +37 -5
  6. package/src/components/Contact.vue +91 -9
  7. package/src/components/Footer.vue +15 -2
  8. package/src/components/FooterMinimal.vue +13 -12
  9. package/src/components/Header.vue +36 -3
  10. package/src/components/Hero.vue +7 -6
  11. package/src/components/Home.vue +42 -1
  12. package/src/components/Plan.vue +6 -3
  13. package/src/components/Portfolio.vue +7 -3
  14. package/src/components/StickyCTA.vue +5 -1
  15. package/src/components/Team.vue +3 -1
  16. package/src/styles/base.css +11 -2
  17. package/src/themes/buildCssVarMap.js +25 -0
  18. package/templates/index.html +8 -3
  19. package/templates/scaffolds/site/.nvmrc +1 -0
  20. package/templates/scaffolds/site/CLAUDE.md +121 -0
  21. package/templates/scaffolds/site/README.md +25 -0
  22. package/templates/scaffolds/site/amplify.yml +22 -0
  23. package/templates/scaffolds/site/package.json +26 -0
  24. package/templates/scaffolds/site/site/assets/img/.gitkeep +0 -0
  25. package/templates/scaffolds/site/site/components/.gitkeep +0 -0
  26. package/templates/scaffolds/site/site/content/content.config.json +3 -0
  27. package/templates/scaffolds/site/site/content/en/pages/home.json +8 -0
  28. package/templates/scaffolds/site/site/content/en/shared.json +5 -0
  29. package/templates/scaffolds/site/site/content/en/site.json +7 -0
  30. package/templates/scaffolds/site/site/style.css +4 -0
  31. package/templates/scaffolds/site/vite.config.js +15 -0
  32. package/themes/base/theme.config.js +100 -57
@@ -1,9 +1,10 @@
1
1
  <template>
2
+ <a class="cms-skip-link" href="#main-content">Skip to main content</a>
2
3
  <IntroGate
3
4
  :enabled="introGateEnabled"
4
5
  v-bind="introGateProps"
5
6
  />
6
- <main>
7
+ <main id="main-content" tabindex="-1">
7
8
  <component
8
9
  v-for="entry in loadedComponents"
9
10
  :is="entry.component"
@@ -115,3 +116,43 @@ watch(
115
116
  );
116
117
  </script>
117
118
 
119
+ <style>
120
+ /* Visually-hidden until focused. The skip link is the first focusable
121
+ element on the page; pressing Tab once reveals it and Enter jumps
122
+ focus past the sticky header into <main>. WCAG 2.4.1 (Bypass Blocks). */
123
+ .cms-skip-link {
124
+ position: absolute;
125
+ top: 0;
126
+ left: 0;
127
+ padding: 12px 20px;
128
+ background: #ffffff;
129
+ color: #1f2a44;
130
+ border: 2px solid #1f2a44;
131
+ border-radius: 0 0 8px 0;
132
+ font-family: var(--ui-font-body, system-ui, -apple-system, sans-serif);
133
+ font-weight: 600;
134
+ text-decoration: none;
135
+ z-index: 9999;
136
+ transform: translateY(-100%);
137
+ transition: transform 0.15s ease;
138
+ }
139
+ .cms-skip-link:focus {
140
+ transform: translateY(0);
141
+ outline: 2px solid #1f2a44;
142
+ outline-offset: 2px;
143
+ }
144
+ @media (prefers-reduced-motion: reduce) {
145
+ .cms-skip-link { transition: none; }
146
+ }
147
+
148
+ /* When the skip link sends focus to <main>, browsers don't render an
149
+ outline on the [tabindex="-1"] element by default — strip it so the
150
+ page doesn't get an unexpected ring around its main region. */
151
+ #main-content:focus { outline: none; }
152
+
153
+ /* Sticky header offset for in-page anchor jumps and skip-link landing.
154
+ Without this, the focused element is hidden under the sticky header
155
+ (fails 2.4.11 Focus Not Obscured). */
156
+ html { scroll-padding-top: 88px; }
157
+ </style>
158
+
@@ -94,7 +94,8 @@ const planItems = computed(() =>
94
94
  width: 70px;
95
95
  height: 70px;
96
96
  border-radius: 50%;
97
- border: 3px solid var(--plan-step-border, rgba(255, 255, 255, 0.3));
97
+ /* 3:1+ against the dark plan-card surface (WCAG 1.4.11). */
98
+ border: 3px solid var(--plan-step-border, var(--brand-plan-step-ring, rgba(255, 255, 255, 0.5)));
98
99
  color: var(--plan-step-color, #ffffff);
99
100
  display: inline-flex;
100
101
  align-items: center;
@@ -107,10 +108,12 @@ const planItems = computed(() =>
107
108
  font-size: 1.15rem;
108
109
  font-weight: 600;
109
110
  margin: 0;
110
- color: var(--plan-card-title, var(--ui-text-primary, #ffffff));
111
+ /* Dark plan-card surface needs light text. Don't fall through to
112
+ --ui-text-primary (dark) — that's the FOUC trap that bit FooterMinimal. */
113
+ color: var(--plan-card-title, var(--brand-plan-card-text, #ffffff));
111
114
  }
112
115
 
113
116
  .plan-card__description {
114
- color: var(--ui-text-muted, rgba(255, 255, 255, 0.78));
117
+ color: var(--plan-card-description, var(--brand-plan-card-muted-text, #c8c2cf));
115
118
  }
116
119
  </style>
@@ -72,7 +72,7 @@
72
72
  </div>
73
73
  <span class="portfolio-tag ui-label-sm">{{ project.displayTags || project.tags }}</span>
74
74
  <h3 class="portfolio-title ui-title-md">{{ project.title }}</h3>
75
- <a class="portfolio-card__link" :href="project.href" aria-label="Visit portfolio link"></a>
75
+ <a class="portfolio-card__link" :href="project.href" :aria-label="`View ${project.title}`"></a>
76
76
  </div>
77
77
  </div>
78
78
  </transition-group>
@@ -340,10 +340,14 @@ function handleAfterLeave() {
340
340
 
341
341
  .portfolio-filter:hover,
342
342
  .portfolio-filter:focus-visible {
343
- color: var(--portfolio-filter-hover-color, var(--brand-accent-electric, #4f6cf0));
344
- border-color: var(--portfolio-filter-hover-border, rgba(79, 108, 240, 0.5));
343
+ color: var(--portfolio-filter-hover-color, var(--brand-accent-electric, #4361dd));
344
+ border-color: var(--portfolio-filter-hover-border, rgba(67, 97, 221, 0.55));
345
345
  transform: translateY(-1px);
346
346
  }
347
+ .portfolio-filter:focus-visible {
348
+ outline: 2px solid var(--portfolio-filter-focus-outline, var(--brand-accent-electric, #4361dd));
349
+ outline-offset: 2px;
350
+ }
347
351
 
348
352
  .portfolio-filter--active {
349
353
  color: var(--portfolio-filter-active-color, #fff);
@@ -220,9 +220,13 @@ function scrollTargetWithOffset(target) {
220
220
  padding: 14px 24px;
221
221
  border: none;
222
222
  border-radius: var(--brand-button-radius, 14px);
223
+ /* The default 3-stop gradient previously failed contrast at the mid stop
224
+ (#0a0a0d on #9a2eff = 3.96:1). Mid stop darkened to #7e23d5 → 5.34:1
225
+ with the dark text. Sites overriding --brand-primary-cta-gradient
226
+ are responsible for verifying contrast against --brand-primary-cta-text. */
223
227
  background: var(
224
228
  --brand-primary-cta-gradient,
225
- linear-gradient(135deg, #ff2d86 0%, #9a2eff 55%, #27f3ff 100%)
229
+ linear-gradient(135deg, #ff2d86 0%, #7e23d5 55%, #2bb8d9 100%)
226
230
  );
227
231
  color: var(--brand-primary-cta-text, #0a0a0d);
228
232
  font-size: clamp(0.95rem, 2.5vw, 1.05rem);
@@ -337,7 +337,9 @@ const buildSocialAriaLabel = (memberName, social) => {
337
337
  font-size: 0.72rem;
338
338
  letter-spacing: 0.18em;
339
339
  text-transform: uppercase;
340
- color: var(--ui-text-muted, var(--brand-border-highlight, rgba(79, 108, 240, 0.8)));
340
+ /* AA-safe muted text on the white card. Don't fall through to a brand
341
+ color rgba — it composites below 4.5:1. */
342
+ color: var(--team-role-color, var(--ui-text-muted, #54627b));
341
343
  }
342
344
 
343
345
  .team-card__bio {
@@ -643,7 +643,9 @@ a:focus-visible {
643
643
  }
644
644
 
645
645
  .ui-form-control::placeholder {
646
- color: var(--ui-form-placeholder, var(--brand-input-placeholder, rgba(84, 98, 123, 0.64)));
646
+ /* Solid fallback (#54627b = 5.66:1 on white). Avoid alpha — composited
647
+ placeholder colors slip below 4.5:1 quickly. */
648
+ color: var(--ui-form-placeholder, var(--brand-input-placeholder, #54627b));
647
649
  }
648
650
 
649
651
  .ui-form-control:focus {
@@ -692,7 +694,9 @@ a:focus-visible {
692
694
  }
693
695
 
694
696
  .ui-input-group__input::placeholder {
695
- color: var(--ui-form-placeholder, var(--brand-input-placeholder, rgba(84, 98, 123, 0.64)));
697
+ /* Solid fallback (#54627b = 5.66:1 on white). Avoid alpha — composited
698
+ placeholder colors slip below 4.5:1 quickly. */
699
+ color: var(--ui-form-placeholder, var(--brand-input-placeholder, #54627b));
696
700
  }
697
701
 
698
702
  .ui-input-group__input:focus {
@@ -852,6 +856,11 @@ a:focus-visible {
852
856
  transform: translate3d(0, 0, 0);
853
857
  }
854
858
 
859
+ .ui-backtotop:focus-visible {
860
+ outline: 2px solid #ffffff;
861
+ outline-offset: 3px;
862
+ }
863
+
855
864
  .ui-backtotop__icon {
856
865
  font-size: 1rem;
857
866
  line-height: 1;
@@ -68,6 +68,12 @@ export function buildCssVarMap(manifest) {
68
68
  const radii = tokens.radii || {};
69
69
  const elevation = tokens.elevation || {};
70
70
  const utility = tokens.utility || {};
71
+ // Section-chrome blocks for surfaces that pair text with non-default
72
+ // backgrounds. Components read these to avoid the fallback-chain trap
73
+ // (e.g. dark hero surface + light --ui-text-primary fallback = invisible).
74
+ const hero = tokens.hero || {};
75
+ const footer = tokens.footer || {};
76
+ const plan = tokens.plan || {};
71
77
 
72
78
  assignPrefixed(
73
79
  vars,
@@ -231,6 +237,25 @@ export function buildCssVarMap(manifest) {
231
237
  setVar(vars, '--status-price-value-color', palette.accent || palette.primary || text.primary);
232
238
  setVar(vars, '--status-price-value-shadow', elevation.raised);
233
239
 
240
+ // Hero text — paired with the hero surface to keep contrast intact when
241
+ // a consumer overrides the hero bg. textOnDark is the fallback when no
242
+ // hero.text is set.
243
+ setVar(vars, '--brand-hero-text', hero.text || text.primary);
244
+ setVar(vars, '--brand-hero-text-on-dark', hero.textOnDark || text.onDark || text.inverse);
245
+ setVar(vars, '--brand-hero-eyebrow', hero.eyebrow || palette.primary);
246
+
247
+ // Footer chrome — FooterMinimal's default bg is dark, so it reads
248
+ // these instead of falling through --ui-text-primary (a light-on-light trap).
249
+ setVar(vars, '--brand-footer-text', footer.text || text.onDark || text.inverse);
250
+ setVar(vars, '--brand-footer-text-muted', footer.textMuted || text.onDarkMuted);
251
+ setVar(vars, '--brand-footer-link-color', footer.linkColor || footer.text || text.onDark || text.inverse);
252
+ setVar(vars, '--brand-footer-link-hover', footer.linkHover || '#ffffff');
253
+
254
+ // Plan card chrome — same dark-surface pairing.
255
+ setVar(vars, '--brand-plan-card-text', plan.cardText || text.inverse);
256
+ setVar(vars, '--brand-plan-card-muted-text', plan.cardMutedText || text.onDarkMuted);
257
+ setVar(vars, '--brand-plan-step-ring', plan.stepRing);
258
+
234
259
  return vars;
235
260
  }
236
261
 
@@ -28,9 +28,14 @@
28
28
  </head>
29
29
  <!-- Google Analytics is loaded dynamically after user consent -->
30
30
  <body>
31
- <main>
32
- <div id="app"></div>
33
- </main>
31
+ <noscript>
32
+ <style>
33
+ /* Scroll-reveal targets stay opacity:0 until JS observes them. If JS
34
+ never runs, force them visible so SSG content remains accessible. */
35
+ .fade-up-in { opacity: 1 !important; transform: none !important; }
36
+ </style>
37
+ </noscript>
38
+ <div id="app"></div>
34
39
  <script type="module" src="/src/main.js"></script>
35
40
  </body>
36
41
  </html>
@@ -0,0 +1 @@
1
+ 20.19.0
@@ -0,0 +1,121 @@
1
+ # site-__SLUG__
2
+
3
+ (staging URL TBD)
4
+
5
+ A site built on the [`@koehler8/cms`](https://github.com/koehler8/cms) framework. **Owner: TBD.** This file is auto-loaded by Claude Code in this directory — read it before making changes.
6
+
7
+ ## Project status
8
+
9
+ Freshly scaffolded via `npx cms-create-site`. Home page renders the bundled `Header` + `Hero` + `Footer` components with placeholder copy. No theme, extension, or custom components yet — see "Where things go" below for the path each new addition should take.
10
+
11
+ ## Working with the site owner
12
+
13
+ Assume the owner is not a developer unless you know otherwise. Default behaviors:
14
+
15
+ - Explain trade-offs in plain language; propose options, don't present decisions.
16
+ - Confirm before structural or visual changes that will be hard to reverse.
17
+ - Push small, reviewable changes frequently and verify on the live URL.
18
+ - Don't over-engineer — most requests can be satisfied by editing `pages/*.json` + bundled CMS components. Reach for a custom Vue component only when the bundled set clearly can't do the job.
19
+
20
+ ## Where things go (the navigation guide)
21
+
22
+ | Goal | Where |
23
+ |---|---|
24
+ | Edit page copy / hero text / section content | `site/content/{locale}/pages/{pageId}.json` under `content.{configKey}.*` keys |
25
+ | Edit site title, theme key, support email | `site/content/{locale}/site.json` |
26
+ | Edit header logo text, nav items, footer flags | `site/content/{locale}/shared.json` |
27
+ | Add a translation | Mirror the keys you want to override into `site/content/{otherLocale}/...` (only set the keys that differ — the rest inherit from the base locale) |
28
+ | Add an asset (image, icon) | `site/assets/img/...`. Reference it from a component via `resolveAsset('img/foo.png')` or a URL string in content (`"img/foo.png"`) |
29
+ | Override the favicon | Drop a real `.ico` at `site/favicon.ico`; the `cms-generate-public-assets` script picks it up |
30
+ | Add a one-off Vue component (this site only) | `site/components/Foo.vue`. Auto-globbed by the CMS — reference it as `"Foo"` in any page's `components[]`. Read content via `inject('pageContent')`, use `<style scoped>` with `var(--brand-*)` tokens. |
31
+ | Add a component intended to be reused on other sites | The local extension at `extensions/<slug>/`. Add `components/Foo.vue`, register it in `extension.config.json`, and add a dynamic import to `index.js`. Eventually publishable as `@koehler8/cms-ext-<slug>`. |
32
+ | Customize colors, typography, surfaces | `themes/<slug>/theme.config.js`. The CMS regenerates `virtual:cms-theme-vars.css` from the manifest on next build; CMS components consume the vars via `var(--brand-*)`. |
33
+ | Activate a theme | Set `"theme": "<slug>"` in `site/content/{baseLocale}/site.json`. |
34
+ | Add a new page | Create `site/content/{locale}/pages/{pageId}.json` with a `path` and `components[]` array. vite-ssg pre-renders one HTML file per page automatically. |
35
+ | Add a new locale | Create `site/content/{locale}/...` mirroring the base locale's structure. Auto-discovered. |
36
+ | Add a new theme to the project | `npx cms-create-theme <slug>` (scaffolds `themes/<slug>/`, then follow the printed next-steps). |
37
+ | Add a new local extension | `npx cms-create-extension <slug>`. |
38
+
39
+ If you find yourself writing a component that conceptually belongs in a "regions" or "agents" or "podcasts" sister site, **promote it from `site/components/` into `extensions/<slug>/`** so it can eventually become a published shared package.
40
+
41
+ ## Stack at a glance
42
+
43
+ - `@koehler8/cms` ^1.0.0-beta.15+ — Vue 3 + vite-ssg framework
44
+ - `@koehler8/cms-ext-compliance` — cookie banner / legal pages (registered by default)
45
+ - `[site/components/](site/components/)` — auto-globbed Vue files for one-off custom UI specific to this site. **The default home for new components.**
46
+ - `[themes/](themes/)`, `[extensions/](extensions/)` — currently empty. Add via the `cms-create-{theme,extension}` CLIs above.
47
+
48
+ ## File layout
49
+
50
+ ```
51
+ site/content/{locale}/ per-locale content (auto-discovered)
52
+ site.json site title, description, theme key, support email
53
+ shared.json cross-page settings — header logo / nav, footer flags
54
+ pages/{pageId}.json per-page components[] + content.{configKey}.* keys
55
+ pages/home.json home page (the only page in a freshly scaffolded site)
56
+ site/components/ auto-globbed Vue files (the default home for one-off
57
+ custom UI). Reference by basename in components[].
58
+ site/assets/img/ site-specific images / icons
59
+ site/style.css non-critical site overrides (loaded async by the CMS;
60
+ fine for things that wouldn't cause a visible flash)
61
+ site/favicon.ico optional override; `cms-generate-public-assets`
62
+ picks it up over the auto-generated letter-favicon
63
+ themes/<slug>/ site-local CMS theme — destined to lift into a
64
+ standalone @koehler8/cms-theme-<slug> repo
65
+ extensions/<slug>/ site-local CMS extension — destined to lift into a
66
+ standalone @koehler8/cms-ext-<slug> repo
67
+ ```
68
+
69
+ ## Common tasks (quick recipes)
70
+
71
+ ### Edit hero / footer copy
72
+ Open `site/content/{baseLocale}/pages/home.json`. Hero text lives under `content.promo.*`; footer text under `content.footer.*` (or whatever configKey the components use). Push and the deploy pipeline picks it up.
73
+
74
+ ### Edit nav links
75
+ `site/content/{baseLocale}/shared.json` → `content.header.navItems[N]`. Each entry is `{ text, href, target? }`. Add `"target": "_blank"` for off-site links.
76
+
77
+ ### Add a custom component
78
+ 1. Create `site/components/Foo.vue`.
79
+ 2. `inject('pageContent')` for content; `<style scoped>` with `var(--brand-*)` tokens.
80
+ 3. Reference as `"Foo"` in any page's `components[]`.
81
+
82
+ ### Add a new page
83
+ 1. Create `site/content/{baseLocale}/pages/{pageId}.json` with a `path` (e.g. `/about`) and `components[]`.
84
+ 2. Add a `navItems` entry pointing at the new path in `shared.json` if it should be in the nav.
85
+ 3. Push — vite-ssg generates `dist/{path}/index.html` automatically.
86
+
87
+ ### Change colors / typography
88
+ Edit `themes/<slug>/theme.config.js`. The CMS regenerates `virtual:cms-theme-vars.css` from the manifest on next build; CMS components and your custom components consume the vars via `var(--brand-*)`, so the change cascades.
89
+
90
+ ## Lockfile and npm version — read before bumping any dep
91
+
92
+ This project pins **Node 20.19 / npm 10.8** to match AWS Amplify exactly. Local installs with a different npm version produce subtly different lockfiles that look fine locally but break CI.
93
+
94
+ Two failure modes seen on sister sites:
95
+ 1. `Cannot find module '@rolldown/binding-linux-x64-gnu'` — npm 11 regen stripped optional-dep entries Amplify needs.
96
+ 2. `npm ci ... Missing: @types/react@..., @noble/hashes@... from lock file` — npm 11 omitted entries npm 10 expects.
97
+
98
+ Rules:
99
+ - **Always run `nvm use` before `npm install`**. `.nvmrc` pins 20.19.0 (which ships npm 10.8.x).
100
+ - **Prefer targeted bumps**: `npm install <pkg>@<ver> --no-audit --no-fund`. **Never** `rm package-lock.json && npm install` unless you're on the pinned npm version.
101
+ - **The build uses `npm install`**, not `npm ci` (see `amplify.yml`) for drift tolerance.
102
+
103
+ ## Local dev
104
+
105
+ ```bash
106
+ nvm use
107
+ cp .env.example .env # placeholder values; not used by default
108
+ npm install
109
+ npm run dev # Vite dev server on http://localhost:5173
110
+ ```
111
+
112
+ Build:
113
+
114
+ ```bash
115
+ npm run generate:public-assets # generates favicon/og-image from site.json
116
+ npm run build:ssg # pre-renders dist/{locale}/{page}/index.html
117
+ ```
118
+
119
+ ## Deploy
120
+
121
+ Pushes to `main` trigger an automatic AWS Amplify build per `amplify.yml`. Set up the Amplify app + custom domain via the AWS console (or `aws amplify create-app` + `create-domain-association`) — see the existing sister sites' setup notes if available, or the koehler8 site repos as reference.
@@ -0,0 +1,25 @@
1
+ # site-__SLUG__
2
+
3
+ A site built on [`@koehler8/cms`](https://github.com/koehler8/cms).
4
+
5
+ For architecture, conventions, and a navigation guide for "where things go" (content, components, themes, extensions), read **[CLAUDE.md](CLAUDE.md)**.
6
+
7
+ ## Local development
8
+
9
+ ```bash
10
+ nvm use # Node 20.19.0 per .nvmrc
11
+ cp .env.example .env
12
+ npm install
13
+ npm run dev
14
+ ```
15
+
16
+ ## Build
17
+
18
+ ```bash
19
+ npm run generate:public-assets
20
+ npm run build:ssg
21
+ ```
22
+
23
+ ## Deployment
24
+
25
+ Pushes to `main` trigger an automatic AWS Amplify build (see [amplify.yml](amplify.yml)).
@@ -0,0 +1,22 @@
1
+ version: 1
2
+ frontend:
3
+ phases:
4
+ preBuild:
5
+ commands:
6
+ - nvm install 20.19.0 && nvm use 20.19.0
7
+ # Use `npm install` (not `npm ci`) so the build tolerates
8
+ # lockfile/npm-version drift. See CLAUDE.md → "Lockfile and
9
+ # npm version" for the rationale.
10
+ - npm install --prefer-offline --no-audit --no-fund
11
+ - npm run generate:public-assets
12
+ build:
13
+ commands:
14
+ - npm run build:ssg
15
+ artifacts:
16
+ baseDirectory: dist
17
+ files:
18
+ - '**/*'
19
+ cache:
20
+ paths:
21
+ - node_modules/**/*
22
+ - .npm/**/*
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@koehler8/site-__SLUG__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:ssg": "vite-ssg build",
10
+ "preview": "vite preview",
11
+ "generate:public-assets": "cms-generate-public-assets --site-dir ./site"
12
+ },
13
+ "dependencies": {
14
+ "@koehler8/cms": "^1.0.0-beta.15",
15
+ "@koehler8/cms-ext-compliance": "^1.0.0-beta.4",
16
+ "@vitejs/plugin-vue": "^6.0.1",
17
+ "vite": "^8.0.3",
18
+ "vite-ssg": "^28.2.2",
19
+ "vue": "^3.5.22",
20
+ "vue-router": "^5.0.4"
21
+ },
22
+ "engines": {
23
+ "node": ">=20.19.0",
24
+ "npm": "^10.8.0"
25
+ }
26
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "baseLocale": "en"
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "components[0]": "Header",
3
+ "components[1]": "Hero",
4
+ "components[2]": "Footer",
5
+ "content.promo.mainText": "__DISPLAY_NAME__",
6
+ "content.promo.tagLine": "Welcome",
7
+ "path": "/"
8
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "content.header.logoText": "__DISPLAY_NAME__",
3
+ "content.header.showLocaleLinks": false,
4
+ "content.header.showWallet": false
5
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "description": "",
3
+ "googleId": "",
4
+ "supportEmail": "",
5
+ "title": "__DISPLAY_NAME__",
6
+ "url": ""
7
+ }
@@ -0,0 +1,4 @@
1
+ /* Site-specific style overrides. Loaded async by the CMS bootstrap, so
2
+ * fine for things that wouldn't cause a visible flash if they applied
3
+ * a few hundred ms late. For critical-path styles (above-the-fold,
4
+ * hide-this-default), see CLAUDE.md → Styles. */
@@ -0,0 +1,15 @@
1
+ import cms from '@koehler8/cms/vite';
2
+
3
+ export default {
4
+ plugins: cms({
5
+ siteDir: './site',
6
+ // Add local themes (created via `npx cms-create-theme <slug>`) here:
7
+ // themes: ['cms-theme-<slug>'],
8
+ // Then activate by setting `"theme": "<slug>"` in site.json.
9
+ extensions: [
10
+ '@koehler8/cms-ext-compliance',
11
+ // Add local extensions (created via `npx cms-create-extension <slug>`) here:
12
+ // 'cms-ext-<slug>',
13
+ ],
14
+ }),
15
+ };