create-bearminion 0.0.0-stage → 1.1.0

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 (121) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +65 -2
  4. package/dist/cli.mjs +1369 -0
  5. package/package.json +53 -3
  6. package/template/.storybook/main.ts +14 -0
  7. package/template/.storybook/preview.css +8 -0
  8. package/template/.storybook/preview.ts +17 -0
  9. package/template/README.md +251 -0
  10. package/template/_gitignore +21 -0
  11. package/template/astro.config.ts +46 -0
  12. package/template/content/pages/404.json +36 -0
  13. package/template/content/pages/about.json +101 -0
  14. package/template/content/pages/index.json +463 -0
  15. package/template/content/pages/preview.json +40 -0
  16. package/template/content/site.json +117 -0
  17. package/template/package.json +37 -0
  18. package/template/src/assets/uploads/favicon.png +0 -0
  19. package/template/src/assets/uploads/hero.png +0 -0
  20. package/template/src/assets/uploads/logo.png +0 -0
  21. package/template/src/assets/uploads/og.png +0 -0
  22. package/template/src/components/atoms/Button.astro +64 -0
  23. package/template/src/components/atoms/Button.stories.ts +42 -0
  24. package/template/src/components/atoms/Emphasis.astro +24 -0
  25. package/template/src/components/atoms/Emphasis.stories.ts +25 -0
  26. package/template/src/components/atoms/Image.astro +78 -0
  27. package/template/src/components/atoms/Image.stories.ts +35 -0
  28. package/template/src/components/atoms/LogoMark.astro +53 -0
  29. package/template/src/components/atoms/LogoMark.stories.ts +25 -0
  30. package/template/src/components/atoms/Typography.astro +50 -0
  31. package/template/src/components/atoms/Typography.stories.ts +20 -0
  32. package/template/src/components/atoms/button.ts +31 -0
  33. package/template/src/components/atoms/image.ts +34 -0
  34. package/template/src/components/atoms/options.ts +39 -0
  35. package/template/src/components/atoms/typography.ts +63 -0
  36. package/template/src/components/molecules/MediaFrame.astro +71 -0
  37. package/template/src/components/molecules/MediaFrame.stories.ts +14 -0
  38. package/template/src/components/molecules/Pill.astro +24 -0
  39. package/template/src/components/molecules/Pill.stories.ts +12 -0
  40. package/template/src/components/molecules/SectionHeader.astro +63 -0
  41. package/template/src/components/molecules/SectionHeader.stories.ts +15 -0
  42. package/template/src/components/molecules/SectionShell.astro +33 -0
  43. package/template/src/components/molecules/SectionShell.stories.ts +25 -0
  44. package/template/src/components/organisms/Footer.astro +133 -0
  45. package/template/src/components/organisms/Footer.stories.ts +16 -0
  46. package/template/src/components/organisms/FooterLinks.astro +32 -0
  47. package/template/src/components/organisms/Header.astro +94 -0
  48. package/template/src/components/organisms/Header.stories.ts +27 -0
  49. package/template/src/components/organisms/MobileBranch.astro +52 -0
  50. package/template/src/components/organisms/MobileMenu.astro +110 -0
  51. package/template/src/components/organisms/MobileMenu.stories.ts +30 -0
  52. package/template/src/components/organisms/NavBranch.astro +61 -0
  53. package/template/src/components/organisms/Navigation.astro +32 -0
  54. package/template/src/components/organisms/Navigation.stories.ts +25 -0
  55. package/template/src/components/organisms/site-definition.ts +28 -0
  56. package/template/src/components/sections/SectionList.astro +53 -0
  57. package/template/src/components/sections/contact/Contact.astro +227 -0
  58. package/template/src/components/sections/contact/Contact.stories.ts +16 -0
  59. package/template/src/components/sections/contact/definition.ts +50 -0
  60. package/template/src/components/sections/contact/wireframe.ts +15 -0
  61. package/template/src/components/sections/cta/Cta.astro +181 -0
  62. package/template/src/components/sections/cta/Cta.stories.ts +19 -0
  63. package/template/src/components/sections/cta/definition.ts +35 -0
  64. package/template/src/components/sections/cta/wireframe.ts +9 -0
  65. package/template/src/components/sections/definitions.ts +54 -0
  66. package/template/src/components/sections/faq/Faq.astro +131 -0
  67. package/template/src/components/sections/faq/Faq.stories.ts +16 -0
  68. package/template/src/components/sections/faq/definition.ts +51 -0
  69. package/template/src/components/sections/faq/wireframe.ts +19 -0
  70. package/template/src/components/sections/features/Features.astro +121 -0
  71. package/template/src/components/sections/features/Features.stories.ts +19 -0
  72. package/template/src/components/sections/features/definition.ts +64 -0
  73. package/template/src/components/sections/features/wireframe.ts +19 -0
  74. package/template/src/components/sections/hero/Hero.astro +155 -0
  75. package/template/src/components/sections/hero/Hero.stories.ts +28 -0
  76. package/template/src/components/sections/hero/definition.ts +36 -0
  77. package/template/src/components/sections/hero/wireframe.ts +12 -0
  78. package/template/src/components/sections/logo-strip/LogoStrip.astro +107 -0
  79. package/template/src/components/sections/logo-strip/LogoStrip.stories.ts +20 -0
  80. package/template/src/components/sections/logo-strip/definition.ts +30 -0
  81. package/template/src/components/sections/logo-strip/wireframe.ts +11 -0
  82. package/template/src/components/sections/pricing/Pricing.astro +210 -0
  83. package/template/src/components/sections/pricing/Pricing.stories.ts +16 -0
  84. package/template/src/components/sections/pricing/definition.ts +62 -0
  85. package/template/src/components/sections/pricing/wireframe.ts +18 -0
  86. package/template/src/components/sections/registry.ts +25 -0
  87. package/template/src/components/sections/shared.ts +13 -0
  88. package/template/src/components/sections/stats/Stats.astro +110 -0
  89. package/template/src/components/sections/stats/Stats.stories.ts +16 -0
  90. package/template/src/components/sections/stats/definition.ts +41 -0
  91. package/template/src/components/sections/stats/wireframe.ts +15 -0
  92. package/template/src/components/sections/testimonials/Testimonials.astro +172 -0
  93. package/template/src/components/sections/testimonials/Testimonials.stories.ts +19 -0
  94. package/template/src/components/sections/testimonials/definition.ts +42 -0
  95. package/template/src/components/sections/testimonials/wireframe.ts +21 -0
  96. package/template/src/components/sections/text-image/TextImage.astro +109 -0
  97. package/template/src/components/sections/text-image/TextImage.stories.ts +19 -0
  98. package/template/src/components/sections/text-image/definition.ts +38 -0
  99. package/template/src/components/sections/text-image/wireframe.ts +12 -0
  100. package/template/src/components/seo/Seo.astro +63 -0
  101. package/template/src/content.config.ts +19 -0
  102. package/template/src/dev/style-guide.astro +124 -0
  103. package/template/src/dev/variants.astro +117 -0
  104. package/template/src/env.d.ts +1 -0
  105. package/template/src/layouts/Base.astro +74 -0
  106. package/template/src/lib/assets.ts +13 -0
  107. package/template/src/lib/cn.ts +6 -0
  108. package/template/src/lib/content-dir.ts +8 -0
  109. package/template/src/lib/content-model.ts +25 -0
  110. package/template/src/lib/content.ts +26 -0
  111. package/template/src/lib/link-attrs.ts +74 -0
  112. package/template/src/lib/render-preview.ts +29 -0
  113. package/template/src/lib/sitemap.ts +36 -0
  114. package/template/src/pages/404.astro +15 -0
  115. package/template/src/pages/[...slug].astro +32 -0
  116. package/template/src/pages/robots.txt.ts +10 -0
  117. package/template/src/stories/args.ts +34 -0
  118. package/template/src/stories/options.ts +63 -0
  119. package/template/src/stories/sample.ts +24 -0
  120. package/template/src/styles/global.css +379 -0
  121. package/template/tsconfig.json +8 -0
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "create-bearminion",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.1.0",
4
+ "description": "Create a BearMinion site: an Astro website you edit by clicking on it.",
5
+ "license": "MIT",
6
+ "author": "Jakub Lange",
7
+ "homepage": "https://github.com/langehelp-software/bearminion#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/langehelp-software/bearminion.git",
11
+ "directory": "packages/create-bearminion"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/langehelp-software/bearminion/issues"
15
+ },
16
+ "keywords": [
17
+ "astro",
18
+ "withastro",
19
+ "cms",
20
+ "git-cms",
21
+ "create",
22
+ "starter",
23
+ "template",
24
+ "bearminion"
25
+ ],
26
+ "engines": {
27
+ "node": ">=22.12.0"
28
+ },
29
+ "type": "module",
30
+ "bin": {
31
+ "create-bearminion": "./dist/cli.mjs"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "template",
36
+ "README.md",
37
+ "CHANGELOG.md",
38
+ "LICENSE"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "provenance": true
43
+ },
44
+ "devDependencies": {
45
+ "@clack/prompts": "^1.8.1",
46
+ "@types/node": "^22.19.0",
47
+ "tsdown": "0.23.1",
48
+ "typescript": "~5.9.3",
49
+ "vitest": "^5.0.4"
50
+ },
51
+ "scripts": {
52
+ "build": "tsdown",
53
+ "test": "vitest run",
54
+ "typecheck": "tsc -p tsconfig.json --noEmit"
55
+ }
6
56
  }
@@ -0,0 +1,14 @@
1
+ import type { StorybookConfig } from '@storybook-astro/framework'
2
+
3
+ const config = {
4
+ stories: ['../src/**/*.stories.@(ts|tsx)'],
5
+ framework: {
6
+ name: '@storybook-astro/framework',
7
+ options: {},
8
+ },
9
+ core: {
10
+ disableTelemetry: true,
11
+ },
12
+ } satisfies StorybookConfig
13
+
14
+ export default config
@@ -0,0 +1,8 @@
1
+ @import "../src/styles/global.css";
2
+
3
+ body,
4
+ .sb-show-main {
5
+ background: var(--t-base-bg);
6
+ color: var(--t-base-fg);
7
+ font-family: var(--font-sans);
8
+ }
@@ -0,0 +1,17 @@
1
+ import type { Preview } from '@storybook-astro/framework'
2
+ import './preview.css'
3
+
4
+ const preview = {
5
+ parameters: {
6
+ layout: 'padded',
7
+ controls: {
8
+ expanded: true,
9
+ matchers: {
10
+ color: /(background|color)$/i,
11
+ date: /Date$/i,
12
+ },
13
+ },
14
+ },
15
+ } satisfies Preview
16
+
17
+ export default preview
@@ -0,0 +1,251 @@
1
+ # BearMinion site
2
+
3
+ The default BearMinion template, created with `npm create bearminion`. Astro and Tailwind render the JSON in `content/`, and while `npm run dev` runs, an inline editor writes that JSON back. `npm run build` writes a static site with no editor in it.
4
+
5
+ ```bash
6
+ npm install # if it was created with --no-install
7
+ npm run dev
8
+ ```
9
+
10
+ Open the site, press Ctrl+E (Cmd+E on a Mac) for the sidebar, click any text to edit it, and Ctrl/Cmd+S to save. Commit `content/` and `src/assets/` like any other code; a host such as Vercel or Netlify builds the site from the repository.
11
+
12
+ | Script | What it does |
13
+ | --- | --- |
14
+ | `npm run dev` | Astro dev server with the editor |
15
+ | `npm run build` | Static site in `dist/`, no editor |
16
+ | `npm run preview` | Serves `dist/` |
17
+ | `npm run typecheck` | `astro check` |
18
+
19
+ This template is yours to change: it is the theme. `bearminion` is the only runtime package from this project, and it reads your section definitions, site definition and theme from `src/lib/content-model.ts`.
20
+
21
+ ## Organisms and sections
22
+
23
+ | Layer | What it is | Where it lives |
24
+ | --- | --- | --- |
25
+ | Atoms | Typed primitives: typography, button, image | `src/components/atoms` |
26
+ | Molecules | Small compositions of atoms: section shell, section header, media frame, pill | `src/components/molecules` |
27
+ | Organisms | Static, site-wide and standalone UI | `src/components/organisms` |
28
+ | Sections | Page-content blocks stored in page JSON | `src/components/sections` |
29
+
30
+ Organisms are Header, Navigation, MobileMenu, and Footer. A modal, a dialog, and a cookie banner belong in this folder too. They are the chrome around every page (or a standalone widget), and they are fed by `content/site.json`.
31
+
32
+ Sections are `hero`, `logoStrip`, `features`, `textImage`, `stats`, `testimonials`, `pricing`, `faq`, `cta`, and `contact`. Each page entry is `{ id, anchor?, type, variant, tone, motion?, props }`. `anchor` is an optional lowercase slug, unique on the page, and it becomes the section element's `id`. `data-section-id` stays the section id. `[data-section-id]` sets `scroll-margin-top: 7rem` so a sticky or floating header does not cover the target. `html` uses `scroll-behavior: smooth`, and `prefers-reduced-motion: reduce` forces `scroll-behavior: auto`. Section components are built from molecules and atoms. A section may use an organism when it needs one, such as a modal. The section root carries `data-section-id`, `data-section-type`, `data-variant`, `data-tone`, and `data-motion`. Atoms accept a `class` and forward `data-*` attributes, including `data-field`, so the editor can find the text.
33
+
34
+ Paragraph fields (`sub`, `intro`, `body`, `description`, `quote`, `note`, `thanks`, and FAQ answers) are markdown. The template renders bold, italic, links, and paragraphs through `bearminion/rich-text`, escapes every other character, and adds `rel="noopener noreferrer"` on `http` and `https` links.
35
+
36
+ ```text
37
+ src/components/
38
+ atoms/ Typography, Button, Image, LogoMark, Emphasis
39
+ molecules/ SectionShell, SectionHeader, MediaFrame, Pill
40
+ organisms/ Header, Navigation, MobileMenu, Footer, plus site-definition.ts (header and footer layouts, limits)
41
+ sections/ one directory per type, plus definitions.ts, registry.ts, shared.ts, SectionList.astro
42
+ hero/ definition.ts (zod schema, defaults, options), wireframe.ts (picker thumbnail), Hero.astro, Hero.stories.ts
43
+ logo-strip/ features/ text-image/ stats/ testimonials/ pricing/ faq/ cta/ contact/
44
+ seo/ Seo
45
+ ```
46
+
47
+ `src/lib/content-model.ts` joins the section definitions and `organisms/site-definition.ts`. `astro.config.ts` passes it to `bearminion()` and `loadContent()`. A section directory holds everything about one type. See [Add a section type](#add-a-section-type) below.
48
+
49
+ ## Add a section type
50
+
51
+ A section type is one directory under `src/components/sections/` plus three one-line registrations. This walks through a `quote` section: a pull quote with an attribution.
52
+
53
+ **1. The definition** (`quote/definition.ts`). The Zod schema validates the JSON and drives the editor's Content fields; `defaults` is what a new section starts with and must pass the schema.
54
+
55
+ ```ts
56
+ import { z } from 'zod'
57
+ import { defineSection, richTextSchema, textSchema } from 'bearminion'
58
+ import { quoteWireframe } from './wireframe'
59
+
60
+ export const quoteSchema = z.object({
61
+ quote: richTextSchema,
62
+ name: textSchema,
63
+ role: textSchema.optional(),
64
+ })
65
+
66
+ export type QuoteProps = z.infer<typeof quoteSchema>
67
+
68
+ export const quote = defineSection({
69
+ type: 'quote',
70
+ label: 'Quote',
71
+ description: 'A pull quote with the name of the person who said it.',
72
+ schema: quoteSchema,
73
+ defaults: { quote: 'A short quote, in the words of the person who said it.', name: 'Alex Morgan', role: 'Role, Company' },
74
+ variants: ['plain', 'card'] as const,
75
+ defaultVariant: 'plain',
76
+ // Props an author may clear. A test checks this list against the schema.
77
+ optional: { props: ['role'] },
78
+ wireframe: quoteWireframe,
79
+ })
80
+ ```
81
+
82
+ **2. The picker thumbnail** (`quote/wireframe.ts`): rectangles in a 100 × 100 frame, `[left, top, width, height, kind]`.
83
+
84
+ ```ts
85
+ import { defineWireframe } from 'bearminion'
86
+
87
+ export const quoteWireframe = defineWireframe([
88
+ [14, 28, 72, 10, 'title'],
89
+ [14, 44, 60, 6, 'text'],
90
+ [14, 64, 24, 6, 'text'],
91
+ ])
92
+ ```
93
+
94
+ **3. The component** (`quote/Quote.astro`). Put `data-field` on every editable element, with the path into the page JSON, so the editor can find it.
95
+
96
+ ```astro
97
+ ---
98
+ import type { Section } from '../definitions'
99
+ import { renderRichText } from 'bearminion/rich-text'
100
+ import SectionShell from '../../molecules/SectionShell.astro'
101
+
102
+ interface Props {
103
+ section: Extract<Section, { type: 'quote' }>
104
+ index: number
105
+ motion?: boolean
106
+ }
107
+
108
+ const { section, index, motion = true } = Astro.props
109
+ const field = (path: string) => `sections.${index}.props.${path}`
110
+ ---
111
+
112
+ <SectionShell id={section.id} anchor={section.anchor} type={section.type} variant={section.variant} tone={section.tone} motion={section.motion ?? motion}>
113
+ <figure class="section-container">
114
+ <blockquote class="text-3xl" data-field={field('quote')} set:html={renderRichText(section.props.quote)} />
115
+ <figcaption>
116
+ <span data-field={field('name')}>{section.props.name}</span>
117
+ {section.props.role && <span class="text-muted" data-field={field('role')}>{section.props.role}</span>}
118
+ </figcaption>
119
+ </figure>
120
+ </SectionShell>
121
+ ```
122
+
123
+ **4. Register it** in three places:
124
+
125
+ - `sections/definitions.ts`: add `quote` to `sectionDefinitions`. This is the schema side, loaded by `astro.config.ts` and the editor, so it must not import `.astro` files.
126
+ - `sections/registry.ts`: add `quote: Quote`. The `satisfies Record<SectionType, unknown>` check fails until you do.
127
+ - `sections/SectionList.astro`: add a `case 'quote'` that renders `<Quote section={section} index={index} motion={motion} />`. The switch is what gives each component its exact section type.
128
+
129
+ Restart `npm run dev`. The section appears in the Add section picker, its fields in the sidebar, and `npm run typecheck` checks the component against the schema. Add a `Quote.stories.ts` next to it if you use Storybook.
130
+
131
+ Removing a section type later is safe: pages that still hold it open in the editor with an "Unknown" section you can delete, and `astro build` fails until they are cleaned up.
132
+
133
+ ## Atoms
134
+
135
+ Design tokens (colors, fonts, and the semantic type scale) are declared in `src/styles/global.css`. Fonts and radii live in `@theme`. The tone colors (`bg`, `fg`, `muted`, `line`, `surface`, `accent`, `on`, `link`) live in `@theme inline`, so a section’s `[data-tone]` variables apply to `text-muted`, `bg-surface`, borders, and the other utilities. Inverse and brand text pairs meet WCAG AA. Atoms use those tokens through Tailwind utilities. Variant styles are built with `class-variance-authority`.
136
+
137
+ ### Typography
138
+
139
+ `Typography.astro` renders the element named by `as` (`h1`–`h6`, `p`, `span`, `blockquote`, `div`, `li`, `figcaption`).
140
+
141
+ | Prop | Values |
142
+ | --- | --- |
143
+ | `size` | `display`, `display-center`, `display-editorial`, `h2`, `h2-lg`, `h3`, `lead`, `body`, `small`, `meta`, `eyebrow`, `mono`, `stat`, plus `xs`–`6xl` |
144
+ | `color` | `default`, `muted`, `accent`, `link`, `on` (the active tone) |
145
+ | `weight` | `regular`, `medium`, `semibold`, `bold` |
146
+ | `font` | `sans` (Bricolage Grotesque), `serif` (Instrument Serif), `mono` (JetBrains Mono) |
147
+ | `align` | `left`, `center`, `right` |
148
+ | `class` | Extra Tailwind classes, merged with `tailwind-merge` |
149
+ | `data-field` | Forwarded onto the element |
150
+
151
+ ### Button
152
+
153
+ `Button.astro` renders an `<a>` when `href` is set, and a `<button>` otherwise. An `http` or `https` href opens in a new tab with `rel="noopener noreferrer"` unless `newTab` is `false`. `newTab` forces a new tab for any href. `id` and `data-analytics` are forwarded.
154
+
155
+ | Prop | Values |
156
+ | --- | --- |
157
+ | `variant` | `primary` (accent fill, optional arrow), `secondary` (inset line), `strong` (foreground on background), `ghost`, `text` |
158
+ | `size` | `sm` (2.5rem, used in the header), `md` (3rem), `lg` (3.25rem) |
159
+ | `arrow` | Appends a trailing arrow. Primary and text links opt in. |
160
+ | `href` | Present for links |
161
+ | `newTab` | Opens in a new window and sets `rel="noopener noreferrer"` |
162
+ | `class`, `data-field` | Forwarded onto the `<a>` or `<button>` |
163
+
164
+ Section JSON links are `{ label, href, newTab?, id?, trackingId? }`. The section component chooses the button variant. `trackingId` is rendered as `data-analytics`.
165
+
166
+ ### Image
167
+
168
+ `Image.astro` uses Astro `<Image>` for files under `src/assets/` and a plain `<img>` for remote `http(s)` URLs. A `src/assets/...` path with no matching file throws at build time, with a message that says to restore the file or choose another image.
169
+
170
+ `MediaFrame.astro` is the image slot of a section: a frame with an aspect ratio, the image inside, or diagonal stripes and the alt text when there is no image yet. The slot's `data-field` sits on the frame, so the editor outlines and opens the whole frame, empty or not. The logo strip does the same on each logo's list item.
171
+
172
+ The site's own images (`logo.png`, `favicon.png`, `og.png`, `hero.png`) live in `src/assets/uploads/`, the editor's image library, so they can be picked again after you replace them. The library refuses to delete an image that a saved page or `site.json` still uses.
173
+
174
+ | Prop | Values |
175
+ | --- | --- |
176
+ | `src`, `alt` | `alt` is required (it may be an empty string) |
177
+ | `aspect` | `auto`, `square`, `video`, `4/3`, `4/5`, `3/2` |
178
+ | `fit` | `cover`, `contain` (CSS `object-fit`) |
179
+ | `rounded` | `none`, `md`, `lg`, `xl`, `2xl`, `3xl`, `full` |
180
+ | `loading` | `lazy` (default) or `eager` |
181
+ | `priority` | Loads the image eagerly |
182
+ | `sizes`, `widths` | Passed through to Astro Image for local files |
183
+ | `class`, `data-field` | Forwarded onto the `<img>` |
184
+
185
+ ## SEO and sitemap
186
+
187
+ `Seo.astro` reads the page `seo` object and the site defaults in `site.json`. The document title is `seo.titleTemplate` with `%s` replaced by the page SEO title. Description falls back to the site description. The canonical URL is `seo.canonical` when set, otherwise the page path joined to `site.url`. Robots is `noindex` or `index`, plus `nofollow` or `follow`. Open Graph, Twitter, and WebPage JSON-LD use the same title, description, and image.
188
+
189
+ `astro.config.ts` sets `site` from `site.json` and adds `@astrojs/sitemap`. The filter keeps content pages whose `seo.noindex` is false. `content/pages/preview.json` is the sample noindex page: it is built, it emits `noindex`, and it is absent from the sitemap. `src/pages/robots.txt.ts` points crawlers at `sitemap-index.xml`.
190
+
191
+ The style guide at `/style-guide` renders every typography size and button variant. `/variants` renders every section, header, and footer variant. They live in `src/dev/` and only `npm run dev` serves them (the `devPages` integration in `astro.config.ts`), so they never reach the built site. Delete that integration and the folder if you do not want them.
192
+
193
+ ## Dev editor
194
+
195
+ `astro.config.ts` registers `bearminion()`. It is active only while `pnpm dev` is running. The production build does not add editor routes, scripts, or the overlay.
196
+
197
+ The page gets a shadow-root overlay. Click text with `data-field` to edit it, and hover a section (`data-section-id`) for add, duplicate, move, and delete. The corner **Editor** button or Ctrl+E / Cmd+E opens the sidebar. Ctrl+S / Cmd+S saves. Escape leaves a field, then closes the add picker or the sidebar. Unsaved edits turn the corner dot amber and trigger a beforeunload warning.
198
+
199
+ A variant change or a new, duplicated, moved, or deleted section is re-rendered by `POST /__bearminion/api/preview`. The handler validates the draft, renders `SectionList` with Astro's container API inside the Vite SSR runner, and returns HTML. The overlay replaces `main#content`. Nothing is written until save. In dev, an image path under `src/assets/` that is missing from the eager glob is served from `/__bearminion/assets` so a new upload can use a plain `<img>` immediately. When the file is gone, that route returns a Missing image placeholder instead, so the page shows the gap. Production still resolves that path with `import.meta.glob` and Astro `<Image>`, and still throws if the file is missing; the editor's save validation keeps content from pointing at a missing file.
200
+
201
+ Header links nest up to three levels (`MAX_NAV_DEPTH`). Desktop navigation uses hover and focus flyouts. The mobile menu uses an accordion. The link picker lists pages, `#anchor` on the current page, `/page#anchor` on other pages, and a custom URL.
202
+
203
+ The API is `/__bearminion/api` on localhost. Saving `content/*.json` asks the editor to reload. If the draft is still dirty, the editor asks to save, discard, or cancel first. Unsaved drafts are kept in `localStorage` until save or discard. An upload does not reload by itself, so the draft can preview the new file before save. Request and response shapes, with curl examples, are in the [Dev API docs](https://github.com/langehelp-software/bearminion/blob/main/docs/dev-api.md), and every sidebar tab is described in [the editor docs](https://github.com/langehelp-software/bearminion/blob/main/docs/editor.md).
204
+
205
+ ```bash
206
+ pnpm dev
207
+ curl -s http://127.0.0.1:4321/__bearminion/api/pages
208
+ ```
209
+
210
+ ## Outside the BearMinion repository
211
+
212
+ Nothing in this folder points outside it. `package.json` asks for `bearminion` by version range, not `workspace:*`, so a copy installs with a plain `npm install`, `pnpm install` or `yarn`. In the BearMinion monorepo pnpm links that range to `packages/editor`.
213
+
214
+ To try unreleased changes from the monorepo, build a tarball with `pnpm --filter bearminion pack` and point the copy at it: `"bearminion": "file:/path/to/bearminion-1.0.0.tgz"`. CI does the same with the packed `create-bearminion` on every pull request.
215
+
216
+ The editor's end-to-end tests and this template's unit tests live in the monorepo's `tests/` package, not here.
217
+
218
+ <!-- storybook:start -->
219
+ ## Storybook
220
+
221
+ Storybook uses `@storybook-astro/framework` (Storybook 10). It renders the `.astro` files on the server with Astro’s container API, so the components stay Astro. Tailwind and the design tokens come from `src/styles/global.css`, imported in `.storybook/preview.css`. The Tailwind Vite plugin is the one already set in `astro.config.ts`.
222
+
223
+ ```bash
224
+ npm run storybook
225
+ npm run build-storybook
226
+ ```
227
+
228
+ `storybook` opens the dev server at `http://localhost:6006`. Controls re-render Astro stories there. `build-storybook` writes a static site to `storybook-static/`. That build pre-renders each story with its saved args, and the Controls panel in the static site does not apply new values.
229
+
230
+ Stories live next to the component as `Component.stories.ts`. The `title` sets the sidebar group: `Atoms/Button`, `Molecules/SectionShell`, `Organisms/Header`, `Sections/Hero`. Props are `args`. Children go in `args.slots.default` (a string, another `.astro` component, or `{ component, props, slots }`). Named slots use the same shape. Section stories pass the matching block from `content/pages/index.json`.
231
+
232
+ ```ts
233
+ import Button from './Button.astro'
234
+
235
+ export default {
236
+ title: 'Atoms/Button',
237
+ component: Button,
238
+ }
239
+
240
+ export const Playground = {
241
+ args: {
242
+ variant: 'primary',
243
+ size: 'md',
244
+ href: '/about',
245
+ slots: { default: 'Read the approach' },
246
+ },
247
+ }
248
+ ```
249
+
250
+ A few limits of this renderer: Astro `<script>` tags are not run, so the mobile menu toggle works on the built site and the canvas shows the menu markup. Local images use Vite `/@fs` URLs, which resolve while `storybook dev` is running. Astro’s image optimizer still runs in `astro build`. The static Storybook output keeps those `/@fs` URLs, so image stories in `storybook-static` do not load the files on their own.
251
+ <!-- storybook:end -->
@@ -0,0 +1,21 @@
1
+ # dependencies
2
+ node_modules/
3
+
4
+ # build output
5
+ dist/
6
+ storybook-static/
7
+
8
+ # generated types
9
+ .astro/
10
+
11
+ # editor version history (local only)
12
+ .editor/
13
+
14
+ # environment
15
+ .env
16
+ .env.*
17
+ !.env.example
18
+
19
+ # logs and OS files
20
+ *.log
21
+ .DS_Store
@@ -0,0 +1,46 @@
1
+ import path from 'node:path'
2
+ import sitemap from '@astrojs/sitemap'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import bearminion, { loadContent } from 'bearminion/astro'
5
+ import type { AstroIntegration } from 'astro'
6
+ import { defineConfig } from 'astro/config'
7
+ import { contentDirectory } from './src/lib/content-dir'
8
+ import { contentModel } from './src/lib/content-model'
9
+ import { createSitemapFilter } from './src/lib/sitemap'
10
+
11
+ const contentDir = contentDirectory()
12
+ // Tolerant, so `astro dev` still starts when a page holds a section type the site dropped: the editor can then remove it.
13
+ const content = loadContent(contentDir, contentModel, { tolerateOrphans: true })
14
+
15
+ /** The style guide and the variants gallery are theme workshops: `astro dev` serves them, `astro build` leaves them out. */
16
+ const devPages: AstroIntegration = {
17
+ name: 'bearminion-site:dev-pages',
18
+ hooks: {
19
+ 'astro:config:setup': ({ command, injectRoute }) => {
20
+ if (command !== 'dev') return
21
+ injectRoute({ pattern: '/style-guide', entrypoint: new URL('./src/dev/style-guide.astro', import.meta.url) })
22
+ injectRoute({ pattern: '/variants', entrypoint: new URL('./src/dev/variants.astro', import.meta.url) })
23
+ },
24
+ },
25
+ }
26
+
27
+ export default defineConfig({
28
+ site: content.site.url,
29
+ integrations: [
30
+ bearminion({
31
+ contentDir,
32
+ ...contentModel,
33
+ git: process.env.BEARMINION_GIT === 'true',
34
+ uploadsDir: process.env.BEARMINION_UPLOADS_DIR
35
+ ? path.resolve(process.env.BEARMINION_UPLOADS_DIR)
36
+ : path.resolve(contentDir, '../src/assets/uploads'),
37
+ }),
38
+ sitemap({
39
+ filter: createSitemapFilter(content.pages),
40
+ }),
41
+ devPages,
42
+ ],
43
+ vite: {
44
+ plugins: [tailwindcss()],
45
+ },
46
+ })
@@ -0,0 +1,36 @@
1
+ {
2
+ "sections": [
3
+ {
4
+ "anchor": "not-found",
5
+ "id": "not-found",
6
+ "props": {
7
+ "eyebrow": "404",
8
+ "heading": "Page not found.",
9
+ "marks": [
10
+ {
11
+ "end": 15,
12
+ "font": "serif",
13
+ "italic": true,
14
+ "start": 5
15
+ }
16
+ ],
17
+ "primary": {
18
+ "href": "/",
19
+ "label": "Back to the homepage"
20
+ },
21
+ "sub": "The link may be broken, or the page may have moved."
22
+ },
23
+ "tone": "base",
24
+ "type": "hero",
25
+ "variant": "centered"
26
+ }
27
+ ],
28
+ "seo": {
29
+ "description": "The page you were looking for does not exist.",
30
+ "nofollow": true,
31
+ "noindex": true,
32
+ "title": "Page not found"
33
+ },
34
+ "slug": "404",
35
+ "title": "Page not found"
36
+ }
@@ -0,0 +1,101 @@
1
+ {
2
+ "sections": [
3
+ {
4
+ "anchor": "intro",
5
+ "id": "intro",
6
+ "props": {
7
+ "eyebrow": "About",
8
+ "heading": "The story behind the work.",
9
+ "marks": [
10
+ {
11
+ "end": 26,
12
+ "font": "serif",
13
+ "italic": true,
14
+ "start": 17
15
+ }
16
+ ],
17
+ "primary": {
18
+ "href": "/#contact",
19
+ "label": "Get in touch"
20
+ },
21
+ "secondary": {
22
+ "href": "/",
23
+ "label": "Back to the homepage"
24
+ },
25
+ "sub": "Open with one or two sentences about who you are and what you care about."
26
+ },
27
+ "tone": "base",
28
+ "type": "hero",
29
+ "variant": "centered"
30
+ },
31
+ {
32
+ "anchor": "story",
33
+ "id": "story",
34
+ "props": {
35
+ "body": "Tell the short version of how you got here: the problem you saw, what you did about it, and what drives you now.",
36
+ "eyebrow": "Our story",
37
+ "heading": "How it started.",
38
+ "image": {
39
+ "alt": "",
40
+ "src": "placeholder"
41
+ },
42
+ "link": {
43
+ "href": "/#features",
44
+ "label": "See what we offer"
45
+ },
46
+ "marks": [
47
+ {
48
+ "end": 15,
49
+ "font": "serif",
50
+ "italic": true,
51
+ "start": 7
52
+ }
53
+ ],
54
+ "points": [
55
+ "When and where you started",
56
+ "What makes your approach different",
57
+ "Who you work with today"
58
+ ]
59
+ },
60
+ "tone": "base",
61
+ "type": "textImage",
62
+ "variant": "left"
63
+ },
64
+ {
65
+ "anchor": "contact",
66
+ "id": "contact",
67
+ "props": {
68
+ "body": "Invite the reader to take the next step, and say what happens when they do.",
69
+ "heading": "Want to work together?",
70
+ "marks": [
71
+ {
72
+ "end": 22,
73
+ "font": "serif",
74
+ "italic": true,
75
+ "start": 13
76
+ }
77
+ ],
78
+ "primary": {
79
+ "href": "/#contact",
80
+ "label": "Contact us"
81
+ },
82
+ "secondary": {
83
+ "href": "/",
84
+ "label": "Back home"
85
+ }
86
+ },
87
+ "tone": "subtle",
88
+ "type": "cta",
89
+ "variant": "centered"
90
+ }
91
+ ],
92
+ "seo": {
93
+ "description": "Who you are, why you started, and the people behind the work.",
94
+ "nofollow": false,
95
+ "noindex": false,
96
+ "ogImage": "src/assets/uploads/og.png",
97
+ "title": "About"
98
+ },
99
+ "slug": "about",
100
+ "title": "About"
101
+ }