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;
---
-
-
-
-
-
- {links.map((l) => (
+
+
+
+
+
+
-
+ href="/subscribe"
+ class={`topbar__link topbar__link--amber ${active === 'subscribe' ? 'is-active' : ''}`}
+ aria-current={active === 'subscribe' ? 'page' : undefined}
+ >Subscribe
+
+
+
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
- Skip to content
-
+ Skip to content
+
+
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;
----
-
-
- Skip to content
-
-
-
-
- channel47
- /
- Plugins
- /
- {plugin.name} {plugin.nameAccent}
-
-
-
-
- {plugin.idx} · {plugin.season}
-
-
- {plugin.live}
-
-
- {plugin.name} {plugin.nameAccent}
- {plugin.tagline}
-
- Install plugin →
- Watch session live
- {plugin.ctaMeta}
-
-
-
-
-
-
- I — What's inside
-
- A working funnel, not a template .
-
-
- The plugin reads your product brief, picks a funnel pattern, and writes the actual files. You install it once and run it on any new launch.
-
-
-
- {plugin.inside.map((t) => (
-
-
{t.num}
-
{t.title}
-
{t.desc}
-
- ))}
-
-
-
-
- II — Sessions
-
- Three sessions. One ships the plugin.
-
-
- Live inside The Vibe Marketers on Tuesdays. Recordings post the next morning to community members; public cuts go out a week later.
-
-
-
- {plugin.sessions.map((s, i) => (
-
- {s.code}
- {s.title}
- {s.date}
-
- {s.kind === 'live' && }
- {s.status}
-
-
- ))}
-
-
-
-
- III — Install
-
- One line. Runs locally with Claude Code.
-
-
- Requires Claude Code >= 1.4 and a paid Anthropic key. The plugin manifest, prompts, and example briefs are MIT-licensed.
-
-
-
- Terminal
- Copy
-
-
- $
- {plugin.installCmd}
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/plugins/index.astro b/src/pages/plugins/index.astro
deleted file mode 100644
index dc970a4..0000000
--- a/src/pages/plugins/index.astro
+++ /dev/null
@@ -1,231 +0,0 @@
----
-import BaseLayout from '../../layouts/BaseLayout.astro';
-import Nav from '../../components/Nav.astro';
-import Footer from '../../components/Footer.astro';
-
-const plugins = [
- { idx: '01', slug: 'creative-strategist', name: 'Creative Strategist', desc: 'Hook libraries, angle generation, and ad-test plans for performance briefs. Built for the strategist who needs to staff out 30 ads in a week.', status: 'Shipped', kind: 'shipped', season: 'S1', episodes: 4, installs: '1,240' },
- { idx: '02', slug: 'funnel-architect', name: 'Funnel Architect', desc: 'Landing pages and email sequences, scoped from a single product brief. End-to-end launch in two sessions.', status: 'In session', kind: 'live', season: 'S2', episodes: 2, installs: '—' },
- { idx: '03', slug: 'media-buyer', name: 'Media Buyer', desc: 'Pacing, creative rotation, and reporting commentary across Meta and Google. Reads the account, writes the deck.', status: 'Shipped', kind: 'shipped', season: 'S1', episodes: 3, installs: '892' },
- { idx: '04', slug: 'klaviyo-operator', name: 'Klaviyo Operator', desc: 'Lifecycle flows from a Shopify catalog. Segments, copy, and a send-time map you can drop into your account.', status: 'On deck', kind: 'ondeck', season: 'S3', episodes: 0, installs: '—' },
- { idx: '05', slug: 'shopify-merchandiser', name: 'Shopify Merchandiser', desc: 'Catalog cleanup, collection logic, and product copy from raw supplier data. Ships a tidy storefront.', status: 'Shipped', kind: 'shipped', season: 'S1', episodes: 5, installs: '604' },
- { idx: '06', slug: 'brief-translator', name: 'Brief Translator', desc: 'Turns a fuzzy stakeholder request into a tight creative brief, paired with a 3-deliverable scope.', status: 'Shipped', kind: 'shipped', season: 'S1', episodes: 2, installs: '1,512' },
- { idx: '07', slug: 'review-miner', name: 'Review Miner', desc: 'Pulls voice-of-customer language out of Amazon, Trustpilot, and YouTube transcripts.', status: 'On deck', kind: 'ondeck', season: 'S3', episodes: 0, installs: '—' },
- { idx: '08', slug: 'pitch-a-plugin', name: 'Pitch a plugin', desc: 'The next season is open. Pitch a plugin worth building — the best ideas come from inside the room.', status: 'Open call', kind: 'open', season: '—', episodes: 0, installs: '—' },
-];
----
-
-
- Skip to content
-
-
-
-
- Plugins — the library
-
- Eight plugins so far. Each one came out of a live session .
-
-
- Install any of them, or watch the session that produced it. Members of The Vibe Marketers get the working files; everyone else gets the public build.
-
-
-
-
-
- All · {plugins.length}
- Shipped
- In session
- On deck
- Open calls
-
-
{plugins.length} plugins
-
-
-
- {plugins.map((p) => {
- const Tag = p.slug === 'funnel-architect' ? 'a' : 'div';
- const tagProps = p.slug === 'funnel-architect' ? { href: `/plugins/${p.slug}` } : {};
- return (
-
-
- {p.idx}
-
-
- {p.status}
-
-
- {p.name}
- {p.desc}
-
- {p.season} · {p.episodes} {p.episodes === 1 ? 'session' : 'sessions'} · {p.installs} installs
- {p.slug === 'funnel-architect' && → }
-
-
- );
- })}
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/studio.astro b/src/pages/studio.astro
deleted file mode 100644
index a6e9bab..0000000
--- a/src/pages/studio.astro
+++ /dev/null
@@ -1,309 +0,0 @@
----
-import BaseLayout from '../layouts/BaseLayout.astro';
-import Nav from '../components/Nav.astro';
-import Footer from '../components/Footer.astro';
-
-const services = [
- { idx: '01', name: 'Marketing systems, end-to-end', desc: 'Site, email, ads, automation — scoped, built, handed off as a system you can run yourself.', price: 'From $24k' },
- { idx: '02', name: 'Shopify storefronts & flows', desc: 'Themes, checkouts, and Klaviyo flows for brands moving real product. Two-week sprints.', price: 'From $9k' },
- { idx: '03', name: 'Performance creative & ads', desc: 'Hook research, creative production, and account ops on Meta and Google. Monthly retainer.', price: '$6k/mo', amber: true },
- { idx: '04', name: 'Agent-powered retainers', desc: 'Ongoing build with Claude Code. Weekly drops, written specs, real ownership.', price: '$8k/mo' },
-];
-
-const cases = [
- { tag: '01 · Shopify · DTC apparel', client: 'Halftone & Co.', desc: 'Replatformed from a custom Webflow site to a Shopify build with a Klaviyo welcome flow and a paid-social creative engine.', stats: [{ v: '+38%', l: 'Conversion' }, { v: '4 wk', l: 'Build time' }] },
- { tag: '02 · System · B2B SaaS', client: 'Northroom', desc: 'Built a content-to-pipeline system: weekly publishing pipeline, MQL scoring, and a sales-enablement plugin.', stats: [{ v: '3.1x', l: 'MQL volume' }, { v: '8 wk', l: 'Build time' }] },
- { tag: '03 · Performance · Coffee', client: 'Forty Beans', desc: 'Hook research, weekly creative drops, and a pacing model that runs across Meta and Google. Now a 6-month retainer.', stats: [{ v: '−24%', l: 'CAC' }, { v: '12 mo', l: 'On retainer' }] },
- { tag: '04 · Klaviyo · Cosmetics', client: 'Lumen Studio', desc: 'Lifecycle rebuild — new welcome, post-purchase, and winback flows generated from review-mining and a tone-of-voice doc.', stats: [{ v: '+62%', l: 'Email rev' }, { v: '2 wk', l: 'Build time' }] },
-];
-
-const process = [
- { num: '01', title: 'Brief', desc: 'One call, one written brief. We agree on the system, the deliverables, and the timeline before I write a line of code.' },
- { num: '02', title: 'Spec', desc: 'I write a public spec — pages, flows, prompts, and the agent setup. You sign off on the spec, not on vibes.' },
- { num: '03', title: 'Build', desc: 'Claude Code does most of the typing. I do the strategy, the review, and the integration. Daily Loom updates.' },
- { num: '04', title: 'Handoff', desc: 'You get the working system, written runbooks, and a recorded walkthrough. Optional retainer to keep shipping.' },
-];
----
-
-
- Skip to content
-
-
-
-
- channel47 studio · client work
-
- Marketing systems for small businesses, built with agents .
-
-
- A small client roster, three to five engagements at a time. Biased toward Shopify and physical-goods brands — open to anything I can ship cleanly.
-
-
-
-
-
- I — Services
-
- Four ways to work together .
-
-
-
- {services.map((s, i) => (
-
- {s.idx}
- {s.name}
- {s.desc}
- {s.price}
-
- ))}
-
-
-
-
- II — Recent work
-
- Four engagements, shipped .
-
-
-
- {cases.map((c) => (
-
- {c.tag}
- {c.client}
- {c.desc}
-
- {c.stats.map((s) => (
-
- ))}
-
-
- ))}
-
-
-
-
- III — How it works
-
- Scope to ship in four steps .
-
-
-
- {process.map((p) => (
-
-
{p.num}
-
{p.title}
-
{p.desc}
-
- ))}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/pages/subscribe.astro b/src/pages/subscribe.astro
index b5854cb..59d206f 100644
--- a/src/pages/subscribe.astro
+++ b/src/pages/subscribe.astro
@@ -4,53 +4,50 @@ import Nav from '../components/Nav.astro';
import Footer from '../components/Footer.astro';
const tiles = [
- { num: '01', title: 'A plugin drop', desc: 'Either the next plugin or a working snippet from the current session. Install link, working files, the prompt I used.' },
- { num: '02', title: 'Session notes', desc: 'What worked, what didn’t, where the agent got stuck. The dirty version, before it gets edited into a video.' },
+ { num: '01', title: 'A skill drop', desc: 'The newest skill or a working snippet from the current lab — install link, working files, and the prompt behind it.' },
+ { num: '02', title: 'Lab notes', desc: 'What worked, what didn\'t, where the agent got stuck. The unedited version, before it becomes a video.' },
{ num: '03', title: 'One thing to read', desc: 'A teardown of a brand, a campaign, or a system worth stealing from. Always linked, never paywalled.' },
];
-const issues = [
- { num: 'No. 047', date: 'Apr 9, 2026', title: 'Funnel Architect — what I learned scoping a plugin from a brief' },
- { num: 'No. 046', date: 'Mar 26, 2026', title: 'Three patterns for hooking a paid-social ad with Claude' },
- { num: 'No. 045', date: 'Mar 12, 2026', title: 'Why I write a public spec for every client engagement' },
- { num: 'No. 044', date: 'Feb 27, 2026', title: 'Klaviyo flows, generated — what works and what to throw out' },
- { num: 'No. 043', date: 'Feb 13, 2026', title: 'Reading the account: how Media Buyer reads pacing reports' },
- { num: 'No. 042', date: 'Jan 30, 2026', title: 'A reading list for marketers shipping with agents' },
-];
+const schema = {
+ '@type': 'WebSite',
+ name: 'channel47 — Build Notes',
+ url: 'https://channel47.dev/subscribe',
+};
---
- Skip to content
-
- The newsletter · roughly twice a month
-
- Get the next plugin before the session.
+
+ Build Notes · roughly twice a month
+
+ Get the next skill before the lab.
-
- Plugin drops, session notes, the occasional teardown. Written the night before each Tuesday. No filler, no roundups.
+
+ Skill drops, lab notes, the occasional teardown. Written the night before each Tuesday. No filler, no roundups.
-
- 4,812 subscribers · 47 issues · zero spam
+ 4,812 subscribers · zero spam · unsubscribe in one click
- I — What you get
-
+ What you get
+
Three things in every issue .
-
- {tiles.map((t) => (
-
+
+ {tiles.map((t, i) => (
+
{t.num}
{t.title}
{t.desc}
@@ -58,24 +55,6 @@ const issues = [
))}
-
-
- II — Past issues
-
- Forty-seven issues. All free , all open.
-
-
-
- {issues.map((it, i) => (
-
- {it.num}
- {it.date}
- {it.title}
- Read →
-
- ))}
-
-
@@ -113,73 +92,18 @@ const issues = [
gap: 1px;
background: var(--color-line);
}
- .what-tile {
- background: var(--color-bg);
- padding: 40px 32px;
- min-height: 220px;
- }
+ .what-tile { background: var(--color-bg); padding: 40px 32px; min-height: 220px; }
.what-tile__num {
+ font-family: var(--font-family-mono);
font-size: 12px;
color: var(--color-amber);
letter-spacing: 0.18em;
margin-bottom: 24px;
}
- .what-tile__title {
- font-size: 22px;
- letter-spacing: -0.015em;
- margin: 0 0 12px;
- }
- .what-tile__desc {
- font-size: 15px;
- line-height: 1.55;
- color: var(--color-text-dim);
- }
-
- .archive {
- list-style: none;
- margin: 80px 0 0;
- padding: 0;
- }
- .issue-row {
- display: grid;
- grid-template-columns: 120px 100px 1fr 100px;
- gap: 32px;
- padding: 28px 0;
- border-top: 1px solid var(--color-line);
- align-items: center;
- cursor: pointer;
- transition: opacity var(--dur-instant);
- }
- .issue-row:hover { opacity: 0.85; }
- .issue-row.is-last { border-bottom: 1px solid var(--color-line); }
- .issue-row__num {
- font-family: var(--font-family-mono);
- font-size: 13px;
- color: var(--color-text-mute);
- letter-spacing: 0.1em;
- }
- .issue-row__date {
- font-size: 12px;
- color: var(--color-text-mute);
- letter-spacing: 0.18em;
- text-transform: uppercase;
- }
- .issue-row__title {
- font-size: 19px;
- letter-spacing: -0.01em;
- }
- .issue-row__arrow {
- font-size: 14px;
- color: var(--color-amber);
- text-align: right;
- }
+ .what-tile__title { font-size: 22px; letter-spacing: -0.015em; margin: 0 0 12px; }
+ .what-tile__desc { font-size: 15px; line-height: 1.55; color: var(--color-text-dim); }
@media (max-width: 900px) {
.what-grid { grid-template-columns: 1fr; }
- .issue-row {
- grid-template-columns: 100px 1fr 60px;
- gap: 16px;
- }
- .issue-row__date { display: none; }
}
diff --git a/src/scripts/subscribe-form.ts b/src/scripts/subscribe-form.ts
index b65cf1a..eb360a3 100644
--- a/src/scripts/subscribe-form.ts
+++ b/src/scripts/subscribe-form.ts
@@ -1,6 +1,12 @@
/**
* Enhance any