diff --git a/CLAUDE.md b/CLAUDE.md index cf0bfd1..5a67f4d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,9 @@ Astro 5 → channel47.dev via Vercel. Static output + one serverless endpoint (`api/subscribe.ts`). -**State:** Fresh slate. Pages were nuked May 2026 to start over. Design system is intact. +**State:** Single-plugin site. Five pages: simple home (4 doors), one plugin page (Field Kit), community (Skool join), subscribe (email capture), contact (solicit work), plus 404. Link prefetching enabled (`prefetch.prefetchAll`). + +**Story:** channel47 builds small marketing skills live in the "skills labs" (inside The Vibe Marketers on Skool), then ships them as one Claude Code plugin — **Field Kit**. Skills chain: Customer Research → Persona → Angle (all live) → Page Writer → Section Builder → Convert Check (next lab; output landing pages for Shopify or Next.js). ## Commands @@ -18,29 +20,33 @@ npm run preview ## Pages -- `/` — placeholder index -- `/api/subscribe` — POST, proxies to Kit API (preserved) - -That's it. Add new pages under `src/pages/`. +- `/` — home: one-line pitch + four "doors" (plugin, labs/Skool, newsletter, contact) +- `/plugin` — Field Kit: hero + install, meta strip, 6 skill rows (3 live / 3 next lab), pipeline, install block, join CTA +- `/community` — Skool join page: hero, stats, what-you-get, member quotes, join CTA. All join CTAs use `SKOOL_URL` +- `/subscribe` — email capture: hero form + "three things in every issue" +- `/contact` — solicit work: services list + project-brief form (email, brief, budget → `/api/subscribe`) +- `/404` — "signal lost" page +- `/api/subscribe` — POST, proxies to Kit API -## Design System (the part that survived) +## Structure ``` src/ +├── consts.ts # SKOOL_URL (affiliate — see Gotchas), PLUGIN_INSTALL, SOCIALS. Single source for links ├── layouts/ -│ └── BaseLayout.astro # Root layout — meta, fonts, schema, scroll reveal +│ └── BaseLayout.astro # Root layout — meta, fonts, JSON-LD schema, skip link, html.js flag, init scripts ├── components/ -│ ├── Nav.astro # Logo-only fixed nav (links stripped, add back as routes ship) -│ ├── Footer.astro # ctrlswing attribution (links stripped) -│ ├── EmailSignup.astro # 3 variants — inline / default / prominent. Posts to /api/subscribe +│ ├── Nav.astro # Fixed glass nav (blur + hairline). Height = --nav-h; body padding-top offsets it +│ ├── Footer.astro # Internal links + socials (from consts), ctrlswing attribution │ ├── LogoAnimated.astro # Animated channel47 logo │ └── Analytics/Analytics.astro ├── styles/ -│ └── main.css # Tailwind v4 @theme tokens, @layer components, keyframes +│ └── main.css # Tailwind v4 @theme tokens, @layer components, reveal styles, keyframes └── scripts/ ├── scroll-reveal.ts # IntersectionObserver scroll reveal - ├── nav-scroll.ts # Nav hide-on-scroll - └── copy-to-clipboard.ts # Copy button utility + ├── nav-scroll.ts # Nav hide-on-scroll (currently inert — no .nav--hidden styles defined) + ├── subscribe-form.ts # Enhances [data-kit-form] forms; sends extra named controls as `fields`; status via [data-form-status] or live region + └── copy-to-clipboard.ts # Copy button utility ([data-install] → clipboard) ``` `DESIGN.md` at repo root is the canonical design system spec. @@ -57,15 +63,18 @@ src/ Tailwind CSS v4 via `@tailwindcss/vite`. Single entry: `src/styles/main.css`. -- `@theme` block defines tokens: warm gray scale, amber accent, font families, radius, animations -- `@layer components` for shared patterns: `.label`, `.wrap`, `.prose`, `.hero`, `.stats`, `.cta`, `.accent-bar` -- Body text is JetBrains Mono. Single accent: amber `#F59E0B` -- shadcn/ui semantic tokens (`background`, `foreground`, `ring`, etc.) defined in `@theme` for React component compatibility +- `@theme` block defines tokens: `bg`/`surface`/`text`/`line` scale, amber accent, font families, radius +- `@layer components` for shared patterns: `.wrap`, `.eyebrow`, `.amber`, `.btn-primary`, `.btn-ghost`, `.head-hero`, `.head-section`, `.head-mid`, `.body-lead`, `.body-mid`, `.section-pad`, `.skip-link`, `.visually-hidden` +- Body text is Geist sans; Geist Mono for code/indices. Single accent: amber `#F5B544` +- `--nav-h` (`:root`) sizes the fixed nav; `body` gets `padding-top: var(--nav-h)` and `[id]` gets matching `scroll-margin-top` +- shadcn/ui semantic tokens (`background`, `foreground`, `ring`) defined in `@theme` for React component compatibility -## Scroll Reveals (two layers) +## Scroll Reveals -1. **Section-level**: `data-section="name"` + IO (threshold 0.15). Adds `.is-visible`. -2. **Element-level**: `BaseLayout` observes `[data-animate]` (threshold 0.1). One-time. Stagger with `data-stagger="1..6"`. +- `data-reveal` — single element, fades up when 12% visible. One-time. +- `data-reveal-stagger` (parent) + `data-reveal-child` (children) — children reveal together with per-child delay via inline `style="--stagger: n"` (capped at 5 in CSS). +- Hidden states are gated behind `html.js` (set inline in BaseLayout ``) and `prefers-reduced-motion: no-preference` — content stays visible without JS or with reduced motion. +- Legacy `data-section` / `data-animate` observers still run but no page uses them. ## Schema (structured data) @@ -84,4 +93,5 @@ Tailwind CSS v4 via `@tailwindcss/vite`. Single entry: `src/styles/main.css`. - **`:global()` required** for cross-component ancestor selectors in scoped styles - **Scroll reveal hides content by default** — `[data-reveal-child]` and `[data-reveal]` start at `opacity: 0`. Content only appears after IntersectionObserver fires `.is-visible`. If a section looks blank, it's the reveal system, not missing data. - **Footer attribution** — `ctrlswing`, not `jackson`. Link: `https://x.com/ctrlswing`. +- **Skool affiliate link** — `SKOOL_URL` in `src/consts.ts` ships with a `YOUR_AFFILIATE_REF` placeholder. It MUST be replaced with the real affiliate ref before launch or community signups won't be attributed. Every join CTA reads from this one constant. - **DataForSEO MCP available** — Use `mcp__dataforseo__*` tools for keyword research. diff --git a/astro.config.mjs b/astro.config.mjs index ae31fc7..d552b9a 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -7,6 +7,7 @@ import react from '@astrojs/react'; export default defineConfig({ site: 'https://channel47.dev', output: 'static', + prefetch: { prefetchAll: true }, adapter: vercel({ webAnalytics: { enabled: true diff --git a/package-lock.json b/package-lock.json index cbc0025..3cca9a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "dependencies": { "@astrojs/react": "^4.4.2", - "@astrojs/rss": "^4.0.15", "@astrojs/sitemap": "^3.6.1", "@astrojs/vercel": "^9.0.3", "@radix-ui/react-slot": "^1.2.3", @@ -104,16 +103,6 @@ "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@astrojs/rss": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@astrojs/rss/-/rss-4.0.15.tgz", - "integrity": "sha512-uXO/k6AhRkIDXmRoc6xQpoPZrimQNUmS43X4+60yunfuMNHtSRN5e/FiSi7NApcZqmugSMc5+cJi8ovqgO+qIg==", - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^5.3.3", - "piccolore": "^0.1.3" - } - }, "node_modules/@astrojs/sitemap": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.0.tgz", @@ -4031,37 +4020,6 @@ "license": "MIT", "optional": true }, - "node_modules/fast-xml-builder": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz", - "integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/fast-xml-parser": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz", - "integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "fast-xml-builder": "^1.0.0", - "strnum": "^2.1.2" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -6748,18 +6706,6 @@ "node": ">=8" } }, - "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/svgo": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", diff --git a/src/components/Footer.astro b/src/components/Footer.astro index f6f4de5..6a4ab1f 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -1,13 +1,27 @@ --- +import { SOCIALS } from '../consts'; + const year = new Date().getFullYear(); + +const siteLinks = [ + { label: 'Plugin', href: '/plugin' }, + { label: 'Community', href: '/community' }, + { label: 'Subscribe', href: '/subscribe' }, + { label: 'Contact', href: '/contact' }, +]; --- @@ -17,24 +31,26 @@ const year = new Date().getFullYear(); display: flex; justify-content: space-between; align-items: center; + gap: 24px; + flex-wrap: wrap; font-size: 12px; color: var(--color-text-mute); letter-spacing: 0.18em; text-transform: uppercase; border-top: 1px solid var(--color-line); } - .footer__links { display: flex; gap: 28px; } + .footer__links { display: flex; gap: 28px; flex-wrap: wrap; } .footer__link { color: var(--color-text-mute); transition: color var(--dur-instant); } .footer__link:hover { color: var(--color-text); } - @media (max-width: 480px) { + @media (max-width: 640px) { .footer { flex-direction: column; - gap: 24px; align-items: flex-start; } + .footer__links { gap: 20px; } } diff --git a/src/components/Nav.astro b/src/components/Nav.astro index a08e2dc..50f6ad6 100644 --- a/src/components/Nav.astro +++ b/src/components/Nav.astro @@ -2,44 +2,58 @@ import LogoAnimated from './LogoAnimated.astro'; interface Props { - active?: 'home' | 'plugins' | 'community' | 'studio' | 'subscribe'; + active?: 'home' | 'plugin' | 'community' | 'contact' | 'subscribe'; } const { active = 'home' } = Astro.props; const links = [ - { key: 'plugins', label: 'Plugins', href: '/plugins' }, + { key: 'plugin', label: 'Plugin', href: '/plugin' }, { key: 'community', label: 'Community', href: '/community' }, - { key: 'studio', label: 'Studio', href: '/studio' }, + { key: 'contact', label: 'Contact', href: '/contact' }, ] as const; --- - + diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..bb7077f --- /dev/null +++ b/src/consts.ts @@ -0,0 +1,19 @@ +/** + * Centralized external links + shared site data. + * Single source of truth so links never drift across pages. + */ + +// Skool community join link — tracks signups via the affiliate ref. +// ⚠️ REPLACE `YOUR_AFFILIATE_REF` with your official Skool affiliate ref +// before launch, or signups will not be attributed to you. +export const SKOOL_URL = + 'https://www.skool.com/the-vibe-marketers?ref=YOUR_AFFILIATE_REF'; + +// One-line install for the Field Kit plugin. +export const PLUGIN_INSTALL = 'claude plugin install channel47/field-kit'; + +export const SOCIALS = { + x: 'https://x.com/ctrlswing', + linkedin: 'https://linkedin.com/in/ctrlswing', + github: 'https://github.com/ctrlswing', +}; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 993c71b..de716eb 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -37,6 +37,8 @@ const ogImage = Astro.site + + @@ -85,8 +87,7 @@ const ogImage = Astro.site - - + + + diff --git a/src/pages/plugins/[slug].astro b/src/pages/plugins/[slug].astro deleted file mode 100644 index f0afad6..0000000 --- a/src/pages/plugins/[slug].astro +++ /dev/null @@ -1,424 +0,0 @@ ---- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import Nav from '../../components/Nav.astro'; -import Footer from '../../components/Footer.astro'; - -export function getStaticPaths() { - return [ - { - params: { slug: 'funnel-architect' }, - props: { - plugin: { - idx: '02', - season: 'Season 02', - name: 'Funnel', - nameAccent: 'Architect', - status: 'In session', - statusKind: 'live', - tagline: 'Landing pages and email sequences, scoped from a single product brief. Drop in a brief, get a launchable funnel — copy, page, sequence — out the other side.', - live: 'In session — Tue 14:00 PT', - ctaMeta: 'Free for community members · $39 standalone', - installCmd: 'claude plugin install channel47/funnel-architect', - meta: [ - { label: 'Status', value: 'In session', amber: true }, - { label: 'Sessions aired', value: '1 of 3' }, - { label: 'Built with', value: 'Claude Code · Sonnet' }, - { label: 'Updated', value: 'Apr 16, 2026' }, - ], - inside: [ - { num: '01', title: 'Brief reader', desc: 'Pulls product, audience, offer, and pricing from a single doc. Asks for what\'s missing.' }, - { num: '02', title: 'Page generator', desc: 'Writes a hero, value props, social proof slot, FAQ, and CTA — as real HTML you can paste into a Shopify section or Webflow.' }, - { num: '03', title: 'Sequence writer', desc: 'Six-email welcome sequence with cadence, subject lines, and send-time hints, ready to drop into Klaviyo.' }, - { num: '04', title: 'Brand voice prompt', desc: 'Keeps the copy on-voice. Reuses your tone-of-voice doc, or generates one from sample copy.' }, - { num: '05', title: 'Funnel patterns', desc: 'Three patterns — single-product, bundle, low-ticket-to-call. Picks the right one from the brief.' }, - { num: '06', title: 'Quality bar', desc: 'Self-critiques each draft against the brief. Flags weak hooks, missing proof, vague CTAs.' }, - ], - sessions: [ - { code: 'S2 · E01', title: 'Scoping the funnel from a single product brief', date: 'Apr 9, 2026', status: 'Aired', kind: 'aired' }, - { code: 'S2 · E02', title: 'Landing page — copy to code, live', date: 'Apr 16, 2026', status: 'Live now', kind: 'live' }, - { code: 'S2 · E03', title: 'Email sequence + handoff to Klaviyo', date: 'Apr 23, 2026', status: 'Coming', kind: 'coming' }, - ], - }, - }, - }, - ]; -} - -const { plugin } = Astro.props; ---- - - - -