# Site Launch

> Take a concept to a live landing page on a real domain: pick and buy the domain (Spaceship), build the page, deploy to Vercel, wire DNS + forms. Use for 'launch a site for X', 'set up a landing page + domain', 'buy a domain and deploy'.

- Skill: `dylanpulver/site-launch` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add dylanpulver/site-launch`
- Raw SKILL.md: https://api.skillmd.com/api/skills/dylanpulver/site-launch/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: dylanpulver (https://skillmd.com/u/dylanpulver)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/dylanpulver/site-launch

---


# Site Launch

Concept → live branded landing page on a real domain, in one pass. This is the codified version of a run
that worked end to end; the fiddly API parts are scripts under `scripts/` so you don't re-derive them.

**Two standing rules, every run:**
- **Confirm before money or DNS.** Buying a domain and changing DNS are outward, hard-to-undo. State what
  you're about to do and get an explicit yes.
- **Verify after every external change** — read it back. `verified:true` from an API is a claim; `curl` the
  live URL, re-GET the DNS zone, test-submit the form. Motion is not mutation.

## Credentials (never paste in chat)

Read from env; keep them in a gitignored file the user sources, e.g. `~/.config/site-launch/env`:

```bash
export VERCEL_TOKEN=vcp_...            # ACCOUNT token (vcp_), NOT an AI-Gateway key (vck_) — see below
export SPACESHIP_API_KEY=...           # X-API-Key
export SPACESHIP_API_SECRET=...        # X-API-Secret  (Spaceship needs BOTH)
```

If a key is missing, ask for it and tell the user exactly where to make it. Never echo a key in command
output.

## The pipeline

### 1. Frame the concept
One line: what it is, who it's for, the single CTA (email signup? book a call? buy?). Everything downstream
serves that one job.

### 2. Name + domain → `domain-scout`
Invoke the **domain-scout** skill to generate candidates, check availability across TLDs/variants, screen
for existing-company collisions, and rank. Present the shortlist; the user picks. Prefer a clean `.com` or
an acceptable prefixed `.com` (`get___`, `___hq`) over a bare non-`.com` for non-technical audiences.

### 3. Acquire the domain (money — confirm)
Default is manual: `node scripts/spaceship.mjs buy <domain>` prints the purchase link; the user buys it in
the browser and confirms. (API-buy can be added once the Spaceship purchase endpoint is verified.)

### 4. Build the landing page (design-pass)
Build a **standalone** `deploy/index.html` (full `<!doctype>` + `<head>` with viewport, description, OG +
Twitter meta, favicon). For real design quality, invoke **impeccable** or **frontend-design**, or apply
their laws directly: tinted neutrals (never pure `#000`/`#fff`), one committed accent, a display+body type
pair, hierarchy through scale+weight, varied spacing, **no em dashes**, and the bans (no side-stripe
borders, no gradient text, no glassmorphism-by-default, no identical-card grids, no hero-metric cliché).
Run the AI-slop test: if the domain alone predicts the palette, rework it.

Page must be: **theme-aware** (light+dark via tokens + `prefers-color-scheme` + `[data-theme]`),
**responsive** (`clamp()` padding so text never hugs edges; `min-width:0` on grid/flex children or they
overflow the viewport; `overflow-x:hidden` on body), and **clear in 5 seconds** (what it is, who for, how,
who-pays above the fold).

**Copy laws (field feedback from real launches):**
- **Assume zero knowledge.** Write for a smart reader who has never heard the category's core concept.
  Every term the hero leans on ("AI agent", "RFP", "escrow") either gets explained or gets cut. The
  founder's curse is assuming the visitor shares their context — they don't.
- **If the concept is novel, the first section under the fold EXPLAINS it** before any stats or features:
  a plain-language "What is X?" with a one-paragraph definition plus a **who / what / when / where / why-it-matters**
  card row. Stats bounce off a reader who doesn't yet know what the numbers are about. (Bonus: a clean,
  structured definition is exactly what AI assistants cite — the explainer is AEO for the site itself.)
- **Display numerals are design objects.** Big stats read as one unit or they read as sloppy: sans (not
  mono) with `font-variant-numeric: tabular-nums`, tight tracking (≈ −.04em), unit/symbol in the accent
  color with normal tracking. Mono is for labels and source lines only. Group each metric visually (rule,
  border, or card) so number → label → source has one deliberate rhythm.

**OG social image** (so links preview nicely in iMessage/Slack): author a 1200×630 HTML at `og/og.html`,
then `bash scripts/render-og.sh og/og.html deploy/og-image.png`. Reference it with an ABSOLUTE url in the
meta: `<meta property="og:image" content="https://<domain>/og-image.png">`. **Read the rendered PNG back**
to confirm it looks right before shipping.

### 5. Deploy to Vercel
```bash
node scripts/vercel.mjs deploy deploy <project-name>
```
Deploys every file in `deploy/` (html as text, images as base64) to production, waits for READY.
**Gotcha:** the token must be an ACCOUNT token (`vcp_`). An AI-Gateway key (`vck_`) authenticates but 403s
with "You don't have permission to create a project." The Vercel CLI is also flaky with tokens; this uses
the REST API on purpose.

### 6. Connect the domain + DNS + SSL
```bash
node scripts/vercel.mjs domain <project-name> <domain>
node scripts/vercel.mjs domain <project-name> www.<domain>
node scripts/spaceship.mjs dns-vercel <domain>     # apex A -> 216.150.1.1/216.150.16.1, www CNAME -> cname.vercel-dns.com
```
`dns-vercel` cleans stale @ A / www CNAME records first (Spaceship PUT is additive), sets Vercel's CURRENT
apex IPs (the classic `76.76.21.21` is deprecated and won't serve), and reads the zone back. Vercel
auto-issues SSL once DNS resolves. **Gotcha:** Spaceship's CNAME field is `cname`, not `target`.

### 7. Wire the form backend
Default: a **shared Supabase `leads` table** (one table serves every site you launch, rows tagged
by site):

```js
fetch("https://<project>.supabase.co/rest/v1/leads", {
  method: "POST",
  headers: { apikey: KEY, Authorization: "Bearer " + KEY,
             "Content-Type": "application/json", Prefer: "return=minimal" },
  body: JSON.stringify({ venture: "<site-slug>", website: ..., email: ...,
                         source: "<domain>", meta: { ts: new Date().toISOString() } })
})
```

Use the **publishable** key (client-side by design) with an RLS **insert-only** policy on `leads` — the
key can add rows, never read them. Keep the inline confirmation UI, show the thanks state even on fetch
error (never strand a submitter). **Test-submit once and verify the row landed** (`select` via the
service key or dashboard). New venture = same table, new `venture` slug — leads for everything live in
one queryable place instead of scattered inboxes.

### 8. Verify end-to-end (don't skip)
- `curl -sI https://<domain>` → 200, valid SSL; `curl` the apex AND www.
- `curl -sI https://<domain>/og-image.png` → 200 image/png; grep the served HTML for the `og:image` meta.
- Render the page at phone width and **read the screenshot** — but measure overflow with a debug banner
  (`scrollWidth > clientWidth`), because headless Chrome has a ~500px min window and a 390-wide screenshot
  is a crop, not proof.
- **The pinch-zoom pass:** recipients WILL zoom into the densest block (the stats, the pricing) and judge
  the craft there. Screenshot the tightest-typography section at 2× and read it like a critic — tracking,
  alignment, number spacing, rhythm between elements. Spacing sloppiness invisible at full-page scale is
  glaring at zoom, and "design and attention to detail" is exactly what a sharp visitor grades you on.
- Test-submit the form.

## Output
A short recap: the live URL, the domain + DNS state, the form destination, and the rotate-your-keys
reminder. The deliverable is a working site the user can share, not a description of one.

## Related
`domain-scout` (naming + availability), `impeccable` / `frontend-design` (the page). Scripts live in
`scripts/` here; they read creds from env and confirm/verify per the standing rules.

