From 99e5520629094e0adb3e7c23f57e748a36b58ab1 Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Wed, 21 Oct 2026 13:03:28 +0100 Subject: [PATCH 1/4] feat(site): add get updates and get in touch cards, sticky header --- AGENTS.md | 6 +- src/components/Contact.astro | 40 ++++++++ src/components/Footer.astro | 126 +----------------------- src/components/Header.astro | 126 ++++++++++++------------ src/components/Subscribe.astro | 120 +++++++++++----------- src/components/Updates.astro | 19 ++++ src/layouts/Base.astro | 1 + src/pages/about.astro | 31 +++--- src/pages/index.astro | 44 ++++++++- src/pages/subscribe-failed.astro | 4 +- src/styles/global.css | 164 ++++++++++++++++++++++++++++++- 11 files changed, 411 insertions(+), 270 deletions(-) create mode 100644 src/components/Contact.astro create mode 100644 src/components/Updates.astro diff --git a/AGENTS.md b/AGENTS.md index ae70dae..df83809 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,8 @@ The source of the public site at `withmarfa.com`: Astro, built to static files a - `src/pages/`: one file per page. `404.astro` is what Cloudflare serves for a missing path. `sitemap.xml.ts` lists every page except those in its `UNLISTED` set. - `src/layouts/Base.astro`: the document head, the header, the column and the footer, used by every page. -- `src/components/`: the header (the Marfa mark, which leads home, a GitHub icon and the Docs pill), the footer (About, the Contact card and Privacy), the question that opens (one at a time within a group), and the sign-up form. -- `src/styles/global.css`: the color tokens, shared with the docs site's canvas and text colors. Light and dark follow the system; there is no toggle. +- `src/components/`: the header (the Marfa mark, which leads home, a GitHub icon, Docs and Get Updates), the Get Updates card with the sign-up form, the footer (About, the Contact card and Privacy), and the question that opens (one at a time within a group). +- `src/styles/global.css`: the color tokens, shared with the docs site's canvas and text colors, and the shared buttons and cards. Light and dark follow the system; there is no toggle. - `src/lib/analytics.ts`: PostHog, loaded after the page. - `public/`: copied as is. `_headers` sets the security headers and the Content Security Policy. `og.png` is the link preview image, rendered from `scripts/og-image.html`. - `worker/index.ts`: the Worker, which answers `POST /api/subscribe`. `worker/worker-configuration.d.ts` is generated by `pnpm types` from `wrangler.jsonc`; regenerate it whenever bindings change, or `pnpm check` fails. @@ -44,7 +44,7 @@ pnpm preview # build, then serve dist/ and the Worker with Wrangler as Cloudf ## Sign-ups -- The form on About posts to `/api/subscribe`. The Worker checks that the request comes from this site, limits each IP address to five tries a minute, ignores bots that fill the hidden `website` field, and keeps the address, lowercased, with the time in the `signups` table of the D1 database `withmarfa-website` (EU jurisdiction). A repeat address changes nothing and gets the same reply, so the form never reveals who is on the list. +- The form in the Get Updates card posts to `/api/subscribe`. The Worker checks that the request comes from this site, limits each IP address to five tries a minute, ignores bots that fill the hidden `website` field, and keeps the address, lowercased, with the time in the `signups` table of the D1 database `withmarfa-website` (EU jurisdiction). A repeat address changes nothing and gets the same reply, so the form never reveals who is on the list. - With JavaScript the form shows the result in place; without it the Worker redirects to `/subscribed` or `/subscribe-failed`. - Nothing logs an address. Invocation logs and traces are off, because they would keep each visitor's location and browser beside the time of their sign-up; only the Worker's own log lines are kept. To read the list, from a shell authenticated to the WithMarfa account: `pnpm exec wrangler d1 execute withmarfa-website --remote --command "SELECT email, created_at FROM signups ORDER BY created_at"`. To remove someone, `DELETE FROM signups WHERE email = '…'`. - A schema change is a new file in `migrations/`, applied with `pnpm exec wrangler d1 migrations apply withmarfa-website --remote` before the code that needs it is merged. The deploy does not apply migrations. diff --git a/src/components/Contact.astro b/src/components/Contact.astro new file mode 100644 index 0000000..835a945 --- /dev/null +++ b/src/components/Contact.astro @@ -0,0 +1,40 @@ +--- +/** + * The Get in Touch card. It shows the address before anything opens a mail + * app, and offers to copy it instead. + */ +const EMAIL = 'hello@withmarfa.com'; +--- + + + + + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 9bde17f..f779816 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -1,47 +1,17 @@ --- import { pagePath } from '../lib/path'; -/** - * The footer, and the Contact card it opens. The card shows the address before - * anything opens a mail app, and offers to copy it instead. - */ + const path = pagePath(Astro.url); -const EMAIL = 'hello@withmarfa.com'; --- - - - - diff --git a/src/components/Header.astro b/src/components/Header.astro index 6309d8d..86de1c5 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -1,47 +1,77 @@ --- import { pagePath } from '../lib/path'; /** - * The Marfa mark, which leads home, and the two ways out: GitHub and the docs. - * The mark is the docs' favicon.svg, drawn in the page's colors. + * The Marfa mark, which leads home; GitHub and the docs; and Get Updates, + * which opens the sign-up card. The mark is the docs' favicon.svg, drawn in + * the page's colors. */ +import Updates from './Updates.astro'; const path = pagePath(Astro.url); --- -
- - - - + +
+ + + + diff --git a/src/components/Subscribe.astro b/src/components/Subscribe.astro index c24ee8a..3f96839 100644 --- a/src/components/Subscribe.astro +++ b/src/components/Subscribe.astro @@ -1,33 +1,46 @@ --- /** * Signs someone up for updates. Without JavaScript the form posts and the - * Worker redirects to /subscribed or /subscribe-failed; with it, the result shows in place. The - * `website` field is left empty by people and filled by bots, which the Worker - * then ignores. + * Worker redirects to /subscribed or /subscribe-failed; with it, the result + * shows in place. The `website` field is left empty by people and filled by + * bots, which the Worker then ignores. */ +interface Props { + /** The card the form sits in, which its Close button hides. */ + card?: string; +} + +const { card } = Astro.props; --- diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index f83d8fa..e3b7ed1 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -48,6 +48,7 @@ const image = new URL('/og.png', Astro.site).href;