Files
zeekay 44935451c0 rebrand: @hanzo/waitlist (monochromatic, brand-neutral, Base-backed)
Drops the Next.js + Upstash Redis stack. The viral waitlist now ships as
a brand-neutral React widget against a Hanzo Base Go plugin.

What changed
- packages/widget/: new @hanzo/waitlist — React component, framework-free
  fetch client, drop-in <hanzo-waitlist> custom element. Zero brand
  colors, zero logos, zero brand names. Consumer brings logo via `logo`
  prop or `<slot name="logo">`. Theming via --hw-* CSS variables with
  fallback chain at usage so ancestor selectors win cleanly.
- apps/web/: Next.js demo rebuilt monochrome. Five generic presets
  (Neutral, Preset A/B/C/D) prove brand swap works without per-brand
  builds. Demo's placeholder logo lives in the demo, not the package.
- apps/api/: removed. Backend logic now lives in
  ~/work/hanzo/base/plugins/waitlist (committed separately).
- packages/sdk/: removed (Preact + Vite). Replaced by packages/widget.
- docs/: collapsed to architecture / api / widget. Old planning docs
  moved to docs/archive/.
- Latest deps: Next 16, React 19.2, Vite 8, TS 5.9, vite-plugin-dts 5,
  @vitejs/plugin-react 6, eslint-config-next 16.

Build output
- ESM 6.76kB, CJS 5.22kB, IIFE 196.99kB / 62.65kB gzip
- Widget styles: 3.89kB / 1.18kB gzip
- Demo first-load: 106kB
2026-06-09 22:57:41 -07:00

2.2 KiB

@hanzo/waitlist

Monochromatic, brand-neutral viral waitlist widget. Backed by Hanzo Base.

import { Waitlist } from '@hanzo/waitlist'
import '@hanzo/waitlist/styles.css'
import MyLogo from './MyLogo' // your component, your SVG, your <img>

export default function Landing() {
  return (
    <Waitlist
      waitlist="my-product"
      baseUrl="https://api.example.com"
      logo={<MyLogo />}
    />
  )
}

The widget ships with no logo and no brand colors. Each consumer brings its own mark via the logo prop (or <slot name="logo"> in the custom element) and its own palette via CSS variables.

Drop-in (no bundler)

<link rel="stylesheet" href="https://cdn.example.com/@hanzo/waitlist/waitlist.css" />
<script src="https://cdn.example.com/@hanzo/waitlist/waitlist.iife.js"></script>

<hanzo-waitlist waitlist="my-product" base-url="https://api.example.com">
  <!-- Bring your own logo. Either slot in markup: -->
  <img slot="logo" src="/logo.svg" alt="My brand" />
  <!-- ...or use the logo-url attribute instead:
  <hanzo-waitlist ... logo-url="/logo.svg" logo-alt="My brand" />
  -->
</hanzo-waitlist>

Theming

Override any CSS variable on .hanzo-waitlist (or any ancestor). The widget ships fully neutral — no hardcoded brand colors, no logos, no embedded identity. Each consumer sets its own tokens once:

.your-brand {
  --hw-accent: <accent>;     /* brand accent */
  --hw-accent-fg: <text>;    /* text color on accent */
  --hw-radius: <radius>;     /* corner radius */
}

Force a theme with theme="light" | "dark". Default theme="auto" follows prefers-color-scheme.

Programmatic client (no React)

import { WaitlistClient } from '@hanzo/waitlist/client'

const c = new WaitlistClient({ baseUrl: 'https://api.example.com' })
const result = await c.join({ waitlist: 'my-product', email: '[email protected]' })

Server

The backend is the waitlist plugin for Hanzo Base. Register it once at boot:

waitlist.MustRegister(app, waitlist.Config{
    Enabled:         true,
    TurnstileSecret: os.Getenv("TURNSTILE_SECRET_KEY"),
    AdminSecret:     os.Getenv("WAITLIST_ADMIN_SECRET"),
})