# newsletter-landing

A free, MIT-licensed **single-page newsletter landing page** template — for solo writers, curators, and indie publishers where the email IS the product. Demonstrated by **Marginalia**, a fictional weekly newsletter about overlooked corners of book history.

**Light-only · Lora + Work Sans · Oxblood `#881337` on cream `#FAF7F2`.**

![Preview](./preview.png)

**Live demo:** [templates.allanninal.dev/newsletter-landing/](https://templates.allanninal.dev/newsletter-landing/)

---

## Features

- **Buttondown subscribe form** by default — plain `<form action>` + `method="post"`, no JavaScript runtime, no SDK. Documented swap to **Mailchimp** and **ConvertKit** in `src/data/newsletter.ts`. **Substack fallback mode** — flip `useSubstackFallback: true` and the inline form is replaced with an external CTA button to your Substack publication.
- **Sample-issue archive grid** — four past-issue cards (title + date + read time + 1–2 sentence teaser + link to the public archive permalink). The single sharpest converter on a writing-focused newsletter landing page; absent from every surveyed free Astro/Substack/ConvertKit template.
- **`Periodical` JSON-LD** (the closest valid schema.org type for a recurring publication; `Newsletter` is not a real type) **+ one `Article` block per sample issue**. Search engines understand both the publication and its individual past pieces.
- **Issue counter band** — full-width sub-band with halftone CSS dot-grid texture: "84 issues · Every Sunday · ~6 min read." Static fields, refresh manually or via build-time fetch to your provider's stats endpoint.
- **Lora + Work Sans pairing** — both variable, both free (Google Fonts), neither used in any prior day's template (Days 1–10). Lora is a transitional serif tuned for screen reading; Work Sans is a sturdy humanist sans. Pairs as "well-edited written work."
- **Cream + oxblood paper-and-ink palette** — `#FAF7F2` paper, `#881337` (Tailwind rose-900) accent. AA-clean across all text. Distinct from Day 5's forest green, Day 7's magenta, Day 4's terracotta, Day 8's burnt orange. Reads as ink on paper, not tech.
- **Decorative drop-cap CSS rule** on issue card titles (`::first-letter`) — pure CSS, no extra markup. The first letter gets oxblood color treatment.
- **CSS-only monogram avatar** — no avatar photo required. Computed from author initials. Used in the site header (small) and the About section (large). Removes a common "I don't have a portrait headshot" friction for newsletter authors.
- **Light-default only — no dark toggle.** Newsletter landing pages are overwhelmingly light-default (Dense Discovery, Craig Mod, Robin Sloan, Kottke, Lenny's). Dark mode would undercut the paper-and-ink register.
- **No external image dependencies.** All "imagery" is CSS — halftone dot-grid texture in the counter band, oxblood rule under headlines, monogram circle. Zero loading hits.
- **41 Playwright tests** covering: Periodical + 4× Article JSON-LD shape; Buttondown form action + email input ARIA + autocomplete + required; both subscribe forms (hero + final) render; 4 issue cards each link to public archive with `noopener noreferrer`; ISO `datetime` on time elements; counter band shows issues / cadence / read time; about section uses CSS-only monogram (zero `<img>`); 3 reader quotes with attributions; final CTA has form + archive link; header subscribe button links to `#subscribe`; LinkedIn footer link uses oxblood accent color; light-only `color-scheme` meta; mobile tap targets ≥40px; issue cards stack to single column on mobile; axe-clean across the whole page.

## Stack

- **Framework:** Astro 4 (static output)
- **Styles:** Tailwind CSS, CSS custom properties for theme tokens
- **Fonts:** Lora Variable + Work Sans Variable (self-hosted via @fontsource-variable)
- **Subscribe form:** Plain HTML `<form>` posting to Buttondown / Mailchimp / ConvertKit (no JS runtime)
- **Schema.org:** `Periodical` (publication) + `Article` (per past issue)
- **Testing:** Playwright, @axe-core/playwright, Lighthouse CI, linkinator

## Quick start

```bash
unzip newsletter-landing-source.zip -d my-newsletter
cd my-newsletter
npm install
cp .env.example .env
npm run dev
```

## Customize

### 1. Newsletter identity

Edit `src/data/newsletter.ts`. The `newsletter` object is the source of truth: name, tagline, description, cadence, founding year, issue count, subscriber count, archive URL.

```ts
export const newsletter = {
  name: 'Marginalia',
  tagline: 'A weekly letter on the overlooked corners of book history.',
  description: '...',
  cadence: 'Every Sunday',
  foundingYear: 2022,
  issueCount: 84,
  subscriberCount: 4200,
  subscriberCountLabel: '4,200',
  averageReadMinutes: 6,
  archiveUrl: 'https://buttondown.email/marginalia/archive/',
  monogram: 'M',
  // ...
};
```

### 2. Subscribe-form provider

The same file exports a `subscribe` config block. Pick exactly one provider:

```ts
export const subscribe = {
  provider: 'buttondown',         // 'buttondown' | 'mailchimp' | 'convertkit'
  useSubstackFallback: false,     // true → external CTA instead of inline form
  substackUrl: 'https://marginalia.substack.com/',
  buttondown: {
    slug: 'marginalia',
    actionUrl: 'https://buttondown.email/api/emails/embed-subscribe/marginalia',
  },
  mailchimp: { action: '...', botTrapName: '...' },
  convertkit: { action: '...', formUid: '...' },
};
```

The Hero and FinalSubscribe components both render the matching provider form automatically. To switch to a Substack publication, set `useSubstackFallback: true` — both forms collapse into a single "Subscribe on Substack" CTA button pointing to `substackUrl`.

### 3. Sample issues

Edit the `issues` array in `src/data/newsletter.ts`. Each entry needs:

```ts
{
  slug: 'lighthouse-keepers-margin',
  title: 'The lighthouse keeper who annotated everything.',
  date: '2026-04-26',                                     // ISO 8601
  readMinutes: 7,
  teaser: '...',
  archiveUrl: 'https://buttondown.email/.../...',         // permalink
}
```

The grid renders the array in order. Show 4 by default. The grid breaks to a single column at < 640px.

### 4. Reader quotes

Edit the `quotes` array. Three entries by default — the layout is set up for exactly three (3-column md+).

```ts
export const quotes = [
  { body: '...', attribution: 'A reader in Berlin' },
  // ...
];
```

### 5. Author / About

Edit the `author` object — name, bio, location, social links. The About section uses a CSS-only monogram avatar computed from the first two initials of `author.name`. No photo required.

### 6. Issue counter / subscriber count

Static fields in `src/data/newsletter.ts` — `issueCount`, `subscriberCount`, `subscriberCountLabel`, `averageReadMinutes`. Update manually before deploys.

For Buttondown users: a build-time `fetch` to `https://api.buttondown.email/v1/subscribers` (with your API key) can populate `subscriberCount` automatically. We don't ship that script — it depends on a per-newsletter API key and runtime environment that varies per host.

### 7. Theme tokens

Edit `:root` in `src/styles/global.css`. The accent comes in three shades:

- `--color-accent: #881337` (rose-900) — buttons, links, focus rings, drop caps
- `--color-accent-hover: #6F0F2D` — primary button hover
- `--color-accent-deep: #5A0A24` — deepest emphasis

If you swap the accent, verify it passes WCAG AA at 14px+ on `--color-bg` (`#FAF7F2`).

### 8. ISSN (optional)

If your newsletter has registered an ISSN, set it in `newsletter.issn` — the `Periodical` JSON-LD will include it. Newsletter authors can request an ISSN free via their country's national library (Library of Congress in the US).

---

## Deploy to GitHub Pages

> The source repo is private — there is no Fork button. Download the source ZIP from [templates.allanninal.dev/newsletter-landing/](https://templates.allanninal.dev/newsletter-landing/) and follow these steps.

1. **Unzip + init**:

   ```bash
   unzip newsletter-landing-source.zip -d my-newsletter
   cd my-newsletter
   git init && git add . && git commit -m "init from newsletter-landing template"
   ```

2. **Push** to a new GitHub repository.

3. **Settings → Pages → Source: GitHub Actions.** The included `pages.yml` builds and deploys.

### Custom domain

Add `CNAME` in `public/`, set `PUBLIC_SITE_URL` and `PUBLIC_BASE_PATH=/` in repo Variables → Actions.

---

## Testing

```bash
npm run test:all
# = npm run build && npm run test && npm run test:links && npm run test:lighthouse
```

| Suite | What it covers |
|---|---|
| `tests/pages.spec.ts` | Renders, h1, footer, no console errors; **`Periodical` JSON-LD** with name + url + description + inLanguage + publisher (Person); **`Article` JSON-LD per sample issue** (4 entries) with headline + datePublished + url + isPartOf; OG image absolute + 1200×630; Twitter `summary_large_image`; canonical absolute; sitemap; robots.txt |
| `tests/interactions.spec.ts` | Hero (tagline + cadence eyebrow + trust line); Buttondown subscribe form action + email input ARIA + required + autocomplete; both forms (hero + final) render; what-you-get 3 items; sample issues 4 cards each linking to archive with `noopener noreferrer`; ISO `datetime` attributes; issue counter band; about section uses CSS-only monogram (no `<img>`); 3 reader quotes with attributions; final CTA form + archive link; header `#subscribe` button; LinkedIn footer accent color; light-only `color-scheme` meta |
| `tests/responsive.spec.ts` | No horizontal scroll at 4 viewports; mobile h1 above the fold; subscribe input + button ≥40px tap targets; issue cards stack to single column on mobile |
| `tests/a11y.spec.ts` | Zero axe violations on `/` (full page — no exclusions; the design intentionally avoids Shiki / syntax-highlighted components that require selective exclusion) |
| `tests/links.spec.ts` | Internal links resolve; hash anchors point to existing IDs |

---

## What this template is NOT

- **Not a paid-tier UI** — no Stripe, no paywall, no member-only content. Writers who need paid tiers use Buttondown's native paid tier or Substack.
- **Not a referral / leaderboard system** — SparkLoop and Beehiiv ship their own; both require server-side tracking.
- **Not a comments / threaded-discussion page** — comments belong on the email platform's hosted archive.
- **Not multi-newsletter** — one template, one newsletter. The Atlantic-style newsletter directory is a different form factor.
- **Not a dark-mode toggle** — newsletter landing pages are overwhelmingly light-default. Dark mode undercuts the paper register.

---

## Support and donations

This template is **free and MIT-licensed**.

If it saved you time: [**Ko-fi — ko-fi.com/allanninal**](https://ko-fi.com/allanninal).

---

## License

MIT — see [LICENSE](./LICENSE).

Part of the [365-template series](https://templates.allanninal.dev/) — Day 11 of 365 free, MIT-licensed static website templates.
