337 lines
44 KiB
Markdown
337 lines
44 KiB
Markdown
---
|
||
phase: 11.2-ready-to-share-summercms-io-website-and-newsletter-plugin
|
||
plan: 01
|
||
type: execute
|
||
wave: 1
|
||
depends_on: []
|
||
files_modified:
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/package.json
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/pnpm-lock.yaml
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/pnpm-workspace.yaml
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/.nvmrc
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/.gitignore
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/README.md
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/tsconfig.json
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/app.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/FeaturesSection.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/WinterSection.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/StartSection.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteFooter.vue
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/tokens.css
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/public/sun-logo.webp
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/public/sun-badge.webp
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/public/favicon.ico
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/public/apple-touch-icon.png
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts
|
||
autonomous: true
|
||
requirements: []
|
||
assumption_delta_decision: no-change
|
||
specless_probe_fallback: "skipped: phase has no requirement IDs to probe (visible skip); ROADMAP SC1-SC5 and the D-IDs are the acceptance contract"
|
||
user_setup: []
|
||
|
||
estimate:
|
||
tokens: 120000
|
||
raw_tokens: 120000
|
||
tasks: 3
|
||
confidence: low
|
||
|
||
must_haves:
|
||
truths:
|
||
- "Per D-04 and SC1, `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate` exits 0 and writes `.output/public/index.html` as real prerendered HTML holding, in order, the sticky header, the hero `#top`, `#why`, `#features`, `#winter`, `#start` and the footer."
|
||
- "Per D-23, colors, type, spacing, breakpoints, copy and the two interactions match design/README.md and `SummerCMS Landing.dc.html`; the only copy deviations are the D-12 credit, the D-39 clone step and the D-26 `PostgreSQL 15+` chip."
|
||
- "Per D-33, every visible string on the page comes from `i18n/locales/en.json` through @nuxtjs/i18n with the single locale `en` served at `/` (strategy `prefix_except_default`), so Phase 11.3 adds `pl.json` without touching component markup."
|
||
- "Per D-34, Roboto 300/400/500/700 and Roboto Mono 400/500 are self-hosted by @nuxt/fonts under `/_fonts/` and the generated HTML and CSS reference no Google font host."
|
||
- "Per D-35, styles are plain CSS: the handoff tokens are custom properties on `:root` in `tokens.css`, used by scoped component styles; no CSS framework package is installed."
|
||
- "Per D-36, every sun image (header logo, hero badge, favicons, OG image) is derived from the original `logo.png` by a committed ImageMagick script, and the handoff `sun-crop.png` placeholder is not shipped."
|
||
- "Per D-37, @nuxtjs/seo emits title, description, canonical, Open Graph, Twitter and schema.org tags, a flat `sitemap.xml` and `robots.txt` at generate time; `og:image` is the absolute `https://summercms.io/og-image.png`; nothing is rendered at runtime."
|
||
- "Per D-45, `app.config.ts` has `landing.showRays` and `landing.scrollSpy`, both `true`; setting either to false removes the hero rays layer or the scroll-spy listener."
|
||
- "Per D-39 and D-40, the terminal card shows three groups and six commands read from the single source `app/data/terminal.json`, and Copy writes exactly the six command lines joined by newlines (no `$`, no comments), then shows `Copied` for 1500 ms."
|
||
- "Per D-47 and D-38, the eight feature tiles, the concept-map link and the Installation guide keep the handoff's extension-less `/docs/...` hrefs, the Docs links point to `/docs`, and both Source links point to `https://git.golem15.com/golem15/summercms`."
|
||
- "Per D-09, the From WinterCMS section carries the handoff copy and concept table unchanged, with no added seasons copy; per D-12, the footer has a `Built by Golem15` link to `https://golem15.com` styled as a footer link."
|
||
- "Per SC1, at viewport widths of 720px or less the header section links are hidden by a CSS media query (rendered in SSR, no width-dependent markup), and every grid collapses to one column on phone widths."
|
||
- "Scroll-spy marks the last of why, features, winter and start whose top is less than 140px from the viewport top; above `#why` no link is active."
|
||
- statement: "Visual fidelity at 1280, 721, 720 and 375px, the sticky header, and the Copy label change are confirmed against the handoff in the end-of-phase UAT."
|
||
verification: backstop
|
||
artifacts:
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts"
|
||
provides: "Nuxt 4 SSG config: fonts, i18n, seo, prerender ignore for /docs"
|
||
contains: "prerender"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts"
|
||
provides: "D-45 landing flags"
|
||
contains: "scrollSpy"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json"
|
||
provides: "all page copy (D-33)"
|
||
contains: "A new dawn in content management"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
|
||
provides: "single source of the six terminal commands (D-40)"
|
||
contains: "./bin/hello greeter:hello"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
|
||
provides: "terminal card with Copy"
|
||
contains: "copyPayload"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts"
|
||
provides: "pure scroll-spy selection"
|
||
contains: "export function activeSection"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts"
|
||
provides: "pure copy payload builder"
|
||
contains: "export function copyPayload"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts"
|
||
provides: "SC1 assertions on the generated output"
|
||
contains: "index.html"
|
||
- path: "../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh"
|
||
provides: "D-36 image derivation from the original sun"
|
||
contains: "magick"
|
||
key_links:
|
||
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
|
||
to: "../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json"
|
||
via: "static JSON import; the card renders and copies only these commands"
|
||
pattern: "data/terminal\\.json"
|
||
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue"
|
||
to: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts"
|
||
via: "copyPayload(groups) builds the clipboard text"
|
||
pattern: "copyPayload\\("
|
||
- from: "../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue"
|
||
to: "../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts"
|
||
via: "scroll listener calls activeSection with section tops"
|
||
pattern: "activeSection\\("
|
||
- from: "../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts"
|
||
to: "/docs links on the page"
|
||
via: "nitro.prerender.ignore keeps generate from crawling /docs, which the Go binary serves"
|
||
pattern: "ignore:.*'/docs'"
|
||
---
|
||
|
||
<objective>
|
||
Build `vue-summercmsio-app`, the Nuxt 4 static site for summercms.io, matching the claude.ai/design handoff exactly (D-23) with the decided deviations (D-12, D-26, D-39). `nuxt generate` produces real prerendered HTML (D-04) with self-hosted fonts (D-34), plain-CSS tokens (D-35), the original sun artwork (D-36), static SEO output (D-37), a single `en` locale ready for Phase 11.3 (D-33), the app-config flags (D-45), and a terminal card fed from one command list (D-39, D-40). Docs links stay extension-less (D-47). The superseded decisions apply in their superseded form: D-06 English only (Polish is Phase 11.3, via D-33), D-08 the handoff's section list, D-10 the "Alpha 0.1 is out" tone, and D-11 Source links on the page (D-38).
|
||
|
||
Purpose: SC1. The Go binary in plan 11.2-02 embeds this output, so the generate output contract here (file layout, `_nuxt/`, `_fonts/`, `_i18n/`, flat `sitemap.xml`) is what the site handler serves.
|
||
|
||
Output: a new git repository at `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app` (it becomes a submodule of `sm-summercmsio-app` in plan 11.2-02), with node:test assertions over the generated output and the two pure utilities.
|
||
</objective>
|
||
|
||
<execution_context>
|
||
@~/.claude/gsd-core/workflows/execute-plan.md
|
||
@~/.claude/gsd-core/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/ROADMAP.md
|
||
@.planning/STATE.md
|
||
@CLAUDE.md
|
||
@.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md
|
||
@.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md
|
||
@.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-PATTERNS.md
|
||
@.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md
|
||
@.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html
|
||
@/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts
|
||
@/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json
|
||
@/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/pnpm-workspace.yaml
|
||
|
||
**Paths.** Commands run from the summercms.go root. `SITE` = `../sm-summercmsio-app/vue-summercmsio-app` (absolute: `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app`). Every file in this plan is written to the SITE repository, which this plan creates. Nothing in summercms.go changes in this plan. Do not run `git add` in the meta repository `summercms/` or in summercms.go; `sm-summercmsio-app/` stays a plain directory until plan 11.2-02 makes it a repository.
|
||
|
||
**Commits.** Commit inside SITE only, one logical change per commit, conventional messages such as `feat(site): ...`, and never a co-author tag.
|
||
|
||
**Design source of truth.** `design/SummerCMS Landing.dc.html` is authoritative for punctuation and details not in design/README.md (RESEARCH "Details found in SummerCMS Landing.dc.html"): typographic apostrophes in "each plugin’s YAML" and "the caller’s transaction", the straight apostrophe in "WHAT'S IN THE BOX", gaps, paddings and the terminal layout. Ignore its `support.js` runtime and its JS width state.
|
||
|
||
<interfaces>
|
||
Pure utilities consumed by components and by node:test (use only erasable TypeScript syntax so Node 22 runs the files without a build: no enums, no namespaces, no parameter properties; type-only imports use `import type`):
|
||
|
||
- `app/utils/terminal.ts`: `export interface TerminalGroup { comment: string; commands: string[] }` and `export function copyPayload(groups: readonly TerminalGroup[]): string` returning every command in order joined by `\n`, with no trailing newline, no `$` and no comment text.
|
||
- `app/utils/scrollSpy.ts`: `export const SPY_SECTIONS` = `['why', 'features', 'winter', 'start']`, `export const SPY_THRESHOLD = 140`, and `export function activeSection(tops: ReadonlyArray<{ id: string; top: number }>, threshold?: number): string` returning the id of the last entry whose `top` is strictly less than the threshold, or `''` when none is.
|
||
- `app/data/terminal.json`: `{ "groups": [ { "comment": "<i18n key>", "commands": [ ... ] } ] }` with exactly these groups in order: `terminal.getFramework` → `git clone https://git.golem15.com/golem15/summercms`, `cd summercms`; `terminal.installCli` → `go install ./cmd/summer`; `terminal.buildExample` → `cd examples/hello`, `summer build`, `./bin/hello greeter:hello`. Plan 11.2-02 reads this file from Go (D-40 check and the HTML drift guard), so the key names and shape are a contract.
|
||
- `app/app.config.ts`: `defineAppConfig({ landing: { showRays: true, scrollSpy: true } })`.
|
||
</interfaces>
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="tracer">
|
||
<name>Task 1: Tracer: `nuxt generate` turns en.json into a prerendered page with the sticky header and hero, self-hosted fonts, the real sun and static SEO output</name>
|
||
<precondition>`node --version` prints v22.18 or newer, `pnpm --version` prints 11.x, and `magick -version` succeeds with WEBP and ICO delegates (RESEARCH Environment Availability).</precondition>
|
||
<files>../sm-summercmsio-app/vue-summercmsio-app/package.json, ../sm-summercmsio-app/vue-summercmsio-app/pnpm-lock.yaml, ../sm-summercmsio-app/vue-summercmsio-app/pnpm-workspace.yaml, ../sm-summercmsio-app/vue-summercmsio-app/.nvmrc, ../sm-summercmsio-app/vue-summercmsio-app/.gitignore, ../sm-summercmsio-app/vue-summercmsio-app/README.md, ../sm-summercmsio-app/vue-summercmsio-app/tsconfig.json, ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/app.config.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/app.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/tokens.css, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png, ../sm-summercmsio-app/vue-summercmsio-app/scripts/derive-images.sh, ../sm-summercmsio-app/vue-summercmsio-app/public/sun-logo.webp, ../sm-summercmsio-app/vue-summercmsio-app/public/sun-badge.webp, ../sm-summercmsio-app/vue-summercmsio-app/public/favicon.ico, ../sm-summercmsio-app/vue-summercmsio-app/public/apple-touch-icon.png, ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts</files>
|
||
<read_first>
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md (Global, 1. Header, 2. Hero, Design Tokens, Assets)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (header and hero markup and inline styles)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md sections "Standard Stack", "Package Legitimacy Audit", "Nuxt 4 (question 4)", "Pattern 1", "Design Handoff Inventory", Pitfalls 1-4 and 6, Open Questions 4-5
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-PATTERNS.md section "SITE/nuxt.config.ts, package.json, pnpm-workspace.yaml, i18n/locales/en.json"
|
||
- /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/nuxt.config.ts (fonts, i18n and site blocks; copy shapes only)
|
||
- /media/nvme/dev/golem15/fonoteka/vue-fonoteka-app/package.json and pnpm-workspace.yaml (scripts, engines, allowBuilds)
|
||
</read_first>
|
||
<action>
|
||
Create the repository and the thinnest production-quality slice through every layer the site has: package config, Nuxt modules, i18n copy, tokens, components, static assets and the generate output, checked by a test over the output.
|
||
|
||
1. Repository (per D-01 and D-43): `mkdir -p /media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app`, then `git init -b master` inside `vue-summercmsio-app` only. Add `.gitignore` (`node_modules/`, `.nuxt/`, `.output/`, `.data/`, `dist`, `.env`), `.nvmrc` with `22.22.0`, and a short `README.md` (what the site is, `pnpm install --frozen-lockfile`, `pnpm generate`, `pnpm test`, `scripts/derive-images.sh`, and that the root app embeds `.output/public`).
|
||
2. Packages (T-11.2-SC): `package.json` with `"name": "vue-summercmsio-app"`, `"private": true`, `"type": "module"`, `"engines": { "node": ">=22.6" }`, scripts `build` (`nuxt build`), `dev` (`nuxt dev`), `generate` (`nuxt generate`), `preview` (`nuxt preview`), `prepare` (`nuxt prepare`), `test` (`node --test tests/*.test.ts`), and no install-time lifecycle script. Install with exact pins via `pnpm add --save-exact nuxt@4.4.8 @nuxt/fonts@0.14.0 @nuxtjs/i18n@10.4.0 @nuxtjs/seo@5.2.1 vue@3.5.35` and `pnpm add -D --save-exact typescript@5.9.3`. These are the versions running in vue-fonoteka-app and approved in RESEARCH "Package Legitimacy Audit". If pnpm cannot resolve any of these exact versions, stop and return a `checkpoint:human-verify` with `gate="blocking-human"` naming the package and version instead of installing another version. Copy fonoteka's `pnpm-workspace.yaml` `allowBuilds` entries for `@parcel/watcher`, `esbuild` and `vue-demi` only (no protobufjs, no sharp, no patchedDependencies). Commit `pnpm-lock.yaml`. Add `tsconfig.json` extending `./.nuxt/tsconfig.json` as fonoteka does.
|
||
3. `nuxt.config.ts` (the spike-verified config in RESEARCH Pattern 1, per D-04, D-33, D-34, D-37): `compatibilityDate: '2025-07-15'`, `devtools: { enabled: false }`, `modules: ['@nuxt/fonts', '@nuxtjs/i18n', '@nuxtjs/seo']`, `css` loading `~/assets/css/tokens.css` then `~/assets/css/base.css`; `fonts.families` Roboto weights 300/400/500/700 and Roboto Mono weights 400/500, each with `styles: ['normal']`, `subsets: ['latin', 'latin-ext']`, `global: true` (Roboto 900 from the prototype is not loaded, per D-34); `i18n` with `strategy: 'prefix_except_default'`, `defaultLocale: 'en'`, one locale `{ code: 'en', language: 'en-US', file: 'en.json', name: 'English' }`, `langDir: 'locales/'`, `baseUrl: 'https://summercms.io'`, `experimental: { prerenderMessages: true }`; `site: { url: 'https://summercms.io', name: 'SummerCMS', description: <the hero description>, defaultLocale: 'en' }`; `ogImage: { enabled: false }`; `sitemap: { autoI18n: false }` (Pitfall 2: one flat file); `linkChecker: { excludeLinks: ['/docs', '/docs/**'] }`; `nitro: { prerender: { ignore: ['/docs'] } }` (Pitfall 1: generate otherwise exits 1 on /docs links); `app.head.link` with `/favicon.ico`, `/apple-touch-icon.png` (sizes 180x180).
|
||
4. `app/app.config.ts` per D-45 (both flags `true`). `app/app.vue` renders `<NuxtPage />`. `app/pages/index.vue` renders `SiteHeader` and `HeroSection` inside a wrapper whose horizontal overflow is clipped with `overflow-x: clip`, never `hidden` (Pitfall 3: a hidden-overflow ancestor breaks the sticky header), and sets the page head with `useSeoMeta`: title `SummerCMS: A new dawn in content management` with a `titleTemplate` that does not append the site name again (spike finding 4), `description` = the hero description, `ogImage` `/og-image.png` with width 1200, height 630 and alt text, `twitterCard: 'summary_large_image'`. Title, description and alt live in `en.json` under `meta.*`.
|
||
5. `i18n/locales/en.json` (D-33): nested keys for `meta`, `header` (logo aria label, wordmark parts "Summer" and "CMS", pill `ALPHA 0.1`, nav labels Why, Features, From WinterCMS, Get started, `Docs →`), `hero` (tagline "A new dawn in content management", description "A content management framework for Go, inspired by WinterCMS. Plugins, the admin SPA and console commands compile into a single binary.", CTA labels "Read the docs" and "Alpha 0.1 is out"). Copy every string verbatim from the handoff. Use no vue-i18n special characters (`@`, `|`, `{`, `}`) in values except deliberate slot placeholders (Pitfall 6).
|
||
6. Styles per D-35: `tokens.css` defines on `:root` the fourteen color tokens from RESEARCH "Tokens → :root custom properties" (`--navy-900` `#18223a` through `--text-4` `#8a94a3`), white border alphas 6/7/8/10/12/14% as `--border-06` … `--border-14`, radii `--radius-sm` 6px, `--radius-card` 16px, `--radius-pill` 999px, and font stacks `--font-sans` (Roboto, system-ui, sans-serif) and `--font-mono` (Roboto Mono, ui-monospace, monospace). `base.css` holds the globals from design/README.md "Global": page background `#233148`, text `#e9edf3`, `html { scroll-behavior: smooth; scroll-padding-top: 72px }` plus `@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto } }` (discretion choice), selection colors, link colors and hover, `.container` (max-width 1120px, padding 0 24px, auto margins), the shared section heading pattern (`.eyebrow`, `.section-title`), and the shared button shapes (primary, secondary, outline) and chip shape used by later sections. Component styles are `<style scoped>` and reference the tokens. No CSS framework package.
|
||
7. `SiteHeader.vue` per design/README.md "1. Header" and the dc.html details: sticky 64px bar, translucent navy with blur, logo link to `#top` (30px sun image `/sun-logo.webp` sized to match the handoff visually, wordmark "Summer" white plus "CMS" `#fbd77e`), the `ALPHA 0.1` pill, the section links in a `flex: 1; min-width: 0; overflow-x: auto; scrollbar-width: none` wrapper with the inner row pushed right, and the `Docs →` button linking `/docs` with the arrow in its own span. Section links are always rendered and hidden by `@media (max-width: 720px)` (Pitfall 4). Give each nav link a `data-section` attribute; active styling is wired in Task 3.
|
||
8. `HeroSection.vue` per "2. Hero": `<section id="top">`, the rays layer rendered only when `useAppConfig().landing.showRays` is true (D-45), the glow, the 180px sun badge holding `/sun-badge.webp` at about 136px (RESEARCH Open Question 5), the H1 with the gradient "CMS", tagline, description, and the two CTAs (`/docs` and `#start`, the secondary with its 8px dot).
|
||
9. Images per D-36 with `scripts/derive-images.sh` (bash, `set -euo pipefail`, ImageMagick `magick` only, run from the SITE root): download the original once with `curl -fsSL https://summercms.io/logo.png -o assets-src/logo.png` and commit it (the live URL disappears at cutover), check it is 746x744 (`magick identify`), then write `public/sun-logo.webp` (60x60, 2x of 30px), `public/sun-badge.webp` (272x272, 2x of 136px), `public/favicon.ico` (16, 32 and 48 px layers), `public/apple-touch-icon.png` (180x180 on `#233148`), and `public/og-image.png` (1200x630 on `#233148` with the sun, the wordmark "Summer" white and "CMS" `#fbd77e`, and the tagline in `#c5ccd6`, set in Roboto from `/usr/share/fonts/TTF/`). Commit the script and every output. Do not copy `design/assets/sun-crop.png`.
|
||
10. `tests/output.test.ts` (node:test; every test file resolves paths from `import.meta.url`, never from the working directory, so it runs from any cwd; reads files under `.output/public` and fails with a clear message when `index.html` is missing): `index.html` contains `<section id="top"`, the hero tagline and description, `href="/docs"`, a `<link rel="canonical" href="https://summercms.io/"`, `og:image` with content `https://summercms.io/og-image.png`, `twitter:card` `summary_large_image`, `<html lang="en` and a JSON-LD `@graph`; `_fonts/` holds at least one `.woff2`; `robots.txt` contains `Sitemap: https://summercms.io/sitemap.xml`; `sitemap.xml` is a regular file containing `<loc>https://summercms.io/</loc>`; at least one `_i18n/**/en/messages.json` exists (Pitfall 7 contract for plan 11.2-02); no generated `.html` or `.css` file mentions either Google font host named in the acceptance criteria. Build the en.json leaf check as a helper reused by later tasks: every leaf string without a `{` placeholder, HTML-escaped the way Vue escapes text (`&`, `<`, `>`, `"`, `'`), appears in `index.html`.
|
||
|
||
Commit as `feat(site): scaffold the Nuxt 4 landing page with header, hero, fonts and SEO`.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm -C ../sm-summercmsio-app/vue-summercmsio-app install --frozen-lockfile && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test</automated>
|
||
<fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, a "not ok" line in the node:test TAP output, or "# fail" with a count above 0</fails_when>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `node -p "const p=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/package.json'); [p.dependencies.nuxt,p.dependencies['@nuxt/fonts'],p.dependencies['@nuxtjs/i18n'],p.dependencies['@nuxtjs/seo'],p.dependencies.vue,p.devDependencies.typescript].join(' ')"` prints `4.4.8 0.14.0 10.4.0 5.2.1 3.5.35 5.9.3`.
|
||
- `test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html && test -f ../sm-summercmsio-app/vue-summercmsio-app/.output/public/sitemap.xml` succeeds (a regular file, not a directory).
|
||
- `ls ../sm-summercmsio-app/vue-summercmsio-app/.output/public/_fonts/*.woff2 | wc -l` prints at least 1.
|
||
- `grep -c "fonts.googleapis.com\|fonts.gstatic.com" ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html` prints 0.
|
||
- `grep -n "ignore: \['/docs'\]" ../sm-summercmsio-app/vue-summercmsio-app/nuxt.config.ts` finds a match.
|
||
- `magick identify ../sm-summercmsio-app/vue-summercmsio-app/public/og-image.png` reports `1200x630`, and `magick identify ../sm-summercmsio-app/vue-summercmsio-app/assets-src/logo.png` reports `746x744`.
|
||
- `git -C ../sm-summercmsio-app/vue-summercmsio-app log --format='%(trailers:key=Co-authored-by,valueonly)' master` prints only empty lines.
|
||
</acceptance_criteria>
|
||
<done>A new vue-summercmsio-app repository generates a prerendered page with the handoff header and hero from en.json, self-hosted Roboto fonts, the original sun in every derived image, static SEO tags, flat sitemap and robots, and its output test passes.</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: Visitors read the Why, Features and From WinterCMS sections and the footer, with every docs, source and credit link from the handoff</name>
|
||
<files>../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/FeaturesSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/WinterSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteFooter.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/assets/css/base.css, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts</files>
|
||
<read_first>
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, app/pages/index.vue, app/assets/css/base.css and tests/output.test.ts (as written in Task 1)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md sections "Section heading pattern", "3. Why", "4. Features", "5. From WinterCMS", "7. Footer"
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (the same sections: grids, gaps, concept-row borders, footer spacer)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md "Every link on the page" table and Pitfall 6
|
||
</read_first>
|
||
<action>
|
||
Add the three content sections and the footer as components, all copy in `en.json` (D-33), styled with the tokens (D-35), matching the handoff exactly (D-23).
|
||
|
||
1. `WhySection.vue` (`<section id="why">`): eyebrow `WHY SUMMER`, H2 "What you tested is exactly what you deploy." (max-width 720px), and the three cards (number in Roboto Mono, title, body) on a `repeat(auto-fit, minmax(260px, 1fr))` grid. Card 2's body has inline code for `fields.yaml` and `columns.yaml` (Roboto Mono 13px, `#e9edf3`): render it with the `<i18n-t>` component and named slots for the two file names (Pitfall 6). Do not use Vue's raw-HTML binding directive anywhere in the project (T-11.2-01).
|
||
2. `FeaturesSection.vue` (`<section id="features">`, full-bleed `#1f2b42` band with top and bottom borders): eyebrow `WHAT'S IN THE BOX`, H2 "The services a content app needs.", and the eight tiles as links on the 1px-divider grid (`gap: 1px` over the border color, outer border, radius 16px, `overflow: hidden`). Tile hrefs per D-47, exactly as the handoff writes them, extension-less: `/docs/backend/admin-spa`, `/docs/database/models`, `/docs/services/routing`, `/docs/services/jobs`, `/docs/services/realtime`, `/docs/services/search`, `/docs/services/mail`, `/docs/console/introduction`. Keep tile titles and bodies verbatim, including "the caller’s transaction" with its typographic apostrophe. Keep the hrefs in the component (or a small typed array next to it) and the copy in en.json.
|
||
3. `WinterSection.vue` (`<section id="winter">`), per D-09 with no added seasons copy: two columns `repeat(auto-fit, minmax(300px, 1fr))`, gap 48px; left column eyebrow `COMING FROM WINTERCMS`, H2 "Same shape. Compiled and typed.", the paragraph, and "See the full concept map →" linking `/docs/setup/coming-from-wintercms` (`display: inline-flex; margin-top: 24px`); right column the concept table with header `WINTERCMS | SUMMERCMS` and the seven rows from design/README.md (`minmax(0,1fr) minmax(0,1.3fr)` columns, Roboto Mono 13px, every row including the last with a bottom divider).
|
||
4. `SiteFooter.vue`: top border, 32px 24px padding, 13px `#8a94a3`; left "© 2026 SummerCMS. Something bright is here."; a `flex: 1` spacer; the link group (gap 20px, `#a3adbd`, hover `#f5c55a`) with Docs → `/docs`, Source → `https://git.golem15.com/golem15/summercms` (D-38), the D-12 credit "Built by Golem15" → `https://golem15.com` styled as a footer link, and "Back to top ↑" → `#top`.
|
||
5. `index.vue` renders header, hero, Why, Features, Winter, then the footer (the Get started section is added in Task 3 between Winter and the footer).
|
||
6. Extend `tests/output.test.ts`: section ids `why`, `features`, `winter` present after `top`; the eight tile hrefs plus `/docs/setup/coming-from-wintercms` each appear as `href="…"`; the Source href and `https://golem15.com` appear; the en.json leaf check passes for the new keys; the seven concept rows (both columns) appear.
|
||
|
||
Commit as `feat(site): add the Why, Features and From WinterCMS sections and the footer`.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test</automated>
|
||
<fails_when>non-zero exit, "Exiting due to prerender errors" in the generate log, or a "not ok" line in the TAP output</fails_when>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `grep -o 'href="/docs/[a-z/-]*"' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html | sort -u | wc -l` prints at least 9 (eight tiles plus the concept map).
|
||
- `grep -c 'href="https://golem15.com' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html` prints at least 1.
|
||
- `grep -c 'caller’s transaction' ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json` prints 1.
|
||
- `grep -rn 'v-html' ../sm-summercmsio-app/vue-summercmsio-app/app` prints nothing.
|
||
- `grep -c '<i18n-t' ../sm-summercmsio-app/vue-summercmsio-app/app/components/WhySection.vue` prints at least 1.
|
||
</acceptance_criteria>
|
||
<done>The Why, Features and From WinterCMS sections and the footer render from en.json with the handoff's links (extension-less docs hrefs, Source, Golem15 credit), and the output test covers them.</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 3: Visitors copy six working commands from the Get started terminal card, and the header highlights the section they are reading</name>
|
||
<files>../sm-summercmsio-app/vue-summercmsio-app/app/components/StartSection.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/components/TerminalCard.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json, ../sm-summercmsio-app/vue-summercmsio-app/app/utils/terminal.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/utils/scrollSpy.ts, ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, ../sm-summercmsio-app/vue-summercmsio-app/app/pages/index.vue, ../sm-summercmsio-app/vue-summercmsio-app/i18n/locales/en.json, ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts, ../sm-summercmsio-app/vue-summercmsio-app/tests/output.test.ts</files>
|
||
<read_first>
|
||
- ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue, app/pages/index.vue, i18n/locales/en.json and tests/output.test.ts (as written in Tasks 1-2)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/README.md sections "6. Get started", "Interactions & Behavior", "State"
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/design/SummerCMS Landing.dc.html (terminal title bar, line blocks, group spacing, chips row, buttons row)
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-RESEARCH.md "Pattern 3: Single-source terminal commands", "Breakpoints/interactions", Pitfall 5
|
||
- .planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-CONTEXT.md D-26, D-38, D-39, D-40, D-45
|
||
</read_first>
|
||
<behavior>
|
||
- copyPayload over the three terminal.json groups returns exactly `git clone https://git.golem15.com/golem15/summercms\ncd summercms\ngo install ./cmd/summer\ncd examples/hello\nsummer build\n./bin/hello greeter:hello` (six lines, no trailing newline, no `$`, no `#`).
|
||
- copyPayload over an empty list returns `''`; over a group with no commands contributes nothing.
|
||
- terminal.json has three groups, six commands, and each group's `comment` key resolves to a string in en.json.
|
||
- activeSection([]) is `''`; when every top is at or above 140 it is `''`; with why at 100 and features at 300 it is `why`; with why -900, features -200, winter 120, start 600 it is `winter`; a top of exactly 140 is not active; a custom threshold is honoured.
|
||
- The generated index.html contains `id="start"`, the chips `Go 1.27` and `PostgreSQL 15+`, each of the six commands, the three comment texts, `href="/docs/setup/installation"`, and the Source href.
|
||
</behavior>
|
||
<action>
|
||
Write the tests from the behavior block first (they fail), then implement until they pass.
|
||
|
||
1. `app/data/terminal.json` exactly as the interfaces block defines it (D-39: the clone step comes first; D-40: this file is the single source the page renders and plan 11.2-02's Go check reads). The comment keys resolve in `en.json` to `get the framework`, `install the summer CLI` and `build and run the example app`; the component prints them after `# `.
|
||
2. `app/utils/terminal.ts` and `app/utils/scrollSpy.ts` exactly as the interfaces block defines them: pure functions, no Vue or Nuxt imports, erasable TypeScript only, so `node --test` imports them directly with explicit `.ts` specifiers.
|
||
3. `TerminalCard.vue` per "6. Get started → terminal card": title bar (`terminal` label, Copy button 500 12px Roboto, radius 6px), body padding 22px 20px, Roboto Mono 14px, line-height 1.9, `overflow-x: auto`, flex column; each line is its own block with `white-space: pre`; comment lines `#8a94a3`; the `$ ` prompt `#f5c55a` in its own span so the command text stays contiguous in the HTML; each group's comment after the first has `margin-top: 16px`. Copy calls `navigator.clipboard?.writeText(copyPayload(groups))` and falls back to a hidden textarea plus `document.execCommand('copy')` when the Clipboard API is unavailable (Pitfall 5); on success the label shows the en.json `Copied` text for 1500 ms, then `Copy`; a failed copy leaves the label unchanged.
|
||
4. `StartSection.vue` (`<section id="start">`, full-bleed `#1f2b42` band with a top border): two columns as in the Winter section with `align-items: center`; left column eyebrow `GET STARTED`, H2 "Build your first app.", the chips row (`margin-top: 24px; gap: 8px`) with `Go 1.27` and `PostgreSQL 15+` (D-26, following D-25; plan 11.2-02 re-runs the PG 15 suites and stops if they fail), the note "Alpha 0.1 is for early adopters. APIs may change before 1.0." (`margin-top: 20px`, 16px/1.65), and the buttons row (`margin-top: 28px`): "Installation guide" primary → `/docs/setup/installation` (padding 13px 24px; extension-less per D-47) and "Source" outline → `https://git.golem15.com/golem15/summercms` (D-38; padding 12px 22px, border 14% white, hover background 8% white and text `#fff`). Right column the TerminalCard. Insert it in `index.vue` between Winter and the footer.
|
||
5. Scroll-spy in `SiteHeader.vue` (D-45): when `useAppConfig().landing.scrollSpy` is true, `onMounted` computes the active id from `getBoundingClientRect().top` of the four sections via `activeSection`, then on every `scroll` event with `{ passive: true }`; `onBeforeUnmount` removes the listener. The active link gets `#f5c55a` text and `rgba(245,197,90,0.1)` background. SSR renders no active link (Pitfall 4). With the flag false no listener is attached.
|
||
6. Extend `tests/output.test.ts` with the last behavior bullet (it reads the six commands and three comment keys from `terminal.json` and `en.json`, never from a second hard-coded list) and with the order check `top` < `why` < `features` < `winter` < `start` < footer.
|
||
|
||
Commit the tests and the implementation as `feat(site): add the Get started terminal card, copy button and scroll-spy`.
|
||
</action>
|
||
<verify>
|
||
<automated>pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test</automated>
|
||
<fails_when>non-zero exit, a "not ok" line in the TAP output, or the TAP summary missing the terminal and scrollSpy test files</fails_when>
|
||
<automated>node --test ../sm-summercmsio-app/vue-summercmsio-app/tests/terminal.test.ts ../sm-summercmsio-app/vue-summercmsio-app/tests/scrollSpy.test.ts</automated>
|
||
<fails_when>non-zero exit, "# pass 0", or "# fail" with a count above 0</fails_when>
|
||
<human-check>Run `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run preview` and open the printed URL next to `design/SummerCMS Landing.dc.html` in a browser. At 1280px, 721px, 720px and 375px wide: layout, colors, type and spacing match; the header stays stuck while scrolling; section links are visible at 721px and hidden at 720px; the active link follows the section in view and none is active above Why; Copy changes to "Copied" for about 1.5s and the clipboard holds six lines without `$` or comments; the hero rays and the sun badge match the handoff.</human-check>
|
||
</verify>
|
||
<acceptance_criteria>
|
||
- `node -e "const t=require('/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app/app/data/terminal.json'); console.log(t.groups.length, t.groups.flatMap(g=>g.commands).length)"` prints `3 6`.
|
||
- `grep -c 'PostgreSQL 15+' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html` prints at least 1.
|
||
- `grep -c './bin/hello greeter:hello' ../sm-summercmsio-app/vue-summercmsio-app/.output/public/index.html` prints at least 1.
|
||
- `grep -n "@media (max-width: 720px)" ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue` finds a match.
|
||
- `grep -n 'passive: true' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue` finds a match.
|
||
- `grep -n 'scrollSpy' ../sm-summercmsio-app/vue-summercmsio-app/app/components/SiteHeader.vue` and `grep -n 'showRays' ../sm-summercmsio-app/vue-summercmsio-app/app/components/HeroSection.vue` both find a match.
|
||
</acceptance_criteria>
|
||
<done>The Get started section shows the D-26 chips, the D-38 links and the D-39 terminal card fed from terminal.json; Copy writes the six commands; scroll-spy follows the handoff rule behind the D-45 flag; unit and output tests pass.</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<threat_model>
|
||
## Trust Boundaries
|
||
|
||
| Boundary | Description |
|
||
|----------|-------------|
|
||
| npm registry → build machine | Third-party packages execute during install and generate |
|
||
| en.json / terminal.json → rendered HTML | Repository copy becomes public HTML; authors are trusted, mistakes are expected |
|
||
| page → visitor clipboard | The Copy button writes text the visitor will paste into a shell |
|
||
| page → third-party origins | Any runtime request to another origin leaks visitor data |
|
||
|
||
## STRIDE Threat Register
|
||
|
||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||
| T-11.2-01 | Tampering (XSS) | components rendering en.json | medium | mitigate | Text interpolation only; inline code through `<i18n-t>` slots; Task 2 criterion greps that no raw-HTML binding exists under app/ |
|
||
| T-11.2-02 | Information disclosure (privacy) | font loading | medium | mitigate | @nuxt/fonts downloads at build and serves from `/_fonts/` (D-34); the output test and a criterion assert no Google font host in the generated HTML or CSS |
|
||
| T-11.2-03 | Tampering | Copy button payload | low | accept | Payload is the bundled terminal.json, identical to the visible lines; the page only writes to the clipboard and never reads it |
|
||
| T-11.2-SC | Tampering | pnpm installs | high | mitigate | Exact pins equal to vue-fonoteka-app's installed versions, approved in RESEARCH Package Legitimacy Audit; committed pnpm-lock.yaml and `--frozen-lockfile`; allowBuilds limited to three packages; any other version stops at a blocking-human checkpoint |
|
||
</threat_model>
|
||
|
||
<verification>
|
||
- `pnpm -C ../sm-summercmsio-app/vue-summercmsio-app install --frozen-lockfile && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app run generate && pnpm -C ../sm-summercmsio-app/vue-summercmsio-app test` green.
|
||
- `git -C ../sm-summercmsio-app/vue-summercmsio-app status --porcelain` prints nothing after the last commit.
|
||
- `git -C . status --porcelain` in summercms.go shows no change from this plan.
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- SC1: `nuxt generate` produces the landing page matching the handoff with sticky header and scroll-spy, hero, Why, Features, From WinterCMS, Get started, a working Copy button and the footer; responsive to phone width with section links hidden at 720px or less (visual parts confirmed in UAT).
|
||
- Inputs for SC2 and SC3: the `.output/public` tree and `app/data/terminal.json` that plan 11.2-02 embeds, link-checks and runs.
|
||
</success_criteria>
|
||
|
||
## Artifacts this phase produces
|
||
|
||
- Repository `vue-summercmsio-app` at `/media/nvme/dev/golem15/summercms.io/summercms/sm-summercmsio-app/vue-summercmsio-app` (branch `master`, no remote yet; D-43).
|
||
- Config: `nuxt.config.ts` (modules `@nuxt/fonts`, `@nuxtjs/i18n`, `@nuxtjs/seo`; `nitro.prerender.ignore`, `sitemap.autoI18n: false`, `ogImage.enabled: false`, `linkChecker.excludeLinks`, `i18n.experimental.prerenderMessages`), `app/app.config.ts` keys `landing.showRays`, `landing.scrollSpy`, `package.json` scripts `build`, `dev`, `generate`, `preview`, `prepare`, `test`.
|
||
- Components: `SiteHeader`, `HeroSection`, `WhySection`, `FeaturesSection`, `WinterSection`, `StartSection`, `TerminalCard`, `SiteFooter`; page `app/pages/index.vue`.
|
||
- TypeScript: `TerminalGroup`, `copyPayload` (`app/utils/terminal.ts`); `SPY_SECTIONS`, `SPY_THRESHOLD`, `activeSection` (`app/utils/scrollSpy.ts`).
|
||
- Data and copy: `app/data/terminal.json` (`groups[].comment`, `groups[].commands`), `i18n/locales/en.json` (keys `meta.*`, `header.*`, `hero.*`, `why.*`, `features.*`, `winter.*`, `start.*`, `terminal.*`, `footer.*`).
|
||
- Styles: `app/assets/css/tokens.css` (`--navy-900` … `--text-4`, `--border-06` … `--border-14`, `--radius-sm`, `--radius-card`, `--radius-pill`, `--font-sans`, `--font-mono`), `app/assets/css/base.css`.
|
||
- Images: `assets-src/logo.png`, `public/sun-logo.webp`, `public/sun-badge.webp`, `public/favicon.ico`, `public/apple-touch-icon.png`, `public/og-image.png`; script `scripts/derive-images.sh`.
|
||
- Tests: `tests/output.test.ts`, `tests/terminal.test.ts`, `tests/scrollSpy.test.ts`.
|
||
|
||
<output>
|
||
Create `.planning/phases/11.2-ready-to-share-summercms-io-website-and-newsletter-plugin/11.2-01-SUMMARY.md` when done
|
||
</output>
|