"use client"; /* Newsletter sign-up. * * Deliberately kept away from the contact form, which promises "No mailing list, no follow-up * sequence" at the point of collection. That promise is about people who write to you; this is a * separate, explicit act by someone who wants to hear about releases. Keeping them visually and * physically apart is what makes both statements true. * * The copy says what it is and what it isn't, because a hospital coordinator deciding whether to * hand over a work address deserves to know the frequency and the exit before they type it. */ import { useState } from "react"; import Turnstile, { awaitTurnstile, resetTurnstile, turnstileOn } from "@/components/Turnstile"; import { track } from "@/lib/analytics"; export default function Subscribe() { const [email, setEmail] = useState(""); const [company, setCompany] = useState(""); // honeypot const [cfToken, setCfToken] = useState(""); const [busy, setBusy] = useState(false); const [done, setDone] = useState(false); const [err, setErr] = useState(""); async function submit(e: React.FormEvent) { e.preventDefault(); if (!email.trim()) { setErr("Add an email address."); return; } setBusy(true); setErr(""); const token = cfToken || (turnstileOn() ? await awaitTurnstile() : ""); const r = await fetch("/api/subscribe", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ email, company, cfToken: token }), }).catch(() => null); const j = r ? await r.json().catch(() => ({})) : {}; setBusy(false); setCfToken(""); resetTurnstile(); // A rejected fetch used to leave the button disabled on "Sending…" with nothing said, so the // form looked like it had eaten the address. Sign-up is confirmed by email either way, so a // second attempt costs nothing. if (!r) { setErr("Couldn’t reach the server. Check the connection and try again."); return; } if (!r.ok) { setErr(j.error || "That didn’t go through. Try again in a moment."); return; } track("newsletter_signup"); setDone(true); } if (done) { return (
Check your inbox.

There’s a confirmation link on its way from hello@threadcount.tech. You’re not on the list until you click it.

); } return (
Product updates

When something ships that changes how the linen room works. A few times a year, never more. Unsubscribe in one click.

{ setEmail(e.target.value); setErr(""); }} aria-label="Your work email" style={{ flex: 1, minWidth: 0, font: "inherit", fontSize: 14.5, padding: "12px 12px", border: "2px solid var(--color-text)", borderRight: 0, background: "#fff", color: "var(--color-text)", borderRadius: 0 }} />
{/* Honeypot — visually hidden, never announced, never focusable. */} setCompany(e.target.value)} style={{ position: "absolute", left: -9999, width: 1, height: 1, opacity: 0 }} /> {err ?

{err}

: null} {turnstileOn() ? : null} ); }