diff --git a/blog-cloudflare/.agents/skills/building-emdash-site/references/configuration.md b/blog-cloudflare/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/blog-cloudflare/.agents/skills/building-emdash-site/references/configuration.md +++ b/blog-cloudflare/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/blog-cloudflare/AGENTS.md b/blog-cloudflare/AGENTS.md index 9af5138..912334c 100644 --- a/blog-cloudflare/AGENTS.md +++ b/blog-cloudflare/AGENTS.md @@ -41,3 +41,64 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A blog with posts, pages, categories, tags, full-text search, and RSS. Designed for personal writing, technical writing, indie newsletters, and anything where the writing is the product. Editorial-tech aesthetic: confident sans-serif, restrained accent, real article structure with bylines and reading time. + +## Pages + +| Page | Path | What it shows | +| ----------- | ------------------ | ------------------------------------------------------------------------------------------------------ | +| Home | `/` | Featured post hero (large image + excerpt), latest posts grid | +| All posts | `/posts` | Article count, full post list with excerpts and tag chips | +| Post detail | `/posts/[slug]` | Featured image, title, body, left meta column (authors + date), right TOC + search + categories gutter | +| Search | `/search` | Full-text search UI | +| Page | `/pages/[slug]` | Static page content (Portable Text) | +| Category | `/category/[slug]` | Posts filtered by category | +| Tag | `/tag/[slug]` | Posts filtered by tag | +| RSS | `/rss.xml` | Generated feed | + +## Schema + +- `posts` collection: `title`, `featured_image`, `content` (Portable Text), `excerpt` (text). +- `pages` collection: `title`, `content` (Portable Text). Used for `/about` etc. +- Taxonomies: `category`, `tag`. +- Single `primary` menu (Home, About, Posts by default). + +Site settings have `title` and `tagline` -- both render in the header / footer. + +## Visual character + +Single typeface: **Inter** on `--font-sans`, used for everything including headings (with tighter letter-spacing on h1/h2). **JetBrains Mono** on `--font-mono` for inline code and code blocks. Body and headings share the same family; weight and size carry the hierarchy. + +The accent is `#0066cc` -- used for links, the post-card title hover, and the search input focus ring. There's also a secondary text colour (`--color-text-secondary`) and a `--color-muted` for meta info. Don't add a second accent. + +The article layout is the standout feature: a three-column reading view with a left meta column (author bylines, date), centred 680px body column, and a right gutter for search, table of contents, and categories. Don't flatten that into one column on desktop -- the layout signals "this is something to read". + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default -- uncomment and change to override. The dark mode palette is defined inside `Base.astro` itself; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:`. To swap the body face, change the `name:` for the entry bound to `cssVariable: "--font-sans"`. Good alternatives: Geist, IBM Plex Sans, Söhne (if you have a licence), Public Sans. If you want a serif-bodied blog, swap to a humanist serif like Source Serif, Crimson Pro, or Lora -- but then also raise `--font-size-base` to `1.0625rem` for readability. + +CSS variables worth knowing: + +- `--color-accent`, `--color-accent-hover`, `--color-on-accent`, `--color-accent-ring` +- `--color-bg`, `--color-bg-subtle`, `--color-surface`, `--color-text`, `--color-text-secondary`, `--color-muted`, `--color-border`, `--color-border-subtle` +- `--font-sans`, `--font-mono` +- `--tracking-tight` / `--tracking-snug` / `--tracking-wide` / `--tracking-wider` -- letter-spacing tokens used across headings and meta labels +- `--content-width` (680px) -- article body column +- `--wide-width` (1200px) -- max container +- `--gutter-width` (200px) -- right sidebar (TOC) on article pages +- `--meta-col-width` (180px) -- left meta column on article pages +- `--avatar-size-{xs,sm,md,lg}` -- byline avatar sizes at different scales + +## What not to do + +- Don't add a second accent colour or coloured section backgrounds. The page should be black, white, and one blue. +- Don't replace Inter with a display sans (Bebas, Anton, etc.). Headings rely on weight contrast, not novelty faces. +- Don't collapse the article gutter on desktop -- it's part of the reading experience. +- Don't use stock blog copy ("Welcome to my blog", "Stay tuned for more"). Write a real tagline that says what this blog is about. +- Don't seed the home page with three identical placeholder posts. If you only have one real post, show one real post. +- Don't enable comments without a plan to moderate them. The template doesn't ship a comments system by default for a reason. diff --git a/blog-cloudflare/astro.config.mjs b/blog-cloudflare/astro.config.mjs index 664fbbe..1b87b1d 100644 --- a/blog-cloudflare/astro.config.mjs +++ b/blog-cloudflare/astro.config.mjs @@ -2,7 +2,7 @@ import cloudflare from "@astrojs/cloudflare"; import react from "@astrojs/react"; import { d1, r2, sandbox } from "@emdash-cms/cloudflare"; import { formsPlugin } from "@emdash-cms/plugin-forms"; -import { webhookNotifierPlugin } from "@emdash-cms/plugin-webhook-notifier"; +import webhookNotifier from "@emdash-cms/plugin-webhook-notifier"; import { defineConfig, fontProviders } from "astro/config"; import emdash from "emdash/astro"; @@ -19,7 +19,7 @@ export default defineConfig({ database: d1({ binding: "DB", session: "auto" }), storage: r2({ binding: "MEDIA" }), plugins: [formsPlugin()], - sandboxed: [webhookNotifierPlugin()], + sandboxed: [webhookNotifier], sandboxRunner: sandbox(), marketplace: "https://marketplace.emdashcms.com", }), diff --git a/blog-cloudflare/package.json b/blog-cloudflare/package.json index 73949d8..12ae0f8 100644 --- a/blog-cloudflare/package.json +++ b/blog-cloudflare/package.json @@ -14,13 +14,13 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/cloudflare": "^13.1.7", + "@astrojs/cloudflare": "^13.5.3", "@astrojs/react": "^5.0.0", - "@emdash-cms/cloudflare": "^0.12.0", + "@emdash-cms/cloudflare": "^0.14.0", "@emdash-cms/plugin-forms": "^0.2.2", - "@emdash-cms/plugin-webhook-notifier": "^0.1.3", - "astro": "^6.0.1", - "emdash": "^0.12.0", + "@emdash-cms/plugin-webhook-notifier": "^0.2.0", + "astro": "^6.3.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, @@ -28,5 +28,6 @@ "@astrojs/check": "^0.9.7", "@cloudflare/workers-types": "^4.20260305.1", "wrangler": "^4.83.0" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/blog/.agents/skills/building-emdash-site/references/configuration.md b/blog/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/blog/.agents/skills/building-emdash-site/references/configuration.md +++ b/blog/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/blog/AGENTS.md b/blog/AGENTS.md index 9af5138..912334c 100644 --- a/blog/AGENTS.md +++ b/blog/AGENTS.md @@ -41,3 +41,64 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A blog with posts, pages, categories, tags, full-text search, and RSS. Designed for personal writing, technical writing, indie newsletters, and anything where the writing is the product. Editorial-tech aesthetic: confident sans-serif, restrained accent, real article structure with bylines and reading time. + +## Pages + +| Page | Path | What it shows | +| ----------- | ------------------ | ------------------------------------------------------------------------------------------------------ | +| Home | `/` | Featured post hero (large image + excerpt), latest posts grid | +| All posts | `/posts` | Article count, full post list with excerpts and tag chips | +| Post detail | `/posts/[slug]` | Featured image, title, body, left meta column (authors + date), right TOC + search + categories gutter | +| Search | `/search` | Full-text search UI | +| Page | `/pages/[slug]` | Static page content (Portable Text) | +| Category | `/category/[slug]` | Posts filtered by category | +| Tag | `/tag/[slug]` | Posts filtered by tag | +| RSS | `/rss.xml` | Generated feed | + +## Schema + +- `posts` collection: `title`, `featured_image`, `content` (Portable Text), `excerpt` (text). +- `pages` collection: `title`, `content` (Portable Text). Used for `/about` etc. +- Taxonomies: `category`, `tag`. +- Single `primary` menu (Home, About, Posts by default). + +Site settings have `title` and `tagline` -- both render in the header / footer. + +## Visual character + +Single typeface: **Inter** on `--font-sans`, used for everything including headings (with tighter letter-spacing on h1/h2). **JetBrains Mono** on `--font-mono` for inline code and code blocks. Body and headings share the same family; weight and size carry the hierarchy. + +The accent is `#0066cc` -- used for links, the post-card title hover, and the search input focus ring. There's also a secondary text colour (`--color-text-secondary`) and a `--color-muted` for meta info. Don't add a second accent. + +The article layout is the standout feature: a three-column reading view with a left meta column (author bylines, date), centred 680px body column, and a right gutter for search, table of contents, and categories. Don't flatten that into one column on desktop -- the layout signals "this is something to read". + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default -- uncomment and change to override. The dark mode palette is defined inside `Base.astro` itself; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:`. To swap the body face, change the `name:` for the entry bound to `cssVariable: "--font-sans"`. Good alternatives: Geist, IBM Plex Sans, Söhne (if you have a licence), Public Sans. If you want a serif-bodied blog, swap to a humanist serif like Source Serif, Crimson Pro, or Lora -- but then also raise `--font-size-base` to `1.0625rem` for readability. + +CSS variables worth knowing: + +- `--color-accent`, `--color-accent-hover`, `--color-on-accent`, `--color-accent-ring` +- `--color-bg`, `--color-bg-subtle`, `--color-surface`, `--color-text`, `--color-text-secondary`, `--color-muted`, `--color-border`, `--color-border-subtle` +- `--font-sans`, `--font-mono` +- `--tracking-tight` / `--tracking-snug` / `--tracking-wide` / `--tracking-wider` -- letter-spacing tokens used across headings and meta labels +- `--content-width` (680px) -- article body column +- `--wide-width` (1200px) -- max container +- `--gutter-width` (200px) -- right sidebar (TOC) on article pages +- `--meta-col-width` (180px) -- left meta column on article pages +- `--avatar-size-{xs,sm,md,lg}` -- byline avatar sizes at different scales + +## What not to do + +- Don't add a second accent colour or coloured section backgrounds. The page should be black, white, and one blue. +- Don't replace Inter with a display sans (Bebas, Anton, etc.). Headings rely on weight contrast, not novelty faces. +- Don't collapse the article gutter on desktop -- it's part of the reading experience. +- Don't use stock blog copy ("Welcome to my blog", "Stay tuned for more"). Write a real tagline that says what this blog is about. +- Don't seed the home page with three identical placeholder posts. If you only have one real post, show one real post. +- Don't enable comments without a plan to moderate them. The template doesn't ship a comments system by default for a reason. diff --git a/blog/astro.config.mjs b/blog/astro.config.mjs index 6621681..726b23c 100644 --- a/blog/astro.config.mjs +++ b/blog/astro.config.mjs @@ -1,6 +1,6 @@ import node from "@astrojs/node"; import react from "@astrojs/react"; -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; import { defineConfig, fontProviders } from "astro/config"; import emdash, { local } from "emdash/astro"; import { sqlite } from "emdash/db"; @@ -22,7 +22,7 @@ export default defineConfig({ directory: "./uploads", baseUrl: "/_emdash/api/media/file", }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ], fonts: [ diff --git a/blog/package.json b/blog/package.json index 756648c..969795f 100644 --- a/blog/package.json +++ b/blog/package.json @@ -14,16 +14,17 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/node": "^10.0.0", + "@astrojs/node": "^10.1.1", "@astrojs/react": "^5.0.0", - "@emdash-cms/plugin-audit-log": "^0.1.3", - "astro": "^6.0.1", + "@emdash-cms/plugin-audit-log": "^0.2.0", + "astro": "^6.3.0", "better-sqlite3": "^12.8.0", - "emdash": "^0.12.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, "devDependencies": { "@astrojs/check": "^0.9.7" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/marketing-cloudflare/.agents/skills/building-emdash-site/references/configuration.md b/marketing-cloudflare/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/marketing-cloudflare/.agents/skills/building-emdash-site/references/configuration.md +++ b/marketing-cloudflare/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/marketing-cloudflare/AGENTS.md b/marketing-cloudflare/AGENTS.md index 9af5138..cd7d0ec 100644 --- a/marketing-cloudflare/AGENTS.md +++ b/marketing-cloudflare/AGENTS.md @@ -41,3 +41,93 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A SaaS-style landing page template with modular content blocks: hero, features, testimonials, pricing, FAQ, plus a real contact page. Designed for product marketing sites, app landing pages, and anything that needs a hero + features + pricing + CTA flow. + +Bolder than the blog and portfolio templates: vibrant gradient accents, isometric illustration in the hero, heavy headline weights. The voice is product-confident without tipping into stock SaaS cliche. + +## Pages + +| Page | Path | What it shows | +| ------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Home | `/` | Marketing blocks in any order (hero, features, testimonials, pricing, FAQ) authored as a Portable Text document on the Home page | +| Pricing | `/pricing` | Same block-driven editor -- "Simple, transparent pricing" page using the `pricing` block | +| Contact | `/contact` | Left column with contact methods (Email / Support / Sales, each with a gradient icon), right column with a form | + +There is no posts collection. Content is entirely authored as marketing blocks inside `pages`. + +## Schema + +- `pages` collection: `title`, `content` (Portable Text containing marketing blocks). +- No taxonomies. +- Four menus: `primary`, `footer_product`, `footer_company`, `footer_support`. + +Site settings have `title` and `tagline`. Title renders in the header; tagline is used in the footer / metadata. + +## Marketing blocks + +This template ships a local plugin at `src/plugins/marketing-blocks/` that registers five Portable Text block types. Editors insert them in the admin's Portable Text editor; they render via `src/components/blocks/{Hero,Features,Testimonials,Pricing,FAQ}.astro` (dispatched from `MarketingBlocks.astro`). + +| Block | Fields | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | +| `marketing.hero` | `headline`, `subheadline`, `primaryCtaLabel`, `primaryCtaUrl`, `secondaryCtaLabel`, `secondaryCtaUrl`, `centered` (toggle) | +| `marketing.features` | `headline`, `subheadline`, repeater of `{ icon, title, description }` | +| `marketing.testimonials` | `headline`, repeater of `{ quote, author, role, company, avatar (URL) }` | +| `marketing.pricing` | `headline`, repeater of `{ name, price, period, description, features (newline-separated string), ctaLabel, ctaUrl, highlighted }` | +| `marketing.faq` | `headline`, repeater of `{ question, answer }` | + +Constraints worth remembering: + +- Block Kit has no nested object element, so a CTA's `{ label, url }` is flattened to sibling fields like `primaryCtaLabel` + `primaryCtaUrl`. The renderer reads the flat keys -- don't try to nest them. +- Repeater sub-fields are scalar only. Lists-of-strings (e.g. pricing features) are a single multiline text field, split on newline at render time. +- There is no media-picker element in the plugin block modal yet, so where image fields exist they are URL strings entered by hand (testimonial `avatar`). Use real URLs, not placeholders. +- The `marketing.hero` block has no image field in the editor schema. The hero renderer falls back to the bundled `/hero-visual.svg` illustration when no image is set. To customise the hero artwork, swap `/hero-visual.svg` in `public/` or extend the plugin schema with an image field (and update `Hero.astro` accordingly). +- Icons in the Features block come from a fixed set: `zap, shield, users, chart, code, globe, heart, star, check, lock, clock, cloud`. Pick from that list. + +## Visual character + +Typography is **Inter** on `--font-sans` with weights up to 800 for headline emphasis. There is no mono font, no serif. Headline tracking is tight. + +Colour is the loudest of any template here. The default palette is: + +- `--color-primary: #6366f1` (indigo) -- main brand colour, used in buttons and links +- `--color-accent: #f472b6` (pink) -- paired with primary in gradients (CTA buttons, icon backgrounds) +- `--color-success`, `--color-warning` -- semantic colours for inline icons (pricing checkmarks) + +Gradients are part of the look (`--color-primary` -> `--color-accent` on the "Get Started" button, on contact-method icons, on the "Most popular" pricing badge). Don't strip them entirely -- the template will look generic without them. Do swap them for a different pair if the brand calls for it. + +Roundness is generous: `--radius` is 10px, `--radius-lg` 16px, plus a `--radius-full` for pills. Shadows are layered (`--shadow-sm` through `--shadow-xl`). + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default. The dark mode palette is defined inside `Base.astro`; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:`. To swap the typeface, change the `name:` for the entry bound to `cssVariable: "--font-sans"`. Inter has 5 weights loaded (400-800) for hero impact -- if you swap, ensure the replacement has comparable weight range. Geist, Plus Jakarta Sans, Manrope, and DM Sans all work well as replacements. + +CSS variables worth knowing: + +- `--color-primary`, `--color-primary-dark`, `--color-primary-light` +- `--color-accent`, `--color-accent-light` +- `--color-bg`, `--color-surface`, `--color-text`, `--color-muted`, `--color-border` +- `--font-sans` +- `--font-size-{xs,sm,base,lg,xl,2xl,3xl,4xl,5xl,6xl}` -- type scale up to 4.5rem for the largest hero +- `--radius-sm` (6px), `--radius` (10px), `--radius-lg` (16px), `--radius-full` +- `--shadow-sm`, `--shadow`, `--shadow-lg`, `--shadow-xl` + +To re-brand, the highest-leverage moves are: + +1. Change `--color-primary` and `--color-accent` to the brand pair. +2. Update the site title (logo wordmark) and tagline. +3. Replace the hero illustration URL. +4. Edit hero `headline` and `subheadline` blocks to specific, concrete copy. + +## What not to do + +- Don't write stock SaaS copy: "Build products people actually want", "Elevate your workflow", "The all-in-one platform for modern teams". These are placeholder. Write what the product actually does, for whom, with one specific outcome. +- Don't ship more than three pricing tiers. Three is the default for a reason -- more makes choice harder, not easier. +- Don't use icon and stock photo combos that fight each other. Pick illustration _or_ photography, not both. +- Don't enable the gradient on every interactive element. The CTA gradient is the signal; if it's on every button, it stops signalling. +- Don't add a hero block followed immediately by another hero block. One hero, then features / testimonials / pricing / FAQ in some order. +- Don't replace the `marketing.pricing` block with a hand-coded table. The block is the data shape downstream renderers expect. diff --git a/marketing-cloudflare/package.json b/marketing-cloudflare/package.json index 39d84a8..15996f9 100644 --- a/marketing-cloudflare/package.json +++ b/marketing-cloudflare/package.json @@ -14,13 +14,13 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/cloudflare": "^13.1.7", + "@astrojs/cloudflare": "^13.5.3", "@astrojs/react": "^5.0.0", - "@emdash-cms/cloudflare": "^0.12.0", + "@emdash-cms/cloudflare": "^0.14.0", "@iconify-json/ph": "^1.2.2", - "astro": "^6.0.1", + "astro": "^6.3.0", "astro-iconset": "^0.0.4", - "emdash": "^0.12.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, @@ -28,5 +28,6 @@ "@astrojs/check": "^0.9.7", "@cloudflare/workers-types": "^4.20260305.1", "wrangler": "^4.83.0" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/marketing/.agents/skills/building-emdash-site/references/configuration.md b/marketing/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/marketing/.agents/skills/building-emdash-site/references/configuration.md +++ b/marketing/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/marketing/AGENTS.md b/marketing/AGENTS.md index 9af5138..cd7d0ec 100644 --- a/marketing/AGENTS.md +++ b/marketing/AGENTS.md @@ -41,3 +41,93 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A SaaS-style landing page template with modular content blocks: hero, features, testimonials, pricing, FAQ, plus a real contact page. Designed for product marketing sites, app landing pages, and anything that needs a hero + features + pricing + CTA flow. + +Bolder than the blog and portfolio templates: vibrant gradient accents, isometric illustration in the hero, heavy headline weights. The voice is product-confident without tipping into stock SaaS cliche. + +## Pages + +| Page | Path | What it shows | +| ------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------- | +| Home | `/` | Marketing blocks in any order (hero, features, testimonials, pricing, FAQ) authored as a Portable Text document on the Home page | +| Pricing | `/pricing` | Same block-driven editor -- "Simple, transparent pricing" page using the `pricing` block | +| Contact | `/contact` | Left column with contact methods (Email / Support / Sales, each with a gradient icon), right column with a form | + +There is no posts collection. Content is entirely authored as marketing blocks inside `pages`. + +## Schema + +- `pages` collection: `title`, `content` (Portable Text containing marketing blocks). +- No taxonomies. +- Four menus: `primary`, `footer_product`, `footer_company`, `footer_support`. + +Site settings have `title` and `tagline`. Title renders in the header; tagline is used in the footer / metadata. + +## Marketing blocks + +This template ships a local plugin at `src/plugins/marketing-blocks/` that registers five Portable Text block types. Editors insert them in the admin's Portable Text editor; they render via `src/components/blocks/{Hero,Features,Testimonials,Pricing,FAQ}.astro` (dispatched from `MarketingBlocks.astro`). + +| Block | Fields | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | +| `marketing.hero` | `headline`, `subheadline`, `primaryCtaLabel`, `primaryCtaUrl`, `secondaryCtaLabel`, `secondaryCtaUrl`, `centered` (toggle) | +| `marketing.features` | `headline`, `subheadline`, repeater of `{ icon, title, description }` | +| `marketing.testimonials` | `headline`, repeater of `{ quote, author, role, company, avatar (URL) }` | +| `marketing.pricing` | `headline`, repeater of `{ name, price, period, description, features (newline-separated string), ctaLabel, ctaUrl, highlighted }` | +| `marketing.faq` | `headline`, repeater of `{ question, answer }` | + +Constraints worth remembering: + +- Block Kit has no nested object element, so a CTA's `{ label, url }` is flattened to sibling fields like `primaryCtaLabel` + `primaryCtaUrl`. The renderer reads the flat keys -- don't try to nest them. +- Repeater sub-fields are scalar only. Lists-of-strings (e.g. pricing features) are a single multiline text field, split on newline at render time. +- There is no media-picker element in the plugin block modal yet, so where image fields exist they are URL strings entered by hand (testimonial `avatar`). Use real URLs, not placeholders. +- The `marketing.hero` block has no image field in the editor schema. The hero renderer falls back to the bundled `/hero-visual.svg` illustration when no image is set. To customise the hero artwork, swap `/hero-visual.svg` in `public/` or extend the plugin schema with an image field (and update `Hero.astro` accordingly). +- Icons in the Features block come from a fixed set: `zap, shield, users, chart, code, globe, heart, star, check, lock, clock, cloud`. Pick from that list. + +## Visual character + +Typography is **Inter** on `--font-sans` with weights up to 800 for headline emphasis. There is no mono font, no serif. Headline tracking is tight. + +Colour is the loudest of any template here. The default palette is: + +- `--color-primary: #6366f1` (indigo) -- main brand colour, used in buttons and links +- `--color-accent: #f472b6` (pink) -- paired with primary in gradients (CTA buttons, icon backgrounds) +- `--color-success`, `--color-warning` -- semantic colours for inline icons (pricing checkmarks) + +Gradients are part of the look (`--color-primary` -> `--color-accent` on the "Get Started" button, on contact-method icons, on the "Most popular" pricing badge). Don't strip them entirely -- the template will look generic without them. Do swap them for a different pair if the brand calls for it. + +Roundness is generous: `--radius` is 10px, `--radius-lg` 16px, plus a `--radius-full` for pills. Shadows are layered (`--shadow-sm` through `--shadow-xl`). + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default. The dark mode palette is defined inside `Base.astro`; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:`. To swap the typeface, change the `name:` for the entry bound to `cssVariable: "--font-sans"`. Inter has 5 weights loaded (400-800) for hero impact -- if you swap, ensure the replacement has comparable weight range. Geist, Plus Jakarta Sans, Manrope, and DM Sans all work well as replacements. + +CSS variables worth knowing: + +- `--color-primary`, `--color-primary-dark`, `--color-primary-light` +- `--color-accent`, `--color-accent-light` +- `--color-bg`, `--color-surface`, `--color-text`, `--color-muted`, `--color-border` +- `--font-sans` +- `--font-size-{xs,sm,base,lg,xl,2xl,3xl,4xl,5xl,6xl}` -- type scale up to 4.5rem for the largest hero +- `--radius-sm` (6px), `--radius` (10px), `--radius-lg` (16px), `--radius-full` +- `--shadow-sm`, `--shadow`, `--shadow-lg`, `--shadow-xl` + +To re-brand, the highest-leverage moves are: + +1. Change `--color-primary` and `--color-accent` to the brand pair. +2. Update the site title (logo wordmark) and tagline. +3. Replace the hero illustration URL. +4. Edit hero `headline` and `subheadline` blocks to specific, concrete copy. + +## What not to do + +- Don't write stock SaaS copy: "Build products people actually want", "Elevate your workflow", "The all-in-one platform for modern teams". These are placeholder. Write what the product actually does, for whom, with one specific outcome. +- Don't ship more than three pricing tiers. Three is the default for a reason -- more makes choice harder, not easier. +- Don't use icon and stock photo combos that fight each other. Pick illustration _or_ photography, not both. +- Don't enable the gradient on every interactive element. The CTA gradient is the signal; if it's on every button, it stops signalling. +- Don't add a hero block followed immediately by another hero block. One hero, then features / testimonials / pricing / FAQ in some order. +- Don't replace the `marketing.pricing` block with a hand-coded table. The block is the data shape downstream renderers expect. diff --git a/marketing/package.json b/marketing/package.json index 703c632..765c6cf 100644 --- a/marketing/package.json +++ b/marketing/package.json @@ -14,17 +14,18 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/node": "^10.0.0", + "@astrojs/node": "^10.1.1", "@astrojs/react": "^5.0.0", "@iconify-json/ph": "^1.2.2", - "astro": "^6.0.1", + "astro": "^6.3.0", "astro-iconset": "^0.0.4", "better-sqlite3": "^12.8.0", - "emdash": "^0.12.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, "devDependencies": { "@astrojs/check": "^0.9.7" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/portfolio-cloudflare/.agents/skills/building-emdash-site/references/configuration.md b/portfolio-cloudflare/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/portfolio-cloudflare/.agents/skills/building-emdash-site/references/configuration.md +++ b/portfolio-cloudflare/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/portfolio-cloudflare/AGENTS.md b/portfolio-cloudflare/AGENTS.md index 9af5138..dca9d70 100644 --- a/portfolio-cloudflare/AGENTS.md +++ b/portfolio-cloudflare/AGENTS.md @@ -41,3 +41,61 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A portfolio for showcasing creative work. Editorial, near-monochrome, with photography as the main visual interest. Designed for designers, photographers, illustrators, studios, and other people whose work speaks for itself when laid out with generous whitespace. + +The design is intentionally restrained. Don't pile on colour, gradients, or decoration -- the work is the decoration. + +## Pages + +| Page | Path | What it shows | +| -------------- | -------------- | ------------------------------------------------------------------------------------------------------ | +| Home | `/` | Centred serif title + tagline, "Selected Work" grid | +| Work index | `/work` | Heading + summary, tag filter chips, full grid | +| Project detail | `/work/[slug]` | Project meta line, big serif title, summary, featured image, Portable Text body, optional gallery, URL | +| About | `/about` | Page content (Portable Text) | +| Contact | `/contact` | Form + email / location / social column | + +## Schema + +- `projects` collection: `title`, `featured_image`, `client`, `year`, `summary` (text), `content` (Portable Text), `gallery` (json -- optional array of `{ url, alt? }` records, see below), `url`. +- `pages` collection: `title`, `content` (Portable Text). Used for `/about`. +- Taxonomies: `category`, `tag`. Used for filtering on the work index. +- Single `primary` menu. + +Site settings have `title` and `tagline` -- both render on the home page (title as the centred serif heading, tagline as italic subtitle). + +The `gallery` field on `projects` is a JSON field, not an EmDash image field. It expects a literal array of `{ url: string, alt?: string }` records (a flat external URL plus optional alt text), and is rendered as-is by `src/pages/work/[slug].astro`. Do NOT confuse it with EmDash image fields like `featured_image`, which take `{ id, provider, alt }` objects from the media library. If you need media-library images in a gallery in the future, the right fix is to change the field type and renderer together. + +## Visual character + +Typography is the design. The display face is **Playfair Display** (serif) on the `--font-serif` CSS variable; the body face is the system sans stack on `--font-sans`. The serif is used for the site title, hero titles, project titles, page titles, and contact column labels. Everything else is the sans. + +The accent colour is barely visible by design -- the only saturated colour on the page should be inside images. The default `--color-accent` (`#7c3aed`) is used sparingly for link hover and focus states. + +Whitespace is generous. Sections breathe. Don't fight that. + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default -- uncomment and change to override. The dark mode palette is defined inside `Base.astro` itself; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:` (the Astro Fonts API). To change the display face, swap the `name:` for any Google Fonts serif and keep `cssVariable: "--font-serif"`. Good pairings: Cormorant Garamond, Fraunces, EB Garamond, DM Serif Display. Avoid changing the body font unless you have a reason -- system sans is deliberately quiet here. + +CSS variables worth knowing: + +- `--color-accent` / `--color-accent-muted` -- the single accent, used very sparingly +- `--color-bg`, `--color-surface`, `--color-text`, `--color-muted`, `--color-border` -- neutral palette +- `--font-serif`, `--font-sans` -- bound to the Fonts API entries in `astro.config.mjs` +- `--font-size-4xl` -- the size of the homepage title and project titles +- `--max-width` (720px), `--wide-width` (1200px) -- column widths + +## What not to do + +- Don't introduce gradients, drop shadows on cards, or coloured section backgrounds. The template's voice is calm and editorial; those break it. +- Don't change `--font-sans` to a display font. Two display faces fight each other. +- Don't add more than one accent colour. +- Don't write generic copy like "Welcome to my portfolio" or "Crafting beautiful experiences". The work should speak; the words should be specific (a client name, a discipline, a year). +- Don't pack the home page with every project. The "Selected Work" framing is intentional -- 3-6 is plenty. +- Don't add a `gallery` of small thumbnails on the home page. Use one strong image per project; the gallery field renders on the project detail page only. diff --git a/portfolio-cloudflare/package.json b/portfolio-cloudflare/package.json index 5a2f56a..503c348 100644 --- a/portfolio-cloudflare/package.json +++ b/portfolio-cloudflare/package.json @@ -14,11 +14,11 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/cloudflare": "^13.1.7", + "@astrojs/cloudflare": "^13.5.3", "@astrojs/react": "^5.0.0", - "@emdash-cms/cloudflare": "^0.12.0", - "astro": "^6.0.1", - "emdash": "^0.12.0", + "@emdash-cms/cloudflare": "^0.14.0", + "astro": "^6.3.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, @@ -26,5 +26,6 @@ "@astrojs/check": "^0.9.7", "@cloudflare/workers-types": "^4.20260305.1", "wrangler": "^4.83.0" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/portfolio/.agents/skills/building-emdash-site/references/configuration.md b/portfolio/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/portfolio/.agents/skills/building-emdash-site/references/configuration.md +++ b/portfolio/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/portfolio/AGENTS.md b/portfolio/AGENTS.md index 9af5138..dca9d70 100644 --- a/portfolio/AGENTS.md +++ b/portfolio/AGENTS.md @@ -41,3 +41,61 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A portfolio for showcasing creative work. Editorial, near-monochrome, with photography as the main visual interest. Designed for designers, photographers, illustrators, studios, and other people whose work speaks for itself when laid out with generous whitespace. + +The design is intentionally restrained. Don't pile on colour, gradients, or decoration -- the work is the decoration. + +## Pages + +| Page | Path | What it shows | +| -------------- | -------------- | ------------------------------------------------------------------------------------------------------ | +| Home | `/` | Centred serif title + tagline, "Selected Work" grid | +| Work index | `/work` | Heading + summary, tag filter chips, full grid | +| Project detail | `/work/[slug]` | Project meta line, big serif title, summary, featured image, Portable Text body, optional gallery, URL | +| About | `/about` | Page content (Portable Text) | +| Contact | `/contact` | Form + email / location / social column | + +## Schema + +- `projects` collection: `title`, `featured_image`, `client`, `year`, `summary` (text), `content` (Portable Text), `gallery` (json -- optional array of `{ url, alt? }` records, see below), `url`. +- `pages` collection: `title`, `content` (Portable Text). Used for `/about`. +- Taxonomies: `category`, `tag`. Used for filtering on the work index. +- Single `primary` menu. + +Site settings have `title` and `tagline` -- both render on the home page (title as the centred serif heading, tagline as italic subtitle). + +The `gallery` field on `projects` is a JSON field, not an EmDash image field. It expects a literal array of `{ url: string, alt?: string }` records (a flat external URL plus optional alt text), and is rendered as-is by `src/pages/work/[slug].astro`. Do NOT confuse it with EmDash image fields like `featured_image`, which take `{ id, provider, alt }` objects from the media library. If you need media-library images in a gallery in the future, the right fix is to change the field type and renderer together. + +## Visual character + +Typography is the design. The display face is **Playfair Display** (serif) on the `--font-serif` CSS variable; the body face is the system sans stack on `--font-sans`. The serif is used for the site title, hero titles, project titles, page titles, and contact column labels. Everything else is the sans. + +The accent colour is barely visible by design -- the only saturated colour on the page should be inside images. The default `--color-accent` (`#7c3aed`) is used sparingly for link hover and focus states. + +Whitespace is generous. Sections breathe. Don't fight that. + +## Customisation + +`src/styles/theme.css` is the only file to edit for visual changes. Every CSS variable from `Base.astro` is listed there as a commented default -- uncomment and change to override. The dark mode palette is defined inside `Base.astro` itself; light-mode overrides in `theme.css` won't affect dark mode. To customise dark mode, add `@media (prefers-color-scheme: dark)` and `:root.dark` rules in `theme.css`. + +Fonts are configured in `astro.config.mjs` under `fonts:` (the Astro Fonts API). To change the display face, swap the `name:` for any Google Fonts serif and keep `cssVariable: "--font-serif"`. Good pairings: Cormorant Garamond, Fraunces, EB Garamond, DM Serif Display. Avoid changing the body font unless you have a reason -- system sans is deliberately quiet here. + +CSS variables worth knowing: + +- `--color-accent` / `--color-accent-muted` -- the single accent, used very sparingly +- `--color-bg`, `--color-surface`, `--color-text`, `--color-muted`, `--color-border` -- neutral palette +- `--font-serif`, `--font-sans` -- bound to the Fonts API entries in `astro.config.mjs` +- `--font-size-4xl` -- the size of the homepage title and project titles +- `--max-width` (720px), `--wide-width` (1200px) -- column widths + +## What not to do + +- Don't introduce gradients, drop shadows on cards, or coloured section backgrounds. The template's voice is calm and editorial; those break it. +- Don't change `--font-sans` to a display font. Two display faces fight each other. +- Don't add more than one accent colour. +- Don't write generic copy like "Welcome to my portfolio" or "Crafting beautiful experiences". The work should speak; the words should be specific (a client name, a discipline, a year). +- Don't pack the home page with every project. The "Selected Work" framing is intentional -- 3-6 is plenty. +- Don't add a `gallery` of small thumbnails on the home page. Use one strong image per project; the gallery field renders on the project detail page only. diff --git a/portfolio/package.json b/portfolio/package.json index a17f0f2..cd5f1e7 100644 --- a/portfolio/package.json +++ b/portfolio/package.json @@ -14,15 +14,16 @@ "typecheck": "astro check" }, "dependencies": { - "@astrojs/node": "^10.0.0", + "@astrojs/node": "^10.1.1", "@astrojs/react": "^5.0.0", - "astro": "^6.0.1", + "astro": "^6.3.0", "better-sqlite3": "^12.8.0", - "emdash": "^0.12.0", + "emdash": "^0.14.0", "react": "19.2.4", "react-dom": "19.2.4" }, "devDependencies": { "@astrojs/check": "^0.9.7" - } + }, + "packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d" } diff --git a/starter-cloudflare/.agents/skills/building-emdash-site/references/configuration.md b/starter-cloudflare/.agents/skills/building-emdash-site/references/configuration.md index 6f4685c..a1eb1ec 100644 --- a/starter-cloudflare/.agents/skills/building-emdash-site/references/configuration.md +++ b/starter-cloudflare/.agents/skills/building-emdash-site/references/configuration.md @@ -113,12 +113,12 @@ Requires a `wrangler.jsonc` with D1 and R2 bindings: Register plugins in `astro.config.mjs`: ```javascript -import { auditLogPlugin } from "@emdash-cms/plugin-audit-log"; +import auditLog from "@emdash-cms/plugin-audit-log"; emdash({ database: sqlite({ url: "file:./data.db" }), storage: local({ directory: "./uploads", baseUrl: "/_emdash/api/media/file" }), - plugins: [auditLogPlugin()], + plugins: [auditLog], }), ``` diff --git a/starter-cloudflare/AGENTS.md b/starter-cloudflare/AGENTS.md index 9af5138..e97ff84 100644 --- a/starter-cloudflare/AGENTS.md +++ b/starter-cloudflare/AGENTS.md @@ -41,3 +41,56 @@ This template ships with `.mcp.json`, `.cursor/mcp.json`, and `.vscode/mcp.json` - `entry.id` is the slug (for URLs). `entry.data.id` is the database ULID (for API calls like `getEntryTerms`). - Always call `Astro.cache.set(cacheHint)` on pages that query content. - Taxonomy names in queries must match the seed's `"name"` field exactly (e.g., `"category"` not `"categories"`). + +## This Template + +A general-purpose starting point with posts, pages, categories, and tags. Less opinionated than the themed templates -- a base for sites that want to define their own design. + +There is intentionally no `theme.css`, no custom font configuration, no styled layouts beyond browser defaults. The home, posts index, post detail, page, category, and tag pages all render with minimal styling. Start here if you want full control over the visual language; start with `blog`, `portfolio`, or `marketing` if you want a designed template to customise. + +## Pages + +| Page | Path | What it shows | +| ----------- | ------------------ | ---------------------------------------------- | +| Home | `/` | Site title + tagline, links into Posts / About | +| All posts | `/posts` | Post list | +| Post detail | `/posts/[slug]` | Post content | +| Page | `/[slug]` | Static page content (e.g. `/about`) | +| Category | `/category/[slug]` | Posts filtered by category | +| Tag | `/tag/[slug]` | Posts filtered by tag | + +## Schema + +- `posts` collection: `title`, `featured_image`, `content` (Portable Text), `excerpt` (text). +- `pages` collection: `title`, `content` (Portable Text). +- Taxonomies: `category`, `tag`. +- Single `primary` menu. + +Site settings have `title` and `tagline`. + +## Visual character + +None imposed. Define your own. + +This template ships without: + +- `src/styles/theme.css` -- create one and import it from `Base.astro` if you want CSS-variable theming. +- Fonts in `astro.config.mjs` -- the `fonts:` array is empty. Add Google Fonts entries with `cssVariable` bindings if you want web fonts. +- A `components/` directory with styled cards / tag lists / etc. -- build them as needed. + +## What to do here + +If you're customising this template, the work is to add design, not to subtract it. Reasonable first moves: + +1. Decide on one display + one body typeface, add them to `astro.config.mjs`, bind them to `--font-display` and `--font-body` CSS variables. +2. Create `src/styles/theme.css` with your colour palette, type scale, and spacing tokens. +3. Add it to `Base.astro` -- the layout already imports a small reset; add your theme above your page styles. +4. Build page-specific styles in each Astro page's `